Compare commits

...

186 Commits

Author SHA1 Message Date
Kegan Dougal
80277583db Fix timestamp hovers #59 2015-10-21 15:05:17 +01:00
David Baker
7142ea8f1e Redundant onclick 2015-10-21 11:15:27 +01:00
Matthew Hodgson
c8f6d46c8b add remoteAudioElement 2015-10-21 01:23:15 +01:00
Matthew Hodgson
5d1ad4d259 only show the zoom button on video calls 2015-10-21 01:23:15 +01:00
David Baker
006f0b00c6 Try refing the container using a functiob as it currently seems to be endinh up with no refs some of the time 2015-10-20 18:23:27 +01:00
David Baker
fe0707535c binding makes no difference - put a null check in to catch the rogue exception 2015-10-20 17:46:11 +01:00
David Baker
da6c5653b1 Try binding actin handler 2015-10-20 12:13:31 +01:00
Matthew Hodgson
79eda12656 doh 2015-10-20 12:10:31 +01:00
Kegan Dougal
c74f7c956f Fix missing getAvatarUrlForMember 2015-10-20 11:49:21 +01:00
Matthew Hodgson
137439243a fix layout 2015-10-20 11:49:08 +01:00
Matthew Hodgson
bb5895c157 move mute state into the existing voice/video buttons for now. move zoom button to that set for now 2015-10-20 11:30:54 +01:00
David Baker
34ad48a5d3 Wire up fullscreen button. Requires react-sdk with sync flag to dispatcher. 2015-10-20 11:03:32 +01:00
David Baker
078134d481 Add fullscreen button (not wired up to anything) 2015-10-20 10:48:08 +01:00
Kegan Dougal
f882ecc31b 'center' the buttons just like the cog/video/voice buttons are. 2015-10-20 10:22:23 +01:00
Kegan Dougal
d302f3eebb Add in voip mute video/audio skin. Needs a bit more CSS tweaks. 2015-10-20 09:55:41 +01:00
David Baker
bdbfc2b6e0 Add the little edit widgets and make those the things that bring up the message context menus. Still need to add them to all event tiles and make the context menus point the right way. 2015-10-19 19:30:04 +01:00
Matthew Hodgson
f256f79418 accidental regression that was causing the room list to scroll horizontally 2015-10-19 18:27:21 +01:00
Matthew Hodgson
d1cda75c8b Date refinements. Show more detailed timestamps for older messages, and try to show a separator as the first message in every room 2015-10-19 18:19:26 +01:00
Matthew Hodgson
530b077a8e reimplement 1a95148dae which somehow got lost during refactoring vector-web into matrix-react-sdk and vector-web 2015-10-19 17:13:51 +01:00
David Baker
11aa6f8c37 Null check userId on member. Fixes #5. 2015-10-19 14:33:45 +01:00
David Baker
e3e49daddb Make 'view source' work 2015-10-15 14:41:12 +01:00
David Baker
4b904d90f2 Slightly improve source path tree in the browser debugger 2015-10-15 14:40:36 +01:00
David Baker
48924a6106 Change resend buttons to a context menu with working resending and non-working 'view source' 2015-10-15 14:14:33 +01:00
David Baker
2adb8bac5c Actually turn on production mode: env vars need to be explicitly passed through 2015-10-14 09:20:18 +01:00
David Baker
c0938f270e Unused code 2015-10-13 16:03:07 +01:00
David Baker
47c1bb35db Un-break member hovering 2015-10-13 15:17:20 +01:00
David Baker
7598be684c Merge pull request #198 from stevenhammerton/sh-cas-auth
Add support for CAS auth
2015-10-13 14:26:03 +01:00
David Baker
1dd707775a Use new js-sdk modified time to avoid unnecessary member tile updates. Also avoid double-updating since we were setting state and then forcing an update which is redundant. 2015-10-13 11:10:43 +01:00
Steven Hammerton
293ee1bbcb Fix typo in comment and remove console.log leftover from debugging 2015-10-12 17:41:56 +01:00
Steven Hammerton
b5357d3298 Remove whitespace 2015-10-12 10:28:39 +01:00
Steven Hammerton
c561647460 Add missing comma 2015-10-12 10:27:45 +01:00
Steven Hammerton
f5039ac9af Use node querystring module to parse query string like name value pairs from fragment 2015-10-12 10:27:21 +01:00
Steven Hammerton
353af6c647 Move CasLogin logic to controller class and logic object in react-sdk 2015-10-12 10:27:16 +01:00
Matthew Hodgson
17c81c1101 icons 2015-10-11 22:51:00 +01:00
Matthew Hodgson
50ebce69b7 make file upload look slightly less crap 2015-10-11 18:42:54 +01:00
Matthew Hodgson
191d56673b fix message spacing on Safari caused by switching back to <li/>s... 2015-10-11 18:21:36 +01:00
Matthew Hodgson
5d962e1feb improve horizontal flexing of timeline 2015-10-11 18:04:01 +01:00
Matthew Hodgson
201caed773 fix rubberband bounce on OSX and iOS 2015-10-11 17:50:24 +01:00
Matthew Hodgson
9be98058b7 fix horizontal overscroll 2015-10-11 17:50:16 +01:00
Matthew Hodgson
ba0ae5ba59 various cosmetic fixes based on design feedback 2015-10-11 17:28:36 +01:00
Matthew Hodgson
9a8a9a4ce4 track RHS collapse state, and implement a basic responsive design 2015-10-11 16:09:46 +01:00
Matthew Hodgson
b05f3343e2 tooltipize BottomLeftMenu too for consistency 2015-10-11 15:00:43 +01:00
Matthew Hodgson
ae506b5b1f fix cursor for unhide LHS button 2015-10-11 13:56:25 +01:00
Matthew Hodgson
93de2307c1 improve collapsed LHS implementation - split the tooltip into its own component; position it with javascript as overflow-y + position absolute = clipping hell; preserve the collapse state between MatrixChat re-renders; fix positioning of the 'show' button; switch to dispatcher for show/hide LHS; remove errant scrollbars 2015-10-11 13:54:38 +01:00
Matthew Hodgson
8bdb5c0745 implement right panel collapsing more correctly too 2015-10-11 02:25:26 +01:00
Matthew Hodgson
47ed8971e3 implement collapsible leftpanel at last 2015-10-11 02:09:14 +01:00
Steven Hammerton
a8d51cdf58 Add support for CAS auth 2015-10-10 18:52:44 +01:00
Matthew Hodgson
a05437e81f add npm i source-map-loader troubleshooting step 2015-10-10 18:51:22 +01:00
David Baker
93f266a4fa Update to new memberlist api and hopefully sort out presence (list wasn't being re-sorted at the right time)
Hopefully fix #11
2015-10-09 17:26:28 +01:00
David Baker
aed1fe9bf1 Merge branch 'master' into develop 2015-10-09 16:40:27 +01:00
David Baker
7296cbfd5b Spellig 2015-10-09 16:38:37 +01:00
David Baker
023034ce4f Check for existence of content.size. Fixes #201. 2015-10-09 16:28:51 +01:00
David Baker
c68ef38399 Use ChangeDisplayNname / ChangeAvatar widgets to prompt for display name & avatar at signup.
Fixes #7.
2015-10-09 11:56:16 +01:00
David Baker
ccc5f30c9b Get the vector 'ongoing conference' bar back (vector now needs a custom RoomView for this: it would be nice it it didn't) 2015-10-08 15:22:16 +01:00
David Baker
c22442f6d1 Add ChangeDisplayName component and use it 2015-10-07 18:44:07 +01:00
David Baker
fca65a8cdb Show invited members separately.
May not be the final look.

Fixes #6
2015-10-06 15:09:42 +01:00
David Baker
807e947146 Add spinner for inviting. Fixes #13. 2015-10-06 14:12:45 +01:00
David Baker
85636ccdad Show end call button in states other than connected and ringback: it's perfectly valid to want to end the call at other times.
Fixes #9
2015-10-05 16:29:07 +01:00
David Baker
490e56bfbb Improved url / hash change handling 2015-10-05 15:32:34 +01:00
David Baker
61f951a33e Merge pull request #190 from vector-im/reactsdk-unfork
New vector using react-sdk
2015-10-05 10:31:39 +01:00
David Baker
53c8b9bcf7 no single quotes because windows 2015-10-05 10:20:02 +01:00
David Baker
df39c3a281 Changes from PR feedback 2015-10-02 16:44:35 +01:00
David Baker
050c6cf72f Dummy merge branch 'master' into reactsdk-unfork
(affected file no longer in this repo)
2015-10-02 14:36:07 +01:00
David Baker
2247d951d6 Add config file for default hs/is urls 2015-10-02 11:48:52 +01:00
David Baker
7b9cd7c232 Update readme 2015-10-01 16:02:44 +01:00
David Baker
c687f32f39 Port over custom vector roomlist controller with mini callview 2015-10-01 10:46:42 +01:00
David Baker
3845a989f6 Get conf calling working, apart from the end call button showing in the right room. 2015-09-30 18:22:15 +01:00
David Baker
94a6f856d1 Use the new interface for providing conf call functionality. Doesn't shoe it in the right room yet. 2015-09-30 16:52:45 +01:00
Matthew Hodgson
c62d97ca04 better XXXs 2015-09-29 12:57:54 +01:00
David Baker
fd6e7663cb Fix notifications 2015-09-28 17:52:34 +01:00
David Baker
7d540572fd Update for new default avatar API. 2015-09-28 17:06:39 +01:00
David Baker
c3f32b74e4 Some missed '/' / '.' replacemement 2015-09-28 14:48:07 +01:00
David Baker
588dbf5693 reskindex 2015-09-28 11:34:11 +01:00
David Baker
91c0df4450 Merge branch 'master' into reactsdk-unfork 2015-09-28 11:32:40 +01:00
David Baker
3ecf19df49 Disable caching on the local http server as commented 2015-09-25 17:38:51 +01:00
David Baker
f778f6adf9 Remove unused stuff 2015-09-25 15:33:38 +01:00
David Baker
796f424a3f Didn't need these in the end 2015-09-25 11:51:35 +01:00
David Baker
409697b35b Oops, these were just at the wrong scope 2015-09-25 11:46:06 +01:00
David Baker
f020f4397c Switch to webpack
Webapck actually supports loading input source maps and generally seems a lot
more solid then browserify (even if their website has an annoying animated
logo).
2015-09-25 11:43:28 +01:00
Matthew Hodgson
5fe41e28d7 make presence work better on the memberlist. kludges around lack of syjs-28. is about as good as angular was now. 2015-09-24 01:58:21 +02:00
Matthew Hodgson
a5a6a35122 don't blindly and inconsistently kill scrollbars 2015-09-24 00:19:40 +02:00
David Baker
bfa4cda2c6 build is no longer used 2015-09-23 10:14:38 +01:00
David Baker
c21dd853f9 Update package.json for renamed skin 2015-09-23 09:48:18 +01:00
Matthew Hodgson
1901fdf889 oops, bogus commit 2015-09-22 22:57:27 +02:00
David Baker
b11abae8e8 More fixing up of paths, requires etc 2015-09-22 19:09:23 +01:00
David Baker
7e72ee891a More fixing up of vector skin 2015-09-22 18:49:04 +01:00
David Baker
40594fc5fa Fix up controller requires 2015-09-22 18:17:19 +01:00
David Baker
dd4cfb25f8 Replace symlinks 2015-09-22 18:06:43 +01:00
David Baker
148dbc23ed Rename base skin to vector 2015-09-22 18:05:55 +01:00
David Baker
682392d02a Move skins dir to src 2015-09-22 18:05:31 +01:00
David Baker
09b81f46b0 Remove controllers that all come from react-sdk 2015-09-22 17:20:22 +01:00
David Baker
2f0df6d37e Merge branch 'master' into reactsdk-unfork 2015-09-22 15:28:50 +01:00
David Baker
616b4fe0f1 Merge pull request #175 from vector-im/matthew/userlist
Reskin the userlist as per the design
2015-09-22 15:27:21 +01:00
Matthew Hodgson
ef3603cd1a oops, rogue debugging stmt 2015-09-22 01:25:58 +02:00
Matthew Hodgson
61c94d63e7 make the new userlist UI actually work 2015-09-22 01:16:45 +02:00
Matthew Hodgson
260e22186b WIP at turning MemberInfo into a ContextualMenu 2015-09-21 19:23:04 +02:00
Matthew Hodgson
048260bb1b WIP at turning MemberInfo into a ContextualMenu 2015-09-21 19:22:29 +02:00
David Baker
a545007a19 Merge branch 'master' into reactsdk-unfork 2015-09-21 18:08:58 +01:00
Matthew Hodgson
56c5f6f46e clarify deployment 2015-09-19 20:17:45 +01:00
David Baker
81db1b2360 Merge pull request #160 from vector-im/conferencing
Add conferencing support
2015-09-18 10:03:02 +01:00
Kegan Dougal
240d5502fe Add a FIXME explaining the situation around alternative FS ASes 2015-09-17 11:47:42 +01:00
Kegan Dougal
7a50166dc6 Move the 'thumbnail' video to the top-left of the screen
This was originally laid out at the MatrixChat level which could then be
CSSified, but Matthew suggests this looks a lot better being at the
RoomList level above recents. Move the rendering logic to RoomList.
2015-09-17 11:37:56 +01:00
Kegan Dougal
9c8b540d14 Actually add the doc 2015-09-17 11:06:50 +01:00
Kegan Dougal
e991beb900 Add conferencing doc 2015-09-17 11:06:08 +01:00
David Baker
f1120562f3 Random console log 2015-09-16 14:14:21 +01:00
David Baker
901574b56e Update package.json 2015-09-16 14:13:16 +01:00
David Baker
fe586f6a36 Merge branch 'master' into reactsdk-unfork 2015-09-16 10:49:04 +01:00
Matthew Hodgson
01d3f2f119 implement /part, /j, and error rather than pass-through unrecognised commands 2015-09-16 01:09:32 +01:00
Matthew Hodgson
0aec086ebb actually link to blog etc from the login page 2015-09-15 17:06:04 +01:00
Kegan Dougal
f89fbffe89 Auto-place a video call if the conf notification is clicked 2015-09-15 15:55:02 +01:00
Kegan Dougal
2b65b4c2dc Hide the local video when in a conf call 2015-09-15 15:49:33 +01:00
Matthew Hodgson
ce2632bbe6 thinko 2015-09-15 15:18:39 +01:00
Kegan Dougal
370310bf82 Use better variable names 2015-09-15 15:02:02 +01:00
Kegan Dougal
f384aa7d9e Add notification to group chat rooms with ongoing conf calls
This notification disappears when in the conf call / when the call is over.
CSS stolen from the desktop notification bar.
2015-09-15 14:18:17 +01:00
Kegan Dougal
353269370f Wire up the "room" CallView for conferencing
This also separates out concerns better - UI elements just need to poke
getCallForRoom rather than care if the thing they are displaying is a
true 1:1 for this room ID or actually a conf room.
2015-09-15 13:19:07 +01:00
Kegan Dougal
7866979c79 Show/hide the Hangup button depending on the state of the conf call. 2015-09-15 13:04:09 +01:00
Kegan Dougal
5e3698de64 Actually enforce 1 call semantics. 2015-09-15 11:43:51 +01:00
Kegan Dougal
59986d8b72 Pass the call around different CallViews to keep media flowing
Previously, the CallView was attached to the RoomView, so you would get
a new CallView each time you changed the room and the one you changed
from would be destroyed. This would destroy media capture/playback as
the element was no longer in the DOM.

This is now fixed by having a "global" CallView which is attached at
the MatrixChat "page" level in the DOM hierarchy. This CallView isn't
scoped to a particular room; it will render any "active" call it can
find that *isn't the current room being displayed*. This has the side
effect of enforcing 1 call per app semantics as only the first active
call found is returned.

This fixes https://github.com/vector-im/vector-web/issues/31
This is unfinished (CSS for the global call view isn't done)
2015-09-15 11:05:53 +01:00
Kegan Dougal
fc892b3580 Hide 1:1 conference rooms 2015-09-11 16:55:48 +01:00
Kegan Dougal
e3b02a295c Check conf user/rooms a bit more efficiently 2015-09-11 16:14:30 +01:00
Kegan Dougal
77401e215e First working outbound conference calling
This has a number of failings currently: 1) It needs to hide the 1:1 conference
room, 2) Swapping tabs on the outbound call mutes audio (this just seems to be
a vector bug since I can repro this on a normal 1:1 voip call), 3) Needs a big
plinth/etc to say the conf call is in progress.
2015-09-11 15:49:47 +01:00
David Baker
ce9fcdbbb5 Old, useless README from the trivial react-sdk example 2015-09-08 17:06:19 +01:00
David Baker
980c71076e Start moving back to basing off react sdk: move vector out of examples dir 2015-09-08 17:02:24 +01:00
David Baker
ee4da24b84 Merge pull request #148 from vector-im/matthew/login
Fix up various login bugs and nastinesses
2015-09-07 10:47:25 +01:00
Matthew Hodgson
737fc74756 Merge branch 'matthew/login' into matthew/userlist 2015-09-01 02:13:52 +03:00
Matthew Hodgson
027ab6ee99 fix login layout on tall screens; limit dialog box width; wait a full second to re-query new HS details 2015-09-01 01:55:13 +03:00
Matthew Hodgson
8214ee8fad fix blinking when toggling advanced 2015-09-01 01:35:47 +03:00
Matthew Hodgson
ab068cc372 improve login, including checkbox fix for advanced options, rechecking when you change server, avoiding flickering when you change HS, better error/spinner layout, and trimming whitespace 2015-08-31 19:30:24 +01:00
Matthew Hodgson
5bab440a1f temporarily pin flux to 2.0, as 2.1 switches to ES6 and Babel which breaks our Dispatcher.js which fails to correctly extend the new Flux dispatcher, presumably due to using our noddy extend.js rather than an ES6-compatible one 2015-08-29 20:28:07 +01:00
David Baker
ef027706b9 Offer to join a room if you're not in it 2015-08-20 16:47:25 +01:00
David Baker
2351ad997c More places where we assumed sender would not be null 2015-08-20 11:46:54 +01:00
David Baker
cb25740961 Fix exception when accepting room invites 2015-08-20 11:43:08 +01:00
Matthew Hodgson
e3798e1b85 WIP fixing up the member list - just needs CSS and testing 2015-08-15 03:06:21 +01:00
Matthew Hodgson
80c3b2c8a3 match the design 2015-08-14 21:14:05 +01:00
Matthew Hodgson
a2e7c4aa77 WIP for fixing the popovers 2015-08-14 19:15:41 +01:00
Matthew Hodgson
25a4f1fde0 comment out ugly thumbnail bg for now 2015-08-14 17:43:37 +01:00
Matthew Hodgson
6b72c992c5 fix 'save changes' linewrap - thanks jfred 2015-08-14 17:08:31 +01:00
Matthew Hodgson
cb7f1aa916 ctrl-alt-num to change rooms 2015-08-14 16:30:19 +01:00
Matthew Hodgson
1176168960 escape key to cancel imageview popup 2015-08-14 15:52:44 +01:00
Matthew Hodgson
24630f598f fix errors with alt-up/down 2015-08-14 15:52:44 +01:00
Matthew Hodgson
316a28838f let's wrap when you go off the beginning 2015-08-14 15:51:57 +01:00
David Baker
960a38fe43 Don't try loading -ve room indices 2015-08-14 15:44:21 +01:00
David Baker
87feb6b076 Revert accidental commit 2015-08-14 15:37:41 +01:00
David Baker
c5e33352b0 You can't just use React classes as normal static classes :( 2015-08-14 15:25:05 +01:00
Matthew Hodgson
e1efb165fd make file thumbnails match the design 2015-08-14 14:44:16 +01:00
David Baker
12e53f5046 Ditch envify as it doesn't actually seem to work. Just setting the env var works fine though. 2015-08-14 14:13:30 +01:00
Matthew Hodgson
4851adf3b0 fix up the look & feel for unread messages warning to make it more visible 2015-08-14 13:53:32 +01:00
Matthew Hodgson
9ed5ca3ccb implement a pretty droptarget when uploading files 2015-08-14 13:20:39 +01:00
Matthew Hodgson
88095d4360 cursor: pointer for toolbar buttons 2015-08-14 13:05:44 +01:00
David Baker
7bdf612ad5 argh 2 2015-08-14 11:25:54 +01:00
David Baker
6d390ebd2f Argh 2015-08-14 11:24:41 +01:00
David Baker
ca09758210 Fix onerror handlers 2015-08-14 10:31:45 +01:00
David Baker
e5099ce3b7 Don't clobber the ready state if the sdk isn't ready yet 2015-08-14 10:31:09 +01:00
David Baker
a3879b507a Fix default avatars 2015-08-14 10:30:47 +01:00
David Baker
7a8537f3dc Don't show blank EventAsText tiles 2015-08-14 10:30:14 +01:00
Matthew Hodgson
001d1c50ef factor out MRoomMemberTile, MCallInviteTile, MCallAnswerTile and MCallHangupTile to just use EventAsTextTile and thus reduce duplication enormously 2015-08-14 00:28:37 +01:00
David Baker
fec266f1c0 Move avatars into their own components so I can add functionality like custom default avatars and onerror sources without having to add it in 13 separate places. Add the aforementioned features. 2015-08-13 19:30:02 +01:00
David Baker
b580fba7db Rooms with notifs turned off should still go bold. 2015-08-13 16:43:59 +01:00
David Baker
8bb836ad49 Assuming this was meant to be temporary 2015-08-12 18:26:43 +01:00
David Baker
eb36a2b242 If we were loaded with a room alias, don't lose that alias when we try to load the corresponding room. 2015-08-12 17:06:45 +01:00
Matthew Hodgson
18be8530fe put key='' on the right element 2015-08-12 01:45:04 +01:00
Matthew Hodgson
cf77a96ac5 make text buttons in the header look clickable with a pointer 2015-08-12 01:33:25 +01:00
Matthew Hodgson
5153954a28 put cancel & save buttons into the roomheader 2015-08-12 01:30:23 +01:00
Matthew Hodgson
bf10a03ab1 revert part of 0eceb737 as we need the auxpanel to have overflow: auto, not overflow: visible (initial)... 2015-08-12 01:30:05 +01:00
Matthew Hodgson
566c0437c0 provide a way to hide the notification nag toolbar 2015-08-12 00:42:04 +01:00
Matthew Hodgson
2ffa450e31 don't try to show negative power levels 2015-08-12 00:41:46 +01:00
Matthew Hodgson
8fd26509ac remove spurious dead code 2015-08-12 00:41:20 +01:00
Matthew Hodgson
3fc4aee269 lots of missing -webkit-flex for safari 2015-08-11 21:00:33 +01:00
David Baker
a20b4d2d2c If we have a current room on load, we should display it! 2015-08-11 17:24:12 +01:00
David Baker
be5aaeaad7 Don't try to fill space if we don't have a message wrapper 2015-08-11 17:20:31 +01:00
David Baker
18c56a171e Focus composer on alt-up/down 2015-08-11 14:43:52 +01:00
David Baker
b44d19d305 Call initial release 0.1.0 and this release 0.1.1 2015-08-10 18:19:30 +01:00
David Baker
a9fc47efd7 changelog, authors and contributing files 2015-08-10 18:09:35 +01:00
David Baker
a45785fe1a Fix blank page on login 2015-08-10 17:12:31 +01:00
David Baker
19d350e876 Merge branch 'email_login' 2015-08-10 16:25:04 +01:00
David Baker
7a1796870a Handle old server that don't support login using email address 2015-08-10 16:22:33 +01:00
David Baker
96cedc237e Actually change to vector IS 2015-08-10 15:50:26 +01:00
David Baker
6ab993f1a9 Change default IS to vector 2015-08-10 15:27:36 +01:00
David Baker
efcc2061b8 Hopefully port over ndarilek's accessibility changes to Vector (albeit with list elements contained within RoomView) 2015-08-10 15:17:15 +01:00
David Baker
10053fa770 Argh, the scrollbars! 2015-08-07 16:57:16 +01:00
David Baker
3519555710 Again, no point displaying scrollbars unless they're actually necessary 2015-08-07 16:48:48 +01:00
David Baker
0eceb737de Remove scrollbars by removing overflow: scroll in places. Add a title for hover over on long (or otherwise) topics. 2015-08-07 16:01:39 +01:00
David Baker
64727cb60e Don't die on malformed VoIP offers 2015-08-07 14:48:50 +01:00
David Baker
711bf583ab missed a file 2015-08-06 16:04:22 +01:00
David Baker
2771907573 Support room aliases in url bar and show them for rooms that have them 2015-08-06 14:58:52 +01:00
David Baker
9d8d4e4896 Support email login 2015-08-04 16:30:41 +01:00
296 changed files with 2783 additions and 6106 deletions

