1
0

Compare commits

..

201 Commits

Author SHA1 Message Date
Erik Johnston
704e7e9f44 Merge branch 'release-v0.3.0' of github.com:matrix-org/synapse 2014-09-18 13:05:07 +01:00
Erik Johnston
c58f7f293d Merge branch 'develop' of github.com:matrix-org/synapse into release-v0.3.0 2014-09-18 12:03:30 +01:00
Erik Johnston
19095552aa Update Change log 2014-09-18 12:03:08 +01:00
Kegan Dougal
a64ff63a41 SYWEB-3 : Boldify if the join_rule is public, rather than visibility so it plays nicer with federation. 2014-09-18 12:02:52 +01:00
Erik Johnston
17db2b27bf Update version in UPGRADE 2014-09-18 12:02:52 +01:00
Kegan Dougal
ac8d73b258 Patch for SYWEB-40 : isStateEvent is not being set correctly, and really shouldn't be a configurable arg in the first place. As a result of being undefined, the events.rooms[rid].members object was not being updated in some cases, which combined with the recents-filter bug (32808e4), caused federated rooms to not appear in the recents list. 2014-09-18 12:02:52 +01:00
Kegan Dougal
a6f5c88b47 Still add the room to the filtered list even if you can't work out the number of users in the room. 2014-09-18 12:02:51 +01:00
David Baker
1c0408de08 unbreak calls in firefox 2014-09-18 11:59:27 +01:00
Kegan Dougal
9cebfd9d90 SYWEB-3 : Boldify if the join_rule is public, rather than visibility so it plays nicer with federation. 2014-09-18 11:35:59 +01:00
Kegan Dougal
fbf221ae6d Patch for SYWEB-40 : isStateEvent is not being set correctly, and really shouldn't be a configurable arg in the first place. As a result of being undefined, the events.rooms[rid].members object was not being updated in some cases, which combined with the recents-filter bug (32808e4), caused federated rooms to not appear in the recents list. 2014-09-18 10:35:44 +01:00
Kegan Dougal
32808e4111 Still add the room to the filtered list even if you can't work out the number of users in the room. 2014-09-18 10:05:34 +01:00
Matthew Hodgson
9f94f9de48 freenode verification 2014-09-17 23:53:53 +01:00
David Baker
575852e6b5 add note to upgrade.rst about web client spec breaking change. 2014-09-17 17:51:22 +01:00
Erik Johnston
10b4291b54 Bump versions 2014-09-17 17:49:01 +01:00
Kegan Dougal
aeaeceb92c Create room entries for public rooms too so their public state is transferred over correctly when you join it. 2014-09-17 16:38:40 +01:00
Kegan Dougal
16f55d4275 webclient SYWEB-3 : Public rooms are bold. Can't think of a nicer way which doesn't clutter the recents list. 2014-09-17 16:38:40 +01:00
Emmanuel ROHEE
246f5d2e20 SYWEB-30: BF: When switching between rooms, pagination flickered between the top of the room before jumping to the bottom of the page 2014-09-17 17:13:07 +02:00
Kegan Dougal
c707b7d128 SYWEB-3 : Added 'visibility' key to rooms returned via /initialSync 2014-09-17 16:09:07 +01:00
Emmanuel ROHEE
65c37cc852 SYWEB-22: Format emote('/me') messages correctly in desktop notification 2014-09-17 16:13:09 +02:00
Erik Johnston
b6818fd4d2 SYN-40: When a user updates their displayname or avatar update all their join events for all the rooms they are currently in. 2014-09-17 15:05:14 +01:00
Emmanuel ROHEE
fe7af80198 BF: edit the actual room name not the displayed room name (which has been computed) 2014-09-17 15:46:12 +02:00
Emmanuel ROHEE
9aed6a06cf SYWEB-15: Always show the room alias as well as its name in the UI 2014-09-17 15:38:20 +02:00
Emmanuel ROHEE
b3a0961c6c SYWEB-7: Use sessionStorage to make per-room history survives when the user navigates through rooms 2014-09-17 14:38:33 +02:00
Emmanuel ROHEE
d9a9a47075 SYWEB-7: Up & down keys let user step through the history as per readline or xchat 2014-09-17 14:18:39 +02:00
Emmanuel ROHEE
f9bb000ccf WEB-35: joins/parts should trigger desktop notifications 2014-09-17 09:41:21 +02:00
Kegan Dougal
d6c0cff3bd Bugfix when content isn't a string. 2014-09-16 16:31:16 +01:00
Kegan Dougal
95e171e19a Don't bing for sent messages. Handle cases where the member is unknown rather than erroring out. 2014-09-16 16:23:20 +01:00
Kegan Dougal
d7b206cc93 Added basic RegExp support. 2014-09-16 16:10:48 +01:00
Emmanuel ROHEE
06dfbdf7c8 WEB-27: We don't need to show the user-count in Recents in the room sidepanel - takes up too much room 2014-09-16 17:07:47 +02:00
Kegan Dougal
3395a3305f Bing on all the things if there are 0 bing words. 2014-09-16 15:47:29 +01:00
Kegan Dougal
5aaa3c09c1 hidden/minimise/focus disaster disclaimer with the TODO 2014-09-16 15:35:23 +01:00
Kegan Dougal
b36a0c71d1 Added utility function containsBingWord and hook up some css to it. 2014-09-16 15:35:23 +01:00
Kegan Dougal
a402e0c5e6 Added bing detection logic. Persist the display name of the user in localstorage for use when binging. 2014-09-16 15:35:23 +01:00
Kegan Dougal
660364d6a7 Move the notification logic out of an individual room controller and into the general event handler, so we can notify for >1 room. 2014-09-16 15:35:23 +01:00
Kegan Dougal
b170fe921e Added a section on bing words if you enable desktop notifications. 2014-09-16 15:35:23 +01:00
David Baker
84372cef4a Time out calls from both ends properly. 2014-09-16 15:26:22 +01:00
Emmanuel ROHEE
890178cf25 Fixed scroll flickering when opening the room 2014-09-16 16:16:11 +02:00
Emmanuel ROHEE
a284de73e6 If an initialSync has been already done on a room, we do not need to paginate back to get more messages 2014-09-16 16:16:11 +02:00
Emmanuel ROHEE
45592ccdfd WEB-29: Improve room page content loading
InitialSync: load the 30 last messages of each room so that a full page of messages can be displayed without additionnal request
2014-09-16 16:16:11 +02:00
David Baker
f4094c5eb3 Update spec with the lifetime field. 2014-09-16 14:54:52 +01:00
David Baker
dd2b933a0d Use event age to recognise which calls are current and which aren't and hence support answering calls that were placed before we loaded the page. 2014-09-16 14:47:10 +01:00
Kegan Dougal
c099b36af3 Comment out password reset for now, until the mechanism is fully discussed (IS token auth vs HS auth) 2014-09-16 13:32:33 +01:00
Kegan Dougal
cc83b06cd1 Added support for the HS to send emails. Use it to send password resets. Added email_smtp_server and email_from_address config args. Added emailutils. 2014-09-16 12:36:39 +01:00
Kegan Dougal
5f30a69a9e Added PasswordResetRestServlet. Hit the IS to confirm the email/user. Need to send email. 2014-09-16 11:22:40 +01:00
Emmanuel ROHEE
faee41c303 Merge remote-tracking branch 'origin/develop' into webclient_data_centralisation 2014-09-16 08:50:53 +02:00
Erik Johnston
1e4b971f95 Fix bug where we didn't always get 'prev_content' key 2014-09-15 17:43:46 +01:00
Emmanuel ROHEE
b0483cd47d Filter room where the user has been banned 2014-09-15 18:22:38 +02:00
Erik Johnston
40d2f38abe Fix bug where we incorrectly calculated 'age_ts' from 'age' key rather than the reverse. Don't transmit age_ts to clients for now. 2014-09-15 16:55:39 +01:00
Erik Johnston
59516a8bb1 Correctly handle receiving 'missing' Pdus from federation, rather than just discarding them. 2014-09-15 16:40:44 +01:00
Emmanuel ROHEE
8aa4b7bf7f Recents must not show temporary fake messages 2014-09-15 17:31:07 +02:00
Erik Johnston
e639a3516d Improve logging in federation handler. 2014-09-15 16:24:03 +01:00
Erik Johnston
0897a09f49 Fix unit tests after adding extra argument on put_json 2014-09-15 16:24:03 +01:00
Erik Johnston
6ac0b4ade8 Fix 'age' key to update on retries 2014-09-15 16:24:03 +01:00
Kegan Dougal
34d7896b06 More helpful 400 error messages. 2014-09-15 16:05:51 +01:00
Kegan Dougal
688c37ebf4 Updated CHANGES and UPGRADE to reflect registration API changes. 2014-09-15 15:53:05 +01:00
Kegan Dougal
2c00e1ecd9 Be consistent when associating keys with login types for registration/login. 2014-09-15 15:38:29 +01:00
Emmanuel ROHEE
42f5b0a6b8 Recents uses data directly from $rootscope.events 2014-09-15 16:31:59 +02:00
Kegan Dougal
14bc4ed59f Merge branch 'develop' of github.com:matrix-org/synapse into registration-api-changes in preparation for re-merge to develop. 2014-09-15 15:27:58 +01:00
Kegan Dougal
0b8a3bc3b9 Update spec to include m.login.email.identity 2014-09-15 15:27:17 +01:00
Kegan Dougal
c04caff55c Fix unit tests. 2014-09-15 15:14:19 +01:00
Kegan Dougal
7f23425e59 Updated cmdclient to use new registration logic. 2014-09-15 15:09:21 +01:00
Kegan Dougal
04fbda46dd Make captcha work again with the new registration logic. 2014-09-15 14:52:39 +01:00
Kegan Dougal
d821755b49 Updated webclient to support the new registration logic. 2014-09-15 14:31:53 +01:00
Erik Johnston
5bd9369a62 Correctly handle the 'age' key in events and pdus 2014-09-15 13:26:11 +01:00
Kegan Dougal
285ecaacd0 Split out password/captcha/email logic. 2014-09-15 12:42:36 +01:00
Kegan Dougal
34878bc26a Added LoginType constants. Created general structure for processing registrations. 2014-09-15 10:23:20 +01:00
Emmanuel ROHEE
76217890c0 BF: inviter field has moved to the room root object 2014-09-15 11:14:10 +02:00
Kegan Dougal
bf6fa6dd3d Merge branch 'develop' of github.com:matrix-org/synapse into registration-api-changes 2014-09-15 09:46:33 +01:00
Emmanuel ROHEE
a9da2ec895 BF: presence and eventMap were not reset at logout. 2014-09-15 10:39:30 +02:00
Emmanuel ROHEE
f3d3441d02 Use "white-space: pre-wrap" for "Text will wrap when necessary, and on line breaks" 2014-09-15 10:22:57 +02:00
Emmanuel ROHEE
3292f70071 Merge remote-tracking branch 'origin/master' into develop 2014-09-15 10:08:47 +02:00
Matthew Hodgson
49b5dd56b5 unbreak wordwrapping by breaking multiline paste for now 2014-09-13 11:38:45 +01:00
Matthew Hodgson
32acb7e903 always scroll to bottom when entering a room 2014-09-13 11:35:36 +01:00
Matthew Hodgson
276b9f1839 more wishlist 2014-09-13 11:26:16 +01:00
Paul "LeoNerd" Evans
7a77aabb4b Define a CLOS-like 'around' modifier as a decorator, to neaten up the 'orig_*' noise of wrapping the setUp()/tearDown() methods 2014-09-12 19:07:29 +01:00
Paul "LeoNerd" Evans
aeb69c0f8c Add some docstrings 2014-09-12 18:46:13 +01:00
Paul "LeoNerd" Evans
d9f3f322c5 Additionally look first for a 'loglevel' attribute on the running test method, before the TestCase 2014-09-12 18:46:13 +01:00
Paul "LeoNerd" Evans
33c4dd4c2d Define a (class) decorator for easily setting a DEBUG logging level on a TestCase 2014-09-12 18:46:13 +01:00
Paul "LeoNerd" Evans
ca8349a897 Allow a TestCase to set a 'loglevel' attribute, which overrides the logging level while that testcase runs 2014-09-12 18:46:13 +01:00
Paul "LeoNerd" Evans
cd62ee3f29 Have all unit tests import from our own subclass of trial's unittest TestCase; set up logging in ONE PLACE ONLY 2014-09-12 18:46:13 +01:00
Erik Johnston
958b52596c Update CHANGES.rst 2014-09-12 18:36:45 +01:00
Erik Johnston
c7bcd87f37 Merge branch 'master' of github.com:matrix-org/synapse into develop 2014-09-12 18:27:44 +01:00
Erik Johnston
80852d1135 Spellcheck 2014-09-12 18:27:04 +01:00
Erik Johnston
84326e2491 Add note about glare support 2014-09-12 18:26:19 +01:00
Erik Johnston
e3aec9bc81 Merge branch 'release-v0.2.3' of github.com:matrix-org/synapse
Conflicts:
	webclient/room/room-controller.js
2014-09-12 18:19:32 +01:00
David Baker
21b45d2a5b Update the spec document to replace the candidate message with the candidates message. 2014-09-12 18:19:19 +01:00
David Baker
842898df15 Send multiple candidates at once instead of all individually. Changes spec to include multiple candidates in a candidate(s) message. 2014-09-12 18:19:19 +01:00
Erik Johnston
afb7f173cf Bump version and change log 2014-09-12 18:13:05 +01:00
Erik Johnston
14975ce5bc Fix bug where we relied on the current_state_events being updated when we are handling type specific persistence 2014-09-12 17:57:02 +01:00
Erik Johnston
667e747ed1 Fix bug where we no longer stored user_id on Pdus 2014-09-12 17:56:21 +01:00
Erik Johnston
39e3fc69e5 Make the state resolution use actual power levels rather than taking them from a Pdu key. 2014-09-12 17:11:09 +01:00
Erik Johnston
b42fe05c51 Fix bug where we incorrectly removed a remote host from the list of hosts in a room when any user from that host left that room even if they weren't the last user from that host in that room 2014-09-12 17:11:09 +01:00
Erik Johnston
ca1ae7cf9b Fix bug where we didn't return a tuple when expected. 2014-09-12 17:11:09 +01:00
Emmanuel ROHEE
3ed39ad20e Clean data when user logs out 2014-09-12 17:43:35 +02:00
David Baker
cc2cee4af6 Retry sending events that fail to send. 2014-09-12 16:32:22 +01:00
Emmanuel ROHEE
6c81752e46 Fixed displayname resolution of emote sender 2014-09-12 17:01:49 +02:00
Emmanuel ROHEE
a2cd942a95 Fixed public room name and users count alignement
Put data into a table to ease layout and manage long strings
2014-09-12 16:46:20 +02:00
David Baker
09a59ce2d3 Some words about glare 2014-09-12 14:24:56 +01:00
David Baker
8b28f7d14e Always pick the incoming call if we've not yet sent out our invite, otherwise the remorte party will see their call get rejected and our call won't come in until our user clicks allow. 2014-09-12 14:06:35 +01:00
David Baker
a81ec21762 Remove the local AV stream from ourselves when handing it off to a new call or we'll close it when we hang up. 2014-09-12 11:51:57 +01:00
Emmanuel ROHEE
9819b3619e CSS m.room.topic and m.room.name events in the history 2014-09-12 11:56:08 +02:00
Emmanuel ROHEE
311dc61803 Handle NAME_EVENT to get room name update event
(TODO: recents needs to be directly plugged to $rootScope.events.rooms)
2014-09-12 10:51:05 +02:00
Emmanuel ROHEE
d934328904 Added edition of room name 2014-09-12 10:48:06 +02:00
Emmanuel ROHEE
6ea20f3503 Show room name updates in room history and recents.
Update it with the latest value
2014-09-12 10:12:56 +02:00
Emmanuel ROHEE
8b3ce85183 BF: temp workaround while /initialSync on a particular room is not available
initRoom on a new room is not called. Call it for any received events
2014-09-12 08:54:18 +02:00
David Baker
a059ca6915 few fixes for errors in glare conditions. still seem to end up with no audio if both calls are placed at the same time. 2014-09-11 19:16:57 +01:00
David Baker
1e05e30472 Put back the line that adds the stream to the invite, otherwise caller->callee audio won't work... 2014-09-11 18:59:22 +01:00
David Baker
81d061e74e Fix bug where web client wold break trying to add the earliest token without having initialised the room if your first page of history contained only events which didn't call initRoom. Just call initRoom in handleMessages since we use it there rather than leaving it to the individual event handling methods. 2014-09-11 17:40:38 +01:00
Emmanuel ROHEE
ceec607e7f Clearly show when an user cannot join a room.
In realtime show who kicked or banned him.
2014-09-11 16:54:57 +02:00
David Baker
fb082cf50f start towards glare support (currently not much better but no worse than before) including fixing a lot of self/var self/this fails that caused chaos when we started to have more than one call in play. 2014-09-11 15:24:18 +01:00
Emmanuel ROHEE
806c49a690 Added support of copy/paste of multi lines content 2014-09-11 15:46:24 +02:00
Emmanuel ROHEE
aa347b52ba Use autofill-event.js to workaround browsers issue: Form model doesn't update on autocomplete
https://github.com/angular/angular.js/issues/1460
2014-09-11 15:07:44 +02:00
Emmanuel ROHEE
6b20fef52a Invite: reset the input when the invitation has been done 2014-09-11 13:52:07 +02:00
Emmanuel ROHEE
c92740e8a9 Enable enter key in the invite input 2014-09-11 13:43:55 +02:00
Emmanuel ROHEE
cc049851d0 On member avatar mouseover, show user_id and power level 2014-09-11 12:01:44 +02:00
Emmanuel ROHEE
14a9652324 Room topic: if the request fails, show the error in the feedback 2014-09-11 11:53:37 +02:00
Emmanuel ROHEE
af44e9556d BF: made input autofocus work when opening the room topic input 2014-09-11 11:49:59 +02:00
Emmanuel ROHEE
7e7eb0efc1 Show room topic change in the chat history and in the recents 2014-09-11 11:31:24 +02:00
Emmanuel ROHEE
8dcb6f24b5 getRoomEventIndex: improved speed for what it is used 2014-09-11 09:11:24 +02:00
Emmanuel ROHEE
44998ca450 Merge remote-tracking branch 'origin/develop' into webclient_initialSync 2014-09-10 18:35:05 +02:00
Emmanuel ROHEE
7a153b5c94 Show echoed emote with transparency 2014-09-10 18:29:52 +02:00
Emmanuel ROHEE
5a06f5c5fc Reenabled transparent echo message. It turns to opaque without flickering now. 2014-09-10 18:24:03 +02:00
Emmanuel ROHEE
6d18b52931 Clean previous request feedback when doing a new request 2014-09-10 17:40:34 +02:00
Emmanuel ROHEE
81ecaf945d BF: Made /op work when providing no power value. 50 is used as default in this case 2014-09-10 17:37:51 +02:00
Emmanuel ROHEE
811716592c Made users count auto updating. Do show it if the info is not available (ex:user has not joined the room yet) 2014-09-10 16:46:06 +02:00
David Baker
e2d2d63bcd Animation on call end icon. 2014-09-10 15:45:09 +01:00
David Baker
dde7ec8e64 Upgrade angularjs to 1.3.0-rc1 since this is new development 2014-09-10 15:43:27 +01:00
Emmanuel ROHEE
30bfa911fc Member event: store use the the latest one 2014-09-10 16:26:11 +02:00
Emmanuel ROHEE
da3f842b8c Removed wrong comments about recents-controller.js: it uses $rootScope.rooms not $rootScope.events.rooms managed by event-handler-service.js and used by other controllers 2014-09-10 14:53:03 +02:00
Emmanuel ROHEE
130cbdd7af dedup events: state events conflict with messages events. Do not consider them in deduplication 2014-09-10 14:45:32 +02:00
Emmanuel ROHEE
b099634ba1 Reenabled handle of room states events in initialSync but do not add them to the displayed messages in the room page.
Show the m.room.member events only when they come from room.messages (from initialSync of pagination) not from room.state.
2014-09-10 14:36:30 +02:00
Emmanuel ROHEE
c2afc6cd0a Presence events do not have event id. Do not discard them 2014-09-10 13:48:33 +02:00
David Baker
80b5470663 Add text for incoming calls 2014-09-10 11:35:14 +01:00
David Baker
7411794fa1 Show mxid in call bar for users with no displayname 2014-09-10 11:21:20 +01:00
David Baker
55fe0d8adc Less buggy rejection of calls when busy 2014-09-10 11:12:02 +01:00
Emmanuel ROHEE
b63dd9506e Improved requests: pagination is done from the data received in initialSync 2014-09-10 12:01:00 +02:00
David Baker
6f256e6380 reject calls if there's already a call in progress 2014-09-10 10:32:05 +01:00
Kegan Dougal
2bd4346075 More rst formatting. 2014-09-09 15:13:50 -07:00
Kegan Dougal
f23e5b17b6 Extra restrictions to make parsing easier. 2014-09-09 15:11:06 -07:00
Kegan Dougal
56a358481e Tyops 2014-09-09 15:00:48 -07:00
Kegan Dougal
d5704cf2a3 Added initial draft for human-readable ID rules. 2014-09-09 14:53:35 -07:00
Kegan Dougal
550e8f32ac Move model to client-server for now. 2014-09-09 13:51:13 -07:00
David Baker
f90ce04a83 Hangup call if user denies media access. 2014-09-09 18:21:03 +01:00
David Baker
ccfb42e4ff Don't try setting up the call if the user has canceled it before allowing permission. 2014-09-09 17:58:26 +01:00
David Baker
25e96f82db Don't break if you press the hangup button before allowing media permission. 2014-09-09 17:52:01 +01:00
David Baker
253c327252 Don't play an engaged tone if we hang up locally. 2014-09-09 17:38:40 +01:00
Erik Johnston
a75f8686ba Fix bug where we used an unbound local variable if we ended up rolling back the persist_event transaction 2014-09-09 16:27:59 +01:00
Emmanuel ROHEE
1ef51e7939 Improved room page loading flow: do pagination only when the members list is available.
Killed an unexpected pagination trigger when the page load: paginateMore
2014-09-09 16:46:30 +02:00
Emmanuel ROHEE
746ed57c0e When the user has been kicked or banned from a room, remove the room from his recents list 2014-09-09 16:31:50 +02:00
Emmanuel ROHEE
5132fcdb8b Made recents list display something when joining a room which we do not have state data yet 2014-09-09 16:10:20 +02:00
Emmanuel ROHEE
332986ba43 BF: prevent joined messages to be displayed twice when joining a room.
Do this by synchronizing the m.room.member joined event from the events stream and the start of the pagination
2014-09-09 16:10:20 +02:00
David Baker
472b4fe48c make calls work in Firefox 2014-09-09 14:54:06 +01:00
Emmanuel ROHEE
fd2d3fcfd7 Removed historical code: recents does not need to manage presences. It is already done by initialSync in eventStreamService 2014-09-09 12:47:42 +02:00
Emmanuel ROHEE
967ac65586 BF: Made the grey background of the current room cover all the cell width 2014-09-09 12:47:42 +02:00
David Baker
16b40cbede Show call invites in the message table 2014-09-09 11:45:36 +01:00
Kegan Dougal
75890d7bdd CSS tweakage 2014-09-08 19:02:23 -07:00
Kegan Dougal
e8f19b4c0d Display a 'Set Topic' button if there is no topic or it's a 0-len string. 2014-09-08 18:59:26 -07:00
Kegan Dougal
6bdb23449a Add ability to set topic by double-clicking on the topic text then hitting enter. 2014-09-08 18:40:34 -07:00
Kegan Dougal
f64cc237fc Fixed bug which displayed an older room topic because it was being returned from /initialSync messages key. Check the ts of the event before clobbering state. 2014-09-08 17:27:51 -07:00
Kegan Dougal
ef2111099a long topic is long. CSS support it 2014-09-08 17:19:04 -07:00
Kegan Dougal
df50a6823f Display public room topics if they exist on the public room list. 2014-09-08 17:14:58 -07:00
Kegan Dougal
324020d5fe Display the room topic in the room, underneath the name of the room. 2014-09-08 15:36:52 -07:00
Kegan Dougal
544691ab05 Update jsfiddles to have more helpful error messages when there is no connection when logging in. 2014-09-08 14:54:10 -07:00
Erik Johnston
5236de5b03 Add slightly helpful advice on how to generate config if you don'y already have one 2014-09-08 22:52:50 +01:00
Erik Johnston
91b370650a Don't autogen config in synctl for the same reasons we don't turn of --generate-config by default on the homeserver - it is liable to confuse people who have moved the config file or have chosen a non standard location.
Also, don't override log file location.
2014-09-08 22:48:46 +01:00
Erik Johnston
e062f2dfa8 Apparently we can't do txn.rollback(), so raise and catch an exception instead. 2014-09-08 22:37:19 +01:00
Kegan Dougal
c1a25756c2 Added demo.details 2014-09-08 14:24:28 -07:00
Kegan Dougal
d692994ea4 Updated jsfiddle links to point to github 2014-09-08 14:16:22 -07:00
Kegan Dougal
a3590dfa26 Bodge to default to '1 users' when you create a room, which is better than blindly assuming a recents controller is writing to rootScope.rooms and setting numUsersInRoom there. 2014-09-08 14:01:34 -07:00
Kegan Dougal
da9b7b0368 Added big massive TODOs on a huge design problem with initial sync 2014-09-08 13:54:09 -07:00
Kegan Dougal
054fad5360 Float right the num users, apply room highlight to user count. 2014-09-08 13:28:55 -07:00
Kegan Dougal
e0954f3b36 Better checks are better. 2014-09-08 12:15:29 -07:00
Kegan Dougal
76fe7d4eba Added num_joined_users key to /publicRooms for each room. Show this information in the webclient. 2014-09-08 12:15:29 -07:00
Erik Johnston
942d8412c4 Handle the case where we don't have a common ancestor 2014-09-08 20:13:27 +01:00
Kegan Dougal
2eaa199e6a Added number of users in recent rooms. 2014-09-08 11:55:29 -07:00
Erik Johnston
83ce57302d Fix bug in state handling where we incorrectly identified a missing pdu. Update tests to catch this case. 2014-09-08 19:50:59 +01:00
Kegan Dougal
de727f854a Make #matrix public rooms bold to make them stand out from the other public rooms. Ideally this would be metadata in /publicRooms to say something like 'featured channel', but for now, just make it a client side check. 2014-09-08 11:33:12 -07:00
Kegan Dougal
0627366b2f Sort the public room list by display name. 2014-09-08 11:17:44 -07:00
Kegan Dougal
586e0df62d Updated spec and api docs to desired new format. 2014-09-08 11:07:52 -07:00
Erik Johnston
c0577ea87a Rollback if we try and insert duplicate events 2014-09-08 18:34:18 +01:00
Emmanuel ROHEE
d81e7dc00e Added /join description 2014-09-08 18:25:56 +02:00
Emmanuel ROHEE
9a5f224931 matrixService.rooms must be renamed matrixService.initialSync now 2014-09-08 18:21:41 +02:00
Emmanuel ROHEE
21d6ce2380 App startup improvements:
- do one and only one initialSync when the app starts. (recents-controller does not do its own anymore)
 - initialSync: get only the last message per room instead of default number of messages (10)

Prevent recents-controller from loosing its data each time the page URL changes
2014-09-08 18:14:35 +02:00
David Baker
972f664b6b add sounds to the calling interface 2014-09-08 16:10:36 +01:00
Emmanuel ROHEE
1dc4ad1efa Merge branch 'origin/release-v0.2.2' into develop 2014-09-08 11:29:47 +02:00
Matthew Hodgson
a0a609e8af fix embarassing bug where in-progress messages get vaped when the previous one gets delivered 2014-09-08 11:28:51 +02:00
Matthew Hodgson
dc1f202eca fix desktop notifs, which were broken in eab463fd 2014-09-08 11:28:51 +02:00
Kegan Dougal
ce5cd2202f Center recaptcha dialog. 2014-09-08 11:28:51 +02:00
Erik Johnston
2df5cb114d Remove disabled change from CHANGES 2014-09-08 11:28:50 +02:00
Matthew Hodgson
ef0304beff disable broken event dup suppression, and fix echo for /me 2014-09-08 11:28:50 +02:00
Kegan Dougal
dd2ae64120 Set the room_alias field when we encounter a new one, rather than only from local storage. 2014-09-08 11:28:50 +02:00
Kegan Dougal
cde6bdfa77 Use the room_display_name when presenting on the home page, and not the room_alias which may not be set. 2014-09-08 11:28:50 +02:00
Kegan Dougal
f397b2264c https when loading recaptcha js 2014-09-08 11:28:50 +02:00
Erik Johnston
768ff1a850 Fix race in presence handler where we evicted things from cache while handling a key therein 2014-09-08 11:28:50 +02:00
Erik Johnston
7735aad9d6 Bump version and changelog 2014-09-08 11:28:50 +02:00
Kegan Dougal
7bff9b6269 Minor spec tweaks. 2014-09-08 11:28:50 +02:00
Emmanuel ROHEE
24f0bb4af5 Revert "BF: Made notification work again (forgot to renamed "offline" to "unavailable")"
This reverts commit c3f9d8e41b.
2014-09-08 11:09:14 +02:00
Emmanuel ROHEE
c3f9d8e41b BF: Made notification work again (forgot to renamed "offline" to "unavailable") 2014-09-08 10:28:07 +02:00
Matthew Hodgson
64b6f09b0d fix embarassing bug where in-progress messages get vaped when the previous one gets delivered 2014-09-06 17:48:16 -07:00
Erik Johnston
a73104b566 Merge branch 'release-v0.2.2' of github.com:matrix-org/synapse 2014-09-06 18:28:24 +01:00
Matthew Hodgson
41907209bb fix desktop notifs, which were broken in eab463fd 2014-09-06 10:26:41 -07:00
109 changed files with 9787 additions and 4392 deletions

View File

@@ -1,3 +1,64 @@
Changes in synapse 0.3.0 (2014-09-18)
=====================================
See UPGRADE for information about changes to the client server API, including
breaking backwards compatibility with VoIP calls and registration API.
Homeserver:
* When a user changes their displayname or avatar the server will now update
all their join states to reflect this.
* The server now adds "age" key to events to indicate how old they are. This
is clock independent, so at no point does any server or webclient have to
assume their clock is in sync with everyone else.
* Fix bug where we didn't correctly pull in missing PDUs.
* Fix bug where prev_content key wasn't always returned.
* Add support for password resets.
Webclient:
* Improve page content loading.
* Join/parts now trigger desktop notifications.
* Always show room aliases in the UI if one is present.
* No longer show user-count in the recents side panel.
* Add up & down arrow support to the text box for message sending to step
through your sent history.
* Don't display notifications for our own messages.
* Emotes are now formatted correctly in desktop notifications.
* The recents list now differentiates between public & private rooms.
* Fix bug where when switching between rooms the pagination flickered before
the view jumped to the bottom of the screen.
* Add support for password resets.
* Add bing word support.
Registration API:
* The registration API has been overhauled to function like the login API. In
practice, this means registration requests must now include the following:
'type':'m.login.password'. See UPGRADE for more information on this.
* The 'user_id' key has been renamed to 'user' to better match the login API.
* There is an additional login type: 'm.login.email.identity'.
* The command client and web client have been updated to reflect these changes.
Changes in synapse 0.2.3 (2014-09-12)
=====================================
Homeserver:
* Fix bug where we stopped sending events to remote home servers if a
user from that home server left, even if there were some still in the
room.
* Fix bugs in the state conflict resolution where it was incorrectly
rejecting events.
Webclient:
* Display room names and topics.
* Allow setting/editing of room names and topics.
* Display information about rooms on the main page.
* Handle ban and kick events in real time.
* VoIP UI and reliability improvements.
* Add glare support for VoIP.
* Improvements to initial startup speed.
* Don't display duplicate join events.
* Local echo of messages.
* Differentiate sending and sent of local echo.
* Various minor bug fixes.
Changes in synapse 0.2.2 (2014-09-06)
=====================================

View File

@@ -1,3 +1,34 @@
Upgrading to v0.3.0
===================
This registration API now closely matches the login API. This introduces a bit
more backwards and forwards between the HS and the client, but this improves
the overall flexibility of the API. You can now GET on /register to retrieve a list
of valid registration flows. Upon choosing one, they are submitted in the same
way as login, e.g::
{
type: m.login.password,
user: foo,
password: bar
}
The default HS supports 2 flows, with and without Identity Server email
authentication. Enabling captcha on the HS will add in an extra step to all
flows: ``m.login.recaptcha`` which must be completed before you can transition
to the next stage. There is a new login type: ``m.login.email.identity`` which
contains the ``threepidCreds`` key which were previously sent in the original
register request. For more information on this, see the specification.
Web Client
----------
The VoIP specification has changed between v0.2.0 and v0.3.0. Users should
refresh any browser tabs to get the latest web client code. Users on
v0.2.0 of the web client will not be able to call those on v0.3.0 and
vice versa.
Upgrading to v0.2.0
===================

View File

@@ -1 +1 @@
0.2.2
0.3.0

View File

@@ -5,3 +5,5 @@ Broad-sweeping stuff which would be nice to have
- homeserver implementation in go
- homeserver implementation in node.js
- client SDKs
- libpurple library
- irssi plugin?

View File

@@ -145,35 +145,50 @@ class SynapseCmd(cmd.Cmd):
<noupdate> : Do not automatically clobber config values.
"""
args = self._parse(line, ["userid", "noupdate"])
path = "/register"
password = None
pwd = None
pwd2 = "_"
while pwd != pwd2:
pwd = getpass.getpass("(Optional) Type a password for this user: ")
if len(pwd) == 0:
print "Not using a password for this user."
break
pwd = getpass.getpass("Type a password for this user: ")
pwd2 = getpass.getpass("Retype the password: ")
if pwd != pwd2:
if pwd != pwd2 or len(pwd) == 0:
print "Password mismatch."
pwd = None
else:
password = pwd
body = {}
body = {
"type": "m.login.password"
}
if "userid" in args:
body["user_id"] = args["userid"]
body["user"] = args["userid"]
if password:
body["password"] = password
reactor.callFromThread(self._do_register, "POST", path, body,
reactor.callFromThread(self._do_register, body,
"noupdate" not in args)
@defer.inlineCallbacks
def _do_register(self, method, path, data, update_config):
url = self._url() + path
json_res = yield self.http_client.do_request(method, url, data=data)
def _do_register(self, data, update_config):
# check the registration flows
url = self._url() + "/register"
json_res = yield self.http_client.do_request("GET", url)
print json.dumps(json_res, indent=4)
passwordFlow = None
for flow in json_res["flows"]:
if flow["type"] == "m.login.recaptcha" or ("stages" in flow and "m.login.recaptcha" in flow["stages"]):
print "Unable to register: Home server requires captcha."
return
if flow["type"] == "m.login.password" and "stages" not in flow:
passwordFlow = flow
break
if not passwordFlow:
return
json_res = yield self.http_client.do_request("POST", url, data=data)
print json.dumps(json_res, indent=4)
if update_config and "user_id" in json_res:
self.config["user"] = json_res["user_id"]

View File

@@ -24,7 +24,7 @@ If you already have an account, you must **login** into it.
`Try out the fiddle`__
.. __: http://jsfiddle.net/4q2jyxng/
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/register_login
Registration
------------
@@ -87,7 +87,7 @@ user and **send a message** to that room.
`Try out the fiddle`__
.. __: http://jsfiddle.net/zL3zto9g/
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/create_room_send_msg
Creating a room
---------------
@@ -137,7 +137,7 @@ join a room **via a room alias** if one was set up.
`Try out the fiddle`__
.. __: http://jsfiddle.net/7fhotf1b/
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/room_memberships
Inviting a user to a room
-------------------------
@@ -183,7 +183,7 @@ of getting events, depending on what the client already knows.
`Try out the fiddle`__
.. __: http://jsfiddle.net/vw11mg37/
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/event_stream
Getting all state
-----------------
@@ -633,4 +633,4 @@ application.
`Try out the fiddle`__
.. __: http://jsfiddle.net/uztL3yme/
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/example_app

View File

@@ -3,35 +3,38 @@
"apis": [
{
"operations": [
{
"method": "GET",
"nickname": "get_registration_info",
"notes": "All login stages MUST be mentioned if there is >1 login type.",
"summary": "Get the login mechanism to use when registering.",
"type": "RegistrationFlows"
},
{
"method": "POST",
"nickname": "register",
"notes": "Volatile: This API is likely to change.",
"nickname": "submit_registration",
"notes": "If this is part of a multi-stage registration, there MUST be a 'session' key.",
"parameters": [
{
"description": "A registration request",
"description": "A registration submission",
"name": "body",
"paramType": "body",
"required": true,
"type": "RegistrationRequest"
"type": "RegistrationSubmission"
}
],
"responseMessages": [
{
"code": 400,
"message": "No JSON object."
"message": "Bad login type"
},
{
"code": 400,
"message": "User ID must only contain characters which do not require url encoding."
},
{
"code": 400,
"message": "User ID already taken."
"message": "Missing JSON keys"
}
],
"summary": "Register with the home server.",
"type": "RegistrationResponse"
"summary": "Submit a registration action.",
"type": "RegistrationResult"
}
],
"path": "/register"
@@ -42,30 +45,68 @@
"application/json"
],
"models": {
"RegistrationResponse": {
"id": "RegistrationResponse",
"RegistrationFlows": {
"id": "RegistrationFlows",
"properties": {
"access_token": {
"description": "The access token for this user.",
"type": "string"
"flows": {
"description": "A list of valid registration flows.",
"type": "array",
"items": {
"$ref": "RegistrationInfo"
}
}
}
},
"RegistrationInfo": {
"id": "RegistrationInfo",
"properties": {
"stages": {
"description": "Multi-stage registration only: An array of all the login types required to registration.",
"items": {
"$ref": "string"
},
"type": "array"
},
"user_id": {
"description": "The fully-qualified user ID.",
"type": "string"
},
"home_server": {
"description": "The name of the home server.",
"type": {
"description": "The first login type that must be used when logging in.",
"type": "string"
}
}
},
"RegistrationRequest": {
"id": "RegistrationRequest",
"RegistrationResult": {
"id": "RegistrationResult",
"properties": {
"access_token": {
"description": "The access token for this user's registration if this is the final stage of the registration process.",
"type": "string"
},
"user_id": {
"description": "The desired user ID. If not specified, a random user ID will be allocated.",
"type": "string",
"required": false
"description": "The user's fully-qualified user ID.",
"type": "string"
},
"next": {
"description": "Multi-stage registration only: The next registration type to submit.",
"type": "string"
},
"session": {
"description": "Multi-stage registration only: The session token to send when submitting the next registration type.",
"type": "string"
}
}
},
"RegistrationSubmission": {
"id": "RegistrationSubmission",
"properties": {
"type": {
"description": "The type of registration being submitted.",
"type": "string"
},
"session": {
"description": "Multi-stage registration only: The session token from an earlier registration stage.",
"type": "string"
},
"_registration_type_defined_keys_": {
"description": "Keys as defined by the specified registration type, e.g. \"user\", \"password\""
}
}
}

1
docs/freenode.txt Normal file
View File

@@ -0,0 +1 @@
NCjcRSEG

79
docs/human-id-rules.rst Normal file
View File

@@ -0,0 +1,79 @@
This document outlines the format for human-readable IDs within matrix.
Overview
--------
UTF-8 is quickly becoming the standard character encoding set on the web. As
such, Matrix requires that all strings MUST be encoded as UTF-8. However,
using Unicode as the character set for human-readable IDs is troublesome. There
are many different characters which appear identical to each other, but would
identify different users. In addition, there are non-printable characters which
cannot be rendered by the end-user. This opens up a security vulnerability with
phishing/spoofing of IDs, commonly known as a homograph attack.
Web browers encountered this problem when International Domain Names were
introduced. A variety of checks were put in place in order to protect users. If
an address failed the check, the raw punycode would be displayed to disambiguate
the address. Similar checks are performed by home servers in Matrix. However,
Matrix does not use punycode representations, and so does not show raw punycode
on a failed check. Instead, home servers must outright reject these misleading
IDs.
Types of human-readable IDs
---------------------------
There are two main human-readable IDs in question:
- Room aliases
- User IDs
Room aliases look like ``#localpart:domain``. These aliases point to opaque
non human-readable room IDs. These pointers can change, so there is already an
issue present with the same ID pointing to a different destination at a later
date.
User IDs look like ``@localpart:domain``. These represent actual end-users, and
unlike room aliases, there is no layer of indirection. This presents a much
greater concern with homograph attacks.
Checks
------
- Similar to web browsers.
- blacklisted chars (e.g. non-printable characters)
- mix of language sets from 'preferred' language not allowed.
- Language sets from CLDR dataset.
- Treated in segments (localpart, domain)
- Additional restrictions for ease of processing IDs.
- Room alias localparts MUST NOT have ``#`` or ``:``.
- User ID localparts MUST NOT have ``@`` or ``:``.
Rejecting
---------
- Home servers MUST reject room aliases which do not pass the check, both on
GETs and PUTs.
- Home servers MUST reject user ID localparts which do not pass the check, both
on creation and on events.
- Any home server whose domain does not pass this check, MUST use their punycode
domain name instead of the IDN, to prevent other home servers rejecting you.
- Error code is ``M_FAILED_HUMAN_ID_CHECK``. (generic enough for both failing
due to homograph attacks, and failing due to including ``:`` s, etc)
- Error message MAY go into further information about which characters were
rejected and why.
- Error message SHOULD contain a ``failed_keys`` key which contains an array
of strings which represent the keys which failed the check e.g::
failed_keys: [ user_id, room_alias ]
Other considerations
--------------------
- Basic security: Informational key on the event attached by HS to say "unsafe
ID". Problem: clients can just ignore it, and since it will appear only very
rarely, easy to forget when implementing clients.
- Moderate security: Requires client handshake. Forces clients to implement
a check, else they cannot communicate with the misleading ID. However, this is
extra overhead in both client implementations and round-trips.
- High security: Outright rejection of the ID at the point of creation /
receiving event. Point of creation rejection is preferable to avoid the ID
entering the system in the first place. However, malicious HSes can just allow
the ID. Hence, other home servers must reject them if they see them in events.
Client never sees the problem ID, provided the HS is correctly implemented.
- High security decided; client doesn't need to worry about it, no additional
protocol complexity aside from rejection of an event.

View File

@@ -1169,8 +1169,14 @@ This event is sent by the caller when they wish to establish a call.
Required keys:
- ``call_id`` : "string" - A unique identifier for the call
- ``offer`` : "offer object" - The session description
- ``version`` : "integer" - The version of the VoIP specification this message
adheres to. This specification is version 0.
- ``version`` : "integer" - The version of the VoIP specification this
message adheres to. This specification is
version 0.
- ``lifetime`` : "integer" - The time in milliseconds that the invite is
valid for. Once the invite age exceeds this
value, clients should discard it. They
should also no longer show the call as
awaiting an answer in the UI.
Optional keys:
None.
@@ -1182,16 +1188,16 @@ This event is sent by the caller when they wish to establish a call.
- ``type`` : "string" - The type of session description, in this case 'offer'
- ``sdp`` : "string" - The SDP text of the session description
``m.call.candidate``
``m.call.candidates``
This event is sent by callers after sending an invite and by the callee after answering.
Its purpose is to give the other party an additional ICE candidate to try using to
Its purpose is to give the other party additional ICE candidates to try using to
communicate.
Required keys:
- ``call_id`` : "string" - The ID of the call this event relates to
- ``version`` : "integer" - The version of the VoIP specification this messages
adheres to. his specification is version 0.
- ``candidate`` : "candidate object" - Object describing the candidate.
- ``candidates`` : "array of candidate objects" - Array of object describing the candidates.
``Candidate Object``
@@ -1249,7 +1255,33 @@ Or a rejected call:
<------- m.call.hangup
Calls are negotiated according to the WebRTC specification.
Glare
-----
This specification aims to address the problem of two users calling each other
at roughly the same time and their invites crossing on the wire. It is a far
better experience for the users if their calls are connected if it is clear
that their intention is to set up a call with one another.
In Matrix, calls are to rooms rather than users (even if those rooms may only
contain one other user) so we consider calls which are to the same room.
The rules for dealing with such a situation are as follows:
- If an invite to a room is received whilst the client is preparing to send an
invite to the same room, the client should cancel its outgoing call and
instead automatically accept the incoming call on behalf of the user.
- If an invite to a room is received after the client has sent an invite to the
same room and is waiting for a response, the client should perform a
lexicographical comparison of the call IDs of the two calls and use the
lesser of the two calls, aborting the greater. If the incoming call is the
lesser, the client should accept this call on behalf of the user.
The call setup should appear seamless to the user as if they had simply placed
a call and the other party had accepted. Thusly, any media stream that had been
setup for use on a call should be transferred and used for the call that
replaces it.
Profiles
========
@@ -1279,12 +1311,6 @@ display name other than it being a valid unicode string.
Registration and login
======================
.. WARNING::
The registration API is likely to change.
.. TODO
- TODO Kegan : Make registration like login (just omit the "user" key on the
initial request?)
Clients must register with a home server in order to use Matrix. After
registering, the client will be given an access token which must be used in ALL
@@ -1297,9 +1323,11 @@ a token sent to their email address, etc. This specification does not define how
home servers should authorise their users who want to login to their existing
accounts, but instead defines the standard interface which implementations
should follow so that ANY client can login to ANY home server. Clients login
using the |login|_ API.
using the |login|_ API. Clients register using the |register|_ API. Registration
follows the same procedure as login, but the path requests are sent to are
different.
The login process breaks down into the following:
The registration/login process breaks down into the following:
1. Determine the requirements for logging in.
2. Submit the login stage credentials.
3. Get credentials or be told the next stage in the login process and repeat
@@ -1357,7 +1385,7 @@ This specification defines the following login types:
- ``m.login.oauth2``
- ``m.login.email.code``
- ``m.login.email.url``
- ``m.login.email.identity``
Password-based
--------------
@@ -1505,6 +1533,31 @@ If the link has not been visited yet, a standard error response with an errcode
``M_LOGIN_EMAIL_URL_NOT_YET`` should be returned.
Email-based (identity server)
-----------------------------
:Type:
``m.login.email.identity``
:Description:
Login is supported by authorising an email address with an identity server.
Prior to submitting this, the client should authenticate with an identity server.
After authenticating, the session information should be submitted to the home server.
To respond to this type, reply with::
{
"type": "m.login.email.identity",
"threepidCreds": [
{
"sid": "<identity server session id>",
"clientSecret": "<identity server client secret>",
"idServer": "<url of identity server authed with, e.g. 'matrix.org:8090'>"
}
]
}
N-Factor Authentication
-----------------------
Multiple login stages can be combined to create N-factor authentication during login.
@@ -2216,6 +2269,9 @@ Transaction:
.. |login| replace:: ``/login``
.. _login: /docs/api/client-server/#!/-login
.. |register| replace:: ``/register``
.. _register: /docs/api/client-server/#!/-registration
.. |/rooms/<room_id>/messages| replace:: ``/rooms/<room_id>/messages``
.. _/rooms/<room_id>/messages: /docs/api/client-server/#!/-rooms/get_messages

View File

@@ -19,7 +19,12 @@ $('.login').live('click', function() {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});

View File

@@ -58,7 +58,12 @@ $('.login').live('click', function() {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});

View File

@@ -0,0 +1,7 @@
name: Example Matrix Client
description: Includes login, live event streaming, creating rooms, sending messages and viewing member lists.
authors:
- matrix.org
resources:
- http://matrix.org
normalize_css: no

View File

@@ -20,7 +20,12 @@ $('.register').live('click', function() {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
@@ -36,7 +41,12 @@ var login = function(user, password) {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
};

View File

@@ -28,7 +28,12 @@ $('.login').live('click', function() {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});

View File

@@ -16,4 +16,4 @@
""" This is a reference implementation of a synapse home server.
"""
__version__ = "0.2.2"
__version__ = "0.3.0"

View File

@@ -50,3 +50,12 @@ class JoinRules(object):
KNOCK = u"knock"
INVITE = u"invite"
PRIVATE = u"private"
class LoginType(object):
PASSWORD = u"m.login.password"
OAUTH = u"m.login.oauth2"
EMAIL_CODE = u"m.login.email.code"
EMAIL_URL = u"m.login.email.url"
EMAIL_IDENTITY = u"m.login.email.identity"
RECAPTCHA = u"m.login.recaptcha"

View File

@@ -17,6 +17,19 @@ from synapse.api.errors import SynapseError, Codes
from synapse.util.jsonobject import JsonEncodedObject
def serialize_event(hs, e):
# FIXME(erikj): To handle the case of presence events and the like
if not isinstance(e, SynapseEvent):
return e
d = e.get_dict()
if "age_ts" in d:
d["age"] = int(hs.get_clock().time_msec()) - d["age_ts"]
del d["age_ts"]
return d
class SynapseEvent(JsonEncodedObject):
"""Base class for Synapse events. These are JSON objects which must abide
@@ -43,6 +56,8 @@ class SynapseEvent(JsonEncodedObject):
"content", # HTTP body, JSON
"state_key",
"required_power_level",
"age_ts",
"prev_content",
]
internal_keys = [
@@ -158,10 +173,6 @@ class SynapseEvent(JsonEncodedObject):
class SynapseStateEvent(SynapseEvent):
valid_keys = SynapseEvent.valid_keys + [
"prev_content",
]
def __init__(self, **kwargs):
if "state_key" not in kwargs:
kwargs["state_key"] = ""

View File

@@ -59,6 +59,14 @@ class EventFactory(object):
if "ts" not in kwargs:
kwargs["ts"] = int(self.clock.time_msec())
# The "age" key is a delta timestamp that should be converted into an
# absolute timestamp the minute we see it.
if "age" in kwargs:
kwargs["age_ts"] = int(self.clock.time_msec()) - int(kwargs["age"])
del kwargs["age"]
elif "age_ts" not in kwargs:
kwargs["age_ts"] = int(self.clock.time_msec())
if etype in self._event_list:
handler = self._event_list[etype]
else:

39
synapse/config/email.py Normal file
View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Copyright 2014 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.
from ._base import Config
class EmailConfig(Config):
def __init__(self, args):
super(EmailConfig, self).__init__(args)
self.email_from_address = args.email_from_address
self.email_smtp_server = args.email_smtp_server
@classmethod
def add_arguments(cls, parser):
super(EmailConfig, cls).add_arguments(parser)
email_group = parser.add_argument_group("email")
email_group.add_argument(
"--email-from-address",
default="FROM@EXAMPLE.COM",
help="The address to send emails from (e.g. for password resets)."
)
email_group.add_argument(
"--email-smtp-server",
default="",
help="The SMTP server to send emails from (e.g. for password resets)."
)

View File

@@ -20,11 +20,15 @@ from .database import DatabaseConfig
from .ratelimiting import RatelimitConfig
from .repository import ContentRepositoryConfig
from .captcha import CaptchaConfig
from .email import EmailConfig
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
RatelimitConfig, ContentRepositoryConfig, CaptchaConfig):
RatelimitConfig, ContentRepositoryConfig, CaptchaConfig,
EmailConfig):
pass
if __name__=='__main__':
if __name__ == '__main__':
import sys
HomeServerConfig.load_config("Generate config", sys.argv[1:], "HomeServer")

View File

@@ -291,6 +291,13 @@ class ReplicationLayer(object):
def on_incoming_transaction(self, transaction_data):
transaction = Transaction(**transaction_data)
for p in transaction.pdus:
if "age" in p:
p["age_ts"] = int(self._clock.time_msec()) - int(p["age"])
del p["age"]
pdu_list = [Pdu(**p) for p in transaction.pdus]
logger.debug("[%s] Got transaction", transaction.transaction_id)
response = yield self.transaction_actions.have_responded(transaction)
@@ -303,8 +310,6 @@ class ReplicationLayer(object):
logger.debug("[%s] Transacition is new", transaction.transaction_id)
pdu_list = [Pdu(**p) for p in transaction.pdus]
dl = []
for pdu in pdu_list:
dl.append(self._handle_new_pdu(pdu))
@@ -405,9 +410,14 @@ class ReplicationLayer(object):
"""Returns a new Transaction containing the given PDUs suitable for
transmission.
"""
pdus = [p.get_dict() for p in pdu_list]
for p in pdus:
if "age_ts" in pdus:
p["age"] = int(self.clock.time_msec()) - p["age_ts"]
return Transaction(
pdus=[p.get_dict() for p in pdu_list],
origin=self.server_name,
pdus=pdus,
ts=int(self._clock.time_msec()),
destination=None,
)
@@ -593,8 +603,21 @@ class _TransactionQueue(object):
logger.debug("TX [%s] Sending transaction...", destination)
# Actually send the transaction
# FIXME (erikj): This is a bit of a hack to make the Pdu age
# keys work
def cb(transaction):
now = int(self._clock.time_msec())
if "pdus" in transaction:
for p in transaction["pdus"]:
if "age_ts" in p:
p["age"] = now - int(p["age_ts"])
return transaction
code, response = yield self.transport_layer.send_transaction(
transaction
transaction,
on_send_callback=cb,
)
logger.debug("TX [%s] Sent transaction", destination)

View File

@@ -144,7 +144,7 @@ class TransportLayer(object):
@defer.inlineCallbacks
@log_function
def send_transaction(self, transaction):
def send_transaction(self, transaction, on_send_callback=None):
""" Sends the given Transaction to it's destination
Args:
@@ -165,10 +165,23 @@ class TransportLayer(object):
data = transaction.get_dict()
# FIXME (erikj): This is a bit of a hack to make the Pdu age
# keys work
def cb(destination, method, path_bytes, producer):
if not on_send_callback:
return
transaction = json.loads(producer.body)
new_transaction = on_send_callback(transaction)
producer.reset(new_transaction)
code, response = yield self.client.put_json(
transaction.destination,
path=PREFIX + "/send/%s/" % transaction.transaction_id,
data=data
data=data,
on_send_callback=cb,
)
logger.debug(

View File

@@ -69,6 +69,7 @@ class Pdu(JsonEncodedObject):
"prev_state_id",
"prev_state_origin",
"required_power_level",
"user_id",
]
internal_keys = [

View File

@@ -15,7 +15,6 @@
from twisted.internet import defer
from synapse.api.events import SynapseEvent
from synapse.util.logutils import log_function
from ._base import BaseHandler
@@ -71,10 +70,7 @@ class EventStreamHandler(BaseHandler):
auth_user, room_ids, pagin_config, timeout
)
chunks = [
e.get_dict() if isinstance(e, SynapseEvent) else e
for e in events
]
chunks = [self.hs.serialize_event(e) for e in events]
chunk = {
"chunk": chunks,
@@ -92,7 +88,9 @@ class EventStreamHandler(BaseHandler):
# 10 seconds of grace to allow the client to reconnect again
# before we think they're gone
def _later():
logger.debug("_later stopped_user_eventstream %s", auth_user)
logger.debug(
"_later stopped_user_eventstream %s", auth_user
)
self.distributor.fire(
"stopped_user_eventstream", auth_user
)

View File

@@ -93,22 +93,18 @@ class FederationHandler(BaseHandler):
"""
event = self.pdu_codec.event_from_pdu(pdu)
logger.debug("Got event: %s", event.event_id)
with (yield self.lock_manager.lock(pdu.context)):
if event.is_state and not backfilled:
is_new_state = yield self.state_handler.handle_new_state(
pdu
)
if not is_new_state:
return
else:
is_new_state = False
# TODO: Implement something in federation that allows us to
# respond to PDU.
if hasattr(event, "state_key") and not is_new_state:
logger.debug("Ignoring old state.")
return
target_is_mine = False
if hasattr(event, "target_host"):
target_is_mine = event.target_host == self.hs.hostname
@@ -139,7 +135,11 @@ class FederationHandler(BaseHandler):
else:
with (yield self.room_lock.lock(event.room_id)):
yield self.store.persist_event(event, backfilled)
yield self.store.persist_event(
event,
backfilled,
is_new_state=is_new_state
)
room = yield self.store.get_room(event.room_id)

View File

@@ -17,9 +17,13 @@ from twisted.internet import defer
from ._base import BaseHandler
from synapse.api.errors import LoginError, Codes
from synapse.http.client import PlainHttpClient
from synapse.util.emailutils import EmailException
import synapse.util.emailutils as emailutils
import bcrypt
import logging
import urllib
logger = logging.getLogger(__name__)
@@ -62,4 +66,41 @@ class LoginHandler(BaseHandler):
defer.returnValue(token)
else:
logger.warn("Failed password login for user %s", user)
raise LoginError(403, "", errcode=Codes.FORBIDDEN)
raise LoginError(403, "", errcode=Codes.FORBIDDEN)
@defer.inlineCallbacks
def reset_password(self, user_id, email):
is_valid = yield self._check_valid_association(user_id, email)
logger.info("reset_password user=%s email=%s valid=%s", user_id, email,
is_valid)
if is_valid:
try:
# send an email out
emailutils.send_email(
smtp_server=self.hs.config.email_smtp_server,
from_addr=self.hs.config.email_from_address,
to_addr=email,
subject="Password Reset",
body="TODO."
)
except EmailException as e:
logger.exception(e)
@defer.inlineCallbacks
def _check_valid_association(self, user_id, email):
identity = yield self._query_email(email)
if identity and "mxid" in identity:
if identity["mxid"] == user_id:
defer.returnValue(True)
return
defer.returnValue(False)
@defer.inlineCallbacks
def _query_email(self, email):
httpCli = PlainHttpClient(self.hs)
data = yield httpCli.get_json(
'matrix.org:8090', # TODO FIXME This should be configurable.
"/_matrix/identity/api/v1/lookup?medium=email&address=" +
"%s" % urllib.quote(email)
)
defer.returnValue(data)

View File

@@ -124,7 +124,7 @@ class MessageHandler(BaseHandler):
)
chunk = {
"chunk": [e.get_dict() for e in events],
"chunk": [self.hs.serialize_event(e) for e in events],
"start": pagin_config.from_token.to_string(),
"end": next_token.to_string(),
}
@@ -268,6 +268,9 @@ class MessageHandler(BaseHandler):
user, pagination_config, None
)
public_rooms = yield self.store.get_rooms(is_public=True)
public_room_ids = [r["room_id"] for r in public_rooms]
limit = pagin_config.limit
if not limit:
limit = 10
@@ -276,6 +279,8 @@ class MessageHandler(BaseHandler):
d = {
"room_id": event.room_id,
"membership": event.membership,
"visibility": ("public" if event.room_id in
public_room_ids else "private"),
}
if event.membership == Membership.INVITE:
@@ -296,7 +301,7 @@ class MessageHandler(BaseHandler):
end_token = now_token.copy_and_replace("room_key", token[1])
d["messages"] = {
"chunk": [m.get_dict() for m in messages],
"chunk": [self.hs.serialize_event(m) for m in messages],
"start": start_token.to_string(),
"end": end_token.to_string(),
}
@@ -304,7 +309,7 @@ class MessageHandler(BaseHandler):
current_state = yield self.store.get_current_state(
event.room_id
)
d["state"] = [c.get_dict() for c in current_state]
d["state"] = [self.hs.serialize_event(c) for c in current_state]
except:
logger.exception("Failed to get snapshot")

View File

@@ -15,9 +15,9 @@
from twisted.internet import defer
from synapse.api.errors import SynapseError, AuthError
from synapse.api.errors import CodeMessageException
from synapse.api.errors import SynapseError, AuthError, CodeMessageException
from synapse.api.constants import Membership
from synapse.api.events.room import RoomMemberEvent
from ._base import BaseHandler
@@ -97,6 +97,8 @@ class ProfileHandler(BaseHandler):
}
)
yield self._update_join_states(target_user)
@defer.inlineCallbacks
def get_avatar_url(self, target_user):
if target_user.is_mine:
@@ -144,6 +146,8 @@ class ProfileHandler(BaseHandler):
}
)
yield self._update_join_states(target_user)
@defer.inlineCallbacks
def collect_presencelike_data(self, user, state):
if not user.is_mine:
@@ -180,3 +184,39 @@ class ProfileHandler(BaseHandler):
)
defer.returnValue(response)
@defer.inlineCallbacks
def _update_join_states(self, user):
if not user.is_mine:
return
joins = yield self.store.get_rooms_for_user_where_membership_is(
user.to_string(),
[Membership.JOIN],
)
for j in joins:
snapshot = yield self.store.snapshot_room(
j.room_id, j.state_key, RoomMemberEvent.TYPE,
j.state_key
)
content = {
"membership": j.content["membership"],
"prev": j.content["membership"],
}
yield self.distributor.fire(
"collect_presencelike_data", user, content
)
new_event = self.event_factory.create_event(
etype=j.type,
room_id=j.room_id,
state_key=j.state_key,
content=content,
user_id=j.state_key,
)
yield self.state_handler.handle_new_event(new_event, snapshot)
yield self._on_new_room_event(new_event, snapshot)

View File

@@ -40,8 +40,7 @@ class RegistrationHandler(BaseHandler):
self.distributor.declare("registered_user")
@defer.inlineCallbacks
def register(self, localpart=None, password=None, threepidCreds=None,
captcha_info={}):
def register(self, localpart=None, password=None):
"""Registers a new client on the server.
Args:
@@ -54,37 +53,6 @@ class RegistrationHandler(BaseHandler):
Raises:
RegistrationError if there was a problem registering.
"""
if captcha_info:
captcha_response = yield self._validate_captcha(
captcha_info["ip"],
captcha_info["private_key"],
captcha_info["challenge"],
captcha_info["response"]
)
if not captcha_response["valid"]:
logger.info("Invalid captcha entered from %s. Error: %s",
captcha_info["ip"], captcha_response["error_url"])
raise InvalidCaptchaError(
error_url=captcha_response["error_url"]
)
else:
logger.info("Valid captcha entered from %s", captcha_info["ip"])
if threepidCreds:
for c in threepidCreds:
logger.info("validating theeepidcred sid %s on id server %s",
c['sid'], c['idServer'])
try:
threepid = yield self._threepid_from_creds(c)
except:
logger.err()
raise RegistrationError(400, "Couldn't validate 3pid")
if not threepid:
raise RegistrationError(400, "Couldn't validate 3pid")
logger.info("got threepid medium %s address %s",
threepid['medium'], threepid['address'])
password_hash = None
if password:
password_hash = bcrypt.hashpw(password, bcrypt.gensalt())
@@ -126,15 +94,54 @@ class RegistrationHandler(BaseHandler):
raise RegistrationError(
500, "Cannot generate user ID.")
# Now we have a matrix ID, bind it to the threepids we were given
if threepidCreds:
for c in threepidCreds:
# XXX: This should be a deferred list, shouldn't it?
yield self._bind_threepid(c, user_id)
defer.returnValue((user_id, token))
@defer.inlineCallbacks
def check_recaptcha(self, ip, private_key, challenge, response):
"""Checks a recaptcha is correct."""
captcha_response = yield self._validate_captcha(
ip,
private_key,
challenge,
response
)
if not captcha_response["valid"]:
logger.info("Invalid captcha entered from %s. Error: %s",
ip, captcha_response["error_url"])
raise InvalidCaptchaError(
error_url=captcha_response["error_url"]
)
else:
logger.info("Valid captcha entered from %s", ip)
@defer.inlineCallbacks
def register_email(self, threepidCreds):
"""Registers emails with an identity server."""
for c in threepidCreds:
logger.info("validating theeepidcred sid %s on id server %s",
c['sid'], c['idServer'])
try:
threepid = yield self._threepid_from_creds(c)
except:
logger.err()
raise RegistrationError(400, "Couldn't validate 3pid")
if not threepid:
raise RegistrationError(400, "Couldn't validate 3pid")
logger.info("got threepid medium %s address %s",
threepid['medium'], threepid['address'])
@defer.inlineCallbacks
def bind_emails(self, user_id, threepidCreds):
"""Links emails with a user ID and informs an identity server."""
# Now we have a matrix ID, bind it to the threepids we were given
for c in threepidCreds:
# XXX: This should be a deferred list, shouldn't it?
yield self._bind_threepid(c, user_id)
def _generate_token(self, user_id):
# urlsafe variant uses _ and - so use . as the separator and replace
# all =s with .s so http clients don't quote =s when it is used as
@@ -149,17 +156,17 @@ class RegistrationHandler(BaseHandler):
def _threepid_from_creds(self, creds):
httpCli = PlainHttpClient(self.hs)
# XXX: make this configurable!
trustedIdServers = [ 'matrix.org:8090' ]
trustedIdServers = ['matrix.org:8090']
if not creds['idServer'] in trustedIdServers:
logger.warn('%s is not a trusted ID server: rejecting 3pid '+
logger.warn('%s is not a trusted ID server: rejecting 3pid ' +
'credentials', creds['idServer'])
defer.returnValue(None)
data = yield httpCli.get_json(
creds['idServer'],
"/_matrix/identity/api/v1/3pid/getValidated3pid",
{ 'sid': creds['sid'], 'clientSecret': creds['clientSecret'] }
{'sid': creds['sid'], 'clientSecret': creds['clientSecret']}
)
if 'medium' in data:
defer.returnValue(data)
defer.returnValue(None)
@@ -170,44 +177,45 @@ class RegistrationHandler(BaseHandler):
data = yield httpCli.post_urlencoded_get_json(
creds['idServer'],
"/_matrix/identity/api/v1/3pid/bind",
{ 'sid': creds['sid'], 'clientSecret': creds['clientSecret'],
'mxid':mxid }
{'sid': creds['sid'], 'clientSecret': creds['clientSecret'],
'mxid': mxid}
)
defer.returnValue(data)
@defer.inlineCallbacks
def _validate_captcha(self, ip_addr, private_key, challenge, response):
"""Validates the captcha provided.
Returns:
dict: Containing 'valid'(bool) and 'error_url'(str) if invalid.
"""
response = yield self._submit_captcha(ip_addr, private_key, challenge,
response = yield self._submit_captcha(ip_addr, private_key, challenge,
response)
# parse Google's response. Lovely format..
lines = response.split('\n')
json = {
"valid": lines[0] == 'true',
"error_url": "http://www.google.com/recaptcha/api/challenge?"+
"error_url": "http://www.google.com/recaptcha/api/challenge?" +
"error=%s" % lines[1]
}
defer.returnValue(json)
@defer.inlineCallbacks
def _submit_captcha(self, ip_addr, private_key, challenge, response):
client = PlainHttpClient(self.hs)
data = yield client.post_urlencoded_get_raw(
"www.google.com:80",
"/recaptcha/api/verify",
accept_partial=True, # twisted dislikes google's response, no content length.
args={
'privatekey': private_key,
# twisted dislikes google's response, no content length.
accept_partial=True,
args={
'privatekey': private_key,
'remoteip': ip_addr,
'challenge': challenge,
'response': response
}
)
defer.returnValue(data)

View File

@@ -335,7 +335,7 @@ class RoomMemberHandler(BaseHandler):
member_list = yield self.store.get_room_members(room_id=room_id)
event_list = [
entry.get_dict()
self.hs.serialize_event(entry)
for entry in member_list
]
chunk_data = {
@@ -593,6 +593,12 @@ class RoomListHandler(BaseHandler):
@defer.inlineCallbacks
def get_public_room_list(self):
chunk = yield self.store.get_rooms(is_public=True)
for room in chunk:
joined_members = yield self.store.get_room_members(
room_id=room["room_id"],
membership=Membership.JOIN
)
room["num_joined_members"] = len(joined_members)
# FIXME (erikj): START is no longer a valid value
defer.returnValue({"start": "START", "end": "END", "chunk": chunk})

View File

@@ -122,7 +122,7 @@ class TwistedHttpClient(HttpClient):
self.hs = hs
@defer.inlineCallbacks
def put_json(self, destination, path, data):
def put_json(self, destination, path, data, on_send_callback=None):
if destination in _destination_mappings:
destination = _destination_mappings[destination]
@@ -131,7 +131,8 @@ class TwistedHttpClient(HttpClient):
"PUT",
path.encode("ascii"),
producer=_JsonProducer(data),
headers_dict={"Content-Type": ["application/json"]}
headers_dict={"Content-Type": ["application/json"]},
on_send_callback=on_send_callback,
)
logger.debug("Getting resp body")
@@ -218,7 +219,7 @@ class TwistedHttpClient(HttpClient):
@defer.inlineCallbacks
def _create_request(self, destination, method, path_bytes, param_bytes=b"",
query_bytes=b"", producer=None, headers_dict={},
retry_on_dns_fail=True):
retry_on_dns_fail=True, on_send_callback=None):
""" Creates and sends a request to the given url
"""
headers_dict[b"User-Agent"] = [b"Synapse"]
@@ -242,6 +243,9 @@ class TwistedHttpClient(HttpClient):
endpoint = self._getEndpoint(reactor, destination);
while True:
if on_send_callback:
on_send_callback(destination, method, path_bytes, producer)
try:
response = yield self.agent.request(
destination,
@@ -310,6 +314,9 @@ class _JsonProducer(object):
""" Used by the twisted http client to create the HTTP body from json
"""
def __init__(self, jsn):
self.reset(jsn)
def reset(self, jsn):
self.body = encode_canonical_json(jsn)
self.length = len(self.body)

View File

@@ -59,7 +59,7 @@ class EventRestServlet(RestServlet):
event = yield handler.get_event(auth_user, event_id)
if event:
defer.returnValue((200, event.get_dict()))
defer.returnValue((200, self.hs.serialize_event(event)))
else:
defer.returnValue((404, "Event not found."))

View File

@@ -73,6 +73,27 @@ class LoginFallbackRestServlet(RestServlet):
return (200, {})
class PasswordResetRestServlet(RestServlet):
PATTERN = client_path_pattern("/login/reset")
@defer.inlineCallbacks
def on_POST(self, request):
reset_info = _parse_json(request)
try:
email = reset_info["email"]
user_id = reset_info["user_id"]
handler = self.handlers.login_handler
yield handler.reset_password(user_id, email)
# purposefully give no feedback to avoid people hammering different
# combinations.
defer.returnValue((200, {}))
except KeyError:
raise SynapseError(
400,
"Missing keys. Requires 'email' and 'user_id'."
)
def _parse_json(request):
try:
content = json.loads(request.content.read())
@@ -85,3 +106,4 @@ def _parse_json(request):
def register_servlets(hs, http_server):
LoginRestServlet(hs).register(http_server)
# TODO PasswordResetRestServlet(hs).register(http_server)

View File

@@ -17,89 +17,218 @@
from twisted.internet import defer
from synapse.api.errors import SynapseError, Codes
from synapse.api.constants import LoginType
from base import RestServlet, client_path_pattern
import synapse.util.stringutils as stringutils
import json
import logging
import urllib
logger = logging.getLogger(__name__)
class RegisterRestServlet(RestServlet):
"""Handles registration with the home server.
This servlet is in control of the registration flow; the registration
handler doesn't have a concept of multi-stages or sessions.
"""
PATTERN = client_path_pattern("/register$")
def __init__(self, hs):
super(RegisterRestServlet, self).__init__(hs)
# sessions are stored as:
# self.sessions = {
# "session_id" : { __session_dict__ }
# }
# TODO: persistent storage
self.sessions = {}
def on_GET(self, request):
if self.hs.config.enable_registration_captcha:
return (200, {
"flows": [
{
"type": LoginType.RECAPTCHA,
"stages": ([LoginType.RECAPTCHA,
LoginType.EMAIL_IDENTITY,
LoginType.PASSWORD])
},
{
"type": LoginType.RECAPTCHA,
"stages": [LoginType.RECAPTCHA, LoginType.PASSWORD]
}
]
})
else:
return (200, {
"flows": [
{
"type": LoginType.EMAIL_IDENTITY,
"stages": ([LoginType.EMAIL_IDENTITY,
LoginType.PASSWORD])
},
{
"type": LoginType.PASSWORD
}
]
})
@defer.inlineCallbacks
def on_POST(self, request):
desired_user_id = None
password = None
register_json = _parse_json(request)
session = (register_json["session"] if "session" in register_json
else None)
login_type = None
if "type" not in register_json:
raise SynapseError(400, "Missing 'type' key.")
try:
register_json = json.loads(request.content.read())
if "password" in register_json:
password = register_json["password"].encode("utf-8")
if type(register_json["user_id"]) == unicode:
desired_user_id = register_json["user_id"].encode("utf-8")
if urllib.quote(desired_user_id) != desired_user_id:
raise SynapseError(
400,
"User ID must only contain characters which do not " +
"require URL encoding.")
except ValueError:
defer.returnValue((400, "No JSON object."))
except KeyError:
pass # user_id is optional
threepidCreds = None
if 'threepidCreds' in register_json:
threepidCreds = register_json['threepidCreds']
captcha = {}
if self.hs.config.enable_registration_captcha:
challenge = None
user_response = None
try:
captcha_type = register_json["captcha"]["type"]
if captcha_type != "m.login.recaptcha":
raise SynapseError(400, "Sorry, only m.login.recaptcha " +
"requests are supported.")
challenge = register_json["captcha"]["challenge"]
user_response = register_json["captcha"]["response"]
except KeyError:
raise SynapseError(400, "Captcha response is required",
errcode=Codes.CAPTCHA_NEEDED)
# TODO determine the source IP : May be an X-Forwarding-For header depending on config
ip_addr = request.getClientIP()
if self.hs.config.captcha_ip_origin_is_x_forwarded:
# use the header
if request.requestHeaders.hasHeader("X-Forwarded-For"):
ip_addr = request.requestHeaders.getRawHeaders(
"X-Forwarded-For")[0]
captcha = {
"ip": ip_addr,
"private_key": self.hs.config.recaptcha_private_key,
"challenge": challenge,
"response": user_response
login_type = register_json["type"]
stages = {
LoginType.RECAPTCHA: self._do_recaptcha,
LoginType.PASSWORD: self._do_password,
LoginType.EMAIL_IDENTITY: self._do_email_identity
}
session_info = self._get_session_info(request, session)
logger.debug("%s : session info %s request info %s",
login_type, session_info, register_json)
response = yield stages[login_type](
request,
register_json,
session_info
)
if "access_token" not in response:
# isn't a final response
response["session"] = session_info["id"]
defer.returnValue((200, response))
except KeyError as e:
logger.exception(e)
raise SynapseError(400, "Missing JSON keys for login type %s." % login_type)
def on_OPTIONS(self, request):
return (200, {})
def _get_session_info(self, request, session_id):
if not session_id:
# create a new session
while session_id is None or session_id in self.sessions:
session_id = stringutils.random_string(24)
self.sessions[session_id] = {
"id": session_id,
LoginType.EMAIL_IDENTITY: False,
LoginType.RECAPTCHA: False
}
return self.sessions[session_id]
def _save_session(self, session):
# TODO: Persistent storage
logger.debug("Saving session %s", session)
self.sessions[session["id"]] = session
def _remove_session(self, session):
logger.debug("Removing session %s", session)
self.sessions.pop(session["id"])
@defer.inlineCallbacks
def _do_recaptcha(self, request, register_json, session):
if not self.hs.config.enable_registration_captcha:
raise SynapseError(400, "Captcha not required.")
challenge = None
user_response = None
try:
challenge = register_json["challenge"]
user_response = register_json["response"]
except KeyError:
raise SynapseError(400, "Captcha response is required",
errcode=Codes.CAPTCHA_NEEDED)
# May be an X-Forwarding-For header depending on config
ip_addr = request.getClientIP()
if self.hs.config.captcha_ip_origin_is_x_forwarded:
# use the header
if request.requestHeaders.hasHeader("X-Forwarded-For"):
ip_addr = request.requestHeaders.getRawHeaders(
"X-Forwarded-For")[0]
handler = self.handlers.registration_handler
yield handler.check_recaptcha(
ip_addr,
self.hs.config.recaptcha_private_key,
challenge,
user_response
)
session[LoginType.RECAPTCHA] = True # mark captcha as done
self._save_session(session)
defer.returnValue({
"next": [LoginType.PASSWORD, LoginType.EMAIL_IDENTITY]
})
@defer.inlineCallbacks
def _do_email_identity(self, request, register_json, session):
if (self.hs.config.enable_registration_captcha and
not session[LoginType.RECAPTCHA]):
raise SynapseError(400, "Captcha is required.")
threepidCreds = register_json['threepidCreds']
handler = self.handlers.registration_handler
yield handler.register_email(threepidCreds)
session["threepidCreds"] = threepidCreds # store creds for next stage
session[LoginType.EMAIL_IDENTITY] = True # mark email as done
self._save_session(session)
defer.returnValue({
"next": LoginType.PASSWORD
})
@defer.inlineCallbacks
def _do_password(self, request, register_json, session):
if (self.hs.config.enable_registration_captcha and
not session[LoginType.RECAPTCHA]):
# captcha should've been done by this stage!
raise SynapseError(400, "Captcha is required.")
password = register_json["password"].encode("utf-8")
desired_user_id = (register_json["user"].encode("utf-8") if "user"
in register_json else None)
if desired_user_id and urllib.quote(desired_user_id) != desired_user_id:
raise SynapseError(
400,
"User ID must only contain characters which do not " +
"require URL encoding.")
handler = self.handlers.registration_handler
(user_id, token) = yield handler.register(
localpart=desired_user_id,
password=password,
threepidCreds=threepidCreds,
captcha_info=captcha)
password=password
)
if session[LoginType.EMAIL_IDENTITY]:
yield handler.bind_emails(user_id, session["threepidCreds"])
result = {
"user_id": user_id,
"access_token": token,
"home_server": self.hs.hostname,
}
defer.returnValue(
(200, result)
)
self._remove_session(session)
defer.returnValue(result)
def on_OPTIONS(self, request):
return (200, {})
def _parse_json(request):
try:
content = json.loads(request.content.read())
if type(content) != dict:
raise SynapseError(400, "Content must be a JSON object.")
return content
except ValueError:
raise SynapseError(400, "Content not JSON.")
def register_servlets(hs, http_server):

View File

@@ -378,7 +378,7 @@ class RoomTriggerBackfill(RestServlet):
handler = self.handlers.federation_handler
events = yield handler.backfill(remote_server, room_id, limit)
res = [event.get_dict() for event in events]
res = [self.hs.serialize_event(event) for event in events]
defer.returnValue((200, res))

View File

@@ -20,6 +20,7 @@
# Imports required for the default HomeServer() implementation
from synapse.federation import initialize_http_replication
from synapse.api.events import serialize_event
from synapse.api.events.factory import EventFactory
from synapse.notifier import Notifier
from synapse.api.auth import Auth
@@ -138,6 +139,9 @@ class BaseHomeServer(object):
object."""
return RoomID.from_string(s, hs=self)
def serialize_event(self, e):
return serialize_event(self, e)
# Build magic accessors for every dependency
for depname in BaseHomeServer.DEPENDENCIES:
BaseHomeServer._make_dependency_method(depname)

View File

@@ -115,6 +115,8 @@ class StateHandler(object):
is_new = yield self._handle_new_state(new_pdu)
logger.debug("is_new: %s %s %s", is_new, new_pdu.pdu_id, new_pdu.origin)
if is_new:
yield self.store.update_current_state(
pdu_id=new_pdu.pdu_id,
@@ -134,7 +136,9 @@ class StateHandler(object):
@defer.inlineCallbacks
@log_function
def _handle_new_state(self, new_pdu):
tree = yield self.store.get_unresolved_state_tree(new_pdu)
tree, missing_branch = yield self.store.get_unresolved_state_tree(
new_pdu
)
new_branch, current_branch = tree
logger.debug(
@@ -142,64 +146,17 @@ class StateHandler(object):
new_branch, current_branch
)
if not current_branch:
# There is no current state
defer.returnValue(True)
return
n = new_branch[-1]
c = current_branch[-1]
if n.pdu_id == c.pdu_id and n.origin == c.origin:
# We have all the PDUs we need, so we can just do the conflict
# resolution.
if len(current_branch) == 1:
# This is a direct clobber so we can just...
defer.returnValue(True)
conflict_res = [
self._do_power_level_conflict_res,
self._do_chain_length_conflict_res,
self._do_hash_conflict_res,
]
for algo in conflict_res:
new_res, curr_res = algo(new_branch, current_branch)
if new_res < curr_res:
defer.returnValue(False)
elif new_res > curr_res:
defer.returnValue(True)
raise Exception("Conflict resolution failed.")
else:
# We need to ask for PDUs.
missing_prev = max(
new_branch[-1], current_branch[-1],
key=lambda x: x.depth
)
if not hasattr(missing_prev, "prev_state_id"):
# FIXME Hmm
# temporary fallback
for algo in conflict_res:
new_res, curr_res = algo(new_branch, current_branch)
if new_res < curr_res:
defer.returnValue(False)
elif new_res > curr_res:
defer.returnValue(True)
return
if missing_branch is not None:
# We're missing some PDUs. Fetch them.
# TODO (erikj): Limit this.
missing_prev = tree[missing_branch][-1]
pdu_id = missing_prev.prev_state_id
origin = missing_prev.prev_state_origin
is_missing = yield self.store.get_pdu(pdu_id, origin) is None
if not is_missing:
raise Exception("Conflict resolution failed.")
raise Exception("Conflict resolution failed")
yield self._replication.get_pdu(
destination=missing_prev.origin,
@@ -211,23 +168,93 @@ class StateHandler(object):
updated_current = yield self._handle_new_state(new_pdu)
defer.returnValue(updated_current)
def _do_power_level_conflict_res(self, new_branch, current_branch):
max_power_new = max(
new_branch[:-1],
key=lambda t: t.power_level
).power_level
if not current_branch:
# There is no current state
defer.returnValue(True)
return
max_power_current = max(
current_branch[:-1],
key=lambda t: t.power_level
).power_level
n = new_branch[-1]
c = current_branch[-1]
return (max_power_new, max_power_current)
common_ancestor = n.pdu_id == c.pdu_id and n.origin == c.origin
def _do_chain_length_conflict_res(self, new_branch, current_branch):
if common_ancestor:
# We found a common ancestor!
if len(current_branch) == 1:
# This is a direct clobber so we can just...
defer.returnValue(True)
else:
# We didn't find a common ancestor. This is probably fine.
pass
result = yield self._do_conflict_res(
new_branch, current_branch, common_ancestor
)
defer.returnValue(result)
@defer.inlineCallbacks
def _do_conflict_res(self, new_branch, current_branch, common_ancestor):
conflict_res = [
self._do_power_level_conflict_res,
self._do_chain_length_conflict_res,
self._do_hash_conflict_res,
]
for algo in conflict_res:
new_res, curr_res = yield defer.maybeDeferred(
algo,
new_branch, current_branch, common_ancestor
)
if new_res < curr_res:
defer.returnValue(False)
elif new_res > curr_res:
defer.returnValue(True)
raise Exception("Conflict resolution failed.")
@defer.inlineCallbacks
def _do_power_level_conflict_res(self, new_branch, current_branch,
common_ancestor):
new_powers_deferreds = []
for e in new_branch[:-1] if common_ancestor else new_branch:
if hasattr(e, "user_id"):
new_powers_deferreds.append(
self.store.get_power_level(e.context, e.user_id)
)
current_powers_deferreds = []
for e in current_branch[:-1] if common_ancestor else current_branch:
if hasattr(e, "user_id"):
current_powers_deferreds.append(
self.store.get_power_level(e.context, e.user_id)
)
new_powers = yield defer.gatherResults(
new_powers_deferreds,
consumeErrors=True
)
current_powers = yield defer.gatherResults(
current_powers_deferreds,
consumeErrors=True
)
max_power_new = max(new_powers)
max_power_current = max(current_powers)
defer.returnValue(
(max_power_new, max_power_current)
)
def _do_chain_length_conflict_res(self, new_branch, current_branch,
common_ancestor):
return (len(new_branch), len(current_branch))
def _do_hash_conflict_res(self, new_branch, current_branch):
def _do_hash_conflict_res(self, new_branch, current_branch,
common_ancestor):
new_str = "".join([p.pdu_id + p.origin for p in new_branch])
c_str = "".join([p.pdu_id + p.origin for p in current_branch])

View File

@@ -36,7 +36,7 @@ from .registration import RegistrationStore
from .room import RoomStore
from .roommember import RoomMemberStore
from .stream import StreamStore
from .pdu import StatePduStore, PduStore
from .pdu import StatePduStore, PduStore, PdusTable
from .transactions import TransactionStore
from .keys import KeyStore
@@ -47,6 +47,11 @@ import os
logger = logging.getLogger(__name__)
class _RollbackButIsFineException(Exception):
""" This exception is used to rollback a transaction without implying
something went wrong.
"""
pass
class DataStore(RoomMemberStore, RoomStore,
RegistrationStore, StreamStore, ProfileStore, FeedbackStore,
@@ -63,7 +68,8 @@ class DataStore(RoomMemberStore, RoomStore,
@defer.inlineCallbacks
@log_function
def persist_event(self, event=None, backfilled=False, pdu=None):
def persist_event(self, event=None, backfilled=False, pdu=None,
is_new_state=True):
stream_ordering = None
if backfilled:
if not self.min_token_deferred.called:
@@ -71,14 +77,17 @@ class DataStore(RoomMemberStore, RoomStore,
self.min_token -= 1
stream_ordering = self.min_token
latest = yield self._db_pool.runInteraction(
self._persist_pdu_event_txn,
pdu=pdu,
event=event,
backfilled=backfilled,
stream_ordering=stream_ordering,
)
defer.returnValue(latest)
try:
yield self._db_pool.runInteraction(
self._persist_pdu_event_txn,
pdu=pdu,
event=event,
backfilled=backfilled,
stream_ordering=stream_ordering,
is_new_state=is_new_state,
)
except _RollbackButIsFineException as e:
pass
@defer.inlineCallbacks
def get_event(self, event_id, allow_none=False):
@@ -102,12 +111,14 @@ class DataStore(RoomMemberStore, RoomStore,
defer.returnValue(event)
def _persist_pdu_event_txn(self, txn, pdu=None, event=None,
backfilled=False, stream_ordering=None):
backfilled=False, stream_ordering=None,
is_new_state=True):
if pdu is not None:
self._persist_event_pdu_txn(txn, pdu)
if event is not None:
return self._persist_event_txn(
txn, event, backfilled, stream_ordering
txn, event, backfilled, stream_ordering,
is_new_state=is_new_state,
)
def _persist_event_pdu_txn(self, txn, pdu):
@@ -116,6 +127,12 @@ class DataStore(RoomMemberStore, RoomStore,
del cols["content"]
del cols["prev_pdus"]
cols["content_json"] = json.dumps(pdu.content)
unrec_keys.update({
k: v for k, v in cols.items()
if k not in PdusTable.fields
})
cols["unrecognized_keys"] = json.dumps(unrec_keys)
logger.debug("Persisting: %s", repr(cols))
@@ -128,7 +145,8 @@ class DataStore(RoomMemberStore, RoomStore,
self._update_min_depth_for_context_txn(txn, pdu.context, pdu.depth)
@log_function
def _persist_event_txn(self, txn, event, backfilled, stream_ordering=None):
def _persist_event_txn(self, txn, event, backfilled, stream_ordering=None,
is_new_state=True):
if event.type == RoomMemberEvent.TYPE:
self._store_room_member_txn(txn, event)
elif event.type == FeedbackEvent.TYPE:
@@ -175,13 +193,14 @@ class DataStore(RoomMemberStore, RoomStore,
try:
self._simple_insert_txn(txn, "events", vals)
except:
logger.exception(
logger.warn(
"Failed to persist, probably duplicate: %s",
event.event_id
event.event_id,
exc_info=True,
)
return
raise _RollbackButIsFineException("_persist_event")
if not backfilled and hasattr(event, "state_key"):
if is_new_state and hasattr(event, "state_key"):
vals = {
"event_id": event.event_id,
"room_id": event.room_id,
@@ -205,8 +224,6 @@ class DataStore(RoomMemberStore, RoomStore,
}
)
return self._get_room_events_max_id_txn(txn)
@defer.inlineCallbacks
def get_current_state(self, room_id, event_type=None, state_key=""):
sql = (

View File

@@ -17,6 +17,7 @@ import logging
from twisted.internet import defer
from synapse.api.errors import StoreError
from synapse.util.logutils import log_function
import collections
import copy
@@ -91,6 +92,7 @@ class SQLBaseStore(object):
self._simple_insert_txn, table, values, or_replace=or_replace
)
@log_function
def _simple_insert_txn(self, txn, table, values, or_replace=False):
sql = "%s INTO %s (%s) VALUES(%s)" % (
("INSERT OR REPLACE" if or_replace else "INSERT"),
@@ -98,6 +100,12 @@ class SQLBaseStore(object):
", ".join(k for k in values),
", ".join("?" for k in values)
)
logger.debug(
"[SQL] %s Args=%s Func=%s",
sql, values.values(),
)
txn.execute(sql, values.values())
return txn.lastrowid
@@ -307,6 +315,10 @@ class SQLBaseStore(object):
d["content"] = json.loads(d["content"])
del d["unrecognized_keys"]
if "age_ts" not in d:
# For compatibility
d["age_ts"] = d["ts"] if "ts" in d else 0
return self.event_factory.create_event(
etype=d["type"],
**d

View File

@@ -17,6 +17,7 @@ from twisted.internet import defer
from ._base import SQLBaseStore, Table, JoinHelper
from synapse.federation.units import Pdu
from synapse.util.logutils import log_function
from collections import namedtuple
@@ -308,8 +309,8 @@ class PduStore(SQLBaseStore):
@defer.inlineCallbacks
def get_oldest_pdus_in_context(self, context):
"""Get a list of Pdus that we haven't backfilled beyond yet (and haven't
seen). This list is used when we want to backfill backwards and is the
"""Get a list of Pdus that we haven't backfilled beyond yet (and havent
seen). This list is used when we want to backfill backwards and is the
list we send to the remote server.
Args:
@@ -516,7 +517,7 @@ class StatePduStore(SQLBaseStore):
if not current:
logger.debug("get_unresolved_state_tree No current state.")
return return_value
return (return_value, None)
return_value.current_branch.append(current)
@@ -524,13 +525,16 @@ class StatePduStore(SQLBaseStore):
txn, new_pdu, current
)
missing_branch = None
for branch, prev_state, state in enum_branches:
if state:
return_value[branch].append(state)
else:
# We don't have prev_state :(
missing_branch = branch
break
return return_value
return (return_value, missing_branch)
def update_current_state(self, pdu_id, origin, context, pdu_type,
state_key):
@@ -622,53 +626,6 @@ class StatePduStore(SQLBaseStore):
return result
def get_next_missing_pdu(self, new_pdu):
"""When we get a new state pdu we need to check whether we need to do
any conflict resolution, if we do then we need to check if we need
to go back and request some more state pdus that we haven't seen yet.
Args:
txn
new_pdu
Returns:
PduIdTuple: A pdu that we are missing, or None if we have all the
pdus required to do the conflict resolution.
"""
return self._db_pool.runInteraction(
self._get_next_missing_pdu, new_pdu
)
def _get_next_missing_pdu(self, txn, new_pdu):
logger.debug(
"get_next_missing_pdu %s %s",
new_pdu.pdu_id, new_pdu.origin
)
current = self._get_current_interaction(
txn,
new_pdu.context, new_pdu.pdu_type, new_pdu.state_key
)
if (not current or not current.prev_state_id
or not current.prev_state_origin):
return None
# Oh look, it's a straight clobber, so wooooo almost no-op.
if (new_pdu.prev_state_id == current.pdu_id
and new_pdu.prev_state_origin == current.origin):
return None
enum_branches = self._enumerate_state_branches(txn, new_pdu, current)
for branch, prev_state, state in enum_branches:
if not state:
return PduIdTuple(
prev_state.prev_state_id,
prev_state.prev_state_origin
)
return None
def handle_new_state(self, new_pdu):
"""Actually perform conflict resolution on the new_pdu on the
assumption we have all the pdus required to perform it.
@@ -752,24 +709,11 @@ class StatePduStore(SQLBaseStore):
return is_current
@classmethod
@log_function
def _enumerate_state_branches(cls, txn, pdu_a, pdu_b):
def _enumerate_state_branches(self, txn, pdu_a, pdu_b):
branch_a = pdu_a
branch_b = pdu_b
get_query = (
"SELECT %(fields)s FROM %(pdus)s as p "
"LEFT JOIN %(state)s as s "
"ON p.pdu_id = s.pdu_id AND p.origin = s.origin "
"WHERE p.pdu_id = ? AND p.origin = ? "
) % {
"fields": _pdu_state_joiner.get_fields(
PdusTable="p", StatePdusTable="s"),
"pdus": PdusTable.table_name,
"state": StatePdusTable.table_name,
}
while True:
if (branch_a.pdu_id == branch_b.pdu_id
and branch_a.origin == branch_b.origin):
@@ -801,13 +745,12 @@ class StatePduStore(SQLBaseStore):
branch_a.prev_state_origin
)
logger.debug("getting branch_a prev %s", pdu_tuple)
txn.execute(get_query, pdu_tuple)
prev_branch = branch_a
res = txn.fetchone()
branch_a = PduEntry(*res) if res else None
logger.debug("getting branch_a prev %s", pdu_tuple)
branch_a = self._get_pdu_tuple(txn, *pdu_tuple)
if branch_a:
branch_a = Pdu.from_pdu_tuple(branch_a)
logger.debug("branch_a=%s", branch_a)
@@ -820,14 +763,13 @@ class StatePduStore(SQLBaseStore):
branch_b.prev_state_id,
branch_b.prev_state_origin
)
txn.execute(get_query, pdu_tuple)
logger.debug("getting branch_b prev %s", pdu_tuple)
prev_branch = branch_b
res = txn.fetchone()
branch_b = PduEntry(*res) if res else None
logger.debug("getting branch_b prev %s", pdu_tuple)
branch_b = self._get_pdu_tuple(txn, *pdu_tuple)
if branch_b:
branch_b = Pdu.from_pdu_tuple(branch_b)
logger.debug("branch_b=%s", branch_b)

View File

@@ -18,6 +18,7 @@ from twisted.internet import defer
from ._base import SQLBaseStore
from synapse.api.constants import Membership
from synapse.util.logutils import log_function
import logging
@@ -29,8 +30,18 @@ class RoomMemberStore(SQLBaseStore):
def _store_room_member_txn(self, txn, event):
"""Store a room member in the database.
"""
target_user_id = event.state_key
domain = self.hs.parse_userid(target_user_id).domain
try:
target_user_id = event.state_key
domain = self.hs.parse_userid(target_user_id).domain
except:
logger.exception("Failed to parse target_user_id=%s", target_user_id)
raise
logger.debug(
"_store_room_member_txn: target_user_id=%s, membership=%s",
target_user_id,
event.membership,
)
self._simple_insert_txn(
txn,
@@ -51,12 +62,30 @@ class RoomMemberStore(SQLBaseStore):
"VALUES (?, ?)"
)
txn.execute(sql, (event.room_id, domain))
else:
sql = (
"DELETE FROM room_hosts WHERE room_id = ? AND host = ?"
elif event.membership != Membership.INVITE:
# Check if this was the last person to have left.
member_events = self._get_members_query_txn(
txn,
where_clause="c.room_id = ? AND m.membership = ? AND m.user_id != ?",
where_values=(event.room_id, Membership.JOIN, target_user_id,)
)
txn.execute(sql, (event.room_id, domain))
joined_domains = set()
for e in member_events:
try:
joined_domains.add(
self.hs.parse_userid(e.state_key).domain
)
except:
# FIXME: How do we deal with invalid user ids in the db?
logger.exception("Invalid user_id: %s", event.state_key)
if domain not in joined_domains:
sql = (
"DELETE FROM room_hosts WHERE room_id = ? AND host = ?"
)
txn.execute(sql, (event.room_id, domain))
@defer.inlineCallbacks
def get_room_member(self, user_id, room_id):
@@ -146,8 +175,13 @@ class RoomMemberStore(SQLBaseStore):
vals = where_dict.values()
return self._get_members_query(clause, vals)
@defer.inlineCallbacks
def _get_members_query(self, where_clause, where_values):
return self._db_pool.runInteraction(
self._get_members_query_txn,
where_clause, where_values
)
def _get_members_query_txn(self, txn, where_clause, where_values):
sql = (
"SELECT e.* FROM events as e "
"INNER JOIN room_memberships as m "
@@ -157,12 +191,11 @@ class RoomMemberStore(SQLBaseStore):
"WHERE %s "
) % (where_clause,)
rows = yield self._execute_and_decode(sql, *where_values)
txn.execute(sql, where_values)
rows = self.cursor_to_dict(txn)
# logger.debug("_get_members_query Got rows %s", rows)
results = yield self._parse_events(rows)
defer.returnValue(results)
results = self._parse_events_txn(txn, rows)
return results
@defer.inlineCallbacks
def user_rooms_intersect(self, user_list):

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" This module allows you to send out emails.
"""
import email.utils
import smtplib
import twisted.python.log
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import logging
logger = logging.getLogger(__name__)
class EmailException(Exception):
pass
def send_email(smtp_server, from_addr, to_addr, subject, body):
"""Sends an email.
Args:
smtp_server(str): The SMTP server to use.
from_addr(str): The address to send from.
to_addr(str): The address to send to.
subject(str): The subject of the email.
body(str): The plain text body of the email.
Raises:
EmailException if there was a problem sending the mail.
"""
if not smtp_server or not from_addr or not to_addr:
raise EmailException("Need SMTP server, from and to addresses. Check " +
"the config to set these.")
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = from_addr
msg['To'] = to_addr
plain_part = MIMEText(body)
msg.attach(plain_part)
raw_from = email.utils.parseaddr(from_addr)[1]
raw_to = email.utils.parseaddr(to_addr)[1]
if not raw_from or not raw_to:
raise EmailException("Couldn't parse from/to address.")
logger.info("Sending email to %s on server %s with subject %s",
to_addr, smtp_server, subject)
try:
smtp = smtplib.SMTP(smtp_server)
smtp.sendmail(raw_from, raw_to, msg.as_string())
smtp.quit()
except Exception as origException:
twisted.python.log.err()
ese = EmailException()
ese.cause = origException
raise ese

9
synctl
View File

@@ -4,7 +4,6 @@ SYNAPSE="synapse/app/homeserver.py"
CONFIGFILE="homeserver.yaml"
PIDFILE="homeserver.pid"
LOGFILE="homeserver.log"
GREEN=$'\e[1;32m'
NORMAL=$'\e[m'
@@ -14,15 +13,13 @@ set -e
case "$1" in
start)
if [ ! -f "$CONFIGFILE" ]; then
echo "No config file found - generating a default one..."
$SYNAPSE -c "$CONFIGFILE" --generate-config
echo "Wrote $CONFIGFILE"
echo "You must now edit this file before continuing"
echo "No config file found"
echo "To generate a config file, run 'python --generate-config'"
exit 1
fi
echo -n "Starting ..."
$SYNAPSE --daemonize -c "$CONFIGFILE" --pid-file "$PIDFILE" --log-file "$LOGFILE"
$SYNAPSE --daemonize -c "$CONFIGFILE" --pid-file "$PIDFILE"
echo "${GREEN}started${NORMAL}"
;;
stop)

View File

@@ -1,6 +1,6 @@
from synapse.api.ratelimiting import Ratelimiter
import unittest
from tests import unittest
class TestRatelimiter(unittest.TestCase):

View File

@@ -15,7 +15,7 @@
from synapse.api.events import SynapseEvent
import unittest
from tests import unittest
class SynapseTemplateCheckTestCase(unittest.TestCase):

View File

@@ -14,11 +14,10 @@
# trial imports
from twisted.internet import defer
from twisted.trial import unittest
from tests import unittest
# python imports
from mock import Mock
import logging
from mock import Mock, ANY
from ..utils import MockHttpResource, MockClock
@@ -28,9 +27,6 @@ from synapse.federation.units import Pdu
from synapse.storage.pdu import PduTuple, PduEntry
logging.getLogger().addHandler(logging.NullHandler())
def make_pdu(prev_pdus=[], **kwargs):
"""Provide some default fields for making a PduTuple."""
pdu_fields = {
@@ -185,7 +181,8 @@ class FederationTestCase(unittest.TestCase):
"depth": 1,
},
]
}
},
on_send_callback=ANY,
)
@defer.inlineCallbacks
@@ -216,7 +213,9 @@ class FederationTestCase(unittest.TestCase):
"content": {"testing": "content here"},
}
],
})
},
on_send_callback=ANY,
)
@defer.inlineCallbacks
def test_recv_edu(self):

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from twisted.trial import unittest
from tests import unittest
from synapse.federation.pdu_codec import (
PduCodec, encode_event_id, decode_event_id

View File

@@ -14,11 +14,10 @@
# limitations under the License.
from twisted.trial import unittest
from tests import unittest
from twisted.internet import defer
from mock import Mock
import logging
from synapse.server import HomeServer
from synapse.http.client import HttpClient
@@ -26,9 +25,6 @@ from synapse.handlers.directory import DirectoryHandler
from synapse.storage.directory import RoomAliasMapping
logging.getLogger().addHandler(logging.NullHandler())
class DirectoryHandlers(object):
def __init__(self, hs):
self.directory_handler = DirectoryHandler(hs)

View File

@@ -14,7 +14,7 @@
from twisted.internet import defer
from twisted.trial import unittest
from tests import unittest
from synapse.api.events.room import (
InviteJoinEvent, MessageEvent, RoomMemberEvent
@@ -26,12 +26,8 @@ from synapse.federation.units import Pdu
from mock import NonCallableMock, ANY
import logging
from ..utils import get_mock_call_args
logging.getLogger().addHandler(logging.NullHandler())
class FederationTestCase(unittest.TestCase):
@@ -78,7 +74,9 @@ class FederationTestCase(unittest.TestCase):
yield self.handlers.federation_handler.on_receive_pdu(pdu, False)
self.datastore.persist_event.assert_called_once_with(ANY, False)
self.datastore.persist_event.assert_called_once_with(
ANY, False, is_new_state=False
)
self.notifier.on_new_room_event.assert_called_once_with(ANY)
@defer.inlineCallbacks

View File

@@ -14,11 +14,10 @@
# limitations under the License.
from twisted.trial import unittest
from tests import unittest
from twisted.internet import defer, reactor
from mock import Mock, call, ANY
import logging
import json
from ..utils import MockHttpResource, MockClock, DeferredMockCallable
@@ -34,9 +33,6 @@ UNAVAILABLE = PresenceState.UNAVAILABLE
ONLINE = PresenceState.ONLINE
logging.getLogger().addHandler(logging.NullHandler())
def _expect_edu(destination, edu_type, content, origin="test"):
return {
"origin": origin,
@@ -92,7 +88,6 @@ class PresenceStateTestCase(unittest.TestCase):
# Mock the RoomMemberHandler
room_member_handler = Mock(spec=[])
hs.handlers.room_member_handler = room_member_handler
logging.getLogger().debug("Mocking room_member_handler=%r", room_member_handler)
# Some local users to test with
self.u_apple = hs.parse_userid("@apple:test")
@@ -324,7 +319,8 @@ class PresenceInvitesTestCase(unittest.TestCase):
"observer_user": "@apple:test",
"observed_user": "@cabbage:elsewhere",
}
)
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -350,7 +346,8 @@ class PresenceInvitesTestCase(unittest.TestCase):
"observer_user": "@cabbage:elsewhere",
"observed_user": "@apple:test",
}
)
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -381,7 +378,8 @@ class PresenceInvitesTestCase(unittest.TestCase):
"observer_user": "@cabbage:elsewhere",
"observed_user": "@durian:test",
}
)
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -770,7 +768,8 @@ class PresencePushTestCase(unittest.TestCase):
"last_active_ago": 0},
],
}
)
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -785,7 +784,8 @@ class PresencePushTestCase(unittest.TestCase):
"last_active_ago": 0},
],
}
)
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -911,6 +911,7 @@ class PresencePushTestCase(unittest.TestCase):
],
}
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -925,6 +926,7 @@ class PresencePushTestCase(unittest.TestCase):
],
}
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -954,6 +956,7 @@ class PresencePushTestCase(unittest.TestCase):
],
}
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -1150,6 +1153,7 @@ class PresencePollingTestCase(unittest.TestCase):
"poll": [ "@potato:remote" ],
},
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -1162,6 +1166,7 @@ class PresencePollingTestCase(unittest.TestCase):
"push": [ {"user_id": "@clementine:test" }],
},
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -1190,6 +1195,7 @@ class PresencePollingTestCase(unittest.TestCase):
"push": [ {"user_id": "@fig:test" }],
},
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -1222,6 +1228,7 @@ class PresencePollingTestCase(unittest.TestCase):
"unpoll": [ "@potato:remote" ],
},
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -1253,6 +1260,7 @@ class PresencePollingTestCase(unittest.TestCase):
],
},
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)

View File

@@ -16,11 +16,10 @@
"""This file contains tests of the "presence-like" data that is shared between
presence and profiles; namely, the displayname and avatar_url."""
from twisted.trial import unittest
from tests import unittest
from twisted.internet import defer
from mock import Mock, call, ANY
import logging
from ..utils import MockClock
@@ -35,9 +34,6 @@ UNAVAILABLE = PresenceState.UNAVAILABLE
ONLINE = PresenceState.ONLINE
logging.getLogger().addHandler(logging.NullHandler())
class MockReplication(object):
def __init__(self):
self.edu_handlers = {}
@@ -69,6 +65,8 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
"is_presence_visible",
"set_profile_displayname",
"get_rooms_for_user_where_membership_is",
]),
handlers=None,
resource_for_federation=Mock(),
@@ -136,6 +134,10 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
# Remote user
self.u_potato = hs.parse_userid("@potato:remote")
self.mock_get_joined = (
self.datastore.get_rooms_for_user_where_membership_is
)
@defer.inlineCallbacks
def test_set_my_state(self):
self.presence_list = [
@@ -156,6 +158,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_push_local(self):
def get_joined(*args):
return defer.succeed([])
self.mock_get_joined.side_effect = get_joined
self.presence_list = [
{"observed_user_id": "@banana:test"},
{"observed_user_id": "@clementine:test"},

View File

@@ -14,18 +14,15 @@
# limitations under the License.
from twisted.trial import unittest
from tests import unittest
from twisted.internet import defer
from mock import Mock
import logging
from synapse.api.errors import AuthError
from synapse.server import HomeServer
from synapse.handlers.profile import ProfileHandler
logging.getLogger().addHandler(logging.NullHandler())
from synapse.api.constants import Membership
class ProfileHandlers(object):
@@ -54,6 +51,7 @@ class ProfileTestCase(unittest.TestCase):
"set_profile_displayname",
"get_profile_avatar_url",
"set_profile_avatar_url",
"get_rooms_for_user_where_membership_is",
]),
handlers=None,
resource_for_federation=Mock(),
@@ -69,6 +67,10 @@ class ProfileTestCase(unittest.TestCase):
self.handler = hs.get_handlers().profile_handler
self.mock_get_joined = (
self.datastore.get_rooms_for_user_where_membership_is
)
# TODO(paul): Icky signal declarings.. booo
hs.get_distributor().declare("changed_presencelike_data")
@@ -87,8 +89,15 @@ class ProfileTestCase(unittest.TestCase):
mocked_set = self.datastore.set_profile_displayname
mocked_set.return_value = defer.succeed(())
self.mock_get_joined.return_value = defer.succeed([])
yield self.handler.set_displayname(self.frank, self.frank, "Frank Jr.")
self.mock_get_joined.assert_called_once_with(
self.frank.to_string(),
[Membership.JOIN]
)
mocked_set.assert_called_with("1234ABCD", "Frank Jr.")
@defer.inlineCallbacks
@@ -139,7 +148,15 @@ class ProfileTestCase(unittest.TestCase):
mocked_set = self.datastore.set_profile_avatar_url
mocked_set.return_value = defer.succeed(())
self.mock_get_joined.return_value = defer.succeed([])
yield self.handler.set_avatar_url(self.frank, self.frank,
"http://my.server/pic.gif")
self.mock_get_joined.assert_called_once_with(
self.frank.to_string(),
[Membership.JOIN]
)
mocked_set.assert_called_with("1234ABCD", "http://my.server/pic.gif")

View File

@@ -15,7 +15,7 @@
from twisted.internet import defer
from twisted.trial import unittest
from tests import unittest
from synapse.api.events.room import (
InviteJoinEvent, RoomMemberEvent, RoomConfigEvent
@@ -27,10 +27,6 @@ from synapse.server import HomeServer
from mock import Mock, NonCallableMock
import logging
logging.getLogger().addHandler(logging.NullHandler())
class RoomMemberHandlerTestCase(unittest.TestCase):

View File

@@ -14,12 +14,11 @@
# limitations under the License.
from twisted.trial import unittest
from tests import unittest
from twisted.internet import defer
from mock import Mock, call, ANY
import json
import logging
from ..utils import MockHttpResource, MockClock, DeferredMockCallable
@@ -27,9 +26,6 @@ from synapse.server import HomeServer
from synapse.handlers.typing import TypingNotificationHandler
logging.getLogger().addHandler(logging.NullHandler())
def _expect_edu(destination, edu_type, content, origin="test"):
return {
"origin": origin,
@@ -173,7 +169,8 @@ class TypingNotificationsTestCase(unittest.TestCase):
"user_id": self.u_apple.to_string(),
"typing": True,
}
)
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)
@@ -223,7 +220,8 @@ class TypingNotificationsTestCase(unittest.TestCase):
"user_id": self.u_apple.to_string(),
"typing": False,
}
)
),
on_send_callback=ANY,
),
defer.succeed((200, "OK"))
)

View File

@@ -14,7 +14,7 @@
# limitations under the License.
""" Tests REST events for /events paths."""
from twisted.trial import unittest
from tests import unittest
# twisted imports
from twisted.internet import defer
@@ -27,14 +27,12 @@ from synapse.server import HomeServer
# python imports
import json
import logging
from ..utils import MockHttpResource, MemoryDataStore
from .utils import RestTestCase
from mock import Mock, NonCallableMock
logging.getLogger().addHandler(logging.NullHandler())
PATH_PREFIX = "/_matrix/client/api/v1"

View File

@@ -15,11 +15,10 @@
"""Tests REST events for /presence paths."""
from twisted.trial import unittest
from tests import unittest
from twisted.internet import defer
from mock import Mock
import logging
from ..utils import MockHttpResource
@@ -28,9 +27,6 @@ from synapse.handlers.presence import PresenceHandler
from synapse.server import HomeServer
logging.getLogger().addHandler(logging.NullHandler())
OFFLINE = PresenceState.OFFLINE
UNAVAILABLE = PresenceState.UNAVAILABLE
ONLINE = PresenceState.ONLINE

View File

@@ -15,7 +15,7 @@
"""Tests REST events for /profile paths."""
from twisted.trial import unittest
from tests import unittest
from twisted.internet import defer
from mock import Mock
@@ -28,6 +28,7 @@ from synapse.server import HomeServer
myid = "@1234ABCD:test"
PATH_PREFIX = "/_matrix/client/api/v1"
class ProfileTestCase(unittest.TestCase):
""" Tests profile management. """

View File

@@ -17,7 +17,7 @@
from twisted.internet import defer
# trial imports
from twisted.trial import unittest
from tests import unittest
from synapse.api.constants import Membership
@@ -95,8 +95,14 @@ class RestTestCase(unittest.TestCase):
@defer.inlineCallbacks
def register(self, user_id):
(code, response) = yield self.mock_resource.trigger("POST", "/register",
'{"user_id":"%s"}' % user_id)
(code, response) = yield self.mock_resource.trigger(
"POST",
"/register",
json.dumps({
"user": user_id,
"password": "test",
"type": "m.login.password"
}))
self.assertEquals(200, code)
defer.returnValue(response)

View File

@@ -14,7 +14,7 @@
# limitations under the License.
from twisted.trial import unittest
from tests import unittest
from twisted.internet import defer
from mock import Mock, call

View File

@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from tests import unittest
from twisted.internet import defer
from twisted.trial import unittest
from mock import Mock, patch

View File

@@ -13,23 +13,32 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from tests import unittest
from twisted.internet import defer
from twisted.trial import unittest
from twisted.python.log import PythonLoggingObserver
from synapse.state import StateHandler
from synapse.storage.pdu import PduEntry
from synapse.federation.pdu_codec import encode_event_id
from synapse.federation.units import Pdu
from collections import namedtuple
from mock import Mock
import mock
ReturnType = namedtuple(
"StateReturnType", ["new_branch", "current_branch"]
)
def _gen_get_power_level(power_level_list):
def get_power_level(room_id, user_id):
return defer.succeed(power_level_list.get(user_id, None))
return get_power_level
class StateTestCase(unittest.TestCase):
def setUp(self):
self.persistence = Mock(spec=[
@@ -38,6 +47,7 @@ class StateTestCase(unittest.TestCase):
"get_latest_pdus_in_context",
"get_current_state_pdu",
"get_pdu",
"get_power_level",
])
self.replication = Mock(spec=["get_pdu"])
@@ -51,10 +61,12 @@ class StateTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_new_state_key(self):
# We've never seen anything for this state before
new_pdu = new_fake_pdu_entry("A", "test", "mem", "x", None, 10)
new_pdu = new_fake_pdu("A", "test", "mem", "x", None, "u")
self.persistence.get_power_level.side_effect = _gen_get_power_level({})
self.persistence.get_unresolved_state_tree.return_value = (
ReturnType([new_pdu], [])
(ReturnType([new_pdu], []), None)
)
is_new = yield self.state.handle_new_state(new_pdu)
@@ -74,11 +86,44 @@ class StateTestCase(unittest.TestCase):
# We do a direct overwriting of the old state, i.e., the new state
# points to the old state.
old_pdu = new_fake_pdu_entry("A", "test", "mem", "x", None, 10)
new_pdu = new_fake_pdu_entry("B", "test", "mem", "x", "A", 5)
old_pdu = new_fake_pdu("A", "test", "mem", "x", None, "u1")
new_pdu = new_fake_pdu("B", "test", "mem", "x", "A", "u2")
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 5,
})
self.persistence.get_unresolved_state_tree.return_value = (
ReturnType([new_pdu, old_pdu], [old_pdu])
(ReturnType([new_pdu, old_pdu], [old_pdu]), None)
)
is_new = yield self.state.handle_new_state(new_pdu)
self.assertTrue(is_new)
self.persistence.get_unresolved_state_tree.assert_called_once_with(
new_pdu
)
self.assertEqual(1, self.persistence.update_current_state.call_count)
self.assertFalse(self.replication.get_pdu.called)
@defer.inlineCallbacks
def test_overwrite(self):
old_pdu_1 = new_fake_pdu("A", "test", "mem", "x", None, "u1")
old_pdu_2 = new_fake_pdu("B", "test", "mem", "x", "A", "u2")
new_pdu = new_fake_pdu("C", "test", "mem", "x", "B", "u3")
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 5,
"u3": 0,
})
self.persistence.get_unresolved_state_tree.return_value = (
(ReturnType([new_pdu, old_pdu_2, old_pdu_1], [old_pdu_1]), None)
)
is_new = yield self.state.handle_new_state(new_pdu)
@@ -98,12 +143,18 @@ class StateTestCase(unittest.TestCase):
# We try to update the state based on an outdated state, and have a
# too low power level.
old_pdu_1 = new_fake_pdu_entry("A", "test", "mem", "x", None, 10)
old_pdu_2 = new_fake_pdu_entry("B", "test", "mem", "x", None, 10)
new_pdu = new_fake_pdu_entry("C", "test", "mem", "x", "A", 5)
old_pdu_1 = new_fake_pdu("A", "test", "mem", "x", None, "u1")
old_pdu_2 = new_fake_pdu("B", "test", "mem", "x", None, "u2")
new_pdu = new_fake_pdu("C", "test", "mem", "x", "A", "u3")
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 10,
"u3": 5,
})
self.persistence.get_unresolved_state_tree.return_value = (
ReturnType([new_pdu, old_pdu_1], [old_pdu_2, old_pdu_1])
(ReturnType([new_pdu, old_pdu_1], [old_pdu_2, old_pdu_1]), None)
)
is_new = yield self.state.handle_new_state(new_pdu)
@@ -123,12 +174,18 @@ class StateTestCase(unittest.TestCase):
# We try to update the state based on an outdated state, but have
# sufficient power level to force the update.
old_pdu_1 = new_fake_pdu_entry("A", "test", "mem", "x", None, 10)
old_pdu_2 = new_fake_pdu_entry("B", "test", "mem", "x", None, 10)
new_pdu = new_fake_pdu_entry("C", "test", "mem", "x", "A", 15)
old_pdu_1 = new_fake_pdu("A", "test", "mem", "x", None, "u1")
old_pdu_2 = new_fake_pdu("B", "test", "mem", "x", None, "u2")
new_pdu = new_fake_pdu("C", "test", "mem", "x", "A", "u3")
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 10,
"u3": 15,
})
self.persistence.get_unresolved_state_tree.return_value = (
ReturnType([new_pdu, old_pdu_1], [old_pdu_2, old_pdu_1])
(ReturnType([new_pdu, old_pdu_1], [old_pdu_2, old_pdu_1]), None)
)
is_new = yield self.state.handle_new_state(new_pdu)
@@ -148,12 +205,18 @@ class StateTestCase(unittest.TestCase):
# We try to update the state based on an outdated state, the power
# levels are the same and so are the branch lengths
old_pdu_1 = new_fake_pdu_entry("A", "test", "mem", "x", None, 10)
old_pdu_2 = new_fake_pdu_entry("B", "test", "mem", "x", None, 10)
new_pdu = new_fake_pdu_entry("C", "test", "mem", "x", "A", 10)
old_pdu_1 = new_fake_pdu("A", "test", "mem", "x", None, "u1")
old_pdu_2 = new_fake_pdu("B", "test", "mem", "x", None, "u2")
new_pdu = new_fake_pdu("C", "test", "mem", "x", "A", "u3")
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 10,
"u3": 10,
})
self.persistence.get_unresolved_state_tree.return_value = (
ReturnType([new_pdu, old_pdu_1], [old_pdu_2, old_pdu_1])
(ReturnType([new_pdu, old_pdu_1], [old_pdu_2, old_pdu_1]), None)
)
is_new = yield self.state.handle_new_state(new_pdu)
@@ -173,13 +236,26 @@ class StateTestCase(unittest.TestCase):
# We try to update the state based on an outdated state, the power
# levels are the same but the branch length of the new one is longer.
old_pdu_1 = new_fake_pdu_entry("A", "test", "mem", "x", None, 10)
old_pdu_2 = new_fake_pdu_entry("B", "test", "mem", "x", None, 10)
old_pdu_3 = new_fake_pdu_entry("C", "test", "mem", "x", "A", 10)
new_pdu = new_fake_pdu_entry("D", "test", "mem", "x", "C", 10)
old_pdu_1 = new_fake_pdu("A", "test", "mem", "x", None, "u1")
old_pdu_2 = new_fake_pdu("B", "test", "mem", "x", None, "u2")
old_pdu_3 = new_fake_pdu("C", "test", "mem", "x", "A", "u3")
new_pdu = new_fake_pdu("D", "test", "mem", "x", "C", "u4")
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 10,
"u3": 10,
"u4": 10,
})
self.persistence.get_unresolved_state_tree.return_value = (
ReturnType([new_pdu, old_pdu_3, old_pdu_1], [old_pdu_2, old_pdu_1])
(
ReturnType(
[new_pdu, old_pdu_3, old_pdu_1],
[old_pdu_2, old_pdu_1]
),
None
)
)
is_new = yield self.state.handle_new_state(new_pdu)
@@ -200,22 +276,38 @@ class StateTestCase(unittest.TestCase):
# triggering a get_pdu request
# The pdu we haven't seen
old_pdu_1 = new_fake_pdu_entry("A", "test", "mem", "x", None, 10)
old_pdu_1 = new_fake_pdu(
"A", "test", "mem", "x", None, "u1", depth=0
)
old_pdu_2 = new_fake_pdu_entry("B", "test", "mem", "x", None, 10)
new_pdu = new_fake_pdu_entry("C", "test", "mem", "x", "A", 20)
old_pdu_2 = new_fake_pdu(
"B", "test", "mem", "x", "A", "u2", depth=1
)
new_pdu = new_fake_pdu(
"C", "test", "mem", "x", "A", "u3", depth=2
)
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 10,
"u3": 20,
})
# The return_value of `get_unresolved_state_tree`, which changes after
# the call to get_pdu
tree_to_return = [ReturnType([new_pdu], [old_pdu_2])]
tree_to_return = [(ReturnType([new_pdu], [old_pdu_2]), 0)]
def return_tree(p):
return tree_to_return[0]
def set_return_tree(*args, **kwargs):
tree_to_return[0] = ReturnType(
[new_pdu, old_pdu_1], [old_pdu_2, old_pdu_1]
def set_return_tree(destination, pdu_origin, pdu_id, outlier=False):
tree_to_return[0] = (
ReturnType(
[new_pdu, old_pdu_1], [old_pdu_2, old_pdu_1]
),
None
)
return defer.succeed(None)
self.persistence.get_unresolved_state_tree.side_effect = return_tree
@@ -227,6 +319,13 @@ class StateTestCase(unittest.TestCase):
self.assertTrue(is_new)
self.replication.get_pdu.assert_called_with(
destination=new_pdu.origin,
pdu_origin=old_pdu_1.origin,
pdu_id=old_pdu_1.pdu_id,
outlier=True
)
self.persistence.get_unresolved_state_tree.assert_called_with(
new_pdu
)
@@ -237,12 +336,233 @@ class StateTestCase(unittest.TestCase):
self.assertEqual(1, self.persistence.update_current_state.call_count)
@defer.inlineCallbacks
def test_missing_pdu_depth_1(self):
# We try to update state against a PDU we haven't yet seen,
# triggering a get_pdu request
# The pdu we haven't seen
old_pdu_1 = new_fake_pdu(
"A", "test", "mem", "x", None, "u1", depth=0
)
old_pdu_2 = new_fake_pdu(
"B", "test", "mem", "x", "A", "u2", depth=2
)
old_pdu_3 = new_fake_pdu(
"C", "test", "mem", "x", "B", "u3", depth=3
)
new_pdu = new_fake_pdu(
"D", "test", "mem", "x", "A", "u4", depth=4
)
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 10,
"u3": 10,
"u4": 20,
})
# The return_value of `get_unresolved_state_tree`, which changes after
# the call to get_pdu
tree_to_return = [
(
ReturnType([new_pdu], [old_pdu_3]),
0
),
(
ReturnType(
[new_pdu, old_pdu_1], [old_pdu_3]
),
1
),
(
ReturnType(
[new_pdu, old_pdu_1], [old_pdu_3, old_pdu_2, old_pdu_1]
),
None
),
]
to_return = [0]
def return_tree(p):
return tree_to_return[to_return[0]]
def set_return_tree(destination, pdu_origin, pdu_id, outlier=False):
to_return[0] += 1
return defer.succeed(None)
self.persistence.get_unresolved_state_tree.side_effect = return_tree
self.replication.get_pdu.side_effect = set_return_tree
self.persistence.get_pdu.return_value = None
is_new = yield self.state.handle_new_state(new_pdu)
self.assertTrue(is_new)
self.assertEqual(2, self.replication.get_pdu.call_count)
self.replication.get_pdu.assert_has_calls(
[
mock.call(
destination=new_pdu.origin,
pdu_origin=old_pdu_1.origin,
pdu_id=old_pdu_1.pdu_id,
outlier=True
),
mock.call(
destination=old_pdu_3.origin,
pdu_origin=old_pdu_2.origin,
pdu_id=old_pdu_2.pdu_id,
outlier=True
),
]
)
self.persistence.get_unresolved_state_tree.assert_called_with(
new_pdu
)
self.assertEquals(
3, self.persistence.get_unresolved_state_tree.call_count
)
self.assertEqual(1, self.persistence.update_current_state.call_count)
@defer.inlineCallbacks
def test_missing_pdu_depth_2(self):
# We try to update state against a PDU we haven't yet seen,
# triggering a get_pdu request
# The pdu we haven't seen
old_pdu_1 = new_fake_pdu(
"A", "test", "mem", "x", None, "u1", depth=0
)
old_pdu_2 = new_fake_pdu(
"B", "test", "mem", "x", "A", "u2", depth=2
)
old_pdu_3 = new_fake_pdu(
"C", "test", "mem", "x", "B", "u3", depth=3
)
new_pdu = new_fake_pdu(
"D", "test", "mem", "x", "A", "u4", depth=1
)
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 10,
"u2": 10,
"u3": 10,
"u4": 20,
})
# The return_value of `get_unresolved_state_tree`, which changes after
# the call to get_pdu
tree_to_return = [
(
ReturnType([new_pdu], [old_pdu_3]),
1,
),
(
ReturnType(
[new_pdu], [old_pdu_3, old_pdu_2]
),
0,
),
(
ReturnType(
[new_pdu, old_pdu_1], [old_pdu_3, old_pdu_2, old_pdu_1]
),
None
),
]
to_return = [0]
def return_tree(p):
return tree_to_return[to_return[0]]
def set_return_tree(destination, pdu_origin, pdu_id, outlier=False):
to_return[0] += 1
return defer.succeed(None)
self.persistence.get_unresolved_state_tree.side_effect = return_tree
self.replication.get_pdu.side_effect = set_return_tree
self.persistence.get_pdu.return_value = None
is_new = yield self.state.handle_new_state(new_pdu)
self.assertTrue(is_new)
self.assertEqual(2, self.replication.get_pdu.call_count)
self.replication.get_pdu.assert_has_calls(
[
mock.call(
destination=old_pdu_3.origin,
pdu_origin=old_pdu_2.origin,
pdu_id=old_pdu_2.pdu_id,
outlier=True
),
mock.call(
destination=new_pdu.origin,
pdu_origin=old_pdu_1.origin,
pdu_id=old_pdu_1.pdu_id,
outlier=True
),
]
)
self.persistence.get_unresolved_state_tree.assert_called_with(
new_pdu
)
self.assertEquals(
3, self.persistence.get_unresolved_state_tree.call_count
)
self.assertEqual(1, self.persistence.update_current_state.call_count)
@defer.inlineCallbacks
def test_no_common_ancestor(self):
# We do a direct overwriting of the old state, i.e., the new state
# points to the old state.
old_pdu = new_fake_pdu("A", "test", "mem", "x", None, "u1")
new_pdu = new_fake_pdu("B", "test", "mem", "x", None, "u2")
self.persistence.get_power_level.side_effect = _gen_get_power_level({
"u1": 5,
"u2": 10,
})
self.persistence.get_unresolved_state_tree.return_value = (
(ReturnType([new_pdu], [old_pdu]), None)
)
is_new = yield self.state.handle_new_state(new_pdu)
self.assertTrue(is_new)
self.persistence.get_unresolved_state_tree.assert_called_once_with(
new_pdu
)
self.assertEqual(1, self.persistence.update_current_state.call_count)
self.assertFalse(self.replication.get_pdu.called)
@defer.inlineCallbacks
def test_new_event(self):
event = Mock()
event.event_id = "12123123@test"
state_pdu = new_fake_pdu_entry("C", "test", "mem", "x", "A", 20)
state_pdu = new_fake_pdu("C", "test", "mem", "x", "A", 20)
snapshot = Mock()
snapshot.prev_state_pdu = state_pdu
@@ -269,24 +589,25 @@ class StateTestCase(unittest.TestCase):
)
def new_fake_pdu_entry(pdu_id, context, pdu_type, state_key, prev_state_id,
power_level):
new_pdu = PduEntry(
def new_fake_pdu(pdu_id, context, pdu_type, state_key, prev_state_id,
user_id, depth=0):
new_pdu = Pdu(
pdu_id=pdu_id,
pdu_type=pdu_type,
state_key=state_key,
power_level=power_level,
user_id=user_id,
prev_state_id=prev_state_id,
origin="example.com",
context="context",
ts=1405353060021,
depth=0,
depth=depth,
content_json="{}",
unrecognized_keys="{}",
outlier=True,
is_state=True,
prev_state_origin="example.com",
have_processed=True,
content={},
)
return new_pdu

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import unittest
from tests import unittest
from synapse.server import BaseHomeServer
from synapse.types import UserID, RoomAlias

79
tests/unittest.py Normal file
View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# Copyright 2014 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.
from twisted.trial import unittest
import logging
# logging doesn't have a "don't log anything at all EVARRRR setting,
# but since the highest value is 50, 1000000 should do ;)
NEVER = 1000000
logging.getLogger().addHandler(logging.StreamHandler())
logging.getLogger().setLevel(NEVER)
def around(target):
"""A CLOS-style 'around' modifier, which wraps the original method of the
given instance with another piece of code.
@around(self)
def method_name(orig, *args, **kwargs):
return orig(*args, **kwargs)
"""
def _around(code):
name = code.__name__
orig = getattr(target, name)
def new(*args, **kwargs):
return code(orig, *args, **kwargs)
setattr(target, name, new)
return _around
class TestCase(unittest.TestCase):
"""A subclass of twisted.trial's TestCase which looks for 'loglevel'
attributes on both itself and its individual test methods, to override the
root logger's logging level while that test (case|method) runs."""
def __init__(self, methodName, *args, **kwargs):
super(TestCase, self).__init__(methodName, *args, **kwargs)
method = getattr(self, methodName)
level = getattr(method, "loglevel",
getattr(self, "loglevel",
NEVER))
@around(self)
def setUp(orig):
old_level = logging.getLogger().level
if old_level != level:
@around(self)
def tearDown(orig):
ret = orig()
logging.getLogger().setLevel(old_level)
return ret
logging.getLogger().setLevel(level)
return orig()
def DEBUG(target):
"""A decorator to set the .loglevel attribute to logging.DEBUG.
Can apply to either a TestCase or an individual test method."""
target.loglevel = logging.DEBUG
return target

View File

@@ -15,7 +15,7 @@
from twisted.internet import defer
from twisted.trial import unittest
from tests import unittest
from synapse.util.lockutils import LockManager
@@ -105,4 +105,4 @@ class LockManagerTestCase(unittest.TestCase):
pass
with (yield self.lock_manager.lock(key)):
pass
pass

View File

@@ -21,8 +21,8 @@ limitations under the License.
'use strict';
angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService'])
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', '$animate', 'matrixService', 'mPresence', 'eventStreamService', 'matrixPhoneService',
function($scope, $location, $rootScope, $timeout, $animate, matrixService, mPresence, eventStreamService, matrixPhoneService) {
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', '$animate', 'matrixService', 'mPresence', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService',
function($scope, $location, $rootScope, $timeout, $animate, matrixService, mPresence, eventStreamService, eventHandlerService, matrixPhoneService) {
// Check current URL to avoid to display the logout button on the login page
$scope.location = $location.path();
@@ -73,7 +73,10 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
// Clean permanent data
matrixService.setConfig({});
matrixService.saveConfig();
// Reset cached data
eventHandlerService.reset();
// And go to the login page
$location.url("login");
};
@@ -96,50 +99,84 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
delete roomMembers[matrixService.config().user_id];
$rootScope.currentCall.user_id = Object.keys(roomMembers)[0];
// set it to the user ID until we fetch the display name
$rootScope.currentCall.userProfile = { displayname: $rootScope.currentCall.user_id };
matrixService.getProfile($rootScope.currentCall.user_id).then(
function(response) {
$rootScope.currentCall.userProfile = response.data;
if (response.data.displayname) $rootScope.currentCall.userProfile.displayname = response.data.displayname;
if (response.data.avatar_url) $rootScope.currentCall.userProfile.avatar_url = response.data.avatar_url;
},
function(error) {
$scope.feedback = "Can't load user profile";
}
);
});
$rootScope.$watch('currentCall.state', function(newVal, oldVal) {
if (newVal == 'ringing') {
angular.element('#ringbackAudio')[0].pause();
angular.element('#ringAudio')[0].load();
angular.element('#ringAudio')[0].play();
} else if (newVal == 'invite_sent') {
angular.element('#ringAudio')[0].pause();
angular.element('#ringbackAudio')[0].load();
angular.element('#ringbackAudio')[0].play();
} else if (newVal == 'ended' && oldVal == 'connected') {
angular.element('#ringAudio')[0].pause();
angular.element('#ringbackAudio')[0].pause();
angular.element('#callendAudio')[0].play();
} else if (newVal == 'ended' && oldVal == 'invite_sent' && $rootScope.currentCall.hangupParty == 'remote') {
angular.element('#ringAudio')[0].pause();
angular.element('#ringbackAudio')[0].pause();
angular.element('#busyAudio')[0].play();
} else if (newVal == 'ended' && oldVal == 'invite_sent' && $rootScope.currentCall.hangupParty == 'local' && $rootScope.currentCall.hangupReason == 'invite_timeout') {
angular.element('#ringAudio')[0].pause();
angular.element('#ringbackAudio')[0].pause();
angular.element('#busyAudio')[0].play();
} else if (oldVal == 'invite_sent') {
angular.element('#ringbackAudio')[0].pause();
} else if (oldVal == 'ringing') {
angular.element('#ringAudio')[0].pause();
}
});
$rootScope.$on(matrixPhoneService.INCOMING_CALL_EVENT, function(ngEvent, call) {
console.trace("incoming call");
console.log("incoming call");
if ($rootScope.currentCall && $rootScope.currentCall.state != 'ended') {
console.log("rejecting call because we're already in a call");
call.hangup();
return;
}
call.onError = $scope.onCallError;
call.onHangup = $scope.onCallHangup;
$rootScope.currentCall = call;
});
$rootScope.$on(matrixPhoneService.REPLACED_CALL_EVENT, function(ngEvent, oldCall, newCall) {
console.log("call ID "+oldCall.call_id+" has been replaced by call ID "+newCall.call_id+"!");
newCall.onError = $scope.onCallError;
newCall.onHangup = $scope.onCallHangup;
$rootScope.currentCall = newCall;
});
$scope.answerCall = function() {
$rootScope.currentCall.answer();
};
$scope.hangupCall = function() {
$rootScope.currentCall.hangup();
$timeout(function() {
var icon = angular.element('#callEndedIcon');
$animate.addClass(icon, 'callIconRotate');
$timeout(function(){
$rootScope.currentCall = undefined;
}, 2000);
}, 100);
};
$rootScope.onCallError = function(errStr) {
$scope.feedback = errStr;
}
$rootScope.onCallHangup = function() {
$timeout(function() {
var icon = angular.element('#callEndedIcon');
$animate.addClass(icon, 'callIconRotate');
$rootScope.onCallHangup = function(call) {
if (call == $rootScope.currentCall) {
$timeout(function(){
$rootScope.currentCall = undefined;
}, 2000);
}, 100);
if (call == $rootScope.currentCall) $rootScope.currentCall = undefined;
}, 4070);
}
}
}]);

View File

@@ -55,20 +55,20 @@ a:active { color: #000; }
margin-left: 4px;
margin-right: 4px;
margin-top: 8px;
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
-moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
-o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
.callIconRotate {
-webkit-transform: rotateZ(45deg);
-moz-transform: rotateZ(45deg);
-ms-transform: rotateZ(45deg);
-o-transform: rotateZ(45deg);
#callEndedIcon {
transition:all linear 0.5s;
}
#callEndedIcon {
transform: rotateZ(45deg);
}
#callEndedIcon.ng-hide {
transform: rotateZ(0deg);
}
#callPeerImage {
width: 32px;
height: 32px;
@@ -220,12 +220,6 @@ a:active { color: #000; }
height: 100%;
}
#roomName {
float: right;
font-size: 16px;
margin-top: 15px;
}
#roomHeader {
margin: auto;
padding-left: 20px;
@@ -263,12 +257,80 @@ a:active { color: #000; }
#mainInput {
width: 100%;
resize: none;
}
.blink {
background-color: #faa;
}
.roomHighlight {
font-weight: bold;
}
.publicTable {
max-width: 480px;
width: 100%;
border-collapse: collapse;
}
.publicTable tr {
width: 100%;
}
.publicTable td {
vertical-align: text-top;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.publicRoomEntry {
max-width: 430px;
}
.publicRoomJoinedUsers {
width: 5em;
text-align: right;
font-size: 12px;
color: #888;
}
.publicRoomTopic {
color: #888;
font-size: 12px;
overflow: hidden;
padding-bottom: 5px;
border-bottom: 1px #ddd solid;
}
#roomName {
font-size: 16px;
text-align: right;
}
#roomTopic {
font-size: 13px;
text-align: right;
}
.roomNameInput, .roomTopicInput {
width: 100%;
}
.roomNameSection, .roomTopicSection {
float: right;
width: 100%;
}
.roomNameSetNew, .roomTopicSetNew {
float: right;
}
.roomHeaderInfo {
float: right;
margin-top: 15px;
width: 50%;
}
/*** Participant list ***/
#usersTableWrapper {
@@ -465,6 +527,10 @@ a:active { color: #000; }
text-align: left ! important;
}
.bubble .message {
/* Wrap words and break lines on CR+LF */
white-space: pre-wrap;
}
.bubble .messagePending {
opacity: 0.3
}
@@ -472,6 +538,10 @@ a:active { color: #000; }
color: #F00;
}
.messageBing {
color: #00F;
}
#room-fullscreen-image {
position: absolute;
top: 0px;
@@ -533,7 +603,11 @@ a:active { color: #000; }
width: auto;
}
.recentsRoomSummaryTS {
.recentsPublicRoom {
font-weight: bold;
}
.recentsRoomSummaryUsersCount, .recentsRoomSummaryTS {
color: #888;
font-size: 12px;
width: 7em;
@@ -546,6 +620,11 @@ a:active { color: #000; }
padding-bottom: 5px;
}
/* Do not show users count in the recents fragment displayed on the room page */
#roomPage .recentsRoomSummaryUsersCount {
width: 0em;
}
/*** Recents in the room page ***/
#roomRecentsTableWrapper {

View File

@@ -16,6 +16,7 @@ limitations under the License.
var matrixWebClient = angular.module('matrixWebClient', [
'ngRoute',
'ngAnimate',
'MatrixWebClientController',
'LoginController',
'RegisterController',

View File

@@ -27,7 +27,8 @@ Typically, this service will store events or broadcast them to any listeners
if typically all the $on method would do is update its own $scope.
*/
angular.module('eventHandlerService', [])
.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', function(matrixService, $rootScope, $q) {
.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence',
function(matrixService, $rootScope, $q, $timeout, mPresence) {
var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT";
var MSG_EVENT = "MSG_EVENT";
var MEMBER_EVENT = "MEMBER_EVENT";
@@ -35,13 +36,9 @@ angular.module('eventHandlerService', [])
var POWERLEVEL_EVENT = "POWERLEVEL_EVENT";
var CALL_EVENT = "CALL_EVENT";
var NAME_EVENT = "NAME_EVENT";
var TOPIC_EVENT = "TOPIC_EVENT";
var RESET_EVENT = "RESET_EVENT"; // eventHandlerService has been resetted
var InitialSyncDeferred = $q.defer();
$rootScope.events = {
rooms: {} // will contain roomId: { messages:[], members:{userid1: event} }
};
// used for dedupping events - could be expanded in future...
// FIXME: means that we leak memory over time (along with lots of the rest
// of the app, given we never try to reap memory yet)
@@ -49,12 +46,71 @@ angular.module('eventHandlerService', [])
$rootScope.presence = {};
// TODO: This is attached to the rootScope so .html can just go containsBingWord
// for determining classes so it is easy to highlight bing messages. It seems a
// bit strange to put the impl in this service though, but I can't think of a better
// file to put it in.
$rootScope.containsBingWord = function(content) {
if (!content || $.type(content) != "string") {
return false;
}
var bingWords = matrixService.config().bingWords;
var shouldBing = false;
// case-insensitive name check for user_id OR display_name if they exist
var myUserId = matrixService.config().user_id;
if (myUserId) {
myUserId = myUserId.toLocaleLowerCase();
}
var myDisplayName = matrixService.config().display_name;
if (myDisplayName) {
myDisplayName = myDisplayName.toLocaleLowerCase();
}
if ( (myDisplayName && content.toLocaleLowerCase().indexOf(myDisplayName) != -1) ||
(myUserId && content.toLocaleLowerCase().indexOf(myUserId) != -1) ) {
shouldBing = true;
}
// bing word list check
if (bingWords && !shouldBing) {
for (var i=0; i<bingWords.length; i++) {
var re = RegExp(bingWords[i]);
if (content.search(re) != -1) {
shouldBing = true;
break;
}
}
}
return shouldBing;
};
var initialSyncDeferred;
var reset = function() {
initialSyncDeferred = $q.defer();
$rootScope.events = {
rooms: {} // will contain roomId: { messages:[], members:{userid1: event} }
};
$rootScope.presence = {};
eventMap = {};
};
reset();
var initRoom = function(room_id) {
if (!(room_id in $rootScope.events.rooms)) {
console.log("Creating new handler entry for " + room_id);
$rootScope.events.rooms[room_id] = {};
$rootScope.events.rooms[room_id].messages = [];
$rootScope.events.rooms[room_id].members = {};
$rootScope.events.rooms[room_id] = {
room_id: room_id,
messages: [],
members: {},
// Pagination information
pagination: {
earliest_token: "END" // how far back we've paginated
}
};
}
};
@@ -64,9 +120,37 @@ angular.module('eventHandlerService', [])
}
};
var handleRoomCreate = function(event, isLiveEvent) {
initRoom(event.room_id);
// Generic method to handle events data
var handleRoomDateEvent = function(event, isLiveEvent, addToRoomMessages) {
// Add topic changes as if they were a room message
if (addToRoomMessages) {
if (isLiveEvent) {
$rootScope.events.rooms[event.room_id].messages.push(event);
}
else {
$rootScope.events.rooms[event.room_id].messages.unshift(event);
}
}
// live events always update, but non-live events only update if the
// ts is later.
var latestData = true;
if (!isLiveEvent) {
var eventTs = event.ts;
var storedEvent = $rootScope.events.rooms[event.room_id][event.type];
if (storedEvent) {
if (storedEvent.ts > eventTs) {
// ignore it, we have a newer one already.
latestData = false;
}
}
}
if (latestData) {
$rootScope.events.rooms[event.room_id][event.type] = event;
}
};
var handleRoomCreate = function(event, isLiveEvent) {
// For now, we do not use the event data. Simply signal it to the app controllers
$rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent);
};
@@ -76,17 +160,64 @@ angular.module('eventHandlerService', [])
};
var handleMessage = function(event, isLiveEvent) {
initRoom(event.room_id);
if (isLiveEvent) {
if (event.user_id === matrixService.config().user_id &&
(event.content.msgtype === "m.text" || event.content.msgtype === "m.emote") ) {
// assume we've already echoed it
// FIXME: track events by ID and ungrey the right message to show it's been delivered
// Assume we've already echoed it. So, there is a fake event in the messages list of the room
// Replace this fake event by the true one
var index = getRoomEventIndex(event.room_id, event.event_id);
if (index) {
$rootScope.events.rooms[event.room_id].messages[index] = event;
}
else {
$rootScope.events.rooms[event.room_id].messages.push(event);
}
}
else {
$rootScope.events.rooms[event.room_id].messages.push(event);
}
if (window.Notification && event.user_id != matrixService.config().user_id) {
var shouldBing = $rootScope.containsBingWord(event.content.body);
// TODO: Binging every message when idle doesn't make much sense. Can we use this more sensibly?
// Unfortunately document.hidden = false on ubuntu chrome if chrome is minimised / does not have focus;
// true when you swap tabs though. However, for the case where the chat screen is OPEN and there is
// another window on top, we want to be notifying for those events. This DOES mean that there will be
// notifications when currently viewing the chat screen though, but that is preferable to the alternative imo.
var isIdle = (document.hidden || matrixService.presence.unavailable === mPresence.getState());
// always bing if there are 0 bing words... apparently.
var bingWords = matrixService.config().bingWords;
if (bingWords && bingWords.length === 0) {
shouldBing = true;
}
if (shouldBing) {
console.log("Displaying notification for "+JSON.stringify(event));
var member = $rootScope.events.rooms[event.room_id].members[event.user_id];
var displayname = undefined;
if (member) {
displayname = member.displayname;
}
var message = event.content.body;
if (event.content.msgtype === "m.emote") {
message = "* " + displayname + " " + message;
}
var notification = new window.Notification(
(displayname || event.user_id) +
" (" + (matrixService.getRoomIdToAliasMapping(event.room_id) || event.room_id) + ")", // FIXME: don't leak room_ids here
{
"body": message,
"icon": member ? member.avatar_url : undefined
});
$timeout(function() {
notification.close();
}, 5 * 1000);
}
}
}
else {
$rootScope.events.rooms[event.room_id].messages.unshift(event);
@@ -100,9 +231,7 @@ angular.module('eventHandlerService', [])
$rootScope.$broadcast(MSG_EVENT, event, isLiveEvent);
};
var handleRoomMember = function(event, isLiveEvent) {
initRoom(event.room_id);
var handleRoomMember = function(event, isLiveEvent, isStateEvent) {
// if the server is stupidly re-relaying a no-op join, discard it.
if (event.prev_content &&
event.content.membership === "join" &&
@@ -112,7 +241,11 @@ angular.module('eventHandlerService', [])
}
// add membership changes as if they were a room message if something interesting changed
if (event.content.prev !== event.content.membership) {
// Exception: Do not do this if the event is a room state event because such events already come
// as room messages events. Moreover, when they come as room messages events, they are relatively ordered
// with other other room messages XXX This is no longer true, you only get a single event, not a room message event.
// FIXME: This possibly reintroduces multiple join messages.
if (event.content.prev !== event.content.membership) { // && !isStateEvent
if (isLiveEvent) {
$rootScope.events.rooms[event.room_id].messages.push(event);
}
@@ -121,8 +254,13 @@ angular.module('eventHandlerService', [])
}
}
$rootScope.events.rooms[event.room_id].members[event.state_key] = event;
$rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent);
// Use data from state event or the latest data from the stream.
// Do not care of events that come when paginating back
if (isStateEvent || isLiveEvent) {
$rootScope.events.rooms[event.room_id].members[event.state_key] = event;
}
$rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent, isStateEvent);
};
var handlePresence = function(event, isLiveEvent) {
@@ -131,8 +269,6 @@ angular.module('eventHandlerService', [])
};
var handlePowerLevels = function(event, isLiveEvent) {
initRoom(event.room_id);
// Keep the latest data. Do not care of events that come when paginating back
if (!$rootScope.events.rooms[event.room_id][event.type] || isLiveEvent) {
$rootScope.events.rooms[event.room_id][event.type] = event;
@@ -140,17 +276,48 @@ angular.module('eventHandlerService', [])
}
};
var handleRoomName = function(event, isLiveEvent) {
console.log("handleRoomName " + isLiveEvent);
initRoom(event.room_id);
$rootScope.events.rooms[event.room_id][event.type] = event;
var handleRoomName = function(event, isLiveEvent, isStateEvent) {
console.log("handleRoomName room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - name: " + event.content.name);
handleRoomDateEvent(event, isLiveEvent, !isStateEvent);
$rootScope.$broadcast(NAME_EVENT, event, isLiveEvent);
};
var handleRoomTopic = function(event, isLiveEvent, isStateEvent) {
console.log("handleRoomTopic room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - topic: " + event.content.topic);
handleRoomDateEvent(event, isLiveEvent, !isStateEvent);
$rootScope.$broadcast(TOPIC_EVENT, event, isLiveEvent);
};
var handleCallEvent = function(event, isLiveEvent) {
$rootScope.$broadcast(CALL_EVENT, event, isLiveEvent);
if (event.type === 'm.call.invite') {
$rootScope.events.rooms[event.room_id].messages.push(event);
}
};
/**
* Get the index of the event in $rootScope.events.rooms[room_id].messages
* @param {type} room_id the room id
* @param {type} event_id the event id to look for
* @returns {Number | undefined} the index. undefined if not found.
*/
var getRoomEventIndex = function(room_id, event_id) {
var index;
var room = $rootScope.events.rooms[room_id];
if (room) {
// Start looking from the tail since the first goal of this function
// is to find a messaged among the latest ones
for (var i = room.messages.length - 1; i > 0; i--) {
var message = room.messages[i];
if (event_id === message.event_id) {
index = i;
break;
}
}
}
return index;
};
return {
@@ -161,19 +328,39 @@ angular.module('eventHandlerService', [])
POWERLEVEL_EVENT: POWERLEVEL_EVENT,
CALL_EVENT: CALL_EVENT,
NAME_EVENT: NAME_EVENT,
TOPIC_EVENT: TOPIC_EVENT,
RESET_EVENT: RESET_EVENT,
reset: function() {
reset();
$rootScope.$broadcast(RESET_EVENT);
},
handleEvent: function(event, isLiveEvent) {
// FIXME: event duplication suppression is all broken as the code currently expect to handles
// events multiple times to get their side-effects...
/*
if (eventMap[event.event_id]) {
console.log("discarding duplicate event: " + JSON.stringify(event));
return;
handleEvent: function(event, isLiveEvent, isStateEvent) {
// FIXME: /initialSync on a particular room is not yet available
// So initRoom on a new room is not called. Make sure the room data is initialised here
if (event.room_id) {
initRoom(event.room_id);
}
else {
eventMap[event.event_id] = 1;
// Avoid duplicated events
// Needed for rooms where initialSync has not been done.
// In this case, we do not know where to start pagination. So, it starts from the END
// and we can have the same event (ex: joined, invitation) coming from the pagination
// AND from the event stream.
// FIXME: This workaround should be no more required when /initialSync on a particular room
// will be available (as opposite to the global /initialSync done at startup)
if (!isStateEvent) { // Do not consider state events
if (event.event_id && eventMap[event.event_id]) {
console.log("discarding duplicate event: " + JSON.stringify(event, undefined, 4));
return;
}
else {
eventMap[event.event_id] = 1;
}
}
*/
if (event.type.indexOf('m.call.') === 0) {
handleCallEvent(event, isLiveEvent);
}
@@ -189,7 +376,8 @@ angular.module('eventHandlerService', [])
handleMessage(event, isLiveEvent);
break;
case "m.room.member":
handleRoomMember(event, isLiveEvent);
isStateEvent = true;
handleRoomMember(event, isLiveEvent, isStateEvent);
break;
case "m.presence":
handlePresence(event, isLiveEvent);
@@ -202,7 +390,10 @@ angular.module('eventHandlerService', [])
handlePowerLevels(event, isLiveEvent);
break;
case 'm.room.name':
handleRoomName(event, isLiveEvent);
handleRoomName(event, isLiveEvent, isStateEvent);
break;
case 'm.room.topic':
handleRoomTopic(event, isLiveEvent, isStateEvent);
break;
default:
console.log("Unable to handle event type " + event.type);
@@ -214,24 +405,130 @@ angular.module('eventHandlerService', [])
// isLiveEvents determines whether notifications should be shown, whether
// messages get appended to the start/end of lists, etc.
handleEvents: function(events, isLiveEvents) {
handleEvents: function(events, isLiveEvents, isStateEvents) {
// XXX FIXME TODO: isStateEvents is being left as undefined sometimes. It makes no sense
// to have isStateEvents as an arg, since things like m.room.member are ALWAYS state events.
for (var i=0; i<events.length; i++) {
this.handleEvent(events[i], isLiveEvents);
this.handleEvent(events[i], isLiveEvents, isStateEvents);
}
},
handleInitialSyncDone: function() {
// Handle messages from /initialSync or /messages
handleRoomMessages: function(room_id, messages, isLiveEvents, dir) {
initRoom(room_id);
var events = messages.chunk;
// Handles messages according to their time order
if (dir && 'b' === dir) {
// paginateBackMessages requests messages to be in reverse chronological order
for (var i=0; i<events.length; i++) {
// FIXME: Being live != being state
this.handleEvent(events[i], isLiveEvents, isLiveEvents);
}
// Store how far back we've paginated
$rootScope.events.rooms[room_id].pagination.earliest_token = messages.end;
}
else {
// InitialSync returns messages in chronological order
for (var i=events.length - 1; i>=0; i--) {
// FIXME: Being live != being state
this.handleEvent(events[i], isLiveEvents, isLiveEvents);
}
// Store where to start pagination
$rootScope.events.rooms[room_id].pagination.earliest_token = messages.start;
}
},
handleInitialSyncDone: function(initialSyncData) {
console.log("# handleInitialSyncDone");
InitialSyncDeferred.resolve($rootScope.events, $rootScope.presence);
initialSyncDeferred.resolve(initialSyncData);
},
// Returns a promise that resolves when the initialSync request has been processed
waitForInitialSyncCompletion: function() {
return InitialSyncDeferred.promise;
return initialSyncDeferred.promise;
},
resetRoomMessages: function(room_id) {
resetRoomMessages(room_id);
},
/**
* Return the last message event of a room
* @param {String} room_id the room id
* @param {Boolean} filterFake true to not take into account fake messages
* @returns {undefined | Event} the last message event if available
*/
getLastMessage: function(room_id, filterEcho) {
var lastMessage;
var room = $rootScope.events.rooms[room_id];
if (room) {
for (var i = room.messages.length - 1; i >= 0; i--) {
var message = room.messages[i];
if (!filterEcho || undefined === message.echo_msg_state) {
lastMessage = message;
break;
}
}
}
return lastMessage;
},
/**
* Compute the room users number, ie the number of members who has joined the room.
* @param {String} room_id the room id
* @returns {undefined | Number} the room users number if available
*/
getUsersCountInRoom: function(room_id) {
var memberCount;
var room = $rootScope.events.rooms[room_id];
if (room) {
memberCount = 0;
for (var i in room.members) {
var member = room.members[i];
if ("join" === member.membership) {
memberCount = memberCount + 1;
}
}
}
return memberCount;
},
/**
* Get the member object of a room member
* @param {String} room_id the room id
* @param {String} user_id the id of the user
* @returns {undefined | Object} the member object of this user in this room if he is part of the room
*/
getMember: function(room_id, user_id) {
var member;
var room = $rootScope.events.rooms[room_id];
if (room) {
member = room.members[user_id];
}
return member;
},
setRoomVisibility: function(room_id, visible) {
if (!visible) {
return;
}
initRoom(room_id);
var room = $rootScope.events.rooms[room_id];
if (room) {
room.visibility = visible;
}
}
};
}]);

View File

@@ -104,24 +104,33 @@ angular.module('eventStreamService', [])
settings.isActive = true;
var deferred = $q.defer();
// FIXME: We are discarding all the messages.
matrixService.rooms().then(
// Initial sync: get all information and the last 30 messages of all rooms of the user
// 30 messages should be enough to display a full page of messages in a room
// without requiring to make an additional request
matrixService.initialSync(30, false).then(
function(response) {
var rooms = response.data.rooms;
for (var i = 0; i < rooms.length; ++i) {
var room = rooms[i];
// console.log("got room: " + room.room_id);
if ("state" in room) {
eventHandlerService.handleEvents(room.state, false);
if ("messages" in room) {
eventHandlerService.handleRoomMessages(room.room_id, room.messages, false);
}
if ("state" in room) {
eventHandlerService.handleEvents(room.state, false, true);
}
eventHandlerService.setRoomVisibility(room.room_id, room.visibility);
}
var presence = response.data.presence;
eventHandlerService.handleEvents(presence, false);
// Initial sync is done
eventHandlerService.handleInitialSyncDone();
eventHandlerService.handleInitialSyncDone(response);
// Start event streaming from that point
settings.from = response.data.end;
doEventStream(deferred);
},

View File

@@ -35,102 +35,155 @@ var forAllTracksOnStream = function(s, f) {
forAllAudioTracksOnStream(s, f);
}
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection; // but not mozRTCPeerConnection because its interface is not compatible
window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription;
window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;
angular.module('MatrixCall', [])
.factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope) {
.factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope, $timeout) {
var MatrixCall = function(room_id) {
this.room_id = room_id;
this.call_id = "c" + new Date().getTime();
this.state = 'fledgling';
this.didConnect = false;
// a queue for candidates waiting to go out. We try to amalgamate candidates into a single candidate message where possible
this.candidateSendQueue = [];
this.candidateSendTries = 0;
}
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
MatrixCall.CALL_TIMEOUT = 60000;
window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
MatrixCall.prototype.createPeerConnection = function() {
var stunServer = 'stun:stun.l.google.com:19302';
var pc;
if (window.mozRTCPeerConnection) {
pc = new window.mozRTCPeerConnection({'url': stunServer});
} else {
pc = new window.RTCPeerConnection({"iceServers":[{"urls":"stun:stun.l.google.com:19302"}]});
}
var self = this;
pc.oniceconnectionstatechange = function() { self.onIceConnectionStateChanged(); };
pc.onsignalingstatechange = function() { self.onSignallingStateChanged(); };
pc.onicecandidate = function(c) { self.gotLocalIceCandidate(c); };
pc.onaddstream = function(s) { self.onAddStream(s); };
return pc;
}
MatrixCall.prototype.placeCall = function() {
self = this;
MatrixCall.prototype.placeCall = function(config) {
var self = this;
matrixPhoneService.callPlaced(this);
navigator.getUserMedia({audio: true, video: false}, function(s) { self.gotUserMediaForInvite(s); }, function(e) { self.getUserMediaFailed(e); });
self.state = 'wait_local_media';
navigator.getUserMedia({audio: config.audio, video: config.video}, function(s) { self.gotUserMediaForInvite(s); }, function(e) { self.getUserMediaFailed(e); });
this.state = 'wait_local_media';
this.direction = 'outbound';
this.config = config;
};
MatrixCall.prototype.initWithInvite = function(msg) {
this.msg = msg;
this.peerConn = new window.RTCPeerConnection({"iceServers":[{"urls":"stun:stun.l.google.com:19302"}]})
self= this;
this.peerConn.oniceconnectionstatechange = function() { self.onIceConnectionStateChanged(); };
this.peerConn.onicecandidate = function(c) { self.gotLocalIceCandidate(c); };
this.peerConn.onsignalingstatechange = function() { self.onSignallingStateChanged(); };
this.peerConn.onaddstream = function(s) { self.onAddStream(s); };
this.peerConn.setRemoteDescription(new RTCSessionDescription(this.msg.offer), self.onSetRemoteDescriptionSuccess, self.onSetRemoteDescriptionError);
MatrixCall.prototype.initWithInvite = function(event) {
this.msg = event.content;
this.peerConn = this.createPeerConnection();
this.peerConn.setRemoteDescription(new RTCSessionDescription(this.msg.offer), this.onSetRemoteDescriptionSuccess, this.onSetRemoteDescriptionError);
this.state = 'ringing';
this.direction = 'inbound';
var self = this;
$timeout(function() {
if (self.state == 'ringing') {
self.state = 'ended';
self.hangupParty = 'remote'; // effectively
self.stopAllMedia();
if (self.peerConn.signalingState != 'closed') self.peerConn.close();
if (self.onHangup) self.onHangup(self);
}
}, this.msg.lifetime - event.age);
};
// perverse as it may seem, sometimes we want to instantiate a call with a hangup message
// (because when getting the state of the room on load, events come in reverse order and
// we want to remember that a call has been hung up)
MatrixCall.prototype.initWithHangup = function(event) {
this.msg = event.content;
this.state = 'ended';
};
MatrixCall.prototype.answer = function() {
console.trace("Answering call "+this.call_id);
self = this;
navigator.getUserMedia({audio: true, video: false}, function(s) { self.gotUserMediaForAnswer(s); }, function(e) { self.getUserMediaFailed(e); });
this.state = 'wait_local_media';
console.log("Answering call "+this.call_id);
var self = this;
if (!this.localAVStream && !this.waitForLocalAVStream) {
navigator.getUserMedia({audio: true, video: false}, function(s) { self.gotUserMediaForAnswer(s); }, function(e) { self.getUserMediaFailed(e); });
this.state = 'wait_local_media';
} else if (this.localAVStream) {
this.gotUserMediaForAnswer(this.localAVStream);
} else if (this.waitForLocalAVStream) {
this.state = 'wait_local_media';
}
};
MatrixCall.prototype.stopAllMedia = function() {
if (this.localAVStream) {
forAllTracksOnStream(this.localAVStream, function(t) {
t.stop();
if (t.stop) t.stop();
});
}
if (this.remoteAVStream) {
forAllTracksOnStream(this.remoteAVStream, function(t) {
t.stop();
if (t.stop) t.stop();
});
}
};
MatrixCall.prototype.hangup = function() {
console.trace("Ending call "+this.call_id);
MatrixCall.prototype.hangup = function(suppressEvent) {
console.log("Ending call "+this.call_id);
this.stopAllMedia();
if (this.peerConn) this.peerConn.close();
this.hangupParty = 'local';
var content = {
version: 0,
call_id: this.call_id,
};
matrixService.sendEvent(this.room_id, 'm.call.hangup', undefined, content).then(this.messageSent, this.messageSendFailed);
this.sendEventWithRetry('m.call.hangup', content);
this.state = 'ended';
if (this.onHangup && !suppressEvent) this.onHangup(this);
};
MatrixCall.prototype.gotUserMediaForInvite = function(stream) {
if (this.successor) {
this.successor.gotUserMediaForAnswer(stream);
return;
}
if (this.state == 'ended') return;
this.localAVStream = stream;
var audioTracks = stream.getAudioTracks();
for (var i = 0; i < audioTracks.length; i++) {
audioTracks[i].enabled = true;
}
this.peerConn = new window.RTCPeerConnection({"iceServers":[{"urls":"stun:stun.l.google.com:19302"}]})
self = this;
this.peerConn.oniceconnectionstatechange = function() { self.onIceConnectionStateChanged(); };
this.peerConn.onsignalingstatechange = function() { self.onSignallingStateChanged(); };
this.peerConn.onicecandidate = function(c) { self.gotLocalIceCandidate(c); };
this.peerConn.onaddstream = function(s) { self.onAddStream(s); };
this.peerConn = this.createPeerConnection();
this.peerConn.addStream(stream);
var self = this;
this.peerConn.createOffer(function(d) {
self.gotLocalOffer(d);
}, function(e) {
self.getLocalOfferFailed(e);
});
this.state = 'create_offer';
$rootScope.$apply(function() {
self.state = 'create_offer';
});
};
MatrixCall.prototype.gotUserMediaForAnswer = function(stream) {
if (this.state == 'ended') return;
this.localAVStream = stream;
var audioTracks = stream.getAudioTracks();
for (var i = 0; i < audioTracks.length; i++) {
audioTracks[i].enabled = true;
}
this.peerConn.addStream(stream);
self = this;
var self = this;
var constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
@@ -138,64 +191,77 @@ angular.module('MatrixCall', [])
},
};
this.peerConn.createAnswer(function(d) { self.createdAnswer(d); }, function(e) {}, constraints);
this.state = 'create_answer';
// This can't be in an apply() because it's called by a predecessor call under glare conditions :(
self.state = 'create_answer';
};
MatrixCall.prototype.gotLocalIceCandidate = function(event) {
console.trace(event);
console.log(event);
if (event.candidate) {
var content = {
version: 0,
call_id: this.call_id,
candidate: event.candidate
};
matrixService.sendEvent(this.room_id, 'm.call.candidate', undefined, content).then(this.messageSent, this.messageSendFailed);
this.sendCandidate(event.candidate);
}
}
MatrixCall.prototype.gotRemoteIceCandidate = function(cand) {
console.trace("Got ICE candidate from remote: "+cand);
var candidateObject = new RTCIceCandidate({
sdpMLineIndex: cand.label,
candidate: cand.candidate
});
this.peerConn.addIceCandidate(candidateObject, function() {}, function(e) {});
console.log("Got ICE candidate from remote: "+cand);
if (this.state == 'ended') {
console.log("Ignoring remote ICE candidate because call has ended");
return;
}
this.peerConn.addIceCandidate(new RTCIceCandidate(cand), function() {}, function(e) {});
};
MatrixCall.prototype.receivedAnswer = function(msg) {
this.peerConn.setRemoteDescription(new RTCSessionDescription(msg.answer), self.onSetRemoteDescriptionSuccess, self.onSetRemoteDescriptionError);
if (this.state == 'ended') return;
this.peerConn.setRemoteDescription(new RTCSessionDescription(msg.answer), this.onSetRemoteDescriptionSuccess, this.onSetRemoteDescriptionError);
this.state = 'connecting';
};
MatrixCall.prototype.gotLocalOffer = function(description) {
console.trace("Created offer: "+description);
console.log("Created offer: "+description);
if (this.state == 'ended') {
console.log("Ignoring newly created offer on call ID "+this.call_id+" because the call has ended");
return;
}
this.peerConn.setLocalDescription(description);
var content = {
version: 0,
call_id: this.call_id,
offer: description
offer: description,
lifetime: MatrixCall.CALL_TIMEOUT
};
matrixService.sendEvent(this.room_id, 'm.call.invite', undefined, content).then(this.messageSent, this.messageSendFailed);
this.state = 'invite_sent';
this.sendEventWithRetry('m.call.invite', content);
var self = this;
$timeout(function() {
if (self.state == 'invite_sent') {
self.hangupReason = 'invite_timeout';
self.hangup();
}
}, MatrixCall.CALL_TIMEOUT);
$rootScope.$apply(function() {
self.state = 'invite_sent';
});
};
MatrixCall.prototype.createdAnswer = function(description) {
console.trace("Created answer: "+description);
console.log("Created answer: "+description);
this.peerConn.setLocalDescription(description);
var content = {
version: 0,
call_id: this.call_id,
answer: description
};
matrixService.sendEvent(this.room_id, 'm.call.answer', undefined, content).then(this.messageSent, this.messageSendFailed);
this.state = 'connecting';
};
MatrixCall.prototype.messageSent = function() {
};
MatrixCall.prototype.messageSendFailed = function(error) {
this.sendEventWithRetry('m.call.answer', content);
var self = this;
$rootScope.$apply(function() {
self.state = 'connecting';
});
};
MatrixCall.prototype.getLocalOfferFailed = function(error) {
@@ -204,33 +270,36 @@ angular.module('MatrixCall', [])
MatrixCall.prototype.getUserMediaFailed = function() {
this.onError("Couldn't start capturing audio! Is your microphone set up?");
this.hangup();
};
MatrixCall.prototype.onIceConnectionStateChanged = function() {
if (this.state == 'ended') return; // because ICE can still complete as we're ending the call
console.trace("Ice connection state changed to: "+this.peerConn.iceConnectionState);
console.log("Ice connection state changed to: "+this.peerConn.iceConnectionState);
// ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet
if (this.peerConn.iceConnectionState == 'completed' || this.peerConn.iceConnectionState == 'connected') {
this.state = 'connected';
this.didConnect = true;
$rootScope.$apply();
var self = this;
$rootScope.$apply(function() {
self.state = 'connected';
self.didConnect = true;
});
}
};
MatrixCall.prototype.onSignallingStateChanged = function() {
console.trace("Signalling state changed to: "+this.peerConn.signalingState);
console.log("call "+this.call_id+": Signalling state changed to: "+this.peerConn.signalingState);
};
MatrixCall.prototype.onSetRemoteDescriptionSuccess = function() {
console.trace("Set remote description");
console.log("Set remote description");
};
MatrixCall.prototype.onSetRemoteDescriptionError = function(e) {
console.trace("Failed to set remote description"+e);
console.log("Failed to set remote description"+e);
};
MatrixCall.prototype.onAddStream = function(event) {
console.trace("Stream added"+event);
console.log("Stream added"+event);
var s = event.stream;
@@ -251,23 +320,127 @@ angular.module('MatrixCall', [])
};
MatrixCall.prototype.onRemoteStreamStarted = function(event) {
this.state = 'connected';
var self = this;
$rootScope.$apply(function() {
self.state = 'connected';
});
};
MatrixCall.prototype.onRemoteStreamEnded = function(event) {
this.state = 'ended';
this.stopAllMedia();
this.onHangup();
console.log("Remote stream ended");
var self = this;
$rootScope.$apply(function() {
self.state = 'ended';
self.hangupParty = 'remote';
self.stopAllMedia();
if (self.peerConn.signalingState != 'closed') self.peerConn.close();
if (self.onHangup) self.onHangup(self);
});
};
MatrixCall.prototype.onRemoteStreamTrackStarted = function(event) {
this.state = 'connected';
var self = this;
$rootScope.$apply(function() {
self.state = 'connected';
});
};
MatrixCall.prototype.onHangupReceived = function() {
console.log("Hangup received");
this.state = 'ended';
this.hangupParty = 'remote';
this.stopAllMedia();
this.onHangup();
if (this.peerConn.signalingState != 'closed') this.peerConn.close();
if (this.onHangup) this.onHangup(this);
};
MatrixCall.prototype.replacedBy = function(newCall) {
console.log(this.call_id+" being replaced by "+newCall.call_id);
if (this.state == 'wait_local_media') {
console.log("Telling new call to wait for local media");
newCall.waitForLocalAVStream = true;
} else if (this.state == 'create_offer') {
console.log("Handing local stream to new call");
newCall.localAVStream = this.localAVStream;
delete(this.localAVStream);
} else if (this.state == 'invite_sent') {
console.log("Handing local stream to new call");
newCall.localAVStream = this.localAVStream;
delete(this.localAVStream);
}
this.successor = newCall;
this.hangup(true);
};
MatrixCall.prototype.sendEventWithRetry = function(evType, content) {
var ev = { type:evType, content:content, tries:1 };
var self = this;
matrixService.sendEvent(this.room_id, evType, undefined, content).then(this.eventSent, function(error) { self.eventSendFailed(ev, error); } );
};
MatrixCall.prototype.eventSent = function() {
};
MatrixCall.prototype.eventSendFailed = function(ev, error) {
if (ev.tries > 5) {
console.log("Failed to send event of type "+ev.type+" on attempt "+ev.tries+". Giving up.");
return;
}
var delayMs = 500 * Math.pow(2, ev.tries);
console.log("Failed to send event of type "+ev.type+". Retrying in "+delayMs+"ms");
++ev.tries;
var self = this;
$timeout(function() {
matrixService.sendEvent(self.room_id, ev.type, undefined, ev.content).then(self.eventSent, function(error) { self.eventSendFailed(ev, error); } );
}, delayMs);
};
// Sends candidates with are sent in a special way because we try to amalgamate them into one message
MatrixCall.prototype.sendCandidate = function(content) {
this.candidateSendQueue.push(content);
var self = this;
if (this.candidateSendTries == 0) $timeout(function() { self.sendCandidateQueue(); }, 100);
};
MatrixCall.prototype.sendCandidateQueue = function(content) {
if (this.candidateSendQueue.length == 0) return;
var cands = this.candidateSendQueue;
this.candidateSendQueue = [];
++this.candidateSendTries;
var content = {
version: 0,
call_id: this.call_id,
candidates: cands
};
var self = this;
console.log("Attempting to send "+cands.length+" candidates");
matrixService.sendEvent(self.room_id, 'm.call.candidates', undefined, content).then(function() { self.candsSent(); }, function(error) { self.candsSendFailed(cands, error); } );
};
MatrixCall.prototype.candsSent = function() {
this.candidateSendTries = 0;
this.sendCandidateQueue();
};
MatrixCall.prototype.candsSendFailed = function(cands, error) {
for (var i = 0; i < cands.length; ++i) {
this.candidateSendQueue.push(cands[i]);
}
if (this.candidateSendTries > 5) {
console.log("Failed to send candidates on attempt "+ev.tries+". Giving up for now.");
this.candidateSendTries = 0;
return;
}
var delayMs = 500 * Math.pow(2, this.candidateSendTries);
++this.candidateSendTries;
console.log("Failed to send candidates. Retrying in "+delayMs+"ms");
var self = this;
$timeout(function() {
self.sendCandidateQueue();
}, delayMs);
};
return MatrixCall;

View File

@@ -26,70 +26,74 @@ angular.module('matrixFilter', [])
// If there is an alias, use it
// TODO: only one alias is managed for now
var alias = matrixService.getRoomIdToAliasMapping(room_id);
if (alias) {
roomName = alias;
}
if (undefined === roomName) {
// Else, build the name from its users
var room = $rootScope.events.rooms[room_id];
if (room) {
var room_name_event = room["m.room.name"];
var room = $rootScope.events.rooms[room_id];
if (room) {
// Get name from room state date
var room_name_event = room["m.room.name"];
if (room_name_event) {
roomName = room_name_event.content.name;
}
else if (alias) {
roomName = alias;
}
else if (room.members) {
// Else, build the name from its users
// FIXME: Is it still required?
// Limit the room renaming to 1:1 room
if (2 === Object.keys(room.members).length) {
for (var i in room.members) {
var member = room.members[i];
if (member.state_key !== matrixService.config().user_id) {
if (room_name_event) {
roomName = room_name_event.content.name;
}
else if (room.members) {
// Limit the room renaming to 1:1 room
if (2 === Object.keys(room.members).length) {
for (var i in room.members) {
var member = room.members[i];
if (member.state_key !== matrixService.config().user_id) {
if (member.state_key in $rootScope.presence) {
// If the user is available in presence, use the displayname there
// as it is the most uptodate
roomName = $rootScope.presence[member.state_key].content.displayname;
}
else if (member.content.displayname) {
roomName = member.content.displayname;
}
else {
roomName = member.state_key;
}
if (member.state_key in $rootScope.presence) {
// If the user is available in presence, use the displayname there
// as it is the most uptodate
roomName = $rootScope.presence[member.state_key].content.displayname;
}
else if (member.content.displayname) {
roomName = member.content.displayname;
}
else {
roomName = member.state_key;
}
}
}
else if (1 === Object.keys(room.members).length) {
// The other member may be in the invite list, get all invited users
var invitedUserIDs = [];
for (var i in room.messages) {
var message = room.messages[i];
if ("m.room.member" === message.type && "invite" === message.membership) {
// Make sure there is no duplicate user
if (-1 === invitedUserIDs.indexOf(message.state_key)) {
invitedUserIDs.push(message.state_key);
}
}
}
// For now, only 1:1 room needs to be renamed. It means only 1 invited user
if (1 === invitedUserIDs.length) {
var userID = invitedUserIDs[0];
}
else if (1 === Object.keys(room.members).length) {
// The other member may be in the invite list, get all invited users
var invitedUserIDs = [];
for (var i in room.messages) {
var message = room.messages[i];
if ("m.room.member" === message.type && "invite" === message.membership) {
// Make sure there is no duplicate user
if (-1 === invitedUserIDs.indexOf(message.state_key)) {
invitedUserIDs.push(message.state_key);
}
}
}
// Try to resolve his displayname in presence global data
if (userID in $rootScope.presence) {
roomName = $rootScope.presence[userID].content.displayname;
}
else {
roomName = userID;
}
// For now, only 1:1 room needs to be renamed. It means only 1 invited user
if (1 === invitedUserIDs.length) {
var userID = invitedUserIDs[0];
// Try to resolve his displayname in presence global data
if (userID in $rootScope.presence) {
roomName = $rootScope.presence[userID].content.displayname;
}
else {
roomName = userID;
}
}
}
}
}
// Always show the alias in the room displayed name
if (roomName && alias && alias !== roomName) {
roomName += " (" + alias + ")";
}
if (undefined === roomName) {
// By default, use the room ID
roomName = room_id;

View File

@@ -22,45 +22,122 @@ angular.module('matrixPhoneService', [])
};
matrixPhoneService.INCOMING_CALL_EVENT = "INCOMING_CALL_EVENT";
matrixPhoneService.REPLACED_CALL_EVENT = "REPLACED_CALL_EVENT";
matrixPhoneService.allCalls = {};
// a place to save candidates that come in for calls we haven't got invites for yet (when paginating backwards)
matrixPhoneService.candidatesByCall = {};
matrixPhoneService.callPlaced = function(call) {
matrixPhoneService.allCalls[call.call_id] = call;
};
$rootScope.$on(eventHandlerService.CALL_EVENT, function(ngEvent, event, isLive) {
if (!isLive) return; // until matrix supports expiring messages
if (event.user_id == matrixService.config().user_id) return;
var msg = event.content;
if (event.type == 'm.call.invite') {
if (event.age == undefined || msg.lifetime == undefined) {
// if the event doesn't have either an age (the HS is too old) or a lifetime
// (the sending client was too old when it sent it) then fall back to old behaviour
if (!isLive) return; // until matrix supports expiring messages
}
if (event.age > msg.lifetime) {
console.log("Ignoring expired call event of type "+event.type);
return;
}
var call = undefined;
if (!isLive) {
// if this event wasn't live then this call may already be over
call = matrixPhoneService.allCalls[msg.call_id];
if (call && call.state == 'ended') {
return;
}
}
var MatrixCall = $injector.get('MatrixCall');
var call = new MatrixCall(event.room_id);
call.call_id = msg.call_id;
call.initWithInvite(msg);
call.initWithInvite(event);
matrixPhoneService.allCalls[call.call_id] = call;
$rootScope.$broadcast(matrixPhoneService.INCOMING_CALL_EVENT, call);
// if we stashed candidate events for that call ID, play them back now
if (!isLive && matrixPhoneService.candidatesByCall[call.call_id] != undefined) {
for (var i = 0; i < matrixPhoneService.candidatesByCall[call.call_id].length; ++i) {
call.gotRemoteIceCandidate(matrixPhoneService.candidatesByCall[call.call_id][i]);
}
}
// Were we trying to call that user (room)?
var existingCall;
var callIds = Object.keys(matrixPhoneService.allCalls);
for (var i = 0; i < callIds.length; ++i) {
var thisCallId = callIds[i];
var thisCall = matrixPhoneService.allCalls[thisCallId];
if (call.room_id == thisCall.room_id && thisCall.direction == 'outbound'
&& (thisCall.state == 'wait_local_media' || thisCall.state == 'create_offer' || thisCall.state == 'invite_sent')) {
existingCall = thisCall;
break;
}
}
if (existingCall) {
// If we've only got to wait_local_media or create_offer and we've got an invite,
// pick the incoming call because we know we haven't sent our invite yet
// otherwise, pick whichever call has the lowest call ID (by string comparison)
if (existingCall.state == 'wait_local_media' || existingCall.state == 'create_offer' || existingCall.call_id > call.call_id) {
console.log("Glare detected: answering incoming call "+call.call_id+" and canceling outgoing call "+existingCall.call_id);
existingCall.replacedBy(call);
call.answer();
$rootScope.$broadcast(matrixPhoneService.REPLACED_CALL_EVENT, existingCall, call);
} else {
console.log("Glare detected: rejecting incoming call "+call.call_id+" and keeping outgoing call "+existingCall.call_id);
call.hangup();
}
} else {
$rootScope.$broadcast(matrixPhoneService.INCOMING_CALL_EVENT, call);
}
} else if (event.type == 'm.call.answer') {
var call = matrixPhoneService.allCalls[msg.call_id];
if (!call) {
console.trace("Got answer for unknown call ID "+msg.call_id);
console.log("Got answer for unknown call ID "+msg.call_id);
return;
}
call.receivedAnswer(msg);
} else if (event.type == 'm.call.candidate') {
} else if (event.type == 'm.call.candidates') {
var call = matrixPhoneService.allCalls[msg.call_id];
if (!call) {
console.trace("Got candidate for unknown call ID "+msg.call_id);
if (!call && isLive) {
console.log("Got candidates for unknown call ID "+msg.call_id);
return;
} else if (!call) {
if (matrixPhoneService.candidatesByCall[msg.call_id] == undefined) {
matrixPhoneService.candidatesByCall[msg.call_id] = [];
}
matrixPhoneService.candidatesByCall[msg.call_id] = matrixPhoneService.candidatesByCall[msg.call_id].concat(msg.candidates);
} else {
for (var i = 0; i < msg.candidates.length; ++i) {
call.gotRemoteIceCandidate(msg.candidates[i]);
}
}
call.gotRemoteIceCandidate(msg.candidate);
} else if (event.type == 'm.call.hangup') {
var call = matrixPhoneService.allCalls[msg.call_id];
if (!call) {
console.trace("Got hangup for unknown call ID "+msg.call_id);
return;
if (!call && isLive) {
console.log("Got hangup for unknown call ID "+msg.call_id);
} else if (!call) {
// if not live, store the fact that the call has ended because we're probably getting events backwards so
// the hangup will come before the invite
var MatrixCall = $injector.get('MatrixCall');
var call = new MatrixCall(event.room_id);
call.call_id = msg.call_id;
call.initWithHangup(event);
matrixPhoneService.allCalls[msg.call_id] = call;
} else {
call.onHangupReceived();
delete(matrixPhoneService.allCalls[msg.call_id]);
}
call.onHangupReceived();
matrixPhoneService.allCalls[msg.call_id] = undefined;
}
});

View File

@@ -81,38 +81,155 @@ angular.module('matrixService', [])
return $http(request);
};
var doRegisterLogin = function(path, loginType, sessionId, userName, password, threepidCreds) {
var data = {};
if (loginType === "m.login.recaptcha") {
var challengeToken = Recaptcha.get_challenge();
var captchaEntry = Recaptcha.get_response();
data = {
type: "m.login.recaptcha",
challenge: challengeToken,
response: captchaEntry
};
}
else if (loginType === "m.login.email.identity") {
data = {
threepidCreds: threepidCreds
};
}
else if (loginType === "m.login.password") {
data = {
user: userName,
password: password
};
}
if (sessionId) {
data.session = sessionId;
}
data.type = loginType;
console.log("doRegisterLogin >>> " + loginType);
return doRequest("POST", path, undefined, data);
};
return {
/****** Home server API ******/
prefix: prefixPath,
// Register an user
register: function(user_name, password, threepidCreds, useCaptcha) {
// The REST path spec
register: function(user_name, password, threepidCreds, useCaptcha) {
// registration is composed of multiple requests, to check you can
// register, then to actually register. This deferred will fire when
// all the requests are done, along with the final response.
var deferred = $q.defer();
var path = "/register";
var data = {
user_id: user_name,
password: password,
threepidCreds: threepidCreds
};
// check we can actually register with this HS.
doRequest("GET", path, undefined, undefined).then(
function(response) {
console.log("/register [1] : "+JSON.stringify(response));
var flows = response.data.flows;
var knownTypes = [
"m.login.password",
"m.login.recaptcha",
"m.login.email.identity"
];
// if they entered 3pid creds, we want to use a flow which uses it.
var useThreePidFlow = threepidCreds != undefined;
var flowIndex = 0;
var firstRegType = undefined;
for (var i=0; i<flows.length; i++) {
var isThreePidFlow = false;
if (flows[i].stages) {
for (var j=0; j<flows[i].stages.length; j++) {
var regType = flows[i].stages[j];
if (knownTypes.indexOf(regType) === -1) {
deferred.reject("Unknown type: "+regType);
return;
}
if (regType == "m.login.email.identity") {
isThreePidFlow = true;
}
if (!useCaptcha && regType == "m.login.recaptcha") {
console.error("Web client setup to not use captcha, but HS demands a captcha.");
deferred.reject({
data: {
errcode: "M_CAPTCHA_NEEDED",
error: "Home server requires a captcha."
}
});
return;
}
}
}
if ( (isThreePidFlow && useThreePidFlow) || (!isThreePidFlow && !useThreePidFlow) ) {
flowIndex = i;
}
if (knownTypes.indexOf(flows[i].type) == -1) {
deferred.reject("Unknown type: "+flows[i].type);
return;
}
}
// looks like we can register fine, go ahead and do it.
console.log("Using flow " + JSON.stringify(flows[flowIndex]));
firstRegType = flows[flowIndex].type;
var sessionId = undefined;
// generic response processor so it can loop as many times as required
var loginResponseFunc = function(response) {
if (response.data.session) {
sessionId = response.data.session;
}
console.log("login response: " + JSON.stringify(response.data));
if (response.data.access_token) {
deferred.resolve(response);
}
else if (response.data.next) {
var nextType = response.data.next;
if (response.data.next instanceof Array) {
for (var i=0; i<response.data.next.length; i++) {
if (useThreePidFlow && response.data.next[i] == "m.login.email.identity") {
nextType = response.data.next[i];
break;
}
else if (!useThreePidFlow && response.data.next[i] != "m.login.email.identity") {
nextType = response.data.next[i];
break;
}
}
}
return doRegisterLogin(path, nextType, sessionId, user_name, password, threepidCreds).then(
loginResponseFunc,
function(err) {
deferred.reject(err);
}
);
}
else {
deferred.reject("Unknown continuation: "+JSON.stringify(response));
}
};
// set the ball rolling
doRegisterLogin(path, firstRegType, undefined, user_name, password, threepidCreds).then(
loginResponseFunc,
function(err) {
deferred.reject(err);
}
);
},
function(err) {
deferred.reject(err);
}
);
if (useCaptcha) {
// Not all home servers will require captcha on signup, but if this flag is checked,
// send captcha information.
// TODO: Might be nice to make this a bit more flexible..
var challengeToken = Recaptcha.get_challenge();
var captchaEntry = Recaptcha.get_response();
var captchaType = "m.login.recaptcha";
data.captcha = {
type: captchaType,
challenge: challengeToken,
response: captchaEntry
};
}
return doRequest("POST", path, undefined, data);
return deferred.promise;
},
// Create a room
@@ -130,8 +247,9 @@ angular.module('matrixService', [])
return doRequest("POST", path, undefined, req);
},
// List all rooms joined or been invited to
rooms: function(limit, feedback) {
// Get the user's current state: his presence, the list of his rooms with
// the last {limit} events
initialSync: function(limit, feedback) {
// The REST path spec
var path = "/initialSync";
@@ -234,6 +352,32 @@ angular.module('matrixService', [])
return doRequest("GET", path, undefined, {});
},
setName: function(room_id, name) {
var data = {
name: name
};
return this.sendStateEvent(room_id, "m.room.name", data);
},
setTopic: function(room_id, topic) {
var data = {
topic: topic
};
return this.sendStateEvent(room_id, "m.room.topic", data);
},
sendStateEvent: function(room_id, eventType, content, state_key) {
var path = "/rooms/$room_id/state/"+eventType;
if (state_key !== undefined) {
path += "/" + state_key;
}
room_id = encodeURIComponent(room_id);
path = path.replace("$room_id", room_id);
return doRequest("PUT", path, undefined, content);
},
sendEvent: function(room_id, eventType, txn_id, content) {
// The REST path spec

View File

@@ -24,7 +24,7 @@ angular.module('mPresence', [])
.service('mPresence', ['$timeout', 'matrixService', function ($timeout, matrixService) {
// Time in ms after that a user is considered as unavailable/away
var UNAVAILABLE_TIME = 5 * 60000; // 5 mins
var UNAVAILABLE_TIME = 3 * 60000; // 3 mins
// The current presence state
var state = undefined;

View File

@@ -53,6 +53,8 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
// Add room_alias & room_display_name members
angular.extend(room, matrixService.getRoomAliasAndDisplayName(room));
eventHandlerService.setRoomVisibility(room.room_id, "public");
}
}
);
@@ -117,6 +119,10 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
matrixService.getDisplayName($scope.config.user_id).then(
function(response) {
$scope.profile.displayName = response.data.displayname;
var config = matrixService.config();
config.display_name = response.data.displayname;
matrixService.setConfig(config);
matrixService.saveConfig();
},
function(error) {
$scope.feedback = "Can't load display name";
@@ -142,4 +148,9 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
refresh();
};
// Clean data when user logs out
$scope.$on(eventHandlerService.RESET_EVENT, function() {
$scope.public_rooms = [];
});
}]);

View File

@@ -24,11 +24,30 @@
<h3>Public rooms</h3>
<div class="public_rooms" ng-repeat="room in public_rooms">
<div>
<a href="#/room/{{ room.room_alias ? room.room_alias : room.room_id }}" >{{ room.room_display_name }}</a>
</div>
</div>
<table class="publicTable">
<tbody ng-repeat="room in public_rooms | orderBy:'room_display_name'"
class="publicRoomEntry"
ng-class="room.room_display_name.toLowerCase().indexOf('#matrix:') === 0 ? 'roomHighlight' : ''">
<tr>
<td class="publicRoomEntry">
<a href="#/room/{{ room.room_alias ? room.room_alias : room.room_id }}" >
{{ room.room_display_name }}
</a>
</td>
<td>
<div class="publicRoomJoinedUsers"
ng-show="room.num_joined_members">
{{ room.num_joined_members }} {{ room.num_joined_members == 1 ? 'user' : 'users' }}
</div>
</td>
</tr>
<tr>
<td colspan="2" class="publicRoomTopic">
{{ room.topic }}
</td>
</tr>
</tbody>
</table>
<br/>
<div>

View File

@@ -15,7 +15,9 @@
<script src="js/angular.min.js"></script>
<script src="js/angular-route.min.js"></script>
<script src="js/angular-sanitize.min.js"></script>
<script src="js/angular-animate.min.js"></script>
<script type='text/javascript' src='js/ng-infinite-scroll-matrix.js'></script>
<script type='text/javascript' src='js/autofill-event.js'></script>
<script src="app.js"></script>
<script src="config.js"></script>
<script src="app-controller.js"></script>
@@ -56,9 +58,12 @@
<br />
<span id="callState">
<span ng-show="currentCall.state == 'invite_sent'">Calling...</span>
<span ng-show="currentCall.state == 'ringing'">Incoming Call</span>
<span ng-show="currentCall.state == 'connecting'">Call Connecting...</span>
<span ng-show="currentCall.state == 'connected'">Call Connected</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'outbound'">Call Rejected</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'remote'">Call Rejected</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'local' && currentCall.hangupReason == undefined">Call Canceled</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'local' && currentCall.hangupReason == 'invite_timeout'">User Not Responding</span>
<span ng-show="currentCall.state == 'ended' && currentCall.didConnect && currentCall.direction == 'outbound'">Call Ended</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'inbound'">Call Canceled</span>
<span ng-show="currentCall.state == 'ended' && currentCall.didConnect && currentCall.direction == 'inbound'">Call Ended</span>
@@ -70,6 +75,22 @@
<button ng-click="hangupCall()">Reject</button>
</span>
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing' && currentCall.state != 'ended' && currentCall.state != 'fledgling'">Hang up</button>
<audio id="ringAudio" loop>
<source src="media/ring.ogg" type="audio/ogg" />
<source src="media/ring.mp3" type="audio/mpeg" />
</audio>
<audio id="ringbackAudio" loop>
<source src="media/ringback.ogg" type="audio/ogg" />
<source src="media/ringback.mp3" type="audio/mpeg" />
</audio>
<audio id="callendAudio">
<source src="media/callend.ogg" type="audio/ogg" />
<source src="media/callend.mp3" type="audio/mpeg" />
</audio>
<audio id="busyAudio">
<source src="media/busy.ogg" type="audio/ogg" />
<source src="media/busy.mp3" type="audio/mpeg" />
</audio>
</div>
<a href id="headerUserId" ng-click='goToUserPage(user_id)'>{{ user_id }}</a>
&nbsp;

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,30 @@
/*
AngularJS v1.2.0
(c) 2010-2012 Google, Inc. http://angularjs.org
AngularJS v1.3.0-rc.1
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(A,s,B){'use strict';s.module("ngAnimate",["ng"]).config(["$provide","$animateProvider",function(N,D){var z=s.noop,k=s.forEach,X=D.$$selectors,T=1,f="$$ngAnimateState",E="ng-animate",g={running:!0};N.decorator("$animate",["$delegate","$injector","$sniffer","$rootElement","$timeout","$rootScope","$document",function(t,A,F,m,G,p,H){function B(a){if(a){var d=[],b={};a=a.substr(1).split(".");(F.transitions||F.animations)&&a.push("");for(var c=0;c<a.length;c++){var h=a[c],f=X[h];f&&!b[h]&&(d.push(A.get(f)),
b[h]=!0)}return d}}function n(a,d,b,c,h,g,m){function p(a){g();if(!0===a)u();else{if(a=b.data(f))a.done=u,b.data(f,a);s(v,"after",u)}}function s(c,h,g){var f=h+"End";k(c,function(k,t){var e=function(){a:{var e=h+"Complete",a=c[t];a[e]=!0;(a[f]||z)();for(a=0;a<c.length;a++)if(!c[a][e])break a;g()}};"before"!=h||"enter"!=a&&"move"!=a?k[h]?k[f]=n?k[h](b,d,e):k[h](b,e):e():e()})}function t(){m&&G(m,0,!1)}function u(){if(!u.hasBeenRun){u.hasBeenRun=!0;var a=b.data(f);a&&(n?q(b):(a.closeAnimationTimeout=
G(function(){q(b)},0,!1),b.data(f,a)));t()}}var r=(" "+((b.attr("class")||"")+" "+d)).replace(/\s+/g,".");c||(c=h?h.parent():b.parent());h=B(r);var n="addClass"==a||"removeClass"==a,r=b.data(f)||{};if(J(b,c)||0===h.length)g(),u();else{var v=[];r.running&&n&&r.structural||k(h,function(c){if(!c.allowCancel||c.allowCancel(b,a,d)){var h=c[a];"leave"==a?(c=h,h=null):c=c["before"+a.charAt(0).toUpperCase()+a.substr(1)];v.push({before:c,after:h})}});0===v.length?(g(),t()):(r.running&&(G.cancel(r.closeAnimationTimeout),
q(b),K(r.animations),(r.done||z)(!0)),"addClass"==a&&b.hasClass(d)||"removeClass"==a&&!b.hasClass(d)?(g(),t()):(b.addClass(E),b.data(f,{running:!0,structural:!n,animations:v,done:p}),s(v,"before",p)))}}function L(a){a=a[0];a.nodeType==T&&k(a.querySelectorAll("."+E),function(a){a=s.element(a);var b=a.data(f);b&&(K(b.animations),q(a))})}function K(a){k(a,function(d){a.beforeComplete||(d.beforeEnd||z)(!0);a.afterComplete||(d.afterEnd||z)(!0)})}function q(a){a[0]==m[0]?g.disabled||(g.running=!1,g.structural=
!1):(a.removeClass(E),a.removeData(f))}function J(a,d){if(g.disabled)return!0;if(a[0]==m[0])return g.disabled||g.running;do{if(0===d.length)break;var b=d[0]==m[0],c=b?g:d.data(f),c=c&&(!!c.disabled||!!c.running);if(b||c)return c;if(b)break}while(d=d.parent());return!0}m.data(f,g);p.$$postDigest(function(){g.running=!1});return{enter:function(a,d,b,c){this.enabled(!1,a);t.enter(a,d,b);p.$$postDigest(function(){n("enter","ng-enter",a,d,b,z,c)})},leave:function(a,d){L(a);this.enabled(!1,a);p.$$postDigest(function(){n("leave",
"ng-leave",a,null,null,function(){t.leave(a)},d)})},move:function(a,d,b,c){L(a);this.enabled(!1,a);t.move(a,d,b);p.$$postDigest(function(){n("move","ng-move",a,d,b,z,c)})},addClass:function(a,d,b){n("addClass",d,a,null,null,function(){t.addClass(a,d)},b)},removeClass:function(a,d,b){n("removeClass",d,a,null,null,function(){t.removeClass(a,d)},b)},enabled:function(a,d){switch(arguments.length){case 2:if(a)q(d);else{var b=d.data(f)||{};b.disabled=!0;d.data(f,b)}break;case 1:g.disabled=!a;break;default:a=
!g.disabled}return!!a}}}]);D.register("",["$window","$sniffer","$timeout",function(f,g,F){function m(e){Q.push(e);F.cancel(R);R=F(function(){k(Q,function(e){e()});Q=[];R=null;C={}},10,!1)}function G(e,a){var w=e.getAttribute("style")||"";e.setAttribute("style",(0<w.length?"; ":"")+a);return w}function p(e,a){var w=a?C[a]:null;if(!w){var b=0,d=0,c=0,l=0,g,n,m,p;k(e,function(e){if(e.nodeType==T){e=f.getComputedStyle(e)||{};m=e[h+D];b=Math.max(H(m),b);p=e[h+S];g=e[h+u];d=Math.max(H(g),d);n=e[I+u];l=
Math.max(H(n),l);var a=H(e[I+D]);0<a&&(a*=parseInt(e[I+r],10)||1);c=Math.max(a,c)}});w={total:0,transitionPropertyStyle:p,transitionDurationStyle:m,transitionDelayStyle:g,transitionDelay:d,transitionDuration:b,animationDelayStyle:n,animationDelay:l,animationDuration:c};a&&(C[a]=w)}return w}function H(e){var a=0;e=s.isString(e)?e.split(/\s*,\s*/):[];k(e,function(e){a=Math.max(parseFloat(e)||0,a)});return a}function E(e){var a=e.parent(),b=a.data(W);b||(a.data(W,++V),b=V);return b+"-"+e[0].className}
function n(e,a){var b=E(e),d=b+" "+a,c={},g=C[d]?++C[d].total:0;if(0<g){var l=a+"-stagger",c=b+" "+l;(b=!C[c])&&e.addClass(l);c=p(e,c);b&&e.removeClass(l)}e.addClass(a);d=p(e,d);l=Math.max(d.transitionDuration,d.animationDuration);if(0===l)return e.removeClass(a),!1;var b=e[0],f="";0<d.transitionDuration&&(e.addClass(U),f+=N+" ",b.style[h+S]="none");k(a.split(" "),function(a,e){f+=(0<e?" ":"")+a+"-active"});e.data(v,{className:a,activeClassName:f,maxDuration:l,classes:a+" "+f,timings:d,stagger:c,
ii:g});return!0}function L(a,b,w){function f(a){a.stopPropagation();a=a.originalEvent||a;var e=a.$manualTimeStamp||a.timeStamp||Date.now();Math.max(e-t,0)>=p&&a.elapsedTime>=n&&w()}var x=a.data(v);if(a.hasClass(b)&&x){var M=a[0],l=x.timings,k=x.stagger,n=x.maxDuration,m=x.activeClassName,p=1E3*Math.max(l.transitionDelay,l.animationDelay),t=Date.now(),s=P+" "+O,r,x=x.ii,u,y="";if(0<l.transitionDuration){M.style[h+S]="";var q=l.transitionPropertyStyle;-1==q.indexOf("all")&&(u=!0,y+=c+"transition-property: "+
q+", "+(g.msie?"-ms-zoom":"clip")+"; ",y+=c+"transition-duration: "+l.transitionDurationStyle+", "+l.transitionDuration+"s; ")}0<x&&(0<k.transitionDelay&&0===k.transitionDuration&&(q=l.transitionDelayStyle,u&&(q+=", "+l.transitionDelay+"s"),y+=c+"transition-delay: "+K(q,k.transitionDelay,x)+"; "),0<k.animationDelay&&0===k.animationDuration&&(y+=c+"animation-delay: "+K(l.animationDelayStyle,k.animationDelay,x)+"; "));0<y.length&&(r=G(M,y));a.on(s,f);a.addClass(m);return function(c){a.off(s,f);a.removeClass(m);
d(a,b);null!=r&&(0<r.length?M.setAttribute("style",r):M.removeAttribute("style"))}}w()}function K(a,b,d){var c="";k(a.split(","),function(a,e){c+=(0<e?",":"")+(d*b+parseInt(a,10))+"s"});return c}function q(a,b){if(n(a,b))return function(c){c&&d(a,b)}}function J(a,b,c){if(a.data(v))return L(a,b,c);d(a,b);c()}function a(a,b,c){var d=q(a,b);if(d){var f=d;m(function(){f=J(a,b,c)});return function(a){(f||z)(a)}}c()}function d(a,b){a.removeClass(b);a.removeClass(U);a.removeData(v)}function b(a,b){var c=
"";a=s.isArray(a)?a:a.split(/\s+/);k(a,function(a,e){a&&0<a.length&&(c+=(0<e?" ":"")+a+b)});return c}var c="",h,O,I,P;A.ontransitionend===B&&A.onwebkittransitionend!==B?(c="-webkit-",h="WebkitTransition",O="webkitTransitionEnd transitionend"):(h="transition",O="transitionend");A.onanimationend===B&&A.onwebkitanimationend!==B?(c="-webkit-",I="WebkitAnimation",P="webkitAnimationEnd animationend"):(I="animation",P="animationend");var D="Duration",S="Property",u="Delay",r="IterationCount",W="$$ngAnimateKey",
v="$$ngAnimateCSS3Data",U="ng-animate-start",N="ng-animate-active",C={},V=0,Q=[],R;return{allowCancel:function(a,c,d){var f=(a.data(v)||{}).classes;if(!f||0<=["enter","leave","move"].indexOf(c))return!0;var h=a.parent(),g=s.element(a[0].cloneNode());g.attr("style","position:absolute; top:-9999px; left:-9999px");g.removeAttr("id");g.html("");k(f.split(" "),function(a){g.removeClass(a)});g.addClass(b(d,"addClass"==c?"-add":"-remove"));h.append(g);a=p(g);g.remove();return 0<Math.max(a.transitionDuration,
a.animationDuration)},enter:function(b,c){return a(b,"ng-enter",c)},leave:function(b,c){return a(b,"ng-leave",c)},move:function(b,c){return a(b,"ng-move",c)},beforeAddClass:function(a,c,d){if(a=q(a,b(c,"-add")))return m(d),a;d()},addClass:function(a,c,d){return J(a,b(c,"-add"),d)},beforeRemoveClass:function(a,c,d){if(a=q(a,b(c,"-remove")))return m(d),a;d()},removeClass:function(a,c,d){return J(a,b(c,"-remove"),d)}}}])}])})(window,window.angular);
(function(D,f,E){'use strict';f.module("ngAnimate",["ng"]).directive("ngAnimateChildren",function(){return function(P,w,g){g=g.ngAnimateChildren;f.isString(g)&&0===g.length?w.data("$$ngAnimateChildren",!0):P.$watch(g,function(f){w.data("$$ngAnimateChildren",!!f)})}}).factory("$$animateReflow",["$$rAF","$document",function(f,w){return function(g){return f(function(){g()})}}]).config(["$provide","$animateProvider",function(P,w){function g(f){for(var g=0;g<f.length;g++){var h=f[g];if(1==h.nodeType)return h}}
function Z(f,h){return g(f)==g(h)}var q=f.noop,h=f.forEach,$=w.$$selectors,W=f.isArray,u={running:!0};P.decorator("$animate",["$delegate","$$q","$injector","$sniffer","$rootElement","$$asyncCallback","$rootScope","$document","$templateRequest",function(Q,D,I,E,v,X,x,Y,J){function K(a,c){var d=a.data("$$ngAnimateState")||{};c&&(d.running=!0,d.structural=!0,a.data("$$ngAnimateState",d));return d.disabled||d.running&&d.structural}function L(a){var c,d=D.defer();d.promise.$$cancelFn=function(){c&&c()};
x.$$postDigest(function(){c=a(function(){d.resolve()})});return d.promise}function M(a,c,d){d=d||{};var e={};h(c.add,function(b){b&&b.length&&(e[b]=e[b]||0,e[b]++)});h(c.remove,function(b){b&&b.length&&(e[b]=e[b]||0,e[b]--)});var R=[];h(d,function(b,a){h(a.split(" "),function(a){R[a]=b})});var g=[],l=[];h(e,function(b,c){var d=f.$$hasClass(a[0],c),e=R[c]||{};0>b?(d||"addClass"==e.event)&&l.push(c):0<b&&(d&&"removeClass"!=e.event||g.push(c))});return 0<g.length+l.length&&[g.join(" "),l.join(" ")]}
function N(a){if(a){var c=[],d={};a=a.substr(1).split(".");(E.transitions||E.animations)&&c.push(I.get($[""]));for(var e=0;e<a.length;e++){var f=a[e],g=$[f];g&&!d[f]&&(c.push(I.get(g)),d[f]=!0)}return c}}function T(a,c,d){function e(b,a){var c=b[a],d=b["before"+a.charAt(0).toUpperCase()+a.substr(1)];if(c||d)return"leave"==a&&(d=c,c=null),U.push({event:a,fn:c}),m.push({event:a,fn:d}),!0}function f(c,r,e){var A=[];h(c,function(b){b.fn&&A.push(b)});var k=0;h(A,function(c,f){var g=function(){a:{if(r){(r[f]||
q)();if(++k<A.length)break a;r=null}e()}};switch(c.event){case "setClass":r.push(c.fn(a,l,b,g));break;case "addClass":r.push(c.fn(a,l||d,g));break;case "removeClass":r.push(c.fn(a,b||d,g));break;default:r.push(c.fn(a,g))}});r&&0===r.length&&e()}var g=a[0];if(g){var l,b;W(d)&&(l=d[0],b=d[1],l?b?d=l+" "+b:(d=l,c="addClass"):(d=b,c="removeClass"));var r="setClass"==c,A=r||"addClass"==c||"removeClass"==c,y=a.attr("class")+" "+d;if(B(y)){var C=q,k=[],m=[],n=q,s=[],U=[],y=(" "+y).replace(/\s+/g,".");h(N(y),
function(b){!e(b,c)&&r&&(e(b,"addClass"),e(b,"removeClass"))});return{node:g,event:c,className:d,isClassBased:A,isSetClassOperation:r,before:function(b){C=b;f(m,k,function(){C=q;b()})},after:function(b){n=b;f(U,s,function(){n=q;b()})},cancel:function(){k&&(h(k,function(b){(b||q)(!0)}),C(!0));s&&(h(s,function(b){(b||q)(!0)}),n(!0))}}}}}function F(a,c,d,e,g,S,l){function b(b){var l="$animate:"+b;m&&m[l]&&0<m[l].length&&X(function(){d.triggerHandler(l,{event:a,className:c})})}function r(){b("before")}
function A(){b("after")}function y(){y.hasBeenRun||(y.hasBeenRun=!0,S())}function C(){if(!C.hasBeenRun){C.hasBeenRun=!0;var r=d.data("$$ngAnimateState");r&&(k&&k.isClassBased?z(d,c):(X(function(){var b=d.data("$$ngAnimateState")||{};p==b.index&&z(d,c,a)}),d.data("$$ngAnimateState",r)));b("close");l()}}var k=T(d,a,c);if(!k)return y(),r(),A(),C(),q;a=k.event;c=k.className;var m=f.element._data(k.node),m=m&&m.events;e||(e=g?g.parent():d.parent());if(H(d,e))return y(),r(),A(),C(),q;e=d.data("$$ngAnimateState")||
{};var n=e.active||{},s=e.totalActive||0,U=e.last;g=!1;if(0<s){s=[];if(k.isClassBased)"setClass"==U.event?(s.push(U),z(d,c)):n[c]&&(t=n[c],t.event==a?g=!0:(s.push(t),z(d,c)));else if("leave"==a&&n["ng-leave"])g=!0;else{for(var t in n)s.push(n[t]);e={};z(d,!0)}0<s.length&&h(s,function(b){b.cancel()})}!k.isClassBased||k.isSetClassOperation||g||(g="addClass"==a==d.hasClass(c));if(g)return y(),r(),A(),b("close"),l(),q;n=e.active||{};s=e.totalActive||0;if("leave"==a)d.one("$destroy",function(b){b=f.element(this);
var a=b.data("$$ngAnimateState");a&&(a=a.active["ng-leave"])&&(a.cancel(),z(b,"ng-leave"))});d.addClass("ng-animate");var p=G++;s++;n[c]=k;d.data("$$ngAnimateState",{last:k,active:n,index:p,totalActive:s});r();k.before(function(b){var l=d.data("$$ngAnimateState");b=b||!l||!l.active[c]||k.isClassBased&&l.active[c].event!=a;y();!0===b?C():(A(),k.after(C))});return k.cancel}function p(a){if(a=g(a))a=f.isFunction(a.getElementsByClassName)?a.getElementsByClassName("ng-animate"):a.querySelectorAll(".ng-animate"),
h(a,function(a){a=f.element(a);(a=a.data("$$ngAnimateState"))&&a.active&&h(a.active,function(a){a.cancel()})})}function z(a,c){if(Z(a,v))u.disabled||(u.running=!1,u.structural=!1);else if(c){var d=a.data("$$ngAnimateState")||{},e=!0===c;!e&&d.active&&d.active[c]&&(d.totalActive--,delete d.active[c]);if(e||!d.totalActive)a.removeClass("ng-animate"),a.removeData("$$ngAnimateState")}}function H(a,c){if(u.disabled)return!0;if(Z(a,v))return u.running;var d,e,g;do{if(0===c.length)break;var h=Z(c,v),l=h?
u:c.data("$$ngAnimateState")||{};if(l.disabled)return!0;h&&(g=!0);!1!==d&&(h=c.data("$$ngAnimateChildren"),f.isDefined(h)&&(d=h));e=e||l.running||l.last&&!l.last.isClassBased}while(c=c.parent());return!g||!d&&e}v.data("$$ngAnimateState",u);var V=x.$watch(function(){return J.totalPendingRequests},function(a,c){0===a&&(V(),x.$$postDigest(function(){x.$$postDigest(function(){u.running=!1})}))}),G=0,O=w.classNameFilter(),B=O?function(a){return O.test(a)}:function(){return!0};return{enter:function(a,c,
d){a=f.element(a);c=c&&f.element(c);d=d&&f.element(d);K(a,!0);Q.enter(a,c,d);return L(function(e){return F("enter","ng-enter",f.element(g(a)),c,d,q,e)})},leave:function(a){a=f.element(a);p(a);K(a,!0);this.enabled(!1,a);return L(function(c){return F("leave","ng-leave",f.element(g(a)),null,null,function(){Q.leave(a)},c)})},move:function(a,c,d){a=f.element(a);c=c&&f.element(c);d=d&&f.element(d);p(a);K(a,!0);Q.move(a,c,d);return L(function(e){return F("move","ng-move",f.element(g(a)),c,d,q,e)})},addClass:function(a,
c){return this.setClass(a,c,[])},removeClass:function(a,c){return this.setClass(a,[],c)},setClass:function(a,c,d){a=f.element(a);a=f.element(g(a));if(K(a))return Q.setClass(a,c,d);c=W(c)?c:c.split(" ");d=W(d)?d:d.split(" ");var e=a.data("$$animateClasses");if(e)return e.add=e.add.concat(c),e.remove=e.remove.concat(d),e.promise;a.data("$$animateClasses",e={add:c,remove:d});return e.promise=L(function(c){var d=a.data("$$animateClasses");a.removeData("$$animateClasses");var l=a.data("$$ngAnimateState")||
{},b=M(a,d,l.active);return b?F("setClass",b,a,null,null,function(){Q.setClass(a,b[0],b[1])},c):c()})},cancel:function(a){a.$$cancelFn()},enabled:function(a,c){switch(arguments.length){case 2:if(a)z(c);else{var d=c.data("$$ngAnimateState")||{};d.disabled=!0;c.data("$$ngAnimateState",d)}break;case 1:u.disabled=!a;break;default:a=!u.disabled}return!!a}}}]);w.register("",["$window","$sniffer","$timeout","$$animateReflow",function(u,w,I,P){function v(a,b){d&&d();c.push(b);d=P(function(){h(c,function(b){b()});
c=[];d=null;B={}})}function X(a,b){var c=g(a);a=f.element(c);S.push(a);c=Date.now()+b;c<=R||(I.cancel(e),R=c,e=I(function(){x(S);S=[]},b,!1))}function x(a){h(a,function(b){(b=b.data("$$ngAnimateCSS3Data"))&&h(b.closeAnimationFns,function(b){b()})})}function Y(a,b){var c=b?B[b]:null;if(!c){var d=0,e=0,g=0,f=0;h(a,function(b){if(1==b.nodeType){b=u.getComputedStyle(b)||{};d=Math.max(J(b[H+"Duration"]),d);e=Math.max(J(b[H+"Delay"]),e);f=Math.max(J(b[G+"Delay"]),f);var a=J(b[G+"Duration"]);0<a&&(a*=parseInt(b[G+
"IterationCount"],10)||1);g=Math.max(a,g)}});c={total:0,transitionDelay:e,transitionDuration:d,animationDelay:f,animationDuration:g};b&&(B[b]=c)}return c}function J(a){var b=0;a=f.isString(a)?a.split(/\s*,\s*/):[];h(a,function(a){b=Math.max(parseFloat(a)||0,b)});return b}function K(c,b,d){c=0<=["ng-enter","ng-leave","ng-move"].indexOf(d);var e,f=b.parent(),h=f.data("$$ngAnimateKey");h||(f.data("$$ngAnimateKey",++a),h=a);e=h+"-"+g(b).getAttribute("class");var f=e+" "+d,h=B[f]?++B[f].total:0,k={};if(0<
h){var m=d+"-stagger",k=e+" "+m;(e=!B[k])&&b.addClass(m);k=Y(b,k);e&&b.removeClass(m)}b.addClass(d);var m=b.data("$$ngAnimateCSS3Data")||{},n=Y(b,f);e=n.transitionDuration;n=n.animationDuration;if(c&&0===e&&0===n)return b.removeClass(d),!1;d=c&&0<e;c=0<n&&0<k.animationDelay&&0===k.animationDuration;b.data("$$ngAnimateCSS3Data",{stagger:k,cacheKey:f,running:m.running||0,itemIndex:h,blockTransition:d,closeAnimationFns:m.closeAnimationFns||[]});b=g(b);d&&(b.style[H+"Property"]="none");c&&(b.style[G+
"PlayState"]="paused");return!0}function L(a,b,c,d){function e(a){b.off(D,f);b.removeClass(m);b.removeClass(n);x&&I.cancel(x);F(b,c);a=g(b);for(var d in s)a.style.removeProperty(s[d])}function f(b){b.stopPropagation();var a=b.originalEvent||b;b=a.$manualTimeStamp||a.timeStamp||Date.now();a=parseFloat(a.elapsedTime.toFixed(3));Math.max(b-E,0)>=B&&a>=w&&d()}var k=g(b);a=b.data("$$ngAnimateCSS3Data");if(-1!=k.getAttribute("class").indexOf(c)&&a){a.blockTransition&&(k.style[H+"Property"]="");var m="",
n="";h(c.split(" "),function(b,a){var c=(0<a?" ":"")+b;m+=c+"-active";n+=c+"-pending"});var s=[],p=a.itemIndex,t=a.stagger,q=0;if(0<p){q=0;0<t.transitionDelay&&0===t.transitionDuration&&(q=t.transitionDelay*p);var u=0;0<t.animationDelay&&0===t.animationDuration&&(u=t.animationDelay*p,s.push(z+"animation-play-state"));q=Math.round(100*Math.max(q,u))/100}q||b.addClass(m);var v=Y(b,a.cacheKey+" "+m),w=Math.max(v.transitionDuration,v.animationDuration);if(0===w)b.removeClass(m),F(b,c),d();else{var p=
Math.max(v.transitionDelay,v.animationDelay),B=1E3*p;0<s.length&&(t=k.getAttribute("style")||"",";"!==t.charAt(t.length-1)&&(t+=";"),k.setAttribute("style",t+" "));var E=Date.now(),D=O+" "+V,p=1E3*(q+1.5*(p+w)),x;0<q&&(b.addClass(n),x=I(function(){x=null;b.addClass(m);b.removeClass(n);0<v.animationDuration&&(k.style[G+"PlayState"]="")},1E3*q,!1));b.on(D,f);a.closeAnimationFns.push(function(){e();d()});a.running++;X(b,p);return e}}else d()}function M(a,b,c,d){if(K(a,b,c,d))return function(a){a&&F(b,
c)}}function N(a,b,c,d){if(b.data("$$ngAnimateCSS3Data"))return L(a,b,c,d);F(b,c);d()}function T(a,b,c,d){var e=M(a,b,c);if(e){var f=e;v(b,function(){f=N(a,b,c,d)});return function(b){(f||q)(b)}}d()}function F(a,b){a.removeClass(b);var c=a.data("$$ngAnimateCSS3Data");c&&(c.running&&c.running--,c.running&&0!==c.running||a.removeData("$$ngAnimateCSS3Data"))}function p(a,b){var c="";a=W(a)?a:a.split(/\s+/);h(a,function(a,d){a&&0<a.length&&(c+=(0<d?" ":"")+a+b)});return c}var z="",H,V,G,O;D.ontransitionend===
E&&D.onwebkittransitionend!==E?(z="-webkit-",H="WebkitTransition",V="webkitTransitionEnd transitionend"):(H="transition",V="transitionend");D.onanimationend===E&&D.onwebkitanimationend!==E?(z="-webkit-",G="WebkitAnimation",O="webkitAnimationEnd animationend"):(G="animation",O="animationend");var B={},a=0,c=[],d,e=null,R=0,S=[];return{enter:function(a,b){return T("enter",a,"ng-enter",b)},leave:function(a,b){return T("leave",a,"ng-leave",b)},move:function(a,b){return T("move",a,"ng-move",b)},beforeSetClass:function(a,
b,c,d){b=p(c,"-remove")+" "+p(b,"-add");if(b=M("setClass",a,b))return v(a,d),b;d()},beforeAddClass:function(a,b,c){if(b=M("addClass",a,p(b,"-add")))return v(a,c),b;c()},beforeRemoveClass:function(a,b,c){if(b=M("removeClass",a,p(b,"-remove")))return v(a,c),b;c()},setClass:function(a,b,c,d){c=p(c,"-remove");b=p(b,"-add");return N("setClass",a,c+" "+b,d)},addClass:function(a,b,c){return N("addClass",a,p(b,"-add"),c)},removeClass:function(a,b,c){return N("removeClass",a,p(b,"-remove"),c)}}}])}])})(window,
window.angular);
//# sourceMappingURL=angular-animate.min.js.map

View File

@@ -1,5 +1,5 @@
/**
* @license AngularJS v1.2.20
* @license AngularJS v1.3.0-rc.1
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -22,12 +22,12 @@
*/
/* global -ngRouteModule */
var ngRouteModule = angular.module('ngRoute', ['ng']).
provider('$route', $RouteProvider);
provider('$route', $RouteProvider),
$routeMinErr = angular.$$minErr('ngRoute');
/**
* @ngdoc provider
* @name $routeProvider
* @kind function
*
* @description
*
@@ -216,10 +216,14 @@ function $RouteProvider(){
* Sets route definition that will be used on route change when no other route definition
* is matched.
*
* @param {Object} params Mapping information to be assigned to `$route.current`.
* @param {Object|string} params Mapping information to be assigned to `$route.current`.
* If called with a string, the value maps to `redirectTo`.
* @returns {Object} self
*/
this.otherwise = function(params) {
if (typeof params === 'string') {
params = {redirectTo: params};
}
this.when(null, params);
return this;
};
@@ -230,10 +234,9 @@ function $RouteProvider(){
'$routeParams',
'$q',
'$injector',
'$http',
'$templateCache',
'$templateRequest',
'$sce',
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) {
/**
* @ngdoc service
@@ -441,6 +444,36 @@ function $RouteProvider(){
reload: function() {
forceReload = true;
$rootScope.$evalAsync(updateRoute);
},
/**
* @ngdoc method
* @name $route#updateParams
*
* @description
* Causes `$route` service to update the current URL, replacing
* current route parameters with those specified in `newParams`.
* Provided property names that match the route's path segment
* definitions will be interpolated into the location's path, while
* remaining properties will be treated as query params.
*
* @param {Object} newParams mapping of URL parameter names to values
*/
updateParams: function(newParams) {
if (this.current && this.current.$$route) {
var searchParams = {}, self=this;
angular.forEach(Object.keys(newParams), function(key) {
if (!self.current.pathParams[key]) searchParams[key] = newParams[key];
});
newParams = angular.extend({}, this.current.params, newParams);
$location.path(interpolate(this.current.$$route.originalPath, newParams));
$location.search(angular.extend({}, $location.search(), searchParams));
}
else {
throw $routeMinErr('norout', 'Tried updating route when with no current route');
}
}
};
@@ -473,9 +506,7 @@ function $RouteProvider(){
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
var val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
var val = m[i];
if (key && val) {
params[key.name] = val;
@@ -518,7 +549,7 @@ function $RouteProvider(){
angular.forEach(locals, function(value, key) {
locals[key] = angular.isString(value) ?
$injector.get(value) : $injector.invoke(value);
$injector.get(value) : $injector.invoke(value, null, null, key);
});
if (angular.isDefined(template = next.template)) {
@@ -532,8 +563,7 @@ function $RouteProvider(){
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
if (angular.isDefined(templateUrl)) {
next.loadedTemplateUrl = templateUrl;
template = $http.get(templateUrl, {cache: $templateCache}).
then(function(response) { return response.data; });
template = $templateRequest(templateUrl);
}
}
if (angular.isDefined(template)) {
@@ -695,7 +725,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
<pre>$location.path() = {{main.$location.path()}}</pre>
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
<pre>$route.current.params = {{main.$route.current.params}}</pre>
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
<pre>$routeParams = {{main.$routeParams}}</pre>
</div>
</file>
@@ -846,7 +875,7 @@ function ngViewFactory( $route, $anchorScroll, $animate) {
currentScope = null;
}
if(currentElement) {
$animate.leave(currentElement, function() {
$animate.leave(currentElement).then(function() {
previousElement = null;
});
previousElement = currentElement;
@@ -869,7 +898,7 @@ function ngViewFactory( $route, $anchorScroll, $animate) {
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, function(clone) {
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
$animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();

View File

@@ -1,14 +1,14 @@
/*
AngularJS v1.2.0
(c) 2010-2012 Google, Inc. http://angularjs.org
AngularJS v1.3.0-rc.1
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(t,c,B){'use strict';function w(s,r,g,a,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",compile:function(k,d,A){return function(u,k,d){function v(){l&&(l.$destroy(),l=null);m&&(h.leave(m),m=null)}function x(){var f=s.current&&s.current.locals,y=f&&f.$template;if(y){var z=u.$new();A(z,function(e){e.html(y);h.enter(e,null,m||k,function(){!c.isDefined(n)||n&&!u.$eval(n)||r()});v();var p=g(e.contents()),q=s.current;l=q.scope=z;m=e;if(q.controller){f.$scope=l;var d=a(q.controller,
f);q.controllerAs&&(l[q.controllerAs]=d);e.data("$ngControllerController",d);e.children().data("$ngControllerController",d)}p(l);l.$emit("$viewContentLoaded");l.$eval(b)})}else v()}var l,m,n=d.autoscroll,b=d.onload||"";u.$on("$routeChangeSuccess",x);x()}}}}t=c.module("ngRoute",["ng"]).provider("$route",function(){function s(a,h){return c.extend(new (c.extend(function(){},{prototype:a})),h)}function r(a,c){var k=c.caseInsensitiveMatch,d={originalPath:a,regexp:a},g=d.keys=[];a=a.replace(/([().])/g,
"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,function(a,c,h,d){a="?"===d?d:null;d="*"===d?d:null;g.push({name:h,optional:!!a});c=c||"";return""+(a?"":c)+"(?:"+(a?c:"")+(d&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");d.regexp=RegExp("^"+a+"$",k?"i":"");return d}var g={};this.when=function(a,h){g[a]=c.extend({reloadOnSearch:!0},h,a&&r(a,h));if(a){var k="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[k]=c.extend({redirectTo:a},r(k,h))}return this};this.otherwise=function(a){this.when(null,
a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,h,k,d,r,u,t,w){function v(){var b=x(),f=n.current;if(b&&f&&b.$$route===f.$$route&&c.equals(b.pathParams,f.pathParams)&&!b.reloadOnSearch&&!m)f.params=b.params,c.copy(f.params,k),a.$broadcast("$routeUpdate",f);else if(b||f)m=!1,a.$broadcast("$routeChangeStart",b,f),(n.current=b)&&b.redirectTo&&(c.isString(b.redirectTo)?h.path(l(b.redirectTo,b.params)).search(b.params).replace():
h.url(b.redirectTo(b.pathParams,h.path(),h.search())).replace()),d.when(b).then(function(){if(b){var a=c.extend({},b.resolve),f,e;c.forEach(a,function(b,f){a[f]=c.isString(b)?r.get(b):r.invoke(b)});c.isDefined(f=b.template)?c.isFunction(f)&&(f=f(b.params)):c.isDefined(e=b.templateUrl)&&(c.isFunction(e)&&(e=e(b.params)),e=w.getTrustedResourceUrl(e),c.isDefined(e)&&(b.loadedTemplateUrl=e,f=u.get(e,{cache:t}).then(function(b){return b.data})));c.isDefined(f)&&(a.$template=f);return d.all(a)}}).then(function(d){b==
n.current&&(b&&(b.locals=d,c.copy(b.params,k)),a.$broadcast("$routeChangeSuccess",b,f))},function(c){b==n.current&&a.$broadcast("$routeChangeError",b,f,c)})}function x(){var b,a;c.forEach(g,function(d,l){var e;if(e=!a){var p=h.path();e=d.keys;var q={};if(d.regexp)if(p=d.regexp.exec(p)){for(var g=1,k=p.length;g<k;++g){var m=e[g-1],n="string"==typeof p[g]?decodeURIComponent(p[g]):p[g];m&&n&&(q[m.name]=n)}e=q}else e=null;else e=null;e=b=e}e&&(a=s(d,{params:c.extend({},h.search(),b),pathParams:b}),a.$$route=
d)});return a||g[null]&&s(g[null],{params:{},pathParams:{}})}function l(a,d){var g=[];c.forEach((a||"").split(":"),function(a,b){if(0===b)g.push(a);else{var c=a.match(/(\w+)(.*)/),h=c[1];g.push(d[h]);g.push(c[2]||"");delete d[h]}});return g.join("")}var m=!1,n={routes:g,reload:function(){m=!0;a.$evalAsync(v)}};a.$on("$locationChangeSuccess",v);return n}]});t.provider("$routeParams",function(){this.$get=function(){return{}}});t.directive("ngView",w);w.$inject=["$route","$anchorScroll","$compile","$controller",
"$animate"]})(window,window.angular);
(function(r,d,z){'use strict';function v(s,h,f){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,e,b,g,u){function w(){k&&(k.remove(),k=null);l&&(l.$destroy(),l=null);n&&(f.leave(n).then(function(){k=null}),k=n,n=null)}function t(){var c=s.current&&s.current.locals;if(d.isDefined(c&&c.$template)){var c=a.$new(),m=s.current;n=u(c,function(c){f.enter(c,null,n||e).then(function(){!d.isDefined(p)||p&&!a.$eval(p)||h()});w()});l=m.scope=c;l.$emit("$viewContentLoaded");
l.$eval(q)}else w()}var l,n,k,p=b.autoscroll,q=b.onload||"";a.$on("$routeChangeSuccess",t);t()}}}function x(d,h,f){return{restrict:"ECA",priority:-400,link:function(a,e){var b=f.current,g=b.locals;e.html(g.$template);var u=d(e.contents());b.controller&&(g.$scope=a,g=h(b.controller,g),b.controllerAs&&(a[b.controllerAs]=g),e.data("$ngControllerController",g),e.children().data("$ngControllerController",g));u(a)}}}r=d.module("ngRoute",["ng"]).provider("$route",function(){function s(a,e){return d.extend(new (d.extend(function(){},
{prototype:a})),e)}function h(a,d){var b=d.caseInsensitiveMatch,g={originalPath:a,regexp:a},f=g.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,e,b){a="?"===b?b:null;b="*"===b?b:null;f.push({name:e,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(b&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");g.regexp=new RegExp("^"+a+"$",b?"i":"");return g}var f={};this.when=function(a,e){f[a]=d.extend({reloadOnSearch:!0},e,a&&h(a,e));if(a){var b=
"/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";f[b]=d.extend({redirectTo:a},h(b,e))}return this};this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,e,b,g,h,r,t){function l(){var c=n(),m=q.current;if(c&&m&&c.$$route===m.$$route&&d.equals(c.pathParams,m.pathParams)&&!c.reloadOnSearch&&!p)m.params=c.params,d.copy(m.params,b),a.$broadcast("$routeUpdate",
m);else if(c||m)p=!1,a.$broadcast("$routeChangeStart",c,m),(q.current=c)&&c.redirectTo&&(d.isString(c.redirectTo)?e.path(k(c.redirectTo,c.params)).search(c.params).replace():e.url(c.redirectTo(c.pathParams,e.path(),e.search())).replace()),g.when(c).then(function(){if(c){var a=d.extend({},c.resolve),e,b;d.forEach(a,function(c,b){a[b]=d.isString(c)?h.get(c):h.invoke(c,null,null,b)});d.isDefined(e=c.template)?d.isFunction(e)&&(e=e(c.params)):d.isDefined(b=c.templateUrl)&&(d.isFunction(b)&&(b=b(c.params)),
b=t.getTrustedResourceUrl(b),d.isDefined(b)&&(c.loadedTemplateUrl=b,e=r(b)));d.isDefined(e)&&(a.$template=e);return g.all(a)}}).then(function(e){c==q.current&&(c&&(c.locals=e,d.copy(c.params,b)),a.$broadcast("$routeChangeSuccess",c,m))},function(d){c==q.current&&a.$broadcast("$routeChangeError",c,m,d)})}function n(){var c,a;d.forEach(f,function(b,g){var f;if(f=!a){var h=e.path();f=b.keys;var l={};if(b.regexp)if(h=b.regexp.exec(h)){for(var k=1,n=h.length;k<n;++k){var p=f[k-1],q=h[k];p&&q&&(l[p.name]=
q)}f=l}else f=null;else f=null;f=c=f}f&&(a=s(b,{params:d.extend({},e.search(),c),pathParams:c}),a.$$route=b)});return a||f[null]&&s(f[null],{params:{},pathParams:{}})}function k(a,b){var e=[];d.forEach((a||"").split(":"),function(a,c){if(0===c)e.push(a);else{var d=a.match(/(\w+)(.*)/),f=d[1];e.push(b[f]);e.push(d[2]||"");delete b[f]}});return e.join("")}var p=!1,q={routes:f,reload:function(){p=!0;a.$evalAsync(l)},updateParams:function(a){if(this.current&&this.current.$$route){var b={},f=this;d.forEach(Object.keys(a),
function(d){f.current.pathParams[d]||(b[d]=a[d])});a=d.extend({},this.current.params,a);e.path(k(this.current.$$route.originalPath,a));e.search(d.extend({},e.search(),b))}else throw y("norout");}};a.$on("$locationChangeSuccess",l);return q}]});var y=d.$$minErr("ngRoute");r.provider("$routeParams",function(){this.$get=function(){return{}}});r.directive("ngView",v);r.directive("ngView",x);v.$inject=["$route","$anchorScroll","$animate"];x.$inject=["$compile","$controller","$route"]})(window,window.angular);
//# sourceMappingURL=angular-route.min.js.map

View File

@@ -1,6 +1,6 @@
/**
* @license AngularJS v1.2.0
* (c) 2010-2012 Google, Inc. http://angularjs.org
* @license AngularJS v1.3.0-rc.1
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
@@ -8,7 +8,7 @@
var $sanitizeMinErr = angular.$$minErr('$sanitize');
/**
* @ngdoc overview
* @ngdoc module
* @name ngSanitize
* @description
*
@@ -16,7 +16,6 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
*
* The `ngSanitize` module provides functionality to sanitize HTML.
*
* {@installModule sanitize}
*
* <div doc-module-components="ngSanitize"></div>
*
@@ -42,8 +41,8 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
/**
* @ngdoc service
* @name ngSanitize.$sanitize
* @function
* @name $sanitize
* @kind function
*
* @description
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
@@ -51,25 +50,28 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
*
* @example
<doc:example module="ngSanitize">
<doc:source>
<example module="sanitizeExample" deps="angular-sanitize.js">
<file name="index.html">
<script>
function Ctrl($scope, $sce) {
$scope.snippet =
'<p style="color:blue">an html\n' +
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
'snippet</p>';
$scope.deliberatelyTrustDangerousSnippet = function() {
return $sce.trustAsHtml($scope.snippet);
};
}
angular.module('sanitizeExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {
$scope.snippet =
'<p style="color:blue">an html\n' +
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
'snippet</p>';
$scope.deliberatelyTrustDangerousSnippet = function() {
return $sce.trustAsHtml($scope.snippet);
};
}]);
</script>
<div ng-controller="Ctrl">
<div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
@@ -101,56 +103,71 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
</tr>
</table>
</div>
</doc:source>
<doc:scenario>
</file>
<file name="protractor.js" type="protractor">
it('should sanitize the html snippet by default', function() {
expect(using('#bind-html-with-sanitize').element('div').html()).
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
});
it('should inline raw snippet if bound to a trusted value', function() {
expect(using('#bind-html-with-trust').element("div").html()).
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should escape snippet without any filter', function() {
expect(using('#bind-default').element('div').html()).
expect(element(by.css('#bind-default div')).getInnerHtml()).
toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
"&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
"snippet&lt;/p&gt;");
});
it('should update', function() {
input('snippet').enter('new <b onclick="alert(1)">text</b>');
expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new <b>text</b>');
expect(using('#bind-html-with-trust').element('div').html()).toBe(
element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
toBe('new <b>text</b>');
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
'new <b onclick="alert(1)">text</b>');
expect(using('#bind-default').element('div').html()).toBe(
expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
"new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
});
</doc:scenario>
</doc:example>
</file>
</example>
*/
var $sanitize = function(html) {
function $SanitizeProvider() {
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
return function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
}));
return buf.join('');
};
}];
}
function sanitizeText(chars) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf));
return buf.join('');
};
var writer = htmlSanitizeWriter(buf, angular.noop);
writer.chars(chars);
return buf.join('');
}
// Regular Expressions for parsing tags and attributes
var START_TAG_REGEXP =
/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,
END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/,
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
BEGIN_TAG_REGEXP = /^</,
BEGING_END_TAGE_REGEXP = /^<\s*\//,
BEGING_END_TAGE_REGEXP = /^<\//,
COMMENT_REGEXP = /<!--(.*?)-->/g,
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/i,
SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
@@ -197,7 +214,7 @@ var validAttrs = angular.extend({}, uriAttrs, makeMap(
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
'scope,scrolling,shape,span,start,summary,target,title,type,'+
'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
'valign,value,vspace,width'));
function makeMap(str) {
@@ -220,10 +237,18 @@ function makeMap(str) {
* @param {object} handler
*/
function htmlParser( html, handler ) {
var index, chars, match, stack = [], last = html;
if (typeof html !== 'string') {
if (html === null || typeof html === 'undefined') {
html = '';
} else {
html = '' + html;
}
}
var index, chars, match, stack = [], last = html, text;
stack.last = function() { return stack[ stack.length - 1 ]; };
while ( html ) {
text = '';
chars = true;
// Make sure we're not in a script or style element
@@ -244,7 +269,7 @@ function htmlParser( html, handler ) {
match = html.match( DOCTYPE_REGEXP );
if ( match ) {
html = html.replace( match[0] , '');
html = html.replace( match[0], '');
chars = false;
}
// end tag
@@ -262,16 +287,23 @@ function htmlParser( html, handler ) {
match = html.match( START_TAG_REGEXP );
if ( match ) {
html = html.substring( match[0].length );
match[0].replace( START_TAG_REGEXP, parseStartTag );
// We only have a valid start-tag if there is a '>'.
if ( match[4] ) {
html = html.substring( match[0].length );
match[0].replace( START_TAG_REGEXP, parseStartTag );
}
chars = false;
} else {
// no ending tag found --- this piece should be encoded as an entity.
text += '<';
html = html.substring(1);
}
}
if ( chars ) {
index = html.indexOf("<");
var text = index < 0 ? html : html.substring( 0, index );
text += index < 0 ? html : html.substring( 0, index );
html = index < 0 ? "" : html.substring( index );
if (handler.chars) handler.chars( decodeEntities(text) );
@@ -351,15 +383,32 @@ function htmlParser( html, handler ) {
}
}
var hiddenPre=document.createElement("pre");
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
var hiddenPre=document.createElement("pre");
function decodeEntities(value) {
hiddenPre.innerHTML=value.replace(/</g,"&lt;");
return hiddenPre.innerText || hiddenPre.textContent || '';
if (!value) { return ''; }
// Note: IE8 does not preserve spaces at the start/end of innerHTML
// so we must capture them and reattach them afterward
var parts = spaceRe.exec(value);
var spaceBefore = parts[1];
var spaceAfter = parts[3];
var content = parts[2];
if (content) {
hiddenPre.innerHTML=content.replace(/</g,"&lt;");
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary
// reflows. However, IE<9 don't support textContent so the innerText
// fallback is necessary.
content = 'textContent' in hiddenPre ?
hiddenPre.textContent : hiddenPre.innerText;
}
return spaceBefore + content + spaceAfter;
}
/**
@@ -367,11 +416,16 @@ function decodeEntities(value) {
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns escaped text
* @returns {string} escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(SURROGATE_PAIR_REGEXP, function (value) {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(NON_ALPHANUMERIC_REGEXP, function(value){
return '&#' + value.charCodeAt(0) + ';';
}).
@@ -389,7 +443,7 @@ function encodeEntities(value) {
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf){
function htmlSanitizeWriter(buf, uriValidator){
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
@@ -403,7 +457,9 @@ function htmlSanitizeWriter(buf){
out(tag);
angular.forEach(attrs, function(value, key){
var lkey=angular.lowercase(key);
if (validAttrs[lkey]===true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
@@ -435,14 +491,14 @@ function htmlSanitizeWriter(buf){
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).value('$sanitize', $sanitize);
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
/* global htmlSanitizeWriter: false */
/* global sanitizeText: false */
/**
* @ngdoc filter
* @name ngSanitize.filter:linky
* @function
* @name linky
* @kind function
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
@@ -458,20 +514,21 @@ angular.module('ngSanitize', []).value('$sanitize', $sanitize);
<span ng-bind-html="linky_expression | linky"></span>
*
* @example
<doc:example module="ngSanitize">
<doc:source>
<example module="linkyExample" deps="angular-sanitize.js">
<file name="index.html">
<script>
function Ctrl($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}
angular.module('linkyExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}]);
</script>
<div ng-controller="Ctrl">
<div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
@@ -503,43 +560,44 @@ angular.module('ngSanitize', []).value('$sanitize', $sanitize);
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
</doc:source>
<doc:scenario>
</file>
<file name="protractor.js" type="protractor">
it('should linkify the snippet with urls', function() {
expect(using('#linky-filter').binding('snippet | linky')).
toBe('Pretty text with some links:&#10;' +
'<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
});
it ('should not linkify snippet without the linky filter', function() {
expect(using('#escaped-html').binding('snippet')).
toBe("Pretty text with some links:\n" +
"http://angularjs.org/,\n" +
"mailto:us@somewhere.org,\n" +
"another@somewhere.org,\n" +
"and one more: ftp://127.0.0.1/.");
it('should not linkify snippet without the linky filter', function() {
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
});
it('should update', function() {
input('snippet').enter('new http://link.');
expect(using('#linky-filter').binding('snippet | linky')).
toBe('new <a href="http://link">http://link</a>.');
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new http://link.');
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
toBe('new http://link.');
expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
.toBe('new http://link.');
});
it('should work with the target property', function() {
expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
expect(element(by.id('linky-target')).
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
toBe('http://angularjs.org/');
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
});
</doc:scenario>
</doc:example>
</file>
</example>
*/
angular.module('ngSanitize').filter('linky', function() {
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,
MAILTO_REGEXP = /^mailto:/;
return function(text, target) {
@@ -547,31 +605,43 @@ angular.module('ngSanitize').filter('linky', function() {
var match;
var raw = text;
var html = [];
// TODO(vojta): use $sanitize instead
var writer = htmlSanitizeWriter(html);
var url;
var i;
var properties = {};
if (angular.isDefined(target)) {
properties.target = target;
}
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
writer.chars(raw.substr(0, i));
properties.href = url;
writer.start('a', properties);
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
writer.end('a');
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
writer.chars(raw);
return html.join('');
addText(raw);
return $sanitize(html.join(''));
function addText(text) {
if (!text) {
return;
}
html.push(sanitizeText(text));
}
function addLink(url, text) {
html.push('<a ');
if (angular.isDefined(target)) {
html.push('target="');
html.push(target);
html.push('" ');
}
html.push('href="');
html.push(url);
html.push('">');
addText(text);
html.push('</a>');
}
};
});
}]);
})(window, window.angular);

View File

@@ -1,14 +1,15 @@
/*
AngularJS v1.2.0
(c) 2010-2012 Google, Inc. http://angularjs.org
AngularJS v1.3.0-rc.1
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(m,g,n){'use strict';function h(a){var d={};a=a.split(",");var c;for(c=0;c<a.length;c++)d[a[c]]=!0;return d}function D(a,d){function c(a,b,c,f){b=g.lowercase(b);if(r[b])for(;e.last()&&s[e.last()];)k("",e.last());t[b]&&e.last()==b&&k("",b);(f=u[b]||!!f)||e.push(b);var l={};c.replace(E,function(a,b,d,c,e){l[b]=p(d||c||e||"")});d.start&&d.start(b,l,f)}function k(a,b){var c=0,k;if(b=g.lowercase(b))for(c=e.length-1;0<=c&&e[c]!=b;c--);if(0<=c){for(k=e.length-1;k>=c;k--)d.end&&d.end(e[k]);e.length=
c}}var b,f,e=[],l=a;for(e.last=function(){return e[e.length-1]};a;){f=!0;if(e.last()&&v[e.last()])a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+e.last()+"[^>]*>","i"),function(a,b){b=b.replace(F,"$1").replace(G,"$1");d.chars&&d.chars(p(b));return""}),k("",e.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&&d.comment(a.substring(4,b)),a=a.substring(b+3),f=!1);else if(w.test(a)){if(b=a.match(w))a=a.replace(b[0],""),f=!1}else if(H.test(a)){if(b=a.match(x))a=
a.substring(b[0].length),b[0].replace(x,k),f=!1}else I.test(a)&&(b=a.match(y))&&(a=a.substring(b[0].length),b[0].replace(y,c),f=!1);f&&(b=a.indexOf("<"),f=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(p(f)))}if(a==l)throw J("badparse",a);l=a}k()}function p(a){q.innerHTML=a.replace(/</g,"&lt;");return q.innerText||q.textContent||""}function z(a){return a.replace(/&/g,"&amp;").replace(K,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function A(a){var d=
!1,c=g.bind(a,a.push);return{start:function(a,b,f){a=g.lowercase(a);!d&&v[a]&&(d=a);d||!0!==B[a]||(c("<"),c(a),g.forEach(b,function(a,b){var d=g.lowercase(b);!0!==L[d]||!0===C[d]&&!a.match(M)||(c(" "),c(b),c('="'),c(z(a)),c('"'))}),c(f?"/>":">"))},end:function(a){a=g.lowercase(a);d||!0!==B[a]||(c("</"),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||c(z(a))}}}var J=g.$$minErr("$sanitize"),y=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,x=/^<\s*\/\s*([\w:-]+)[^>]*>/,
E=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,I=/^</,H=/^<\s*\//,F=/\x3c!--(.*?)--\x3e/g,w=/<!DOCTYPE([^>]*?)>/i,G=/<!\[CDATA\[(.*?)]]\x3e/g,M=/^((ftp|https?):\/\/|mailto:|tel:|#)/i,K=/([^\#-~| |!])/g,u=h("area,br,col,hr,img,wbr");m=h("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr");n=h("rp,rt");var t=g.extend({},n,m),r=g.extend({},m,h("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),
s=g.extend({},n,h("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),v=h("script,style"),B=g.extend({},u,r,s,t),C=h("background,cite,href,longdesc,src,usemap"),L=g.extend({},C,h("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),
q=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[];D(a,A(d));return d.join("")});g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,d=/^mailto:/;return function(c,k){if(!c)return c;var b,f=c,e=[],l=A(e),h,m,n={};g.isDefined(k)&&(n.target=k);for(;b=f.match(a);)h=b[0],b[2]==b[3]&&(h="mailto:"+h),m=b.index,l.chars(f.substr(0,m)),n.href=h,l.start("a",n),l.chars(b[0].replace(d,"")),l.end("a"),
f=f.substring(m+b[0].length);l.chars(f);return e.join("")}})})(window,window.angular);
(function(q,g,r){'use strict';function F(a){var d=[];t(d,g.noop).chars(a);return d.join("")}function m(a){var d={};a=a.split(",");var c;for(c=0;c<a.length;c++)d[a[c]]=!0;return d}function G(a,d){function c(a,b,c,h){b=g.lowercase(b);if(u[b])for(;f.last()&&v[f.last()];)e("",f.last());w[b]&&f.last()==b&&e("",b);(h=x[b]||!!h)||f.push(b);var n={};c.replace(H,function(a,b,d,c,e){n[b]=s(d||c||e||"")});d.start&&d.start(b,n,h)}function e(a,b){var c=0,e;if(b=g.lowercase(b))for(c=f.length-1;0<=c&&f[c]!=b;c--);
if(0<=c){for(e=f.length-1;e>=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,l,f=[],n=a,h;for(f.last=function(){return f[f.length-1]};a;){h="";l=!0;if(f.last()&&y[f.last()])a=a.replace(new RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(I,"$1").replace(J,"$1");d.chars&&d.chars(s(b));return""}),e("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&&d.comment(a.substring(4,
b)),a=a.substring(b+3),l=!1);else if(z.test(a)){if(b=a.match(z))a=a.replace(b[0],""),l=!1}else if(K.test(a)){if(b=a.match(A))a=a.substring(b[0].length),b[0].replace(A,e),l=!1}else L.test(a)&&((b=a.match(B))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(B,c)),l=!1):(h+="<",a=a.substring(1)));l&&(b=a.indexOf("<"),h+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(s(h)))}if(a==n)throw M("badparse",a);n=a}e()}function s(a){if(!a)return"";var d=N.exec(a);a=d[1];var c=d[3];if(d=d[2])p.innerHTML=
d.replace(/</g,"&lt;"),d="textContent"in p?p.textContent:p.innerText;return a+d+c}function C(a){return a.replace(/&/g,"&amp;").replace(O,function(a){var c=a.charCodeAt(0);a=a.charCodeAt(1);return"&#"+(1024*(c-55296)+(a-56320)+65536)+";"}).replace(P,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function t(a,d){var c=!1,e=g.bind(a,a.push);return{start:function(a,l,f){a=g.lowercase(a);!c&&y[a]&&(c=a);c||!0!==D[a]||(e("<"),e(a),g.forEach(l,function(c,f){var k=
g.lowercase(f),l="img"===a&&"src"===k||"background"===k;!0!==Q[k]||!0===E[k]&&!d(c,l)||(e(" "),e(f),e('="'),e(C(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=g.lowercase(a);c||!0!==D[a]||(e("</"),e(a),e(">"));a==c&&(c=!1)},chars:function(a){c||e(C(a))}}}var M=g.$$minErr("$sanitize"),B=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,A=/^<\/\s*([\w:-]+)[^>]*>/,H=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,L=/^</,
K=/^<\//,I=/\x3c!--(.*?)--\x3e/g,z=/<!DOCTYPE([^>]*?)>/i,J=/<!\[CDATA\[(.*?)]]\x3e/g,O=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,P=/([^\#-~| |!])/g,x=m("area,br,col,hr,img,wbr");q=m("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr");r=m("rp,rt");var w=g.extend({},r,q),u=g.extend({},q,m("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),v=g.extend({},r,m("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
y=m("script,style"),D=g.extend({},x,u,v,w),E=m("background,cite,href,longdesc,src,usemap"),Q=g.extend({},E,m("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width")),p=document.createElement("pre"),N=/^(\s*)([\s\S]*?)(\s*)$/;g.module("ngSanitize",[]).provider("$sanitize",
function(){this.$get=["$$sanitizeUri",function(a){return function(d){var c=[];G(d,t(c,function(c,b){return!/^unsafe/.test(a(c,b))}));return c.join("")}}]});g.module("ngSanitize").filter("linky",["$sanitize",function(a){var d=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,c=/^mailto:/;return function(e,b){function l(a){a&&k.push(F(a))}function f(a,c){k.push("<a ");g.isDefined(b)&&(k.push('target="'),k.push(b),k.push('" '));k.push('href="');k.push(a);k.push('">');l(c);k.push("</a>")}
if(!e)return e;for(var n,h=e,k=[],m,p;n=h.match(d);)m=n[0],n[2]==n[3]&&(m="mailto:"+m),p=n.index,l(h.substr(0,p)),f(m,n[0].replace(c,"")),h=h.substring(p+n[0].length);l(h);return a(k.join(""))}}])})(window,window.angular);
//# sourceMappingURL=angular-sanitize.min.js.map

7909
webclient/js/angular.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,200 +1,237 @@
/*
AngularJS v1.2.0
(c) 2010-2012 Google, Inc. http://angularjs.org
AngularJS v1.3.0-rc.1
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(W,O,s){'use strict';function L(b){return function(){var a=arguments[0],c,a="["+(b?b+":":"")+a+"] http://errors.angularjs.org/undefined/"+(b?b+"/":"")+a;for(c=1;c<arguments.length;c++)a=a+(1==c?"?":"&")+"p"+(c-1)+"="+encodeURIComponent("function"==typeof arguments[c]?arguments[c].toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof arguments[c]?"undefined":"string"!=typeof arguments[c]?JSON.stringify(arguments[c]):arguments[c]);return Error(a)}}function pb(b){if(null==b||ya(b))return!1;
var a=b.length;return 1===b.nodeType&&a?!0:D(b)||J(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function q(b,a,c){var d;if(b)if(B(b))for(d in b)"prototype"!=d&&("length"!=d&&"name"!=d&&b.hasOwnProperty(d))&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==q)b.forEach(a,c);else if(pb(b))for(d=0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function Nb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function Jc(b,a,c){for(var d=
Nb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function Ob(b){return function(a,c){b(c,a)}}function Za(){for(var b=ia.length,a;b;){b--;a=ia[b].charCodeAt(0);if(57==a)return ia[b]="A",ia.join("");if(90==a)ia[b]="0";else return ia[b]=String.fromCharCode(a+1),ia.join("")}ia.unshift("0");return ia.join("")}function Pb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function u(b){var a=b.$$hashKey;q(arguments,function(a){a!==b&&q(a,function(a,c){b[c]=a})});Pb(b,a);return b}function U(b){return parseInt(b,
10)}function Qb(b,a){return u(new (u(function(){},{prototype:b})),a)}function t(){}function za(b){return b}function aa(b){return function(){return b}}function x(b){return"undefined"==typeof b}function z(b){return"undefined"!=typeof b}function T(b){return null!=b&&"object"==typeof b}function D(b){return"string"==typeof b}function qb(b){return"number"==typeof b}function Ja(b){return"[object Date]"==Ka.apply(b)}function J(b){return"[object Array]"==Ka.apply(b)}function B(b){return"function"==typeof b}
function $a(b){return"[object RegExp]"==Ka.apply(b)}function ya(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function Kc(b){return b&&(b.nodeName||b.on&&b.find)}function Lc(b,a,c){var d=[];q(b,function(b,f,g){d.push(a.call(c,b,f,g))});return d}function ab(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function La(b,a){var c=ab(b,a);0<=c&&b.splice(c,1);return a}function da(b,a){if(ya(b)||b&&b.$evalAsync&&b.$watch)throw Ma("cpws");if(a){if(b===
a)throw Ma("cpi");if(J(b))for(var c=a.length=0;c<b.length;c++)a.push(da(b[c]));else{c=a.$$hashKey;q(a,function(b,c){delete a[c]});for(var d in b)a[d]=da(b[d]);Pb(a,c)}}else(a=b)&&(J(b)?a=da(b,[]):Ja(b)?a=new Date(b.getTime()):$a(b)?a=RegExp(b.source):T(b)&&(a=da(b,{})));return a}function Mc(b,a){a=a||{};for(var c in b)b.hasOwnProperty(c)&&"$$"!==c.substr(0,2)&&(a[c]=b[c]);return a}function Aa(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&
"object"==c)if(J(b)){if(!J(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!Aa(b[d],a[d]))return!1;return!0}}else{if(Ja(b))return Ja(a)&&b.getTime()==a.getTime();if($a(b)&&$a(a))return b.toString()==a.toString();if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||ya(b)||ya(a)||J(a))return!1;c={};for(d in b)if("$"!==d.charAt(0)&&!B(b[d])){if(!Aa(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c.hasOwnProperty(d)&&"$"!==d.charAt(0)&&a[d]!==s&&!B(a[d]))return!1;return!0}return!1}function Rb(){return O.securityPolicy&&
O.securityPolicy.isActive||O.querySelector&&!(!O.querySelector("[ng-csp]")&&!O.querySelector("[data-ng-csp]"))}function rb(b,a){var c=2<arguments.length?ta.call(arguments,2):[];return!B(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,c.concat(ta.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function Nc(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)?c=s:ya(a)?c="$WINDOW":a&&O===a?c="$DOCUMENT":a&&(a.$evalAsync&&
a.$watch)&&(c="$SCOPE");return c}function ma(b,a){return"undefined"===typeof b?s:JSON.stringify(b,Nc,a?" ":null)}function Sb(b){return D(b)?JSON.parse(b):b}function Na(b){b&&0!==b.length?(b=w(""+b),b=!("f"==b||"0"==b||"false"==b||"no"==b||"n"==b||"[]"==b)):b=!1;return b}function ea(b){b=y(b).clone();try{b.html("")}catch(a){}var c=y("<div>").append(b).html();try{return 3===b[0].nodeType?w(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+w(b)})}catch(d){return w(c)}}function Tb(b){try{return decodeURIComponent(b)}catch(a){}}
function Ub(b){var a={},c,d;q((b||"").split("&"),function(b){b&&(c=b.split("="),d=Tb(c[0]),z(d)&&(b=z(c[1])?Tb(c[1]):!0,a[d]?J(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Vb(b){var a=[];q(b,function(b,d){J(b)?q(b,function(b){a.push(ua(d,!0)+(!0===b?"":"="+ua(b,!0)))}):a.push(ua(d,!0)+(!0===b?"":"="+ua(b,!0)))});return a.length?a.join("&"):""}function sb(b){return ua(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ua(b,a){return encodeURIComponent(b).replace(/%40/gi,
"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function Oc(b,a){function c(a){a&&d.push(a)}var d=[b],e,f,g=["ng:app","ng-app","x-ng-app","data-ng-app"],h=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;q(g,function(a){g[a]=!0;c(O.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(q(b.querySelectorAll("."+a),c),q(b.querySelectorAll("."+a+"\\:"),c),q(b.querySelectorAll("["+a+"]"),c))});q(d,function(a){if(!e){var b=h.exec(" "+a.className+" ");b?(e=a,f=
(b[2]||"").replace(/\s+/g,",")):q(a.attributes,function(b){!e&&g[b.name]&&(e=a,f=b.value)})}});e&&a(e,f?[f]:[])}function Wb(b,a){var c=function(){b=y(b);if(b.injector()){var c=b[0]===O?"document":ea(b);throw Ma("btstrpd",c);}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");c=Xb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animate",function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;
if(W&&!d.test(W.name))return c();W.name=W.name.replace(d,"");bb.resumeBootstrap=function(b){q(b,function(b){a.push(b)});c()}}function cb(b,a){a=a||"_";return b.replace(Pc,function(b,d){return(d?a:"")+b.toLowerCase()})}function tb(b,a,c){if(!b)throw Ma("areq",a||"?",c||"required");return b}function Oa(b,a,c){c&&J(b)&&(b=b[b.length-1]);tb(B(b),a,"not a function, got "+(b&&"object"==typeof b?b.constructor.name||"Object":typeof b));return b}function na(b,a){if("hasOwnProperty"===b)throw Ma("badname",
a);}function ub(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g<f;g++)d=a[g],b&&(b=(e=b)[d]);return!c&&B(b)?rb(e,b):b}function vb(b){if(b.startNode===b.endNode)return y(b.startNode);var a=b.startNode,c=[a];do{a=a.nextSibling;if(!a)break;c.push(a)}while(a!==b.endNode);return y(c)}function Qc(b){function a(a,b,c){return a[b]||(a[b]=c())}var c=L("$injector");return a(a(b,"angular",Object),"module",function(){var b={};return function(e,f,g){na(e,"module");f&&b.hasOwnProperty(e)&&(b[e]=
null);return a(b,e,function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return n}}if(!f)throw c("nomod",e);var b=[],d=[],l=a("$injector","invoke"),n={_invokeQueue:b,_runBlocks:d,requires:f,name:e,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animateProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider",
"register"),directive:a("$compileProvider","directive"),config:l,run:function(a){d.push(a);return this}};g&&l(g);return n})}})}function Pa(b){return b.replace(Rc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(Sc,"Moz$1")}function wb(b,a,c,d){function e(b){var e=c&&b?[this.filter(b)]:[this],m=a,k,l,n,r,p,C;if(!d||null!=b)for(;e.length;)for(k=e.shift(),l=0,n=k.length;l<n;l++)for(r=y(k[l]),m?r.triggerHandler("$destroy"):m=!m,p=0,r=(C=r.children()).length;p<r;p++)e.push(Ba(C[p]));return f.apply(this,
arguments)}var f=Ba.fn[b],f=f.$original||f;e.$original=f;Ba.fn[b]=e}function Q(b){if(b instanceof Q)return b;if(!(this instanceof Q)){if(D(b)&&"<"!=b.charAt(0))throw xb("nosel");return new Q(b)}if(D(b)){var a=O.createElement("div");a.innerHTML="<div>&#160;</div>"+b;a.removeChild(a.firstChild);yb(this,a.childNodes);y(O.createDocumentFragment()).append(this)}else yb(this,b)}function zb(b){return b.cloneNode(!0)}function Qa(b){Yb(b);var a=0;for(b=b.childNodes||[];a<b.length;a++)Qa(b[a])}function Zb(b,
a,c,d){if(z(d))throw xb("offargs");var e=ja(b,"events");ja(b,"handle")&&(x(a)?q(e,function(a,c){Ab(b,c,a);delete e[c]}):q(a.split(" "),function(a){x(c)?(Ab(b,a,e[a]),delete e[a]):La(e[a]||[],c)}))}function Yb(b,a){var c=b[db],d=Ra[c];d&&(a?delete Ra[c].data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),Zb(b)),delete Ra[c],b[db]=s))}function ja(b,a,c){var d=b[db],d=Ra[d||-1];if(z(c))d||(b[db]=d=++Tc,d=Ra[d]={}),d[a]=c;else return d&&d[a]}function $b(b,a,c){var d=ja(b,"data"),e=z(c),f=!e&&
z(a),g=f&&!T(a);d||g||ja(b,"data",d={});if(e)d[a]=c;else if(f){if(g)return d&&d[a];u(d,a)}else return d}function Bb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function Cb(b,a){a&&b.setAttribute&&q(a.split(" "),function(a){b.setAttribute("class",Y((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+Y(a)+" "," ")))})}function Db(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g,
" ");q(a.split(" "),function(a){a=Y(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",Y(c))}}function yb(b,a){if(a){a=a.nodeName||!z(a.length)||ya(a)?[a]:a;for(var c=0;c<a.length;c++)b.push(a[c])}}function ac(b,a){return eb(b,"$"+(a||"ngController")+"Controller")}function eb(b,a,c){b=y(b);9==b[0].nodeType&&(b=b.find("html"));for(a=J(a)?a:[a];b.length;){for(var d=0,e=a.length;d<e;d++)if((c=b.data(a[d]))!==s)return c;b=b.parent()}}function bc(b,a){var c=fb[a.toLowerCase()];return c&&
cc[b.nodeName]&&c}function Uc(b,a){var c=function(c,e){c.preventDefault||(c.preventDefault=function(){c.returnValue=!1});c.stopPropagation||(c.stopPropagation=function(){c.cancelBubble=!0});c.target||(c.target=c.srcElement||O);if(x(c.defaultPrevented)){var f=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;f.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented||!1===c.returnValue};q(a[e||c.type],function(a){a.call(b,c)});8>=P?(c.preventDefault=
null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function Ca(b){var a=typeof b,c;"object"==a&&null!==b?"function"==typeof(c=b.$$hashKey)?c=b.$$hashKey():c===s&&(c=b.$$hashKey=Za()):c=b;return a+":"+c}function Sa(b){q(b,this.put,this)}function dc(b){var a,c;"function"==typeof b?(a=b.$inject)||(a=[],b.length&&(c=b.toString().replace(Vc,""),c=c.match(Wc),q(c[1].split(Xc),function(b){b.replace(Yc,function(b,
c,d){a.push(d)})})),b.$inject=a):J(b)?(c=b.length-1,Oa(b[c],"fn"),a=b.slice(0,c)):Oa(b,"fn",!0);return a}function Xb(b){function a(a){return function(b,c){if(T(b))q(b,Ob(a));else return a(b,c)}}function c(a,b){na(a,"service");if(B(b)||J(b))b=n.instantiate(b);if(!b.$get)throw Ta("pget",a);return l[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[],c,d,f,h;q(a,function(a){if(!k.get(a)){k.put(a,!0);try{if(D(a))for(c=Ua(a),b=b.concat(e(c.requires)).concat(c._runBlocks),d=c._invokeQueue,
f=0,h=d.length;f<h;f++){var g=d[f],m=n.get(g[0]);m[g[1]].apply(m,g[2])}else B(a)?b.push(n.invoke(a)):J(a)?b.push(n.invoke(a)):Oa(a,"module")}catch(l){throw J(a)&&(a=a[a.length-1]),l.message&&(l.stack&&-1==l.stack.indexOf(l.message))&&(l=l.message+"\n"+l.stack),Ta("modulerr",a,l.stack||l.message||l);}}});return b}function f(a,b){function c(d){if(a.hasOwnProperty(d)){if(a[d]===g)throw Ta("cdep",m.join(" <- "));return a[d]}try{return m.unshift(d),a[d]=g,a[d]=b(d)}finally{m.shift()}}function d(a,b,e){var f=
[],h=dc(a),g,k,m;k=0;for(g=h.length;k<g;k++){m=h[k];if("string"!==typeof m)throw Ta("itkn",m);f.push(e&&e.hasOwnProperty(m)?e[m]:c(m))}a.$inject||(a=a[g]);switch(b?-1:f.length){case 0:return a();case 1:return a(f[0]);case 2:return a(f[0],f[1]);case 3:return a(f[0],f[1],f[2]);case 4:return a(f[0],f[1],f[2],f[3]);case 5:return a(f[0],f[1],f[2],f[3],f[4]);case 6:return a(f[0],f[1],f[2],f[3],f[4],f[5]);case 7:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6]);case 8:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],
f[7]);case 9:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8]);case 10:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8],f[9]);default:return a.apply(b,f)}}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=(J(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return T(e)||B(e)?e:c},get:c,annotate:dc,has:function(b){return l.hasOwnProperty(b+h)||a.hasOwnProperty(b)}}}var g={},h="Provider",m=[],k=new Sa,l={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,
["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,aa(b))}),constant:a(function(a,b){na(a,"constant");l[a]=b;r[a]=b}),decorator:function(a,b){var c=n.get(a+h),d=c.$get;c.$get=function(){var a=p.invoke(d,c);return p.invoke(b,null,{$delegate:a})}}}},n=l.$injector=f(l,function(){throw Ta("unpr",m.join(" <- "));}),r={},p=r.$injector=f(r,function(a){a=n.get(a+h);return p.invoke(a.$get,a)});q(e(b),function(a){p.invoke(a||t)});return p}function Zc(){var b=!0;this.disableAutoScrolling=
function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;q(a,function(a){b||"a"!==w(a.nodeName)||(b=a)});return b}function f(){var b=c.hash(),d;b?(d=g.getElementById(b))?d.scrollIntoView():(d=e(g.getElementsByName(b)))?d.scrollIntoView():"top"===b&&a.scrollTo(0,0):a.scrollTo(0,0)}var g=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(f)});return f}]}function $c(b,a,c,d){function e(a){try{a.apply(null,ta.call(arguments,1))}finally{if(C--,
0===C)for(;H.length;)try{H.pop()()}catch(b){c.error(b)}}}function f(a,b){(function gb(){q(I,function(a){a()});A=b(gb,a)})()}function g(){v=null;S!=h.url()&&(S=h.url(),q(Z,function(a){a(h.url())}))}var h=this,m=a[0],k=b.location,l=b.history,n=b.setTimeout,r=b.clearTimeout,p={};h.isMock=!1;var C=0,H=[];h.$$completeOutstandingRequest=e;h.$$incOutstandingRequestCount=function(){C++};h.notifyWhenNoOutstandingRequests=function(a){q(I,function(a){a()});0===C?a():H.push(a)};var I=[],A;h.addPollFn=function(a){x(A)&&
f(100,n);I.push(a);return a};var S=k.href,G=a.find("base"),v=null;h.url=function(a,c){k!==b.location&&(k=b.location);if(a){if(S!=a)return S=a,d.history?c?l.replaceState(null,"",a):(l.pushState(null,"",a),G.attr("href",G.attr("href"))):(v=a,c?k.replace(a):k.href=a),h}else return v||k.href.replace(/%27/g,"'")};var Z=[],E=!1;h.onUrlChange=function(a){if(!E){if(d.history)y(b).on("popstate",g);if(d.hashchange)y(b).on("hashchange",g);else h.addPollFn(g);E=!0}Z.push(a);return a};h.baseHref=function(){var a=
G.attr("href");return a?a.replace(/^https?\:\/\/[^\/]*/,""):""};var oa={},$="",va=h.baseHref();h.cookies=function(a,b){var d,e,f,h;if(a)b===s?m.cookie=escape(a)+"=;path="+va+";expires=Thu, 01 Jan 1970 00:00:00 GMT":D(b)&&(d=(m.cookie=escape(a)+"="+escape(b)+";path="+va).length+1,4096<d&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"));else{if(m.cookie!==$)for($=m.cookie,d=$.split("; "),oa={},f=0;f<d.length;f++)e=d[f],h=e.indexOf("="),0<h&&(a=
unescape(e.substring(0,h)),oa[a]===s&&(oa[a]=unescape(e.substring(h+1))));return oa}};h.defer=function(a,b){var c;C++;c=n(function(){delete p[c];e(a)},b||0);p[c]=!0;return c};h.defer.cancel=function(a){return p[a]?(delete p[a],r(a),e(t),!0):!1}}function bd(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new $c(b,d,a,c)}]}function cd(){this.$get=function(){function b(b,d){function e(a){a!=n&&(r?r==a&&(r=a.n):r=a,f(a.n,a.p),f(a,n),n=a,n.n=null)}function f(a,b){a!=b&&(a&&
(a.p=b),b&&(b.n=a))}if(b in a)throw L("$cacheFactory")("iid",b);var g=0,h=u({},d,{id:b}),m={},k=d&&d.capacity||Number.MAX_VALUE,l={},n=null,r=null;return a[b]={put:function(a,b){var c=l[a]||(l[a]={key:a});e(c);if(!x(b))return a in m||g++,m[a]=b,g>k&&this.remove(r.key),b},get:function(a){var b=l[a];if(b)return e(b),m[a]},remove:function(a){var b=l[a];b&&(b==n&&(n=b.p),b==r&&(r=b.n),f(b.n,b.p),delete l[a],delete m[a],g--)},removeAll:function(){m={};g=0;l={};n=r=null},destroy:function(){l=h=m=null;delete a[b]},
info:function(){return u({},h,{size:g})}}}var a={};b.info=function(){var b={};q(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function dd(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function ec(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,f=/^\s*(https?|ftp|mailto|tel|file):/,g=/^\s*(https?|ftp|file):|data:image\//,h=/^(on[a-z]+|formaction)$/;this.directive=function k(d,e){na(d,"directive");
D(d)?(tb(e,"directiveFactory"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];q(a[d],function(a,f){try{var h=b.invoke(a);B(h)?h={compile:aa(h)}:!h.compile&&h.link&&(h.compile=aa(h.link));h.priority=h.priority||0;h.index=f;h.name=h.name||d;h.require=h.require||h.controller&&h.name;h.restrict=h.restrict||"A";e.push(h)}catch(g){c(g)}});return e}])),a[d].push(e)):q(d,Ob(k));return this};this.aHrefSanitizationWhitelist=function(a){return z(a)?(f=a,this):
f};this.imgSrcSanitizationWhitelist=function(a){return z(a)?(g=a,this):g};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document","$sce","$animate",function(b,l,n,r,p,C,H,I,A,S,G){function v(a,b,c,d,e){a instanceof y||(a=y(a));q(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=y(b).wrap("<span></span>").parent()[0])});var f=E(a,b,a,c,d,e);return function(b,c){tb(b,"scope");for(var d=c?Da.clone.call(a):a,e=0,h=
d.length;e<h;e++){var g=d[e];1!=g.nodeType&&9!=g.nodeType||d.eq(e).data("$scope",b)}Z(d,"ng-scope");c&&c(d,b);f&&f(b,d,d);return d}}function Z(a,b){try{a.addClass(b)}catch(c){}}function E(a,b,c,d,e,f){function h(a,c,d,e){var f,k,l,n,p,r,C,X=[];p=0;for(r=c.length;p<r;p++)X.push(c[p]);C=p=0;for(r=g.length;p<r;C++)k=X[C],c=g[p++],f=g[p++],l=y(k),c?(c.scope?(n=a.$new(),l.data("$scope",n),Z(l,"ng-scope")):n=a,(l=c.transclude)||!e&&b?c(f,n,k,d,function(b){return function(c){var d=a.$new();d.$$transcluded=
!0;return b(d,c).on("$destroy",rb(d,d.$destroy))}}(l||b)):c(f,n,k,s,e)):f&&f(a,k.childNodes,s,e)}for(var g=[],k,l,n,p=0;p<a.length;p++)l=new R,k=oa(a[p],[],l,0===p?d:s,e),k=(f=k.length?M(k,a[p],l,b,c,null,[],[],f):null)&&f.terminal||!a[p].childNodes||!a[p].childNodes.length?null:E(a[p].childNodes,f?f.transclude:b),g.push(f),g.push(k),n=n||f||k,f=null;return n?h:null}function oa(a,b,c,f,h){var g=c.$attr,k;switch(a.nodeType){case 1:N(b,ka(Ea(a).toLowerCase()),"E",f,h);var l,n,p;k=a.attributes;for(var r=
0,C=k&&k.length;r<C;r++){var H=!1,v=!1;l=k[r];if(!P||8<=P||l.specified){n=l.name;p=ka(n);Fa.test(p)&&(n=cb(p.substr(6),"-"));var q=p.replace(/(Start|End)$/,"");p===q+"Start"&&(H=n,v=n.substr(0,n.length-5)+"end",n=n.substr(0,n.length-6));p=ka(n.toLowerCase());g[p]=n;c[p]=l=Y(P&&"href"==n?decodeURIComponent(a.getAttribute(n,2)):l.value);bc(a,p)&&(c[p]=!0);w(a,b,l,p);N(b,p,"A",f,h,H,v)}}a=a.className;if(D(a)&&""!==a)for(;k=e.exec(a);)p=ka(k[2]),N(b,p,"C",f,h)&&(c[p]=Y(k[3])),a=a.substr(k.index+k[0].length);
break;case 3:t(b,a.nodeValue);break;case 8:try{if(k=d.exec(a.nodeValue))p=ka(k[1]),N(b,p,"M",f,h)&&(c[p]=Y(k[2]))}catch(S){}}b.sort(wa);return b}function $(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw fa("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return y(d)}function va(a,b,c){return function(d,e,f,h){e=$(e[0],b,c);return a(d,e,f,h)}}function M(a,b,c,d,e,f,h,k,g){function p(a,b,c,
d){if(a){c&&(a=va(a,c,d));a.require=F.require;if(G===F||F.$$isolateScope)a=Q(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=va(b,c,d));b.require=F.require;if(G===F||F.$$isolateScope)b=Q(b,{isolateScope:!0});k.push(b)}}function r(a,b){var c,d="data",e=!1;if(D(a)){for(;"^"==(c=a.charAt(0))||"?"==c;)a=a.substr(1),"^"==c&&(d="inheritedData"),e=e||"?"==c;c=b[d]("$"+a+"Controller");8==b[0].nodeType&&b[0].$$controller&&(c=c||b[0].$$controller,b[0].$$controller=null);if(!c&&!e)throw fa("ctreq",a,ba);}else J(a)&&
(c=[],q(a,function(a){c.push(r(a,b))}));return c}function S(a,d,e,f,g){var p,X,v,E,I,K;p=b===e?c:Mc(c,new R(y(e),c.$attr));X=p.$$element;if(G){var va=/^\s*([@=&])(\??)\s*(\w*)\s*$/;f=y(e);K=d.$new(!0);N&&N===G.$$originalDirective?f.data("$isolateScope",K):f.data("$isolateScopeNoTemplate",K);Z(f,"ng-isolate-scope");q(G.scope,function(a,b){var c=a.match(va)||[],e=c[3]||b,f="?"==c[2],c=c[1],h,g,k;K.$$isolateBindings[b]=c+e;switch(c){case "@":p.$observe(e,function(a){K[b]=a});p.$$observers[e].$$scope=
d;p[e]&&(K[b]=l(p[e])(d));break;case "=":if(f&&!p[e])break;g=C(p[e]);k=g.assign||function(){h=K[b]=g(d);throw fa("nonassign",p[e],G.name);};h=K[b]=g(d);K.$watch(function(){var a=g(d);a!==K[b]&&(a!==h?h=K[b]=a:k(d,a=h=K[b]));return a});break;case "&":g=C(p[e]);K[b]=function(a){return g(d,a)};break;default:throw fa("iscp",G.name,b,a);}})}A&&q(A,function(a){var b={$scope:a===G||a.$$isolateScope?K:d,$element:X,$attrs:p,$transclude:g},c;I=a.controller;"@"==I&&(I=p[a.name]);c=H(I,b);8==X[0].nodeType?X[0].$$controller=
c:X.data("$"+a.name+"Controller",c);a.controllerAs&&(b.$scope[a.controllerAs]=c)});f=0;for(v=h.length;f<v;f++)try{E=h[f],E(E.isolateScope?K:d,X,p,E.require&&r(E.require,X))}catch(M){n(M,ea(X))}f=d;G&&(G.template||null===G.templateUrl)&&(f=K);a&&a(f,e.childNodes,s,g);for(f=k.length-1;0<=f;f--)try{E=k[f],E(E.isolateScope?K:d,X,p,E.require&&r(E.require,X))}catch($){n($,ea(X))}}g=g||{};var E=-Number.MAX_VALUE,I,A=g.controllerDirectives,G=g.newIsolateScopeDirective,N=g.templateDirective;g=g.transcludeDirective;
for(var M=c.$$element=y(b),F,ba,t,wa=d,x,ga=0,w=a.length;ga<w;ga++){F=a[ga];var u=F.$$start,Fa=F.$$end;u&&(M=$(b,u,Fa));t=s;if(E>F.priority)break;if(t=F.scope)I=I||F,F.templateUrl||(Va("new/isolated scope",G,F,M),T(t)&&(G=F));ba=F.name;!F.templateUrl&&F.controller&&(t=F.controller,A=A||{},Va("'"+ba+"' controller",A[ba],F,M),A[ba]=F);if(t=F.transclude)F.$$tlb||(Va("transclusion",g,F,M),g=F),"element"==t?(E=F.priority,t=$(b,u,Fa),M=c.$$element=y(O.createComment(" "+ba+": "+c[ba]+" ")),b=M[0],L(e,y(ta.call(t,
0)),b),wa=v(t,d,E,f&&f.name,{transcludeDirective:g})):(t=y(zb(b)).contents(),M.html(""),wa=v(t,d));if(F.template)if(Va("template",N,F,M),N=F,t=B(F.template)?F.template(M,c):F.template,t=fc(t),F.replace){f=F;t=y("<div>"+Y(t)+"</div>").contents();b=t[0];if(1!=t.length||1!==b.nodeType)throw fa("tplrt",ba,"");L(e,M,b);w={$attr:{}};t=oa(b,[],w);var P=a.splice(ga+1,a.length-(ga+1));G&&z(t);a=a.concat(t).concat(P);gb(c,w);w=a.length}else M.html(t);if(F.templateUrl)Va("template",N,F,M),N=F,F.replace&&(f=
F),S=ad(a.splice(ga,a.length-ga),M,c,e,wa,h,k,{controllerDirectives:A,newIsolateScopeDirective:G,templateDirective:N,transcludeDirective:g}),w=a.length;else if(F.compile)try{x=F.compile(M,c,wa),B(x)?p(null,x,u,Fa):x&&p(x.pre,x.post,u,Fa)}catch(ed){n(ed,ea(M))}F.terminal&&(S.terminal=!0,E=Math.max(E,F.priority))}S.scope=I&&!0===I.scope;S.transclude=g&&wa;return S}function z(a){for(var b=0,c=a.length;b<c;b++)a[b]=Qb(a[b],{$$isolateScope:!0})}function N(d,e,f,h,g,l,p){if(e===g)return null;g=null;if(a.hasOwnProperty(e)){var r;
e=b.get(e+c);for(var C=0,H=e.length;C<H;C++)try{r=e[C],(h===s||h>r.priority)&&-1!=r.restrict.indexOf(f)&&(l&&(r=Qb(r,{$$start:l,$$end:p})),d.push(r),g=r)}catch(E){n(E)}}return g}function gb(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,f){"class"==f?(Z(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?e.attr("style",e.attr("style")+";"+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||
(a[f]=b,d[f]=c[f])})}function ad(a,b,c,d,e,f,h,g){var k=[],n,l,C=b[0],H=a.shift(),v=u({},H,{templateUrl:null,transclude:null,replace:null,$$originalDirective:H}),I=B(H.templateUrl)?H.templateUrl(b,c):H.templateUrl;b.html("");r.get(S.getTrustedResourceUrl(I),{cache:p}).success(function(p){var r;p=fc(p);if(H.replace){p=y("<div>"+Y(p)+"</div>").contents();r=p[0];if(1!=p.length||1!==r.nodeType)throw fa("tplrt",H.name,I);p={$attr:{}};L(d,b,r);var S=oa(r,[],p);T(H.scope)&&z(S);a=S.concat(a);gb(c,p)}else r=
C,b.html(p);a.unshift(v);n=M(a,r,c,e,b,H,f,h,g);q(d,function(a,c){a==r&&(d[c]=b[0])});for(l=E(b[0].childNodes,e);k.length;){p=k.shift();var S=k.shift(),Z=k.shift(),G=k.shift(),A=b[0];S!==C&&(A=zb(r),L(Z,y(S),A));n(l,p,A,d,G)}k=null}).error(function(a,b,c,d){throw fa("tpload",d.url);});return function(a,b,c,d,e){k?(k.push(b),k.push(c),k.push(d),k.push(e)):n(l,b,c,d,e)}}function wa(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function Va(a,b,c,d){if(b)throw fa("multidir",
b.name,c.name,a,ea(d));}function t(a,b){var c=l(b,!0);c&&a.push({priority:0,compile:aa(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);Z(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function x(a,b){if("xlinkHref"==b||"IMG"!=Ea(a)&&("src"==b||"ngSrc"==b))return S.RESOURCE_URL}function w(a,b,c,d){var e=l(c,!0);if(e){if("multiple"===d&&"SELECT"===Ea(a))throw fa("selmulti",ea(a));b.push({priority:100,compile:function(){return{pre:function(b,c,f){c=
f.$$observers||(f.$$observers={});if(h.test(d))throw fa("nodomevents");if(e=l(f[d],!0,x(a,d)))f[d]=e(b),(c[d]||(c[d]=[])).$$inter=!0,(f.$$observers&&f.$$observers[d].$$scope||b).$watch(e,function(a){f.$set(d,a)})}}}})}}function L(a,b,c){var d=b[0],e=b.length,f=d.parentNode,h,g;if(a)for(h=0,g=a.length;h<g;h++)if(a[h]==d){a[h++]=c;g=h+e-1;for(var k=a.length;h<k;h++,g++)g<k?a[h]=a[g]:delete a[h];a.length-=e-1;break}f&&f.replaceChild(c,d);a=O.createDocumentFragment();a.appendChild(d);c[y.expando]=d[y.expando];
d=1;for(e=b.length;d<e;d++)f=b[d],y(f).remove(),a.appendChild(f),delete b[d];b[0]=c;b.length=1}function Q(a,b){return u(function(){return a.apply(null,arguments)},a,b)}var R=function(a,b){this.$$element=a;this.$attr=b||{}};R.prototype={$normalize:ka,$addClass:function(a){a&&0<a.length&&G.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&G.removeClass(this.$$element,a)},$set:function(a,b,c,d){function e(a,b){var c=[],d=a.split(/\s+/),f=b.split(/\s+/),h=0;a:for(;h<d.length;h++){for(var g=
d[h],k=0;k<f.length;k++)if(g==f[k])continue a;c.push(g)}return c}if("class"==a)b=b||"",c=this.$$element.attr("class")||"",this.$removeClass(e(c,b).join(" ")),this.$addClass(e(b,c).join(" "));else{var h=bc(this.$$element[0],a);h&&(this.$$element.prop(a,b),d=h);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=cb(a,"-"));h=Ea(this.$$element);if("A"===h&&"href"===a||"IMG"===h&&"src"===a)if(!P||8<=P)h=xa(b).href,""!==h&&("href"===a&&!h.match(f)||"src"===a&&!h.match(g))&&(this[a]=b="unsafe:"+
h);!1!==c&&(null===b||b===s?this.$$element.removeAttr(d):this.$$element.attr(d,b))}(c=this.$$observers)&&q(c[a],function(a){try{a(b)}catch(c){n(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);I.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var ba=l.startSymbol(),ga=l.endSymbol(),fc="{{"==ba||"}}"==ga?za:function(a){return a.replace(/\{\{/g,ba).replace(/}}/g,ga)},Fa=/^ngAttr[A-Z]/;return v}]}function ka(b){return Pa(b.replace(fd,""))}
function gd(){var b={},a=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,d){na(a,"controller");T(a)?u(b,a):b[a]=d};this.$get=["$injector","$window",function(c,d){return function(e,f){var g,h,m;D(e)&&(g=e.match(a),h=g[1],m=g[3],e=b.hasOwnProperty(h)?b[h]:ub(f.$scope,h,!0)||ub(d,h,!0),Oa(e,h,!0));g=c.instantiate(e,f);if(m){if(!f||"object"!=typeof f.$scope)throw L("$controller")("noscp",h||e.name,m);f.$scope[m]=g}return g}}]}function hd(){this.$get=["$window",function(b){return y(b.document)}]}function id(){this.$get=
["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function gc(b){var a={},c,d,e;if(!b)return a;q(b.split("\n"),function(b){e=b.indexOf(":");c=w(Y(b.substr(0,e)));d=Y(b.substr(e+1));c&&(a[c]=a[c]?a[c]+(", "+d):d)});return a}function hc(b){var a=T(b)?b:s;return function(c){a||(a=gc(b));return c?a[w(c)]||null:a}}function ic(b,a,c){if(B(c))return c(b,a);q(c,function(c){b=c(b,a)});return b}function jd(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},
e=this.defaults={transformResponse:[function(d){D(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=Sb(d)));return d}],transformRequest:[function(a){return T(a)&&"[object File]"!==Ka.apply(a)?ma(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:d,put:d,patch:d},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},f=this.interceptors=[],g=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,n,r){function p(a){function c(a){var b=
u({},a,{data:ic(a.data,a.headers,d.transformResponse)});return 200<=a.status&&300>a.status?b:n.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},f=function(a){function b(a){var c;q(a,function(b,d){B(b)&&(c=b(),null!=c?a[d]=c:delete a[d])})}var c=e.headers,d=u({},a.headers),f,h,c=u({},c.common,c[w(a.method)]);b(c);b(d);a:for(f in c){a=w(f);for(h in d)if(w(h)===a)continue a;d[f]=c[f]}return d}(a);u(d,a);d.headers=f;d.method=Ga(d.method);(a=Eb(d.url)?b.cookies()[d.xsrfCookieName||
e.xsrfCookieName]:s)&&(f[d.xsrfHeaderName||e.xsrfHeaderName]=a);var h=[function(a){f=a.headers;var b=ic(a.data,hc(f),a.transformRequest);x(a.data)&&q(f,function(a,b){"content-type"===w(b)&&delete f[b]});x(a.withCredentials)&&!x(e.withCredentials)&&(a.withCredentials=e.withCredentials);return C(a,b,f).then(c,c)},s],g=n.when(d);for(q(A,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&h.push(a.response,a.responseError)});h.length;){a=h.shift();
var k=h.shift(),g=g.then(a,k)}g.success=function(a){g.then(function(b){a(b.data,b.status,b.headers,d)});return g};g.error=function(a){g.then(null,function(b){a(b.data,b.status,b.headers,d)});return g};return g}function C(b,c,f){function g(a,b,c){q&&(200<=a&&300>a?q.put(s,[a,b,gc(c)]):q.remove(s));k(b,a,c);d.$$phase||d.$apply()}function k(a,c,d){c=Math.max(c,0);(200<=c&&300>c?r.resolve:r.reject)({data:a,status:c,headers:hc(d),config:b})}function m(){var a=ab(p.pendingRequests,b);-1!==a&&p.pendingRequests.splice(a,
1)}var r=n.defer(),C=r.promise,q,A,s=H(b.url,b.params);p.pendingRequests.push(b);C.then(m,m);(b.cache||e.cache)&&(!1!==b.cache&&"GET"==b.method)&&(q=T(b.cache)?b.cache:T(e.cache)?e.cache:I);if(q)if(A=q.get(s),z(A)){if(A.then)return A.then(m,m),A;J(A)?k(A[1],A[0],da(A[2])):k(A,200,{})}else q.put(s,C);x(A)&&a(b.method,s,c,g,f,b.timeout,b.withCredentials,b.responseType);return C}function H(a,b){if(!b)return a;var c=[];Jc(b,function(a,b){null===a||x(a)||(J(a)||(a=[a]),q(a,function(a){T(a)&&(a=ma(a));
c.push(ua(b)+"="+ua(a))}))});return a+(-1==a.indexOf("?")?"?":"&")+c.join("&")}var I=c("$http"),A=[];q(f,function(a){A.unshift(D(a)?r.get(a):r.invoke(a))});q(g,function(a,b){var c=D(a)?r.get(a):r.invoke(a);A.splice(b,0,{response:function(a){return c(n.when(a))},responseError:function(a){return c(n.reject(a))}})});p.pendingRequests=[];(function(a){q(arguments,function(a){p[a]=function(b,c){return p(u(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){p[a]=
function(b,c,d){return p(u(d||{},{method:a,url:b,data:c}))}})})("post","put");p.defaults=e;return p}]}function kd(){this.$get=["$browser","$window","$document",function(b,a,c){return ld(b,md,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function ld(b,a,c,d,e,f){function g(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;P?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=
d;e.body.appendChild(c);return d}return function(e,m,k,l,n,r,p,C){function H(){A=-1;G&&G();v&&v.abort()}function I(a,d,e,h){var g=f||xa(m).protocol;Z&&c.cancel(Z);G=v=null;d="file"==g?e?200:404:d;a(1223==d?204:d,e,h);b.$$completeOutstandingRequest(t)}var A;b.$$incOutstandingRequestCount();m=m||b.url();if("jsonp"==w(e)){var s="_"+(d.counter++).toString(36);d[s]=function(a){d[s].data=a};var G=g(m.replace("JSON_CALLBACK","angular.callbacks."+s),function(){d[s].data?I(l,200,d[s].data):I(l,A||-2);delete d[s]})}else{var v=
new a;v.open(e,m,!0);q(n,function(a,b){z(a)&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(4==v.readyState){var a=v.getAllResponseHeaders();I(l,A||v.status,v.responseType?v.response:v.responseText,a)}};p&&(v.withCredentials=!0);C&&(v.responseType=C);v.send(k||null)}if(0<r)var Z=c(H,r);else r&&r.then&&r.then(H)}}function nd(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler",
"$sce",function(c,d,e){function f(f,k,l){for(var n,r,p=0,C=[],H=f.length,q=!1,A=[];p<H;)-1!=(n=f.indexOf(b,p))&&-1!=(r=f.indexOf(a,n+g))?(p!=n&&C.push(f.substring(p,n)),C.push(p=c(q=f.substring(n+g,r))),p.exp=q,p=r+h,q=!0):(p!=H&&C.push(f.substring(p)),p=H);(H=C.length)||(C.push(""),H=1);if(l&&1<C.length)throw jc("noconcat",f);if(!k||q)return A.length=H,p=function(a){try{for(var b=0,c=H,h;b<c;b++)"function"==typeof(h=C[b])&&(h=h(a),h=l?e.getTrusted(l,h):e.valueOf(h),null===h||x(h)?h="":"string"!=
typeof h&&(h=ma(h))),A[b]=h;return A.join("")}catch(g){a=jc("interr",f,g.toString()),d(a)}},p.exp=f,p.parts=C,p}var g=b.length,h=a.length;f.startSymbol=function(){return b};f.endSymbol=function(){return a};return f}]}function od(){this.$get=["$rootScope","$window","$q",function(b,a,c){function d(d,g,h,m){var k=a.setInterval,l=a.clearInterval,n=c.defer(),r=n.promise,p=0,C=z(m)&&!m;h=z(h)?h:0;r.then(null,null,d);r.$$intervalId=k(function(){n.notify(p++);0<h&&p>=h&&(n.resolve(p),l(r.$$intervalId),delete e[r.$$intervalId]);
C||b.$apply()},g);e[r.$$intervalId]=n;return r}var e={};d.cancel=function(a){return a&&a.$$intervalId in e?(e[a.$$intervalId].reject("canceled"),clearInterval(a.$$intervalId),delete e[a.$$intervalId],!0):!1};return d}]}function pd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",
gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",
mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function kc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=sb(b[a]);return b.join("/")}function lc(b,a){var c=xa(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=U(c.port)||qd[c.protocol]||null}function mc(b,a){var c="/"!==b.charAt(0);c&&(b="/"+b);var d=xa(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=Ub(d.search);a.$$hash=decodeURIComponent(d.hash);
a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function la(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Wa(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Fb(b){return b.substr(0,Wa(b).lastIndexOf("/")+1)}function nc(b,a){this.$$html5=!0;a=a||"";var c=Fb(b);lc(b,this);this.$$parse=function(a){var b=la(c,a);if(!D(b))throw Gb("ipthprfx",a,c);mc(b,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Vb(this.$$search),b=this.$$hash?
"#"+sb(this.$$hash):"";this.$$url=kc(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$rewrite=function(d){var e;if((e=la(b,d))!==s)return d=e,(e=la(a,e))!==s?c+(la("/",e)||e):b+d;if((e=la(c,d))!==s)return c+e;if(c==d+"/")return c}}function Hb(b,a){var c=Fb(b);lc(b,this);this.$$parse=function(d){var e=la(b,d)||la(c,d),e="#"==e.charAt(0)?la(a,e):this.$$html5?e:"";if(!D(e))throw Gb("ihshprfx",d,a);mc(e,this);this.$$compose()};this.$$compose=function(){var c=Vb(this.$$search),
e=this.$$hash?"#"+sb(this.$$hash):"";this.$$url=kc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$rewrite=function(a){if(Wa(b)==Wa(a))return a}}function oc(b,a){this.$$html5=!0;Hb.apply(this,arguments);var c=Fb(b);this.$$rewrite=function(d){var e;if(b==Wa(d))return d;if(e=la(c,d))return b+a+e;if(c===d+"/")return c}}function hb(b){return function(){return this[b]}}function pc(b,a){return function(c){if(x(c))return this[b];this[b]=a(c);this.$$compose();return this}}
function rd(){var b="",a=!1;this.hashPrefix=function(a){return z(a)?(b=a,this):b};this.html5Mode=function(b){return z(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,f){function g(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),a)}var h,m=d.baseHref(),k=d.url();a?(m=k.substring(0,k.indexOf("/",k.indexOf("//")+2))+(m||"/"),e=e.history?nc:oc):(m=Wa(k),e=Hb);h=new e(m,"#"+b);h.$$parse(h.$$rewrite(k));f.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&
2!=a.which){for(var b=y(a.target);"a"!==w(b[0].nodeName);)if(b[0]===f[0]||!(b=b.parent())[0])return;var e=b.prop("href"),g=h.$$rewrite(e);e&&(!b.attr("target")&&g&&!a.isDefaultPrevented())&&(a.preventDefault(),g!=d.url()&&(h.$$parse(g),c.$apply(),W.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=k&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$broadcast("$locationChangeStart",a,h.absUrl()).defaultPrevented?d.url(h.absUrl()):(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);
g(b)}),c.$$phase||c.$digest()))});var l=0;c.$watch(function(){var a=d.url(),b=h.$$replace;l&&a==h.absUrl()||(l++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),g(a))}));h.$$replace=!1;return l});return h}]}function sd(){var b=!0,a=this;this.debugEnabled=function(a){return z(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+
a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||t;return e.apply?function(){var a=[];q(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function ha(b,a,c){if("string"!==typeof b&&"[object String]"!==Ka.apply(b))return b;
if("constructor"===b&&!c)throw pa("isecfld",a);if("_"===b.charAt(0)||"_"===b.charAt(b.length-1))throw pa("isecprv",a);return b}function Xa(b,a){if(b&&b.constructor===b)throw pa("isecfn",a);if(b&&b.document&&b.location&&b.alert&&b.setInterval)throw pa("isecwindow",a);if(b&&(b.nodeName||b.on&&b.find))throw pa("isecdom",a);return b}function ib(b,a,c,d,e){e=e||{};a=a.split(".");for(var f,g=0;1<a.length;g++){f=ha(a.shift(),d);var h=b[f];h||(h={},b[f]=h);b=h;b.then&&e.unwrapPromises&&(qa(d),"$$v"in b||
function(a){a.then(function(b){a.$$v=b})}(b),b.$$v===s&&(b.$$v={}),b=b.$$v)}f=ha(a.shift(),d);return b[f]=c}function qc(b,a,c,d,e,f,g){ha(b,f);ha(a,f);ha(c,f);ha(d,f);ha(e,f);return g.unwrapPromises?function(h,g){var k=g&&g.hasOwnProperty(b)?g:h,l;if(null===k||k===s)return k;(k=k[b])&&k.then&&(qa(f),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);if(!a||null===k||k===s)return k;(k=k[a])&&k.then&&(qa(f),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);if(!c||null===k||
k===s)return k;(k=k[c])&&k.then&&(qa(f),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);if(!d||null===k||k===s)return k;(k=k[d])&&k.then&&(qa(f),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);if(!e||null===k||k===s)return k;(k=k[e])&&k.then&&(qa(f),"$$v"in k||(l=k,l.$$v=s,l.then(function(a){l.$$v=a})),k=k.$$v);return k}:function(f,g){var k=g&&g.hasOwnProperty(b)?g:f;if(null===k||k===s)return k;k=k[b];if(!a||null===k||k===s)return k;k=k[a];if(!c||null===k||k===s)return k;
k=k[c];if(!d||null===k||k===s)return k;k=k[d];return e&&null!==k&&k!==s?k=k[e]:k}}function rc(b,a,c){if(Ib.hasOwnProperty(b))return Ib[b];var d=b.split("."),e=d.length,f;if(a.csp)f=6>e?qc(d[0],d[1],d[2],d[3],d[4],c,a):function(b,f){var h=0,g;do g=qc(d[h++],d[h++],d[h++],d[h++],d[h++],c,a)(b,f),f=s,b=g;while(h<e);return g};else{var g="var l, fn, p;\n";q(d,function(b,d){ha(b,c);g+="if(s === null || s === undefined) return s;\nl=s;\ns="+(d?"s":'((k&&k.hasOwnProperty("'+b+'"))?k:s)')+'["'+b+'"];\n'+(a.unwrapPromises?
'if (s && s.then) {\n pw("'+c.replace(/\"/g,'\\"')+'");\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n':"")});var g=g+"return s;",h=new Function("s","k","pw",g);h.toString=function(){return g};f=function(a,b){return h(a,b,qa)}}"hasOwnProperty"!==b&&(Ib[b]=f);return f}function td(){var b={},a={csp:!1,unwrapPromises:!1,logPromiseWarnings:!0};this.unwrapPromises=function(b){return z(b)?(a.unwrapPromises=!!b,this):a.unwrapPromises};this.logPromiseWarnings=
function(b){return z(b)?(a.logPromiseWarnings=b,this):a.logPromiseWarnings};this.$get=["$filter","$sniffer","$log",function(c,d,e){a.csp=d.csp;qa=function(b){a.logPromiseWarnings&&!sc.hasOwnProperty(b)&&(sc[b]=!0,e.warn("[$parse] Promise found in the expression `"+b+"`. Automatic unwrapping of promises in Angular expressions is deprecated."))};return function(d){var e;switch(typeof d){case "string":if(b.hasOwnProperty(d))return b[d];e=new Jb(a);e=(new Ya(e,c,a)).parse(d,!1);"hasOwnProperty"!==d&&
(b[d]=e);return e;case "function":return d;default:return t}}}]}function ud(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return vd(function(a){b.$evalAsync(a)},a)}]}function vd(b,a){function c(a){return a}function d(a){return g(a)}var e=function(){var h=[],m,k;return k={resolve:function(a){if(h){var c=h;h=s;m=f(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],m.then(a[0],a[1],a[2])})}},reject:function(a){k.resolve(g(a))},notify:function(a){if(h){var c=h;h.length&&b(function(){for(var b,
d=0,e=c.length;d<e;d++)b=c[d],b[2](a)})}},promise:{then:function(b,f,g){var k=e(),C=function(d){try{k.resolve((B(b)?b:c)(d))}catch(e){k.reject(e),a(e)}},H=function(b){try{k.resolve((B(f)?f:d)(b))}catch(c){k.reject(c),a(c)}},q=function(b){try{k.notify((B(g)?g:c)(b))}catch(d){a(d)}};h?h.push([C,H,q]):m.then(C,H,q);return k.promise},"catch":function(a){return this.then(null,a)},"finally":function(a){function b(a,c){var d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,f){var h=null;try{h=
(a||c)()}catch(g){return b(g,!1)}return h&&B(h.then)?h.then(function(){return b(e,f)},function(a){return b(a,!1)}):b(e,f)}return this.then(function(a){return d(a,!0)},function(a){return d(a,!1)})}}}},f=function(a){return a&&B(a.then)?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},g=function(c){return{then:function(f,g){var l=e();b(function(){try{l.resolve((B(g)?g:d)(c))}catch(b){l.reject(b),a(b)}});return l.promise}}};return{defer:e,reject:g,when:function(h,m,k,l){var n=
e(),r,p=function(b){try{return(B(m)?m:c)(b)}catch(d){return a(d),g(d)}},C=function(b){try{return(B(k)?k:d)(b)}catch(c){return a(c),g(c)}},q=function(b){try{return(B(l)?l:c)(b)}catch(d){a(d)}};b(function(){f(h).then(function(a){r||(r=!0,n.resolve(f(a).then(p,C,q)))},function(a){r||(r=!0,n.resolve(C(a)))},function(a){r||n.notify(q(a))})});return n.promise},all:function(a){var b=e(),c=0,d=J(a)?[]:{};q(a,function(a,e){c++;f(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||
b.reject(a)})});0===c&&b.resolve(d);return b.promise}}}function wd(){var b=10,a=L("$rootScope");this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse","$browser",function(c,d,e,f){function g(){this.$id=Za();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$postDigestQueue=[];this.$$listeners={};this.$$isolateBindings=
{}}function h(b){if(l.$$phase)throw a("inprog",l.$$phase);l.$$phase=b}function m(a,b){var c=e(a);Oa(c,b);return c}function k(){}g.prototype={constructor:g,$new:function(a){a?(a=new g,a.$root=this.$root,a.$$asyncQueue=this.$$asyncQueue,a.$$postDigestQueue=this.$$postDigestQueue):(a=function(){},a.prototype=this,a=new a,a.$id=Za());a["this"]=a;a.$$listeners={};a.$parent=this;a.$$watchers=a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=
this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,c){var d=m(a,"watch"),e=this.$$watchers,f={fn:b,last:k,get:d,exp:a,eq:!!c};if(!B(b)){var h=m(b||t,"listener");f.fn=function(a,b,c){h(c)}}if("string"==typeof a&&d.constant){var g=f.fn;f.fn=function(a,b,c){g.call(this,a,b,c);La(e,f)}}e||(e=this.$$watchers=[]);e.unshift(f);return function(){La(e,f)}},$watchCollection:function(a,b){var c=this,d,f,h=0,g=e(a),k=[],l={},m=0;return this.$watch(function(){f=
g(c);var a,b;if(T(f))if(pb(f))for(d!==k&&(d=k,m=d.length=0,h++),a=f.length,m!==a&&(h++,d.length=m=a),b=0;b<a;b++)d[b]!==f[b]&&(h++,d[b]=f[b]);else{d!==l&&(d=l={},m=0,h++);a=0;for(b in f)f.hasOwnProperty(b)&&(a++,d.hasOwnProperty(b)?d[b]!==f[b]&&(h++,d[b]=f[b]):(m++,d[b]=f[b],h++));if(m>a)for(b in h++,d)d.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(m--,delete d[b])}else d!==f&&(d=f,h++);return h},function(){b(f,d,c)})},$digest:function(){var c,e,f,g,m=this.$$asyncQueue,q=this.$$postDigestQueue,s,t,G=
b,v,y=[],E,z,$;h("$digest");do{t=!1;for(v=this;m.length;)try{$=m.shift(),$.scope.$eval($.expression)}catch(x){d(x)}do{if(g=v.$$watchers)for(s=g.length;s--;)try{(c=g[s])&&((e=c.get(v))!==(f=c.last)&&!(c.eq?Aa(e,f):"number"==typeof e&&"number"==typeof f&&isNaN(e)&&isNaN(f)))&&(t=!0,c.last=c.eq?da(e):e,c.fn(e,f===k?e:f,v),5>G&&(E=4-G,y[E]||(y[E]=[]),z=B(c.exp)?"fn: "+(c.exp.name||c.exp.toString()):c.exp,z+="; newVal: "+ma(e)+"; oldVal: "+ma(f),y[E].push(z)))}catch(M){d(M)}if(!(g=v.$$childHead||v!==this&&
v.$$nextSibling))for(;v!==this&&!(g=v.$$nextSibling);)v=v.$parent}while(v=g);if(t&&!G--)throw l.$$phase=null,a("infdig",b,ma(y));}while(t||m.length);for(l.$$phase=null;q.length;)try{q.shift()()}catch(w){d(w)}},$destroy:function(){if(l!=this&&!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);
this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return e(a)(this,b)},$evalAsync:function(a){l.$$phase||l.$$asyncQueue.length||f.defer(function(){l.$$asyncQueue.length&&l.$digest()});this.$$asyncQueue.push({scope:this,expression:a})},$$postDigest:function(a){this.$$postDigestQueue.push(a)},$apply:function(a){try{return h("$apply"),this.$eval(a)}catch(b){d(b)}finally{l.$$phase=
null;try{l.$digest()}catch(c){throw d(c),c;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[ab(c,b)]=null}},$emit:function(a,b){var c=[],e,f=this,h=!1,g={name:a,targetScope:f,stopPropagation:function(){h=!0},preventDefault:function(){g.defaultPrevented=!0},defaultPrevented:!1},k=[g].concat(ta.call(arguments,1)),l,m;do{e=f.$$listeners[a]||c;g.currentScope=f;l=0;for(m=e.length;l<m;l++)if(e[l])try{e[l].apply(null,k)}catch(q){d(q)}else e.splice(l,
1),l--,m--;if(h)break;f=f.$parent}while(f);return g},$broadcast:function(a,b){var c=this,e=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},h=[f].concat(ta.call(arguments,1)),g,k;do{c=e;f.currentScope=c;e=c.$$listeners[a]||[];g=0;for(k=e.length;g<k;g++)if(e[g])try{e[g].apply(null,h)}catch(l){d(l)}else e.splice(g,1),g--,k--;if(!(e=c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(e=c.$$nextSibling);)c=c.$parent}while(c=e);return f}};var l=
new g;return l}]}function xd(b){if("self"===b)return b;if(D(b)){if(-1<b.indexOf("***"))throw ra("iwcard",b);b=b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08").replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return RegExp("^"+b+"$")}if($a(b))return RegExp("^"+b.source+"$");throw ra("imatcher");}function tc(b){var a=[];z(b)&&q(b,function(b){a.push(xd(b))});return a}function yd(){this.SCE_CONTEXTS=ca;var b=["self"],a=[];this.resourceUrlWhitelist=function(a){arguments.length&&
(b=tc(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=tc(b));return a};this.$get=["$log","$document","$injector",function(c,d,e){function f(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var g=function(a){throw ra("unsafe");};e.has("$sanitize")&&(g=e.get("$sanitize"));var h=f(),
m={};m[ca.HTML]=f(h);m[ca.CSS]=f(h);m[ca.URL]=f(h);m[ca.JS]=f(h);m[ca.RESOURCE_URL]=f(m[ca.URL]);return{trustAs:function(a,b){var c=m.hasOwnProperty(a)?m[a]:null;if(!c)throw ra("icontext",a,b);if(null===b||b===s||""===b)return b;if("string"!==typeof b)throw ra("itype",a);return new c(b)},getTrusted:function(c,d){if(null===d||d===s||""===d)return d;var e=m.hasOwnProperty(c)?m[c]:null;if(e&&d instanceof e)return d.$$unwrapTrustedValue();if(c===ca.RESOURCE_URL){var e=xa(d.toString()),f,h,q=!1;f=0;for(h=
b.length;f<h;f++)if("self"===b[f]?Eb(e):b[f].exec(e.href)){q=!0;break}if(q)for(f=0,h=a.length;f<h;f++)if("self"===a[f]?Eb(e):a[f].exec(e.href)){q=!1;break}if(q)return d;throw ra("insecurl",d.toString());}if(c===ca.HTML)return g(d);throw ra("unsafe");},valueOf:function(a){return a instanceof h?a.$$unwrapTrustedValue():a}}}]}function zd(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$document","$sceDelegate",function(a,c,d){if(b&&P&&(c=c[0].documentMode,
c!==s&&8>c))throw ra("iequirks");var e=da(ca);e.isEnabled=function(){return b};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;b||(e.trustAs=e.getTrusted=function(a,b){return b},e.valueOf=za);e.parseAs=function(b,c){var d=a(c);return d.literal&&d.constant?d:function(a,c){return e.getTrusted(b,d(a,c))}};var f=e.parseAs,g=e.getTrusted,h=e.trustAs;q(ca,function(a,b){var c=w(b);e[Pa("parse_as_"+c)]=function(b){return f(a,b)};e[Pa("get_trusted_"+c)]=function(b){return g(a,b)};e[Pa("trust_as_"+
c)]=function(b){return h(a,b)}});return e}]}function Ad(){this.$get=["$window","$document",function(b,a){var c={},d=U((/android (\d+)/.exec(w((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g,h=/^(Moz|webkit|O|ms)(?=[A-Z])/,m=f.body&&f.body.style,k=!1,l=!1;if(m){for(var n in m)if(k=h.exec(n)){g=k[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in m&&"webkit");k=!!("transition"in m||g+"Transition"in m);l=!!("animation"in m||g+"Animation"in
m);!d||k&&l||(k=D(f.body.style.webkitTransition),l=D(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hashchange:"onhashchange"in b&&(!f.documentMode||7<f.documentMode),hasEvent:function(a){if("input"==a&&9==P)return!1;if(x(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:Rb(),vendorPrefix:g,transitions:k,animations:l,msie:P}}]}function Bd(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,h,m){var k=
c.defer(),l=k.promise,n=z(m)&&!m;h=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a),d(a)}finally{delete f[l.$$timeoutId]}n||b.$apply()},h);l.$$timeoutId=h;f[h]=k;return l}var f={};e.cancel=function(b){return b&&b.$$timeoutId in f?(f[b.$$timeoutId].reject("canceled"),delete f[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return e}]}function xa(b){P&&(R.setAttribute("href",b),b=R.href);R.setAttribute("href",b);return{href:R.href,protocol:R.protocol?R.protocol.replace(/:$/,""):"",host:R.host,
search:R.search?R.search.replace(/^\?/,""):"",hash:R.hash?R.hash.replace(/^#/,""):"",hostname:R.hostname,port:R.port,pathname:R.pathname&&"/"===R.pathname.charAt(0)?R.pathname:"/"+R.pathname}}function Eb(b){b=D(b)?xa(b):b;return b.protocol===uc.protocol&&b.host===uc.host}function Cd(){this.$get=aa(W)}function vc(b){function a(d,e){if(T(d)){var f={};q(d,function(b,c){f[c]=a(c,b)});return f}return b.factory(d+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+
c)}}];a("currency",wc);a("date",xc);a("filter",Dd);a("json",Ed);a("limitTo",Fd);a("lowercase",Gd);a("number",yc);a("orderBy",zc);a("uppercase",Hd)}function Dd(){return function(b,a,c){if(!J(b))return b;var d=typeof c,e=[];e.check=function(a){for(var b=0;b<e.length;b++)if(!e[b](a))return!1;return!0};"function"!==d&&(c="boolean"===d&&c?function(a,b){return bb.equals(a,b)}:function(a,b){b=(""+b).toLowerCase();return-1<(""+a).toLowerCase().indexOf(b)});var f=function(a,b){if("string"==typeof b&&"!"===
b.charAt(0))return!f(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if("$"!==d.charAt(0)&&f(a[d],b))return!0}return!1;case "array":for(d=0;d<a.length;d++)if(f(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a={$:a};case "object":for(var g in a)"$"==g?function(){if(a[g]){var b=g;e.push(function(c){return f(c,a[b])})}}():
function(){if("undefined"!=typeof a[g]){var b=g;e.push(function(c){return f(ub(c,b),a[b])})}}();break;case "function":e.push(a);break;default:return b}for(var d=[],h=0;h<b.length;h++){var m=b[h];e.check(m)&&d.push(m)}return d}}function wc(b){var a=b.NUMBER_FORMATS;return function(b,d){x(d)&&(d=a.CURRENCY_SYM);return Ac(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function yc(b){var a=b.NUMBER_FORMATS;return function(b,d){return Ac(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}
function Ac(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var f=0>b;b=Math.abs(b);var g=b+"",h="",m=[],k=!1;if(-1!==g.indexOf("e")){var l=g.match(/([\d\.]+)e(-?)(\d+)/);l&&"-"==l[2]&&l[3]>e+1?g="0":(h=g,k=!0)}if(k)0<e&&(-1<b&&1>b)&&(h=b.toFixed(e));else{g=(g.split(Bc)[1]||"").length;x(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));g=Math.pow(10,e);b=Math.round(b*g)/g;b=(""+b).split(Bc);g=b[0];b=b[1]||"";var l=0,n=a.lgSize,r=a.gSize;if(g.length>=n+r)for(l=g.length-n,k=0;k<l;k++)0===(l-k)%r&&0!==
k&&(h+=c),h+=g.charAt(k);for(k=l;k<g.length;k++)0===(g.length-k)%n&&0!==k&&(h+=c),h+=g.charAt(k);for(;b.length<e;)b+="0";e&&"0"!==e&&(h+=d+b.substr(0,e))}m.push(f?a.negPre:a.posPre);m.push(h);m.push(f?a.negSuf:a.posSuf);return m.join("")}function Kb(b,a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function V(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(0<c||e>-c)e+=c;0===e&&-12==c&&(e=12);return Kb(e,a,d)}}function jb(b,a){return function(c,
d){var e=c["get"+b](),f=Ga(a?"SHORT"+b:b);return d[f][e]}}function xc(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,m=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=U(b[9]+b[10]),g=U(b[9]+b[11]));h.call(a,U(b[1]),U(b[2])-1,U(b[3]));f=U(b[4]||0)-f;g=U(b[5]||0)-g;h=U(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));m.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
return function(c,e){var f="",g=[],h,m;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;D(c)&&(c=Id.test(c)?U(c):a(c));qb(c)&&(c=new Date(c));if(!Ja(c))return c;for(;e;)(m=Jd.exec(e))?(g=g.concat(ta.call(m,1)),e=g.pop()):(g.push(e),e=null);q(g,function(a){h=Kd[a];f+=h?h(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return f}}function Ed(){return function(b){return ma(b,!0)}}function Fd(){return function(b,a){if(!J(b)&&!D(b))return b;a=U(a);if(D(b))return a?0<=a?b.slice(0,a):b.slice(a,
b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);0<a?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function zc(b){return function(a,c,d){function e(a,b){return Na(b)?function(b,c){return a(c,b)}:a}if(!J(a)||!c)return a;c=J(c)?c:[c];c=Lc(c,function(a){var c=!1,d=a||za;if(D(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))c="-"==a.charAt(0),a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),f=typeof c,g=typeof e;f==g?("string"==f&&(c=
c.toLowerCase(),e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=f<g?-1:1;return c},c)});for(var f=[],g=0;g<a.length;g++)f.push(a[g]);return f.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(0!==e)return e}return 0},d))}}function sa(b){B(b)&&(b={link:b});b.restrict=b.restrict||"AC";return aa(b)}function Cc(b,a){function c(a,c){c=c?"-"+cb(c,"-"):"";b.removeClass((a?kb:lb)+c).addClass((a?lb:kb)+c)}var d=this,e=b.parent().controller("form")||mb,f=0,g=d.$error={},h=[];d.$name=a.name||a.ngForm;
d.$dirty=!1;d.$pristine=!0;d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(Ha);c(!0);d.$addControl=function(a){na(a.$name,"input");h.push(a);a.$name&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];q(g,function(b,c){d.$setValidity(c,!0,a)});La(h,a)};d.$setValidity=function(a,b,h){var n=g[a];if(b)n&&(La(n,h),n.length||(f--,f||(c(b),d.$valid=!0,d.$invalid=!1),g[a]=!1,c(!0,a),e.$setValidity(a,!0,d)));else{f||c(b);if(n){if(-1!=ab(n,h))return}else g[a]=n=[],
f++,c(!1,a),e.$setValidity(a,!1,d);n.push(h);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(Ha).addClass(nb);d.$dirty=!0;d.$pristine=!1;e.$setDirty()};d.$setPristine=function(){b.removeClass(nb).addClass(Ha);d.$dirty=!1;d.$pristine=!0;q(h,function(a){a.$setPristine()})}}function ob(b,a,c,d,e,f){var g=function(){var e=a.val();Na(c.ngTrim||"T")&&(e=Y(e));d.$viewValue!==e&&b.$apply(function(){d.$setViewValue(e)})};if(e.hasEvent("input"))a.on("input",g);else{var h,m=function(){h||(h=
f.defer(function(){g();h=null}))};a.on("keydown",function(a){a=a.keyCode;91===a||(15<a&&19>a||37<=a&&40>=a)||m()});a.on("change",g);if(e.hasEvent("paste"))a.on("paste cut",m)}d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)};var k=c.ngPattern,l=function(a,b){if(d.$isEmpty(b)||a.test(b))return d.$setValidity("pattern",!0),b;d.$setValidity("pattern",!1);return s};k&&((e=k.match(/^\/(.*)\/([gim]*)$/))?(k=RegExp(e[1],e[2]),e=function(a){return l(k,a)}):e=function(c){var d=b.$eval(k);
if(!d||!d.test)throw L("ngPattern")("noregexp",k,d,ea(a));return l(d,c)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var n=U(c.ngMinlength);e=function(a){if(!d.$isEmpty(a)&&a.length<n)return d.$setValidity("minlength",!1),s;d.$setValidity("minlength",!0);return a};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var r=U(c.ngMaxlength);e=function(a){if(!d.$isEmpty(a)&&a.length>r)return d.$setValidity("maxlength",!1),s;d.$setValidity("maxlength",!0);return a};d.$parsers.push(e);
d.$formatters.push(e)}}function Lb(b,a){b="ngClass"+b;return function(){return{restrict:"AC",link:function(c,d,e){function f(b){if(!0===a||c.$index%2===a)h&&!Aa(b,h)&&e.$removeClass(g(h)),e.$addClass(g(b));h=da(b)}function g(a){if(J(a))return a.join(" ");if(T(a)){var b=[];q(a,function(a,c){a&&b.push(c)});return b.join(" ")}return a}var h;c.$watch(e[b],f,!0);e.$observe("class",function(a){f(c.$eval(e[b]))});"ngClass"!==b&&c.$watch("$index",function(d,f){var h=d&1;h!==f&1&&(h===a?(h=c.$eval(e[b]),e.$addClass(g(h))):
(h=c.$eval(e[b]),e.$removeClass(g(h))))})}}}}var w=function(b){return D(b)?b.toLowerCase():b},Ga=function(b){return D(b)?b.toUpperCase():b},P,y,Ba,ta=[].slice,Ld=[].push,Ka=Object.prototype.toString,Ma=L("ng"),bb=W.angular||(W.angular={}),Ua,Ea,ia=["0","0","0"];P=U((/msie (\d+)/.exec(w(navigator.userAgent))||[])[1]);isNaN(P)&&(P=U((/trident\/.*; rv:(\d+)/.exec(w(navigator.userAgent))||[])[1]));t.$inject=[];za.$inject=[];var Y=function(){return String.prototype.trim?function(b){return D(b)?b.trim():
b}:function(b){return D(b)?b.replace(/^\s*/,"").replace(/\s*$/,""):b}}();Ea=9>P?function(b){b=b.nodeName?b:b[0];return b.scopeName&&"HTML"!=b.scopeName?Ga(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var Pc=/[A-Z]/g,Md={full:"1.2.0",major:1,minor:"NG_VERSION_MINOR",dot:0,codeName:"timely-delivery"},Ra=Q.cache={},db=Q.expando="ng-"+(new Date).getTime(),Tc=1,Dc=W.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+
a,c)},Ab=W.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},Rc=/([\:\-\_]+(.))/g,Sc=/^moz([A-Z])/,xb=L("jqLite"),Da=Q.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===O.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),Q(W).on("load",a))},toString:function(){var b=[];q(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?y(this[b]):y(this[this.length+b])},length:0,
push:Ld,sort:[].sort,splice:[].splice},fb={};q("multiple selected checked disabled readOnly required open".split(" "),function(b){fb[w(b)]=b});var cc={};q("input select option textarea button form details".split(" "),function(b){cc[Ga(b)]=!0});q({data:$b,inheritedData:eb,scope:function(b){return y(b).data("$scope")||eb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return y(b).data("$isolateScope")||y(b).data("$isolateScopeNoTemplate")},controller:ac,injector:function(b){return eb(b,
"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Bb,css:function(b,a,c){a=Pa(a);if(z(c))b.style[a]=c;else{var d;8>=P&&(d=b.currentStyle&&b.currentStyle[a],""===d&&(d="auto"));d=d||b.style[a];8>=P&&(d=""===d?s:d);return d}},attr:function(b,a,c){var d=w(a);if(fb[d])if(z(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||t).specified?d:s;else if(z(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,
2),null===b?s:b},prop:function(b,a,c){if(z(c))b[a]=c;else return b[a]},text:function(){function b(b,d){var e=a[b.nodeType];if(x(d))return e?b[e]:"";b[e]=d}var a=[];9>P?(a[1]="innerText",a[3]="nodeValue"):a[1]=a[3]="textContent";b.$dv="";return b}(),val:function(b,a){if(x(a)){if("SELECT"===Ea(b)&&b.multiple){var c=[];q(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(x(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<
d.length;c++)Qa(d[c]);b.innerHTML=a}},function(b,a){Q.prototype[a]=function(a,d){var e,f;if((2==b.length&&b!==Bb&&b!==ac?a:d)===s){if(T(a)){for(e=0;e<this.length;e++)if(b===$b)b(this[e],a);else for(f in a)b(this[e],f,a[f]);return this}e=b.$dv;f=e===s?Math.min(this.length,1):this.length;for(var g=0;g<f;g++){var h=b(this[g],a,d);e=e?e+h:h}return e}for(e=0;e<this.length;e++)b(this[e],a,d);return this}});q({removeData:Yb,dealoc:Qa,on:function a(c,d,e,f){if(z(f))throw xb("onargs");var g=ja(c,"events"),
h=ja(c,"handle");g||ja(c,"events",g={});h||ja(c,"handle",h=Uc(c,g));q(d.split(" "),function(d){var f=g[d];if(!f){if("mouseenter"==d||"mouseleave"==d){var l=O.body.contains||O.body.compareDocumentPosition?function(a,c){var d=9===a.nodeType?a.documentElement:a,e=c&&c.parentNode;return a===e||!!(e&&1===e.nodeType&&(d.contains?d.contains(e):a.compareDocumentPosition&&a.compareDocumentPosition(e)&16))}:function(a,c){if(c)for(;c=c.parentNode;)if(c===a)return!0;return!1};g[d]=[];a(c,{mouseleave:"mouseout",
mouseenter:"mouseover"}[d],function(a){var c=a.relatedTarget;c&&(c===this||l(this,c))||h(a,d)})}else Dc(c,d,h),g[d]=[];f=g[d]}f.push(e)})},off:Zb,replaceWith:function(a,c){var d,e=a.parentNode;Qa(a);q(new Q(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];q(a.childNodes,function(a){1===a.nodeType&&c.push(a)});return c},contents:function(a){return a.childNodes||[]},append:function(a,c){q(new Q(c),function(c){1!==a.nodeType&&11!==a.nodeType||
a.appendChild(c)})},prepend:function(a,c){if(1===a.nodeType){var d=a.firstChild;q(new Q(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=y(c)[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){Qa(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;q(new Q(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:Db,removeClass:Cb,toggleClass:function(a,c,d){x(d)&&(d=!Bb(a,c));(d?Db:Cb)(a,c)},parent:function(a){return(a=
a.parentNode)&&11!==a.nodeType?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;null!=a&&1!==a.nodeType;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName(c)},clone:zb,triggerHandler:function(a,c,d){c=(ja(a,"events")||{})[c];d=d||[];var e=[{preventDefault:t,stopPropagation:t}];q(c,function(c){c.apply(a,e.concat(d))})}},function(a,c){Q.prototype[c]=function(c,e,f){for(var g,h=0;h<this.length;h++)x(g)?(g=a(this[h],c,e,f),z(g)&&
(g=y(g))):yb(g,a(this[h],c,e,f));return z(g)?g:this};Q.prototype.bind=Q.prototype.on;Q.prototype.unbind=Q.prototype.off});Sa.prototype={put:function(a,c){this[Ca(a)]=c},get:function(a){return this[Ca(a)]},remove:function(a){var c=this[a=Ca(a)];delete this[a];return c}};var Wc=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,Xc=/,/,Yc=/^\s*(_?)(\S+?)\1\s*$/,Vc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ta=L("$injector"),Nd=L("$animate"),Od=["$provide",function(a){this.$$selectors={};this.register=function(c,d){var e=
c+"-animation";if(c&&"."!=c.charAt(0))throw Nd("notcsel",c);this.$$selectors[c.substr(1)]=e;a.factory(e,d)};this.$get=["$timeout",function(a){return{enter:function(d,e,f,g){f=f&&f[f.length-1];var h=e&&e[0]||f&&f.parentNode,m=f&&f.nextSibling||null;q(d,function(a){h.insertBefore(a,m)});g&&a(g,0,!1)},leave:function(d,e){d.remove();e&&a(e,0,!1)},move:function(a,c,f,g){this.enter(a,c,f,g)},addClass:function(d,e,f){e=D(e)?e:J(e)?e.join(" "):"";q(d,function(a){Db(a,e)});f&&a(f,0,!1)},removeClass:function(d,
e,f){e=D(e)?e:J(e)?e.join(" "):"";q(d,function(a){Cb(a,e)});f&&a(f,0,!1)},enabled:t}}]}],fa=L("$compile");ec.$inject=["$provide"];var fd=/^(x[\:\-_]|data[\:\-_])/i,md=W.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw L("$httpBackend")("noxhr");},jc=L("$interpolate"),Pd=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,qd={http:80,https:443,ftp:21},Gb=
L("$location");oc.prototype=Hb.prototype=nc.prototype={$$html5:!1,$$replace:!1,absUrl:hb("$$absUrl"),url:function(a,c){if(x(a))return this.$$url;var d=Pd.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));(d[2]||d[1])&&this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:hb("$$protocol"),host:hb("$$host"),port:hb("$$port"),path:pc("$$path",function(a){return"/"==a.charAt(0)?a:"/"+a}),search:function(a,c){switch(arguments.length){case 0:return this.$$search;case 1:if(D(a))this.$$search=
Ub(a);else if(T(a))this.$$search=a;else throw Gb("isrcharg");break;default:x(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:pc("$$hash",za),replace:function(){this.$$replace=!0;return this}};var pa=L("$parse"),sc={},qa,Ia={"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:t,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return z(d)?z(e)?d+e:d:z(e)?e:s},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(z(d)?d:0)-(z(e)?
e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":t,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=
e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Qd={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Jb=function(a){this.options=a};Jb.prototype={constructor:Jb,lex:function(a){this.text=a;this.index=0;this.ch=s;this.lastCh=":";this.tokens=[];var c;for(a=[];this.index<this.text.length;){this.ch=
this.text.charAt(this.index);if(this.is("\"'"))this.readString(this.ch);else if(this.isNumber(this.ch)||this.is(".")&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(this.ch))this.readIdent(),this.was("{,")&&("{"===a[0]&&(c=this.tokens[this.tokens.length-1]))&&(c.json=-1===c.text.indexOf("."));else if(this.is("(){}[].,;:?"))this.tokens.push({index:this.index,text:this.ch,json:this.was(":[,")&&this.is("{[")||this.is("}]:,")}),this.is("{[")&&a.unshift(this.ch),this.is("}]")&&a.shift(),
this.index++;else if(this.isWhitespace(this.ch)){this.index++;continue}else{var d=this.ch+this.peek(),e=d+this.peek(2),f=Ia[this.ch],g=Ia[d],h=Ia[e];h?(this.tokens.push({index:this.index,text:e,fn:h}),this.index+=3):g?(this.tokens.push({index:this.index,text:d,fn:g}),this.index+=2):f?(this.tokens.push({index:this.index,text:this.ch,fn:f,json:this.was("[,:")&&this.is("+-")}),this.index+=1):this.throwError("Unexpected next character ",this.index,this.index+1)}this.lastCh=this.ch}return this.tokens},
is:function(a){return-1!==a.indexOf(this.ch)},was:function(a){return-1!==a.indexOf(this.lastCh)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=
d||this.index;c=z(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw pa("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=w(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}a*=
1;this.tokens.push({index:c,text:a,json:!0,fn:function(){return a}})},readIdent:function(){for(var a=this,c="",d=this.index,e,f,g,h;this.index<this.text.length;){h=this.text.charAt(this.index);if("."===h||this.isIdent(h)||this.isNumber(h))"."===h&&(e=this.index),c+=h;else break;this.index++}if(e)for(f=this.index;f<this.text.length;){h=this.text.charAt(f);if("("===h){g=c.substr(e-d+1);c=c.substr(0,e-d);this.index=f;break}if(this.isWhitespace(h))f++;else break}d={index:d,text:c};if(Ia.hasOwnProperty(c))d.fn=
Ia[c],d.json=Ia[c];else{var m=rc(c,this.options,this.text);d.fn=u(function(a,c){return m(a,c)},{assign:function(d,e){return ib(d,c,e,a.text,a.options)}})}this.tokens.push(d);g&&(this.tokens.push({index:e,text:".",json:!1}),this.tokens.push({index:e+1,text:g,json:!1}))},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,f=!1;this.index<this.text.length;){var g=this.text.charAt(this.index),e=e+g;if(f)"u"===g?(g=this.text.substring(this.index+1,this.index+5),g.match(/[\da-f]{4}/i)||
this.throwError("Invalid unicode escape [\\u"+g+"]"),this.index+=4,d+=String.fromCharCode(parseInt(g,16))):d=(f=Qd[g])?d+f:d+g,f=!1;else if("\\"===g)f=!0;else{if(g===a){this.index++;this.tokens.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});return}d+=g}this.index++}this.throwError("Unterminated quote",c)}};var Ya=function(a,c,d){this.lexer=a;this.$filter=c;this.options=d};Ya.ZERO=function(){return 0};Ya.prototype={constructor:Ya,parse:function(a,c){this.text=a;this.json=c;this.tokens=
this.lexer.lex(a);c&&(this.assignment=this.logicalOR,this.functionCall=this.fieldAccess=this.objectIndex=this.filterChain=function(){this.throwError("is not valid json",{text:a,index:0})});var d=c?this.primary():this.statements();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);d.literal=!!d.literal;d.constant=!!d.constant;return d},primary:function(){var a;if(this.expect("("))a=this.filterChain(),this.consume(")");else if(this.expect("["))a=this.arrayDeclaration();
else if(this.expect("{"))a=this.object();else{var c=this.expect();(a=c.fn)||this.throwError("not a primary expression",c);c.json&&(a.constant=!0,a.literal=!0)}for(var d;c=this.expect("(","[",".");)"("===c.text?(a=this.functionCall(a,d),d=null):"["===c.text?(d=a,a=this.objectIndex(a)):"."===c.text?(d=a,a=this.fieldAccess(a)):this.throwError("IMPOSSIBLE");return a},throwError:function(a,c){throw pa("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},peekToken:function(){if(0===this.tokens.length)throw pa("ueoe",
this.text);return this.tokens[0]},peek:function(a,c,d,e){if(0<this.tokens.length){var f=this.tokens[0],g=f.text;if(g===a||g===c||g===d||g===e||!(a||c||d||e))return f}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.json&&!a.json&&this.throwError("is not valid json",a),this.tokens.shift(),a):!1},consume:function(a){this.expect(a)||this.throwError("is unexpected, expecting ["+a+"]",this.peek())},unaryFn:function(a,c){return u(function(d,e){return a(d,e,c)},{constant:c.constant})},
ternaryFn:function(a,c,d){return u(function(e,f){return a(e,f)?c(e,f):d(e,f)},{constant:a.constant&&c.constant&&d.constant})},binaryFn:function(a,c,d){return u(function(e,f){return c(e,f,a,d)},{constant:a.constant&&d.constant})},statements:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.filterChain()),!this.expect(";"))return 1===a.length?a[0]:function(c,d){for(var e,f=0;f<a.length;f++){var g=a[f];g&&(e=g(c,d))}return e}},filterChain:function(){for(var a=
this.expression(),c;;)if(c=this.expect("|"))a=this.binaryFn(a,c.fn,this.filter());else return a},filter:function(){for(var a=this.expect(),c=this.$filter(a.text),d=[];;)if(a=this.expect(":"))d.push(this.expression());else{var e=function(a,e,h){h=[h];for(var m=0;m<d.length;m++)h.push(d[m](a,e));return c.apply(a,h)};return function(){return e}}},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary(),c,d;return(d=this.expect("="))?(a.assign||this.throwError("implies assignment but ["+
this.text.substring(0,d.index)+"] can not be assigned to",d),c=this.ternary(),function(d,f){return a.assign(d,c(d,f),f)}):a},ternary:function(){var a=this.logicalOR(),c,d;if(this.expect("?")){c=this.ternary();if(d=this.expect(":"))return this.ternaryFn(a,c,this.ternary());this.throwError("expected :",d)}else return a},logicalOR:function(){for(var a=this.logicalAND(),c;;)if(c=this.expect("||"))a=this.binaryFn(a,c.fn,this.logicalAND());else return a},logicalAND:function(){var a=this.equality(),c;if(c=
this.expect("&&"))a=this.binaryFn(a,c.fn,this.logicalAND());return a},equality:function(){var a=this.relational(),c;if(c=this.expect("==","!=","===","!=="))a=this.binaryFn(a,c.fn,this.equality());return a},relational:function(){var a=this.additive(),c;if(c=this.expect("<",">","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=
this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(Ya.ZERO,a.fn,this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this,d=this.expect().text,e=rc(d,this.options,this.text);return u(function(c,d,h){return e(h||a(c,d),d)},{assign:function(e,g,h){return ib(a(e,h),d,g,c.text,c.options)}})},objectIndex:function(a){var c=
this,d=this.expression();this.consume("]");return u(function(e,f){var g=a(e,f),h=ha(d(e,f),c.text,!0),m;if(!g)return s;(g=Xa(g[h],c.text))&&(g.then&&c.options.unwrapPromises)&&(m=g,"$$v"in g||(m.$$v=s,m.then(function(a){m.$$v=a})),g=g.$$v);return g},{assign:function(e,f,g){var h=ha(d(e,g),c.text);return Xa(a(e,g),c.text)[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this;return function(f,g){for(var h=
[],m=c?c(f,g):f,k=0;k<d.length;k++)h.push(d[k](f,g));k=a(f,g,m)||t;Xa(m,e.text);Xa(k,e.text);h=k.apply?k.apply(m,h):k(h[0],h[1],h[2],h[3],h[4]);return Xa(h,e.text)}},arrayDeclaration:function(){var a=[],c=!0;if("]"!==this.peekToken().text){do{var d=this.expression();a.push(d);d.constant||(c=!1)}while(this.expect(","))}this.consume("]");return u(function(c,d){for(var g=[],h=0;h<a.length;h++)g.push(a[h](c,d));return g},{literal:!0,constant:c})},object:function(){var a=[],c=!0;if("}"!==this.peekToken().text){do{var d=
this.expect(),d=d.string||d.text;this.consume(":");var e=this.expression();a.push({key:d,value:e});e.constant||(c=!1)}while(this.expect(","))}this.consume("}");return u(function(c,d){for(var e={},m=0;m<a.length;m++){var k=a[m];e[k.key]=k.value(c,d)}return e},{literal:!0,constant:c})}};var Ib={},ra=L("$sce"),ca={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},R=O.createElement("a"),uc=xa(W.location.href,!0);vc.$inject=["$provide"];wc.$inject=["$locale"];yc.$inject=["$locale"];var Bc=
".",Kd={yyyy:V("FullYear",4),yy:V("FullYear",2,0,!0),y:V("FullYear",1),MMMM:jb("Month"),MMM:jb("Month",!0),MM:V("Month",2,1),M:V("Month",1,1),dd:V("Date",2),d:V("Date",1),HH:V("Hours",2),H:V("Hours",1),hh:V("Hours",2,-12),h:V("Hours",1,-12),mm:V("Minutes",2),m:V("Minutes",1),ss:V("Seconds",2),s:V("Seconds",1),sss:V("Milliseconds",3),EEEE:jb("Day"),EEE:jb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Kb(Math[0<
a?"floor":"ceil"](a/60),2)+Kb(Math.abs(a%60),2))}},Jd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,Id=/^\-?\d+$/;xc.$inject=["$locale"];var Gd=aa(w),Hd=aa(Ga);zc.$inject=["$parse"];var Rd=aa({restrict:"E",compile:function(a,c){8>=P&&(c.href||c.name||c.$set("href",""),a.append(O.createComment("IE fix")));return function(a,c){c.on("click",function(a){c.attr("href")||a.preventDefault()})}}}),Mb={};q(fb,function(a,c){if("multiple"!=a){var d=ka("ng-"+c);Mb[d]=function(){return{priority:100,
compile:function(){return function(a,f,g){a.$watch(g[d],function(a){g.$set(c,!!a)})}}}}}});q(["src","srcset","href"],function(a){var c=ka("ng-"+a);Mb[c]=function(){return{priority:99,link:function(d,e,f){f.$observe(c,function(c){c&&(f.$set(a,c),P&&e.prop(a,f[a]))})}}}});var mb={$addControl:t,$removeControl:t,$setValidity:t,$setDirty:t,$setPristine:t};Cc.$inject=["$element","$attrs","$scope"];var Ec=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:Cc,compile:function(){return{pre:function(a,
e,f,g){if(!f.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};Dc(e[0],"submit",h);e.on("$destroy",function(){c(function(){Ab(e[0],"submit",h)},0,!1)})}var m=e.parent().controller("form"),k=f.name||f.ngForm;k&&ib(a,k,g,k);if(m)e.on("$destroy",function(){m.$removeControl(g);k&&ib(a,k,s,k);u(g,mb)})}}}}}]},Sd=Ec(),Td=Ec(!0),Ud=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,Vd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/,Wd=
/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,Fc={text:ob,number:function(a,c,d,e,f,g){ob(a,c,d,e,f,g);e.$parsers.push(function(a){var c=e.$isEmpty(a);if(c||Wd.test(a))return e.$setValidity("number",!0),""===a?null:c?a:parseFloat(a);e.$setValidity("number",!1);return s});e.$formatters.push(function(a){return e.$isEmpty(a)?"":""+a});d.min&&(a=function(a){var c=parseFloat(d.min);if(!e.$isEmpty(a)&&a<c)return e.$setValidity("min",!1),s;e.$setValidity("min",!0);return a},e.$parsers.push(a),e.$formatters.push(a));
d.max&&(a=function(a){var c=parseFloat(d.max);if(!e.$isEmpty(a)&&a>c)return e.$setValidity("max",!1),s;e.$setValidity("max",!0);return a},e.$parsers.push(a),e.$formatters.push(a));e.$formatters.push(function(a){if(e.$isEmpty(a)||qb(a))return e.$setValidity("number",!0),a;e.$setValidity("number",!1);return s})},url:function(a,c,d,e,f,g){ob(a,c,d,e,f,g);a=function(a){if(e.$isEmpty(a)||Ud.test(a))return e.$setValidity("url",!0),a;e.$setValidity("url",!1);return s};e.$formatters.push(a);e.$parsers.push(a)},
email:function(a,c,d,e,f,g){ob(a,c,d,e,f,g);a=function(a){if(e.$isEmpty(a)||Vd.test(a))return e.$setValidity("email",!0),a;e.$setValidity("email",!1);return s};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){x(d.name)&&c.attr("name",Za());c.on("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var f=d.ngTrueValue,g=d.ngFalseValue;D(f)||
(f=!0);D(g)||(g=!1);c.on("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==f};e.$formatters.push(function(a){return a===f});e.$parsers.push(function(a){return a?f:g})},hidden:t,button:t,submit:t,reset:t},Gc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,f,g){g&&(Fc[w(f.type)]||Fc.text)(d,e,f,g,c,a)}}}],lb="ng-valid",kb="ng-invalid",Ha="ng-pristine",
nb="ng-dirty",Xd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,f){function g(a,c){c=c?"-"+cb(c,"-"):"";e.removeClass((a?kb:lb)+c).addClass((a?lb:kb)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var h=f(d.ngModel),m=h.assign;if(!m)throw L("ngModel")("nonassign",d.ngModel,ea(e));this.$render=t;this.$isEmpty=function(a){return x(a)||
""===a||null===a||a!==a};var k=e.inheritedData("$formController")||mb,l=0,n=this.$error={};e.addClass(Ha);g(!0);this.$setValidity=function(a,c){n[a]!==!c&&(c?(n[a]&&l--,l||(g(!0),this.$valid=!0,this.$invalid=!1)):(g(!1),this.$invalid=!0,this.$valid=!1,l++),n[a]=!c,g(c,a),k.$setValidity(a,c,this))};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(nb).addClass(Ha)};this.$setViewValue=function(d){this.$viewValue=d;this.$pristine&&(this.$dirty=!0,this.$pristine=!1,e.removeClass(Ha).addClass(nb),
k.$setDirty());q(this.$parsers,function(a){d=a(d)});this.$modelValue!==d&&(this.$modelValue=d,m(a,d),q(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}}))};var r=this;a.$watch(function(){var c=h(a);if(r.$modelValue!==c){var d=r.$formatters,e=d.length;for(r.$modelValue=c;e--;)c=d[e](c);r.$viewValue!==c&&(r.$viewValue=c,r.$render())}})}],Yd=function(){return{require:["ngModel","^?form"],controller:Xd,link:function(a,c,d,e){var f=e[0],g=e[1]||mb;g.$addControl(f);a.$on("$destroy",function(){g.$removeControl(f)})}}},
Zd=aa({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Hc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var f=function(a){if(d.required&&e.$isEmpty(a))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(f);e.$parsers.unshift(f);d.$observe("required",function(){f(e.$viewValue)})}}}},$d=function(){return{require:"ngModel",link:function(a,c,d,e){var f=(a=/\/(.*)\//.exec(d.ngList))&&
RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){if(!x(a)){var c=[];a&&q(a.split(f),function(a){a&&c.push(Y(a))});return c}});e.$formatters.push(function(a){return J(a)?a.join(", "):s});e.$isEmpty=function(a){return!a||!a.length}}}},ae=/^(true|false|\d+)$/,be=function(){return{priority:100,compile:function(a,c){return ae.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},ce=sa(function(a,c,d){c.addClass("ng-binding").data("$binding",
d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==s?"":a)})}),de=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],ee=["$sce","$parse",function(a,c){return function(d,e,f){e.addClass("ng-binding").data("$binding",f.ngBindHtml);var g=c(f.ngBindHtml);d.$watch(function(){return(g(d)||"").toString()},function(c){e.html(a.getTrustedHtml(g(d))||"")})}}],fe=Lb("",!0),ge=
Lb("Odd",0),he=Lb("Even",1),ie=sa({compile:function(a,c){c.$set("ngCloak",s);a.removeClass("ng-cloak")}}),je=[function(){return{scope:!0,controller:"@"}}],Ic={};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ka("ng-"+a);Ic[c]=["$parse",function(d){return{compile:function(e,f){var g=d(f[c]);return function(c,d,e){d.on(w(a),function(a){c.$apply(function(){g(c,{$event:a})})})}}}}]});
var ke=["$animate",function(a){return{transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,compile:function(c,d,e){return function(c,d,h){var m,k;c.$watch(h.ngIf,function(l){Na(l)?(k=c.$new(),e(k,function(c){m={startNode:c[0],endNode:c[c.length++]=O.createComment(" end ngIf: "+h.ngIf+" ")};a.enter(c,d.parent(),d)})):(k&&(k.$destroy(),k=null),m&&(a.leave(vb(m)),m=null))})}}}}],le=["$http","$templateCache","$anchorScroll","$compile","$animate","$sce",function(a,c,d,e,f,g){return{restrict:"ECA",
priority:400,terminal:!0,transclude:"element",compile:function(h,m,k){var l=m.ngInclude||m.src,n=m.onload||"",r=m.autoscroll;return function(h,m){var q=0,s,t,y=function(){s&&(s.$destroy(),s=null);t&&(f.leave(t),t=null)};h.$watch(g.parseAsResourceUrl(l),function(g){var l=function(){!z(r)||r&&!h.$eval(r)||d()},x=++q;g?(a.get(g,{cache:c}).success(function(a){if(x===q){var c=h.$new();k(c,function(d){y();s=c;t=d;t.html(a);f.enter(t,null,m,l);e(t.contents())(s);s.$emit("$includeContentLoaded");h.$eval(n)})}}).error(function(){x===
q&&y()}),h.$emit("$includeContentRequested")):y()})}}}}],me=sa({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),ne=sa({terminal:!0,priority:1E3}),oe=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,f,g){var h=g.count,m=g.$attr.when&&f.attr(g.$attr.when),k=g.offset||0,l=e.$eval(m)||{},n={},r=c.startSymbol(),p=c.endSymbol(),s=/^when(Minus)?(.+)$/;q(g,function(a,c){s.test(c)&&(l[w(c.replace("when","").replace("Minus","-"))]=f.attr(g.$attr[c]))});
q(l,function(a,e){n[e]=c(a.replace(d,r+h+"-"+k+p))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in l||(c=a.pluralCat(c-k));return n[c](e,f,!0)},function(a){f.text(a)})}}}],pe=["$parse","$animate",function(a,c){var d=L("ngRepeat");return{transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(e,f,g){return function(e,f,k){var l=k.ngRepeat,n=l.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),r,p,s,t,z,A,x,G={$id:Ca};if(!n)throw d("iexp",l);k=
n[1];z=n[2];(n=n[4])?(r=a(n),p=function(a,c,d){x&&(G[x]=a);G[A]=c;G.$index=d;return r(e,G)}):(s=function(a,c){return Ca(c)},t=function(a){return a});n=k.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!n)throw d("iidexp",k);A=n[3]||n[1];x=n[2];var v={};e.$watchCollection(z,function(a){var k,n,r=f[0],z,M={},G,N,w,I,D,u,J=[];if(pb(a))D=a,z=p||s;else{z=p||t;D=[];for(w in a)a.hasOwnProperty(w)&&"$"!=w.charAt(0)&&D.push(w);D.sort()}G=D.length;n=J.length=D.length;for(k=0;k<n;k++)if(w=a===D?k:
D[k],I=a[w],I=z(w,I,k),na(I,"`track by` id"),v.hasOwnProperty(I))u=v[I],delete v[I],M[I]=u,J[k]=u;else{if(M.hasOwnProperty(I))throw q(J,function(a){a&&a.startNode&&(v[a.id]=a)}),d("dupes",l,I);J[k]={id:I};M[I]=!1}for(w in v)v.hasOwnProperty(w)&&(u=v[w],k=vb(u),c.leave(k),q(k,function(a){a.$$NG_REMOVED=!0}),u.scope.$destroy());k=0;for(n=D.length;k<n;k++){w=a===D?k:D[k];I=a[w];u=J[k];J[k-1]&&(r=J[k-1].endNode);if(u.startNode){N=u.scope;z=r;do z=z.nextSibling;while(z&&z.$$NG_REMOVED);u.startNode!=z&&
c.move(vb(u),null,y(r));r=u.endNode}else N=e.$new();N[A]=I;x&&(N[x]=w);N.$index=k;N.$first=0===k;N.$last=k===G-1;N.$middle=!(N.$first||N.$last);N.$odd=!(N.$even=0===(k&1));u.startNode||g(N,function(a){a[a.length++]=O.createComment(" end ngRepeat: "+l+" ");c.enter(a,null,y(r));r=a;u.scope=N;u.startNode=r&&r.endNode?r.endNode:a[0];u.endNode=a[a.length-1];M[u.id]=u})}v=M})}}}}],qe=["$animate",function(a){return function(c,d,e){c.$watch(e.ngShow,function(c){a[Na(c)?"removeClass":"addClass"](d,"ng-hide")})}}],
re=["$animate",function(a){return function(c,d,e){c.$watch(e.ngHide,function(c){a[Na(c)?"addClass":"removeClass"](d,"ng-hide")})}}],se=sa(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&q(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),te=["$animate",function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,f){var g,h,m=[];c.$watch(e.ngSwitch||e.on,function(d){for(var l=0,n=m.length;l<n;l++)m[l].$destroy(),a.leave(h[l]);h=[];
m=[];if(g=f.cases["!"+d]||f.cases["?"])c.$eval(e.change),q(g,function(d){var e=c.$new();m.push(e);d.transclude(e,function(c){var e=d.element;h.push(c);a.enter(c,e.parent(),e)})})})}}}],ue=sa({transclude:"element",priority:800,require:"^ngSwitch",compile:function(a,c,d){return function(a,f,g,h){h.cases["!"+c.ngSwitchWhen]=h.cases["!"+c.ngSwitchWhen]||[];h.cases["!"+c.ngSwitchWhen].push({transclude:d,element:f})}}}),ve=sa({transclude:"element",priority:800,require:"^ngSwitch",compile:function(a,c,d){return function(a,
c,g,h){h.cases["?"]=h.cases["?"]||[];h.cases["?"].push({transclude:d,element:c})}}}),we=sa({controller:["$element","$transclude",function(a,c){if(!c)throw L("ngTransclude")("orphan",ea(a));this.$transclude=c}],link:function(a,c,d,e){e.$transclude(function(a){c.html("");c.append(a)})}}),xe=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],ye=L("ngOptions"),ze=aa({terminal:!0}),Ae=["$compile","$parse",function(a,
c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/,e={$setViewValue:t};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var m=this,k={},l=e,n;m.databound=d.ngModel;m.init=function(a,c,d){l=a;n=d};m.addOption=function(c){na(c,'"option value"');k[c]=!0;l.$viewValue==c&&(a.val(c),n.parent()&&n.remove())};m.removeOption=
function(a){this.hasOption(a)&&(delete k[a],l.$viewValue==a&&this.renderUnknownOption(a))};m.renderUnknownOption=function(c){c="? "+Ca(c)+" ?";n.val(c);a.prepend(n);a.val(c);n.prop("selected",!0)};m.hasOption=function(a){return k.hasOwnProperty(a)};c.$on("$destroy",function(){m.renderUnknownOption=t})}],link:function(e,g,h,m){function k(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(v.parent()&&v.remove(),c.val(a),""===a&&A.prop("selected",!0)):x(a)&&A?c.val(""):e.renderUnknownOption(a)};
c.on("change",function(){a.$apply(function(){v.parent()&&v.remove();d.$setViewValue(c.val())})})}function l(a,c,d){var e;d.$render=function(){var a=new Sa(d.$viewValue);q(c.find("option"),function(c){c.selected=z(a.get(c.value))})};a.$watch(function(){Aa(e,d.$viewValue)||(e=da(d.$viewValue),d.$render())});c.on("change",function(){a.$apply(function(){var a=[];q(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function n(e,f,g){function h(){var a={"":[]},c=[""],d,k,
s,u,x;u=g.$modelValue;x=r(e)||[];var A=n?Nb(x):x,H,K,B;K={};s=!1;var E,L;if(t)if(v&&J(u))for(s=new Sa([]),B=0;B<u.length;B++)K[m]=u[B],s.put(v(e,K),u[B]);else s=new Sa(u);for(B=0;H=A.length,B<H;B++){k=B;if(n){k=A[B];if("$"===k.charAt(0))continue;K[n]=k}K[m]=x[k];d=p(e,K)||"";(k=a[d])||(k=a[d]=[],c.push(d));t?d=z(s.remove(v?v(e,K):q(e,K))):(v?(d={},d[m]=u,d=v(e,d)===v(e,K)):d=u===q(e,K),s=s||d);E=l(e,K);E=z(E)?E:"";k.push({id:v?v(e,K):n?A[B]:B,label:E,selected:d})}t||(w||null===u?a[""].unshift({id:"",
label:"",selected:!s}):s||a[""].unshift({id:"?",label:"",selected:!0}));K=0;for(A=c.length;K<A;K++){d=c[K];k=a[d];y.length<=K?(u={element:G.clone().attr("label",d),label:k.label},x=[u],y.push(x),f.append(u.element)):(x=y[K],u=x[0],u.label!=d&&u.element.attr("label",u.label=d));E=null;B=0;for(H=k.length;B<H;B++)s=k[B],(d=x[B+1])?(E=d.element,d.label!==s.label&&E.text(d.label=s.label),d.id!==s.id&&E.val(d.id=s.id),E[0].selected!==s.selected&&E.prop("selected",d.selected=s.selected)):(""===s.id&&w?L=
w:(L=D.clone()).val(s.id).attr("selected",s.selected).text(s.label),x.push({element:L,label:s.label,id:s.id,selected:s.selected}),E?E.after(L):u.element.append(L),E=L);for(B++;x.length>B;)x.pop().element.remove()}for(;y.length>K;)y.pop()[0].element.remove()}var k;if(!(k=u.match(d)))throw ye("iexp",u,ea(f));var l=c(k[2]||k[1]),m=k[4]||k[6],n=k[5],p=c(k[3]||""),q=c(k[2]?k[1]:m),r=c(k[7]),v=k[8]?c(k[8]):null,y=[[{element:f,label:""}]];w&&(a(w)(e),w.removeClass("ng-scope"),w.remove());f.html("");f.on("change",
function(){e.$apply(function(){var a,c=r(e)||[],d={},h,k,l,p,u,x,w;if(t)for(k=[],p=0,x=y.length;p<x;p++)for(a=y[p],l=1,u=a.length;l<u;l++){if((h=a[l].element)[0].selected){h=h.val();n&&(d[n]=h);if(v)for(w=0;w<c.length&&(d[m]=c[w],v(e,d)!=h);w++);else d[m]=c[h];k.push(q(e,d))}}else if(h=f.val(),"?"==h)k=s;else if(""===h)k=null;else if(v)for(w=0;w<c.length;w++){if(d[m]=c[w],v(e,d)==h){k=q(e,d);break}}else d[m]=c[h],n&&(d[n]=h),k=q(e,d);g.$setViewValue(k)})});g.$render=h;e.$watch(h)}if(m[1]){var r=m[0],
p=m[1],t=h.multiple,u=h.ngOptions,w=!1,A,D=y(O.createElement("option")),G=y(O.createElement("optgroup")),v=D.clone();m=0;for(var B=g.children(),E=B.length;m<E;m++)if(""===B[m].value){A=w=B.eq(m);break}r.init(p,w,v);if(t&&(h.required||h.ngRequired)){var L=function(a){p.$setValidity("required",!h.required||a&&a.length);return a};p.$parsers.push(L);p.$formatters.unshift(L);h.$observe("required",function(){L(p.$viewValue)})}u?n(e,g,p):t?l(e,g,p):k(e,g,p,r)}}}}],Be=["$interpolate",function(a){var c={addOption:t,
removeOption:t};return{restrict:"E",priority:100,compile:function(d,e){if(x(e.value)){var f=a(d.text(),!0);f||e.$set("value",d.text())}return function(a,d,e){var k=d.parent(),l=k.data("$selectController")||k.parent().data("$selectController");l&&l.databound?d.prop("selected",!1):l=c;f?a.$watch(f,function(a,c){e.$set("value",a);a!==c&&l.removeOption(c);l.addOption(a)}):l.addOption(e.value);d.on("$destroy",function(){l.removeOption(e.value)})}}}}],Ce=aa({restrict:"E",terminal:!0});(Ba=W.jQuery)?(y=
Ba,u(Ba.fn,{scope:Da.scope,isolateScope:Da.isolateScope,controller:Da.controller,injector:Da.injector,inheritedData:Da.inheritedData}),wb("remove",!0,!0,!1),wb("empty",!1,!1,!1),wb("html",!1,!1,!0)):y=Q;bb.element=y;(function(a){u(a,{bootstrap:Wb,copy:da,extend:u,equals:Aa,element:y,forEach:q,injector:Xb,noop:t,bind:rb,toJson:ma,fromJson:Sb,identity:za,isUndefined:x,isDefined:z,isString:D,isFunction:B,isObject:T,isNumber:qb,isElement:Kc,isArray:J,version:Md,isDate:Ja,lowercase:w,uppercase:Ga,callbacks:{counter:0},
$$minErr:L,$$csp:Rb});Ua=Qc(W);try{Ua("ngLocale")}catch(c){Ua("ngLocale",[]).provider("$locale",pd)}Ua("ng",["ngLocale"],["$provide",function(a){a.provider("$compile",ec).directive({a:Rd,input:Gc,textarea:Gc,form:Sd,script:xe,select:Ae,style:Ce,option:Be,ngBind:ce,ngBindHtml:ee,ngBindTemplate:de,ngClass:fe,ngClassEven:he,ngClassOdd:ge,ngCloak:ie,ngController:je,ngForm:Td,ngHide:re,ngIf:ke,ngInclude:le,ngInit:me,ngNonBindable:ne,ngPluralize:oe,ngRepeat:pe,ngShow:qe,ngStyle:se,ngSwitch:te,ngSwitchWhen:ue,
ngSwitchDefault:ve,ngOptions:ze,ngTransclude:we,ngModel:Yd,ngList:$d,ngChange:Zd,required:Hc,ngRequired:Hc,ngValue:be}).directive(Mb).directive(Ic);a.provider({$anchorScroll:Zc,$animate:Od,$browser:bd,$cacheFactory:cd,$controller:gd,$document:hd,$exceptionHandler:id,$filter:vc,$interpolate:nd,$interval:od,$http:jd,$httpBackend:kd,$location:rd,$log:sd,$parse:td,$rootScope:wd,$q:ud,$sce:zd,$sceDelegate:yd,$sniffer:Ad,$templateCache:dd,$timeout:Bd,$window:Cd})}])})(bb);y(O).ready(function(){Oc(O,Wb)})})(window,
document);!angular.$$csp()&&angular.element(document).find("head").prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}.ng-animate-start{clip:rect(0,auto,auto,0);-ms-zoom:1.0001;}.ng-animate-active{clip:rect(-1px,auto,auto,0);-ms-zoom:1;}</style>');
(function(t,Y,s){'use strict';function K(b){return function(){var a=arguments[0],c;c="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.3.0-rc.1/"+(b?b+"/":"")+a;for(a=1;a<arguments.length;a++){c=c+(1==a?"?":"&")+"p"+(a-1)+"=";var d=encodeURIComponent,e;e=arguments[a];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;c+=d(e)}return Error(c)}}function Na(b){if(null==b||Oa(b))return!1;var a=b.length;return 1===b.nodeType&&
a?!0:C(b)||L(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function r(b,a,c){var d,e;if(b)if(D(b))for(d in b)"prototype"==d||"length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d)||a.call(c,b[d],d,b);else if(L(b)||Na(b)){var f="object"!==typeof b;d=0;for(e=b.length;d<e;d++)(f||d in b)&&a.call(c,b[d],d,b)}else if(b.forEach&&b.forEach!==r)b.forEach(a,c,b);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d,b);return b}function Zb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}
function od(b,a,c){for(var d=Zb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function $b(b){return function(a,c){b(c,a)}}function pd(){return++bb}function ac(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function E(b){for(var a=b.$$hashKey,c=1,d=arguments.length;c<d;c++){var e=arguments[c];if(e)for(var f=Object.keys(e),g=0,h=f.length;g<h;g++){var m=f[g];b[m]=e[m]}}ac(b,a);return b}function U(b){return parseInt(b,10)}function bc(b,a){return E(new (E(function(){},{prototype:b})),a)}function w(){}
function Pa(b){return b}function da(b){return function(){return b}}function F(b){return"undefined"===typeof b}function B(b){return"undefined"!==typeof b}function S(b){return null!==b&&"object"===typeof b}function C(b){return"string"===typeof b}function ea(b){return"number"===typeof b}function fa(b){return"[object Date]"===Ga.call(b)}function D(b){return"function"===typeof b}function cb(b){return"[object RegExp]"===Ga.call(b)}function Oa(b){return b&&b.window===b}function Qa(b){return b&&b.$evalAsync&&
b.$watch}function qd(b){return!(!b||!(b.nodeName||b.prop&&b.attr&&b.find))}function rd(b){var a={};b=b.split(",");var c;for(c=0;c<b.length;c++)a[b[c]]=!0;return a}function pa(b){return P(b.nodeName||b[0].nodeName)}function sd(b,a,c){var d=[];r(b,function(b,f,g){d.push(a.call(c,b,f,g))});return d}function Ra(b,a){var c=b.indexOf(a);0<=c&&b.splice(c,1);return a}function Ha(b,a,c,d){if(Oa(b)||Qa(b))throw Sa("cpws");if(a){if(b===a)throw Sa("cpi");c=c||[];d=d||[];if(S(b)){var e=c.indexOf(b);if(-1!==e)return d[e];
c.push(b);d.push(a)}if(L(b))for(var f=a.length=0;f<b.length;f++)e=Ha(b[f],null,c,d),S(b[f])&&(c.push(b[f]),d.push(e)),a.push(e);else{var g=a.$$hashKey;L(a)?a.length=0:r(a,function(c,b){delete a[b]});for(f in b)b.hasOwnProperty(f)&&(e=Ha(b[f],null,c,d),S(b[f])&&(c.push(b[f]),d.push(e)),a[f]=e);ac(a,g)}}else if(a=b)L(b)?a=Ha(b,[],c,d):fa(b)?a=new Date(b.getTime()):cb(b)?(a=new RegExp(b.source,b.toString().match(/[^\/]*$/)[0]),a.lastIndex=b.lastIndex):S(b)&&(e=Object.create(Object.getPrototypeOf(b)),
a=Ha(b,e,c,d));return a}function qa(b,a){if(L(b)){a=a||[];for(var c=0,d=b.length;c<d;c++)a[c]=b[c]}else if(S(b))for(c in a=a||{},b)if("$"!==c.charAt(0)||"$"!==c.charAt(1))a[c]=b[c];return a||b}function ra(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&"object"==c)if(L(b)){if(!L(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ra(b[d],a[d]))return!1;return!0}}else{if(fa(b))return fa(a)?ra(b.getTime(),a.getTime()):!1;if(cb(b)&&
cb(a))return b.toString()==a.toString();if(Qa(b)||Qa(a)||Oa(b)||Oa(a)||L(a))return!1;c={};for(d in b)if("$"!==d.charAt(0)&&!D(b[d])){if(!ra(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c.hasOwnProperty(d)&&"$"!==d.charAt(0)&&a[d]!==s&&!D(a[d]))return!1;return!0}return!1}function db(b,a,c){return b.concat(Ta.call(a,c))}function cc(b,a){var c=2<arguments.length?Ta.call(arguments,2):[];return!D(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,c.concat(Ta.call(arguments,0))):
a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function td(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)&&"$"===b.charAt(1)?c=s:Oa(a)?c="$WINDOW":a&&Y===a?c="$DOCUMENT":Qa(a)&&(c="$SCOPE");return c}function sa(b,a){return"undefined"===typeof b?s:JSON.stringify(b,td,a?" ":null)}function dc(b){return C(b)?JSON.parse(b):b}function ta(b){b=G(b).clone();try{b.empty()}catch(a){}var c=G("<div>").append(b).html();try{return 3===b[0].nodeType?P(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,
function(a,c){return"<"+P(c)})}catch(d){return P(c)}}function ec(b){try{return decodeURIComponent(b)}catch(a){}}function fc(b){var a={},c,d;r((b||"").split("&"),function(b){b&&(c=b.replace(/\+/g,"%20").split("="),d=ec(c[0]),B(d)&&(b=B(c[1])?ec(c[1]):!0,Ab.call(a,d)?L(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Bb(b){var a=[];r(b,function(b,d){L(b)?r(b,function(b){a.push(Da(d,!0)+(!0===b?"":"="+Da(b,!0)))}):a.push(Da(d,!0)+(!0===b?"":"="+Da(b,!0)))});return a.length?a.join("&"):""}
function eb(b){return Da(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Da(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function ud(b,a){var c,d,e=fb.length;b=G(b);for(d=0;d<e;++d)if(c=fb[d]+a,C(c=b.attr(c)))return c;return null}function vd(b,a){var c,d,e={};r(fb,function(a){a+="app";!c&&b.hasAttribute&&b.hasAttribute(a)&&(c=b,d=b.getAttribute(a))});
r(fb,function(a){a+="app";var e;!c&&(e=b.querySelector("["+a.replace(":","\\:")+"]"))&&(c=e,d=e.getAttribute(a))});c&&(e.strictDi=null!==ud(c,"strict-di"),a(c,d?[d]:[],e))}function gc(b,a,c){S(c)||(c={});c=E({strictDi:!1},c);var d=function(){b=G(b);if(b.injector()){var d=b[0]===Y?"document":ta(b);throw Sa("btstrpd",d.replace(/</,"&lt;").replace(/>/,"&gt;"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);
a.unshift("ng");d=Cb(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;t&&e.test(t.name)&&(c.debugInfoEnabled=!0,t.name=t.name.replace(e,""));if(t&&!f.test(t.name))return d();t.name=t.name.replace(f,"");Ea.resumeBootstrap=function(b){r(b,function(b){a.push(b)});d()}}function wd(){t.name="NG_ENABLE_DEBUG_INFO!"+t.name;t.location.reload()}function xd(b){return Ea.element(b).injector().get("$$testability")}
function Db(b,a){a=a||"_";return b.replace(yd,function(b,d){return(d?a:"")+b.toLowerCase()})}function zd(){var b;hc||((la=t.jQuery)&&la.fn.on?(G=la,E(la.fn,{scope:Ia.scope,isolateScope:Ia.isolateScope,controller:Ia.controller,injector:Ia.injector,inheritedData:Ia.inheritedData}),b=la.cleanData,la.cleanData=function(a){var c;if(Eb)Eb=!1;else for(var d=0,e;null!=(e=a[d]);d++)(c=la._data(e,"events"))&&c.$destroy&&la(e).triggerHandler("$destroy");b(a)}):G=V,Ea.element=G,hc=!0)}function Fb(b,a,c){if(!b)throw Sa("areq",
a||"?",c||"required");return b}function gb(b,a,c){c&&L(b)&&(b=b[b.length-1]);Fb(D(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ja(b,a){if("hasOwnProperty"===b)throw Sa("badname",a);}function ic(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g<f;g++)d=a[g],b&&(b=(e=b)[d]);return!c&&D(b)?cc(e,b):b}function hb(b){var a=b[0];b=b[b.length-1];var c=[a];do{a=a.nextSibling;if(!a)break;c.push(a)}while(a!==b);return G(c)}function Ad(b){function a(a,
b,c){return a[b]||(a[b]=c())}var c=K("$injector"),d=K("ng");b=a(b,"angular",Object);b.$$minErr=b.$$minErr||K;return a(b,"module",function(){var b={};return function(f,g,h){if("hasOwnProperty"===f)throw d("badname","module");g&&b.hasOwnProperty(f)&&(b[f]=null);return a(b,f,function(){function a(c,d,e,f){f||(f=b);return function(){f[e||"push"]([c,d,arguments]);return q}}if(!g)throw c("nomod",f);var b=[],d=[],e=[],l=a("$injector","invoke","push",d),q={_invokeQueue:b,_configBlocks:d,_runBlocks:e,requires:g,
name:f,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animateProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:l,run:function(a){e.push(a);return this}};h&&l(h);return q})}})}function Bd(b){E(b,{bootstrap:gc,copy:Ha,extend:E,equals:ra,element:G,forEach:r,
injector:Cb,noop:w,bind:cc,toJson:sa,fromJson:dc,identity:Pa,isUndefined:F,isDefined:B,isString:C,isFunction:D,isObject:S,isNumber:ea,isElement:qd,isArray:L,version:Cd,isDate:fa,lowercase:P,uppercase:ib,callbacks:{counter:0},getTestability:xd,$$minErr:K,$$csp:Ua,reloadWithDebugInfo:wd,$$hasClass:jb});Va=Ad(t);try{Va("ngLocale")}catch(a){Va("ngLocale",[]).provider("$locale",Dd)}Va("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:Ed});a.provider("$compile",jc).directive({a:Fd,input:kc,
textarea:kc,form:Gd,script:Hd,select:Id,style:Jd,option:Kd,ngBind:Ld,ngBindHtml:Md,ngBindTemplate:Nd,ngClass:Od,ngClassEven:Pd,ngClassOdd:Qd,ngCloak:Rd,ngController:Sd,ngForm:Td,ngHide:Ud,ngIf:Vd,ngInclude:Wd,ngInit:Xd,ngNonBindable:Yd,ngPluralize:Zd,ngRepeat:$d,ngShow:ae,ngStyle:be,ngSwitch:ce,ngSwitchWhen:de,ngSwitchDefault:ee,ngOptions:fe,ngTransclude:ge,ngModel:he,ngList:ie,ngChange:je,pattern:lc,ngPattern:lc,required:mc,ngRequired:mc,minlength:nc,ngMinlength:nc,maxlength:oc,ngMaxlength:oc,ngValue:ke,
ngModelOptions:le}).directive({ngInclude:me}).directive(kb).directive(pc);a.provider({$anchorScroll:ne,$animate:oe,$browser:pe,$cacheFactory:qe,$controller:re,$document:se,$exceptionHandler:te,$filter:qc,$interpolate:ue,$interval:ve,$http:we,$httpBackend:xe,$location:ye,$log:ze,$parse:Ae,$rootScope:Be,$q:Ce,$$q:De,$sce:Ee,$sceDelegate:Fe,$sniffer:Ge,$templateCache:He,$templateRequest:Ie,$$testability:Je,$timeout:Ke,$window:Le,$$rAF:Me,$$asyncCallback:Ne})}])}function Wa(b){return b.replace(Oe,function(a,
b,d,e){return e?d.toUpperCase():d}).replace(Pe,"Moz$1")}function rc(b){b=b.nodeType;return 1===b||!b||9===b}function sc(b,a){var c,d,e=a.createDocumentFragment(),f=[];if(Gb.test(b)){c=c||e.appendChild(a.createElement("div"));d=(Qe.exec(b)||["",""])[1].toLowerCase();d=ia[d]||ia._default;c.innerHTML=d[1]+b.replace(Re,"<$1></$2>")+d[2];for(d=d[0];d--;)c=c.lastChild;f=db(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";r(f,function(a){e.appendChild(a)});
return e}function V(b){if(b instanceof V)return b;var a;C(b)&&(b=ba(b),a=!0);if(!(this instanceof V)){if(a&&"<"!=b.charAt(0))throw Hb("nosel");return new V(b)}if(a){a=Y;var c;b=(c=Se.exec(b))?[a.createElement(c[1])]:(c=sc(b,a))?c.childNodes:[]}tc(this,b)}function Ib(b){return b.cloneNode(!0)}function lb(b,a){a||mb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d<e;d++)mb(c[d])}function uc(b,a,c,d){if(B(d))throw Hb("offargs");var e=(d=nb(b))&&d.events;if(d&&d.handle)if(a)r(a.split(" "),
function(a){F(c)?(b.removeEventListener(a,e[a],!1),delete e[a]):Ra(e[a]||[],c)});else for(a in e)"$destroy"!==a&&b.removeEventListener(a,e[a],!1),delete e[a]}function mb(b,a){var c=b.ng339,d=c&&ob[c];d&&(a?delete d.data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),uc(b)),delete ob[c],b.ng339=s))}function nb(b,a){var c=b.ng339,c=c&&ob[c];a&&!c&&(b.ng339=c=++Te,c=ob[c]={events:{},data:{},handle:s});return c}function Jb(b,a,c){if(rc(b)){var d=B(c),e=!d&&a&&!S(a),f=!a;b=(b=nb(b,!e))&&b.data;
if(d)b[a]=c;else{if(f)return b;if(e)return b&&b[a];E(b,a)}}}function jb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function Kb(b,a){a&&b.setAttribute&&r(a.split(" "),function(a){b.setAttribute("class",ba((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+ba(a)+" "," ")))})}function Lb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");r(a.split(" "),function(a){a=
ba(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",ba(c))}}function tc(b,a){if(a)if(a.nodeType)b[b.length++]=a;else{var c=a.length;if("number"===typeof c&&a.window!==a){if(c)for(var d=0;d<c;d++)b[b.length++]=a[d]}else b[b.length++]=a}}function vc(b,a){return pb(b,"$"+(a||"ngController")+"Controller")}function pb(b,a,c){9==b.nodeType&&(b=b.documentElement);for(a=L(a)?a:[a];b;){for(var d=0,e=a.length;d<e;d++)if((c=G.data(b,a[d]))!==s)return c;b=b.parentNode||11===b.nodeType&&b.host}}
function wc(b){for(lb(b,!0);b.firstChild;)b.removeChild(b.firstChild)}function xc(b,a){a||lb(b);var c=b.parentNode;c&&c.removeChild(b)}function yc(b,a){var c=qb[a.toLowerCase()];return c&&zc[pa(b)]&&c}function Ue(b,a){var c=b.nodeName;return("INPUT"===c||"TEXTAREA"===c)&&Ac[a]}function Ve(b,a){var c=function(c,e){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=a[e||c.type],g=f?f.length:0;if(g){1<g&&(f=qa(f));for(var h=0;h<g;h++)f[h].call(b,c)}};c.elem=b;return c}function Ka(b,a){var c=
b&&b.$$hashKey;if(c)return"function"===typeof c&&(c=b.$$hashKey()),c;c=typeof b;return c="function"==c||"object"==c&&null!==b?b.$$hashKey=c+":"+(a||pd)():c+":"+b}function Xa(b,a){if(a){var c=0;this.nextUid=function(){return++c}}r(b,this.put,this)}function We(b){return(b=b.toString().replace(Bc,"").match(Cc))?"function("+(b[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function Mb(b,a,c){var d;if("function"===typeof b){if(!(d=b.$inject)){d=[];if(b.length){if(a)throw C(c)&&c||(c=b.name||We(b)),La("strictdi",
c);a=b.toString().replace(Bc,"");a=a.match(Cc);r(a[1].split(Xe),function(a){a.replace(Ye,function(a,b,c){d.push(c)})})}b.$inject=d}}else L(b)?(a=b.length-1,gb(b[a],"fn"),d=b.slice(0,a)):gb(b,"fn",!0);return d}function Cb(b,a){function c(a){return function(b,c){if(S(b))r(b,$b(a));else return a(b,c)}}function d(a,b){Ja(a,"service");if(D(b)||L(b))b=p.instantiate(b);if(!b.$get)throw La("pget",a);return n[a+"Provider"]=b}function e(a,b){return d(a,{$get:b})}function f(a){var b=[],c;r(a,function(a){function d(a){var b,
c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=p.get(e[0]);f[e[1]].apply(f,e[2])}}if(!k.get(a)){k.put(a,!0);try{C(a)?(c=Va(a),b=b.concat(f(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)):D(a)?b.push(p.invoke(a)):L(a)?b.push(p.invoke(a)):gb(a,"module")}catch(e){throw L(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),La("modulerr",a,e.stack||e.message||e);}}});return b}function g(b,c){function d(a){if(b.hasOwnProperty(a)){if(b[a]===
h)throw La("cdep",a+" <- "+m.join(" <- "));return b[a]}try{return m.unshift(a),b[a]=h,b[a]=c(a)}catch(e){throw b[a]===h&&delete b[a],e;}finally{m.shift()}}function e(b,c,f,h){"string"===typeof f&&(h=f,f=null);var g=[];h=Mb(b,a,h);var k,m,l;m=0;for(k=h.length;m<k;m++){l=h[m];if("string"!==typeof l)throw La("itkn",l);g.push(f&&f.hasOwnProperty(l)?f[l]:d(l))}L(b)&&(b=b[k]);return b.apply(c,g)}return{invoke:e,instantiate:function(a,b,c){var d=function(){};d.prototype=(L(a)?a[a.length-1]:a).prototype;
d=new d;a=e(a,d,b,c);return S(a)||D(a)?a:d},get:d,annotate:Mb,has:function(a){return n.hasOwnProperty(a+"Provider")||b.hasOwnProperty(a)}}}a=!0===a;var h={},m=[],k=new Xa([],!0),n={$provide:{provider:c(d),factory:c(e),service:c(function(a,b){return e(a,["$injector",function(a){return a.instantiate(b)}])}),value:c(function(a,b){return e(a,da(b))}),constant:c(function(a,b){Ja(a,"constant");n[a]=b;l[a]=b}),decorator:function(a,b){var c=p.get(a+"Provider"),d=c.$get;c.$get=function(){var a=q.invoke(d,
c);return q.invoke(b,null,{$delegate:a})}}}},p=n.$injector=g(n,function(){throw La("unpr",m.join(" <- "));}),l={},q=l.$injector=g(l,function(a){var b=p.get(a+"Provider");return q.invoke(b.$get,b,s,a)});r(f(b),function(a){q.invoke(a||w)});return q}function ne(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;r(a,function(a){b||"a"!==pa(a)||(b=a)});return b}function f(){var b=c.hash(),d;b?(d=g.getElementById(b))?
d.scrollIntoView():(d=e(g.getElementsByName(b)))?d.scrollIntoView():"top"===b&&a.scrollTo(0,0):a.scrollTo(0,0)}var g=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(f)});return f}]}function Ne(){this.$get=["$$rAF","$timeout",function(b,a){return b.supported?function(a){return b(a)}:function(b){return a(b,0,!1)}}]}function Ze(b,a,c,d){function e(a){try{a.apply(null,Ta.call(arguments,1))}finally{if(A--,0===A)for(;u.length;)try{u.pop()()}catch(b){c.error(b)}}}function f(a,
b){(function R(){r(x,function(a){a()});z=b(R,a)})()}function g(){y=null;T!=h.url()&&(T=h.url(),r(Q,function(a){a(h.url())}))}var h=this,m=a[0],k=b.location,n=b.history,p=b.setTimeout,l=b.clearTimeout,q={};h.isMock=!1;var A=0,u=[];h.$$completeOutstandingRequest=e;h.$$incOutstandingRequestCount=function(){A++};h.notifyWhenNoOutstandingRequests=function(a){r(x,function(a){a()});0===A?a():u.push(a)};var x=[],z;h.addPollFn=function(a){F(z)&&f(100,p);x.push(a);return a};var T=k.href,v=a.find("base"),y=
null;h.url=function(a,c){k!==b.location&&(k=b.location);n!==b.history&&(n=b.history);if(a){if(T!=a)return T=a,d.history?c?n.replaceState(null,"",a):(n.pushState(null,"",a),v.attr("href",v.attr("href"))):(y=a,c?k.replace(a):k.href=a),h}else return y||k.href.replace(/%27/g,"'")};var Q=[],ca=!1;h.onUrlChange=function(a){if(!ca){if(d.history)G(b).on("popstate",g);if(d.hashchange)G(b).on("hashchange",g);else h.addPollFn(g);ca=!0}Q.push(a);return a};h.$$checkUrlChange=g;h.baseHref=function(){var a=v.attr("href");
return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};var J={},N="",M=h.baseHref();h.cookies=function(a,b){var d,e,f,h;if(a)b===s?m.cookie=encodeURIComponent(a)+"=;path="+M+";expires=Thu, 01 Jan 1970 00:00:00 GMT":C(b)&&(d=(m.cookie=encodeURIComponent(a)+"="+encodeURIComponent(b)+";path="+M).length+1,4096<d&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"));else{if(m.cookie!==N)for(N=m.cookie,d=N.split("; "),J={},f=0;f<d.length;f++)e=d[f],h=e.indexOf("="),
0<h&&(a=decodeURIComponent(e.substring(0,h)),J[a]===s&&(J[a]=decodeURIComponent(e.substring(h+1))));return J}};h.defer=function(a,b){var c;A++;c=p(function(){delete q[c];e(a)},b||0);q[c]=!0;return c};h.defer.cancel=function(a){return q[a]?(delete q[a],l(a),e(w),!0):!1}}function pe(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new Ze(b,d,a,c)}]}function qe(){this.$get=function(){function b(b,d){function e(a){a!=p&&(l?l==a&&(l=a.n):l=a,f(a.n,a.p),f(a,p),p=a,p.n=null)}
function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw K("$cacheFactory")("iid",b);var g=0,h=E({},d,{id:b}),m={},k=d&&d.capacity||Number.MAX_VALUE,n={},p=null,l=null;return a[b]={put:function(a,b){if(k<Number.MAX_VALUE){var c=n[a]||(n[a]={key:a});e(c)}if(!F(b))return a in m||g++,m[a]=b,g>k&&this.remove(l.key),b},get:function(a){if(k<Number.MAX_VALUE){var b=n[a];if(!b)return;e(b)}return m[a]},remove:function(a){if(k<Number.MAX_VALUE){var b=n[a];if(!b)return;b==p&&(p=b.p);b==l&&(l=b.n);f(b.n,
b.p);delete n[a]}delete m[a];g--},removeAll:function(){m={};g=0;n={};p=l=null},destroy:function(){n=h=m=null;delete a[b]},info:function(){return E({},h,{size:g})}}}var a={};b.info=function(){var b={};r(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function He(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function jc(b,a){var c={},d=/^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,e=/(([\d\w_\-]+)(?:\:([^;]+))?;?)/,f=rd("ngSrc,ngSrcset,src,srcset"),g=
/^(on[a-z]+|formaction)$/;this.directive=function k(a,d){Ja(a,"directive");C(a)?(Fb(d,"directiveFactory"),c.hasOwnProperty(a)||(c[a]=[],b.factory(a+"Directive",["$injector","$exceptionHandler",function(b,d){var e=[];r(c[a],function(c,f){try{var h=b.invoke(c);D(h)?h={compile:da(h)}:!h.compile&&h.link&&(h.compile=da(h.link));h.priority=h.priority||0;h.index=f;h.name=h.name||a;h.require=h.require||h.controller&&h.name;h.restrict=h.restrict||"EA";e.push(h)}catch(g){d(g)}});return e}])),c[a].push(d)):
r(a,$b(k));return this};this.aHrefSanitizationWhitelist=function(b){return B(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a.imgSrcSanitizationWhitelist(b),this):a.imgSrcSanitizationWhitelist()};var h=!0;this.debugInfoEnabled=function(a){return B(a)?(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri",
function(a,b,p,l,q,A,u,x,z,T,v){function y(a,b){try{a.addClass(b)}catch(c){}}function Q(a,b,c,d,e){a instanceof G||(a=G(a));r(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=G(b).wrap("<span></span>").parent()[0])});var f=ca(a,b,a,c,d,e);Q.$$addScopeClass(a);var h=null,g=a,k;return function(b,c,d,e,l){Fb(b,"scope");h||(h=(l=l&&l[0])?"foreignobject"!==pa(l)&&l.toString().match(/SVG/)?"svg":"html":"html");"html"!==h&&a[0]!==k&&(g=G(Nb(h,G("<div>").append(a).html())));k=a[0];l=c?Ia.clone.call(g):
g;if(d)for(var n in d)l.data("$"+n+"Controller",d[n].instance);Q.$$addScopeInfo(l,b);c&&c(l,b);f&&f(b,l,l,e);return l}}function ca(a,b,c,d,e,f){function h(a,c,d,e){var f,k,l,n,u,q,ua;if(p)for(ua=Array(c.length),n=0;n<g.length;n+=3)f=g[n],ua[f]=c[f];else ua=c;n=0;for(u=g.length;n<u;)k=ua[g[n++]],c=g[n++],f=g[n++],c?(c.scope?(l=a.$new(),Q.$$addScopeInfo(G(k),l)):l=a,q=c.transcludeOnThisElement?J(a,c.transclude,e,c.elementTranscludeOnThisElement):!c.templateOnThisElement&&e?e:!e&&b?J(a,b):null,c(f,l,
k,d,q)):f&&f(a,k.childNodes,s,e)}for(var g=[],k,l,n,u,p,q=0;q<a.length;q++){k=new Ob;l=N(a[q],[],k,0===q?d:s,e);(f=l.length?H(l,a[q],k,b,c,null,[],[],f):null)&&f.scope&&Q.$$addScopeClass(k.$$element);k=f&&f.terminal||!(n=a[q].childNodes)||!n.length?null:ca(n,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)g.push(q,f,k),u=!0,p=p||f;f=null}return u?h:null}function J(a,b,c,d){return function(e,f,h,g){var k=!1;e||(e=a.$new(),k=e.$$transcluded=!0);f=b(e,f,h,c,g);if(k&&
!d)f.on("$destroy",function(){e.$destroy()});return f}}function N(b,f,h,g,l){var n=h.$attr,u;switch(b.nodeType){case 1:R(f,va(pa(b)),"E",g,l);for(var p,q,T,A=b.attributes,z=0,v=A&&A.length;z<v;z++){var x=!1,J=!1;p=A[z];if(!X||8<=X||p.specified){u=p.name;p=ba(p.value);q=va(u);if(T=U.test(q))u=Db(q.substr(6),"-");var W=q.replace(/(Start|End)$/,""),r;a:{var H=W;if(c.hasOwnProperty(H)){r=void 0;for(var H=a.get(H+"Directive"),O=0,Q=H.length;O<Q;O++)if(r=H[O],r.multiElement){r=!0;break a}}r=!1}r&&q===W+
"Start"&&(x=u,J=u.substr(0,u.length-5)+"end",u=u.substr(0,u.length-6));q=va(u.toLowerCase());n[q]=u;if(T||!h.hasOwnProperty(q))h[q]=p,yc(b,q)&&(h[q]=!0);ya(b,f,p,q,T);R(f,q,"A",g,l,x,J)}}b=b.className;if(C(b)&&""!==b)for(;u=e.exec(b);)q=va(u[2]),R(f,q,"C",g,l)&&(h[q]=ba(u[3])),b=b.substr(u.index+u[0].length);break;case 3:t(f,b.nodeValue);break;case 8:try{if(u=d.exec(b.nodeValue))q=va(u[1]),R(f,q,"M",g,l)&&(h[q]=ba(u[2]))}catch(M){}}f.sort(F);return f}function M(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&
a.hasAttribute(b)){do{if(!a)throw ja("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return G(d)}function O(a,b,c){return function(d,e,f,h,g){e=M(e[0],b,c);return a(d,e,f,h,g)}}function H(a,c,d,e,f,h,g,k,l){function u(a,b,c,d){if(a){c&&(a=O(a,c,d));a.require=I.require;a.directiveName=ha;if(y===I||I.$$isolateScope)a=ga(a,{isolateScope:!0});g.push(a)}if(b){c&&(b=O(b,c,d));b.require=I.require;b.directiveName=ha;if(y===
I||I.$$isolateScope)b=ga(b,{isolateScope:!0});k.push(b)}}function T(a,b,c,d){var e,f="data",h=!1;if(C(b)){for(;"^"==(e=b.charAt(0))||"?"==e;)b=b.substr(1),"^"==e&&(f="inheritedData"),h=h||"?"==e;e=null;d&&"data"===f&&(e=d[b])&&(e=e.instance);e=e||c[f]("$"+b+"Controller");if(!e&&!h)throw ja("ctreq",b,a);}else L(b)&&(e=[],r(b,function(b){e.push(T(a,b,c,d))}));return e}function z(a,e,f,h,l){function u(a,b,c){var d;Qa(a)||(c=b,b=a,a=s);E&&(d=W);c||(c=E?M.parent():M);return l(a,b,d,c)}var p,v,ua,x,W,O,
M,R;c===f?(R=d,M=d.$$element):(M=G(f),R=new Ob(M,d));y&&(x=e.$new(!0));O=l&&u;J&&(H={},W={},r(J,function(a){var b={$scope:a===y||a.$$isolateScope?x:e,$element:M,$attrs:R,$transclude:O};ua=a.controller;"@"==ua&&(ua=R[a.name]);b=A(ua,b,!0,a.controllerAs);W[a.name]=b;E||M.data("$"+a.name+"Controller",b.instance);H[a.name]=b}));if(y){var N=/^\s*([@=&])(\??)\s*(\w*)\s*$/;Q.$$addScopeInfo(M,x,!0,!(ca&&(ca===y||ca===y.$$originalDirective)));Q.$$addScopeClass(M,!0);h=H&&H[y.name];var xa=x;h&&h.identifier&&
!0===y.bindToController&&(xa=h.instance);r(y.scope,function(a,c){var d=a.match(N)||[],f=d[3]||c,h="?"==d[2],d=d[1],g,k,l,u;x.$$isolateBindings[c]=d+f;switch(d){case "@":R.$observe(f,function(a){x[c]=a});R.$$observers[f].$$scope=e;R[f]&&(xa[c]=b(R[f])(e));break;case "=":if(h&&!R[f])break;k=q(R[f]);u=k.literal?ra:function(a,b){return a===b||a!==a&&b!==b};l=k.assign||function(){g=xa[c]=k(e);throw ja("nonassign",R[f],y.name);};g=xa[c]=k(e);h=e.$watch(q(R[f],function(a){u(a,xa[c])||(u(a,g)?l(e,a=xa[c]):
xa[c]=a);return g=a}),null,k.literal);x.$on("$destroy",h);break;case "&":k=q(R[f]);xa[c]=function(a){return k(e,a)};break;default:throw ja("iscp",y.name,c,a);}})}H&&(r(H,function(a){a()}),H=null);h=0;for(p=g.length;h<p;h++)v=g[h],Dc(v,v.isolateScope?x:e,M,R,v.require&&T(v.directiveName,v.require,M,W),O);h=e;y&&(y.template||null===y.templateUrl)&&(h=x);a&&a(h,f.childNodes,s,l);for(h=k.length-1;0<=h;h--)v=k[h],Dc(v,v.isolateScope?x:e,M,R,v.require&&T(v.directiveName,v.require,M,W),O)}l=l||{};for(var v=
-Number.MAX_VALUE,x,J=l.controllerDirectives,H,y=l.newIsolateScopeDirective,ca=l.templateDirective,R=l.nonTlbTranscludeDirective,w=!1,F=!1,E=l.hasElementTranscludeDirective,aa=d.$$element=G(c),I,ha,t,P=e,za,ma=0,ya=a.length;ma<ya;ma++){I=a[ma];var U=I.$$start,X=I.$$end;U&&(aa=M(c,U,X));t=s;if(v>I.priority)break;if(t=I.scope)I.templateUrl||(S(t)?(K("new/isolated scope",y||x,I,aa),y=I):K("new/isolated scope",y,I,aa)),x=x||I;ha=I.name;!I.templateUrl&&I.controller&&(t=I.controller,J=J||{},K("'"+ha+"' controller",
J[ha],I,aa),J[ha]=I);if(t=I.transclude)w=!0,I.$$tlb||(K("transclusion",R,I,aa),R=I),"element"==t?(E=!0,v=I.priority,t=aa,aa=d.$$element=G(Y.createComment(" "+ha+": "+d[ha]+" ")),c=aa[0],rb(f,Ta.call(t,0),c),P=Q(t,e,v,h&&h.name,{nonTlbTranscludeDirective:R})):(t=G(Ib(c)).contents(),aa.empty(),P=Q(t,e));if(I.template)if(F=!0,K("template",ca,I,aa),ca=I,t=D(I.template)?I.template(aa,d):I.template,t=V(t),I.replace){h=I;t=Gb.test(t)?G(Nb(I.templateNamespace,ba(t))):[];c=t[0];if(1!=t.length||1!==c.nodeType)throw ja("tplrt",
ha,"");rb(f,aa,c);ya={$attr:{}};t=N(c,[],ya);var Z=a.splice(ma+1,a.length-(ma+1));y&&W(t);a=a.concat(t).concat(Z);B(d,ya);ya=a.length}else aa.html(t);if(I.templateUrl)F=!0,K("template",ca,I,aa),ca=I,I.replace&&(h=I),z=$e(a.splice(ma,a.length-ma),aa,d,f,w&&P,g,k,{controllerDirectives:J,newIsolateScopeDirective:y,templateDirective:ca,nonTlbTranscludeDirective:R}),ya=a.length;else if(I.compile)try{za=I.compile(aa,d,P),D(za)?u(null,za,U,X):za&&u(za.pre,za.post,U,X)}catch($){p($,ta(aa))}I.terminal&&(z.terminal=
!0,v=Math.max(v,I.priority))}z.scope=x&&!0===x.scope;z.transcludeOnThisElement=w;z.elementTranscludeOnThisElement=E;z.templateOnThisElement=F;z.transclude=P;l.hasElementTranscludeDirective=E;return z}function W(a){for(var b=0,c=a.length;b<c;b++)a[b]=bc(a[b],{$$isolateScope:!0})}function R(b,d,e,f,h,g,l){if(d===h)return null;h=null;if(c.hasOwnProperty(d)){var n;d=a.get(d+"Directive");for(var u=0,q=d.length;u<q;u++)try{n=d[u],(f===s||f>n.priority)&&-1!=n.restrict.indexOf(e)&&(g&&(n=bc(n,{$$start:g,
$$end:l})),b.push(n),h=n)}catch(T){p(T)}}return h}function B(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;r(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});r(b,function(b,f){"class"==f?(y(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||(a[f]=b,d[f]=c[f])})}function $e(a,b,c,d,e,f,h,g){var k=[],n,u,p=b[0],q=a.shift(),
T=E({},q,{templateUrl:null,transclude:null,replace:null,$$originalDirective:q}),v=D(q.templateUrl)?q.templateUrl(b,c):q.templateUrl,A=q.templateNamespace;b.empty();l(z.getTrustedResourceUrl(v)).then(function(l){var z,x;l=V(l);if(q.replace){l=Gb.test(l)?G(Nb(A,ba(l))):[];z=l[0];if(1!=l.length||1!==z.nodeType)throw ja("tplrt",q.name,v);l={$attr:{}};rb(d,b,z);var O=N(z,[],l);S(q.scope)&&W(O);a=O.concat(a);B(c,l)}else z=p,b.html(l);a.unshift(T);n=H(a,z,c,e,b,q,f,h,g);r(d,function(a,c){a==z&&(d[c]=b[0])});
for(u=ca(b[0].childNodes,e);k.length;){l=k.shift();x=k.shift();var M=k.shift(),Q=k.shift(),O=b[0];if(x!==p){var R=x.className;g.hasElementTranscludeDirective&&q.replace||(O=Ib(z));rb(M,G(x),O);y(G(O),R)}x=n.transcludeOnThisElement?J(l,n.transclude,Q):Q;n(u,l,O,d,x)}k=null});return function(a,b,c,d,e){a=e;k?(k.push(b),k.push(c),k.push(d),k.push(a)):(n.transcludeOnThisElement&&(a=J(b,n.transclude,e)),n(u,b,c,d,a))}}function F(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?
-1:1:a.index-b.index}function K(a,b,c,d){if(b)throw ja("multidir",b.name,c.name,a,ta(d));}function t(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=a.parent();var b=!!a.length;b&&Q.$$addBindingClass(a);return function(a,c){var e=c.parent();b||Q.$$addBindingClass(e);Q.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function Nb(a,b){a=P(a||"html");switch(a){case "svg":case "math":var c=Y.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;
default:return b}}function za(a,b){if("srcdoc"==b)return z.HTML;var c=pa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return z.RESOURCE_URL}function ya(a,c,d,e,h){var k=b(d,!0);if(k){if("multiple"===e&&"select"===pa(a))throw ja("selmulti",ta(a));c.push({priority:100,compile:function(){return{pre:function(c,d,l){d=l.$$observers||(l.$$observers={});if(g.test(e))throw ja("nodomevents");if(k=b(l[e],!0,za(a,e),f[e]||h))l[e]=k(c),(d[e]||(d[e]=[])).$$inter=!0,(l.$$observers&&
l.$$observers[e].$$scope||c).$watch(k,function(a,b){"class"===e&&a!=b?l.$updateClass(a,b):l.$set(e,a)})}}}})}}function rb(a,b,c){var d=b[0],e=b.length,f=d.parentNode,h,g;if(a)for(h=0,g=a.length;h<g;h++)if(a[h]==d){a[h++]=c;g=h+e-1;for(var k=a.length;h<k;h++,g++)g<k?a[h]=a[g]:delete a[h];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=Y.createDocumentFragment();a.appendChild(d);G(c).data(G(d).data());la?(Eb=!0,la.cleanData([d])):delete G.cache[d[G.expando]];d=1;for(e=b.length;d<
e;d++)f=b[d],G(f).remove(),a.appendChild(f),delete b[d];b[0]=c;b.length=1}function ga(a,b){return E(function(){return a.apply(null,arguments)},a,b)}function Dc(a,b,c,d,e,f){try{a(b,c,d,e,f)}catch(h){p(h,ta(c))}}var Ob=function(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a};Ob.prototype={$normalize:va,$addClass:function(a){a&&0<a.length&&T.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&T.removeClass(this.$$element,
a)},$updateClass:function(a,b){var c=Ec(a,b);c&&c.length&&T.addClass(this.$$element,c);(c=Ec(b,a))&&c.length&&T.removeClass(this.$$element,c)},$set:function(a,b,c,d){var e=this.$$element[0],f=yc(e,a),h=Ue(e,a),e=a;f?(this.$$element.prop(a,b),d=f):h&&(this[h]=b,e=h);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=Db(a,"-"));f=pa(this.$$element);if("a"===f&&"href"===a||"img"===f&&"src"===a)this[a]=b=v(b,"src"===a);!1!==c&&(null===b||b===s?this.$$element.removeAttr(d):this.$$element.attr(d,
b));(a=this.$$observers)&&r(a[e],function(a){try{a(b)}catch(c){p(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);u.$evalAsync(function(){e.$$inter||b(c[a])});return function(){Ra(e,b)}}};var ma=b.startSymbol(),ha=b.endSymbol(),V="{{"==ma||"}}"==ha?Pa:function(a){return a.replace(/\{\{/g,ma).replace(/}}/g,ha)},U=/^ngAttr[A-Z]/;Q.$$addBindingInfo=h?function(a,b){var c=a.data("$binding")||[];L(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:w;
Q.$$addBindingClass=h?function(a){y(a,"ng-binding")}:w;Q.$$addScopeInfo=h?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:w;Q.$$addScopeClass=h?function(a,b){y(a,b?"ng-isolate-scope":"ng-scope")}:w;return Q}]}function va(b){return Wa(b.replace(af,""))}function Ec(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),f=0;a:for(;f<d.length;f++){for(var g=d[f],h=0;h<e.length;h++)if(g==e[h])continue a;c+=(0<c.length?" ":"")+g}return c}function re(){var b={},a=!1,c=/^(\S+)(\s+as\s+(\w+))?$/;
this.register=function(a,c){Ja(a,"controller");S(a)?E(b,a):b[a]=c};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(d,e){function f(a,b,c,d){if(!a||!S(a.$scope))throw K("$controller")("noscp",d,b);a.$scope[b]=c}return function(g,h,m,k){var n,p,l;m=!0===m;k&&C(k)&&(l=k);C(g)&&(k=g.match(c),p=k[1],l=l||k[3],g=b.hasOwnProperty(p)?b[p]:ic(h.$scope,p,!0)||(a?ic(e,p,!0):s),gb(g,p,!0));if(m)return m=function(){},m.prototype=(L(g)?g[g.length-1]:g).prototype,n=new m,l&&f(h,l,n,
p||g.name),E(function(){d.invoke(g,n,h,p);return n},{instance:n,identifier:l});n=d.instantiate(g,h,p);l&&f(h,l,n,p||g.name);return n}}]}function se(){this.$get=["$window",function(b){return G(b.document)}]}function te(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Fc(b){var a={},c,d,e;if(!b)return a;r(b.split("\n"),function(b){e=b.indexOf(":");c=P(ba(b.substr(0,e)));d=ba(b.substr(e+1));c&&(a[c]=a[c]?a[c]+", "+d:d)});return a}function Gc(b){var a=S(b)?b:
s;return function(c){a||(a=Fc(b));return c?a[P(c)]||null:a}}function Hc(b,a,c){if(D(c))return c(b,a);r(c,function(c){b=c(b,a)});return b}function we(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults={transformResponse:[function(d){C(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=dc(d)));return d}],transformRequest:[function(a){return S(a)&&"[object File]"!==Ga.call(a)&&"[object Blob]"!==Ga.call(a)?sa(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},
post:qa(d),put:qa(d),patch:qa(d)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},f=!1;this.useApplyAsync=function(a){return B(a)?(f=!!a,this):f};var g=this.interceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,p,l){function q(a){function b(a){var d=E({},a,{data:Hc(a.data,a.headers,c.transformResponse)});a=a.status;return 200<=a&&300>a?d:p.reject(d)}var c={method:"get",transformRequest:e.transformRequest,transformResponse:e.transformResponse},
d=function(a){var b=e.headers,c=E({},a.headers),d,f,b=E({},b.common,b[P(a.method)]);a:for(d in b){a=P(d);for(f in c)if(P(f)===a)continue a;c[d]=b[d]}(function(a){var b;r(a,function(c,d){D(c)&&(b=c(),null!=b?a[d]=b:delete a[d])})})(c);return c}(a);E(c,a);c.headers=d;c.method=ib(c.method);var f=[function(a){d=a.headers;var c=Hc(a.data,Gc(d),a.transformRequest);F(c)&&r(d,function(a,b){"content-type"===P(b)&&delete d[b]});F(a.withCredentials)&&!F(e.withCredentials)&&(a.withCredentials=e.withCredentials);
return A(a,c,d).then(b,b)},s],h=p.when(c);for(r(z,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var g=f.shift(),h=h.then(a,g)}h.success=function(a){h.then(function(b){a(b.data,b.status,b.headers,c)});return h};h.error=function(a){h.then(null,function(b){a(b.data,b.status,b.headers,c)});return h};return h}function A(c,g,k){function l(a,b,c,e){function h(){z(b,a,c,e)}O&&(200<=a&&
300>a?O.put(W,[a,b,Fc(c),e]):O.remove(W));f?d.$applyAsync(h):(h(),d.$$phase||d.$apply())}function z(a,b,d,e){b=Math.max(b,0);(200<=b&&300>b?r.resolve:r.reject)({data:a,status:b,headers:Gc(d),config:c,statusText:e})}function A(){var a=q.pendingRequests.indexOf(c);-1!==a&&q.pendingRequests.splice(a,1)}var r=p.defer(),M=r.promise,O,H,W=u(c.url,c.params);q.pendingRequests.push(c);M.then(A,A);!c.cache&&!e.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(O=S(c.cache)?c.cache:S(e.cache)?e.cache:
x);if(O)if(H=O.get(W),B(H)){if(H&&D(H.then))return H.then(A,A),H;L(H)?z(H[1],H[0],qa(H[2]),H[3]):z(H,200,{},"OK")}else O.put(W,M);F(H)&&((H=Ic(c.url)?b.cookies()[c.xsrfCookieName||e.xsrfCookieName]:s)&&(k[c.xsrfHeaderName||e.xsrfHeaderName]=H),a(c.method,W,g,l,k,c.timeout,c.withCredentials,c.responseType));return M}function u(a,b){if(!b)return a;var c=[];od(b,function(a,b){null===a||F(a)||(L(a)||(a=[a]),r(a,function(a){S(a)&&(a=fa(a)?a.toISOString():sa(a));c.push(Da(b)+"="+Da(a))}))});0<c.length&&
(a+=(-1==a.indexOf("?")?"?":"&")+c.join("&"));return a}var x=c("$http"),z=[];r(g,function(a){z.unshift(C(a)?l.get(a):l.invoke(a))});q.pendingRequests=[];(function(a){r(arguments,function(a){q[a]=function(b,c){return q(E(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){r(arguments,function(a){q[a]=function(b,c,d){return q(E(d||{},{method:a,url:b,data:c}))}})})("post","put","patch");q.defaults=e;return q}]}function bf(b){if(8>=X&&(!b.match(/^(get|post|head|put|delete|options)$/i)||
!t.XMLHttpRequest))return new t.ActiveXObject("Microsoft.XMLHTTP");if(t.XMLHttpRequest)return new t.XMLHttpRequest;throw K("$httpBackend")("noxhr");}function xe(){this.$get=["$browser","$window","$document",function(b,a,c){return cf(b,bf,b.defer,a.angular.callbacks,c[0])}]}function cf(b,a,c,d,e){function f(a,b,c){var f=e.createElement("script"),n=null;f.type="text/javascript";f.src=a;f.async=!0;n=function(a){f.removeEventListener("load",n,!1);f.removeEventListener("error",n,!1);e.body.removeChild(f);
f=null;var g=-1,q="unknown";a&&("load"!==a.type||d[b].called||(a={type:"error"}),q=a.type,g="error"===a.type?404:200);c&&c(g,q)};f.addEventListener("load",n,!1);f.addEventListener("error",n,!1);e.body.appendChild(f);return n}return function(e,h,m,k,n,p,l,q){function A(){x=-1;T&&T();v&&v.abort()}function u(a,d,e,f,g){Q&&c.cancel(Q);T=v=null;0===d&&(d=e?200:"file"==Aa(h).protocol?404:0);a(1223===d?204:d,e,f,g||"");b.$$completeOutstandingRequest(w)}var x;b.$$incOutstandingRequestCount();h=h||b.url();
if("jsonp"==P(e)){var z="_"+(d.counter++).toString(36);d[z]=function(a){d[z].data=a;d[z].called=!0};var T=f(h.replace("JSON_CALLBACK","angular.callbacks."+z),z,function(a,b){u(k,a,d[z].data,"",b);d[z]=w})}else{var v=a(e);v.open(e,h,!0);r(n,function(a,b){B(a)&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(v&&4==v.readyState){var a=null,b=null,c="";-1!==x&&(a=v.getAllResponseHeaders(),b="response"in v?v.response:v.responseText);-1===x&&10>X||(c=v.statusText);u(k,x||v.status,b,a,c)}};
l&&(v.withCredentials=!0);if(q)try{v.responseType=q}catch(y){if("json"!==q)throw y;}v.send(m||null)}if(0<p)var Q=c(A,p);else p&&D(p.then)&&p.then(A)}}function ue(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function f(a){return"\\\\\\"+a}function g(f,g,q,A){function u(c){return c.replace(k,b).replace(n,a)}function x(a){try{var b;var c=q?e.getTrusted(q,a):e.valueOf(a);
if(null==c)b="";else{switch(typeof c){case "string":break;case "number":c=""+c;break;default:c=sa(c)}b=c}return b}catch(h){a=Pb("interr",f,h.toString()),d(a)}}A=!!A;for(var z,T,v=0,r=[],Q=[],s=f.length,J=[],N=[];v<s;)if(-1!=(z=f.indexOf(b,v))&&-1!=(T=f.indexOf(a,z+h)))v!==z&&J.push(u(f.substring(v,z))),v=f.substring(z+h,T),r.push(v),Q.push(c(v,x)),v=T+m,N.push(J.length),J.push("");else{v!==s&&J.push(u(f.substring(v)));break}if(q&&1<J.length)throw Pb("noconcat",f);if(!g||r.length){var M=function(a){for(var b=
0,c=r.length;b<c;b++){if(A&&F(a[b]))return;J[N[b]]=a[b]}return J.join("")};return E(function(a){var b=0,c=r.length,e=Array(c);try{for(;b<c;b++)e[b]=Q[b](a);return M(e)}catch(h){a=Pb("interr",f,h.toString()),d(a)}},{exp:f,expressions:r,$$watchDelegate:function(a,b,c){var d;return a.$watchGroup(Q,function(c,e){var f=M(c);D(b)&&b.call(this,f,c!==e?d:f,a);d=f},c)}})}}var h=b.length,m=a.length,k=new RegExp(b.replace(/./g,f),"g"),n=new RegExp(a.replace(/./g,f),"g");g.startSymbol=function(){return b};g.endSymbol=
function(){return a};return g}]}function ve(){this.$get=["$rootScope","$window","$q","$$q",function(b,a,c,d){function e(e,h,m,k){var n=a.setInterval,p=a.clearInterval,l=0,q=B(k)&&!k,A=(q?d:c).defer(),u=A.promise;m=B(m)?m:0;u.then(null,null,e);u.$$intervalId=n(function(){A.notify(l++);0<m&&l>=m&&(A.resolve(l),p(u.$$intervalId),delete f[u.$$intervalId]);q||b.$apply()},h);f[u.$$intervalId]=A;return u}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),
delete f[b.$$intervalId],!0):!1};return e}]}function Dd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),
DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a",short:"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function Qb(b){b=b.split("/");for(var a=b.length;a--;)b[a]=eb(b[a]);return b.join("/")}function Jc(b,a,c){b=Aa(b,c);a.$$protocol=
b.protocol;a.$$host=b.hostname;a.$$port=U(b.port)||df[b.protocol]||null}function Kc(b,a,c){var d="/"!==b.charAt(0);d&&(b="/"+b);b=Aa(b,c);a.$$path=decodeURIComponent(d&&"/"===b.pathname.charAt(0)?b.pathname.substring(1):b.pathname);a.$$search=fc(b.search);a.$$hash=decodeURIComponent(b.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function wa(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ya(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Rb(b){return b.substr(0,
Ya(b).lastIndexOf("/")+1)}function Lc(b,a){this.$$html5=!0;a=a||"";var c=Rb(b);Jc(b,this,b);this.$$parse=function(a){var e=wa(c,a);if(!C(e))throw sb("ipthprfx",a,c);Kc(e,this,b);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Bb(this.$$search),b=this.$$hash?"#"+eb(this.$$hash):"";this.$$url=Qb(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;(f=wa(b,d))!==s?
(g=f,g=(f=wa(a,f))!==s?c+(wa("/",f)||f):b+g):(f=wa(c,d))!==s?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function Sb(b,a){var c=Rb(b);Jc(b,this,b);this.$$parse=function(d){var e=wa(b,d)||wa(c,d),e="#"==e.charAt(0)?wa(a,e):this.$$html5?e:"";if(!C(e))throw sb("ihshprfx",d,a);Kc(e,this,b);d=this.$$path;var f=/^\/[A-Z]:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));f.exec(e)||(d=(e=f.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Bb(this.$$search),e=this.$$hash?
"#"+eb(this.$$hash):"";this.$$url=Qb(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ya(b)==Ya(a)?(this.$$parse(a),!0):!1}}function Mc(b,a){this.$$html5=!0;Sb.apply(this,arguments);var c=Rb(b);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ya(d)?f=d:(g=wa(c,d))?f=b+a+g:c===d+"/"&&(f=c);f&&this.$$parse(f);return!!f};this.$$compose=function(){var c=Bb(this.$$search),e=this.$$hash?"#"+eb(this.$$hash):
"";this.$$url=Qb(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function tb(b){return function(){return this[b]}}function Nc(b,a){return function(c){if(F(c))return this[b];this[b]=a(c);this.$$compose();return this}}function ye(){var b="",a=!1;this.hashPrefix=function(a){return B(a)?(b=a,this):b};this.html5Mode=function(b){return B(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,f){function g(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),
a)}var h,m=d.baseHref(),k=d.url();if(a){if(!m)throw sb("nobase");m=k.substring(0,k.indexOf("/",k.indexOf("//")+2))+(m||"/");e=e.history?Lc:Mc}else m=Ya(k),e=Sb;h=new e(m,"#"+b);h.$$parseLinkUrl(k,k);var n=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&2!=a.which){for(var b=G(a.target);"a"!==pa(b[0]);)if(b[0]===f[0]||!(b=b.parent())[0])return;var e=b.prop("href"),g=b.attr("href")||b.attr("xlink:href");S(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=Aa(e.animVal).href);
n.test(e)||!e||b.attr("target")||a.isDefaultPrevented()||!h.$$parseLinkUrl(e,g)||(a.preventDefault(),h.absUrl()!=d.url()&&(c.$apply(),t.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=k&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);c.$broadcast("$locationChangeStart",a,b).defaultPrevented?(h.$$parse(b),d.url(b)):g(b)}),c.$$phase||c.$digest())});var p=0;c.$watch(function(){var a=d.url(),b=h.$$replace;p&&a==h.absUrl()||
(p++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),g(a))}));h.$$replace=!1;return p});return h}]}function ze(){var b=!0,a=this;this.debugEnabled=function(a){return B(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=
c.console||{},e=b[a]||b.log||w;a=!1;try{a=!!e.apply}catch(m){}return a?function(){var a=[];r(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function na(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw oa("isecfld",a);return b}function Ba(b,
a){if(b){if(b.constructor===b)throw oa("isecfn",a);if(b.window===b)throw oa("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw oa("isecdom",a);if(b===Object)throw oa("isecobj",a);}return b}function ub(b,a,c,d){Ba(b,d);a=a.split(".");for(var e,f=0;1<a.length;f++){e=na(a.shift(),d);var g=Ba(b[e],d);g||(g={},b[e]=g);b=g}e=na(a.shift(),d);Ba(b[e],d);return b[e]=c}function Oc(b,a,c,d,e,f){na(b,f);na(a,f);na(c,f);na(d,f);na(e,f);return function(f,h){var m=h&&h.hasOwnProperty(b)?h:
f;if(null==m)return m;m=m[b];if(!a)return m;if(null==m)return s;m=m[a];if(!c)return m;if(null==m)return s;m=m[c];if(!d)return m;if(null==m)return s;m=m[d];return e?null==m?s:m=m[e]:m}}function Pc(b,a,c){var d=Qc[b];if(d)return d;var e=b.split("."),f=e.length;if(a.csp)d=6>f?Oc(e[0],e[1],e[2],e[3],e[4],c):function(a,b){var d=0,g;do g=Oc(e[d++],e[d++],e[d++],e[d++],e[d++],c)(a,b),b=s,a=g;while(d<f);return g};else{var g="";r(e,function(a,b){na(a,c);g+="if(s == null) return undefined;\ns="+(b?"s":'((l&&l.hasOwnProperty("'+
a+'"))?l:s)')+"."+a+";\n"});g+="return s;";a=new Function("s","l",g);a.toString=da(g);a.assign=function(a,c){return ub(a,b,c,b)};d=a}d.sharedGetter=!0;return Qc[b]=d}function Ae(){var b=Object.create(null),a={csp:!1};this.$get=["$filter","$sniffer",function(c,d){function e(a){var b=a;a.sharedGetter&&(b=function(b,c){return a(b,c)},b.literal=a.literal,b.constant=a.constant,b.assign=a.assign);return b}function f(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;D(b)&&b.apply(this,
arguments);B(a)&&d.$$postDigest(function(){B(f)&&e()})},c)}function g(a,b,c,d){function e(a){var b=!0;r(a,function(a){B(a)||(b=!1)});return b}var f;return f=a.$watch(function(a){return d(a)},function(a,c,d){D(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(a)&&f()})},c)}function h(a,b,c,d){var e;return e=a.$watch(function(a){return d(a)},function(a,c,d){D(b)&&b.apply(this,arguments);e()},c)}function m(a,b){if(!b)return a;var c=function(c,d){var e=a(c,d),f=b(e,c,d);return B(e)?f:e};c.$$watchDelegate=
a.$$watchDelegate;return c}a.csp=d.csp;return function(d,n){var p,l,q;switch(typeof d){case "string":return q=d=d.trim(),p=b[q],p||(":"===d.charAt(0)&&":"===d.charAt(1)&&(l=!0,d=d.substring(2)),p=new Tb(a),p=(new Za(p,c,a)).parse(d),p.constant?p.$$watchDelegate=h:l&&(p=e(p),p.$$watchDelegate=p.literal?g:f),b[q]=p),m(p,n);case "function":return m(d,n);default:return m(w,n)}}}]}function Ce(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return Rc(function(a){b.$evalAsync(a)},a)}]}function De(){this.$get=
["$browser","$exceptionHandler",function(b,a){return Rc(function(a){b.defer(a)},a)}]}function Rc(b,a){function c(a,b,c){function d(b){return function(c){e||(e=!0,b.call(a,c))}}var e=!1;return[d(b),d(c)]}function d(){this.$$state={status:0}}function e(a,b){return function(c){b.call(a,c)}}function f(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,b(function(){var b,d,e;e=c.pending;c.processScheduled=!1;c.pending=s;for(var f=0,h=e.length;f<h;++f){d=e[f][0];b=e[f][c.status];try{D(b)?d.resolve(b(c.value)):
1===c.status?d.resolve(c.value):d.reject(c.value)}catch(g){d.reject(g),a(g)}}}))}function g(){this.promise=new d;this.resolve=e(this,this.resolve);this.reject=e(this,this.reject);this.notify=e(this,this.notify)}var h=K("$q",TypeError);d.prototype={then:function(a,b,c){var d=new g;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&f(this.$$state);return d.promise},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return k(b,
!0,a)},function(b){return k(b,!1,a)},b)}};g.prototype={resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(h("qcycle",a)):this.$$resolve(a))},$$resolve:function(b){var d,e;e=c(this,this.$$resolve,this.$$reject);try{if(S(b)||D(b))d=b&&b.then;D(d)?(this.promise.$$state.status=-1,d.call(b,e[0],e[1],this.notify)):(this.promise.$$state.value=b,this.promise.$$state.status=1,f(this.promise.$$state))}catch(h){e[1](h),a(h)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},
$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;f(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,h=d.length;f<h;f++){e=d[f][0];b=d[f][3];try{e.notify(D(b)?b(c):c)}catch(g){a(g)}}})}};var m=function(a,b){var c=new g;b?c.resolve(a):c.reject(a);return c.promise},k=function(a,b,c){var d=null;try{D(c)&&(d=c())}catch(e){return m(e,!1)}return d&&D(d.then)?d.then(function(){return m(a,
b)},function(a){return m(a,!1)}):m(a,b)},n=function(a,b,c,d){var e=new g;e.resolve(a);return e.promise.then(b,c,d)},p=function q(a){if(!D(a))throw h("norslvr",a);if(!(this instanceof q))return new q(a);var b=new g;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};p.defer=function(){return new g};p.reject=function(a){var b=new g;b.reject(a);return b.promise};p.when=n;p.all=function(a){var b=new g,c=0,d=L(a)?[]:{};r(a,function(a,e){c++;n(a).then(function(a){d.hasOwnProperty(e)||
(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};return p}function Me(){this.$get=["$window","$timeout",function(b,a){var c=b.requestAnimationFrame||b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame,d=b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.mozCancelAnimationFrame||b.webkitCancelRequestAnimationFrame,e=!!c,f=e?function(a){var b=c(a);return function(){d(b)}}:function(b){var c=a(b,16.66,!1);return function(){a.cancel(c)}};
f.supported=e;return f}]}function Be(){var b=10,a=K("$rootScope"),c=null,d=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse","$browser",function(e,f,g,h){function m(){this.$id=++bb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$postDigestQueue=[];this.$$listeners={};this.$$listenerCount=
{};this.$$isolateBindings={};this.$$applyAsyncQueue=[]}function k(b){if(A.$$phase)throw a("inprog",A.$$phase);A.$$phase=b}function n(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function p(){}function l(){for(var a=A.$$applyAsyncQueue;a.length;)try{a.shift()()}catch(b){f(b)}d=null}function q(){null===d&&(d=h.defer(function(){A.$apply(l)}))}m.prototype={constructor:m,$new:function(a){a?(a=new m,a.$root=this.$root,a.$$asyncQueue=this.$$asyncQueue,
a.$$postDigestQueue=this.$$postDigestQueue):(this.$$ChildScope||(this.$$ChildScope=function(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$id=++bb;this.$$ChildScope=null},this.$$ChildScope.prototype=this),a=new this.$$ChildScope);a["this"]=a;a.$parent=this;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,
d){var e=g(a);if(e.$$watchDelegate)return e.$$watchDelegate(this,b,d,e);var f=this.$$watchers,h={fn:b,last:p,get:e,exp:a,eq:!!d};c=null;D(b)||(h.fn=w);f||(f=this.$$watchers=[]);f.unshift(h);return function(){Ra(f,h);c=null}},$watchGroup:function(a,b){function c(){g=!1;k?(k=!1,b(e,e,h)):b(e,d,h)}var d=Array(a.length),e=Array(a.length),f=[],h=this,g=!1,k=!0;if(!a.length){var m=!0;h.$evalAsync(function(){m&&b(e,e,h)});return function(){m=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=
a;d[0]=c;b(e,a===c?e:d,f)});r(a,function(a,b){var k=h.$watch(a,function(a,f){e[b]=a;d[b]=f;g||(g=!0,h.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){var c=this,d,e,f,h=1<b.length,k=0,m=g(a,function(a){d=a;var b,c,f,h;if(S(d))if(Na(d))for(e!==l&&(e=l,q=e.length=0,k++),a=d.length,q!==a&&(k++,e.length=q=a),b=0;b<a;b++)h=e[b],f=d[b],c=h!==h&&f!==f,c||h===f||(k++,e[b]=f);else{e!==n&&(e=n={},q=0,k++);a=0;for(b in d)d.hasOwnProperty(b)&&(a++,f=
d[b],h=e[b],b in e?(c=h!==h&&f!==f,c||h===f||(k++,e[b]=f)):(q++,e[b]=f,k++));if(q>a)for(b in k++,e)d.hasOwnProperty(b)||(q--,delete e[b])}else e!==d&&(e=d,k++);return k}),l=[],n={},p=!0,q=0;return this.$watch(m,function(){p?(p=!1,b(d,d,c)):b(d,f,c);if(h)if(S(d))if(Na(d)){f=Array(d.length);for(var a=0;a<d.length;a++)f[a]=d[a]}else for(a in f={},d)Ab.call(d,a)&&(f[a]=d[a]);else f=d})},$digest:function(){var e,g,m,n,q=this.$$asyncQueue,r=this.$$postDigestQueue,s,B,J=b,N,M=[],O,H,W;k("$digest");h.$$checkUrlChange();
this===A&&null!==d&&(h.defer.cancel(d),l());c=null;do{B=!1;for(N=this;q.length;){try{W=q.shift(),W.scope.$eval(W.expression)}catch(R){f(R)}c=null}a:do{if(n=N.$$watchers)for(s=n.length;s--;)try{if(e=n[s])if((g=e.get(N))!==(m=e.last)&&!(e.eq?ra(g,m):"number"===typeof g&&"number"===typeof m&&isNaN(g)&&isNaN(m)))B=!0,c=e,e.last=e.eq?Ha(g,null):g,e.fn(g,m===p?g:m,N),5>J&&(O=4-J,M[O]||(M[O]=[]),H=D(e.exp)?"fn: "+(e.exp.name||e.exp.toString()):e.exp,H+="; newVal: "+sa(g)+"; oldVal: "+sa(m),M[O].push(H));
else if(e===c){B=!1;break a}}catch(t){f(t)}if(!(n=N.$$childHead||N!==this&&N.$$nextSibling))for(;N!==this&&!(n=N.$$nextSibling);)N=N.$parent}while(N=n);if((B||q.length)&&!J--)throw A.$$phase=null,a("infdig",b,sa(M));}while(B||q.length);for(A.$$phase=null;r.length;)try{r.shift()()}catch(G){f(G)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(this!==A){for(var b in this.$$listenerCount)n(this,this.$$listenerCount[b],b);a.$$childHead==
this&&(a.$$childHead=this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=null;this.$$listeners={};this.$$watchers=this.$$asyncQueue=this.$$postDigestQueue=[];this.$destroy=this.$digest=this.$apply=w;this.$on=this.$watch=this.$watchGroup=
function(){return w}}}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a){A.$$phase||A.$$asyncQueue.length||h.defer(function(){A.$$asyncQueue.length&&A.$digest()});this.$$asyncQueue.push({scope:this,expression:a})},$$postDigest:function(a){this.$$postDigestQueue.push(a)},$apply:function(a){try{return k("$apply"),this.$eval(a)}catch(b){f(b)}finally{A.$$phase=null;try{A.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&A.$$applyAsyncQueue.push(b);
q()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){c[c.indexOf(b)]=null;n(e,1,a)}},$emit:function(a,b){var c=[],d,e=this,h=!1,g={name:a,targetScope:e,stopPropagation:function(){h=!0},preventDefault:function(){g.defaultPrevented=!0},defaultPrevented:!1},k=db([g],arguments,1),m,l;do{d=e.$$listeners[a]||c;g.currentScope=e;m=0;for(l=
d.length;m<l;m++)if(d[m])try{d[m].apply(null,k)}catch(n){f(n)}else d.splice(m,1),m--,l--;if(h)return g.currentScope=null,g;e=e.$parent}while(e);g.currentScope=null;return g},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var h=db([e],arguments,1),g,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];g=0;for(k=d.length;g<k;g++)if(d[g])try{d[g].apply(null,h)}catch(m){f(m)}else d.splice(g,
1),g--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var A=new m;return A}]}function Ed(){var b=/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(a){return B(a)?(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,f;if(!X||8<=X)if(f=
Aa(c).href,""!==f&&!f.match(e))return"unsafe:"+f;return c}}}function ef(b){if("self"===b)return b;if(C(b)){if(-1<b.indexOf("***"))throw Ca("iwcard",b);b=b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08").replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+b+"$")}if(cb(b))return new RegExp("^"+b.source+"$");throw Ca("imatcher");}function Sc(b){var a=[];B(b)&&r(b,function(b){a.push(ef(b))});return a}function Fe(){this.SCE_CONTEXTS=ka;var b=["self"],a=[];
this.resourceUrlWhitelist=function(a){arguments.length&&(b=Sc(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=Sc(b));return a};this.$get=["$injector",function(c){function d(a,b){return"self"===a?Ic(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}
var f=function(a){throw Ca("unsafe");};c.has("$sanitize")&&(f=c.get("$sanitize"));var g=e(),h={};h[ka.HTML]=e(g);h[ka.CSS]=e(g);h[ka.URL]=e(g);h[ka.JS]=e(g);h[ka.RESOURCE_URL]=e(h[ka.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw Ca("icontext",a,b);if(null===b||b===s||""===b)return b;if("string"!==typeof b)throw Ca("itype",a);return new c(b)},getTrusted:function(c,e){if(null===e||e===s||""===e)return e;var g=h.hasOwnProperty(c)?h[c]:null;if(g&&e instanceof g)return e.$$unwrapTrustedValue();
if(c===ka.RESOURCE_URL){var g=Aa(e.toString()),p,l,q=!1;p=0;for(l=b.length;p<l;p++)if(d(b[p],g)){q=!0;break}if(q)for(p=0,l=a.length;p<l;p++)if(d(a[p],g)){q=!1;break}if(q)return e;throw Ca("insecurl",e.toString());}if(c===ka.HTML)return f(e);throw Ca("unsafe");},valueOf:function(a){return a instanceof g?a.$$unwrapTrustedValue():a}}}]}function Ee(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sniffer","$sceDelegate",function(a,c,d){if(b&&c.msie&&8>c.msieDocumentMode)throw Ca("iequirks");
var e=qa(ka);e.isEnabled=function(){return b};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;b||(e.trustAs=e.getTrusted=function(a,b){return b},e.valueOf=Pa);e.parseAs=function(b,c){var d=a(c);return d.literal&&d.constant?d:a(c,function(a){return e.getTrusted(b,a)})};var f=e.parseAs,g=e.getTrusted,h=e.trustAs;r(ka,function(a,b){var c=P(b);e[Wa("parse_as_"+c)]=function(b){return f(a,b)};e[Wa("get_trusted_"+c)]=function(b){return g(a,b)};e[Wa("trust_as_"+c)]=function(b){return h(a,
b)}});return e}]}function Ge(){this.$get=["$window","$document",function(b,a){var c={},d=U((/android (\d+)/.exec(P((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g=f.documentMode,h,m=/^(Moz|webkit|O|ms)(?=[A-Z])/,k=f.body&&f.body.style,n=!1,p=!1;if(k){for(var l in k)if(n=m.exec(l)){h=n[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in k&&"webkit");n=!!("transition"in k||h+"Transition"in k);p=!!("animation"in k||h+"Animation"in
k);!d||n&&p||(n=C(f.body.style.webkitTransition),p=C(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hashchange:"onhashchange"in b&&(!g||7<g),hasEvent:function(a){if("input"==a&&9==X)return!1;if(F(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:Ua(),vendorPrefix:h,transitions:n,animations:p,android:d,msie:X,msieDocumentMode:g}}]}function Ie(){this.$get=["$templateCache","$http","$q",function(b,a,c){function d(e,f){function g(){h.totalPendingRequests--;
if(!f)throw ja("tpload",e);return c.reject()}var h=d;h.totalPendingRequests++;return a.get(e,{cache:b}).then(function(a){a=a.data;if(!a||0===a.length)return g();h.totalPendingRequests--;b.put(e,a);return a},g)}d.totalPendingRequests=0;return d}]}function Je(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var d=Ea.element(a).data("$binding");d&&r(d,function(d){c?(new RegExp("(^|\\s)"+
b+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,c){for(var g=["ng-","data-ng-","ng\\:"],h=0;h<g.length;++h){var m=a.querySelectorAll("["+g[h]+"model"+(c?"=":"*=")+'"'+b+'"]');if(m.length)return m}},getLocation:function(){return c.url()},setLocation:function(a){a!==c.url()&&(c.url(a),b.$digest())},whenStable:function(b){a.notifyWhenNoOutstandingRequests(b)}}}]}function Ke(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(b,
a,c,d,e){function f(f,m,k){var n=B(k)&&!k,p=(n?d:c).defer(),l=p.promise;m=a.defer(function(){try{p.resolve(f())}catch(a){p.reject(a),e(a)}finally{delete g[l.$$timeoutId]}n||b.$apply()},m);l.$$timeoutId=m;g[m]=p;return l}var g={};f.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),delete g[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return f}]}function Aa(b,a){var c=b;X&&(Z.setAttribute("href",c),c=Z.href);Z.setAttribute("href",c);return{href:Z.href,protocol:Z.protocol?
Z.protocol.replace(/:$/,""):"",host:Z.host,search:Z.search?Z.search.replace(/^\?/,""):"",hash:Z.hash?Z.hash.replace(/^#/,""):"",hostname:Z.hostname,port:Z.port,pathname:"/"===Z.pathname.charAt(0)?Z.pathname:"/"+Z.pathname}}function Ic(b){b=C(b)?Aa(b):b;return b.protocol===Tc.protocol&&b.host===Tc.host}function Le(){this.$get=da(t)}function qc(b){function a(c,d){if(S(c)){var e={};r(c,function(b,c){e[c]=a(c,b)});return e}return b.factory(c+"Filter",d)}this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+
"Filter")}}];a("currency",Uc);a("date",Vc);a("filter",ff);a("json",gf);a("limitTo",hf);a("lowercase",jf);a("number",Wc);a("orderBy",Xc);a("uppercase",kf)}function ff(){return function(b,a,c){if(!L(b))return b;var d=typeof c,e=[];e.check=function(a,b){for(var c=0;c<e.length;c++)if(!e[c](a,b))return!1;return!0};"function"!==d&&(c="boolean"===d&&c?function(a,b){return Ea.equals(a,b)}:function(a,b){if(a&&b&&"object"===typeof a&&"object"===typeof b){for(var d in a)if("$"!==d.charAt(0)&&Ab.call(a,d)&&c(a[d],
b[d]))return!0;return!1}b=(""+b).toLowerCase();return-1<(""+a).toLowerCase().indexOf(b)});var f=function(a,b){if("string"==typeof b&&"!"===b.charAt(0))return!f(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if("$"!==d.charAt(0)&&f(a[d],b))return!0}return!1;case "array":for(d=0;d<a.length;d++)if(f(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a=
{$:a};case "object":for(var g in a)(function(b){"undefined"!==typeof a[b]&&e.push(function(c){return f("$"==b?c:c&&c[b],a[b])})})(g);break;case "function":e.push(a);break;default:return b}d=[];for(g=0;g<b.length;g++){var h=b[g];e.check(h,g)&&d.push(h)}return d}}function Uc(b){var a=b.NUMBER_FORMATS;return function(b,d){F(d)&&(d=a.CURRENCY_SYM);return null==b?b:Yc(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function Wc(b){var a=b.NUMBER_FORMATS;return function(b,d){return null==
b?b:Yc(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Yc(b,a,c,d,e){if(!isFinite(b)||S(b))return"";var f=0>b;b=Math.abs(b);var g=b+"",h="",m=[],k=!1;if(-1!==g.indexOf("e")){var n=g.match(/([\d\.]+)e(-?)(\d+)/);n&&"-"==n[2]&&n[3]>e+1?(g="0",b=0):(h=g,k=!0)}if(k)0<e&&-1<b&&1>b&&(h=b.toFixed(e));else{g=(g.split(Zc)[1]||"").length;F(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);0===b&&(f=!1);b=(""+b).split(Zc);g=b[0];b=b[1]||"";var n=
0,p=a.lgSize,l=a.gSize;if(g.length>=p+l)for(n=g.length-p,k=0;k<n;k++)0===(n-k)%l&&0!==k&&(h+=c),h+=g.charAt(k);for(k=n;k<g.length;k++)0===(g.length-k)%p&&0!==k&&(h+=c),h+=g.charAt(k);for(;b.length<e;)b+="0";e&&"0"!==e&&(h+=d+b.substr(0,e))}m.push(f?a.negPre:a.posPre);m.push(h);m.push(f?a.negSuf:a.posSuf);return m.join("")}function vb(b,a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function $(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();
if(0<c||e>-c)e+=c;0===e&&-12==c&&(e=12);return vb(e,a,d)}}function wb(b,a){return function(c,d){var e=c["get"+b](),f=ib(a?"SHORT"+b:b);return d[f][e]}}function $c(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function ad(b){return function(a){var c=$c(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return vb(a,b)}}function Vc(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:
a.setFullYear,m=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=U(b[9]+b[10]),g=U(b[9]+b[11]));h.call(a,U(b[1]),U(b[2])-1,U(b[3]));f=U(b[4]||0)-f;g=U(b[5]||0)-g;h=U(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));m.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e,f){var g="",h=[],m,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;C(c)&&(c=lf.test(c)?U(c):a(c));ea(c)&&(c=new Date(c));if(!fa(c))return c;
for(;e;)(k=mf.exec(e))?(h=db(h,k,1),e=h.pop()):(h.push(e),e=null);f&&"UTC"===f&&(c=new Date(c.getTime()),c.setMinutes(c.getMinutes()+c.getTimezoneOffset()));r(h,function(a){m=nf[a];g+=m?m(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function gf(){return function(b){return sa(b,!0)}}function hf(){return function(b,a){if(!L(b)&&!C(b))return b;a=Infinity===Math.abs(Number(a))?Number(a):U(a);if(C(b))return a?0<=a?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?
a=b.length:a<-b.length&&(a=-b.length);0<a?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Xc(b){return function(a,c,d){function e(a,b){return b?function(b,c){return a(c,b)}:a}function f(a,b){var c=typeof a,d=typeof b;return c==d?(fa(a)&&fa(b)&&(a=a.valueOf(),b=b.valueOf()),"string"==c&&(a=a.toLowerCase(),b=b.toLowerCase()),a===b?0:a<b?-1:1):c<d?-1:1}if(!Na(a)||!c)return a;c=L(c)?c:[c];c=sd(c,function(a){var c=!1,d=a||Pa;if(C(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))c=
"-"==a.charAt(0),a=a.substring(1);d=b(a);if(d.constant){var h=d();return e(function(a,b){return f(a[h],b[h])},c)}}return e(function(a,b){return f(d(a),d(b))},c)});for(var g=[],h=0;h<a.length;h++)g.push(a[h]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(0!==e)return e}return 0},d))}}function Fa(b){D(b)&&(b={link:b});b.restrict=b.restrict||"AC";return da(b)}function bd(b,a,c,d){var e=this,f=b.parent().controller("form")||xb,g=[];e.$error={};e.$$success={};e.$pending=
s;e.$name=a.name||a.ngForm;e.$dirty=!1;e.$pristine=!0;e.$valid=!0;e.$invalid=!1;e.$submitted=!1;f.$addControl(e);b.addClass(Ma);e.$rollbackViewValue=function(){r(g,function(a){a.$rollbackViewValue()})};e.$commitViewValue=function(){r(g,function(a){a.$commitViewValue()})};e.$addControl=function(a){Ja(a.$name,"input");g.push(a);a.$name&&(e[a.$name]=a)};e.$removeControl=function(a){a.$name&&e[a.$name]===a&&delete e[a.$name];r(e.$pending,function(b,c){e.$setValidity(c,null,a)});r(e.$error,function(b,
c){e.$setValidity(c,null,a)});Ra(g,a)};cd({ctrl:this,$element:b,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(Ra(d,c),0===d.length&&delete a[b])},parentForm:f,$animate:d});e.$setDirty=function(){d.removeClass(b,Ma);d.addClass(b,yb);e.$dirty=!0;e.$pristine=!1;f.$setDirty()};e.$setPristine=function(){d.setClass(b,Ma,yb+" ng-submitted");e.$dirty=!1;e.$pristine=!0;e.$submitted=!1;r(g,function(a){a.$setPristine()})};e.$setSubmitted=function(){d.addClass(b,
"ng-submitted");e.$submitted=!0;f.$setSubmitted()}}function Ub(b){b.$formatters.push(function(a){return b.$isEmpty(a)?a:a.toString()})}function $a(b,a,c,d,e,f){a.prop("validity");var g=a[0].placeholder,h={},m=P(a[0].type);if(!e.android){var k=!1;a.on("compositionstart",function(a){k=!0});a.on("compositionend",function(){k=!1;n()})}var n=function(b){if(!k){var e=a.val(),f=b&&b.type;X&&"input"===(b||h).type&&a[0].placeholder!==g?g=a[0].placeholder:("password"===m||c.ngTrim&&"false"===c.ngTrim||(e=ba(e)),
(d.$viewValue!==e||""===e&&d.$$hasNativeValidators)&&d.$setViewValue(e,f))}};if(e.hasEvent("input"))a.on("input",n);else{var p,l=function(a){p||(p=f.defer(function(){n(a);p=null}))};a.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||l(a)});if(e.hasEvent("paste"))a.on("paste cut",l)}a.on("change",n);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)}}function zb(b,a){return function(c){var d;if(fa(c))return c;if(C(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-
1)&&(c=c.substring(1,c.length-1));if(of.test(c))return new Date(c);b.lastIndex=0;if(c=b.exec(c))return c.shift(),d={yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0},r(c,function(b,c){c<a.length&&(d[a[c]]=+b)}),new Date(d.yyyy,d.MM-1,d.dd,d.HH,d.mm,d.ss||0)}return NaN}}function ab(b,a,c,d){return function(e,f,g,h,m,k,n){function p(a){return B(a)?fa(a)?a:c(a):s}dd(e,f,g,h);$a(e,f,g,h,m,k);var l=h&&h.$options&&h.$options.timezone;h.$$parserName=b;h.$parsers.push(function(b){return h.$isEmpty(b)?null:a.test(b)?(b=
c(b),"UTC"===l&&b.setMinutes(b.getMinutes()-b.getTimezoneOffset()),b):s});h.$formatters.push(function(a){return fa(a)?n("date")(a,d,l):""});if(B(g.min)||g.ngMin){var q;h.$validators.min=function(a){return h.$isEmpty(a)||F(q)||c(a)>=q};g.$observe("min",function(a){q=p(a);h.$validate()})}if(B(g.max)||g.ngMax){var r;h.$validators.max=function(a){return h.$isEmpty(a)||F(r)||c(a)<=r};g.$observe("max",function(a){r=p(a);h.$validate()})}}}function dd(b,a,c,d){(d.$$hasNativeValidators=S(a[0].validity))&&
d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?s:b})}function ed(b,a,c,d,e){if(B(d)){b=b(d);if(!b.constant)throw K("ngModel")("constexpr",c,d);return b(a)}return e}function cd(b){function a(a,b){b&&!f[a]?(k.addClass(e,a),f[a]=!0):!b&&f[a]&&(k.removeClass(e,a),f[a]=!1)}function c(b,c){b=b?"-"+Db(b,"-"):"";a(pf+b,!0===c);a(qf+b,!1===c)}var d=b.ctrl,e=b.$element,f={},g=b.set,h=b.unset,m=b.parentForm,k=b.$animate;d.$setValidity=function(b,e,f){e===s?(d.$pending||
(d.$pending={}),g(d.$pending,b,f)):(d.$pending&&h(d.$pending,b,f),fd(d.$pending)&&(d.$pending=s));"boolean"!==typeof e?(h(d.$error,b,f),h(d.$$success,b,f)):e?(h(d.$error,b,f),g(d.$$success,b,f)):(g(d.$error,b,f),h(d.$$success,b,f));d.$pending?(a(gd,!0),d.$valid=d.$invalid=s,c("",null)):(a(gd,!1),d.$valid=fd(d.$error),d.$invalid=!d.$valid,c("",d.$valid));e=d.$pending&&d.$pending[b]?s:d.$error[b]?!1:d.$$success[b]?!0:null;c(b,e);m.$setValidity(b,e,d)};c("",!0)}function fd(b){if(b)for(var a in b)return!1;
return!0}function Vb(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],n=0;n<b.length;n++)if(e==b[n])continue a;c.push(e)}return c}function e(a){if(!L(a)){if(C(a))return a.split(" ");if(S(a)){var b=[];r(a,function(a,c){a&&(b=b.concat(c.split(" ")))});return b}}return a}return{restrict:"AC",link:function(f,g,h){function m(a,b){var c=g.data("$classCounts")||{},d=[];r(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});
g.data("$classCounts",c);return d.join(" ")}function k(b){if(!0===a||f.$index%2===a){var k=e(b||[]);if(!n){var q=m(k,1);h.$addClass(q)}else if(!ra(b,n)){var r=e(n),q=d(k,r),k=d(r,k),q=m(q,1),k=m(k,-1);q&&q.length&&c.addClass(g,q);k&&k.length&&c.removeClass(g,k)}}n=qa(b)}var n;f.$watch(h[b],k,!0);h.$observe("class",function(a){k(f.$eval(h[b]))});"ngClass"!==b&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var k=e(f.$eval(h[b]));g===a?(g=m(k,1),h.$addClass(g)):(g=m(k,-1),h.$removeClass(g))}})}}}]}
var rf=/^\/(.+)\/([a-z]*)$/,P=function(b){return C(b)?b.toLowerCase():b},Ab=Object.prototype.hasOwnProperty,ib=function(b){return C(b)?b.toUpperCase():b},X,G,la,Ta=[].slice,sf=[].push,Ga=Object.prototype.toString,Sa=K("ng"),Ea=t.angular||(t.angular={}),Va,bb=0;X=U((/msie (\d+)/.exec(P(navigator.userAgent))||[])[1]);isNaN(X)&&(X=U((/trident\/.*; rv:(\d+)/.exec(P(navigator.userAgent))||[])[1]));w.$inject=[];Pa.$inject=[];var L=Array.isArray,ba=function(b){return C(b)?b.trim():b},Ua=function(){if(B(Ua.isActive_))return Ua.isActive_;
var b=!(!Y.querySelector("[ng-csp]")&&!Y.querySelector("[data-ng-csp]"));if(!b)try{new Function("")}catch(a){b=!0}return Ua.isActive_=b},fb=["ng-","data-ng-","ng:","x-ng-"],yd=/[A-Z]/g,hc=!1,Eb,Cd={full:"1.3.0-rc.1",major:1,minor:3,dot:0,codeName:"backyard-atomicity"};V.expando="ng339";var ob=V.cache={},Te=1;V._data=function(b){return this.cache[b[this.expando]]||{}};var Oe=/([\:\-\_]+(.))/g,Pe=/^moz([A-Z])/,tf={mouseleave:"mouseout",mouseenter:"mouseover"},Hb=K("jqLite"),Se=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,
Gb=/<|&#?\w+;/,Qe=/<([\w:]+)/,Re=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ia={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ia.optgroup=ia.option;ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead;ia.th=ia.td;var Ia=V.prototype={ready:function(b){function a(){c||(c=
!0,b())}var c=!1;"complete"===Y.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),V(t).on("load",a),this.on("DOMContentLoaded",a))},toString:function(){var b=[];r(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?G(this[b]):G(this[this.length+b])},length:0,push:sf,sort:[].sort,splice:[].splice},qb={};r("multiple selected checked disabled readOnly required open".split(" "),function(b){qb[P(b)]=b});var zc={};r("input select option textarea button form details".split(" "),
function(b){zc[b]=!0});var Ac={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};r({data:Jb,removeData:mb},function(b,a){V[a]=b});r({data:Jb,inheritedData:pb,scope:function(b){return G.data(b,"$scope")||pb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return G.data(b,"$isolateScope")||G.data(b,"$isolateScopeNoTemplate")},controller:vc,injector:function(b){return pb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:jb,
css:function(b,a,c){a=Wa(a);if(B(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=P(a);if(qb[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||w).specified?d:s;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?s:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(F(b)){var d=a.nodeType;return 1===d||3===d?a.textContent:""}a.textContent=
b}b.$dv="";return b}(),val:function(b,a){if(F(a)){if(b.multiple&&"select"===pa(b)){var c=[];r(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(F(a))return b.innerHTML;lb(b,!0);b.innerHTML=a},empty:wc},function(b,a){V.prototype[a]=function(a,d){var e,f,g=this.length;if(b!==wc&&(2==b.length&&b!==jb&&b!==vc?a:d)===s){if(S(a)){for(e=0;e<g;e++)if(b===Jb)b(this[e],a);else for(f in a)b(this[e],f,a[f]);return this}e=b.$dv;
g=e===s?Math.min(g,1):g;for(f=0;f<g;f++){var h=b(this[f],a,d);e=e?e+h:h}return e}for(e=0;e<g;e++)b(this[e],a,d);return this}});r({removeData:mb,on:function a(c,d,e,f){if(B(f))throw Hb("onargs");if(rc(c)){var g=nb(c,!0);f=g.events;var h=g.handle;h||(h=g.handle=Ve(c,f));for(var g=0<=d.indexOf(" ")?d.split(" "):[d],m=g.length;m--;){d=g[m];var k=f[d];k||(f[d]=[],"mouseenter"===d||"mouseleave"===d?a(c,tf[d],function(a){var c=a.relatedTarget;c&&(c===this||this.contains(c))||h(a,d)}):"$destroy"!==d&&c.addEventListener(d,
h,!1),k=f[d]);k.push(e)}}},off:uc,one:function(a,c,d){a=G(a);a.on(c,function f(){a.off(c,d);a.off(c,f)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;lb(a);r(new V(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];r(a.childNodes,function(a){1===a.nodeType&&c.push(a)});return c},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,c){var d=a.nodeType;if(1===d||11===d){c=new V(c);for(var d=0,e=c.length;d<
e;d++)a.appendChild(c[d])}},prepend:function(a,c){if(1===a.nodeType){var d=a.firstChild;r(new V(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=G(c).eq(0).clone()[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:xc,detach:function(a){xc(a,!0)},after:function(a,c){var d=a,e=a.parentNode;c=new V(c);for(var f=0,g=c.length;f<g;f++){var h=c[f];e.insertBefore(h,d.nextSibling);d=h}},addClass:Lb,removeClass:Kb,toggleClass:function(a,c,d){c&&r(c.split(" "),function(c){var f=
d;F(f)&&(f=!jb(a,c));(f?Lb:Kb)(a,c)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,c){return a.getElementsByTagName?a.getElementsByTagName(c):[]},clone:Ib,triggerHandler:function(a,c,d){var e,f;e=c.type||c;var g=nb(a);if(g=(g=g&&g.events)&&g[e])e={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopPropagation:w,type:e,target:a},c.type&&(e=E(e,c)),
c=qa(g),f=d?[e].concat(d):[e],r(c,function(c){c.apply(a,f)})}},function(a,c){V.prototype[c]=function(c,e,f){for(var g,h=0,m=this.length;h<m;h++)F(g)?(g=a(this[h],c,e,f),B(g)&&(g=G(g))):tc(g,a(this[h],c,e,f));return B(g)?g:this};V.prototype.bind=V.prototype.on;V.prototype.unbind=V.prototype.off});Xa.prototype={put:function(a,c){this[Ka(a,this.nextUid)]=c},get:function(a){return this[Ka(a,this.nextUid)]},remove:function(a){var c=this[a=Ka(a,this.nextUid)];delete this[a];return c}};var Cc=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,
Xe=/,/,Ye=/^\s*(_?)(\S+?)\1\s*$/,Bc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,La=K("$injector");Cb.$$annotate=Mb;var uf=K("$animate"),oe=["$provide",function(a){this.$$selectors={};this.register=function(c,d){var e=c+"-animation";if(c&&"."!=c.charAt(0))throw uf("notcsel",c);this.$$selectors[c.substr(1)]=e;a.factory(e,d)};this.classNameFilter=function(a){1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null);return this.$$classNameFilter};this.$get=["$$q","$$asyncCallback",function(a,d){function e(){f||
(f=a.defer(),d(function(){f.resolve();f=null}));return f.promise}var f;return{enter:function(a,c,d){d?d.after(a):c.prepend(a);return e()},leave:function(a){a.remove();return e()},move:function(a,c,d){return this.enter(a,c,d)},addClass:function(a,c){c=C(c)?c:L(c)?c.join(" "):"";r(a,function(a){Lb(a,c)});return e()},removeClass:function(a,c){c=C(c)?c:L(c)?c.join(" "):"";r(a,function(a){Kb(a,c)});return e()},setClass:function(a,c,d){this.addClass(a,c);this.removeClass(a,d);return e()},enabled:w,cancel:w}}]}],
ja=K("$compile");jc.$inject=["$provide","$$sanitizeUriProvider"];var af=/^(x[\:\-_]|data[\:\-_])/i,Pb=K("$interpolate"),vf=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,df={http:80,https:443,ftp:21},sb=K("$location");Mc.prototype=Sb.prototype=Lc.prototype={$$html5:!1,$$replace:!1,absUrl:tb("$$absUrl"),url:function(a){if(F(a))return this.$$url;a=vf.exec(a);a[1]&&this.path(decodeURIComponent(a[1]));(a[2]||a[1])&&this.search(a[3]||"");this.hash(a[5]||"");return this},protocol:tb("$$protocol"),host:tb("$$host"),
port:tb("$$port"),path:Nc("$$path",function(a){a=a?a.toString():"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,c){switch(arguments.length){case 0:return this.$$search;case 1:if(C(a)||ea(a))a=a.toString(),this.$$search=fc(a);else if(S(a))r(a,function(c,e){null==c&&delete a[e]}),this.$$search=a;else throw sb("isrcharg");break;default:F(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:Nc("$$hash",function(a){return a?a.toString():""}),replace:function(){this.$$replace=
!0;return this}};var oa=K("$parse"),wf=Function.prototype.call,xf=Function.prototype.apply,yf=Function.prototype.bind,hd=Object.create(null);r({"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:function(){}},function(a,c){a.constant=a.literal=a.sharedGetter=!0;hd[c]=a});var Wb=E(Object.create(null),{"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:s},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a,
c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":w,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,
c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}}),zf={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Tb=function(a){this.options=a};Tb.prototype={constructor:Tb,lex:function(a){this.text=a;this.index=0;this.ch=s;for(this.tokens=[];this.index<this.text.length;)if(this.ch=this.text.charAt(this.index),
this.is("\"'"))this.readString(this.ch);else if(this.isNumber(this.ch)||this.is(".")&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(this.ch))this.readIdent();else if(this.is("(){}[].,;:?"))this.tokens.push({index:this.index,text:this.ch}),this.index++;else if(this.isWhitespace(this.ch))this.index++;else{a=this.ch+this.peek();var c=a+this.peek(2),d=Wb[this.ch],e=Wb[a],f=Wb[c];f?(this.tokens.push({index:this.index,text:c,fn:f}),this.index+=3):e?(this.tokens.push({index:this.index,
text:a,fn:e}),this.index+=2):d?(this.tokens.push({index:this.index,text:this.ch,fn:d}),this.index+=1):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a){return-1!==a.indexOf(this.ch)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=
a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=B(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw oa("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=P(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&
e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}a*=1;this.tokens.push({index:c,text:a,constant:!0,fn:function(){return a}})},readIdent:function(){for(var a=this.text,c="",d=this.index,e,f,g,h;this.index<this.text.length;){h=this.text.charAt(this.index);if("."===h||this.isIdent(h)||this.isNumber(h))"."===h&&(e=this.index),c+=h;else break;this.index++}e&&"."===
c[c.length-1]&&(this.index--,c=c.slice(0,-1),e=c.lastIndexOf("."),-1===e&&(e=s));if(e)for(f=this.index;f<this.text.length;){h=this.text.charAt(f);if("("===h){g=c.substr(e-d+1);c=c.substr(0,e-d);this.index=f;break}if(this.isWhitespace(h))f++;else break}this.tokens.push({index:d,text:c,fn:hd[c]||Pc(c,this.options,a)});g&&(this.tokens.push({index:e,text:"."}),this.tokens.push({index:e+1,text:g}))},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,f=!1;this.index<this.text.length;){var g=
this.text.charAt(this.index),e=e+g;if(f)"u"===g?(f=this.text.substring(this.index+1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d+=zf[g]||g,f=!1;else if("\\"===g)f=!0;else{if(g===a){this.index++;this.tokens.push({index:c,text:e,string:d,constant:!0,fn:function(){return d}});return}d+=g}this.index++}this.throwError("Unterminated quote",c)}};var Za=function(a,c,d){this.lexer=a;this.$filter=c;this.options=
d};Za.ZERO=E(function(){return 0},{sharedGetter:!0,constant:!0});Za.prototype={constructor:Za,parse:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.statements();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);a.literal=!!a.literal;a.constant=!!a.constant;return a},primary:function(){var a;if(this.expect("("))a=this.filterChain(),this.consume(")");else if(this.expect("["))a=this.arrayDeclaration();else if(this.expect("{"))a=this.object();else{var c=this.expect();
(a=c.fn)||this.throwError("not a primary expression",c);c.constant&&(a.constant=!0,a.literal=!0)}for(var d;c=this.expect("(","[",".");)"("===c.text?(a=this.functionCall(a,d),d=null):"["===c.text?(d=a,a=this.objectIndex(a)):"."===c.text?(d=a,a=this.fieldAccess(a)):this.throwError("IMPOSSIBLE");return a},throwError:function(a,c){throw oa("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},peekToken:function(){if(0===this.tokens.length)throw oa("ueoe",this.text);return this.tokens[0]},
peek:function(a,c,d,e){if(0<this.tokens.length){var f=this.tokens[0],g=f.text;if(g===a||g===c||g===d||g===e||!(a||c||d||e))return f}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},consume:function(a){this.expect(a)||this.throwError("is unexpected, expecting ["+a+"]",this.peek())},unaryFn:function(a,c){return E(function(d,e){return a(d,e,c)},{constant:c.constant})},ternaryFn:function(a,c,d){return E(function(e,f){return a(e,f)?c(e,f):d(e,f)},{constant:a.constant&&
c.constant&&d.constant})},binaryFn:function(a,c,d){return E(function(e,f){return c(e,f,a,d)},{constant:a.constant&&d.constant})},statements:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.filterChain()),!this.expect(";"))return 1===a.length?a[0]:function(c,d){for(var e,f=0,g=a.length;f<g;f++)e=a[f](c,d);return e}},filterChain:function(){for(var a=this.expression(),c;c=this.expect("|");)a=this.binaryFn(a,c.fn,this.filter());return a},filter:function(){var a=
this.expect(),c=this.$filter(a.text),d,e;if(this.peek(":"))for(d=[],e=[];this.expect(":");)d.push(this.expression());return da(function(a,g,h){if(e){e[0]=h;for(h=d.length;h--;)e[h+1]=d[h](a,g);return c.apply(s,e)}return c(h)})},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary(),c,d;return(d=this.expect("="))?(a.assign||this.throwError("implies assignment but ["+this.text.substring(0,d.index)+"] can not be assigned to",d),c=this.ternary(),function(d,f){return a.assign(d,
c(d,f),f)}):a},ternary:function(){var a=this.logicalOR(),c,d;if(this.expect("?")){c=this.assignment();if(d=this.expect(":"))return this.ternaryFn(a,c,this.assignment());this.throwError("expected :",d)}else return a},logicalOR:function(){for(var a=this.logicalAND(),c;c=this.expect("||");)a=this.binaryFn(a,c.fn,this.logicalAND());return a},logicalAND:function(){var a=this.equality(),c;if(c=this.expect("&&"))a=this.binaryFn(a,c.fn,this.logicalAND());return a},equality:function(){var a=this.relational(),
c;if(c=this.expect("==","!=","===","!=="))a=this.binaryFn(a,c.fn,this.equality());return a},relational:function(){var a=this.additive(),c;if(c=this.expect("<",">","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a;
return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(Za.ZERO,a.fn,this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this.text,d=this.expect().text,e=Pc(d,this.options,c);return E(function(c,d,h){return e(h||a(c,d))},{assign:function(e,g,h){(h=a(e,h))||a.assign(e,h={});return ub(h,d,g,c)}})},objectIndex:function(a){var c=this.text,d=this.expression();this.consume("]");return E(function(e,f){var g=a(e,f),h=d(e,f);na(h,
c);return g?Ba(g[h],c):s},{assign:function(e,f,g){var h=na(d(e,g),c);(g=Ba(a(e,g),c))||a.assign(e,g={});return g[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this.text,f=d.length?[]:null;return function(g,h){var m=c?c(g,h):g,k=a(g,h,m)||w;if(f)for(var n=d.length;n--;)f[n]=Ba(d[n](g,h),e);Ba(m,e);if(k){if(k.constructor===k)throw oa("isecfn",e);if(k===wf||k===xf||k===yf)throw oa("isecff",e);
}m=k.apply?k.apply(m,f):k(f[0],f[1],f[2],f[3],f[4]);return Ba(m,e)}},arrayDeclaration:function(){var a=[],c=!0;if("]"!==this.peekToken().text){do{if(this.peek("]"))break;var d=this.expression();a.push(d);d.constant||(c=!1)}while(this.expect(","))}this.consume("]");return E(function(c,d){for(var g=[],h=0,m=a.length;h<m;h++)g.push(a[h](c,d));return g},{literal:!0,constant:c})},object:function(){var a=[],c=!0;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;var d=this.expect(),d=d.string||
d.text;this.consume(":");var e=this.expression();a.push({key:d,value:e});e.constant||(c=!1)}while(this.expect(","))}this.consume("}");return E(function(c,d){for(var e={},m=0,k=a.length;m<k;m++){var n=a[m];e[n.key]=n.value(c,d)}return e},{literal:!0,constant:c})}};var Qc=Object.create(null),Ca=K("$sce"),ka={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},ja=K("$compile"),Z=Y.createElement("a"),Tc=Aa(t.location.href,!0);qc.$inject=["$provide"];Uc.$inject=["$locale"];Wc.$inject=["$locale"];
var Zc=".",nf={yyyy:$("FullYear",4),yy:$("FullYear",2,0,!0),y:$("FullYear",1),MMMM:wb("Month"),MMM:wb("Month",!0),MM:$("Month",2,1),M:$("Month",1,1),dd:$("Date",2),d:$("Date",1),HH:$("Hours",2),H:$("Hours",1),hh:$("Hours",2,-12),h:$("Hours",1,-12),mm:$("Minutes",2),m:$("Minutes",1),ss:$("Seconds",2),s:$("Seconds",1),sss:$("Milliseconds",3),EEEE:wb("Day"),EEE:wb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(vb(Math[0<
a?"floor":"ceil"](a/60),2)+vb(Math.abs(a%60),2))},ww:ad(2),w:ad(1)},mf=/((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,lf=/^\-?\d+$/;Vc.$inject=["$locale"];var jf=da(P),kf=da(ib);Xc.$inject=["$parse"];var Fd=da({restrict:"E",compile:function(a,c){8>=X&&(c.href||c.name||c.$set("href",""),a.append(Y.createComment("IE fix")));if(!c.href&&!c.xlinkHref&&!c.name)return function(a,c){var f="[object SVGAnimatedString]"===Ga.call(c.prop("href"))?"xlink:href":"href";c.on("click",
function(a){c.attr(f)||a.preventDefault()})}}}),kb={};r(qb,function(a,c){if("multiple"!=a){var d=va("ng-"+c);kb[d]=function(){return{restrict:"A",priority:100,link:function(a,f,g){a.$watch(g[d],function(a){g.$set(c,!!a)})}}}}});r(Ac,function(a,c){kb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(rf))){f.$set("ngPattern",new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});r(["src","srcset","href"],function(a){var c=
va("ng-"+a);kb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===Ga.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href",g=null);f.$observe(c,function(c){c?(f.$set(h,c),X&&g&&e.prop(g,f[h])):"href"===a&&f.$set(h,null)})}}}});var xb={$addControl:w,$removeControl:w,$setValidity:w,$$setPending:w,$setDirty:w,$setPristine:w,$setSubmitted:w,$$clearControlValidity:w};bd.$inject=["$element","$attrs","$scope","$animate"];var id=function(a){return["$timeout",
function(c){return{name:"form",restrict:a?"EAC":"E",controller:bd,compile:function(){return{pre:function(a,e,f,g){if(!f.action){var h=function(c){a.$apply(function(){g.$commitViewValue();g.$setSubmitted()});c.preventDefault?c.preventDefault():c.returnValue=!1};e[0].addEventListener("submit",h,!1);e.on("$destroy",function(){c(function(){e[0].removeEventListener("submit",h,!1)},0,!1)})}var m=e.parent().controller("form"),k=f.name||f.ngForm;k&&ub(a,k,g,k);if(m)e.on("$destroy",function(){m.$removeControl(g);
k&&ub(a,k,s,k);E(g,xb)})}}}}}]},Gd=id(),Td=id(!0),of=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,Af=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,Bf=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,Cf=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,jd=/^(\d{4})-(\d{2})-(\d{2})$/,kd=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d))?$/,Xb=/^(\d{4})-W(\d\d)$/,ld=/^(\d{4})-(\d\d)$/,md=/^(\d\d):(\d\d)(?::(\d\d))?$/,
Df=/(\s+|^)default(\s+|$)/,Yb=new K("ngModel"),nd={text:function(a,c,d,e,f,g){$a(a,c,d,e,f,g);Ub(e)},date:ab("date",jd,zb(jd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":ab("datetimelocal",kd,zb(kd,"yyyy MM dd HH mm ss".split(" ")),"yyyy-MM-ddTHH:mm:ss"),time:ab("time",md,zb(md,["HH","mm","ss"]),"HH:mm:ss"),week:ab("week",Xb,function(a){if(fa(a))return a;if(C(a)){Xb.lastIndex=0;var c=Xb.exec(a);if(c){a=+c[1];var d=+c[2],c=$c(a),d=7*(d-1);return new Date(a,0,c.getDate()+d)}}return NaN},"yyyy-Www"),
month:ab("month",ld,zb(ld,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,g){dd(a,c,d,e);$a(a,c,d,e,f,g);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:Cf.test(a)?parseFloat(a):s});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!ea(a))throw Yb("numfmt",a);a=a.toString()}return a});if(d.min||d.ngMin){var h;e.$validators.min=function(a){return e.$isEmpty(a)||F(h)||a>=h};d.$observe("min",function(a){B(a)&&!ea(a)&&(a=parseFloat(a,10));h=ea(a)&&!isNaN(a)?a:s;e.$validate()})}if(d.max||
d.ngMax){var m;e.$validators.max=function(a){return e.$isEmpty(a)||F(m)||a<=m};d.$observe("max",function(a){B(a)&&!ea(a)&&(a=parseFloat(a,10));m=ea(a)&&!isNaN(a)?a:s;e.$validate()})}},url:function(a,c,d,e,f,g){$a(a,c,d,e,f,g);Ub(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||Af.test(d)}},email:function(a,c,d,e,f,g){$a(a,c,d,e,f,g);Ub(e);e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||Bf.test(d)}},radio:function(a,
c,d,e){F(d.name)&&c.attr("name",++bb);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,m){var k=ed(m,a,"ngTrueValue",d.ngTrueValue,!0),n=ed(m,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&&a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==k};e.$formatters.push(function(a){return ra(a,
k)});e.$parsers.push(function(a){return a?k:n})},hidden:w,button:w,submit:w,reset:w,file:w},kc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:function(f,g,h,m){m[0]&&(nd[P(h.type)]||nd.text)(f,g,h,m[0],c,a,d,e)}}}],pf="ng-valid",qf="ng-invalid",Ma="ng-pristine",yb="ng-dirty",gd="ng-pending",Ef=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q",function(a,c,d,e,f,g,h,m,k){this.$modelValue=this.$viewValue=
Number.NaN;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=s;this.$name=d.name;var n=f(d.ngModel),p=null,l=this,q=function(){var c=n(a);l.$options&&l.$options.getterSetter&&D(c)&&(c=c());return c},A=function(c){var d;l.$options&&l.$options.getterSetter&&D(d=n(a))?d(l.$modelValue):n.assign(a,
l.$modelValue)};this.$$setOptions=function(a){l.$options=a;if(!(n.assign||a&&a.getterSetter))throw Yb("nonassign",d.ngModel,ta(e));};this.$render=w;this.$isEmpty=function(a){return F(a)||""===a||null===a||a!==a};var u=e.inheritedData("$formController")||xb,x=0;e.addClass(Ma).addClass("ng-untouched");cd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:u,$animate:g});this.$setPristine=function(){l.$dirty=!1;l.$pristine=!0;g.removeClass(e,yb);g.addClass(e,
Ma)};this.$setUntouched=function(){l.$touched=!1;l.$untouched=!0;g.setClass(e,"ng-untouched","ng-touched")};this.$setTouched=function(){l.$touched=!0;l.$untouched=!1;g.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){h.cancel(p);l.$viewValue=l.$$lastCommittedViewValue;l.$render()};this.$validate=function(){ea(l.$modelValue)&&isNaN(l.$modelValue)||this.$$parseAndValidate()};this.$$runValidators=function(a,c,d,e){function f(){var a=!0;r(l.$validators,function(e,f){var h=e(c,
d);a=a&&h;g(f,h)});return a?!0:(r(l.$asyncValidators,function(a,c){g(c,null)}),m(),!1)}function h(){var a=[];r(l.$asyncValidators,function(e,f){var h=e(c,d);if(!h||!D(h.then))throw Yb("$asyncValidators",h);g(f,s);a.push(h.then(function(){g(f,!0)},function(a){g(f,!1)}))});a.length?k.all(a).then(m):m()}function g(a,c){n===x&&l.$setValidity(a,c)}function m(){n===x&&e()}x++;var n=x;(function(a){var c=l.$$parserName||"parse";if(a===s)g(c,null);else if(g(c,a),!a)return r(l.$validators,function(a,c){g(c,
null)}),r(l.$asyncValidators,function(a,c){g(c,null)}),m(),!1;return!0})(a)&&f()&&h()};this.$commitViewValue=function(){var a=l.$viewValue;h.cancel(p);if(l.$$lastCommittedViewValue!==a||""===a&&l.$$hasNativeValidators)l.$$lastCommittedViewValue=a,l.$pristine&&(l.$dirty=!0,l.$pristine=!1,g.removeClass(e,Ma),g.addClass(e,yb),u.$setDirty()),this.$$parseAndValidate()};this.$$parseAndValidate=function(){for(var a=!0,c=l.$$lastCommittedViewValue,d=c,e=0;e<l.$parsers.length;e++)if(d=l.$parsers[e](d),F(d)){a=
!1;break}ea(l.$modelValue)&&isNaN(l.$modelValue)&&(l.$modelValue=q());var f=l.$modelValue,h=l.$options&&l.$options.allowInvalid;h&&(l.$modelValue=d,l.$modelValue!==f&&l.$$writeModelToScope());l.$$runValidators(a,d,c,function(){h||(l.$modelValue=l.$valid?d:s,l.$modelValue!==f&&l.$$writeModelToScope())})};this.$$writeModelToScope=function(){A(l.$modelValue);r(l.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};this.$setViewValue=function(a,c){l.$viewValue=a;l.$options&&!l.$options.updateOnDefault||
l.$$debounceViewValueCommit(c)};this.$$debounceViewValueCommit=function(c){var d=0,e=l.$options;e&&B(e.debounce)&&(e=e.debounce,ea(e)?d=e:ea(e[c])?d=e[c]:ea(e["default"])&&(d=e["default"]));h.cancel(p);d?p=h(function(){l.$commitViewValue()},d):m.$$phase?l.$commitViewValue():a.$apply(function(){l.$commitViewValue()})};a.$watch(function(){var a=q();if(a!==l.$modelValue){l.$modelValue=a;for(var c=l.$formatters,d=c.length,e=a;d--;)e=c[d](e);l.$viewValue!==e&&(l.$viewValue=l.$$lastCommittedViewValue=e,
l.$render(),l.$$runValidators(s,a,e,w))}return a})}],he=function(){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Ef,link:{pre:function(a,c,d,e){var f=e[0],g=e[1]||xb;f.$$setOptions(e[2]&&e[2].$options);g.$addControl(f);a.$on("$destroy",function(){g.$removeControl(f)})},post:function(a,c,d,e){var f=e[0];if(f.$options&&f.$options.updateOn)c.on(f.$options.updateOn,function(a){f.$$debounceViewValueCommit(a&&a.type)});c.on("blur",function(c){f.$touched||a.$apply(function(){f.$setTouched()})})}}}},
je=da({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),mc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){e&&(d.required=!0,e.$validators.required=function(a,c){return!d.required||!e.$isEmpty(c)},d.$observe("required",function(){e.$validate()}))}}},lc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f,g=d.ngPattern||d.pattern;d.$observe("pattern",function(a){C(a)&&0<a.length&&
(a=new RegExp(a));if(a&&!a.test)throw K("ngPattern")("noregexp",g,a,ta(c));f=a||s;e.$validate()});e.$validators.pattern=function(a){return e.$isEmpty(a)||F(f)||f.test(a)}}}}},oc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("maxlength",function(a){f=U(a)||0;e.$validate()});e.$validators.maxlength=function(a,c){return e.$isEmpty(c)||c.length<=f}}}}},nc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",
function(a){f=U(a)||0;e.$validate()});e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}},ie=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?ba(f):f;e.$parsers.push(function(a){if(!F(a)){var c=[];a&&r(a.split(h),function(a){a&&c.push(g?ba(a):a)});return c}});e.$formatters.push(function(a){return L(a)?a.join(f):s});e.$isEmpty=function(a){return!a||!a.length}}}},Ff=/^(true|false|\d+)$/,
ke=function(){return{restrict:"A",priority:100,compile:function(a,c){return Ff.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},le=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,c){var d=this;this.$options=a.$eval(c.ngModelOptions);this.$options.updateOn!==s?(this.$options.updateOnDefault=!1,this.$options.updateOn=ba(this.$options.updateOn.replace(Df,function(){d.$options.updateOnDefault=
!0;return" "}))):this.$options.updateOnDefault=!0}]}},Ld=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);c.$watch(f.ngBind,function(a){e.text(a==s?"":a)})}}}}],Nd=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate));c.$$addBindingInfo(f,d.expressions);g.$observe("ngBindTemplate",function(a){f.text(a)})}}}}],Md=
["$sce","$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],Od=Vb("",!0),Qd=Vb("Odd",0),Pd=Vb("Even",1),Rd=Fa({compile:function(a,c){c.$set("ngCloak",s);a.removeClass("ng-cloak")}}),Sd=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],
pc={},Gf={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=va("ng-"+a);pc[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h=d(g[c]);return function(c,d){var f=P(a);d.on(f,function(a){var d=function(){h(c,{$event:a})};Gf[f]&&e.$$phase?c.$evalAsync(d):c.$apply(d)})}}}}]});var Vd=["$animate",function(a){return{multiElement:!0,
transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,m,k;c.$watch(e.ngIf,function(c){c?m||g(function(c,f){m=f;c[c.length++]=Y.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),m&&(m.$destroy(),m=null),h&&(k=hb(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],Wd=["$templateRequest","$anchorScroll","$animate","$sce",function(a,c,d,e){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",
controller:Ea.noop,compile:function(f,g){var h=g.ngInclude||g.src,m=g.onload||"",k=g.autoscroll;return function(f,g,l,q,r){var u=0,s,z,t,v=function(){z&&(z.remove(),z=null);s&&(s.$destroy(),s=null);t&&(d.leave(t).then(function(){z=null}),z=t,t=null)};f.$watch(e.parseAsResourceUrl(h),function(e){var h=function(){!B(k)||k&&!f.$eval(k)||c()},l=++u;e?(a(e,!0).then(function(a){if(l===u){var c=f.$new();q.template=a;a=r(c,function(a){v();d.enter(a,null,g).then(h)});s=c;t=a;s.$emit("$includeContentLoaded");
f.$eval(m)}},function(){l===u&&(v(),f.$emit("$includeContentError"))}),f.$emit("$includeContentRequested")):(v(),q.template=null)})}}}}],me=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(sc(f.template,Y).childNodes)(c,function(a){d.append(a)},s,s,d)):(d.html(f.template),a(d.contents())(c))}}}],Xd=Fa({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Yd=Fa({terminal:!0,priority:1E3}),
Zd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,f,g){var h=g.count,m=g.$attr.when&&f.attr(g.$attr.when),k=g.offset||0,n=e.$eval(m)||{},p={},l=c.startSymbol(),q=c.endSymbol(),s=/^when(Minus)?(.+)$/;r(g,function(a,c){s.test(c)&&(n[P(c.replace("when","").replace("Minus","-"))]=f.attr(g.$attr[c]))});r(n,function(a,e){p[e]=c(a.replace(d,l+h+"-"+k+q))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in n||(c=a.pluralCat(c-k));return p[c](e)},
function(a){f.text(a)})}}}],$d=["$parse","$animate",function(a,c){var d=K("ngRepeat"),e=function(a,c,d,e,k,n,p){a[d]=e;k&&(a[k]=n);a.$index=c;a.$first=0===c;a.$last=c===p-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(c&1))};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,g){var h=g.ngRepeat,m=Y.createComment(" end ngRepeat: "+h+" "),k=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
if(!k)throw d("iexp",h);var n=k[1],p=k[2],l=k[3],q=k[4],k=n.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!k)throw d("iidexp",n);var A=k[3]||k[1],u=k[2];if(l&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(l)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(l)))throw d("badident",l);var t,z,B,v,w={$id:Ka};q?t=a(q):(B=function(a,c){return Ka(c)},v=function(a){return a});return function(a,f,g,k,n){t&&(z=function(c,d,e){u&&(w[u]=c);w[A]=d;w.$index=e;return t(a,w)});
var q=Object.create(null);a.$watchCollection(p,function(g){var k,p,t=f[0],J,x=Object.create(null),w,N,E,F,y,C,ga;l&&(a[l]=g);if(Na(g))y=g,p=z||B;else{p=z||v;y=[];for(ga in g)g.hasOwnProperty(ga)&&"$"!=ga.charAt(0)&&y.push(ga);y.sort()}w=y.length;ga=Array(w);for(k=0;k<w;k++)if(N=g===y?k:y[k],E=g[N],F=p(N,E,k),q[F])C=q[F],delete q[F],x[F]=C,ga[k]=C;else{if(x[F])throw r(ga,function(a){a&&a.scope&&(q[a.id]=a)}),d("dupes",h,F,sa(E));ga[k]={id:F,scope:s,clone:s};x[F]=!0}for(J in q){C=q[J];F=hb(C.clone);
c.leave(F);if(F[0].parentNode)for(k=0,p=F.length;k<p;k++)F[k].$$NG_REMOVED=!0;C.scope.$destroy()}for(k=0;k<w;k++)if(N=g===y?k:y[k],E=g[N],C=ga[k],C.scope){J=t;do J=J.nextSibling;while(J&&J.$$NG_REMOVED);C.clone[0]!=J&&c.move(hb(C.clone),null,G(t));t=C.clone[C.clone.length-1];e(C.scope,k,A,E,u,N,w)}else n(function(a,d){C.scope=d;var f=m.cloneNode(!1);a[a.length++]=f;c.enter(a,null,G(t));t=f;C.clone=a;x[C.id]=C;e(C.scope,k,A,E,u,N,w)});q=x})}}}}],ae=["$animate",function(a){return{restrict:"A",multiElement:!0,
link:function(c,d,e){c.$watch(e.ngShow,function(c){a[c?"removeClass":"addClass"](d,"ng-hide")})}}}],Ud=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(c,d,e){c.$watch(e.ngHide,function(c){a[c?"addClass":"removeClass"](d,"ng-hide")})}}}],be=Fa(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&r(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),ce=["$animate",function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,
d,e,f){var g=[],h=[],m=[],k=[],n=function(a,c){return function(){a.splice(c,1)}};c.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=m.length;d<e;++d)a.cancel(m[d]);d=m.length=0;for(e=k.length;d<e;++d){var s=hb(h[d].clone);k[d].$destroy();(m[d]=a.leave(s)).then(n(m,d))}h.length=0;k.length=0;(g=f.cases["!"+c]||f.cases["?"])&&r(g,function(c){c.transclude(function(d,e){k.push(e);var f=c.element;d[d.length++]=Y.createComment(" end ngSwitchWhen: ");h.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],
de=Fa({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,c,d,e,f){e.cases["!"+d.ngSwitchWhen]=e.cases["!"+d.ngSwitchWhen]||[];e.cases["!"+d.ngSwitchWhen].push({transclude:f,element:c})}}),ee=Fa({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,c,d,e,f){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:f,element:c})}}),ge=Fa({restrict:"EAC",link:function(a,c,d,e,f){if(!f)throw K("ngTransclude")("orphan",ta(c));f(function(a){c.empty();
c.append(a)})}}),Hd=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],Hf=K("ngOptions"),fe=da({restrict:"A",terminal:!0}),Id=["$compile","$parse",function(a,c){var d=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,e={$setViewValue:w};return{restrict:"E",require:["select",
"?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var m=this,k={},n=e,p;m.databound=d.ngModel;m.init=function(a,c,d){n=a;p=d};m.addOption=function(c,d){Ja(c,'"option value"');k[c]=!0;n.$viewValue==c&&(a.val(c),p.parent()&&p.remove());d[0].hasAttribute("selected")&&(d[0].selected=!0)};m.removeOption=function(a){this.hasOption(a)&&(delete k[a],n.$viewValue==a&&this.renderUnknownOption(a))};m.renderUnknownOption=function(c){c="? "+Ka(c)+" ?";p.val(c);a.prepend(p);a.val(c);p.prop("selected",
!0)};m.hasOption=function(a){return k.hasOwnProperty(a)};c.$on("$destroy",function(){m.renderUnknownOption=w})}],link:function(e,g,h,m){function k(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(y.parent()&&y.remove(),c.val(a),""===a&&w.prop("selected",!0)):F(a)&&w?c.val(""):e.renderUnknownOption(a)};c.on("change",function(){a.$apply(function(){y.parent()&&y.remove();d.$setViewValue(c.val())})})}function n(a,c,d){var e;d.$render=function(){var a=new Xa(d.$viewValue);r(c.find("option"),
function(c){c.selected=B(a.get(c.value))})};a.$watch(function(){ra(e,d.$viewValue)||(e=qa(d.$viewValue),d.$render())});c.on("change",function(){a.$apply(function(){var a=[];r(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function p(e,f,h){function g(){z||(e.$$postDigest(k),z=!0)}function k(){z=!1;var a={"":[]},c=[""],d,g,l,s,t;l=h.$modelValue;s=F(e)||[];var A=p?Zb(s):s,G,y,D;y={};D=!1;if(q)if(g=h.$modelValue,x&&L(g))for(D=new Xa([]),d={},t=0;t<g.length;t++)d[n]=
g[t],D.put(x(e,d),g[t]);else D=new Xa(g);t=D;var H,K;for(D=0;G=A.length,D<G;D++){g=D;if(p){g=A[D];if("$"===g.charAt(0))continue;y[p]=g}y[n]=s[g];d=r(e,y)||"";(g=a[d])||(g=a[d]=[],c.push(d));q?d=B(t.remove(x?x(e,y):w(e,y))):(x?(d={},d[n]=l,d=x(e,d)===x(e,y)):d=l===w(e,y),t=t||d);H=m(e,y);H=B(H)?H:"";g.push({id:x?x(e,y):p?A[D]:D,label:H,selected:d})}q||(u||null===l?a[""].unshift({id:"",label:"",selected:!t}):t||a[""].unshift({id:"?",label:"",selected:!0}));y=0;for(A=c.length;y<A;y++){d=c[y];g=a[d];
E.length<=y?(l={element:v.clone().attr("label",d),label:g.label},s=[l],E.push(s),f.append(l.element)):(s=E[y],l=s[0],l.label!=d&&l.element.attr("label",l.label=d));H=null;D=0;for(G=g.length;D<G;D++)d=g[D],(t=s[D+1])?(H=t.element,t.label!==d.label&&H.text(t.label=d.label),t.id!==d.id&&H.val(t.id=d.id),H[0].selected!==d.selected&&(H.prop("selected",t.selected=d.selected),X&&H.prop("selected",t.selected))):(""===d.id&&u?K=u:(K=C.clone()).val(d.id).prop("selected",d.selected).attr("selected",d.selected).text(d.label),
s.push({element:K,label:d.label,id:d.id,selected:d.selected}),H?H.after(K):l.element.append(K),H=K);for(D++;s.length>D;)s.pop().element.remove()}for(;E.length>y;)E.pop()[0].element.remove()}var l;if(!(l=t.match(d)))throw Hf("iexp",t,ta(f));var m=c(l[2]||l[1]),n=l[4]||l[6],p=l[5],r=c(l[3]||""),w=c(l[2]?l[1]:n),F=c(l[7]),x=l[8]?c(l[8]):null,E=[[{element:f,label:""}]];u&&(a(u)(e),u.removeClass("ng-scope"),u.remove());f.empty();f.on("change",function(){e.$apply(function(){var a,c=F(e)||[],d={},g,l,m,
r,t,u,v;if(q)for(l=[],r=0,u=E.length;r<u;r++)for(a=E[r],m=1,t=a.length;m<t;m++){if((g=a[m].element)[0].selected){g=g.val();p&&(d[p]=g);if(x)for(v=0;v<c.length&&(d[n]=c[v],x(e,d)!=g);v++);else d[n]=c[g];l.push(w(e,d))}}else if(g=f.val(),"?"==g)l=s;else if(""===g)l=null;else if(x)for(v=0;v<c.length;v++){if(d[n]=c[v],x(e,d)==g){l=w(e,d);break}}else d[n]=c[g],p&&(d[p]=g),l=w(e,d);h.$setViewValue(l);k()})});h.$render=k;e.$watchCollection(F,g);q&&e.$watchCollection(function(){return h.$modelValue},g)}if(m[1]){var l=
m[0];m=m[1];var q=h.multiple,t=h.ngOptions,u=!1,w,z=!1,C=G(Y.createElement("option")),v=G(Y.createElement("optgroup")),y=C.clone();h=0;for(var E=g.children(),D=E.length;h<D;h++)if(""===E[h].value){w=u=E.eq(h);break}l.init(m,u,y);q&&(m.$isEmpty=function(a){return!a||0===a.length});t?p(e,g,m):q?n(e,g,m):k(e,g,m,l)}}}}],Kd=["$interpolate",function(a){var c={addOption:w,removeOption:w};return{restrict:"E",priority:100,compile:function(d,e){if(F(e.value)){var f=a(d.text(),!0);f||e.$set("value",d.text())}return function(a,
d,e){var k=d.parent(),n=k.data("$selectController")||k.parent().data("$selectController");n&&n.databound?d.prop("selected",!1):n=c;f?a.$watch(f,function(a,c){e.$set("value",a);c!==a&&n.removeOption(c);n.addOption(a,d)}):n.addOption(e.value,d);d.on("$destroy",function(){n.removeOption(e.value)})}}}}],Jd=da({restrict:"E",terminal:!1});t.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(zd(),Bd(Ea),G(Y).ready(function(){vd(Y,gc)}))})(window,document);
!window.angular.$$csp()&&window.angular.element(document).find("head").prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-animate){display:none !important;}ng\\:form{display:block;}</style>');
//# sourceMappingURL=angular.min.js.map

117
webclient/js/autofill-event.js Executable file
View File

@@ -0,0 +1,117 @@
/**
* Autofill event polyfill ##version:1.0.0##
* (c) 2014 Google, Inc.
* License: MIT
*/
(function(window) {
var $ = window.jQuery || window.angular.element;
var rootElement = window.document.documentElement,
$rootElement = $(rootElement);
addGlobalEventListener('change', markValue);
addValueChangeByJsListener(markValue);
$.prototype.checkAndTriggerAutoFillEvent = jqCheckAndTriggerAutoFillEvent;
// Need to use blur and not change event
// as Chrome does not fire change events in all cases an input is changed
// (e.g. when starting to type and then finish the input by auto filling a username)
addGlobalEventListener('blur', function(target) {
// setTimeout needed for Chrome as it fills other
// form fields a little later...
window.setTimeout(function() {
findParentForm(target).find('input').checkAndTriggerAutoFillEvent();
}, 20);
});
window.document.addEventListener('DOMContentLoaded', function() {
// The timeout is needed for Chrome as it auto fills
// login forms some time after DOMContentLoaded!
window.setTimeout(function() {
$rootElement.find('input').checkAndTriggerAutoFillEvent();
}, 200);
}, false);
return;
// ----------
function jqCheckAndTriggerAutoFillEvent() {
var i, el;
for (i=0; i<this.length; i++) {
el = this[i];
if (!valueMarked(el)) {
markValue(el);
triggerChangeEvent(el);
}
}
}
function valueMarked(el) {
var val = el.value,
$$currentValue = el.$$currentValue;
if (!val && !$$currentValue) {
return true;
}
return val === $$currentValue;
}
function markValue(el) {
el.$$currentValue = el.value;
}
function addValueChangeByJsListener(listener) {
var jq = window.jQuery || window.angular.element,
jqProto = jq.prototype;
var _val = jqProto.val;
jqProto.val = function(newValue) {
var res = _val.apply(this, arguments);
if (arguments.length > 0) {
forEach(this, function(el) {
listener(el, newValue);
});
}
return res;
}
}
function addGlobalEventListener(eventName, listener) {
// Use a capturing event listener so that
// we also get the event when it's stopped!
// Also, the blur event does not bubble.
rootElement.addEventListener(eventName, onEvent, true);
function onEvent(event) {
var target = event.target;
listener(target);
}
}
function findParentForm(el) {
while (el) {
if (el.nodeName === 'FORM') {
return $(el);
}
el = el.parentNode;
}
return $();
}
function forEach(arr, listener) {
if (arr.forEach) {
return arr.forEach(listener);
}
var i;
for (i=0; i<arr.length; i++) {
listener(arr[i]);
}
}
function triggerChangeEvent(element) {
var doc = window.document;
var event = doc.createEvent("HTMLEvents");
event.initEvent("change", true, true);
element.dispatchEvent(event);
}
})(window);

BIN
webclient/media/busy.mp3 Normal file

Binary file not shown.

BIN
webclient/media/busy.ogg Normal file

Binary file not shown.

BIN
webclient/media/callend.mp3 Normal file

Binary file not shown.

BIN
webclient/media/callend.ogg Normal file

Binary file not shown.

BIN
webclient/media/ring.mp3 Normal file

Binary file not shown.

BIN
webclient/media/ring.ogg Normal file

Binary file not shown.

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