5
.gitignore vendored
View File

@@ -1,4 +1,3 @@
node_modules
build
bundle.css
bundle.js
vector/bundle.*
lib

9
AUTHORS.rst Normal file
View File

@@ -0,0 +1,9 @@
Vector is written mainly by the Vector team, building upon the Matrix React
SDK. Vector also welcomes external contributions. Third party contributors
include:
* Nolan Darilek (https://github.com/ndarilek)
Accessibility and semantic markup contributions
* https://github.com/neko259
Improved scrollbar CSS

20
CHANGES.rst Normal file
View File

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

4
CONTRIBUTING.rst Normal file
View File

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

146
README.md
View File

@@ -10,138 +10,36 @@ Getting started
2. Clone the repo: `git clone https://github.com/vector-im/vector-web.git`
3. Switch to the SDK directory: `cd vector-web`
4. Install the prerequisites: `npm install`
5. Switch to the example directory: `cd examples/vector`
6. Install the example app prerequisites: `npm install`
7. Build the example and start a server: `npm start`
5. Start the development builder and a testing server: `npm start`
6. Wait a few seconds for the initial build to finish.
7. Open http://127.0.0.1:8080/ in your browser to see your newly built Vector.
Now open http://127.0.0.1:8080/ in your browser to see your newly built
Vector.
With `npm start`, any changes you make to the source files will cause a rebuild so
your changes will show up when you refresh.
For production use, run `npm run build` to build all the necessary files
into the `vector` directory and run your own server.
Development
===========
To work on the CSS and Javascript and have the bundle files update as you
change the source files, you'll need to do two extra things:
You can work on any of the source files within Vector with the setup above,
and your changes will cause an instant rebuild. If you also need to make
changes to the react sdk, you can:
1. Link the react sdk package into the example:
`cd vector-web/examples/vector; npm link ../../`
2. Start a watcher for the CSS files:
`cd vector-web; npm run start:css`
`npm link path/to/your/react/sdk`
2. Start the development rebuilder in your react SDK directory:
`npm start`
Note that you may need to restart the CSS builder if you add a new file. Note
that `npm start` builds debug versions of the javascript and CSS, which are
much larger than the production versions build by the `npm run build` commands.
If you add or remove any components from the Vector skin, you will need to rebuild
the skin's index by running, `npm run reskindex`.
IMPORTANT: If you customise components in your application (and hence require
react from your app) you must be sure to:
You may need to run `npm i source-map-loader` in matrix-js-sdk if you get errors
about "Cannot resolve module 'source-map-loader'" due to shortcomings in webpack.
1. Make your app depend on react directly
2. If you `npm link` matrix-react-sdk, manually remove the 'react' directory
from matrix-react-sdk's `node_modules` folder, otherwise browserify will
pull in both copies of react which causes the app to break.
Deployment
==========
How to customise the SDK
========================
Just run `npm build` and then mount the `vector` directory on your webserver to
actually serve up the app, which is entirely static content.
The matrix-react-sdk provides well-defined reusable UI components which may be
customised/replaced by the developer to build into an app. A set of consistent
UI components (View + CSS classes) is called a 'skin' - currently the SDK
provides a very vanilla whitelabelled 'base skin'. In future the SDK could
provide alternative skins (probably by extending the base skin) that provide more
specific look and feels (e.g. "IRC-style", "Skype-style") etc. However, unlike
Wordpress themes and similar, we don't normally expect app developers to define
reusable skins. Instead you just go and incorporate your view customisations
into your actual app.
The SDK uses the 'atomic' design pattern as seen at http://patternlab.io to
encourage a very modular and reusable architecture, making it easy to
customise and use UI widgets independently of the rest of the SDK and your app.
In practice this means:
* The UI of the app is strictly split up into a hierarchy of components.
* Each component has its own:
* View object defined as a React javascript class containing embedded
HTML expressed in React's JSX notation.
* CSS file, which defines the styling specific to that component.
* Components are loosely grouped into the 5 levels outlined by atomic design:
* atoms: fundamental building blocks (e.g. a timestamp tag)
* molecules: "group of atoms which functions together as a unit"
(e.g. a message in a chat timeline)
* organisms: "groups of molecules (and atoms) which form a distinct section
of a UI" (e.g. a view of a chat room)
* templates: "a reusable configuration of organisms" - used to combine and
style organisms into a well-defined global look and feel
* pages: specific instances of templates.
Good separation between the components is maintained by adopting various best
practices that anyone working with the SDK needs to be be aware of and uphold:
* Views are named with upper camel case (e.g. molecules/MessageTile.js)
* The view's CSS file MUST have the same name (e.g. molecules/MessageTile.css)
* Per-view CSS is optional - it could choose to inherit all its styling from
the context of the rest of the app, although this is unusual for any but
the simplest atoms and molecules.
* The view MUST *only* refer to the CSS rules defined in its own CSS file.
'Stealing' styling information from other components (including parents)
is not cool, as it breaks the independence of the components.
* CSS classes are named with an app-specific namespacing prefix to try to avoid
CSS collisions. The base skin shipped by Matrix.org with the matrix-react-sdk
uses the naming prefix "mx_". A company called Yoyodyne Inc might use a
prefix like "yy_" for its app-specific classes.
* CSS classes use upper camel case when they describe React components - e.g.
.mx_MessageTile is the selector for the CSS applied to a MessageTile view.
* CSS classes for DOM elements within a view which aren't components are named
by appending a lower camel case identifier to the view's class name - e.g.
.mx_MessageTile_randomDiv is how you'd name the class of an arbitrary div
within the MessageTile view.
* We deliberately use vanilla CSS 3.0 to avoid adding any more magic
dependencies into the mix than we already have. App developers are welcome
to use whatever floats their boat however.
* The CSS for a component can however override the rules for child components.
For instance, .mx_RoomList .mx_RoomTile {} would be the selector to override
styles of RoomTiles when viewed in the context of a RoomList view.
Overrides *must* be scoped to the View's CSS class - i.e. don't just define
.mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its
own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override
only to the context of RoomList views. N.B. overrides should be relatively
rare as in general CSS inheritence should be enough.
* Components should render only within the bounding box of their outermost DOM
element. Page-absolute positioning and negative CSS margins and similar are
generally not cool and stop the component from being reused easily in
different places.
* We don't use the atomify library itself, as React already provides most
of the modularity requirements it brings to the table.
With all this in mind, here's how you go about skinning the react SDK UI
components to embed a Matrix client into your app:
* Create a new NPM project. Be sure to directly depend on react, (otherwise
you can end up with two copies of react).
* Create an index.js file that sets up react. Add require statements for
React, the ComponentBroker and matrix-react-sdk and a call to Render
the root React element as in the examples.
* Create React classes for any custom components you wish to add. These
can be based off the files in `views` in the `matrix-react-sdk` package,
modifying the require() statement appropriately.
You only need to copy files you want to customise.
* Add a ComponentBroker.set() call for each of your custom components. These
must come *before* `require("matrix-react-sdk")`.
* Add a way to build your project: we suggest copying the browserify calls
from the example projects, but you could use grunt or gulp.
* Create an index.html file pulling in your compiled index.js file, the
CSS bundle from matrix-react-sdk.
For more specific detail on any of these steps, look at the `custom` example in
matrix-react-sdk/examples.

4
config.json Normal file
View File

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

52
docs/conferencing.md Normal file
View File

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

View File

@@ -1,40 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MTextTileController = require("matrix-react-sdk/src/controllers/molecules/MTextTile");
module.exports = React.createClass({
displayName: 'MTextTile',
mixins: [MTextTileController],
render: function() {
var content = this.props.mxEvent.getContent();
return (
<span ref="content" className="mx_MTextTile mx_MessageTile_content" onClick={this.onClick}>
{content.body}
</span>
);
},
onClick: function(ev) {
global.alert(this.props.mxEvent.getContent().body);
}
});

View File

@@ -1,4 +0,0 @@
matrix-react-example
====================
An example of how to use the Matrix React SDK to build a more customised app

View File

@@ -1,12 +0,0 @@
<!doctype html>
<html lang="en" style="height: 100%; overflow: hidden">
<head>
<meta charset="utf-8">
<title>Matrix React SDK Custom Example</title>
</head>
<body style="height: 100%; ">
<section id="matrixchat" style="height: 100%; "></section>
<script src="bundle.js"></script>
<link rel="stylesheet" href="node_modules/matrix-react-sdk/bundle.css">
</body>
</html>

View File

@@ -1,40 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
// Remember to make your project depend on react directly as soon as
// you add a require('react') to any file in your project. Do not rely
// on react being pulled in via matrix-react-sdk: browserify breaks
// horribly in this situation and can end up pulling in multiple copies
// of react.
var React = require("react");
// We pull in the component broker first, separately, as we need to replace
// components before the SDK loads.
var ComponentBroker = require("matrix-react-sdk/src/ComponentBroker");
var CustomMTextTile = require('./CustomMTextTile');
ComponentBroker.set('molecules/MTextTile', CustomMTextTile);
var MatrixReactSdk = require("matrix-react-sdk");
//var MatrixReactSdk = require("../../src/index");
React.render(
<MatrixReactSdk.MatrixChat />,
document.getElementById('matrixchat')
);

View File

@@ -1,29 +0,0 @@
{
"name": "matrix-react-example",
"version": "0.0.1",
"description": "Example usage of matrix-react-sdk",
"author": "matrix.org",
"repository": {
"type": "git",
"url": "https://github.com/matrix-org/matrix-react-sdk"
},
"license": "Apache-2.0",
"devDependencies": {
"browserify": "^10.2.3",
"envify": "^3.4.0",
"http-server": "^0.8.0",
"matrix-react-sdk": "../../",
"npm-css": "^0.2.3",
"parallelshell": "^1.2.0",
"reactify": "^1.1.1",
"uglify-js": "^2.4.23",
"watchify": "^3.2.1"
},
"scripts": {
"build": "browserify -t [ envify --NODE_ENV production ] -g reactify index.js | uglifyjs -c -m -o bundle.js",
"start": "parallelshell 'watchify -v -d -g reactify index.js -o bundle.js' 'http-server'"
},
"dependencies": {
"react": "^0.13.3"
}
}

View File

@@ -1,4 +0,0 @@
matrix-react-example
====================
A simple example of how to use the Matrix React SDK

View File

@@ -1 +0,0 @@
../../skins/base/fonts/

View File

@@ -1 +0,0 @@
../../skins/base/img

View File

@@ -1,25 +0,0 @@
{
"name": "matrix-react-example",
"version": "0.0.1",
"description": "Example usage of matrix-react-sdk",
"author": "matrix.org",
"repository": {
"type": "git",
"url": "https://github.com/matrix-org/matrix-react-sdk"
},
"license": "Apache-2.0",
"devDependencies": {
"browserify": "^10.2.3",
"envify": "^3.4.0",
"http-server": "^0.8.0",
"matrix-react-sdk": "../../",
"parallelshell": "^1.2.0",
"reactify": "^1.1.1",
"uglify-js": "^2.4.23",
"watchify": "^3.2.1"
},
"scripts": {
"build": "browserify --ignore olm -t [ envify --NODE_ENV production ] -t reactify index.js | uglifyjs -c -m -o bundle.js",
"start": "parallelshell \"watchify --ignore olm -v -d -t reactify index.js -o bundle.js\" \"http-server\""
}
}

View File

@@ -1,40 +1,48 @@
{
"name": "matrix-react-sdk",
"name": "vector-web",
"version": "0.0.1",
"description": "SDK for matrix.org using React",
"description": "Vector webapp",
"author": "matrix.org",
"repository": {
"type": "git",
"url": "https://github.com/matrix-org/matrix-react-sdk"
"url": "https://github.com/vector-im/vector-web"
},
"license": "Apache-2.0",
"main": "src/index.js",
"style": "bundle.css",
"scripts": {
"build:skins": "jsx skins build/skins",
"build:logic": "jsx src build/src",
"build:js": "npm run build:skins && npm run build:logic",
"start:js": "jsx -w skins/base/views/ build --source-map-inline",
"build:css": "catw 'skins/base/css/**/*.css' -o bundle.css -c uglifycss --no-watch",
"start:css": "catw 'skins/base/css/**/*.css' -o bundle.css -v",
"build": "npm run build:js && npm run build:css",
"start": "parallelshell \"npm run start:js\" \"npm run start:css\"",
"prepublish": "npm run build"
"reskindex": "reskindex vector -h src/skins/vector/header",
"build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/bundle.css -c uglifycss --no-watch",
"build:compile": "babel --source-maps -d lib src",
"build:bundle": "NODE_ENV=production webpack -p lib/vector/index.js vector/bundle.js",
"build": "npm run build:css && npm run build:compile && npm run build:bundle",
"start:js": "webpack -w src/vector/index.js vector/bundle.js",
"start:skins:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/bundle.css",
"//cache": "Note the -c 1 below due to https://code.google.com/p/chromium/issues/detail?id=508270",
"start": "parallelshell \"npm run start:js\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
"clean": "rimraf lib vector/bundle.css vector/bundle.js vector/bundle.js.map",
"prepublish": "npm run build:css && npm run build:compile"
},
"dependencies": {
"classnames": "^2.1.2",
"filesize": "^3.1.2",
"flux": "^2.0.3",
"matrix-js-sdk": "git://github.com/matrix-org/matrix-js-sdk.git#develop",
"flux": "~2.0.3",
"linkifyjs": "^2.0.0-beta.4",
"matrix-js-sdk": "^0.2.2",
"matrix-react-sdk": "^0.0.1",
"q": "^1.4.1",
"react": "^0.13.3",
"react-loader": "^1.4.0",
"linkifyjs": "^2.0.0-beta.4"
"react-loader": "^1.4.0"
},
"devDependencies": {
"babel": "^5.8.23",
"babel-core": "^5.8.25",
"babel-loader": "^5.3.2",
"catw": "^1.0.1",
"http-server": "^0.8.4",
"json-loader": "^0.5.3",
"parallelshell": "^1.2.0",
"react-tools": "^0.13.3",
"rimraf": "^2.4.3",
"source-map-loader": "^0.1.5",
"uglifycss": "0.0.15"
}
}

View File

@@ -1,73 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MemberInfo {
text-align: center;
border: 1px solid #a9dbf4;
border-radius: 8px;
background-color: #fff;
position: absolute;
width: 200px;
margin-left: -295px;
margin-top: 0px;
z-index: 1000;
padding: 6px;
}
.mx_MemberInfo_chevron {
padding: 12px;
position: absolute;
right: -21px;
top: 0px;
}
/*
* a hacky shim to extend the hitmask of the overlay to overlap
* better with the main menu itself
*/
.mx_MemberInfo_shim {
position: absolute;
left: 212px;
width: 40px;
height: 100%;
}
.mx_MemberInfo_avatar {
padding: 6px;
}
.mx_MemberInfo_avatarImg {
border-radius: 128px;
}
.mx_MemberInfo_field {
padding: 6px;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_MemberInfo_button {
vertical-align: middle;
max-width: 100px;
height: 36px;
background-color: #50e3c2;
line-height: 36px;
border-radius: 36px;
color: #fff;
margin: auto;
margin-top: 6px;
margin-bottom: 6px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,67 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var classNames = require('classnames');
var dis = require("../../../../src/dispatcher");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
module.exports = React.createClass({
displayName: 'BottomLeftMenu',
onSettingsClick: function() {
dis.dispatch({action: 'view_user_settings'});
},
onRoomDirectoryClick: function() {
dis.dispatch({action: 'view_room_directory'});
},
onCreateRoomClick: function() {
dis.dispatch({action: 'view_create_room'});
},
render: function() {
return (
<div className="mx_BottomLeftMenu">
<div className="mx_BottomLeftMenu_options">
<div className="mx_RoomTile" onClick={this.onCreateRoomClick}>
<div className="mx_RoomTile_avatar">
<img src="img/create-big.png" alt="Create new room" title="Create new room" width="42" height="42"/>
</div>
<div className="mx_RoomTile_name">Create new room</div>
</div>
<div className="mx_RoomTile" onClick={this.onRoomDirectoryClick}>
<div className="mx_RoomTile_avatar">
<img src="img/directory-big.png" alt="Directory" title="Directory" width="42" height="42"/>
</div>
<div className="mx_RoomTile_name">Directory</div>
</div>
<div className="mx_RoomTile" onClick={this.onSettingsClick}>
<div className="mx_RoomTile_avatar">
<img src="img/settings-big.png" alt="Settings" title="Settings" width="42" height="42"/>
</div>
<div className="mx_RoomTile_name">Settings</div>
</div>
</div>
</div>
);
}
});

View File

@@ -1,120 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var MemberInfoController = require("../../../../src/controllers/molecules/MemberInfo");
module.exports = React.createClass({
displayName: 'MemberInfo',
mixins: [MemberInfoController],
componentDidMount: function() {
var self = this;
var memberInfo = this.getDOMNode();
var memberListScroll = document.getElementsByClassName("mx_MemberList_border")[0];
if (memberListScroll) {
memberInfo.style.top = (memberInfo.parentElement.offsetTop - memberListScroll.scrollTop) + "px";
}
},
getDuration: function(time) {
if (!time) return;
var t = parseInt(time / 1000);
var s = t % 60;
var m = parseInt(t / 60) % 60;
var h = parseInt(t / (60 * 60)) % 24;
var d = parseInt(t / (60 * 60 * 24));
if (t < 60) {
if (t < 0) {
return "0s";
}
return s + "s";
}
if (t < 60 * 60) {
return m + "m";
}
if (t < 24 * 60 * 60) {
return h + "h";
}
return d + "d ";
},
render: function() {
var power;
if (this.props.member) {
var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png";
power = <img src={ img } className="mx_MemberTile_power" width="48" height="48" alt=""/>;
}
var activeAgo = "unknown";
if (this.state.active >= 0) {
activeAgo = this.getDuration(this.state.active);
}
var kickButton, banButton, muteButton, giveModButton;
if (this.state.can.kick) {
kickButton = <div className="mx_MemberInfo_button" onClick={this.onKick}>
Kick
</div>;
}
if (this.state.can.ban) {
banButton = <div className="mx_MemberInfo_button" onClick={this.onBan}>
Ban
</div>;
}
if (this.state.can.mute) {
var muteLabel = this.state.muted ? "Unmute" : "Mute";
muteButton = <div className="mx_MemberInfo_button" onClick={this.onMuteToggle}>
{muteLabel}
</div>;
}
if (this.state.can.modifyLevel) {
var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod";
giveModButton = <div className="mx_MemberInfo_button" onClick={this.onModToggle}>
{giveOpLabel}
</div>
}
var opLabel;
if (this.state.isTargetMod) {
var level = this.props.member.powerLevelNorm + "%";
opLabel = <div className="mx_MemberInfo_field">Moderator ({level})</div>
}
return (
<div className="mx_MemberInfo">
<img className="mx_MemberInfo_chevron" src="img/chevron-right.png" width="9" height="16" />
<div className="mx_MemberInfo_shim"></div>
<div className="mx_MemberInfo_avatar">
<img className="mx_MemberInfo_avatarImg"
src={ this.props.member ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.member, 128, 128, "crop") : null }
width="128" height="128" alt=""/>
</div>
<div className="mx_MemberInfo_field">{this.props.member.userId}</div>
{opLabel}
<div className="mx_MemberInfo_field">Presence: {this.state.presence}</div>
<div className="mx_MemberInfo_field">Last active: {activeAgo}</div>
<div className="mx_MemberInfo_button" onClick={this.onChatClick}>Start chat</div>
{muteButton}
{kickButton}
{banButton}
{giveModButton}
</div>
);
}
});

View File

@@ -1,107 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../src/ComponentBroker');
var Modal = require("../../../../src/Modal");
var MemberTileController = require("../../../../src/controllers/molecules/MemberTile");
var MemberInfo = ComponentBroker.get('molecules/MemberInfo');
var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
// The Lato WOFF doesn't include sensible combining diacritics, so Chrome chokes on rendering them.
// Revert to Arial when this happens, which on OSX works at least.
var zalgo = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/;
module.exports = React.createClass({
displayName: 'MemberTile',
mixins: [MemberTileController],
// XXX: should these be in the controller?
getInitialState: function() {
return { 'hover': false };
},
mouseEnter: function(e) {
this.setState({ 'hover': true });
},
mouseLeave: function(e) {
this.setState({ 'hover': false });
},
render: function() {
var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId;
var power;
if (this.props.member) {
var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png";
power = <img src={ img } className="mx_MemberTile_power" width="48" height="48" alt=""/>;
}
var presenceClass = "mx_MemberTile_offline";
var mainClassName = "mx_MemberTile ";
if (this.props.member.user) {
if (this.props.member.user.presence === "online") {
presenceClass = "mx_MemberTile_online";
}
else if (this.props.member.user.presence === "unavailable") {
presenceClass = "mx_MemberTile_unavailable";
}
}
mainClassName += presenceClass;
var name = this.props.member.name;
if (isMyUser) name += " (me)";
var leave = isMyUser ? <span className="mx_MemberTile_leave" onClick={this.onLeaveClick}>X</span> : null;
var nameClass = this.state.hover ? "mx_MemberTile_nameSpan" : "mx_MemberTile_name";
if (zalgo.test(name)) {
nameClass += " mx_MemberTile_zalgo";
}
var nameEl;
if (this.state.hover) {
nameEl =
<div className="mx_MemberTile_nameWrapper">
<MemberInfo member={this.props.member} />
<span className={nameClass}>{name}</span>
{leave}
</div>
}
else {
nameEl =
<div className={nameClass}>
{name}
{leave}
</div>
}
return (
<div className={mainClassName} onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }>
<div className="mx_MemberTile_avatar">
<img className="mx_MemberTile_avatarImg"
src={ this.props.member ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.member, 40, 40, "crop") : null }
width="40" height="40" alt=""/>
{ power }
</div>
{ nameEl }
</div>
);
}
});

View File

@@ -1,87 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var classNames = require("classnames");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../src/ComponentBroker');
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
var SenderProfile = ComponentBroker.get('molecules/SenderProfile');
var UnknownMessageTile = ComponentBroker.get('molecules/UnknownMessageTile');
var tileTypes = {
'm.text': ComponentBroker.get('molecules/MTextTile'),
'm.notice': ComponentBroker.get('molecules/MNoticeTile'),
'm.emote': ComponentBroker.get('molecules/MEmoteTile'),
'm.image': ComponentBroker.get('molecules/MImageTile'),
'm.file': ComponentBroker.get('molecules/MFileTile')
};
var MessageTileController = require("../../../../src/controllers/molecules/MessageTile");
module.exports = React.createClass({
displayName: 'MessageTile',
mixins: [MessageTileController],
render: function() {
var content = this.props.mxEvent.getContent();
var msgtype = content.msgtype;
var TileType = UnknownMessageTile;
if (msgtype && tileTypes[msgtype]) {
TileType = tileTypes[msgtype];
}
var classes = classNames({
mx_MessageTile: true,
mx_MessageTile_sending: ['sending', 'queued'].indexOf(
this.props.mxEvent.status
) !== -1,
mx_MessageTile_notSent: this.props.mxEvent.status == 'not_sent',
mx_MessageTile_highlight: this.shouldHighlight(),
mx_MessageTile_continuation: this.props.continuation,
mx_MessageTile_last: this.props.last,
});
var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
var avatar, sender, resend;
if (!this.props.continuation) {
avatar = (
<div className="mx_MessageTile_avatar">
<img src={ this.props.mxEvent.sender ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.mxEvent.sender, 40, 40, "crop") : null } width="40" height="40" alt=""/>
</div>
);
sender = <SenderProfile mxEvent={this.props.mxEvent} />;
}
if (this.props.mxEvent.status === "not_sent" && !this.state.resending) {
resend = <button className="mx_MessageTile_msgOption" onClick={this.onResend}>
Resend
</button>;
}
return (
<div className={classes}>
{ avatar }
{ timestamp }
{ resend }
{ sender }
<TileType mxEvent={this.props.mxEvent} />
</div>
);
},
});

View File

@@ -1,50 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../../src/ComponentBroker');
var MCallAnswerTileController = require("../../../../../src/controllers/molecules/voip/MCallAnswerTile");
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
module.exports = React.createClass({
displayName: 'MCallAnswerTile',
mixins: [MCallAnswerTileController],
getAnswerText: function(event) {
var senderName = event.sender ? event.sender.name : "Someone";
return senderName + " answered the call.";
},
render: function() {
// XXX: for now, just cheekily borrow the css from message tile...
return (
<div className="mx_MessageTile mx_MessageTile_notice">
<div className="mx_MessageTile_avatar">
<img src={ this.props.mxEvent.sender ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.mxEvent.sender, 40, 40, "crop") : null } width="40" height="40" alt=""/>
</div>
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
<span className="mx_SenderProfile"></span>
<span className="mx_MessageTile_content">
{this.getAnswerText(this.props.mxEvent)}
</span>
</div>
);
},
});

View File

@@ -1,50 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../../src/ComponentBroker');
var MCallHangupTileController = require("../../../../../src/controllers/molecules/voip/MCallHangupTile");
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
module.exports = React.createClass({
displayName: 'MCallHangupTile',
mixins: [MCallHangupTileController],
getHangupText: function(event) {
var senderName = event.sender ? event.sender.name : "Someone";
return senderName + " ended the call.";
},
render: function() {
// XXX: for now, just cheekily borrow the css from message tile...
return (
<div className="mx_MessageTile mx_MessageTile_notice">
<div className="mx_MessageTile_avatar">
<img src={ this.props.mxEvent.sender ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.mxEvent.sender, 40, 40, "crop") : null } width="40" height="40" alt=""/>
</div>
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
<span className="mx_SenderProfile"></span>
<span className="mx_MessageTile_content">
{this.getHangupText(this.props.mxEvent)}
</span>
</div>
);
},
});

View File

@@ -1,56 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../../../../src/MatrixClientPeg");
var ComponentBroker = require('../../../../../src/ComponentBroker');
var MCallInviteTileController = require("../../../../../src/controllers/molecules/voip/MCallInviteTile");
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
module.exports = React.createClass({
displayName: 'MCallInviteTile',
mixins: [MCallInviteTileController],
getInviteText: function(event) {
var senderName = event.sender ? event.sender.name : "Someone";
// FIXME: Find a better way to determine this from the event?
var type = "voice";
if (event.getContent().offer &&
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
type = "video";
}
return senderName + " placed a " + type + " call.";
},
render: function() {
// XXX: for now, just cheekily borrow the css from message tile...
return (
<div className="mx_MessageTile mx_MessageTile_notice">
<div className="mx_MessageTile_avatar">
<img src={ this.props.mxEvent.sender ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.mxEvent.sender, 40, 40, "crop") : null } width="40" height="40" alt=""/>
</div>
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
<span className="mx_SenderProfile"></span>
<span className="mx_MessageTile_content">
{this.getInviteText(this.props.mxEvent)}
</span>
</div>
);
},
});

View File

@@ -1,41 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var RoomList = ComponentBroker.get('organisms/RoomList');
var BottomLeftMenu = ComponentBroker.get('molecules/BottomLeftMenu');
var IncomingCallBox = ComponentBroker.get('molecules/voip/IncomingCallBox');
var RoomCreate = ComponentBroker.get('molecules/RoomCreate');
module.exports = React.createClass({
displayName: 'LeftPanel',
render: function() {
return (
<div className="mx_LeftPanel">
<img className="mx_LeftPanel_hideButton" src="img/hide.png" width="32" height="32" alt="<"/>
<IncomingCallBox />
<RoomList selectedRoom={this.props.selectedRoom} />
<BottomLeftMenu />
</div>
);
}
});

View File

@@ -1,116 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var classNames = require('classnames');
var MemberListController = require("../../../../src/controllers/organisms/MemberList");
var ComponentBroker = require('../../../../src/ComponentBroker');
var MemberTile = ComponentBroker.get("molecules/MemberTile");
var EditableText = ComponentBroker.get("atoms/EditableText");
module.exports = React.createClass({
displayName: 'MemberList',
mixins: [MemberListController],
getInitialState: function() {
return { editing: false };
},
// FIXME: combine this more nicely with the MemberInfo positioning stuff...
onMemberListScroll: function(ev) {
if (this.refs.memberListScroll) {
var memberListScroll = this.refs.memberListScroll.getDOMNode();
// offset the current MemberInfo bubble
var memberInfo = document.getElementsByClassName("mx_MemberInfo")[0];
if (memberInfo) {
memberInfo.style.top = (memberInfo.parentElement.offsetTop - memberListScroll.scrollTop) + "px";
}
}
},
makeMemberTiles: function() {
var self = this;
return Object.keys(self.state.memberDict).map(function(userId) {
var m = self.state.memberDict[userId];
return (
<MemberTile key={userId} member={m} ref={userId} />
);
});
},
onPopulateInvite: function(inputText, shouldSubmit) {
// reset back to placeholder
this.refs.invite.setValue("Invite", false, true);
this.setState({ editing: false });
if (!shouldSubmit) {
return; // enter key wasn't pressed
}
this.onInvite(inputText);
},
onClickInvite: function(ev) {
this.setState({ editing: true });
this.refs.invite.onClickDiv();
ev.stopPropagation();
ev.preventDefault();
},
inviteTile: function() {
// if (this.state.inviting) {
// return (
// <div></div>
// );
// }
var classes = classNames({
mx_MemberTile: true,
mx_MemberTile_inviteEditing: this.state.editing,
});
return (
<div className={ classes } onClick={ this.onClickInvite } >
<div className="mx_MemberTile_avatar"><img src="img/create-big.png" width="40" height="40" alt=""/></div>
<div className="mx_MemberTile_name">
<EditableText ref="invite" label="Invite" placeHolder="@user:domain.com" initialValue="" onValueChanged={this.onPopulateInvite}/>
</div>
</div>
);
},
render: function() {
return (
<div className="mx_MemberList">
<div className="mx_MemberList_chevron">
<img src="img/chevron.png" width="24" height="13"/>
</div>
<div className="mx_MemberList_border" ref="memberListScroll" onScroll={ this.onMemberListScroll }>
<h2>Members</h2>
<div className="mx_MemberList_wrapper">
{this.makeMemberTiles()}
{this.inviteTile()}
</div>
</div>
</div>
);
}
});

53
src/Avatar.js Normal file
View File

@@ -0,0 +1,53 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
module.exports = {
avatarUrlForMember: function(member, width, height, resizeMethod) {
var url = member.getAvatarUrl(
MatrixClientPeg.get().getHomeserverUrl(),
width,
height,
resizeMethod
);
if (!url) {
// member can be null here currently since on invites, the JS SDK
// does not have enough info to build a RoomMember object for
// the inviter.
url = this.defaultAvatarUrlForString(member ? member.userId : '');
}
return url;
},
defaultAvatarUrlForString: function(s) {
var total = 0;
for (var i = 0; i < s.length; ++i) {
total += s.charCodeAt(i);
}
switch (total % 3) {
case 0:
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9QjNbxSKP4eagAFnTseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAABSwCRWJw31gAAAAASUVORK5CYII=";
case 1:
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9chOaxgCP4eagAFk9seHFErFYLBaLxWKxWCwWi8Vi8cX4CzAAtKMCks/JG8MAAAAASUVORK5CYII=";
case 2:
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9YzNayQCP4eagADldseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAAyiACeHwPiu4AAAAASUVORK5CYII=";
}
}
}

View File

@@ -1,230 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
/*
* Manages a list of all the currently active calls.
*
* This handler dispatches when voip calls are added/updated/removed from this list:
* {
* action: 'call_state'
* room_id: <room ID of the call>
* }
*
* To know the state of the call, this handler exposes a getter to
* obtain the call for a room:
* var call = CallHandler.getCall(roomId)
* var state = call.call_state; // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing
*
* This handler listens for and handles the following actions:
* {
* action: 'place_call',
* type: 'voice|video',
* room_id: <room that the place call button was pressed in>
* }
*
* {
* action: 'incoming_call'
* call: MatrixCall
* }
*
* {
* action: 'hangup'
* room_id: <room that the hangup button was pressed in>
* }
*
* {
* action: 'answer'
* room_id: <room that the answer button was pressed in>
* }
*/
var MatrixClientPeg = require("./MatrixClientPeg");
var Modal = require("./Modal");
var ComponentBroker = require('./ComponentBroker');
var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
var Matrix = require("matrix-js-sdk");
var dis = require("./dispatcher");
var calls = {
//room_id: MatrixCall
};
function play(audioId) {
// TODO: Attach an invisible element for this instead
// which listens?
var audio = document.getElementById(audioId);
if (audio) {
audio.load();
audio.play();
}
}
function pause(audioId) {
// TODO: Attach an invisible element for this instead
// which listens?
var audio = document.getElementById(audioId);
if (audio) {
audio.pause();
}
}
function _setCallListeners(call) {
call.on("error", function(err) {
console.error("Call error: %s", err);
console.error(err.stack);
call.hangup();
_setCallState(undefined, call.roomId, "ended");
});
call.on("hangup", function() {
_setCallState(undefined, call.roomId, "ended");
});
// map web rtc states to dummy UI state
// ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing
call.on("state", function(newState, oldState) {
if (newState === "ringing") {
_setCallState(call, call.roomId, "ringing");
pause("ringbackAudio");
}
else if (newState === "invite_sent") {
_setCallState(call, call.roomId, "ringback");
play("ringbackAudio");
}
else if (newState === "ended" && oldState === "connected") {
_setCallState(call, call.roomId, "ended");
pause("ringbackAudio");
play("callendAudio");
}
else if (newState === "ended" && oldState === "invite_sent" &&
(call.hangupParty === "remote" ||
(call.hangupParty === "local" && call.hangupReason === "invite_timeout")
)) {
_setCallState(call, call.roomId, "busy");
pause("ringbackAudio");
play("busyAudio");
Modal.createDialog(ErrorDialog, {
title: "Call Timeout",
description: "The remote side failed to pick up."
});
}
else if (oldState === "invite_sent") {
_setCallState(call, call.roomId, "stop_ringback");
pause("ringbackAudio");
}
else if (oldState === "ringing") {
_setCallState(call, call.roomId, "stop_ringing");
pause("ringbackAudio");
}
else if (newState === "connected") {
_setCallState(call, call.roomId, "connected");
pause("ringbackAudio");
}
});
}
function _setCallState(call, roomId, status) {
console.log(
"Call state in %s changed to %s (%s)", roomId, status, (call ? call.state : "-")
);
calls[roomId] = call;
if (call) {
call.call_state = status;
}
dis.dispatch({
action: 'call_state',
room_id: roomId
});
}
dis.register(function(payload) {
switch (payload.action) {
case 'place_call':
if (calls[payload.room_id]) {
return; // don't allow >1 call to be placed.
}
var room = MatrixClientPeg.get().getRoom(payload.room_id);
if (!room) {
console.error("Room %s does not exist.", payload.room_id);
return;
}
var members = room.getJoinedMembers();
if (members.length !== 2) {
var text = members.length === 1 ? "yourself." : "more than 2 people.";
Modal.createDialog(ErrorDialog, {
description: "You cannot place a call with " + text
});
console.error(
"Fail: There are %s joined members in this room, not 2.",
room.getJoinedMembers().length
);
return;
}
console.log("Place %s call in %s", payload.type, payload.room_id);
var call = Matrix.createNewMatrixCall(
MatrixClientPeg.get(), payload.room_id
);
_setCallListeners(call);
_setCallState(call, call.roomId, "ringback");
if (payload.type === 'voice') {
call.placeVoiceCall();
}
else if (payload.type === 'video') {
call.placeVideoCall(
payload.remote_element,
payload.local_element
);
}
else {
console.error("Unknown call type: %s", payload.type);
}
break;
case 'incoming_call':
if (calls[payload.call.roomId]) {
payload.call.hangup("busy");
return; // don't allow >1 call to be received, hangup newer one.
}
var call = payload.call;
_setCallListeners(call);
_setCallState(call, call.roomId, "ringing");
break;
case 'hangup':
if (!calls[payload.room_id]) {
return; // no call to hangup
}
calls[payload.room_id].hangup();
_setCallState(null, payload.room_id, "ended");
break;
case 'answer':
if (!calls[payload.room_id]) {
return; // no call to answer
}
calls[payload.room_id].answer();
_setCallState(calls[payload.room_id], payload.room_id, "connected");
dis.dispatch({
action: "view_room",
room_id: payload.room_id
});
break;
}
});
module.exports = {
getCall: function(roomId) {
return calls[roomId] || null;
}
};

View File

@@ -1,117 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
function load(name) {
var module = require("../skins/base/views/"+name);
return module;
};
var ComponentBroker = function() {
this.components = {};
};
ComponentBroker.prototype = {
get: function(name) {
if (this.components[name]) {
return this.components[name];
}
this.components[name] = load(name);
return this.components[name];
},
set: function(name, module) {
this.components[name] = module;
}
};
// We define one Component Broker globally, because the intention is
// very much that it is a singleton. Relying on there only being one
// copy of the module can be dicey and not work as browserify's
// behaviour with multiple copies of files etc. is erratic at best.
// XXX: We can still end up with the same file twice in the resulting
// JS bundle which is nonideal.
if (global.componentBroker === undefined) {
global.componentBroker = new ComponentBroker();
}
module.exports = global.componentBroker;
// We need to tell browserify to include all the components
// by direct require syntax in here, but we don't want them
// to be evaluated in this file because then we wouldn't be
// able to override them. if (0) does this.
// Must be in this file (because the require is file-specific) and
// must be at the end because the components include this file.
if (0) {
require('../skins/base/views/atoms/LogoutButton');
require('../skins/base/views/atoms/EnableNotificationsButton');
require('../skins/base/views/atoms/MessageTimestamp');
require('../skins/base/views/atoms/create_room/CreateRoomButton');
require('../skins/base/views/atoms/create_room/RoomAlias');
require('../skins/base/views/atoms/create_room/Presets');
require('../skins/base/views/atoms/EditableText');
require('../skins/base/views/molecules/MatrixToolbar');
require('../skins/base/views/molecules/RoomTile');
require('../skins/base/views/molecules/MessageTile');
require('../skins/base/views/molecules/SenderProfile');
require('../skins/base/views/molecules/UnknownMessageTile');
require('../skins/base/views/molecules/MTextTile');
require('../skins/base/views/molecules/MNoticeTile');
require('../skins/base/views/molecules/MEmoteTile');
require('../skins/base/views/molecules/MImageTile');
require('../skins/base/views/molecules/MFileTile');
require('../skins/base/views/molecules/MRoomMemberTile');
require('../skins/base/views/molecules/RoomHeader');
require('../skins/base/views/molecules/MessageComposer');
require('../skins/base/views/molecules/ProgressBar');
require('../skins/base/views/molecules/ServerConfig');
require('../skins/base/views/organisms/MemberList');
require('../skins/base/views/molecules/MemberTile');
require('../skins/base/views/organisms/RoomList');
require('../skins/base/views/organisms/RoomView');
require('../skins/base/views/templates/Login');
require('../skins/base/views/templates/Register');
require('../skins/base/views/organisms/Notifier');
require('../skins/base/views/organisms/CreateRoom');
require('../skins/base/views/molecules/UserSelector');
require('../skins/base/views/organisms/UserSettings');
require('../skins/base/views/molecules/ChangeAvatar');
require('../skins/base/views/molecules/ChangePassword');
require('../skins/base/views/molecules/RoomSettings');
// new for vector
require('../skins/base/views/organisms/LeftPanel');
require('../skins/base/views/organisms/RightPanel');
require('../skins/base/views/organisms/LogoutPrompt');
require('../skins/base/views/organisms/RoomDirectory');
require('../skins/base/views/molecules/RoomCreate');
require('../skins/base/views/molecules/RoomDropTarget');
require('../skins/base/views/molecules/BottomLeftMenu');
require('../skins/base/views/molecules/DateSeparator');
require('../skins/base/views/atoms/voip/VideoFeed');
require('../skins/base/views/atoms/ImageView');
require('../skins/base/views/molecules/voip/VideoView');
require('../skins/base/views/molecules/voip/CallView');
require('../skins/base/views/molecules/voip/IncomingCallBox');
require('../skins/base/views/molecules/voip/MCallInviteTile');
require('../skins/base/views/molecules/voip/MCallAnswerTile');
require('../skins/base/views/molecules/voip/MCallHangupTile');
require('../skins/base/views/molecules/EventAsTextTile');
require('../skins/base/views/molecules/MemberInfo');
require('../skins/base/views/organisms/ErrorDialog');
require('../skins/base/views/organisms/QuestionDialog');
}

View File

@@ -1,82 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var q = require('q');
var extend = require('./extend');
function infoForImageFile(imageFile) {
var deferred = q.defer();
// Load the file into an html element
var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {
img.src = e.target.result;
// Once ready, returns its size
img.onload = function() {
deferred.resolve({
w: img.width,
h: img.height
});
};
img.onerror = function(e) {
deferred.reject(e);
};
};
reader.onerror = function(e) {
deferred.reject(e);
};
reader.readAsDataURL(imageFile);
return deferred.promise;
}
function sendContentToRoom(file, roomId, matrixClient) {
var content = {
body: file.name,
info: {
size: file.size,
mimetype: file.type
}
};
var def = q.defer();
if (file.type.indexOf('image/') == 0) {
content.msgtype = 'm.image';
infoForImageFile(file).then(function(imageInfo) {
extend(content.info, imageInfo);
def.resolve();
});
} else {
content.msgtype = 'm.file';
def.resolve();
}
return def.promise.then(function() {
return matrixClient.uploadContent(file);
}).then(function(url) {
content.url = url;
return matrixClient.sendMessage(roomId, content);
});
}
module.exports = {
sendContentToRoom: sendContentToRoom
};

71
src/ContextualMenu.js Normal file
View File

@@ -0,0 +1,71 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
// Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
// pass in a custom control as the actual body.
module.exports = {
ContextualMenuContainerId: "mx_ContextualMenu_Container",
getOrCreateContainer: function() {
var container = document.getElementById(this.ContextualMenuContainerId);
if (!container) {
container = document.createElement("div");
container.id = this.ContextualMenuContainerId;
document.body.appendChild(container);
}
return container;
},
createMenu: function (Element, props) {
var self = this;
var closeMenu = function() {
React.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) props.onFinished.apply(null, arguments);
};
var position = {
top: props.top - 20,
right: props.right + 8,
};
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
// property set here so you can't close the menu from a button click!
var menu = (
<div className="mx_ContextualMenu_wrapper">
<div className="mx_ContextualMenu" style={position}>
<img className="mx_ContextualMenu_chevron" src="img/chevron-right.png" width="9" height="16" />
<Element {...props} onFinished={closeMenu}/>
</div>
<div className="mx_ContextualMenu_background" onClick={closeMenu}></div>
</div>
);
React.render(menu, this.getOrCreateContainer());
return {close: closeMenu};
},
};

View File

@@ -1,101 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
// A thing that holds your Matrix Client
var Matrix = require("matrix-js-sdk");
var matrixClient = null;
var localStorage = window.localStorage;
function deviceId() {
var id = Math.floor(Math.random()*16777215).toString(16);
id = "W" + "000000".substring(id.length) + id;
if (localStorage) {
id = localStorage.getItem("mx_device_id") || id;
localStorage.setItem("mx_device_id", id);
}
return id;
}
function createClient(hs_url, is_url, user_id, access_token) {
var opts = {
baseUrl: hs_url,
idBaseUrl: is_url,
accessToken: access_token,
userId: user_id
};
if (localStorage) {
opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage);
opts.deviceId = deviceId();
}
matrixClient = Matrix.createClient(opts);
}
if (localStorage) {
var hs_url = localStorage.getItem("mx_hs_url");
var is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
var access_token = localStorage.getItem("mx_access_token");
var user_id = localStorage.getItem("mx_user_id");
if (access_token && user_id && hs_url) {
createClient(hs_url, is_url, user_id, access_token);
}
}
module.exports = {
get: function() {
return matrixClient;
},
unset: function() {
matrixClient = null;
},
replaceUsingUrls: function(hs_url, is_url) {
matrixClient = Matrix.createClient({
baseUrl: hs_url,
idBaseUrl: is_url
});
},
replaceUsingAccessToken: function(hs_url, is_url, user_id, access_token) {
if (localStorage) {
try {
localStorage.clear();
} catch (e) {
console.warn("Error using local storage");
}
}
createClient(hs_url, is_url, user_id, access_token);
if (localStorage) {
try {
localStorage.setItem("mx_hs_url", hs_url);
localStorage.setItem("mx_is_url", is_url);
localStorage.setItem("mx_user_id", user_id);
localStorage.setItem("mx_access_token", access_token);
} catch (e) {
console.warn("Error using local storage: can't persist session!");
}
} else {
console.warn("No local storage available: can't persist session!");
}
}
};

View File

@@ -1,62 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var q = require('q');
module.exports = {
DialogContainerId: "mx_Dialog_Container",
getOrCreateContainer: function() {
var container = document.getElementById(this.DialogContainerId);
if (!container) {
container = document.createElement("div");
container.id = this.DialogContainerId;
document.body.appendChild(container);
}
return container;
},
createDialog: function (Element, props) {
var self = this;
var closeDialog = function() {
React.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) props.onFinished.apply(null, arguments);
};
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
// property set here so you can't close the dialog from a button click!
var dialog = (
<div className="mx_Dialog_Wrapper">
<div className="mx_Dialog">
<Element {...props} onFinished={closeDialog}/>
</div>
<div className="mx_Dialog_Background" onClick={closeDialog}></div>
</div>
);
React.render(dialog, this.getOrCreateContainer());
return {close: closeDialog};
},
};

View File

@@ -1,107 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
var MatrixClientPeg = require("./MatrixClientPeg");
// Time in ms after that a user is considered as unavailable/away
var UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
var PRESENCE_STATES = ["online", "offline", "unavailable"];
// The current presence state
var state, timer;
module.exports = {
/**
* Start listening the user activity to evaluate his presence state.
* Any state change will be sent to the Home Server.
*/
start: function() {
var self = this;
this.running = true;
if (undefined === state) {
// The user is online if they move the mouse or press a key
document.onmousemove = function() { self._resetTimer(); };
document.onkeypress = function() { self._resetTimer(); };
this._resetTimer();
}
},
/**
* Stop tracking user activity
*/
stop: function() {
this.running = false;
if (timer) {
clearTimeout(timer);
timer = undefined;
}
state = undefined;
},
/**
* Get the current presence state.
* @returns {string} the presence state (see PRESENCE enum)
*/
getState: function() {
return state;
},
/**
* Set the presence state.
* If the state has changed, the Home Server will be notified.
* @param {string} newState the new presence state (see PRESENCE enum)
*/
setState: function(newState) {
if (newState === state) {
return;
}
if (PRESENCE_STATES.indexOf(newState) === -1) {
throw new Error("Bad presence state: " + newState);
}
if (!this.running) {
return;
}
state = newState;
MatrixClientPeg.get().setPresence(state).done(function() {
console.log("Presence: %s", newState);
}, function(err) {
console.error("Failed to set presence: %s", err);
});
},
/**
* Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
* @private
*/
_onUnavailableTimerFire: function() {
this.setState("unavailable");
},
/**
* Callback called when the user made an action on the page
* @private
*/
_resetTimer: function() {
var self = this;
this.setState("online");
// Re-arm the timer
clearTimeout(timer);
timer = setTimeout(function() {
self._onUnavailableTimerFire();
}, UNAVAILABLE_TIME_MS);
}
};

View File

@@ -1,36 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
function tsOfNewestEvent(room) {
if (room.timeline.length) {
return room.timeline[room.timeline.length - 1].getTs();
}
else {
return Number.MAX_SAFE_INTEGER;
}
}
function mostRecentActivityFirst(roomList) {
return roomList.sort(function(a,b) {
return tsOfNewestEvent(b) - tsOfNewestEvent(a);
});
}
module.exports = {
mostRecentActivityFirst: mostRecentActivityFirst
};

View File

@@ -1,253 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var MatrixClientPeg = require("./MatrixClientPeg");
var dis = require("./dispatcher");
var encryption = require("./encryption");
var reject = function(msg) {
return {
error: msg
};
};
var success = function(promise) {
return {
promise: promise
};
};
var commands = {
// Change your nickname
nick: function(room_id, args) {
if (args) {
return success(
MatrixClientPeg.get().setDisplayName(args)
);
}
return reject("Usage: /nick <display_name>");
},
encrypt: function(room_id, args) {
if (args == "on") {
var client = MatrixClientPeg.get();
var members = client.getRoom(room_id).currentState.members;
var user_ids = Object.keys(members);
return success(
encryption.enableEncryption(client, room_id, user_ids)
);
}
if (args == "off") {
var client = MatrixClientPeg.get();
return success(
encryption.disableEncryption(client, room_id)
);
}
return reject("Usage: encrypt <on/off>");
},
// Change the room topic
topic: function(room_id, args) {
if (args) {
return success(
MatrixClientPeg.get().setRoomTopic(room_id, args)
);
}
return reject("Usage: /topic <topic>");
},
// Invite a user
invite: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
return success(
MatrixClientPeg.get().invite(room_id, matches[1])
);
}
}
return reject("Usage: /invite <userId>");
},
// Join a room
join: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var room_alias = matches[1];
// Try to find a room with this alias
var rooms = MatrixClientPeg.get().getRooms();
var roomId;
for (var i = 0; i < rooms.length; i++) {
var aliasEvents = rooms[i].currentState.getStateEvents(
"m.room.aliases"
);
for (var j = 0; j < aliasEvents.length; j++) {
var aliases = aliasEvents[j].getContent().aliases || [];
for (var k = 0; k < aliases.length; k++) {
if (aliases[k] === room_alias) {
roomId = rooms[i].roomId;
break;
}
}
if (roomId) { break; }
}
if (roomId) { break; }
}
if (roomId) { // we've already joined this room, view it.
dis.dispatch({
action: 'view_room',
room_id: roomId
});
return success();
}
else {
// attempt to join this alias.
return success(
MatrixClientPeg.get().joinRoom(room_alias).then(
function(room) {
dis.dispatch({
action: 'view_room',
room_id: room.roomId
});
})
);
}
}
}
return reject("Usage: /join <room_alias>");
},
// Kick a user from the room with an optional reason
kick: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return success(
MatrixClientPeg.get().kick(room_id, matches[1], matches[3])
);
}
}
return reject("Usage: /kick <userId> [<reason>]");
},
// Ban a user from the room with an optional reason
ban: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return success(
MatrixClientPeg.get().ban(room_id, matches[1], matches[3])
);
}
}
return reject("Usage: /ban <userId> [<reason>]");
},
// Unban a user from the room
unban: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
// Reset the user membership to "leave" to unban him
return success(
MatrixClientPeg.get().unban(room_id, matches[1])
);
}
}
return reject("Usage: /unban <userId>");
},
// Define the power level of a user
op: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(\d+))?$/);
var powerLevel = 50; // default power level for op
if (matches) {
var user_id = matches[1];
if (matches.length === 4 && undefined !== matches[3]) {
powerLevel = parseInt(matches[3]);
}
if (powerLevel !== NaN) {
var room = MatrixClientPeg.get().getRoom(room_id);
if (!room) {
return reject("Bad room ID: " + room_id);
}
var powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", ""
);
return success(
MatrixClientPeg.get().setPowerLevel(
room_id, user_id, powerLevel, powerLevelEvent
)
);
}
}
}
return reject("Usage: /op <userId> [<power level>]");
},
// Reset the power level of a user
deop: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var room = MatrixClientPeg.get().getRoom(room_id);
if (!room) {
return reject("Bad room ID: " + room_id);
}
var powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", ""
);
return success(
MatrixClientPeg.get().setPowerLevel(
room_id, args, undefined, powerLevelEvent
)
);
}
}
return reject("Usage: /deop <userId>");
}
};
module.exports = {
/**
* Process the given text for /commands and perform them.
* @param {string} roomId The room in which the command was performed.
* @param {string} input The raw text input by the user.
* @return {Object|null} An object with the property 'error' if there was an error
* processing the command, or 'promise' if a request was sent out.
* Returns null if the input didn't match a command.
*/
processInput: function(roomId, input) {
// trim any trailing whitespace, as it can confuse the parser for
// IRC-style commands
input = input.replace(/\s+$/, "");
if (input[0] === "/" && input[1] !== "/") {
var bits = input.match(/^(\S+?)( +(.*))?$/);
var cmd = bits[1].substring(1).toLowerCase();
var args = bits[3];
if (commands[cmd]) {
return commands[cmd](roomId, args);
}
}
return null; // not a command
}
};

View File

@@ -1,82 +0,0 @@
function textForMemberEvent(ev) {
// XXX: SYJS-16
var senderName = ev.sender ? ev.sender.name : ev.getSender();
var targetName = ev.target ? ev.target.name : ev.getStateKey();
var reason = ev.getContent().reason ? (
" Reason: " + ev.getContent().reason
) : "";
switch (ev.getContent().membership) {
case 'invite':
return senderName + " invited " + targetName + ".";
case 'ban':
return senderName + " banned " + targetName + "." + reason;
case 'join':
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
return ev.getSender() + " changed their display name from " +
ev.getPrevContent().displayname + " to " +
ev.getContent().displayname;
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
return ev.getSender() + " set their display name to " + ev.getContent().displayname;
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) {
return ev.getSender() + " removed their display name";
} else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) {
return ev.getSender() + " removed their profile picture";
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) {
return ev.getSender() + " changed their profile picture";
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
return ev.getSender() + " set a profile picture";
}
} else {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
return targetName + " joined the room.";
}
return '';
case 'leave':
if (ev.getSender() === ev.getStateKey()) {
return targetName + " left the room.";
}
else if (ev.getPrevContent().membership === "ban") {
return senderName + " unbanned " + targetName + ".";
}
else if (ev.getPrevContent().membership === "join") {
return senderName + " kicked " + targetName + "." + reason;
}
else {
return targetName + " left the room.";
}
}
};
function textForTopicEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return senderDisplayName + ' changed the topic to, "' + ev.getContent().topic + '"';
};
function textForMessageEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
var message = senderDisplayName + ': ' + ev.getContent().body;
if (ev.getContent().msgtype === "m.emote") {
message = "* " + senderDisplayName + " " + message;
} else if (ev.getContent().msgtype === "m.image") {
message = senderDisplayName + " sent an image.";
}
return message;
};
var handlers = {
'm.room.message': textForMessageEvent,
'm.room.topic': textForTopicEvent,
'm.room.member': textForMemberEvent
};
module.exports = {
textForEvent: function(ev) {
var hdlr = handlers[ev.getType()];
if (!hdlr) return "";
return hdlr(ev);
}
}

View File

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

View File

@@ -1,88 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
module.exports = {
propTypes: {
onValueChanged: React.PropTypes.func,
initialValue: React.PropTypes.string,
label: React.PropTypes.string,
placeHolder: React.PropTypes.string,
},
Phases: {
Display: "display",
Edit: "edit",
},
getDefaultProps: function() {
return {
onValueChanged: function() {},
initialValue: '',
label: 'Click to set',
placeholder: '',
};
},
getInitialState: function() {
return {
value: this.props.initialValue,
phase: this.Phases.Display,
}
},
componentWillReceiveProps: function(nextProps) {
this.setState({
value: nextProps.initialValue
});
},
getValue: function() {
return this.state.value;
},
setValue: function(val, shouldSubmit, suppressListener) {
var self = this;
this.setState({
value: val,
phase: this.Phases.Display,
}, function() {
if (!suppressListener) {
self.onValueChanged(shouldSubmit);
}
});
},
edit: function() {
this.setState({
phase: this.Phases.Edit,
});
},
cancelEdit: function() {
this.setState({
phase: this.Phases.Display,
});
this.onValueChanged(false);
},
onValueChanged: function(shouldSubmit) {
this.props.onValueChanged(this.state.value, shouldSubmit);
},
};

View File

@@ -1,57 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var ComponentBroker = require("../../ComponentBroker");
var Notifier = ComponentBroker.get('organisms/Notifier');
var dis = require("../../dispatcher");
module.exports = {
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onAction: function(payload) {
if (payload.action !== "notifier_enabled") {
return;
}
this.forceUpdate();
},
enabled: function() {
return Notifier.isEnabled();
},
onClick: function() {
var self = this;
if (!Notifier.supportsDesktopNotifications()) {
return;
}
if (!Notifier.isEnabled()) {
Notifier.setEnabled(true, function() {
self.forceUpdate();
});
} else {
Notifier.setEnabled(false);
}
this.forceUpdate();
},
};

View File

@@ -1,27 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var dis = require("../../dispatcher");
module.exports = {
onClick: function() {
dis.dispatch({
action: 'logout'
});
},
};

View File

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

View File

@@ -1,35 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
module.exports = {
propTypes: {
onCreateRoom: React.PropTypes.func,
},
getDefaultProps: function() {
return {
onCreateRoom: function() {},
};
},
onClick: function() {
this.props.onCreateRoom();
},
};

View File

@@ -1,40 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var Presets = {
PrivateChat: "private_chat",
PublicChat: "public_chat",
Custom: "custom",
};
module.exports = {
propTypes: {
onChange: React.PropTypes.func,
preset: React.PropTypes.string
},
Presets: Presets,
getDefaultProps: function() {
return {
onChange: function() {},
};
},
};

View File

@@ -1,49 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
module.exports = {
propTypes: {
// Specifying a homeserver will make magical things happen when you,
// e.g. start typing in the room alias box.
homeserver: React.PropTypes.string,
alias: React.PropTypes.string,
onChange: React.PropTypes.func,
},
getDefaultProps: function() {
return {
onChange: function() {},
alias: '',
};
},
getAliasLocalpart: function() {
var room_alias = this.props.alias;
if (room_alias && this.props.homeserver) {
var suffix = ":" + this.props.homeserver;
if (room_alias.startsWith("#") && room_alias.endsWith(suffix)) {
room_alias = room_alias.slice(1, -suffix.length);
}
}
return room_alias;
},
};

View File

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

View File

@@ -1,71 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
module.exports = {
propTypes: {
onFinished: React.PropTypes.func,
initialAvatarUrl: React.PropTypes.string.isRequired,
},
Phases: {
Display: "display",
Uploading: "uploading",
Error: "error",
},
getDefaultProps: function() {
return {
onFinished: function() {},
};
},
getInitialState: function() {
return {
avatarUrl: this.props.initialAvatarUrl,
phase: this.Phases.Display,
}
},
setAvatarFromFile: function(file) {
var newUrl = null;
this.setState({
phase: this.Phases.Uploading
});
var self = this;
MatrixClientPeg.get().uploadContent(file).then(function(url) {
newUrl = url;
return MatrixClientPeg.get().setAvatarUrl(url);
}).done(function() {
self.setState({
phase: self.Phases.Display,
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl)
});
}, function(error) {
self.setState({
phase: this.Phases.Error
});
self.onError(error);
});
},
}

View File

@@ -1,78 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
module.exports = {
propTypes: {
onFinished: React.PropTypes.func,
},
Phases: {
Edit: "edit",
Uploading: "uploading",
Error: "error",
Success: "Success"
},
getDefaultProps: function() {
return {
onFinished: function() {},
};
},
getInitialState: function() {
return {
phase: this.Phases.Edit,
errorString: ''
}
},
changePassword: function(old_password, new_password) {
var cli = MatrixClientPeg.get();
var authDict = {
type: 'm.login.password',
user: cli.credentials.userId,
password: old_password
};
this.setState({
phase: this.Phases.Uploading,
errorString: '',
})
var d = cli.setPassword(authDict, new_password);
var self = this;
d.then(function() {
self.setState({
phase: self.Phases.Success,
errorString: '',
})
}, function(err) {
self.setState({
phase: self.Phases.Error,
errorString: err.toString()
})
});
},
}

View File

@@ -1,30 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var linkify = require('linkifyjs');
var linkifyElement = require('linkifyjs/element');
var linkifyMatrix = require('../../linkify-matrix');
linkifyMatrix(linkify);
module.exports = {
componentDidMount: function() {
linkifyElement(this.refs.content.getDOMNode(), linkifyMatrix.options);
}
};

View File

@@ -1,44 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var filesize = require('filesize');
module.exports = {
presentableTextForFile: function(content) {
var linkText = 'Attachment';
if (content.body && content.body.length > 0) {
linkText = content.body;
}
var additionals = [];
if (content.info) {
if (content.info.mimetype && content.info.mimetype.length > 0) {
additionals.push(content.info.mimetype);
}
if (content.info.size) {
additionals.push(filesize(content.info.size));
}
}
if (additionals.length > 0) {
linkText += ' (' + additionals.join(', ') + ')';
}
return linkText;
}
};

View File

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

View File

@@ -1,28 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var linkify = require('linkifyjs');
var linkifyElement = require('linkifyjs/element');
var linkifyMatrix = require('../../linkify-matrix.js');
linkifyMatrix(linkify);
module.exports = {
componentDidMount: function() {
linkifyElement(this.refs.content.getDOMNode(), linkifyMatrix.options);
}
};

View File

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

View File

@@ -1,30 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var linkify = require('linkifyjs');
var linkifyElement = require('linkifyjs/element');
var linkifyMatrix = require('../../linkify-matrix');
linkifyMatrix(linkify);
module.exports = {
componentDidMount: function() {
linkifyElement(this.refs.content.getDOMNode(), linkifyMatrix.options);
}
};

View File

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

View File

@@ -1,321 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* State vars:
* 'presence' : string (online|offline|unavailable etc)
* 'active' : number (ms ago; can be -1)
* 'can': {
* kick: boolean,
* ban: boolean,
* mute: boolean,
* modifyLevel: boolean
* },
* 'muted': boolean,
* 'isTargetMod': boolean
*/
'use strict';
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
var Modal = require("../../Modal");
var ComponentBroker = require('../../ComponentBroker');
var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
module.exports = {
componentDidMount: function() {
var self = this;
// listen for presence changes
function updateUserState(event, user) {
if (!self.props.member) { return; }
if (user.userId === self.props.member.userId) {
self.setState({
presence: user.presence,
active: user.lastActiveAgo
});
}
}
MatrixClientPeg.get().on("User.presence", updateUserState);
this.userPresenceFn = updateUserState;
// listen for power level changes
function updatePowerLevel(event, member) {
if (!self.props.member) { return; }
if (member.roomId !== self.props.member.roomId) {
return;
}
// only interested in changes to us or them
var myUserId = MatrixClientPeg.get().credentials.userId;
if ([myUserId, self.props.member.userId].indexOf(member.userId) === -1) {
return;
}
self.setState(self._calculateOpsPermissions());
}
MatrixClientPeg.get().on("RoomMember.powerLevel", updatePowerLevel);
this.updatePowerLevelFn = updatePowerLevel;
// work out the current state
if (this.props.member) {
var usr = MatrixClientPeg.get().getUser(this.props.member.userId) || {};
var memberState = this._calculateOpsPermissions();
memberState.presence = usr.presence || "offline";
memberState.active = usr.lastActiveAgo || -1;
this.setState(memberState);
}
},
componentWillUnmount: function() {
MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
MatrixClientPeg.get().removeListener(
"RoomMember.powerLevel", this.updatePowerLevelFn
);
},
onKick: function() {
var roomId = this.props.member.roomId;
var target = this.props.member.userId;
var self = this;
MatrixClientPeg.get().kick(roomId, target).done(function() {
// NO-OP; rely on the m.room.member event coming down else we could
// get out of sync if we force setState here!
console.log("Kick success");
}, function(err) {
Modal.createDialog(ErrorDialog, {
title: "Kick error",
description: err.message
});
});
},
onBan: function() {
var roomId = this.props.member.roomId;
var target = this.props.member.userId;
var self = this;
MatrixClientPeg.get().ban(roomId, target).done(function() {
// NO-OP; rely on the m.room.member event coming down else we could
// get out of sync if we force setState here!
console.log("Ban success");
}, function(err) {
Modal.createDialog(ErrorDialog, {
title: "Ban error",
description: err.message
});
});
},
onMuteToggle: function() {
var roomId = this.props.member.roomId;
var target = this.props.member.userId;
var self = this;
var room = MatrixClientPeg.get().getRoom(roomId);
if (!room) {
return;
}
var powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", ""
);
if (!powerLevelEvent) {
return;
}
var isMuted = this.state.muted;
var powerLevels = powerLevelEvent.getContent();
var levelToSend = (
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
powerLevels.events_default
);
var level;
if (isMuted) { // unmute
level = levelToSend;
}
else { // mute
level = levelToSend - 1;
}
MatrixClientPeg.get().setPowerLevel(roomId, target, level, powerLevelEvent).done(
function() {
// NO-OP; rely on the m.room.member event coming down else we could
// get out of sync if we force setState here!
console.log("Mute toggle success");
}, function(err) {
Modal.createDialog(ErrorDialog, {
title: "Mute error",
description: err.message
});
});
},
onModToggle: function() {
var roomId = this.props.member.roomId;
var target = this.props.member.userId;
var room = MatrixClientPeg.get().getRoom(roomId);
if (!room) {
return;
}
var powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", ""
);
if (!powerLevelEvent) {
return;
}
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (!me) {
return;
}
var defaultLevel = powerLevelEvent.getContent().users_default;
var modLevel = me.powerLevel - 1;
// toggle the level
var newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
MatrixClientPeg.get().setPowerLevel(roomId, target, newLevel, powerLevelEvent).done(
function() {
// NO-OP; rely on the m.room.member event coming down else we could
// get out of sync if we force setState here!
console.log("Mod toggle success");
}, function(err) {
Modal.createDialog(ErrorDialog, {
title: "Mod error",
description: err.message
});
});
},
onChatClick: function() {
// check if there are any existing rooms with just us and them (1:1)
// If so, just view that room. If not, create a private room with them.
var rooms = MatrixClientPeg.get().getRooms();
var userIds = [
this.props.member.userId,
MatrixClientPeg.get().credentials.userId
];
var existingRoomId = null;
for (var i = 0; i < rooms.length; i++) {
var members = rooms[i].getJoinedMembers();
if (members.length === 2) {
var hasTargetUsers = true;
for (var j = 0; j < members.length; j++) {
if (userIds.indexOf(members[j].userId) === -1) {
hasTargetUsers = false;
break;
}
}
if (hasTargetUsers) {
existingRoomId = rooms[i].roomId;
break;
}
}
}
if (existingRoomId) {
dis.dispatch({
action: 'view_room',
room_id: existingRoomId
});
}
else {
MatrixClientPeg.get().createRoom({
invite: [this.props.member.userId],
preset: "private_chat"
}).done(function(res) {
dis.dispatch({
action: 'view_room',
room_id: res.room_id
});
}, function(err) {
console.error(
"Failed to create room: %s", JSON.stringify(err)
);
});
}
},
getInitialState: function() {
return {
presence: "offline",
active: -1,
can: {
kick: false,
ban: false,
mute: false,
modifyLevel: false
},
muted: false,
isTargetMod: false
}
},
_calculateOpsPermissions: function() {
var defaultPerms = {
can: {},
muted: false,
modifyLevel: false
};
var room = MatrixClientPeg.get().getRoom(this.props.member.roomId);
if (!room) {
return defaultPerms;
}
var powerLevels = room.currentState.getStateEvents(
"m.room.power_levels", ""
);
if (!powerLevels) {
return defaultPerms;
}
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
var them = this.props.member;
return {
can: this._calculateCanPermissions(
me, them, powerLevels.getContent()
),
muted: this._isMuted(them, powerLevels.getContent()),
isTargetMod: them.powerLevel > powerLevels.getContent().users_default
};
},
_calculateCanPermissions: function(me, them, powerLevels) {
var can = {
kick: false,
ban: false,
mute: false,
modifyLevel: false
};
var canAffectUser = them.powerLevel < me.powerLevel;
if (!canAffectUser) {
console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
return can;
}
var editPowerLevel = (
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
powerLevels.state_default
);
can.kick = me.powerLevel >= powerLevels.kick;
can.ban = me.powerLevel >= powerLevels.ban;
can.mute = me.powerLevel >= editPowerLevel;
can.modifyLevel = me.powerLevel > them.powerLevel;
return can;
},
_isMuted: function(member, powerLevelContent) {
if (!powerLevelContent || !member) {
return false;
}
var levelToSend = (
(powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) ||
powerLevelContent.events_default
);
return member.powerLevel < levelToSend;
}
};

View File

@@ -1,60 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var dis = require("../../dispatcher");
var Modal = require("../../Modal");
var ComponentBroker = require('../../ComponentBroker');
var QuestionDialog = ComponentBroker.get("organisms/QuestionDialog");
var Loader = require("react-loader");
var MatrixClientPeg = require("../../MatrixClientPeg");
module.exports = {
onClick: function() {
dis.dispatch({
action: 'view_user',
user_id: this.props.member.userId
});
},
onLeaveClick: function() {
var roomId = this.props.member.roomId;
Modal.createDialog(QuestionDialog, {
title: "Leave room",
description: "Are you sure you want to leave the room?",
onFinished: function(should_leave) {
if (should_leave) {
var d = MatrixClientPeg.get().leave(roomId);
var modal = Modal.createDialog(Loader);
d.then(function() {
modal.close();
dis.dispatch({action: 'view_next_room'});
}, function(err) {
modal.close();
Modal.createDialog(ErrorDialog, {
title: "Failed to leave room",
description: err.toString()
});
});
}
}
});
}
};

View File

@@ -1,414 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var MatrixClientPeg = require("../../MatrixClientPeg");
var SlashCommands = require("../../SlashCommands");
var Modal = require("../../Modal");
var ComponentBroker = require('../../ComponentBroker');
var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
var dis = require("../../dispatcher");
var KeyCode = {
ENTER: 13,
TAB: 9,
SHIFT: 16,
UP: 38,
DOWN: 40
};
var TYPING_USER_TIMEOUT = 10000;
var TYPING_SERVER_TIMEOUT = 30000;
module.exports = {
componentWillMount: function() {
this.tabStruct = {
completing: false,
original: null,
index: 0
};
this.sentHistory = {
// The list of typed messages. Index 0 is more recent
data: [],
// The position in data currently displayed
position: -1,
// The room the history is for.
roomId: null,
// The original text before they hit UP
originalText: null,
// The textarea element to set text to.
element: null,
init: function(element, roomId) {
this.roomId = roomId;
this.element = element;
this.position = -1;
var storedData = window.sessionStorage.getItem(
"history_" + roomId
);
if (storedData) {
this.data = JSON.parse(storedData);
}
if (this.roomId) {
this.setLastTextEntry();
}
},
push: function(text) {
// store a message in the sent history
this.data.unshift(text);
window.sessionStorage.setItem(
"history_" + this.roomId,
JSON.stringify(this.data)
);
// reset history position
this.position = -1;
this.originalText = null;
},
// move in the history. Returns true if we managed to move.
next: function(offset) {
if (this.position === -1) {
// user is going into the history, save the current line.
this.originalText = this.element.value;
}
else {
// user may have modified this line in the history; remember it.
this.data[this.position] = this.element.value;
}
if (offset > 0 && this.position === (this.data.length - 1)) {
// we've run out of history
return false;
}
// retrieve the next item (bounded).
var newPosition = this.position + offset;
newPosition = Math.max(-1, newPosition);
newPosition = Math.min(newPosition, this.data.length - 1);
this.position = newPosition;
if (this.position !== -1) {
// show the message
this.element.value = this.data[this.position];
}
else if (this.originalText !== undefined) {
// restore the original text the user was typing.
this.element.value = this.originalText;
}
return true;
},
saveLastTextEntry: function() {
// save the currently entered text in order to restore it later.
// NB: This isn't 'originalText' because we want to restore
// sent history items too!
var text = this.element.value;
window.sessionStorage.setItem("input_" + this.roomId, text);
},
setLastTextEntry: function() {
var text = window.sessionStorage.getItem("input_" + this.roomId);
if (text) {
this.element.value = text;
}
}
};
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this.sentHistory.init(
this.refs.textarea.getDOMNode(),
this.props.room.roomId
);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
this.sentHistory.saveLastTextEntry();
},
onAction: function(payload) {
switch (payload.action) {
case 'focus_composer':
this.refs.textarea.getDOMNode().focus();
break;
}
},
onKeyDown: function (ev) {
if (ev.keyCode === KeyCode.ENTER) {
var input = this.refs.textarea.getDOMNode().value;
if (input.length === 0) {
ev.preventDefault();
return;
}
this.sentHistory.push(input);
this.onEnter(ev);
}
else if (ev.keyCode === KeyCode.TAB) {
var members = [];
if (this.props.room) {
members = this.props.room.getJoinedMembers();
}
this.onTab(ev, members);
}
else if (ev.keyCode === KeyCode.UP || ev.keyCode === KeyCode.DOWN) {
this.sentHistory.next(
ev.keyCode === KeyCode.UP ? 1 : -1
);
ev.preventDefault();
}
else if (ev.keyCode !== KeyCode.SHIFT && this.tabStruct.completing) {
// they're resuming typing; reset tab complete state vars.
this.tabStruct.completing = false;
this.tabStruct.index = 0;
}
var self = this;
setTimeout(function() {
if (self.refs.textarea.getDOMNode().value != '') {
self.onTypingActivity();
} else {
self.onFinishedTyping();
}
}, 10);
},
onEnter: function(ev) {
var contentText = this.refs.textarea.getDOMNode().value;
var cmd = SlashCommands.processInput(this.props.room.roomId, contentText);
if (cmd) {
ev.preventDefault();
if (!cmd.error) {
this.refs.textarea.getDOMNode().value = '';
}
if (cmd.promise) {
cmd.promise.done(function() {
console.log("Command success.");
}, function(err) {
console.error("Command failure: %s", err);
Modal.createDialog(ErrorDialog, {
title: "Server error",
description: err.message
});
});
}
else if (cmd.error) {
console.error(cmd.error);
Modal.createDialog(ErrorDialog, {
title: "Command error",
description: cmd.error
});
}
return;
}
var content = null;
if (/^\/me /i.test(contentText)) {
content = {
msgtype: 'm.emote',
body: contentText.substring(4)
};
} else {
content = {
msgtype: 'm.text',
body: contentText
};
}
MatrixClientPeg.get().sendMessage(this.props.room.roomId, content).then(function() {
dis.dispatch({
action: 'message_sent'
});
}, function() {
dis.dispatch({
action: 'message_send_failed'
});
});
this.refs.textarea.getDOMNode().value = '';
ev.preventDefault();
},
onTab: function(ev, sortedMembers) {
var textArea = this.refs.textarea.getDOMNode();
if (!this.tabStruct.completing) {
this.tabStruct.completing = true;
this.tabStruct.index = 0;
// cache starting text
this.tabStruct.original = textArea.value;
}
// loop in the right direction
if (ev.shiftKey) {
this.tabStruct.index --;
if (this.tabStruct.index < 0) {
// wrap to the last search match, and fix up to a real index
// value after we've matched.
this.tabStruct.index = Number.MAX_VALUE;
}
}
else {
this.tabStruct.index++;
}
var searchIndex = 0;
var targetIndex = this.tabStruct.index;
var text = this.tabStruct.original;
var search = /@?([a-zA-Z0-9_\-:\.]+)$/.exec(text);
// console.log("Searched in '%s' - got %s", text, search);
if (targetIndex === 0) { // 0 is always the original text
textArea.value = text;
}
else if (search && search[1]) {
// console.log("search found: " + search+" from "+text);
var expansion;
// FIXME: could do better than linear search here
for (var i=0; i<sortedMembers.length; i++) {
var member = sortedMembers[i];
if (member.name && searchIndex < targetIndex) {
if (member.name.toLowerCase().indexOf(search[1].toLowerCase()) === 0) {
expansion = member.name;
searchIndex++;
}
}
}
if (searchIndex < targetIndex) { // then search raw mxids
for (var i=0; i<sortedMembers.length; i++) {
if (searchIndex >= targetIndex) {
break;
}
var userId = sortedMembers[i].userId;
// === 1 because mxids are @username
if (userId.toLowerCase().indexOf(search[1].toLowerCase()) === 1) {
expansion = userId;
searchIndex++;
}
}
}
if (searchIndex === targetIndex ||
targetIndex === Number.MAX_VALUE) {
// xchat-style tab complete, add a colon if tab
// completing at the start of the text
if (search[0].length === text.length) {
expansion += ": ";
}
else {
expansion += " ";
}
textArea.value = text.replace(
/@?([a-zA-Z0-9_\-:\.]+)$/, expansion
);
// cancel blink
textArea.style["background-color"] = "";
if (targetIndex === Number.MAX_VALUE) {
// wrap the index around to the last index found
this.tabStruct.index = searchIndex;
targetIndex = searchIndex;
}
}
else {
// console.log("wrapped!");
textArea.style["background-color"] = "#faa";
setTimeout(function() {
textArea.style["background-color"] = "";
}, 150);
textArea.value = text;
this.tabStruct.index = 0;
}
}
else {
this.tabStruct.index = 0;
}
// prevent the default TAB operation (typically focus shifting)
ev.preventDefault();
},
onTypingActivity: function() {
this.isTyping = true;
if (!this.userTypingTimer) {
this.sendTyping(true);
}
this.startUserTypingTimer();
this.startServerTypingTimer();
},
onFinishedTyping: function() {
this.isTyping = false;
this.sendTyping(false);
this.stopUserTypingTimer();
this.stopServerTypingTimer();
},
startUserTypingTimer: function() {
this.stopUserTypingTimer();
var self = this;
this.userTypingTimer = setTimeout(function() {
self.isTyping = false;
self.sendTyping(self.isTyping);
self.userTypingTimer = null;
}, TYPING_USER_TIMEOUT);
},
stopUserTypingTimer: function() {
if (this.userTypingTimer) {
clearTimeout(this.userTypingTimer);
this.userTypingTimer = null;
}
},
startServerTypingTimer: function() {
if (!this.serverTypingTimer) {
var self = this;
this.serverTypingTimer = setTimeout(function() {
if (self.isTyping) {
self.sendTyping(self.isTyping);
self.startServerTypingTimer();
}
}, TYPING_SERVER_TIMEOUT / 2);
}
},
stopServerTypingTimer: function() {
if (this.serverTypingTimer) {
clearTimeout(this.servrTypingTimer);
this.serverTypingTimer = null;
}
},
sendTyping: function(isTyping) {
MatrixClientPeg.get().sendTyping(
this.props.room.roomId,
this.isTyping, TYPING_SERVER_TIMEOUT
).done();
},
refreshTyping: function() {
if (this.typingTimeout) {
clearTimeout(this.typingTimeout);
this.typingTimeout = null;
}
}
};

View File

@@ -1,50 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var MatrixClientPeg = require("../../MatrixClientPeg");
module.exports = {
shouldHighlight: function() {
var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; }
return actions.tweaks.highlight;
},
getInitialState: function() {
return {
resending: false
};
},
onResend: function() {
var self = this;
self.setState({
resending: true
});
MatrixClientPeg.get().resendEvent(
this.props.mxEvent, MatrixClientPeg.get().getRoom(
this.props.mxEvent.getRoomId()
)
).finally(function() {
self.setState({
resending: false
});
})
}
};

View File

@@ -1,26 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
module.exports = {
propTypes: {
value: React.PropTypes.number,
max: React.PropTypes.number
},
};

View File

@@ -1,95 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
/*
* State vars:
* this.state.call_state = the UI state of the call (see CallHandler)
*/
var React = require('react');
var dis = require("../../dispatcher");
var CallHandler = require("../../CallHandler");
module.exports = {
propTypes: {
room: React.PropTypes.object.isRequired,
editing: React.PropTypes.bool,
onSettingsClick: React.PropTypes.func,
onSaveClick: React.PropTypes.func,
},
getDefaultProps: function() {
return {
editing: false,
onSettingsClick: function() {},
onSaveClick: function() {},
};
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
if (this.props.room) {
var call = CallHandler.getCall(this.props.room.roomId);
var callState = call ? call.call_state : "ended";
this.setState({
call_state: callState
});
}
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onAction: function(payload) {
// if we were given a room_id to track, don't handle anything else.
if (payload.room_id && this.props.room &&
this.props.room.roomId !== payload.room_id) {
return;
}
if (payload.action !== 'call_state') {
return;
}
var call = CallHandler.getCall(payload.room_id);
var callState = call ? call.call_state : "ended";
this.setState({
call_state: callState
});
},
onVideoClick: function() {
dis.dispatch({
action: 'place_call',
type: "video",
room_id: this.props.room.roomId
});
},
onVoiceClick: function() {
dis.dispatch({
action: 'place_call',
type: "voice",
room_id: this.props.room.roomId
});
},
onHangupClick: function() {
dis.dispatch({
action: 'hangup',
room_id: this.props.room.roomId
});
}
};

View File

@@ -1,28 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var dis = require("../../dispatcher");
module.exports = {
onClick: function() {
dis.dispatch({
action: 'view_room',
room_id: this.props.room.roomId
});
},
};

View File

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

View File

@@ -1,64 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require("react");
module.exports = {
propTypes: {
onHsUrlChanged: React.PropTypes.func,
onIsUrlChanged: React.PropTypes.func,
default_hs_url: React.PropTypes.string,
default_is_url: React.PropTypes.string
},
getDefaultProps: function() {
return {
onHsUrlChanged: function() {},
onIsUrlChanged: function() {},
defaultHsUrl: 'https://matrix.org/',
defaultIsUrl: 'https://matrix.org/'
};
},
getInitialState: function() {
return {
hs_url: this.props.defaultHsUrl,
is_url: this.props.defaultIsUrl,
}
},
hsChanged: function(ev) {
this.setState({hs_url: ev.target.value}, function() {
this.props.onHsUrlChanged(this.state.hs_url);
});
},
isChanged: function(ev) {
this.setState({is_url: ev.target.value}, function() {
this.props.onIsUrlChanged(this.state.is_url);
});
},
getHsUrl: function() {
return this.state.hs_url;
},
getIsUrl: function() {
return this.state.is_url;
},
};

View File

@@ -1,45 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
module.exports = {
propTypes: {
onChange: React.PropTypes.func,
selected_users: React.PropTypes.arrayOf(React.PropTypes.string),
},
getDefaultProps: function() {
return {
onChange: function() {},
selected: [],
};
},
addUser: function(user_id) {
if (this.props.selected_users.indexOf(user_id == -1)) {
this.props.onChange(this.props.selected_users.concat([user_id]));
}
},
removeUser: function(user_id) {
this.props.onChange(this.props.selected_users.filter(function(e) {
return e != user_id;
}));
},
};

View File

@@ -15,8 +15,11 @@ limitations under the License.
*/
'use strict';
var dis = require("../../../dispatcher");
var CallHandler = require("../../../CallHandler");
var dis = require("matrix-react-sdk/lib/dispatcher");
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var VectorConferenceHandler = require('../../../modules/VectorConferenceHandler');
/*
* State vars:
@@ -24,14 +27,30 @@ var CallHandler = require("../../../CallHandler");
*
* Props:
* this.props.room = Room (JS SDK)
*
* Internal state:
* this._trackedRoom = (either from props.room or programatically set)
*/
module.exports = {
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this._trackedRoom = null;
if (this.props.room) {
this.showCall(this.props.room.roomId);
this._trackedRoom = this.props.room;
this.showCall(this._trackedRoom.roomId);
}
else {
var call = CallHandler.getAnyActiveCall();
if (call) {
console.log(
"Global CallView is now tracking active call in room %s",
call.roomId
);
this._trackedRoom = MatrixClientPeg.get().getRoom(call.roomId);
this.showCall(call.roomId);
}
}
},
@@ -40,26 +59,32 @@ module.exports = {
},
onAction: function(payload) {
// if we were given a room_id to track, don't handle anything else.
if (payload.room_id && this.props.room &&
this.props.room.roomId !== payload.room_id) {
return;
}
if (payload.action !== 'call_state') {
// don't filter out payloads for room IDs other than props.room because
// we may be interested in the conf 1:1 room
if (payload.action !== 'call_state' || !payload.room_id) {
return;
}
this.showCall(payload.room_id);
},
showCall: function(roomId) {
var call = CallHandler.getCall(roomId);
var call = (
CallHandler.getCallForRoom(roomId) ||
VectorConferenceHandler.getConferenceCallForRoom(roomId)
);
if (call) {
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
// N.B. the remote video element is used for playback for audio for voice calls
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
// give a separate element for audio stream playback - both for voice calls
// and for the voice stream of screen captures
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
}
if (call && call.type === "video" && call.state !== 'ended') {
this.getVideoView().getLocalVideoElement().style.display = "initial";
// if this call is a conf call, don't display local video as the
// conference will have us in it
this.getVideoView().getLocalVideoElement().style.display = (
call.confUserId ? "none" : "initial"
);
this.getVideoView().getRemoteVideoElement().style.display = "initial";
}
else {

View File

@@ -1,75 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var dis = require("../../../dispatcher");
var CallHandler = require("../../../CallHandler");
module.exports = {
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
getInitialState: function() {
return {
incomingCall: null
}
},
onAction: function(payload) {
if (payload.action !== 'call_state') {
return;
}
var call = CallHandler.getCall(payload.room_id);
if (!call || call.call_state !== 'ringing') {
this.setState({
incomingCall: null,
});
this.getRingAudio().pause();
return;
}
if (call.call_state === "ringing") {
this.getRingAudio().load();
this.getRingAudio().play();
}
else {
this.getRingAudio().pause();
}
this.setState({
incomingCall: call
});
},
onAnswerClick: function() {
dis.dispatch({
action: 'answer',
room_id: this.state.incomingCall.roomId
});
},
onRejectClick: function() {
dis.dispatch({
action: 'hangup',
room_id: this.state.incomingCall.roomId
});
}
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,135 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require("react");
var MatrixClientPeg = require("../../MatrixClientPeg");
var PresetValues = require('../atoms/create_room/Presets').Presets;
var q = require('q');
var encryption = require("../../encryption");
module.exports = {
propTypes: {
onRoomCreated: React.PropTypes.func,
},
phases: {
CONFIG: "CONFIG", // We're waiting for user to configure and hit create.
CREATING: "CREATING", // We're sending the request.
CREATED: "CREATED", // We successfully created the room.
ERROR: "ERROR", // There was an error while trying to create room.
},
getDefaultProps: function() {
return {
onRoomCreated: function() {},
};
},
getInitialState: function() {
return {
phase: this.phases.CONFIG,
error_string: "",
is_private: true,
share_history: false,
default_preset: PresetValues.PrivateChat,
topic: '',
room_name: '',
invited_users: [],
};
},
onCreateRoom: function() {
var options = {};
if (this.state.room_name) {
options.name = this.state.room_name;
}
if (this.state.topic) {
options.topic = this.state.topic;
}
if (this.state.preset) {
if (this.state.preset != PresetValues.Custom) {
options.preset = this.state.preset;
} else {
options.initial_state = [
{
type: "m.room.join_rules",
content: {
"join_rules": this.state.is_private ? "invite" : "public"
}
},
{
type: "m.room.history_visibility",
content: {
"history_visibility": this.state.share_history ? "shared" : "invited"
}
},
];
}
}
options.invite = this.state.invited_users;
var alias = this.getAliasLocalpart();
if (alias) {
options.room_alias_name = alias;
}
var cli = MatrixClientPeg.get();
if (!cli) {
// TODO: Error.
console.error("Cannot create room: No matrix client.");
return;
}
var deferred = cli.createRoom(options);
var response;
if (this.state.encrypt) {
deferred = deferred.then(function(res) {
response = res;
return encryption.enableEncryption(
cli, response.roomId, options.invite
);
}).then(function() {
return q(response) }
);
}
this.setState({
phase: this.phases.CREATING,
});
var self = this;
deferred.then(function (resp) {
self.setState({
phase: self.phases.CREATED,
});
self.props.onRoomCreated(resp.room_id);
}, function(err) {
self.setState({
phase: self.phases.ERROR,
error_string: err.toString(),
});
});
}
};

View File

@@ -1,39 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require("react");
module.exports = {
propTypes: {
title: React.PropTypes.string,
description: React.PropTypes.string,
button: React.PropTypes.string,
focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired,
},
getDefaultProps: function() {
var self = this;
return {
title: "Error",
description: "An error has occurred.",
button: "OK",
focus: true,
};
},
};

View File

@@ -1,35 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var dis = require("../../dispatcher");
module.exports = {
logOut: function() {
dis.dispatch({action: 'logout'});
if (this.props.onFinished) {
this.props.onFinished();
}
},
cancelPrompt: function() {
if (this.props.onFinished) {
this.props.onFinished();
}
}
};

View File

@@ -1,162 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require("react");
var MatrixClientPeg = require("../../MatrixClientPeg");
var Modal = require("../../Modal");
var ComponentBroker = require('../../ComponentBroker');
var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
var INITIAL_LOAD_NUM_MEMBERS = 50;
module.exports = {
getInitialState: function() {
var members = this.roomMembers(INITIAL_LOAD_NUM_MEMBERS);
return {
memberDict: members
};
},
componentWillMount: function() {
var cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember);
cli.on("Room", this.onRoom); // invites
},
componentWillUnmount: function() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room", this.onRoom);
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
}
},
componentDidMount: function() {
var self = this;
setTimeout(function() {
if (!self.isMounted()) return;
self.setState({
memberDict: self.roomMembers()
});
}, 50);
// Attach a SINGLE listener for global presence changes then locate the
// member tile and re-render it. This is more efficient than every tile
// evar attaching their own listener.
function updateUserState(event, user) {
var tile = self.refs[user.userId];
if (tile) {
tile.forceUpdate();
}
}
MatrixClientPeg.get().on("User.presence", updateUserState);
this.userPresenceFn = updateUserState;
},
// Remember to set 'key' on a MemberList to the ID of the room it's for
/*componentWillReceiveProps: function(newProps) {
},*/
onRoom: function(room) {
if (room.roomId !== this.props.roomId) {
return;
}
// We listen for room events because when we accept an invite
// we need to wait till the room is fully populated with state
// before refreshing the member list else we get a stale list.
this._updateList();
},
onRoomStateMember: function(ev, state, member) {
this._updateList();
},
_updateList: function() {
var members = this.roomMembers();
this.setState({
memberDict: members
});
},
onInvite: function(inputText) {
var self = this;
// sanity check the input
inputText = inputText.trim(); // react requires es5-shim so we know trim() exists
if (inputText[0] !== '@' || inputText.indexOf(":") === -1) {
console.error("Bad user ID to invite: %s", inputText);
Modal.createDialog(ErrorDialog, {
title: "Invite Error",
description: "Malformed user ID. Should look like '@localpart:domain'"
});
return;
}
self.setState({
inviting: true
});
console.log("Invite %s to %s", inputText, this.props.roomId);
MatrixClientPeg.get().invite(this.props.roomId, inputText).done(
function(res) {
console.log("Invited");
self.setState({
inviting: false
});
}, function(err) {
console.error("Failed to invite: %s", JSON.stringify(err));
Modal.createDialog(ErrorDialog, {
title: "Server error whilst inviting",
description: err.message
});
self.setState({
inviting: false
});
});
},
roomMembers: function(limit) {
if (!this.props.roomId) return {};
var cli = MatrixClientPeg.get();
var room = cli.getRoom(this.props.roomId);
if (!room) return {};
var all_members = room.currentState.members;
var all_user_ids = Object.keys(all_members);
all_user_ids.sort(function(userIdA, userIdB) {
var userA = all_members[userIdA].user;
var userB = all_members[userIdB].user;
var latA = userA ? userA.lastActiveAgo || Number.MAX_VALUE : Number.MAX_VALUE;
var latB = userB ? userB.lastActiveAgo || Number.MAX_VALUE : Number.MAX_VALUE;
return latA - latB;
});
var to_display = {};
var count = 0;
for (var i = 0; i < all_user_ids.length && (limit === undefined || count < limit); ++i) {
var user_id = all_user_ids[i];
var m = all_members[user_id];
if (m.membership == 'join' || m.membership == 'invite') {
to_display[user_id] = m;
++count;
}
}
return to_display;
}
};

View File

@@ -1,111 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
/*
* Dispatches:
* {
* action: "notifier_enabled",
* value: boolean
* }
*/
module.exports = {
start: function() {
this.boundOnRoomTimeline = this.onRoomTimeline.bind(this);
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
},
stop: function() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
}
},
supportsDesktopNotifications: function() {
return !!global.Notification;
},
havePermission: function() {
if (!this.supportsDesktopNotifications()) return false;
return global.Notification.permission == 'granted';
},
setEnabled: function(enable, callback) {
console.log("Notifier.setEnabled => %s", enable);
if(enable) {
if (!this.havePermission()) {
var self = this;
global.Notification.requestPermission(function() {
if (callback) {
callback();
dis.dispatch({
action: "notifier_enabled",
value: true
});
}
});
}
if (!global.localStorage) return;
global.localStorage.setItem('notifications_enabled', 'true');
if (this.havePermission) {
dis.dispatch({
action: "notifier_enabled",
value: true
});
}
}
else {
if (!global.localStorage) return;
global.localStorage.setItem('notifications_enabled', 'false');
dis.dispatch({
action: "notifier_enabled",
value: false
});
}
},
isEnabled: function() {
if (!this.havePermission()) return false;
if (!global.localStorage) return true;
var enabled = global.localStorage.getItem('notifications_enabled');
if (enabled === null) return true;
return enabled === 'true';
},
onRoomTimeline: function(ev, room, toStartOfTimeline) {
if (toStartOfTimeline) return;
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
if (!this.isEnabled()) {
return;
}
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions && actions.notify) {
this.displayNotification(ev, room);
}
}
};

View File

@@ -1,39 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require("react");
module.exports = {
propTypes: {
title: React.PropTypes.string,
description: React.PropTypes.string,
button: React.PropTypes.string,
focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired,
},
getDefaultProps: function() {
var self = this;
return {
title: "",
description: "",
button: "OK",
focus: true,
};
},
};

View File

@@ -17,12 +17,15 @@ limitations under the License.
'use strict';
var React = require("react");
var MatrixClientPeg = require("../../MatrixClientPeg");
var RoomListSorter = require("../../RoomListSorter");
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var RoomListSorter = require("matrix-react-sdk/lib/RoomListSorter");
var dis = require("matrix-react-sdk/lib/dispatcher");
var ComponentBroker = require('../../ComponentBroker');
var sdk = require('matrix-react-sdk');
var VectorConferenceHandler = require("../../modules/VectorConferenceHandler");
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
var RoomTile = ComponentBroker.get("molecules/RoomTile");
var HIDE_CONFERENCE_CHANS = true;
module.exports = {
componentWillMount: function() {
@@ -38,7 +41,27 @@ module.exports = {
});
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
onAction: function(payload) {
switch (payload.action) {
// listen for call state changes to prod the render method, which
// may hide the global CallView if the call it is tracking is dead
case 'call_state':
this._recheckCallElement(this.props.selectedRoom);
break;
case 'view_tooltip':
this.tooltip = payload.tooltip;
this._repositionTooltip();
if (this.tooltip) this.tooltip.style.display = 'block';
break
}
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room", this.onRoom);
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
@@ -48,6 +71,7 @@ module.exports = {
componentWillReceiveProps: function(newProps) {
this.state.activityMap[newProps.selectedRoom] = undefined;
this._recheckCallElement(newProps.selectedRoom);
this.setState({
activityMap: this.state.activityMap
});
@@ -73,13 +97,11 @@ module.exports = {
if (actions && actions.tweaks && actions.tweaks.highlight) {
hl = 2;
}
if (actions.notify) {
// obviously this won't deep copy but this shouldn't be necessary
var amap = this.state.activityMap;
amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
// obviously this won't deep copy but this shouldn't be necessary
var amap = this.state.activityMap;
amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
newState.activityMap = amap;
}
newState.activityMap = amap;
}
this.setState(newState);
},
@@ -98,25 +120,63 @@ module.exports = {
getRoomList: function() {
return RoomListSorter.mostRecentActivityFirst(
MatrixClientPeg.get().getRooms().filter(function(room) {
var member = room.getMember(MatrixClientPeg.get().credentials.userId);
return member && (member.membership == "join" || member.membership == "invite");
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
var shouldShowRoom = (
me && (me.membership == "join" || me.membership == "invite")
);
// hiding conf rooms only ever toggles shouldShowRoom to false
if (shouldShowRoom && HIDE_CONFERENCE_CHANS) {
// we want to hide the 1:1 conf<->user room and not the group chat
var joinedMembers = room.getJoinedMembers();
if (joinedMembers.length === 2) {
var otherMember = joinedMembers.filter(function(m) {
return m.userId !== me.userId
})[0];
if (VectorConferenceHandler.isConferenceUser(otherMember)) {
// console.log("Hiding conference 1:1 room %s", room.roomId);
shouldShowRoom = false;
}
}
}
return shouldShowRoom;
})
);
},
_recheckCallElement: function(selectedRoomId) {
// if we aren't viewing a room with an ongoing call, but there is an
// active call, show the call element - we need to do this to make
// audio/video not crap out
var activeCall = CallHandler.getAnyActiveCall();
var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
var showCall = (activeCall && !callForRoom);
this.setState({
show_call_element: showCall
});
},
_repositionTooltip: function(e) {
if (this.tooltip && this.tooltip.parentElement) {
var scroll = this.getDOMNode();
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.scrollTop) + "px";
}
},
makeRoomTiles: function() {
var self = this;
var RoomTile = sdk.getComponent("molecules.RoomTile");
return this.state.roomList.map(function(room) {
var selected = room.roomId == self.props.selectedRoom;
return (
<RoomTile
room={room}
key={room.roomId}
collapsed={self.props.collapsed}
selected={selected}
unread={self.state.activityMap[room.roomId] === 1}
highlight={self.state.activityMap[room.roomId] === 2}
/>
);
});
},
}
};

View File

@@ -14,36 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var MatrixClientPeg = require("../../MatrixClientPeg");
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var React = require("react");
var q = require("q");
var ContentMessages = require("../../ContentMessages");
var WhoIsTyping = require("../../WhoIsTyping");
var Modal = require("../../Modal");
var ComponentBroker = require('../../ComponentBroker');
var ContentMessages = require("matrix-react-sdk/lib//ContentMessages");
var WhoIsTyping = require("matrix-react-sdk/lib/WhoIsTyping");
var Modal = require("matrix-react-sdk/lib/Modal");
var sdk = require('matrix-react-sdk/lib/index');
var CallHandler = require('matrix-react-sdk/lib/CallHandler');
var VectorConferenceHandler = require('../../modules/VectorConferenceHandler');
var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
var dis = require("../../dispatcher");
var dis = require("matrix-react-sdk/lib/dispatcher");
var PAGINATE_SIZE = 20;
var INITIAL_SIZE = 100;
var ComponentBroker = require('../../ComponentBroker');
var Notifier = ComponentBroker.get('organisms/Notifier');
var tileTypes = {
'm.room.message': ComponentBroker.get('molecules/MessageTile'),
'm.room.member': ComponentBroker.get('molecules/MRoomMemberTile'),
'm.call.invite': ComponentBroker.get('molecules/voip/MCallInviteTile'),
'm.call.answer': ComponentBroker.get('molecules/voip/MCallAnswerTile'),
'm.call.hangup': ComponentBroker.get('molecules/voip/MCallHangupTile'),
'm.room.topic': ComponentBroker.get('molecules/EventAsTextTile'),
};
var DateSeparator = ComponentBroker.get('molecules/DateSeparator');
var INITIAL_SIZE = 20;
module.exports = {
getInitialState: function() {
@@ -52,7 +36,8 @@ module.exports = {
messageCap: INITIAL_SIZE,
editingRoomSettings: false,
uploadingRoomSettings: false,
numUnreadMessages: 0
numUnreadMessages: 0,
draggingFile: false,
}
},
@@ -61,6 +46,7 @@ module.exports = {
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Room.name", this.onRoomName);
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
this.atBottom = true;
},
@@ -69,6 +55,8 @@ module.exports = {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
messageWrapper.removeEventListener('drop', this.onDrop);
messageWrapper.removeEventListener('dragover', this.onDragOver);
messageWrapper.removeEventListener('dragleave', this.onDragLeaveOrEnd);
messageWrapper.removeEventListener('dragend', this.onDragLeaveOrEnd);
}
dis.unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
@@ -82,6 +70,7 @@ module.exports = {
switch (payload.action) {
case 'message_send_failed':
case 'message_sent':
case 'message_resend_started':
this.setState({
room: MatrixClientPeg.get().getRoom(this.props.roomId)
});
@@ -91,15 +80,20 @@ module.exports = {
this.forceUpdate();
break;
case 'call_state':
if (this.props.roomId !== payload.room_id) {
break;
}
// scroll to bottom
var messageWrapper = this.refs.messageWrapper;
if (messageWrapper) {
messageWrapper = messageWrapper.getDOMNode();
messageWrapper.scrollTop = messageWrapper.scrollHeight;
if (CallHandler.getCallForRoom(this.props.roomId)) {
// Call state has changed so we may be loading video elements
// which will obscure the message log.
// scroll to bottom
var messageWrapper = this.refs.messageWrapper;
if (messageWrapper) {
messageWrapper = messageWrapper.getDOMNode();
messageWrapper.scrollTop = messageWrapper.scrollHeight;
}
}
// possibly remove the conf call notification if we're now in
// the conf
this._updateConfCallNotification();
break;
}
},
@@ -127,13 +121,13 @@ module.exports = {
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
this.atBottom = (
messageWrapper.scrollHeight - messageWrapper.scrollTop <=
messageWrapper.scrollHeight - messageWrapper.scrollTop <=
(messageWrapper.clientHeight + 150)
);
}
var currentUnread = this.state.numUnreadMessages;
if (!toStartOfTimeline &&
if (!toStartOfTimeline &&
(ev.getSender() !== MatrixClientPeg.get().credentials.userId)) {
// update unread count when scrolled up
if (this.atBottom) {
@@ -167,17 +161,51 @@ module.exports = {
this.forceUpdate();
},
onRoomStateMember: function(ev, state, member) {
if (member.roomId !== this.props.roomId ||
member.userId !== VectorConferenceHandler.getConferenceUserIdForRoom(member.roomId)) {
return;
}
this._updateConfCallNotification();
},
_updateConfCallNotification: function() {
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (!room) return;
var confMember = room.getMember(
VectorConferenceHandler.getConferenceUserIdForRoom(this.props.roomId)
);
if (!confMember) {
return;
}
var confCall = VectorConferenceHandler.getConferenceCallForRoom(confMember.roomId);
// A conf call notification should be displayed if there is an ongoing
// conf call but this cilent isn't a part of it.
this.setState({
displayConfCallNotification: (
(!confCall || confCall.call_state === "ended") &&
confMember.membership === "join"
)
});
},
componentDidMount: function() {
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
messageWrapper.addEventListener('drop', this.onDrop);
messageWrapper.addEventListener('dragover', this.onDragOver);
messageWrapper.addEventListener('dragleave', this.onDragLeaveOrEnd);
messageWrapper.addEventListener('dragend', this.onDragLeaveOrEnd);
messageWrapper.scrollTop = messageWrapper.scrollHeight;
this.fillSpace();
}
this._updateConfCallNotification();
},
componentDidUpdate: function() {
@@ -201,6 +229,7 @@ module.exports = {
},
fillSpace: function() {
if (!this.refs.messageWrapper) return;
var messageWrapper = this.refs.messageWrapper.getDOMNode();
if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
this.setState({paginating: true});
@@ -271,6 +300,7 @@ module.exports = {
var items = ev.dataTransfer.items;
if (items.length == 1) {
if (items[0].kind == 'file') {
this.setState({ draggingFile : true });
ev.dataTransfer.dropEffect = 'copy';
}
}
@@ -279,12 +309,19 @@ module.exports = {
onDrop: function(ev) {
ev.stopPropagation();
ev.preventDefault();
this.setState({ draggingFile : false });
var files = ev.dataTransfer.files;
if (files.length == 1) {
this.uploadFile(files[0]);
}
},
onDragLeaveOrEnd: function(ev) {
ev.stopPropagation();
ev.preventDefault();
this.setState({ draggingFile : false });
},
uploadFile: function(file) {
this.setState({
upload: {
@@ -319,6 +356,17 @@ module.exports = {
},
getEventTiles: function() {
var tileTypes = {
'm.room.message': sdk.getComponent('molecules.MessageTile'),
'm.room.member' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.invite' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.answer' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.hangup' : sdk.getComponent('molecules.EventAsTextTile'),
'm.room.topic' : sdk.getComponent('molecules.EventAsTextTile'),
};
var DateSeparator = sdk.getComponent('molecules.DateSeparator');
var ret = [];
var count = 0;
@@ -350,9 +398,16 @@ module.exports = {
continuation = false;
}
}
if (i === 1) { // n.b. 1, not 0, as the 0th event is an m.room.create and so doesn't show on the timeline
var ts1 = this.state.room.timeline[i].getTs();
dateSeparator = <DateSeparator key={ts1} ts={ts1}/>;
continuation = false;
}
if (!TileType) continue;
ret.unshift(
<TileType key={mxEv.getId()} mxEvent={mxEv} continuation={continuation} last={last}/>
<li key={mxEv.getId()}><TileType mxEvent={mxEv} continuation={continuation} last={last}/></li>
);
if (dateSeparator) {
ret.unshift(dateSeparator);
@@ -431,6 +486,7 @@ module.exports = {
if (deferreds.length) {
var self = this;
q.all(deferreds).fail(function(err) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to set state",
description: err.toString()

View File

@@ -1,72 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var MatrixClientPeg = require("../../MatrixClientPeg");
var React = require("react");
var q = require('q');
var dis = require("../../dispatcher");
var version = require('../../../package.json').version;
var ComponentBroker = require('../../ComponentBroker');
module.exports = {
Phases: {
Loading: "loading",
Display: "display",
},
getInitialState: function() {
return {
displayName: null,
avatarUrl: null,
threePids: [],
clientVersion: version,
phase: this.Phases.Loading,
};
},
changeDisplayname: function(new_displayname) {
if (this.state.displayName == new_displayname) return;
var self = this;
return MatrixClientPeg.get().setDisplayName(new_displayname).then(
function() { self.setState({displayName: new_displayname}); },
function(err) { console.err(err); }
);
},
componentWillMount: function() {
var self = this;
var cli = MatrixClientPeg.get();
var profile_d = cli.getProfileInfo(cli.credentials.userId);
var threepid_d = cli.getThreePids();
q.all([profile_d, threepid_d]).then(
function(resps) {
self.setState({
displayName: resps[0].displayname,
avatarUrl: resps[0].avatar_url,
threepids: resps[1].threepids,
phase: self.Phases.Display,
});
},
function(err) { console.err(err); }
);
}
}

View File

@@ -1,265 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
// should be atomised
var Loader = require("react-loader");
var MatrixClientPeg = require("../../MatrixClientPeg");
var RoomListSorter = require("../../RoomListSorter");
var Presence = require("../../Presence");
var dis = require("../../dispatcher");
var ComponentBroker = require('../../ComponentBroker');
var Notifier = ComponentBroker.get('organisms/Notifier');
module.exports = {
PageTypes: {
RoomView: "room_view",
UserSettings: "user_settings",
CreateRoom: "create_room",
RoomDirectory: "room_directory",
},
AuxPanel: {
RoomSettings: "room_settings",
},
getInitialState: function() {
var s = {
logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials),
ready: false,
aux_panel: null,
};
if (s.logged_in) {
if (MatrixClientPeg.get().getRooms().length) {
s.page_type = this.PageTypes.RoomView;
} else {
s.page_type = this.PageTypes.RoomDirectory;
}
}
return s;
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
if (this.state.logged_in) {
this.startMatrixClient();
}
this.focusComposer = false;
document.addEventListener("keydown", this.onKeyDown);
window.addEventListener("focus", this.onFocus);
if (this.state.logged_in) {
this.notifyNewScreen('');
} else {
this.notifyNewScreen('login');
}
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
document.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("focus", this.onFocus);
},
componentDidUpdate: function() {
if (this.focusComposer) {
dis.dispatch({action: 'focus_composer'});
this.focusComposer = false;
}
},
onAction: function(payload) {
var roomIndexDelta = 1;
switch (payload.action) {
case 'logout':
this.replaceState({
logged_in: false,
ready: false
});
if (window.localStorage) {
window.localStorage.clear();
}
Notifier.stop();
Presence.stop();
MatrixClientPeg.get().stopClient();
MatrixClientPeg.get().removeAllListeners();
MatrixClientPeg.unset();
this.notifyNewScreen('');
break;
case 'start_registration':
if (this.state.logged_in) return;
var newState = payload.params || {};
newState.screen = 'register';
if (
payload.params &&
payload.params.client_secret &&
payload.params.session_id &&
payload.params.hs_url &&
payload.params.is_url &&
payload.params.sid
) {
newState.register_client_secret = payload.params.client_secret;
newState.register_session_id = payload.params.session_id;
newState.register_hs_url = payload.params.hs_url;
newState.register_is_url = payload.params.is_url;
newState.register_id_sid = payload.params.sid;
}
this.replaceState(newState);
this.notifyNewScreen('register');
break;
case 'start_login':
if (this.state.logged_in) return;
this.replaceState({
screen: 'login'
});
this.notifyNewScreen('login');
break;
case 'view_room':
this.focusComposer = true;
this.setState({
currentRoom: payload.room_id,
page_type: this.PageTypes.RoomView,
});
this.notifyNewScreen('room/'+payload.room_id);
break;
case 'view_prev_room':
roomIndexDelta = -1;
case 'view_next_room':
var allRooms = RoomListSorter.mostRecentActivityFirst(
MatrixClientPeg.get().getRooms()
);
var roomIndex = -1;
for (var i = 0; i < allRooms.length; ++i) {
if (allRooms[i].roomId == this.state.currentRoom) {
roomIndex = i;
break;
}
}
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
this.setState({
currentRoom: allRooms[roomIndex].roomId
});
this.notifyNewScreen('room/'+allRooms[roomIndex].roomId);
break;
case 'view_user_settings':
this.setState({
page_type: this.PageTypes.UserSettings,
});
break;
case 'view_create_room':
this.setState({
page_type: this.PageTypes.CreateRoom,
});
break;
case 'view_room_directory':
this.setState({
page_type: this.PageTypes.RoomDirectory,
});
break;
case 'notifier_enabled':
this.forceUpdate();
break;
}
},
onLoggedIn: function() {
this.setState({
screen: undefined,
logged_in: true
});
this.startMatrixClient();
this.notifyNewScreen('');
},
startMatrixClient: function() {
var cli = MatrixClientPeg.get();
var self = this;
cli.on('syncComplete', function() {
if (!self.state.currentRoom) {
var firstRoom = null;
if (cli.getRooms() && cli.getRooms().length) {
firstRoom = RoomListSorter.mostRecentActivityFirst(
cli.getRooms()
)[0].roomId;
self.setState({ready: true, currentRoom: firstRoom});
self.notifyNewScreen('room/'+firstRoom);
} else {
self.setState({ready: true});
}
} else {
self.setState({ready: true});
}
dis.dispatch({action: 'focus_composer'});
});
cli.on('Call.incoming', function(call) {
dis.dispatch({
action: 'incoming_call',
call: call
});
});
Notifier.start();
Presence.start();
cli.startClient();
},
onKeyDown: function(ev) {
if (ev.altKey) {
switch (ev.keyCode) {
case 38:
dis.dispatch({action: 'view_prev_room'});
ev.stopPropagation();
break;
case 40:
dis.dispatch({action: 'view_next_room'});
ev.stopPropagation();
break;
}
}
},
onFocus: function(ev) {
dis.dispatch({action: 'focus_composer'});
},
showScreen: function(screen, params) {
if (screen == 'register') {
dis.dispatch({
action: 'start_registration',
params: params
});
} else if (screen == 'login') {
dis.dispatch({
action: 'start_login',
params: params
});
} else if (screen.indexOf('room/') == 0) {
var roomId = screen.split('/')[1];
dis.dispatch({
action: 'view_room',
room_id: roomId
});
}
},
notifyNewScreen: function(screen) {
if (this.props.onNewScreen) {
this.props.onNewScreen(screen);
}
}
};

View File

@@ -1,97 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../MatrixClientPeg");
var Matrix = require("matrix-js-sdk");
var dis = require("../../dispatcher");
var ComponentBroker = require("../../ComponentBroker");
module.exports = {
getInitialState: function() {
return {
step: 'choose_hs',
busy: false,
currentStep: 0,
totalSteps: 1
};
},
setStep: function(step) {
this.setState({ step: step, errorText: '', busy: false });
},
onHSChosen: function() {
MatrixClientPeg.replaceUsingUrls(
this.getHsUrl(),
this.getIsUrl()
);
this.setState({
hs_url: this.getHsUrl(),
is_url: this.getIsUrl()
});
this.setStep("fetch_stages");
var cli = MatrixClientPeg.get();
this.setState({busy: true});
var self = this;
cli.loginFlows().done(function(result) {
self.setState({
flows: result.flows,
currentStep: 1,
totalSteps: result.flows.length+1
});
self.setStep('stage_'+result.flows[0].type);
}, function(error) {
self.setStep("choose_hs");
self.setState({errorText: 'Unable to contact the given Home Server'});
});
},
onUserPassEntered: function(ev) {
ev.preventDefault();
this.setState({busy: true});
var self = this;
var formVals = this.getFormVals();
MatrixClientPeg.get().login('m.login.password', {
'user': formVals.username,
'password': formVals.password
}).done(function(data) {
MatrixClientPeg.replaceUsingAccessToken(
self.state.hs_url, self.state.is_url,
data.user_id, data.access_token
);
if (self.props.onLoggedIn) {
self.props.onLoggedIn();
}
}, function(error) {
self.setStep("stage_m.login.password");
self.setState({errorText: 'Login failed.'});
});
},
showRegister: function(ev) {
ev.preventDefault();
dis.dispatch({
action: 'start_registration'
});
}
};

View File

@@ -16,332 +16,43 @@ limitations under the License.
'use strict';
var React = require('react');
var extend = require('matrix-react-sdk/lib/extend');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var BaseRegisterController = require('matrix-react-sdk/lib/controllers/templates/Register.js');
var MatrixClientPeg = require("../../MatrixClientPeg");
var Matrix = require("matrix-js-sdk");
var dis = require("../../dispatcher");
var RegisterController = {};
extend(RegisterController, BaseRegisterController);
var ComponentBroker = require("../../ComponentBroker");
RegisterController.onRegistered = function(user_id, access_token) {
MatrixClientPeg.replaceUsingAccessToken(
this.state.hs_url, this.state.is_url, user_id, access_token
);
module.exports = {
FieldErrors: {
PasswordMismatch: 'PasswordMismatch',
TooShort: 'TooShort',
Missing: 'Missing',
InUse: 'InUse'
},
this.setState({
step: 'profile',
busy: true
});
getInitialState: function() {
return {
step: 'initial',
busy: false,
currentStep: 0,
totalSteps: 1
};
},
componentWillMount: function() {
this.savedParams = {
email: '',
username: '',
password: '',
confirmPassword: ''
};
this.readNewProps();
},
componentWillReceiveProps: function() {
this.readNewProps();
},
readNewProps: function() {
if (this.props.clientSecret && this.props.hsUrl &&
this.props.isUrl && this.props.sessionId &&
this.props.idSid) {
this.authSessionId = this.props.sessionId;
MatrixClientPeg.replaceUsingUrls(
this.props.hsUrl,
this.props.isUrl
);
this.setState({
hs_url: this.props.hsUrl,
is_url: this.props.isUrl
});
this.savedParams = {client_secret: this.props.clientSecret};
this.setState({busy: true});
var isLocation = document.createElement('a');
isLocation.href = this.props.isUrl;
var auth = {
type: 'm.login.email.identity',
threepid_creds: {
sid: this.props.idSid,
client_secret: this.savedParams.client_secret,
id_server: isLocation.host
}
};
this.tryRegister(auth);
}
},
componentDidUpdate: function() {
// Just putting a script tag into the returned jsx doesn't work, annoyingly,
// so we do this instead.
if (this.refs.recaptchaContainer) {
var scriptTag = document.createElement('script');
window.mx_on_recaptcha_loaded = this.onCaptchaLoaded;
scriptTag.setAttribute('src', "https://www.google.com/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit");
this.refs.recaptchaContainer.getDOMNode().appendChild(scriptTag);
}
},
setStep: function(step) {
this.setState({ step: step, errorText: '', busy: false });
},
getSupportedStageTypes: function() {
return ['m.login.email.identity', 'm.login.recaptcha'];
},
chooseFlow: function(flows) {
// this is fairly simple right now
var supportedTypes = this.getSupportedStageTypes();
var emailFlow = null;
var otherFlow = null;
for (var flowI = 0; flowI < flows.length; ++flowI) {
var flow = flows[flowI];
var flowHasEmail = false;
var flowSupported = true;
for (var stageI = 0; stageI < flow.stages.length; ++stageI) {
var stage = flow.stages[stageI];
if (supportedTypes.indexOf(stage) == -1) {
flowSupported = false;
}
if (stage == 'm.login.email.identity') {
flowHasEmail = true;
}
}
if (flowSupported) {
if (flowHasEmail) {
emailFlow = flow;
} else {
otherFlow = flow;
}
}
}
if (
this.savedParams.email != '' ||
this.completedStages.indexOf('m.login.email.identity') > -1
) {
return emailFlow;
} else {
return otherFlow;
}
},
firstUncompletedStageIndex: function(flow) {
if (this.completedStages === undefined) return 0;
for (var i = 0; i < flow.stages.length; ++i) {
if (this.completedStages.indexOf(flow.stages[i]) == -1) {
return i;
}
}
},
numCompletedStages: function(flow) {
if (this.completedStages === undefined) return 0;
var nCompleted = 0;
for (var i = 0; i < flow.stages.length; ++i) {
if (this.completedStages.indexOf(flow.stages[i]) > -1) {
++nCompleted;
}
}
return nCompleted;
},
onInitialStageSubmit: function(ev) {
ev.preventDefault();
var formVals = this.getRegFormVals();
this.savedParams = formVals;
var badFields = {};
if (formVals.password != formVals.confirmPassword) {
badFields.confirmPassword = this.FieldErrors.PasswordMismatch;
}
if (formVals.password == '') {
badFields.password = this.FieldErrors.Missing;
} else if (formVals.password.length < 6) {
badFields.password = this.FieldErrors.Length;
}
if (formVals.username == '') {
badFields.username = this.FieldErrors.Missing;
}
if (Object.keys(badFields).length > 0) {
this.onBadFields(badFields);
return;
}
MatrixClientPeg.replaceUsingUrls(
this.getHsUrl(),
this.getIsUrl()
);
this.setState({
hs_url: this.getHsUrl(),
is_url: this.getIsUrl()
});
var cli = MatrixClientPeg.get();
this.setState({busy: true});
var self = this;
this.tryRegister();
},
startStage: function(stageName) {
var self = this;
this.setStep('stage_'+stageName);
switch(stageName) {
case 'm.login.email.identity':
self.setState({
busy: true
});
var cli = MatrixClientPeg.get();
this.savedParams.client_secret = cli.generateClientSecret();
this.savedParams.send_attempt = 1;
var nextLink = this.props.registrationUrl +
'?client_secret=' +
encodeURIComponent(this.savedParams.client_secret) +
"&hs_url=" +
encodeURIComponent(this.state.hs_url) +
"&is_url=" +
encodeURIComponent(this.state.is_url) +
"&session_id=" +
encodeURIComponent(this.authSessionId);
cli.requestEmailToken(
this.savedParams.email,
this.savedParams.client_secret,
this.savedParams.send_attempt,
nextLink
).done(function(response) {
self.setState({
busy: false,
});
self.setStep('stage_m.login.email.identity');
}, function(error) {
self.setStep('initial');
var newState = {busy: false};
if (error.errcode == 'THREEPID_IN_USE') {
self.onBadFields({email: self.FieldErrors.InUse});
} else {
newState.errorText = 'Unable to contact the given Home Server';
}
self.setState(newState);
});
break;
case 'm.login.recaptcha':
if (!this.authParams || !this.authParams['m.login.recaptcha'].public_key) {
this.setState({
errorText: "This server has not supplied enough information for Recaptcha authentication"
});
}
break;
}
},
onRegistered: function(user_id, access_token) {
MatrixClientPeg.replaceUsingAccessToken(
this.state.hs_url, this.state.is_url, user_id, access_token
);
if (this.props.onLoggedIn) {
this.props.onLoggedIn();
}
},
onCaptchaLoaded: function() {
if (this.refs.recaptchaContainer) {
var sitekey = this.authParams['m.login.recaptcha'].public_key;
global.grecaptcha.render('mx_recaptcha', {
'sitekey': sitekey,
'callback': this.onCaptchaDone
});
}
},
onCaptchaDone: function(captcha_response) {
this.tryRegister({
type: 'm.login.recaptcha',
response: captcha_response
var self = this;
var cli = MatrixClientPeg.get();
cli.getProfileInfo(cli.credentials.userId).done(function(result) {
self.setState({
avatarUrl: result.avatar_url,
busy: false
});
},
tryRegister: function(auth) {
var self = this;
MatrixClientPeg.get().register(
this.savedParams.username,
this.savedParams.password,
this.authSessionId,
auth
).done(function(result) {
self.onRegistered(result.user_id, result.access_token);
}, function(error) {
if (error.httpStatus == 401 && error.data.flows) {
self.authParams = error.data.params;
self.authSessionId = error.data.session;
self.completedStages = error.data.completed || [];
var flow = self.chooseFlow(error.data.flows);
var flowStage = self.firstUncompletedStageIndex(flow);
var numDone = self.numCompletedStages(flow);
self.setState({
busy: false,
flows: flow,
currentStep: 1+numDone,
totalSteps: flow.stages.length+1,
flowStage: flowStage
});
self.startStage(flow.stages[flowStage]);
} else {
self.setStep("initial");
var newState = {
busy: false,
errorText: "Unable to contact the given Home Server"
};
if (error.name == 'M_USER_IN_USE') {
delete newState.errorText;
self.onBadFields({
username: self.FieldErrors.InUse
});
} else if (error.httpStatus == 401) {
newState.errorText = "Authorisation failed!";
} else if (error.httpStatus >= 400 && error.httpStatus < 500) {
newState.errorText = "Registration failed!";
} else if (error.httpStatus >= 500 && error.httpStatus < 600) {
newState.errorText = "Server error during registration!";
} else if (error.name == "M_MISSING_PARAM") {
// The HS hasn't remembered the login params from
// the first try when the login email was sent.
newState.errorText = "This home server does not support resuming registration.";
}
self.setState(newState);
}
function(err) {
console.err(err);
self.setState({
busy: false
});
},
});
};
showLogin: function(ev) {
ev.preventDefault();
dis.dispatch({
action: 'start_login'
});
RegisterController.onAccountReady = function() {
if (this.props.onLoggedIn) {
this.props.onLoggedIn();
}
};
module.exports = RegisterController;

View File

@@ -1,37 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var flux = require("flux");
var extend = require("./extend");
var MatrixDispatcher = function() {
flux.Dispatcher.call(this);
};
extend(MatrixDispatcher.prototype, flux.Dispatcher.prototype);
MatrixDispatcher.prototype.dispatch = function(payload) {
if (this.dispatching) {
setTimeout(flux.Dispatcher.prototype.dispatch.bind(this, payload), 0);
} else {
this.dispatching = true;
flux.Dispatcher.prototype.dispatch.call(this, payload);
this.dispatching = false;
}
}
module.exports = new MatrixDispatcher();

View File

@@ -1,40 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
function enableEncyption(client, roomId, members) {
members = members.slice(0);
members.push(client.credentials.userId);
// TODO: Check the keys actually match what keys the user has.
// TODO: Don't redownload keys each time.
return client.downloadKeys(members, "forceDownload").then(function(res) {
return client.setRoomEncryption(roomId, {
algorithm: "m.olm.v1.curve25519-aes-sha2",
members: members,
});
})
}
function disableEncryption(client, roomId) {
return client.disableRoomEncryption(roomId);
}
module.exports = {
enableEncryption: enableEncyption,
disableEncryption: disableEncryption,
}

View File

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

View File

@@ -1,116 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var extend = require('./extend');
function matrixLinkify(linkify) {
// Text tokens
var TT = linkify.scanner.TOKENS;
var TextToken = TT.Base;
// Multi tokens
var MT = linkify.parser.TOKENS;
var MultiToken = MT.Base;
var S_START = linkify.parser.start;
var ROOMALIAS = function(value) {
MultiToken.call(this, value);
this.type = 'roomalias';
this.isLink = true;
};
ROOMALIAS.prototype = new MultiToken();
var S_HASH = new linkify.parser.State();
var S_HASH_NAME = new linkify.parser.State();
var S_HASH_NAME_COLON = new linkify.parser.State();
var S_HASH_NAME_COLON_DOMAIN = new linkify.parser.State();
var S_HASH_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
var S_ROOMALIAS = new linkify.parser.State(ROOMALIAS);
var roomname_tokens = [
TT.DOT,
TT.PLUS,
TT.NUM,
TT.DOMAIN,
TT.TLD
];
S_START.on(TT.POUND, S_HASH);
S_HASH.on(roomname_tokens, S_HASH_NAME);
S_HASH_NAME.on(roomname_tokens, S_HASH_NAME);
S_HASH_NAME.on(TT.DOMAIN, S_HASH_NAME);
S_HASH_NAME.on(TT.COLON, S_HASH_NAME_COLON);
S_HASH_NAME_COLON.on(TT.DOMAIN, S_HASH_NAME_COLON_DOMAIN);
S_HASH_NAME_COLON_DOMAIN.on(TT.DOT, S_HASH_NAME_COLON_DOMAIN_DOT);
S_HASH_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_HASH_NAME_COLON_DOMAIN);
S_HASH_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_ROOMALIAS);
var USERID = function(value) {
MultiToken.call(this, value);
this.type = 'userid';
this.isLink = true;
};
USERID.prototype = new MultiToken();
var S_AT = new linkify.parser.State();
var S_AT_NAME = new linkify.parser.State();
var S_AT_NAME_COLON = new linkify.parser.State();
var S_AT_NAME_COLON_DOMAIN = new linkify.parser.State();
var S_AT_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
var S_USERID = new linkify.parser.State(USERID);
var username_tokens = [
TT.DOT,
TT.PLUS,
TT.NUM,
TT.DOMAIN,
TT.TLD
];
S_START.on(TT.AT, S_AT);
S_AT.on(username_tokens, S_AT_NAME);
S_AT_NAME.on(username_tokens, S_AT_NAME);
S_AT_NAME.on(TT.DOMAIN, S_AT_NAME);
S_AT_NAME.on(TT.COLON, S_AT_NAME_COLON);
S_AT_NAME_COLON.on(TT.DOMAIN, S_AT_NAME_COLON_DOMAIN);
S_AT_NAME_COLON_DOMAIN.on(TT.DOT, S_AT_NAME_COLON_DOMAIN_DOT);
S_AT_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_AT_NAME_COLON_DOMAIN);
S_AT_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_USERID);
}
matrixLinkify.options = {
formatHref: function (href, type) {
switch (type) {
case 'roomalias':
return '#';
case 'userid':
return '#';
default:
return href;
}
}
}
module.exports = matrixLinkify;

View File

@@ -0,0 +1,134 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
var q = require("q");
var Matrix = require("matrix-js-sdk");
var Room = Matrix.Room;
var CallHandler = require('matrix-react-sdk/lib/CallHandler');
// FIXME: This currently forces Vector to try to hit the matrix.org AS for conferencing.
// This is bad because it prevents people running their own ASes from being used.
// This isn't permanent and will be customisable in the future: see the proposal
// at docs/conferencing.md for more info.
var USER_PREFIX = "fs_";
var DOMAIN = "matrix.org";
function ConferenceCall(matrixClient, groupChatRoomId) {
this.client = matrixClient;
this.groupRoomId = groupChatRoomId;
this.confUserId = module.exports.getConferenceUserIdForRoom(this.groupRoomId);
}
ConferenceCall.prototype.setup = function() {
var self = this;
return this._joinConferenceUser().then(function() {
return self._getConferenceUserRoom();
}).then(function(room) {
// return a call for *this* room to be placed. We also tack on
// confUserId to speed up lookups (else we'd need to loop every room
// looking for a 1:1 room with this conf user ID!)
var call = Matrix.createNewMatrixCall(self.client, room.roomId);
call.confUserId = self.confUserId;
return call;
});
};
ConferenceCall.prototype._joinConferenceUser = function() {
// Make sure the conference user is in the group chat room
var groupRoom = this.client.getRoom(this.groupRoomId);
if (!groupRoom) {
return q.reject("Bad group room ID");
}
var member = groupRoom.getMember(this.confUserId);
if (member && member.membership === "join") {
return q();
}
return this.client.invite(this.groupRoomId, this.confUserId);
};
ConferenceCall.prototype._getConferenceUserRoom = function() {
// Use an existing 1:1 with the conference user; else make one
var rooms = this.client.getRooms();
var confRoom = null;
for (var i = 0; i < rooms.length; i++) {
var confUser = rooms[i].getMember(this.confUserId);
if (confUser && confUser.membership === "join" &&
rooms[i].getJoinedMembers().length === 2) {
confRoom = rooms[i];
break;
}
}
if (confRoom) {
return q(confRoom);
}
return this.client.createRoom({
preset: "private_chat",
invite: [this.confUserId]
}).then(function(res) {
return new Room(res.room_id);
});
};
/**
* Check if this room member is in fact a conference bot.
* @param {RoomMember} The room member to check
* @return {boolean} True if it is a conference bot.
*/
module.exports.isConferenceUser = function(roomMember) {
if (roomMember.userId.indexOf("@" + USER_PREFIX) !== 0) {
return false;
}
var base64part = roomMember.userId.split(":")[0].substring(1 + USER_PREFIX.length);
if (base64part) {
var decoded = new Buffer(base64part, "base64").toString();
// ! $STUFF : $STUFF
return /^!.+:.+/.test(decoded);
}
return false;
};
module.exports.getConferenceUserIdForRoom = function(roomId) {
// abuse browserify's core node Buffer support (strip padding ='s)
var base64RoomId = new Buffer(roomId).toString("base64").replace(/=/g, "");
return "@" + USER_PREFIX + base64RoomId + ":" + DOMAIN;
};
module.exports.createNewMatrixCall = function(client, roomId) {
var confCall = new ConferenceCall(
client, roomId
);
return confCall.setup();
};
module.exports.getConferenceCallForRoom = function(roomId) {
// search for a conference 1:1 call for this group chat room ID
var activeCall = CallHandler.getAnyActiveCall();
if (activeCall && activeCall.confUserId) {
var thisRoomConfUserId = module.exports.getConferenceUserIdForRoom(
roomId
);
if (thisRoomConfUserId === activeCall.confUserId) {
return activeCall;
}
}
return null;
};
module.exports.ConferenceCall = ConferenceCall;
module.exports.slot = 'conference';

View File

@@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
module.exports = {
};
.mx_MemberAvatar {
z-index: 20;
border-radius: 20px;
background-color: #dbdbdb;
}

View File

@@ -14,6 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
html {
/* hack to stop overscroll bounce on OSX and iOS.
N.B. Breaks things when we have legitimate horizontal overscroll */
height: 100%;
overflow: hidden;
}
body {
font-family: 'Lato', Helvetica, Arial, Sans-Serif;
font-size: 16px;
@@ -34,18 +41,46 @@ h2 {
margin-bottom: 16px;
}
/* FIXME: show them on hoverover, and fix for firefox */
::-webkit-scrollbar {
display: none;
a:hover,
a:link,
a:visited {
color: #80CEF4;
}
html {
overflow: -moz-scrollbars-none;
.mx_ContextualMenu_background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 1.0;
z-index: 2000;
}
/* FIXME: why is all the dialog stuff in here rather than in per-component files? */
.mx_ContextualMenu {
border: 1px solid #a9dbf4;
border-radius: 8px;
background-color: #fff;
color: #747474;
position: fixed;
z-index: 2001;
padding: 6px;
}
.mx_Dialog_Background {
.mx_ContextualMenu_chevron {
padding: 12px;
position: absolute;
right: -21px;
top: 0px;
}
.mx_ContextualMenu_field {
padding: 3px 6px 3px 6px;
cursor: pointer;
}
.mx_Dialog_background {
position: fixed;
top: 0;
left: 0;
@@ -56,7 +91,7 @@ html {
z-index: 2000;
}
.mx_Dialog_Wrapper {
.mx_Dialog_wrapper {
position: fixed;
top: 0;
left: 0;
@@ -83,6 +118,7 @@ html {
font-size: 16px;
position: relative;
border-radius: 8px;
max-width: 75%;
}
.mx_ImageView {
@@ -121,4 +157,4 @@ html {
font-weight: bold;
font-size: 20px;
line-height: 1.4;
}
}

View File

@@ -1,7 +1,6 @@
.mx_RoomDropTarget,
.mx_RoomList_favourites_label,
.mx_RoomList_archive_label,
.mx_LeftPanel_hideButton,
.mx_RoomHeader_search,
.mx_RoomSettings_encrypt,
.mx_CreateRoom_encrypt,

View File

@@ -14,18 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
.mx_MImageTile_thumbnail {
/*
background-color: #fff;
border: 2px solid #fff;
border-radius: 1px;
*/
}
var React = require('react');
.mx_MImageTile_download {
color: #80cef4;
cursor: pointer;
}
module.exports = {
propTypes: {
room: React.PropTypes.object.isRequired,
},
.mx_MImageTile_download a {
color: #80cef4;
text-decoration: none;
}
getInitialState: function() {
return {
power_levels_changed: false
};
}
};
.mx_MImageTile_download img {
padding-right: 8px;
}

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