diff --git a/.dockerignore b/.dockerignore index 7dbd3fc52..f1ff7f3e0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ .meteor/local packages/*/.build* packages/*/.npm* - +node_modules/* +.git/* diff --git a/.gitignore b/.gitignore index 53bac4059..f8538912d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .vscode node_modules .eslintrc.js +*.keystore diff --git a/.meteor/cordova-plugins b/.meteor/cordova-plugins new file mode 100644 index 000000000..19471b59c --- /dev/null +++ b/.meteor/cordova-plugins @@ -0,0 +1,43 @@ +com.leon.cordova.wechat@https://github.com/PluginCordova/wechat-sharpai/tarball/097dfb6bd49de3d1e91f38147ecf61295a7ea07f +com.moya.imagebase64@https://github.com/PluginCordova/imagebase64/tarball/1ca00955ab6e0de1bd7f7130bd5de4568570ba51 +com.raananw.imageResizer@https://github.com/PluginCordova/imageresizer/tarball/6cd03b21069ee4d2166b8701cbfd69b7f1244c24 +com.share.wechatShare@https://github.com/PluginCordova/wechatShare-sharpai/tarball/f60e70855122cb4e959fb4b5874a889fc00305ea +com.synconset.imagepicker@https://github.com/PluginCordova/imagepicker-sharpai/tarball/333e4ea541d2cf7b316ab085cb1b4736e6e749ca +com.verso.cordova.clipboard@https://github.com/PluginCordova/clipboard/tarball/5ca018f3ccba6b6460ac556ac8520556371c5028 +com.wordsbaking.cordova.tts@https://github.com/PluginCordova/cordova-plugin-tts/tarball/2d556f109a54c894c43579b944ba879a46354c74 +cordova-plugin-actionsheet@https://github.com/PluginCordova/cordova-plugin-actionsheet/tarball/e57d0e8b1769af20aea91dc05bcbab937b48f6f9 +cordova-plugin-apprate@https://github.com/PluginCordova/apprate/tarball/b318cabf92aec3ed1b0d90c7803a5ff07c75ae4a +cordova-plugin-camera@https://github.com/PluginCordova/cordova-plugin-camera/tarball/394a5b1dbf9de3a76f64127acaf8366e4cf82920 +cordova-plugin-crop@https://github.com/PluginCordova/cordova-plugin-crop/tarball/dfa847173a9c1d449a20004f7baf5c0e8ba47bf2 +cordova-plugin-fastrde-mqtt@https://github.com/PluginCordova/cordova-plugin-fastrde-mqtt/tarball/b7e1e5ca1b93f40cef522845c747d02b3cc84022 +cordova-plugin-device@1.1.1 +cordova-plugin-device-motion@1.2.0 +cordova-plugin-dialogs@1.2.0 +cordova-plugin-file@4.1.1 +cordova-plugin-file-transfer@1.6.0 +cordova-plugin-http@1.2.0 +cordova-plugin-inappbrowser@https://github.com/PluginCordova/inappbrowser/tarball/61e7d527d8b381e0e76277ee385823151f20c90a +cordova-plugin-iosaudiopicker@https://github.com/PluginCordova/iosaudiopicker/tarball/c0ed773bc6b6de8211466d136e1992e880b25812 +cordova-plugin-jcore@1.1.12 +cordova-plugin-keyboard@1.1.5 +cordova-plugin-media@https://github.com/PluginCordova/media/tarball/2d158b0ef7173cad2b2f84c05c62727932635f8e +cordova-plugin-meteor-webapp@https://github.com/panter/cordova-plugin-meteor-webapp.git#7bb095ce393d17f662cc6bb596ce91acf743d17a +cordova-plugin-network-information@1.2.0 +cordova-plugin-splashscreen@https://github.com/PluginCordova/sharpAiSplashScreen/tarball/d23b8a319c67a2ebcf858806bf9800224fd7deb3 +cordova-plugin-statusbar@https://github.com/PluginCordova/cordova-plugin-statusbar/tarball/995e4fdf9de78609ff0fb30bab2610b9e5293410 +cordova-plugin-themeablebrowser@https://github.com/PluginCordova/cordova-plugin-themeablebrowser/tarball/12117b16548e0ad309280c2a4b8c82cf313e2693 +cordova-plugin-wkwebview-engine@1.1.0 +cordova-plugin-x-socialsharing@https://github.com/PluginCordova/SocialSharing-PhoneGap-Plugin/tarball/22a83973a1ca46265a96197e29195c899223c7d1 +cordova-plugin-zeroconf@https://github.com/PluginCordova/cordova-plugin-zeroconf/tarball/a6cc00ad27e518213201aefcaee0e1751fe28952 +ionic-plugin-keyboard@2.2.1 +jpush-phonegap-plugin@3.3.2 +meteor-ios10-csp-fix@0.1.0 +nl.x-services.plugins.toast@2.5.2 +org.apache.cordova.plugin.tts@https://github.com/PluginCordova/tts/tarball/befd838419d210fc7d85f171fabb243734b546a9 +org.hotshare.customDialog@https://github.com/PluginCordova/customDialog/tarball/3faf6ccfbede32c85a47c91dd2e0cf257243ba30 +org.hotshare.shareExtension@https://github.com/PluginCordova/share-extension/tarball/2cde17f067b5856d8261f0e5154ca64ae2ec2a0b +org.hotshare.userinfo@https://github.com/PluginCordova/userInfo/tarball/1f7cee2f71cefb88a272186c28f9707f8b71c97c +org.sharpai.everywhere.appsetup@https://github.com/lorriexingfang/sharpAiAppSetUp/tarball/35463af6441eecc61ac949d6c77c069549f74051 +org.zy.yuancheng.qq@https://github.com/PluginCordova/qq/tarball/9fccd8e93e2a75ea8bb2ad887ad84f1d32dbc52b +phonegap-plugin-barcodescanner@https://github.com/PluginCordova/phonegap-plugin-barcodescanner/tarball/a2bcdecc133bfacf7caa003c234ea1d383db674a +phonegap-plugin-push@1.8.2 diff --git a/.meteor/packages b/.meteor/packages index 1b893aee7..4e009cdf9 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -63,7 +63,7 @@ shell-server nicolaslopezj:excel-export feiwu:simple-chat ground:db -sraita:echarts@3.0.0 +sraita:echarts@0.0.1 matb33:collection-hooks video-analysis@1.0.0 momentjs:moment diff --git a/.meteor/platforms b/.meteor/platforms index efeba1b50..8a3a35f9f 100644 --- a/.meteor/platforms +++ b/.meteor/platforms @@ -1,2 +1,2 @@ -server browser +server diff --git a/.meteor/versions b/.meteor/versions index 0a3492618..8a7ec243a 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -137,7 +137,7 @@ shell-server@0.2.1 simple:json-routes@2.1.0 spacebars@1.0.13 spacebars-compiler@1.0.13 -sraita:echarts@3.0.0 +sraita:echarts@0.0.1 srp@1.0.9 standard-minifier-css@1.2.1 standard-minifier-js@1.2.0_1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..6f22976c4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +sudo: required +language: shell +services: docker +script: + - docker build -f Dockerfile.run . diff --git a/API.md b/API.md new file mode 100644 index 000000000..a86bec23b --- /dev/null +++ b/API.md @@ -0,0 +1,552 @@ + + +# App Server API + +## Content + +- [Register](#Register) +- [Login](#Login) +- [Exit](#Exit) +- [Person](#person) + - [Query persons](#QueryPersons) + - [Get person](#GetPerson) + - [Rename person](#RenamePerson) + - [Delete person](#DeletePerson) + - [Delete Label of person pic](#Delete_Labelled_Person_Pic) +- [Device](#Device) + - [Query Device](#Query_Device) + - [Delete Device](#Delete_Device) +- [Group](#group) + - [Query Group](#Query_Group) + - [Create Group](#Create_Group) + - [Rename Group](#Rename_Group) + - [Delete Group](#Delete_Group) + - [Get Group Members Info](#Get_Group_Members_Info) + - [Add User](#Add_User) + - [Add Device](#Add_Device) + - [Label](#Label) + - [Batch Label](#Batch_Label) + - [Label Unknown](#Label_Unknown) +- [AI Messages](#ai-message) + - [Query Messages](#Query_Messages) + +--------- +### Register +``` +POST /api/v1/sign-up +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| username | string | yes | username | +| email | string | yes | Email address | +| password | string | yes | passowrd length > 6 | + +``` +curl -X POST -H "Content-type: application/json" http://server_address/api/v1/sign-up -d '{"username": "test11", "email": "xxxx@xxx.xx", "password": "xxxxxx"}' +``` +Example respones: + +``` +{ + "success": true +} +``` + + +### Login + +1. Login with username/password then get token +2. Use token with API +3. When finish API calls, use [Quit](#Quit) to delete authToken for security. No expire setup for authToken. + + +``` +POST /api/v1/login +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| username | string | yes | username | +| password | string | yes | password | + +``` +curl -X POST http://server_address/api/v1/login/ -d "username=test&password=password" +``` +Example respones: + +``` +{ + "status": "success", // Login success + "data":{ + "authToken": "qpmW0Vx4RFudmSqjGz0Idqj169pcHNqthQW--3LMtLi", + "userId": "rzMssYa8LAN7iuMe" + } +} +``` +### Exit +Use Exit to delete authToken on server side. If need authToken again, please call [Login](#Login)。 + +``` +POST /api/v1/logout +``` +``` +// X-Auth-Token & X-User-Id gets from login api +curl -X POST -H "X-Auth-Token: qpmW0Vx4RFudmSqjGz0Idqj169pcHNqthQW--3LMtLi" -H "X-User-Id: rzMssYa8LAN7iuMe" http://server_address/api/v1/logout +``` +Example respones: + +``` +{ + "status": "success", // logout success + "data": { + "message": "You've been logged out!" + } +} +``` + + +### Person +### QueryPersons +``` +GET /api/v1/persons +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| groupId | string | yes | Group ID | +| name | string | yes | person name | +| faceId | string | no | face id | + +``` +curl -X GET http://server_address/api/v1/persons?groupId=xxxxx&name=xxx +``` +Example respones: +``` +[ + { + "_id": "02db41813a9513816357444b", + "group_id": "xxx", // Group id + "faceId": "xxxxxxxxxx", // Face id + "url": "http://workaiossqn.tiegushi.com/8a89f902-32d8-11e8-8756-a4caa09c959f", // face image url + "name": "APITEST1", // Labelled person name + "label_times": 2, // Labelled times + "createAt": "2019-03-20T08:33:07.899Z", // Create duration + "updateAt": "2019-03-20T08:33:11.835Z" // Update duration + } +] +``` + +### GetPerson +``` +GET api/v1/persons/:id +``` +``` +curl -X GET http://server_address/api/v1/persons/4d18827a8e888af5e2631821 +``` +Example respones: +``` +{ + "_id": "4d18827a8e888af5e2631821", + "group_id": "816291518ef7a551be6c9223", + "faceId": "de357f6497bce902e48ec9be", + "url": "http://cdn.workaioss.tiegushi.com/3032dc7e-6b23-11e9-a278-78c2c0981ed1", + "name": "眼镜1" +} +``` + +### RenamePerson +``` +// Need authToken +PATCH /api/v1/persons/:id +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| name | string | yes | Label a new person | + +``` +curl -X PATCH -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/persons/xxxx -d '{"name":"test2"}' +``` +Example respones: +``` +{ + "success": true +} +``` + +### DeletePerson +``` +// Need Token +DELETE /api/v1/persons/:id +``` + +``` +curl -X DELETE -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/persons/xxxx +``` +Example respones: +``` +{ + "success": true +} +``` + +### Delete_Labelled_Person_Pic +``` +// Need Token +PUT /api/v1/persons/:personId/faces/deletion +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| faces | Array(object) | yes | person picture to be deleted { faces: [ {id: faceId, url: faceUrl}, ...] } | + +``` +create data.json file +{ + "faces": [ + { + "id": "28D6R16C12005885", // faceId + "url": "http://workaiossqn.tiegushi.co/d25a07c-32d9-11e8-8756-a4caa09c959f", // pic_1 + + }, + { + "id": "28D6R16C12005885", + "url": "http://workaiossqn.tiegushi.co/d25a07c-32d9-11e8-8756-a4caa09c959f", // pic_2 + }, + ...... + ] +} + +curl -X PUT "@data.json" -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/persons/xxxxxx/faces/deletion +``` +Example respones: +``` +{ + "success": true +} +``` +### Device +### QueryDevice +``` +GET /api/v1/devices +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| groupId | string | yes | Group ID | + +``` +curl -X GET http://server_address/api/v1/devices?groupId=xxxxx +``` +Example respones: +``` +[ + { + "uuid": "xxxx", // device uuid + "name": "xxxx", // device display name + "in_out": "inout", // the type of device (in/out/inout) + "groupId": "xxxxxxx", // group id + "createAt": "2019-02-22T03:23:35.904Z", // create duration + "camera_run": false, // camera online status + "islatest": false, // if up to date + "online": true // online status of device + } +] +``` + +### Delete_Device +``` +// Need token +DELETE /api/v1/devices/:uuid +``` + +``` +curl -X DELETE -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/devices/xxxx +``` +Example respones: +``` +{ + "success": true // success +} +``` +### Group +### Query_Group +``` +GET /api/v1/groups +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| groupName | string | yes | group name | +| creator | string | yes | creator of group | + + +``` +curl -X GET http://server_address/api/v1/groups?groupName=xxxxx&creator=xxxx +``` +Example respones: +``` +{ + "_id": "xxxxxxxxxx", + "name": groupName, // group name + "icon": "", + "describe": "", + "create_time": "2019-03-20T06:04:16.569Z", // create duration + "template": {}, + "offsetTimeZone": 8, + "last_text": "", + "last_time": "2019-03-20T06:04:16.569Z", + "barcode": "http://server_address/xxxxx", // group QR code + "creator": { + "id": "xxxxxxxx", // creator id + "name": creator // name of creator + } +} +``` + +### Create_Group +``` +// need authToken +POST /api/v1/groups +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| name | string | yes | group name | + +``` +curl -X POST -H "X-Auth-Token: GMh-1Dtg3909k5IOxJozqhjFQQPDkQ1FtKOtJ2stbq6" -H "X-User-Id: YxbWum7KPTds8Lmi5" http://server_address/api/v1/groups -d "name=xxx" +``` +Example respones: +``` +{ + "groupId": "8b129fc47a3fa97cbd6f7837" // group id +} +``` + +### Rename_Group +``` +// need token +PATCH /api/v1/groups/:id +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| name | string | yes | group name | +``` + +curl -X PATCH -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/groups/8b129fc47a3fa97cbd6f7837 -d '{"name":"test2"}' +``` + +Example respones: +``` +{ + "success": true +} +``` + +### Delete_Group +``` +// Need authToken +DELETE /api/v1/groups/:id +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| name | string | yes | group name | +``` + +curl -X DELETE -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/groups/8b129fc47a3fa97cbd6f7837 +``` + +Example respones: +``` +{ + "success": true +} +``` + +### Get_Group_Members_Info +``` +GET /api/v1/groups/:groupId/person +``` +``` +curl -X GET http://server_address/api/v1/groups/9933aa9c429695857e9d52dd/person +``` + +Example respones: +``` +[ + { + "_id": "71f3fd7f055e5aa01bc29fcd", + "group_id": "9933aa9c429695857e9d52dd", // group id + "faceId": "12967", // face id + "url": "http://onm4mnb4w.bkt.clouddn.com/8855772a-2b0d-11e7-9bfc-d065caa81a04",// face image url + "name": "A", // labelled person name + "faces": [ + { + "id": "12967", // face image id + "url": "http://onm4mnb4w.bkt.clouddn.com/8855772a-2b0d-11e7-9bfc-d065caa81a04" // face image url + } + ] + }, + ... +] +``` + +### Add_User +``` +// Need authToken +POST /api/v1/groups/:groupId/users +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| userId | string | yes | userId | +``` +curl -X POST -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/groups/8b129fc47a3fa97cbd6f7837/users -d "userId=ejxqmx3PDK8yo88F" +``` + +Example respones: +``` +{ + "success": true +} +``` + +### Add_Device +``` +// Need authToken +POST /api/v1/groups/:groupId/devices +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| uuid | string | yes | device uuid | +| deviceName | string | yes | device display name | +| type | string | yes | device type ("in" or "out" or "inout" ) | + +``` +curl -X POST -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/groups/8b129fc47a3fa97cbd6f7837/devices -d '{"uuid": "123456", "deviceName": "test", "type": "in"}' +``` +Example respones: +``` +{ + "success": true +} +``` + +### Label +``` +//需要鉴权 +POST /api/v1/groups/:groupId/faces +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| uuid | string | yes | device id | +| imgUrl | string | yes | image url(112*112) | +| name | string | yes | labelled name of person ( at least one with faceId) | +| faceId | string | yes | labelled to faceId (at leaset one with name) | +| type | string | yes | type of image face/human_shape | +| position | null | no | position of device | +| current_ts | integer | no | current time ms | +| accuracy | boolean | no | picture accuracy | +| fuzziness | integer | no | picture fuzziness | +| sqlid | integer | no | local sql id | +| style | string | no | face labelling type( front, left_side, right_side) default:front | +| img_ts | integer | no | duration of image created | +| tid | string | no | id of sequence picture | +| p_ids | string | no | id of person detected at the same time | +``` +create data.json file +{ + "uuid": "28D6R16C12005885", + "imgUrl": "http://workaiossqn.tiegushi.co d25a07c-32d9-11e8-8756-a4caa09c959f", + “name”: "TESTNAME", + "faceId": "xxxx", //use name or faceId for labelling + "type": "face", + "current_ts": 1522276593387.0, + "accuracy": 1, + "fuzziness": 443, + "sqlid": 0, + "style": "front", + "img_ts": "1522276708297.0" +} + +curl -X POST "@data.json" -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/groups/xxxxxx/faces +``` +Example respones: +``` +{ + "success": true +} +``` +### Batch_Label +``` +//Need authToken +POST /api/v1/groups/:groupId/faces/batch +``` +``` +create data.json file +{ + "create": [ + { + "uuid": "28D6R16C12005885", + "imgUrl": "http://workaiossqn.tiegushi.co d25a07c-32d9-11e8-8756-a4caa09c959f", // picture 1 + “name”: "TESTNAME1", // labelled name of picture 1 + ... // same as Label API + }, + { + "uuid": "28D6R16C12005885", + "imgUrl": "http://workaiossqn.tiegushi.co d25a07c-32d9-11e8-8756-a4caa09c9591", // picture 2 + “name”: "TESTNAME2", // labelled name of picture 2 + ... + } + ... + ] +} + +curl -X POST "@data.json" -H "X-Auth-Token: P-ybnuSg6pHZJt_kx_nUdy5kEQYww2h3rursj13LkxX" -H "X-User-Id: YxbWum7KPTds8Lmi5" -H "Content-type: application/json" http://server_address/api/v1/groups/xxxxxx/faces/batch +``` +Example respones: +``` +{ + "success": true +} +``` +### Label_Unknown +Label unknown person detected +``` +POST groups/:groupId/strangers/:strangerId/label +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| name | string | yes | name to be labelled | + +``` +curl -X POST -H "Content-type: application/json" http://server_address/api/v1/groups/xxxxx/strangers/xxxxx/label -d '{"name": "test"}' +``` + +Example respones: +``` +{ + "success": true +} +``` + +### Ai Message +### Query_Messages +``` +GET /api/v1/ai-messages +``` +| Attribute | Type | Required | Description | +|:------------|:------|:----------|:-------------| +| personId | string | yes | personID | +| isRead | Boolean | no | if read, default: false (true or false) | + +``` +curl -X GET http://server_address/api/v1/ai-messages?personId=xxxxx +``` +Example respones: +``` +[ + { + "_id": "tSMCXWE5vEEAnAuns", + "msg": "Let's hold a meeting at 10:00am", + "personId": "99ab5706859a9cce7070db9e", + "groupId": "a5119193a661db15fc425f6c", + "isRead": false, + "createdAt": "2019-05-06T10:32:05.506Z" + } +] +``` + diff --git a/Dockerfile b/Dockerfile index 7e489979e..607749908 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM kadirahq/meteord:base -ADD hotShareWeb.tar.gz /bundle/ +ADD ApiServer.tar.gz /bundle/ RUN mv /bundle/bundle /built_app WORKDIR /built_app/programs/server RUN npm i diff --git a/Dockerfile.run b/Dockerfile.run new file mode 100644 index 000000000..f4692c17b --- /dev/null +++ b/Dockerfile.run @@ -0,0 +1,15 @@ +FROM ubuntu:18.04 +RUN apt-get update && apt-get install -y curl python2.7 python-pip build-essential locales \ + && rm -rf /var/lib/apt/lists/* \ + && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 + +ENV LANG en_US.utf8 +ENV LC_ALL en_US.UTF-8 + +RUN curl https://install.meteor.com/?release=1.4.1.2 | sh +ADD ./ /root/apiserver +WORKDIR /root/apiserver + +RUN meteor npm install --save jquery wolfy87-eventemitter eventie +RUN meteor build ../ && rm ../apiserver.tar.gz +CMD meteor run diff --git a/README.md b/README.md index e69de29bb..9b5da0ad4 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,52 @@ +# Get started [![Build Status](https://travis-ci.org/SharpAI/ApiServer.svg?branch=master)](https://travis-ci.org/SharpAI/ApiServer) + +## [Install Meteor(Offical)](https://www.meteor.com/install) +### Windows +``` +choco install meteor +``` +### OSX/Linux +``` +curl https://install.meteor.com/ | sh +``` +## Start API Server +``` +git clone https://github.com/SharpAI/ApiServer +cd ApiServer +meteor run +``` +## Test REST API + +### Register New User +``` +curl -X POST -H "Content-type: application/json" http://localhost:3000/api/v1/sign-up -d '{"username": "test11", "email": "xxxx@xxx.xx", "password": "xxxxxx"}' +{ + "success": true +} +``` +### Get Token +``` +curl -X POST http://localhost:3000/api/v1/login/ -d "username=test11&password=xxxxxx" +{ + "status": "success", + "data": { + "authToken": "mGRTZ_aNbL2EAobchvLmmlLmbn2e5EXdj8WR8DmSZw0", + "userId": "s9pxAWqwHzLaBKP5w" + } +} +``` +### Delete Token +``` +curl -X POST -H "X-Auth-Token: mGRTZ_aNbL2EAobchvLmmlLmbn2e5EXdj8WR8DmSZw0" -H "X-User-Id: s9pxAWqwHzLaBKP5w" http://localhost:3000/api/v1/logout +{ + "status": "success", + "data": { + "message": "You've been logged out!" + } +} +``` + +# [Full API Document](API.md) + + +## Test API Server setup at 165.232.62.29 diff --git a/build_bundle.sh b/build_bundle.sh index d92ec5107..e91bc74a3 100755 --- a/build_bundle.sh +++ b/build_bundle.sh @@ -1,11 +1,7 @@ #!/bin/bash -#cd ../hotShareWeb-StaticJS -#npm run build -#cd - -#git diff . -rm -rf hotShareWeb.tar.gz +rm -rf ApiServer.tar.gz meteor build --architecture=os.linux.x86_64 ../ -mv ../hotShareWeb.tar.gz ./ +mv ../ApiServer.tar.gz ./ -echo "run \"docker build -t lambdazhang/raidcdn:sharpai-version .\"" +echo "run \"docker build -t shareai/api_server:version .\"" diff --git a/client/0_ios_push_init.js b/client/0_ios_push_init.js new file mode 100644 index 000000000..dff505b1f --- /dev/null +++ b/client/0_ios_push_init.js @@ -0,0 +1,111 @@ +if (Meteor.isClient) { + console.log('on isCordova'); + Meteor.startup(function () { + var onDeviceReady2; + console.log('on startup'); + if (device.platform === 'iOS') { + console.log('on IOS'); + /* + this.onNotificationAPN = function(event) { + console.log('Got message'); + if(event.badge){ + Session.set('waitReadCount', event.badge); + } + if (event.foreground === '0') { + console.log('Push notification when background'); + window.refreshMainDataSource(); + return; + } + if (event.alert) { + PUB.toast(event.alert); + window.plugin.notification.local.cancelAll(); + window.plugin.notification.local.add({ + message: event.alert + }); + return window.refreshMainDataSource(); + } + };*/ + onDeviceReady2 = function () { + var registerInterval1 = window.setInterval(function () { + console.log('on onDeviceReady2'); + var push = PushNotification.init({ + ios: { + alert: "true", + badge: "true", + sound: "true", + clearBadge: "true" + } + }); + + push.on('registration', function (data) { + // data.registrationId + result = data.registrationId; + console.log('Got registrationID ' + result); + Session.set('registrationID', result); + Session.set('registrationType', 'iOS'); + localStorage.setItem('registrationID', result); + window.clearInterval(registerInterval1); + return window.updatePushNotificationToken('iOS', result); + }); + + push.on('notification', function (data) { + // data.message, + // data.title, + // data.count, + // data.sound, + // data.image, + // data.additionalData + + console.log('Got message'); + SimpleChat.onPushNotifacation(); + if (data.count) { + Session.set('waitReadCount', data.count); + } + if(Meteor.user().profile.waitReadCount > 0){ + Meteor.users.update({_id: Meteor.user()._id}, {$set: {'profile.waitReadCount': 0}}); + } + if (data.additionalData.foreground === false) { + console.log('Push notification when background'); + window.refreshMainDataSource(); + return; + } + if (data.message) { + PUB.toast(data.message); + return window.refreshMainDataSource(); + } + }); + + push.on('error', function (e) { + // e.message + return console.log('No Push Notification support in this build error = ' + e.message); + }); + /*window.plugins.pushNotification.register(function(result) { + console.log('Got registrationID ' + result); + Session.set('registrationID', result); + Session.set('registrationType', 'iOS'); + localStorage.setItem('registrationID', result); + window.clearInterval(registerInterval1); + return window.updatePushNotificationToken('iOS', result); + }, function(error) { + return console.log('No Push Notification support in this build error = ' + error); + }, { + "badge": "true", + "sound": "true", + "alert": "true", + "ecb": "window.onNotificationAPN" + });*/ + }, 20000); + }; + Deps.autorun(function () { + document.addEventListener("deviceready", onDeviceReady2, false); + Meteor.setTimeout(function () { + console.log('localstorageTimeout'); + if (Session.get('registrationID') == '' || Session.get('registrationID') == undefined && localStorage.getItem('registrationID')) { + console.log(localStorage.getItem('registrationID')); + window.updatePushNotificationToken('iOS', localStorage.getItem('registrationID')); + } + }, 2000); + }); + } + }); +} \ No newline at end of file diff --git a/client/4_router_client.coffee b/client/4_router_client.coffee index 9b0d405be..92c8c6047 100644 --- a/client/4_router_client.coffee +++ b/client/4_router_client.coffee @@ -6,252 +6,677 @@ subs = new SubsManager({ }); if Meteor.isClient - Session.setDefault("postPageScrollTop", 0) @refreshPostContent=()-> layoutHelperInit() Session.set("displayPostContent",false) - Meteor.setTimeout ()-> + setTimeout ()-> Session.set("displayPostContent",true) + calcPostSignature(window.location.href.split('#')[0]) ,300 - Router.onBeforeAction ()-> - console.log 'Router.url === '+this.url - if Meteor.loggingIn() - this.next() + renderPost = (self,post)-> + if !post + return self.render 'postNotFound' + + # if post and Session.get('postContent') and post.owner isnt Meteor.userId() and post._id is Session.get('postContent')._id and String(post.createdAt) isnt String(Session.get('postContent').createdAt) + # Session.set('postContent',post) + # refreshPostContent() + # toastr.info('作者修改了帖子内容.') + # else + Session.set('postContent',post) + Session.set('focusedIndex',undefined) + if post and post.addontitle and (post.addontitle isnt '') + documentTitle = "『故事贴』" + post.title + ":" + post.addontitle + else if post + documentTitle = "『故事贴』" + post.title + Session.set("DocumentTitle",documentTitle) + if post + self.render 'showPosts', {data: post} + else + self.render 'unpublish' + Session.set 'channel','posts/'+self.params._id + renderPost2 = (self,post)-> + if !post or (post.isReview is false and post.owner isnt Meteor.userId()) + return this.render 'postNotFound' + if post and Session.get('postContent') and post.owner isnt Meteor.userId() and post._id is Session.get('postContent')._id and String(post.createdAt) isnt String(Session.get('postContent').createdAt) + Session.set('postContent',post) + refreshPostContent() + #toastr.info('作者修改了帖子内容.') + else + Session.set('postContent',post) + Session.set('focusedIndex',undefined) + if post and post.addontitle and (post.addontitle isnt '') + documentTitle = "『故事贴』" + post.title + ":" + post.addontitle + else if post + documentTitle = "『故事贴』" + post.title + Session.set("DocumentTitle",documentTitle) + if post + self.render 'showPosts', {data: post} + else + self.render 'unpublish' + Session.set 'channel','posts/'+self.params._id + renderPost3 = (self,post)-> + if !post or (post.isReview is false and post.owner isnt Meteor.userId()) + return this.render 'postNotFound' + if post and Session.get('postContent') and post.owner isnt Meteor.userId() and post._id is Session.get('postContent')._id and String(post.createdAt) isnt String(Session.get('postContent').createdAt) + Session.set('postContent',post) + refreshPostContent() + #toastr.info('作者修改了帖子内容.') + else + Session.set('postContent',post) + + if Session.get("doSectionForward") is true + Session.set("doSectionForward",false) + Session.set("postPageScrollTop",0) + document.body.scrollTop = 0 + Session.set("refComment",['']) + Session.set('focusedIndex',self.params._index) + if post.addontitle and (post.addontitle isnt '') + documentTitle = post.title + ":" + post.addontitle + else + documentTitle = post.title + Session.set("DocumentTitle",documentTitle) + favicon = document.createElement('link') + favicon.id = 'icon' + favicon.rel = 'icon' + favicon.href = post.mainImage + document.head.appendChild(favicon) + unless Session.equals('channel','posts/'+self.params._id+'/'+self.params._index) + refreshPostContent() + self.render 'showPosts', {data: post} + Session.set('channel','posts/'+self.params._id+'/'+self.params._index) + Meteor.startup ()-> + Tracker.autorun ()-> + channel = Session.get 'channel' + $(window).off('scroll') + console.log('channel changed to '+channel+' Off Scroll') + setTimeout -> + Session.set 'focusOn',channel + ,300 + Tracker.autorun ()-> + if Session.get('channel') isnt 'addPost' and (Session.get('focusOn') is 'addPost') + console.log('Leaving addPost mode') + Session.set('showContentInAddPost',false) + if window.iabHandle + window.iabHandle.close() + window.iabHandle = null + Router.route '/',()-> + this.render 'home' + Session.set 'channel','home' return - if Meteor.userId() - this.next() + Router.route '/notice',()-> + this.render 'notice' return - else - if this.url.indexOf('/simple-chat') != -1 - console.log 'is open simple-chat!' - #Session.set('disableAnonymousLogin',true); - Session.set('routerWillRenderPage',this.url+'?id='+this.params.query['id']) - Router.go('/authOverlay') - this.next() + Router.route '/message',()-> + this.render 'chatGroups' + Session.set 'channel','message' + return + Router.route '/group/add',()-> + if Meteor.isCordova is true + this.render 'groupAdd' + return + Router.route '/seriesList',()-> + this.render 'seriesList' + Session.set 'channel','seriesList' + Router.route '/mySeries',()-> + this.render 'mySeries' + Session.set 'channel','mySeries' + Router.route '/series',()-> + Session.set('seriesId','') + this.render 'series' + return + Router.route '/series/:_id',()-> + Meteor.subscribe("oneSeries",this.params._id) + Meteor.subscribe("seriesFollow", this.params._id) + console.log(this.params._id) + seriesContent = Series.findOne({_id: this.params._id}) + Session.set('seriesId',this.params._id) + Session.set('seriesContent',seriesContent) + this.render 'series' + return + Router.route '/splashScreen',()-> + this.render 'splashScreen' + Session.set 'channel', 'splashScreen' + return + Router.route '/_showImgOne',()-> + this.render '_showImgOne' + Session.set 'channel', '_showImgOne' + return + Router.route '/search',()-> + if Meteor.isCordova is true + this.render 'search' + Session.set 'channel','search' + return + Router.route '/searchFollow',()-> + if Meteor.isCordova is true + this.render 'searchFollow' + Session.set 'channel','searchFollow' + return + Router.route '/searchPeopleAndTopic',()-> + if Meteor.isCordova is true + this.render 'searchPeopleAndTopic' + Session.set 'channel','searchPeopleAndTopic' + return + Router.route '/cropImage',()-> + this.render 'cropImage' + return + Router.route '/addressBook',()-> + if Meteor.isCordova is true + this.render 'addressBook' + Session.set 'channel','addressBook' + return + Router.route '/groupsList',()-> + if Meteor.isCordova is true + Meteor.subscribe("get-my-group",Meteor.userId()) + this.render 'groupsList' + Session.set 'channel','groupsList' + return + Router.route '/groupsProfile/:_type/:_id',()-> + console.log 'this.params._type'+this.params._type + if this.params._type is 'group' + limit = withShowGroupsUserMaxCount || 29; + Meteor.subscribe("get-group-user-with-limit",this.params._id,limit) + else + Meteor.subscribe('usersById',this.params._id) + console.log(this.params._id) + Session.set('groupsId',this.params._id) + Session.set('groupsType',this.params._type) + this.render 'groupsProfile' + return + Router.route '/simpleUserProfile/:_id',()-> + Session.set('simpleUserProfileUserId',this.params._id) + this.render 'simpleUserProfile' + return + Router.route '/timeline',()-> + if Meteor.isCordova is true + this.render 'timeline' + Session.set 'channel','timeline' + return + Router.route '/clusteringFix/:_id',()-> + this.render 'clusteringFix' + return + Router.route '/clusteringFixPerson/:gid/:fid',()-> + this.render 'clusteringFixPerson' + return + # Router.route '/homePage',()-> + # if Meteor.isCordova is true + # this.render 'homePage' + # Session.set 'channel','homePage' + # return + Router.route '/timelineAlbum/:_uuid',()-> + console.log "TimeLine album: run into this page" + this.render 'timelineAlbum' + return + Router.route '/ishavestranger/',()-> + this.render 'haveStranger' + return + Router.route '/device/dashboard/:group_id',()-> + this.render 'deviceDashboard' + return + Router.route '/recognitionCounts/:group_id',()-> + this.render 'recognitionCounts' + return + Router.route '/explore',()-> + if Meteor.isCordova is true + this.render 'explore' + Session.set 'channel','explore' + return + Router.route '/bell',()-> + if Meteor.isCordova is true + this.render 'bell' + Session.set 'channel','bell' + return + Router.route '/user',()-> + if Meteor.isCordova is true + this.render 'user' + Session.set 'channel','user' + return + Router.route '/dashboard',()-> + if Meteor.isCordova is true + this.render 'dashboard' + return + Router.route '/perfShow/:_id',()-> + if Meteor.isCordova is true + this.render 'perfShow' + return + Router.route '/followers',()-> + if Meteor.isCordova is true + this.render 'followers' return - else if this.url.indexOf('/posts') != -1 or this.url.indexOf('/series') != -1 - Session.set('routerWillRenderPage',this.url) - Router.go('/authOverlay') - this.next() + Router.route '/add',()-> + if Meteor.isCordova is true + this.render 'addPost' + Session.set 'channel','addPost' + return + Router.route '/registerFollow',()-> + if Meteor.isCordova is true + this.render 'registerFollow' + Session.set 'channel','registerFollow' + return + Router.route '/authOverlay',()-> + if Meteor.isCordova is true + this.render 'authOverlay' + Session.set 'channel','authOverlay' return - #Router.go('/authOverlay') else - this.next() + this.render 'webHome' return - Router.route '/authOverlay',()-> - this.render 'authOverlay' - #Session.set('disableAnonymousLogin',true); - return - Router.route '/loginForm', ()-> - this.render 'loginForm' - #Session.set('disableAnonymousLogin',true); - return - Router.route '/signupForm', ()-> - this.render 'signupForm' - #Session.set('disableAnonymousLogin',true); - return - Router.route '/recoveryForm', ()-> - #Session.set('disableAnonymousLogin',true); - this.render 'recoveryForm' - return - Router.route '/deal_page',()-> - #Session.set('disableAnonymousLogin',true); - this.render 'deal_page' - return - Router.route '/bell',()-> - this.render 'bell' - Session.set 'channel','bell' - return - Router.route '/redirect/:_id',()-> - Session.set('nextPostID',this.params._id) - this.render 'redirect' - return - Router.route '/import', ()-> - this.render 'importPost' - Router.route '/VEWorld', ()-> - this.render 'VEWorld' - Router.route '/VEOffice/:_id', ()-> - this.render 'VEOffice' - Router.route '/series/:_id', { - waitOn: -> - [subs.subscribe("oneSeries", this.params._id)] + Router.route '/loginForm', ()-> + Session.set 'channel','loginForm' + this.render 'loginForm' + return + Router.route '/signupForm', ()-> + this.render 'signupForm' + return + Router.route '/recoveryForm', ()-> + this.render 'recoveryForm' + return + Router.route '/introductoryPage',()-> + this.render 'introductoryPage' + Session.set 'channel','introductoryPage' + return + Router.route '/introductoryPage1',()-> + this.render 'introductoryPage1' + Session.set 'channel','introductoryPage1' + return + Router.route '/introductoryPage2',()-> + this.render 'introductoryPage2' + Session.set 'channel','introductoryPage2' + return + + Router.route '/webHome',()-> + this.render 'webHome' + return + Router.route '/help',()-> + this.render 'help' + return + Router.route '/progressBar',()-> + if Meteor.isCordova is true + this.render 'progressBar' + Session.set 'channel','progressBar' + return + Router.route '/redirect/:_id',()-> + Session.set('nextPostID',this.params._id) + this.render 'redirect' + return + Router.route '/groupInstallTest/:_id/:uuid',()-> + Session.set('channel','groupInstallTest/'+this.params._id+'/'+this.params.uuid); + this.render 'groupInstallTest' + return + Router.route '/groupPerson/:_id', ()-> + this.render 'groupPerson' + return + Router.route '/groupDevices/:_id', ()-> + this.render 'groupDevices' + return + Router.route '/dayTasks/:_id', ()-> + this.render 'dayTasks' + return + Router.route '/bindGroupUser', ()-> + this.render 'bindGroupUser' + return + Router.route '/bindUserPopup/:_id',()-> + this.render 'bindUserPopup' + return + Router.route '/comReporter/:_id',()-> + this.render 'companyItem' + return + Router.route '/collectList',()-> + Meteor.subscribe('collectedMessages', {sort: {collectDate: -1}, limit: 10}) + this.render 'collectList' + return + Router.route '/newLabel',()-> + this.render 'newLabel' + return + + # Router.route '/posts/:_id', { + # waitOn: -> + # [subs.subscribe("publicPosts",this.params._id), + # subs.subscribe("postViewCounter",this.params._id), + # subs.subscribe("postsAuthor",this.params._id), + # subs.subscribe "pcomments"] + # loadingTemplate: 'loadingPost' + # action: -> + # post = Posts.findOne({_id: this.params._id}) + # if !post or (post.isReview is false and post.owner isnt Meteor.userId()) + # return this.render 'postNotFound' + + # if post and Session.get('postContent') and post.owner isnt Meteor.userId() and post._id is Session.get('postContent')._id and String(post.createdAt) isnt String(Session.get('postContent').createdAt) + # Session.set('postContent',post) + # refreshPostContent() + # toastr.info('作者修改了帖子内容.') + # else + # Session.set('postContent',post) + # Session.set('focusedIndex',undefined) + # if post and post.addontitle and (post.addontitle isnt '') + # documentTitle = "『故事贴』" + post.title + ":" + post.addontitle + # else if post + # documentTitle = "『故事贴』" + post.title + # Session.set("DocumentTitle",documentTitle) + # if post + # this.render 'showPosts', {data: post} + # else + # this.render 'unpublish' + # if Session.get("readMomentsPost") is true + # Session.set 'readMomentsPost',false + # Session.set 'needReviewPostStyle',true + # Session.set 'channel','posts/'+this.params._id + # } + Router.route '/posts/:_id', { + loadingTemplate: 'loadingPost' + action: -> + self = this + this.render 'loadingPost' + if ajaxCDN? + ajaxCDN.abort() + ajaxCDN = null + console.log(this.params._id); + subs.subscribe "publicPosts",this.params._id, ()-> + console.log('subs loaded:', self.params._id); + subs.subscribe("postViewCounter",this.params._id) + subs.subscribe("postsAuthor",this.params._id) + subs.subscribe "pcomments" + post = Posts.findOne({_id: this.params._id}) + unless post + # console.log("by ajax:", this.params._id) + ajaxCDN = $.getJSON(rest_api_url+"/raw/"+this.params._id,(json,result)-> + post = Posts.findOne({_id: self.params._id}) + if post + console.log('show subs post:', self.params._id); + renderPost2(self, post) + else if(result && result is 'success' && json && json.status && json.status is 'ok' && json.data) + console.log('show ajax post:', self.params._id); + Posts._connection._livedata_data({msg: 'added', collection: 'posts', id: new Mongo.ObjectID()._str, fields: json.data}) # insert post data + post = json.data + renderPost2(self, post) + else + this.render 'unpublish' + ) + return + renderPost2(self, post) + } + Router.route '/newposts/:_id', { + action: -> + self = this + self.render 'loadingPost' + Meteor.subscribe("reading",self.params._id) + newpostsData = Session.get 'newpostsdata' + if newpostsData + renderPost(self,newpostsData) + else + post = Meteor.call("getPostContent",self.params._id) + renderPost(self,post) + } + + Router.route '/newposts1/:_id', { + action: -> + self = this + self.render 'loadingPost' + Meteor.subscribe("reading",self.params._id) + $.getJSON(rest_api_url+"/raw/"+self.params._id,(json,result)-> + if(result && result is 'success' && json && json.status && json.status is 'ok' && json.data) + post = json.data + renderPost(self,post) + else + post = Meteor.call("getPostContent",self.params._id) + renderPost(self,post) + ) + } + Router.route '/draftposts/:_id', { action: -> - series = Series.findOne({_id: this.params._id}) - Session.set('seriesContent',series) - this.render 'series', {data: series} - fastRender: true + post = Session.get('postContent') + + if post and post.addontitle and (post.addontitle isnt '') + documentTitle = "『故事贴』" + post.title + ":" + post.addontitle + else if post + documentTitle = "『故事贴』" + post.title + Session.set("DocumentTitle",documentTitle) + this.render 'showDraftPosts', {data: post} + Session.set 'draftposts','draftposts/'+this.params._id } - Router.route '/posts/:_id', { - waitOn: -> - [subs.subscribe("publicPosts", this.params._id), - subs.subscribe("postsAuthor",this.params._id), - subs.subscribe "pcomments"] + # Router.route '/posts/:_id/:_index', { + # waitOn: -> + # [Meteor.subscribe("publicPosts",this.params._id), + # Meteor.subscribe("postsAuthor",this.params._id), + # Meteor.subscribe "pcomments"] + # loadingTemplate: 'loadingPost' + # action: -> + # if Session.get("doSectionForward") is true + # Session.set("doSectionForward",false) + # Session.set("postPageScrollTop",0) + # document.body.scrollTop = 0 + # post = Posts.findOne({_id: this.params._id}) + # if !post or (post.isReview is false and post.owner isnt Meteor.userId()) + # return this.render 'postNotFound' + + # unless post + # console.log "Cant find the request post" + # this.render 'postNotFound' + # return + # Session.set("refComment",['']) + # if post and Session.get('postContent') and post.owner isnt Meteor.userId() and post._id is Session.get('postContent')._id and String(post.createdAt) isnt String(Session.get('postContent').createdAt) + # Session.set('postContent',post) + # refreshPostContent() + # toastr.info('作者修改了帖子内容.') + # else + # Session.set('postContent',post) + # Session.set('focusedIndex',this.params._index) + # if post.addontitle and (post.addontitle isnt '') + # documentTitle = post.title + ":" + post.addontitle + # else + # documentTitle = post.title + # Session.set("DocumentTitle",documentTitle) + # favicon = document.createElement('link') + # favicon.id = 'icon' + # favicon.rel = 'icon' + # favicon.href = post.mainImage + # document.head.appendChild(favicon) + + # unless Session.equals('channel','posts/'+this.params._id+'/'+this.params._index) + # refreshPostContent() + # this.render 'showPosts', {data: post} + # Session.set('channel','posts/'+this.params._id+'/'+this.params._index) + # fastRender: true + # } + Router.route '/posts/:_id/:_index', { loadingTemplate: 'loadingPost' action: -> + self = this + this.render 'loadingPost' + if ajaxCDN? + ajaxCDN.abort() + ajaxCDN = null + console.log(this.params._id); + subs.subscribe "publicPosts",this.params._id, ()-> + console.log('subs loaded:', self.params._id); + subs.subscribe("postViewCounter",this.params._id) + subs.subscribe("postsAuthor",this.params._id) + subs.subscribe "pcomments" post = Posts.findOne({_id: this.params._id}) - # Server won't push data if post not review - #if !post or (post.isReview is false and post.owner isnt Meteor.userId()) - # return this.render 'postNotFound' unless post - console.log "Cant find the request post" - this.render 'postNotFound' + # console.log("by ajax:", this.params._id) + ajaxCDN = $.getJSON(rest_api_url+"/raw/"+this.params._id,(json,result)-> + post = Posts.findOne({_id: self.params._id}) + if post + console.log('show subs post:', self.params._id); + renderPost3(self, post) + else if(result && result is 'success' && json && json.status && json.status is 'ok' && json.data) + console.log('show ajax post:', self.params._id); + Posts._connection._livedata_data({msg: 'added', collection: 'posts', id: new Mongo.ObjectID()._str, fields: json.data}) # insert post data + post = json.data + renderPost3(self, post) + else + this.render 'unpublish' + ) return - Session.set("refComment",['']) - if post and Session.get('postContent') and post.owner isnt Meteor.userId() and post._id is Session.get('postContent')._id and String(post.createdAt) isnt String(Session.get('postContent').createdAt) - Session.set('postContent',post) - refreshPostContent() - toastr.info('作者修改了帖子内容.') - else - Session.set('postContent',post) - Session.set('focusedIndex',undefined) - if post.addontitle and (post.addontitle isnt '') - documentTitle = post.title + ":" + post.addontitle - else - documentTitle = post.title - Session.set("DocumentTitle",documentTitle) - favicon = document.createElement('link') - favicon.id = 'icon' - favicon.rel = 'icon' - favicon.href = post.mainImage - document.head.appendChild(favicon) - - unless Session.equals('channel','posts/'+this.params._id) - refreshPostContent() - this.render 'showPosts', {data: post} - Session.set 'channel','posts/'+this.params._id + renderPost3(self, post) fastRender: true } - Router.route '/view_posts/:_id', { + # Router.route '/allDrafts',()-> + # if Meteor.isCordova is true + # this.render 'allDrafts' + # Session.set 'channel','allDrafts' + # return + Router.route '/allDrafts', { waitOn: -> - [subs.subscribe("publicPosts", this.params._id), - subs.subscribe("postsAuthor",this.params._id), - subs.subscribe "pcomments"] + [Meteor.subscribe("saveddrafts")] loadingTemplate: 'loadingPost' action: -> - post = Posts.findOne({_id: this.params._id}) - # if !post or (post.isReview is false and post.owner isnt Meteor.userId()) - # return this.render 'postNotFound' - unless post - console.log "Cant find the request post" - this.render 'postNotFound' - return - Session.set("refComment",['']) - if post and Session.get('postContent') and post.owner isnt Meteor.userId() and post._id is Session.get('postContent')._id and String(post.createdAt) isnt String(Session.get('postContent').createdAt) - Session.set('postContent',post) - refreshPostContent() - toastr.info('作者修改了帖子内容.') - else - Session.set('postContent',post) - Session.set('focusedIndex',undefined) - if post.addontitle and (post.addontitle isnt '') - documentTitle = post.title + ":" + post.addontitle - else - documentTitle = post.title - Session.set("DocumentTitle",documentTitle) - favicon = document.createElement('link') - favicon.id = 'icon' - favicon.rel = 'icon' - favicon.href = post.mainImage - document.head.appendChild(favicon) - - unless Session.equals('channel','posts/'+this.params._id) - refreshPostContent() - this.render 'showPosts', {data: post} - Session.set 'channel','posts/'+this.params._id - fastRender: true + if Meteor.isCordova is true + this.render 'allDrafts' + Session.set 'channel','allDrafts' } - Router.route '/posts/:_id/:_index', { - name: 'post_index' - waitOn: -> - [Meteor.subscribe("publicPosts",this.params._id), - Meteor.subscribe("postsAuthor",this.params._id), - Meteor.subscribe "pcomments"] - loadingTemplate: 'loadingPost' - action: -> - if Session.get("doSectionForward") is true - Session.set("doSectionForward",false) - Session.set("postPageScrollTop",0) - document.body.scrollTop = 0 - post = Posts.findOne({_id: this.params._id}) - if !post or (post.isReview is false and post.owner isnt Meteor.userId()) - return this.render 'postNotFound' - unless post - console.log "Cant find the request post" - this.render 'postNotFound' + Router.route '/myPosts',()-> + if Meteor.isCordova is true + this.render 'myPosts' + Session.set 'channel','myPosts' return - Session.set("refComment",['']) - ### - Meteor.subscribe "refcomments",()-> - Meteor.setTimeout ()-> - refComment = RefComments.find() - if refComment.count() > 0 - Session.set("refComment",refComment.fetch()) - ,2000 - ### - if post and Session.get('postContent') and post.owner isnt Meteor.userId() and post._id is Session.get('postContent')._id and String(post.createdAt) isnt String(Session.get('postContent').createdAt) - Session.set('postContent',post) - refreshPostContent() - toastr.info('作者修改了帖子内容.') - else - Session.set('postContent',post) - Session.set('focusedIndex',this.params._index) - if post.addontitle and (post.addontitle isnt '') - documentTitle = post.title + ":" + post.addontitle - else - documentTitle = post.title - Session.set("DocumentTitle",documentTitle) - favicon = document.createElement('link') - favicon.id = 'icon' - favicon.rel = 'icon' - favicon.href = post.mainImage - document.head.appendChild(favicon) - - unless Session.equals('channel','posts/'+this.params._id+'/'+this.params._index) - refreshPostContent() + Router.route '/my_email',()-> + if Meteor.isCordova is true + this.render 'my_email' + Session.set 'channel','my_email' + return + Router.route '/my_accounts_management', { + waitOn: -> + [Meteor.subscribe("userRelation")] + loadingTemplate: 'loadingPost' + action: -> + if Meteor.isCordova is true + this.render 'accounts_management' + Session.set 'channel','my_accounts_management' + } + # Router.route '/my_accounts_management',()-> + # if Meteor.isCordova is true + # this.render 'accounts_management' + # Session.set 'channel','my_accounts_management' + # return + Router.route '/my_accounts_management_addnew',()-> + if Meteor.isCordova is true + this.render 'accounts_management_addnew' + Session.set 'channel','my_accounts_management_addnew' + return + Router.route '/my_password',()-> + if Meteor.isCordova is true + this.render 'my_password' + Session.set 'channel','my_password' + return + Router.route '/my_notice',()-> + if Meteor.isCordova is true + this.render 'my_notice' + Session.set 'channel','my_notice' + return + Router.route '/my_blacklist',()-> + if Meteor.isCordova is true + this.render 'my_blacklist' + Session.set 'channel','my_blacklist' + return + Router.route '/display_lang',()-> + if Meteor.isCordova is true + this.render 'display_lang' + Session.set 'channel','display_lang' + return + Router.route '/my_about',()-> + if Meteor.isCordova is true + this.render 'my_about' + Session.set 'channel','my_about' + return + Router.route '/deal_page',()-> + if Meteor.isCordova is true + this.render 'deal_page' + Session.set 'channel','deal_page' + return + Router.route '/topicPosts',()-> + if Meteor.isCordova is true + this.render 'topicPosts' + Session.set 'channel','topicPosts' + return + Router.route '/addTopicComment',()-> + if Meteor.isCordova is true + this.render 'addTopicComment' + Session.set 'addTopicComment_server_import', this.params.query.server_import + Session.set 'channel','addTopicComment' + return + Router.route '/thanksReport',()-> + if Meteor.isCordova is true + this.render 'thanksReport' + Session.set 'channel','thanksReport' + return + Router.route '/reportPost',()-> + if Meteor.isCordova is true + this.render 'reportPost' + Session.set 'channel','reportPost' + return + Router.route '/userProfile',()-> + if Meteor.isCordova is true + this.render 'userProfile' + Session.set 'channel','userProfile' + return + Router.route 'userProfilePage1', + template: 'userProfile' + path: '/userProfilePage1' + Router.route 'userProfilePage2', + template: 'userProfile' + path: '/userProfilePage2' + Router.route 'userProfilePage3', + template: 'userProfile' + path: '/userProfilePage3' + Router.route 'searchMyPosts', + template: 'searchMyPosts' + path: '/searchMyPosts' + Router.route 'unpublish', + template: 'unpublish' + path: '/unpublish' + Router.route 'setNickname', + template: 'setNickname' + path: '/setNickname' + Router.route '/userProfilePage',()-> + this.render 'userProfilePage' + return + Router.route '/hotPosts/:_id',()-> + this.render 'hotPosts' + return + Router.route 'recommendStory',()-> + this.render 'recommendStory' + return + Router.route '/selectTemplate',()-> + this.render 'selectTemplate' + return + Router.route '/scene',()-> + this.render 'scene' + return + Router.route '/addHomeAIBox',()-> + this.render 'addHomeAIBox' + return + Router.route '/scanFailPrompt',()-> + this.render 'scanFailPrompt' + return + Router.route '/setGroupname',()-> + this.render 'setGroupname' + return + Router.route '/setDevicename',()-> + this.render 'setDevicename',{ + data:()-> + curDevice = Session.get('curDevice'); + return curDevice + } + return + Router.route '/checkInOutMsgList',()-> + this.render 'checkInOutMsgList' + return + Router.route '/groupUserHide/:_id',()-> + this.render 'groupUserHide' + return - this.render 'showPosts', {data: post} - Session.set('channel','posts/'+this.params._id+'/'+this.params._index) - fastRender: true - } - Router.route '/',()-> - this.render 'webHome' - return - Router.route '/help',()-> - this.render 'help' - return - Router.route '/reset/:token',()-> - this.render 'resetPwd' - Router.route 'userProfilePage1', - template: 'userProfile' - path: '/userProfilePage1' - Router.route 'userProfilePage2', - template: 'userProfile' - path: '/userProfilePage2' - Router.route 'userProfilePage3', - template: 'userProfile' - path: '/userProfilePage3' - Router.route 'searchMyPosts', - template: 'searchMyPosts' - path: '/searchMyPosts' - Router.route 'unpublish', - template: 'unpublish' - path: '/unpublish' - Router.route 'setNickname', - template: 'setNickname' - path: '/setNickname' - Router.route '/userProfilePage',()-> - this.render 'userProfilePage' - return - Router.route '/hotPosts/:_id',()-> - this.render 'hotPosts' - return - Router.route 'recommendStory',()-> - this.render 'recommendStory' - return - Router.route '/groupsProfile/:_id',()-> - limit = withShowGroupsUserMaxCount || 29; - Meteor.subscribe("get-group-user-with-limit",this.params._id,limit) - console.log(this.params._id) - Session.set('groupsId',this.params._id) - this.render 'groupsProfile' - return + Router.route '/faces', ()-> + Session.set 'channel','faces' + this.render 'faces' + return + Router.route '/scannerAddDevice', ()-> + this.render 'scannerAddDevice' + return + Router.route '/chooseLabelType/:uuid',()-> + Session.set 'channel','chooseLabelType/'+this.params.uuid + this.render 'chooseLabelType' + return + Router.route '/autolabel/:uuid',()-> + this.render 'autolabel' + return diff --git a/client/addressBook/addressBook.coffee b/client/addressBook/addressBook.coffee new file mode 100644 index 000000000..fff216b35 --- /dev/null +++ b/client/addressBook/addressBook.coffee @@ -0,0 +1,55 @@ +if Meteor.isClient + Template.addressBook.rendered=-> + #$('.content').css 'min-height',$(window).height() +# $('.mainImage').css('height',$(window).height()*0.55) + $('.content').scroll (event)-> + target = $("#showMoreFollowsResults"); + FOLLOWS_ITEMS_INCREMENT = 10; + if (!target.length) + return; + threshold = $('.content').scrollTop() + $('.content').height() - target.height(); + if target.offset().top < threshold + if (!target.data("visible")) + target.data("visible", true); + Session.set("followersitemsLimit", + Session.get("followersitemsLimit") + FOLLOWS_ITEMS_INCREMENT); + else + if (target.data("visible")) + target.data("visible", false); + Template.addressBook.helpers + myFollows:()-> + Follower.find({"userId":Meteor.userId()}, {sort: {createdAt: -1}}, {limit:Session.get("followersitemsLimit")}) + moreResults:-> + !(Follower.find({"userId":Meteor.userId()}).count() < Session.get("followersitemsLimit")) + loading:-> + Session.equals('followersCollection','loading') + loadError:-> + Session.equals('followersCollection','error') + Template.addressBook.events + 'click #follow':(event)-> + PUB.page('/searchFollow'); + 'click .newFriends':(event)-> + #PUB.page('/newFriendsList') + 'click .groupslist':(event)-> + PUB.page('/groupsList') + 'click .devicelist':(event)-> + PUB.page('/timeline') + 'click .recentlyPeople':(event)-> + #PUB.page('/recentlyList') + 'click .followItem': (event)-> + console.log 'click .followItem' + PUB.page('/simpleUserProfile/'+this.followerId); + # url = '/simple-chat/to/user?id='+ this.followerId + # setTimeout ()-> + # PUB.page(url) + # ,animatePageTrasitionTimeout + # if isIOS + # if (event.clientY + $('.home #footer').height()) >= $(window).height() + # console.log 'should be triggered in scrolling' + # return false + # $('.chatGroups').addClass('animated ' + animateOutLowerEffect); + # console.log this.followerId + # url = '/simple-chat/to/group?id='+this.followerId + # setTimeout ()-> + # Router.go(url) + # ,animatePageTrasitionTimeout diff --git a/client/addressBook/addressBook.html b/client/addressBook/addressBook.html new file mode 100644 index 000000000..e1ded3ad4 --- /dev/null +++ b/client/addressBook/addressBook.html @@ -0,0 +1,63 @@ + diff --git a/client/addressBook/addressBook.less b/client/addressBook/addressBook.less new file mode 100644 index 000000000..0f6cbb0df --- /dev/null +++ b/client/addressBook/addressBook.less @@ -0,0 +1,15 @@ + +.addressBook .head .rightButton .fa-plus{ + position: relative; + bottom: 8px; + font-size: 8px; + color: #ffffff +} +.addressBook .content{position:relative;padding-top: 40px;padding-bottom: 55px;-webkit-touch-callout: none;background-color: #eee;} +.addressBook .content .eachViewer{ + background-color: white; +} +.addressBook .content .icon{width: 40px;height: 40px;margin: 10px;object-fit: cover;} +.addressBook .content .groupsName{color: black;} +.addressBook .line{text-align:center;width: 100%; height: 1px;} +.addressBook .line span{background: #333; width: 100%; height: 1px; display: block;} \ No newline at end of file diff --git a/client/addressBook/groupsList.coffee/groupsList.coffee b/client/addressBook/groupsList.coffee/groupsList.coffee new file mode 100644 index 000000000..b8ae5b543 --- /dev/null +++ b/client/addressBook/groupsList.coffee/groupsList.coffee @@ -0,0 +1,44 @@ +if Meteor.isClient + Template.groupsList.rendered=-> + $('.content').css 'min-height',$(window).height() +# $('.mainImage').css('height',$(window).height()*0.55) + ### + $(window).scroll (event)-> + target = $("#showMoreFollowsResults"); + FOLLOWS_ITEMS_INCREMENT = 10; + if (!target.length) + return; + threshold = $(window).scrollTop() + $(window).height() - target.height(); + if target.offset().top < threshold + if (!target.data("visible")) + target.data("visible", true); + Session.set("followersitemsLimit", + Session.get("followersitemsLimit") + FOLLOWS_ITEMS_INCREMENT); + else + if (target.data("visible")) + target.data("visible", false); + ### + Template.groupsList.helpers + myGroups:()-> + SimpleChat.GroupUsers.find({user_id:Meteor.userId()}, {sort: {createdAt: -1}}) + # moreResults:-> + # !(SimpleChat.GroupUsers.find({"userId":Meteor.userId()}).count() < Session.get("followersitemsLimit")) + loading:-> + Session.equals('followersCollection','loading') + loadError:-> + Session.equals('followersCollection','error') + Template.groupsList.events + 'click #groupsListPageback':(event)-> + PUB.back() + 'click .groupItem': (event)-> + console.log 'click .groupItem' + if isIOS + if (event.clientY + $('.home #footer').height()) >= $(window).height() + console.log 'should be triggered in scrolling' + return false + $('.groupsList').addClass('animated ' + animateOutLowerEffect); + console.log this.group_id + url = '/simple-chat/to/group?id='+this.group_id + setTimeout ()-> + PUB.page(url) + ,animatePageTrasitionTimeout diff --git a/client/addressBook/groupsList.coffee/groupsList.html b/client/addressBook/groupsList.coffee/groupsList.html new file mode 100644 index 000000000..4617a73c5 --- /dev/null +++ b/client/addressBook/groupsList.coffee/groupsList.html @@ -0,0 +1,46 @@ + diff --git a/client/addressBook/groupsList.coffee/groupsList.less b/client/addressBook/groupsList.coffee/groupsList.less new file mode 100644 index 000000000..1449a8373 --- /dev/null +++ b/client/addressBook/groupsList.coffee/groupsList.less @@ -0,0 +1,39 @@ +.groupsList { + .head .rightButton .fa-plus { + position: relative; + bottom: 8px; + font-size: 8px; + color: #ffffff + } + .content { + position: relative; + padding-top: 40px; + padding-bottom: 55px; + -webkit-touch-callout: none; + background-color: #eee; + .eachViewer { + background-color: white; + } + .icon { + width: 40px; + height: 40px; + margin: 10px; + object-fit: cover; + } + .fa-users{width: 50px;height: 50px; line-height: 50px; margin: 10px;border-radius: 6px; display: inline-block; text-align: center; background-color: #2196f3; color: #fff; font-size: 24px;} + .groupsName { + color: black; + } + .line { + text-align: center; + width: 100%; + height: 1px; + span { + background: #333; + width: 100%; + height: 1px; + display: block; + } + } + } +} diff --git a/client/android_gcm_init.coffee b/client/android_gcm_init.coffee new file mode 100644 index 000000000..bb2f63e32 --- /dev/null +++ b/client/android_gcm_init.coffee @@ -0,0 +1,50 @@ +if 0 #Meteor.isCordova + Meteor.startup -> + if device.platform is 'android' || device.platform is 'Android' || device.platform is 'amazon-fireos' + + registeredHandle = (e)-> + if ( e.regid.length > 0 ) + console.log("regID = " + e.regid) + Session.set('registrationID',e.regid); + Session.set('registrationType','GCM'); + window.updatePushNotificationToken('GCM',e.regid) + messageHandle = (e)-> + # if this flag is set, this notification happened while we were in the foreground. + # you might want to play a sound to get the user's attention, throw up a dialog, etc. + if (e.foreground) + console.log 'INLINE NOTIFICATION' + #PUB.toast JSON.stringify(e.payload) + if e.payload.message + PUB.toast e.payload.message + window.plugin.notification.local.add {title: '故事贴',message: e.payload.message} + # on Android soundname is outside the payload. + # On Amazon FireOS all custom attributes are contained within payload + # soundfile = e.soundname || e.payload.sound; + # if the notification contains a soundname, play it. + # playing a sound also requires the org.apache.cordova.media plugin + # my_media = new Media("/android_asset/www/"+ soundfile); + # my_media.play(); + else + # otherwise we were launched because the user touched a notification in the notification tray. + if e.coldstart + console.log 'COLDSTART NOTIFICATION' + else + console.log 'BACKGROUND NOTIFICATION' + console.log 'MSG: ' + e.payload.message + console.log 'MSGCNT: ' + e.payload.msgcnt + console.log 'TIMESTAMP: ' + e.payload.timeStamp + + @onNotification = (e)-> + console.log ('event is ' + e.event) + switch e.event + when 'registered' then registeredHandle e + when 'message' then messageHandle e + when 'error' then console.log 'Error ' + e.msg + else console.log 'Unknown, an event was received and we do not know what it is' + onDeviceReady = -> + window.plugins.pushNotification.register -> + console.log 'GCM register call succed' + , -> + console.log 'GCM register call failed' + , {"senderID":"72736982367","ecb":"onNotification"} + document.addEventListener("deviceready", onDeviceReady, false); diff --git a/client/android_jpush_init.js b/client/android_jpush_init.js new file mode 100644 index 000000000..d1fcd4766 --- /dev/null +++ b/client/android_jpush_init.js @@ -0,0 +1,144 @@ +if (Meteor.isCordova) { + /** + * Get registration ID in Cordova + * + * @method updateRegistrationID + * @param {Function} callback + * @return callback(got_registration_id,registration_id) + */ + var updateRegistrationID = function (callback){ + window.plugins.jPushPlugin.getRegistrationID(function(data) { + console.log("JPushPlugin:registrationID is "+data); + if(callback === null || callback=== undefined){ + return; + } + if(data ===null || data ===undefined){ + callback(false,null); + return; + } + if(data===''){ + console.log('RegisterationID is not set'); + callback(false,null); + } else { + callback(true,data); + } + }); + } + + var openNotificationInAndroidCallback = function(data){ + try{ + console.log("JPushPlugin:openNotificationInAndroidCallback"); + console.log("##RDBG data: " + JSON.stringify(data)); + SimpleChat.onPushNotifacation(); + + bToObj = data; + var message = bToObj.message; + var extras = bToObj.extras; + + var type = extras["cn.jpush.android.EXTRA"]["type"]; + switch(type){ + case "dailyrecommend": + var postId = extras["cn.jpush.android.EXTRA"]["postId"]; + console.log('##RDBG dailyrecommend postid: ' + postId); + Meteor.subscribe('postInfoById', postId, { + onStop: function() {}, + onReady: function(){ + console.log("##RDBG dailyrecommend onReady"); + PUB.page('/posts/'+postId); + } + }); + break; + case "comment": + if(Meteor.user()){ + //var postId = extras["cn.jpush.android.EXTRA"]["postId"]; + if(Meteor.user().profile.waitReadCount > 0) + Meteor.users.update({_id: Meteor.user()._id}, {$set: {'profile.waitReadCount': 0}}); + Router.go('/bell'); + } + break; + case "read": + if(Meteor.user()){ + //var postId = extras["cn.jpush.android.EXTRA"]["postId"]; + //PUB.page('/posts/'+postId); + } + break; + } + } + catch(exception){ + console.log("JPushPlugin:openCallback "+exception); + } + } + + /** + * Called when receive push notification from Server and APP is running + * + * @method pushNotificationCallback + * @param {Object} data Push notification context + */ + var pushNotificationCallback = function(data){ + try{ + console.log("JPushPlugin:receiveMessageInAndroidCallback"); + console.log("##RDBG data: " + JSON.stringify(data)); + SimpleChat.onPushNotifacation(); + /*data=data.replace('"{','{').replace('}"','}'); + var bToObj=JSON.parse(data); + var message = bToObj.message; + var extras = bToObj.extras; + + console.log(message); + console.log(extras['cn.jpush.android.MSG_ID']); + console.log(extras['cn.jpush.android.CONTENT_TYPE']); + console.log(extras['cn.jpush.android.EXTRA']);*/ + } + catch(exception){ + console.log("JPushPlugin:pushCallback "+exception); + } + } + + Meteor.startup(function(){ + if(device.platform === 'Android' ){ + // No longer jpush has call_native + /* + document.addEventListener("pause", onPause, false); + document.addEventListener("resume", onResume, false); + function onPause(){ + window.plugins.jPushPlugin.call_native("onPause", new Array(), null); + } + function onResume(){ + window.plugins.jPushPlugin.call_native("onResume", null, null); + } + */ + document.addEventListener("deviceready", onDeviceReady, false); + // PhoneGap加载完毕 + function onDeviceReady() { + Session.set('uuid',device.uuid); + window.plugins.jPushPlugin.receiveMessageInAndroidCallback = pushNotificationCallback; + window.plugins.jPushPlugin.openNotificationInAndroidCallback = openNotificationInAndroidCallback; + window.plugins.jPushPlugin.init(); + window.plugins.jPushPlugin.setBasicPushNotificationBuilder(); + var registerInterval = window.setInterval( function(){ + updateRegistrationID(function(got,registrationID){ + if(got===true){ + console.log('Got registrationID ' + registrationID); + var registerType = Session.get('registrationType'); + if ( !registerType || registerType === 'JPush'){ + Session.set('registrationID',registrationID); + Session.set('registrationType','JPush'); + window.clearInterval(registerInterval); + window.updatePushNotificationToken('JPush',registrationID); + } else { + window.clearInterval(registerInterval); + } + } else { + if(Session.get('registrationType') && Session.get('registrationID')){ + window.clearInterval(registerInterval); + return; + } + console.log("Didn't get registrationID, need retry later"); + } + }) + },20000 ); + } + } + }); +} diff --git a/client/autolabel/autolabel.html b/client/autolabel/autolabel.html new file mode 100644 index 000000000..38b51d076 --- /dev/null +++ b/client/autolabel/autolabel.html @@ -0,0 +1,81 @@ + + \ No newline at end of file diff --git a/client/autolabel/autolabel.js b/client/autolabel/autolabel.js new file mode 100644 index 000000000..e4474e8b2 --- /dev/null +++ b/client/autolabel/autolabel.js @@ -0,0 +1,83 @@ +//0:刚进入 1:处理中 2:成功 3:失败 +var progress = new ReactiveVar(0); +//0:刚进入显示开始 1:点击开始后显示结束按钮 +var btn_pro = new ReactiveVar(0); +var timeRange = {}; +Template.chooseLabelType.events({ + 'click #goAutolabel':function(e){ + var uuid = Router.current().params.uuid; + return PUB.page('/autolabel/'+uuid); + }, + 'click #goLabelstranger':function(e){ + var uuid = Router.current().params.uuid; + return PUB.page('/timelineAlbum/' + uuid + '?from=groupchat'); + // return PUB.page('/ishavestranger/'); + }, + 'click .back':function(e){ + btn_pro.set(0); + return PUB.back(); + } +}) +Template.autolabel.helpers({ + isShow:function(tag){ + + if(progress.get() == tag){ + return true; + } + return false; + }, + btnShow:function(tag){ + if(btn_pro.get() == tag){ + return true; + } + return false; + } +}) +Template.autolabel.events({ + 'click .back':function(e){ + progress.set(0); + return PUB.back(); + }, + 'click #start':function(e,t){ + //person_name + var person_name = t.find('.input-box').value; + console.log(person_name); + if(!person_name){ + return PUB.toast('请输入标注人名称'); + } + btn_pro.set(1); + //记录时间 + var now = new Date(); + var hour = new Date(now.getFullYear(),now.getMonth(),now.getDate(),now.getHours(),0,0); + var min = now.getMinutes(); + timeRange = { + start:{ + hour:hour, + min:min + } + }; + + timeRange.person_name = person_name; + }, + 'click #end':function(e){ + btn_pro.set(0); + //记录结束时间 + var now = new Date(); + var hour = new Date(now.getFullYear(),now.getMonth(),now.getDate(),now.getHours(),0,0); + var min = now.getMinutes(); + timeRange.end = { + hour:hour, + min:min + } + //进入处理状态 + progress.set(1); + Meteor.call('autolabel',timeRange,Router.current().params.uuid,function(err,res){ + if(err || (res && res.code==1)){ + console.log(err,res); + progress.set(3); + }else{ + progress.set(2); + } + }) + } +}) \ No newline at end of file diff --git a/client/autolabel/autolabel.less b/client/autolabel/autolabel.less new file mode 100644 index 000000000..be19abe9e --- /dev/null +++ b/client/autolabel/autolabel.less @@ -0,0 +1,85 @@ +.chooseLabelType{ + .box{ + display: flex; + flex-direction: row; + justify-content:space-around; + margin-top: 50px; + width:100%; + .circle{ + display: flex; + justify-content: center; + align-items: center; + width: 100px; + height: 100px; + border-radius: 50%; + } + p{ + text-align: center; + margin-top: 10px; + font-size: 18px; + color: #333333; + } + } + +} +.autolabel{ + .pre-label{ + margin-top: 50px; + text-align: center; + font-size: 18px; + } + .input-box{ + width: 80%; + padding: 0px 10px; + height: 40px; + line-height: 40px; + border: 1px solid #b3b3b3; + } + .btn-box{ + width: 50%; + height: 40px; + color: white; + margin-top: 50px; + border: none; + border-radius: 2px; + } + .labeling,.label-success,.label-failure{ + background: none; + margin-top: 50px; + text-align: center; + font-size: 18px; + color: #37a7fe; + .img-box{ + margin-top: 30px; + + } + } + .loading{ + -webkit-animation: spin 2s linear infinite; + animation: spin 2s linear infinite; + } + @-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } + } + @keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } + } +} \ No newline at end of file diff --git a/client/autologin/anonymousLogin.coffee b/client/autologin/anonymousLogin.coffee index dbbc59d96..6cee8c3f5 100644 --- a/client/autologin/anonymousLogin.coffee +++ b/client/autologin/anonymousLogin.coffee @@ -1,39 +1,44 @@ -# if Meteor.isClient -# unless Meteor.isCordova -# @anonymouslogin = ()-> -# if Session.get('disableAnonymousLogin') is true -# return -# console.log 'anonymouslogin' -# unless Meteor.user() -# createUser = ()-> -# uuid = Meteor.uuid() -# Accounts.createUser { -# username:uuid, -# password:'123456', -# 'profile':{ -# fullname:'匿名', -# icon:'/userPicture.png', -# anonymous:true, -# browser:true -# } -# } -# ,(error)-> -# console.log('Registration Error is ' + JSON.stringify(error)) -# unless error -# amplify.store('uuid',uuid) -# console.log('Registration Success, now logging on '+ uuid) -# Meteor.loginWithPassword(uuid,'123456',(error)-> -# unless error -# if window.updateMyOwnLocationAddress -# window.updateMyOwnLocationAddress() -# ) -# if amplify.store('uuid') -# Meteor.loginWithPassword(amplify.store('uuid'),'123456',(error)-> -# unless error -# if window.updateMyOwnLocationAddress -# window.updateMyOwnLocationAddress() -# else -# createUser() -# ) -# else -# createUser() +if Meteor.isClient + unless Meteor.isCordova + Meteor.startup -> + unless Meteor.user() + createUser = ()-> + uuid = Meteor.uuid() + Accounts.createUser { + username:uuid, + password:'123456', + 'profile':{ + fullname:'匿名', + icon:'/userPicture.png', + anonymous:true, + browser:true, + language: if isUSVersion then 'en' else 'zh' + } + } + ,(error)-> + console.log('Registration Error is ' + JSON.stringify(error)) + unless error + amplify.store('uuid',uuid) + console.log('Registration Success, now logging on '+ uuid) + Meteor.loginWithPassword(uuid,'123456',(error)-> + unless error + checkShareUrl() + Meteor.call 'updateUserLanguage', Meteor.userId(), 'en' + if window.updateMyOwnLocationAddress + window.updateMyOwnLocationAddress() + ) + if amplify.store('uuid') + Meteor.loginWithPassword(amplify.store('uuid'),'123456',(error)-> + unless error + checkShareUrl() + if isUSVersion + Meteor.call 'updateUserLanguage', Meteor.userId(), 'en' + else + Meteor.call 'updateUserLanguage', Meteor.userId(), 'zh' + if window.updateMyOwnLocationAddress + window.updateMyOwnLocationAddress() + else + createUser() + ) + else + createUser() diff --git a/client/bell/bell.coffee b/client/bell/bell.coffee index 53327c857..6a8c03ae8 100644 --- a/client/bell/bell.coffee +++ b/client/bell/bell.coffee @@ -17,14 +17,6 @@ if Meteor.isClient else if (target.data("visible")) target.data("visible", false); - ctt_msgs = $('#content_msgs') - if (Feeds.find().count() > 3) - ctt_msgs.css('overflow', 'hidden') - ctt_msgs.css('minHeight', '50px') - ctt_msgs.css('maxHeight', '240px') - ctt_msgs.css('position', 'relative') - ctt_msgs.after('
查看更多
') - Template.bell.helpers notReadCount: ()-> Feeds.find({isRead:{$ne: true}, checked:{$ne: true}}).count() @@ -33,7 +25,7 @@ if Meteor.isClient if (new Date() - new Date(createAt).getTime() ) > (7 * 24 * 3600 * 1000) return false if index > 20 - return false + return false if check or read return false else if arguments.length is 2 @@ -55,16 +47,10 @@ if Meteor.isClient return feeds else Session.get('persistentFeedsForMe') - isPComment:(eventType)-> - eventType is 'pcomments' - isPLike:(eventType)-> - eventType is 'like' - isPDislike:(eventType)-> - eventType is 'dislike' - isPCommentReply:(eventType)-> - eventType is 'pcommentReply' isAlsoComment:(eventType)-> eventType is 'pcomment' + isPCommentReply:(eventType)-> + eventType is 'pcommentReply' isAlsoFavourite:(eventType)-> eventType is 'pfavourite' isPcommentOwner:(eventType)-> @@ -94,16 +80,9 @@ if Meteor.isClient noMessages:-> if Feeds.find().count() > 0 or Session.equals('feedsCollection','loading') return false - else + else return true Template.bell.events - 'click .readmore': (e, t)-> - ctt_msgs = $('#content_msgs') - ctt_msgs.css('overflow', '') - ctt_msgs.css('minHeight', '') - ctt_msgs.css('maxHeight', '') - ctt_msgs.css('position', '') - $('.readmore').remove() 'click .closePersonalLetter': ()-> Session.set('inPersonalLetterView',false) $('body').css('overflow-y','auto') @@ -114,7 +93,12 @@ if Meteor.isClient document.getElementById(this._id + 'content').style.display='block' $(".bellAlertBackground").fadeIn 300 'click .contentList': (e)-> - trackEvent("blackMsgBox", "BlkMsgClickEachPost") + history = [] + history.push { + view: 'bell' + scrollTop: document.body.scrollTop + } + Session.set "history_view", history if this.pindex? Session.set("pcurrentIndex",this.pindex) Session.set("pcommetsId",this.owner) @@ -151,4 +135,9 @@ if Meteor.isClient createAt: new Date() } 'click #follow': (event)-> - history.go(-1) + Router.go '/searchFollow' + 'click #search': (event)-> + Router.go '/searchPeopleAndTopic' + 'click #bellPageback':(event)-> + PUB.back() + diff --git a/client/bell/bell.html b/client/bell/bell.html index 83f5ca228..c5aaa1ce6 100644 --- a/client/bell/bell.html +++ b/client/bell/bell.html @@ -2,43 +2,52 @@
- 我的消息 -
+ {{#if loading}} + + {{_ "receiving"}} + {{else}} + 系统消息 + {{/if}} +
+ +
+
-
+
+ + {{#if loadError}} +
{{_ "loadFailNotification"}}
+ {{else}} + {{#if noMessages}} +
{{_ "noMessages"}}
+ {{/if}} + {{/if}} + {{#unless noMessages}} {{#each eventFeeds}} - {{#if isPCommentReply eventType}} + {{#if isAlsoComment eventType}} {{#if notRead isRead checked @index createdAt}} -
+
{{/if}} -
{{ownerName}} 回复了您在《{{postTitle}}》的评论: "{{commentMsg}}"
+
{{ownerName}} {{_ "alsoCommentThisStory"}} 《{{postTitle}}》
{{time_diff createdAt}}
{{/if}} - {{#if isAlsoComment eventType}} + {{#if isPCommentReply eventType}} {{#if notRead isRead checked @index createdAt}}
{{/if}} - {{#if isPComment subType}} -
{{ownerName}} 也点评了此故事 《{{postTitle}}》: "{{commentMsg}}"
- {{else}} - {{#if isPLike subType}} -
{{ownerName}} 赞了 《{{postTitle}}》
- {{else}} - {{#if isPDislike subType}} -
{{ownerName}} 踩了 《{{postTitle}}》
- {{else}} -
{{ownerName}} 也点评了此故事 《{{postTitle}}》
- {{/if}} - {{/if}} - {{/if}} +
{{ownerName}} {{_ "pCommentReplyThisStory"}} 《{{postTitle}}》 {{_ "pCommentReplyThisStoryEnd"}}
{{time_diff createdAt}}
@@ -72,7 +81,7 @@
{{/if}} -
{{ownerName}} 也赞了此故事 《{{postTitle}}》
+
{{ownerName}} {{_ "alsoFavouriteThisStory"}} 《{{postTitle}}》
{{time_diff createdAt}}
@@ -85,7 +94,7 @@
{{/if}} -
{{ownerName}} 点评了您的故事 《{{postTitle}}》
+
{{ownerName}} {{_ "commentYourStory"}} 《{{postTitle}}》
{{time_diff createdAt}}
@@ -98,7 +107,7 @@
{{/if}} -
{{ownerName}} 回复了您参与讨论的故事 《{{postTitle}}》
+
{{ownerName}} {{_ "replyStoryYouJoin"}} 《{{postTitle}}》
{{time_diff createdAt}}
@@ -111,7 +120,7 @@
{{/if}} -
{{ownerName}} 回复了您的故事 《{{postTitle}}》
+
{{ownerName}} {{_ "replyYourStory"}} 《{{postTitle}}》
{{time_diff createdAt}}
@@ -124,7 +133,7 @@
{{/if}} -
{{ownerName}} 发布了新故事 《{{postTitle}}》
+
{{ownerName}} {{_ "publishNewStory"}} 《{{postTitle}}》
{{time_diff createdAt}}
@@ -137,7 +146,7 @@
{{/if}} -
{{recommander}} 推荐您一个新故事 《{{postTitle}}》
+
{{recommander}} {{_ "recommendAStory"}} 《{{postTitle}}》
{{time_diff createdAt}}
@@ -150,12 +159,12 @@
{{/if}} -
{{requester}} 邀请您加为好友!
+
{{requester}}{{_ "askFriends"}}
{{time_diff createdAt}}
{{#if isFriend requesterId}} -
已添加
+
{{_ "alreadyAdded"}}
{{else}} -
接受邀请
+
{{_ "acceptInvitation"}}
{{/if}}
@@ -171,9 +180,9 @@
{{requestee}}
{{time_diff createdAt}}
{{#if isFriend requesteeId}} -
已添加
+
{{_ "alreadyAdded"}}
{{else}} -
已发送邀请
+
{{_ "alreadySendInvitation"}}
{{/if}}
@@ -181,11 +190,51 @@
{{/if}} {{/each}} + {{/unless}} {{#if moreResults}}
- 加载中... + {{_ "loading"}}
{{/if}}
+
+ diff --git a/client/chatGroups/bubbleTips/bubbleTips.html b/client/chatGroups/bubbleTips/bubbleTips.html new file mode 100644 index 000000000..fff544dab --- /dev/null +++ b/client/chatGroups/bubbleTips/bubbleTips.html @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/client/chatGroups/bubbleTips/bubbleTips.js b/client/chatGroups/bubbleTips/bubbleTips.js new file mode 100644 index 000000000..bc3861dc9 --- /dev/null +++ b/client/chatGroups/bubbleTips/bubbleTips.js @@ -0,0 +1,3 @@ +Template.bubbleTipHintTemplate.rendered = function () { + $('body').addClass('bubbleTip-html-body'); +}; diff --git a/client/chatGroups/bubbleTips/bubbleTips.less b/client/chatGroups/bubbleTips/bubbleTips.less new file mode 100644 index 000000000..773352256 --- /dev/null +++ b/client/chatGroups/bubbleTips/bubbleTips.less @@ -0,0 +1,15 @@ +.bubbleTip-layer{ + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 9999999; +} +.bubbleTip-container{ + position: absolute; + top: 60px; + width: 100%; + text-align: center; +} +.bubbleTip-html-body{overflow-y: hidden !important;} \ No newline at end of file diff --git a/client/chatGroups/chatGroups.coffee b/client/chatGroups/chatGroups.coffee new file mode 100644 index 000000000..e1ddd2c14 --- /dev/null +++ b/client/chatGroups/chatGroups.coffee @@ -0,0 +1,178 @@ +if Meteor.isClient + @sysMsgToUserId = 'fTnmgpdDN4hF9re8F' + Template.chatGroups.created = -> + if Session.get('offlineMsgOverflow') + PUB.toast('收件箱已满,消息可能丢失......') + Session.set('offlineMsgOverflow', false) + + Template.chatGroups.rendered=-> + #$('.content').css 'min-height',$(window).height() + #Meteor.subscribe("get-my-group", Meteor.userId()) +# $('.mainImage').css('height',$(window).height()*0.55) + # $(window).scroll (event)-> + # target = $("#showMoreFollowsResults"); + # FOLLOWS_ITEMS_INCREMENT = 10; + # if (!target.length) + # return; + # threshold = $(window).scrollTop() + $(window).height() - target.height(); + # if target.offset().top < threshold + # if (!target.data("visible")) + # target.data("visible", true); + # Session.set("followersitemsLimit", + # Session.get("followersitemsLimit") + FOLLOWS_ITEMS_INCREMENT); + # else + # if (target.data("visible")) + # target.data("visible", false); + msgSess = SimpleChat.MsgSession.find({userId: Meteor.userId(),toUserId:{$ne: sysMsgToUserId}}, {sort: {updateAt: -1}}) + if (msgSess) + msgSess.forEach (sess)-> + if sess.sessionType is 'group' and sess.toUserId + Meteor.subscribe 'getStrangersByGroupId', sess.toUserId + + Template.chatGroups.helpers + hasNewLabelMsg:()-> + Session.get('hasNewLabelMsg') + syncing:()-> + Session.get('history_message') + showBubbleTipHintTemplate:()-> + Session.equals('needShowBubble','true') + msgSession2: ()-> + SimpleChat.MsgSession.update({toUserId:Session.get('touserid6')},{$set:{toUserName: Session.get('tousername6')}}) + return SimpleChat.MsgSession.find({userId: Meteor.userId(),toUserId:{$ne: sysMsgToUserId}}, {sort: {updateAt: -1}}) + isGroup: (msg)-> + return msg.to_type is 'group' + hasVal: (val)-> + console.log('hasVal:', if val then true else false) + return if val then true else false + hasCount: (val)-> + #Session.set('hasNewLabelMsg',true) + return val > 0 + formatTime: (val)-> + return get_diff_time(val) + # msgSession: ()-> + # SimpleChat.GroupUsers.find({user_id:Meteor.userId()}, {sort: {createdAt: -1}}) + # myChatGroups:()-> + # Follower.find({"userId":Meteor.userId()}, {sort: {createdAt: -1}}, {limit:Session.get("followersitemsLimit")}) + hasSysMessages:()-> + sysMsg = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId:sysMsgToUserId}) + if sysMsg + return true + return false + showRedSpot:()-> + msgSession = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId:sysMsgToUserId}) + if msgSession && msgSession.count + return msgSession.count > 0 + else + return false + # moreResults:-> + # !(Follower.find({"userId":Meteor.userId()}).count() < Session.get("followersitemsLimit")) + # loading:-> + # Session.equals('followersCollection','loading') + # loadError:-> + # Session.equals('followersCollection','error') + updateTotalReadCount = ()-> + totalReadCount = -1 + msgSess = SimpleChat.MsgSession.find() + msgSess.forEach((msg)-> + if (msg.count) + totalReadCount = totalReadCount + msg.count + ) + if (totalReadCount < 0) + Session.set('hasNewLabelMsg', false) + Template.chatGroups.events + 'click #joinTestChatGroups':(event)-> + event.stopImmediatePropagation() + PUB.page '/introductoryPage2' + 'click #createNewChatGroups':(event)-> + event.stopImmediatePropagation() + #Router.go('/group/add') + Session.set('fromCreateNewGroups',true); + Router.go('/setGroupname'); + #ScanBarcodeByBarcodeScanner() + 'click #addNewFriends':(event)-> + event.stopImmediatePropagation() + PUB.page '/searchFollow' + 'click #scanbarcode':(event)-> + event.stopImmediatePropagation() + ScanBarcodeByBarcodeScanner() + 'click #scanimage':(event)-> + event.stopImmediatePropagation() + DecodeImageFromAlum() + 'click #sysBell':(event)-> + # Meteor.defer ()-> + # msgSession = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId:sysMsgToUserId}); + # if msgSession + # SimpleChat.MsgSession.update({_id:msgSession._id},{$set:{count:0}}); + PUB.page('/checkInOutMsgList') + 'click li': (event)-> + if isIOS + if (event.clientY + $('.home #footer').height()) >= $(window).height() + console.log 'should be triggered in scrolling' + return false + critiaria = { group_id: { $eq: this.toUserId} }; + face_settings = {"face_list" : ["front","human_shape"], "fuzziness" : "100"} + if face_settings + critiaria.imgs = {$elemMatch: {style: {$in: face_settings.face_list}, fuzziness: {$gte: parseInt(face_settings.fuzziness)}}}; + scursor = Strangers.find(critiaria); + strange = scursor.count() + urlMsg = '/simple-chat/to/'+this.sessionType+'?id='+ this.toUserId + url = '/ishaveStranger/' + SimpleChat.MsgSession.update({_id: this._id}, {$set: {count: 0}}) + Session.set("isMark",true) + Session.set("session_type", this.sessionType) + Session.set("toUser_id",this.toUserId) + + strangeToUserId = this.toUserId + scursor.observeChanges + removed: (id)-> + count = Strangers.find(critiaria).count() + console.log('##RDBG, stranger count: ' + count) + if count is 0 and Router.current().originalUrl is '/ishaveStranger/' and Session.get('toUser_id') is strangeToUserId + PUB.page(urlMsg) + + if strange + PUB.page(url) + else + setTimeout ()-> + PUB.page(urlMsg) + ,animatePageTrasitionTimeout + + #Session.set('hasNewLabelMsg',false) + #updateTotalReadCount() + 'click .delBtnContent': (e,t)-> + e.stopImmediatePropagation(); + isSysDel = $(e.currentTarget).hasClass('sysDelBtn'); + userId = Meteor.userId(); + if isSysDel + SimpleChat.MsgSession.remove({userId: userId,toUserId:sysMsgToUserId},(err,num)-> + if(err) + return console.log('del MsgSession Err:',err); + console.log('num =',num) + # remove local msg with this Session + SimpleChat.Messages.remove({'to.id': sysMsgToUserId,'form.id': userId}); + SimpleChat.Messages.remove({'to.id': userId,'form.id': sysMsgToUserId}); + Meteor.setTimeout(()-> + SimpleChat.MessagesHis.remove({'to.id': sysMsgToUserId,'form.id': userId}); + SimpleChat.MessagesHis.remove({'to.id': userId,'form.id': sysMsgToUserId}); + ,100) + ); + return; + _id = e.currentTarget.id; + type = $(e.currentTarget).data('type'); + toUserId = $(e.currentTarget).data('touserid'); + $(e.target).parents('li').slideUp('fast', ()-> + $(e.target).parent('li').remove(); + # remove current list + SimpleChat.MsgSession.remove({_id: _id},(err,num)-> + if(err) + return console.log('del MsgSession Err:',err); + console.log('num =',num) + # remove local msg with this Session + SimpleChat.Messages.remove({'to.id': toUserId,'form.id': userId}); + SimpleChat.Messages.remove({'to.id': userId,'form.id': toUserId}); + Meteor.setTimeout(()-> + SimpleChat.MessagesHis.remove({'to.id': toUserId,'form.id': userId}); + SimpleChat.MessagesHis.remove({'to.id': userId,'form.id': toUserId}); + ,100) + ); + ); diff --git a/client/chatGroups/chatGroups.html b/client/chatGroups/chatGroups.html new file mode 100644 index 000000000..1c5ad6cd6 --- /dev/null +++ b/client/chatGroups/chatGroups.html @@ -0,0 +1,112 @@ + + + \ No newline at end of file diff --git a/client/chatGroups/checkInOutMsgList/checkInOutMsgList.coffee b/client/chatGroups/checkInOutMsgList/checkInOutMsgList.coffee new file mode 100644 index 000000000..1f49d8e33 --- /dev/null +++ b/client/chatGroups/checkInOutMsgList/checkInOutMsgList.coffee @@ -0,0 +1,133 @@ +Template.checkInOutMsgListImageItem.rendered=-> + this.$('img.lazy').lazyload() + +Template.checkInOutMsgList.rendered=-> + $('.content').css 'min-height',$(window).height() + Session.set('sysMsgIsRead',false); + Session.set('sysMsgLimit',20); + SimpleChat.loadMoreMesage({'to.id':Meteor.userId(),'form.id':sysMsgToUserId,is_read:Session.get('sysMsgIsRead')},{limit:Session.get('sysMsgLimit'),sort:{create_time:-1}},Session.get('sysMsgLimit')); + $(window).scroll (event)-> + target = $("#showMoreResults"); + MSG_IEMS_INCREMENT = 10; + if (!target.length) + return; + threshold = $(window).scrollTop() + $(window).height() - target.height(); + if target.offset().top < threshold + if (!target.data("visible")) + target.data("visible", true); + Session.set("sysMsgLimit", Session.get("sysMsgLimit") + MSG_IEMS_INCREMENT); + SimpleChat.loadMoreMesage({'to.id':Meteor.userId(),'form.id':sysMsgToUserId,is_read:Session.get('sysMsgIsRead')},{limit:Session.get('sysMsgLimit'),sort:{create_time:-1}},Session.get('sysMsgLimit')); + else + if (target.data("visible")) + target.data("visible", false); +Template.checkInOutMsgList.helpers + noMsg:()-> + count = SimpleChat.Messages.find({'to.id':Meteor.userId(),'form.id':sysMsgToUserId,is_read:Session.get('sysMsgIsRead')}).count(); + !(count > 0) + msgType:()-> + is_read = Session.get('sysMsgIsRead') + if is_read + return '已读' + return '未读' + isStartWork:()-> + if this.images and this.images.length > 0 + return true + return false + workTimeType:()-> + if this.checkin_out is 'in' + return '上班' + else if this.checkin_out is 'out' + return '下班' + return '' + start_work_time:(val)-> + date = new Date(val) + return date.getHours()+':'+ date.getMinutes() + + formatTime: (val)-> + return get_diff_time(val) + mySysMsg:()-> + return SimpleChat.Messages.find({'to.id':Meteor.userId(),'form.id':sysMsgToUserId,is_read:Session.get('sysMsgIsRead')},{sort: {create_time: -1}}) + moreResults:-> + count = SimpleChat.Messages.find({'to.id':Meteor.userId(),'form.id':sysMsgToUserId,is_read:Session.get('sysMsgIsRead')}).count(); + !(count < Session.get("sysMsgLimit")) +Template.checkInOutMsgList.events + 'click .top-unread-btn': (event)-> + Session.set('sysMsgIsRead',false) + $('.checkInOutMsgList .active').removeClass('active') + $(event.currentTarget).addClass('active') + 'click .top-read-btn':(event)-> + Session.set('sysMsgIsRead',true) + $('.checkInOutMsgList .active').removeClass('active') + $(event.currentTarget).addClass('active') + SimpleChat.loadMoreMesage({'to.id':Meteor.userId(),'form.id':sysMsgToUserId,is_read:Session.get('sysMsgIsRead')},{limit:Session.get('sysMsgLimit'),sort:{create_time:-1}},Session.get('sysMsgLimit')); + 'click .leftButton': (event)-> + msgSession = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId:sysMsgToUserId}); + if msgSession + SimpleChat.MsgSession.update({_id:msgSession._id},{$set:{count:0}}); + PUB.back() + 'click #list ul li':(e)-> + msgId = e.currentTarget.id + slef = this; + confirm_callBack = (index)-> + if index is 1 + img = slef.images[0]; + data = { + user_id:Meteor.userId(), + face_id:img.id, + person_info:{ + 'uuid': slef.people_uuid, + 'name': img.label, + 'group_id': slef.group_id, + 'img_url': img.url, + 'type': img_type, + 'ts': new Date(slef.create_time).getTime(), + 'accuracy': img.accuracy, # 准确度 + 'fuzziness': img.fuzziness, # 模糊度 + 'sqlid': img.sqlid, + 'style': img.style # 正脸 || 侧脸 + } + } + if slef.checkin_out is 'in' + data.checkin_time = slef.create_time; + else if slef.checkin_out is 'out' + data.checkout_time = slef.create_time; + Meteor.call('ai-checkin-out',data,(error,res)-> + if error || !res || res.result isnt 'succ' + PUB.toast '记录失败,请重试' + console.log 'ai-checkin-out error:' + error + return + PUB.toast '记录成功!' + ) + # ... + else + #跳转至时间轴 + if slef.people_uuid + PUB.page '/timelineAlbum/'+slef.people_uuid + else + PUB.page '/timeline' + confirm_text = '是否将该时间记录到每日出勤报告?' + if slef.checkin_out and slef.checkin_out isnt '' + if slef.images and slef.images.length > 0 + + # try + # navigator.notification.confirm(confirm_text, + # (index)-> + # confirm_callBack(index) + # ,'提示',['确定','修改']); + # catch e + # if confirm(confirm_text) + # confirm_callBack(1) + # else + # confirm_callBack(2) + else + #跳转至时间轴 + if slef.people_uuid + Session.set('wantModifyTime',slef.create_time); + PUB.page '/timelineAlbum/'+slef.people_uuid + else + PUB.page '/timeline' + SimpleChat.Messages.update({_id:msgId},{$set:{is_read:true}}); + 'click .check_in_btn':(e)-> + Session.set('wantModify',true); + Session.set('wantModifyTime',this.create_time); + PUB.page '/timelineAlbum/'+this.people_uuid \ No newline at end of file diff --git a/client/chatGroups/checkInOutMsgList/checkInOutMsgList.html b/client/chatGroups/checkInOutMsgList/checkInOutMsgList.html new file mode 100644 index 000000000..8b52a77ad --- /dev/null +++ b/client/chatGroups/checkInOutMsgList/checkInOutMsgList.html @@ -0,0 +1,54 @@ + + + diff --git a/client/chatGroups/checkInOutMsgList/checkInOutMsgList.less b/client/chatGroups/checkInOutMsgList/checkInOutMsgList.less new file mode 100644 index 000000000..425094685 --- /dev/null +++ b/client/chatGroups/checkInOutMsgList/checkInOutMsgList.less @@ -0,0 +1,69 @@ +.checkInOutMsgList { + .head .title{ + display: inline; + border-style: solid; + border-width: 1px; + border-radius: 3px; + strong{ + padding: 0px 10px; + } + .active{ + color: #37a7fe;; + background-color: #fff; + } + } + #wrapper{ + padding-top: 50px; + position: relative; + .no-msg-title{ + position: fixed; + top: 30%; + width: 100%; + text-align: center; + color: #9e9e9e; + } + ul li{ + padding: 10px 0; + border-bottom-style: solid; + border-bottom-width: 1px; + border-bottom-color: #dbdbdb; + position: relative; + font-size: 13px; + img{ + width: 60px; + object-fit: cover; + margin-top: 5px; + position: absolute; + } + .textContent{ + margin-left: 70px; + position: relative; + margin-right: 60px; + /* margin-top: 10px; */ + font-size: 13px; + height: 80px; + .text{margin-bottom: 15px;top: 0px;position: relative;width: 100%} + } + .text{margin-right: 60px;} + .time{height: 20px; line-height: 20px; position: absolute; right: 10px; top: 10px; color: #9e9e9e; font-size: 12px;} + .workTime{ + /* + display: inline-table; + margin-left: 20px; + text-align: center; + margin-top: -10px; + */ + margin-bottom: 5px; + } + .check_in_btn{ + color: #37a7fe; + font-weight: 400; + span{ + border: solid 1px; + padding: 1px 8px; + border-radius: 5px; + } + } + } + } +} \ No newline at end of file diff --git a/client/chatGroups/clusteringFix/clusteringFix.html b/client/chatGroups/clusteringFix/clusteringFix.html new file mode 100644 index 000000000..e7246598c --- /dev/null +++ b/client/chatGroups/clusteringFix/clusteringFix.html @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/client/chatGroups/clusteringFix/clusteringFix.js b/client/chatGroups/clusteringFix/clusteringFix.js new file mode 100644 index 000000000..c2ec57140 --- /dev/null +++ b/client/chatGroups/clusteringFix/clusteringFix.js @@ -0,0 +1,67 @@ +var limit = new ReactiveVar(0); +var limitSetp = 10 + +window.clusteringLazyloadInterval = null; +var initLazyload = function(){ + if(clusteringLazyloadInterval){ + window.clearInterval(clusteringLazyloadInterval); + } + window.setInterval(function(){ + $('ul').find('img.lazy:not([src])').lazyload({ + container: $('ul') + }); + },600); +} +Template.clusteringFix.onRendered(function() { + limit.set(20); + var group_id = Router.current().params._id; + Session.set('group_person_loaded',false); + Session.set('group_person_loadmore','loaded'); + Meteor.subscribe('group_person',group_id, limit.get(),function(){ + Session.set('group_person_loaded',true); + }); + + // scroll Loading + $(document).scroll(function(){ + if($('ul').height() - document.body.clientHeight - $(document).scrollTop() + 50 <= 0){ + console.log('start load more'); + Session.set('group_person_loadmore','loading'); + var limitCount = limit.get() + limitSetp; + Meteor.subscribe('group_person',group_id, limitCount,function(){ + Session.set('group_person_loadmore','loaded'); + limit.set(limitCount); + }); + } + }); + initLazyload(); +}); + +Template.clusteringFix.helpers({ + lists: function(){ + var group_id = Router.current().params._id; + return Person.find({group_id: group_id},{limit: limit.get(), sort:{createAt: -1}}).fetch(); + }, + isLoaded: function(){ + return Session.get('group_person_loaded'); + }, + isLoadingMore: function(){ + return Session.equals('group_person_loadmore','loading'); + } +}); + +Template.clusteringFix.events({ + 'click .back': function(e){ + return PUB.back(); + }, + 'click .personListItem': function(e){ + var faceId = e.currentTarget.id; + var group_id = Router.current().params._id; + return PUB.page('/clusteringFixPerson/'+group_id+'/'+faceId); + } +}); + +Template.clusteringFix.onDestroyed(function(){ + if(clusteringLazyloadInterval){ + window.clearInterval(clusteringLazyloadInterval); + } +}); \ No newline at end of file diff --git a/client/chatGroups/clusteringFix/clusteringFix.less b/client/chatGroups/clusteringFix/clusteringFix.less new file mode 100644 index 000000000..420decb48 --- /dev/null +++ b/client/chatGroups/clusteringFix/clusteringFix.less @@ -0,0 +1,88 @@ +.clustering{ + padding-top: 50px; padding-bottom: 40px; + .personLists { + margin: 0; + padding: 0 10px; + border-bottom: 1px solid #efefef; + border-top: 1px solid #efefef; + overflow: hidden; + } + .personListItem { + margin: 0; + padding: 0; + color: #000; + height: 64px; + overflow: hidden; + list-style: none; + border-bottom: 1px solid #efefef; + img { + padding: 10px; + padding-left: 0; + height: 64px; + width: 54px; + object-fit: cover; + float: left; + } + .personName { + line-height: 64px; + font-size: 16px; + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .right-ft { + display: inline-block; + float: right; + height: 64px; + line-height: 64px; + color: #888; + i{font-size: 20px;} + } + } + .personListItem:last-child{ + border: none; + } + + .clusteringLists { + color: #000; + margin: 0 5px; + padding: 0; + overflow: hidden; + } + .clusteringItem { + width: 20%; + padding: 5px; + list-style: none; + float: left; + img { + width: 100%; + height: 100%; + object-fit: cover; + } + .personName{ + font-size: 14px; + margin: 0; + height: 30px; + line-height: 30px; + width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + .clusteringItem.selected img { + box-shadow: 0 0px 6px 2px red; + border-radius: 4px; + } + #clusteringDel { + position: fixed; + bottom: 0; + background: #39a8fe; + width: 100%; + height: 40px; + line-height: 40px; + text-align: center; + cursor: pointer; + } +} \ No newline at end of file diff --git a/client/chatGroups/clusteringFix/clusteringFixPerson.html b/client/chatGroups/clusteringFix/clusteringFixPerson.html new file mode 100644 index 000000000..94bd5e9d0 --- /dev/null +++ b/client/chatGroups/clusteringFix/clusteringFixPerson.html @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/client/chatGroups/clusteringFix/clusteringFixPerson.js b/client/chatGroups/clusteringFix/clusteringFixPerson.js new file mode 100644 index 000000000..28d828ada --- /dev/null +++ b/client/chatGroups/clusteringFix/clusteringFixPerson.js @@ -0,0 +1,107 @@ +var limit = new ReactiveVar(0); +var selectedLists = new ReactiveVar([]); +var limitSetp = 50 + +window.clusteringLazyloadInterval = null; +var initLazyload = function(){ + if(clusteringLazyloadInterval){ + window.clearInterval(clusteringLazyloadInterval); + } + window.setInterval(function(){ + $('ul').find('img.lazy:not([src])').lazyload({ + container: $('ul') + }); + },600); +} +Template.clusteringFixPerson.onRendered(function() { + var rowHeight = Math.ceil(($('body').width() - 10) / 5); + var rowCount = Math.floor(($('body').height() - 80) / rowHeight) + var canFill = rowCount * 5; + + limit.set(canFill); + var group_id = Router.current().params.gid; + var faceId = Router.current().params.fid; + Session.set('group_person_loaded',false); + Session.set('group_person_loadmore','loaded'); + Meteor.subscribe('clusteringLists',group_id, faceId, limit.get(),function(){ + Session.set('group_person_loaded',true); + }); + + // scroll Loading + // $(document).scroll(function(){ + // if($('ul').height() - document.body.clientHeight - $(document).scrollTop() + 50 <= 0){ + // console.log('start load more'); + // Session.set('group_person_loadmore','loading'); + // var limitCount = limit.get() + limitSetp; + // Meteor.subscribe('clusteringLists',group_id,faceId, limitCount,function(){ + // Session.set('group_person_loadmore','loaded'); + // limit.set(limitCount); + // }); + // } + // }); + + initLazyload(); +}); + +Template.clusteringFixPerson.helpers({ + lists: function(){ + var group_id = Router.current().params.gid; + var faceId = Router.current().params.fid; + return Clustering.find({group_id: group_id, faceId: faceId, marked: {$ne: true}},{limit: limit.get()}).fetch(); + }, + selectedListsCount: function(){ + var lists = selectedLists.get() || []; + return lists.length; + }, + isLoaded: function(){ + return Session.get('group_person_loaded'); + }, + isLoadingMore: function(){ + return Session.equals('group_person_loadmore','loading'); + } +}); + +Template.clusteringFixPerson.events({ + 'click .back': function(e){ + return PUB.back(); + }, + 'click .clusteringItem': function(e){ + var lists = selectedLists.get() || []; + var _id = e.currentTarget.id + if($(e.currentTarget).hasClass('selected')){ + lists.splice(lists.indexOf(_id),1); + } else { + lists.push(_id); + } + console.log(lists); + selectedLists.set(lists); + $(e.currentTarget).toggleClass('selected'); + }, + 'click #clusteringDel': function(e){ + var ids = selectedLists.get() || []; + var marked_ids = []; + PUB.showWaitLoading('处理中'); + + $('.clusteringItem').not('.selected').each(function(item){ + marked_ids.push($(this).attr('id')) + }); + console.log(marked_ids); + + Meteor.call('clusteringFixPersons',ids, marked_ids,function(error, result){ + PUB.hideWaitLoading(); + if(error){ + console.log(error); + return PUB.toast('请重试!'); + } + selectedLists.set([]); + return PUB.toast(result); + }); + } +}); + + +Template.clusteringFixPerson.onDestroyed(function(){ + if(clusteringLazyloadInterval){ + window.clearInterval(clusteringLazyloadInterval); + } +}); \ No newline at end of file diff --git a/client/chatGroups/groupDevices/groupDevices.html b/client/chatGroups/groupDevices/groupDevices.html new file mode 100644 index 000000000..aa8bc02b9 --- /dev/null +++ b/client/chatGroups/groupDevices/groupDevices.html @@ -0,0 +1,125 @@ + + + diff --git a/client/chatGroups/groupDevices/groupDevices.js b/client/chatGroups/groupDevices/groupDevices.js new file mode 100644 index 000000000..600beb48d --- /dev/null +++ b/client/chatGroups/groupDevices/groupDevices.js @@ -0,0 +1,208 @@ +Template.groupDevices.onRendered(function () { + var group_id = Router.current().params._id; + Meteor.subscribe('device_by_groupId', group_id); +}); + +Template.groupDevices.helpers({ + currentFWVer: function(firmwareVersion){ + if(firmwareVersion){ + return firmwareVersion + } else { + return '未知' + } + }, + isChecked: function(autoUpdate){ + if(autoUpdate){ + return 'checked' + } else { + return '' + } + }, + lists: function() { + var group_id = Router.current().params._id; + return Devices.find({groupId: group_id}).fetch(); + }, + isEditing:function(uuid){ + if(Session.get('isEditing') == uuid){ + return true; + } + return false; + }, + isShow:function(uuid,tag){ + if(Session.get('isEditing') == uuid && tag==1){ + return 'display:none'; + } + if(Session.get('isEditing') != uuid && tag==2){ + return 'display:none'; + } + return ''; + }, + getId:function(uuid){ + return "input_"+uuid; + }, + isLatest: function(uuid){ + var curDevice = Devices.findOne({uuid:uuid}); + if(!curDevice.islatest && curDevice.islatest == null && curDevice.islatest == undefined){ + return 'display:none'; + } + return ''; + } +}); + +var cameraParams = {}; +function isValidateIPaddress(ipaddress) { + if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) { + return (true); + } + + return (false); +} +Template.groupDevices.events({ + 'click .latest_update': function(e,t){ + PUB.toast('您的脸脸安全盒当前是最新版本'); + return + }, + 'click .set-camera': function(e,t){ + cameraParams.id = this._id; + cameraParams.uuid = this.uuid; + $('#cameraSettings').modal('show'); + }, + 'click .saveCameraSettings': function(e,t){ + var rtsp = $('#rtspurl').val(); + rtsp = rtsp.trim(); + if (rtsp == "") { + PUB.toast('摄像头RTSP URL不能为空!'); + return; + } + + var obj = { + camrtspurl: rtsp, + uuid: cameraParams.uuid, + id: cameraParams.id, + groupId: Router.current().params._id + }; + + console.log('##RDBG obj: ' + JSON.stringify(obj)); + sendMqttMessage('/camerasettings', obj); + + $('#cameraSettings').modal('hide'); + }, + 'click #switch_update': function(e,t){ + console.log('switch update') + if(this.autoUpdate){ + Devices.update({_id:this._id},{$set:{autoUpdate:false}}) + } else { + Devices.update({_id:this._id},{$set:{autoUpdate:true}}) + } + return; + }, + 'click .name':function(e){ + var self = this; + Session.set('isEditing',this.uuid); + Meteor.setTimeout(function(){ + var node = $("#input_"+self.uuid); + var t = node.val(); + node.val("").focus().val(t); + },1000); + // var td = $(e.currentTarget); + // var text = $(e.currentTarget).text(); + // var input = $(''); + // $(e.currentTarget).html(input); + // $('input').click(function(){ + // return false; + // }); //阻止表单默认点击行为 + // var len = text.length; + // // $('input').setSelectionRange(len,len); + // $('input').select(); + // $('input').blur(function(event){ + // var nextxt=$(event.currentTarget).val(); + // if(nextxt == ''||nextxt == self.name){ + // nextxt = self.name; + // }else{ + // Meteor.call('change_device_name',self._id,self.uuid,self.groupId,nextxt,function(err){ + // if(err){ + // console.log(err); + // PUB.toast('修改失败,请重试'); + // td.html(self.name); + // } + // }) + // } + // td.html(nextxt); + // }); //表单失去焦点文本框变成文本 + }, + 'blur input':function(e){ + var self = this; + var newName = $(e.currentTarget).val(); + if(newName == ''||newName == this.name){ + Session.set('isEditing',null); + return; + } + Meteor.call('change_device_name', this._id, this.uuid, this.groupId, newName, function (err) { + if (err) { + console.log(err); + PUB.toast('修改失败,请重试'); + }else{ + Session.set('isEditing',null); + } + }) + }, + 'click .delBtnContent':function(e){ + var uuid = $(e.currentTarget).data('uuid'); + var id = e.currentTarget.id; + var group_id = Router.current().params._id; + //删除设备 + Meteor.call('delete_device',id,uuid,group_id,function(err){ + if(err){ + console.log(err); + return; + } + PUB.toast('删除成功'); + }) + }, + 'click .back': function(){ + return PUB.back(); + }, + 'click .goTimelime': function(e){ + var group_id = Router.current().params._id; + Session.set("channel",'groupDevices/'+group_id); + return PUB.page('/timelineAlbum/'+e.currentTarget.id+'?from=timeline'); + }, + 'click .goEdit':function(){ + var self = this; + if(!self.name){ + self.name = '未知设备'; + } + Session.set('curDevice',self); + var group_id = Router.current().params._id; + Session.set("channel",'groupDevices/'+group_id); + return PUB.page('/setDevicename'); + } +}); +Template.setDevicename.events({ + 'click .left-btn':function(){ + PUB.back(); + }, + 'click .right-btn':function(){ + $('.setGroupname-form').submit(); + }, + 'submit .setGroupname-form':function(e){ + e.preventDefault(); + var newName = e.target.text.value; + if(newName == ''){ + PUB.toast('请输入设备名'); + return; + } + if(newName == this.name){ + PUB.toast('设备名没有修改'); + return; + } + Meteor.call('change_device_name',this._id,this.uuid,this.groupId,newName,function(err){ + if(err){ + console.log(err); + PUB.toast('修改失败,请重试'); + }else{ + return PUB.back(); + } + }) + } +}) diff --git a/client/chatGroups/groupDevices/groupDevices.less b/client/chatGroups/groupDevices/groupDevices.less new file mode 100644 index 000000000..9fdde8ecf --- /dev/null +++ b/client/chatGroups/groupDevices/groupDevices.less @@ -0,0 +1,136 @@ +.groupDevicesList{ + min-height:%; + padding-top: 40px; + overflow:hidden; + #deviceList{ + text-align: left; + .title{ + text-align: right; + padding-right: 10px; + color: #54adf3; + } + } + .deviceItem{ + height:60px; + padding:10px 0; + position:relative; + + .delBtnContent{ + height: 60px; + line-height: 60px; + width: 100px; + text-align: center; + position: absolute; + right: -100px; + background: #f44336; + color: #fff; + border-bottom: 1px solid #ccc; + box-sizing: border-box; + top: 0px; + } + .edit{ + /* height: 30px; */ + /* line-height: 30px; */ + color:red; + padding-left: 10px; + padding-right: 10px + } + .name{ + margin-left: 10px; + } + + .automatic_update{ + font-size: 80%; + background-color: #FFFFFF; + border-radius: 5%; + color: #39a9fd; + border: 1px #39a9fd solid; + float: right; + position: relative; + top: 0.9rem; + } + .value-fa{ + display: inline-block; + text-align: right; + height: 20px; + line-height: 20xp; + font-size: 16px; + color: #888888; + float: right; + position: relative; + top: 1.4rem; + right: 1.5rem; + + .switch{ + position: relative; + width: 52px; + height: 23px; + border: 1px solid #dfdfdf; + border-radius: 16px; + box-sizing: border-box; + background-color: #dfdfdf; + transition: background-color 0.1s, border 0.1s; + -webkit-appearance: none; + margin: 0; + margin-top:-6px; + outline:none; + } + + .switch:checked{ + border-color: #04be02; + background-color: #04be02; + } + .switch:checked:before{transform: scale(0);} + .switch:before { + content: " "; + position: absolute; + top: 0; + left: 0; + width: 50px; + height: 20px; + border-radius: 10px; + background-color: #fdfdfd; + transition: transform 0.35s cubic-bezier(0.45, 1, 0.4, 1); + } + .switch:checked:after{transform: translateX(20px);} + .switch:after { + content: " "; + position: absolute; + top: 0; + left: 0; + width: 30px; + height: 20px; + border-radius: 10px; + background-color: #ffffff; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); + transition: transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35); + } + } + } + .usericon{ + width:40px; + height:40px; + border-radius:4px; + } + .latest_update{ + width:25px; + height:25px; + } + .set-camera{ + height: 40px; + position: absolute; + right: 80px; + display: inline-block; + line-height: 43px; + } + .set-camera .set-size{ + display:inline-block; + height: 20px; + font-size: 12px; + color: #54adf3; + line-height: 15px; + border: 1px #54adf3 solid; + padding: 2px 3px 2px 3px; + border-radius: 3px; + } +} diff --git a/client/chatGroups/groupPersons/groupPerson.html b/client/chatGroups/groupPersons/groupPerson.html new file mode 100644 index 000000000..a1334f461 --- /dev/null +++ b/client/chatGroups/groupPersons/groupPerson.html @@ -0,0 +1,39 @@ + \ No newline at end of file diff --git a/client/chatGroups/groupPersons/groupPerson.js b/client/chatGroups/groupPersons/groupPerson.js new file mode 100644 index 000000000..5e68b70f1 --- /dev/null +++ b/client/chatGroups/groupPersons/groupPerson.js @@ -0,0 +1,123 @@ +var view = null; +var dadaset_view = null; +var limit = new ReactiveVar(0); + +Template.groupPerson.helpers({ + is_type: function(val){ + return type.get() === val; + }, + isLoading:function(){ + return Session.get('group_person_loaded') != true; + }, + list: function(id){ + var arrEnglish = []; + var arrPinyin = []; + var group_id = Router.current().params._id; + /* + Person.find(selector, {sort: {name: 1}, limit: label_limit.get()}).forEach(function(item){ + arrEnglish.push(item); + }); + */ + + Person.find({group_id: group_id},{limit: limit.get(), sort:{name: 1}}).forEach(function(item){ + if(Session.get('is_human_shape') && !!item.human_shape){ + arrEnglish.push(item); + }else if(!Session.get('is_human_shape') && !!item.faces){ + arrEnglish.push(item); + } + }); + + /* + Person.find({group_id: group_id},{limit: limit.get(), sort:{createAt: -1}}).forEach(function(item){ + if(item.name && item.name.charCodeAt(0) > 255){ + item.pinyin = makePy(item.name)[0]; + arrPinyin.push(item); + } else { + arrEnglish.push(item); + } + }); + var compare = function (prop) { + return function (obj1, obj2) { + var val1 = obj1[prop]; + var val2 = obj2[prop]; + // 移除首尾空格 + val1 = val1.replace(/(^\s*)|(\s*$)/g, ""); + val2 = val2.replace(/(^\s*)|(\s*$)/g, ""); + // 统一英文字符为大写 + val1 = val1.toLocaleUpperCase(); + val2 = val2.toLocaleUpperCase(); + if (val1 < val2) { + return -1; + } else if (val1 > val2) { + return 1; + } else { + return 0; + } + } + } + arrEnglish = arrEnglish.sort(compare("name")); + arrPinyin = arrPinyin.sort(compare("pinyin")); + arrEnglish = arrEnglish.concat(arrPinyin); + */ + + return arrEnglish; + }, + is_human_shape:function(){ + return Session.get('is_human_shape'); + } +}); + +Template.groupPerson.events({ + 'click .back': function(){ + limit.set(0); + return PUB.back(); + }, + 'click .human_bth': function(){ + Session.set('is_human_shape', true); + }, + 'click .face_bth': function(){ + Session.set('is_human_shape', false); + } +}); + +Template.groupPerson.onRendered(function(){ + var group_id = Router.current().params._id; + + Meteor.subscribe('device_by_groupId',group_id); + + Meteor.subscribe('group_person',group_id, limit.get(),{ + onReady: function(){ + Session.set('group_person_loaded',true); + }, + onStop: function(err){ + console.log(err); + } + }); + this.$('.photos').each(function(){ + $(this).scroll(function(){ + var height = $(this).find('> ul').height(); + var top = $(this).scrollTop(); + + if ($(this).scrollTop() <= 0){ + var _limit = 0; + _limit = limit.get() + 20; + limit.set(_limit); + //Meteor.subscribe('group_person',group_id, limit.get()); + console.log('==已经滚动到顶部了 =='); + } else if (height - top <= $(this).height() - 20){ + limit.set(limit.get()+20); + console.log('==已经滚动到底部了 =='); + Meteor.subscribe('group_person',group_id, limit.get()); + } + }); + }); + + // disable img longpress default events + //this blocks android's image click event, disable it on android + if (device.platform != 'Android') { + $(document).on('touchstart','img', function(e){ + e.stopPropagation(); + e.preventDefault(); + }); + } +}); diff --git a/client/chatGroups/groupScore/groupInstallTest.html b/client/chatGroups/groupScore/groupInstallTest.html new file mode 100644 index 000000000..9df6d0188 --- /dev/null +++ b/client/chatGroups/groupScore/groupInstallTest.html @@ -0,0 +1,101 @@ + + + + \ No newline at end of file diff --git a/client/chatGroups/groupScore/groupInstallTest.js b/client/chatGroups/groupScore/groupInstallTest.js new file mode 100644 index 000000000..20798c5f8 --- /dev/null +++ b/client/chatGroups/groupScore/groupInstallTest.js @@ -0,0 +1,337 @@ +//0:不显示 1:开始测试 2:点击右上角 3:安装检查 +var showPop = new ReactiveVar(0); +var labelScore = new ReactiveVar('---'); +var roateScore = new ReactiveVar('---'); +var showRes = new ReactiveVar(true); +var timer; +var uuid; +var deviceUserId; +Template.groupInstallTest.onRendered(function(){ + var group_id = Router.current().params._id; + uuid = Router.current().params.uuid; + Meteor.subscribe('devices-by-uuid',uuid,function(err){ + if(err){ + console.log(err); + return; + } + + var st = Session.get('isStarting'); + if(st && st.label_score){ + labelScore.set(st.label_score); + } + if(st && st.roate_score){ + roateScore.set(st.roate_score); + } + var dev = Devices.findOne({uuid: uuid}); + if (!(dev.online && dev.camera_run)) { + showPop.set(4); + } + else if(!st){ + showPop.set(1); + } + }); + +}) + +Template.groupInstallTest.helpers({ + popupConfig:function(){ + var s = showPop.get(); + var isShow = s === 0?"display:none;":""; + var content = ''; + var btn = ''; + var head = ''; + var showFoot = ''; + var showClose = ''; + var deviceImg = '/device_offline.png'; + var cameraImg = '/camera_offline.png'; + var dev = Devices.findOne({uuid: Router.current().params.uuid}); + switch(s){ + case 1: + content = '请在点击开始后,按照摄像头部署方向以正常速度来回走过,检查你的摄像头安装角度和识别率'; + btn = '开始'; + break; + case 2: + content = '设备未接通,请查看右上角帮助'; + // btn = '确定'; + showFoot = 'display:none'; + break; + case 3: + head = '评测帮助'; + content = '

失败原因

1.“网络堵塞”可能导致部署评测失败,但是网络堵塞并不会影响来了吗的其它功能。

2. 如果想得到准确的部署评测分数,您可以根据以下操作来进行调整:' + + '

操作

先在镜头前行走1-2次后,再点击部署评测

说明

操作过程中,你可以去时间轴里观察行人照片出现的时间,参考下列标准检测你的网络堵塞状态

<=10秒,网络正常;

'+ + '

=>30秒,网络拥挤;

=>60秒,网络堵塞;

' + btn = "确定"; + break; + case 4: + head = '设备状态'; + if (dev.online) + deviceImg = '/face_box_online.svg'; + else + deviceImg = '/face_box_offline.svg'; + if (dev.camera_run) + cameraImg = '/camera_online.svg'; + else + cameraImg = '/camera_offline.svg'; + content = '
脸脸盒:
摄像头:

您的设备未接通,请检查设备连接后再次进行部署评测

'; + btn = "放弃"; + showClose = 'display: none;'; + break; + + } + return { + isShow:isShow, + content:content, + btn:btn, + head:head, + showFoot:showFoot, + showClose:showClose + } + }, +}) +Template.groupInstallTest.events({ + 'click .url_review':function(e){ + showPop.set(1); + }, + 'click .url_time':function(){ + if (Session.get("myHotPostsChanged")) + Session.set("myHotPostsChanged", false) + showPop.set(0); + PUB.page('/timeline') + }, + 'click .check':function(e){ + showPop.set(3); + }, + 'click .back':function(e){ + e.preventDefault(); + e.stopPropagation(); + // var s = Session.get('isStarting'); + // if(s && s.isTesting){ + // return PUB.toast('正在测试中,请勿离开'); + // } + labelScore.set('---'); + roateScore.set('---'); + Meteor.clearTimeout(timer); + timer = null; + Session.set('isStarting',null); + return PUB.back(); + }, + 'click #operate':function(e){ + e.stopPropagation(); + var t = showPop.get(); + if (t == 4) { + showPop.set(0); + setTimeout(function() { + labelScore.set('---'); + roateScore.set('---'); + Meteor.clearTimeout(timer); + timer = null; + Session.set('isStarting',null); + return PUB.back(); + }, 50); + } + if(t == 1){ + var group_id = Router.current().params._id; + Session.set('isStarting',{ + isTesting:true, + group_id:group_id + }) + //开始测试 + $('.progress-bar').addClass('time'); + var du = Meteor.users.findOne({username:uuid}); + deviceUserId = du._id; + timer = Meteor.setTimeout(test_score,40*1000); + } + showPop.set(0); + }, + +}) +var test_score = function(){ + $('.progress').hide(); + $('.progress-bar').removeClass('time'); + var st = Session.get('isStarting'); + st.isTesting = false; + st.showScore = true; + + var totalCount = message_queue.length; + var labelArr = _.filter(message_queue,function(m){ + if(m.label && m.label != ''){ + return true; + } + return false; + }); + var frontArr = _.filter(message_queue,function(m){ + if(m.style == "human_shape"){ + return m.style = "human_shape" + } + return m.style == 'front' + }); + var front_len = frontArr.length; + + if(totalCount != 0){ + roateScore.set(Math.floor(front_len/totalCount * 100) + ''); + showRes.set(true); + }else{ + roateScore.set('0'); + showPop.set(2); + showRes.set(false); + } + if(front_len != 0){ + if(labelArr.length >= front_len){ + labelScore.set(100 + ''); + }else{ + labelScore.set(Math.floor(labelArr.length/front_len * 100) + ''); + } + + }else{ + labelScore.set('0'); + } + message_queue = []; + if(totalCount == 0 || front_len == 0){ + st.status = "fail"; + }else{ + st.status = "success"; + } + Session.set('isStarting',st); +} +Template.popup.events({ + 'click .close':function(){ + showPop.set(0); + } +}) +Template.score.helpers({ + label_score:function(){ + return labelScore.get(); + }, + roate_score:function(){ + return roateScore.get(); + }, + cancel:function(){ + var s = Session.get('isStarting'); + if(!s || s.isTesting){ + return true; + } + return false; + }, + showRes:function(){ + var s = Session.get('isStarting'); + if(!s || s.isTesting || !showRes.get()){ + return true; + } + return false; + }, + isSuccess:function(){ + var s = Session.get('isStarting'); + if(s.status == "success"){ + return true; + } + return false; + }, + testres:function(tag){ + console.log(tag); + if(tag == 1 && labelScore.get()>70){ + return true; + } + if(tag == 2 && roateScore.get()>70){ + return true; + } + return false; + }, + showBtn:function(){ + var s = Session.get('isStarting'); + if(!s || s.isTesting){ + return false; + } + return true; + }, + showCancel:function(){ + var s = Session.get('isStarting'); + if(s && s.isTesting){ + return true; + } + return false; + } +}) + +Template.score.onRendered(function(){ +}) +Template.score.events({ + 'click #cancel':function(){ + Meteor.clearTimeout(timer); + var s = Session.get('isStarting'); + s.isTesting = false; + Session.set('isStarting',s); + message_queue = []; + // $('.progress').hide(); + showRes.set(false); + $('.progress-bar').removeClass('time'); + }, + 'click #restart':function(e){ + labelScore.set('---'); + roateScore.set('---'); + showRes.set(false); + message_queue = []; + var st = Session.get('isStarting'); + st.isTesting = true; + st.showScore = null; + Session.set('isStarting',st); + $('.progress').show(); + $('.progress-bar').addClass('time'); + timer = Meteor.setTimeout(test_score,40*1000); + }, + 'click #goTimeLine':function(){ + var group_id = Router.current().params._id; + var deviceLists = Devices.find({groupId: group_id}).fetch(); + var st = Session.get('isStarting'); + st.label_score = labelScore.get(); + st.roate_score = roateScore.get(); + Session.set('isStarting',st); + var uuid = Router.current().params.uuid; + return PUB.page('/timelineAlbum/'+uuid+'?from=groupchat'); + }, + 'click #goLink':function(e){ + var ref = cordova.ThemeableBrowser.open('http://workaiossqn.tiegushi.com/description.pdf', '_blank', { + title: { + color: '#000000', + showPageTitle: true, + staticText: '部署说明' + }, + closeButton: { + image: 'back', + imagePressed: 'back_pressed', + align: 'left', + event: 'closePressed' + }, + statusbar: { + color: '#37a7fe' + }, + toolbar: { + height: 44, + color: '#37a7fe' + } + }); + ref.addEventListener('closePressed', function(event) { + return ref.close(); + }); + } +}) +var message_queue = []; +GroupInstallTest = function(message){ + //users表 username(uuid) ==> message.form.id(users表的_id) + var uuid = Router.current().params.uuid; + message = JSON.parse(message); + console.log('GroupInstallTest'); + if(message.event_type == "motion"){ + return; + } + if(message.form.id != deviceUserId){ + console.log('rmMsg',message.form.name,deviceUserId); + return; + } + + if(message.images && message.images.length>0){ + message_queue.push.apply(message_queue,message.images); + } + console.log('GroupInstallTest',message_queue.length); +} \ No newline at end of file diff --git a/client/chatGroups/groupScore/groupInstallTest.less b/client/chatGroups/groupScore/groupInstallTest.less new file mode 100644 index 000000000..49b6d4098 --- /dev/null +++ b/client/chatGroups/groupScore/groupInstallTest.less @@ -0,0 +1,202 @@ +.groupInstallTest{ + + .gif-box{ + width: 100%; + height: auto; + // img{ + // max-height: 200px; + // } + text-align: center; + padding-bottom: 40px; + } + + .checkbox-custom { + position: relative; + padding: 0 0 0 40px; + margin-bottom: 7px; + margin-top: 0; + font-size: 16px; + } + /* + 将初始的checkbox的样式改变 + */ + .checkbox-custom input[type="checkbox"] { + opacity: 0;/*将初始的checkbox隐藏起来*/ + position: absolute; + cursor: pointer; + z-index: 2; + margin: -6px 0 0 0; + top: 50%; + left: 3px; + } + /* + 设计新的checkbox,位置 + */ + .checkbox-custom label:before { + content: ''; + position: absolute; + top: 50%; + left: 10px; + margin-top: -9px; + width: 19px; + height: 18px; + display: inline-block; + border-radius: 2px; + border: 1px solid #bbb; + background: #fff; + } + /* + 点击初始的checkbox,将新的checkbox关联起来 + */ + .checkbox-custom input[type="checkbox"]:checked +label:after { + position: absolute; + display: inline-block; + font-family: 'FontAwesome'; + content: '\F00C'; + top: 50%; + left: 14px; + margin-top: -5px; + font-size: 11px; + line-height: 1; + width: 16px; + height: 16px; + color: #333; + } + .checkbox-custom label { + cursor: pointer; + line-height: 1.2; + font-weight: normal;/*改变了rememberme的字体*/ + margin-bottom: 0; + text-align: left; + } + +} +.popup{ + background: rgba(0, 0, 0, 0.5); + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999; + .pp-dialog { + background: #fff; + width: 320px; + min-height: 108px; + top: 25%; + position: absolute; + left: 47%; + // margin-top: -200px; + margin-left: -150px; + border-radius: 8px; + overflow: hidden; + .pp-header{ + padding:0 16px; + height: 40px; + position: relative; + margin-top:-20px; + .close{ + position: absolute; + right: 0; + top: 0; + // display: inline-block; + width: 40px; + height: 40px; + text-align: right; + padding: 10px; + } + .title{ + text-align: center; + color: #555; + font-weight: bold; + padding-top:16px; + } + } + .pp-content{ + padding:10px; + color: #555; + text-indent:1em; + font-size: 18px; + } + .pp-foot { + font-size: 18px; overflow: hidden;border-top: 1px solid #efefef; + height: 48px; + // position: absolute; + // bottom: 0; + // left: 0; + // width: 100%; + button { + color: #555; + border: none; + background: none; + width: 100%; + height: 48px; + text-align: center; + font-size: 20px; + font-weight: bold; + } + } + } +} +.score{ + text-align: center; + font-size: 18px; + .score-box{ + margin: 10px; + line-height: 40px; + padding-left: 50px; + text-align: left; + font-size: 15px; + color: #333333; + } + button{ + width: 100px; + color: #f5f5f5; + border-radius: 5px; + background:#607590; + height: 40px; + border: none; + // font-weight: bold; + font-size: 16px; + } + label{ + font-weight: normal; + } + #restart,#cancel{ + margin-top: 20px; + width: 64%; + height: 50px; + background: #ff453c; + font-size: 16px; + } + + .time{ + animation: timer 40s linear 1; + } + .testing{ + text-align: center; + font-size: 15px; + color: #333333; + .dot{ + display: inline-block; + width: 5px; + height: 5px; + border-radius: 50%; + background: #333333; + animation: loading 1s infinite ease-in-out; + } + } +} + +@keyframes timer{ + 0%{width: 0%} + 100%{width: 100%} +} +@keyframes loading { + 0%,100%{ + transform: scale(0.5); + } + 60%{ + transform: scale(1); + } +} \ No newline at end of file diff --git a/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.coffee b/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.coffee new file mode 100644 index 000000000..8bff906b6 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.coffee @@ -0,0 +1,63 @@ +if Meteor.isClient + Template.groupAllUser.rendered=-> + Session.set('isSearching', false) + groupid = Session.get('groupsId') + Meteor.subscribe("get-group-user",groupid) + # $('#search-box').bind('propertychange input',(e)-> + # text = $(e.target).val().trim() + # if text.length > 0 + # Session.set 'isSearching', true + # Session.set 'noSearchResult',false + # Session.set 'searchLoading', true + # else + # Session.set 'isSearching', false + # return + # GroupUsersSearch.search text + # ) + Template.groupAllUser.helpers + userCount:-> + Counts.get('groupsUserCountBy-'+Session.get('groupsId')) + isMobile:()-> + Meteor.isCordova + isSearching:-> + if Session.get('isSearching') is false + false + else + true + noSearchResult:-> + return Session.get("noSearchResult") + searchLoading:-> + return Session.get('searchLoading') + groupUsers:()-> + return SimpleChat.GroupUsers.find({group_id:Session.get('groupsId')},{sort: {createdAt: 1}}) + userIsGroupCreator:()-> + group = SimpleChat.Groups.findOne({_id: Session.get('groupsId')}) + if group and group.creator and group.creator.id is this.user_id + return true + return false + # getGroupsUsers:()-> + # groupUsersSearchData = GroupUsersSearch.getData( + # transform: (matchText, regExp) -> + # #return matchText.replace(regExp, "$&") + # matchText + # sort: createdAt: -1) + # if GroupUsersSearch.getStatus().loaded == true + # if groupUsersSearchData.length == 0 + # Meteor.setTimeout (-> + # Session.set 'noSearchResult', true + # Session.set 'searchLoading', false + # return + # ), 500 + # else + # Session.set 'noSearchResult', false + # Session.set 'searchLoading', false + # return groupUsersSearchData + Template.groupAllUser.events + 'click #groupAllUserPageback':(event)-> + Session.set("groupsProfileMenu","groupInformation") + 'click #addUserInGroup':(event)-> + Session.set("groupsProfileMenu","inviteFriendIntoGroup") + 'click .userItem': (event)-> + #Session.set("groupsProfileMenu","setGroupname") + console.log event.currentTarget.id + PUB.page('/simpleUserProfile/'+event.currentTarget.id); diff --git a/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.html b/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.html new file mode 100644 index 000000000..57e789304 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.html @@ -0,0 +1,69 @@ + diff --git a/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.less b/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.less new file mode 100644 index 000000000..ca49ff254 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupAllUser/groupAllUser.less @@ -0,0 +1,69 @@ +.groupAllUser { + .head{ + background: #37a7fe; + } + #wrapper{ + position: relative; + padding-top: 40px; + padding-bottom: 55px; + -webkit-touch-callout: none; + } + ul{ + margin:0; + //padding-left:4% !important; + padding: 0px; + overflow:hidden; + list-style-type:none; + li{ + position: relative; + display: inline-block; + width:16%; + //margin-right: 2%; + margin-left:2.5%; + margin-bottom: 10px; + .group-admin-label { + position: absolute; + bottom: 0; + right: 0; + z-index: 999; + width: 20px; + height: 20px; + } + .box { + position: relative; + width: 100%; /* desired width */ + margin-top: 10px; + // margin-bottom: 5px; + } + + .box:before { + content: ""; + display: block; + padding-top: 100%; /* initial ratio of 1:1*/ + } + + .boxcontent { + position: absolute; + top: 5px; + left: 5px; + bottom: 5px; + right: 5px; + } + + img{ + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 5px; + } + .userName{ + text-align: center; + height: 16px; + overflow: hidden; + text-overflow:ellipsis; + color: black; + } + + } + } +} diff --git a/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.html b/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.html new file mode 100644 index 000000000..589ce34b1 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.html @@ -0,0 +1,245 @@ + \ No newline at end of file diff --git a/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.js b/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.js new file mode 100644 index 000000000..1b1ccc459 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.js @@ -0,0 +1,537 @@ + +var theCurrentDay = new ReactiveVar(null); +var theDisplayDay = new ReactiveVar(null); +var today = new ReactiveVar(null); +var todayUTC = new ReactiveVar(null); + + +var groupDevicesLoading = new ReactiveVar(false); +var workStatusLoading = new ReactiveVar(false); + +var showTimeLayerGroupUser = new ReactiveVar({}); + + +var parseDate = function(currentDay){ + //var today = new Date(Session.get('today')); + var year = currentDay.getFullYear(); + var month = currentDay.getMonth() + 1; + var date = year + '-' + month + '-' +currentDay.getDate(); + // if (currentDay.getDate() === today.getDate()) { + // date = date + ' 今天'; + // } + // else if (currentDay.getDate() - today.getDate() === -1 ) { + // date = date + ' 昨天'; + // } + //else { + var day = ''; + switch(currentDay.getDay()) + { + case 0: + day = '周日'; + break; + case 1: + day = '周一'; + break; + case 2: + day = '周二'; + break; + case 3: + day = '周三'; + break; + case 4: + day = '周四'; + break; + case 5: + day = '周五'; + break; + case 6: + day = '周六'; + break; + default: + break; + } + date = date + ' ' +day; + //} + return date; +}; + +Template.groupCheckInoutInfo.onRendered(function () { + + var now = new Date(); + var displayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , + 0, 0, 0, 0); + + theCurrentDay.set(date); // UTC日期 + todayUTC.set(date); + theDisplayDay.set(displayDate); // 当前显示日期 + today.set(displayDate); // 今天 + + var group_id = Session.get('groupsId'); + Meteor.subscribe('group_workstatus', group_id, date, { + onReady: function(){ + groupDevicesLoading.set(false); + workStatusLoading.set(false); + } + }); +}); + +Template.groupCheckInoutInfo.helpers({ + timeLayer: function(){ + var _obj = showTimeLayerGroupUser.get(); + var date = theCurrentDay.get(); + var status = WorkStatus.findOne({_id: _obj._id}); + + var time = null; + var result = {}; + if(_obj.in_out == 'in'){ + time = new Date(status.in_time); + time = time.shortTime(_obj.time_offset); + + result.src = status.in_image; + result.video_src = status.in_video; + } else { + time = new Date(status.out_time); + time = time.shortTime(_obj.time_offset) + + result.src = status.out_image; + result.video_src = status.out_video; + } + result.time = time; + return result; + }, + workStatusList: function(){ + var group_id = Session.get('groupsId'); + var date = theCurrentDay.get(); + console.log(date) + return WorkStatus.find({group_id: group_id,date: date}).fetch(); + }, + isLoading:function(){ + return workStatusLoading.get(); + }, + has_day_before:function(group_id){ + var lastday = today.get() - 7 * 24 * 60 * 60 *1000; //7天前 + return theDisplayDay.get() > lastday; + }, + day_title:function(){ + var currentDay = new Date(theDisplayDay.get()); + return parseDate(currentDay); + }, + has_day_after:function(group_id){ + // 可以查看后面两天天数据 + _today = new Date(today.get()); + _today.setDate(_today.getDate() + 2); + _today = new Date(_today.getFullYear(), _today.getMonth(), _today.getDate()).getTime(); + return theDisplayDay.get() < _today; + }, + show_back_today: function(group_id){ + return theDisplayDay.get() !== today.get(); + }, + devices: function(){ + var group_id = Session.get('groupsId'); + var in_out = Session.get('modifyMyStatus_in_out'); + return Devices.find({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}).fetch(); + }, + enable_push:function(app_notifaction_status){ + return app_notifaction_status === 'on'; + }, + //显示在监控组的状态(历史出现都离开监控组) + isStatusIN: function(status){ + if (this.in_time > 0) { + var date = new Date(this.in_time); + var time_offset = 8 + var group = SimpleChat.Groups.findOne({_id: this.group_id}); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + var fomatDate = date.shortTime(time_offset); + var isToday = fomatDate.indexOf('今天') > -1 ? true : false; + if (!isToday) { + return false; + } + } + return status === 'in'; + }, + //当天在监控组的状态 + isCurrentStatusIN:function(status){ + return status === 'in'; + }, + isInStatusNormal: function(in_status){ + return in_status === 'normal'; + }, + isInStatusWarning: function(in_status){ + return in_status === 'warning'; + }, + isInStatusError: function(in_status){ + return in_status === 'error'; + }, + isInStatusUnknown: function(in_status){ + return in_status === 'unknown'; + }, + + isOutStatusNormal: function(out_status){ + return out_status === 'normal'; + }, + isOutStatusWarning: function(out_status){ + return out_status === 'warning'; + }, + isOutStatusError: function(out_status){ + return out_status === 'error'; + }, + isOutStatusUnknown: function(out_status){ + return out_status === 'unknown'; + }, + isMySelf: function(app_user_id){ + return Meteor.userId() === app_user_id; + }, + InComTimeLen: function(group_id){ + var group_id = this.group_id; + var diff = 0; + var out_time = this.out_time; + var today_end = this.out_time; + var time_offset = 8 + var group = SimpleChat.Groups.findOne({_id: group_id}); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + + function DateTimezone(d, time_offset) { + if (time_offset == undefined){ + if (d.getTimezoneOffset() == 420){ + time_offset = -7 + }else { + time_offset = 8 + } + } + // 取得 UTC time + var utc = d.getTime() + (d.getTimezoneOffset() * 60000); + var local_now = new Date(utc + (3600000*time_offset)) + var today_now = new Date(local_now.getFullYear(), local_now.getMonth(), local_now.getDate(), + local_now.getHours(), local_now.getMinutes()); + + return today_now; + } + + //计算out_time + if(this.in_time) { + //var today_start_utc = new Date(Date.now()).setUTCHours(0,0,0,0); + + var date = new Date(this.in_time); + var fomatDate = date.shortTime(time_offset); + var isToday = fomatDate.indexOf('今天') > -1 ? true : false; + //不是今天的时间没有out_time的或者是不是今天时间,最后一次拍到的是进门的状态的都计算到当天结束 + if((!out_time && !isToday) || (this.status === 'in' && !isToday)) { + date = DateTimezone(date,time_offset); + day_end = new Date(date).setHours(23,59,59); + //day_end = new Date(this.in_time).setUTCHours(0,0,0,0) + (24 - time_offset)*60*60*1000 - 1; + out_time = day_end; + this.in_time = date.getTime(); + } + //今天的时间(没有离开过监控组) + else if(!out_time && isToday) { + var now_time = Date.now(); + out_time = now_time; + } + //今天的时间(离开监控组又回到监控组) + else if(out_time && this.status === 'in' && isToday) { + var now_time = Date.now(); + out_time = now_time; + } + } + + if(this.in_time && out_time){ + diff = out_time - this.in_time; + } + + if(diff > 24*60*60*1000) + diff = 24*60*60*1000; + else if(diff < 0) + diff = 0; + + var min = diff / 1000 / 60 ; + var hour = Math.floor(min/60)+' h '+Math.floor(min%60) + ' min'; + if(min < 60){ + hour = Math.floor(min%60) + ' min'; + } + if(diff == 0){ + hour = '0 min'; + } + return hour; + }, + modifyStatusClass: function(app_user_id){ + if(Meteor.userId() !== app_user_id){ + return 'modifyTaStatus'; + } + return 'modifyMyStatus'; + }, + whatsUpLists: function(){ + console.log(this); + var lists = []; + if(typeof(this.whats_up) == 'string'){ // 旧数据兼容 + lists.push({ + person_name: this.person_name, + content: this.whats_up, + ts: this.in_time // 当天上班时间 + }); + } else { + lists = this.whats_up; + } + return lists; + }, + getShortTime: function(ts,group_id){ + var time_offset = 8 + var group = SimpleChat.Groups.findOne({_id: group_id}); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + var time = new Date(ts); + return time.shortTime(time_offset,true); + }, + + isHistoryNoOut: function(group_id){ + var outtime = 0; + + if(this.out_time){ + outtime = this.out_time; + } + var isHistory = false; + if(theDisplayDay.get() && theDisplayDay.get() !== today.get()){ + isHistory = true; + } + var noOut = false; + if(outtime == 0){ + noOut = true; + } + return isHistory && noOut; + } +}); + +Template.groupCheckInoutInfo.events({ + 'click .deviceItem': function(e){ + $('#selectDevicesInOut').modal('hide'); + $('.homePage .content').removeClass('content_box'); + var taId = Session.get('modifyMyStatus_ta_id'); + var pageUrl = '/timelineAlbum/'+e.currentTarget.id; + Session.set('wantModify',true); + if(taId){ + pageUrl = '/timelineAlbum/'+e.currentTarget.id+'?taId='+taId; + } + setTimeout(function(){ + PUB.page(pageUrl); + },1000); + }, + 'click .editWhatsUp':function(e){ + var _id = e.currentTarget.id; + var whats_up = $(e.currentTarget).data('whatsup'); + $('.homePage .content').addClass('content_box'); + $('.saveWhatsUp').attr('id',_id); + Session.set('editWhatsUpData',this); + $('#EditorWhatsUp').val(''); + $('#myModal').modal('show'); + setTimeout(function(){ + $('#EditorWhatsUp').focus(); + },1000); + }, + 'click .saveWhatsUp':function(e){ + var _id = e.currentTarget.id; + var content = $('#EditorWhatsUp').val(); + if(!content || content.length < 1 || content.replace(/\s+/gim, '').length ==0){ // 内容为空或者全是空白字符, 不能提交 + $('#EditorWhatsUp').val(''); + return $('#EditorWhatsUp').focus(); + } + var data = Session.get('editWhatsUpData'); + console.log(data); + $('#'+_id).data('whatsup',whats_up); + var group_id = $('#'+_id).data('groupid'); + var statusId = $('#'+_id).data('id'); + var group = SimpleChat.GroupUsers.findOne({group_id:group_id,user_id: Meteor.userId()}); + console.log("group info is:", JSON.stringify(group)); + var editorName = group.user_name; + //whats_up = editorName + ":" + whats_up; + + var whats_up = data.whats_up || [] + if(whats_up && typeof(whats_up) === 'string'){ + whats_up = [{ + person_name: data.person_name, + content: whats_up, + ts: data.in_time // 当天上班时间 + }] + } + + whats_up.push({ + person_name: editorName, + content: content, + ts: Date.now() + }); + + WorkStatus.update({_id:_id},{ + $set:{whats_up:whats_up} + },function(err,num){ + if(err){ + return PUB.toast('请重试'); + } + if(!group){ + return; + } + var msgObj = { + _id: new Mongo.ObjectID()._str, + form:{ + id: group.user_id, + name: group.user_name, + icon: group.user_icon + }, + to: { + id: group.group_id, + name: group.group_name, + icon: group.group_icon + }, + to_type: 'group', + type: 'text', + text: '更新了今日简述:\r\n'+content, + create_time: new Date(), + is_read: false, + // send_status: 'sending' + }; + console.log(msgObj) + sendMqttGroupMessage(group_id,msgObj); + }); + $('#myModal').modal('hide'); + $('.homePage .content').removeClass('content_box'); + }, + 'click .close ,click .cancelWhatsUp':function(e){ + $('.homePage .content').removeClass('content_box'); + }, + 'click .modifyMyStatus':function(e){ + var group_id = Session.get('groupsId'); + var in_out = $(e.currentTarget).data('inout'); + var currentDay = theDisplayDay.get(); //当前显示的日期 + currentDay = new Date(currentDay); + currentDay.setHours(23); + currentDay.setMinutes(59); + Session.set('wantModifyTime',currentDay); + modifyStatusFun(group_id,in_out); + }, + 'click .modifyTaStatus': function(e){ + var group_id = Session.get('groupsId'); + var in_out = $(e.currentTarget).data('inout'); + var taId = $(e.currentTarget).data('taid'); + var taName = $(e.currentTarget).data('taname'); + var currentDay = theDisplayDay.get(); //当前显示的日期 + currentDay = new Date(currentDay); + currentDay.setHours(23); + currentDay.setMinutes(59); + Session.set('wantModifyTime',currentDay); + Session.set('modifyMyStatus_ta_name',taName); + + modifyStatusFun(group_id, in_out, taId); + // navigator.notification.confirm('要帮「'+taName+'」出现吗?',function(index){ + // if(index === 2){ + // modifyStatusFun(group_id, in_out, taId); + // } + // },'提示',['取消','帮TA出现']); + }, + 'click .in-out-pic': function(e){ + e.stopImmediatePropagation(); + var src = $(e.currentTarget).attr('src') + var time = new Date($(e.currentTarget).data('time')); + var group_id = $(e.currentTarget).data('groupid') + var time_offset = 8 + // if (group_id == '73c125cc48a83a95882fced3'){ + // //SWLAB + // time_offset = -7 + // }else if (group_id == 'd2bc4601dfc593888618e98f'){ + // //Kuming LAB + // time_offset = 8 + // } + + var group = SimpleChat.Groups.findOne({_id: group_id}); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + + // $('.timeLayer').html(time.shortTime(time_offset)); + // $('.imgLayer img.img_item').attr('src',src); + // var video_src = this.in_video || this.out_video; + var in_out = $(e.currentTarget).data('inout'); + var video_src = null; + if(in_out == 'in'){ + video_src = this.in_video; + } + if(in_out == 'out'){ + video_src = this.out_video; + } + + showTimeLayerGroupUser.set({ + _id: this._id, + in_out: in_out, + time_offset: time_offset + }); + // if (video_src) { + // $('.img_container .video-play-tip').show(); + // $('.img_container').addClass('videos'); + // $('.img_container').data('videosrc',video_src); + // } + $('.homePage').addClass('blur-element'); + $('#footer').addClass('blur-element'); + $('.inOutPicPreview').fadeIn('fast'); + }, + 'click .inOutPicPreview': function(e){ + $('.img_container .video-play-tip').hide(); + $('.img_container').data('videosrc',''); + $('.img_container').removeClass('videos'); + $('.blur-element').removeClass('blur-element'); + $('.inOutPicPreview').fadeOut('fast'); + }, + 'click .videos':function(e){ + e.stopImmediatePropagation(); + var video_src = $(e.currentTarget).data('videosrc'); + openVideoInBrowser(video_src); + }, + 'click .check_in_out':function(e){ + Router.go('/timeline'); + }, + 'click .day_before':function(e){ + e.stopImmediatePropagation(); + var currentDay = theCurrentDay.get() - 24 * 60 * 60 * 1000; + theCurrentDay.set(currentDay); + + var displayDay = theDisplayDay.get() - 24 * 60 * 60 * 1000; + theDisplayDay.set(displayDay); + + workStatusLoading.set(true); + Meteor.subscribe('group_workstatus', Session.get('groupsId'), theCurrentDay.get(), { + onReady: function(){ + workStatusLoading.set(false); + } + }); + }, + 'click .day_after':function(e){ + e.stopImmediatePropagation(); + var currentDay = theCurrentDay.get() + 24 * 60 * 60 * 1000; + theCurrentDay.set(currentDay); + + var displayDay = theDisplayDay.get() + 24 * 60 * 60 * 1000; + theDisplayDay.set(displayDay); + + workStatusLoading.set(true); + Meteor.subscribe('group_workstatus', Session.get('groupsId'), theCurrentDay.get(), { + onReady: function(){ + workStatusLoading.set(false); + } + }); + }, + 'click .day_today': function(e){ + e.stopImmediatePropagation(); + var group_id = Session.get('groupsId'); + + theCurrentDay.set(todayUTC.get()); + theDisplayDay.set(today.get()); + + workStatusLoading.set(true); + Meteor.subscribe('group_workstatus', Session.get('groupsId'), todayUTC.get(), { + onReady: function(){ + workStatusLoading.set(false); + } + }); + } +}); \ No newline at end of file diff --git a/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.less b/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.less new file mode 100644 index 000000000..bcce8da87 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupCheckInOutInfo/groupCheckInoutInfo.less @@ -0,0 +1,68 @@ +.groupCheckInoutInfo { + .panel-default >.panel-heading { + background: #fff; + padding: 0; + height: 40px; + line-height: 40px; + border-bottom: 1px solid #d4d4d4; + } + .table-bordered { + border: none !important; + } + .panel{ + min-height: 200px; + box-shadow: none; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + margin: 5px 0 10px 0; + } + tr td:first-child{ + border-right: 1px solid #ddd; + } + td,th{vertical-align:middle !important;text-align:center; border-bottom: none; border-top: none;} + + td i.fa {font-size: 22px; vertical-align: middle;} + .app-user-status{font-size: 12px !important;} + .tips-table{border:none;} + .tips-table td{text-align: justify;} + .tips-table td i.fa{font-size:18px;} + /*.editWhatsUp{white-space: nowrap; max-width: 200px;overflow: hidden; text-overflow: ellipsis;}*/ + input,textarea { + -webkit-touch-callout: default; + -webkit-user-select: text; + user-select: text; + } + .whats-up{ + text-align: justify; + font-size:12px; + height:30px; + line-height:30px; + max-width: 200px; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + .whats-up i{float: left;} + .whats-up p {padding-left: 20px; margin-bottom: 5px;} + .in-out-pic{ + width: 28px; + height:28px; + border: none; + border-radius:4px; + margin-right: 2px; + } + .fa-as-btn{ + border-radius: 4px; + width: 28px; + height: 28px; + line-height: 28px; + text-align: center; + background: #c1c1c1; + font-size: 16px !important; + background: linear-gradient(#efefef,#cccccc); + border: 1px solid #ccc; + box-sizing: border-box; + } + .modifyTaStatus,.modifyMyStatus{ + min-width: 70px; + padding: 4px 2px !important; + } +} \ No newline at end of file diff --git a/client/chatGroups/groupsProfile/groupPhoto/index.html b/client/chatGroups/groupsProfile/groupPhoto/index.html new file mode 100644 index 000000000..740127617 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupPhoto/index.html @@ -0,0 +1,150 @@ + + + + + + + + + + diff --git a/client/chatGroups/groupsProfile/groupPhoto/index.js b/client/chatGroups/groupsProfile/groupPhoto/index.js new file mode 100644 index 000000000..80d17c32a --- /dev/null +++ b/client/chatGroups/groupsProfile/groupPhoto/index.js @@ -0,0 +1,767 @@ +var view = null; +var dadaset_view = null; +var type = new ReactiveVar(''); +var limit1 = new ReactiveVar(0); +var limit2 = new ReactiveVar(0); +var limit3 = new ReactiveVar(0); +var selected = new ReactiveVar([]); +var selected2 = new ReactiveVar([]); +var lebeledPreLists = new ReactiveVar([]); +var limitSetp = 10; + +//重新训练 +retrain = function (group_id) { + var group = SimpleChat.Groups.findOne({ _id:group_id }); + var user = Meteor.user(); + var msg = { + _id: new Mongo.ObjectID()._str, + form: { + id: Meteor.userId(), + name: user.profile && user.profile.fullname ? user.profile.fullname : user.username, + icon: user.profile && user.profile.icon ? user.profile.icon : '/userPicture.png' + }, + to: { + id: group._id, + name: group.name, + icon: group.icon + }, + to_type: 'group', + type: 'text', + text: 'train', + create_time: new Date(), + is_read: false, + wait_classify: false + }; + sendMqttGroupMessage(msg.to.id, msg, function (err) { + if (err) { + console.log(err); + } + }); +} + +Template.groupPhoto.helpers({ + is_type: function(val){ + return type.get() === val; + }, + isLoading:function(){ + return Session.get('group_person_loaded') != true; + }, + is_hover: function(val){ + return type.get() === val ? 'hover' : ''; + }, + is_selected: function(){ + return selected.get().length > 0 ; + }, + is_selected2: function(){ + return selected2.get().length > 0 ; + }, + list1: function(id){ + return SimpleChat.Messages.find({is_people: true, 'to.id': id, admin_label: {$ne: true}}, {limit: limit1.get(), sort: {create_time: 1}}) + }, + list2: function(id){ + // return SimpleChat.GroupPhotoLabel.find({group_id: id}, {limit: limit2.get(), sort: {create_time: 1}}) + // return Person.find({group_id: id},{limit: limit2.get(), sort:{createAt: -1}}).fetch(); + var arrEnglish = []; + var arrPinyin = []; + Person.find({group_id: id},{limit: limit2.get(), sort:{createAt: -1}}).forEach(function(item){ + if(item.name && item.name.charCodeAt(0) > 255){ + item.pinyin = makePy(item.name)[0]; + arrPinyin.push(item); + } else { + arrEnglish.push(item); + } + }); + var compare = function (prop) { + return function (obj1, obj2) { + var val1 = obj1[prop]; + var val2 = obj2[prop]; + // 移除首尾空格 + val1 = val1.replace(/(^\s*)|(\s*$)/g, ""); + val2 = val2.replace(/(^\s*)|(\s*$)/g, ""); + // 统一英文字符为大写 + val1 = val1.toLocaleUpperCase(); + val2 = val2.toLocaleUpperCase(); + if (val1 < val2) { + return -1; + } else if (val1 > val2) { + return 1; + } else { + return 0; + } + } + } + arrEnglish = arrEnglish.sort(compare("name")); + arrPinyin = arrPinyin.sort(compare("pinyin")); + arrEnglish = arrEnglish.concat(arrPinyin); + return arrEnglish; + }, + conMainStyle: function(){ + if(type.get() === '未标注'){ + if(selected.get().length > 0){ + return 'bottom: 100px;'; + } else { + return 'bottom: 40px;'; + } + } else { + return 'bottom: 60px;'; + } + } +}); + +Template.groupPhoto.events({ + 'click .back': function(){ + Template.groupPhoto.close(); + }, + 'click .nav li': function(e){ + type.set($(e.currentTarget).html()); + }, + 'click .btn-default': function(e, t){ + var type = ''; + var call_back_handle = function(nameOrReason){ + if (!nameOrReason) { + $('.btn-default').show(); + return; + } + + var localTask = []; + var wait_labels = []; + + $(e.currentTarget).html('处理中...'); + var select = selected.get(); + select.map(function(item){ + var ids = item.split('|'); + var msgObj = SimpleChat.Messages.findOne({_id: ids[0]}); + msgObj.images.map(function(img, i){ + if (img._id === ids[1]){ + if(_.pluck(wait_labels, '_id').indexOf(msgObj.people_uuid + '|' + msgObj.people_id) === -1){ + wait_labels.push({ + _id: msgObj.people_uuid + '|' + msgObj.people_id, + id: msgObj.people_id, + uuid: msgObj.people_uuid, + url: img.url, + img_type: img.img_type, + label:img.label, + style: img.style, + sqlid: img.sqlid + }); + } + + // update local da + console.log('label', i); + localTask.push({ + _id: ids[0], + index: i, + obj: img, + uuid:msgObj.people_uuid, + create_time:msgObj.create_time, + group_id: t.data.id + }); + } + }); + }); + + console.log('nameOrReason:', nameOrReason, 'labels:', wait_labels); + console.log('local task:', localTask); + + Meteor.call('upLabels', t.data.id, nameOrReason, wait_labels,type, function(err, res){ + var flag = type === 'label' ? '标注' : '删除'; + if (err || !res){ + $(e.currentTarget).html(flag); + $('.btn-default').show(); + return alert(flag + '失败~'); + } + + localTask.map(function(task){ + var $set = JSON.parse('{"images.'+task.index+'.admin_label": true}'); + console.log('update local db:', $set, 'id:', task._id); + SimpleChat.Messages.update({_id: task._id}, {$set: $set}, function(err, num){ + if (err || num <= 0) + return; + var flag_label = type === 'label' ? nameOrReason : '已删'; + var is_delete = type === 'label' ? false : true; + // 生成群相册的标注信息(一张照片一条) + SimpleChat.GroupPhotoLabel.insert({ + msg_id: task._id, + img__id: task.obj._id, + img_id: task.obj.id, + img_uuid: task.uuid, + img_type: task.obj.img_type, + img_style: task.obj.style, + img_sqlid: task.obj.sqlid, + img_index: task.index, + img_label: flag_label, + img_url: task.obj.url, + is_delete:is_delete, + group_id: task.group_id, + create_time: new Date() + }); + // 处理是否已经全部标注 + var obj = SimpleChat.Messages.findOne({_id: task._id}); + if (obj && obj.images && obj.images){ + obj.admin_label = true; + obj.images.map(function(img){ + if (!img.admin_label) + obj.admin_label = false; + }); + if (obj.admin_label === true) + SimpleChat.Messages.update({_id: task._id}, {$set: {admin_label: true}}); + } + if (is_delete) { + return; + } + try { + if(task.obj.img_type && task.obj.img_type == 'face') { + var person_info = { + //'id': res[updateObj.images[i].label].faceId, + 'uuid': task.uuid, + 'name': nameOrReason, + 'group_id': task.group_id, + 'img_url': task.obj.url, + 'type': task.obj.img_type, + 'ts': new Date(task.create_time).getTime(), + 'accuracy': 1, + 'fuzziness': 1, + 'sqlid': task.obj.sqlid, + 'style': task.obj.style + }; + var data = { + face_id:task.obj.id, + person_info: person_info, + formLabel:true //是否是聊天室标记 + }; + console.log('data in index.js is: ',JSON.stringify(data)); + //Meteor.call('send-person-to-web', person_info, function(err, res){}); + Meteor.call('ai-checkin-out',data,function(err,res){}); + } + } catch(e){} + + }); + }); + $(e.currentTarget).html(flag); + $('.btn-default').show(); + selected.set([]); + alert(flag+'完成~'); + loadMoreImg(); + }); + + }; + if (e.currentTarget.id == 'not-label-del'){ + type = 'delete'; + $('.label-btn').hide(); + SimpleChat.show_remove(call_back_handle); + return; + } + if (e.currentTarget.id == 'not-label-label') { + type = 'label'; + $('.del-btn').hide(); + SimpleChat.show_label(t.data.id, call_back_handle); + return; + } + + if (e.currentTarget.id == 'labeled-label'){ + console.log('a is not b'); + var lists = lebeledPreLists.get(); + console.log(lists); + // To DO + return; + } + if (e.currentTarget.id == 'labeled-del') { + console.log('is not a'); + var lists = lebeledPreLists.get(); + console.log(lists); + // 从person 表中删除相应face + Meteor.call('remove-person-face',lists, function(err, res){ + if(err){ + console.log('groupPhoto labeled del Err:'+err); + return PUB.toast('删除失败,请重试!'); + } + for(var i=0; i < lists.length; i++) { + // 告诉平板, 这不是a + var trainsetObj = { + group_id: lists[i].group_id, + type: 'trainset', + url: lists[i].face_url, + person_id: '', + device_id: lists[i].device_id, + face_id: lists[i].face_id, + drop: true, + img_type: 'face', + style:'front', + sqlid: 0 + } + console.log('groupPhoto labeled del trainsetObj='+JSON.stringify(trainsetObj)); + sendMqttMessage('/device/'+lists[i].group_id, trainsetObj); + try{ + $('#'+lists[i].face_id).remove(); + } catch (err){} + }; + selected2.set([]); + lebeledPreLists.set([]); + }); + return; + } + } +}); + + +var loadMoreImg = function(){ + var img_item_count = $('.wait-label-item').length; + if (img_item_count >= 20) { + return; + } + limit = limit1.get() + limitSetp; + limit1.set(limit); + SimpleChat.withMessageHisEnable && SimpleChat.loadMoreMesage({is_people: true, 'to.id': data.id,admin_label: {$ne: true}}, {limit: limit, sort: {create_time: -1}}, limit); +} + +// lazyload +var lazyloadInitTimeout = {'未标注': null, '已标注': null,'labelDataset':null}; +var lazyloadInit = function($ul, type){ + lazyloadInitTimeout[type] && Meteor.clearTimeout(lazyloadInitTimeout[type]); + lazyloadInitTimeout[type] = Meteor.setTimeout(function(){ + $ul.find('img.lazy:not([src])').lazyload({ + container: $ul.parent() + }); + lazyloadInitTimeout[type] && Meteor.clearTimeout(lazyloadInitTimeout[type]); + }, 600); +}; + +Template.groupPhoto.onRendered(function(){ + var group_id = Router.current().params._id; + Meteor.subscribe('group_person',group_id, limit2.get(),{ + onReady: function(){ + Session.set('group_person_loaded',true); + }, + onStop: function(err){ + console.log(err); + } + }); + var data = this.data; + SimpleChat.withMessageHisEnable && SimpleChat.loadMoreMesage({is_people: true, 'to.id': data.id,admin_label: {$ne: true}}, {limit: limitSetp, sort: {create_time: -1}}, limitSetp); + + this.$('.photos').each(function(){ + $(this).scroll(function(){ + var height = $(this).find('> ul').height(); + var top = $(this).scrollTop(); + if ($(this).scrollTop() <= 0){ + var limit = 0; + if (type.get() === '未标注'){ + limit = limit1.get() + limitSetp; + limit1.set(limit); + SimpleChat.withMessageHisEnable && SimpleChat.loadMoreMesage({is_people: true, 'to.id': data.id,admin_label: {$ne: true}}, {limit: limit, sort: {create_time: -1}}, limit); + } else { + limit = limit2.get() + 50; + limit2.set(limit); + Meteor.subscribe('group_person',group_id, limit2.get()); + } + console.log('==已经滚动到顶部了 '+type.get()+' =='); + } else if (height-top <= $(this).height() -20){ + if (type.get() === '未标注') + limit1.set(limit1.get()+limitSetp); + else + limit2.set(limit2.get()+50); + console.log('==已经滚动到底部了 '+type.get()+' =='); + } + }); + }); + + // disable img longpress default events + $(document).on('touchstart','img', function(e){ + e.stopPropagation(); + e.preventDefault(); + }); +}); + +Template.groupPhotoImg.onRendered(function(){ + var $img = this.$('img'); + // console.log($img, $img.parent().parent(), $img.parent().parent().attr('data-type')); + lazyloadInit($img.parent().parent(), $img.parent().parent().attr('data-type')); +}); + +Template.groupPhotoImg1.onRendered(function(){ + var $img = this.$('img'); + // console.log($img, $img.parent().parent(), $img.parent().parent().attr('data-type')); + lazyloadInit($img.parent().parent(), $img.parent().parent().attr('data-type')); +}); + +Template.groupPhotoImg.helpers({ + has_selected: function(val1, val2){ + var id = val1 + '|' + val2; + return selected.get().indexOf(id) >= 0; + }, + is_type: function(val){ + return type.get() === val; + } +}); + +Template.groupPhotoImg.events({ + 'click li': function(e, t){ + if (type.get() != '未标注') + return; + + var id = e.currentTarget.id + '|' + this._id; + var res = selected.get(); + var index = res.indexOf(id); + if (index >= 0) + res.splice(index, 1); + else + res.push(id); + selected.set(res); + console.log(id); + } +}); + +Template.groupPhoto.open = function(id){ + view && Blaze.remove(view); + type.set('未标注'); + limit1.set(limitSetp); + limit2.set(50); + selected.set([]); + + var data = { + id: id, + limit: type.get() === '未标注' ? limit1.get() : limit2.get(), + // list1: SimpleChat.Messages.find({is_people: true, 'to.id': id}, {limit: limit1.get(), sort: {create_time: 1}}), + // list2: SimpleChat.Messages.find({is_people: true, 'to.id': id}, {limit: limit1.get(), sort: {create_time: 1}}), + type: type.get() + }; + Session.set('group_person_loaded',false); + view = Blaze.renderWithData(Template.groupPhoto, data, document.body); + $('body').css('overflow', 'hidden'); + $('.groupsProfile').hide(); +}; + +Template.groupPhoto.close = function(){ + // 释放变量 + limit1.set(0); + limit2.set(0); + selected.set([]); + selected2.set([]); + lebeledPreLists.set([]); + + view && Blaze.remove(view); + view = null; + $('body').css('overflow', 'auto'); + $('.groupsProfile').show(); +}; + + +// Template.groupPhotoImg1.helpers({ +// has_selected: function(id){ +// return selected2.get().indexOf(id) >= 0; +// }, +// }); + +Template.groupPhotoImg1.helpers({ + getLabelTimes: function() { + var times = this.label_times; + if(times && Number(times) > 999){ + return '999+'; + } + return times; + }, + imgCount:function(){ + if(Session.get('is_human_shape')){ + return this.human_shape.length; + } + return this.faces.length; + }, + imageCount: function(){ + //return LableDadaSet.find({group_id: groupid, name: name}).count(); + return this.imgCount + }, + isNeedLabelMore: function() { + return this.imgCount < 15; + }, + url: function(){ + if(Session.get('is_human_shape')){ + return this.human_shape[0].url; + } + return this.url; + }, + human_shape:function(){ + return Session.get('is_human_shape') ? 'human_shape' : ''; + } +}); + +Template.groupPhotoImg1.events({ + 'click .labelMoreImage': function (e) { + e.stopPropagation(); + e.preventDefault(); + + var group_id = Router.current().params._id; + var deviceLists = Devices.find({groupId: group_id}).fetch(); + + if (deviceLists && deviceLists.length > 0) { + if(deviceLists.length == 1 && deviceLists[0].uuid) { + return PUB.page('/timelineAlbum/'+deviceLists[0].uuid+'?from=groupchat'); + } else { + Session.set('_groupChatDeviceLists',deviceLists); + return $('._checkGroupDevice').fadeIn(); + } + } + return PUB.toast('该监控组下暂无脸脸盒'); + + }, + 'click li': function(e){ + var self = this; + + // remove or reName Person + var options = { + title: '请选择', + buttonLabels: ['查看此人的全部照片','删除此人','重命名此人'], + addCancelButtonWithLabel: '取消', + androidEnableCancelButton: true + }; + + window.plugins.actionsheet.show(options, function(index) { + switch (index) { + case 1: + return Template.person_labelDataset.open(self.group_id,self.name); + break; + case 2: + var confirmTitle = '要删除「'+self.name+'」吗?'; + PUB.confirm(confirmTitle, function() { + Meteor.call('removePersonById', self._id, function(error, result) { + if (error) { + console.log(error); + return PUB.toast('删除失败,请重试!'); + } + var trainsetObj = { + group_id: self.group_id, + face_id: self.faceId, + drop_person: true, + }; + console.log('removePersonById'+JSON.stringify(trainsetObj)); + sendMqttMessage('/device/'+self.group_id, trainsetObj); + //重新训练 + retrain(self.group_id); + return PUB.toast('已删除'); + }); + }); + break; + case 3: + var promptTip = '输入一个新的姓名以重命名此Person'; + var promptTitle = '重命名「'+self.name+'」'; + navigator.notification.prompt(promptTip, function(results) { + var newName = results.input1; + newName = newName.replace(/(^\s*)|(\s*$)/g,""); + if (results.buttonIndex == 2) { + if (!newName) { + return PUB.toast('请输入姓名'); + } + Meteor.call('renamePerson',self._id, newName,function(err, res) { + if (err) { + console.log('==sr==,error = '+err); + return PUB.toast('重命名失败') + } + return PUB.toast('已将「'+self.name+'」重命名为「'+newName+'」'); + }); + } + }, promptTitle, ['取消','重命名'], ''); + break; + default: + break; + } + }); + + // return Template.person_labelDataset.open(group_id,name); + /*var id = this.id; + var res = selected2.get(); + var index = res.indexOf(id); + var lists = lebeledPreLists.get(); + if(index >= 0){ + res.splice(index, 1); + lists.splice(index,1); + } else { + res.push(id); + lists.push({ + face_id: this.id, + face_url: this.url, + group_id: $(e.currentTarget).data('gid'), + device_id: $(e.currentTarget).data('did'), + faceId: $(e.currentTarget).data('fid'), + name: $(e.currentTarget).data('name') + }); + } + console.log(res); + console.log(lists); + selected2.set(res); + lebeledPreLists.set(lists);*/ + } +}) + +Template.labelDatasetImg.onRendered(function(){ + var $img = this.$('img'); + // console.log($img, $img.parent().parent(), $img.parent().parent().attr('data-type')); + lazyloadInit($img.parent().parent(), $img.parent().parent().attr('data-type')); +}); + +Template.labelDatasetImg.helpers({ + isAutoLabel: function(id) { + console.log("isAutoLabel: id = "+id); + return id != null && id.indexOf("autolabel") >= 0; + }, + has_selected: function(id){ + return selected2.get().indexOf(id) >= 0; + }, +}); + +Template.labelDatasetImg.events({ + 'click li': function(e){ + e.preventDefault(); + e.stopPropagation(); + var id = this._id; + var res = selected2.get(); + var index = res.indexOf(id); + var lists = lebeledPreLists.get(); + if(index >= 0){ + res.splice(index, 1); + lists.splice(index,1); + } else { + res.push(id); + lists.push({ + face_id: this.id, + face_url: this.url, + group_id: $(e.currentTarget).data('gid'), + device_id: $(e.currentTarget).data('did'), + faceId: $(e.currentTarget).data('fid'), + name: $(e.currentTarget).data('name') + }); + } + console.log(res); + console.log(lists); + selected2.set(res); + lebeledPreLists.set(lists); + } +}); + +Template.person_labelDataset.open = function(group_id,name){ + dadaset_view && Blaze.remove(dadaset_view); + limit3.set(50); + selected2.set([]); + + var data = { + group_id: group_id, + name:name, + limit: limit3.get(), + }; + Session.set('lableDadaSetLoaded',false); + dadaset_view = Blaze.renderWithData(Template.person_labelDataset, data, document.body); +}; + +Template.person_labelDataset.close = function(){ + // 释放变量 + limit3.set(0); + selected2.set([]); + lebeledPreLists.set([]); + + dadaset_view && Blaze.remove(dadaset_view); + dadaset_view = null; +}; + +Template.person_labelDataset.onRendered(function(){ + var data = this.data; + var group_id = data && data.group_id ; + Meteor.subscribe('person_labelDataset',group_id,data.name, limit3.get(),{ + onReady: function(){ + Session.set('lableDadaSetLoaded',true); + }, + onStop: function(err){ + console.log(err); + } + }); + $('.photos').scroll(function(){ + var height = $(this).find('> ul').height(); + var top = $(this).scrollTop(); + if (height-top <= $(this).height() -20){ + var limit = limit3.get() + 50; + limit3.set(limit); + Meteor.subscribe('person_labelDataset',group_id,data.name, limit3.get()); + } + }); +}); + +Template.person_labelDataset.helpers({ + list:function(){ + var list = LableDadaSet.find({group_id: this.group_id,name:this.name},{limit: limit3.get(), sort:{createAt: -1}}).fetch(); + return LableDadaSet.find({group_id: this.group_id,name:this.name},{limit: limit3.get(), sort:{createAt: -1}}).fetch(); + }, + is_selected2:function(){ + return selected2.get().length > 0 ; + }, + isLoading:function(){ + return Session.get('lableDadaSetLoaded') != true; + } +}); + +Template.person_labelDataset.events({ + // 全选 + 'click #selectAll': function(){ + var lists = []; + var preLists = []; + LableDadaSet.find({group_id: this.group_id,name:this.name},{limit: limit3.get(), sort:{createAt: -1}}).forEach(function(item){ + lists.push(item._id); + preLists.push({ + face_id: item.id, + face_url: item.url, + group_id: item.group_id, + device_id: item.uuid, + faceId: item.id, + name: item.name + }); + }); + + console.log(lists); + console.log(preLists); + selected2.set(lists); + lebeledPreLists.set(preLists); + }, + // 全不选 + 'click #unSelectAll': function(){ + selected2.set([]); + lebeledPreLists.set([]); + }, + 'click .back': function(){ + Template.person_labelDataset.close(); + }, + 'click .btn-default': function(e, t){ + if (e.currentTarget.id == 'labeled-del') { + console.log('is not a'); + var lists = lebeledPreLists.get(); + console.log(lists); + // 从person 表中删除相应face + Meteor.call('remove-person-face',lists, function(err, res){ + if(err){ + console.log('groupPhoto labeled del Err:'+err); + return PUB.toast('删除失败,请重试!'); + } + for(var i=0; i < lists.length; i++) { + // 告诉平板, 这不是a + var trainsetObj = { + group_id: lists[i].group_id, + type: 'trainset', + url: lists[i].face_url, + person_id: '', + device_id: lists[i].device_id, + face_id: lists[i].face_id, + drop: true, + img_type: 'face', + style:'front', + sqlid: 0 + } + console.log('groupPhoto labeled del trainsetObj='+JSON.stringify(trainsetObj)); + sendMqttMessage('/device/'+lists[i].group_id, trainsetObj); + try{ + $('#'+lists[i].face_id).remove(); + } catch (err){} + }; + selected2.set([]); + lebeledPreLists.set([]); + //重新训练 + retrain(lists[0].group_id); + }); + return; + } + } +}); diff --git a/client/chatGroups/groupsProfile/groupPhoto/index.less b/client/chatGroups/groupsProfile/groupPhoto/index.less new file mode 100644 index 000000000..a7c8588b8 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupPhoto/index.less @@ -0,0 +1,100 @@ +._group-photo,.person_labelDataset{ + position: absolute; left: 0; top: 0; right: 0; bottom: 0; background-color: rgb(238,238,238); z-index: 9999; + .head{ + ul.nav{ + width: 120px; margin: 0px auto; background-image: none; + li{width: 50%; height: 40px; line-height: 40px; float: left; font-size: 14px;} + li.hover{font-size: 16px;} + li.hover:before{content: ""; position: absolute; left: 50%; bottom: 0; width: 20px; height: 2px; margin-left: -10px; background-color: #fff;} + } + } + .con-main{ + position: absolute; left: 0; right: 0; bottom: 100px; top: 45px; background-color: #fff; overflow: hidden; + .photos{ + position: absolute; left: 0; right: 0; top: 0; bottom: 0; overflow: auto; visibility: hidden; -webkit-tap-highlight-color: none;-webkit-overflow-scrolling: touch; + ul{margin: 0; padding: 10px 5px; text-align: center;} + li{ + padding: 3px; display: inline-block; border-radius: 3px; overflow: hidden; position: relative; + img{width: 60px; height: 60px; background-color: #ccc;} + .selected{ + position: absolute;right: 5px;top: 5px;width: 24px;height: 24px;border-radius: 50%;background-color: #fff;padding: 1px;box-shadow: 0px 0px 2px #616161; + i{background-color: #2196f3;color: #fff;border-radius: 50%;font-size: 18px;line-height: 22px;width: 22px;} + } + // .label{ + // position: absolute; left: 3px; bottom: 3px; right: 3px; height: 20px; line-height: 20px; font-size: 12px; background: rgba(0, 0, 0, 0.25); + // } + .label { + display: block;background: none;color: #555;width: 60px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;padding: 5px 0; + } + } + li.group-photo-labeled-item{ + background: #fff; + display: block; + color: #555; + text-align: justify; + border-bottom: 1px solid #eee; + border-radius: 0; + padding: 10px 0; + margin: 0 10px; + img { + height: 50px; width: 50px; margin-right: 10px; float: left;background-color: #ccc; + } + .name{ font-size: 14px; margin: 0; line-height: 30px; height: 30px;} + p{font-size: 12px; margin: 0; line-height: 20px;} + .label_times{color: #39a8fe;height: 50px;line-height: 50px;display: inline-block;position: absolute;right: 0;text-align: right;width: 80px;top: 10px;cursor: pointer;} + } + li.group-photo-labeled-item:last-child{ + border: none; + } + li.human_shape{ + width: 20%; + display: inline-block; + margin: 0; + float: left; + text-align: center; + padding: 10px 20px; + } + li.human_shape img{ + width: 100%; + height: auto; + } + li.human_shape .name{ + display: block; + clear: both; + height: auto; + img{ + display: block; + margin: 0 auto; + } + } + li.human_shape p{ + display: block; + clear: both; + position: relative; + margin: 0 auto; + line-height: 20px; + text-align: center; + .label_times{ + position: relative; + text-align: center; + line-height: 30px; + } + } + } + .photos.hover{visibility: visible;} + } + .buttons{ + position: absolute; left: 0; right: 0; bottom: 0; height: 90px; line-height: 90px; text-align: center; font-size: 14px; color: #37a7fe; padding: 2.5px; box-sizing: border-box; + .btn-default{background-color: #37a7fe;color: #fff;border-radius: 5px;height: 40px;line-height: 40px; font-size:16px;} + } + .foot-tips{ + position: absolute; + left: 0; + right: 0; + bottom: 0; + color: #37a7fe; + padding: 10px 16px; + text-align: center; + p{text-align: justify; margin: 0;} + } +} \ No newline at end of file diff --git a/client/chatGroups/groupsProfile/groupUserHide/groupUserHide.html b/client/chatGroups/groupsProfile/groupUserHide/groupUserHide.html new file mode 100644 index 000000000..c1b366b60 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupUserHide/groupUserHide.html @@ -0,0 +1,96 @@ + diff --git a/client/chatGroups/groupsProfile/groupUserHide/groupUserHide.js b/client/chatGroups/groupsProfile/groupUserHide/groupUserHide.js new file mode 100644 index 000000000..bc4675fdf --- /dev/null +++ b/client/chatGroups/groupsProfile/groupUserHide/groupUserHide.js @@ -0,0 +1,231 @@ +Template.groupUserHide.onRendered(function(){ + var group_id = Router.current().params._id; + Session.set('groupUserHideLimit',20); + Session.set('groupUserHideLoaded','loading'); + Session.set('groupUserHideLoadedCount',0); + Meteor.subscribe('group-user-relations',group_id,Session.get('groupUserHideLimit'),function(){ + Session.set('groupUserHideLoaded','loaded'); + }); + Session.set('noshowPerson',true); + $('html,body').scrollTop(0); + // 页面滚动监听 + $(document).on('scroll', function(){ + var diff = $('.userHideLists').height() - $('html,body').scrollTop() - $('body').height() + 40; + if(diff < 0){ + if(!Session.equals('groupUserHideLoaded','loaded')){ + console.log('loading', diff) + var limit = Session.get('groupUserHideLimit'); + limit = limit + 20; + Session.set('groupUserHideLoaded','loading'); + Meteor.subscribe('group-user-relations',group_id,limit,function(){ + Session.set('groupUserHideLoaded','loaded'); + var count = WorkAIUserRelations.find({'group_id':group_id}).count(); + if(Session.get('groupUserHideLoadedCount') < count){ + Session.set('groupUserHideLoadedCount',count); + Session.set('groupUserHideLimit',limit); + } + }); + } + } + }); +}); + +Template.groupUserHide.helpers({ + lists: function () { + var group_id = Router.current().params._id; + var list = WorkAIUserRelations.find({'group_id':group_id}).fetch(); + var groupUser = SimpleChat.GroupUsers.findOne({group_id:group_id,user_id:Meteor.userId()}); + var arr = []; + if(groupUser.settings && groupUser.settings.not_notify_acquaintance){ + arr = groupUser.settings.not_notify_acquaintance; + } + for(var i=0;i + group_intime = '09:00' + group_outtime = '18:00' + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + + if (group and group.group_intime) + group_intime = group.group_intime + + if (group && group.group_outtime) + group_outtime = group.group_outtime + + group_intime = group_intime.split(":") + group_outtime = group_outtime.split(":") + + $('#groupInOutTime').mobiscroll().range({ + defaultVaule: [new Date(),new Date()], + theme: 'material', + lang: 'zh', + display: 'bottom', + controls: ['time'], + maxWidth: 100, + setText: '设置', + fromText: '上班时间', + toText:'下班时间', + defaultValue: [ + new Date(new Date().setHours(group_intime[0], group_intime[1], 0, 0)),new Date(new Date().setHours(group_outtime[0], group_outtime[1], 0, 0)) + ], + onSet: (value, inst)-> + val = value.valueText; + vals = val.split(' - '); + group_intime = vals[0]; + group_outtime = vals[1]; + + inArr = group_intime.split(":") + outArr = group_outtime.split(":") + inMin = Number(inArr[0]) * 60 + Number(inArr[1]) + outMin = Number(outArr[0]) * 60 + Number(outArr[1]) + if(outMin <= inMin) + return PUB.toast('下班时间早于上班时间,请重试') + Meteor.call('updateGroupInOutTime',Session.get('groupsId'),group_intime, group_outtime) + }) + initGroupIndex = (user_id)-> + size = 0 + groupList = SimpleChat.GroupUsers.find({user_id: user_id}, { sort: { index: 1 }}).fetch(); + console.log(groupList); + if groupList + for group in groupList + SimpleChat.GroupUsers.update(group._id, { $set: { index: size } }); + size = size + 1 + groupDelOrQuitCB = (err,id,isDel)-> + errMsg = '退出失败,请重试~' + if isDel + errMsg = '删除失败,请重试~' + console.log(err) + if err or !id + #return PUB.toast(errMsg) + PUB.toast(errMsg) + return PUB.page '/' + if mqtt.host + mqtt.unsubscribe("/msg/g/" + id) + MsgSessionId = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId: id}) + console.log MsgSessionId + if MsgSessionId + SimpleChat.MsgSession.remove(MsgSessionId._id) + try + where = {'to.id': id, to_type: 'group'}; + SimpleChat.Messages.remove(where); + Meteor.setTimeout(()-> + if SimpleChat.MessagesHis.find(where).count() > 0 + SimpleChat.MessagesHis.remove(where); + ,100) + SimpleChat.MessagesHis.remove(where); + catch e + console.log 'remove-group-user err:'+e + Meteor.setTimeout(()-> + PUB.back() + ,100) + Session.setDefault("groupsProfileMenu",'groupInformation') + Template.groupsProfile.helpers + whichOne:()-> + Session.get("groupsProfileMenu") + Template.groupInformation.rendered=-> + $('.content').css 'min-height',$(window).height() + groupid = Session.get('groupsId') + Meteor.subscribe("get-group",groupid, { + onReady:()-> + initGroupInOutTimeSet() + onError:()-> + initGroupInOutTimeSet + }) + Meteor.subscribe('group-user-counter',groupid) + Meteor.subscribe('loginuser-in-group',groupid, Meteor.userId()) + UI.registerHelper('checkedIf',(val)-> + return if val then 'checked' else '' + ) + Template.groupInformation.helpers + userTypeIsAdmin:()-> + user = Meteor.user() + if user and user.profile and user.profile.userType is 'admin' + return true + return false + getGroupInOutTime: ()-> + group_intime = '09:00' + group_outtime = '18:00' + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.group_intime + group_intime = group.group_intime + if group and group.group_outtime + group_outtime = group.group_outtime + return group_intime+' - '+group_outtime + groupAccuracyType: ()-> + group = SimpleChat.GroupUsers.findOne({group_id:Session.get('groupsId'),user_id:Meteor.userId()}) + if group and group.groupAccuracyType + groupAccuracyType = group.groupAccuracyType + if groupAccuracyType is 'accurate' + return '精确匹配' + else + return '宽松匹配' + rejectUnknowMember: ()-> + groupUser = SimpleChat.GroupUsers.findOne({group_id: Session.get('groupsId'), user_id: Meteor.userId()}) + if groupUser and groupUser.allowUnknowMember + return true + return false + reciveGif: ()-> + isShow = false + result = null + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.settings + result = group.settings.receive_gif + if result is true or result is null or result is undefined + isShow = true + return isShow + realTimeEmail:()-> + isShow = false + result = null + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.settings + result = group.settings.real_time_email + if result is true or result is null or result is undefined + isShow = true + return isShow + whats_up_send: ()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group + return group.whats_up_send + return false + rejectLabelMsg: ()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + return group.rejectLabelMsg + userIsAdmin: ()-> + user = Meteor.user() + isAdmin = user.profile and user.profile.userType and user.profile.userType is 'admin' + return isAdmin && withSwitchNormalLabelMsg + isGroup:()-> + if Session.get('groupsType') is 'group' + return true + else + return false + groupName:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.name + return group.name + else + return '[无]' + hasBarCode:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.barcode + return true + else + return false + + barcodeUrl:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.barcode + return group.barcode + hasTemplate:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.template and group.template._id + return true + else + return false + templateName:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + return group.template.name + templateIcon:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + return group.template.icon + hasAnnouncement:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.announcement and group.announcement.length > 0 + return true + else + return false + groupAnnouncement:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + return group.announcement + isMobile:()-> + Meteor.isCordova + show_more:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + return group.announcement.length > 2 + isGroupCreator:()-> + # 具有以下特殊权限 + # 1.监控组名称修改 + # 2.解散监控组 + # 3.群管理员管理权限 + + group = SimpleChat.Groups.findOne({_id: Session.get('groupsId')}) + if group and group.creator and group.creator.id is Meteor.userId() + return true + return false + isGroupAdmin:()-> + # 具有以下特殊权限 + # 1.清空训练记录 + # 2.报告栏成员管理 + # 3.识别规则设置 + if Template.groupInformation.__helpers.get('isGroupCreator')() + return true + groupUser = SimpleChat.GroupUsers.findOne({group_id:Session.get('groupsId'), user_id: Meteor.userId()}) + if groupUser and groupUser.isGroupAdmin + return true + return false + + Template.groupInformation.events + 'click #recognitionCounts': (event)-> + group_id = Session.get('groupsId') + PUB.page('/recognitionCounts/'+group_id) + 'click #groupDevice': (event)-> + group_id = Session.get('groupsId') + PUB.page '/groupDevices/'+group_id + 'click #clusteringFix': (event)-> + group_id = Session.get('groupsId') + PUB.page('/clusteringFix/'+ group_id) + 'click #groupInOutTime':(event)-> + $('#groupInOutTime').mobiscroll('show') + return false + 'click .groupUserHide':(event)-> + group_id = Session.get('groupsId') + Session.set 'scrollTop',$('html,body').scrollTop() + PUB.page('/groupUserHide/'+group_id) + 'click #groupsProfilePageback':(event)-> + return PUB.back() + groupid = Session.get('groupsId') + type = Session.get('groupsType') + url = '/simple-chat/to/'+type+'?id='+groupid + Router.go(url) + 'click .groupAccuracy': (event)-> + Session.set("groupsProfileMenu","groupAccuracy") + 'click .groupEmail': (event)-> + Session.set("groupsProfileMenu","groupEmail") + 'click .editName': (event)-> + Session.set("groupsProfileMenu","setGroupname") + 'click .barcode': (event)-> + Session.set("groupsProfileMenu","groupBarCode") + 'click .deleteAndExit':(event)-> + if event.currentTarget.id is 'delThisGroup' + return PUB.confirm('删除后,将不再保留本监控组相关信息',()-> + Meteor.call('creator-delete-group',Session.get('groupsId'), Meteor.userId(),(err,id)-> + groupDelOrQuitCB(err,id,true) + initGroupIndex(Meteor.userId()) + ) + ) + + PUB.confirm('退出后,将不再接收本监控组消息',()-> + Meteor.call('remove-group-user',Session.get('groupsId'),Meteor.userId(),(err,id)-> + groupDelOrQuitCB(err,id, false) + initGroupIndex(Meteor.userId()) + # console.log(err) + # if err or !id + # return PUB.toast('删除失败,请重试~') + # if mqtt_connection + # mqtt_connection.unsubscribe("/msg/g/" + id) + # MsgSessionId = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId: id}) + # if MsgSessionId + # SimpleChat.MsgSession.remove(MsgSessionId._id) + # try + # where = {'to.id': id, to_type: 'group'}; + # SimpleChat.Messages.remove(where); + # Meteor.setTimeout(()-> + # if SimpleChat.MessagesHis.find(where).count() > 0 + # SimpleChat.MessagesHis.remove(where); + # ,100) + # SimpleChat.MessagesHis.remove(where); + # catch e + # console.log 'remove-group-user err:'+e + # Meteor.setTimeout(()-> + # PUB.back() + # ,100) + ) + + return PUB.page '/' + ) + 'click .groupPhoto':(event)-> + Template.groupPhoto.open(Session.get('groupsId')); + 'click .scanPerfBarcode':(event)-> + console.log 'scan performance barcode' + cordova.plugins.barcodeScanner.scan((result)-> + console.log("We got a barcode\n" + "Result: " + + result.text + "\n" + "Format: " + + result.format + + "\n" + "Cancelled: " + + result.cancelled); + if (result.text) + console.log 'result.txt: ' + result.text + txtObj = JSON.parse(result.text); + Meteor.call('set-perf-link',Session.get('groupsId'), txtObj, (err, ret)-> + console.log 'set-perf-link, err: ' + err + ', ret: ' + ret + if err + PUB.toast '扫描失败,请重试~' + return + PUB.toast '扫描成功!可查看绩效~' + ) + if (result.cancelled) + return; + if (result.alumTapped) + return; + , (error)-> + alert("Scanning failed: " + error); + , { + preferFrontCamera: false, # iOS and Android + showFlipCameraButton: true, # iOS and Android + showTorchButton: true, # iOS and Android + torchOn: true, # Android, launch with the torch switched on (if available) + prompt: "Place a barcode inside the scan area", # Android + resultDisplayDuration: 500, # Android, display scanned text for X ms. 0 suppresses it entirely, default 1500 + formats: "QR_CODE,PDF_417", # default: all but PDF_417 and RSS_EXPANDED + orientation: "landscape", # Android only (portrait|landscape), default unset so it rotates with the device + #disableAnimations: true, # iOS + #disableSuccessBeep: false // iOS + } + ); + 'click .checkPerf':(event)-> + group_id = Session.get('groupsId') + group = SimpleChat.Groups.findOne({_id: group_id}) + + perf_url = null + console.log('Got group Info='+JSON.stringify(group)) + if group and group.perf_info and group.perf_info.reportUrl + perf_url = group.perf_info.reportUrl + console.log "perf url is not 0: " + perf_url + # if group is not null and group is not undefined + # console.log "group is not 0 " + # if group.perf_info is not null and group.perf_info is not undefined + # console.log "perf info is not 0 " + # if group.perf_info.perf_url is not null and group.perf_info.perf_url is not undefined + # perf_url = group.perf_info.perf_url + # console.log "perf url is not 0: " + perf_url + + if perf_url is null + console.log "perf url is null, browser: " + perf_url + perf_url = 'http://aixd.raidcdn.cn/reporter/f5ZocsFpQn9CApmy8' + #cordova.InAppBrowser.open(perf_url, '_system') + 'click .emptyMessages':(event)-> + PUB.confirm('确定要清空训练记录吗?',()-> + type = Session.get('groupsType') + to = Session.get('groupsId') + if type is 'group' + where = {'to.id': to, to_type: type}; + else + where = { + $or: [ + {'form.id': Meteor.userId(), 'to.id': to, to_type: type}, + {'form.id': to, 'to.id': Meteor.userId(), to_type: type} + ] + }; + console.log('where:', where); + window.plugins.toast.showLongCenter('请稍候~') + try + SimpleChat.Messages.remove(where); + Meteor.setTimeout(()-> + if SimpleChat.MessagesHis.find(where).count() > 0 + SimpleChat.MessagesHis.remove(where); + ,100) + catch e + console.log 'remove-group-user err:'+e + + console.log '训练记录已清空'; + SimpleChat.MsgSession.update({toUserId:to},{$set:{lastText:''}}) + window.plugins.toast.hide(); + # groupid = Session.get('groupsId') + # type = Session.get('groupsType') + # url = '/simple-chat/to/'+type+'?id='+groupid + # Router.go(url) + ) + + 'click .copy':(event)-> + value = $(event.currentTarget).prev().text() + console.log (value) + cordova.plugins.clipboard.copy(value) + PUB.toast('复制成功~') + 'click .show_more':(event)-> + $show = $('.show_more'); + if $('.announcementVal').find('._close').length > 0 + $show.html('') + $('.announcementVal').find('.announcement_item').removeClass('_close'); + else + $show.html(''); + $('.announcementVal').find('.announcement_item').addClass('_close'); + 'click #switchNormalLabelMsg':(event)-> + $('#switchNormalLabelMsg').attr('disabled') + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + rejectLabelMsg = group.rejectLabelMsg + if rejectLabelMsg + SimpleChat.Groups.update({_id: Session.get('groupsId')},{$set:{rejectLabelMsg: false}},(err,result)-> + $('#switchNormalLabelMsg').removeAttr('disabled') + ) + else + SimpleChat.Groups.update({_id: Session.get('groupsId')},{$set:{rejectLabelMsg: true}},(err,result)-> + $('#switchNormalLabelMsg').removeAttr('disabled') + ) + 'click #switchRejectUnknowMember':(event)-> + Meteor.call('updateGroupUserallowUnknowMember',Session.get('groupsId'), Meteor.userId()) + 'change #switchReciveGif':(event)-> + isReceive = false + if $('#switchReciveGif').is(":checked") is true + isReceive = true + group_id = Session.get('groupsId') + Meteor.call('update_group_settings', group_id, {'settings.receive_gif':isReceive}); + return + 'change #switchRealTimeEmail':(event)-> + isRealTime = false + if $('#switchRealTimeEmail').is(":checked") is true + isRealTime = true + group_id = Session.get('groupsId') + Meteor.call('update_group_settings', group_id, {'settings.real_time_email':isRealTime}); + return + 'click #switch_whats_up_send':(event)-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if (group && group.whats_up_send) + Meteor.call('uGroupWhatsUp', Session.get('groupsId'), false) + else + Meteor.call('uGroupWhatsUp', Session.get('groupsId'), true) + + Template.groupUsers.helpers + isGroup:()-> + if Session.get('groupsType') is 'group' + return true + else + return false + groupUsers:()-> + limit = withShowGroupsUserMaxCount || 29; + return SimpleChat.GroupUsers.find({group_id:Session.get('groupsId')},{sort: {createdAt: 1},limit:limit}) + moreResults:()-> + limit = withShowGroupsUserMaxCount || 29; + return Counts.get('groupsUserCountBy-'+Session.get('groupsId')) > limit + isMobile:()-> + Meteor.isCordova + chat_user_id:()-> + Session.get('groupsId') + chat_user_Icon:()-> + users = Meteor.users.findOne({_id:Session.get('groupsId')}) + if users and users.profile + return users.profile.icon + else + return '/userPicture.png' + chat_user_Name:()-> + users = Meteor.users.findOne({_id:Session.get('groupsId')}) + if users and users.profile + return users.profile.fullname || users.username + else + return '' + userIsGroupCreator:()-> + group = SimpleChat.Groups.findOne({_id: Session.get('groupsId')}) + if group and group.creator and group.creator.id is this.user_id + return true + return false + + Template.groupUsers.events + 'click #addUserInGroup':(event)-> + Session.set("groupsProfileMenu","inviteFriendIntoGroup") + 'click #showAllResults':(event)-> + Session.set("groupsProfileMenu","groupAllUser") + 'click .userItem': (event)-> + #Session.set("groupsProfileMenu","setGroupname") + console.log event.currentTarget.id + PUB.page('/simpleUserProfile/'+event.currentTarget.id); + + Template.setGroupname.helpers + placeholderText:()-> + if Session.equals('fromCreateNewGroups',true) + return '输入监控组名称' + return '输入新的监控组名称' + groupName:()-> + if Session.equals('fromCreateNewGroups',true) + return Session.get('AI_Group_Name') || '' + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.name + return group.name + else + return '' + Template.setGroupname.events + 'click .left-btn':(event)-> + if Session.equals('fromCreateNewGroups',true) + Session.set('fromCreateNewGroups',false); + Router.go('/'); + Session.set("groupsProfileMenu","groupInformation") + 'click .right-btn':(e)-> + $('.setGroupname-form').submit() + 'submit .setGroupname-form': (e)-> + e.preventDefault(); + if e.target.text.value isnt '' + console.log 'Change Groups Name to ' +e.target.text.value + if Session.equals('fromCreateNewGroups',true) + Session.set('fromCreateNewGroups',false); + #Session.set('AI_Group_Name',e.target.text.value); + create_group_fun(e.target.text.value); + #Router.go('/selectTemplate'); + return + Meteor.call('updateGroupName',Session.get('groupsId'),e.target.text.value,(error)-> + SimpleChat.MsgSession.update({toUserId:Session.get('groupsId')},{$set:{toUserName:e.target.text.value}}) + ) + + Session.set("groupsProfileMenu","groupInformation") + else + PUB.toast '监控组名称不能为空~' + false + + Template.groupBarCode.helpers + groupIcon:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.icon + return group.icon + else + return '' + groupName:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.name + return group.name + else + return '' + barcodeUrl:()-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}) + if group and group.barcode + return group.barcode + Template.groupBarCode.events + 'click #groupBarCodePageback':(event)-> + Session.set("groupsProfileMenu","groupInformation") + 'click #savebarcode':(event)-> + group = SimpleChat.Groups.findOne({_id:Session.get('groupsId')}); + cordova.plugins.barcodeScanner.saveBarCodeToPhotoAlum group.barcode, ((result) -> + console.log 'res:' + result + PUB.toast '保存成功!' + return + ), (error) -> + console.log 'error:' + error + PUB.toast '保存失败!' + return + 'click #scanbarcode':(event)-> + ScanBarcodeByBarcodeScanner() + Template.groupAccuracy.helpers + isAccurateAccuray: ()-> + group = SimpleChat.GroupUsers.findOne({group_id:Session.get('groupsId'),user_id:Meteor.userId()}) + if group and group.groupAccuracyType + groupAccuracyType = group.groupAccuracyType + return groupAccuracyType is 'accurate' + Template.groupAccuracy.events + 'click .left-btn':(event)-> + if Session.equals('fromCreateNewGroups',true) + Session.set('fromCreateNewGroups',false); + Router.go('/'); + Session.set("groupsProfileMenu","groupInformation") + 'click .selectAccuracy':(event)-> + groupAccuracyType =event.currentTarget.id; + Meteor.call('updateGroupAccuracyType',Session.get('groupsId'),groupAccuracyType) + if Session.equals('fromCreateNewGroups',true) + Session.set('fromCreateNewGroups',false); + Router.go('/'); + Session.set("groupsProfileMenu","groupInformation") + Template.groupEmail.helpers + reportEmails: ()-> + emails = [] + groupId = Session.get 'groupsId' + + groupUser = SimpleChat.GroupUsers.findOne({group_id: groupId,user_id:Meteor.userId()}) + if groupUser.report_emails + report_emails = groupUser.report_emails + rmails = report_emails.split(',') + for emailAddr in rmails + emails.push({ reportEmailAddr: emailAddr }) + emails + Template.groupEmail.events + 'click .left-btn':(event)-> + if Session.equals('fromCreateNewGroups',true) + Session.set('fromCreateNewGroups',false); + Router.go('/'); + Session.set("groupsProfileMenu","groupInformation") + 'click .adE':(event)-> + ss = $(".inpEmail").val() + ret = ss.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) + if not ret + PUB.toast '无效邮箱地址!' + return + groupId = Session.get 'groupsId' + user_id = Meteor.userId() + groupUser = SimpleChat.GroupUsers.findOne({group_id: groupId,user_id:user_id}) + if groupUser.report_emails + arr = groupUser.report_emails.split(',') + if _.contains(arr,ss) + PUB.toast '此邮箱已添加' + return + report_emails = groupUser.report_emails + ',' + ss + else + report_emails = ss + + Meteor.call('updateGroupUserReportEmails', groupId, user_id,report_emails) + + $(".inpEmail").val("") + 'click .deleEmaile':(event)-> + groupId = Session.get 'groupsId' + newEmails = '' + isFirst = true + user_id = Meteor.userId() + groupUser = SimpleChat.GroupUsers.findOne({group_id: groupId,user_id:user_id}) + if groupUser.report_emails + aMails = groupUser.report_emails.split(',') + for em in aMails + if em isnt this.reportEmailAddr + if isFirst + isFirst = false + newEmails = newEmails + em + else + newEmails = newEmails + ',' + em + Meteor.call('updateGroupUserReportEmails', groupId, user_id,newEmails) diff --git a/client/chatGroups/groupsProfile/groupsProfile.html b/client/chatGroups/groupsProfile/groupsProfile.html new file mode 100644 index 000000000..c6ed6aaa2 --- /dev/null +++ b/client/chatGroups/groupsProfile/groupsProfile.html @@ -0,0 +1,359 @@ + + + + + + + + + + + + diff --git a/client/chatGroups/groupsProfile/groupsProfile.less b/client/chatGroups/groupsProfile/groupsProfile.less new file mode 100644 index 000000000..5871e15fd --- /dev/null +++ b/client/chatGroups/groupsProfile/groupsProfile.less @@ -0,0 +1,413 @@ +.groupInformation { + .head{ + background: #37a7fe; + } + .content { + position: relative; + padding-top: 40px; + padding-bottom: 55px; + -webkit-touch-callout: none; + background-color: #eee; + .icon { + width: 40px; + height: 40px; + margin: 10px; + object-fit: cover; + } + .groupsName { + color: black; + } + .barcode{ + img{ + width: 32px; + position: relative; + top: -5px; + } + } + .copy{ + top: 20px; + right: 10px; + left: auto; + color: #888888; + border-style: solid; + border-width: 1px; + padding: 2px 5px; + height: auto; + border-radius: 5px; + font-size: 14px; + } + .template_value{ + // height: auto; + // top: 20px; + // position: relative; + // //padding: 10px; + // padding-right: 10px; + // padding-bottom: 10px; + // margin-bottom: 10px; + text-align: right; + img{ + width: 40px; + height: 50px; + margin-left: 20px; + margin-right: 10px; + //border-radius: 10px; + } + } + .announcementVal{ + height: auto; + top: 20px; + position: relative; + padding: 10px; + text-align: left; + word-break: break-all; + margin-bottom: 10px; + .announcement_item{ + position: relative; + } + div._close:nth-child(n+3){display: none !important;} + .dataset_url{ + padding: 10px 0px; + } + .copy{ + top: 10px; + right: 10px; + left: auto; + color: #888888; + border-style: solid; + border-width: 1px; + padding: 2px 5px; + height: auto; + border-radius: 5px; + font-size: 14px; + } + } + .line { + text-align: center; + width: 100%; + height: 1px; + span { + background: #333; + width: 100%; + height: 1px; + display: block; + } + } + .emptyMessages{ + position: relative; + font-weight: 400; + font-size: 16px; + background-color: #fff; + height: 45px; + // border-radius: 5px; + /* padding-left: 5%; */ + padding-top: 10px; + padding-left: 10px; + //margin-top: 10px; + margin-bottom: 10px; + + } + .scanPerfBarcode,.groupPhoto,.groupEmail{ + position: relative; + font-weight: 400; + font-size: 16px; + background-color: #fff; + height: 45px; + // border-radius: 5px; + /* padding-left: 5%; */ + padding-top: 10px; + padding-left: 10px; + //margin-top: 10px; + margin-bottom: 10px; + + } + .checkPerf{ + position: relative; + font-weight: 400; + font-size: 16px; + background-color: #fff; + height: 45px; + // border-radius: 5px; + /* padding-left: 5%; */ + padding-top: 10px; + padding-left: 10px; + //margin-top: 10px; + margin-bottom: 10px; + + } + .deleteAndExit{ + position: relative; + left: 2.5%; + width: 95%; + text-align: center; + font-size: 18px; + background-color: #f56262; + color: #ffffff !important; + height: 45px; + border-radius: 5px; + /* padding-left: 5%; */ + padding-top: 10px; + } + } + .disbaleNormalLabelMsg{ + line-height: 32px; + background: #ffffff; + padding: 5px 10px; + font-size: 16px; + } + .switch{ + position: relative; + width: 52px; + height: 32px; + border: 1px solid #dfdfdf; + outline: 0; + border-radius: 16px; + box-sizing: border-box; + background-color: #dfdfdf; + transition: background-color 0.1s, border 0.1s; + -webkit-appearance: none; + margin: 0; + } + .switch:checked{ + border-color: #04be02; + background-color: #04be02; + } + .switch:before{ + content: " "; + position: absolute; + top: 0; + left: 0; + width: 50px; + height: 30px; + border-radius: 15px; + background-color: #fdfdfd; + transition: transform 0.35s cubic-bezier(0.45, 1, 0.4, 1); + } + .switch:checked:before{transform: scale(0);} + .switch:after{ + content: " "; + position: absolute; + top: 0; + left: 0; + width: 30px; + height: 30px; + border-radius: 15px; + background-color: #ffffff; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); + transition: transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35); + } + .switch:checked:after{transform: translateX(20px);} +} + +.groupUsers { + background-color: white; + ul{ + margin:0; + //padding-left:4% !important; + overflow:hidden; + list-style-type:none; + padding-bottom: 10px; + li{ + position: relative; + display: inline-block; + width:16%; + //margin-right: 2%; + margin-left: 3.3%; + margin-bottom: 8%; + float: left; + height: 0; + padding-bottom: 16%; + .group-admin-label { + position: absolute; + bottom: 0; + right: 0; + z-index: 9; + width: 20px; + height: 20px; + } + .box { + position: relative; + width: 100%; /* desired width */ + margin-top: 10px; + // margin-bottom: 5px; + } + + .box:before { + content: ""; + display: block; + padding-top: 100%; /* initial ratio of 1:1*/ + } + + .boxcontent { + position: absolute; + top: 5px; + left: 5px; + bottom: 5px; + right: 5px; + } + + img{ + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 5px; + position: absolute; + } + .userName{ + text-align: center; + height: 16px; + overflow: hidden; + text-overflow:ellipsis; + font-size: 12px; + white-space: nowrap; + } + + } + } + #showAllResults{ + text-align: center; + font-size: 16px; + color: gray; + padding-top: 20px; + padding-bottom: 20px; + } +} +.setGroupname{ + background-color: #EFEFF4;height: 100%; + .head{ + background: #37a7fe; + position: relative; + text-align: center; + color: #fff; + font-weight: bold; + .left-btn{position: absolute; left: 10px; top: 0px;color: #fff;font-weight: normal;} + .right-btn{ + position: absolute; right: 10px; top: 0px;color: #fff;font-weight: normal; + i{color: #fff;} + } + } + .setup{ + padding: 0;margin: 0;background-color: #fff; + li{ + border-top: 1px solid #D9D9D9;padding: 10px;position: relative; + label{display: block;position: absolute;left: 0; top: 50%; height: 20px; line-height: 20xp; margin-top: -10px; font-size: 16px; font-weight:normal;} + .value{ + display: block;text-align: right;height: 20px; line-height: 20xp;font-size: 16px;color: #888888; + i.fa-angle-right{padding-left: 5px; font-size: 20px;} + } + .value.select{color: #007AFF;} + .value.checked{ + .false{width: 33px;height: 20px;background: url(/checked_false.png);background-size: 100%; float: right;} + .true{width: 33px;height: 20px;background: url(/checked_true.png);background-size: 100%;float: right;} + } + .input{ + width: 100%; + input{width: 100%;border: none;color: black;} + } + } + li:first-child{border-top: none;} + } +} +.groupBarCode { + .head{ + background: #37a7fe; + color: #fff; + .dropdown { + cursor: pointer; + #btn-more{ + top: -45px; + } + .dropdown-backdrop{ + background-color: rgba(0, 0, 0, 0.1) + } + .dropdown-menu { + padding: 0; + right: 5px; + left: auto; + border-radius: 5px; + li { + background-color: transparent !important; + text-align: center; + padding: 12px; + } + a { + font-size: 15px; + } + } + .add-more-drop-item{ + margin-right: 10px; + font-size: 20px; + color: #666; + .fa-plus{ + position: relative; + bottom: 8px; + font-size: 8px; + } + } + } + } + .content { + position: relative; + padding-top: 40px; + padding-bottom: 55px; + -webkit-touch-callout: none; + background-color: white; + .top img{ + margin-left: 20px; + margin: 20px 10px 20px 20px; + width: 15%; + border-radius: 5px; + } + .fa-users{width: 50px;height: 50px; line-height: 50px; margin: 10px;border-radius: 6px; display: inline-block; text-align: center; background-color: #2196f3; color: #fff; font-size: 24px;} + .barcode { + text-align: center; + img{ + width: 46%; + } + } + } +} + + +.groupEmail { + ul{ + color: red; + } +} +.tips{ + position: fixed; + top: 40%; + left: 0; + right: 0; + bottom: 0; + width: 80%; + height: 30%; + text-align: left; + box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19); + background-color: lemonchiffon; + margin-left:auto; + margin-right: auto; + transform: rotate(-10deg); + -o-transform: rotate(-10deg); + -webkit-transform: rotate(-10deg); + -moz-transform: rotate(-10deg); + // .tip{ + // background-color: #37a7fe; + // margin-top:50px; + // padding: 10px; + // border: 1px solid; + // border-radius: 5px; + h1{ + text-align: center; + color: #d82222; + } + .line{ + border: 1px solid #888888; + margin: 20px 0; + } + p{ + font-size: 16px; + color: #d05241; + font-weight: 400; + } + // } +} \ No newline at end of file diff --git a/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.coffee b/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.coffee new file mode 100644 index 000000000..9351063c0 --- /dev/null +++ b/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.coffee @@ -0,0 +1,100 @@ +if Meteor.isClient + users = new ReactiveVar([]) + Template.inviteFriendIntoGroup.rendered=-> + users.set([]) + groupid = Session.get('groupsId') + if Session.get('groupsType') is 'group' + Meteor.subscribe("get-group-user",groupid) + else + users.set([{'followerId':groupid}]) + $('.content').css 'min-height',$(window).height() +# $('.mainImage').css('height',$(window).height()*0.55) + $(window).scroll (event)-> + target = $("#showMoreFollowsResults"); + FOLLOWS_ITEMS_INCREMENT = 10; + if (!target.length) + return; + threshold = $(window).scrollTop() + $(window).height() - target.height(); + if target.offset().top < threshold + if (!target.data("visible")) + target.data("visible", true); + Session.set("followersitemsLimit", + Session.get("followersitemsLimit") + FOLLOWS_ITEMS_INCREMENT); + else + if (target.data("visible")) + target.data("visible", false); + Template.inviteFriendIntoGroup.helpers + myFollows:()-> + Follower.find({"userId":Meteor.userId()}, {sort: {createdAt: -1}}, {limit:Session.get("followersitemsLimit")}) + moreResults:-> + !(Follower.find({"userId":Meteor.userId()}).count() < Session.get("followersitemsLimit")) + loading:-> + Session.equals('followersCollection','loading') + loadError:-> + Session.equals('followersCollection','error') + is_selected: (followerId)-> + return _.pluck(users.get(), 'followerId').indexOf(followerId) isnt -1 + is_invited:(followerId)-> + if Session.get('groupsType') is 'group' + SimpleChat.GroupUsers.findOne({group_id:Session.get('groupsId'),user_id:followerId}) + else + if Session.get('groupsId') is followerId + return true + else + return false + Template.inviteFriendIntoGroup.events + 'click .leftButton':(event)-> + Session.set("groupsProfileMenu","groupInformation") + 'click .rightButton':(event)-> + selected = users.get() + if selected.length <= 0 + Session.set("groupsProfileMenu","groupInformation") + return + if Session.get('groupsType') is 'group' + Meteor.call 'add-group-urser', Session.get('groupsId'), _.pluck(selected, 'followerId'), (err, id)-> + console.log(err) + if err or !id + return PUB.toast('添加失败,请重试~') + Session.set("groupsProfileMenu","groupInformation") + else + groupid = new Mongo.ObjectID()._str; + Meteor.call 'create-group',groupid,null,_.pluck(selected, 'followerId'), (err, id)-> + console.log(err) + if err or !id + return PUB.toast('添加失败,请重试~') + Meteor.subscribe('get-group',id,{ + onReady:()-> + group = SimpleChat.Groups.findOne({_id:id}); + msgObj = { + _id: new Mongo.ObjectID()._str, + form: { + id: '', + name: '系统', + icon: '' + }, + to: { + id: group._id, + name: group.name, + icon: group.icon + }, + images: [], + to_type: "group", + type: "system", + text: '欢迎加入'+group.name , + create_time: new Date(), + is_read: false + }; + sendMqttGroupMessage(group._id, msgObj); + }); + Router.go('/simple-chat/to/group?id='+id) + 'click .followItem': (event)-> + # console.log(this); + $i = $(event.currentTarget).find('i'); + if $i.hasClass('is-invited-item') + return + selected = users.get() + if _.pluck(selected, 'followerId').indexOf(this.followerId) is -1 + selected.push(this) + else + selected.splice(_.pluck(selected, 'followerId').indexOf(this.followerId), 1) + users.set(selected) diff --git a/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.html b/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.html new file mode 100644 index 000000000..788890d53 --- /dev/null +++ b/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.html @@ -0,0 +1,48 @@ + diff --git a/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.less b/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.less new file mode 100644 index 000000000..bbafa8e9c --- /dev/null +++ b/client/chatGroups/groupsProfile/inviteFriendIntoGroup/inviteFriendIntoGroup.less @@ -0,0 +1,39 @@ +.inviteFriendIntoGroup{ + .head { + background: #37a7fe; + .rightButton .fa-plus{ + position: relative; + bottom: 8px; + font-size: 8px; + color: #ffffff + } + } + .content{ + position:relative; + padding-top: 40px; + padding-bottom: 55px; + -webkit-touch-callout: none; + background-color: #eee; + .eachViewer{ + background-color: white; + } + .icon{width: 40px;height: 40px;margin: 10px;} + .groupsName{color: black;} + .line{ + text-align:center; + width: 100%; + height: 1px; + span{background: #333; width: 100%; height: 1px; display: block;} + } + + } + .followItem{ + position: relative; padding-left: 50px; + i{width: 40px; height: 40px; line-height: 40px; text-align: center; font-size: 24px; color: #ccc; position: absolute; left: 10px; top: 50%; margin-top: -20px;} + i.fa-circle{color: #4caf50;} + .is-invited-item{ + color: #ccc !important; + } + } + +} diff --git a/client/chatGroups/newLabel/newLabel.html b/client/chatGroups/newLabel/newLabel.html new file mode 100644 index 000000000..76f3adb9f --- /dev/null +++ b/client/chatGroups/newLabel/newLabel.html @@ -0,0 +1,47 @@ + diff --git a/client/chatGroups/newLabel/new_label.js b/client/chatGroups/newLabel/new_label.js new file mode 100644 index 000000000..3dbb476dc --- /dev/null +++ b/client/chatGroups/newLabel/new_label.js @@ -0,0 +1,25 @@ +var isShowNoPerson = new ReactiveVar(false); + +Template.newLabel.helpers({ + showNoPersonBtn: function() { + return isShowNoPerson.get(); + } +}); + +Template.newLabel.events({ + 'click .right-handle-btn': function(e) { + e.stopImmediatePropagation(); + isShowNoPerson.set(!isShowNoPerson.get()); + }, + 'click .newLabel-wrap': function() { + if (isShowNoPerson.get()) { + isShowNoPerson.set(false); + } + }, + 'click .no-person-btn': function() { + + }, + 'click .back': function() { + return Router.go('/message'); + } +}); \ No newline at end of file diff --git a/client/chatGroups/newLabel/new_label.less b/client/chatGroups/newLabel/new_label.less new file mode 100644 index 000000000..00fa37eb8 --- /dev/null +++ b/client/chatGroups/newLabel/new_label.less @@ -0,0 +1,129 @@ +.newLabel-wrap { + min-height: 100%; + background: linear-gradient(to bottom, #0A85C9, #ADFFFF); + ul { + padding: 0 !important; + margin: 0 !important; + } + .newLabel-head { + display: flex; + align-items: center; + height: 60px; + position: fixed; + width: 100%; + .leftButton { + width: 40px; + font-size: 32px; + } + .head-text { + flex: 1; + text-align: center; + margin-bottom: 0; + font-size: 16px; + } + .newLabel-head-right { + position: relative; + margin-right: 15px; + } + .right-handle-btn { + width: 23px; + height: 6px; + background: url(icon/icon-head-handle.png) no-repeat; + background-size: contain; + } + .no-person-btn { + position: absolute; + width: 130px; + text-align: center; + right: 0; + top: -14px; + background: #fff; + border-radius: 4px; + padding: 6px 5px; + color: #333; + } + } + .content { + padding-top: 100px; + } + .content-person-photos { + width: 84%; + max-width: 315px; + margin: 0 auto; + .person-photos-main { + position: relative; + .person-photos-big { + border-radius: 6px 6px 0 0; + min-height: 100px; + background: #305982; + img { + width: 100%; + } + } + .person-photos-head-wrap { + position: absolute; + bottom: -50%; + width: 100%; + height: 69px; + text-align: center; + li { + display: inline-block; + margin-left: 20px; + width: 66px; + height: 66px; + border-radius: 50%; + border: 3px solid #fff; + overflow: hidden; + img { + width: 100%; + } + } + li:first-child { + margin-left: 0; + } + } + + } + .person-photos-footer { + background: #fff; + border-radius: 0 0 6px 6px; + height: 128px; + } + } + .content-label { + margin-top: 50px; + text-align: center; + color: #45A2FF; + .label-title { + font-size: 22px; + margin-bottom: 30px; + } + .label-handle { + li { + display: inline-block; + margin-left: 50px; + } + li:first-child { + margin-left: 0; + } + .handle-btn { + display: inline-block; + width: 22px; + height: 22px; + margin-bottom: 20px; + background-size: contain !important; + background-repeat: no-repeat !important; + } + .handle-unknow { + background: url(icon/icon-no.png); + } + .handle-know { + width: 28px; + background: url(icon/icon-yes.png); + } + .handle-text { + font-size: 14px; + } + } + } +} \ No newline at end of file diff --git a/client/chatGroups/recognitionCounts/recognitionCounts.html b/client/chatGroups/recognitionCounts/recognitionCounts.html new file mode 100644 index 000000000..a4f7e5631 --- /dev/null +++ b/client/chatGroups/recognitionCounts/recognitionCounts.html @@ -0,0 +1,39 @@ + \ No newline at end of file diff --git a/client/chatGroups/recognitionCounts/recognitionCounts.js b/client/chatGroups/recognitionCounts/recognitionCounts.js new file mode 100644 index 000000000..1f05a898f --- /dev/null +++ b/client/chatGroups/recognitionCounts/recognitionCounts.js @@ -0,0 +1,140 @@ +var isLoading = new ReactiveVar(false); +var timeRange = new ReactiveVar([]); +var group_id = new ReactiveVar(''); + +var personCounts = new ReactiveVar({}); + +var initTimeRangeSet = function() { + var range = timeRange.get(); + + $('#timeRange').mobiscroll().range({ + defaultVaule: [new Date(),new Date()], + theme: 'material', + lang: 'zh', + display: 'bottom', + controls: ['time'], + maxWidth: 100, + setText: '设置', + fromText: '开始时间', + toText:'结束时间', + defaultValue: [ + new Date(range[0]),new Date(range[1]) + ], + onSet: function(value, inst){ + var val = value.valueText; + + var vals = val.split(' - '); + var startArr = vals[0].split(":"); + var endArr = vals[1].split(":"); + + var now = new Date(); + + var range = timeRange.get(); + range[0] = new Date(now.getFullYear(),now.getMonth(), now.getDate() , + Number(startArr[0]), Number(startArr[1]), 0, 0); + range[1] = new Date(now.getFullYear(),now.getMonth(), now.getDate() , + Number(endArr[0]), Number(endArr[1]), 0, 0); + + timeRange.set(range); + } + }); +}; + +Template.recognitionCounts.onRendered(function(){ + isLoading.set(true); + group_id.set(Router.current().params.group_id); + + var now = new Date(); + var range = []; + range[0] = new Date(now.getFullYear(),now.getMonth(), now.getDate() , + 0, 0, 0, 0); + range[1] = new Date(now.getFullYear(),now.getMonth(), now.getDate() , + 23, 59, 59, 0); + + timeRange.set(range); + initTimeRangeSet(); + Meteor.subscribe('group-device-timeline', group_id.get(),range, function() { + isLoading.set(false); + }); + Meteor.setTimeout(function(){ + $(document).scrollTop(0); + },50); +}); + +Template.recognitionCounts.helpers({ + lists: function () { + var selector = { + group_id: group_id.get(), + hour: { + $gte: timeRange.get()[0], + $lte: timeRange.get()[1] + } + }; + var lists = []; + var counts = {}; + DeviceTimeLine.find(selector, {sort:{hour: -1}}).forEach(function(item){ + var uuid = item.uuid; + + for(x in item.perMin) { + var hour = new Date(item.hour) + hour = hour.setMinutes(x); + + var personIds = []; + + item.perMin[x].forEach( function(doc) { + if(doc.person_id && doc.person_name && doc.person_name != null && doc.person_name != '') { + var index = personIds.indexOf(doc.person_id); + if(index < 0){ + personIds.push(doc.person_id); + var obj = doc; + obj.time = hour; + obj.uuid = uuid; + lists.push(obj); + + if(counts[doc.person_name]) { + counts[doc.person_name].count += 1; + } else { + counts[doc.person_name] = { + name: doc.person_name, + count: 1 + }; + } + } + } + }); + } + }); + lists.sort(function(a,b){ + return a.time - b.time; + }); + personCounts.set(counts); + return lists; + }, + counts: function(){ + var counts = personCounts.get(); + var lists = []; + for(x in counts) { + lists.push(counts[x]); + } + return lists; + }, + getTime: function() { + var d = new Date(this.time); + return d.parseDate('YYYY-MM-DD hh:mm'); + }, + getRange: function() { + var range = timeRange.get(); + var start = new Date(range[0]); + var end = new Date(range[1]); + return start.parseDate('hh:mm') + ' - ' + end.parseDate('hh:mm'); + } +}); + +Template.recognitionCounts.events({ + 'click .back': function(e) { + return PUB.back(); + }, + 'click #timeRange': function(e) { + return $('#timeRange').mobiscroll('show'); + } +}) \ No newline at end of file diff --git a/client/chatGroups/style.less b/client/chatGroups/style.less new file mode 100644 index 000000000..800bd7fb3 --- /dev/null +++ b/client/chatGroups/style.less @@ -0,0 +1,124 @@ +ul#msg-session{ + padding: 0px; + li{ + height: 70px; background-color: #fff; color: #000; margin-top: 1px; padding: 15px 10px 10px 70px; position: relative; cursor: pointer; + // -webkit-tap-highlight-color: rgba(158, 158, 158, 0.22); + // overflow:hidden; + // -webkit-backface-visibility: hidden; + // -webkit-tap-highlight-color: transparent; + // animation: msg-session-animation 1.25s ease-in; + img.icon, .fa{display: block; width: 50px !important; height: 50px !important; position: absolute; left: 10px; top: 10px; margin: 0px !important; border-radius: 0px !important; border-radius: 50% !important;object-fit: cover;} + .name{color: #000;} + .text{ color: #9e9e9e; font-size: 12px; overflow: hidden;text-overflow:ellipsis;white-space: nowrap;} + .time{height: 20px; line-height: 20px; position: absolute; right: 10px; top: 15px; color: #9e9e9e; font-size: 12px;} + .count{width: 14px; height: 14px; line-height: 20px; text-align: center; border-radius: 50%; background-color: red; color: #fff; position: absolute; left: 45px; top: 10px;z-index: 99 !important} + } + li:first-child{ margin-top: 0px;} + + .delBtnContent{ + height: 70px; + line-height: 70px; + width: 100px; + text-align: center; + position: absolute; + right: -100px; + background: #f44336; + color: #fff; + border-bottom: 1px solid #ccc; + // border-top: 1px solid #ccc; + box-sizing: border-box; + top: 0px; + } +} + + +// .ripple{ +// overflow:hidden; +// -webkit-backface-visibility: hidden; +// -webkit-tap-highlight-color: transparent; +// } + +// .ripple-effect{ +// position: absolute; +// border-radius: 50%; +// width: 50px; +// height: 50px; +// /*background: white;*/ +// background: #bdbdbd; /* 默认的颜色效果 */ +// animation: ripple-animation 1.25s ease-in; +// } + + +@keyframes msg-session-animation { + from { + transform: scale(1); + opacity: 0.4; + } + to { + transform: scale(100); + opacity: 0; + } +} + +.modaling{ + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(0,0,0,0.5); + text-align: center; + z-index:1000;//9999; +} +.syncing{ + position: absolute; + top: 50%; + left: 50%; + margin-top: -71px; + margin-left: -80px; + // width: 160px; + // height: 100px; + .icon{ + font-size: 80px; + animation: spin 1000ms infinite linear; + } + .tip{ + font-size: 20px; + } +} +@keyframes spin { + 0% { transform: rotate(360deg); } + 100% { transform: rotate(0deg); } +} +.check_img_type{ + position: absolute; + top: 7px; + right: 2vw; + display: table; + text-align: right; + white-space: nowrap; + height: 2px; + vertical-align: middle; + span{ + width: 48px; + text-align: center; + background:rgba(255,255,255,1); + opacity:0.9; + color: #057EDB; + line-height: 26px; + display: table-cell; + margin: 0; + vertical-align: middle; + } + .human_bth{ + border-radius: 2px 0px 0px 2px; + } + .face_bth{ + border-radius: 0px 2px 2px 0px; + } + .type_on{ + background: #007AD8; + color: #FFF; + opacity:1; + } +} \ No newline at end of file diff --git a/client/companyLists/companyItem.js b/client/companyLists/companyItem.js new file mode 100644 index 000000000..75dbbd9ef --- /dev/null +++ b/client/companyLists/companyItem.js @@ -0,0 +1,251 @@ +var isLoading = new ReactiveVar(false); +var activeShow = new ReactiveVar('monthly'); + +var getHourMinutesTime = function(value) { + var val = value.toString().split('.'); + var h = val[0]; + var m = val[1] * 60 / 100; + return h+' 小时'+ Math.floor(m) + ' 分'; +}; + +var DateTimezone = function(d, time_offset) { + if (time_offset == undefined){ + if (d.getTimezoneOffset() == 420){ + time_offset = -7 + }else { + time_offset = 8 + } + } + // 取得 UTC time + var utc = d.getTime() + (d.getTimezoneOffset() * 60000); + var local_now = new Date(utc + (3600000*time_offset)) + var today_now = new Date(local_now.getFullYear(), local_now.getMonth(), local_now.getDate(), + local_now.getHours(), local_now.getMinutes()); + + return today_now; +}; + +window.inCompanyTimeLength = function(time_offset, status){ + var self = status; + + var diff = 0; + var out_time = self.out_time; + var today_end = self.out_time; + + if(self.in_time) { + var date = new Date(self.in_time); + var fomatDate = date.shortTime(time_offset); + var isToday = fomatDate.indexOf('今天') > -1 ? true : false; + + //不是今天的时间没有out_time的或者是不是今天时间,最后一次拍到的是进门的状态的都计算到当天结束 + if((!out_time && !isToday) || (self.status === 'in' && !isToday)) { + date = DateTimezone(date,time_offset); + day_end = new Date(date).setHours(23,59,59); + //day_end = new Date(this.in_time).setUTCHours(0,0,0,0) + (24 - time_offset)*60*60*1000 - 1; + out_time = day_end; + self.in_time = date.getTime(); + } + //今天的时间(没有离开过监控组) + else if(!out_time && isToday) { + var now_time = Date.now(); + out_time = now_time; + } + //今天的时间(离开监控组又回到监控组) + else if(out_time && this.status === 'in' && isToday) { + var now_time = Date.now(); + out_time = now_time; + } + } + + if(self.in_time && out_time){ + diff = out_time - self.in_time; + } + + if(diff > 24*60*60*1000) { + if(self.in_time) { + var date = DateTimezone(date,time_offset); + var day_end = new Date(date).setHours(23,59,59); + out_time = day_end; + diff = out_time - self.in_time; + } else { + diff = 16 * 60 * 60 * 1000; + } + } else if(diff < 0) { + diff = 0; + } + + return diff; +}; + +var options = { + title : { + show: false, + }, + grid:{ + left:40,right:10,top:10,bottom:30 + }, + tooltip : { + trigger: 'axis', + formatter: function (params,ticket,callback) { + var res = '日期: ' + params[0].name; + for (var i = 0, l = params.length; i < l; i++) { + if(params[i].seriesType == 'bar'){ + res += '
出现时间: ' + getHourMinutesTime(params[i].value); + } + } + return res; + } + }, + color: ['#3398DB','#FF5722'], + legend: { + x: 'center', + y: 'top', + data:[] + }, + toolbox: { + show : false, + }, + calculable : true, + xAxis : [ + { + type : 'category', + boundaryGap : '5px', + data:[] + } + ], + yAxis : [ + { + type : 'value', + axisLabel : { + formatter: function (value) { + return parseInt(value) + ' h'; + } + }, + splitNumber:3 + } + ], + series : [] +}; + + +var fillChartData = function() { + var group_id = Router.current().params._id; + var lineChartGroup = echarts.init(document.getElementById('lineChartGroup')); + var group = SimpleChat.Groups.findOne({_id: group_id}); + var time_offset = 8; + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + + var showLen = 30; + var now = new Date(); + var displayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , 0, 0, 0, 0); + var date_reg = Date.UTC(group.create_time.getFullYear(),group.create_time.getMonth(), group.create_time.getDate() , 0, 0, 0, 0); + var date_span = (date - date_reg) / (24 * 3600 * 1000); + if( date_span < 30){ + showLen = date_span; + } + + if( activeShow.get() == 'weekly' && date_span > 6) { + showLen = 7; + } + + // var date = displayDate.get(); + + options.legend.data=['时间']; + options.xAxis[0].data = []; + options.series[0] = { + type: 'bar', + data:[] + }; + options.series[1] = { + type:'line', + smooth:true, + data:[] + }; + + for(var i = showLen; i >= 0 ; i--){ + var d = date - (i * 24 * 60 * 60 * 1000); + + var status = WorkStatus.find({group_id: group_id, date:d}).fetch(); + var counts = status.length; + + var timeLen = 0; + status.forEach(function(item) { + var diff = inCompanyTimeLength(time_offset, item); + timeLen += Math.abs(diff); + }); + + if(counts > 0){ + timeLen = timeLen / counts; + } else { + timeLen = 0; + } + + var ts = new Date(d).parseDate('MM-DD'); // TODO: time_offset + timeLen = timeLen / (60 * 60 * 1000); + timeLen = timeLen.toFixed(2); + options.xAxis[0].data.push(ts); + options.series[0].data.push(timeLen); + options.series[1].data.push(timeLen); + }; + + console.log(options); + isLoading.set(false); + lineChartGroup.setOption(options); +}; + +Template.companyItem.onRendered(function () { + + var group_id = Router.current().params._id; + isLoading.set(true); + + var now = new Date(); + var displayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , 0, 0, 0, 0); + + Meteor.subscribe('WorkStatusListsByGroup', date, group_id, { + onReady: function() {} + }); + Meteor.subscribe('get-group',group_id , { + onReady: function() { + fillChartData(); + } + }); + +}); + +Template.companyItem.helpers({ + getActiveShow: function(str) { + if( str == activeShow.get() ) { + return 'active'; + } + return ''; + }, + + data: function() { + var group_id = Router.current().params._id; + return SimpleChat.Groups.findOne({_id: group_id}); + }, + + isLoading: function() { + return isLoading.get(); + } +}); + +Template.companyItem.events({ + 'click .back': function(e) { + return PUB.back(); + }, + 'click .weekly': function(e){ + activeShow.set('weekly'); + isLoading.set(true); + fillChartData(); + }, + 'click .monthly': function(e){ + activeShow.set('monthly'); + isLoading.set(true); + fillChartData(); + } +}) diff --git a/client/companyLists/companyLists.coffee b/client/companyLists/companyLists.coffee new file mode 100644 index 000000000..a717ecd71 --- /dev/null +++ b/client/companyLists/companyLists.coffee @@ -0,0 +1,44 @@ +if Meteor.isClient + Template.companyLists.rendered=-> + #$('.content').css 'min-height',$(window).height() + # Template.companyLists.onRendered ()-> + # Meteor.subscribe('get-my-group', Meteor.userId(), { + # onReady: ()-> + # Session.set('companyListLoading', false) + # }); + getBoundCompany = ()-> + company_array = [] + groups = SimpleChat.GroupUsers.find({user_id: Meteor.userId()}).fetch(); + if groups + for group in groups + if group.perf_info + is_exist = false + for item in company_array + if group.perf_info.companyId is item.companyId + is_exist = true; + break; + unless is_exist + company_array.push(group.perf_info) + #company_array.push({companyName: 'text company1'}) + #company_array.push({companyName: 'test company2'}) + company_array + Template.companyLists.helpers + isLoading: ()-> + loading = true + if Session.get('companyListLoading') is false + loading = false + loading + hasBoundCompany: ()-> + company_array = getBoundCompany() + company_array.length > 0 + # companies: ()-> + # getBoundCompany() + # return SimpleChat.GroupUsers.find({user_id: Meteor.userId()}).fetch() + Template.companyLists.events + 'click .companyLists ul li':(e, t)-> + # Session.set('reportUrl', this.reportUrl) + # Session.set('perfShowTitle', this.group_name) + # Router.go '/perfShow/'+this.group_id + return + Session.set('deviceDashboardTitle', this.group_name) + PUB.page '/device/dashboard/'+this.group_id diff --git a/client/companyLists/companyLists.html b/client/companyLists/companyLists.html new file mode 100644 index 000000000..6c816b0d3 --- /dev/null +++ b/client/companyLists/companyLists.html @@ -0,0 +1,55 @@ + + + + diff --git a/client/companyLists/companyLists.js b/client/companyLists/companyLists.js new file mode 100644 index 000000000..be16be2fd --- /dev/null +++ b/client/companyLists/companyLists.js @@ -0,0 +1,49 @@ +var theCurrentDay = new ReactiveVar(null); +var theDisplayDay = new ReactiveVar(null); +var groupsData = new ReactiveVar([]); + +var limit = new ReactiveVar(3); + +window.companyCharts = {}; +Template.companyLists.onRendered(function () { + window.companyCharts = {}; + //Meteor.subscribe('get-my-group', Meteor.userId()); + + var now = new Date(); + var displayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , 0, 0, 0, 0); + + theCurrentDay.set(date); // UTC日期 + theDisplayDay.set(displayDate); // 当前显示日期 + Session.set('companyListLoading', true); + Meteor.subscribe('userGroupsWorkstatusLists', date, limit.get(), { + onReady: function() { + Session.set('companyListLoading', false) + } + }); + + // scroll bottom to load more + $('.content').scroll(function(){ + if( $('.content').scrollTop() + $('.content')[0].offsetHeight >=$('.content')[0].scrollHeight){ + console.log('load more'); + var _limit = limit.get() + 3; + Meteor.subscribe('userGroupsWorkstatusLists', date,_limit, { + onReady: function() { + Session.set('companyListLoading', false); + limit.set(_limit); + } + }); + } + }); + +}); + +Template.companyLists.helpers({ + companies: function() { + return SimpleChat.GroupUsers.find({user_id: Meteor.userId()},{limit: limit.get()}).fetch() + } +}); + +Template.companyLists.onDestroyed(function() { + window.companyCharts = null; +}); diff --git a/client/companyLists/companyLists.less b/client/companyLists/companyLists.less new file mode 100644 index 000000000..25fc53958 --- /dev/null +++ b/client/companyLists/companyLists.less @@ -0,0 +1,95 @@ +.companyLists,.companyReporterPage { + margin-top: 4px; + ul#company-list{ + padding: 0px; + margin: 5px 0; + // li{ + // height: 70px; background-color: #fff; color: #000; margin-top: 1px; padding: 15px 10px 10px 70px; position: relative; cursor: pointer; + // .fa{display: block; line-height: 50px; font-size: 24px; text-align: center; color: white; background-color: #2196f3; width: 50px !important; height: 50px !important; position: absolute; left: 10px; top: 10px; margin: 0px !important; border-radius: 0px !important; border-radius: 50% !important;} + // .name{color: #000; margin-top: 9px; font-size: 16px;} + // } + .company-item{ + list-style: none; + color: #555; + font-size: 12px; + background: #fff; + margin: 15px 0; border-top: 1px solid #efefef; border-bottom: 1px solid #efefef; + .title{ + padding: 0 10px; + overflow: hidden; + height: 40px; + line-height: 40px; + button{ + outline: 0; + border: 0; + margin: 0; + width: 50px; + background: none; + color: #5f5f5f; + height: 38px; + line-height: 38px; + border-bottom: 2px solid #ddd; + } + .active{ border-bottom: 2px solid #39a8fe;} + } + h2{margin: 0;padding: 10px 0;font-size: 16px;border-bottom: 1px solid #efefef;} + .company-content{ height: 260px;background: #fff; padding: 5px; position: relative;} + } + } + .noBoundCompany{ + position:fixed; width:100%;text-align: center; margin-top:40%; color: black; font-size: 16px; + } + + .lineChartLoadingLayer{ + position: absolute; + left: 0; + top: 40px; + width: 100%; + height: 100%; + background: rgba(255, 255, 255, 0.78); + z-index: 999; + .lineChartLoading { + display: inline-block; + position: absolute; + top: 50%; + left: 50%; + margin-top: -20px; + margin-left: -60px; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + } + } +} + +.companyReporterPage{ + height: 100%; + .head{ + strong{text-align: left;width: 100%;display: inline-block;padding-left: 40px;} + .rightButton{ + padding-top: 0 !important; + button{ + outline: 0; + border: 0; + margin: 0; + width: 50px; + background: none; + color: #eee; + font-size: 14px; + height: 40px; + line-height: 40px; + position: relative; + z-index: 99; + } + button.active{font-size: 16px; color: #fff;} + } + } + + .company-content{ + height: 40%; + margin: 10px; + border-radius: 4px; + background: #efefef; + } +} \ No newline at end of file diff --git a/client/companyLists/perfShow.coffee b/client/companyLists/perfShow.coffee new file mode 100644 index 000000000..ac1a11f65 --- /dev/null +++ b/client/companyLists/perfShow.coffee @@ -0,0 +1,9 @@ +if Meteor.isClient + Template.perfShow.helpers + reportUrl: ()-> + Session.get('reportUrl') + title: ()-> + return Session.get('perfShowTitle') || '绩效展示' + Template.perfShow.events + 'click .perfShow .leftButton':(e, t)-> + history.back() diff --git a/client/companyLists/perfShow.html b/client/companyLists/perfShow.html new file mode 100644 index 000000000..69c76944f --- /dev/null +++ b/client/companyLists/perfShow.html @@ -0,0 +1,25 @@ + diff --git a/client/companyLists/perfShow.js b/client/companyLists/perfShow.js new file mode 100644 index 000000000..4a7a50c29 --- /dev/null +++ b/client/companyLists/perfShow.js @@ -0,0 +1,205 @@ +var getHourMinutesTime = function (minutes){ + return (Math.floor(minutes/60) + " h " + (minutes%60) + " min" ); +} +var getHourMinutesTime2 = function (minutes){ + if(minutes > 0){ + return (minutes/60).toFixed(1)+" h"; + } + return '' +} + +// 图表 option 定义 +var weeklyBarChartOption = { + color: ['#009688'], + backgroundColor: '#fff', + tooltip : { + trigger: 'axis', + axisPointer : { // 坐标轴指示器,坐标轴触发有效 + type : 'shadow' // 默认为直线,可选为:'line' | 'shadow' + }, + formatter: function (params,ticket,callback) { + var res = '日期 : ' + params[0].name + for (var i = 0, l = params.length; i < l; i++) { + res += '
' + params[i].seriesName + ' : ' + getHourMinutesTime(params[i].value); + } + return res; + } + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + top: '20px', + containLabel: true, + }, + xAxis : [ + { + type : 'category', + data : [] + } + ], + yAxis : [ + { + type : 'value', + axisLabel : { + formatter: function (value) { + return parseInt(value/60) + ' h'; + } + }, + splitNumber:3 + } + ], + series : [ + { + name:'平均上班时间', + type:'bar', + barWidth: '60%', + data:[], + label: { + normal: { + show: true, + position: 'top', + formatter: function (params) { + return getHourMinutesTime2(params.data); + } + } + }, + } + ] +}; + +var monthlyBarChartOption = {}; +$.extend( true, monthlyBarChartOption, weeklyBarChartOption ,{ + series: [{ + barGap: 0, + label: { + normal: { + show: false + } + } + }] +}); + + +// 填充最近一周周报数据 +fillWeeklyData = function(){ + var group_id = Router.current().params._id; + + var dates = []; + + var datas = []; + for(var i=7; i >0 ;i--){ + var now = new Date(); + now.setDate(now.getDate()-i); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() ,0, 0, 0, 0); + var ts = new Date(date) + ts = ts.parseDate('MM-DD') + dates.push(ts); + var count = 0; + var timeLen = 0; + WorkStatus.find({group_id: group_id, date:date}, {sort:{date: -1}}).forEach(function(item){ + count += 1; + var in_time = item.in_time || 0; + var out_time = item.out_time || 0; + var len = 0; + if(in_time && out_time){ + len = parseInt(out_time - in_time); + } + if(len && len !== NaN && len > 0){ + timeLen += len; + } + }); + + var data = 0; + if(count > 0){ + data = parseInt(timeLen/count/1000/60); + } + datas.push(data); + } + + weeklyBarChartOption.xAxis[0].data = dates; + weeklyBarChartOption.series[0].data = datas; + + console.log(weeklyBarChartOption); + weeklyBarChart.setOption(weeklyBarChartOption); + weeklyBarChart.hideLoading(); +} + +// 填充最近一月月报数据 +fillmonthlyData = function(){ + var group_id = Router.current().params._id; + + var dates = []; + + var datas = []; + for(var i=30; i >0 ;i--){ + var now = new Date(); + now.setDate(now.getDate()-i); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() ,0, 0, 0, 0); + var ts = new Date(date) + ts = ts.parseDate('MM-DD') + dates.push(ts); + + var count = 0; + var timeLen = 0; + WorkStatus.find({group_id: group_id, date:date}, {sort:{date: -1}}).forEach(function(item){ + count += 1; + var in_time = item.in_time || 0; + var out_time = item.out_time || 0; + var len = 0; + if(in_time && out_time){ + len = parseInt(out_time - in_time); + } + if(len && len !== NaN && len > 0){ + timeLen += len; + } + }); + + var data = 0; + if(count > 0){ + data = parseInt(timeLen/count/1000/60); + } + datas.push(data); + } + + monthlyBarChartOption.xAxis[0].data = dates; + monthlyBarChartOption.series[0].data = datas; + + console.log(monthlyBarChartOption); + monthlyBarChart.setOption(monthlyBarChartOption); + monthlyBarChart.hideLoading(); +} + +Template.perfShow.onRendered(function(){ + var chartLoadingOpt = { + text: '加载中', + color: '#37a7fe', + textColor: '#37a7fe' + }; + weeklyBarChart = echarts.init(document.getElementById('weeklyBarChart')); + weeklyBarChart.showLoading(chartLoadingOpt); + + monthlyBarChart = echarts.init(document.getElementById('monthlyBarChart')); + monthlyBarChart.showLoading(chartLoadingOpt); + + var group_id = Router.current().params._id; + var dates = []; + + for(var i=30; i > 0 ;i--){ + var now = new Date(); + now.setDate(now.getDate()-i); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() ,0, 0, 0, 0); + dates.push(date); + } + Meteor.subscribe('groupWorkStatusHistory',group_id,dates,{ + onReady: function(){ + fillWeeklyData(); + fillmonthlyData(); + }, + onStop: function(){ + fillWeeklyData(); + fillmonthlyData(); + } + }); + +}); \ No newline at end of file diff --git a/client/compatibility/crop.css b/client/compatibility/crop.css new file mode 100755 index 000000000..7bac17ad0 --- /dev/null +++ b/client/compatibility/crop.css @@ -0,0 +1,166 @@ +/*---------------------------------------------------------*/ +/* cropper styling begins here... +/*---------------------------------------------------------*/ +.crop { + display:block; + width:100%; + text-align:center; + position:relative; +} + +div[data-imgcrop] { + position:relative; + display:inline-block +} + +div[data-mask='true'] .crop-container { + overflow:hidden; + position:relative +} + +.cropMain .crop-img { + position:absolute +} +.cropMain .crop-overlay { + position:relative; + top:0; + left:0; + width:100%; + height:100%; + cursor: grab; + cursor: -webkit-grab; + cursor: -moz-grab +} +.grabcursor, +.cropMain .crop-overlay:active { + cursor: grabbing; + cursor: -webkit-grabbing; + cursor: -moz-grabbing +} +.cropMain .crop-container { + overflow:hidden +} +.cropMain .crop-shadow { + position:absolute; + width:100%; + height:100%; + box-shadow:0 0 0 2000px rgba(255, 255, 255, .85) !important; + -moz-box-shadow:0 0 0 2000px rgba(255, 255, 255, .85) !important; + -webkit-box-shadow:0 0 0 2000px rgba(255, 255, 255, .85) !important; + -khtml-box-shadow:0 0 0 2000px rgba(255, 255, 255, .85) !important; + -ms-box-shadow:0 0 0 2000px rgba(255, 255, 255, .85) !important; + background-image: url('img/move.png'); + background-size: 48px 48px; + background-repeat: no-repeat; + background-position: center; + +} +/* +.cropMain .crop-container:after { + content:""; + position:absolute; + top:0; + left:0; + width:100%; + height:100%; + z-index:5999; + + this box shadow is the mask effect + box-shadow:0 0 0 2000px rgba(255, 255, 255, .85); +} +*/ + + +/*---------------------------------------------------------*/ +/* zoom slider: input[type="range"] styling +/*---------------------------------------------------------*/ + +div[data-imgcrop] input[type="range"] { + -webkit-appearance: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); + + margin: 0; + border: none; + padding: 0; + border-radius: 0; + outline: none; /* no focus outline */ + + width:100%; + height:4px; + background:#eee; + + bottom:calc(50% - 2px); + left:calc(50% + 2px); + position:absolute; + z-index:6001; + -webkit-transform:rotate(270deg); + -ms-transform:rotate(270deg); + transform:rotate(270deg); + cursor: ns-resize +} +div[data-imgcrop] input[type="range"]:active { + cursor:ns-resize +} +div[data-imgcrop] input[type="range"]::-moz-range-track { + border: inherit; + background: transparent +} + +div[data-imgcrop] input[type="range"]::-ms-track { + border: inherit; + color: transparent; /* don't drawn vertical reference line */ + background: transparent +} +/* removes dotted border in firefox */ +div[data-imgcrop] input[type=range]::-moz-focus-outer { border: 0 } + + +div[data-imgcrop] input[type="range"]::-ms-fill-lower, +div[data-imgcrop] input[type="range"]::-ms-fill-upper { + background: transparent +} + +div[data-imgcrop] input[type="range"]::-ms-tooltip { + display: none +} + +/* thumb */ +div[data-imgcrop] input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width:15px; + height:20px; + border-radius:5px; + background:#fff; + box-shadow:-1px 0 3px rgba(0, 0, 0, .2), inset 4px 0 8px rgba(0, 0, 0, .1); + border:none +} +div[data-imgcrop] input[type="range"]::-moz-range-thumb { + width:15px; + height:20px; + border-radius:5px; + background:#fff; + box-shadow:-1px 0 3px rgba(0, 0, 0, .2), inset 4px 0 8px rgba(0, 0, 0, .1); + border:none +} +div[data-imgcrop] input[type="range"]::-ms-thumb { + width:15px; + height:20px; + border-radius:5px; + background:#fbc93d; + border:none +} + +.imgMask div { + width: 33.33%; + height: 33.33%; + float: left; +} +.imgMask div:nth-child(-n+3) { + border-bottom: 1px solid #fff; +} +.imgMask div:nth-child(-n+6) { + border-bottom: 1px solid #fff; +} +.imgMask div:nth-child(1), .imgMask div:nth-child(2), .imgMask div:nth-child(4), .imgMask div:nth-child(5), .imgMask div:nth-child(7), .imgMask div:nth-child(8) { + border-right: 1px solid #fff; +} \ No newline at end of file diff --git a/client/compatibility/crop.js b/client/compatibility/crop.js new file mode 100644 index 000000000..871c39b17 --- /dev/null +++ b/client/compatibility/crop.js @@ -0,0 +1,635 @@ +var CROP = (function () { + + return function () { + + // Code Dependant Variables + this.eles = { + ele: undefined, + container: undefined, + img: undefined, + overlay: undefined, + shadow: undefined, + grid: undefined, + preview: undefined, + callback: undefined + }; + + this.img = undefined; + this.imgInfo = { + style: '', + aw: 0, + ah: 0, + w: 0, + h: 0, + at: 0, + al: 0, + t: 0, + l: 0, + s: 1, // scale + v: 1 + }; + + this.init = function(ele) { + + $(ele.container) + .attr({ + 'data-imgcrop': '', + 'data-mask': ele.mask + }) + //.append('
'); + .append('
'); + + // set min zoom + this.imgInfo.s = ele.zoom.min; + if (ele.style) { + this.imgInfo.v = ele.zoom.value; + } + if (ele.style) { + this.imgInfo.style = ele.style; + } + if (ele.callback) { + this.eles.callback = ele.callback; + } + + + /* + Elements + */ + var umm = ele, + shell = $(ele.container), + zoom = shell.find('input'), + cropMain = shell.find('.cropMain'), + cropPreview = ele.preview, + img, + container, + overlay, + shadow, + grid, + that = this; + + + /* + Container + */ + container = $('
') + .attr({ + class: 'crop-container' + }) + .css({ + width: ele.width + 'px', + height: ele.height + 'px' + }); + + /* + Image + */ + img = $('') + .attr('class', 'crop-img') + .css({ + zIndex: 5999, + top: 0, + left: 0 + }); + + + + + /* + Crop Overlay + */ + overlay = $('
') + .attr({ + class: 'crop-overlay', + draggable: "true" + }) + .css({ + zIndex: 6000 + }); + + /* + Crop shadow + */ + shadow = $('
') + .attr({ + class: 'crop-shadow' + }) + .css({ + top:0, + left:0 + }); + + grid = '
'; + + // Add Elements + container.append(overlay); + container.append(img); + container.append(shadow); + container.append(grid); + cropMain.append(container); + + this.eles.ele = cropMain; + this.eles.container = container; + this.eles.img = img; + this.eles.overlay = overlay; + this.eles.shadow = shadow; + this.eles.grid = grid; + this.eles.preview = cropPreview; + + // load image + this.loadImg(ele.image); + if (this.imgInfo.style != '') { + zoom.val(this.imgInfo.v); + rangeColor(zoom); + } + + + /* + Bind Events + */ + container.resize(function () { + that.imgSize(); + }); + + + /* + Overlay Movement + */ + overlay.bind(((document.ontouchstart !== null) ? 'mousedown' : 'touchstart'), function (e) { + if (e.originalEvent.touches.length >= 2) { + //console.log("multiple touchs, mousedown, "+e.originalEvent.changedTouches.length); + return; + } + + // apply grab cursor + $('body').addClass('grabcursor'); + + var o = $(this), + mousedown = { + x: (e.originalEvent.touches[0].pageX), + y: (e.originalEvent.touches[0].pageY) + }, + elepos = { + x: o.parent().offset().left, + y: o.parent().offset().top + }; + + e.preventDefault(); + + + $(document).bind(((document.ontouchmove !== null) ? 'mousemove' : 'touchmove'), function (e) { + + if (e.originalEvent.touches.length >= 2) { + //console.log("multiple touchs, touchmove, "+e.originalEvent.changedTouches.length); + return; + } + var mousepos = { + x: (e.originalEvent.changedTouches[0].pageX || mousedown.x), + y: (e.originalEvent.changedTouches[0].pageY || mousedown.y) + }; + + + if (mousedown.y !== mousepos.y) { + + if (parseInt(o.css('top')) === 0) o.css({ + top: that.eles.ele.offset().top, + left: that.eles.ele.offset().left + }); + + // Move Image + that.imgMove({ + t: parseInt(o.css('top')) - (elepos.y - (mousedown.y - mousepos.y)), + l: parseInt(o.css('left')) - (elepos.x - (mousedown.x - mousepos.x)) + }); + + // Reposition Overlay + o.css({ + left: elepos.x - (mousedown.x - mousepos.x), + top: elepos.y - (mousedown.y - mousepos.y) + }); + + } + + }); + + $(document).bind(((document.ontouchend !== null) ? 'mouseup' : 'touchend'), function (e) { + if (e.originalEvent.touches.length >= 2) { + //console.log("multiple touchs, touchmove, "+e.originalEvent.changedTouches.length); + return; + } + // remove grab cursor + $('body').removeClass('grabcursor'); + + $(document).unbind(((document.ontouchmove !== null) ? 'mousemove' : 'touchmove')); + overlay.css({ + top: 0, + left: 0 + }); + }); + + return false; + }); + + + // config slide + // -------------------------------------------------------------------------- + + $('input[type=range]').on("input change", function () { + + that.slider(zoom.val()); + + rangeColor(zoom); + + }); + + that.zoom = function(num) { + + if(num === 'min' || num === 'max') + var num = parseInt(zoom.attr(num)); + + that.slider(num); + zoom.val(num); + rangeColor(zoom); + + }; + + + // zoom slider progress color + function rangeColor(self) { + + var val = self.val(), + min = self.attr('min'), + max = self.attr('max'), + pos = Math.round(((val - min) / (max - min)) * 100), + style = "display:none; background: linear-gradient(to right, #fbc93d " + pos + "%, #eee " + (pos + 0.1) + "%);"; + + // apply background color to range progress + self.attr('style', style); + + } + + }; + + this.loadImg = function (url) { + var that = this; + + this.eles.img.removeAttr('style').attr('src', url) + .load(function () { + that.imgSize(); + if (that.eles.callback) { + that.eles.callback(); + } + }); + }; + + this.imgSize = function () { + var img = this.eles.img, + imgSize = { + w: img.css('width', '').width(), + h: img.css('height', '').height() + }, + c = this.eles.container; + + var holderRatio = { + wh: this.eles.container.width() / this.eles.container.height(), + hw: this.eles.container.height() / this.eles.container.width() + }; + + this.imgInfo.aw = imgSize.w; + this.imgInfo.ah = imgSize.h; + + if (imgSize.w * holderRatio.hw < imgSize.h * holderRatio.wh) { + + this.imgInfo.w = c.width(); + this.imgInfo.h = this.imgInfo.w * (imgSize.h / imgSize.w); + this.imgInfo.al = 0; + + } else { + + this.imgInfo.h = c.height(); + this.imgInfo.w = this.imgInfo.h * (imgSize.w / imgSize.h); + this.imgInfo.at = 0; + } + + if (this.imgInfo.style != '') { + var getPorp = function(styleStr, propertyName) { + if ((styleStr == null) || (styleStr == '')) { + return ''; + } + var styleAttrs = styleStr.split(';'); + for (i = 0, len = styleAttrs.length; i < len; i++) { + var item = styleAttrs[i]; + var styleValue = item.split(':'); + if (styleValue[0].trim() === propertyName) { + //if (styleValue[1].indexOf('%') >= 0) { + return styleValue[1].trim(); + //} + } + } + return ''; + } + var styleArray = this.imgInfo.style.split(';'); + var topValue = parseFloat(getPorp(this.imgInfo.style, 'top')); + var leftValue = parseFloat(getPorp(this.imgInfo.style, 'left')); + this.imgInfo.t = -c.height() * topValue / 100; + this.imgInfo.l = -c.width() * leftValue / 100; + this.imgInfo.s = this.imgInfo.v; + this.slider(this.imgInfo.v); + //img.attr('style', this.imgInfo.style); + } else { + this.imgResize(); + } + + }; + + + this.imgResize = function (scale) { + + var img = this.eles.img, + imgInfo = this.imgInfo, + oldScale = imgInfo.s; + + imgInfo.s = scale || imgInfo.s; + + img.css({ + width: imgInfo.w * imgInfo.s, + height: imgInfo.h * imgInfo.s + }); + + // Move Image Based on Size Changes + this.imgMove({ + t: -((imgInfo.h * oldScale) - (imgInfo.h * imgInfo.s)) / 2, + l: -((imgInfo.w * oldScale) - (imgInfo.w * imgInfo.s)) / 2 + }); + }; + + this.imgMove = function (move) { + + var img = this.eles.img, + imgInfo = this.imgInfo, + c = this.eles.container; + + imgInfo.t += move.t; + imgInfo.l += move.l; + + var t = imgInfo.at - imgInfo.t, + l = imgInfo.al - imgInfo.l; + + + if (t >= 0) t = imgInfo.t = 0; + + else if (t < -((imgInfo.h * imgInfo.s) - c.height())) { + t = -((imgInfo.h * imgInfo.s) - c.height()); + imgInfo.t = ((imgInfo.at === 0) ? (imgInfo.h * imgInfo.s) - c.height() : (imgInfo.h * imgInfo.s) - c.height()); + } + + if (l >= 0) l = imgInfo.l = 0; + + else if (l < -((imgInfo.w * imgInfo.s) - c.width())) { + l = -((imgInfo.w * imgInfo.s) - c.width()); + imgInfo.l = ((imgInfo.al === 0) ? (imgInfo.w * imgInfo.s) - c.width() : (imgInfo.w * imgInfo.s) - c.width()); + } + + // Set Position + img.css({ + top: t, + left: l + }); + + + if(this.eles.preview) + this.preview(this.eles.preview.width, this.eles.preview.height, this.eles.preview.container); + + }; + + + + /* + Slider + */ + this.slider = function (slideval) { + + this.imgResize(slideval); + + }; + + + + + // return cropped data: coordinates & base64 string + // -------------------------------------------------------------------------- + + this.data = function(width, height, filetype) { + + var self = this, + imgInfo = self.imgInfo, + c = self.eles.container, + img = self.eles.img; + + var original = new Image(); + original.src = img.attr('src'); + + // draw image to canvas + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + w = Math.round((c.width() - (0 * 2)) * (imgInfo.aw / (imgInfo.w * imgInfo.s))), + h = Math.round((c.height() - (0 * 2)) * (imgInfo.ah / (imgInfo.h * imgInfo.s))), + x = Math.round(-(parseInt(img.css('left')) - 0) * (imgInfo.aw / (imgInfo.w * imgInfo.s))), + y = Math.round(-(parseInt(img.css('top')) - 0) * (imgInfo.ah / (imgInfo.h * imgInfo.s))); + + canvas.getContext('2d').drawImage(original, x, y, w, h, 0, 0, width, height); + + data = { + width: width, + height: height, + image: canvas.toDataURL("image/"+filetype), + filetype: filetype + }; + + return data; + }; + + + + + + // show preview + // -------------------------------------------------------------------------- + + this.preview = function(width, height, target) { + + // if #preview element doesn't exist, create + if(!$(target + ' img').length) + $(target).append(''); + + var self = this, + imgInfo = self.imgInfo, + c = self.eles.container, + img = self.eles.img; + + var original = new Image(); + original.src = img.attr('src'); + + // draw image to canvas + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + w = Math.round((c.width() - (0 * 2)) * (imgInfo.aw / (imgInfo.w * imgInfo.s))), + h = Math.round((c.height() - (0 * 2)) * (imgInfo.ah / (imgInfo.h * imgInfo.s))), + x = Math.round(-(parseInt(img.css('left')) - 0) * (imgInfo.aw / (imgInfo.w * imgInfo.s))), + y = Math.round(-(parseInt(img.css('top')) - 0) * (imgInfo.ah / (imgInfo.h * imgInfo.s))); + + canvas.getContext('2d').drawImage(original, x, y, w, h, 0, 0, width, height); + + $(target + ' img') + .attr('src', canvas.toDataURL()); + + + }; + + + + // flip image + // -------------------------------------------------------------------------- + + this.flip = function() { + + var self = this, + imgInfo = self.imgInfo, + c = self.eles.container, + img = self.eles.img; + + // get current width & height + var original = new Image(); + original.src = img.attr('src'); + height = original.naturalHeight; + width = original.naturalWidth; + + canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + canvas.getContext('2d').translate(width, 0); + canvas.getContext('2d').scale(-1, 1); + canvas.getContext('2d').drawImage(original, 0, 0); + canvas.getContext('2d').translate(width, 0); + canvas.getContext('2d').scale(-1, 1); + + c.find('.crop-img') + .removeAttr('style') + .attr('src', canvas.toDataURL()); + + }; + + + + + // rotate 90 degrees + // -------------------------------------------------------------------------- + + this.rotate = function() { + + var self = this, + imgInfo = self.imgInfo, + c = self.eles.container, + img = self.eles.img; + + // get current width & height + var original = new Image(); + original.src = img.attr('src'); + height = original.naturalHeight; + width = original.naturalWidth; + + canvas = document.createElement('canvas'); + canvas.width = height; + canvas.height = width; + + canvas.getContext('2d').translate(canvas.width / 2, canvas.height / 2); + canvas.getContext('2d').rotate(Math.PI / 2); + canvas.getContext('2d').drawImage(original, -width / 2, -height / 2); + canvas.getContext('2d').rotate(-Math.PI / 2); + canvas.getContext('2d').translate(-canvas.width / 2, -canvas.height / 2); + + c.find('.crop-img') + .removeAttr('style') + .attr('src', canvas.toDataURL()); + + }; + + + + // download image + // -------------------------------------------------------------------------- + + this.download = function(width, height, filename, filetype, link) { + + var self = this, + imgInfo = self.imgInfo, + c = self.eles.container, + img = self.eles.img; + + var original = new Image(); + original.src = img.attr('src'); + + // draw image to canvas + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + w = Math.round((c.width() - (0 * 2)) * (imgInfo.aw / (imgInfo.w * imgInfo.s))), + h = Math.round((c.height() - (0 * 2)) * (imgInfo.ah / (imgInfo.h * imgInfo.s))), + x = Math.round(-(parseInt(img.css('left')) - 0) * (imgInfo.aw / (imgInfo.w * imgInfo.s))), + y = Math.round(-(parseInt(img.css('top')) - 0) * (imgInfo.ah / (imgInfo.h * imgInfo.s))); + + canvas.getContext('2d').drawImage(original, x, y, w, h, 0, 0, width, height); + + $('#' + link) + .attr('href', canvas.toDataURL("image/"+filetype)) + .attr('download', filename + '.' + filetype); + + }; + + + + // import new image + // -------------------------------------------------------------------------- + + this.import = function() { + + $('body').append(''); + $('#importIMG').click(); + + var self = this, + imgInfo = self.imgInfo, + c = self.eles.container, + img = self.eles.img; + + oFReader = new FileReader(), rFilter = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i; + + + $('#importIMG').change(function() { + + if(document.getElementById("importIMG").files.length === 0) return; + var oFile = document.getElementById("importIMG").files[0]; + if(!rFilter.test(oFile.type)) return; + oFReader.readAsDataURL(oFile); + + $('#importIMG').remove(); + + }); + + oFReader.onload = function (oFREvent) { + + c.find('.crop-img') + .removeAttr('style') + .attr('src', oFREvent.target.result); + + }; + + }; + + }; + +}()); diff --git a/client/compatibility/readability_class.js b/client/compatibility/readability_class.js new file mode 100644 index 000000000..74109dfd4 --- /dev/null +++ b/client/compatibility/readability_class.js @@ -0,0 +1,859 @@ +var dbg = function(s) { + if(typeof console !== 'undefined') + console.log("Readability: " + s); +}; + +/* + * Readability. An Arc90 Lab Experiment. + * Website: http://lab.arc90.com/experiments/readability + * Source: http://code.google.com/p/arc90labs-readability + * + * Copyright (c) 2009 Arc90 Inc + * Readability is licensed under the Apache License, Version 2.0. +**/ + +Readability = (function(){ + var readability = { + version: '0.5.1', + emailSrc: 'http://lab.arc90.com/experiments/readability/email.php', + kindleSrc: 'http://lab.arc90.com/experiments/readability/kindle.php', + iframeLoads: 0, + frameHack: false, /** + * The frame hack is to workaround a firefox bug where if you + * pull content out of a frame and stick it into the parent element, the scrollbar won't appear. + * So we fake a scrollbar in the wrapping div. + **/ + bodyCache: null, /* Cache the body HTML in case we need to re-use it later */ + documentBodyElement: document.createElement("body"), + + readStyle: 'style-newspaper', + readSize: 'size-large', + readMargin: 'margin-wide', + + /** + * All of the regular expressions in use within readability. + * Defined up here so we don't instantiate them repeatedly in loops. + **/ + regexps: { + unlikelyCandidatesRe: /combx|comment|disqus|foot|header|menu|meta|nav|rss|shoutbox|sidebar|sponsor/i, + okMaybeItsACandidateRe: /and|article|body|column|main/i, + positiveRe: /article|body|content|entry|hentry|page|pagination|post|text/i, + negativeRe: /combx|comment|contact|foot|footer|footnote|link|media|meta|promo|related|scroll|shoutbox|sponsor|tags|widget/i, + divToPElementsRe: /<(a|blockquote|dl|div|img|ol|p|pre|table|ul)/i, + replaceBrsRe: /(]*>[ \n\r\t]*){2,}/gi, + replaceFontsRe: /<(\/?)font[^>]*>/gi, + trimRe: /^\s+|\s+$/g, + normalizeRe: /\s{2,}/g, + killBreaksRe: /((\s| ?)*){1,}/g, + videoRe: /http:\/\/(www\.)?(youtube|vimeo)\.com/i + }, + + /** + * Runs readability. + * + * Workflow: + * 1. Prep the document by removing script tags, css, etc. + * 2. Build readability's DOM tree. + * 3. Grab the article content from the current dom tree. + * 4. Replace the current DOM tree with the new one. + * 5. Read peacefully. + * + * @return element + **/ + init: function(documentBodyElement, preserveUnlikelyCandidates) { + preserveUnlikelyCandidates = (typeof preserveUnlikelyCandidates == 'undefined') ? false : preserveUnlikelyCandidates; + readability.bodyCache = documentBodyElement.innerHTML; + readability.documentBodyElement.innerHTML = documentBodyElement.innerHTML; + readability.prepDocument(); + + /* Build readability's DOM tree */ + var overlay = document.createElement("DIV"); + var innerDiv = document.createElement("DIV"); + var articleTools = readability.getArticleTools(); + var articleTitle = readability.getArticleTitle(); + var articleContent = readability.grabArticle(preserveUnlikelyCandidates); + var articleFooter = readability.getArticleFooter(); + + /** + * If we attempted to strip unlikely candidates on the first run through, and we ended up with no content, + * that may mean we stripped out the actual content so we couldn't parse it. So re-run init while preserving + * unlikely candidates to have a better shot at getting our content out properly. + **/ + if(readability.getInnerText(articleContent, false) == "") + { + if(!preserveUnlikelyCandidates) { + readability.documentBodyElement.innerHTML = readability.bodyCache; + return readability.init(true); + } + else + { + articleContent.innerHTML = "

Sorry, readability was unable to parse this page for content. If you feel like it should have been able to, please let us know by submitting an issue.

"; + } + } + + overlay.id = "readOverlay"; + innerDiv.id = "readInner"; + + /* Apply user-selected styling */ + readability.documentBodyElement.className = readability.readStyle; + overlay.className = readability.readStyle; + innerDiv.className = readability.readMargin + " " + readability.readSize; + + /* Glue the structure of our document together. */ + //articleContent.appendChild( articleFooter ); + // innerDiv.appendChild( articleTitle ); + innerDiv.appendChild( articleContent ); + // overlay.appendChild( articleTools ); + overlay.appendChild( innerDiv ); + + /* Clear the old HTML, insert the new content. */ + readability.documentBodyElement.innerHTML = ""; + readability.documentBodyElement.insertBefore(overlay, readability.documentBodyElement.firstChild); + + if(readability.frameHack) + { + var readOverlay = $(readability.documentBodyElement).find('#readOverlay')[0]; + readOverlay.style.height = '100%'; + readOverlay.style.overflow = 'auto'; + } + + return readability.documentBodyElement; + }, + + /** + * Get the article tools Element that has buttons like reload, print, email. + * + * @return void + **/ + getArticleTools: function () { + var articleTools = document.createElement("DIV"); + + articleTools.id = "readTools"; + articleTools.innerHTML = "\ + Reload Original Page\ + Print Page\ + Email Page\ + Send to Kindle\ + "; + + return articleTools; + }, + + /** + * Get the article title as an H1. Currently just uses document.title, + * we might want to be smarter in the future. + * + * @return void + **/ + getArticleTitle: function () { + var articleTitle = document.createElement("H1"); + articleTitle.innerHTML = '';//document.title; + + return articleTitle; + }, + + /** + * Get the footer with the readability mark etc. + * + * @return void + **/ + getArticleFooter: function () { + var articleFooter = document.createElement("DIV"); + + articleFooter.id = "readFooter"; + articleFooter.innerHTML = "\ + \ + \ + Follow us on Twitter »\ + \ + "; + + return articleFooter; + }, + + /** + * Prepare the HTML document for readability to scrape it. + * This includes things like stripping javascript, CSS, and handling terrible markup. + * + * @return void + **/ + prepDocument: function () { + var frames = $(readability.documentBodyElement).find('frame'); + if(frames.length > 0) + { + var bestFrame = null; + var bestFrameSize = 0; + for(var frameIndex = 0; frameIndex < frames.length; frameIndex++) + { + var frameSize = frames[frameIndex].offsetWidth + frames[frameIndex].offsetHeight; + var canAccessFrame = false; + try { + frames[frameIndex].contentWindow.document.body; + canAccessFrame = true; + } catch(e) {} + + if(canAccessFrame && frameSize > bestFrameSize) + { + bestFrame = frames[frameIndex]; + bestFrameSize = frameSize; + } + } + + if(bestFrame) + { + var newBody = document.createElement('body'); + newBody.innerHTML = bestFrame.contentWindow.document.body.innerHTML; + newBody.style.overflow = 'scroll'; + readability.documentBodyElement = newBody; + + var frameset = $(readability.documentBodyElement).find('frameset')[0]; + if(frameset) + frameset.parentNode.removeChild(frameset); + + readability.frameHack = true; + } + } + + /* Turn all double br's into p's */ + /* Note, this is pretty costly as far as processing goes. Maybe optimize later. */ + readability.documentBodyElement.innerHTML = readability.documentBodyElement.innerHTML.replace(readability.regexps.replaceBrsRe, '

').replace(readability.regexps.replaceFontsRe, '<$1span>') + }, + + /** + * Prepare the article node for display. Clean out any inline styles, + * iframes, forms, strip extraneous

tags, etc. + * + * @param Element + * @return void + **/ + prepArticle: function (articleContent) { + readability.cleanStyles(articleContent); + readability.killBreaks(articleContent); + + /* Clean out junk from the article content */ + readability.clean(articleContent, "form"); + readability.clean(articleContent, "object"); + readability.clean(articleContent, "h1"); + /** + * If there is only one h2, they are probably using it + * as a header and not a subheader, so remove it since we already have a header. + ***/ + if(articleContent.getElementsByTagName('h2').length == 1) + readability.clean(articleContent, "h2"); + readability.clean(articleContent, "iframe"); + + readability.cleanHeaders(articleContent); + + /* Do these last as the previous stuff may have removed junk that will affect these */ + readability.cleanConditionally(articleContent, "table"); + readability.cleanConditionally(articleContent, "ul"); + readability.cleanConditionally(articleContent, "div"); + + /* Remove extra paragraphs */ + var articleParagraphs = articleContent.getElementsByTagName('p'); + for(i = articleParagraphs.length-1; i >= 0; i--) + { + var imgCount = articleParagraphs[i].getElementsByTagName('img').length; + var embedCount = articleParagraphs[i].getElementsByTagName('embed').length; + var objectCount = articleParagraphs[i].getElementsByTagName('object').length; + + if(imgCount == 0 && embedCount == 0 && objectCount == 0 && readability.getInnerText(articleParagraphs[i], false) == '') + { + articleParagraphs[i].parentNode.removeChild(articleParagraphs[i]); + } + } + + try { + articleContent.innerHTML = articleContent.innerHTML.replace(/]*>\s*

topCandidate.readability.contentScore) + topCandidate = candidates[i]; + } + + /** + * If we still have no top candidate, just use the body as a last resort. + * We also have to copy the body node so it is something we can modify. + **/ + if (topCandidate == null || topCandidate.tagName == "BODY") + { + topCandidate = document.createElement("DIV"); + topCandidate.innerHTML = readability.documentBodyElement.innerHTML; + readability.documentBodyElement.innerHTML = ""; + readability.documentBodyElement.appendChild(topCandidate); + readability.initializeNode(topCandidate); + } + + + /** + * Now that we have the top candidate, look through its siblings for content that might also be related. + * Things like preambles, content split by ads that we removed, etc. + **/ + var articleContent = document.createElement("DIV"); + articleContent.id = "readability-content"; + var siblingScoreThreshold = Math.max(10, topCandidate.readability.contentScore * 0.2); + var siblingNodes = topCandidate.parentNode.childNodes; + for(var i=0, il=siblingNodes.length; i < il; i++) + { + var siblingNode = siblingNodes[i]; + var append = false; + + dbg("Looking at sibling node: " + siblingNode + " (" + siblingNode.className + ":" + siblingNode.id + ")" + ((typeof siblingNode.readability != 'undefined') ? (" with score " + siblingNode.readability.contentScore) : '')); + dbg("Sibling has score " + (siblingNode.readability ? siblingNode.readability.contentScore : 'Unknown')); + + if(siblingNode === topCandidate) + { + append = true; + } + + if(typeof siblingNode.readability != 'undefined' && siblingNode.readability.contentScore >= siblingScoreThreshold) + { + append = true; + } + + if(siblingNode.nodeName == "P") { + var linkDensity = readability.getLinkDensity(siblingNode); + var nodeContent = readability.getInnerText(siblingNode); + var nodeLength = nodeContent.length; + + if(nodeLength > 80 && linkDensity < 0.25) + { + append = true; + } + else if(nodeLength < 80 && linkDensity == 0 && nodeContent.search(/\.( |$)/) !== -1) + { + append = true; + } + } + + if(append) + { + dbg("Appending node: " + siblingNode) + + /* Append sibling and subtract from our list because it removes the node when you append to another node */ + articleContent.appendChild(siblingNode); + i--; + il--; + } + } + + /** + * So we have all of the content that we need. Now we clean it up for presentation. + **/ + readability.prepArticle(articleContent); + + return articleContent; + }, + + /** + * Get the inner text of a node - cross browser compatibly. + * This also strips out any excess whitespace to be found. + * + * @param Element + * @return string + **/ + getInnerText: function (e, normalizeSpaces) { + var textContent = ""; + + normalizeSpaces = (typeof normalizeSpaces == 'undefined') ? true : normalizeSpaces; + + if (navigator.appName == "Microsoft Internet Explorer") + textContent = e.innerText.replace( readability.regexps.trimRe, "" ); + else + textContent = e.textContent.replace( readability.regexps.trimRe, "" ); + + if(normalizeSpaces) + return textContent.replace( readability.regexps.normalizeRe, " "); + else + return textContent; + }, + + /** + * Get the number of times a string s appears in the node e. + * + * @param Element + * @param string - what to split on. Default is "," + * @return number (integer) + **/ + getCharCount: function (e,s) { + s = s || ","; + return readability.getInnerText(e).split(s).length; + }, + + /** + * Remove the style attribute on every e and under. + * TODO: Test if getElementsByTagName(*) is faster. + * + * @param Element + * @return void + **/ + cleanStyles: function (e) { + e = e || document; + var cur = e.firstChild; + + if(!e) + return; + + // Remove any root styles, if we're able. + if(typeof e.removeAttribute == 'function' && e.className != 'readability-styled') + e.removeAttribute('style'); + + // Go until there are no more child nodes + while ( cur != null ) { + if ( cur.nodeType == 1 ) { + // Remove style attribute(s) : + if(cur.className != "readability-styled") { + cur.removeAttribute("style"); + } + readability.cleanStyles( cur ); + } + cur = cur.nextSibling; + } + }, + + /** + * Get the density of links as a percentage of the content + * This is the amount of text that is inside a link divided by the total text in the node. + * + * @param Element + * @return number (float) + **/ + getLinkDensity: function (e) { + var links = e.getElementsByTagName("a"); + var textLength = readability.getInnerText(e).length; + var linkLength = 0; + for(var i=0, il=links.length; i'); + } + catch (e) { + dbg("KillBreaks failed - this is an IE bug. Ignoring."); + } + }, + + /** + * Clean a node of all elements of type "tag". + * (Unless it's a youtube/vimeo video. People love movies.) + * + * @param Element + * @param string tag to clean + * @return void + **/ + clean: function (e, tag) { + var targetList = e.getElementsByTagName( tag ); + var isEmbed = (tag == 'object' || tag == 'embed'); + + for (var y=targetList.length-1; y >= 0; y--) { + /* Allow youtube and vimeo videos through as people usually want to see those. */ + if(isEmbed && targetList[y].innerHTML.search(readability.regexps.videoRe) !== -1) + { + continue; + } + + targetList[y].parentNode.removeChild(targetList[y]); + } + }, + + /** + * Clean an element of all tags of type "tag" if they look fishy. + * "Fishy" is an algorithm based on content length, classnames, link density, number of images & embeds, etc. + * + * @return void + **/ + cleanConditionally: function (e, tag) { + var tagsList = e.getElementsByTagName(tag); + var curTagsLength = tagsList.length; + + /** + * Gather counts for other typical elements embedded within. + * Traverse backwards so we can remove nodes at the same time without effecting the traversal. + * + * TODO: Consider taking into account original contentScore here. + **/ + for (var i=curTagsLength-1; i >= 0; i--) { + var weight = readability.getClassWeight(tagsList[i]); + + dbg("Cleaning Conditionally " + tagsList[i] + " (" + tagsList[i].className + ":" + tagsList[i].id + ")" + ((typeof tagsList[i].readability != 'undefined') ? (" with score " + tagsList[i].readability.contentScore) : '')); + + if(weight < 0) + { + tagsList[i].parentNode.removeChild(tagsList[i]); + } + else if ( readability.getCharCount(tagsList[i],',') < 10) { + /** + * If there are not very many commas, and the number of + * non-paragraph elements is more than paragraphs or other ominous signs, remove the element. + **/ + + var p = tagsList[i].getElementsByTagName("p").length; + var img = tagsList[i].getElementsByTagName("img").length; + var li = tagsList[i].getElementsByTagName("li").length-100; + var input = tagsList[i].getElementsByTagName("input").length; + + var embedCount = 0; + var embeds = tagsList[i].getElementsByTagName("embed"); + for(var ei=0,il=embeds.length; ei < il; ei++) { + if (embeds[ei].src.search(readability.regexps.videoRe) == -1) { + embedCount++; + } + } + + var linkDensity = readability.getLinkDensity(tagsList[i]); + var contentLength = readability.getInnerText(tagsList[i]).length; + var toRemove = false; + + if ( img > p ) { + toRemove = true; + } else if(li > p && tag != "ul" && tag != "ol") { + toRemove = true; + } else if( input > Math.floor(p/3) ) { + toRemove = true; + } else if(contentLength < 25 && (img == 0 || img > 2) ) { + toRemove = true; + } else if(weight < 25 && linkDensity > .2) { + toRemove = true; + } else if(weight >= 25 && linkDensity > .5) { + toRemove = true; + } else if((embedCount == 1 && contentLength < 75) || embedCount > 1) { + toRemove = true; + } + + if(toRemove) { + tagsList[i].parentNode.removeChild(tagsList[i]); + } + } + } + }, + + /** + * Clean out spurious headers from an Element. Checks things like classnames and link density. + * + * @param Element + * @return void + **/ + cleanHeaders: function (e) { + for (var headerIndex = 1; headerIndex < 7; headerIndex++) { + var headers = e.getElementsByTagName('h' + headerIndex); + for (var i=headers.length-1; i >=0; i--) { + if (readability.getClassWeight(headers[i]) < 0 || readability.getLinkDensity(headers[i]) > 0.33) { + headers[i].parentNode.removeChild(headers[i]); + } + } + } + }, + + /** + * Show the email popup. + * + * @return void + **/ + emailBox: function () { + var emailContainer = document.getElementById('email-container'); + if(null != emailContainer) + { + return; + } + + var emailContainer = document.createElement('div'); + emailContainer.setAttribute('id', 'email-container'); + emailContainer.innerHTML = ''; + + readability.documentBodyElement.appendChild(emailContainer); + }, + + /** + * Show the email popup. + * + * @return void + **/ + kindleBox: function () { + var kindleContainer = $(readability.documentBodyElement).find('#kindle-container')[0]; + if(null != kindleContainer) + { + return; + } + + var kindleContainer = document.createElement('div'); + kindleContainer.setAttribute('id', 'kindle-container'); + kindleContainer.innerHTML = ''; + + readability.documentBodyElement.appendChild(kindleContainer); + + /* Dynamically create a form to be POSTed to the iframe */ + var formHtml = '

'; + + readability.documentBodyElement.innerHTML += formHtml; + $(readability.documentBodyElement).find('#readabilityKindleForm')[0].submit(); + }, + + /** + * Close the email popup. This is a hacktackular way to check if we're in a "close loop". + * Since we don't have crossdomain access to the frame, we can only know when it has + * loaded again. If it's loaded over 3 times, we know to close the frame. + * + * @return void + **/ + removeFrame: function () { + readability.iframeLoads++; + if (readability.iframeLoads > 3) + { + var emailContainer = $(readability.documentBodyElement).find('#email-container')[0]; + if (null !== emailContainer) { + emailContainer.parentNode.removeChild(emailContainer); + } + + var kindleContainer = $(readability.documentBodyElement).find('#kindle-container')[0]; + if (null !== kindleContainer) { + kindleContainer.parentNode.removeChild(kindleContainer); + } + + readability.iframeLoads = 0; + } + }, + + htmlspecialchars: function (s) { + if (typeof(s) == "string") { + s = s.replace(/&/g, "&"); + s = s.replace(/"/g, """); + s = s.replace(/'/g, "'"); + s = s.replace(//g, ">"); + } + + return s; + } + }; + + function Readability(){} + Readability.prototype.execute = function(documentBodyElement){return readability.init(documentBodyElement);} + return Readability; +})(); diff --git a/client/connection/connection.coffee b/client/connection/connection.coffee new file mode 100644 index 000000000..1332c7cad --- /dev/null +++ b/client/connection/connection.coffee @@ -0,0 +1,32 @@ +### +if Meteor.isClient + Meteor.startup ()-> + offlineHandler = ()-> + console.log "offline now" + onlineHandler = ()-> + console.log "online now" + document.addEventListener("offline", offlineHandler, false) + document.addEventListener("online", onlineHandler, false) + checkConnection=()-> + networkState = navigator.connection.type; + states = {}; + states[Connection.UNKNOWN] = 'Unknown connection' + states[Connection.ETHERNET] = 'Ethernet connection' + states[Connection.WIFI] = 'WiFi connection' + states[Connection.CELL_2G] = 'Cell 2G connection' + states[Connection.CELL_3G] = 'Cell 3G connection' + states[Connection.CELL_4G] = 'Cell 4G connection' + states[Connection.CELL] = 'Cell generic connection' + states[Connection.NONE] = 'No network connection' + + console.log('Connection type: ' + states[networkState]) + + Deps.autorun ()-> + if Meteor.status().connected is false + console.log "Disconnected from server" + else + console.log "Connected to Server" + console.log("Reconnect in "+(Meteor.status().retryTime - (new Date()).getTime())+" Status is "+JSON.stringify(Meteor.status())) + checkConnection() + +### \ No newline at end of file diff --git a/client/ddp_url.html b/client/ddp_url.html new file mode 100644 index 000000000..35ae97185 --- /dev/null +++ b/client/ddp_url.html @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/client/deviceDashboard/deviceDashboard.html b/client/deviceDashboard/deviceDashboard.html new file mode 100644 index 000000000..323d0ba57 --- /dev/null +++ b/client/deviceDashboard/deviceDashboard.html @@ -0,0 +1,123 @@ + + + + diff --git a/client/deviceDashboard/deviceDashboard.js b/client/deviceDashboard/deviceDashboard.js new file mode 100644 index 000000000..d48e2cd86 --- /dev/null +++ b/client/deviceDashboard/deviceDashboard.js @@ -0,0 +1,596 @@ +var date = new ReactiveVar(null); +var today = new ReactiveVar(null); +var time_offset = new ReactiveVar(8); + +var lists = new ReactiveVar([]); +var isOut = new ReactiveVar(false); +var limit = new ReactiveVar(200); +var ckeckInNames = new ReactiveVar([]); +var theCurrentDay = new ReactiveVar(null); +var theDisplayDay = new ReactiveVar(null); +var popObj = new ReactiveVar({}); +var isLoading = new ReactiveVar(false); +var dateList = new ReactiveVar([]); +var curTime = new ReactiveVar(null); + //前7天加入dateList +var _dateList = []; +for(var i=7;i>-2;i--){ + var t = moment().subtract(i, 'day'); + var weekStr = t.format('ddd'); + var d = t.format('MM/DD'); + var utc = Date.UTC(t.year(),t.month(),t.date(),0,0,0,0); + _dateList.push({ + week:weekStr, + date:d, + utc:utc, + id:(7-i)+'date' + }) + } + dateList.set(_dateList); + curTime.set(dateList.get()[7]); +var parseDate = function(currentDay){ + //var today = new Date(Session.get('today')); + var year = currentDay.getFullYear(); + var month = currentDay.getMonth() + 1; + var date = year + '-' + month + '-' +currentDay.getDate(); + // if (currentDay.getDate() === today.getDate()) { + // date = date + ' 今天'; + // } + // else if (currentDay.getDate() - today.getDate() === -1 ) { + // date = date + ' 昨天'; + // } + //else { + var day = ''; + switch(currentDay.getDay()) + { + case 0: + day = '周日'; + break; + case 1: + day = '周一'; + break; + case 2: + day = '周二'; + break; + case 3: + day = '周三'; + break; + case 4: + day = '周四'; + break; + case 5: + day = '周五'; + break; + case 6: + day = '周六'; + break; + default: + break; + } + date = date + ' ' +day; + //} + return date; +}; +Template.deviceDashboard.onRendered(function () { + var group_id = Router.current().params.group_id; + isOut.set(false); + isLoading.set(true); + var now = new Date(); + var swiper = new Swiper('#slideDate',{ + slidesPerView :'auto', + resistanceRatio : 0, + onInit: function(s){ + //Swiper初始化了 + }, + onClick: function(s){ + var index = s.clickedIndex; + var _curTime = dateList.get()[index]; + isLoading.set(true); + Meteor.subscribe('WorkStatusByGroup',_curTime.utc, group_id,{ + onReady:function(){ + isLoading.set(false); + } + }); + // alert(s.clickedIndex); + $('#'+curTime.get().id).removeClass('selected'); + curTime.set(_curTime); + $('#'+_curTime.id).addClass('selected'); + $('#'+_curTime.id).find(".week_whet").addClass('back_bottom'); + } + }); + swiper.slideTo(7,0,false); + curTime.set(dateList.get()[7]); + $('#'+curTime.get().id).addClass('selected'); + $('#'+curTime.get().id).find(".week_whet").addClass('back_bottom'); + var _today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + var _date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , + 0, 0, 0, 0); + date.set(_date); //UTC日期 + today.set(_today); + theDisplayDay.set(_today); // 当前显示日期 + theCurrentDay.set(_date); // UTC日期 + + Meteor.subscribe('device_by_groupId', group_id); + + Meteor.subscribe('get-group', group_id,{ + onReady: function(){ + var _group = SimpleChat.Groups.findOne({_id: group_id}); + if(_group && _group.offsetTimeZone){ + time_offset.set(_group.offsetTimeZone); + } + } + }); + Meteor.subscribe('WorkStatusByGroup',curTime.get().utc, group_id,{ + onReady:function(){ + isLoading.set(false); + var checkin_names = ckeckInNames.get(); + WorkStatus.find({group_id: group_id, date: curTime.get().utc}).forEach(function(item) { + if(!item.in_time && !item.out_time) { + checkin_names.push(item.person_name); + } + }); + + ckeckInNames.set(checkin_names); + } + }); + + Meteor.subscribe('group_person',group_id, limit.get(),{ + onReady: function(){ + Session.set('group_person_loaded',true); + }, + onStop: function(err){ + console.log(err); + } + }); + +}); + +Template.deviceDashboard.helpers({ + dateList:function(){ + return dateList.get(); + }, + showHint:function(){ + return Session.get('showHint'); + }, + has_day_before:function(group_id){ + var lastday = today.get() - 7 * 24 * 60 * 60 *1000; //7天前 + return theDisplayDay.get() > lastday; + }, + day_title:function(){ + var currentDay = new Date(theDisplayDay.get()); + return parseDate(currentDay); + }, + has_day_after:function(group_id){ + // 可以查看后面两天天数据 + var _today = new Date(today.get()); + _today.setDate(_today.getDate() + 2); + _today = new Date(_today.getFullYear(), _today.getMonth(), _today.getDate()).getTime(); + return theDisplayDay.get() < _today; + }, + isLoading: function() { + return isLoading.get(); + }, + checkInLists: function() { + // var now = new Date(); + // var _today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + // var _date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , + // 0, 0, 0, 0); + + var group_id = Router.current().params.group_id; + + // date.set(_date); //UTC日期 + // today.set(_today); + + var frList =[]; + //查询今日出现的人 并且存在frList数组中 + WorkStatus.find({group_id: group_id, date:curTime.get().utc}).forEach(function(item){ + if (item.in_time || item.out_time) { + frList.push(item); + } + }) + //查找重复出现的人并且删除此人 + WorkStatus.find({group_id: group_id, date:curTime.get().utc}).forEach( function (item,index) { + if (item.in_time || item.out_time) { + for (var i = index + 1; i < frList.length; i++) { + if(item.person_name && item.person_name == frList[i].person_name){ + //删除重复出现的人 + frList.splice(i,1) + } + } + } + }); + return frList; + }, + zeroLists: function() { + if (isOut.get()) { + lists = Template.deviceDashboard.__helpers.get('unCkeckLists').call(); + } + else { + lists = Template.deviceDashboard.__helpers.get('checkInLists').call(); + } + return lists.length == 0; + }, + unCkeckLists: function() { + // var now = new Date(); + // var _today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + // var _date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , + // 0, 0, 0, 0); + + var group_id = Router.current().params.group_id; + + var lists = []; + WorkStatus.find({group_id: group_id, date:curTime.get().utc}).forEach( function (item) { + if (item.in_time || item.out_time) { + lists.push(item.person_name); + } + }); + + return Person.find({group_id: group_id, name: {$nin: lists}},{limit: limit.get(), sort:{createAt: -1}}).fetch(); + }, + groupName: function(){ + return Session.get('deviceDashboardTitle'); + }, + isOut: function(){ + return isOut.get(); + }, + getCheckInImage: function() { + if(this.in_time && this.in_image) { + return this.in_image; + } + if(this.out_time && this.out_image) { + return this.out_image; + } + }, + getTime: function(){ + var ts = null; + if(this.in_time) { + ts = this.in_time; + } + else if(this.out_time) { + ts = this.out_time; + } + + if(!ts || ts == null || ts == 0){ + return '-/-'; + } + + // var time = new Date(ts); + // return time.shortTime(time_offset.get()); + return moment(ts).utcOffset(time_offset.get()).format('ahh:mm'); + } +}); + +Template.deviceDashboard.events({ + 'click #goHint':function(e,t){ + Session.set('notice-from','deviceDashboard'); + Session.set('showHint',true); + }, + 'click .leftButton': function(){ + return PUB.back(); + }, + // 'click .rightButton': function(){ + // if(isOut.get()){ + // isOut.set(false); + // } else { + // isOut.set(true); + // } + // }, + 'click .SignNo': function(){ + isOut.set(true); + $(".SignNo").css({"color":"#39A8FE","background":"#ffffff"}) + $(".SignYes").css({"color":"#ffffff","background":"#148CE9"}) + }, + 'click .SignYes':function(){ + isOut.set(false); + $(".SignNo").css({"color":"#ffffff","background":"#148CE9"}) + $(".SignYes").css({"color":"#39A8FE","background":"#ffffff"}) + }, + 'click .popItem': function(e) { + popObj.set(this); + $('.deviceDashPoppage').fadeIn(); + }, + // goNextDay + 'click .nextDay': function(e) { + e.stopImmediatePropagation(); + var currentDay = theCurrentDay.get() + 24 * 60 * 60 * 1000; + theCurrentDay.set(currentDay); + + var displayDay = theDisplayDay.get() + 24 * 60 * 60 * 1000; + theDisplayDay.set(displayDay); + var group_id = Router.current().params.group_id; + isLoading.set(true); + Meteor.subscribe('WorkStatusByGroup',theCurrentDay.get(), group_id,{ + onReady:function(){ + isLoading.set(false); + } + }); + }, + // goPrevDay + 'click .prevDay': function(e) { + e.stopImmediatePropagation(); + var currentDay = theCurrentDay.get() - 24 * 60 * 60 * 1000; + theCurrentDay.set(currentDay); + + var displayDay = theDisplayDay.get() - 24 * 60 * 60 * 1000; + theDisplayDay.set(displayDay); + var group_id = Router.current().params.group_id; + isLoading.set(true); + Meteor.subscribe('WorkStatusByGroup',theCurrentDay.get(), group_id,{ + onReady:function(){ + isLoading.set(false); + } + }); + }, +}); + +Template.deviceDashPoppage.helpers({ + showFollowButton: function (){ + return withFollowFeature; + }, + isFollowing: function(){ + if(Meteor.userId() && popObj && popObj.get() && popObj.get().person_id[0].id ){ + var followDoc = NotificationFollowList.findOne({_id: Meteor.userId()}) + return followDoc && followDoc.hasOwnProperty(popObj.get().person_id[0].id) + } else { + return false; + } + }, + data: function() { + var obj = popObj.get(); + var url = obj.in_image?obj.in_image:obj.out_image; + + var diff = 0; + var out_time = obj.out_time; + if (!obj.out_time) { + var now_time = Date.now(); + out_time = now_time; + } + + if (obj.in_time && out_time){ + diff = out_time - obj.in_time; + } + + if(diff > 24*60*60*1000){ + diff = 24*60*60*1000; + } else if(diff < 0) { + diff = 0; + } + + var min = diff / 1000 / 60 ; + var in_company_tlen = Math.floor(min/60)+' h '+Math.floor(min%60) + ' min'; + if(min < 60){ + in_company_tlen = Math.floor(min%60) + ' min'; + } + if(diff == 0){ + in_company_tlen = '0 min'; + } + var person_id = obj.person_id[0].id; + + return { + _id: obj._id, + url: url, + person_id: person_id, + name: obj.person_name, + in_company_tlen: in_company_tlen, + // in_time: obj.in_time? (new Date(obj.in_time)).shortTime(time_offset.get()): null, + // out_time: obj.out_time? (new Date(obj.out_time)).shortTime(time_offset.get()): null + in_time: obj.in_time? (moment(obj.in_time).utcOffset(time_offset.get()).format('ahh:mm')): null, + out_time: obj.out_time? (moment(obj.out_time).utcOffset(time_offset.get()).format('ahh:mm')): null + } + } +}); + +Template.deviceDashPoppage.events({ + 'click .follow': function(e){ + //var id = popObj.get()._id; + //NotificationFollowList.update({user_id:Meteor.userId()},{ $set: {id: 1} },{upsert:true,multi,false}) + var isFollowing = e.target.dataset.following + var userId = e.target.dataset.userid + + if(userId && Meteor.userId()){ + var hasFollowingList = NotificationFollowList.findOne({_id:Meteor.userId()}) + if(hasFollowingList){ + var field = {} + field[userId] = 1 + if(isFollowing){ + NotificationFollowList.update({_id:Meteor.userId()},{ $unset: field }) + } else { + NotificationFollowList.update({_id:Meteor.userId()},{ $set: field }) + } + } else { + if(isFollowing){ + } else { + var doc = { + _id:Meteor.userId() + } + doc[userId] = 1 + NotificationFollowList.insert(doc) + } + } + } + e.stopImmediatePropagation(); + }, + 'click .deviceDashPoppage, click #closeDDPop': function (e) { + return $('.deviceDashPoppage').fadeOut(); + }, + 'click .resetWorkStatus': function (e) { + e.stopImmediatePropagation(); + // PUB.confirm('是否要移除该成员当前出现信息?请确认!', function() { + // var obj = popObj.get(); + // var personId = obj.person_id[0].id; + + // Meteor.call('resetMemberWorkStatus',e.currentTarget.id, personId, function(error, result) { + // if (error) { + // return PUB.toast('请重试~'); + // } + // $('.deviceDashPoppage').fadeOut(); + // return PUB.toast('已移除'); + // }); + // }); + var obj = popObj.get(); + var personId = obj.person_id[0].id; + var group_id = obj.group_id; + var url = obj.in_image?obj.in_image:obj.out_image; + var uuid = obj.in_uuid?obj.in_uuid:obj.out_uuid; + //直接转到标识人页面 + // Session.set('channel','device/dashboard/'+group_id); + // Router.page(''); + //2.删除出现记录 + Meteor.call('resetMemberWorkStatus', e.currentTarget.id, personId, function (error, result) { + if (error) { + return PUB.toast('请重试~'); + } + $('.deviceDashPoppage').fadeOut(); + + // return PUB.toast('已移除'); + SimpleChat.show_label(group_id, url, function (name) { + //1.将照片添加到name的训练集 + if (!name) { + return; + } + // PUB.showWaitLoading('处理中'); + var setNames = []; + Meteor.call('get-id-by-name1', uuid, name, group_id, function (err, res) { + if (err || !res) { + return PUB.toast('标注失败,请重试~'); + } + var faceId = null; + if (res && res.faceId) { + faceId = res.faceId; + } else { + faceId = new Mongo.ObjectID()._str; + } + // 发送消息给平板 + var trainsetObj = { + group_id: group_id, + type: 'trainset', + url: url, + person_id: personId, + device_id: uuid, + face_id: faceId, + drop: false, + img_type: 'face', + // style:item.style, + // sqlid:item.sqlid + }; + sendMqttMessage('/device/' + group_id, trainsetObj); + + setNames.push({ + uuid: uuid, + id: faceId, //item.person_id, + url: url, + name: name, + }); + + if (setNames.length > 0) { + Meteor.call('add-label-dataset', group_id, setNames); + } + // PUB.hideWaitLoading(); + }) + }) + }); + }, + 'click .changeWorkStatus': function (e) { + e.stopImmediatePropagation(); + var obj = popObj.get(); + var group_id = obj.group_id; + var personId = obj.person_id[0].id; + var url; + var uuid; + var ts = obj.in_time; + var checkin_time; + var checkout_time; + if(obj.in_image&&obj.in_time){ + url = obj.in_image; + checkin_time = obj.in_time; + uuid = Devices.findOne({groupId:group_id,in_out:{$in: ['in', 'inout']}}).uuid; + }else{ + url = obj.out_image; + checkout_time = obj.out_time; + uuid = Devices.findOne({groupId:group_id,in_out:{$in: ['out', 'inout']}}).uuid; + } + Meteor.call('get-guest-name',group_id,function(err,guest){ + Session.set('default-label-name',guest); + changeLogic(); + }) + var changeLogic = function () { + //修改:1.删除出现记录 2.加入训练集 + //屏蔽返回按钮 + // Session.set('no-back', true); + SimpleChat.show_label(group_id, url, function (name) { + Session.set('default-label-name', ''); + if (!name || name == '') { + return; + } + // PUB.confirm('修改当前出现信息,并将它加入到' + name + '的训练集?', function () { + Meteor.call('resetMemberWorkStatus', e.currentTarget.id, personId, function (error, result) { + if (error) { + return PUB.toast('请重试~'); + } + $('.deviceDashPoppage').fadeOut(); + var setNames = []; + Meteor.call('get-id-by-name1', uuid, name, group_id, function (err, res) { + if (err || !res) { + return PUB.toast('标注失败,请重试~'); + } + var faceId = null; + if (res && res.faceId) { + faceId = res.faceId; + } else { + faceId = new Mongo.ObjectID()._str; + } + // 发送消息给平板 + var trainsetObj = { + group_id: group_id, + type: 'trainset', + url: url, + person_id: faceId, + device_id: uuid, + face_id: faceId, + drop: false, + img_type: 'face', + // style:item.style, + // sqlid:item.sqlid + }; + sendMqttMessage('/device/' + group_id, trainsetObj); + + setNames.push({ + uuid: uuid, + id: faceId, //item.person_id, + url: url, + name: name, + }); + + if (setNames.length > 0) { + Meteor.call('set-person-names', group_id, setNames); + } + var person_info = { + 'uuid': uuid, + 'name': name, + 'group_id': group_id, + 'img_url': url, + 'type': 'face', + 'ts': ts, + // 'accuracy': item.accuracy, + // 'fuzziness': item.fuzziness, + // 'sqlid':item.sqlid, + // 'style':item.style + }; + var data = { + face_id: faceId, + checkin_time: checkin_time, + checkout_time: checkout_time, + person_info: person_info, + formLabel: true + }; + + Meteor.call('ai-checkin-out', data, function (err, res) { }); + //重新训练 + retrain(group_id); + }); + }) + }); + } + + } +}); diff --git a/client/deviceDashboard/deviceDashboard.less b/client/deviceDashboard/deviceDashboard.less new file mode 100644 index 000000000..d62f6c45c --- /dev/null +++ b/client/deviceDashboard/deviceDashboard.less @@ -0,0 +1,238 @@ +.deviceDashboard{ + .lists{ + margin: 0; + padding: 16px; + // margin-top: 40px; + color: #555; + .tip { + text-align: center; + font-size: 12px; + } + .listsItem{ + list-style: none; + float: left; + width: 33.3%; + text-align: center; + margin: 16px 0; + transition: all .75s ease-in-out; + img{ + width: 64px; + height: 64px; + border-radius: 50%; + margin-bottom: 5px; + } + p{ + font-size: 12px; + white-space: nowrap; + height: 20px; + line-height: 20px; + margin: 0; + padding: 0px; + text-align: center; + } + span.time { + font-size: 10px; + background: #ececec; + border-radius: 8px; + padding: 2px 6px; + } + } + } + .date-head{ + position: relative; + overflow: hidden; + padding: 10px 10px 0px; + color: rgb(116, 173, 199); + margin-top:40px; + text-align: center; + // text-shadow: 1px 1px #333; + .imgHelper{ + float: left; + width: 80px; + margin-right: 16px; + img{ width: 100%; height: 100%; object-fit: contain;} + } + .time{ margin: 10px 0;padding: 0;font-size: 18px;} + .navigation{ + position: absolute; + left: 0; + right: 0; + top: 50%; + margin: 0; + overflow: hidden; + width: 100%; + height: 40px; + margin-top: -20px; + line-height: 40px; + li{ + list-style: none; + padding: 0 10px; + position: absolute; + cursor: pointer; + width: 64px; + height: 40px; + border-radius: 50%; + font-size: 30px; + text-shadow: 1px 1px #656565; + } + li.prevDay{left: 0; text-align: left;} + li.nextDay{right: 0; text-align: right;} + } + } + .loading{ + text-align: center; + img{width: 50px; + height: 50px;} + } +} + +.deviceDashPoppage { + background: rgba(0, 0, 0, 0.14); + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999; + display: none; +} + +.dd-dialog { + background: #fff; + width: 300px; + min-height: 200px; + top: 50%; + position: absolute; + left: 50%; + margin-top: -200px; + margin-left: -150px; + border-radius: 8px; + overflow: hidden; +} +.dd-content, .dd-header{ + padding:0 16px; +} +.dd-header { + background: #455a64; + height: 80px; + position: relative; + .name { + margin: 0; + margin-top: 52px; + display: inline-block; + font-size: 20px; + margin-left: 15px; + } + .follow { + position: absolute; + margin: 0; + right: 15px; + margin-top: 52px; + display: inline-block; + font-size: 20px; + margin-right: 15px; + } + #closeDDPop { + position: absolute; + right: 0; + top: 0; + display: inline-block; + width: 40px; + height: 40px; + text-align: right; + padding: 10px; + } +} +.dd-header .icon { + width: 64px; + height: 64px; + margin: 0 auto; + border: 4px solid #ccc; + border-radius: 50%; + overflow: hidden; + float: left; + margin-top: 32px; + margin-left: 8px; +} + +.dd-header .icon img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.dd-content{ + color: #555; + padding-bottom: 16px; + .inCompanyTime { + font-size: 20px; + text-align: center; + color: #455a64; + margin: 20px 0; + } +} + +.dd-foot { + font-size: 0; overflow: hidden;border-top: 1px solid #efefef; + button { + color: #555; + border: none; + background: none; + width: 100%; + height: 48px; + text-align: center; + font-size: 14px; + } +} +#slideDate >ul{ + padding: 0px; + margin: 0px; + border-radius: 10px; + .selected{ + margin-left: -1px; + z-index: 1; + color: #39A8FE; + } + .back_bottom{ + border-bottom: 1px solid #39A8FE; + } +} +#slideDate >ul>li{ + color: #333333; + width: 100px; + height: 60px; + font-size: 14px !important; + position: relative; + border-bottom: 1px solid #CCCCCC; +} + +#slideDate >ul>li:not(.selected):not(:last-child):after{ + content: ""; + position: absolute; + bottom:15px; + width: 1px; + height: 30px; + background: #F0FFFF; + right: 0px; +} + + +.Sign-fa{ + display: inline-block; + width: 5rem; + height: 25px; + text-align: center; + line-height: 25px; + background-color: #148CE9; + color: #ffffff; + border-radius: 2px; +} +.SignNo{ + margin-right: -3px; + display: inline-block +} +.SignYes{ + margin-left: 0px; + background-color: #ffffff; + color: #39A8FE; +} \ No newline at end of file diff --git a/client/disable_console_log.js b/client/disable_console_log.js index 49015131b..4a84c6337 100644 --- a/client/disable_console_log.js +++ b/client/disable_console_log.js @@ -1,6 +1,7 @@ if ( Meteor.isClient ){ var console = {}; console.log = function(){}; + console.info = function(){}; window.console = console; } diff --git a/client/disable_copy_past.css b/client/disable_copy_past.css index 03e5b63bc..0a38e2deb 100644 --- a/client/disable_copy_past.css +++ b/client/disable_copy_past.css @@ -1,9 +1,9 @@ * { - -webkit-user-select: auto; - -khtml-user-select: auto; - -moz-user-select: auto; - -ms-user-select: auto; - user-select: auto; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .edit textarea { @@ -22,20 +22,17 @@ input { user-select: auto ; } textarea { - -webkit-user-select: auto ; - -khtml-user-select: auto ; - -moz-user-select: auto ; - -ms-user-select: auto ; - user-select: auto ; -} -select { -webkit-user-select: none ; -khtml-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; user-select: none ; } -img { +.img-with-hold { + -webkit-user-select:none; + -webkit-touch-callout:none; +} +select { -webkit-user-select: none ; -khtml-user-select: none ; -moz-user-select: none ; diff --git a/client/disable_hot_code_push.js b/client/disable_hot_code_push.js new file mode 100644 index 000000000..3e071d2d4 --- /dev/null +++ b/client/disable_hot_code_push.js @@ -0,0 +1,14 @@ +if(Meteor.isCordova){ + if(Package.reload){ + Package.reload = null; + } + if ( Package.autoupdate.Autoupdate.clearAutoupdateCache ) { + Package.autoupdate.Autoupdate.clearAutoupdateCache = function(a){return true;}; + } + if ( Package.autoupdate.Autoupdate._retrySubscription ) { + Package.autoupdate.Autoupdate._retrySubscription = function(){return true;}; + } + if(Package.autoupdate.Autoupdate.newClientAvailable){ + Package.autoupdate.Autoupdate.newClientAvailable = function(){return false;}; + } +} \ No newline at end of file diff --git a/client/explore/changeSeriesImage.html b/client/explore/changeSeriesImage.html new file mode 100644 index 000000000..70f85dec6 --- /dev/null +++ b/client/explore/changeSeriesImage.html @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/client/explore/changeSeriesImage.js b/client/explore/changeSeriesImage.js new file mode 100644 index 000000000..dc431c40d --- /dev/null +++ b/client/explore/changeSeriesImage.js @@ -0,0 +1,50 @@ +if (Meteor.isClient){ + Template.changeSeriesImage.onRendered(function(){ + $('.mainImagesList').css('min-height',$(window).height()); + }); + + Template.changeSeriesImage.events({ + 'click .mainImagesListback' :function(e){ + $('body').removeAttr('style'); + $('.mainImagesList').hide(); + $('#seriesTitle').show(); + }, + 'click .mainImagesListImport' :function(e){ + var mainImgUrl = ''; + $('input[class="mainImageListInput"]').each(function(){ + if($(this).prop('checked') === true){ + mainImgUrl = $(this).attr('value'); + } + }); + if (mainImgUrl !== ''){ + var seriesContent = Session.get('seriesContent'); + seriesContent.mainImage = mainImgUrl + Session.set('seriesContent',seriesContent); + $('.mainImagesList').hide(); + $('#seriesTitle').show(); + } else { + PUB.toast('请选择图片!'); + } + }, + 'click .mainImageListInput':function(e){ + $('.mainImageListInput').prop('checked',false); + Meteor.setTimeout(function(){ + $(e.currentTarget).prop('checked',true); + },50); + } + }); + + Template.changeSeriesImage.helpers({ + mainImage:function(){ + if(Session.get('seriesContent')){ + return Session.get('seriesContent').mainImage; + } else { + return false; + } + }, + officialImages:function(){ + var arr = [{num:1},{num:2},{num:3},{num:4},{num:5},{num:6},{num:7},{num:8},{num:9},{num:10},{num:11},{num:12},{num:13},{num:14},{num:15},{num:16},{num:17},{num:18},{num:19},{num:20},{num:21},{num:22},{num:23},{num:24},{num:25},{num:26},{num:27},{num:28},{num:29},{num:30},{num:31},{num:32},{num:33},{num:34},{num:35},{num:36},{num:37}]; + return arr; + } + }); +} \ No newline at end of file diff --git a/client/explore/explore.coffee b/client/explore/explore.coffee new file mode 100644 index 000000000..5071b8bd6 --- /dev/null +++ b/client/explore/explore.coffee @@ -0,0 +1,99 @@ +#space 2 +if Meteor.isClient + Template.explore.rendered=-> + #$('.content').css 'min-height',$(window).height() + + Template.explore.events + 'click .top-series-btn': (event)-> + Router.go '/seriesList' + 'click #follow': (event)-> + Router.go '/searchFollow' + 'click #search': (event)-> + Router.go '/searchPeopleAndTopic' + 'click .clickHelp':(event)-> + PUB.page '/help' + 'click .closebtn':()-> + $('.app-rate').fadeOut() + promptForRatingWindowButtonClickHandler(2) + 'click .btn-rate1':()-> + $('.app-rate').fadeOut() + promptForRatingWindowButtonClickHandler(3) + 'click .btn-rate2':()-> + $('.app-rate').fadeOut() + promptForRatingWindowButtonClickHandler(3) + 'click #album-select':(e)-> + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + prepareToEditorMode() + PUB.page '/add' + Meteor.defer ()-> + selectMediaFromAblum(20, (cancel, result,currentCount,totalCount)-> + if cancel + #$('#level2-popup-menu').modal('hide'); + PUB.back() + return + if result + console.log 'Local is ' + result.smallImage + Drafts.insert {type:'image', isImage:true, owner: Meteor.userId(), imgUrl:result.smallImage, filename:result.filename, URI:result.URI, layout:''} + if currentCount >= totalCount + Meteor.setTimeout(()-> + Template.addPost.__helpers.get('saveDraft')() + ,100) + ) + 'click #web-import':(e)-> + #if we choose to use server import + if withServerImport is true + Session.set('display_select_import_way',true) + #if we disable server import and just want to use mobile side import + else + Session.set('display_select_import_way',false) + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + prepareToEditorMode() + PUB.page '/add' + cordova.plugins.clipboard.paste (link)-> + regexToken = /\b(((http|https?)+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig + matchArray = regexToken.exec( link ) + if matchArray isnt null + importLink = matchArray[0] + if matchArray[0].indexOf('http') is -1 + importLink = "http://"+matchArray[0] + Meteor.setTimeout(()-> + handleDirectLinkImport(importLink,1) + ,100) + else + handleAddedLink(null) + window.plugins.toast.showLongCenter("粘贴板内容并非有效连接,请手动粘贴\n浏览器内容加载后,点击地址栏右侧\"导入\"按钮"); + ,()-> + handleAddedLink(null) + window.plugins.toast.showLongCenter("无法获得粘贴板数据,请手动粘贴\n浏览器内容加载后,点击地址栏右侧\"导入\"按钮"); + 'click #share-import':(e)-> + window.plugins.shareExtension.getShareData ((data) -> + if data + editFromShare(data) + window.plugins.shareExtension.emptyData ((count)-> + if count == 0 + return Session.set('wait_import_count',false) + Session.set('wait_import_count',true) + ),-> + console.log 'deleteShareData was failed!' + ), -> + Session.set('wait_import_count',false) + console.log 'getShareData was Error!' + 'click #photo-select':(e)-> + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + prepareToEditorMode() + PUB.page '/add' + Meteor.defer ()-> + if window.takePhoto + window.takePhoto (result)-> + console.log 'result from camera is ' + JSON.stringify(result) + if result + Drafts.insert {type:'image', isImage:true, owner: Meteor.userId(), imgUrl:result.smallImage, filename:result.filename, URI:result.URI, layout:''} + Meteor.setTimeout(()-> + Template.addPost.__helpers.get('saveDraft')() + ,100) + else + PUB.back() + diff --git a/client/explore/explore.html b/client/explore/explore.html new file mode 100644 index 000000000..fbb1094de --- /dev/null +++ b/client/explore/explore.html @@ -0,0 +1,40 @@ + diff --git a/client/explore/explore.less b/client/explore/explore.less new file mode 100644 index 000000000..186683bc3 --- /dev/null +++ b/client/explore/explore.less @@ -0,0 +1,105 @@ +body{background-color: #111;min-height: 100%;height: auto;color: #fff} +.explore{min-height: 100%;height: auto;} +.explore .content{padding-top: 40px;padding-bottom:50px;background-color: #eeeeee !important} +.explore #wrapper{ + background-color: white; +} +.explore .head .dropdown { + cursor: pointer; + #add-new-post{ + top: -40px; + } + .dropdown-menu { + padding: 0; + right: 5px; + left: auto; + border-radius: 5px; + li { + background-color: transparent !important; + text-align: center; + padding: 12px; + } + a { + font-size: 15px; + } + } +} + +body.modal-open {overflow: auto;} + +.app-rate{ + position: fixed; + display: none; + top: 20%; + z-index: 11111; + text-align: center; + left: 8%; + right: 8%; + height: 50%; + .rate-content{ + position: relative; + text-align: center; + border-radius: 5px; + height: 100%; + width: 100%; + background: #fff; + color: #77a9d8; + .head-icon{ + height: 40%; + background-position: center; + margin: 0; + top: -12%; + position: relative; + background-image: url(/img/rateicon.png); + background-repeat: no-repeat; + background-size: contain; + } + .closebtn{ + height: 20px; + background-image: url(/img/closebtn.png); + width: 20px; + background-repeat: no-repeat; + background-size: cover; + position: absolute; + top: 10px; + right: 10px; + } + .title{ + position: relative; + text-align: center; + font-weight: bold; + width: 100%; + color: #77a9d8; + margin: 0; + top: -10%; + } + .text{ + position: relative; + padding-top: 5px; + top: -10%; + font-weight: bold; + text-align: center; + width: 100%; + color: #8d8d8d; + } + .btn-rate1{ + width: 80%; + margin: auto; + font-weight: normal !important; + border: 1px solid #8d8d8d; + border-radius: 5px; + padding: 5px; + } + .btn-rate2{ + width: 80%; + margin: 10px auto; + font-weight: normal !important; + border: 1px solid #77a9d8; + border-radius: 5px; + padding: 5px 10px 5px 25px; + background: #77a9d8; + color: #fff; + text-align: center; + } + } +} \ No newline at end of file diff --git a/client/explore/mySeries.coffee b/client/explore/mySeries.coffee new file mode 100644 index 000000000..0b0f9c29a --- /dev/null +++ b/client/explore/mySeries.coffee @@ -0,0 +1,37 @@ +Template.mySeries.rendered=-> + $('.content').css 'min-height',$(window).height() + $(window).scroll (event)-> + target = $("#showMoreResults"); + SERIES_ITEMS_INCREMENT = 10; + if (!target.length) + return; + threshold = $(window).scrollTop() + $(window).height() - target.height(); + if target.offset().top < threshold + if (!target.data("visible")) + target.data("visible", true); + Session.set("seriesitemsLimit", Session.get("seriesitemsLimit") + SERIES_ITEMS_INCREMENT); + else + if (target.data("visible")) + target.data("visible", false); +Template.mySeries.helpers + mySeries:()-> + mySeries = Series.find({owner:Meteor.userId()}, {sort: {createdAt: -1}}) + Session.setPersistent('persistentMySeries', mySeries.fetch()) + return mySeries + moreResults:-> + !(Series.find().count() < Session.get("seriesitemsLimit")) + loading:-> + Session.equals('seriesCollection','loading') + loadError:-> + Session.equals('seriesCollection','error') + showSeriesHint:-> + return !localStorage.getItem('seriesHint'); +Template.mySeries.events + 'click .back': (event)-> + Router.go '/seriesList' + 'click #follow': (event)-> + Router.go '/searchFollow' + 'click .seriesImages ul li':(e)-> + seriesId = e.currentTarget.id + Session.set('isSeriesEdit',false) + Router.go '/series/' + seriesId diff --git a/client/explore/mySeries.html b/client/explore/mySeries.html new file mode 100644 index 000000000..fb6e47fbf --- /dev/null +++ b/client/explore/mySeries.html @@ -0,0 +1,42 @@ + \ No newline at end of file diff --git a/client/explore/selectAuthorPosts.js b/client/explore/selectAuthorPosts.js new file mode 100644 index 000000000..c417e9410 --- /dev/null +++ b/client/explore/selectAuthorPosts.js @@ -0,0 +1,62 @@ +Template.selectAuthorPosts.onRendered(function(){ + SERIES_AUTHOR_POST_LIMIT = 15; + Session.get('authorPublishPostForSeries','loaded'); + Session.set('seriesAuthorPostsLimit',SERIES_AUTHOR_POST_LIMIT); + Session.set('seriesAuthorPostsCount',0); + $('.author-self-posts-container').scroll(function(event){ + var container = $('.author-self-posts-container'); + var target = $('.author-self-posts-container ul'); + var containerMargin = 40; + var threshold = target.height() - container.scrollTop() - container.height() - containerMargin; + if(threshold < 0){ + if(Session.get('authorPublishPostForSeries') === 'loaded'){ + Session.set('authorPublishPostForSeries','loading'); + Session.set('seriesAuthorPostsLimit',Session.get('seriesAuthorPostsLimit') + SERIES_AUTHOR_POST_LIMIT); + console.log('need pull more') + } + } + }); + +}); +Template.selectAuthorPosts.helpers({ + userPostsLists: function(){ + var ids = Session.get('selectedPostIds'); + console.table(ids) + if (!ids) + ids = []; + if(ids.length > 0){ + var posts = Posts.find({owner:Meteor.userId(),_id:{"$nin":ids},publish:{"$ne":false}}, {sort: {createdAt: -1}}, {limit:Session.get('seriesAuthorPostsLimit')}); + } else { + var posts = Posts.find({owner:Meteor.userId(),publish:{"$ne":false}}, {sort: {createdAt: -1}}, {limit:Session.get('seriesAuthorPostsLimit')}); + } + return posts; + }, + showLoadingElem: function(){ + return Session.equals('authorPublishPostForSeries','loading'); + }, + allPostsLoaded: function() { + return Session.equals('authorPublishPostForSeries','loadedall'); + } +}); + +Template.selectAuthorPosts.events({ + 'click .cancel': function(){ + Session.set('seriesAuthorPostsCount',0); + $('.author-self-posts').hide(); + }, + 'click .done': function(){ + if( $('.author-post-item-select').length === 0){ + return PUB.toast('至少选中一个故事哦~') + } + var selectPosts = $('.author-post-item-select').clone(); + selectPosts.each(function(){ + $(this).removeClass('author-post-item author-post-item-select').addClass('series-post-item series-post-item-not-select series-select-item') + }) + $('#editSeriesList').append(selectPosts) + Session.set('seriesAuthorPostsCount',0); + return $('.author-self-posts').hide(); + }, + 'click .author-post-item': function(e,t){ + return $(e.currentTarget).toggleClass('author-post-item-select'); + }, +}) diff --git a/client/explore/seletAutoPosts.html b/client/explore/seletAutoPosts.html new file mode 100644 index 000000000..c5eab9c8f --- /dev/null +++ b/client/explore/seletAutoPosts.html @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/client/explore/series.html b/client/explore/series.html new file mode 100644 index 000000000..c7bea98d4 --- /dev/null +++ b/client/explore/series.html @@ -0,0 +1,99 @@ + diff --git a/client/explore/series.js b/client/explore/series.js new file mode 100644 index 000000000..32dedc960 --- /dev/null +++ b/client/explore/series.js @@ -0,0 +1,354 @@ +var updatePostsLatestSeries = function(postLists, seriesId, seriesTitle) { + if (!seriesId || !postLists) + return false; + var i = 0; + for (i = 0; i < postLists.length; i++) { + Posts.update({_id: postLists[i].postId}, {$set: {latestSeries: {seriesId: seriesId, seriesTitle: seriesTitle}}}); + } +}; + +var uploadSeriesImage = function(data) { + multiThreadUploadFileWhenPublishInCordova(data, null, function(err, result) { + var i, item, len; + if (!result) { + window.plugins.toast.showShortBottom('上传失败,请稍后重试'); + return; + } + if (result.length < 1) { + window.plugins.toast.showShortBottom('上传失败,请稍后重试'); + return; + } + for (i = 0, len = result.length; i < len; i++) { + item = result[i]; + if (item.uploaded) { + if (item.type === 'image' && item.imgUrl) { + var mainImage = item.imgUrl; + var postdata = Session.get('seriresuploadData') + var owner = Meteor.user(); + if(postdata.isNewSeries){ + Series.insert({ + title: postdata.title, + mainImage: mainImage, + owner: owner._id, + ownerName: owner.profile.fullname ? owner.profile.fullname: owner.username, + ownerIcon: owner.profile.icon, + createdAt: new Date(), + postLists: postdata.postLists, + publish: true + },function(err,_id){ + if(err){ + console.log('insert series ERR=',err) + } else { + console.log('insert series successed ,ID=',_id); + updatePostsLatestSeries(postdata.postLists, _id, postdata.title); + } + }); + } else { + console.log('update series') + Series.update({ + _id: Session.get('seriesId') + },{ + $set:{ + title: postdata.title, + mainImage: mainImage, + postLists: postdata.postLists, + updateAt: new Date(), + publish: true + } + },function(err,num){ + if(err){ + console.log('update series ERR=',err) + } else { + console.log('update series successed ,num=',num); + updatePostsLatestSeries(postdata.postLists, Session.get('seriesId'), postdata.title); + } + }); + } + Session.set('seriesId',''); + Session.set('seriesContent',''); + Session.set('isSeriesEdit',false); + Router.go('/seriesList'); + } + } + } + if (err) { + window.plugins.toast.showShortBottom('上传失败,请稍后重试'); + return; + } + return removeImagesFromCache(data); + }); +} +var updateOrInsertSeries = function(isNewSeries,publish){ + if($('#seriesTitle').val() === ''){ + return PUB.toast('请输入标题'); + } + if($(".series-post-item").length === 0){ + return PUB.toast('请至少添加一个故事') + } + var title = $('#seriesTitle').val(); + var posts = [] + var mainImage = '' + var owner = Meteor.user(); + var ownerName = owner.profile.fullname ? owner.profile.fullname: owner.username; + var ownerIcon = owner.profile.icon + var num = 0; + $('.series-post-item').each(function(index){ + posts.push({ + postId:$(this).attr('id'), + postTitle: $(this).data('title'), + postAddonTitle: $(this).data('addontitle'), + postMainImage: $(this).data('image'), + postIndex: num, + postOwner: owner._id, + postOwnerName: ownerName, + postOwnerIcon: ownerIcon + }); + num++; + }); + Session.set('seriresuploadData', {postLists:posts,title:title,isNewSeries:isNewSeries}); + if($('.series-title').data('image')){ + mainImage = $('.series-title').data('image'); + if(isNewSeries){ + Series.insert({ + title: title, + mainImage: mainImage, + owner: owner._id, + ownerName: ownerName, + ownerIcon: ownerIcon, + createdAt: new Date(), + postLists: posts, + publish: publish + },function(err,_id){ + if(err){ + console.log('insert series ERR=',err) + } else { + console.log('insert series successed ,ID=',_id) + updatePostsLatestSeries(posts, _id, title); + } + }); + } else { + console.log('update series') + Series.update({ + _id: Session.get('seriesId') + },{ + $set:{ + title: title, + mainImage: mainImage, + postLists: posts, + updateAt: new Date(), + publish: true + } + },function(err,num){ + if(err){ + console.log('update series ERR=',err) + } else { + console.log('update series successed ,num=',num); + updatePostsLatestSeries(posts, Session.get('seriesId'), title); + } + }); + } + Session.set('seriesId',''); + Session.set('seriesContent',''); + Session.set('isSeriesEdit',false); + Router.go('/seriesList'); + }else{ + uploadSeriesImage(Session.get('seriesContent').imageData) + } +} +Template.series.helpers({ + canFollowSeries: function() { + seriesOwner = Session.get('seriesContent').owner; + if (seriesOwner == Meteor.userId()) { + return false; + } + seriesFollow = SeriesFollow.find({owner: Meteor.userId(), seriesId: Session.get('seriesId')}); + if (seriesFollow && seriesFollow.count() > 0) { + return false; + } + return true; + }, + postsLists: function(){ + if(Session.get('seriesContent')){ + return Session.get('seriesContent').postLists + } + }, + isSeriesEdit: function(){ + return Session.equals('isSeriesEdit',true); + }, + postCounts: function(){ + var seriesContent = Session.get('seriesContent') + return (seriesContent && seriesContent.postLists)?seriesContent.postLists.length : 0 + }, + seriesTitle: function(){ + if(Session.get('seriesContent') && Session.get('seriesContent').title){ + return Session.get('seriesContent').title; + } else { + return ""; + } + }, + getImagePath: function(path,uri,id){ + return getImagePath(path,uri,id); + }, + mainImage: function() { + if(Session.get('seriesContent')){ + if(Session.get('seriesContent').mainImage) + return Session.get('seriesContent').mainImage; + else if (Session.get('seriesContent').imageData) + return Session.get('seriesContent').imageData[0] + } else { + return 'http://data.tiegushi.com/ocmainimages/mainimage5.jpg'; + } + }, + showPublishBtn: function(){ + if(Session.get('seriesContent')){ + return !Session.get('seriesContent').publish && Template.series.__helpers.get('postCounts')() + } else { + return true; + } + }, + isSeriesOwner: function(){ + if(Session.get('seriesContent') && Session.get('seriesContent').owner){ + return Session.get('seriesContent').owner === Meteor.userId() + } else { + return false; + } + } +}); + +Template.series.events({ + 'click .series-follow': function(e){ + series = Session.get('seriesContent'); + SeriesFollow.insert({owner: Meteor.userId(), seriesId: series._id, title: series.title, mainImage: series.mainImage, createdAt: new Date()}); + }, + 'click .share-btn': function(e){ + console.log('will share'); + $('.shareSeries').show(); + }, + 'click .back': function(e,t){ + if(!Session.get('seriesIsSaved') && Session.get('isSeriesEdit')){ + if(Session.get('seriesId') && Session.get('seriesId') !== ''){ + navigator.notification.confirm('这个操作无法撤销', function(r){ + if(r !== 1){ + updateOrInsertSeries(false,true); + } else { + Router.go('/seriesList'); + } + },'您确定要放弃未保存的修改吗?', ['放弃修改','保存修改']); + } else { + navigator.notification.confirm('这个操作无法撤销', function(r){ + if(r == 1){ + Session.set('seriesContent',''); + Router.go('/seriesList'); + } + },'您确定要放弃未保存的修改吗?', ['放弃修改','继续编辑']); + } + }else{ + Router.go('/seriesList'); + } + }, + 'click #edit': function(e,t){ + return Session.set('isSeriesEdit',true); + }, + 'click #save':function(){ + Session.set('seriesIsSaved',true); + }, + 'click .editAndAddNew': function(e,t){ + Session.set('isSeriesEdit',true); + Session.set('seriesIsSaved',false); + $('.author-self-posts').toggle(); + }, + 'click #seriesTitle':function(e,t){ + e.preventDefault(); + e.stopPropagation(); + $(this).focus(); + Session.set('seriesIsSaved',false); + }, + 'click .series-title':function(){ + $('.mainImageTools').toggle(); + }, + 'click .imageToolBtn': function(e,t){ + $('.mainImageTools').hide(); + if(e.currentTarget.id === 'useOfficalImage'){ + $('#seriesTitle').hide(); + $('.mainImagesList').show(); + } else { + Meteor.defer(function() { + selectMediaFromAblum(1, function(cancel, result) { + var data; + if (cancel) { + // PUB.back(); + return; + } + if (result) { + TempDrafts.insert({type:'image',isSeriesImg:true, isImage:true, owner: Meteor.userId(), imgUrl:result.smallImage, filename:result.filename, URI:result.URI, layout:''}); + var data = TempDrafts.find({isSeriesImg:true}).fetch(); + var oldSeriesContent = Session.get('seriesContent') + oldSeriesContent.imageData = data + delete oldSeriesContent.mainImage + Session.set('seriesContent',oldSeriesContent); + TempDrafts.remove({}); + } + }); + }); + } + }, + 'click .series-select-item': function(e,t){ + $(e.currentTarget).toggleClass('series-post-item-not-select'); + return $(e.currentTarget).toggleClass('series-post-item-select'); + }, + 'click .addNewPost': function(){ + var postCounts = Posts.find({owner:Meteor.userId(),publish:{"$ne":false}}).count(); + if(postCounts === 0){ + return PUB.toast('你还没有发表过故事哦,先去发表一篇故事吧~') + } + var ids = []; + $('.series-post-item').each(function(){ + ids.push($(this).attr('id')) + }); + Session.set('selectedPostIds',ids); + Session.set('seriesIsSaved',false); + $('.author-self-posts').toggle(); + }, + 'click .has-dropdown, click .series-dropdown': function(){ + $('.series-dropdown').toggle(); + }, + 'click #removeSelected': function(e,t){ + if($(".series-post-item-select").length === 0){ + return PUB.toast('请至少选择一个要删除故事') + } + Session.set('seriesIsSaved',false); + $('.series-post-item-select').remove(); + var ids = []; + $('.series-post-item').each(function(){ + ids.push($(this).attr('id')) + }); + Session.set('selectedPostIds',ids); + }, + 'click .viewModal':function(e,t){ + Session.set('fromSeries', {status: true, id: Session.get('seriesId')}); + return Router.go('/posts/'+e.currentTarget.id); + }, + 'click #del':function(e,t){ + Series.remove({_id: Session.get('seriesId')}); + Router.go ('/seriesList'); + }, + 'click .publish':function(e,t){ + if(Session.get('seriesId') && Session.get('seriesId') !== ''){ + updateOrInsertSeries(false,true); + } else { + updateOrInsertSeries(true,true); + } + } +}); + +Template.series.onRendered(function(){ + $('.series').click(function(e){ + var target = $(e.target); + if(target.closest('.series-dropdown').length == 0){ + $('.series-dropdown').hide(); + } + if(target.closest('.mainImageTools').length == 0){ + $('.mainImageTools').hide(); + } + }); +}) diff --git a/client/explore/series.less b/client/explore/series.less new file mode 100644 index 000000000..8acf012f0 --- /dev/null +++ b/client/explore/series.less @@ -0,0 +1,324 @@ +.series ,.author-self-posts{ + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: hidden; + color: #000; +} +.series-head{ + position: absolute; + z-index: 90; + top: 0; + width: 100%; + height: 40px; + overflow: hidden; + background: #37a7fe; + box-shadow: 0 -1px 13px #000; +} +.series-btns{ + font-size: 0; + text-align: right; + color: #fff; +} +.series-btns div { + display: inline-block; + width: 56px; + height: 40px; + line-height: 40px; + text-align: center; + color: #fff; + font-size: 14px; +} +.series-btns .back{ float: left}; +.series-btns i{ + font-size: 22px; + display: block; + line-height: 40px; + color: #fff; +} + +.series-title{ + margin-top: 40px; + height: 300px; + overflow: hidden; + position: relative; + width: 100%; + h1,h2{word-wrap: break-word;text-shadow: 2px 2px 2px #000, 0px 0px 10px #000, 0px 0px 20px #000;} + h1,h2,textarea{ + position: absolute; + left: 10px; + right: 10px; + bottom: 10px; + color: #fff; + font-size: 20px; + line-height: 25px; + max-height: 100px; + overflow: hidden; + letter-spacing: 1px; + + } + textarea{ + border: 1px dotted #ccc; + outline: none; + border-radius: 4px; + // background: none; + width: 90%; + height: 80px; + left: 5%; + resize: none; + background: rgba(0,0,0,.26); + user-select: auto; + -webkit-user-select: auto; + -ms-user-select: auto; + -moz-user-select: auto; + -webkit-touch-callout: default; + z-index: 9; + } +} +.series-title:after{ + position: absolute; + // content: ""; + height: 50%; + left: 0; + right: 0; + bottom: 0; + background: liner-gradient(to top,rgba(0,0,0,.26),rgba(0,0,0,0)); +} +.series-container,.author-self-posts-container{ + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + background: #fefefe; +} +.series-posts-lists{ + margin: 10px 5px; + padding: 10px 0; +} +.series-posts-lists:after{ + content:"."; display:block; height:0; visibility:hidden; clear:both; zoom: 1; +} +.series-post-item,.author-post-item{ + position: relative; + list-style: none; + width: 100%; + float: left; + margin-bottom: 5px; + background: #fff; + h2,h4{ + margin: 0; padding: 0; letter-spacing: 1px; + } + h2{ + font-size: 14px; + font-weight: bold; + color: #000; + line-height: 20px; + height: 26px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + }h4{ + line-height: 22px; + font-size: 12px; + color: #777; + margin: 0; + padding: 0; + height: 44px; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp:2; + -webkit-box-orient: vertical; + } +} +.author-post-item,.series-select-item{ + padding-left: 34px; +} +.author-post-item:before,.series-select-item:before{ + position: absolute; + content:''; // \f1db + top: 32px; + left: 5px; + width: 24px; + height: 24px; + box-sizing: border-box; + border: none; + background: url(/series-select-n.png) no-repeat; + background-size: 100%; + background-position: center; + // z-index: 9; +} +.series-post-item-select:before,.author-post-item-select:before{ + position: absolute; + content:''; // \f1db + top: 32px; + left: 5px; + width: 24px; + height: 24px; + box-sizing: border-box; + border: none; + background: url(/series-select-p.png) no-repeat; + background-size: 100%; + background-position: center; + // z-index: 9; +} +.series-post-info{ + margin: 10px 5px; + color: #000; +} +.series-img-helper { + height: 80px; + margin: 5px; + float: left; + margin-right: 10px; + img{ + width: 110px; + height: 80px; + object-fit: cover; + } +} + +.series-dropdown{ + position: fixed; + display: none; + z-index: 99; + background: #fff; + top: 36px; + right: 0; + margin: 4px; + width: 120px; + padding: 0; + border-radius: 4px; + box-shadow: 3px 0 13px #ccc,0 3px 13px #ccc; +} +.series-dropdown li{ + list-style: none; + color: #000; + font-size: 14px; + line-height: 32px; + text-align: center; +} + +.series-foot{ + position: absolute; + height: 40px; + left: 0; + right: 0; + bottom: 0; + background: #fff; + font-size: 0; + color: #000; + box-shadow: 0 -2px 12px #ccc; +} +.series-foot div{ + height: 40px; + line-height: 40px; + text-align: center; + display: inline-block; + width: 50%; + font-size: 14px; +} + +.author-self-posts{ + z-index: 9990; + display: none; + background: #fff; +} +.author-self-posts-container,.change-series-image-container{ + top: 48px; +} + +.editAndAddNew{ + text-align: center; + width: 120px; + height: 48px; + line-height: 48px; + border-radius: 4px; + border: 1px solid #000; + margin: 0 auto; + margin-top: 100px; +} + +.mainImageTools{ + position: absolute; + top: 350px; + background: #2196F3; + margin: 0 auto; + left: 0; + right: 0; + width: 80px; + border-radius: 4px; + z-index: 9; + padding: 4px; + display: none; + color: #fff; + span{ + padding: 4px 10px; + } + +} +.mainImageTools:before{ + position: absolute; + top: -8px; + content: ""; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #2196F3; + left: 50%; + margin-left: -8px; +} +.loadingStatus{ + line-height: 40px; + text-align: center; +} + +// share +.shareSeries{ + position: absolute; + display:none; + z-index: 999; + background: rgba(0,0,0,.16); + left: 0; + right: 0; + top: 0; + bottom: 0; + .share-series-container{ + position: absolute; + bottom: 5px; + left: 5px; + right: 5px; + color: #333; + } + .share-series-btns{ + position: absolute; + bottom: 55px; + margin: 0; + padding: 10px; + background: #fff; + width: 100%; + border-radius: 8px; + font-size: 12px; + li{ + list-style: none; + float: left; + height: 86px; + width: 25%; + text-align: center; + } + img{ + display: block; + margin: 10px auto; + } + } + .cancelShareSeries{ + background: #fff; + line-height: 50px; + text-align: center; + border-radius: 8px; + } +} \ No newline at end of file diff --git a/client/explore/seriesFooter.html b/client/explore/seriesFooter.html new file mode 100644 index 000000000..e22a4ce43 --- /dev/null +++ b/client/explore/seriesFooter.html @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/client/explore/seriesHint/seriesHint.html b/client/explore/seriesHint/seriesHint.html new file mode 100644 index 000000000..3416c28d2 --- /dev/null +++ b/client/explore/seriesHint/seriesHint.html @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/client/explore/seriesHint/seriesHint.js b/client/explore/seriesHint/seriesHint.js new file mode 100644 index 000000000..a5bc84eea --- /dev/null +++ b/client/explore/seriesHint/seriesHint.js @@ -0,0 +1,3 @@ +Template.seriesHint.rendered = function () { + $('body').addClass('intros-html-body'); +}; diff --git a/client/explore/seriesList.coffee b/client/explore/seriesList.coffee new file mode 100644 index 000000000..6253d132e --- /dev/null +++ b/client/explore/seriesList.coffee @@ -0,0 +1,79 @@ +Template.seriesList.rendered=-> + $('.content').css 'min-height',$(window).height() + $(window).scroll (event)-> + target = $("#showMoreResults"); + SERIES_ITEMS_INCREMENT = 10; + if (!target.length) + return; + threshold = $(window).scrollTop() + $(window).height() - target.height(); + if target.offset().top < threshold + if (!target.data("visible")) + target.data("visible", true); + Session.set("seriesitemsLimit", Session.get("seriesitemsLimit") + SERIES_ITEMS_INCREMENT); + else + if (target.data("visible")) + target.data("visible", false); +Template.seriesList.helpers + noSeries:()-> + !(Series.find().count() > 0) + mySeries:()-> + # mySeries = Series.find({owner:Meteor.userId(),publish:{"$ne":false}}, {sort: {createdAt: -1}}) + mySeries = Series.find({owner:Meteor.userId()}, {sort: {createdAt: -1}}) + Session.setPersistent('persistentMySeries', mySeries.fetch()) + return mySeries + moreResults:-> + !(Series.find().count() < Session.get("seriesitemsLimit")) + loading:-> + Session.equals('seriesCollection','loading') + loadError:-> + Session.equals('seriesCollection','error') + showSeriesHint:-> + return !localStorage.getItem('seriesHint'); +Template.seriesList.events + 'click .top-home-btn': (event)-> + Router.go '/explore' + 'click #follow': (event)-> + Router.go '/searchFollow' + 'click .clickHelp':(event)-> + PUB.page '/help' + 'click .seriesImages ul li':(e)-> + seriesId = e.currentTarget.id + Session.set('isSeriesEdit',false) + Router.go '/series/' + seriesId +Template.seriesFooter.helpers + haveSeries:()-> + Series.find({owner:Meteor.userId()}).count() > 0 +Template.seriesFooter.events + 'click #user':(e)-> + PUB.page('/mySeries') + 'click #album-select':(e)-> + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + Session.set('isSeriesEdit',true) + PUB.page '/series' + Meteor.defer ()-> + selectMediaFromAblum 1, (cancel, result)-> + if cancel + PUB.back() + return + if result + TempDrafts.insert {type:'image',isSeriesImg:true, isImage:true, owner: Meteor.userId(), imgUrl:result.smallImage, filename:result.filename, URI:result.URI, layout:''} + data = TempDrafts.find({isSeriesImg:true}).fetch() + Session.set('seriesContent',{imageData:data, postLists: [],publish: false}) + TempDrafts.remove({}) + 'click #photo-select':(e)-> + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + Session.set('isSeriesEdit',true) + PUB.page '/series' + Meteor.defer ()-> + if window.takePhoto + window.takePhoto (result)-> + # console.log 'result from camera is ' + JSON.stringify(result) + if result + TempDrafts.insert {type:'image',isSeriesImg:true, isImage:true, owner: Meteor.userId(), imgUrl:result.smallImage, filename:result.filename, URI:result.URI, layout:''} + data = TempDrafts.find({isSeriesImg:true}).fetch() + Session.set('seriesContent',{imageData:data, postLists: [],publish: false}) + TempDrafts.remove({}) + else + PUB.back() \ No newline at end of file diff --git a/client/explore/seriesList.html b/client/explore/seriesList.html new file mode 100644 index 000000000..ee6b9ab4f --- /dev/null +++ b/client/explore/seriesList.html @@ -0,0 +1,63 @@ + diff --git a/client/explore/seriesList.less b/client/explore/seriesList.less new file mode 100644 index 000000000..d3614102d --- /dev/null +++ b/client/explore/seriesList.less @@ -0,0 +1,91 @@ +.seriesList #wrapper{ + padding-top: 50px; + position: relative; + .no-series-title{ + position: fixed; + top: 30%; + width: 100%; + text-align: center; + color: #9e9e9e; + img{ + display: block;margin: 0 auto; margin-bottom: 16px; + } + } + .fa-long-arrow-down{ + position: fixed; + bottom: 60px; + width: 100%; + text-align: center; + font-size: 2.5em; + color: #37a7fe; + -webkit-animation: twinkling 1s infinite 0.9s ease-in-out alternate; + animation: twinkling 1s infinite 0.9s ease-in-out alternate; + } + ul li{ + color: #fff; + line-height: 24px; + padding-right: 2%; + overflow: hidden; + text-shadow: 2px 2px 2px #000, 0px 0px 10px #000, 0px 0px 20px #000; + text-align: left; + margin-right: 2%; + margin-left: 2%; + position: relative; + height: 220px; + float: left; + width: 46%; + margin-bottom: 8px; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + img{ + z-index: -1; + min-height: 100%; + width: 100%; + position:absolute; + object-fit: cover; + } + .css-post-title{ + position: relative; + top: 40%; + left: 5px; + } + .title{ + text-overflow: ellipsis; + // white-space: nowrap; + overflow: hidden; + text-align: center; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + transform: translateY(-40%); + } + } +} +.seriesOwner{ + position: absolute; + left: 10px; + right: 10px; + bottom: 10px; + height: 26px; + text-align: center; + span{ + padding-left: 34px; + line-height: 26px; + font-size: 12px; + max-width: 100%; + display: inline-block; + letter-spacing: 1px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +@-webkit-keyframes twinkling{ + 0%{ opacity:0.3; } + 100%{ opacity:0.8; } +} +@keyframes twinkling{ + 0%{ opacity:0.3; } + 100%{ opacity:0.8; } +} \ No newline at end of file diff --git a/client/explore/shareSeries.html b/client/explore/shareSeries.html new file mode 100644 index 000000000..ca12982bd --- /dev/null +++ b/client/explore/shareSeries.html @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/client/explore/shareSeries.js b/client/explore/shareSeries.js new file mode 100644 index 000000000..c37ba0004 --- /dev/null +++ b/client/explore/shareSeries.js @@ -0,0 +1,202 @@ +// self = Session.get('seriesContent) +getSeriesSharingPath = function(data){ + var url = "http://" + server_domain_name +'/series/'+data._id; + return url; +}; + +shareSeriesToWXTimeLine = function(title,description,thumbData,url){ + // shareSeriesToWechat(title,description,thumbData,url,WeChat.Scene.timeline); + var param = { + "title": title, + "summary": description, + "image_url": thumbData, + "target_url": url + } + if (device.platform === 'Android') { + shareSeriesToWechat(title,description,thumbData,url,WeChat.Scene.timeline); + } else { + return WechatShare.share({ + scene: 2, + message: { + title: param.title, + description: param.summary, + thumbData: param.image_url, + url: param.target_url + } + }, function() { + // if (hotPosts.length > 0 || (Meteor.user().profile && Meteor.user().profile.web_follower_count && Meteor.user().profile.web_follower_count > 0)) { + // $('.shareReaderClub,.shareReaderClubBackground').show(); + // } + // window.PUB.toast('分享成功!'); + // var shareType = Session.get("shareToWechatType"); + // if(shareType[1] && shareType[1] == true){ + // Meteor.setTimeout (function(){ + // $('.shareTheReadingRoom,.shareAlertBackground').fadeIn(300) + // },3000); + // shareType[1] = false; + // Session.set("shareToWechatType",shareType); + // } + }, function() { + window.PUB.toast('分享失败!你安装微信了吗?'); + }); + } +}; +shareSeriesToWXSession = function(title,description,thumbData,url) { + // shareSeriesToWechat(title,description,thumbData,url,WeChat.Scene.session); + var param = { + "title": title, + "summary": description, + "image_url": thumbData, + "target_url": url + } + if (device.platform === 'Android') { + shareSeriesToWechat(title,description,thumbData,url,WeChat.Scene.session); + } else { + return WechatShare.share({ + scene: 1, + message: { + title: param.title, + description: param.summary, + thumbData: param.image_url, + url: param.target_url + } + }, function() { + // if (hotPosts.length > 0 || (Meteor.user().profile && Meteor.user().profile.web_follower_count && Meteor.user().profile.web_follower_count > 0)) { + // // $('.shareReaderClub,.shareReaderClubBackground').show(); + // Router.go('/hotPosts/' + Session.get('postContent')._id); + // } + // window.PUB.toast('分享成功!'); + // var shareType = Session.get("shareToWechatType"); + // if(shareType[1] && shareType[1] == true){ + // Meteor.setTimeout (function(){ + // $('.shareTheReadingRoom,.shareAlertBackground').fadeIn(300) + // },3000); + // shareType[1] = false; + // Session.set("shareToWechatType",shareType); + // } + }, function() { + window.PUB.toast('分享失败!你安装微信了吗?'); + }); + } +}; +shareSeriesToWechat = function(title,description,thumbData,url,type) { + WeChat.share({ + title: title, + description: description, + thumbData: thumbData, + url: url + }, type, function () { + /*var hotPosts = _.filter(Session.get('hottestPosts') || [], function(value) { + return true; + }); + if (hotPosts.length > 0){ + Router.go('/hotPosts/' + Session.get('postContent')._id); + }*/ + }, function (reason) { + // 分享失败 + if (reason === 'ERR_WECHAT_NOT_INSTALLED') { + PUB.toast(TAPi18n.__("wechatNotInstalled")); + } else { + PUB.toast(TAPi18n.__("failToShare")); + } + console.log(reason); + }); +}; + +shareSeriesToQQ = function (title,description,imageUrl,url){ + var args = {}; + args.url = url; + args.title = title; + args.description = description; + args.imageUrl = imageUrl; + args.appName = TAPi18n.__("gst"); + YCQQ.shareToQQ(function(){ + console.log("share success"); + // var shareType = Session.get("shareToWechatType"); + // if(shareType[1] && shareType[1] == true){ + // $('.shareTheReadingRoom,.shareAlertBackground').fadeIn(300) + // shareType[1] = false; + // Session.set("shareToWechatType",shareType); + // } + },function(reason){ + if (reason ==='QQ Client is not installed') { + PUB.toast(TAPi18n.__("qqNotInstalled")); + } else { + PUB.toast(TAPi18n.__("failToShare")); + } + },args); +}; +shareSeriesToQQZone = function (title,description,imageUrl,url){ + var args = {}; + args.url = url; + args.title = title; + args.description = TAPi18n.__("hotShareQQZoneShare"); + var imgs =[]; + imgs.push(imageUrl); + args.imageUrl = imgs; + YCQQ.shareToQzone(function () { + console.log("share success"); + // var shareType = Session.get("shareToWechatType"); + // if(shareType[1] && shareType[1] == true){ + // $('.shareTheReadingRoom,.shareAlertBackground').fadeIn(300) + // shareType[1] = false; + // Session.set("shareToWechatType",shareType); + // } + }, function (failReason) { + console.log(failReason); + }, args); +}; +shareSeriesToSystem = function(title,description,thumbData,url) { + window.plugins.toast.showShortCenter(TAPi18n.__("preparePicAndWait")); + downloadFromBCS(thumbData, function(result){ + if (result) { + window.plugins.socialsharing.share(title, description, result, url); + } else { + PUB.toast(TAPi18n.__("failToGetPicAndTryAgain")); + } + }); +}; + +serieShareTo = function(to,self){ + var title = self.ownerName +'的合辑《'+ self.title +'》'; + var description = '' + var imageUrl = self.mainImage; + var url = getSeriesSharingPath(self); + if(to === 'QQShare'){ + return shareSeriesToQQ(title,title,imageUrl,url); + } + if(to === 'QQzoneShare'){ + return shareSeriesToQQZone(title,title,imageUrl,url); + } + if(to === 'system'){ + return shareSeriesToSystem(title,title,imageUrl,url); + } + + window.plugins.toast.showShortCenter(TAPi18n.__("preparePicAndWait")); + downloadFromBCS(imageUrl, function(result){ + if (result) { + if(to === 'WXSesssion'){ + shareSeriesToWXSession(title,description,result,url); + } else if(to === "WXTimeline"){ + shareSeriesToWXTimeLine(title,description,result,url); + } + } else { + PUB.toast(TAPi18n.__("failToGetPicAndTryAgain")); + } + }); +}; + +Template.shareSeries.events({ + 'click .share-series-btns li':function(e){ + console.log('share id is ') + console.log(e.currentTarget.id) + serieShareTo(e.currentTarget.id,Session.get('seriesContent')); + $('.shareSeries').hide(); + }, + 'click .cancelShareSeries':function(){ + $('.shareSeries').hide(); + }, + 'click .shareSeries':function(){ + $('.shareSeries').hide(); + } +}); diff --git a/client/faces/faces.html b/client/faces/faces.html new file mode 100644 index 000000000..92749d251 --- /dev/null +++ b/client/faces/faces.html @@ -0,0 +1,88 @@ + + + + + + \ No newline at end of file diff --git a/client/faces/faces.js b/client/faces/faces.js new file mode 100644 index 000000000..ffb863c0f --- /dev/null +++ b/client/faces/faces.js @@ -0,0 +1,63 @@ +var url = 'http://workaiossqn.tiegushi.com/c432cc7e-3157-11e8-8057-c81451c13caf'; +var isLoading = new ReactiveVar(false); +var personArr = new ReactiveVar([]); + +Template.faces.onRendered(function() { + personArr.set([]); + isLoading.set(true); + Meteor.subscribe('getFaces', function() { + isLoading.set(false); + }); +}); + +Template.faces.helpers({ + isLoading: function () { + return isLoading.get(); + }, + data: function() { + var groupIds = []; + SimpleChat.GroupUsers.find({user_id: Meteor.userId()}).forEach(function(item) { + groupIds.push(item.group_id) + }); + return Faces.findOne({group_id: {$in: groupIds}},{sort:{createdAt: -1}}); + }, + getFace: function (data) { + if (data.faces && data.faces[0]) { + return data.faces[0]; + } + return {}; + }, + isMultiFaces: function(data) { + if (data && data.faces && data.faces.length > 1) { + return true; + } + return false; + }, + formatDate: function(){ + var date = new Date(this.img_ts); + var group_id = this.group_id; + var time_offset = 8; + var group = SimpleChat.Groups.findOne({_id: group_id}); + console.log(group); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + return date.shortTime(time_offset) + }, +}); + +Template.faces.events({ + 'click .singleLeft': function (e) { + Meteor.call('faceLabelAsUnknown', this._id); + }, + 'click .singleRight': function (e) { + var self = this; + Meteor.call('faceLabelAsPerson', self); + }, + 'click .multiLeft': function (e) { + + }, + 'click .multiRight': function (e) { + + } +}); \ No newline at end of file diff --git a/client/faces/faces.less b/client/faces/faces.less new file mode 100644 index 000000000..a21c44d95 --- /dev/null +++ b/client/faces/faces.less @@ -0,0 +1,77 @@ +.facesPage{ + height: 100%; background: #efefef; + .content{height: 100%;} + #completeThisLabel { + position: fixed; + right: 10px; + bottom: 68px; + border: 0; + background: #39a8fe; + color: #fff; + height: 36px; + width: 64px; + border-radius: 20px; + box-shadow: 0px 4px 15px -5px #000000; + } +} + +.face-title{ + font-size: 18px; + text-align: center; + line-height: 26px; + margin: 10px 0; +} +.single-faces{ + .face-box{ + margin: 10px; border-radius: 8px; background: #fff; overflow: hidden; text-align: center; + .icon{ + width: 80px; height: 80px; border: 4px solid #eee; border-radius: 50%; overflow: hidden; margin: 32px auto; + img { width: 100%; height: 100%; object-fit: cover; } + } + } + + .face-ft { + margin-top: 32px; + text-align: center; + + .btn-groups{ + border-top: 1px solid #ddd; + padding: 20px 0; + + button{ + border: 0; outline: 0;width: 30%; background: none; + img{ height: 20px; margin: 4px auto;} + label{ height: 20px; line-height: 20px; font-size: 12px; display: block; color: #555;margin-top: 10px;} + } + } + } +} + +.multi-faces{ + .face-box{ + background: #fff; border-radius: 8px; margin: 16px 10px; padding: 16px; + .icon{ + width: 80px; height: 80px; border: 4px solid #ccc; border-radius: 50%; overflow: hidden; float: left; margin-right: 10px;margin-left: -16px; + img { width: 100%; height: 100%; object-fit: cover; } + } + } + .face-hd{ + p{ margin: 5px 0; font-size: 12px; line-height: 14px;} + p.time{padding-top: 20px; font-size: 16px;} + height: 60px; + border-bottom: 1px solid #ccc; + margin-left: 16px; + } + .face-ft { + margin-top: 15px; + text-align: justify; + overflow: hidden; + + .btn-groups{float: right;} + button{ + border: 0; outline: 0;width: 80px; background: none; + img{ height: 10px; margin: 4px auto;} + label{ height: 20px; line-height: 20px; font-size: 12px; color: #555;margin-top: 10px;} + } + } +} \ No newline at end of file diff --git a/client/footer.coffee b/client/footer.coffee new file mode 100644 index 000000000..a610d3547 --- /dev/null +++ b/client/footer.coffee @@ -0,0 +1,373 @@ +#space 2 +if Meteor.isClient + + # $('#level2-popup-menu').on('hide.bs.modal', function (e) { + # alert("hidden") + # }).on('show.bs.modal', function (e) { + # alert("show"); + # }); + $('#level2-popup-menu').on('hide.bs.modal', (e) -> + alert 'hidden' + return + ).on 'show.bs.modal', (e) -> + alert 'show' + return + Template.footer.helpers + hasNewLabelMsg: ()-> + Session.get('hasNewLabelMsg') + display_select_import_way: ()-> + Session.equals 'display_select_import_way',true + is_wait_read_count: (count)-> + count > 0 + limit_top_read_count: (count)-> + count >= 99 + wait_read_count:-> + me = Meteor.user() + if me + # if Session.equals('updataFeedsWithMe',true) + # return 0 + # else + return Feeds.find({ + followby: Meteor.userId(), + isRead:{$ne: true}, + checked:{$ne: true}, + eventType:{$ne:'share'}, + createdAt: {$gt: new Date((new Date()).getTime() - 7 * 24 * 3600 * 1000)} + },{ + limit: 99 + }).count() + # waitReadCount = Session.get('waitReadCount') + #if me.profile and me.profile.waitReadCount + #waitReadCount = me.profile.waitReadCount + # if waitReadCount is undefined or isNaN(waitReadCount) + # waitReadCount = 0 + # if Session.get('channel') is 'bell' and waitReadCount > 0 + # waitReadCount = 0 + # Session.set('waitReadCount',0) + # Meteor.users.update({_id: Meteor.user()._id}, {$set: {'profile.waitReadCount': 0}}); + # return waitReadCount + else + 0 + wait_import_count:-> + return Session.get('wait_import_count') + + focus_style:(channelName)-> + channel = Session.get "focusOn" + if channel is channelName + $('.foot-btn').removeClass('focus') + return "focus" + else + return "" + addressBookImgSrc:(channelName)-> + channel = Session.get "focusOn" + if channel is channelName + return "/addressbook_s.png" + else + return "/addressbook.png" + + icon_size:(channelName)-> + channel = Session.get "focusOn" + if channel is channelName + return true + display_footer:()-> + show_foot_url = ['/','/message','/timeline', '/explore', '/user','/faces'] + console.log "document_body_scrollTop=" + Session.get("document_body_scrollTop") + console.log("show_foot_url", show_foot_url, location, location.pathname, Router.current().route.path()) + + setTimeout( + ()-> + if show_foot_url.indexOf(Router.current().route.path()) isnt -1 + $('.content').scrollTop(Session.get("document_body_scrollTop")) + else + document.body.scrollTop = Session.get("document_body_scrollTop") + 0 + ) + return show_foot_url.indexOf(Router.current().route.path()) isnt -1 + # Meteor.isCordova + fade:-> + if isAndroidFunc() + '' + else + 'fade' + @prepareToEditorMode = ()-> + TempDrafts.remove({}) + $('body').removeClass('modal-open') + Session.set 'isReviewMode','0' + Session.set('draftTitle', ''); + Session.set('draftAddontitle', ''); + Drafts.remove({}) + Session.set 'NewImgAdd','true' + @checkShareUrl = () -> + if Meteor.user() + window.plugins.userinfo.setUserInfo Meteor.user()._id, (-> + console.log 'setUserInfo was success ' + return + ), -> + console.log 'setUserInfo was Error!' + return + setTimeout(()-> + waitImportCount = ShareURLs.find().count() + console.log 'waitImportCount :' + waitImportCount + if waitImportCount > 0 + data = ShareURLs.find().fetch() + console.log 'CustomDialog show!' + #CustomDialog.show data[0] + ,100) + + @editFromShare = (data)-> + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + prepareToEditorMode() + PUB.page '/add' + console.log 'type is ' + data.type + console.log 'content'+data.content[0] + if data.type is 'url' + setTimeout(()-> + handleDirectLinkImport(data.content[0],1) + ,100) + return + if data.type is 'image' + Meteor.defer ()-> + importImagesFromShareExtension(data.content, (cancel, result,currentCount,totalCount)-> + if cancel + #$('#level2-popup-menu').modal('hide'); + PUB.back() + return + if result + console.log 'Local is ' + result.smallImage + Drafts.insert {type:'image', isImage:true, owner: Meteor.userId(), imgUrl:result.smallImage, filename:result.filename, URI:result.URI, layout:''} + if currentCount >= totalCount + setTimeout(()-> + Template.addPost.__helpers.get('saveDraft')() + ,100) + ) + # setTimeout(()-> + # handleDirectLinkImport(url) + # ,100) + Template.footer.events + 'click #message':(e)-> + #Session.set('hasNewLabelMsg', false) + if (Session.get("myHotPostsChanged")) + Session.set("myHotPostsChanged", false) + navigator.notification.confirm( + '您改变了热门帖子, 要保存吗?' + (index)-> + if index is 2 + saveHotPosts() + PUB.page('/message') + '提示' + ['暂不','保存'] + ) + return + PUB.page('/message') + 'click #homePage':(e)-> + #Session.set('hasNewLabelMsg', false) + if (Session.get("myHotPostsChanged")) + #Session.set("myHotPostsChanged", false) + navigator.notification.confirm( + '您改变了热门帖子, 要保存吗?' + (index)-> + if index is 2 + saveHotPosts() + PUB.page('/') + '提示' + ['暂不','保存'] + ) + return + PUB.page('/') + 'click #timeline':(e)-> + if (Session.get("myHotPostsChanged")) + Session.set("myHotPostsChanged", false) + navigator.notification.confirm( + '您改变了热门帖子, 要保存吗?' + (index)-> + if index is 2 + saveHotPosts() + PUB.page('/timeline') + '提示' + ['暂不','保存'] + ) + return + PUB.page('/timeline') + 'click #explore':(e)-> + if (Session.get("myHotPostsChanged")) + Session.set("myHotPostsChanged", false) + navigator.notification.confirm( + '您改变了热门帖子, 要保存吗?' + (index)-> + if index is 2 + saveHotPosts() + PUB.page('/explore') + '提示' + ['暂不','保存'] + ) + return + PUB.page('/explore') + 'click #faces':(e)-> + PUB.page('/faces') + 'click #bell':(e)-> + Meteor.defer ()-> + me = Meteor.user() + if me and me.profile and me.profile.waitReadCount + if me.profile.waitReadCount > 0 + Meteor.users.update({_id: Meteor.user()._id}, {$set: {'profile.waitReadCount': 0}}); + if (Session.get("myHotPostsChanged")) + Session.set("myHotPostsChanged", false) + navigator.notification.confirm( + '您改变了热门帖子, 要保存吗?' + (index)-> + if index is 2 + saveHotPosts() + PUB.page('/bell') + '提示' + ['暂不','保存'] + ) + return + PUB.page('/bell') + 'click #user':(e)-> + $('.importProgressBar, .b-modal, .toEditingProgressBar').remove() + if (Session.get("myHotPostsChanged")) + Session.set("myHotPostsChanged", false) + navigator.notification.confirm( + '您改变了热门帖子, 要保存吗?' + (index)-> + if index is 2 + saveHotPosts() + PUB.page('/user') + '提示' + ['暂不','保存'] + ) + return + PUB.page('/user') + 'click #add': (e)-> + if (Session.get("myHotPostsChanged")) + Session.set("myHotPostsChanged", false) + navigator.notification.confirm( + '您改变了热门帖子, 要保存吗?' + (index)-> + if index is 2 + saveHotPosts() + $('.importProgressBar, .b-modal, .toEditingProgressBar').remove() + Tips.show('_tips_addPost') + '提示' + ['暂不','保存'] + ) + return + $('.importProgressBar, .b-modal, .toEditingProgressBar').remove() + Tips.show('_tips_addPost') + if Session.get('persistentLoginStatus') and !Meteor.userId() and !Meteor.loggingIn() + window.plugins.toast.showLongCenter("登录超时,需要重新登录~"); + e.stopPropagation() + PUB.page('/') + 'click #album-select':(e)-> + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + prepareToEditorMode() + PUB.page '/add' + Meteor.defer ()-> + selectMediaFromAblum(20, (cancel, result,currentCount,totalCount)-> + if cancel + #$('#level2-popup-menu').modal('hide'); + PUB.back() + return + if result + console.log 'Local is ' + result.smallImage + Drafts.insert {type:'image', isImage:true, owner: Meteor.userId(), imgUrl:result.smallImage, filename:result.filename, URI:result.URI, layout:''} + if currentCount >= totalCount + Meteor.setTimeout(()-> + Template.addPost.__helpers.get('saveDraft')() + ,100) + ) + 'click #web-import':(e)-> + $('#level2-popup-menu').modal('hide') + #if we choose to use server import + if withServerImport is true + Session.set('display_select_import_way',true) + #if we disable server import and just want to use mobile side import + else + Session.set('display_select_import_way',false) + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + prepareToEditorMode() + PUB.page '/add' + cordova.plugins.clipboard.paste (link)-> + regexToken = /\b(((http|https?)+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig + matchArray = regexToken.exec( link ) + if matchArray isnt null + importLink = matchArray[0] + if matchArray[0].indexOf('http') is -1 + importLink = "http://"+matchArray[0] + Meteor.setTimeout(()-> + handleDirectLinkImport(importLink,1) + ,100) + else + handleAddedLink(null) + window.plugins.toast.showLongCenter("粘贴板内容并非有效连接,请手动粘贴\n浏览器内容加载后,点击地址栏右侧\"导入\"按钮"); + ,()-> + handleAddedLink(null) + window.plugins.toast.showLongCenter("无法获得粘贴板数据,请手动粘贴\n浏览器内容加载后,点击地址栏右侧\"导入\"按钮"); + 'click #share-import':(e)-> + window.plugins.shareExtension.getShareData ((data) -> + if data + editFromShare(data) + window.plugins.shareExtension.emptyData ((count)-> + if count == 0 + return Session.set('wait_import_count',false) + Session.set('wait_import_count',true) + ),-> + console.log 'deleteShareData was failed!' + ), -> + Session.set('wait_import_count',false) + console.log 'getShareData was Error!' + 'click #photo-select':(e)-> + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + prepareToEditorMode() + PUB.page '/add' + Meteor.defer ()-> + if window.takePhoto + window.takePhoto (result)-> + console.log 'result from camera is ' + JSON.stringify(result) + if result + Drafts.insert {type:'image', isImage:true, owner: Meteor.userId(), imgUrl:result.smallImage, filename:result.filename, URI:result.URI, layout:''} + Meteor.setTimeout(()-> + Template.addPost.__helpers.get('saveDraft')() + ,100) + else + PUB.back() + + Template.selectImportWay.helpers + hasAssocaitedUsers: ()-> + (AssociatedUsers.find({}).count() > 0) or (UserRelation.find({userId: Meteor.userId()}).count() > 0) + serverImportClick = (e, t)-> + Session.set('post_improt_way',e.currentTarget.id) + Session.set('display_select_import_way',undefined) + Meteor.defer ()-> + $('.modal-backdrop.in').remove() + prepareToEditorMode() + PUB.page '/add' + cordova.plugins.clipboard.paste (link)-> + regexToken = /\b(((http|https?)+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig + matchArray = regexToken.exec( link ) + if matchArray isnt null + importLink = matchArray[0] + if matchArray[0].indexOf('http') is -1 + importLink = "http://"+matchArray[0] + Meteor.setTimeout(()-> + if e.currentTarget.id is 'serverImport' + Session.set 'isServerImport', true + handleDirectLinkImport(importLink) + else + handleDirectLinkImport(importLink,1) + ,100) + else + handleAddedLink(null) + window.plugins.toast.showLongCenter("粘贴板内容并非有效连接,请手动粘贴\n浏览器内容加载后,点击地址栏右侧\"导入\"按钮"); + ,()-> + handleAddedLink(null) + window.plugins.toast.showLongCenter("无法获得粘贴板数据,请手动粘贴\n浏览器内容加载后,点击地址栏右侧\"导入\"按钮"); + Template.selectImportWay.events + 'click #mask': -> + Session.set('display_select_import_way',undefined) + 'click .importWayBtn':(e,t)-> + serverImportClick(e, t) \ No newline at end of file diff --git a/client/footer.html b/client/footer.html new file mode 100644 index 000000000..a19476bb6 --- /dev/null +++ b/client/footer.html @@ -0,0 +1,102 @@ + + + diff --git a/client/group/add.coffee b/client/group/add.coffee new file mode 100644 index 000000000..9305455a2 --- /dev/null +++ b/client/group/add.coffee @@ -0,0 +1,85 @@ +if Meteor.isClient + users = new ReactiveVar([]) + Template.groupAdd.rendered=-> + users.set([]) + $('.content').css 'min-height',$(window).height() +# $('.mainImage').css('height',$(window).height()*0.55) + $(window).scroll (event)-> + target = $("#showMoreResults"); + FOLLOWS_ITEMS_INCREMENT = 10; + if (!target.length) + return; + threshold = $(window).scrollTop() + $(window).height() - target.height(); + if target.offset().top < threshold + if (!target.data("visible")) + target.data("visible", true); + Session.set("followersitemsLimit", + Session.get("followersitemsLimit") + FOLLOWS_ITEMS_INCREMENT); + else + if (target.data("visible")) + target.data("visible", false); + Template.groupAdd.helpers + myFollows:()-> + Follower.find({"userId":Meteor.userId()}, {sort: {createdAt: -1}}, {limit:Session.get("followersitemsLimit")}) + moreResults:-> + !(Follower.find({"userId":Meteor.userId()}).count() < Session.get("followersitemsLimit")) + loading:-> + Session.equals('followersCollection','loading') + loadError:-> + Session.equals('followersCollection','error') + is_selected: (followerId)-> + return _.pluck(users.get(), 'followerId').indexOf(followerId) isnt -1 + Template.groupAdd.events + 'click .leftButton':(event)-> + history.go(-1) + 'click .rightButton':(event)-> + selected = users.get() + Session.set('selected_followers',_.pluck(selected, 'followerId')); + Router.go('/selectTemplate') + # if selected.length <= 0 + # return PUB.toast('没有选择任何用户~') + ### + Meteor.call 'create-group', null, null, _.pluck(selected, 'followerId'), (err, id)-> + console.log(err) + if err or !id + return PUB.toast('创建监控组失败,请重试~') + Meteor.subscribe('get-group',id,{ + onReady:()-> + group = SimpleChat.Groups.findOne({_id:id}); + msgObj = { + _id: new Mongo.ObjectID()._str, + form: { + id: '', + name: '系统', + icon: '' + }, + to: { + id: group._id, + name: group.name, + icon: group.icon + }, + images: [], + to_type: "group", + type: "system", + text: '欢迎加入'+group.name , + create_time: new Date(), + is_read: false + }; + sendMqttGroupMessage(group._id, msgObj); + }) + Meteor.setTimeout( + ()-> + Router.go('/simple-chat/to/group?id=' + id) + 50 + ) + history.go(-1) + ### + 'click .followItem': (event)-> + # console.log(this); + $i = $(event.currentTarget).find('i'); + selected = users.get() + if _.pluck(selected, 'followerId').indexOf(this.followerId) is -1 + selected.push(this) + else + selected.splice(_.pluck(selected, 'followerId').indexOf(this.followerId), 1) + users.set(selected) diff --git a/client/group/add.html b/client/group/add.html new file mode 100644 index 000000000..00e52a12c --- /dev/null +++ b/client/group/add.html @@ -0,0 +1,48 @@ + diff --git a/client/group/add.less b/client/group/add.less new file mode 100644 index 000000000..bd80d84f7 --- /dev/null +++ b/client/group/add.less @@ -0,0 +1,22 @@ + +.groupAdd .head .rightButton .fa-plus{ + position: relative; + bottom: 8px; + font-size: 8px; + color: #ffffff +} +.groupAdd .content{position:relative;padding-top: 40px;padding-bottom: 55px;-webkit-touch-callout: none;background-color: #eee;} +.groupAdd .content .eachViewer{ + background-color: white; +} +.groupAdd .content .icon{width: 40px;height: 40px;margin: 10px;} +.groupAdd .content .groupsName{color: black;} +.groupAdd .line{text-align:center;width: 100%; height: 1px;} +.groupAdd .line span{background: #333; width: 100%; height: 1px; display: block;} +.groupAdd{ + .followItem{ + position: relative; padding-left: 50px; + i{width: 40px; height: 40px; line-height: 40px; text-align: center; font-size: 24px; color: #ccc; position: absolute; left: 10px; top: 50%; margin-top: -20px;} + i.fa-circle{color: #4caf50;} + } +} \ No newline at end of file diff --git a/client/group/loadingPost.html b/client/group/loadingPost.html new file mode 100644 index 000000000..945c5beb0 --- /dev/null +++ b/client/group/loadingPost.html @@ -0,0 +1,36 @@ + + + diff --git a/client/group/selectTemplate/selectTemplate.coffee b/client/group/selectTemplate/selectTemplate.coffee new file mode 100644 index 000000000..15ec086c8 --- /dev/null +++ b/client/group/selectTemplate/selectTemplate.coffee @@ -0,0 +1,91 @@ +if Meteor.isClient + @create_group_fun = (name,followers,template)-> + group_name = name || null; + selected_followers = followers || null; + selected_template = selected_template || null; + offsetTimeZone = (new Date().getTimezoneOffset())/-60 + Meteor.call 'create-group1', null, group_name, selected_followers,selected_template,offsetTimeZone, (err, id)-> + console.log(err) + if err or !id + return PUB.toast('创建监控组失败,请重试~') + Session.set('AI_Group_Name',null); + Session.set('touserid6', id) + Session.set('tousername6',group_name) + Meteor.subscribe('get-group',id,{ + onReady:()-> + # 欢迎消息重复 + # group = SimpleChat.Groups.findOne({_id:id}); + # msgObj = { + # _id: new Mongo.ObjectID()._str, + # form: { + # id: '', + # name: '系统', + # icon: '' + # }, + # to: { + # id: group._id, + # name: group.name, + # icon: group.icon + # }, + # images: [], + # to_type: "group", + # type: "system", + # text: '欢迎加入'+group.name , + # create_time: new Date(), + # is_read: false + # }; + # sendMqttGroupMessage(group._id, msgObj); + }) + Meteor.setTimeout( + ()-> + Session.set("history_view",null) + Router.go('/simple-chat/to/group?id=' + id) + 50 + ) + Template.selectTemplate.rendered=-> + $('.content').css 'min-height',$(window).height() + api_url = rest_api_url + '/restapi/workai-group-template' + succ_return = (res)-> + console.log("cordovaHTTP result="+res.data) + try + result = JSON.parse(res.data) + if result and result.group_templates + Session.set('group_templates',result.group_templates) + Session.set('group_templates_loading','success') + else + Session.set('group_templates_loading','error') + catch e + Session.set('group_templates_loading','error') + error_return = (res)-> + console.log res + Session.set('group_templates_loading','error') + cordovaHTTP.get api_url, {}, {}, succ_return, error_return + Session.set('group_templates_loading','true') + Session.set('selected_template',null) + Template.selectTemplate.helpers + groupTemplates:()-> + Session.get('group_templates') + loading:-> + Session.equals('group_templates_loading','true') + loadError:-> + Session.equals('group_templates_loading','error') + is_selected: (_id)-> + selected = Session.get('selected_template') + if selected and selected._id is _id + return true + return false + Template.selectTemplate.events + 'click .leftButton':(event)-> + Session.set('fromCreateNewGroups',true); + history.go(-1) + 'click .rightButton':(event)-> + selected_followers = Session.get('selected_followers') + selected_template = Session.get('selected_template') + # if selected.length <= 0 + # return PUB.toast('没有选择任何用户~') + group_name = Session.get('AI_Group_Name'); + create_group_fun(selected_followers,selected_template,group_name); + 'click .tempItem': (event)-> + # console.log(this); + #$i = $(event.currentTarget).find('i'); + Session.set('selected_template',this) diff --git a/client/group/selectTemplate/selectTemplate.html b/client/group/selectTemplate/selectTemplate.html new file mode 100644 index 000000000..cd29e7114 --- /dev/null +++ b/client/group/selectTemplate/selectTemplate.html @@ -0,0 +1,38 @@ + diff --git a/client/group/selectTemplate/selectTemplate.less b/client/group/selectTemplate/selectTemplate.less new file mode 100644 index 000000000..adc615345 --- /dev/null +++ b/client/group/selectTemplate/selectTemplate.less @@ -0,0 +1,22 @@ + +.selectTemplate .head .rightButton .fa-plus{ + position: relative; + bottom: 8px; + font-size: 8px; + color: #ffffff +} +.selectTemplate .content{position:relative;padding-top: 40px;padding-bottom: 55px;-webkit-touch-callout: none;background-color: #eee;} +.selectTemplate .content .eachViewer{ + background-color: white; +} +.selectTemplate .content .icon{width: 40px;height: 40px;margin: 10px;} +.selectTemplate .content .tempName{color: black;} +.selectTemplate .line{text-align:center;width: 100%; height: 1px;} +.selectTemplate .line span{background: #333; width: 100%; height: 1px; display: block;} +.selectTemplate{ + .tempItem{ + position: relative; padding-left: 50px; + i{width: 40px; height: 40px; line-height: 40px; text-align: center; font-size: 24px; color: #ccc; position: absolute; left: 10px; top: 50%; margin-top: -20px;} + i.fa-circle{color: #4caf50;} + } +} \ No newline at end of file diff --git a/client/home/home.coffee b/client/home/home.coffee new file mode 100644 index 000000000..aa24bb081 --- /dev/null +++ b/client/home/home.coffee @@ -0,0 +1,162 @@ +@showScanTipHint = new ReactiveVar(false) + +#space 2 +if Meteor.isClient + toNum = (a) -> + if a is null or a is undefined or a is '' + return + + c = a.split('.') + num_place = [ + '' + '0' + '00' + '000' + '0000' + ] + r = num_place.reverse() + i = 0 + while i < c.length + len = c[i].length + c[i] = r[len] + c[i] + i++ + res = c.join('') + res + @checkNewVersion2 = -> + platform = if Blaze._globalHelpers.isIOS() then 'ios' else (if Blaze._globalHelpers.isAndroid() then 'android' else 'others') + HTTP.get(version_host_url,(err,result)-> + if err + console.log(err) + return + data = result.data + console.log(data) + currentVersion = version_of_build + if platform is 'ios' + stableVersion = data.stable_ios + latestVersion = data.latest_ios + stableRelease = data.stable_release_ios + latestRelease = data.latest_release_ios + if platform is 'android' + stableVersion = data.stable_android + latestVersion = data.latest_android + stableRelease = data.stable_release_android + latestRelease = data.latest_release_android + window.localStorage.setItem('stableVersion',stableVersion) + window.localStorage.setItem('latestVersion',latestVersion) + if window.compareVersions(currentVersion,stableVersion) + # 强制升级 + window.updateAPPVersion(data.stable_title,stableRelease, data.stable_styles,false) + else if window.compareVersions(currentVersion, latestVersion) < 0 + updateTipTimes = window.localStorage.getItem('stable_update_tip_times') + if !updateTipTimes + window.localStorage.setItem('stable_update_tip_times',1) + window.localStorage.setItem('stable_update_tip_times',Number(updateTipTimes)+1) + if updateTipTimes and updateTipTimes%data.latest_times is 0 + window.updateAPPVersion(data.latest_title,latestRelease, data.latest_styles,true) + ) + @checkNewVersion = -> + platform = if Blaze._globalHelpers.isIOS() then 'ios' else (if Blaze._globalHelpers.isAndroid() then 'android' else 'others') + # version = Versions.findOne({}) + if !window.localStorage.getItem("latestVersion") + console.log 'no localStorage latestVersion.' + window.localStorage.setItem("latestVersion", version_of_build) + # if version and version[platform] + # latestVersion = version[platform] + # else + # latestVersion = version_of_build + latestVersion = window.localStorage.getItem("latestVersion") + _latestVersion = toNum(latestVersion) + _version_of_build = toNum(version_of_build) + # _localLatestVersion = toNum(window.localStorage.getItem("latestVersion")) + + # if _latestVersion > _version_of_build and _latestVersion > _localLatestVersion + if _latestVersion > _version_of_build + # window.localStorage.setItem("latestVersion", latestVersion) + console.log 'set latestVersionAlert true. ' + # Session.set('latestVersionAlert', true) # 使用新的升级提醒 + else + Session.set('latestVersionAlert', false) + + if _latestVersion > _version_of_build + console.log '有新版本:' + _latestVersion + return true + if _latestVersion <= _version_of_build + console.log '当前版本已是最新版本!' + return false + @goToUpdate = -> + if Blaze._globalHelpers.isIOS() + cordova.InAppBrowser.open('https://itunes.apple.com/app/gu-shi-tie/id957024953', '_system') + else + cordova.InAppBrowser.open('http://a.app.qq.com/o/simple.jsp?pkgname=org.hotshare.everywhere', '_system') + + Template.home.onRendered ()-> + # 首页数据直接在首页订阅,不需要等mqtt连接成功,提升首页打开速度 + Meteor.subscribe 'get-my-group', Meteor.userId() + + if !localStorage.getItem('scantipFlag') + maskDescription.set({ + maskEllipse: false, + x: '31%', + y: '100%', + width: '50', + height: '50', + trsx: 0, + trsy: -50, + scantipContainerStyle: "position: absolute; bottom: 0px; width: 100%; height: 150px;", + iconClass: 'fa fa-3x fa-hand-o-down', + iconStyle: 'display: block; position: absolute; bottom: 60px; left: 31%;', + spanStyle: 'display: block; font-size: 18px; position: absolute; bottom: 110px; left: 20%;', + spanContent: '点击时间轴查看设备在线状态' + }) + + currentTip = 'timeLineTab' + showScanTipHint.set(true) + + Template.home.helpers + wasLogon:()-> + Session.get('persistentLoginStatus') + isCordova:()-> + Meteor.isCordova + isFirstLog:()-> + Session.get('isFlag'); + isAndroid:()-> + isAndroidFunc() + showDailyReporterTip:()-> + if localStorage.getItem('hideDailyReporterTip') is 'true' + return false + else + return true + showScanTipHintTemplate:()-> + showScanTipHint.get() + + Template.home.events + # 'click .top-series-btn': (event)-> + # Router.go '/seriesList' + # 'click #follow': (event)-> + # Router.go '/searchFollow' + # 'click .clickHelp':(event)-> + # PUB.page '/help' + 'click .closebtn':()-> + $('.app-rate').fadeOut() + promptForRatingWindowButtonClickHandler(2) + 'click .btn-rate1':()-> + $('.app-rate').fadeOut() + promptForRatingWindowButtonClickHandler(3) + 'click .btn-rate2':()-> + $('.app-rate').fadeOut() + promptForRatingWindowButtonClickHandler(3) + 'click .dailyReporterTip':(event)-> + localStorage.setItem('hideDailyReporterTip', 'true') + $('.dailyReporterTip').fadeOut() + Template.home.rendered=-> + flag = window.localStorage.getItem("firstLog") == 'first' + Session.set('isFlag', !flag) + checkNewVersion() + +# Tracker.autorun((t)-> +# if !Session.get('isFlag') and Session.get('latestVersionAlert') +# t.stop() +# setTimeout(()-> +# Dialogs.alert('我们已为您备好更有趣新版本,记得去更新哦~', null, '新版本提示', '好的') +# , 1000) +# ); diff --git a/client/home/home.html b/client/home/home.html new file mode 100644 index 000000000..0d3da4cca --- /dev/null +++ b/client/home/home.html @@ -0,0 +1,52 @@ + diff --git a/client/homeAI/addHomeAIBox.coffee b/client/homeAI/addHomeAIBox.coffee new file mode 100644 index 000000000..22c84f77f --- /dev/null +++ b/client/homeAI/addHomeAIBox.coffee @@ -0,0 +1,10 @@ +if Meteor.isClient + # Template.introductoryPage.rendered=-> + # $('.content').css 'min-height',$(window).height() + + Template.addHomeAIBox.events + 'click .leftButton':(event)-> + Router.go('/scene'); + 'click #addHomeAIBoxBtn':(event)-> + Session.set('addHomeAIBox',true); + window.ScanBarcodeByBarcodeScanner() \ No newline at end of file diff --git a/client/homeAI/addHomeAIBox.html b/client/homeAI/addHomeAIBox.html new file mode 100644 index 000000000..c9a5a99bf --- /dev/null +++ b/client/homeAI/addHomeAIBox.html @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/client/homeAI/addHomeAIBox.less b/client/homeAI/addHomeAIBox.less new file mode 100644 index 000000000..a9bc3d48d --- /dev/null +++ b/client/homeAI/addHomeAIBox.less @@ -0,0 +1,57 @@ +.addHomeAIBox{ + background-image: url(homeAI/addHomeAIBoxBg.png); + background-repeat: no-repeat; + background-size: cover !important; + color: #fff !important; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1000; + .addHomeAIBoxBg{ + padding: 8%; + font-size: 16px; + .infoArea{ + padding-top: 20px; + h2{ + font-weight: 500; + } + .infoItem{ + margin-top: 40px; + img{ + position: absolute; + width: 60px; + margin: 10px 10px 10px 0; + } + .info_text{ + margin-left: 70px; + p{ + font-weight: 400; + margin: 0px; + } + } + } + } + .actionArea{ + position: absolute; + bottom: 15%; + left: 8%; + right: 8%; + #addHomeAIBoxBtn{ + text-align: center; + font-size: 18px; + background-color: #37a7fe; + height: 45px; + border-radius: 5px; + padding-top: 10px; + } + span{ + margin-left: 120px; + top: 30px; + border-bottom-style: solid; + border-bottom-width: 1px; + } + } + } +} diff --git a/client/homeAI/scanFailPrompt/scanFailPrompt.coffee b/client/homeAI/scanFailPrompt/scanFailPrompt.coffee new file mode 100644 index 000000000..548297e30 --- /dev/null +++ b/client/homeAI/scanFailPrompt/scanFailPrompt.coffee @@ -0,0 +1,8 @@ +if Meteor.isClient + # Template.introductoryPage.rendered=-> + # $('.content').css 'min-height',$(window).height() + + Template.scanFailPrompt.events + 'click .rightButton':(event)-> + Session.set('addHomeAIBox',true) + window.ScanBarcodeByBarcodeScanner() \ No newline at end of file diff --git a/client/homeAI/scanFailPrompt/scanFailPrompt.html b/client/homeAI/scanFailPrompt/scanFailPrompt.html new file mode 100644 index 000000000..1790dfd9a --- /dev/null +++ b/client/homeAI/scanFailPrompt/scanFailPrompt.html @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/client/homeAI/scanFailPrompt/scanFailPrompt.less b/client/homeAI/scanFailPrompt/scanFailPrompt.less new file mode 100644 index 000000000..baf821c36 --- /dev/null +++ b/client/homeAI/scanFailPrompt/scanFailPrompt.less @@ -0,0 +1,40 @@ +.scanFailPrompt{ + .head{ + background: rgb(254, 252, 255) !important; + border-bottom-style: solid; + border-bottom-width: 0.1px; + strong{ + color: black; + } + .rightButton{ + color: #37a7fe; + font-weight: 400; + } + } + .content{ + padding-top: 44px; + background: rgb(241, 239, 242); + top: 0; + bottom: 0; + left: 0; + right: 0; + position: fixed; + .item{ + //padding: 20px; + padding: 10px 20px 10px 5px; + font-size: 16px; + font-weight: 400; + height: auto; + span{ + display: block; + margin-left: 30px; + } + i{ + position: absolute; + margin: 8px 10px 0 10px; + font-size: 10px; + } + } + + } +} diff --git a/client/homePage/homePage.html b/client/homePage/homePage.html new file mode 100644 index 000000000..d661034a3 --- /dev/null +++ b/client/homePage/homePage.html @@ -0,0 +1,110 @@ + + + diff --git a/client/homePage/homePage.js b/client/homePage/homePage.js new file mode 100644 index 000000000..640c2dfe2 --- /dev/null +++ b/client/homePage/homePage.js @@ -0,0 +1,319 @@ +Template.homePage.onRendered(function () { + var now = new Date(); + var displayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + var date = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), + 0, 0, 0, 0); + + Session.set('theCurrentDay', date); //UTC日期 + Session.set('theDisplayDay', displayDate); //当前显示的日期 + Session.set('today', displayDate); //今天 + Meteor.subscribe('userGroups'); + Meteor.subscribe('WorkStatus', date, { + onReady: function () { + Session.set('WorkStatusLoading', false); + } + }); + + homePageMethods.createMoveAnimate(this); +}); + +var homePageMethods = { + initGroupListIndex: function() { + var noSortList = SimpleChat.GroupUsers.find({ user_id: Meteor.userId(), index: {$exists: false} }, { sort: { create_time: -1 } }).fetch(); + var sortList = SimpleChat.GroupUsers.find({ user_id: Meteor.userId(), index: {$exists: true} }, { sort: { index: 1 } }).fetch(); + if (noSortList.length === 0) { + return; + } + noSortList.concat(sortList).forEach(function (item, index) { + SimpleChat.GroupUsers.update({ _id: item._id}, { $set: { index: index } }); + }); + }, + moveGroupItem: function(currentIndex, targetIndex) { + var targetId = SimpleChat.GroupUsers.findOne({ user_id: Meteor.userId(), index : targetIndex })._id; + var currentItemId = SimpleChat.GroupUsers.findOne({ user_id: Meteor.userId(), index : currentIndex })._id; + SimpleChat.GroupUsers.update({ _id: targetId }, { $set: { index: currentIndex } }); + SimpleChat.GroupUsers.update({ _id: currentItemId }, { $set: { index: targetIndex } }); + }, + createMoveAnimate: function(context) { + context.find('.content')._uihooks = { + moveElement: function (node, next) { + var $node = $(node), + $next = $(next), + height = $node.outerHeight(), + oldTop = $node.offset().top, + newTop = 0, + $inBetween = $next.nextUntil($node); + + if ($inBetween.length === 0) { + $inBetween = $node.nextUntil($next); + } + $node.insertBefore($next); + newTop = $node.offset().top; + $node.removeClass('animate') + .css('top', oldTop - newTop); + $inBetween.removeClass('animate') + .css('top', oldTop - newTop > 0 ? -height : height); + $node.offset(); + $node.add($inBetween).addClass('animate') + .css('top', 0); + } + }; + } +}; + +Template.homePage.onCreated(function() { + homePageMethods.initGroupListIndex(); +}); + +Template.homePage.helpers({ + companys: function () { + var lists = []; + var sortList = SimpleChat.GroupUsers.find({ user_id: Meteor.userId() }, { sort: { index: 1 } }).fetch(); + sortList.forEach(function (item) { + var group = SimpleChat.Groups.findOne({ _id: item.group_id }); + if (group) { + group.index = item.index; + lists.push(group); + } + }); + return lists; + }, + noCompany:function(){ + var lists = Template.homePage.__helpers.get('companys')(); + if (lists.length == 0) { + return 'background-image:url(nogroup_bg.png);background-repeat:no-repeat;background-size:cover'; + } + return ''; + }, + hide_content:function(){ + var lists = Template.homePage.__helpers.get('companys')(); + if (lists.length == 0) { + return 'display:none'; + } + return ''; + }, + onlyOneCompany: function () { + var lists = Template.homePage.__helpers.get('companys')(); + if (lists.length == 1) { + return 'width: 100%;'; + } + return ''; + }, + getGroupInOutTime: function () { + var group_intime = '09:00'; + var group_outtime = '18:00'; + group = SimpleChat.Groups.findOne({ _id: Session.get('groupsId') }) + if (this.group_intime) { + group_intime = this.group_intime; + } + if (this.group_outtime) { + group_outtime = this.group_outtime; + } + return group_intime + ' - ' + group_outtime; + }, + getInCount: function () { + + // var counts = WorkStatus.find({ group_id: this._id, date: Session.get('theCurrentDay'), status: { $in: ['in', 'out'] } }).count(); + var counts = _.uniq(_.map(WorkStatus.find({group_id: this._id, date: Session.get('theCurrentDay')}).fetch(), function(ws){return ws.person_name})).length; + return counts + }, + isShowDownArrow: function(index) { + return index < SimpleChat.GroupUsers.find({ user_id: Meteor.userId() }).count() - 1; + } +}); + +Template.homePage.events({ + 'shown.bs.dropdown .dropdown': function(e) { + if (!localStorage.getItem('createCompanyFlag')) { + maskDescription.set({ + maskEllipse: false, + x: '100%', + y: '0%', + width: '140', + height: '40', + trsx: -160, + trsy: 50, + scantipContainerStyle: "position: absolute; top: 50px; width: 100%; height: 150px;", + iconClass: 'fa fa-3x fa-hand-o-up', + iconStyle: 'display: block; position: absolute; top: 50px; right: 80px;', + spanStyle: 'display: block; font-size: 18px; position: absolute; top: 5px; right: 180px;', + spanContent: '点击此处创建监控组' + }); + currentTip = 'createCompanyMenu'; + showScanTipHint.set(true); + } + }, + 'hidden.bs.dropdown .dropdown': function(e) { + }, + 'click .viewWorkStatus': function (e) { + Session.set('deviceDashboardTitle', this.name); + return PUB.page('/device/dashboard/' + this._id); + }, + 'click .goInstallTest':function(e){ + var group_id = this._id; + //根据group_id得到group下的设备列表 + Meteor.call('getDeviceListByGroupId', group_id, function (err, deviceLists) { + if(err){ + console.log('getDeviceListByGroupId:',err); + return; + } + console.log("device lists is: ", JSON.stringify(deviceLists)); + if (deviceLists && deviceLists.length > 0) { + if (deviceLists.length == 1 && deviceLists[0].uuid) { + console.log("enter this device install test"); + return PUB.page('/groupInstallTest/'+group_id+'/' + deviceLists[0].uuid); + } else { + Session.set('_groupChatDeviceLists', deviceLists); + Session.set('toPath','/groupInstallTest/'+group_id); + $('._checkGroupDevice').fadeIn(); + return; + } + } + return PUB.toast('该监控组下暂无脸脸盒'); + }); + }, + 'click .goGroupPerson': function (e) { + return PUB.page('/groupPerson/' + this._id); + }, + 'click .goGroupProfile': function (e) { + return PUB.page('/groupsProfile/group/' + e.currentTarget.id); + }, + 'click .goPersonHistory': function (e) { + Session.set('workstatus_group', this); + return workStatusPopPage.show(); + }, + 'click .goStranger': function (e) { + var group_id = this._id; + console.log('group id is: ', group_id); + //根据group_id得到group下的设备列表 + Meteor.call('getDeviceListByGroupId', group_id, function (err, deviceLists) { + if(err){ + console.log('getDeviceListByGroupId:',err); + return; + } + Session.set("timelinehref",true) + console.log("device lists is: ", JSON.stringify(deviceLists)); + if (deviceLists && deviceLists.length > 0) { + if (deviceLists.length == 1 && deviceLists[0].uuid) { + console.log("enter this device timeline") + // return PUB.page('/timelineAlbum/' + deviceLists[0].uuid + '?from=groupchat'); + // return PUB.page('/chooseLabelType/' + deviceLists[0].uuid); + var uuid = deviceLists[0].uuid; + return PUB.page('/timelineAlbum/' + uuid + '?from=groupchat'); + } else { + console.log("select a device") + Session.set('_groupChatDeviceLists', deviceLists); + // Session.set('toPath','/timelineAlbum'); + //workStatusPopPage.close(); + $('._checkGroupDevice').fadeIn(); + //workStatusPopPage.hide(); + $('.homePage').css('z-index', 0) + return; + } + } + return PUB.toast('该监控组下暂无脸脸盒'); + }) + + }, + 'click #joinTestChatGroups': function (event) { + event.stopImmediatePropagation(); + return PUB.page('/introductoryPage2'); + }, + 'click #createNewChatGroups': function (event) { + //跳转到这个页面取消 + event.stopImmediatePropagation() + Session.set('fromCreateNewGroups', true); + Session.set('notice-from','createNewChatGroups'); + // return Router.go('/setGroupname'); + //PUB.page('/notice'); + return Router.go('/setGroupname'); + }, + 'click #qrcodeadddevice': function (event) { + //event.stopImmediatePropagation(); + return QRCodeAddDevice(); + }, + 'click #scanbarcode': function (event) { + event.stopImmediatePropagation(); + return ScanBarcodeByBarcodeScanner(); + }, + 'click #scanimage': function (event) { + event.stopImmediatePropagation(); + return DecodeImageFromAlum(); + }, + 'click #scanadddevice': function (event) { + event.stopImmediatePropagation(); + return Router.go('/scannerAddDevice'); + }, + // 周报 / 月报 查看 + 'click .goGroupReporter': function (event) { + event.stopImmediatePropagation(); + return PUB.page('/comReporter/' + this._id); + }, + 'click .sort-arrow-up .fa': function(event) { + event.stopImmediatePropagation(); + homePageMethods.moveGroupItem.call(this, this.index, this.index - 1); + }, + 'click .sort-arrow-down .fa': function(event) { + event.stopImmediatePropagation(); + homePageMethods.moveGroupItem.call(this, this.index, this.index + 1); + } +}) +Template.notice.onCreated(function(){ + this.curSrc = new ReactiveVar(''); + var type = Session.get('notice-from'); + if(type == 'timelineAlbum'){ + this.curSrc.set('/moshengren.png'); + }else if(type == 'deviceDashboard'){ + this.curSrc.set('/hint.jpg'); + } + // else if(type == 'createNewChatGroups'){ + // this.curSrc.set('/createGroup3.png'); + // } +}) +Template.notice.helpers({ + src:function(){ + var t = Template.instance(); + + return t.curSrc.get(); + } +}) +Template.notice.events({ + 'click #notice_img':function(e,t){ + e.stopImmediatePropagation(); + e.preventDefault(); + var t = Template.instance(); + var type = Session.get('notice-from'); + if(type == 'timelineAlbum'){ + return; + }else if(type == 'deviceDashboard'){ + // if(t.curSrc.get()=='/hint.jpg'){ + // //t.curSrc.set('/createGroup2.jpg'); + // }else{ + // Session.set('showHint',false); + // } + Session.set('showHint',false); + }else if(type == 'createNewChatGroups'){ + return Router.go('/setGroupname'); + } + }, + 'click #back':function(e,t){ + e.stopImmediatePropagation(); + e.preventDefault(); + var type = Session.get('notice-from'); + if(type == 'timelineAlbum'){ + Session.set('showHint',false); + return; + } + if(type == 'deviceDashboard'){ + if(t.curSrc.get()=='/createGroup2.jpg'){ + t.curSrc.set('/hint.jpg'); + }else{ + Session.set('showHint',false); + } + } + if(type == 'createNewChatGroups'){ + // Router.go('/'); + PUB.back(); + } + } +}) diff --git a/client/homePage/homePage.less b/client/homePage/homePage.less new file mode 100644 index 000000000..a929ab765 --- /dev/null +++ b/client/homePage/homePage.less @@ -0,0 +1,208 @@ +.inOutPicPreview{ + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + z-index: 99999; + display: none; + .timeLayer{ + height: 64px; + line-height: 64px; + text-align: justify; + font-size: 22px; + color: #000; + text-align: center; + margin: 0 16px; + } + .imgLayer{text-align: center; margin-top:80px;position: relative; + .img_container{ + display: inline-block; + position: relative; + .img_item {height: 160px;width: 160px;object-fit: cover;} + } + .video-play-tip{ + position: absolute;top: 0;left: 0;width: 100%;height: 100%;display: none; + img{position: absolute;width: 40px !important; height: 40px !important; top: 50%;left: 50%;margin-left: -12px;margin-top: -12px;border: none; outline: none; } + } + } + .closeBtn{ + + } +} +.blur-element{ + -webkit-filter: blur(10px); + filter: blur(10px); +} + +.workstatusLoading{ + position:fixed; width:100%;text-align: center; margin-top:40%; color: black; font-size: 16px; + img{width: 110px;} +} + +.date_select{ + // background-color: rgba(255, 255, 255, 0.15); + color: #000; + position: relative; + .title{ text-align: center;padding: 0 80px;} + .select_btn{ + margin-left: 4px; + top: 0px; + color: #9E9E9E; + border-radius: 20px; + width: 30px; + position: absolute; + font-size: 25px; + text-align: center; + } + .day_before{ + left: 0px; + } + .day_after{ + right: 0px; + } + +} + +// .homePage #connection-lost-banner{ +// top: 80px !important; +// } + +.homePage{ + color: #555; + background: #efefef; + height: 100%; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + .companyLists{ + margin: 0; padding: 5px;overflow: hidden; width: 100%; background: #efefef; + position: absolute; top: 40px; bottom: 50px; overflow-y: auto;-webkit-overflow-scrolling : touch; + } + .companyItem{ + list-style: none; + float: left; + width: 50%; + padding: 5px; + text-align: center; + font-size: 12px; + .container{ + background: #fff; + border-radius: 4px; + padding: 32px 10px; + cursor: pointer; + width: 100%; + } + .logo{ + width: 48px; height: 48px; margin: 0 auto; margin-bottom: 32px; border-radius: 50%; border: 2px solid #efefef; + img{ + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; + } + } + h2{ + font-size: 18px; line-height:20px; margin:10px auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + .in-status,.out-status{position: relative;} + .out-status {display: inline-block;margin-left: 20px;} + .in-status:before, .out-status:before{ + position: absolute; + content: ""; + left: -10px; + top: 50%; + margin-top: -5px; + height: 8px; + width: 8px; + border-radius: 50%; + background: #009688; + } + .out-status:before{ background: orangered;} + } +} + +.companyUl{ + margin:10px 0; padding: 0; border-top: 1px solid #efefef; border-bottom: 1px solid #efefef;background: #fff; + position: relative; + li{ + list-style: none; + border-bottom: 1px solid #efefef; + line-height: 40px; + padding: 5px 10px 5px 10px; + height: 50px; + margin-left: 50px; + position: relative; + .icon{ + width: 22px; + height: 22px; + position: absolute; + left: -32px; + text-align: center; + img{width: 100%;height: 100%;} + .fa{color: #a3a3a3; font-size: 20px; line-height: 20px;padding-top: 8px;} + } + label{ + font-size: 16px; + font-size: 14px; + font-weight: normal; + } + .value { + float: right; + color: #39a8fe; + } + .fa { + width: 24px; + height: 12px; + text-align: center; + } + .arrow-icon-up, .arrow-icon-down { + background-size: 50% 100% !important; + background-position: center !important; + background-repeat: no-repeat !important; + float: right; + } + .arrow-icon-up { + background: url(/img/arrow-up.png); + } + .arrow-icon-down { + background: url(/img/arrow-down.png); + } + + } + li.mainInfo{ + margin: 0; + line-height: 34px; + padding: 5px 10px; + // height: 64px; + height: auto !important; + .icon{ + width: 40px; + height: 40px; + position: relative; + margin-right: 10px; + border-radius: 50%; + background: #efefef; + left: auto; + float: left; + img{width: 80%;height: 80%;margin: 10%;} + } + label{ + font-size: 16px; + font-weight: bold; + margin-bottom: 0; + } + .value { + // width: 96px; + } + .sort-arrow { + line-height: 0; + } + } +} + .companyUl.animate { + transition: top 300ms 0s; + } \ No newline at end of file diff --git a/client/homePage/homePage_bak.html b/client/homePage/homePage_bak.html new file mode 100644 index 000000000..c5deaf2ad --- /dev/null +++ b/client/homePage/homePage_bak.html @@ -0,0 +1,390 @@ + diff --git a/client/homePage/homePage_bak.js b/client/homePage/homePage_bak.js new file mode 100644 index 000000000..41388f3e8 --- /dev/null +++ b/client/homePage/homePage_bak.js @@ -0,0 +1,620 @@ +var showTimeLayerGroupUser = new ReactiveVar({}); + +Template.homePage1.onRendered(function () { + var now = new Date(); + var displayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , + 0, 0, 0, 0); + + Session.set('theCurrentDay',date); //UTC日期 + Session.set('theDisplayDay',displayDate); //当前显示的日期 + Session.set('today',displayDate); //今天 + Meteor.subscribe('group_devices',function(){ + Session.set('groupDevicesLoading',false); + }); + Meteor.subscribe('WorkStatus',date,{ + onReady:function(){ + Session.set('WorkStatusLoading',false); + } + }); + if(!Session.equals('homePagesForm', 'joinTestGroup')){ + $('body').append('
'); + } + Session.set('homePagesForm', '') + $('.content').scrollTop(Session.get('document_body_scrollTop')) +}); + +var parseDate = function(currentDay){ + //var today = new Date(Session.get('today')); + var year = currentDay.getFullYear(); + var month = currentDay.getMonth() + 1; + var date = year + '-' + month + '-' +currentDay.getDate(); + // if (currentDay.getDate() === today.getDate()) { + // date = date + ' 今天'; + // } + // else if (currentDay.getDate() - today.getDate() === -1 ) { + // date = date + ' 昨天'; + // } + //else { + var day = ''; + switch(currentDay.getDay()) + { + case 0: + day = '周日'; + break; + case 1: + day = '周一'; + break; + case 2: + day = '周二'; + break; + case 3: + day = '周三'; + break; + case 4: + day = '周四'; + break; + case 5: + day = '周五'; + break; + case 6: + day = '周六'; + break; + default: + break; + } + date = date + ' ' +day; + //} + return date; +}; + +Template.homePage1.helpers({ + timeLayer: function(){ + var _obj = showTimeLayerGroupUser.get(); + var status = WorkStatus.findOne({_id: _obj._id}); + + var time = null; + var result = {}; + if(_obj.in_out == 'in'){ + time = new Date(status.in_time); + time = time.shortTime(_obj.time_offset); + result.src = status.in_image; + result.video_src = status.in_video; + } else { + time = new Date(status.out_time); + time = time.shortTime(_obj.time_offset) + + result.src = status.out_image; + result.video_src = status.out_video; + } + result.time = time; + return result; + }, + isLoading:function(){ + if (Session.get('WorkStatusLoading') === false) { + return false; + } + return true; + }, + isLoadingGroup:function(group_id){ + if (Session.get('WorkStatusLoading_' + group_id) === true) { + return true; + } + return false; + }, + has_day_before:function(group_id){ + var currentDay = Session.get('theDisplayDay-'+group_id) || Session.get('theDisplayDay'); //当前显示的日期 + var today = Session.get('today'); //今天 + var lastday = today - 7 * 24 * 60 * 60 *1000; //7天前 + return currentDay > lastday; + }, + day_title:function(group_id){ + var currentDay = Session.get('theDisplayDay-'+group_id) || Session.get('theDisplayDay'); //当前显示的日期 + currentDay = new Date(currentDay); + return parseDate(currentDay); + }, + has_day_after:function(group_id){ + var currentDay = Session.get('theDisplayDay-'+group_id) || Session.get('theDisplayDay'); //当前显示的日期 + var today = Session.get('today'); //今天 + + // 可以查看后面两天天数据 + today = new Date(today); + today.setDate(today.getDate() + 2); + today = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime(); + return currentDay < today; + }, + show_back_today: function(group_id){ + var currentDay = Session.get('theDisplayDay-'+group_id) || Session.get('theDisplayDay'); //当前显示的日期 + var today = Session.get('today'); //今天 + return currentDay !== today; + }, + lists: function(){ + var lists = []; + SimpleChat.GroupUsers.find({user_id:Meteor.userId()},{sort:{create_time:-1}}).forEach(function(item){ + var devices = Devices.find({groupId: item.group_id},{sort:{createAt:-1}}).fetch(); + var obj = { + group_id: item.group_id, + group_name: item.group_name, + devices:[] + }; + if(devices.length > 0){ + // lists.push({ + // group_id: item.group_id, + // group_name: item.group_name, + // devices:devices + // }); + obj.devices = devices; + } + lists.push(obj); + }); + return lists; + }, + devices: function(){ + var group_id = Session.get('modifyMyStatus_group_id'); + var in_out = Session.get('modifyMyStatus_in_out'); + return Devices.find({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}).fetch(); + }, + workstatus: function(group_id){ + if(group_id){ + var date = Session.get('theCurrentDay-'+group_id) || Session.get('theCurrentDay'); + return WorkStatus.find({ + group_id: group_id, + date: date + }).fetch(); + } + return []; + }, + enable_push:function(app_notifaction_status){ + return app_notifaction_status === 'on'; + }, + //显示在监控组的状态(历史出现都离开监控组) + isStatusIN: function(status){ + if (this.in_time > 0) { + var date = new Date(this.in_time); + var time_offset = 8 + var group = SimpleChat.Groups.findOne({_id: this.group_id}); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + var fomatDate = date.shortTime(time_offset); + var isToday = fomatDate.indexOf('今天') > -1 ? true : false; + if (!isToday) { + return false; + } + } + return status === 'in'; + }, + //当天在监控组的状态 + isCurrentStatusIN:function(status){ + return status === 'in'; + }, + isInStatusNormal: function(in_status){ + return in_status === 'normal'; + }, + isInStatusWarning: function(in_status){ + return in_status === 'warning'; + }, + isInStatusError: function(in_status){ + return in_status === 'error'; + }, + isInStatusUnknown: function(in_status){ + return in_status === 'unknown'; + }, + + isOutStatusNormal: function(out_status){ + return out_status === 'normal'; + }, + isOutStatusWarning: function(out_status){ + return out_status === 'warning'; + }, + isOutStatusError: function(out_status){ + return out_status === 'error'; + }, + isOutStatusUnknown: function(out_status){ + return out_status === 'unknown'; + }, + isMySelf: function(app_user_id){ + return Meteor.userId() === app_user_id; + }, + InComTimeLen: function(group_id){ + var group_id = this.group_id; + var diff = 0; + var out_time = this.out_time; + var today_end = this.out_time; + var time_offset = 8 + var group = SimpleChat.Groups.findOne({_id: group_id}); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + + function DateTimezone(d, time_offset) { + if (time_offset == undefined){ + if (d.getTimezoneOffset() == 420){ + time_offset = -7 + }else { + time_offset = 8 + } + } + // 取得 UTC time + var utc = d.getTime() + (d.getTimezoneOffset() * 60000); + var local_now = new Date(utc + (3600000*time_offset)) + var today_now = new Date(local_now.getFullYear(), local_now.getMonth(), local_now.getDate(), + local_now.getHours(), local_now.getMinutes()); + + return today_now; + } + + //计算out_time + if(this.in_time) { + //var today_start_utc = new Date(Date.now()).setUTCHours(0,0,0,0); + + var date = new Date(this.in_time); + var fomatDate = date.shortTime(time_offset); + var isToday = fomatDate.indexOf('今天') > -1 ? true : false; + //不是今天的时间没有out_time的或者是不是今天时间,最后一次拍到的是进门的状态的都计算到当天结束 + if((!out_time && !isToday) || (this.status === 'in' && !isToday)) { + date = DateTimezone(date,time_offset); + day_end = new Date(date).setHours(23,59,59); + //day_end = new Date(this.in_time).setUTCHours(0,0,0,0) + (24 - time_offset)*60*60*1000 - 1; + out_time = day_end; + this.in_time = date.getTime(); + } + //今天的时间(没有离开过监控组) + else if(!out_time && isToday) { + var now_time = Date.now(); + out_time = now_time; + } + //今天的时间(离开监控组又回到监控组) + else if(out_time && this.status === 'in' && isToday) { + var now_time = Date.now(); + out_time = now_time; + } + } + + if(this.in_time && out_time){ + diff = out_time - this.in_time; + } + + if(diff > 24*60*60*1000) + diff = 24*60*60*1000; + else if(diff < 0) + diff = 0; + + var min = diff / 1000 / 60 ; + var hour = Math.floor(min/60)+' h '+Math.floor(min%60) + ' min'; + if(min < 60){ + hour = Math.floor(min%60) + ' min'; + } + if(diff == 0){ + hour = '0 min'; + } + return hour; + }, + modifyStatusClass: function(app_user_id){ + if(Meteor.userId() !== app_user_id){ + return 'modifyTaStatus'; + } + return 'modifyMyStatus'; + }, + whatsUpLists: function(){ + console.log(this); + var lists = []; + if(typeof(this.whats_up) == 'string'){ // 旧数据兼容 + lists.push({ + person_name: this.person_name, + content: this.whats_up, + ts: this.in_time // 当天上班时间 + }); + } else { + lists = this.whats_up; + } + return lists; + }, + getShortTime: function(ts,group_id){ + var time_offset = 8 + var group = SimpleChat.Groups.findOne({_id: group_id}); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + var time = new Date(ts); + return time.shortTime(time_offset,true); + }, + + isHistoryNoOut: function(group_id){ + var outtime = 0; + + if(this.out_time){ + outtime = this.out_time; + } + var isHistory = false; + if(Session.get('theDisplayDay-'+group_id) && Session.get('theDisplayDay-'+group_id) !== Session.get('today')){ + isHistory = true; + } + var noOut = false; + if(outtime == 0){ + noOut = true; + } + return isHistory && noOut; + } +}); +modifyStatusFun = function(group_id,in_out,taId){ + if (!group_id || !in_out) { + return; + } + Session.set('modifyMyStatus_ta_id',null); + var deviceCount = Devices.find({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}).count(); + console.log(in_out); + console.log(group_id); + console.log(deviceCount); + if(deviceCount === 0){ + return PUB.toast('未找到该群组下,方向为"'+in_out+'"的设备'); + } + if(deviceCount === 1){ + var device = Devices.findOne({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}) + Session.set('wantModify',true); + if(taId){ + return PUB.page('/timelineAlbum/'+device.uuid+'?taId='+taId); + } + return PUB.page('/timelineAlbum/'+device.uuid); + } + if(deviceCount > 1){ + Session.set('modifyMyStatus_group_id',group_id); + Session.set('modifyMyStatus_in_out',in_out); + Session.set('modifyMyStatus_ta_id',taId); + $('.homePage .content').addClass('content_box'); + $('.user .content').addClass('content_box'); + return $('#selectDevicesInOut').modal('show'); + } +}; + +Template.homePage1.events({ + 'click .deviceItem': function(e){ + $('#selectDevicesInOut').modal('hide'); + $('.homePage .content').removeClass('content_box'); + var taId = Session.get('modifyMyStatus_ta_id'); + var pageUrl = '/timelineAlbum/'+e.currentTarget.id; + Session.set('wantModify',true); + if(taId){ + pageUrl = '/timelineAlbum/'+e.currentTarget.id+'?taId='+taId; + } + setTimeout(function(){ + PUB.page(pageUrl); + },1000); + }, + 'click .editWhatsUp':function(e){ + var _id = e.currentTarget.id; + var whats_up = $(e.currentTarget).data('whatsup'); + $('.homePage .content').addClass('content_box'); + $('.saveWhatsUp').attr('id',_id); + Session.set('editWhatsUpData',this); + $('#EditorWhatsUp').val(''); + $('#myModal').modal('show'); + setTimeout(function(){ + $('#EditorWhatsUp').focus(); + },1000); + }, + 'click .saveWhatsUp':function(e){ + var _id = e.currentTarget.id; + var content = $('#EditorWhatsUp').val(); + if(!content || content.length < 1 || content.replace(/\s+/gim, '').length ==0){ // 内容为空或者全是空白字符, 不能提交 + $('#EditorWhatsUp').val(''); + return $('#EditorWhatsUp').focus(); + } + var data = Session.get('editWhatsUpData'); + console.log(data); + $('#'+_id).data('whatsup',whats_up); + var group_id = $('#'+_id).data('groupid'); + var statusId = $('#'+_id).data('id'); + var group = SimpleChat.GroupUsers.findOne({group_id:group_id,user_id: Meteor.userId()}); + console.log("group info is:", JSON.stringify(group)); + var editorName = group.user_name; + //whats_up = editorName + ":" + whats_up; + + var whats_up = data.whats_up || [] + if(whats_up && typeof(whats_up) === 'string'){ + whats_up = [{ + person_name: data.person_name, + content: whats_up, + ts: data.in_time // 当天上班时间 + }] + } + + whats_up.push({ + person_name: editorName, + content: content, + ts: Date.now() + }); + + WorkStatus.update({_id:_id},{ + $set:{whats_up:whats_up} + },function(err,num){ + if(err){ + return PUB.toast('请重试'); + } + if(!group){ + return; + } + var msgObj = { + _id: new Mongo.ObjectID()._str, + form:{ + id: group.user_id, + name: group.user_name, + icon: group.user_icon + }, + to: { + id: group.group_id, + name: group.group_name, + icon: group.group_icon + }, + to_type: 'group', + type: 'text', + text: '更新了今日简述:\r\n'+content, + create_time: new Date(), + is_read: false, + // send_status: 'sending' + }; + console.log(msgObj) + sendMqttGroupMessage(group_id,msgObj); + }); + $('#myModal').modal('hide'); + $('.homePage .content').removeClass('content_box'); + }, + 'click .close ,click .cancelWhatsUp':function(e){ + $('.homePage .content').removeClass('content_box'); + }, + 'click .modifyMyStatus':function(e){ + var group_id = e.currentTarget.id; + var in_out = $(e.currentTarget).data('inout'); + var currentDay = Session.get('theDisplayDay-'+group_id) || Session.get('theDisplayDay'); //当前显示的日期 + currentDay = new Date(currentDay); + currentDay.setHours(23); + currentDay.setMinutes(59); + Session.set('wantModifyTime',currentDay); + modifyStatusFun(group_id,in_out); + }, + 'click .modifyTaStatus': function(e){ + var group_id = e.currentTarget.id; + var in_out = $(e.currentTarget).data('inout'); + var taId = $(e.currentTarget).data('taid'); + var taName = $(e.currentTarget).data('taname'); + var currentDay = Session.get('theDisplayDay-'+group_id) || Session.get('theDisplayDay'); //当前显示的日期 + currentDay = new Date(currentDay); + currentDay.setHours(23); + currentDay.setMinutes(59); + Session.set('wantModifyTime',currentDay); + Session.set('modifyMyStatus_ta_name',taName); + + modifyStatusFun(group_id, in_out, taId); + // navigator.notification.confirm('要帮「'+taName+'」出现吗?',function(index){ + // if(index === 2){ + // modifyStatusFun(group_id, in_out, taId); + // } + // },'提示',['取消','帮TA出现']); + }, + 'click .in-out-pic': function(e){ + e.stopImmediatePropagation(); + var src = $(e.currentTarget).attr('src') + var time = new Date($(e.currentTarget).data('time')); + var group_id = $(e.currentTarget).data('groupid') + var time_offset = 8 + // if (group_id == '73c125cc48a83a95882fced3'){ + // //SWLAB + // time_offset = -7 + // }else if (group_id == 'd2bc4601dfc593888618e98f'){ + // //Kuming LAB + // time_offset = 8 + // } + + var group = SimpleChat.Groups.findOne({_id: group_id}); + if (group && group.offsetTimeZone) { + time_offset = group.offsetTimeZone; + } + + // $('.timeLayer').html(time.shortTime(time_offset)); + // $('.imgLayer img.img_item').attr('src',src); + // var video_src = this.in_video || this.out_video; + var in_out = $(e.currentTarget).data('inout'); + var video_src = null; + if(in_out == 'in'){ + video_src = this.in_video; + } + if(in_out == 'out'){ + video_src = this.out_video; + } + + showTimeLayerGroupUser.set({ + _id: this._id, + in_out: in_out, + time_offset: time_offset + }); + // if (video_src) { + // $('.img_container .video-play-tip').show(); + // $('.img_container').addClass('videos'); + // $('.img_container').data('videosrc',video_src); + // } + $('.homePage').addClass('blur-element'); + $('#footer').addClass('blur-element'); + $('.inOutPicPreview').fadeIn('fast'); + }, + 'click .inOutPicPreview': function(e){ + $('.img_container .video-play-tip').hide(); + $('.img_container').data('videosrc',''); + $('.img_container').removeClass('videos'); + $('.blur-element').removeClass('blur-element'); + $('.inOutPicPreview').fadeOut('fast'); + }, + 'click .videos':function(e){ + e.stopImmediatePropagation(); + var video_src = $(e.currentTarget).data('videosrc'); + openVideoInBrowser(video_src); + }, + 'click .check_in_out':function(e){ + Router.go('/timeline'); + }, + /*'click .panel-heading':function(e){ + var group_id = e.currentTarget.id; + var group = SimpleChat.GroupUsers.findOne({group_id:group_id,user_id:Meteor.userId()}); + if (group.companyId) { + Session.set('reportUrl', group.perf_info.reportUrl); + Router.go('perfShow'); + } + else{ + PUB.toast('尚未绑定监控组~快去扫描绩效二维码进行绑定吧!'); + } + },*/ + 'click .day_before':function(e){ + e.stopImmediatePropagation(); + var group_id = $(e.currentTarget).data('groupid'); + var currentDay = Session.get('theCurrentDay-'+group_id) || Session.get('theCurrentDay'); + currentDay = currentDay - 24 * 60 * 60 * 1000; + Session.set('theCurrentDay-'+group_id, currentDay); + + var theDisplayDay = Session.get('theDisplayDay-'+group_id) || Session.get('theDisplayDay'); + theDisplayDay = theDisplayDay - 24 * 60 * 60 * 1000; + Session.set('theDisplayDay-'+group_id, theDisplayDay); + + + Session.set('WorkStatusLoading_' + group_id,true); + console.log(currentDay) + console.log(group_id) + Meteor.subscribe('WorkStatusByGroup',currentDay,group_id,{ + onReady:function(){ + Session.set('WorkStatusLoading_' + group_id,false); + } + }); + }, + 'click .day_after':function(e){ + e.stopImmediatePropagation(); + var group_id = $(e.currentTarget).data('groupid'); + var currentDay = Session.get('theCurrentDay-'+group_id) || Session.get('theCurrentDay'); + currentDay = currentDay + 24 * 60 * 60 * 1000; + Session.set('theCurrentDay-'+group_id, currentDay); + + var theDisplayDay = Session.get('theDisplayDay-'+group_id) || Session.get('theDisplayDay'); + theDisplayDay = theDisplayDay + 24 * 60 * 60 * 1000; + Session.set('theDisplayDay-'+group_id, theDisplayDay); + + Session.set('WorkStatusLoading_' + group_id,true); + Meteor.subscribe('WorkStatusByGroup',currentDay,group_id,{ + onReady:function(){ + Session.set('WorkStatusLoading_' + group_id,false); + } + }); + }, + 'click .day_today': function(e){ + e.stopImmediatePropagation(); + var group_id = $(e.currentTarget).data('groupid'); + var currentDay = Session.get('theCurrentDay'); + Session.set('theCurrentDay-'+group_id, currentDay); + + var theDisplayDay = Session.get('theDisplayDay'); + Session.set('theDisplayDay-'+group_id, theDisplayDay); + + Session.set('WorkStatusLoading_' + group_id,true); + Meteor.subscribe('WorkStatusByGroup',currentDay,group_id,{ + onReady:function(){ + Session.set('WorkStatusLoading_' + group_id,false); + } + }); + } +}); diff --git a/client/homePage/qrcode_device_add.js b/client/homePage/qrcode_device_add.js new file mode 100644 index 000000000..7e0dad837 --- /dev/null +++ b/client/homePage/qrcode_device_add.js @@ -0,0 +1,84 @@ +window.QRCodeAddDevice = function() { + cordova.plugins.barcodeScanner.scan( + function(result) { + /* + Result: f681ffe35abf + Format: QR_CODE + Cancelled: 0 + */ + var checkDeviceQRCode = function(code){ + var regex = /^[a-z0-9]{12,64}$/; + if(!regex.test(code)){ + return false; + } + return true; + } + if (!checkDeviceQRCode(result.text)) { + PUB.toast('请扫描有效的脸脸盒二维码!'); + return; + } + console.log("We got a barcode\n" + + "Result: " + result.text + "\n" + + "Format: " + result.format + "\n" + + "Cancelled: " + result.cancelled); + var gotoPage = '/'; + //var requiredStr = rest_api_url+'/simple-chat/to/group?id=' + if (result.text) { + console.log('device barcode is '+result.text ) + + var self = { + "domain":"local.", + "port":8000, + "txtRecord":{ + "macAddress":result.text, + "uuid":result.text + },"ipv4Addresses":[""], + "hostname":"deepeyeBox.local.", + "ipv6Addresses":[], + "type":"_DeepEye._tcp.", + "name":"deepeyeBox", + "uuid":result.text, + "get_group_only": true, + "isInDB":false + } + console.log("self = "+JSON.stringify(self)); + return window.SELECT_CREATE_GROUP.show(self, function(group_id, group_name) { + //var msgBody = {_id: new Mongo.ObjectID()._str,group_id:group_id, uuid: result.text, type: 'text', text: 'groupchanged'}; + Meteor.call('join-group',result.text,group_id,result.text,"in",function(err,result){ + console.log('meteor call result:',result) + PUB.toast('您的设备已经切换到:'+group_name) + //sendMqttMessage('/msg/d/'+result.text, msgBody); + }); + //$.post("http://workaihost.tiegushi.com/restapi/workai-join-group", {uuid: result.text, group_id: group_id, name: result.text, in_out: "in"}, function(data) { + // var msgBody = {_id: new Mongo.ObjectID()._str, uuid: result.text, type: 'text', text: 'groupchanged'}; + // sendMqttMessage('/msg/d/'+result.text, msgBody); + //}); + //toastr('已添加设备:'+result.text) + window.SELECT_CREATE_GROUP.close() + Router.go('/'); + + }); + } else if (result.cancelled) { + //Router.go(gotoPage); + return; + } else if (result.alumTapped) { + //DecodeImageFromAlum(); + return; + } + }, + function(error) { + alert("Scanning failed: " + error); + }, { + preferFrontCamera: false, // iOS and Android + showFlipCameraButton: true, // iOS and Android + showTorchButton: true, // iOS and Android + torchOn: false, // Android, launch with the torch switched on (if available) + prompt: "Place a barcode inside the scan area", // Android + resultDisplayDuration: 500, // Android, display scanned text for X ms. 0 suppresses it entirely, default 1500 + formats: "QR_CODE,PDF_417", // default: all but PDF_417 and RSS_EXPANDED + orientation: "landscape", // Android only (portrait|landscape), default unset so it rotates with the device + //disableAnimations: true, // iOS + //disableSuccessBeep: false // iOS + } + ); +} diff --git a/client/homePage/scanTips/scanTips.html b/client/homePage/scanTips/scanTips.html new file mode 100644 index 000000000..807b13234 --- /dev/null +++ b/client/homePage/scanTips/scanTips.html @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/client/homePage/scanTips/scanTips.js b/client/homePage/scanTips/scanTips.js new file mode 100644 index 000000000..a49bd56ec --- /dev/null +++ b/client/homePage/scanTips/scanTips.js @@ -0,0 +1,103 @@ +maskDescription = new ReactiveVar({ + maskEllipse: true, + cx: '100%', + cy: '0%', + rx: '20', + ry: '20', + trsx: -25, + trsy: 20, + scantipContainerStyle: "position: absolute; top: 10px; right: 50px;", + iconClass: 'fa fa-3x fa-hand-o-right', + iconStyle: 'display: block;', + spanStyle: 'display: block; position: relative; left: -10px;', + spanContent: '点击加号' +}); +currentTip = 'timeLineTab'; + +Template.scanTipHintTemplate.rendered = function () { + $('body').addClass('scantip-html-body'); +}; + +Template.scanTipHintTemplate.helpers({ + maskDesc: function() { + return maskDescription.get(); + } +}); + +function hideScanTipLayer(){ + $('body').removeClass('scantip-html-body'); + showScanTipHint.set(false); + if (currentTip == 'timeLineTab') { + maskDescription.set({ + maskEllipse: false, + x: '58%', + y: '100%', + width: '50', + height: '50', + trsx: 0, + trsy: -50, + scantipContainerStyle: "position: absolute; bottom: 0px; width: 100%; height: 150px;", + iconClass: 'fa fa-3x fa-hand-o-down', + iconStyle: 'display: block; position: absolute; bottom: 60px; left: 59%;', + spanStyle: 'display: block; font-size: 18px; white-space: nowrap; position: absolute; bottom: 110px; left: 59%; transform: translateX(-50%);', + spanContent: '点击消息查看识别动态' + }); + currentTip = 'messageTab'; + showScanTipHint.set(true); + } + else if (currentTip == 'messageTab') { + maskDescription.set({ + maskEllipse: true, + cx: '100%', + cy: '0%', + rx: '20', + ry: '20', + trsx: -25, + trsy: 20, + scantipContainerStyle: "position: absolute; top: 0px; width: 100%; height: 150px;", + iconClass: 'fa fa-3x fa-hand-o-right', + iconStyle: 'display: block; position: absolute; right: 60px; top: 5px;', + spanStyle: 'display: block; font-size: 18px; text-align: right; position: absolute; top: 10px; right: 120px;', + spanContent: '点击加号' + }); + currentTip = 'plusMark'; + showScanTipHint.set(true); + } + else if (currentTip == 'plusMark') { + localStorage.setItem('scantipFlag',true); + } + else if (currentTip == 'createCompanyMenu') { + maskDescription.set({ + maskEllipse: false, + x: '100%', + y: '0%', + width: '180', + height: '40', + trsx: -190, + trsy: 225, + scantipContainerStyle: "position: absolute; top: 225px; width: 100%; height: 150px;", + iconClass: 'fa fa-3x fa-hand-o-up', + iconStyle: 'display: block; position: absolute; top: 50px; right: 100px;', + spanStyle: 'display: block; font-size: 18px; position: absolute; top: 5px; right: 200px;', + spanContent: '点击此处扫码添加脸脸盒' + }); + currentTip = 'scanFaceBox'; + showScanTipHint.set(true); + } + else if (currentTip == 'scanFaceBox') { + localStorage.setItem('createCompanyFlag',true); + } +} + +Template.scanTipHintTemplate.events({ + 'click .scantip-layer': function(e) { + e.preventDefault(); + e.stopPropagation(); + hideScanTipLayer(); + }, + 'touchstart .scantip-layer': function(e) { + e.preventDefault(); + e.stopPropagation(); + hideScanTipLayer(); + }, +}); diff --git a/client/homePage/scanTips/scanTips.less b/client/homePage/scanTips/scanTips.less new file mode 100644 index 000000000..37d17385a --- /dev/null +++ b/client/homePage/scanTips/scanTips.less @@ -0,0 +1,27 @@ +.scantip-layer{ + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 9999999; +} +.scantip-container{ + position: absolute; + bottom: 60px; + width: 100%; + text-align: center; +} +.scantip-html-body{overflow-y: hidden !important;} +#confirmScanTipHintTip{ + background: url(/scantip/scanhint3.png); + background-repeat: no-repeat; + background-size: 100%; + width: 140px; + height: 60px; + margin-top: 20px; + border: none; + outline: none; + cursor: pointer; + z-index: 999999; +} \ No newline at end of file diff --git a/client/homePage/workStatusPopPage.html b/client/homePage/workStatusPopPage.html new file mode 100644 index 000000000..a96aa3c4b --- /dev/null +++ b/client/homePage/workStatusPopPage.html @@ -0,0 +1,286 @@ + + + + \ No newline at end of file diff --git a/client/homePage/workStatusPopPage.js b/client/homePage/workStatusPopPage.js new file mode 100644 index 000000000..18ced7069 --- /dev/null +++ b/client/homePage/workStatusPopPage.js @@ -0,0 +1,812 @@ +var view = null; +var clusterView = null; + +var isLoading = new ReactiveVar(false); +var group = new ReactiveVar({}); +var theCurrentDay = new ReactiveVar(null); +var theDisplayDay = new ReactiveVar(null); +var today = new ReactiveVar(null); +var todayUTC = new ReactiveVar(null); + +var whatsup = new ReactiveVar({}); +var dateList = new ReactiveVar([]); +var curTime = new ReactiveVar(null); + //前7天加入dateList +var _dateList = []; +for(var i=7;i>-1;i--){ + var t = moment().subtract(i, 'day'); + var weekStr = t.format('ddd'); + var d = t.format('MM/DD'); + var utc = Date.UTC(t.year(),t.month(),t.date(),0,0,0,0); + _dateList.push({ + t:t, + week:weekStr, + date:d, + utc:utc, + id:(7-i)+'date' + }) + } +dateList.set(_dateList); +workStatusPopPage = { + show: function(template, showClose){ + workStatusPopPage.close(); + var data = Session.get('workstatus_group') + view = Blaze.renderWithData(Template.workStatusPopPage, data, document.body); + }, + display: function(){ + $('.workStatusPopPage').show(); + }, + hide: function(){ + $('.workStatusPopPage').hide(); + }, + close: function(){ + if(view){ + Blaze.remove(view); + view = null; + } + }, + isShow: function(){ + return view != null; + } +}; + +var modifyStatusFun = function(group_id,in_out,taId){ + if (!group_id || !in_out) { + return; + } + Session.set('modifyMyStatus_ta_id',null); + var deviceCount = Devices.find({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}).count(); + console.log(in_out); + console.log(group_id); + console.log(deviceCount); + if(deviceCount === 0){ + return PUB.toast('未找到该群组下,方向为"'+in_out+'"的设备'); + } + if(deviceCount === 1){ + workStatusPopPage.close(); + var device = Devices.findOne({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}) + Session.set('wantModify',true); + if(taId){ + return PUB.page('/timelineAlbum/'+device.uuid+'?taId='+taId); + } + return PUB.page('/timelineAlbum/'+device.uuid); + } + if(deviceCount > 1){ + Session.set('modifyMyStatus_group_id',group_id); + Session.set('modifyMyStatus_in_out',in_out); + Session.set('modifyMyStatus_ta_id',taId); + $('.homePage .content').addClass('content_box'); + $('.user .content').addClass('content_box'); + return $('#selectDevicesInOut').modal('show'); + } +}; + +var parseDate = function(currentDay){ + //var today = new Date(Session.get('today')); + var year = currentDay.getFullYear(); + var month = currentDay.getMonth() + 1; + var date = year + '-' + month + '-' +currentDay.getDate(); + // if (currentDay.getDate() === today.getDate()) { + // date = date + ' 今天'; + // } + // else if (currentDay.getDate() - today.getDate() === -1 ) { + // date = date + ' 昨天'; + // } + //else { + var day = ''; + switch(currentDay.getDay()) + { + case 0: + day = '周日'; + break; + case 1: + day = '周一'; + break; + case 2: + day = '周二'; + break; + case 3: + day = '周三'; + break; + case 4: + day = '周四'; + break; + case 5: + day = '周五'; + break; + case 6: + day = '周六'; + break; + default: + break; + } + date = date + ' ' +day; + //} + return date; +}; + +Template.workStatusPopPage.onRendered(function(){ + var data = this.data; // groups info + group.set(data); + + var now = new Date(); + var swiper = new Swiper('#slideDate2',{ + slidesPerView :'auto', + resistanceRatio : 0, + onInit: function(s){ + //Swiper初始化了 + }, + onClick: function(s){ + var index = s.clickedIndex; + var _curTime = dateList.get()[index]; + isLoading.set(true); + Meteor.subscribe('group_workstatus', group.get()._id, _curTime.utc, { + onReady:function(){ + isLoading.set(false); + } + }); + // alert(s.clickedIndex); + $('#'+curTime.get().id).removeClass('selected'); + curTime.set(_curTime); + $('#'+_curTime.id).addClass('selected'); + } + }); + swiper.slideTo(7,0,false); + curTime.set(dateList.get()[7]); + $('#'+curTime.get().id).addClass('selected'); + var displayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , + 0, 0, 0, 0); + + theCurrentDay.set(date); // UTC日期 + todayUTC.set(date); + theDisplayDay.set(displayDate); // 当前显示日期 + today.set(displayDate); // 今天 + isLoading.set(true); + Meteor.subscribe('group_workstatus',data._id, curTime.get().utc,{ + onReady:function(){ + isLoading.set(false); + } + }); +}); +Template.workStatusPopPage.helpers({ + dateList:function(){ + return dateList.get(); + }, + has_day_before:function(group_id){ + var lastday = today.get() - 7 * 24 * 60 * 60 *1000; //7天前 + return theDisplayDay.get() > lastday; + }, + day_title:function(){ + var currentDay = new Date(theDisplayDay.get()); + return parseDate(currentDay); + }, + has_day_after:function(group_id){ + // 可以查看后面两天天数据 + _today = new Date(today.get()); + _today.setDate(_today.getDate() + 2); + _today = new Date(_today.getFullYear(), _today.getMonth(), _today.getDate()).getTime(); + return theDisplayDay.get() < _today; + }, + isLoading: function() { + return isLoading.get(); + }, + hasWorkStatus: function () { + return WorkStatus.find({group_id: group.get()._id,date: curTime.get().utc}).count() > 0; + }, + lists: function(){ + var frList =[]; + //查询今日出现的人 并且存在frList数组中 + WorkStatus.find({group_id: group.get()._id, date:curTime.get().utc}).forEach(function(item){ + if (item.in_time || item.out_time) { + frList.push(item); + } + }) + //查找重复出现的人并且删除此人 + WorkStatus.find({group_id: group.get()._id, date:curTime.get().utc}).forEach( function (item,index) { + if (item.in_time || item.out_time) { + for (var i = index + 1; i < frList.length; i++) { + if(item.person_name && item.person_name == frList[i].person_name){ + //删除重复出现的人 + frList.splice(i,1) + } + } + } + }); + + // var lists = []; + // WorkStatus.find({group_id: group.get()._id,date: curTime.get().utc}).forEach( function (item) { + // if (item.in_time || item.out_time) { + // lists.push(item); + // } + // }); + return frList; + }, + devices: function(){ + var group_id = Session.get('modifyMyStatus_group_id') || group.get()._id; + var in_out = Session.get('modifyMyStatus_in_out'); + return Devices.find({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}).fetch(); + }, + getIcon: function(){ + // return this.out_image || this.in_image; + if(this.in_time && this.in_image) { + return this.in_image; + } + if(this.out_time && this.out_image) { + return this.out_image; + } + }, + enable_push:function(){ + if (this.app_notifaction_status && this.app_notifaction_status === 'on') { + return 'text-success'; + } + return 'text-gray'; + }, + bind_app_user: function() { + if (this.app_user_id) { + return 'text-success'; + } + return 'text-gray'; + }, + getInOutStatus: function(){ + if (this.in_time > 0) { // 不是今天一律算 out + var date = new Date(this.in_time); + var time_offset = 8 + var _group = group.get(); + if (_group && _group.offsetTimeZone) { + time_offset = _group.offsetTimeZone; + } + var fomatDate = date.shortTime(time_offset); + var isToday = fomatDate.indexOf('今天') > -1 ? true : false; + if (!isToday) { + return 'bg-gray'; + } + } + if(this.status == 'in'){ + return 'bg-success'; + } + return 'bg-gray'; + }, + getStatus: function(type){ + var status = ''; + if(type == 'in'){ + status = this.in_status; + } + if(type == 'out'){ + status = this.out_status; + } + switch (status) { + case 'normal': + return 'bg-success'; + break; + case 'warning': + return 'bg-warning'; + break; + case 'error': + return 'bg-error'; + break; + case 'unknown': + return 'bg-gray'; + break; + default: + return 'bg-gray'; + break; + } + }, + getTime: function(type){ + var time = null; + if(type == 'in'){ + time = this.in_time; + } + if(type == 'out') { + time = this.out_time; + if(this.status == 'in'){ + time = null; + } + } + var time_offset = 8; + var _group = group.get(); + if (_group && _group.offsetTimeZone) { + time_offset = _group.offsetTimeZone; + } + if(time){ + // time = new Date(time); + // time = time.shortTime(time_offset) + time = moment(time).utcOffset(time_offset).format('ahh:mm'); + } else { + time = '--:--'; + } + return time; + }, + getWhatsup: function(){ + if(this.whats_up && this.whats_up.length > 0){ + return this.whats_up[0]; + } + return '今天还没有工作安排...'; + }, + whatsup: function(){ + return whatsup.get(); + }, + getShortTime: function(ts,group_id){ + var time_offset = 8 + var _group = group.get(); + if (_group && _group.offsetTimeZone) { + time_offset = _group.offsetTimeZone; + } + var time = new Date(this.ts); + return time.shortTime(time_offset,true); + }, + _checkGroupDevice_status: function() { + var status = Session.get('_checkGroupDevice_status'); + console.log("_checkGroupDevice_status status="+status); + $('.workStatusPopPage').css('z-index', 999990) + if (status == 'status_open_device') { + $('.workStatusPopPage').css('display', 'none') + } + } +}); + + +Template.workStatusPopPage.events({ + 'click #switch': function(e) { + //console.log("Frank.switch: this="+JSON.stringify(this)); + /* + Session.set('clusterworkstatus_group', this); + return clusterWorkStatusPopPage.show(); + */ + // get the device list + var group_id = Session.get('modifyMyStatus_group_id') || group.get()._id; + console.log('group id is: ',group_id); + + var deviceLists = Devices.find({groupId: group_id}).fetch(); + console.log("device lists is: ", JSON.stringify(deviceLists)); + + if (deviceLists && deviceLists.length > 0) { + if(deviceLists.length == 1 && deviceLists[0].uuid) { + console.log("enter this device timeline") + workStatusPopPage.close(); + return PUB.page('/timelineAlbum/'+deviceLists[0].uuid+'?from=groupchat'); + } else { + console.log("select a device") + Session.set('_groupChatDeviceLists',deviceLists); + //workStatusPopPage.close(); + $('._checkGroupDevice').fadeIn(); + //workStatusPopPage.hide(); + $('.workStatusPopPage').css('z-index', 2000) + return; + } + } + return PUB.toast('该监控组下暂无脸脸盒'); + }, + 'click #closeStausPop': function(){ + return workStatusPopPage.close(); + }, + 'click .back':function(){ + return workStatusPopPage.close(); + }, + // edit What's up + 'click .editWhatsup': function(e){ + whatsup.set({ + _id: this._id, + content: this.whats_up, + name: this.person_name + }); + + $('#EditorWhatsUp').val(''); + $('#myModal').modal('show'); + setTimeout(function(){ + $('#EditorWhatsUp').focus(); + },1000); + }, + // save whats up + 'click .saveWhatsUp':function(e){ + var _whatsup = whatsup.get(); + var content = $('#EditorWhatsUp').val(); + if(!content || content.length < 1 || content.replace(/\s+/gim, '').length ==0){ // 内容为空或者全是空白字符, 不能提交 + $('#EditorWhatsUp').val(''); + return $('#EditorWhatsUp').focus(); + } + console.log(_whatsup) + var whats_up = _whatsup.content || []; + + whats_up.push({ + person_name: _whatsup.name, + content: content, + ts: Date.now() + }); + + WorkStatus.update({_id:_whatsup._id},{ + $set:{whats_up:whats_up} + },function(err,num){ + if(err){ + return PUB.toast('请重试'); + } + var _group = group.get(); + var user = Meteor.user(); + var msgObj = { + _id: new Mongo.ObjectID()._str, + form:{ + id: user._id, + name: user.profile.fullname?user.profile.fullname:user.username, + icon: user.profile.icon + }, + to: { + id: _group._id, + name: _group.name, + icon: _group.icon + }, + to_type: 'group', + type: 'text', + text: '更新了今日简述:\r\n'+content, + create_time: new Date(), + is_read: false, + // send_status: 'sending' + }; + console.log(msgObj) + sendMqttGroupMessage(_group._id,msgObj); + }); + $('#myModal').modal('hide'); + $('.homePage .content').removeClass('content_box'); + }, + 'click .cancelWhatsUp':function(e){ + $('.homePage .content').removeClass('content_box'); + }, + // 修改上班时间 + 'click .editInTime': function(e) { + var isMyself = false; + if( this.app_user_id && this.app_user_id == Meteor.userId() ){ + isMyself = true; + } + + var group_id = group.get()._id; + + // var currentDay = theDisplayDay.get(); //当前显示的日期 + // currentDay = new Date(currentDay); + // currentDay.setHours(23); + // currentDay.setMinutes(59); + var t = curTime.get().t; + var currentDay = new Date(t.year(),t.month(),t.date(),23,59,0); + Session.set('wantModifyTime',currentDay); + + if(isMyself){ + modifyStatusFun(group_id,'in'); + } else { + modifyStatusFun(group_id, 'in', this.app_user_id); + } + }, + // 修改下班时间 + 'click .editOutTime': function(e) { + var isMyself = false; + if( this.app_user_id && this.app_user_id == Meteor.userId() ){ + isMyself = true; + } + + var group_id = group.get()._id; + + // var currentDay = theDisplayDay.get(); //当前显示的日期 + // currentDay = new Date(currentDay); + // currentDay.setHours(23); + // currentDay.setMinutes(59); + // Session.set('wantModifyTime',currentDay); + var t = curTime.get().t; + var currentDay = new Date(t.year(),t.month(),t.date(),23,59,0); + Session.set('wantModifyTime',currentDay); + + if(isMyself){ + modifyStatusFun(group_id,'out'); + } else { + modifyStatusFun(group_id, 'out', this.app_user_id); + } + }, + // goNextDay + 'click .nextDay': function(e) { + e.stopImmediatePropagation(); + var currentDay = theCurrentDay.get() + 24 * 60 * 60 * 1000; + theCurrentDay.set(currentDay); + + var displayDay = theDisplayDay.get() + 24 * 60 * 60 * 1000; + theDisplayDay.set(displayDay); + + isLoading.set(true); + Meteor.subscribe('group_workstatus', group.get()._id, theCurrentDay.get(), function() { + isLoading.set(false); + }); + }, + // goPrevDay + 'click .prevDay': function(e) { + e.stopImmediatePropagation(); + var currentDay = theCurrentDay.get() - 24 * 60 * 60 * 1000; + theCurrentDay.set(currentDay); + + var displayDay = theDisplayDay.get() - 24 * 60 * 60 * 1000; + theDisplayDay.set(displayDay); + + isLoading.set(true); + Meteor.subscribe('group_workstatus',group.get()._id, theCurrentDay.get(), function() { + isLoading.set(false); + }); + }, + 'click .deviceItem': function(e){ + $('#selectDevicesInOut').modal('hide'); + $('.homePage .content').removeClass('content_box'); + var taId = Session.get('modifyMyStatus_ta_id'); + var pageUrl = '/timelineAlbum/'+e.currentTarget.id; + Session.set('wantModify',true); + if(taId){ + pageUrl = '/timelineAlbum/'+e.currentTarget.id+'?taId='+taId; + } + setTimeout(function(){ + PUB.page(pageUrl); + workStatusPopPage.close(); + },1000); + }, +}); + + + + +clusterWorkStatusPopPage = { + show: function(template, showClose){ + if (view) { + $('.workStatusPopPage').hide(); + } + clusterWorkStatusPopPage.close(); + var data = Session.get('clusterworkstatus_group') + clusterView = Blaze.renderWithData(Template.clusterWorkStatusPopPage, data, document.body); + }, + close: function(){ + if (view) { + $('.workStatusPopPage').show(); + } + if(clusterView){ + Blaze.remove(clusterView); + clusterView = null; + } + }, + isShow: function(){ + return clusterView != null; + } +}; + + +var limit = new ReactiveVar(0); +var limitSetp = 20 +var lazyloadInterval = null; + +var initLazyload = function(){ + if(lazyloadInterval){ + window.clearInterval(lazyloadInterval); + } + window.setInterval(function(){ + $('ul').find('img.lazy:not([src])').lazyload({ + container: $('ul') + }); + },100); +} + +Template.clusterWorkStatusPopPage.onRendered(function(){ + console.log("Template.clusterWorkStatusPopPage.onRendered") + limit.set(20); + var that = Template.currentData(); + //console.log("that="+JSON.stringify(that)); + //that={"_id":"69561431c099f4908de060ed","name":"AEI-SV","icon":"","describe":"","create_time":"2018-01-18T21:36:08.296Z","template":{},"offsetTimeZone":-8,"last_text":"","last_time":"2018-01-18T21:36:08.296Z","barcode":"http://workaicdn.tiegushi.com/restapi/workai-group-qrcode?group_id=69561431c099f4908de060ed","creator":{"id":"QXtP69enBsk2ipzkC","name":"Xing19"},"perf_info":{"companyId":"bb06315a5fa95d66b28d850e","companyName":"AEI-SV","reportUrl":"http://aixd.raidcdn.cn/reporter/bb06315a5fa95d66b28d850e"},"companyId":"bb06315a5fa95d66b28d850e","report_emails":"xfang@actiontec.com"} + var group_id = that._id; + console.log("workStatusPopPage.onRendered: group_id="+group_id); + Meteor.subscribe('group_cluster_person', group_id, limit.get(),function(){ + Session.set('group_clusterperson_loaded',true); + }); + + // scroll Loading + $('.clusterWorkStatusPopPage .employeeLists').scroll(function(){ + if($('ul').height() - document.body.clientHeight - $(document).scrollTop() + 50 <= 0){ + console.log('start load more'); + Session.set('group_clusterperson_loaded',false); + Session.set('group_clusterperson_loadmore','loading'); + var limitCount = limit.get() + limitSetp; + Meteor.subscribe('group_cluster_person', group_id, limitCount,function(){ + Session.set('group_clusterperson_loadmore','loaded'); + limit.set(limitCount); + }); + } + }); + initLazyload(); +}); + +Template.clusterWorkStatusPopPage.helpers({ + has_day_before:function(group_id){ + var lastday = today.get() - 7 * 24 * 60 * 60 *1000; //7天前 + return theDisplayDay.get() > lastday; + }, + day_title:function(){ + var currentDay = new Date(theDisplayDay.get()); + return parseDate(currentDay); + }, + has_day_after:function(group_id){ + // 可以查看后面两天天数据 + _today = new Date(today.get()); + _today.setDate(_today.getDate() + 2); + _today = new Date(_today.getFullYear(), _today.getMonth(), _today.getDate()).getTime(); + return theDisplayDay.get() < _today; + }, + isLoading: function() { + //return isLoading.get(); + return false; + }, + hasWorkStatus: function () { + //return ClusterWorkStatus.find({group_id: group.get()._id,date: theCurrentDay.get()}).count() > 0; + return true; + }, + lists: function(){ + //return ClusterWorkStatus.find({group_id: group.get()._id,date: theCurrentDay.get()}).fetch(); + //SimpleChat.GroupUsers.find, simplechat_groupusers + //{"_id":"69561431c099f4908de060ed","name":"AEI-SV","icon":"","describe":"","create_time":"2018-01-18T21:36:08.296Z","template":{},"offsetTimeZone":-8,"last_text":"","last_time":"2018-01-18T21:36:08.296Z","barcode":"http://workaicdn.tiegushi.com/restapi/workai-group-qrcode?group_id=69561431c099f4908de060ed","creator":{"id":"QXtP69enBsk2ipzkC","name":"Xing19"},"perf_info":{"companyId":"bb06315a5fa95d66b28d850e","companyName":"AEI-SV","reportUrl":"http://aixd.raidcdn.cn/reporter/bb06315a5fa95d66b28d850e"},"companyId":"bb06315a5fa95d66b28d850e","report_emails":"xfang@actiontec.com"} + return ClusterPerson.find({group_id:this._id}); + }, + devices: function(){ + var group_id = Session.get('modifyMyStatus_group_id') || group.get()._id; + var in_out = Session.get('modifyMyStatus_in_out'); + return Devices.find({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}).fetch(); + }, + getIcon: function(){ + //console.log("this.faces="+JSON.stringify(this.faces)); + //this.faces=[{"id":"4541e6938d4dd867393bc7ab","url":"http://workaiossqn.tiegushi.com/3d322fc0-fcc4-11e7-a288-c894bb5f8c5f"}] + if (this.faces && this.faces.length > 0) + return this.faces[this.faces.length-1].url; + return ''; + }, + enable_push:function(){ + if (this.app_notifaction_status && this.app_notifaction_status === 'on') { + return 'text-success'; + } + return 'text-gray'; + }, + bind_app_user: function() { + if (this.app_user_id) { + return 'text-success'; + } + return 'text-gray'; + }, + getInOutStatus: function(){ + if (this.in_time > 0) { + var date = new Date(this.in_time); + var time_offset = 8 + var _group = group.get(); + if (_group && _group.offsetTimeZone) { + time_offset = _group.offsetTimeZone; + } + var fomatDate = date.shortTime(time_offset); + var isToday = fomatDate.indexOf('今天') > -1 ? true : false; + if (!isToday) { + return 'bg-gray'; + } + } + return 'bg-success'; + }, + getStatus: function(type){ + var status = ''; + if(type == 'in'){ + status = this.in_status; + } + if(type == 'out'){ + status = this.out_status; + } + switch (status) { + case 'normal': + return 'bg-success'; + break; + case 'warning': + return 'bg-warning'; + break; + case 'error': + return 'bg-error'; + break; + case 'unknown': + return 'bg-gray'; + break; + default: + return 'bg-gray'; + break; + } + }, + getTime: function(type){ + var time = null; + if(type == 'in'){ + time = this.in_time; + } + if(type == 'out') { + time = this.out_time; + } + var time_offset = 8; + var _group = group.get(); + if (_group && _group.offsetTimeZone) { + time_offset = _group.offsetTimeZone; + } + if(time){ + time = new Date(time); + time = time.shortTime(time_offset) + } else { + time = '--:--'; + } + return time; + }, + getWhatsup: function(){ + if(this.whats_up && this.whats_up.length > 0){ + return this.whats_up[0]; + } + return '今天还没有工作安排...'; + }, + whatsup: function(){ + return whatsup.get(); + }, + getShortTime: function(ts,group_id){ + var time_offset = 8 + var _group = group.get(); + if (_group && _group.offsetTimeZone) { + time_offset = _group.offsetTimeZone; + } + var time = new Date(this.ts); + return time.shortTime(time_offset,true); + }, +}); + +Template.clusterWorkStatusPopPage.events({ + 'click #closeStausPop': function(){ + clusterWorkStatusPopPage.close(); + return; + }, + 'click .icon': function() { + var people_uuid = new Mongo.ObjectID()._str; + var dt = new Date(); + var data = { + "_id": new Mongo.ObjectID()._str, + "to": { + "id": this.group_id, + "name": this.name, + "icon": "" + }, + "images": [ + ], + "to_type": "group", + "type": "text", + "text": "", + "create_time": new Date(), + "people_id": people_uuid+dt.getTime()+dt.getMilliseconds(), + "people_uuid": people_uuid, + //"people_his_id": "f5awGYvgEA94iCNFF", + "wait_lable": true, + "is_people": true, + "is_read": false, + "tid": people_uuid+dt.getTime() + }; + if (this.faces && this.faces.length > 0) { + for (var i=0; iul{ + padding: 0px; + margin: 0px; + border-radius: 10px; + } + #slideDate2 >ul>li{ + color: white; + width: 100px; + height: 60px; + // border-right: 1px solid #ececec; + background-color:#4682B4; + font-size: 14px !important; + position: relative; + } + #slideDate2 >ul>li:not(.selected):not(:last-child):after{ + content: ""; + position: absolute; + bottom:15px; + width: 1px; + height: 30px; + background: #F0FFFF; + right: 0px; + } + .selected{ + // border: 1px solid #4682B4; + border-top:10px solid #F0FFFF; + background:#37a7fe !important; + border-top-left-radius:5px; + border-top-right-radius: 5px; + margin-left: -1px; + z-index: 1; + } + .workStatusHead{ + // background-color: #03A9F4; + overflow: hidden; + padding: 15px; + color: #fff; + position: relative; + text-align: center; + text-shadow: 1px 1px #333; + background-image: url(/img/default-company-logo.png); + background-repeat: no-repeat; + background-position: center; + background-size: 80px; + .imgHelper{ + float: left; + width: 80px; + margin-right: 16px; + img{ width: 100%; height: 100%; object-fit: contain;} + } + .title{ margin: 10px 0;padding: 0;font-size: 18px;} + .time{margin: 5px 0; font-size: 12px;} + .navigation{ + position: absolute; + left: 0; + right: 0; + top: 50%; + margin: 0; + overflow: hidden; + width: 100%; + height: 40px; + margin-top: -20px; + line-height: 40px; + li{ + list-style: none; + padding: 0 10px; + position: absolute; + cursor: pointer; + width: 64px; + height: 40px; + border-radius: 50%; + font-size: 30px; + text-shadow: 1px 1px #656565; + } + li.prevDay{left: 0; text-align: left;} + li.nextDay{right: 0; text-align: right;} + } + } + .employeeLists{ + margin: 5px; margin-bottom: 50px; padding: 5px; + margin-top: 0px; + padding-top: 0px; + } + .employeeItem{ + list-style: none; + padding: 5px; + text-align: justify; + font-size: 12px; + position: relative; + .dropdown{ + position: absolute; + top: 10px; + right: 10px; + .dropdown-toggle{ + display: inline-block; height: 40px; line-height: 40px; width: 40px; text-align: center; font-size: 16px; cursor: pointer; position: relative; + } + .dropdown-menu { + position: absolute; + right: 0; + left: auto; + } + } + .container{ + background: #fff; + border-radius: 4px; + padding: 16px; + padding-bottom: 0; + cursor: pointer; + } + .pull-left{ + float: left; + margin-right: 16px; + } + .icon{ + width: 64px; height: 64px; margin: 0 auto; margin-bottom: 5px; border: 4px solid #efefef; + img{ + width: 100%; + height: 100%; + object-fit: cover; + } + } + h2{ + font-size: 14px; line-height:20px; margin:5px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + p{margin: 5px 0;} + .status-bar{ + display: inline-block; + padding: 2px 8px; + background: #eee; + border-radius: 10px; + white-space: nowrap; + span{ font-size: 12px; display: inline-block; margin:0;} + } + .in-out-status{ + display: inline-block; + height: 10px; + width: 10px; + border-radius: 50%; + margin-left: 5px; + } + .text-success{color: #009688;} + .text-gray{ color: #9E9E9E;} + .bg-success{ background-color: #009688;} + .bg-warning{ background-color: orangered;} + .bg-gray { background-color: #9E9E9E;} + .bg-error {background-color: red;} + .whats-up{ + float: left; width: 100%; line-height: 20px; margin: 5px 0; + .toggle{ + float: right; + font-size: 14px; + width: 60px; + text-align: right; + cursor: pointer; + height: 25px; + } + } + } + #closeStausPop { + position: fixed; + bottom: 0; + left: 0; + right: 0; + text-align: center; + height: 40px; + line-height: 40px; + font-size: 18px; + cursor: pointer; + } + .noData ,.loading{ + margin-top: 20%; + text-align: center; + img{width: 80%;} + } +} + + + +.clusterWorkStatusPopPage{ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling : touch; + // background: #efefef; + color: #555; + z-index: 999990; + background: -webkit-linear-gradient(#03a9f4,#03a9f4 10%,#efefef 40%, #efefef); + background: linear-gradient(#03a9f4,#03a9f4 10%,#efefef 40%, #efefef); + input,textarea { + -webkit-touch-callout: default; + -webkit-user-select: text; + user-select: text; + } + .workStatusHead{ + // background-color: #03A9F4; + overflow: hidden; + padding: 15px; + color: #fff; + position: relative; + text-align: center; + text-shadow: 1px 1px #333; + background-image: url(/img/default-company-logo.png); + background-repeat: no-repeat; + background-position: center; + background-size: 80px; + .imgHelper{ + float: left; + width: 80px; + margin-right: 16px; + img{ width: 100%; height: 100%; object-fit: contain;} + } + .title{ margin: 10px 0;padding: 0;font-size: 18px;} + .time{margin: 5px 0; font-size: 12px;} + .navigation{ + position: absolute; + left: 0; + right: 0; + top: 50%; + margin: 0; + overflow: hidden; + width: 100%; + height: 40px; + margin-top: -20px; + line-height: 40px; + li{ + list-style: none; + padding: 0 10px; + position: absolute; + cursor: pointer; + width: 64px; + height: 40px; + border-radius: 50%; + font-size: 30px; + text-shadow: 1px 1px #656565; + } + li.prevDay{left: 0; text-align: left;} + li.nextDay{right: 0; text-align: right;} + } + } + .employeeLists{ + margin: 5px; margin-bottom: 50px; padding: 5px;overflow: scroll; height:100%; + } + .employeeItem{ + list-style: none; + padding: 5px; + text-align: justify; + font-size: 12px; + position: relative; + .dropdown{ + position: absolute; + top: 10px; + right: 10px; + .dropdown-toggle{ + display: inline-block; height: 40px; line-height: 40px; width: 40px; text-align: center; font-size: 16px; cursor: pointer; position: relative; + } + .dropdown-menu { + position: absolute; + right: 0; + left: auto; + } + } + .container{ + background: #fff; + border-radius: 4px; + padding: 16px; + padding-bottom: 0; + cursor: pointer; + } + .pull-left{ + float: left; + margin-right: 16px; + } + .icon{ + width: 64px; height: 64px; margin: 0 auto; margin-bottom: 5px; border: 4px solid #efefef; + img{ + width: 100%; + height: 100%; + object-fit: cover; + } + } + h2{ + font-size: 14px; line-height:20px; margin:5px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + p{margin: 5px 0;} + .status-bar{ + display: inline-block; + padding: 2px 8px; + background: #eee; + border-radius: 10px; + white-space: nowrap; + span{ font-size: 12px; display: inline-block; margin:0;} + } + .in-out-status{ + display: inline-block; + height: 10px; + width: 10px; + border-radius: 50%; + margin-left: 5px; + } + .text-success{color: #009688;} + .text-gray{ color: #9E9E9E;} + .bg-success{ background-color: #009688;} + .bg-warning{ background-color: orangered;} + .bg-gray { background-color: #9E9E9E;} + .whats-up{ + float: left; width: 100%; line-height: 20px; margin: 5px 0; + .toggle{ + float: right; + font-size: 14px; + width: 60px; + text-align: right; + cursor: pointer; + height: 25px; + } + } + } + #closeStausPop { + position: fixed; + bottom: 0; + left: 0; + right: 0; + text-align: center; + height: 40px; + line-height: 40px; + font-size: 18px; + cursor: pointer; + } + .noData ,.loading{ + margin-top: 20%; + text-align: center; + img{width: 80%;} + } +} \ No newline at end of file diff --git a/client/index.html b/client/index.html index b1c3baf76..3993c9bde 100644 --- a/client/index.html +++ b/client/index.html @@ -1,7 +1,7 @@ - - + + diff --git a/client/introductoryPage/introductoryPage.coffee b/client/introductoryPage/introductoryPage.coffee new file mode 100644 index 000000000..0eb8d4153 --- /dev/null +++ b/client/introductoryPage/introductoryPage.coffee @@ -0,0 +1,151 @@ +if Meteor.isClient + # Template.introductoryPage.rendered=-> + # $('.content').css 'min-height',$(window).height() + joinTestGroup2 = (groupid, type)-> + Meteor.call 'add-group-urser', groupid, [ Meteor.userId() ], (err, result)-> + if err + console.log err + return PUB.toast('添加失败,请重试~') + gotoPage = '/' + if result is 'succ' + PUB.toast '添加成功' + SimpleChat.onShowTipsMessages(true,type) + Meteor.call 'get-group-intro', groupid, type, (err,result)-> + if err + console.log err + if result and result.text + SimpleChat.Messages.insert(result) + if result == 'not find group' + PUB.toast '添加失败,请重试~' + # show TestGroup Tip + Session.set('homePagesForm', 'joinTestGroup') + Meteor.setTimeout ()-> + $('body').append('
') + ,300 + return Router.go(gotoPage) + + joinTestGroup = (groupid,type)-> + return joinTestGroup2(groupid, type) + #groupid = 'd2bc4601dfc593888618e98f' + Meteor.call 'add-group-urser', groupid, [ Meteor.userId() ], (err, result) -> + if err + console.log err + return PUB.toast('添加失败,请重试~') + gotoPage = '/'; + if result == 'succ' + PUB.toast '添加成功' + gotoPage = '/simple-chat/to/group?id=' + groupid + #window.localStorage.setItem("simple_chat_need_show_tips",'true') + SimpleChat.onShowTipsMessages(true,type) + PUB.page '/' + Meteor.setTimeout ()-> + PUB.page gotoPage + ,300 + Meteor.call 'get-group-intro', groupid, type, (err,result)-> + if err + console.log err + if result and result.text + SimpleChat.Messages.insert(result); + relations = WorkAIUserRelations.findOne({'app_user_id':Meteor.userId()}); + unless relations + Meteor.setTimeout(()-> + Router.go('/timeline'); + ,500); + if result == 'not find group' + PUB.toast '添加失败,请重试~' + return Router.go(gotoPage) + return + Template.introductoryPage.helpers + enable_home_ai:()-> + if window.localStorage.getItem("enableHomeAI") == 'true' + return true + return false + Template.introductoryPage.events + 'click .leftButton':(event)-> + Router.go('/scene'); + 'click #joinGroup':(event)-> + Router.go('/introductoryPage1'); + 'click #createGroup':(event)-> + window.localStorage.setItem("isSecondUse",'true'); + #Router.go('/group/add'); + Session.set('fromCreateNewGroups',true); + Session.set('notice-from','createNewChatGroups'); + Router.go('/setGroupname'); + #PUB.page '/notice' + 'click #joinTestGroup':(event)-> + # Session.set('needShowBubble','false'); + # window.localStorage.setItem("isSecondUse",'true'); + # joinTestGroup() + Session.set('needShowBubble','false'); + window.localStorage.setItem("isSecondUse",'true'); + #讯动训练营 + #groupid = 'd2bc4601dfc593888618e98f' + #type = 'FACE' + #joinTestGroup(groupid,type) + PUB.page '/' + #PUB.page('/introductoryPage2') + 'click #skipStep':(event)-> + Session.set('needShowBubble','false'); + window.localStorage.setItem("isSecondUse",'true'); + PUB.page '/' + Meteor.setTimeout ()-> + PUB.page '/posts/uRyvJDmL88gd4BbBF' + ,300 + + # Template.introductoryPage1.rendered=-> + # $('.content').css 'min-height',$(window).height() + + Template.introductoryPage1.events + 'click .leftButton':(event)-> + Router.go('/introductoryPage') + 'click #scanBarcode':(event)-> + ScanBarcodeByBarcodeScanner(); + window.localStorage.setItem("isSecondUse",'true'); + 'click #loadBarCodeFromAlbum':(event)-> + #PUB.page('/newFriendsList') + window.localStorage.setItem("isSecondUse",'true'); + DecodeImageFromAlum(); + 'click #joinTestGroup':(event)-> + # Session.set('needShowBubble','false'); + # window.localStorage.setItem("isSecondUse",'true'); + # joinTestGroup() + #PUB.page('/introductoryPage2') + Session.set('needShowBubble','false'); + window.localStorage.setItem("isSecondUse",'true'); + #讯动训练营 + groupid = 'd2bc4601dfc593888618e98f' + type = 'FACE' + joinTestGroup(groupid,type) + 'click #skipStep':(event)-> + Session.set('needShowBubble','false'); + window.localStorage.setItem("isSecondUse",'true'); + PUB.page '/' + Meteor.setTimeout ()-> + PUB.page '/posts/uRyvJDmL88gd4BbBF' + ,300 + + Template.introductoryPage2.events + 'click .leftButton':(event)-> + PUB.back(); + + 'click #joinFaceGroup':(event)-> + Session.set('needShowBubble','false'); + window.localStorage.setItem("isSecondUse",'true'); + #讯动训练营 + groupid = 'd2bc4601dfc593888618e98f' + type = 'FACE' + joinTestGroup(groupid,type) + 'click #joinNLPGroup':(event)-> + Session.set('needShowBubble','false'); + window.localStorage.setItem("isSecondUse",'true'); + #nlp训练营 + groupid = '92bf785ddbe299bac9d1ca82' + type = 'NLP' + joinTestGroup(groupid,type) + # 'click #skipStep':(event)-> + # Session.set('needShowBubble','false'); + # window.localStorage.setItem("isSecondUse",'true'); + # PUB.page '/' + # Meteor.setTimeout ()-> + # PUB.page '/posts/uRyvJDmL88gd4BbBF' + # ,300 \ No newline at end of file diff --git a/client/introductoryPage/introductoryPage.html b/client/introductoryPage/introductoryPage.html new file mode 100644 index 000000000..a33f86e42 --- /dev/null +++ b/client/introductoryPage/introductoryPage.html @@ -0,0 +1,64 @@ + + + + + \ No newline at end of file diff --git a/client/introductoryPage/introductoryPage.less b/client/introductoryPage/introductoryPage.less new file mode 100644 index 000000000..13e384914 --- /dev/null +++ b/client/introductoryPage/introductoryPage.less @@ -0,0 +1,228 @@ +.introductoryPage{ + // background-image: url(theme_blue/loginbg2.jpg); + // background-repeat: no-repeat; + // background-size: cover !important; + color: #1a1a1a !important; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1000; + .actionArea{ + position: absolute; + left: 20%; + right: 20%; + top: 200px; + height: 50%; + text-align: center; + } + .introductory-btn{ + position: relative; + // left: 2.5%; + width: 100%; + text-align: center; + font-size: 18px; + // background-color: #37a7fe; + // color: #ffffff !important; + color: #088ef0; + border: 1px solid #088ef0; + height: 45px; + border-radius: 5px; + /* padding-left: 5%; */ + padding-top: 10px; + } + #createGroup{ + margin-top: 20px; + } + #skipStep{ + bottom: 10px; + position: absolute; + width: 40%; + right: 0px; + color: #333 !important; + font-size: 16px; + background: #fff; + border-style: solid; + // border-color: #37a7fe; + border-width: 1px; + border: none; + border-radius: 0; + border-bottom: 1px solid #333; + height: auto; + } + #joinTestGroup{ + margin-top: 20px; + border-color: #088ef0; + background: -webkit-gradient(linear, left top, left bottom, from(#34a5f8), to(#088ef0)); + background: linear-gradient(#34a5f8, #088ef0); + border-style: solid; + border-width: 2px; + color: #fff; + /* + background-color: #fff; + color: #37a7fe !important;; + margin-top: 20px; + font-size: 16px; + span{ + background: #37a7fe; + width: 45%; + margin-left: 28%; + height: 1px; + display: block; + } + */ + } +} + +.introductoryPage1{ + // background-image: url(theme_blue/loginbg2.jpg); + // background-repeat: no-repeat; + // background-size: cover !important; + color: #1a1a1a !important; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1000; + .actionArea{ + position: absolute; + left: 20%; + right: 20%; + top: 200px; + height: 50%; + text-align: center; + } + .introductory-btn{ + position: relative; + width: 100%; + text-align: center; + font-size: 18px; + // background-color: #37a7fe; + // color: #ffffff !important; + color: #088ef0; + border: 1px solid #088ef0; + height: 45px; + border-radius: 5px; + /* padding-left: 5%; */ + padding-top: 10px; + } + #loadBarCodeFromAlbum{ + margin-top: 20px; + } + #skipStep{ + bottom: 10px; + position: absolute; + width: 40%; + right: 0px; + color: #333 !important; + font-size: 16px; + background: #fff; + border-style: solid; + // border-color: #37a7fe; + border-width: 1px; + border: none; + border-radius: 0; + border-bottom: 1px solid #333; + height: auto; + } + #joinTestGroup{ + // background-color: #fff; + // color: #37a7fe !important;; + margin-top: 20px; + border-color: #088ef0; + background: -webkit-gradient(linear, left top, left bottom, from(#34a5f8), to(#088ef0)); + background: linear-gradient(#34a5f8, #088ef0); + border-style: solid; + border-width: 2px; + color: #fff; + // font-size: 16px; + // span{ + // background: #37a7fe; + // width: 45%; + // margin-left: 28%; + // height: 1px; + // display: block; + // } + } +} + +.introductoryPage2{ + // background-image: url(theme_blue/loginbg2.jpg); + // background-repeat: no-repeat; + // background-size: cover !important; + color: #1a1a1a !important; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1000; + .actionArea{ + position: absolute; + left: 20%; + right: 20%; + top: 200px; + height: 50%; + text-align: center; + } + .introductory-btn{ + position: relative; + // left: 2.5%; + width: 100%; + text-align: center; + font-size: 17px; + //background-color: #37a7fe; + color: #ffffff !important; + height: 45px; + border-radius: 5px; + /* padding-left: 5%; */ + padding-top: 10px; + background-color: #fff; + color: #37a7fe !important;; + margin-top: 20px; + //font-size: 16px; + span{ + background: #37a7fe; + width: 35%; + margin-left: 32%; + height: 1px; + display: block; + } + } + #joinFaceGroup{ + span{ + width: 75% !important; + margin-left: 12% !important; + } + } + #joinNLPGroup{ + span{ + width: 66% !important; + margin-left: 16% !important; + } + } +} + +.joinTestGroupTips,.homePageTips1{ + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0,0,0,.68); + z-index: 999999; + background-image: url(intros/km-demo-tip.png); + background-size: 100%; + background-position: center; + background-repeat: no-repeat; +} + +.homePageTips1{ + background: rgba(0,0,0,.78); + background-image: url(intros/home-page-tip1.png); + background-size: 100%; + background-position: center; + background-repeat: no-repeat; +} \ No newline at end of file diff --git a/client/intros/intros.html b/client/intros/intros.html index a6a416980..4ad884d7b 100644 --- a/client/intros/intros.html +++ b/client/intros/intros.html @@ -1,6 +1,6 @@ diff --git a/client/isHaveStranger/isHaveStranger.js b/client/isHaveStranger/isHaveStranger.js new file mode 100755 index 000000000..fcdd9f23a --- /dev/null +++ b/client/isHaveStranger/isHaveStranger.js @@ -0,0 +1,204 @@ +//0:刚进入显示开始 1:点击开始后显示结束按钮 +var btn_pro = new ReactiveVar(0); +var markList = new ReactiveVar([]); +Session.set("isMark", true) + +// var datas = null +// $.get('http://192.168.31.113:5000/api/parameters', function(data){ +// datas = data +// }) +// Date.prototypeishaveStranger.Format = function(fmt) { +// var o = { +// "M+": this.getMonth() + 1, +// "d+": this.getDate(), +// "h+": this.getHours(), +// "m+": this.getMinutes(), +// "s+": this.getSeconds(), +// "q+": Math.floor((this.getMonth() + 3) / 3), +// "S": this.getMilliseconds() +// }; +// if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); +// for (var k in o) +// if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); +// return fmt; +// } + +Template.haveStranger.events({ + 'click .back': function(e) { + btn_pro.set(0); + return PUB.back(); + }, + 'click .skip': function(e){ + var sessionType = Session.get("session_type") + var toUserId = Session.get("toUser_id") + var url = '/simple-chat/to/' + sessionType + '?id=' + toUserId + return PUB.page(url) + }, + 'click .stranges-item': function(e, t) { + var sessionType = Session.get("session_type") + var toUserId = Session.get("toUser_id") + var critiaria = { group_id: { $eq: toUserId} }; + var face_settings = {"face_list" : ["front","human_shape"], "fuzziness" : "100"}; + if (face_settings) { + critiaria.imgs = {$elemMatch: {style: {$in: face_settings.face_list}, fuzziness: {$gte: parseInt(face_settings.fuzziness)}}}; + } + var url = '/simple-chat/to/' + sessionType + '?id=' + toUserId + var tagName = $(e.target).prop("tagName").toLowerCase() + var $li = $(e.currentTarget) + var id = $li.attr("data") + if (tagName === "a" || tagName === "i") { + if ($(e.target).attr("id") === "reject" || $(e.target).parent().attr("id") === "reject") { + Session.set("isMark", true) + Strangers.remove({ _id: id }) + + var len = Strangers.find(critiaria).fetch().length + $('#stranger-contain').find($li).remove() + if (len == 0) { + PUB.page(url) + } + } else { + var curData = Strangers.find({ _id: id }).fetch()[0].imgs + Session.set("isMark", false) + markList.set(curData) + } + } + if (((tagName === "a" || tagName === "i")) && ($(e.target).attr("id") === "accept" || $(e.target).parent().attr("id") === "accept")) { + var imgData = markList.get() + var val = $("#mark_val").val() + var uuid = this.uuid + var id = this._id + var group_id = this.group_id + var imgTs = (new Date(this.createTime)).getTime() + // imgData = JSON.parse(imgData) + // var faceId = new Mongo.ObjectID()._str + Session.set("isMark", true) + + + var call_back_handle = function(name) { + if (!name) { + return; + } + + PUB.showWaitLoading('处理中'); + var setNames = []; + Meteor.call('get-id-by-name1', uuid, name, group_id, function(err, res) { + if (err || !res) { + + return PUB.toast('标注失败,请重试'); + } + var faceId = null; + if (res && res.faceId) { + faceId = res.faceId; + } else if (imgData[0].person_name != null && imgData[0].person_name != undefined) { + faceId = new Mongo.ObjectID()._str; + } else { + faceId = imgData[0].faceid; + } + + imgData.forEach(function(item) { + if (face_settings) { + if (!(face_settings.face_list.includes(item.style) && item.fuzziness >= face_settings.fuzziness)) + return; + } + // 发送消息给平板 + var trainsetObj = { + group_id: group_id, + type: 'trainset', + url: item.url, + person_id: item.faceid, + device_id: uuid, + face_id: faceId, + drop: false, + img_type: 'face', + style: item.style, + sqlid: item.sqlid + }; + console.log("==sr==. timeLine multiSelect: " + JSON.stringify(trainsetObj)); + sendMqttMessage('/device/' + group_id, trainsetObj); + + setNames.push({ + uuid: uuid, + id: faceId, //item.person_id, + url: item.url, + name: name, + sqlid: item.sqlid, + style: item.style + }); + }); + + if (setNames.length > 0) { + Meteor.call('set-person-names', group_id, setNames); + } + imgData.forEach(function(item) { + if (face_settings) { + if (!(face_settings.face_list.includes(item.style) && item.fuzziness >= face_settings.fuzziness)) + return; + } + + try { + var ts = imgTs ? imgTs : new Date().getTime(); + var person_info = { + 'uuid': uuid, + 'person_id': item.faceid, + 'name': name, + 'group_id': group_id, + 'img_url': item.url, + 'type': 'face', + 'ts': ts, + 'accuracy': item.accuracy, + 'fuzziness': item.fuzziness, + 'sqlid': item.sqlid, + 'style': item.style + }; + var data = { + face_id: item.faceid, + person_info: person_info, + formLabel: true + }; + + Meteor.call('ai-checkin-out', data, function(err, res) { + + }); + } catch (e) {} + }); + PUB.hideWaitLoading(); + Strangers.remove({ _id: id }) + //Meteor.call('removeStrangers', id) + var len = Strangers.find(critiaria).fetch().length + $('#stranger-contain').find($li).remove() + if (len == 0) { + PUB.page(url) + } + }); + }; + SimpleChat.show_label(group_id, this.avatar, call_back_handle); + } + } +}) + +Template.haveStranger.helpers({ + isHaveStranger: function() { + var critiaria = { group_id: { $eq: Session.get("toUser_id")} }; + var face_settings = {"face_list" : ["front","human_shape"], "fuzziness" : "100"}; + if (face_settings) { + critiaria.imgs = {$elemMatch: {style: {$in: face_settings.face_list}, fuzziness: {$gte: parseInt(face_settings.fuzziness)}}}; + } + st = Strangers.find(critiaria); + if (st.count() > 0) + return true; + else + return false; + }, + Stranger_people: function() { + var critiaria = { group_id: { $eq: Session.get("toUser_id")} }; + var face_settings = {"face_list" : ["front","human_shape"], "fuzziness" : "100"}; + if (face_settings) { + critiaria.imgs = {$elemMatch: {style: {$in: face_settings.face_list}, fuzziness: {$gte: parseInt(face_settings.fuzziness)}}}; + } + st = Strangers.find(critiaria); + return st + }, + isMark: function() { + return Session.get("isMark") + } +}) \ No newline at end of file diff --git a/client/isHaveStranger/isHaveStranger.less b/client/isHaveStranger/isHaveStranger.less new file mode 100755 index 000000000..84ed32751 --- /dev/null +++ b/client/isHaveStranger/isHaveStranger.less @@ -0,0 +1,155 @@ +body { + overflow: hidden; +} +.noStranger-main { + height: 100%; + position: relative; + .noStranger { + position: absolute; + top: 35%; + transform: translateY(-50%); + text-align: center; + width: 100%; + p { + color: #888; + font-size: 20px; + line-height: 20px; + margin-bottom: 50px; + } + a { + display: inline-block; + padding: 10px 25px; + background-color: #37a7fe; + font-size: 16px; + line-height: 16px; + color:#fff; + } + } +} +.card-msg { + height: 70%; + box-sizing: border-box; + padding: 81% 0px 0px 0px; + overflow: hidden; + background:url(./theme/theme1.jpg) no-repeat center; + background-size: cover; + margin-bottom:20px; +} +.card-info { + z-index: 3; + height:100%; + width: 100%; + position: relative; + padding-top:8%; + background-color: #fff; + // border:1px solid #eee; +} +.card-avatar { + width: 112px; + height: 112px; + position: absolute; + left: 50%; + transform: translateX(-50%); + top:-290%; + border-radius: 50%; + background:url(./theme/theme1.jpg) no-repeat center; + background-size: cover; +} +.color-time { + color:#999; + margin-bottom: 15px; + text-align: center; +} +.tip-msg { + z-index:10; + font-size:16px; + margin-bottom:20px; + margin-top:40px; + color: #000; + text-align: center; +} +p { + color: #000; + font-size: 14px; + text-align: left; + text-indent:0px; + //padding-left: 1.3rem; +} +.title_failure{ + color: red; + font-size: 16px; + margin-bottom: 3px; +} + +.failure_info{ + color: #64748D; + margin: 0px; + font-size: 14px; +} +.perform_perform{ + margin-top: 12px; + color: #666; + margin-bottom: 3px; + font-size: 16px; +} +.perform_order{ + color: #666666; + margin-bottom: 5px; + font-size: 14px; +} +.url_fa{ + color: #39A8FE; + font-size: 14px; +} + +.mark-text { + width: 0; + border: 0; + transition: all 0.3s ease 0s; + padding:3px 6px; +} +#stranger-contain { + position: relative; + width: 100%; + height: 100%; +} + +#stranger-contain .stranges-item { + background-color: #fff; + position: absolute; + top: 0; + left: 5%; + transition:all 0.3s ease 0s; + width: 90%; + font-size: 16.5px; + // height: 350px; + text-align: center; + overflow: hidden; + a { + display: inline-block; + width: 80px; + height: 80px; + padding:29px 10px; + background-color: #eee; + text-align: center; + border-radius:50%; + } +} +.button { + padding: 4px 7px; + width: 50px; + color:#fff; +} +.sub-sure { + margin-bottom: 20px; + text-align: center; +} +.skip{ + font-size: 120%; + width: 28%; + z-index: 99999; + position: relative; + left: 36.5%; + margin-top: 10%; + color: #999; +} diff --git a/client/isIOS.coffee b/client/isIOS.coffee new file mode 100644 index 000000000..5c5770d65 --- /dev/null +++ b/client/isIOS.coffee @@ -0,0 +1 @@ +@isIOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false) diff --git a/client/lib/01_mobiscroll/mobiscroll.custom-3.0.0-beta2.min.css b/client/lib/01_mobiscroll/mobiscroll.custom-3.0.0-beta2.min.css new file mode 100755 index 000000000..817d7fc8a --- /dev/null +++ b/client/lib/01_mobiscroll/mobiscroll.custom-3.0.0-beta2.min.css @@ -0,0 +1,2 @@ +/* 46f259de-8808-4478-8705-cf88a7cc8c12 */ +@font-face{font-family:'icons_mobiscroll';src:url('icons_mobiscroll.eot?noveut');src:url('icons_mobiscroll.eot?#iefixnoveut') format('embedded-opentype'),url('icons_mobiscroll.ttf?noveut') format('truetype'),url('icons_mobiscroll.woff?noveut') format('woff'),url('icons_mobiscroll.woff') format('woff'),url('icons_mobiscroll.svg?noveut#icons_mobiscroll') format('svg');font-style:normal}.mbsc-ic:before{font-family:'icons_mobiscroll';speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.mbsc-ic-aid:before{content:"\f100"}.mbsc-ic-airplane:before{content:"\f101"}.mbsc-ic-alarm2:before{content:"\f102"}.mbsc-ic-arrow-down5:before{content:"\f103"}.mbsc-ic-arrow-down6:before{content:"\f104"}.mbsc-ic-arrow-left2:before{content:"\f105"}.mbsc-ic-arrow-left4:before{content:"\f106"}.mbsc-ic-arrow-left5:before{content:"\f107"}.mbsc-ic-arrow-left6:before{content:"\f108"}.mbsc-ic-arrow-right2:before{content:"\f109"}.mbsc-ic-arrow-right4:before{content:"\f10a"}.mbsc-ic-arrow-right5:before{content:"\f10b"}.mbsc-ic-arrow-right6:before{content:"\f10c"}.mbsc-ic-arrow-up5:before{content:"\f10d"}.mbsc-ic-arrow-up6:before{content:"\f10e"}.mbsc-ic-attachment:before{content:"\f10f"}.mbsc-ic-backspace:before{content:"\f110"}.mbsc-ic-backspace3:before{content:"\f111"}.mbsc-ic-backspace4:before{content:"\f112"}.mbsc-ic-bars:before{content:"\f113"}.mbsc-ic-book:before{content:"\f114"}.mbsc-ic-bubble:before{content:"\f115"}.mbsc-ic-bubbles:before{content:"\f116"}.mbsc-ic-bullhorn:before{content:"\f117"}.mbsc-ic-calendar:before{content:"\f118"}.mbsc-ic-camera:before{content:"\f119"}.mbsc-ic-cart:before{content:"\f11a"}.mbsc-ic-checkmark:before{content:"\f11b"}.mbsc-ic-clock:before{content:"\f11c"}.mbsc-ic-close:before{content:"\f11d"}.mbsc-ic-cloud-download:before{content:"\f11e"}.mbsc-ic-cloud-upload:before{content:"\f11f"}.mbsc-ic-cogs:before{content:"\f120"}.mbsc-ic-connection:before{content:"\f121"}.mbsc-ic-copy2:before{content:"\f122"}.mbsc-ic-copy3:before{content:"\f123"}.mbsc-ic-credit:before{content:"\f124"}.mbsc-ic-disk:before{content:"\f125"}.mbsc-ic-download:before{content:"\f126"}.mbsc-ic-drawer:before{content:"\f127"}.mbsc-ic-droplet:before{content:"\f128"}.mbsc-ic-earth:before{content:"\f129"}.mbsc-ic-eye:before{content:"\f12a"}.mbsc-ic-eye-blocked:before{content:"\f12b"}.mbsc-ic-fa-leaf:before{content:"\f12c"}.mbsc-ic-fa-rotate-left:before{content:"\f12d"}.mbsc-ic-file4:before{content:"\f12e"}.mbsc-ic-film:before{content:"\f12f"}.mbsc-ic-flag:before{content:"\f130"}.mbsc-ic-folder:before{content:"\f131"}.mbsc-ic-forward:before{content:"\f132"}.mbsc-ic-foundation-mail:before{content:"\f133"}.mbsc-ic-foundation-minus-circle:before{content:"\f134"}.mbsc-ic-globe:before{content:"\f135"}.mbsc-ic-heart:before{content:"\f136"}.mbsc-ic-history:before{content:"\f137"}.mbsc-ic-home:before{content:"\f138"}.mbsc-ic-image2:before{content:"\f139"}.mbsc-ic-ion-android-system-windows:before{content:"\f13a"}.mbsc-ic-ion-bluetooth:before{content:"\f13b"}.mbsc-ic-ion-ios7-checkmark-empty:before{content:"\f13c"}.mbsc-ic-ion-navigate:before{content:"\f13d"}.mbsc-ic-key2:before{content:"\f13e"}.mbsc-ic-library:before{content:"\f13f"}.mbsc-ic-link:before{content:"\f140"}.mbsc-ic-location:before{content:"\f141"}.mbsc-ic-lock2:before{content:"\f142"}.mbsc-ic-loop2:before{content:"\f143"}.mbsc-ic-map:before{content:"\f144"}.mbsc-ic-material-arrow-back:before{content:"\f145"}.mbsc-ic-material-backspace:before{content:"\f146"}.mbsc-ic-material-check-box-outline-blank:before{content:"\f147"}.mbsc-ic-material-equalizer:before{content:"\f148"}.mbsc-ic-material-keyboard-arrow-down:before{content:"\f149"}.mbsc-ic-material-keyboard-arrow-left:before{content:"\f14a"}.mbsc-ic-material-keyboard-arrow-right:before{content:"\f14b"}.mbsc-ic-material-keyboard-arrow-up:before{content:"\f14c"}.mbsc-ic-material-pause:before{content:"\f14d"}.mbsc-ic-material-play-arrow:before{content:"\f14e"}.mbsc-ic-material-repeat:before{content:"\f14f"}.mbsc-ic-material-shuffle:before{content:"\f150"}.mbsc-ic-material-skip-next:before{content:"\f151"}.mbsc-ic-material-skip-previous:before{content:"\f152"}.mbsc-ic-material-star:before{content:"\f153"}.mbsc-ic-material-star-outline:before{content:"\f154"}.mbsc-ic-material-stop:before{content:"\f155"}.mbsc-ic-minus:before{content:"\f156"}.mbsc-ic-mobile:before{content:"\f157"}.mbsc-ic-music:before{content:"\f158"}.mbsc-ic-newspaper:before{content:"\f159"}.mbsc-ic-office:before{content:"\f15a"}.mbsc-ic-pause2:before{content:"\f15b"}.mbsc-ic-pencil:before{content:"\f15c"}.mbsc-ic-phone:before{content:"\f15d"}.mbsc-ic-play:before{content:"\f15e"}.mbsc-ic-play3:before{content:"\f15f"}.mbsc-ic-plus:before{content:"\f160"}.mbsc-ic-redo2:before{content:"\f161"}.mbsc-ic-remove:before{content:"\f162"}.mbsc-ic-reply:before{content:"\f163"}.mbsc-ic-sad2:before{content:"\f164"}.mbsc-ic-share:before{content:"\f165"}.mbsc-ic-smiley2:before{content:"\f166"}.mbsc-ic-stack:before{content:"\f167"}.mbsc-ic-star:before{content:"\f168"}.mbsc-ic-star2:before{content:"\f169"}.mbsc-ic-star3:before{content:"\f16a"}.mbsc-ic-stop2:before{content:"\f16b"}.mbsc-ic-stopwatch:before{content:"\f16c"}.mbsc-ic-support:before{content:"\f16d"}.mbsc-ic-tag:before{content:"\f16e"}.mbsc-ic-thumbs-up:before{content:"\f16f"}.mbsc-ic-thumbs-up2:before{content:"\f170"}.mbsc-ic-undo2:before{content:"\f171"}.mbsc-ic-unlocked:before{content:"\f172"}.mbsc-ic-upload:before{content:"\f173"}.mbsc-ic-user4:before{content:"\f174"}.mbsc-ic-volume-medium:before{content:"\f175"}.mbsc-progress{position:relative;display:block;margin:0;z-index:0;-ms-touch-action:pan-y;touch-action:pan-y;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.mbsc-progress progress{display:none}.mbsc-progress .mbsc-input-wrap{position:relative;display:block}.mbsc-progress .mbsc-input-ic{position:absolute;height:2em;width:2em;line-height:2em;text-align:center}.mbsc-progress-cont{position:relative;display:block;width:100%;height:100%}.mbsc-progress-track{position:relative;display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-progress-bar{position:absolute;top:0;left:0;width:0;height:100%}.mbsc-progress-anim .mbsc-progress-bar{-webkit-transition:width .1s ease-in-out;-moz-transition:width .1s ease-in-out;transition:width .1s ease-in-out}.mbsc-progress-value{position:absolute;top:50%;width:3em;overflow:hidden;margin-top:-.5em;line-height:1em}.mbsc-progress-value-right .mbsc-progress-value{right:0;text-align:right}.mbsc-progress-value-left .mbsc-progress-value{left:0;text-align:left}.mbsc-progress-step-label{position:absolute;top:1em;width:3.5em;margin-left:-1.75em;font-size:.75em;text-align:center;overflow:hidden}.mbsc-mobiscroll.mbsc-progress{padding:1em}.mbsc-mobiscroll.mbsc-progress .mbsc-label{font-size:.75em}.mbsc-mobiscroll.mbsc-progress .mbsc-input-ic{top:.0625em;margin:0;color:#787878}.mbsc-mobiscroll.mbsc-progress.mbsc-ic-left{padding-left:3.25em}.mbsc-mobiscroll.mbsc-progress.mbsc-ic-right{padding-right:3.25em}.mbsc-mobiscroll.mbsc-progress.mbsc-ic-left .mbsc-left-ic{right:auto;left:-2.625em}.mbsc-mobiscroll.mbsc-progress.mbsc-ic-right .mbsc-right-ic{left:auto;right:-2.625em}.mbsc-mobiscroll .mbsc-progress-cont{padding:1em 0}.mbsc-mobiscroll .mbsc-progress-track{background:#dedede;border-radius:1em;height:.125em}.mbsc-mobiscroll .mbsc-progress-bar{background:#4eccc4}.mbsc-mobiscroll .mbsc-progress-value{width:3.75em;font-size:.875em}.mbsc-mobiscroll.mbsc-progress.mbsc-progress-value-left .mbsc-input-wrap{padding-left:3.5em}.mbsc-mobiscroll.mbsc-progress.mbsc-progress-value-right .mbsc-input-wrap{padding-right:3.5em}.mbsc-android-holo.mbsc-progress{padding:.75em}.mbsc-android-holo.mbsc-progress .mbsc-input-ic{top:.25em}.mbsc-android-holo.mbsc-progress.mbsc-ic-right{padding-right:3.25em}.mbsc-android-holo.mbsc-progress.mbsc-ic-left{padding-left:3.25em}.mbsc-android-holo.mbsc-progress.mbsc-ic-right .mbsc-right-ic{left:auto;right:-2.375em}.mbsc-android-holo.mbsc-progress.mbsc-ic-left .mbsc-left-ic{right:auto;left:-2.375em}.mbsc-android-holo.mbsc-progress .mbsc-input-wrap{padding:0}.mbsc-android-holo.mbsc-progress .mbsc-label{padding:0;font-size:.75em}.mbsc-android-holo .mbsc-progress-cont{padding:1em 0}.mbsc-android-holo .mbsc-progress-track{background:#818181;height:.125em}.mbsc-android-holo .mbsc-progress-bar{background:#31c6e7}.mbsc-android-holo .mbsc-progress-value{font-size:.75em;width:4em}.mbsc-android-holo.mbsc-progress.mbsc-progress-value-left .mbsc-input-wrap{padding-left:3.25em}.mbsc-android-holo.mbsc-progress.mbsc-progress-value-right .mbsc-input-wrap{padding-right:3.25em}.mbsc-android-holo .mbsc-progress-step-label{top:1.5em}.mbsc-wp.mbsc-progress{padding:.75em 1em}.mbsc-wp.mbsc-progress .mbsc-label{padding-bottom:.3125em;color:#878787;font-size:1em}.mbsc-wp.mbsc-progress .mbsc-input-wrap{padding:0}.mbsc-wp.mbsc-progress .mbsc-input-ic{color:#fff;top:-.1875em}.mbsc-wp.mbsc-progress.mbsc-ic-right{padding-right:3.25em}.mbsc-wp.mbsc-progress.mbsc-ic-left{padding-left:3.25em}.mbsc-wp.mbsc-progress.mbsc-ic-right .mbsc-right-ic{left:auto;right:-2.375em}.mbsc-wp.mbsc-progress.mbsc-ic-left .mbsc-left-ic{right:auto;left:-2.375em}.mbsc-wp.mbsc-progress.mbsc-progress-value-left .mbsc-input-wrap{padding-left:3.5em}.mbsc-wp.mbsc-progress.mbsc-progress-value-right .mbsc-input-wrap{padding-right:3.5em}.mbsc-wp .mbsc-progress-cont{padding:.75em 0}.mbsc-wp .mbsc-progress-track{background:#1f1f1f;height:.125em}.mbsc-wp .mbsc-progress-bar{background:#1a9fe0}.mbsc-wp .mbsc-progress-value{margin-top:-.4375em;width:3.5em}.mbsc-wp .mbsc-progress-step-label{top:1.5em}.mbsc-material.mbsc-progress{padding:.75em 1em}.mbsc-material.mbsc-progress .mbsc-input-wrap{padding:0}.mbsc-material.mbsc-progress .mbsc-input-ic{top:.3125em;margin:0}.mbsc-material.mbsc-progress.mbsc-ic-left{padding-left:3.25em}.mbsc-material.mbsc-progress.mbsc-ic-right{padding-right:3.25em}.mbsc-material.mbsc-progress.mbsc-ic-left .mbsc-left-ic{right:auto;left:-2.625em}.mbsc-material.mbsc-progress.mbsc-ic-right .mbsc-right-ic{left:auto;right:-2.625em}.mbsc-material.mbsc-progress .mbsc-label{font-size:.75em}.mbsc-material .mbsc-progress-cont{padding:1.25em 0}.mbsc-material .mbsc-progress-track{background:#b2b2b2;height:.125em}.mbsc-material .mbsc-progress-bar{background:#009688}.mbsc-material .mbsc-progress-value{font-size:.75em;width:4em}.mbsc-material .mbsc-progress.mbsc-progress-value-left .mbsc-input-wrap{padding-left:3.25em}.mbsc-material .mbsc-progress.mbsc-progress-value-right .mbsc-input-wrap{padding-right:3.25em}.mbsc-ios.mbsc-progress{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;min-height:2.75em;margin-top:-1px;border-bottom:1px solid #ccc;border-top:1px solid #ccc;padding:.625em 1em;background:#fff;align-items:center}.mbsc-ios.mbsc-progress.mbsc-progress-w .mbsc-label{padding:0}.mbsc-ios.mbsc-progress .mbsc-input-ic{top:.0625em;margin:0}.mbsc-ios.mbsc-progress.mbsc-ic-left .mbsc-left-ic{right:auto;left:-.375em}.mbsc-ios.mbsc-progress.mbsc-ic-right .mbsc-right-ic{left:auto;right:-.375em}.mbsc-ios.mbsc-progress .mbsc-input-wrap{position:relative;padding:0 .75em}.mbsc-ios.mbsc-progress.mbsc-progress-w.mbsc-ic-left .mbsc-input-wrap{padding-left:2.5em}.mbsc-ios.mbsc-progress.mbsc-progress-w.mbsc-ic-right .mbsc-input-wrap{padding-right:2.5em}.mbsc-ios.mbsc-progress.mbsc-progress-value-left .mbsc-input-wrap{padding-left:3.5em}.mbsc-ios.mbsc-progress.mbsc-progress-value-right .mbsc-input-wrap{padding-right:3.5em}.mbsc-ios.mbsc-progress.mbsc-ic-left.mbsc-progress-value-left .mbsc-input-wrap{padding-left:5.5em}.mbsc-ios.mbsc-progress.mbsc-ic-right.mbsc-progress-value-right .mbsc-input-wrap{padding-left:5.5em}.mbsc-ios.mbsc-progress.mbsc-ic-left.mbsc-progress-value-left .mbsc-progress-value{left:2.2857em}.mbsc-ios.mbsc-progress.mbsc-ic-right.mbsc-progress-value-right .mbsc-progress-value{right:2.2857em}.mbsc-ios .mbsc-progress-cont{padding:1em 0}.mbsc-ios .mbsc-progress-track{background:#dedede;border-radius:1em;height:.125em}.mbsc-ios .mbsc-progress-bar{background:#1272dc;z-index:1}.mbsc-ios .mbsc-progress-value{width:3.715em;font-size:.875em;color:#b6b6b6}.mbsc-ios .mbsc-progress-step-label{top:1.25em;color:#b6b6b6}.mbsc-progress.mbsc-slider input{display:none}.mbsc-progress-anim .mbsc-slider-handle-cont{-webkit-transition:left .1s ease-in-out;-moz-transition:left .1s ease-in-out;transition:left .1s ease-in-out}.mbsc-slider-handle-cont{position:absolute;width:2em;height:2em;right:-1em;top:.0625em;margin-top:-1em;cursor:pointer}.mbsc-slider-handle-cont.mbsc-slider-handle-left{left:0}.mbsc-progress-track .mbsc-slider-handle-cont{margin-left:-1em}.mbsc-slider-handle{position:absolute;top:.125em;right:50%;margin:-.5em -.5em 0 0;z-index:2;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-slider .mbsc-slider-handle:focus,.mbsc-slider .mbsc-active .mbsc-slider-handle{outline:0;z-index:15}.mbsc-slider-tooltip{position:absolute;right:50%;opacity:0;color:#000}.mbsc-slider-step{position:absolute;top:0;width:.125em;height:100%;margin-left:-.0625em}.mbsc-mobiscroll.mbsc-slider .mbsc-input-wrap{padding:0 .5em}.mbsc-mobiscroll .mbsc-slider-step{background:#f7f7f7}.mbsc-mobiscroll .mbsc-slider-handle{top:50%;width:1em;height:1em;border-radius:1.125em;background:#4eccc4;-webkit-transform:scale(1);-moz-transform:scale(1);transform:scale(1);-webkit-transition:-webkit-transform .2s ease-in-out;-moz-transition:-moz-transform .2s ease-in-out;transition:transform .2s ease-in-out}.mbsc-mobiscroll .mbsc-slider-handle:focus,.mbsc-mobiscroll .mbsc-active .mbsc-slider-handle{-webkit-transform:scale(1.3);-moz-transform:scale(1.3);transform:scale(1.3)}.mbsc-mobiscroll.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-track{opacity:.4}.mbsc-mobiscroll.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-slider-handle{-webkit-transform:scale(1);-moz-transform:scale(1);transform:scale(1)}.mbsc-mobiscroll .mbsc-slider-tooltip{top:-1.91666em;width:2.33333333em;height:2.33333333em;border-radius:2em;margin-right:-1.1666em;line-height:2.33333333em;text-align:center;font-size:.75em;color:#eee;background-color:#4eccc4;z-index:5;-webkit-transform:scale(.4) translate3d(0,5em,0);-moz-transform:scale(.4) translate3d(0,5em,0);transform:scale(.4) translate3d(0,5em,0);-webkit-transition:-webkit-transform .2s ease-in-out,opacity .2s ease-in-out;-moz-transition:-moz-transform .2s ease-in-out,opacity .2s ease-in-out;transition:transform .2s ease-in-out,opacity .2s ease-in-out}.mbsc-mobiscroll .mbsc-slider-handle:focus ~ .mbsc-slider-tooltip,.mbsc-mobiscroll .mbsc-active .mbsc-slider-tooltip{opacity:1;-webkit-transform:translate3d(0,0,0) scale(1);-moz-transform:translate3d(0,0,0) scale(1);transform:translate3d(0,0,0) scale(1)}.mbsc-mobiscroll.mbsc-slider-has-tooltip .mbsc-slider-handle:focus,.mbsc-mobiscroll.mbsc-slider-has-tooltip .mbsc-active .mbsc-slider-handle{-webkit-transform:scale(.5);-moz-transform:scale(.5);transform:scale(.5)}.mbsc-android-holo.mbsc-slider .mbsc-input-wrap{padding:0 .9375em}.mbsc-android-holo .mbsc-slider-step{background:#fff}.mbsc-android-holo .mbsc-slider-handle{top:50%;width:.625em;height:.625em;margin:-.3125em -.3125em 0 0;border-radius:.625em;background:#31c6e7}.mbsc-android-holo .mbsc-slider-handle.mbsc-active{background:#fff}.mbsc-android-holo .mbsc-slider-handle:after{position:absolute;top:50%;left:50%;margin-top:-.9375em;margin-left:-.9375em;border-radius:1.875em;height:1.875em;width:1.875em;content:' ';background:rgba(67,198,231,.3)}.mbsc-android-holo .mbsc-slider-handle:focus:after,.mbsc-android-holo .mbsc-active .mbsc-slider-handle:after{margin-top:-1em;margin-left:-1em;border:1px solid #29799c}.mbsc-android-holo .mbsc-slider-tooltip{width:3em;top:-1.25em;margin-right:-1.5em;text-align:center;color:#fff;background:#000;-webkit-transition:opacity .2s ease-in-out;-moz-transition:opacity .2s ease-in-out;transition:opacity .2s ease-in-out}.mbsc-android-holo .mbsc-slider-handle:focus ~ .mbsc-slider-tooltip,.mbsc-android-holo .mbsc-active .mbsc-slider-tooltip{opacity:1}.mbsc-android-holo.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-track{background:#fff;opacity:.4}.mbsc-android-holo.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-slider-handle{width:.25em;height:.25em;margin:-.125em -.125em 0 0;border-radius:.625em;background:#fff}.mbsc-android-holo.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-slider-handle:after{margin-top:-.9375em;margin-left:-.9375em;border:0;background:rgba(200,200,200,.5)}.mbsc-android-holo.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-bar{background:#a9a9a9}.mbsc-wp.mbsc-slider .mbsc-input-wrap{padding:0 .3125em}.mbsc-wp.mbsc-slider .mbsc-input-ic{top:.125em}.mbsc-wp.mbsc-slider .mbsc-progress-track{height:.75em}.mbsc-wp .mbsc-slider-handle{top:50%;width:.625em;height:1.125em;margin:-.25em -.3125em 0 0;background:#fff}.mbsc-wp .mbsc-slider-tooltip{height:2.16665em;width:3.16665em;margin-right:-1.6666em;top:-2.75em;border:2px solid #808080;line-height:2.16665em;background:#fff;text-align:center;font-size:.75em;font-weight:bold;color:#808080;-webkit-transition:opacity .2s ease-in-out;-moz-transition:opacity .2s ease-in-out;transition:opacity .2s ease-in-out}.mbsc-wp .mbsc-slider-handle:focus ~ .mbsc-slider-tooltip,.mbsc-wp .mbsc-active .mbsc-slider-tooltip{opacity:1}.mbsc-wp .mbsc-slider-step{background:#000}.mbsc-wp .mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-track{opacity:.4}.mbsc-material.mbsc-slider .mbsc-input-wrap{padding:0 .5em}.mbsc-material .mbsc-slider-step{background:#000}.mbsc-material .mbsc-slider-handle{top:50%;width:.75em;height:.75em;margin:-.5em -.5em 0 0;border:2px solid #009688;border-radius:1.125em;background:#009688;-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);transform:scale(1);-webkit-transition:-webkit-transform .2s ease-in-out;-moz-transition:-moz-transform .2s ease-in-out;transition:transform .2s ease-in-out}.mbsc-material .mbsc-slider-handle:focus,.mbsc-material .mbsc-active .mbsc-slider-handle{-webkit-transform:scale(1.4);-moz-transform:scale(1.4);-ms-transform:scale(1.4);transform:scale(1.4)}.mbsc-material .mbsc-slider-start .mbsc-slider-handle{border-color:#b2b2b2;background:#eee}.mbsc-material .mbsc-slider-handle:before{content:'';position:absolute;z-index:-1;top:-.625em;left:-.625em;width:2em;height:2em;opacity:0;background:rgba(0,0,0,.1);border-radius:2.875em;-webkit-transition:opacity .2s ease-in-out;-moz-transition:opacity .2s ease-in-out;transition:opacity .2s ease-in-out}.mbsc-material .mbsc-active .mbsc-slider-handle:before{opacity:1}.mbsc-material .mbsc-slider-tooltip{margin-right:-1.16665em;top:-2.5em;width:2.33333333em;height:2.33333333em;border-radius:2.33333333em;line-height:2.33333333em;text-align:center;font-size:.75em;color:#eee;background-color:#009688;-webkit-transform:scale(.4) translate3d(0,6em,0);-moz-transform:scale(.4) translate3d(0,6em,0);-ms-transform:scale(.4) translate(0,6em);transform:scale(.4) translate3d(0,6em,0);-webkit-transition:-webkit-transform .2s ease-in-out,opacity .2s ease-in-out;-moz-transition:-moz-transform .2s ease-in-out,opacity .2s ease-in-out;transition:transform .2s ease-in-out,opacity .2s ease-in-out}.mbsc-material .mbsc-slider-tooltip:before{position:absolute;content:'';width:0;height:0;border-left:.8333em solid transparent;border-right:.8333em solid transparent;border-top:1em solid #009688;bottom:-.66666em;left:.3333em}.mbsc-material.mbsc-slider-has-tooltip .mbsc-slider-handle:focus ~ .mbsc-slider-tooltip,.mbsc-material.mbsc-slider-has-tooltip .mbsc-active .mbsc-slider-tooltip{opacity:1;-webkit-transform:translate3d(0,0,0) scale(1);-moz-transform:translate3d(0,0,0) scale(1);-ms-transform:translate(0,0) scale(1);transform:translate3d(0,0,0) scale(1)}.mbsc-material.mbsc-slider-has-tooltip .mbsc-slider-handle:focus,.mbsc-material.mbsc-slider-has-tooltip .mbsc-active .mbsc-slider-handle{-webkit-transform:scale(0);-moz-transform:scale(0);-ms-transform:scale(0);transform:scale(0)}.mbsc-material.mbsc-slider-has-tooltip .mbsc-slider-start .mbsc-slider-tooltip{background:#b2b2b2}.mbsc-material.mbsc-slider-has-tooltip .mbsc-slider-start .mbsc-slider-tooltip:before{border-top-color:#b2b2b2}.mbsc-material.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-track{opacity:.4}.mbsc-material.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-bar{background:#b2b2b2}.mbsc-material.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-slider-handle{background:#b2b2b2;border-color:#b2b2b2;-webkit-transform:scale(.7);-moz-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7);-webkit-box-shadow:0 0 0 .3125em #eee;box-shadow:0 0 0 .3125em #eee}.mbsc-material.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-slider-start .mbsc-slider-handle{border-color:#b2b2b2;background:#eee}.mbsc-ios .mbsc-slider-handle{top:50%;width:1.5em;height:1.5em;margin:-.75em -.75em 0 0;background:#fff;border-radius:1.25em;-webkit-box-shadow:0 0 1em rgba(0,0,0,.1),0 0 .0625em rgba(0,0,0,.15),0 .125em .125em rgba(0,0,0,.15);box-shadow:0 0 1em rgba(0,0,0,.1),0 0 .0625em rgba(0,0,0,.15),0 .125em .125em rgba(0,0,0,.15)}.mbsc-ios .mbsc-slider-tooltip{font-size:.875em;width:3em;margin-right:-1.5em;top:-1em;text-align:center;color:#b6b6b6;opacity:0;-webkit-transition:opacity .2s ease-in-out;-moz-transition:opacity .2s ease-in-out;transition:opacity .2s ease-in-out}.mbsc-ios .mbsc-slider-handle:focus ~ .mbsc-slider-tooltip,.mbsc-ios .mbsc-active .mbsc-slider-tooltip{opacity:1}.mbsc-ios .mbsc-slider-step{height:.5em;margin-top:-.1875em;margin-left:-.0625em;background:#dedede}.mbsc-ios.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-track{opacity:.4}@-webkit-keyframes mbsc-lv-spin{from{-webkit-transform:rotate(180deg);opacity:0}to{-webkit-transform:rotate(0);opacity:1}}@-webkit-keyframes mbsc-lv-remove-right{to{-webkit-transform:translateX(100%)}}@-webkit-keyframes mbsc-lv-remove-left{to{-webkit-transform:translateX(-100%)}}@-webkit-keyframes mbsc-lv-add-right{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-webkit-keyframes mbsc-lv-add-left{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-webkit-keyframes mbsc-lv-pop-in{from{-webkit-transform:scale(0);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-webkit-keyframes mbsc-lv-pop-out{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(0);opacity:0}}@-webkit-keyframes mbsc-lv-collapse{from{padding:0;border:0}to{padding:0;border:0;height:0}}@-webkit-keyframes mbsc-lv-expand{from{padding:0;border:0;height:0}to{padding:0;border:0}}@-webkit-keyframes mbsc-lv-fill{from{-webkit-transform:scale(1,0)}to{-webkit-transform:scale(1,1)}}@-moz-keyframes mbsc-lv-spin{from{-moz-transform:rotate(180deg);opacity:0}to{-moz-transform:rotate(0);opacity:1}}@-moz-keyframes mbsc-lv-remove-right{to{-moz-transform:translateX(100%)}}@-moz-keyframes mbsc-lv-remove-left{to{-moz-transform:translateX(-100%)}}@-moz-keyframes mbsc-lv-add-right{from{-moz-transform:translateX(100%)}}@-moz-keyframes mbsc-lv-add-left{from{-moz-transform:translateX(-100%)}}@-moz-keyframes mbsc-lv-pop-in{from{-moz-transform:scale(0);opacity:0}}@-moz-keyframes mbsc-lv-pop-out{to{-moz-transform:scale(0);opacity:0}}@-moz-keyframes mbsc-lv-collapse{from{padding:0;border:0}to{padding:0;border:0;height:0}}@-moz-keyframes mbsc-lv-expand{from{padding:0;border:0;height:0}to{padding:0;border:0}}@-moz-keyframes mbsc-lv-fill{from{-moz-transform:scale(1,0)}to{-moz-transform:scale(1,1)}}@keyframes mbsc-lv-spin{from{transform:rotate(180deg);opacity:0}to{transform:rotate(0);opacity:1}}@keyframes mbsc-lv-remove-right{to{transform:translateX(100%)}}@keyframes mbsc-lv-remove-left{to{transform:translateX(-100%)}}@keyframes mbsc-lv-add-right{from{transform:translateX(100%)}to{transform:translateX(0)}}@keyframes mbsc-lv-add-left{from{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes mbsc-lv-pop-in{from{transform:scale(0);opacity:0}to{transform:scale(1)}}@keyframes mbsc-lv-pop-out{from{transform:scale(1)}to{transform:scale(0);opacity:0}}@keyframes mbsc-lv-collapse{from{padding:0;border:0}to{padding:0;border:0;height:0}}@keyframes mbsc-lv-expand{from{padding:0;border:0;height:0}to{padding:0;border:0}}@keyframes mbsc-lv-fill{from{transform:scale(1,0)}to{transform:scale(1,1)}}.mbsc-lv-stage-c-v,.mbsc-lv-item,.mbsc-lv-gr-title,.mbsc-lv-ic-text{-webkit-transform:translate3d(0,0,0)}.mbsc-lv,.mbsc-lv-fill-item{-webkit-backface-visibility:hidden}.mbsc-lv-cont{overflow:hidden;-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-ms-touch-action:pan-y;user-select:none;touch-action:pan-y}.mbsc-lv-rtl{direction:rtl}.mbsc-lv{display:none;position:relative;list-style:none;margin:0;padding:0}.mbsc-lv-dummy{display:block;margin-top:-1px;-webkit-backface-visibility:visible}.mbsc-lv-v{display:block}.mbsc-lv-gr-title{position:relative;z-index:2}.mbsc-lv-fixed-header{position:fixed;display:none;top:0;left:0;list-style:none;width:100%;z-index:10}.mbsc-lv-fixed-header-ctx{position:absolute}.mbsc-lv .mbsc-lv-item{position:relative;z-index:1;list-style:none}.mbsc-lv .mbsc-lv-item.mbsc-lv-item-swiping{overflow:visible}.mbsc-lv-stage-c{overflow:hidden;position:absolute;top:0;left:0;width:100%;display:none}.mbsc-lv-stage-c-v{display:block}.mbsc-lv-item-new-left{-webkit-transform:translateX(-100%)}.mbsc-lv-item-new-right{-webkit-transform:translateX(100%)}.mbsc-lv-item-remove-right{-webkit-animation:mbsc-lv-remove-right 200ms;-webkit-animation-delay:1ms;-moz-animation:mbsc-lv-remove-right 200ms;animation:mbsc-lv-remove-right 200ms}.mbsc-lv-item-remove-left{-webkit-animation:mbsc-lv-remove-left 200ms;-webkit-animation-delay:1ms;-moz-animation:mbsc-lv-remove-left 200ms;animation:mbsc-lv-remove-left 200ms}.mbsc-lv-item-add-right{-webkit-animation:mbsc-lv-add-right 200ms;-moz-animation:mbsc-lv-add-right 200ms;animation:mbsc-lv-add-right 200ms}.mbsc-lv-item-add-left{-webkit-animation:mbsc-lv-add-left 200ms;-moz-animation:mbsc-lv-add-left 200ms;animation:mbsc-lv-add-left 200ms}.mbsc-lv-item-pop-in{-webkit-animation:mbsc-lv-pop-in 200ms;-moz-animation:mbsc-lv-pop-in 200ms;animation:mbsc-lv-pop-in 200ms}.mbsc-lv-item-pop-out{-webkit-animation:mbsc-lv-pop-out 200ms;-moz-animation:mbsc-lv-pop-out 200ms;animation:mbsc-lv-pop-out 200ms}.mbsc-lv-item-collapse{visibility:hidden;-webkit-animation:mbsc-lv-collapse 200ms;-moz-animation:mbsc-lv-collapse 200ms;animation:mbsc-lv-collapse 200ms}.mbsc-lv-item-expand{visibility:hidden;-webkit-animation:mbsc-lv-expand 200ms;-moz-animation:mbsc-lv-expand 200ms;animation:mbsc-lv-expand 200ms}.mbsc-lv .mbsc-lv-item.mbsc-lv-item-undo{position:absolute;left:0;right:0}.mbsc-lv-cont .mbsc-lv-item.mbsc-lv-item-dragging{z-index:100;position:absolute;left:0;right:0;opacity:.9;-webkit-box-shadow:0 0 .625em rgba(0,0,0,.5);box-shadow:0 0 .625em rgba(0,0,0,.5);cursor:pointer;cursor:-webkit-grabbing;cursor:grabbing}.mbsc-lv-item-dragging .mbsc-lv-handle-c{cursor:-webkit-grabbing;cursor:grabbing}.mbsc-lv .mbsc-lv-item.mbsc-lv-ph{padding:0;border:0;visibility:hidden}.mbsc-lv-fill-item{position:absolute;z-index:1000;top:0;left:0;width:100%;height:100%;background:#000;opacity:.1}.mbsc-lv-handle-c{position:absolute;z-index:4;top:0;height:100%;width:40px;-ms-touch-action:none;touch-action:none;cursor:pointer;cursor:-webkit-grab;cursor:grab}.mbsc-lv-item-h-right{right:0}.mbsc-lv-item-h-left{left:0}.mbsc-lv-cont.mbsc-lv-handle-left .mbsc-lv-item{padding-left:40px}.mbsc-lv-cont.mbsc-lv-handle-right .mbsc-lv-item{padding-right:40px}.mbsc-lv-handle-bar-c{position:absolute;width:100%;top:50%;margin-top:-7px}.mbsc-lv-handle-bar{position:relative;height:2px;margin:2px 10px;background:#888}.mbsc-lv-ic,.mbsc-lv-ic-m{top:0;text-align:center;text-shadow:none;font-size:.75em}.mbsc-lv-ic-s{position:absolute;top:50%;margin-top:-0.9375em;width:2.8em;height:1.875em;line-height:1.875em;font-size:1.25em}.mbsc-lv-ic-c{position:absolute;top:0;height:100%;line-height:1.875em;color:#fff;cursor:pointer}.mbsc-lv-ic-text{text-shadow:none;font-size:1em}.mbsc-ic-none{border:0}.mbsc-ic-none:before{content:"0";font-size:0;color:rgba(0,0,0,0)}.mbsc-lv-ic-anim .mbsc-lv-ic-s{opacity:0;-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);transform:rotate(180deg)}.mbsc-lv-ic-anim .mbsc-lv-ic-a{opacity:1;-webkit-animation:mbsc-lv-spin 200ms;-webkit-transform:rotate(0);-moz-animation:mbsc-lv-spin 200ms;-moz-transform:rotate(0);animation:mbsc-lv-spin 200ms;transform:rotate(0)}.mbsc-lv-ic-anim .mbsc-lv-ic-v{opacity:1;-webkit-animation:none;-webkit-transform:rotate(0);-moz-animation:none;-moz-transform:rotate(0);animation:none;transform:rotate(0)}.mbsc-lv-ic-left{left:0}.mbsc-lv-ic-move-left{left:0;-webkit-transform:translateX(-100%);-moz-transform:translateX(-100%);transform:translateX(-100%)}.mbsc-lv-ic-right .mbsc-lv-ic,.mbsc-lv-ic-move-left .mbsc-lv-ic{right:0}.mbsc-lv-ic-right .mbsc-lv-ic-text,.mbsc-lv-ic-move-left .mbsc-lv-ic-text{padding:0 3.5em 0 .625em}.mbsc-lv-ic-right{right:0}.mbsc-lv-ic-move-right{right:0;-webkit-transform:translateX(100%);-moz-transform:translateX(100%);transform:translateX(100%)}.mbsc-lv-ic-left .mbsc-lv-ic,.mbsc-lv-ic-move-right .mbsc-lv-ic{left:0}.mbsc-lv-ic-left .mbsc-lv-ic-text,.mbsc-lv-ic-move-right .mbsc-lv-ic-text{padding:0 .625em 0 3.5em}.mbsc-lv-ic-c .mbsc-lv-ic-only{width:0;padding:0 0 0 3.5em}.mbsc-lv-ic-c .mbsc-lv-ic-text-only{padding:0 .625em}.mbsc-lv-multi{position:absolute;top:0;display:none;width:90%;height:100%;table-layout:fixed}.mbsc-lv-multi-ic-right{right:0}.mbsc-lv-multi-ic-left{left:0}.mbsc-lv-right .mbsc-lv-multi-ic-right{display:table}.mbsc-lv-left .mbsc-lv-multi-ic-left{display:table}.mbsc-lv-ic-m{display:table-cell;vertical-align:middle;padding:0 .25em;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mbsc-lv-ic-m:before{display:block;padding:.25em 0;font-size:1.333334em}.mbsc-lv-ic-disabled{opacity:.5;filter:alpha(opacity=50)}.mbsc-lv-sl-c{position:relative;-webkit-animation-fill-mode:forwards;-webkit-transform:translateX(0);-moz-transform:translateX(0);transform:translateX(0)}.mbsc-lv-sl-r{-webkit-animation:mbsc-lv-remove-left 300ms;-moz-animation:mbsc-lv-remove-left 300ms;animation:mbsc-lv-remove-left 300ms}.mbsc-lv-sl-l{-webkit-animation:mbsc-lv-remove-right 300ms;-moz-animation:mbsc-lv-remove-right 300ms;animation:mbsc-lv-remove-right 300ms}.mbsc-lv-sl-l .mbsc-lv-sl-curr,.mbsc-lv-sl-r .mbsc-lv-sl-curr{position:absolute;top:0;left:0;width:100%}.mbsc-lv-sl-r .mbsc-lv-sl-new{-webkit-transform:translateX(100%);-moz-transform:translateX(100%);transform:translateX(100%)}.mbsc-lv-sl-l .mbsc-lv-sl-new{-webkit-transform:translateX(-100%);-moz-transform:translateX(-100%);transform:translateX(-100%)}.mbsc-lv-arr{position:absolute;top:50%;width:2em;height:2em;font-size:1em;line-height:2em;margin-top:-1em}.mbsc-lv .mbsc-lv-item.mbsc-lv-parent{padding-right:2.25em}.mbsc-lv .mbsc-lv-item.mbsc-lv-back{padding-left:2.25em}.mbsc-lv-parent .mbsc-lv-arr{right:0}.mbsc-lv-handle-right .mbsc-lv-parent .mbsc-lv-arr{right:40px}.mbsc-lv-back .mbsc-lv-arr{left:0}.mbsc-lv .mbsc-lv-txt{margin:0;font-weight:normal}.mbsc-lv h1.mbsc-lv-txt{font-size:2em}.mbsc-lv h2.mbsc-lv-txt{font-size:1.5em}.mbsc-lv h3.mbsc-lv-txt{font-size:1.125em}.mbsc-lv h4.mbsc-lv-txt{font-size:1em}.mbsc-lv h5.mbsc-lv-txt{font-size:.875em}.mbsc-lv h6.mbsc-lv-txt{font-size:.625em}.mbsc-lv p.mbsc-lv-txt{font-size:.75em}.mbsc-lv-img{position:absolute;max-height:2.8em;max-width:2.8em;top:50%;margin-top:-1.4em}.mbsc-lv-cont .mbsc-lv-item.mbsc-lv-img-left{padding-left:4.5em}.mbsc-lv-cont .mbsc-lv-item.mbsc-lv-img-right{padding-right:4.5em}.mbsc-lv-img-left .mbsc-lv-img{left:1.25em}.mbsc-lv-img-right .mbsc-lv-img{right:1.25em}.mbsc-lv-handle-left.mbsc-lv-cont .mbsc-lv-img-left{padding-left:6.5em}.mbsc-lv-handle-left .mbsc-lv-img-left .mbsc-lv-img{left:2.8em}.mbsc-lv-handle-right.mbsc-lv-cont .mbsc-lv-img-right{padding-right:6.5em}.mbsc-lv-handle-right .mbsc-lv-img-right .mbsc-lv-img{right:2.8em}.mbsc-lv-item-ic{position:absolute;top:50%;margin-top:-1em;width:2em;height:2em;line-height:2em;text-align:center}.mbsc-lv-cont .mbsc-lv-item.mbsc-lv-item-ic-left{padding-left:3.2em}.mbsc-lv-cont .mbsc-lv-item.mbsc-lv-item-ic-right{padding-right:3.2em}.mbsc-lv-item-ic-left .mbsc-lv-item-ic{left:.8em}.mbsc-lv-item-ic-right .mbsc-lv-item-ic{right:.8em}.mbsc-lv-handle-left.mbsc-lv-cont .mbsc-lv-item-ic-left{padding-left:4.8em}.mbsc-lv-handle-left .mbsc-lv-item-ic-left .mbsc-lv-item-ic{left:2.3em}.mbsc-lv-handle-right.mbsc-lv-cont .mbsc-lv-item-ic-right{padding-right:4.8em}.mbsc-lv-handle-right .mbsc-lv-item-ic-right .mbsc-lv-item-ic{right:2.3em}.mbsc-lv-mobiscroll{font-family:arial,verdana,sans-serif;padding-top:1px}.mbsc-lv-mobiscroll .mbsc-lv{background:#b1b1b1}.mbsc-lv-mobiscroll .mbsc-lv-item{margin-top:-1px;padding:1.25em;background:#f7f7f7;color:#454545;font-weight:normal}.mbsc-lv-mobiscroll.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#eee}.mbsc-lv-mobiscroll .mbsc-lv-item-hl:after,.mbsc-lv-mobiscroll .mbsc-lv-item-active::after{content:'';position:absolute;width:100%;height:100%;top:0;left:0;display:block;background:rgba(78,204,196,.3)}.mbsc-lv-mobiscroll .mbsc-lv-gr-title{padding:0 1.25em;font-size:.75em;text-transform:uppercase;line-height:2em;background:#4eccc4;margin-top:-1px;color:#f7f7f7}.mbsc-lv-mobiscroll .mbsc-lv-ic-m{color:#fff}.mbsc-lv-mobiscroll .mbsc-lv-arr{color:#4eccc4}.mbsc-lv-mobiscroll .mbsc-lv-handle-bar{background:#4eccc4}.mbsc-lv-mobiscroll .mbsc-lv-item.mbsc-lv-item-dragging{margin:0;background:#4eccc4;color:#f7f7f7;-webkit-box-shadow:none;box-shadow:none}.mbsc-lv-mobiscroll .mbsc-lv-item-dragging .mbsc-lv-handle-bar{background:#f7f7f7}.mbsc-lv-mobiscroll .mbsc-lv-item-dragging .mbsc-lv-arr{color:#f7f7f7}.mbsc-lv-android-holo{font-family:arial,verdana,sans-serif}.mbsc-lv-android-holo .mbsc-lv{background:#484848}.mbsc-lv-android-holo .mbsc-lv-item{margin-top:-1px;padding:1.25em;background:#292829;border-top:1px solid #484848;border-bottom:1px solid #484848;color:#fff;font-weight:normal;text-shadow:none}.mbsc-lv-android-holo.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#383838}.mbsc-lv-android-holo .mbsc-lv-item-hl,.mbsc-lv-android-holo .mbsc-lv .mbsc-lv-item.mbsc-lv-item-active{background:#484848}.mbsc-lv-android-holo .mbsc-lv-fill-item{background:rgba(255,255,255,.5)}.mbsc-lv-android-holo .mbsc-lv-handle-bar-c{margin-top:-9px;border-left:1px solid #888}.mbsc-lv-android-holo .mbsc-lv-handle-bar{margin:3px 10px;background:#888}.mbsc-lv-android-holo.mbsc-lv-handle-right .mbsc-lv-item{padding-right:50px}.mbsc-lv-android-holo.mbsc-lv-handle-left .mbsc-lv-item{padding-left:50px}.mbsc-lv-android-holo.mbsc-lv-handle-left .mbsc-lv-handle-bar-c{border-left:0;border-right:1px solid #888}.mbsc-lv-android-holo .mbsc-lv-gr-title{background:#292829;line-height:2em;color:#bbb;font-size:.8em;font-weight:bold;padding:1em 1.625em 0 1.625em;margin-top:-1px;border-bottom:2px solid #484848;text-transform:uppercase}.mbsc-lv-android-holo .mbsc-lv-ic-m{color:#fff}.mbsc-lv-android-holo .mbsc-lv .mbsc-lv-item.mbsc-lv-item-dragging{margin:0;background:#31b6e7;background:rgba(49,182,231,.5);-webkit-box-shadow:none;box-shadow:none}.mbsc-lv-android-holo .mbsc-lv-gr-title .mbsc-lv-img{height:2.4em;margin-top:-.1em}.mbsc-lv-android-holo.mbsc-lv-handle-right .mbsc-lv-img-right .mbsc-lv-img{right:3.2em}.mbsc-lv-android-holo.mbsc-lv-handle-left .mbsc-lv-img-left .mbsc-lv-img{left:3.2em}.mbsc-lv-android-holo.mbsc-lv-handle-right .mbsc-lv-item-ic-right .mbsc-lv-item-ic{right:2.8em}.mbsc-lv-android-holo.mbsc-lv-handle-left .mbsc-lv-item-ic-left .mbsc-lv-item-ic{left:2.8em}.mbsc-lv-android-holo.mbsc-lv-handle-left .mbsc-lv-img-left{padding-left:6.8em}.mbsc-lv-android-holo.mbsc-lv-handle-right .mbsc-lv-img-right{padding-right:6.8em}.mbsc-lv-android-holo.mbsc-lv-handle-left .mbsc-lv-item-ic-left{padding-left:5em}.mbsc-lv-android-holo.mbsc-lv-handle-right .mbsc-lv-item-ic-right{padding-right:5em}.mbsc-lv-wp .mbsc-lv{background:#b1b1b1}.mbsc-lv-wp .mbsc-lv-item{margin-top:-1px;padding:1.25em;background:#1f1f1f;color:#fff;font-family:Segoe UI,arial,verdana,sans-serif;font-weight:normal;text-shadow:none}.mbsc-lv-wp .mbsc-lv-fill-item{background:rgba(255,255,255,.5)}.mbsc-lv-wp .mbsc-lv .mbsc-lv-item.mbsc-lv-item-dragging{margin:0;opacity:.7;-webkit-box-shadow:none;box-shadow:none}.mbsc-lv-wp .mbsc-lv-handle-bar{background:#888}.mbsc-lv-wp .mbsc-lv-gr-title{background:#1f1f1f;color:#fff;font-size:1.5em;padding:0 .45em;line-height:2em}.mbsc-lv-wp .mbsc-lv-ic-m{color:#1f1f1f}.mbsc-lv-wp.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#303030}.mbsc-lv-wp .mbsc-lv-item-hl,.mbsc-lv-wp .mbsc-lv .mbsc-lv-item.mbsc-lv-item-active{background:#b1b1b1;color:#000}.mbsc-lv-material{font-family:arial,verdana,sans-serif;padding-top:1px}.mbsc-lv-material .mbsc-lv{background:#b1b1b1}.mbsc-lv-material .mbsc-lv-item{overflow:hidden;margin-top:-1px;padding:1.1875em 1em;background:#eee;color:#5b5b5b;font-weight:normal}.mbsc-lv-material .mbsc-lv-item:after{content:'';position:absolute;width:100%;height:100%;top:0;left:0;display:block;pointer-events:none;-webkit-transition:background-color .2s ease-out;-moz-transition:background-color .2s ease-out;transition:background-color .2s ease-out}.mbsc-lv-material .mbsc-lv-item-hl:after,.mbsc-lv-material .mbsc-lv-item-active::after,.mbsc-lv-material .mbsc-lv-item-dragging:after{background:rgba(0,0,0,.1)}.mbsc-lv-material .mbsc-lv-item-dragging{margin:0}.mbsc-lv-material .mbsc-lv-item-swiping{overflow:visible}.mbsc-lv-material.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#f7f7f7}.mbsc-lv-material.mbsc-lv-handle-left .mbsc-lv-item{padding-left:3.125em}.mbsc-lv-material.mbsc-lv-handle-right .mbsc-lv-item{padding-right:3.125em}.mbsc-lv-material .mbsc-lv-gr-title{margin-top:-1px;font-size:.875em;font-weight:bold;padding:.5714em 1.14285em;background:#eee;color:#009688}.mbsc-lv-material .mbsc-lv-ic-m{color:#fff}.mbsc-lv-material .mbsc-lv-arr{color:#009688}.mbsc-lv-material .mbsc-lv-arr:before{font-size:1.5em}.mbsc-lv-material .mbsc-lv-handle-c{width:3.125em}.mbsc-lv-material .mbsc-lv-handle-bar{margin:.125em 1em;background:#009688}.mbsc-lv-material .mbsc-lv-img{max-height:2.5em;max-width:2.5em;margin-top:-1.25em}.mbsc-lv-material .mbsc-lv-img-left .mbsc-lv-img{left:1em}.mbsc-lv-material .mbsc-lv-img-right .mbsc-lv-img{right:1em}.mbsc-lv-material.mbsc-lv-handle-left .mbsc-lv-img-left .mbsc-lv-img{left:3.125em}.mbsc-lv-material.mbsc-lv-handle-right .mbsc-lv-img-right .mbsc-lv-img{left:3.125em}.mbsc-lv-material .mbsc-lv-item-ic-left .mbsc-lv-item-ic{left:1em}.mbsc-lv-material.mbsc-lv-cont .mbsc-lv-item.mbsc-lv-item-ic-left{padding-left:4em}.mbsc-lv-material .mbsc-lv-item-ic-right .mbsc-lv-item-ic{right:1em}.mbsc-lv-material.mbsc-lv-cont .mbsc-lv-item.mbsc-lv-item-ic-right{padding-right:4em}.mbsc-lv-material p.mbsc-lv-txt{color:#757575}.mbsc-lv-ios{font-family:arial,verdana,sans-serif;border-top:1px solid #ccc}.mbsc-lv-ios .mbsc-lv{background:#e3e3e3}.mbsc-lv-ios .mbsc-lv-item{margin-top:-1px;padding:.938em 1.25em;background:#fff;border-top:1px solid #ccc;border-bottom:1px solid #ccc;color:#000;font-weight:normal;text-shadow:none}.mbsc-lv-ios.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#fafafa}.mbsc-lv-ios .mbsc-lv-item-hl,.mbsc-lv-ios .mbsc-lv .mbsc-lv-item.mbsc-lv-item-active{background:#d9d9d9;border-color:#d9d9d9}.mbsc-lv-ios .mbsc-lv-item.mbsc-lv-item-dragging{margin:0}.mbsc-lv-ios .mbsc-lv-handle-c{width:3.75em}.mbsc-lv-ios .mbsc-lv-handle-bar{margin:.125em .9375em;background:#ccc}.mbsc-lv-ios.mbsc-lv-handle-left .mbsc-lv-item{padding-left:3.75em}.mbsc-lv-ios.mbsc-lv-handle-right .mbsc-lv-item{padding-right:3.75em}.mbsc-lv-ios.mbsc-lv-handle-right .mbsc-lv-parent .mbsc-lv-arr{right:3.125em}.mbsc-lv-ios .mbsc-lv-gr-title{margin-top:-1px;padding:0 1.615em;background:#f5f5f5;color:#777;font-size:.875em;font-weight:bold;line-height:2em;text-transform:uppercase;border-top:1px solid #ccc;border-bottom:1px solid #ccc}.mbsc-lv-ios .mbsc-lv-ic-m{color:#777}.mbsc-lv-ios .mbsc-lv-arr{color:#ccc}.mbsc-lv-ios.mbsc-lv-handle-right .mbsc-lv-img-right .mbsc-lv-img{right:3.4em}.mbsc-lv-ios.mbsc-lv-handle-left .mbsc-lv-img-left .mbsc-lv-img{left:3.4em}.mbsc-lv-ios.mbsc-lv-handle-right .mbsc-lv-item-ic-right .mbsc-lv-item-ic{right:3em}.mbsc-lv-ios.mbsc-lv-handle-left .mbsc-lv-item-ic-left .mbsc-lv-item-ic{left:3em}.mbsc-lv-ios.mbsc-lv-handle-left .mbsc-lv-img-left{padding-left:7em}.mbsc-lv-ios.mbsc-lv-handle-right .mbsc-lv-img-right{padding-right:7em}.mbsc-lv-ios.mbsc-lv-handle-left .mbsc-lv-item-ic-left{padding-left:5.2em}.mbsc-lv-ios.mbsc-lv-handle-right .mbsc-lv-item-ic-right{padding-right:5.2em}.mbsc-fr-popup,.mbsc-fr-overlay{-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:rgba(0,0,0,0)}.mbsc-fr-lock{-ms-touch-action:none;touch-action:none}.mbsc-fr-popup{max-width:98%;position:absolute;top:0;left:0;z-index:2;font-size:12px;text-shadow:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-ms-touch-action:pan-y;touch-action:pan-y}.mbsc-fr-popup:focus{outline:0;outline-offset:-2px}.mbsc-rtl{direction:rtl}.mbsc-fr-popup,.mbsc-fr-btn-cont{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-fr-w{min-width:200px;zoom:1;overflow:hidden;text-align:center;font-family:arial,verdana,sans-serif;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-fr-persp,.mbsc-fr-overlay{width:100%;height:100%;position:absolute;top:0;left:0}.mbsc-fr-persp{z-index:99998;pointer-events:auto}.mbsc-fr-overlay{z-index:1;background:rgba(0,0,0,.7)}.mbsc-fr-liq .mbsc-fr-popup{max-width:100%}.mbsc-fr-top .mbsc-fr-popup,.mbsc-fr-bottom .mbsc-fr-popup{width:100%;max-width:100%}.mbsc-fr-inline .mbsc-fr-popup{position:static;display:inline-block;max-width:100%}.mbsc-fr-inline.mbsc-fr-liq .mbsc-fr-persp .mbsc-fr-popup{display:block}.mbsc-fr-inline .mbsc-fr-persp{position:static}.mbsc-fr-bubble .mbsc-fr-popup{margin:20px 0}.mbsc-fr-bubble .mbsc-fr-arr-w{position:absolute;z-index:1;left:0;width:100%}.mbsc-fr-bubble-top .mbsc-fr-arr-w{bottom:-36px}.mbsc-fr-bubble-bottom .mbsc-fr-arr-w{top:-36px}.mbsc-fr-bubble .mbsc-fr-arr-i{margin:0 30px;position:relative;height:36px}.mbsc-fr-bubble .mbsc-fr-arr{display:block}.mbsc-fr-arr{display:none;position:absolute;left:0;width:0;height:0;border-width:18px 18px;border-style:solid;margin-left:-18px}.mbsc-fr-bubble-bottom .mbsc-fr-arr{top:0}.mbsc-fr-bubble-top .mbsc-fr-arr{bottom:0}.mbsc-fr-hdn{width:0;height:0;margin:0;padding:0;border:0;overflow:hidden}.mbsc-fr-hdr{overflow:hidden;text-overflow:ellipsis}.mbsc-fr-btn{overflow:hidden;display:block;text-decoration:none;white-space:nowrap;text-overflow:ellipsis;vertical-align:top}.mbsc-fr-btn-e{cursor:pointer}.mbsc-fr-btn-d{cursor:default}.mbsc-fr-btn-cont{display:table;width:100%;text-align:center}.mbsc-fr-btn-cont .mbsc-fr-btn-d{opacity:.3}.mbsc-fr-btn-w{vertical-align:top;display:table-cell;position:relative;z-index:5}.mbsc-fr-btn-w .mbsc-fr-btn:before{padding:.375em}.mbsc-wdg .mbsc-wdg-c{position:relative;z-index:0;padding:1em;font-size:14px;text-align:left;white-space:normal}.mbsc-mobiscroll .mbsc-fr-w{min-width:16em;background:#f7f7f7;color:#454545;font-size:16px}.mbsc-mobiscroll .mbsc-fr-hdr{padding:0 .6666em;padding-top:.6666em;color:#4eccc4;font-size:.75em;text-transform:uppercase;min-height:2em;line-height:2em}.mbsc-mobiscroll .mbsc-fr-btn-cont{display:block;overflow:hidden;text-align:right;padding:0 .5em .5em .5em}.mbsc-mobiscroll .mbsc-fr-btn-w{display:block;float:right}.mbsc-mobiscroll .mbsc-rtl .mbsc-fr-btn-w{float:left}.mbsc-mobiscroll .mbsc-fr-btn{height:2.5em;line-height:2.5em;padding:0 1em;color:#4eccc4;text-transform:uppercase}.mbsc-mobiscroll .mbsc-fr-btn-a{background:rgba(78,204,196,.3)}.mbsc-mobiscroll .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #f7f7f7 transparent}.mbsc-mobiscroll .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#f7f7f7 transparent transparent transparent}.mbsc-android-holo .mbsc-fr-w{background:#292829;color:#fff;border-radius:.1875em;font-size:16px}.mbsc-android-holo .mbsc-fr-hdr{color:#31b6e7;padding:0 .5em;min-height:2em;line-height:2em;border-bottom:2px solid #31b6e7;font-size:1.125em}.mbsc-android-holo .mbsc-fr-btn-cont{border-top:1px solid #424542}.mbsc-android-holo .mbsc-fr-btn{height:2.6em;line-height:2.6em;color:#fff;font-size:.875em}.mbsc-android-holo .mbsc-fr-btn-a{background:#29799c}.mbsc-android-holo .mbsc-fr-btn-w .mbsc-fr-btn{border-left:1px solid #424542}.mbsc-android-holo .mbsc-fr-btn-w:first-child .mbsc-fr-btn{border:0;border-radius:0 0 0 .1875em}.mbsc-android-holo .mbsc-fr-btn-w:last-child .mbsc-fr-btn{border-radius:0 0 .1875em 0}.mbsc-android-holo .mbsc-rtl .mbsc-fr-btn-w .mbsc-fr-btn{border:0;border-right:1px solid #424542}.mbsc-android-holo .mbsc-rtl .mbsc-fr-btn-w:last-child .mbsc-fr-btn{border-radius:0 0 0 .1875em}.mbsc-android-holo .mbsc-rtl .mbsc-fr-btn-w:first-child .mbsc-fr-btn{border:0;border-radius:0 0 .1875em 0}.mbsc-android-holo.mbsc-fr-liq .mbsc-fr-w,.mbsc-android-holo.mbsc-fr-top .mbsc-fr-w,.mbsc-android-holo.mbsc-fr-bottom .mbsc-fr-w,.mbsc-android-holo.mbsc-fr-liq .mbsc-fr-btn-w .mbsc-fr-btn,.mbsc-android-holo.mbsc-fr-top .mbsc-fr-btn-w .mbsc-fr-btn,.mbsc-android-holo.mbsc-fr-bottom .mbsc-fr-btn-w .mbsc-fr-btn{border-radius:0}.mbsc-android-holo .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #292829 transparent}.mbsc-android-holo .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#292829 transparent transparent transparent}.mbsc-wp .mbsc-fr-w{padding:.625em;background:#1f1f1f;color:#fff;font-size:16px}.mbsc-wp .mbsc-fr-hdr{font-size:.75em;padding:.833em;min-height:14px;padding-top:0}.mbsc-wp .mbsc-fr-btn-cont{display:block}.mbsc-wp .mbsc-fr-btn-w{display:inline-block}.mbsc-wp .mbsc-fr-btn,.mbsc-wp .mbsc-ic:before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-wp .mbsc-fr-btn{position:relative;top:0;min-width:2.9em;display:inline-block;height:1.818em;padding:2.636em .455em 0 .455em;color:#fff;line-height:1.818em;font-size:.688em;text-transform:lowercase;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.mbsc-wp .mbsc-fr-btn:before{position:absolute;top:.3em;left:50%;width:2.2em;height:2.2em;margin:0 -1.3em;padding:0;border:2px solid #fff;line-height:2.2em;font-size:.909em;text-align:center;border-radius:1000px}.mbsc-wp .mbsc-fr-btn-a{top:-.3em}.mbsc-wp .mbsc-fr-btn-a:before{background:#1a9fe0;border-color:#1a9fe0;color:#fff}.mbsc-wp .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #1f1f1f transparent}.mbsc-wp .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#1f1f1f transparent transparent transparent}.mbsc-material .mbsc-fr-overlay{background:rgba(0,0,0,.6);filter:Alpha(Opacity=60)}.mbsc-material .mbsc-fr-w{min-width:15em;background:#eee;border-radius:.1875em;color:#5b5b5b;font-size:16px;box-shadow:0 .5em 1em rgba(0,0,0,.2)}.mbsc-material .mbsc-fr-hdr{padding:0 .6666em;padding-top:.6666em;color:#009688;font-size:.75em;font-weight:bold;text-transform:uppercase;min-height:2em;line-height:2em}.mbsc-material .mbsc-fr-btn-cont{display:block;overflow:hidden;text-align:right;padding:0 .5em .5em .5em}.mbsc-material .mbsc-fr-btn-w{display:block;float:right}.mbsc-material .mbsc-rtl .mbsc-fr-btn-w{float:left}.mbsc-material .mbsc-fr-btn{position:relative;height:2.4em;line-height:2.4em;padding:0 1em;font-weight:bold;color:#009688;text-transform:uppercase}.mbsc-material .mbsc-fr-btn-cont .mbsc-fr-btn{font-size:.9375em}.mbsc-material .mbsc-fr-btn-e{-webkit-transition:background-color .2s ease-out;-moz-transition:background-color .2s ease-out;transition:background-color .2s ease-out}.mbsc-material .mbsc-fr-btn-a{background:rgba(0,0,0,.1);border-radius:2px}.mbsc-material.mbsc-fr-inline .mbsc-fr-w{box-shadow:none}.mbsc-material.mbsc-fr-liq .mbsc-fr-w,.mbsc-material.mbsc-fr-top .mbsc-fr-w,.mbsc-material.mbsc-fr-bottom .mbsc-fr-w{border-radius:0}.mbsc-material .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #eee transparent}.mbsc-material .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#eee transparent transparent transparent}.mbsc-ios .mbsc-fr-overlay{background:rgba(0,0,0,.2);filter:Alpha(Opacity=20)}.mbsc-ios .mbsc-fr-w{position:relative;background:#f7f7f7;color:#000;padding-top:3.333334em;font-size:12px}.mbsc-ios .mbsc-fr-hdr{padding:0 .416667em;color:#9d9d9d;line-height:2.5em;min-height:2.5em;border-bottom:1px solid #acacac}.mbsc-ios .mbsc-fr-btn-cont{position:absolute;top:0;left:0;width:100%;border-bottom:1px solid #acacac}.mbsc-ios .mbsc-fr-btn{height:2.352941em;line-height:2.352941em;padding:0 .588235em;display:inline-block;color:#007aff;font-size:1.416667em}.mbsc-ios .mbsc-fr-btn-a{opacity:.5}.mbsc-ios .mbsc-fr-btn-w{display:block;float:right}.mbsc-ios .mbsc-fr-btn-c{float:left}.mbsc-ios .mbsc-fr-btn-s .mbsc-fr-btn{font-weight:bold}.mbsc-ios .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #f7f7f7 transparent}.mbsc-ios .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#f7f7f7 transparent transparent transparent}.mbsc-ios.mbsc-fr-bubble .mbsc-fr-w{border-radius:8px}.mbsc-ios.mbsc-fr-nobtn .mbsc-fr-w{padding-top:0}.mbsc-bootstrap .mbsc-fr-popup{display:block;padding:0}.mbsc-bootstrap .mbsc-fr-hdr{padding:0 14px;min-height:37px;line-height:37px}.mbsc-bootstrap .mbsc-fr-w{font-family:inherit;padding:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-bootstrap .mbsc-fr-overlay{background:rgba(0,0,0,.5)}.mbsc-bootstrap .mbsc-fr-btn-cont{padding:0 2px 4px 2px}.mbsc-bootstrap .mbsc-fr-btn{display:block;margin:0 2px}.mbsc-bootstrap.mbsc-inline .mbsc-fr-popup{display:inline-block}.mbsc-bootstrap.mbsc-fr-liq .mbsc-fr-popup,.mbsc-bootstrap.mbsc-fr-top .mbsc-fr-popup,.mbsc-bootstrap.mbsc-fr-bottom .mbsc-fr-popup{border-radius:0}.mbsc-bootstrap.mbsc-fr-bubble .mbsc-fr-popup{margin:11px 0}.mbsc-bootstrap .mbsc-fr-arr-i{height:0;margin:0 10px;padding:0;border:0;display:block}.mbsc-bootstrap .mbsc-fr-bubble-top .mbsc-fr-arr-w{bottom:0}.mbsc-bootstrap .mbsc-fr-bubble-bottom .mbsc-fr-arr-w{top:0}.mbsc-form{font-size:16px;font-family:arial,verdana,sans-serif;-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-text-size-adjust:100%}.mbsc-wdg .mbsc-w-p .mbsc-form{background:0}.mbsc-rtl{direction:rtl}.mbsc-err-msg{display:block;font-size:.75em}.mbsc-checkbox,.mbsc-switch,.mbsc-radio,.mbsc-stepper-cont{line-height:1.25em}.mbsc-checkbox,.mbsc-switch,.mbsc-btn,.mbsc-radio,.mbsc-segmented,.mbsc-stepper-cont{-ms-touch-action:pan-y;touch-action:pan-y}.mbsc-input,.mbsc-checkbox,.mbsc-switch,.mbsc-btn,.mbsc-radio,.mbsc-segmented,.mbsc-stepper-cont{position:relative;display:block;margin:0;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mbsc-input input,.mbsc-input select,.mbsc-input textarea,.mbsc-checkbox input,.mbsc-switch input,.mbsc-radio input,.mbsc-segmented input,.mbsc-btn{margin:0;padding:0;border:0;border-radius:0;outline:0;font-family:inherit;-webkit-appearance:none;-moz-appearance:none;appearance:none}.mbsc-checkbox input,.mbsc-switch input,.mbsc-radio input,.mbsc-select select,.mbsc-segmented input{position:absolute;z-index:3;top:0;left:0;width:100%;height:100%;border:0;opacity:0;margin:0;filter:alpha(opacity=0)}.mbsc-btn,.mbsc-input input,.mbsc-input textarea,.mbsc-checkbox-box,.mbsc-checkbox-box:after,.mbsc-radio-box,.mbsc-radio-box:after,.mbsc-switch-track,.mbsc-input-wrap,.mbsc-segmented,.mbsc-progress progress,.mbsc-stepper-cont{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-segmented-content,.mbsc-btn-flat .mbsc-btn-ic{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-desc{display:block;font-size:.75em;color:#a0a0a0}.mbsc-label{display:block}.mbsc-input textarea,.mbsc-input input,.mbsc-input select{display:block;width:100%;font-size:1em}.mbsc-input input:focus,.mbsc-input textarea:focus,.mbsc-input select:focus,.mbsc-btn:focus{outline:0}.mbsc-input .mbsc-label{font-size:.875em}.mbsc-input-wrap{position:relative;display:block}.mbsc-input-ic{position:absolute;height:2em;width:2em;line-height:2em;text-align:center}.mbsc-ic-right .mbsc-input-ic{right:.5em}.mbsc-ic-left .mbsc-input-ic{left:.5em}.mbsc-ic-right input{padding-right:2.4em}.mbsc-ic-left input{padding-left:2.4em}.mbsc-input textarea{resize:none;overflow:hidden;line-height:1.5em}.mbsc-input .mbsc-textarea-scroll{overflow:auto}.mbsc-select select{top:-1px}.mbsc-select-ic{display:none}.mbsc-checkbox{padding:.5em}.mbsc-checkbox-box{position:absolute;top:50%;display:block;width:1.375em;height:1.375em}.mbsc-checkbox-box:after{content:'';position:absolute;display:block;opacity:0;-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.mbsc-checkbox input:checked+.mbsc-checkbox-box:after{opacity:1}.mbsc-radio{padding:.5em}.mbsc-radio-box{position:absolute;top:50%;display:block;width:1.25em;height:1.25em;border-radius:1.25em}.mbsc-radio-box:after{content:'';position:absolute;top:50%;left:50%;width:.625em;height:.625em;margin-top:-.3125em;margin-left:-.3125em;border-radius:.625em;opacity:0}.mbsc-radio input:checked+.mbsc-radio-box:after{opacity:1}.mbsc-switch{padding:.5em}.mbsc-switch-track{position:absolute;top:50%;display:block;width:3.375em;height:1.625em;-webkit-transition:background-color .2s ease-in-out,border .2s ease-in-out;-moz-transition:background-color .2s ease-in-out,border .2s ease-in-out;transition:background-color .2s ease-in-out,border .2s ease-in-out}.mbsc-switch .mbsc-switch-track .mbsc-progress-track{height:100%;background:0}.mbsc-switch-track .mbsc-slider-handle-cont{top:50%;z-index:4}.mbsc-switch-handle{position:absolute;display:block}.mbsc-switch-txt-off,.mbsc-switch-txt-on{position:absolute;top:0;left:0;display:none;width:100%;height:100%;font-size:.625em;text-align:center;line-height:2em}.mbsc-segmented{display:table;table-layout:fixed;width:100%}.mbsc-segmented-item{display:table-cell;position:relative;vertical-align:top;text-align:center}.mbsc-segmented-content{position:relative;display:block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-size:.875em;z-index:2}.mbsc-segmented input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-step-disabled .mbsc-segmented-content,.mbsc-segmented input:disabled+.mbsc-segmented-content{z-index:0}.mbsc-stepper{position:absolute;display:block;width:auto;right:1em;top:50%}.mbsc-stepper-cont .mbsc-label{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.mbsc-segmented-item:focus{outline:0}.mbsc-stepper input{position:absolute;left:4.142857em;width:4.142857em;height:100%;padding:0;margin:0;border:0;outline:0;box-shadow:none;font-size:.875em;text-align:center;opacity:1;z-index:4;background:transparent;-webkit-appearance:none;-moz-appearance:textfield;appearance:none}.mbsc-stepper input::-webkit-outer-spin-button,.mbsc-stepper input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.mbsc-form .mbsc-stepper-val-right .mbsc-stepper input{left:auto;right:0}.mbsc-form .mbsc-stepper-val-left .mbsc-stepper input{left:0}.mbsc-stepper .mbsc-segmented-item{width:3.625em}.mbsc-stepper-cont.mbsc-stepper-val-left .mbsc-stepper .mbsc-segmented-item:nth-child(2) .mbsc-segmented-content,.mbsc-stepper-cont.mbsc-stepper-val-right .mbsc-stepper .mbsc-segmented-item:last-child .mbsc-segmented-content{border:0;background:transparent}.mbsc-form .mbsc-stepper-cont .mbsc-stepper{padding:0}.mbsc-segmented-item .mbsc-control,.mbsc-stepper .mbsc-segmented-content{cursor:pointer}.mbsc-step-disabled .mbsc-segmented-content,.mbsc-segmented input:disabled{cursor:auto}.mbsc-btn{position:relative;display:inline-block;overflow:hidden;vertical-align:middle;text-align:center;text-overflow:ellipsis;font-size:1em;cursor:pointer}.mbsc-btn-ic{line-height:1}.mbsc-btn-block{display:block;width:100%;margin-left:0!important;margin-right:0!important}.mbsc-padding{padding:1em}.mbsc-form h1,.mbsc-form h2,.mbsc-form h3,.mbsc-form h4,.mbsc-form h5,.mbsc-form h6{margin:0;padding:0;color:inherit;font-weight:normal;font-family:inherit}.mbsc-form p{margin:1em 0;padding:0;font-size:1em}.mbsc-form a{text-decoration:none}.mbsc-form a:hover{text-decoration:underline}.mbsc-form h1{margin:.347826em 0;font-size:2.875em}.mbsc-form h2{margin:.470588em 0;font-size:2.125em}.mbsc-form h3{margin:.666666em 0;font-size:1.5em}.mbsc-form h4{margin:.8em 0;font-size:1.25em}.mbsc-form h5{margin:1.066666em 0;font-size:.9375em}.mbsc-form h6{margin:1.333333em 0;font-size:.75em}.mbsc-ultra-bold{font-weight:900}.mbsc-bold{font-weight:bold}.mbsc-medium{font-weight:500}.mbsc-light{font-weight:300}.mbsc-thin{font-weight:100}.mbsc-italic{font-style:italic}.mbsc-mobiscroll.mbsc-form{background:#f7f7f7;color:#454545}.mbsc-mobiscroll.mbsc-form *::-moz-selection{color:#fff;background:#4eccc4}.mbsc-mobiscroll.mbsc-form *::selection{color:#fff;background:#4eccc4}.mbsc-mobiscroll .mbsc-desc{color:#6d6d6d}.mbsc-mobiscroll .mbsc-divider{padding:1.5em 1em .5em 1em;color:#4eccc4}.mbsc-mobiscroll .mbsc-err-msg{padding-top:.75em;font-size:.75em;color:#de3226}.mbsc-mobiscroll .mbsc-input textarea,.mbsc-mobiscroll .mbsc-input input{height:2.125em;padding:0 0 1px 0;background:transparent;border-bottom:1px solid #787878;font-size:1em;color:#1f1f1f}.mbsc-mobiscroll .mbsc-input select:focus ~ input,.mbsc-mobiscroll .mbsc-input textarea:focus,.mbsc-mobiscroll .mbsc-input input:focus{border-color:#4eccc4}.mbsc-mobiscroll .mbsc-input select:disabled ~ input,.mbsc-mobiscroll .mbsc-input select:disabled ~ .mbsc-select-ic,.mbsc-mobiscroll .mbsc-input textarea:disabled,.mbsc-mobiscroll .mbsc-input input:disabled{opacity:.4}.mbsc-mobiscroll .mbsc-input textarea:-webkit-autofill,.mbsc-mobiscroll .mbsc-input input:-webkit-autofill{padding:0 .25em}.mbsc-mobiscroll .mbsc-input{padding:.5em 1em}.mbsc-mobiscroll .mbsc-input-ic{top:.0625em;margin:-2px 0 0 0;color:#787878}.mbsc-mobiscroll .mbsc-input.mbsc-ic-left{padding-left:3.25em}.mbsc-mobiscroll .mbsc-input.mbsc-ic-right{padding-right:3.25em}.mbsc-mobiscroll .mbsc-ic-left .mbsc-ic.mbsc-left-ic{right:auto;left:-2.625em}.mbsc-mobiscroll .mbsc-ic-right .mbsc-ic.mbsc-right-ic{left:auto;right:-2.625em}.mbsc-mobiscroll .mbsc-input.mbsc-err input,.mbsc-mobiscroll .mbsc-input.mbsc-err textarea{padding:0;border-bottom:1px solid #ec2a32}.mbsc-mobiscroll .mbsc-input .mbsc-label{font-size:.75em}.mbsc-mobiscroll .mbsc-input textarea{padding-top:.25em}.mbsc-mobiscroll .mbsc-select input{padding-right:1.25em}.mbsc-mobiscroll .mbsc-select-ic{position:absolute;display:block;height:1.25em;width:1.25em;top:.5625em;right:0;text-align:center}.mbsc-mobiscroll .mbsc-checkbox{padding:1em 3.125em 1em 1em}.mbsc-mobiscroll .mbsc-checkbox-box{background:#4eccc4;margin-top:-.5625em;width:1.125em;height:1.125em;right:1em}.mbsc-mobiscroll .mbsc-checkbox-box:after{top:.25em;left:.185em;width:.8125em;height:.4375em;border:.125em solid #fff;border-top:0;border-right:0}.mbsc-mobiscroll .mbsc-checkbox input:disabled+.mbsc-checkbox-box{background:#d6d6d6}.mbsc-mobiscroll .mbsc-radio{padding:1em 3.125em 1em 1em}.mbsc-mobiscroll .mbsc-radio-box{right:1em;width:1.125em;height:1.125em;margin-top:-.5625em;background:transparent;border:.125em solid #4eccc4}.mbsc-mobiscroll .mbsc-radio-box:after{width:.5em;height:.5em;margin-top:-.25em;margin-left:-.25em;background:#4eccc4;border-radius:.625em}.mbsc-mobiscroll .mbsc-radio input:checked+.mbsc-radio-box{background:transparent}.mbsc-mobiscroll .mbsc-radio input:disabled+.mbsc-radio-box{border-color:#d6d6d6}.mbsc-mobiscroll .mbsc-radio input:disabled+.mbsc-radio-box:after{background:#d6d6d6}.mbsc-mobiscroll .mbsc-checkbox input:disabled ~ .mbsc-label,.mbsc-mobiscroll .mbsc-checkbox input:disabled ~ .mbsc-desc,.mbsc-mobiscroll .mbsc-radio input:disabled ~ .mbsc-label,.mbsc-mobiscroll .mbsc-radio input:disabled ~ .mbsc-desc,.mbsc-mobiscroll .mbsc-switch input:disabled ~ .mbsc-label,.mbsc-mobiscroll .mbsc-switch input:disabled ~ .mbsc-desc{opacity:.4}.mbsc-mobiscroll .mbsc-checkbox-box:after,.mbsc-mobiscroll .mbsc-radio-box:after{opacity:1;-webkit-transform:scale(0) rotate(-45deg);-moz-transform:scale(0) rotate(-45deg);-ms-transform:scale(0) rotate(-45deg);transform:scale(0) rotate(-45deg);-webkit-transition:-webkit-transform .1s ease-out;-moz-transition:-moz-transform .1s ease-out;transition:transform .1s ease-out}.mbsc-mobiscroll .mbsc-checkbox input:checked+.mbsc-checkbox-box:after,.mbsc-mobiscroll .mbsc-radio input:checked+.mbsc-radio-box:after{opacity:1;-webkit-transform:scale(1) rotate(-45deg);-moz-transform:scale(1) rotate(-45deg);-ms-transform:scale(1) rotate(-45deg);transform:scale(1) rotate(-45deg)}.mbsc-mobiscroll .mbsc-btn{margin:.5em;padding:.6875em;background:#4eccc4;color:#f0f0f0;font-size:1em;text-transform:uppercase}.mbsc-mobiscroll .mbsc-btn-ic{padding-right:.6875em}.mbsc-mobiscroll .mbsc-btn-icon-only .mbsc-btn-ic{padding:0 .5em}.mbsc-mobiscroll .mbsc-btn:disabled{background:#dedede}.mbsc-mobiscroll .mbsc-btn.mbsc-active{opacity:.6}.mbsc-mobiscroll .mbsc-btn-flat.mbsc-active{opacity:1;background:rgba(78,204,196,.3)}.mbsc-mobiscroll .mbsc-btn-flat{background:transparent;color:#4eccc4;border-color:transparent}.mbsc-mobiscroll .mbsc-btn-flat:disabled{background:transparent;color:#c9c9c9}.mbsc-mobiscroll .mbsc-btn-group{padding:0 .5em}.mbsc-mobiscroll a{color:#4eccc4}.mbsc-mobiscroll .mbsc-switch{padding:1em 4.375em 1em 1em}.mbsc-mobiscroll .mbsc-switch-track{right:1em;width:2.375em;height:.875em;padding:0 .75em;margin-top:-.4375em;background:#dedede;border-radius:1.25em}.mbsc-mobiscroll .mbsc-switch-handle{top:.25em;left:.25em;margin:0;width:1.5em;height:1.5em;background:#c9c9c9;border-radius:1.25em}.mbsc-mobiscroll .mbsc-active .mbsc-switch-handle{-webkit-transform:scale(1);-moz-transform:scale(1);transform:scale(1)}.mbsc-mobiscroll .mbsc-switch input:checked+.mbsc-switch-track{background:#b4e5e2}.mbsc-mobiscroll .mbsc-switch input:checked+.mbsc-switch-track .mbsc-switch-handle{background:#4eccc4}.mbsc-mobiscroll .mbsc-switch input:disabled+.mbsc-switch-track{background:#d6d6d6;opacity:.7}.mbsc-mobiscroll .mbsc-switch input:disabled+.mbsc-switch-track .mbsc-switch-handle{background:#e2e2e2}.mbsc-mobiscroll .mbsc-segmented{padding:.5em 1em}.mbsc-mobiscroll .mbsc-segmented-content{height:2.28571428em;margin:0 -.071428em;line-height:2.28575em;padding:0 .285714em;border:.142857em solid #4eccc4;text-transform:uppercase;color:#4eccc4}.mbsc-mobiscroll .mbsc-stepper input{color:#454545}.mbsc-mobiscroll .mbsc-stepper .mbsc-active .mbsc-segmented-content,.mbsc-mobiscroll .mbsc-segmented input:checked+.mbsc-segmented-content{background:#4eccc4;color:#f7f7f7}.mbsc-mobiscroll .mbsc-segmented input.mbsc-active+.mbsc-segmented-content{background:rgba(78,204,196,.25);color:#f7f7f7}.mbsc-mobiscroll .mbsc-stepper-cont{padding:1.75em 12.875em 1.75em 1em}.mbsc-mobiscroll .mbsc-stepper{margin-top:-1.125em}.mbsc-mobiscroll .mbsc-segmented input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-mobiscroll .mbsc-step-disabled .mbsc-segmented-content,.mbsc-mobiscroll .mbsc-segmented input:disabled+.mbsc-segmented-content{color:#d6d6d6;border-color:#d6d6d6}.mbsc-mobiscroll .mbsc-stepper input:disabled{color:#d6d6d6;-webkit-text-fill-color:#d6d6d6}.mbsc-mobiscroll .mbsc-segmented input:disabled:checked+.mbsc-segmented-content{background:#d6d6d6;color:#f0f0f0}.mbsc-mobiscroll .mbsc-stepper .mbsc-active.mbsc-step-disabled .mbsc-segmented-content{background:transparent;color:#d6d6d6}.mbsc-android-holo.mbsc-form{background:#000;color:#fff}.mbsc-android-holo.mbsc-form *::-moz-selection{color:#fff;background:#31c6e7}.mbsc-android-holo.mbsc-form *::selection{color:#fff;background:#31c6e7}.mbsc-android-holo .mbsc-padding{padding:.75em}.mbsc-android-holo .mbsc-divider{margin:0 1em;padding:2em 0 1em 0;border-bottom:1px solid #31c6e7;font-size:.75em;color:#31c6e7;text-transform:uppercase}.mbsc-android-holo .mbsc-err-msg{color:#f35047;float:right;padding-top:.375em}.mbsc-android-holo .mbsc-input{padding:.75em}.mbsc-android-holo .mbsc-input textarea,.mbsc-android-holo .mbsc-input input{height:2em;padding:0 .75em;background:#000;border:0;border-bottom:5px solid #a9a9a9;outline:4px solid #000;outline-offset:-5px;color:#fff}.mbsc-android-holo.mbsc-wdg .mbsc-input textarea,.mbsc-android-holo.mbsc-wdg .mbsc-input input,.mbsc-lv-android-holo .mbsc-input textarea,.mbsc-lv-android-holo .mbsc-input input{background:#292929;outline:4px solid #292929}.mbsc-android-holo .mbsc-input textarea{padding:.25em .75em 0 .75em}.mbsc-android-holo .mbsc-select input{padding:0 .75em;outline:0;border-width:0 0 1px;padding-bottom:4px}.mbsc-android-holo .mbsc-input-ic{top:0;margin-top:-3px}.mbsc-android-holo .mbsc-input select:focus+input,.mbsc-android-holo .mbsc-input textarea:focus,.mbsc-android-holo .mbsc-input input:focus{border-color:#31c6e7}.mbsc-android-holo .mbsc-input .mbsc-control:focus ~ .mbsc-select-ic{border-color:transparent #31c6e7 #31c6e7 transparent}.mbsc-android-holo .mbsc-input select:disabled ~ input,.mbsc-android-holo .mbsc-input textarea:disabled,.mbsc-android-holo .mbsc-input input:disabled{opacity:.4}.mbsc-android-holo .mbsc-input textarea:-webkit-autofill,.mbsc-android-holo .mbsc-input input:-webkit-autofill{border-color:#faffbd;box-shadow:0 0 0 50em #000 inset;-webkit-text-fill-color:#fff}.mbsc-android-holo .mbsc-input.mbsc-err input,.mbsc-android-holo .mbsc-input.mbsc-err textarea{border-color:#f35047}.mbsc-android-holo .mbsc-input .mbsc-label{font-size:.75em;padding:0 1em}.mbsc-android-holo .mbsc-ic-left input,.mbsc-android-holo .mbsc-ic-left textarea{padding-left:2.4em}.mbsc-android-holo .mbsc-ic-right input,.mbsc-android-holo .mbsc-ic-right textarea{padding-right:2.4em}.mbsc-android-holo .mbsc-select .mbsc-input-wrap .mbsc-select-ic{position:absolute;display:block;height:0;width:0;bottom:0;right:0;border:5px solid black;border-color:transparent #a9a9a9 #a9a9a9 transparent}.mbsc-android-holo .mbsc-ic-left .mbsc-left-ic{right:auto;left:.125em}.mbsc-android-holo .mbsc-ic-right .mbsc-right-ic{left:auto;right:.125em}.mbsc-android-holo .mbsc-select-ic:before{display:none}.mbsc-android-holo .mbsc-checkbox input:disabled+.mbsc-checkbox-box,.mbsc-android-holo .mbsc-checkbox input:disabled ~ .mbsc-label,.mbsc-android-holo .mbsc-checkbox input:disabled ~ .mbsc-desc,.mbsc-android-holo .mbsc-radio input:disabled+.mbsc-radio-box,.mbsc-android-holo .mbsc-radio input:disabled ~ .mbsc-label,.mbsc-android-holo .mbsc-radio input:disabled ~ .mbsc-desc,.mbsc-android-holo .mbsc-switch input:disabled+.mbsc-switch-track,.mbsc-android-holo .mbsc-switch input:disabled ~ .mbsc-label,.mbsc-android-holo .mbsc-switch input:disabled ~ .mbsc-desc{opacity:.4}.mbsc-android-holo .mbsc-checkbox{padding:1.25em 3em 1.25em .75em}.mbsc-android-holo .mbsc-checkbox-box{width:1em;height:1em;border:1px solid #4e4e4e;background:transparent;right:1.25em;margin-top:-.5em}.mbsc-android-holo .mbsc-checkbox-box:after{top:0;left:16%;width:.925em;height:.438em;border:3px solid #31c6e7;border-top:0;border-right:0}.mbsc-android-holo .mbsc-checkbox input:disabled+.mbsc-checkbox-box:after{border-color:#444}.mbsc-android-holo .mbsc-checkbox input.mbsc-active+.mbsc-checkbox-box:before{position:absolute;height:2em;width:2em;top:50%;left:50%;margin-top:-1em;margin-left:-1em;border-radius:3px;content:' ';background:rgba(55,55,55,.6)}.mbsc-android-holo .mbsc-radio{padding:1.25em 3.25em 1.25em .75em}.mbsc-android-holo .mbsc-radio-box{background:transparent;border:2px solid #4e4e4e;right:1.25em;margin-top:-.625em}.mbsc-android-holo .mbsc-radio-box:after{background:#31c6e7}.mbsc-android-holo .mbsc-radio input:checked:disabled+.mbsc-radio-box:after{background:#7b7e82}.mbsc-android-holo .mbsc-radio input.mbsc-active+.mbsc-radio-box:before{position:absolute;top:50%;left:50%;margin-top:-1.25em;margin-left:-1.25em;border-radius:2.5em;height:2.5em;width:2.5em;content:' ';background:rgba(55,55,55,.6)}.mbsc-android-holo .mbsc-btn{margin:.5em .25em;padding:.625em;color:#fff;background:#5c5c5c;border-radius:2px;-webkit-box-shadow:inset 0 1px 1px rgba(255,255,255,.2),0 1px 1px rgba(0,0,0,.5);box-shadow:inset 0 1px 1px rgba(255,255,255,.2),0 1px 1px rgba(0,0,0,.5)}.mbsc-android-holo .mbsc-btn-ic{padding-right:.625em}.mbsc-android-holo .mbsc-btn-icon-only .mbsc-btn-ic{padding:0 .625em}.mbsc-android-holo .mbsc-btn.mbsc-active{background:#757575}.mbsc-android-holo .mbsc-btn:disabled{background:#e5e5e5;color:#b5b5b5;-webkit-box-shadow:none;box-shadow:none}.mbsc-android-holo .mbsc-btn-flat{padding:0;border-color:transparent;background:transparent;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}.mbsc-android-holo .mbsc-btn-flat.mbsc-active{opacity:.6;background:transparent;border-color:transparent;-webkit-box-shadow:none;box-shadow:none}.mbsc-android-holo .mbsc-btn-flat:disabled{opacity:.2;background:transparent;color:#fff}.mbsc-android-holo .mbsc-btn-group{padding:.25em .5em}.mbsc-android-holo a{color:#31c6e7}.mbsc-android-holo .mbsc-switch{padding:1.25em 7.25em 1.25em .75em}.mbsc-android-holo .mbsc-switch-track{background:#464646;width:4.625em;height:1.25em;right:1.25em;padding:0 1.125em;margin-top:-.625em}.mbsc-android-holo .mbsc-switch-handle{top:.375em;left:-.125em;height:100%;width:2.25em;height:1.25em;margin:0;background:#676767;border-radius:0;text-transform:uppercase;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.2),inset 0 1px 0 rgba(255,255,255,.2),inset 1px 0 0 rgba(0,0,0,.1),inset -1px 0 0 rgba(0,0,0,.1);box-shadow:inset 0 -1px 0 rgba(0,0,0,.2),inset 0 1px 0 rgba(255,255,255,.2),inset 1px 0 0 rgba(0,0,0,.1),inset -1px 0 0 rgba(0,0,0,.1)}.mbsc-android-holo .mbsc-switch-handle:after{display:none}.mbsc-android-holo .mbsc-switch input:checked+.mbsc-switch-track .mbsc-switch-txt-off{display:none}.mbsc-android-holo .mbsc-switch-handle .mbsc-switch-txt-off,.mbsc-android-holo .mbsc-switch input:checked+.mbsc-switch-track .mbsc-switch-txt-on{display:block}.mbsc-android-holo .mbsc-switch input.mbsc-active+.mbsc-switch-track .mbsc-switch-handle{background:#737373}.mbsc-android-holo .mbsc-switch input:checked+.mbsc-switch-track .mbsc-switch-handle{background:#29799c}.mbsc-android-holo .mbsc-switch input:disabled+.mbsc-switch-track .mbsc-switch-handle{background:#a6a7a6}.mbsc-android-holo .mbsc-segmented{padding:.75em}.mbsc-android-holo .mbsc-segmented-content{height:2.2857145em;margin:0 -.07142857em;line-height:2.2857145em;padding:0 .428571em;text-transform:capitalize;border:.142857em solid #31c6e7}.mbsc-android-holo .mbsc-stepper-cont{padding:1.75em 12.375em 1.75em .75em}.mbsc-android-holo .mbsc-segmented input:checked+.mbsc-segmented-content{background:#31c6e7}.mbsc-android-holo .mbsc-segmented input.mbsc-active+.mbsc-segmented-content{background:rgba(49,198,231,.35)}.mbsc-android-holo .mbsc-stepper-control .mbsc-segmented-content{border:0;-webkit-box-shadow:inset 0 1px 1px rgba(255,255,255,.2),0 1px 1px rgba(0,0,0,.5);box-shadow:inset 0 1px 1px rgba(255,255,255,.2),0 1px 1px rgba(0,0,0,.5)}.mbsc-android-holo .mbsc-stepper-minus .mbsc-segmented-content,.mbsc-android-holo .mbsc-segmented-item:first-child .mbsc-segmented-content{border-top-left-radius:.142857em;border-bottom-left-radius:.142857em}.mbsc-android-holo .mbsc-stepper-plus .mbsc-segmented-content,.mbsc-android-holo .mbsc-segmented-item:last-child .mbsc-segmented-content{border-top-right-radius:.142857em;border-bottom-right-radius:.142857em}.mbsc-android-holo .mbsc-stepper .mbsc-stepper-val{border:0;background:#f5f5f5;color:#000;-webkit-box-shadow:inset 0 1px 1px rgba(255,255,255,.2),0 1px 1px rgba(0,0,0,.5);box-shadow:inset 0 1px 1px rgba(255,255,255,.2),0 1px 1px rgba(0,0,0,.5)}.mbsc-android-holo .mbsc-stepper-control .mbsc-segmented-content{background:#5c5c5c}.mbsc-android-holo .mbsc-stepper .mbsc-active .mbsc-segmented-content{background:#757575}.mbsc-android-holo .mbsc-stepper{right:.75em;margin-top:-1em}.mbsc-android-holo .mbsc-stepper .mbsc-segmented-content{margin:0}.mbsc-android-holo .mbsc-segmented input:disabled+.mbsc-segmented-content{color:#b5b5b5;border-color:#5c5c5c}.mbsc-android-holo .mbsc-segmented input:disabled:checked+.mbsc-segmented-content,.mbsc-android-holo .mbsc-stepper input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-android-holo .mbsc-stepper .mbsc-step-disabled .mbsc-segmented-content{background:#e5e5e5;color:#b5b5b5;border-color:#e5e5e5}.mbsc-android-holo .mbsc-stepper input:disabled ~ .mbsc-segmented-item .mbsc-stepper-val{background:#f5f5f5}.mbsc-android-holo .mbsc-stepper-val-left .mbsc-segmented-item:nth-child(2) .mbsc-segmented-content,.mbsc-android-holo .mbsc-stepper-val-right .mbsc-segmented-item:last-child .mbsc-segmented-content{-webkit-box-shadow:none;box-shadow:none}.mbsc-android-holo .mbsc-stepper input{color:#000;z-index:3}.mbsc-android-holo .mbsc-stepper-val-left input,.mbsc-android-holo .mbsc-stepper-val-right input{color:#fff}.mbsc-android-holo .mbsc-stepper input:disabled{color:#b5b5b5;-webkit-text-fill-color:#b5b5b5}.mbsc-wp.mbsc-form{background-color:#060709;color:#fff}.mbsc-wp.mbsc-form *::-moz-selection{color:#fff;background:#1a9fe0}.mbsc-wp.mbsc-form *::selection{color:#fff;background:#1a9fe0}.mbsc-wp .mbsc-input-ic{top:0;margin-top:0;color:#000}.mbsc-wp .mbsc-divider{font-size:1.5em;padding:0 .5em;line-height:2em}.mbsc-wp .mbsc-err-msg{color:#d30101}.mbsc-wp .mbsc-input{padding:.75em 1em}.mbsc-wp .mbsc-input textarea,.mbsc-wp .mbsc-input input{height:2em;padding:0 .3125em;background:#ccc;border:.125em solid #ccc;font-size:1em;color:#2b2b2b}.mbsc-wp .mbsc-input textarea{padding-top:.125em;padding-bottom:.125em}.mbsc-wp .mbsc-input input::-webkit-input-placeholder{color:#5c5c5c}.mbsc-wp .mbsc-input input::-ms-input-placeholder{color:#5c5c5c}.mbsc-wp .mbsc-input input::-moz-placeholder{color:#5c5c5c}.mbsc-wp .mbsc-input select:focus+input,.mbsc-wp .mbsc-input textarea:focus,.mbsc-wp .mbsc-input input:focus{background:#fff;border-color:#1a9fe0;color:#000}.mbsc-wp .mbsc-input select:disabled+input,.mbsc-wp .mbsc-input textarea:disabled,.mbsc-wp .mbsc-input input:disabled{background:#000;border-color:#4e4e4e;color:#4e4e4e}.mbsc-wp .mbsc-ic-left input,.mbsc-wp .mbsc-ic-left textarea{padding-left:2.25em}.mbsc-wp .mbsc-ic-right input,.mbsc-wp .mbsc-ic-right textarea{padding-right:2.25em}.mbsc-wp .mbsc-ic-left .mbsc-left-ic{right:auto;left:.25em}.mbsc-wp .mbsc-ic-right .mbsc-right-ic{left:auto;right:.25em}.mbsc-wp .mbsc-input.mbsc-err input,.mbsc-wp .mbsc-input.mbsc-err textarea{border-color:#d30101}.mbsc-wp .mbsc-input .mbsc-label{padding-bottom:.3125em;color:#878787;font-size:.8125em}.mbsc-wp .mbsc-select input{background:transparent;border-color:#fff;color:#fff}.mbsc-wp .mbsc-select .mbsc-input-wrap .mbsc-ic{color:#fff}.mbsc-wp .mbsc-input select.mbsc-active+input{background:#1a9fe0;border-color:#fff;color:#fff}.mbsc-wp .mbsc-select select:focus ~ .mbsc-ic{color:#000}.mbsc-wp .mbsc-select select.mbsc-active ~ .mbsc-ic{color:#fff}.mbsc-wp .mbsc-checkbox-box,.mbsc-wp .mbsc-radio-box{margin-top:-.6875em;border:.125em solid #fff}.mbsc-wp .mbsc-checkbox input:disabled+.mbsc-checkbox-box,.mbsc-wp .mbsc-checkbox input:disabled ~ .mbsc-label,.mbsc-wp .mbsc-checkbox input:disabled ~ .mbsc-desc,.mbsc-wp .mbsc-radio input:disabled+.mbsc-radio-box,.mbsc-wp .mbsc-radio input:disabled ~ .mbsc-label,.mbsc-wp .mbsc-radio input:disabled ~ .mbsc-desc,.mbsc-wp .mbsc-switch input:disabled+.mbsc-switch-track,.mbsc-wp .mbsc-switch input:disabled ~ .mbsc-label,.mbsc-wp .mbsc-switch input:disabled ~ .mbsc-desc{opacity:.2}.mbsc-wp .mbsc-checkbox{padding:1.125em 1em 1.125em 2.875em}.mbsc-wp .mbsc-checkbox-box{width:1.3125em;height:1.3125em;left:1em}.mbsc-wp .mbsc-checkbox-box:after{top:20%;left:10%;width:.875em;height:.475em;border:.1875em solid #fff;border-top:0;border-right:0}.mbsc-wp .mbsc-checkbox input.mbsc-active+.mbsc-checkbox-box{background:#1a9fe0}.mbsc-wp .mbsc-checkbox input.mbsc-active+.mbsc-checkbox-box:after{border-color:#fff}.mbsc-wp .mbsc-radio{padding:1.125em 3.25em 1.125em 1em}.mbsc-wp .mbsc-radio-box{right:1.125em}.mbsc-wp .mbsc-radio-box:after{background:#fff}.mbsc-wp .mbsc-radio input.mbsc-active+.mbsc-radio-box{background:#1a9fe0}.mbsc-wp .mbsc-radio input.mbsc-active+.mbsc-radio-box:after{background:#1a9fe0}.mbsc-wp .mbsc-btn{margin:.5em .25em;padding:.3125em .5em;background:transparent;border:.125em solid #fff;border-radius:0;color:#fff;text-transform:lowercase;-webkit-transform:translate3d(0,0,0);-webkit-transition:-webkit-transform .2s ease-in-out;-moz-transition:-moz-transform .2s ease-in-out;transition:transform .2s ease-in-out}.mbsc-wp .mbsc-btn-ic{padding-right:.5em;color:#fff}.mbsc-wp .mbsc-btn-icon-only .mbsc-btn-ic{padding:0}.mbsc-wp .mbsc-btn:disabled{opacity:.3}.mbsc-wp .mbsc-btn.mbsc-active{background:#1a9fe0;color:#fff;-webkit-transform:translate3d(0,-2px,0);-moz-transform:translate3d(0,-2px,0);transform:translate3d(0,-2px,0)}.mbsc-wp .mbsc-btn.mbsc-active .mbsc-btn-ic{background:#1a9fe0;color:#fff}.mbsc-wp .mbsc-btn-flat{border:0;font-size:.75em}.mbsc-wp .mbsc-btn-flat .mbsc-btn-ic{display:block;width:1.833334em;height:1.833334em;margin:0 auto .5em auto;padding:0;border:.1666em solid #fff;border-radius:2em;line-height:1.833334em;text-align:center}.mbsc-wp .mbsc-btn-flat:disabled{border:0}.mbsc-wp .mbsc-btn-flat.mbsc-active{background:transparent;color:#fff}.mbsc-wp .mbsc-btn-flat.mbsc-active .mbsc-btn-ic{border-color:#1a9fe0}.mbsc-wp .mbsc-btn-group{padding:.25em .75em}.mbsc-wp a{color:#1a9fe0}.mbsc-wp .mbsc-switch{padding:1.125em 5.875em 1.125em 1em}.mbsc-wp .mbsc-switch-track{right:1.125em;width:3.5em;height:1.125em;margin-top:-0.645em;padding:0 .375em;-webkit-box-shadow:inset 0 0 0 2px #000,0px 0 0 2px #fff;box-shadow:inset 0 0 0 2px #000,0px 0 0 2px #fff}.mbsc-wp .mbsc-switch-handle{z-index:1;top:50%;left:50%;height:1.625em;width:1em;margin:-.9375em 0 0 -.625em;background:#fff;border:.125em solid #000}.mbsc-wp .mbsc-switch input:checked+.mbsc-switch-track{background:#1a9fe0}.mbsc-wp .mbsc-switch input:checked:disabled+.mbsc-switch-track{background:#fff}.mbsc-wp .mbsc-segmented{padding:.75em 1em}.mbsc-wp .mbsc-segmented-content{height:2em;margin:0 -.07142857em;line-height:2em;padding:0 .375em;border:.142857em solid #fff;color:#fff;text-transform:lowercase}.mbsc-wp .mbsc-segmented input:checked+.mbsc-segmented-content{background:#fff;color:#000}.mbsc-wp .mbsc-segmented input.mbsc-active+.mbsc-segmented-content{background:#1a9fe0}.mbsc-wp .mbsc-stepper{margin-top:-1em}.mbsc-wp .mbsc-stepper .mbsc-active .mbsc-segmented-content{background:#1a9fe0}.mbsc-wp .mbsc-stepper-cont{padding:1.75em 13em 1.75em 1em}.mbsc-wp .mbsc-stepper input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-wp .mbsc-step-disabled .mbsc-segmented-content,.mbsc-wp .mbsc-segmented input:disabled+.mbsc-segmented-content{color:#505050;border-color:#505050}.mbsc-wp .mbsc-stepper input:disabled{color:#505050;-webkit-text-fill-color:#505050}.mbsc-wp .mbsc-segmented input:disabled:checked+.mbsc-segmented-content{color:#000;background:#505050}.mbsc-wp .mbsc-stepper .mbsc-active.mbsc-step-disabled .mbsc-segmented-content{background:transparent}.mbsc-wp .mbsc-stepper input{color:#fff}.mbsc-material.mbsc-form{background-color:#eee;color:#6d6d6d}.mbsc-material.mbsc-form *::-moz-selection{color:#fff;background:#009688}.mbsc-material.mbsc-form *::selection{color:#fff;background:#009688}.mbsc-material .mbsc-desc{color:#6d6d6d}.mbsc-material .mbsc-divider{padding:1.5em 1em .5em 1em;color:#009688}.mbsc-material .mbsc-err-msg{padding-top:.75em;font-size:.75em;color:#de3226}.mbsc-material .mbsc-input textarea,.mbsc-material .mbsc-input input{height:2.125em;padding:0 0 1px 0;background-color:transparent;border-bottom:1px solid #6d6d6d;font-size:1em;color:#1f1f1f}.mbsc-material .mbsc-input select:focus ~ input,.mbsc-material .mbsc-input textarea:focus,.mbsc-material .mbsc-input input:focus{padding-bottom:0;border-bottom:2px solid #009688}.mbsc-material .mbsc-input select:disabled ~ input,.mbsc-material .mbsc-input textarea:disabled,.mbsc-material .mbsc-input input:disabled{border-style:dotted;opacity:.5}.mbsc-material .mbsc-input textarea:-webkit-autofill,.mbsc-material .mbsc-input input:-webkit-autofill{padding:0 .25em}.mbsc-material .mbsc-input{padding:.5em 1em}.mbsc-material .mbsc-input-ic{top:.0625em;margin:-2px 0 0 0}.mbsc-material .mbsc-input.mbsc-ic-left{padding-left:3.25em}.mbsc-material .mbsc-input.mbsc-ic-right{padding-right:3.25em}.mbsc-material .mbsc-ic-left .mbsc-left-ic{right:auto;left:-2.625em}.mbsc-material .mbsc-ic-right .mbsc-right-ic{left:auto;right:-2.625em}.mbsc-material .mbsc-input.mbsc-err input,.mbsc-material .mbsc-input.mbsc-err textarea{padding:0;border-bottom:2px solid #de3226}.mbsc-material .mbsc-input .mbsc-label{font-size:.75em}.mbsc-material .mbsc-input textarea{padding-top:.25em}.mbsc-material .mbsc-select input{padding-right:1em}.mbsc-material .mbsc-select .mbsc-input-wrap:after{content:'';position:absolute;height:0;width:0;top:.875em;right:0;border:5px solid #6d6d6d;border-color:#6d6d6d transparent transparent transparent}.mbsc-material .mbsc-checkbox,.mbsc-material .mbsc-radio{padding:.9375em 3.5em .9375em 1em;line-height:1.25em}.mbsc-material .mbsc-checkbox-box,.mbsc-material .mbsc-radio-box{right:1.25em;width:1.125em;height:1.125em;margin-top:-.5625em;background:transparent;border:.125em solid #6d6d6d;-webkit-transition:background-color .1s ease-out;-moz-transition:background-color .1s ease-out;transition:background-color .1s ease-out}.mbsc-material .mbsc-checkbox-box:after,.mbsc-material .mbsc-radio-box:after{opacity:1;-webkit-transform:scale(0) rotate(-45deg);-moz-transform:scale(0) rotate(-45deg);-ms-transform:scale(0) rotate(-45deg);transform:scale(0) rotate(-45deg);-webkit-transition:-webkit-transform .1s ease-out;-moz-transition:-moz-transform .1s ease-out;transition:transform .1s ease-out}.mbsc-material .mbsc-checkbox input:checked+.mbsc-checkbox-box:after,.mbsc-material .mbsc-radio input:checked+.mbsc-radio-box:after{-webkit-transform:scale(1) rotate(-45deg);-moz-transform:scale(1) rotate(-45deg);-ms-transform:scale(1) rotate(-45deg);transform:scale(1) rotate(-45deg)}.mbsc-material .mbsc-checkbox-box:before,.mbsc-material .mbsc-radio-box:before{content:'';position:absolute;top:-1em;left:-1em;z-index:-1;width:2.875em;height:2.875em;opacity:0;background:rgba(0,0,0,.1);border-radius:2.875em;-webkit-transition:opacity .2s ease-in-out;-moz-transition:opacity .2s ease-in-out;transition:opacity .2s ease-in-out}.mbsc-material .mbsc-checkbox input:disabled+.mbsc-checkbox-box,.mbsc-material .mbsc-checkbox input:disabled ~ .mbsc-label,.mbsc-material .mbsc-checkbox input:disabled ~ .mbsc-desc,.mbsc-material .mbsc-radio input:disabled+.mbsc-radio-box,.mbsc-material .mbsc-radio input:disabled ~ .mbsc-label,.mbsc-material .mbsc-radio input:disabled ~ .mbsc-desc,.mbsc-material .mbsc-switch input:disabled ~ .mbsc-label,.mbsc-material .mbsc-switch input:disabled ~ .mbsc-desc{opacity:.3}.mbsc-material .mbsc-checkbox input.mbsc-active+.mbsc-checkbox-box:before,.mbsc-material .mbsc-radio input.mbsc-active+.mbsc-radio-box:before,.mbsc-material .mbsc-switch input.mbsc-active+.mbsc-switch-track .mbsc-switch-handle:before{opacity:1}.mbsc-material .mbsc-checkbox-box{border-radius:.1875em}.mbsc-material .mbsc-checkbox-box:after{top:.125em;left:.0625em;width:.8125em;height:.4375em;border:.125em solid #fff;border-top:0;border-right:0}.mbsc-material .mbsc-checkbox input:checked+.mbsc-checkbox-box{background:#009688;border-color:#009688}.mbsc-material .mbsc-radio-box:after{width:.625em;height:.625em;margin-top:-.3125em;margin-left:-.3125em;background:#009688;border-radius:.625em}.mbsc-material .mbsc-radio input:checked+.mbsc-radio-box{background:transparent;border-color:#009688}.mbsc-material .mbsc-btn{margin:.5em;padding:.7143em;background:#d6d6d6;border-radius:.2143em;color:#000;font-size:.875em;font-weight:bold;text-transform:uppercase;-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.25);box-shadow:0 2px 5px 0 rgba(0,0,0,.25);-webkit-transition:-webkit-box-shadow .2s ease-out,background-color .2s ease-out;-moz-transition:box-shadow .2s ease-out,background-color .2s ease-out;transition:box-shadow .2s ease-out,background-color .2s ease-out}.mbsc-material .mbsc-btn-ic{padding:0 .5em 0 0}.mbsc-material .mbsc-btn-icon-only .mbsc-btn-ic{padding:0 .5em}.mbsc-material .mbsc-btn.mbsc-active{background:#cbcbcb;-webkit-box-shadow:0 4px 8px 0 rgba(0,0,0,0.4);box-shadow:0 4px 8px 0 rgba(0,0,0,0.4)}.mbsc-material .mbsc-btn:disabled{color:#a6a6a6;-webkit-box-shadow:none;box-shadow:none}.mbsc-material .mbsc-btn-flat{background:transparent;-webkit-box-shadow:none;box-shadow:none}.mbsc-material .mbsc-btn-flat.mbsc-active{background-color:#d6d6d6;-webkit-box-shadow:none;box-shadow:none}.mbsc-material .mbsc-btn-group{padding:0 .5em;text-align:right}.mbsc-material a{color:#009688}.mbsc-material .mbsc-switch{padding:.9375em 4em .9375em 1em;line-height:1.25em}.mbsc-material .mbsc-switch-track{right:1.25em;width:1.75em;height:.875em;padding:0 .25em;margin-top:-.4375em;background:#b2b2b2;border-radius:1.25em}.mbsc-material .mbsc-switch .mbsc-switch-handle{z-index:auto;top:.375em;left:.375em;width:1.25em;height:1.25em;margin:0;border:0;background:#fafafa;border-radius:1.25em;box-shadow:0 3px 1px -2px rgba(0,0,0,0.2),0 1px 5px 0 rgba(0,0,0,0.12)}.mbsc-material .mbsc-switch-handle:before{top:-.625em;left:-.625em;width:2.5em;height:2.5em}.mbsc-material .mbsc-switch-handle,.mbsc-material .mbsc-active .mbsc-switch-handle{-webkit-transform:none;-moz-transform:none;transform:none}.mbsc-material .mbsc-switch input:checked+.mbsc-switch-track{background-color:#a6d3cf}.mbsc-material .mbsc-switch input:checked+.mbsc-switch-track .mbsc-switch-handle{background:#009688}.mbsc-material .mbsc-switch input:disabled+.mbsc-switch-track{background:#d6d6d6}.mbsc-material .mbsc-switch input:disabled+.mbsc-switch-track .mbsc-switch-handle{background:#b9b9b9}.mbsc-material .mbsc-segmented{padding:.75em}.mbsc-material .mbsc-segmented-content{height:2.2857145em;margin:0 -.07142857em;line-height:2.2857145em;padding:0 .428571em;border:.142857em solid #009688;color:#000;text-transform:capitalize;-webkit-backface-visibility:hidden}.mbsc-material .mbsc-segmented-item:first-child .mbsc-segmented-content,.mbsc-material .mbsc-stepper-minus .mbsc-segmented-content{border-top-left-radius:.214286em;border-bottom-left-radius:.214286em}.mbsc-material .mbsc-stepper-plus .mbsc-segmented-content,.mbsc-material .mbsc-segmented-item:last-child .mbsc-segmented-content{border-top-right-radius:.214286em;border-bottom-right-radius:.214286em}.mbsc-material .mbsc-segmented input:checked+.mbsc-segmented-content,.mbsc-material .mbsc-segmented input:checked ~ .mbsc-segmented-content{background:#009688;color:#eee}.mbsc-material .mbsc-segmented input.mbsc-active+.mbsc-segmented-content,.mbsc-material .mbsc-segmented .mbsc-active .mbsc-segmented-content{background:#cbcbcb;color:#000}.mbsc-material .mbsc-segmented input:disabled+.mbsc-segmented-content{color:#a6a6a6;border-color:#b2b2b2}.mbsc-material .mbsc-segmented input:disabled:checked+.mbsc-segmented-content{background:#d6d6d6;color:#a6a6a6;border-color:#d6d6d6}.mbsc-material .mbsc-stepper-cont{padding:1.75em 12.875em 1.75em 1em}.mbsc-material .mbsc-stepper{margin-top:-1.125em}.mbsc-material .mbsc-stepper .mbsc-segmented-content{border-color:#d6d6d6;border-left:0;border-right:0}.mbsc-material .mbsc-stepper-control .mbsc-segmented-content{background:#d6d6d6;border:0;height:2.5714285em;line-height:2.5714285em}.mbsc-material .mbsc-segmented input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-material .mbsc-segmented .mbsc-step-disabled .mbsc-segmented-content{background:#d6d6d6;color:#a6a6a6}.mbsc-material .mbsc-segmented input:disabled ~ .mbsc-segmented-item .mbsc-stepper-val{background:#eee}.mbsc-material .mbsc-stepper input:disabled{color:#d6d6d6;-webkit-text-fill-color:#d6d6d6}.mbsc-ios.mbsc-form{background:#f7f7f7;color:#000}.mbsc-ios.mbsc-form *::-moz-selection{color:#fff;background:#1272dc}.mbsc-ios.mbsc-form *::selection{color:#fff;background:#1272dc}.mbsc-ios .mbsc-input-ic{color:#7f8797}.mbsc-ios .mbsc-padding{padding:.875em}.mbsc-ios .mbsc-divider{padding:0 1.1666em;background:#f5f5f5;border-bottom:1px solid #ccc;color:#777;font-size:.75em;font-weight:bold;line-height:2em;text-transform:uppercase}.mbsc-ios .mbsc-input{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:0;background:#fff;border-bottom:1px solid #ccc}.mbsc-ios .mbsc-textarea textarea,.mbsc-ios .mbsc-input input{height:2.75em;padding:0 1em;background:transparent}.mbsc-ios .mbsc-input input::-webkit-input-placeholder{color:#ccc}.mbsc-ios .mbsc-input input::-ms-input-placeholder{color:#ccc}.mbsc-ios .mbsc-input input::-moz-placeholder{color:#ccc}.mbsc-ios .mbsc-input .mbsc-label,.mbsc-ios .mbsc-progress .mbsc-label{-webkit-box-flex:0;-webkit-flex:0 auto;-moz-box-flex:0;-moz-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:25%;max-width:12.5em;padding-left:1em;overflow:hidden;font-size:1em;line-height:2.75em;color:#020202;white-space:nowrap;text-overflow:ellipsis;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-ios .mbsc-input-wrap{position:static;-webkit-box-flex:1;-webkit-flex:1 auto;-moz-box-flex:1;-moz-flex:1 auto;-ms-flex:1 auto;flex:1 auto}.mbsc-ios .mbsc-input select:disabled ~ input,.mbsc-ios .mbsc-input select:disabled ~ .mbsc-ic,.mbsc-ios .mbsc-input input:disabled{opacity:.5}.mbsc-ios .mbsc-err{border-bottom-color:#d8332a;z-index:1}.mbsc-ios .mbsc-err-msg{padding:.5em 1.3333em;color:#d8332a}.mbsc-ios .mbsc-select input{padding-right:2.25em}.mbsc-ios .mbsc-select-ic{position:absolute;display:block;height:1.25em;width:1.25em;top:.875em;right:1em;text-align:center}.mbsc-ios .mbsc-ic-right .mbsc-select-ic{right:3.4em}.mbsc-ios .mbsc-textarea .mbsc-input-wrap{padding:.625em 0}.mbsc-ios .mbsc-textarea textarea{height:3em}.mbsc-ios .mbsc-input-ic{top:.375em;margin-top:-1px}.mbsc-ios .mbsc-ic-left .mbsc-input-wrap{padding-left:2.25em}.mbsc-ios .mbsc-ic-right .mbsc-input-wrap{padding-right:2.25em}.mbsc-ios .mbsc-ic-left .mbsc-label{padding-left:3.25em}.mbsc-ios .mbsc-ic-left .mbsc-label ~ .mbsc-input-wrap{padding-left:0}.mbsc-ios .mbsc-ic-left .mbsc-left-ic{right:auto;left:.625em}.mbsc-ios .mbsc-ic-right .mbsc-right-ic{left:auto;right:.625em}.mbsc-ios .mbsc-checkbox,.mbsc-ios .mbsc-radio{padding:.875em 3.5em .875em .875em;background:#fff;border-bottom:1px solid #ccc}.mbsc-ios .mbsc-checkbox input:disabled+.mbsc-checkbox-box,.mbsc-ios .mbsc-checkbox input:disabled ~ .mbsc-label,.mbsc-ios .mbsc-checkbox input:disabled ~ .mbsc-desc,.mbsc-ios .mbsc-radio input:disabled+.mbsc-radio-box,.mbsc-ios .mbsc-radio input:disabled ~ .mbsc-label,.mbsc-ios .mbsc-radio input:disabled ~ .mbsc-desc,.mbsc-ios .mbsc-switch input:disabled+.mbsc-switch-track,.mbsc-ios .mbsc-switch input:disabled ~ .mbsc-label,.mbsc-ios .mbsc-switch input:disabled ~ .mbsc-desc{opacity:.3}.mbsc-ios .mbsc-checkbox-box:after,.mbsc-ios .mbsc-radio-box:after{-webkit-transition:opacity .2s ease-in-out;-moz-transition:opacity .2s ease-in-out;transition:opacity .2s ease-in-out}.mbsc-ios .mbsc-checkbox-box{right:.875em;width:1.75em;height:1.75em;margin-top:-.875em;background:transparent;border:.125em solid #1272dc;border-radius:2em}.mbsc-ios .mbsc-checkbox-box:after{top:32%;left:26%;width:.75em;height:.375em;border:.125em solid #1272dc;border-top:0;border-right:0}.mbsc-ios .mbsc-radio-box{right:1.125em;margin-top:-.625em;background:transparent}.mbsc-ios .mbsc-radio-box:after{position:absolute;top:44%;left:23%;width:1em;height:.5em;border:.125em solid #1272dc;border-top:0;border-right:0;border-radius:0;-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.mbsc-ios .mbsc-radio input:disabled ~ .mbsc-radio-label{color:#a6a7a6}.mbsc-ios .mbsc-btn{margin:.5em .25em;padding:.625em 1.375em;background:#fff;border:0;border-radius:.25em;color:#1272dc;-webkit-transition:background-color .2s ease-in-out;-moz-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out}.mbsc-ios .mbsc-btn-ic{padding-right:.625em}.mbsc-ios .mbsc-btn-icon-only .mbsc-btn-ic{padding:0}.mbsc-ios .mbsc-btn.mbsc-active{background:#f5f5f5}.mbsc-ios .mbsc-btn:disabled{background:#dfdfdf;color:#c4c4c4}.mbsc-ios .mbsc-btn-flat{background:transparent;border-color:transparent}.mbsc-ios .mbsc-btn-flat.mbsc-active{opacity:.6}.mbsc-ios .mbsc-btn-flat:disabled{background:transparent;opacity:.8;color:#c4c4c4}.mbsc-ios .mbsc-btn-group{padding:.375em .625em}.mbsc-ios a{color:#1272dc}.mbsc-ios .mbsc-switch{padding:.875em 4.875em .875em .875em;background:#fff;border-bottom:1px solid #ccc}.mbsc-ios .mbsc-switch-track{right:.875em;width:3em;height:1.75em;padding:0;margin-top:-.96875em;background:#e5e5e5;border:.09375em solid #e5e5e5;border-radius:1.25em;box-sizing:content-box}.mbsc-ios .mbsc-switch-track:after{content:'';position:absolute;z-index:1;top:0;right:0;bottom:0;left:0;background:#fff;border-radius:1.25em;-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);transform:scale(1);-webkit-transition:-webkit-transform .2s ease-out;-moz-transition:-moz-transform .2s ease-out;transition:transform .2s ease-out}.mbsc-ios .mbsc-switch-track .mbsc-progress-track{margin:0 .875em}.mbsc-ios .mbsc-switch-handle{z-index:2;top:50%;left:50%;width:1.75em;height:1.75em;margin:-.875em 0 0 -.875em;background:#fff;border-radius:1.75em;-webkit-box-shadow:0 0 1em rgba(0,0,0,.1),0 0 .0625em rgba(0,0,0,.15),0 .125em .125em rgba(0,0,0,.15);box-shadow:0 0 1em rgba(0,0,0,.1),0 0 .0625em rgba(0,0,0,.15),0 .125em .125em rgba(0,0,0,.15);-webkit-transition:-webkit-transform .2s ease-out;-moz-transition:-moz-transform .2s ease-out;transition:transform .2s ease-out}.mbsc-ios .mbsc-switch input:checked+.mbsc-switch-track{background:#4cd764;border-color:#4cd764}.mbsc-ios .mbsc-switch input:checked+.mbsc-switch-track:after{-webkit-transform:scale(0);-moz-transform:scale(0);-ms-transform:scale(0);transform:scale(0)}.mbsc-ios .mbsc-segmented{padding:.75em;background-color:#fff}.mbsc-ios .mbsc-stepper{display:block}.mbsc-ios .mbsc-segmented-content{height:2.142857142em;margin-left:-.07142857em;line-height:2.142857142857143em;padding:0 .42857em;color:#1272dc;text-transform:capitalize;border:.07142857em solid #1272dc;background:#fff}.mbsc-ios .mbsc-stepper-minus .mbsc-segmented-content,.mbsc-ios .mbsc-segmented-item:first-child .mbsc-segmented-content{border-top-left-radius:.25em;border-bottom-left-radius:.25em}.mbsc-ios .mbsc-stepper-plus .mbsc-segmented-content,.mbsc-ios .mbsc-segmented-item:last-child .mbsc-segmented-content{border-top-right-radius:.25em;border-bottom-right-radius:.25em}.mbsc-ios .mbsc-stepper .mbsc-active .mbsc-segmented-content,.mbsc-ios .mbsc-segmented input:checked+.mbsc-segmented-content{background:#1272dc;color:#fff}.mbsc-ios .mbsc-segmented input.mbsc-active+.mbsc-segmented-content{background:rgba(18,114,220,.15);color:#1272dc}.mbsc-ios .mbsc-stepper-cont{padding:1.5em 12.625em 1.5em 1em;margin-top:-1px;border-bottom:1px solid #ccc;border-top:1px solid #ccc;background:#fff}.mbsc-ios .mbsc-stepper{right:.75em;margin-top:-1em}.mbsc-ios .mbsc-segmented input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-ios .mbsc-step-disabled .mbsc-segmented-content,.mbsc-ios .mbsc-segmented input:disabled+.mbsc-segmented-content{color:#c4c4c4;border-color:#c4c4c4}.mbsc-ios .mbsc-stepper input:disabled{color:#c4c4c4;-webkit-text-fill-color:#c4c4c4}.mbsc-ios .mbsc-stepper .mbsc-segmented-item{width:3.14285em}.mbsc-ios .mbsc-segmented input:disabled:checked+.mbsc-segmented-content{background:#dfdfdf}.mbsc-ios .mbsc-stepper .mbsc-active.mbsc-step-disabled .mbsc-segmented-content{background:transparent;color:#c4c4c4}.mbsc-ios .mbsc-stepper input{color:#000;width:3.64285em;left:3.64285em;z-index:3}.mbsc-sc-whl-o,.mbsc-sc-btn{-webkit-transform:translateZ(0)}.mbsc-sc-whl-l,.mbsc-sc-whl{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-sc-whl-gr-c{max-width:100%;vertical-align:middle;display:inline-block;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-fr-liq .mbsc-sc-whl-gr-c{display:block}.mbsc-sc-whl-gr{margin:0 auto;position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.mbsc-sc-whl-w{max-width:100%;-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;position:relative}.mbsc-sc-whl-o{position:absolute;z-index:2;top:0;right:0;bottom:0;left:0;pointer-events:none}.mbsc-sc-whl-l{position:absolute;z-index:1;top:50%;left:0;right:0;pointer-events:none}.mbsc-sc-whl{overflow:hidden;border-radius:1px;border-top:1px solid transparent;border-bottom:1px solid transparent}.mbsc-sc-whl-c{position:relative;z-index:1;top:50%;border-top:1px solid transparent;border-bottom:1px solid transparent}.mbsc-sc-whl-sc{position:relative}.mbsc-sc-itm{position:relative;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mbsc-sc-itm-inv{opacity:.3}.mbsc-sc-lbl{position:absolute;left:0;right:0;bottom:100%;display:none}.mbsc-sc-lbl-v .mbsc-sc-lbl{display:block}.mbsc-sc-btn{position:absolute;z-index:2;left:0;right:0;cursor:pointer;opacity:1;-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.mbsc-sc-btn:before{position:absolute;top:0;right:0;left:0}.mbsc-sc-whl-a .mbsc-sc-btn{opacity:0}.mbsc-sc-btn-plus{bottom:0}.mbsc-sc-btn-minus{top:0}.mbsc-sc-itm-ml{width:100%;height:auto;display:inline-block;vertical-align:middle;white-space:normal}.mbsc-sel-hdn{position:absolute;height:1px!important;width:1px!important;left:0;overflow:hidden;clip:rect(1px,1px,1px,1px)}.mbsc-sel-gr{padding:0 .25em;opacity:1;font-weight:bold;text-align:left}.mbsc-sc-whl-multi .mbsc-sc-whl-c{top:0;height:100%;border:0}.mbsc-sc-whl-multi .mbsc-sc-whl-l{display:none}.mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{position:absolute;top:0;left:0;width:1em;text-align:center}.mbsc-mobiscroll .mbsc-sc-whl-gr{padding:.5em .25em}.mbsc-mobiscroll .mbsc-sc-whl-w{margin:0 .25em}.mbsc-mobiscroll .mbsc-sc-whl-o{display:none}.mbsc-mobiscroll .mbsc-sc-lbl-v .mbsc-sc-whl-w{margin-top:1.875em}.mbsc-mobiscroll .mbsc-sc-lbl{color:#4eccc4;font-size:.75em;line-height:2.5em;text-transform:uppercase}.mbsc-mobiscroll .mbsc-sc-whl-l{border-top:1px solid #4eccc4;border-bottom:1px solid #4eccc4}.mbsc-mobiscroll .mbsc-sc-btn{color:#4eccc4;background:#f7f7f7}.mbsc-mobiscroll .mbsc-sc-btn:before{font-size:1.5em}.mbsc-mobiscroll .mbsc-sc-btn-a:before{background:rgba(78,204,196,.3)}.mbsc-mobiscroll .mbsc-sc-itm{padding:0 .25em;font-size:1.375em}.mbsc-mobiscroll .mbsc-sc-itm.mbsc-btn-a{background:rgba(78,204,196,.3)}.mbsc-mobiscroll .mbsc-sc-whl .mbsc-sel-gr{padding:0 .25em;font-size:1.125em}.mbsc-mobiscroll .mbsc-sc-whl-multi .mbsc-sc-itm{padding:0 1.818181em}.mbsc-mobiscroll .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{font-size:1.818181em;color:#4eccc4}.mbsc-android-holo .mbsc-sc-whl-gr{padding:.0625em .625em}.mbsc-android-holo .mbsc-sc-lbl-v{padding:1.875em .625em .0625em .625em}.mbsc-android-holo .mbsc-sc-lbl{font-size:.75em;line-height:2.5em}.mbsc-android-holo .mbsc-sc-whl-w{margin:0 .625em}.mbsc-android-holo .mbsc-sc-whl-o{background:-webkit-gradient(linear,left bottom,left top,from(#282828),color-stop(0.52,rgba(40,40,40,0)),color-stop(0.48,rgba(40,40,40,0)),to(#282828));background:-webkit-linear-gradient(#282828,rgba(40,40,40,0) 52%,rgba(40,40,40,0) 48%,#282828);background:linear-gradient(#282828,rgba(40,40,40,0) 52%,rgba(40,40,40,0) 48%,#282828)}.mbsc-android-holo .mbsc-sc-whl-l{border-top:2px solid #31b6e7;border-bottom:2px solid #31b6e7}.mbsc-android-holo .mbsc-sc-itm{color:#fff;font-size:1.125em}.mbsc-android-holo .mbsc-sc-itm.mbsc-btn-a{background:rgba(49,182,231,.5)}.mbsc-android-holo .mbsc-sc-btn{color:#7e7e7e;background:#292829}.mbsc-android-holo .mbsc-sc-btn-a{background:#292829;color:#319abd}.mbsc-android-holo .mbsc-sc-btn:before{font-size:1.625em}.mbsc-android-holo .mbsc-sc-whl-multi .mbsc-sc-whl-o{display:none}.mbsc-android-holo .mbsc-sc-whl-multi .mbsc-sc-itm{padding:0 2.25em}.mbsc-android-holo .mbsc-sc-whl-multi .mbsc-sc-itm:after{content:'';position:absolute;z-index:1;top:50%;left:auto;right:.625em;width:.875em;height:.875em;margin-top:-.5625em;border:1px solid #424542}.mbsc-android-holo .mbsc-sc-itm-ph:after{display:none}.mbsc-android-holo .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{z-index:2;top:50%;left:auto;right:.625em;width:.875em;color:#31b6e7;margin-top:-.625em;line-height:.875em;text-shadow:0 0 .375em #29799c}.mbsc-android-holo .mbsc-sc-whl .mbsc-sel-gr{padding:0 .375em;font-size:1em;font-weight:normal}.mbsc-android-holo .mbsc-sc-whl .mbsc-sel-gr:after{display:none}.mbsc-android-holo .mbsc-dt-whl-h:after{content:':';position:absolute;top:50%;left:100%;width:1.25em;height:2em;margin-top:-1em;line-height:2em}.mbsc-android-holo .mbsc-rtl .mbsc-dt-whl-h:after{left:auto;right:100%}.mbsc-wp .mbsc-sc-itm,.mbsc-wp .mbsc-sc-itm:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-wp .mbsc-sc-whl-gr{padding:.1875em}.mbsc-wp .mbsc-sc-lbl-v{padding-top:1.875em}.mbsc-wp .mbsc-sc-lbl{font-size:.75em;line-height:2.5em}.mbsc-wp .mbsc-sc-btn{background:#1f1f1f}.mbsc-wp .mbsc-sc-btn:before{position:absolute;top:50%;left:50%;width:2.2em;height:2.2em;margin:-1.3em 0 0 -1.3em;color:#fff;line-height:2.2em;font-size:.625em;text-align:center;border:2px solid #fff;border-radius:2.2em}.mbsc-wp .mbsc-sc-itm{opacity:0;padding:0 .384615em;font-size:1.625em;letter-spacing:-.038em;text-align:left}.mbsc-wp .mbsc-sc-itm:after{content:'';position:absolute;z-index:-1;top:.136363em;right:.136363em;bottom:.136363em;left:.136363em;border:2px solid #4c4c4c}.mbsc-wp .mbsc-sc-whl-anim .mbsc-sc-itm,.mbsc-wp .mbsc-sc-whl-wpa .mbsc-sc-itm{opacity:1}.mbsc-wp .mbsc-sc-whl-anim .mbsc-sc-itm-inv,.mbsc-wp .mbsc-sc-whl-wpa .mbsc-sc-itm-inv{opacity:.3}.mbsc-wp .mbsc-sc-itm-ph:after{display:none}.mbsc-wp .mbsc-sc-whl .mbsc-sc-itm-sel{color:#fff}.mbsc-wp .mbsc-sc-whl .mbsc-sc-itm-sel,.mbsc-wp .mbsc-sc-whl .mbsc-sc-itm.mbsc-btn-a{opacity:1}.mbsc-wp .mbsc-sc-itm-sel:after,.mbsc-wp .mbsc-sc-itm.mbsc-btn-a:after,.mbsc-wp .mbsc-sc-btn-a:before{background:#1a9fe0;border-color:#1a9fe0}.mbsc-wp .mbsc-sc-whl-w:not(.mbsc-sc-whl-multi) .mbsc-sc-whl-anim .mbsc-sc-itm-sel:after,.mbsc-wp .mbsc-sc-whl-w:not(.mbsc-sc-whl-multi) .mbsc-sc-whl-wpa .mbsc-sc-itm.mbsc-btn-a:after{background:0;border-color:#4c4c4c}.mbsc-wp .mbsc-sc-whl-multi .mbsc-sc-itm{opacity:1;padding-left:1.5375em}.mbsc-wp .mbsc-sc-whl-multi .mbsc-sc-itm-inv{opacity:.3}.mbsc-wp .mbsc-sc-whl-multi .mbsc-sc-itm:after{content:'';position:absolute;z-index:1;top:50%;left:.192307em;width:.923076em;height:.923076em;margin-top:-.461538em;border:2px solid #fff}.mbsc-wp .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{z-index:2;top:50%;left:.3125em;width:1.375em;margin-top:-.5em;font-size:.615384em;line-height:1.25em;color:#fff}.mbsc-wp .mbsc-sc-whl .mbsc-sel-gr{opacity:1;padding-left:.227272em;font-size:1.375em;font-weight:normal}.mbsc-wp .mbsc-sc-whl .mbsc-sel-gr:after{display:none}.mbsc-wp.mbsc-dt .mbsc-sc-itm{padding-top:4%}.mbsc-wp .mbsc-dt-day,.mbsc-wp .mbsc-dt-month{position:absolute;bottom:.454545em;display:block;line-height:1.636363em;font-size:.423076em;letter-spacing:normal}.mbsc-material .mbsc-sc-whl-gr{padding:2em .25em}.mbsc-material .mbsc-sc-cp{padding:.5em .25em}.mbsc-material .mbsc-sc-lbl-v{padding:2em .25em 0 .25em}.mbsc-material .mbsc-sc-lbl{line-height:2.666666em;color:#009688;font-size:.75em;font-weight:bold;text-transform:uppercase}.mbsc-material .mbsc-sc-whl-w{margin:0 .25em;padding:.5em 0}.mbsc-material .mbsc-sc-itm{padding:0 .272727em;font-size:1.375em}.mbsc-material .mbsc-sc-itm.mbsc-btn-a{background:rgba(0,0,0,.1);border-radius:2px}.mbsc-material .mbsc-sc-whl-l{border-top:2px solid #009688;border-bottom:2px solid #009688;z-index:4}.mbsc-material .mbsc-sc-cp .mbsc-sc-whl-w{padding:2em 0}.mbsc-material .mbsc-sc-btn{height:2em!important;line-height:2em!important;color:#009688;background:#eee;overflow:hidden}.mbsc-material .mbsc-sc-btn:before{font-size:1.5em}.mbsc-material .mbsc-sc-btn-a{background:rgba(0,0,0,.1)}.mbsc-material .mbsc-sc-whl-multi .mbsc-sc-itm{padding:0 1.818181em}.mbsc-material .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{width:1.818181em;color:#009688}.mbsc-material .mbsc-sc-whl .mbsc-sel-gr{padding:0 .333333em;font-size:1.125em}.mbsc-ios .mbsc-sc-lbl{color:#ababab;line-height:2.5em}.mbsc-ios .mbsc-sc-whl-gr{padding:.833333em}.mbsc-ios .mbsc-sc-lbl-v{padding-top:2.5em}.mbsc-ios .mbsc-sc-whl-l{margin:0 -.833333em;border-top:1px solid #dbdbdb;border-bottom:1px solid #dbdbdb}.mbsc-ios .mbsc-sc-whl-o{background:-webkit-gradient(linear,left bottom,left top,from(#f7f7f7),color-stop(0.52,rgba(245,245,245,0)),color-stop(0.48,rgba(245,245,245,0)),to(#f7f7f7));background:-webkit-linear-gradient(#f7f7f7,rgba(245,245,245,0) 52%,rgba(245,245,245,0) 48%,#f7f7f7);background:linear-gradient(#f7f7f7,rgba(245,245,245,0) 52%,rgba(245,245,245,0) 48%,#f7f7f7)}.mbsc-ios .mbsc-sc-itm{padding:0 .5em;color:#9d9d9d;font-size:1.833333em}.mbsc-ios .mbsc-sc-itm.mbsc-btn-a{background:rgba(0,122,255,.2)}.mbsc-ios .mbsc-sc-itm-sel{color:#000}.mbsc-ios .mbsc-sc-cp .mbsc-sc-whl .mbsc-sc-itm{text-align:center}.mbsc-ios .mbsc-sc-btn{color:#007aff;background:#f7f7f7}.mbsc-ios .mbsc-sc-btn:before{font-size:2em}.mbsc-ios .mbsc-sc-btn-a:before{opacity:.5}.mbsc-ios .mbsc-sc-whl-multi .mbsc-sc-whl-o{display:none}.mbsc-ios .mbsc-sc-whl-multi .mbsc-sc-itm{padding:0 1.818181em;color:#000}.mbsc-ios .mbsc-sc-whl-multi .mbsc-sc-itm-sel{color:#007aff}.mbsc-ios .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{font-size:1.818181em}.mbsc-ios .mbsc-sc-whl .mbsc-sel-gr{padding-left:.277777em;color:#9d9d9d;font-weight:normal;font-size:1.222222em}.mbsc-ios.mbsc-sel-gr-whl .mbsc-sc-whl-multi{padding-left:1.66666em}.mbsc-ios .mbsc-dt-whl-m .mbsc-sc-itm{text-align:left}.mbsc-ios .mbsc-dt-whl-d .mbsc-sc-itm,.mbsc-ios .mbsc-dt-whl-h .mbsc-sc-itm,.mbsc-ios .mbsc-dt-whl-i .mbsc-sc-itm,.mbsc-ios .mbsc-dt-whl-s .mbsc-sc-itm{text-align:right}.mbsc-bootstrap .mbsc-sc-whl-gr-c{overflow:visible}.mbsc-bootstrap .mbsc-sc-whl-gr{padding:4px 2px}.mbsc-bootstrap .mbsc-sc-lbl-v{padding-top:30px}.mbsc-bootstrap .mbsc-sc-lbl{line-height:30px}.mbsc-bootstrap .mbsc-sc-whl-w{margin:0 2px}.mbsc-bootstrap .mbsc-sc-whl-l{z-index:2;margin:0 -2px;background:rgba(0,0,0,.2);-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-bootstrap .mbsc-ltr .mbsc-sc-whl-w:first-child .mbsc-sc-whl-l,.mbsc-bootstrap .mbsc-rtl .mbsc-sc-whl-w:last-child .mbsc-sc-whl-l{margin-left:-4px}.mbsc-bootstrap .mbsc-ltr .mbsc-sc-whl-w:last-child .mbsc-sc-whl-l,.mbsc-bootstrap .mbsc-rtl .mbsc-sc-whl-w:first-child .mbsc-sc-whl-l{margin-right:-4px}.mbsc-bootstrap .mbsc-sc-itm{padding:0 5px;font-size:26px}.mbsc-bootstrap .mbsc-sc-itm.mbsc-btn-a{background:rgba(0,0,0,.1)}.mbsc-bootstrap .mbsc-sc-cp .mbsc-sc-whl-w{padding:30px 0}.mbsc-bootstrap .mbsc-sc-btn{height:30px!important;line-height:30px!important}.mbsc-bootstrap .mbsc-sc-btn-plus{top:auto}.mbsc-bootstrap .mbsc-sc-btn-a{background:rgba(0,0,0,.1)}.mbsc-bootstrap .mbsc-sc-whl-multi .mbsc-sc-itm{padding:0 40px}.mbsc-bootstrap .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{width:40px;font-size:16px}.mbsc-bootstrap .mbsc-sc-whl .mbsc-sel-gr{padding:0 5px;font-size:20px}.mbsc-np .mbsc-fr-w{font-size:1.333333em}.mbsc-np-hdr{position:relative}.mbsc-np-dsp{padding:.5em 1.75em .5em .5em;text-align:left;font-size:2em;line-height:.8}.mbsc-np-sup{display:inline-block;font-size:.375em;vertical-align:top}.mbsc-np-del{position:absolute;top:0;right:0;height:100%;width:3.5em;text-align:center}.mbsc-np-del:before{display:block;width:100%;position:absolute;top:50%;left:0;margin-top:-.5em;font-size:2em;line-height:1}.mbsc-np-ph{display:inline-block;min-width:.5625em;text-align:center;vertical-align:top}.mbsc-np-tbl-c{min-width:15em;display:inline-block}.mbsc-fr-top .mbsc-np-tbl-c,.mbsc-fr-bottom .mbsc-np-tbl-c{display:block}.mbsc-np-tbl{display:table;width:100%;font-size:1.375em}.mbsc-np-row{display:table-row}.mbsc-np-btn{position:relative;z-index:0;width:33.3333333333%;display:table-cell;text-align:center;vertical-align:middle;height:3em;-webkit-user-select:none}.mbsc-np-btn.mbsc-fr-btn-a{opacity:1}.mbsc-rtl .mbsc-np-dsp{padding-left:1.75em;padding-right:.5em;text-align:right}.mbsc-rtl .mbsc-np-del{left:0;right:auto;-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.mbsc-np-time{text-transform:uppercase}.mbsc-np-ts-h,.mbsc-np-ts-m{font-weight:bold}.mbsc-mobiscroll .mbsc-np-del{color:#4eccc4}.mbsc-mobiscroll .mbsc-np-tbl-c{padding:.5em}.mbsc-mobiscroll .mbsc-np-btn.mbsc-fr-btn-d{opacity:.5}.mbsc-android .mbsc-np-hdr{border-bottom:2px solid #555}.mbsc-android .mbsc-np-tbl-c{padding:1px}.mbsc-android .mbsc-np-btn{border:1px solid #000;color:#000;background:#ccc;background:-webkit-gradient(linear,left bottom,left top,from(#ccc),to(#eee));background:-webkit-linear-gradient(#eee,#ccc);background:-moz-linear-gradient(#eee,#ccc);background:linear-gradient(#eee,#ccc)}.mbsc-android .mbsc-np-btn.mbsc-fr-btn-d{opacity:.7}.mbsc-android .mbsc-np-btn-empty{background:0}.mbsc-android-holo .mbsc-np-hdr{border-bottom:1px solid #424542}.mbsc-android-holo .mbsc-np-btn.mbsc-fr-btn-d{opacity:.3}.mbsc-android-holo-light .mbsc-np-hdr{border-bottom:1px solid #dbdbdb}.mbsc-bootstrap.mbsc-np .mbsc-fr-btn-cont{padding:4px 2px}.mbsc-bootstrap .mbsc-np-del.mbsc-fr-btn-a{opacity:.5}.mbsc-bootstrap .mbsc-np-btn{border-top-width:0;border-right-width:0;font-size:inherit;border-radius:0}.mbsc-bootstrap .mbsc-np-row:first-child .mbsc-np-btn{border-top-width:1px}.mbsc-bootstrap .mbsc-ltr .mbsc-np-btn:first-child,.mbsc-bootstrap .mbsc-rtl .mbsc-np-btn:last-child{border-left-width:0}.mbsc-bootstrap.mbsc-inline .mbsc-np-row:last-child .mbsc-np-btn{border-bottom-width:0}.mbsc-bootstrap .mbsc-np-row .mbsc-np-btn.mbsc-np-btn-empty{background:0}.mbsc-ios-classic.mbsc-np .mbsc-fr-w{padding:0;background:#282a39}.mbsc-ios-classic .mbsc-np-del{color:#4d5463;text-shadow:0 1px 0 rgba(255,255,255,0.7)}.mbsc-ios-classic .mbsc-np-btn{border-top:1px solid #2d3034;border-left:1px solid #2d3034;background:#6f7684;background:-webkit-gradient(linear,left top,left bottom,from(#6f7684),to(#4e5564));background:-webkit-linear-gradient(#6f7684,#4e5564);background:-moz-linear-gradient(#6f7684,#4e5564);background:linear-gradient(#6f7684,#4e5564);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3) inset;box-shadow:0 1px 0 rgba(255,255,255,.3) inset;text-shadow:0 -1px 0 rgba(52,64,77,0.7);font-weight:bold}.mbsc-ios-classic .mbsc-ltr .mbsc-np-btn:first-child,.mbsc-ios-classic .mbsc-rtl .mbsc-np-btn:last-child{border-left:0}.mbsc-ios-classic .mbsc-np-btn.mbsc-fr-btn-a{background:#eee;color:#000;text-shadow:0 1px 0 rgba(255,255,255,0.7)}.mbsc-ios-classic .mbsc-np-btn.mbsc-fr-btn-d{opacity:.5}.mbsc-ios-classic .mbsc-np-btn-empty,.mbsc-ios-classic .mbsc-np-hdr{color:#000;background:#eee;background:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#bbb));background:-webkit-linear-gradient(#eee,#bbb);background:-moz-linear-gradient(#eee,#bbb);background:linear-gradient(#eee,#bbb)}.mbsc-ios.mbsc-np .mbsc-fr-w{padding-top:2.5em}.mbsc-ios.mbsc-np .mbsc-fr-btn{font-size:1.0625em}.mbsc-ios .mbsc-np-btn{border-top:1px solid #ddd;border-left:1px solid #ddd;font-size:1.375em;height:2.2em}.mbsc-ios .mbsc-ltr .mbsc-np-btn:first-child,.mbsc-ios .mbsc-rtl .mbsc-np-btn:last-child{border-left:0}.mbsc-ios .mbsc-np-btn.mbsc-fr-btn-a{background:#ddd}.mbsc-ios .mbsc-np-btn.mbsc-fr-btn-d{color:#ddd}.mbsc-ios .mbsc-np-btn-empty{background:#ddd}.mbsc-ios .mbsc-np-ts-h,.mbsc-ios .mbsc-np-ts-m{font-weight:normal}.mbsc-jqm.mbsc-np .mbsc-fr-btn-cont{padding:4px 2px}.mbsc-jqm .mbsc-np-del.mbsc-fr-btn-a{opacity:.5}.mbsc-jqm .mbsc-np-btn{border-top-width:0;border-right-width:0;font-size:1.2em;height:2.5em;padding:0}.mbsc-jqm .mbsc-np-row:first-child .mbsc-np-btn{border-top-width:1px}.mbsc-jqm.mbsc-inline .mbsc-np-row:last-child .mbsc-np-btn{border-bottom-width:0}.mbsc-jqm .mbsc-ltr .mbsc-np-btn:first-child,.mbsc-jqm .mbsc-rtl .mbsc-np-btn:first-child{border-left-width:0}.mbsc-jqm .mbsc-np-btn .ui-btn-inner{height:100%;padding:0;font-size:1em;line-height:2.5em}.mbsc-jqm .mbsc-np-row .mbsc-np-btn.mbsc-np-btn-empty{background:0}.mbsc-sense-ui .mbsc-np-hdr{background:#000;border-bottom:2px solid #fff}.mbsc-sense-ui .mbsc-np-btn.mbsc-fr-btn-d{color:#666}.mbsc-sense .mbsc-np-hdr{background:#121212;color:#fff}.mbsc-sense .mbsc-np-btn{border-top:1px solid #bfbfbf;border-left:1px solid #bfbfbf;color:#2c2e2f;-webkit-box-shadow:0 .0625em 0 rgba(255,255,255,.5) inset;box-shadow:0 .0625em 0 rgba(255,255,255,.5) inset}.mbsc-sense .mbsc-ltr .mbsc-np-btn:first-child,.mbsc-sense .mbsc-rtl .mbsc-np-btn:last-child{border-left:0}.mbsc-sense .mbsc-np-btn.mbsc-fr-btn-a{background:#bad7e1}.mbsc-sense .mbsc-np-btn.mbsc-fr-btn-d{color:#bfbfbf}.mbsc-wp.mbsc-np .mbsc-fr-w{padding:2px}.mbsc-wp.mbsc-np .mbsc-fr-btn-cont{padding-top:4px}.mbsc-wp .mbsc-np-del.mbsc-fr-btn-a{top:0;opacity:.5}.mbsc-wp .mbsc-np-del.mbsc-fr-btn-a:before{background:0;color:#fff}.mbsc-wp .mbsc-np-btn{top:0;background:#333;border:2px solid #1f1f1f}.mbsc-wp .mbsc-np-btn.mbsc-fr-btn-a{background:#1a9fe0}.mbsc-wp .mbsc-np-btn.mbsc-fr-btn-d{color:#666}.mbsc-wp .mbsc-np-btn-empty{background:0}.mbsc-wp-light .mbsc-np-del.mbsc-fr-btn-a:before{color:#000}.mbsc-wp-light .mbsc-np-btn{border-color:#dedede;background:#ccc}.mbsc-wp-light .mbsc-np-btn.mbsc-fr-btn-a{background:#b5b5b5}.mbsc-wp-light .mbsc-np-btn.mbsc-fr-btn-d{color:#999}.mbsc-wp-light .mbsc-np-btn-empty{background:0}.mbsc-material .mbsc-np-del,.mbsc-material .mbsc-np-btn{overflow:hidden}.mbsc-material .mbsc-np-btn{font-size:1.4545em;height:2.0625em}.mbsc-material .mbsc-np-del{color:#009688;font-size:.875em}.mbsc-material .mbsc-np-tbl-c{padding:.5em}.mbsc-material .mbsc-np-btn.mbsc-fr-btn-d{opacity:.5}.mbsc-cal-sc-m-cell,.mbsc-cal-days,.mbsc-cal-slide,.mbsc-cal-row,.mbsc-cal-day,.mbsc-cal-day-fg,.mbsc-cal-week-nr{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-cal-sc-c,.mbsc-cal-week-nrs-c,.mbsc-cal-c,.mbsc-cal-slide,.mbsc-cal-tab .mbsc-cal-tab-i,.mbsc-cal-sc .mbsc-cal-sc-cell-i{-webkit-backface-visibility:hidden;backface-visibility:hidden}.mbsc-calendar .mbsc-fr-c{position:relative;margin:0 auto}.mbsc-calendar .mbsc-cal-hdn{width:0;height:0;padding:0;margin:0;overflow:hidden;display:none}.mbsc-cal-c{position:relative;-ms-touch-action:pan-y;touch-action:pan-y}.mbsc-cal-vertical .mbsc-cal-body{-ms-touch-action:none;touch-action:none}.mbsc-cal-pnl{display:inline-block;vertical-align:middle}.mbsc-cal-tabbed .mbsc-cal-pnl{display:table;position:absolute;top:0;width:100%;height:100%}.mbsc-cal-tabbed .mbsc-cal-pnl-h{display:none}.mbsc-cal-tabbed .mbsc-cal-pnl-i{display:table-cell;vertical-align:middle}.mbsc-cal-tabbed .mbsc-cal{width:auto!important}.mbsc-anim-trans .mbsc-fr-persp .mbsc-cal-c{-webkit-backface-visibility:visible;backface-visibility:visible}.mbsc-cal-anim-c{position:relative;overflow:hidden;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-transform:translate3d(0,0,0);-ms-transform:translate(0,0);transform:translate3d(0,0,0)}.mbsc-cal-anim{position:relative;height:100%;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.mbsc-cal-slide{opacity:.5;position:absolute;top:0;left:0;width:100%;height:100%;-webkit-transition:opacity 300ms;-webkit-perspective:1000px;perspective:1000px}.mbsc-cal-slide:first-child{position:relative}.mbsc-cal-slide-a{opacity:1}.mbsc-cal-header{line-height:40px;text-align:center}.mbsc-cal table{width:100%;height:100%;border-collapse:collapse;table-layout:fixed}.mbsc-cal th{line-height:30px;text-align:center}.mbsc-cal-nb{border:0}.mbsc-cal-body{position:relative}.mbsc-cal-table{height:100%;width:100%;display:table;table-layout:fixed}.mbsc-cal-row{white-space:nowrap;display:table-row}.mbsc-cal-day{line-height:2.5em;width:14.2857%;height:16.6666%;position:relative;display:table-cell;vertical-align:middle;text-align:right;cursor:default}.mbsc-cal-day-v{cursor:pointer}.mbsc-cal-day .mbsc-cal-day-i{position:relative;height:100%}.mbsc-cal-day-diff .mbsc-cal-day-i{opacity:.3}.mbsc-cal-day-diff.mbsc-cal-day-sel .mbsc-cal-day-i{opacity:1}.mbsc-cal-hide-diff .mbsc-cal-day-diff .mbsc-cal-day-i{visibility:hidden}.mbsc-cal-day-fg{padding:5px;position:relative;z-index:2}.mbsc-cal-weeks .mbsc-cal-days-c,.mbsc-cal-weeks .mbsc-cal-anim-c{padding-left:26px}.mbsc-cal-week-nrs-c{display:none;position:absolute;width:26px;height:100%;left:0;z-index:3}.mbsc-cal-week-nrs{display:table;width:100%;height:100%}.mbsc-cal-week-nr{position:relative;display:none;height:16.6666%;text-align:center;font-weight:bold}.mbsc-cal-week-nr-i{display:table-cell;vertical-align:middle;padding-top:1px}.mbsc-cal-weeks .mbsc-cal-week-nrs-c{display:block}.mbsc-cal-week-nrs .mbsc-cal-week-nr{display:table-row}.mbsc-rtl .mbsc-cal-weeks .mbsc-cal-days-c,.mbsc-rtl .mbsc-cal-weeks .mbsc-cal-anim-c{padding-left:0;padding-right:26px}.mbsc-rtl .mbsc-cal-week-nrs-c{left:auto;right:0}.mbsc-cal-sc-c{position:absolute;top:0;left:0;width:100%;height:100%;z-index:5;overflow:hidden}.mbsc-cal-h{visibility:hidden}.mbsc-cal-p-out{-webkit-animation:200ms mbsc-anim-f-out ease-in;-moz-animation:200ms mbsc-anim-f-out ease-in;animation:200ms mbsc-anim-f-out ease-in}.mbsc-cal-p-in{-webkit-animation:200ms mbsc-anim-f-in ease-out;-moz-animation:200ms mbsc-anim-f-in ease-out;animation:200ms mbsc-anim-f-in ease-out}.mbsc-old .mbsc-cal-p-in,.mbsc-old .mbsc-cal-p-out{-webkit-animation:none;-moz-animation:none;animation:none}.mbsc-cal-sc{position:relative;height:100%;white-space:nowrap;font-size:1.3333em}.mbsc-cal-sc-p{position:absolute;top:0;left:0;overflow:hidden;width:100%;height:100%;-webkit-transform:translate3d(0,0,0)}.mbsc-cal-sc-tbl{display:table;width:100%;height:100%;table-layout:fixed}.mbsc-cal-sc-row{display:table-row}.mbsc-cal-sc-cell{cursor:pointer;display:table-cell;vertical-align:middle;text-align:center}.mbsc-cal-sc-m-cell{height:25%}.mbsc-cal-btnc{position:relative;font-size:1.5em}.mbsc-cal-btnw{position:relative;height:40px}.mbsc-cal .mbsc-cal-btn{position:absolute;top:0;height:40px;width:36px;margin:0;padding:0;cursor:pointer}.mbsc-cal .mbsc-fr-btn-d{opacity:.3}.mbsc-cal-prev{left:0}.mbsc-cal-next{right:0}.mbsc-cal-month,.mbsc-cal-year{cursor:pointer}.mbsc-cal-btnc-ym .mbsc-cal-month,.mbsc-cal-btnc-ym .mbsc-cal-year{display:block;margin:0 36px;overflow:hidden;text-overflow:ellipsis}.mbsc-cal-btnc-ym .mbsc-cal-btnw{width:50%;display:inline-block;white-space:nowrap;vertical-align:top}.mbsc-cal-btn-txt{overflow:hidden}.mbsc-cal-btn-txt:before{display:block}.mbsc-cal-liq .mbsc-fr-popup{display:block}.mbsc-cal-liq .mbsc-cal-pnl{width:100%}.mbsc-cal-liq .mbsc-cal{width:auto}.mbsc-cal-liq.mbsc-fr-center .mbsc-fr-popup{width:100%;top:0;left:0}.mbsc-cal-liq.mbsc-fr-center .mbsc-fr-overlay{display:none}.mbsc-cal-liq.mbsc-fr-center .mbsc-fr-w{width:auto!important}.mbsc-cal-tabs{line-height:40px;display:none;color:#000}.mbsc-cal-tabbed .mbsc-cal-tabs{display:block}.mbsc-cal-tabbed .mbsc-fr-w{width:auto!important}.mbsc-cal-tabs ul,.mbsc-cal-tabs li{margin:0;padding:0;list-style:none}.mbsc-cal-tabs ul{width:100%;display:table}.mbsc-cal-tab{width:33.33%;display:table-cell;cursor:pointer}.mbsc-cal-tab .mbsc-cal-tab-i{display:block;text-decoration:none}.mbsc-cal-multi .mbsc-cal-slide{padding-left:8px}.mbsc-ltr .mbsc-cal-multi .mbsc-cal-anim-c{margin-right:-8px}.mbsc-rtl .mbsc-cal-multi .mbsc-cal-anim-c{margin-left:-8px}.mbsc-cal-multi .mbsc-cal-anim{margin-left:-8px}.mbsc-cal-multi .mbsc-cal-btnw{display:block;width:auto}.mbsc-cal-multi .mbsc-cal-btnw-m{display:inline-block;white-space:nowrap;vertical-align:top}.mbsc-cal-multi .mbsc-cal-days-c{overflow:hidden}.mbsc-cal-multi .mbsc-cal-days{float:left}.mbsc-cal-m .mbsc-cal{font-size:1.2em}.mbsc-cal-l .mbsc-cal{font-size:1.4em}.mbsc-cal-ev .mbsc-cal-day-fg{padding-bottom:20px}.mbsc-cal-day-m{position:absolute;left:0;right:0;bottom:4px;line-height:0}.mbsc-cal-day-m-t{display:block;overflow:hidden;white-space:nowrap}.mbsc-cal-day-m-c{display:inline-block;vertical-align:top;width:4px;height:4px;margin:0 1px;border-radius:4px}.mbsc-cal-day-ic-c,.mbsc-cal-day-txt-c{position:absolute;left:0;right:0;bottom:0;overflow:hidden}.mbsc-cal-day-ic-c{padding:0 5px;font-size:1em;line-height:20px;text-overflow:ellipsis;text-shadow:none}.mbsc-cal-day-txt{padding:0 2px;font-size:.8333em;font-weight:normal;line-height:19px;overflow:hidden;white-space:nowrap;text-align:center;text-overflow:ellipsis;text-shadow:none;margin-top:2px}.mbsc-mobiscroll .mbsc-cal-c{padding:.5em}.mbsc-mobiscroll .mbsc-cal-sc{font-size:1em}.mbsc-mobiscroll .mbsc-cal-sc-c{background:#f7f7f7}.mbsc-mobiscroll .mbsc-cal-btnc{font-size:1.125em}.mbsc-mobiscroll .mbsc-cal-hl-now .mbsc-cal-today{color:#4eccc4}.mbsc-mobiscroll .mbsc-cal-btn-txt{color:#4eccc4;font-size:1em;line-height:40px}.mbsc-mobiscroll .mbsc-cal-days-c{margin-left:-8px}.mbsc-mobiscroll .mbsc-cal-days{padding-left:8px;color:#4eccc4}.mbsc-mobiscroll .mbsc-cal-days th{font-size:.6875em;font-weight:normal;border-bottom:1px solid #4eccc4}.mbsc-mobiscroll .mbsc-cal-day{line-height:1.875em;text-align:center}.mbsc-mobiscroll .mbsc-cal-day-fg{font-size:.8125em}.mbsc-mobiscroll .mbsc-cal-day-inv .mbsc-cal-day-fg{opacity:.3}.mbsc-mobiscroll .mbsc-cal .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i,.mbsc-mobiscroll .mbsc-cal .mbsc-cal-day-sel .mbsc-cal-day-i{background:#4eccc4;color:#fff}.mbsc-mobiscroll .mbsc-cal-day-hl .mbsc-cal-day-i{border-color:#4eccc4}.mbsc-mobiscroll .mbsc-cal-week-nrs-c{font-size:.6875em;color:#4eccc4;background:#f7f7f7}.mbsc-mobiscroll .mbsc-cal-week-nr-i{padding-top:0;font-weight:normal}.mbsc-mobiscroll .mbsc-cal-tabs{line-height:1.875em;padding:.5em .5em 0 .5em;text-transform:uppercase}.mbsc-mobiscroll .mbsc-cal-tabbed .mbsc-fr-w{padding-top:.5em}.mbsc-mobiscroll .mbsc-cal-tabbed .mbsc-fr-hdr{padding-top:0}.mbsc-mobiscroll .mbsc-cal-tab{border:1px solid #4eccc4;font-size:.7em}.mbsc-mobiscroll .mbsc-cal-tabs ul{width:auto;margin:0 auto}.mbsc-mobiscroll .mbsc-cal-tab .mbsc-cal-tab-i{padding:0 1.2em;color:#454545}.mbsc-mobiscroll .mbsc-ltr .mbsc-cal-tab:first-child{border-right:0}.mbsc-mobiscroll .mbsc-ltr .mbsc-cal-tab:last-child{border-left:0}.mbsc-mobiscroll .mbsc-rtl .mbsc-cal-tab:last-child{border-right:0}.mbsc-mobiscroll .mbsc-rtl .mbsc-cal-tab:first-child{border-left:0}.mbsc-mobiscroll .mbsc-cal-tabs .mbsc-cal-tab-sel{background:#4eccc4}.mbsc-mobiscroll .mbsc-cal-tabs .mbsc-cal-tab-sel .mbsc-cal-tab-i{color:#f7f7f7}.mbsc-mobiscroll .mbsc-cal-day-m{bottom:4px}.mbsc-mobiscroll .mbsc-cal-day-m-c{background:#454545}.mbsc-mobiscroll .mbsc-cal-day-sel .mbsc-cal-day-m-c{background:#f7f7f7}.mbsc-mobiscroll .mbsc-cal-day-txt{color:#f7f7f7;background:#454545}.mbsc-mobiscroll .mbsc-cal-day-txt,.mbsc-mobiscroll .mbsc-cal-day-ic-c{font-size:.625em}.mbsc-android-holo .mbsc-cal-sc-c{background:#292829}.mbsc-android-holo .mbsc-cal-sc-m-cell{border-top:1px solid #444}.mbsc-android-holo .mbsc-cal-btn.mbsc-fr-btn-a{background:0}.mbsc-android-holo .mbsc-cal-week-nrs-c{background:#292829}.mbsc-android-holo .mbsc-cal-week-nr-i{color:#555;padding:0;border-top:1px solid #444}.mbsc-android-holo .mbsc-cal{padding:.834em;font-size:.75em}.mbsc-android-holo .mbsc-cal-header{line-height:3em}.mbsc-android-holo .mbsc-cal-btnw{height:2em}.mbsc-android-holo .mbsc-cal-btn{height:2.25em;line-height:2.25em}.mbsc-android-holo .mbsc-cal-hl-now .mbsc-cal-today{color:#31b6e7}.mbsc-android-holo .mbsc-cal-btn-txt{color:#7e7e7e;font-size:1.875em}.mbsc-android-holo .mbsc-fr-btn-a .mbsc-cal-btn-txt{color:#319abd}.mbsc-android-holo .mbsc-cal th{color:#555}.mbsc-android-holo .mbsc-cal-day{border-top:1px solid #444;text-align:center}.mbsc-android-holo .mbsc-cal-day-inv .mbsc-cal-day-fg{opacity:.3}.mbsc-android-holo .mbsc-cal .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i,.mbsc-android-holo .mbsc-cal .mbsc-cal-day-sel .mbsc-cal-day-i{color:#fff;background:#31b6e7;background:rgba(49,182,231,.5)}.mbsc-android-holo .mbsc-cal .mbsc-cal-day-hl .mbsc-cal-day-i{background:#31b6e7;color:#fff}.mbsc-android-holo.mbsc-cal-liq .mbsc-cal{padding:0}.mbsc-android-holo .mbsc-cal-tabs{background:#333;color:#fff;text-transform:uppercase;font-size:.625em;font-weight:bold;line-height:1.4em}.mbsc-android-holo .mbsc-cal-tab{border:5px solid #333;border-left:0;border-right:0}.mbsc-android-holo .mbsc-cal-tab:first-child .mbsc-cal-tab-i{border-color:transparent}.mbsc-android-holo .mbsc-cal-tabs .mbsc-cal-tab-i{color:#fff;margin:.8em 0;border-left:1px solid #444}.mbsc-android-holo .mbsc-rtl .mbsc-cal-tabs .mbsc-cal-tab-i{border-left:0;border-right:1px solid #444}.mbsc-android-holo .mbsc-cal-tabs .mbsc-cal-tab-sel{border-bottom:5px solid #31b6e7}.mbsc-android-holo .mbsc-cal-day-m{left:3px;right:3px;bottom:3px}.mbsc-android-holo .mbsc-cal-day-m-t{display:table;table-layout:fixed;width:100%}.mbsc-android-holo .mbsc-cal-day-m-c{display:table-cell;height:3px;background:#31b6e7;border-radius:0}.mbsc-wp .mbsc-cal-sc-m-cell{background:#1f1f1f;padding:.167em}.mbsc-wp .mbsc-cal-sc-m-cell .mbsc-cal-sc-cell-i{top:0;height:100%;border-width:1px}.mbsc-wp .mbsc-cal-sc-empty .mbsc-cal-sc-cell-i{border:0}.mbsc-wp .mbsc-cal-tabs .mbsc-cal-tab-sel .mbsc-cal-tab-i,.mbsc-wp .mbsc-cal-day-sel .mbsc-cal-day-i,.mbsc-wp .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i{color:#fff;background:#1a9fe0;border-color:#1a9fe0}.mbsc-wp.mbsc-calendar .mbsc-fr-btn-cont{padding-top:.625em}.mbsc-wp .mbsc-cal-btnc{text-transform:lowercase}.mbsc-wp .mbsc-cal-btn{padding:0;position:absolute}.mbsc-wp .mbsc-cal-btn.mbsc-fr-btn-a{top:-.273em}.mbsc-wp .mbsc-cal-btn:after{display:none}.mbsc-wp .mbsc-cal-btn-txt{font-size:1.1666em}.mbsc-wp .mbsc-cal-btn-txt:before{position:absolute;top:50%;left:50%;width:1.525em;height:1.525em;margin:-.9em 0 0 -.9em;color:#fff;border:2px solid #fff;line-height:1.525em;border-radius:1000px}.mbsc-wp .mbsc-fr-btn-a .mbsc-cal-btn-txt:before{background:#1a9fe0;border-color:#1a9fe0;color:#fff}.mbsc-wp .mbsc-cal{font-size:.75em}.mbsc-wp .mbsc-cal th{font-weight:normal}.mbsc-wp .mbsc-cal-day{padding:.167em;line-height:1.667em}.mbsc-wp .mbsc-cal-day .mbsc-cal-day-i{top:0;height:100%;padding:0;border:0}.mbsc-wp .mbsc-cal-day-fg{height:100%;border:1px solid #4c4c4c;padding:.833em .417em 0 .417em}.mbsc-wp .mbsc-cal-day-inv .mbsc-cal-day-fg{color:rgba(255,255,255,.3)}.mbsc-wp .mbsc-cal-day-sel .mbsc-cal-day-fg{border-color:#1a9fe0}.mbsc-wp .mbsc-cal-week-nrs-c{background:#1f1f1f}.mbsc-wp .mbsc-cal-week-nr-i{padding-top:.833em}.mbsc-wp .mbsc-cal-day-hl .mbsc-cal-day-fg{background:#fff;border-color:#fff;color:#000}.mbsc-wp .mbsc-cal-hl-now .mbsc-cal-today{color:#1a9fe0}.mbsc-wp .mbsc-cal-sc-cell-i .mbsc-cal-sc-cell,.mbsc-wp .mbsc-cal-tabs .mbsc-cal-tab-i{border:1px solid #4c4c4c;color:#fff}.mbsc-wp .mbsc-cal-tabs{font-size:.75em;padding:0 0 .313em 0}.mbsc-wp .mbsc-ltr .mbsc-cal-tab .mbsc-cal-tab-i{border-left-width:0}.mbsc-wp .mbsc-ltr .mbsc-cal-tab:first-child .mbsc-cal-tab-i{border-left-width:1px}.mbsc-wp .mbsc-rtl .mbsc-cal-tab .mbsc-cal-tab-i{border-right-width:0}.mbsc-wp .mbsc-rtl .mbsc-cal-tab:first-child .mbsc-cal-tab-i{border-right-width:1px}.mbsc-wp .mbsc-cal-day-m{top:3px;left:3px;right:3px;bottom:auto}.mbsc-wp .mbsc-cal-day-m-t{display:table;table-layout:fixed;width:100%}.mbsc-wp .mbsc-cal-day-m-c{display:table-cell;height:3px;background:#fff;border-radius:0}.mbsc-wp .mbsc-cal-m-bottom .mbsc-cal-day-fg{padding-bottom:.417em}.mbsc-wp .mbsc-cal-ev .mbsc-cal-day-fg{padding-bottom:1.667em}.mbsc-wp .mbsc-cal-day-txt{color:#fff;background:#444}.mbsc-material .mbsc-cal-c{padding:.5em .5em 0 .5em}.mbsc-material .mbsc-cal-sc{font-size:1em}.mbsc-material .mbsc-cal-sc-c{background:#eee}.mbsc-material .mbsc-cal-btnc{font-size:1.125em}.mbsc-material .mbsc-cal .mbsc-cal-btn{height:2.2222em;width:2.2222em;line-height:2.2222em}.mbsc-material .mbsc-cal-hl-now .mbsc-cal-today{color:#009688}.mbsc-material .mbsc-cal-btn-txt{color:#009688;font-size:1.875em}.mbsc-material .mbsc-cal-year,.mbsc-material .mbsc-cal-month{font-weight:bold;line-height:2.2222em}.mbsc-material .mbsc-cal-days-c{padding-bottom:.375em}.mbsc-material .mbsc-cal-days th{font-size:.75em;font-weight:bold;line-height:2.5em}.mbsc-material .mbsc-cal-day{line-height:1.875em;text-align:center;padding-bottom:.6875em}.mbsc-material .mbsc-cal-day-fg{font-size:.875em;width:2.4286em;height:2.4286em;line-height:2.4em;border:1px solid transparent;text-align:center;padding:0;margin:0 auto}.mbsc-material .mbsc-cal-day-inv .mbsc-cal-day-fg{color:rgba(91,91,91,.3)}.mbsc-material .mbsc-cal .mbsc-cal-day-sel .mbsc-cal-day-fg{border-radius:2.3em;background:rgba(13,155,141,.3)}.mbsc-material .mbsc-cal-sc .mbsc-cal-sc-cell-i{display:inline-block;width:4em;height:4em;line-height:4em;border-radius:4em}.mbsc-material .mbsc-cal-sc .mbsc-cal-sc-cell-i .mbsc-cal-sc-cell{display:block}.mbsc-material .mbsc-cal-sc .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i{background:rgba(13,155,141,.3)}.mbsc-material .mbsc-cal-week-nrs-c{font-size:.6875em;color:#5b5b5b;background:#eee}.mbsc-material .mbsc-cal-week-nr-i{vertical-align:top;padding-top:1em}.mbsc-material .mbsc-cal-tabs{line-height:1.875em;padding:.5em .5em 0 .5em;text-transform:uppercase}.mbsc-material .mbsc-cal-tabbed .mbsc-fr-hdr{padding-top:0}.mbsc-material .mbsc-cal-tabbed .mbsc-cal-c{padding-top:0}.mbsc-material .mbsc-cal-tab{font-size:.75em}.mbsc-material .mbsc-cal-tab .mbsc-cal-tab-i{padding:0 .5em;color:#5b5b5b}.mbsc-material .mbsc-cal-tabs .mbsc-cal-tab-sel .mbsc-cal-tab-i{color:#009688}.mbsc-material .mbsc-cal-day-m{bottom:.0625em;-webkit-transition:bottom .1s ease-out;-moz-transition:bottom .1s ease-out;transition:bottom .1s ease-out}.mbsc-material .mbsc-cal-day-m-c{background:#009688}.mbsc-material .mbsc-cal-day-sel .mbsc-cal-day-m{bottom:-.45em}.mbsc-material .mbsc-cal-day-txt{margin:0 1px;color:#eee;background:#7c7c7c}.mbsc-material .mbsc-cal-ev .mbsc-cal-day .mbsc-cal-day-i{padding-bottom:1.125em}.mbsc-material .mbsc-cal-day-txt,.mbsc-material .mbsc-cal-day-ic-c{font-size:.625em;line-height:1.5em}.mbsc-ios.mbsc-calendar.mbsc-inline .mbsc-fr-popup{border:1px solid #e3e3e3}.mbsc-ios.mbsc-calendar.mbsc-fr-bubble .mbsc-fr-w{padding-bottom:.666667em}.mbsc-ios .mbsc-cal-c{margin:0;padding:0}.mbsc-ios .mbsc-cal{color:#000}.mbsc-ios .mbsc-cal-header .mbsc-fr-btn{font-size:.944445em}.mbsc-ios .mbsc-cal-table{font-size:1.1667em}.mbsc-ios .mbsc-cal-hl-now .mbsc-cal-today{color:#007aff}.mbsc-ios .mbsc-cal-btn-txt{color:#007aff;font-size:1.352941em;line-height:1.73913em}.mbsc-ios .mbsc-cal th,.mbsc-ios .mbsc-cal-week-nr{font-weight:normal;font-size:1.166667em}.mbsc-ios .mbsc-cal-week-nrs-c{background:#f7f7f7}.mbsc-ios .mbsc-cal-day{padding:.142857em .357142em .714285em .357142em;background:#fff;border-top:1px solid #e3e3e3;color:#000;line-height:1.7141em}.mbsc-ios .mbsc-cal-day-i{position:static;text-align:center}.mbsc-ios .mbsc-cal .mbsc-cal-sc-cell-i{position:static;text-align:center}.mbsc-ios .mbsc-cal .mbsc-cal-day-fg{width:1.714286em;height:1.714286em;border:1px solid transparent;text-align:center;padding:0;margin:0 auto}.mbsc-ios .mbsc-cal-day-sel .mbsc-cal-day-fg{border-radius:1000px;border:1px solid #007aff;background:#007aff;color:#fff}.mbsc-ios .mbsc-cal-day-hl .mbsc-cal-day-fg{border:1px solid #007aff;background:0;color:#000;border-radius:1000px}.mbsc-ios .mbsc-cal-day-inv .mbsc-cal-day-fg{color:rgba(0,0,0,.3)}.mbsc-ios .mbsc-cal-day-inv.mbsc-cal-day-sel .mbsc-cal-day-fg{color:rgba(255,255,255,.4)}.mbsc-ios .mbsc-cal-sc-c{background:#fff}.mbsc-ios .mbsc-cal-sc-m-cell{background:#fff;border-top:1px solid #e3e3e3}.mbsc-ios .mbsc-cal-sc .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i,.mbsc-ios .mbsc-cal-sc .mbsc-cal-day-sel .mbsc-cal-day-i{display:inline-block;width:auto;height:auto;background:#007aff;color:#fff;padding:0 .625016em;line-height:1.625115em;border-radius:13px}.mbsc-ios .mbsc-cal-tabs{padding:.833334em .833334em .416667em .833334em;font-size:1em}.mbsc-ios .mbsc-cal-tab{border:1px solid #007aff;line-height:1.928571em;font-size:1.166667em}.mbsc-ios .mbsc-cal-tab .mbsc-cal-tab-i{padding:0 .714285em;color:#007aff}.mbsc-ios .mbsc-ltr .mbsc-cal-tab:first-child{border-right:0;border-radius:4px 0 0 4px}.mbsc-ios .mbsc-ltr .mbsc-cal-tab:last-child{border-left:0;border-radius:0 4px 4px 0}.mbsc-ios .mbsc-rtl .mbsc-cal-tab:last-child{border-right:0;border-radius:4px 0 0 4px}.mbsc-ios .mbsc-rtl .mbsc-cal-tab:first-child{border-left:0;border-radius:0 4px 4px 0}.mbsc-ios .mbsc-cal-tabs .mbsc-cal-tab-sel{background:#007aff}.mbsc-ios .mbsc-cal-tabs .mbsc-cal-tab-sel .mbsc-cal-tab-i{color:#fff}.mbsc-ios .mbsc-cal-ev .mbsc-cal-day{padding-bottom:1.428571em}.mbsc-ios .mbsc-cal-day-m{bottom:.142857em}.mbsc-ios .mbsc-cal-day-m-c{width:.428571em;height:.428571em;border-radius:.428571em;background:#ccc}.mbsc-ios .mbsc-cal-day-txt-c{width:auto;left:.142857em;right:.142857em;bottom:.142857em}.mbsc-ios .mbsc-cal-day-txt{background:#c3d7ef;color:#555;line-height:1.142857em;margin:0}.mbsc-bootstrap .mbsc-cal-c{padding:4px}.mbsc-bootstrap .mbsc-cal-sc-c{max-width:none;display:block;padding:0;border:0;-webkit-box-shadow:none;border-radius:0;box-shadow:none}.mbsc-bootstrap .mbsc-cal-btn{background:0;padding:0;border:0;text-indent:-9999px;-webkit-box-shadow:none;box-shadow:none;color:inherit}.mbsc-bootstrap .mbsc-cal-btn-txt{height:40px;text-decoration:none}.mbsc-bootstrap .mbsc-cal-btn-txt .glyphicon{display:block;position:absolute;top:50%;left:50%;margin:-7px 0 0 -7px;text-indent:0}.mbsc-bootstrap .mbsc-cal-day{padding:1px;text-align:center}.mbsc-bootstrap .mbsc-cal-day .mbsc-cal-day-i{display:block;border:0;line-height:29px}.mbsc-bootstrap .mbsc-cal-day-inv .mbsc-cal-day-fg{opacity:.3}.mbsc-bootstrap .mbsc-cal-day-hl .mbsc-cal-day-frame{position:absolute;z-index:1;top:0;left:0;width:100%;height:100%;background:#000;opacity:.1}.mbsc-bootstrap .mbsc-cal-tabs ul{padding:4px 4px 0 4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-bootstrap .mbsc-cal-tab{margin-bottom:-1px}.mbsc-bootstrap .mbsc-cal-week-nrs-c{padding:0;border:0;min-width:0;max-width:none;-webkit-box-shadow:none;border-radius:0;box-shadow:none}.mbsc-bootstrap .mbsc-cal-day-m-c{background:#aaa}.mbsc-color-preview{height:24px;line-height:24px;-webkit-transition:background-color .2s;-moz-transition:background-color .2s;transition:background-color .2s}.mbsc-color .mbsc-sc-itm{padding:0}.mbsc-color .mbsc-sc-itm.mbsc-btn-a{background:0}.mbsc-color-itm{width:100%;height:100%;border-bottom:1px solid rgba(170,170,170,0.5);border-top:1px solid rgba(255,255,255,0.5)}.mbsc-color-itm-a{height:100%;background:rgba(255,255,255,.3);display:none}.mbsc-sc-itm.mbsc-btn-a .mbsc-color-itm-a{display:block}.mbsc-mobiscroll.mbsc-color .mbsc-sc-whl-l{display:none}.mbsc-mobiscroll .mbsc-color-preview{height:auto;font-size:.75em;line-height:2.5em;text-transform:uppercase}.mbsc-mobiscroll .mbsc-color-itm{border-top:1px solid #f7f7f7;border-bottom:0}.mbsc-android-holo.mbsc-color .mbsc-sc-whl-l{display:none}.mbsc-android-holo .mbsc-color-preview{height:2.375em;line-height:2.375em}.mbsc-bootstrap.mbsc-color .mbsc-sc-whl-l{display:none}.mbsc-ios.mbsc-color .mbsc-sc-whl-w{margin:0 .25em}.mbsc-ios.mbsc-color .mbsc-sc-whl-l{display:none}.mbsc-ios .mbsc-color-preview{border-bottom:1px solid #acacac}.mbsc-jqm.mbsc-color .mbsc-sc-whl-l{display:none}.mbsc-jqm .mbsc-color-preview{text-shadow:none;border-bottom:1px solid #222}.mbsc-wp.mbsc-color .mbsc-sc-whl-w{margin:0 .25em}.mbsc-wp.mbsc-color .mbsc-sc-itm{opacity:1}.mbsc-wp.mbsc-color .mbsc-sc-itm:after{display:none}.mbsc-wp .mbsc-color-preview{margin:-.625em -625em 0 -625em;border-bottom:1px solid #fff}.mbsc-material .mbsc-color-preview{height:auto;font-size:.75em;line-height:2.5em;text-transform:uppercase}.mbsc-material.mbsc-color .mbsc-sc-whl-l{display:none}.mbsc-timer .mbsc-timer-lbl{display:none}.mbsc-mobiscroll.mbsc-timer .mbsc-fr-btn-cont{display:table}.mbsc-mobiscroll.mbsc-timer .mbsc-fr-btn-w{width:1px;text-align:left;display:table-cell;float:none}.mbsc-mobiscroll.mbsc-timer .mbsc-fr-btn-w:last-child{width:auto;text-align:right}.mbsc-mobiscroll.mbsc-timer .mbsc-fr-btn-w .mbsc-fr-btn{display:inline-block;vertical-align:middle;text-align:center;text-decoration:none}.mbsc-android-holo.mbsc-timer .mbsc-sc-whl-gr{padding-top:.0625em}.mbsc-android-holo.mbsc-timer .mbsc-sc-lbl-v .mbsc-timer-lbl{display:inline;visibility:hidden;font-size:1.125em;padding:0 0 0 .375em}.mbsc-android-holo.mbsc-timer .mbsc-sc-lbl{z-index:3;top:50%;right:0;bottom:auto;left:auto;width:auto;margin-top:-1.25em;padding:0 .375em;color:#31b6e7;font-size:1.125em;font-weight:bold;text-transform:lowercase}.mbsc-android-holo.mbsc-timer .mbsc-sc-itm{text-overflow:clip}.mbsc-wp.mbsc-timer .mbsc-sc-whl-gr{padding:0}.mbsc-wp.mbsc-timer .mbsc-sc-lbl{z-index:3;top:50%;right:auto;width:auto;height:auto;margin:1.545454em 0 0 .818181em;color:#fff;line-height:1.636363em;font-size:.6875em}.mbsc-wp.mbsc-timer .mbsc-sc-whl-anim .mbsc-sc-lbl{display:none}.mbsc-wp.mbsc-timer .mbsc-timer-running .mbsc-sc-whl-anim .mbsc-sc-lbl{display:block}.mbsc-wp .mbsc-timer-locked .mbsc-sc-itm{opacity:0}.mbsc-wp .mbsc-timer-locked .mbsc-sc-itm-sel,.mbsc-wp .mbsc-timer-running .mbsc-timer-whl-seconds .mbsc-sc-itm,.mbsc-wp .mbsc-timer-running .mbsc-timer-whl-fract .mbsc-sc-itm{opacity:1}.mbsc-material.mbsc-timer .mbsc-fr-btn-cont{display:table}.mbsc-material.mbsc-timer .mbsc-fr-btn-w{width:1px;text-align:left;display:table-cell;float:none}.mbsc-material.mbsc-timer .mbsc-fr-btn-w:last-child{width:auto;text-align:right}.mbsc-material.mbsc-timer .mbsc-fr-btn-w .mbsc-fr-btn{display:inline-block;vertical-align:middle;text-align:center;text-decoration:none}.mbsc-ios.mbsc-timer .mbsc-sc-whl-gr{padding:.833333em 0}.mbsc-ios.mbsc-timer .mbsc-sc-lbl{z-index:3;top:50%;bottom:auto;right:0;left:auto;width:auto;margin-top:-.75em;padding:0 .55em;color:#007aff;font-size:1.666667em;line-height:1.5em;text-transform:lowercase}.mbsc-ios.mbsc-timer .mbsc-sc-itm{text-overflow:clip}.mbsc-ios .mbsc-sc-lbl-v .mbsc-timer-lbl{display:inline;visibility:hidden;padding-left:.5em;font-size:.909090em;text-transform:lowercase}.mbsc-range-btn-t,.mbsc-range-btn-c{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-range-btn-t{display:table;width:100%}.mbsc-range-btn-c{display:table-cell;width:50%;cursor:pointer}.mbsc-range-btn{text-decoration:none;display:block;line-height:20px}.mbsc-mobiscroll .mbsc-range-btn-t{padding:.5em .25em 0 .25em}.mbsc-mobiscroll .mbsc-range-btn{margin:0 .3333em;padding:.3333em;border:1px solid #4eccc4;font-size:.75em}.mbsc-mobiscroll .mbsc-range-btn-sel .mbsc-range-btn{background:#4eccc4;color:#f7f7f7}.mbsc-mobiscroll.mbsc-range .mbsc-fr-w{padding:0}.mbsc-mobiscroll.mbsc-range .mbsc-fr-hdr{padding-top:.6666em}.mbsc-mobiscroll.mbsc-range .mbsc-cal-table .mbsc-cal-day-sel .mbsc-cal-day-i{color:#454545;background:rgba(78,204,196,.3)}.mbsc-mobiscroll.mbsc-range .mbsc-cal-table .mbsc-cal-day-hl .mbsc-cal-day-i{background:#4eccc4;color:#f7f7f7}.mbsc-android-holo .mbsc-range-btn-t{padding:4px 2px}.mbsc-android-holo .mbsc-range-btn{margin:0 2px;padding:4px;color:#fff;background:#444;font-size:.75em;border-radius:0}.mbsc-android-holo .mbsc-range-btn-sel .mbsc-range-btn{background:#31b6e7}.mbsc-android-holo-light .mbsc-range-btn{background:#e5e5e5;color:#000}.mbsc-android-holo-light .mbsc-range-btn-sel .mbsc-range-btn{background:#4bbde8}.mbsc-wp .mbsc-range-btn-t{font-size:.75em}.mbsc-wp .mbsc-range-btn-c{padding-bottom:.833em}.mbsc-wp .mbsc-range-btn{border-width:1px;color:#fff;border:1px solid #4c4c4c}.mbsc-wp .mbsc-range-btn-sel .mbsc-range-btn{color:#fff;background:#1a9fe0;border-color:#1a9fe0}.mbsc-wp .mbsc-range-btn-start .mbsc-range-btn{margin-right:.417em}.mbsc-wp .mbsc-range-btn-end .mbsc-range-btn{margin-left:.417em}.mbsc-material .mbsc-range-btn-t{border-bottom:1px solid #c6c6c6;font-size:.875em}.mbsc-material .mbsc-range-btn{padding:.5714em .2857em;color:#5b5b5b;border-bottom:2px solid transparent;margin-bottom:-1px}.mbsc-material .mbsc-range-btn-sel .mbsc-range-btn{border-color:#009688;color:#009688}.mbsc-material.mbsc-range .mbsc-cal .mbsc-cal-day-sel .mbsc-cal-day-fg{background:0}.mbsc-material.mbsc-range .mbsc-cal-day-sel .mbsc-cal-day-frame{position:absolute;top:0;width:100%;height:100%;opacity:1;color:#5b5b5b;background:rgba(13,155,141,.1);-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mbsc-material.mbsc-range .mbsc-cal-sel-start .mbsc-cal-day-frame{right:0;width:50%;padding-left:1.0526em}.mbsc-material.mbsc-range .mbsc-cal-sel-end .mbsc-cal-day-frame{left:0;width:50%;padding-right:1.0526em}.mbsc-material.mbsc-range .mbsc-cal-sel-start.mbsc-cal-sel-end .mbsc-cal-day-frame{width:0;padding:0}.mbsc-material.mbsc-range .mbsc-cal-sel-start .mbsc-cal-day-frame,.mbsc-material.mbsc-range .mbsc-cal-day:first-child .mbsc-cal-day-frame{border-top-left-radius:3em;border-bottom-left-radius:3em}.mbsc-material.mbsc-range .mbsc-cal-sel-end .mbsc-cal-day-frame,.mbsc-material.mbsc-range .mbsc-cal-day:last-child .mbsc-cal-day-frame{border-top-right-radius:3em;border-bottom-right-radius:3em}.mbsc-material.mbsc-range .mbsc-cal-table .mbsc-cal-day-hl .mbsc-cal-day-i .mbsc-cal-day-fg{color:#eee;background:rgba(13,155,141,.8)}.mbsc-ios .mbsc-range-btn-t{width:auto;display:block}.mbsc-ios .mbsc-range-btn-c{width:auto;display:block}.mbsc-ios .mbsc-range-btn{position:relative;margin:0;padding:.416667em .833333em;background:#f7f7f7;border:0;border-bottom:1px solid #acacac;color:#000;text-align:left;font-weight:bold;border-radius:0}.mbsc-ios .mbsc-range-btn-v{position:absolute;top:.416667em;right:.833333em;font-weight:normal}.mbsc-ios .mbsc-range-btn-sel .mbsc-range-btn{background:#e3e3e3}.mbsc-ios.mbsc-range .mbsc-cal-day{padding:.3571em}.mbsc-ios.mbsc-range .mbsc-cal-week-nr{padding-top:.583333em;line-height:2em;vertical-align:top}.mbsc-bootstrap .mbsc-range-btn-t{padding:4px 2px 0 2px}.mbsc-bootstrap .mbsc-range-btn{margin:0 2px}.mbsc-bootstrap.mbsc-range .mbsc-cal-day-hl .mbsc-cal-day-frame{opacity:.3}.mbsc-cal-events{display:none;position:absolute;z-index:4;left:0;right:0;padding:5px}.mbsc-cal-events-t{visibility:hidden}.mbsc-cal-events-i{overflow:hidden}.mbsc-cal-events-sc{position:relative;-webkit-backface-visibility:hidden}.mbsc-cal-events-v{display:block}.mbsc-cal-event-list{margin:0;padding:0}.mbsc-cal-event{margin-top:5px;padding:5px 60px 5px 10px;line-height:20px;list-style:none;position:relative;text-align:left;white-space:nowrap;font-size:1em}.mbsc-cal-event:first-child{margin:0}.mbsc-cal-event-color{position:absolute;top:0;left:0;width:5px;height:100%}.mbsc-cal-event-text{overflow:hidden;text-overflow:ellipsis}.mbsc-cal-event-time{display:inline-block;width:5em}.mbsc-cal-event-dur{position:absolute;top:50%;right:5px;height:20px;margin-top:-10px;line-height:20px}.mbsc-cal-events-arr{position:absolute;bottom:-14px;left:50%;width:0;height:0;margin-left:-7px;border:7px solid #fff;border-color:#fff transparent transparent transparent}.mbsc-cal-events-b .mbsc-cal-events-arr{bottom:auto;top:-14px;border-color:transparent transparent #fff transparent}.mbsc-mobiscroll .mbsc-cal-events{margin:0 0 5px 0;padding:0;color:#f7f7f7;background:#858585}.mbsc-mobiscroll .mbsc-cal-events-b{margin:10px 0 0 0}.mbsc-mobiscroll .mbsc-cal-event{margin:0;padding:10px 60px 10px 15px;font-size:.75em}.mbsc-mobiscroll .mbsc-cal-event-color{background:#4eccc4}.mbsc-mobiscroll .mbsc-cal-event-time,.mbsc-mobiscroll .mbsc-cal-event-dur{color:#4eccc4}.mbsc-mobiscroll .mbsc-cal-events-arr{border-color:#858585 transparent transparent transparent}.mbsc-mobiscroll .mbsc-cal-events-b .mbsc-cal-events-arr{border-color:transparent transparent #858585 transparent}.mbsc-android-holo .mbsc-cal-events{background:#fff;margin:.375em .375em .5em .375em}.mbsc-android-holo .mbsc-cal-events-b{margin:.5em .375em .375em .375em}.mbsc-android-holo .mbsc-cal-event{background:#7e7e7e;font-size:.75em}.mbsc-android-holo .mbsc-cal-event-color{background:#31b6e7}.mbsc-android-holo .mbsc-cal-day-txt{background:#444;color:#fff}.mbsc-wp .mbsc-cal-events{font-size:.75em;background:#fff;margin:0 0 .667em 0}.mbsc-wp .mbsc-cal-events-b{margin:.667em 0 0 0}.mbsc-wp .mbsc-cal-event{color:#fff;background:#4c4c4c;border:1px solid #4c4c4c}.mbsc-wp .mbsc-cal-event-color{background:#fff}.mbsc-material .mbsc-cal-events{margin:1em 1em .6875em 1em;padding:0;color:#eee}.mbsc-material .mbsc-cal-events-b{margin:.375em 1em 1em 1em}.mbsc-material .mbsc-cal-event{margin:0;padding:.625em 5em .625em .625em;font-size:.75em}.mbsc-material .mbsc-cal-event-color{width:100%;background:#009688}.mbsc-material .mbsc-cal-event-time,.mbsc-material .mbsc-cal-event-text{position:relative;z-index:1}.mbsc-material .mbsc-cal-event-time,.mbsc-material .mbsc-cal-event-dur{color:#eee}.mbsc-material .mbsc-cal-events-arr{border-color:#009688 transparent transparent transparent}.mbsc-material .mbsc-cal-events-b .mbsc-cal-events-arr{border-color:transparent transparent #009688 transparent}.mbsc-ios .mbsc-cal-events{margin:.5em .5em .583333em .5em;background:#000;background:rgba(0,0,0,0.8)}.mbsc-ios .mbsc-cal-events-b{margin:.583333em .5em .5em .5em}.mbsc-ios .mbsc-cal-event{margin:0;padding:.5em .5em .5em 1.5em;background:#f7f7f7;border-top:1px solid #eee;color:#000;font-weight:bold}.mbsc-ios .mbsc-cal-event:first-child{border:0}.mbsc-ios .mbsc-cal-event-color{top:50%;left:.5em;margin-top:-.25em;width:.5em;height:.5em;background:#94b8dd;border:1px solid rgba(0,0,0,0.3);border-radius:.5em}.mbsc-ios .mbsc-cal-events-arr{border-color:rgba(0,0,0,0.8) transparent transparent transparent}.mbsc-ios .mbsc-cal-events-b .mbsc-cal-events-arr{border-color:transparent transparent rgba(0,0,0,0.8) transparent}.mbsc-bootstrap .mbsc-cal-events{width:auto;max-width:none;margin:3px 2px 10px 2px}.mbsc-bootstrap .mbsc-cal-events-b{margin:10px 2px 3px 2px}.mbsc-bootstrap .mbsc-cal-events-i{padding-bottom:1px}.mbsc-bootstrap .mbsc-cal-events-v{display:block}.mbsc-bootstrap .mbsc-cal-event-color{background:#fff}.mbsc-ms-c{overflow:hidden;position:relative;font-size:16px;-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-ms-touch-action:pan-y;user-select:none;touch-action:pan-y;text-align:center}.mbsc-ms-item,.mbsc-ms-item-i{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mbsc-ms-sc{position:relative}.mbsc-ms-rtl{direction:rtl}.mbsc-ms{margin:0;padding:0;list-style:none;white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:translate3d(0,0,0);display:inline-block}.mbsc-ms-item{height:100%;position:relative;margin:0;padding:0;display:inline-block;vertical-align:middle;text-align:center;font-size:.75em}.mbsc-ms-item-i{min-width:4em;padding:.25em .5em;height:100%;position:relative;display:block;text-decoration:none;cursor:pointer;overflow:hidden;-webkit-transition-property:color,background,border-color,text-shadow;-webkit-transition-duration:200ms;-moz-transition-property:color,background,border-color,text-shadow;-moz-transition-duration:200ms;transition-property:color,background,border-color,text-shadow;transition-duration:200ms}.mbsc-ms-c .mbsc-ms .mbsc-ms-item-i{font-weight:normal;color:inherit}.mbsc-ms-item-i-t{width:100%;height:100%;display:table}.mbsc-ms-item-i-c{padding:.5em 0;display:table-cell;vertical-align:middle}.mbsc-ms .mbsc-btn-d .mbsc-ms-item-i{opacity:.5;cursor:default}.mbsc-ms-hasw .mbsc-ms-item{white-space:normal}.mbsc-ms-hasw .mbsc-ms-item .mbsc-ms-item-i{min-width:0}.mbsc-ms-icons .mbsc-ms-item-i{min-width:6em}.mbsc-ms-icons .mbsc-ms-item-i-c{padding-top:2.75em;padding-bottom:0}.mbsc-ms-icons.mbsc-ms-txt .mbsc-ms-item-i-c{padding-bottom:.5em}.mbsc-ms-ic:before{position:absolute;top:.25em;left:0;right:0;display:block;text-align:center;font-size:2em;line-height:1.2em}.mbsc-ms-top,.mbsc-ms-bottom{z-index:9999;position:fixed;left:0;right:0}.mbsc-ms-ctx.mbsc-ms-top,.mbsc-ms-ctx.mbsc-ms-bottom{position:absolute}.mbsc-ms-top{top:0}.mbsc-ms-bottom{bottom:0}.mbsc-mobiscroll.mbsc-ms-c{background:#f7f7f7;color:#454545}.mbsc-mobiscroll .mbsc-ms-item{font-size:1em}.mbsc-mobiscroll.mbsc-ms-icons .mbsc-ms-item{font-size:.75em}.mbsc-mobiscroll .mbsc-btn-a .mbsc-ms-item-i{background:rgba(78,204,196,.3)}.mbsc-mobiscroll.mbsc-ms-b .mbsc-ms-item-i{border:2px solid transparent;border-width:2px 0}.mbsc-mobiscroll.mbsc-ms-b.mbsc-ms-top{border-bottom:1px solid #4eccc4}.mbsc-mobiscroll.mbsc-ms-b.mbsc-ms-bottom{border-top:1px solid #4eccc4}.mbsc-mobiscroll.mbsc-ms-b .mbsc-ms-item-sel .mbsc-ms-item-i{border-bottom-color:#4eccc4}.mbsc-mobiscroll.mbsc-ms-b.mbsc-ms-bottom .mbsc-ms-item-sel .mbsc-ms-item-i{border-top-color:#4eccc4;border-bottom-color:transparent}.mbsc-mobiscroll.mbsc-ms-a .mbsc-ms-item-sel .mbsc-ms-item-i{color:#4eccc4}.mbsc-android-holo.mbsc-ms-c{background:#292929;color:#fff}.mbsc-android-holo .mbsc-ms-item-i{text-transform:uppercase;padding:.5em 0}.mbsc-android-holo .mbsc-ms-item-i-c{padding-left:.75em;padding-right:.75em}.mbsc-android-holo .mbsc-ms-ic:before{top:.375em}.mbsc-android-holo .mbsc-btn-a .mbsc-ms-item-i{color:#fff;background:#29799c}.mbsc-android-holo.mbsc-ms-a .mbsc-ms-item-sel{color:#31b6e7}.mbsc-android-holo.mbsc-ms-b.mbsc-ms-top{border-bottom:1px solid #31b6e7}.mbsc-android-holo.mbsc-ms-b.mbsc-ms-bottom{border-top:1px solid #31b6e7}.mbsc-android-holo.mbsc-ms-b .mbsc-ms-item-i{border:1px solid transparent;border-width:3px 0}.mbsc-android-holo.mbsc-ms-b .mbsc-ms-item-i-c{border-left:1px solid #444}.mbsc-android-holo .mbsc-btn-a .mbsc-ms-item-i-c,.mbsc-android-holo.mbsc-ms-ltr .mbsc-ms-item:first-child .mbsc-ms-item-i-c,.mbsc-android-holo.mbsc-ms-rtl .mbsc-ms-item:last-child .mbsc-ms-item-i-c{border-color:transparent}.mbsc-android-holo.mbsc-ms-b .mbsc-ms-item-sel .mbsc-ms-item-i{border-bottom-color:#31b6e7}.mbsc-android-holo.mbsc-ms-b.mbsc-ms-bottom .mbsc-ms-item-sel .mbsc-ms-item-i{border-top-color:#31b6e7;border-bottom-color:transparent}.mbsc-wp.mbsc-ms-c{background:#1f1f1f;color:#fff;font-family:Segoe UI,arial,verdana,sans-serif}.mbsc-wp .mbsc-ms-item{font-size:2em;color:#4c4c4c;text-transform:lowercase;white-space:nowrap}.mbsc-wp .mbsc-ms-item-i{min-width:0;padding:0 .25em;-webkit-transition:-webkit-transform 200ms;-moz-transition:-moz-transform 200ms;transition:transform 200ms}.mbsc-wp.mbsc-ms-icons .mbsc-ms-item-i-c{padding-top:3.5em}.mbsc-wp .mbsc-ms-ic:before{top:.6em;left:50%;right:auto;margin-left:-1.1em;font-size:12px;width:2.2em;height:2.2em;line-height:2.2em;border:2px solid #fff;border-radius:1000px;-webkit-transform:translate3d(-2px,0,0);-moz-transform:translate3d(-2px,0,0);-ms-transform:translate3d(-2px,0,0);transform:translate3d(-2px,0,0)}.mbsc-wp .mbsc-ms-item.mbsc-btn-a .mbsc-ms-item-i{-webkit-transform:translate3d(0,-.2em,0);-moz-transform:translate3d(0,-.2em,0);transform:translate3d(0,-.2em,0)}.mbsc-wp .mbsc-ms-item-sel .mbsc-ms-ic:before,.mbsc-wp .mbsc-btn-a .mbsc-ms-ic:before{background:#fff;color:#1f1f1f}.mbsc-wp.mbsc-ms-icons .mbsc-ms-item{font-size:.625em;color:#fff}.mbsc-wp.mbsc-ms-icons .mbsc-ms-item-i{min-width:6em;padding:.5em}.mbsc-wp .mbsc-ms-item-sel{color:#fff}.mbsc-material.mbsc-ms-c{background:#eee;color:#5b5b5b}.mbsc-material .mbsc-ms-item{overflow:hidden;font-size:1em}.mbsc-material.mbsc-ms-icons .mbsc-ms-item{font-size:.75em}.mbsc-material .mbsc-btn-a .mbsc-ms-item-i{background:rgba(0,0,0,.1)}.mbsc-material.mbsc-ms-b .mbsc-ms-item-i{border:2px solid transparent;border-width:2px 0}.mbsc-material.mbsc-ms-b.mbsc-ms-top{border-bottom:1px solid #009688}.mbsc-material.mbsc-ms-b.mbsc-ms-bottom{border-top:1px solid #009688}.mbsc-material.mbsc-ms-b .mbsc-ms-item-sel .mbsc-ms-item-i{border-bottom-color:#009688;color:#009688}.mbsc-material.mbsc-ms-b.mbsc-ms-bottom .mbsc-ms-item-sel .mbsc-ms-item-i{border-top-color:#009688;border-bottom-color:transparent}.mbsc-material.mbsc-ms-a .mbsc-ms-item-sel .mbsc-ms-item-i{color:#009688}.mbsc-ios.mbsc-ms-c{background:#f7f7f7;border-top:1px solid #ccc;border-bottom:1px solid #ccc;color:#888}.mbsc-ios.mbsc-ms-top{border-top:0}.mbsc-ios.mbsc-ms-bottom{border-bottom:0}.mbsc-ios .mbsc-ms-item{font-size:1em}.mbsc-ios.mbsc-ms-icons .mbsc-ms-item{font-size:.75em}.mbsc-ios.mbsc-ms-icons.mbsc-ms-txt .mbsc-ms-item-i-c{padding-bottom:.25em}.mbsc-ios.mbsc-ms-nosel .mbsc-ms-item,.mbsc-ios .mbsc-ms-item-sel{color:#007aff}.mbsc-ios.mbsc-ms-nosel .mbsc-btn-a .mbsc-ms-item-i{opacity:.5}.mbsc-bootstrap.mbsc-ms-c{display:block;padding:0;border-radius:0;border-width:1px 0;max-width:none;-webkit-box-shadow:none;box-shadow:none}.mbsc-bootstrap.mbsc-ms-inline{z-index:0}.mbsc-bootstrap.mbsc-ms-top{bottom:auto;border-top-width:0}.mbsc-bootstrap.mbsc-ms-bottom{top:auto;border-bottom-width:0}.mbsc-bootstrap.mbsc-ms-c .mbsc-ms-item{float:none;margin:0;border-width:0 0 0 1px;border-radius:0}.mbsc-bootstrap.mbsc-ms-ltr .mbsc-ms-item:first-child,.mbsc-bootstrap.mbsc-ms-rtl .mbsc-ms-item:last-child{border:0}.mbsc-rating .mbsc-ltr .mbsc-sc-itm{text-align:left}.mbsc-rating .mbsc-rtl .mbsc-sc-itm{text-align:right}.mbsc-rating-txt{padding-left:5px;margin-top:-0.1em}.mbsc-rating-icon{display:inline-block;width:1.8em;text-align:center;font-size:22px;vertical-align:top;color:#e9bb2f}.mbsc-rating-icon-unf{color:#eee}.mbsc-rating-circle{display:inline-block;background:#959595;text-align:center;padding:0;margin:0 7px;width:26px;height:26px;font-family:Verdana;font-size:16px;line-height:26px;vertical-align:top;border-radius:20px;position:relative;top:50%;margin-top:-13px}.mbsc-rating-circle-unf{background:#5d5d5d}.mbsc-mobiscroll .mbsc-rating-icon{color:#4eccc4}.mbsc-mobiscroll .mbsc-rating-icon-unf{color:rgba(78,204,196,.3)}.mbsc-mobiscroll .mbsc-rating-circle{background:#4eccc4;color:#f7f7f7}.mbsc-mobiscroll .mbsc-rating-circle-unf{background:0;width:24px;height:24px;border:1px solid #4eccc4}.mbsc-android-holo .mbsc-rating-icon{color:#31b6e7}.mbsc-android-holo .mbsc-rating-icon-same{opacity:.3}.mbsc-android-holo .mbsc-rating-circle{color:#000;background:#999}.mbsc-android-holo .mbsc-rating-circle-unf{background:#636363}.mbsc-android-holo-light .mbsc-rating-circle{color:#fff}.mbsc-ios .mbsc-rating-icon-unf{color:#d5d5d5}.mbsc-ios .mbsc-rating-circle{background:#0c80fe;width:2em;height:2em;font-size:.545454em;line-height:2em;color:#fff}.mbsc-ios .mbsc-rating-circle-unf{background:0;width:1.833333em;height:1.833333em;border:1px solid #0c80fe}.mbsc-wp .mbsc-rating-icon{color:#fff;vertical-align:middle}.mbsc-wp .mbsc-rating-txt{display:inline-block;vertical-align:middle}.mbsc-wp .mbsc-rating-icon-same{opacity:.3}.mbsc-wp .mbsc-rating-circle{width:24px;height:24px;margin-top:-12px;background:#fff;font-size:11px;line-height:24px;color:#000}.mbsc-wp .mbsc-rating-circle-unf{background:#646464}.mbsc-wp-light .mbsc-rating-icon{color:#000}.mbsc-wp-light .mbsc-rating-circle{background:#000;color:#fff}.mbsc-wp-light .mbsc-rating-circle-unf{background:#646464}.mbsc-material .mbsc-rating-icon{width:1.4285em;color:#009688;font-size:1.2729em}.mbsc-material .mbsc-rating-icon-unf{opacity:.5}.mbsc-material .mbsc-rating-circle{background:#009688;color:#eee}.mbsc-material .mbsc-rating-circle-unf{background:0;width:22px;height:22px;border:2px solid #009688}.mbsc-ltr .mbsc-img-w{text-align:left}.mbsc-ltr .mbsc-img-txt{margin:0 0 0 .5em}.mbsc-rtl .mbsc-img-w{text-align:right}.mbsc-rtl .mbsc-img-txt{margin:0 .5em 0 0}.mbsc-img-w .mbsc-ic{display:inline-block;margin:0 .5em}.mbsc-img-txt{display:inline-block}.mbsc-img-c{min-width:50px;height:28px;margin-top:-2px;display:inline-block;text-align:center;vertical-align:middle;line-height:normal}.mbsc-img{max-height:28px}.mbsc-ts .mbsc-ts-lbl{display:none}.mbsc-android-holo.mbsc-ts .mbsc-fr-hdr{font-size:16px}.mbsc-ios.mbsc-ts .mbsc-sc-lbl-v .mbsc-ts-lbl{display:inline;visibility:hidden;padding-left:.5em;font-size:.909090em;text-transform:lowercase}.mbsc-ios.mbsc-ts .mbsc-sc-whl-gr{padding:.833333em 0}.mbsc-ios.mbsc-ts .mbsc-sc-lbl{padding:0 .55em;margin-top:-.75em;left:auto;top:50%;right:0;width:auto;z-index:3;color:#007aff;font-size:1.666667em;line-height:1.5em;text-transform:lowercase}.mbsc-wp.mbsc-ts .mbsc-sc-whl-gr{padding:0}.mbsc-wp.mbsc-ts .mbsc-sc-lbl{z-index:3;top:50%;right:auto;width:auto;height:auto;margin:1.545454em 0 0 .818181em;color:#fff;line-height:1.636363em;font-size:.6875em}.mbsc-wp.mbsc-ts .mbsc-sc-whl-anim .mbsc-sc-lbl{display:none}.mbsc-material .mbsc-ripple,.mbsc-lv-material .mbsc-ripple{position:absolute;top:0;left:0;opacity:0;border-radius:1000em;background:#000;pointer-events:none;-webkit-transform:scale(0);-moz-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .8s cubic-bezier(0.25,0.8,0.25,1),opacity .8s cubic-bezier(0.25,0.8,0.25,1);-moz-transition:-moz-transform .8s cubic-bezier(0.25,0.8,0.25,1),opacity .8s cubic-bezier(0.25,0.8,0.25,1);transition:transform .8s cubic-bezier(0.25,0.8,0.25,1),opacity .8s cubic-bezier(0.25,0.8,0.25,1)}.mbsc-material .mbsc-ripple-scaled,.mbsc-lv-material .mbsc-ripple-scaled{-webkit-transform:scale(1);-moz-transform:scale(1);transform:scale(1)}.mbsc-material .mbsc-ripple-visible,.mbsc-lv-material .mbsc-ripple-visible{opacity:.1}.mbsc-anim-trans .mbsc-fr-persp{overflow:hidden;-webkit-perspective:1000px;-moz-perspective:1000px;perspective:1000px}.mbsc-anim-trans .mbsc-fr-popup,.mbsc-anim-trans .mbsc-fr-overlay{-webkit-animation-fill-mode:forwards;-webkit-animation-duration:200ms;-moz-animation-fill-mode:forwards;-moz-animation-duration:200ms;animation-fill-mode:forwards;animation-duration:200ms}.mbsc-anim-trans .mbsc-fr-overlay{-webkit-backface-visibility:hidden}.mbsc-anim-in .mbsc-fr-popup{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:225ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:225ms;animation-timing-function:ease-out;animation-duration:225ms}.mbsc-anim-out .mbsc-fr-popup{-webkit-animation-timing-function:ease-in;-webkit-animation-duration:195ms;-moz-animation-timing-function:ease-in;-moz-animation-duration:195ms;animation-timing-function:ease-in;animation-duration:195ms}.mbsc-anim-in .mbsc-fr-overlay{-webkit-animation-name:mbsc-anim-f-in;-moz-animation-name:mbsc-anim-f-in;animation-name:mbsc-anim-f-in}.mbsc-anim-out .mbsc-fr-overlay{-webkit-animation-name:mbsc-anim-f-out;-moz-animation-name:mbsc-anim-f-out;animation-name:mbsc-anim-f-out}.mbsc-anim-flip,.mbsc-anim-swing,.mbsc-anim-slidehorizontal,.mbsc-anim-slidevertical,.mbsc-anim-slidedown,.mbsc-anim-slideup,.mbsc-anim-fade{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-moz-backface-visibility:hidden;-moz-transform:translateX(0);backface-visibility:hidden;transform:translateX(0)}.mbsc-anim-swing,.mbsc-anim-slidehorizontal,.mbsc-anim-slidevertical,.mbsc-anim-slidedown,.mbsc-anim-slideup,.mbsc-anim-fade{-webkit-transform-origin:0 0;-moz-transform-origin:0 0;transform-origin:0 0}.mbsc-anim-flip,.mbsc-anim-pop{-webkit-transform-origin:50% 50%;-moz-transform-origin:50% 50%;transform-origin:50% 50%}.mbsc-anim-in .mbsc-anim-pop{opacity:1;-webkit-animation-name:mbsc-anim-p-in;-webkit-transform:scale(1);-moz-animation-name:mbsc-anim-p-in;-moz-transform:scale(1);transform:scale(1);animation-name:mbsc-anim-p-in}.mbsc-anim-out .mbsc-anim-pop{opacity:0;-webkit-animation-name:mbsc-anim-p-out;-moz-animation-name:mbsc-anim-p-out;animation-name:mbsc-anim-p-out}.mbsc-anim-in .mbsc-anim-flip{opacity:1;-webkit-animation-name:mbsc-anim-fl-in;-webkit-transform:scale(1);-moz-animation-name:mbsc-anim-fl-in;-moz-transform:scale(1);animation-name:mbsc-anim-fl-in;transform:scale(1)}.mbsc-anim-out .mbsc-anim-flip{opacity:0;animation-name:mbsc-anim-fl-out;-webkit-animation-name:mbsc-anim-fl-out;-moz-animation-name:mbsc-anim-fl-out}.mbsc-anim-in .mbsc-anim-swing{opacity:1;-webkit-animation-name:mbsc-anim-sw-in;-webkit-transform:scale(1);-moz-animation-name:mbsc-anim-sw-in;-moz-transform:scale(1);transform:scale(1);animation-name:mbsc-anim-sw-in}.mbsc-anim-out .mbsc-anim-swing{opacity:0;-webkit-animation-name:mbsc-anim-sw-out;-moz-animation-name:mbsc-anim-sw-out;animation-name:mbsc-anim-sw-out}.mbsc-anim-in .mbsc-anim-slidehorizontal{opacity:1;-webkit-animation-name:mbsc-anim-sh-in;-webkit-transform:scale(1);-moz-animation-name:mbsc-anim-sh-in;-moz-transform:scale(1);transform:scale(1);animation-name:mbsc-anim-sh-in}.mbsc-anim-out .mbsc-anim-slidehorizontal{opacity:0;-webkit-animation-name:mbsc-anim-sh-out;-moz-animation-name:mbsc-anim-sh-out;animation-name:mbsc-anim-sh-out}.mbsc-anim-in .mbsc-anim-slidevertical{opacity:1;-webkit-animation-name:mbsc-anim-sv-in;-webkit-transform:scale(1);-moz-animation-name:mbsc-anim-sv-in;-moz-transform:scale(1);animation-name:mbsc-anim-sv-in;transform:scale(1)}.mbsc-anim-out .mbsc-anim-slidevertical{opacity:0;-webkit-animation-name:mbsc-anim-sv-out;-moz-animation-name:mbsc-anim-sv-out;animation-name:mbsc-anim-sv-out}.mbsc-anim-in .mbsc-anim-slidedown{-webkit-animation-name:mbsc-anim-sd-in;-webkit-transform:scale(1);-moz-animation-name:mbsc-anim-sd-in;-moz-transform:scale(1);animation-name:mbsc-anim-sd-in;transform:scale(1)}.mbsc-anim-out .mbsc-anim-slidedown{animation-name:mbsc-anim-sd-out;-webkit-animation-name:mbsc-anim-sd-out;-webkit-transform:translateY(-100%);-moz-animation-name:mbsc-anim-sd-out;-moz-transform:translateY(-100%)}.mbsc-anim-in .mbsc-anim-slideup{-webkit-animation-name:mbsc-anim-su-in;-webkit-transform:scale(1);-moz-animation-name:mbsc-anim-su-in;-moz-transform:scale(1);transform:scale(1);animation-name:mbsc-anim-su-in}.mbsc-anim-out .mbsc-anim-slideup{animation-name:mbsc-anim-su-out;-webkit-animation-name:mbsc-anim-su-out;-webkit-transform:translateY(100%);-moz-animation-name:mbsc-anim-su-out;-moz-transform:translateY(100%)}.mbsc-anim-in .mbsc-anim-fade{opacity:1;-webkit-animation-name:mbsc-anim-f-in;-moz-animation-name:mbsc-anim-f-in;animation-name:mbsc-anim-f-in}.mbsc-anim-out .mbsc-anim-fade{opacity:0;-webkit-animation-name:mbsc-anim-f-out;-moz-animation-name:mbsc-anim-f-out;animation-name:mbsc-anim-f-out}@keyframes mbsc-anim-f-in{from{opacity:0}to{opacity:1}}@-webkit-keyframes mbsc-anim-f-in{from{opacity:0}to{opacity:1}}@-moz-keyframes mbsc-anim-f-in{from{opacity:0}to{opacity:1}}@keyframes mbsc-anim-f-out{from{visibility:visible;opacity:1}to{opacity:0}}@-webkit-keyframes mbsc-anim-f-out{from{visibility:visible;opacity:1}to{opacity:0}}@-moz-keyframes mbsc-anim-f-out{from{visibility:visible;opacity:1}to{opacity:0}}@keyframes mbsc-anim-p-in{from{opacity:0;transform:scale(0.8)}to{opacity:1;transform:scale(1)}}@-webkit-keyframes mbsc-anim-p-in{from{opacity:0;-webkit-transform:scale(0.8)}to{opacity:1;-webkit-transform:scale(1)}}@-moz-keyframes mbsc-anim-p-in{from{opacity:0;-moz-transform:scale(0.8)}to{opacity:1;-moz-transform:scale(1)}}@keyframes mbsc-anim-p-out{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(0.8)}}@-webkit-keyframes mbsc-anim-p-out{from{opacity:1;-webkit-transform:scale(1)}to{opacity:0;-webkit-transform:scale(0.8)}}@-moz-keyframes mbsc-anim-p-out{from{opacity:1;-moz-transform:scale(1)}to{opacity:0;-moz-transform:scale(0.8)}}@keyframes mbsc-anim-fl-in{from{opacity:0;transform:rotateY(90deg)}to{opacity:1;transform:rotateY(0)}}@-webkit-keyframes mbsc-anim-fl-in{from{opacity:0;-webkit-transform:rotateY(90deg)}to{opacity:1;-webkit-transform:rotateY(0)}}@-moz-keyframes mbsc-anim-fl-in{from{opacity:0;-moz-transform:rotateY(90deg)}to{opacity:1;-moz-transform:rotateY(0)}}@keyframes mbsc-anim-fl-out{from{opacity:1;transform:rotateY(0deg)}to{opacity:0;transform:rotateY(-90deg)}}@-webkit-keyframes mbsc-anim-fl-out{from{opacity:1;-webkit-transform:rotateY(0deg)}to{opacity:0;-webkit-transform:rotateY(-90deg)}}@-moz-keyframes mbsc-anim-fl-out{from{opacity:1;-moz-transform:rotateY(0deg)}to{opacity:0;-moz-transform:rotateY(-90deg)}}@keyframes mbsc-anim-sw-in{from{opacity:0;transform:rotateY(-90deg)}to{opacity:1;transform:rotateY(0deg)}}@-webkit-keyframes mbsc-anim-sw-in{from{opacity:0;-webkit-transform:rotateY(-90deg)}to{opacity:1;-webkit-transform:rotateY(0deg)}}@-moz-keyframes mbsc-anim-sw-in{from{opacity:0;-moz-transform:rotateY(-90deg)}to{opacity:1;-moz-transform:rotateY(0deg)}}@keyframes mbsc-anim-sw-out{from{opacity:1;transform:rotateY(0deg)}to{opacity:0;transform:rotateY(-90deg)}}@-webkit-keyframes mbsc-anim-sw-out{from{opacity:1;-webkit-transform:rotateY(0deg)}to{opacity:0;-webkit-transform:rotateY(-90deg)}}@-moz-keyframes mbsc-anim-sw-out{from{opacity:1;-moz-transform:rotateY(0deg)}to{opacity:0;-moz-transform:rotateY(-90deg)}}@keyframes mbsc-anim-sh-in{from{opacity:0;transform:translateX(-100%)}to{opacity:1;transform:translateX(0)}}@-webkit-keyframes mbsc-anim-sh-in{from{opacity:0;-webkit-transform:translateX(-100%)}to{opacity:1;-webkit-transform:translateX(0)}}@-moz-keyframes mbsc-anim-sh-in{from{opacity:0;-moz-transform:translateX(-100%)}to{opacity:1;-moz-transform:translateX(0)}}@keyframes mbsc-anim-sh-out{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(100%)}}@-webkit-keyframes mbsc-anim-sh-out{from{opacity:1;-webkit-transform:translateX(0)}to{opacity:0;-webkit-transform:translateX(100%)}}@-moz-keyframes mbsc-anim-sh-out{from{opacity:1;-moz-transform:translateX(0)}to{opacity:0;-moz-transform:translateX(100%)}}@keyframes mbsc-anim-sv-in{from{opacity:0;transform:translateY(-100%)}to{opacity:1;transform:translateY(0)}}@-webkit-keyframes mbsc-anim-sv-in{from{opacity:0;-webkit-transform:translateY(-100%)}to{opacity:1;-webkit-transform:translateY(0)}}@-moz-keyframes mbsc-anim-sv-in{from{opacity:0;-moz-transform:translateY(-100%)}to{opacity:1;-moz-transform:translateY(0)}}@keyframes mbsc-anim-sv-out{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(100%)}}@-webkit-keyframes mbsc-anim-sv-out{from{opacity:1;-webkit-transform:translateY(0)}to{opacity:0;-webkit-transform:translateY(100%)}}@-moz-keyframes mbsc-anim-sv-out{from{opacity:1;-moz-transform:translateY(0)}to{opacity:0;-moz-transform:translateY(100%)}}@keyframes mbsc-anim-sd-in{from{transform:translateY(-100%)}to{transform:translateY(0)}}@-webkit-keyframes mbsc-anim-sd-in{from{opacity:1;-webkit-transform:translateY(-100%)}to{opacity:1;-webkit-transform:translateY(0)}}@-moz-keyframes mbsc-anim-sd-in{from{-moz-transform:translateY(-100%)}to{-moz-transform:translateY(0)}}@keyframes mbsc-anim-sd-out{from{transform:translateY(0)}to{transform:translateY(-100%)}}@-webkit-keyframes mbsc-anim-sd-out{from{opacity:1;-webkit-transform:translateY(0)}to{opacity:1;-webkit-transform:translateY(-100%)}}@-moz-keyframes mbsc-anim-sd-out{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(-100%)}}@keyframes mbsc-anim-su-in{from{transform:translateY(100%)}to{transform:translateY(0)}}@-webkit-keyframes mbsc-anim-su-in{from{opacity:1;-webkit-transform:translateY(100%)}to{opacity:1;-webkit-transform:translateY(0)}}@-moz-keyframes mbsc-anim-su-in{from{-moz-transform:translateY(100%)}to{-moz-transform:translateY(0)}}@keyframes mbsc-anim-su-out{from{transform:translateY(0)}to{transform:translateY(100%)}}@-webkit-keyframes mbsc-anim-su-out{from{opacity:1;-webkit-transform:translateY(0)}to{opacity:1;-webkit-transform:translateY(100%)}}@-moz-keyframes mbsc-anim-su-out{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(100%)}}.mbsc-material-dark .mbsc-ripple,.mbsc-lv-material-dark .mbsc-ripple{background:#fff}.mbsc-material-dark .mbsc-fr-w{background:#303030;color:#c2c2c2}.mbsc-material-dark .mbsc-fr-hdr{color:#81ccc4}.mbsc-material-dark .mbsc-fr-btn{color:#81ccc4}.mbsc-material-dark .mbsc-fr-btn-a{background:rgba(255,255,255,0.1)}.mbsc-material-dark .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #303030 transparent}.mbsc-material-dark .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#303030 transparent transparent transparent}.mbsc-material-dark .mbsc-sc-lbl{color:#81ccc4}.mbsc-material-dark .mbsc-sc-itm.mbsc-btn-a{background:rgba(255,255,255,0.1)}.mbsc-material-dark .mbsc-sc-whl-l{border-color:#81ccc4}.mbsc-material-dark .mbsc-sc-btn{color:#81ccc4;background:#303030}.mbsc-material-dark .mbsc-sc-btn-a{background:rgba(255,255,255,0.1)}.mbsc-material-dark .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{color:#81ccc4}.mbsc-material-dark .mbsc-rating-icon{color:#81ccc4}.mbsc-material-dark .mbsc-rating-circle{background:#81ccc4;color:#303030}.mbsc-material-dark .mbsc-rating-circle-unf{background:0;border-color:#81ccc4}.mbsc-material-dark .mbsc-np-del{color:#81ccc4}.mbsc-material-dark .mbsc-cal-sc-c{background:#303030}.mbsc-material-dark .mbsc-cal-btn-txt{color:#81ccc4}.mbsc-material-dark .mbsc-cal-day-inv .mbsc-cal-day-fg{color:rgba(194,194,194,0.3)}.mbsc-material-dark .mbsc-cal .mbsc-cal-day-sel .mbsc-cal-day-fg{background:rgba(129,204,196,0.3)}.mbsc-material-dark .mbsc-cal-hl-now .mbsc-cal-today{color:#81ccc4}.mbsc-material-dark .mbsc-cal-sc .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i{background:rgba(129,204,196,0.3)}.mbsc-material-dark .mbsc-cal-week-nrs-c{color:#c2c2c2;background:#303030}.mbsc-material-dark .mbsc-cal-tab .mbsc-cal-tab-i{color:#c2c2c2}.mbsc-material-dark .mbsc-cal-tabs .mbsc-cal-tab-sel .mbsc-cal-tab-i{color:#81ccc4}.mbsc-material-dark .mbsc-cal-day-m-c{background:#81ccc4}.mbsc-material-dark .mbsc-cal-day-txt{color:#303030}.mbsc-material-dark .mbsc-cal-events{color:#303030}.mbsc-material-dark .mbsc-cal-event-color{background:#81ccc4}.mbsc-material-dark .mbsc-cal-event-time,.mbsc-material-dark .mbsc-cal-event-dur{color:#303030}.mbsc-material-dark .mbsc-cal-events-arr{border-color:#81ccc4 transparent transparent transparent}.mbsc-material-dark .mbsc-cal-events-b .mbsc-cal-events-arr{border-color:transparent transparent #81ccc4 transparent}.mbsc-material-dark .mbsc-range-btn-t{border-color:#c7c7c7}.mbsc-material-dark .mbsc-range-btn{color:#c2c2c2}.mbsc-material-dark .mbsc-range-btn-sel .mbsc-range-btn{border-color:#81ccc4;color:#81ccc4}.mbsc-material-dark.mbsc-range .mbsc-cal-day-sel .mbsc-cal-day-frame{color:#c2c2c2;background:rgba(129,204,196,0.1)}.mbsc-material-dark.mbsc-range .mbsc-cal-table .mbsc-cal-day-hl .mbsc-cal-day-i .mbsc-cal-day-fg{color:#303030;background:rgba(129,204,196,0.8)}.mbsc-material-dark.mbsc-ms-c{background:#303030;color:#c2c2c2}.mbsc-material-dark .mbsc-btn-a .mbsc-ms-item-i{background:rgba(255,255,255,0.1)}.mbsc-material-dark.mbsc-ms-b.mbsc-ms-top{border-bottom:1px solid #81ccc4}.mbsc-material-dark.mbsc-ms-b.mbsc-ms-bottom{border-top:1px solid #81ccc4}.mbsc-material-dark.mbsc-ms-b .mbsc-ms-item-sel .mbsc-ms-item-i{border-bottom-color:#81ccc4;color:#81ccc4}.mbsc-material-dark.mbsc-ms-b.mbsc-ms-bottom .mbsc-ms-item-sel .mbsc-ms-item-i{border-top-color:#81ccc4;border-bottom-color:transparent}.mbsc-material-dark.mbsc-ms-a .mbsc-ms-item-sel .mbsc-ms-item-i{color:#81ccc4}.mbsc-lv-material-dark .mbsc-lv-item{background:#303030;color:#c2c2c2}.mbsc-lv-material-dark .mbsc-lv-item-hl:after,.mbsc-lv-material-dark .mbsc-lv-item-active::after,.mbsc-lv-material-dark .mbsc-lv-item-dragging:after{background:rgba(255,255,255,0.1)}.mbsc-lv-material-dark.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#383838}.mbsc-lv-material-dark .mbsc-lv-gr-title{background:#303030;color:#81ccc4}.mbsc-lv-material-dark .mbsc-lv-arr{color:#81ccc4}.mbsc-lv-material-dark .mbsc-lv-handle-bar{background:#81ccc4}.mbsc-lv-material-dark p.mbsc-lv-txt{color:#dbdbdb}.mbsc-material-dark .mbsc-progress-track{background:#5c5c5c}.mbsc-material-dark .mbsc-progress-bar{background:#81ccc4}.mbsc-material-dark .mbsc-slider-step{background:#fff}.mbsc-material-dark .mbsc-slider-handle{background:#81ccc4;border-color:#81ccc4}.mbsc-material-dark .mbsc-slider-start .mbsc-slider-handle{border-color:#5c5c5c;background:#303030}.mbsc-material-dark .mbsc-slider-handle:before{background:rgba(255,255,255,0.1)}.mbsc-material-dark .mbsc-slider-tooltip{color:#303030;background:#81ccc4}.mbsc-material-dark .mbsc-slider-tooltip:before{border-top-color:#81ccc4}.mbsc-material-dark.mbsc-slider-has-tooltip .mbsc-slider-start .mbsc-slider-tooltip{background:#5c5c5c}.mbsc-material-dark.mbsc-slider-has-tooltip .mbsc-slider-start .mbsc-slider-tooltip:before{border-top-color:#5c5c5c}.mbsc-material-dark.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-bar{background:#5c5c5c}.mbsc-material-dark.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-slider-handle{background:#5c5c5c;border-color:#5c5c5c;-webkit-box-shadow:0 0 0 .3125em #303030;box-shadow:0 0 0 .3125em #303030}.mbsc-material-dark.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-slider-start .mbsc-slider-handle{border-color:#5c5c5c;background:#303030}.mbsc-material-dark.mbsc-form{background-color:#303030;color:#d4d4d4}.mbsc-material-dark.mbsc-form *::-moz-selection{background:#81ccc4}.mbsc-material-dark.mbsc-form *::selection{background:#81ccc4}.mbsc-material-dark .mbsc-desc{color:#d4d4d4}.mbsc-material-dark .mbsc-divider{color:#81ccc4}.mbsc-material-dark a{color:#81ccc4}.mbsc-material-dark .mbsc-input textarea,.mbsc-material-dark .mbsc-input input{border-bottom:1px solid #d4d4d4;color:#858585}.mbsc-material-dark .mbsc-input input::-webkit-input-placeholder{color:#a8a8a8}.mbsc-material-dark .mbsc-input input::-moz-placeholder{color:#a8a8a8}.mbsc-material-dark .mbsc-input input:-ms-input-placeholder{color:#a8a8a8}.mbsc-material-dark .mbsc-input select:focus ~ input,.mbsc-material-dark .mbsc-input textarea:focus,.mbsc-material-dark .mbsc-input input:focus{border-bottom:2px solid #81ccc4}.mbsc-material-dark .mbsc-checkbox-box:before,.mbsc-material-dark .mbsc-radio-box:before,.mbsc-material-dark .mbsc-switch-handle:before{background:rgba(255,255,255,0.1)}.mbsc-material-dark .mbsc-select .mbsc-input-wrap:after{border:5px solid #d4d4d4;border-color:#d4d4d4 transparent transparent transparent}.mbsc-material-dark .mbsc-checkbox-box,.mbsc-material-dark .mbsc-radio-box{border-color:#d4d4d4}.mbsc-material-dark .mbsc-checkbox-box:after{border-color:#303030}.mbsc-material-dark .mbsc-checkbox input:checked+.mbsc-checkbox-box{background:#81ccc4;border-color:#81ccc4}.mbsc-material-dark .mbsc-radio-box:after{background:#81ccc4}.mbsc-material-dark .mbsc-radio input:checked+.mbsc-radio-box{border-color:#81ccc4}.mbsc-material-dark .mbsc-btn{background:#5c5c5c;color:#fff}.mbsc-material-dark .mbsc-btn.mbsc-active{background:#737373}.mbsc-material-dark .mbsc-btn-flat{background:transparent}.mbsc-material-dark .mbsc-btn:disabled{color:#bdbdbd}.mbsc-material-dark .mbsc-btn-flat.mbsc-active{background-color:#5c5c5c}.mbsc-material-dark .mbsc-switch-track{background:#5c5c5c}.mbsc-material-dark .mbsc-switch .mbsc-switch-handle{background:#b8b8b8}.mbsc-material-dark .mbsc-switch input:checked+.mbsc-switch-track{background-color:rgba(129,204,196,0.3)}.mbsc-material-dark .mbsc-switch input:checked+.mbsc-switch-track .mbsc-switch-handle{background:#81ccc4}.mbsc-material-dark .mbsc-switch input:disabled+.mbsc-switch-track{background:#5c5c5c}.mbsc-material-dark .mbsc-switch input:disabled+.mbsc-switch-track .mbsc-switch-handle{background:#595959}.mbsc-material-dark .mbsc-segmented-content{border-color:#81ccc4;color:#fff}.mbsc-material-dark .mbsc-segmented input:checked+.mbsc-segmented-content,.mbsc-material-dark .mbsc-segmented input:checked ~ .mbsc-segmented-content{background:#81ccc4;color:#303030}.mbsc-material-dark .mbsc-segmented input.mbsc-active+.mbsc-segmented-content,.mbsc-material-dark .mbsc-segmented .mbsc-active .mbsc-segmented-content{background:#737373;color:#fff}.mbsc-material-dark .mbsc-segmented input:disabled+.mbsc-segmented-content{color:#bdbdbd;border-color:#5c5c5c}.mbsc-material-dark .mbsc-segmented input:disabled:checked+.mbsc-segmented-content{background:#5c5c5c;color:#bdbdbd;border-color:#5c5c5c}.mbsc-material-dark .mbsc-stepper .mbsc-segmented-content{border-color:#5c5c5c}.mbsc-material-dark .mbsc-stepper-control .mbsc-segmented-content{background:#5c5c5c}.mbsc-material-dark .mbsc-stepper input{color:#fff}.mbsc-material-dark .mbsc-segmented input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-material-dark .mbsc-segmented .mbsc-step-disabled .mbsc-segmented-content{background:#5c5c5c;color:#bdbdbd}.mbsc-material-dark .mbsc-segmented input:disabled ~ .mbsc-segmented-item .mbsc-stepper-val{border-color:#5c5c5c;background:#303030}.mbsc-material-dark .mbsc-stepper input:disabled{color:#5c5c5c;-webkit-text-fill-color:#5c5c5c}.mbsc-android-holo-light .mbsc-fr-w,.mbsc-android-holo-light .mbsc-cal-sc-c{background:#f5f5f5}.mbsc-android-holo-light .mbsc-fr-w,.mbsc-android-holo-light .mbsc-fr-btn,.mbsc-android-holo-light .mbsc-sc-itm,.mbsc-android-holo-light .mbsc-range-btn,.mbsc-android-holo-light .mbsc-cal-tabs .mbsc-cal-tab-i{color:#000}.mbsc-android-holo-light .mbsc-fr-hdr{color:#31b7e8;border-color:#31b7e8}.mbsc-android-holo-light .mbsc-sc-itm.mbsc-btn-a{background:rgba(49,183,232,0.5)}.mbsc-android-holo-light .mbsc-sc-btn{color:#7d7d7d;background:#f5f5f5}.mbsc-android-holo-light .mbsc-sc-btn-a{color:#319abd;background:#f5f5f5}.mbsc-android-holo-light .mbsc-sc-whl-l{border-color:#31b7e8}.mbsc-android-holo-light .mbsc-fr-btn-a{background:#28799c;color:black}.mbsc-android-holo-light .mbsc-sc-whl-multi .mbsc-sc-itm:after{border-color:#d9d4d4}.mbsc-android-holo-light .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{color:#31b7e8;text-shadow:0 0 5px #28799c}.mbsc-android-holo-light .mbsc-sc-whl-o{background:-webkit-gradient(linear,left bottom,left top,from(#f5f5f5),color-stop(0.52,rgba(245,245,245,0)),color-stop(0.48,rgba(245,245,245,0)),to(#f5f5f5));background:-webkit-linear-gradient(#f5f5f5,rgba(245,245,245,0) 52%,rgba(245,245,245,0) 48%,#f5f5f5);background:linear-gradient(#f5f5f5,rgba(245,245,245,0) 52%,rgba(245,245,245,0) 48%,#f5f5f5)}.mbsc-android-holo-light .mbsc-fr-btn-cont,.mbsc-android-holo-light .mbsc-fr-btn-w .mbsc-fr-btn{border-color:#d9d4d4}.mbsc-android-holo-light .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #f5f5f5 transparent}.mbsc-android-holo-light .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#f5f5f5 transparent transparent transparent}.mbsc-android-holo-light .mbsc-cal-day-m-c{background:#31b7e8}.mbsc-android-holo-light .mbsc-cal .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i,.mbsc-android-holo-light .mbsc-cal .mbsc-cal-day-sel .mbsc-cal-day-i{background:#31b7e8;background:rgba(49,183,232,0.5)}.mbsc-android-holo-light .mbsc-cal-hl-now .mbsc-cal-today{color:#31b7e8}.mbsc-android-holo-light .mbsc-fr-btn-a .mbsc-cal-btn-txt{color:#319abd}.mbsc-android-holo-light .mbsc-cal-tabs .mbsc-range-btn-sel,.mbsc-android-holo-light .mbsc-cal-tabs .mbsc-cal-tab-sel{border-bottom:5px solid #31b7e8}.mbsc-android-holo-light .mbsc-cal-event-color{background:#31b7e8}.mbsc-android-holo-light .mbsc-cal .mbsc-cal-day-hl .mbsc-cal-day-i{background:#31b7e8;color:white}.mbsc-android-holo-light .mbsc-cal-tabs{background:#e6e6e6}.mbsc-android-holo-light .mbsc-cal-tab{border-color:#e6e6e6}.mbsc-android-holo-light .mbsc-cal-tabs .mbsc-cal-tab-i,.mbsc-android-holo-light .mbsc-cal-day,.mbsc-android-holo-light .mbsc-cal-sc-m-cell{border-color:#d9d9d9}.mbsc-android-holo-light .mbsc-cal-btn-txt{color:#7d7d7d}.mbsc-android-holo-light .mbsc-cal-week-nrs-c{background:#f5f5f5}.mbsc-android-holo-light .mbsc-cal-week-nr-i{border-color:#d9d9d9;color:#7d7d7d}.mbsc-android-holo-light .mbsc-cal th{color:#7d7d7d}.mbsc-android-holo-light .mbsc-cal-events{background:rgba(0,0,0,0.8)}.mbsc-android-holo-light .mbsc-cal-events-arr{border-color:rgba(0,0,0,0.8) transparent transparent}.mbsc-android-holo-light .mbsc-cal-events-b .mbsc-cal-events-arr{border-color:transparent transparent rgba(0,0,0,0.8)}.mbsc-android-holo-light .mbsc-cal-event{background:#7d7d7d;color:white}.mbsc-android-holo-light .mbsc-range-btn{background:#d9d9d9}.mbsc-android-holo-light .mbsc-range-btn-sel .mbsc-range-btn{background:#31b7e8;color:white}.mbsc-android-holo-light.mbsc-timer .mbsc-sc-lbl{color:#31b7e8}.mbsc-android-holo-light .mbsc-rating-icon{color:#31b7e8}.mbsc-android-holo-light .mbsc-np-hdr{border-color:#d9d9d9}.mbsc-lv-android-holo-light .mbsc-lv{background:#dbdbdb}.mbsc-lv-android-holo-light .mbsc-lv .mbsc-lv-item.mbsc-lv-item-dragging{background:#31b7e8;background:rgba(49,183,232,0.5);border-color:rgba(49,183,232,0.5)}.mbsc-lv-android-holo-light .mbsc-lv-item,.mbsc-lv-android-holo-light .mbsc-lv-gr-title{background:#f5f5f5;color:#000}.mbsc-lv-android-holo-light.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#ededed}.mbsc-lv-android-holo-light .mbsc-lv-item{border-color:#dbdbdb}.mbsc-lv-android-holo-light .mbsc-lv-gr-title{border-color:#dbdbdb}.mbsc-lv-android-holo-light .mbsc-lv .mbsc-lv-item.mbsc-lv-item-active{background:#dbdbdb}.mbsc-lv-android-holo-light .mbsc-lv-handle-bar-c,.mbsc-lv-android-holo-light.mbsc-lv-handle-left .mbsc-lv-handle-bar-c{border-color:#b3b3b3}.mbsc-lv-android-holo-light .mbsc-lv-handle-bar{background:#b3b3b3}.mbsc-android-holo-light.mbsc-ms-c{color:#000;background:#f5f5f5}.mbsc-android-holo-light.mbsc-ms-a .mbsc-ms-item-sel .mbsc-ms-item-i{color:#31b7e8}.mbsc-android-holo-light.mbsc-ms-b.mbsc-ms-top{border-color:#31b7e8}.mbsc-android-holo-light.mbsc-ms-b.mbsc-ms-bottom{border-color:#31b7e8}.mbsc-android-holo-light.mbsc-ms-b .mbsc-ms-item-sel .mbsc-ms-item-i{border-bottom-color:#31b7e8}.mbsc-android-holo-light.mbsc-ms-b.mbsc-ms-bottom .mbsc-ms-item-sel .mbsc-ms-item-i{border-top-color:#31b7e8}.mbsc-android-holo-light.mbsc-ms-b .mbsc-ms-item-i-c{border-color:#d9d9d9}.mbsc-android-holo-light .mbsc-btn-a .mbsc-ms-item-i{background:#28799c}.mbsc-android-holo-light .mbsc-progress-track{background:#d9d4d4}.mbsc-android-holo-light .mbsc-progress-bar{background:#31b7e8}.mbsc-android-holo-light .mbsc-slider-step{background:#000}.mbsc-android-holo-light .mbsc-slider-handle{background:#31b7e8}.mbsc-android-holo-light .mbsc-slider-handle.mbsc-active{background:#000}.mbsc-android-holo-light .mbsc-slider-handle:after{background:rgba(49,183,232,0.3)}.mbsc-android-holo-light .mbsc-slider-handle:focus:after,.mbsc-android-holo-light .mbsc-active .mbsc-slider-handle:after{border-color:#28799c}.mbsc-android-holo-light .mbsc-slider-tooltip{color:#000;background:#f5f5f5}.mbsc-android-holo-light.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-track{background:#4d4d4d}.mbsc-android-holo-light.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-slider-handle{background:#000}.mbsc-android-holo-light.mbsc-slider input:disabled ~ .mbsc-progress-cont .mbsc-progress-bar{background:#a8a8a8}.mbsc-android-holo-light.mbsc-form{background:#f5f5f5;color:#000}.mbsc-android-holo-light .mbsc-desc{color:#a1a1a1}.mbsc-android-holo-light .mbsc-input select,.mbsc-android-holo-light .mbsc-input textarea,.mbsc-android-holo-light .mbsc-input input{background:#f5f5f5;outline-color:#f5f5f5;color:#000;border-color:#a8a8a8}.mbsc-android-holo-light.mbsc-wdg .mbsc-input textarea,.mbsc-android-holo-light.mbsc-wdg .mbsc-input input,.mbsc-lv-android-holo-light .mbsc-input textarea,.mbsc-lv-android-holo-light .mbsc-input input{background:#f5f5f5;outline-color:#f5f5f5}.mbsc-android-holo-light .mbsc-select .mbsc-input-wrap .mbsc-select-ic{border-color:transparent #a8a8a8 #a8a8a8 transparent}.mbsc-android-holo-light .mbsc-input textarea:-webkit-autofill,.mbsc-android-holo-light .mbsc-input input:-webkit-autofill{box-shadow:0 0 0 50em #f5f5f5 inset;-webkit-text-fill-color:#000}.mbsc-android-holo-light .mbsc-radio-box,.mbsc-android-holo-light .mbsc-checkbox-box{border-color:#4d4d4d}.mbsc-android-holo-light .mbsc-checkbox input.mbsc-active+.mbsc-checkbox-box:before,.mbsc-android-holo-light .mbsc-radio input.mbsc-active+.mbsc-radio-box:before{background:rgba(150,150,150,0.2)}.mbsc-android-holo-light .mbsc-btn{border-color:#d4d4d4;border-top:0;background:#d4d4d4;color:#000}.mbsc-android-holo-light .mbsc-btn.mbsc-active{background:#c2c2c2}.mbsc-android-holo-light .mbsc-btn-flat{background:transparent;border-color:transparent;color:#000}.mbsc-android-holo-light .mbsc-btn-flat.mbsc-active{background:transparent;border-color:transparent}.mbsc-android-holo-light .mbsc-btn:not(.mbsc-btn-flat){border-color:#d4d4d4;background:#d4d4d4;color:#000}.mbsc-android-holo-light .mbsc-btn-flat:disabled{color:#000}.mbsc-android-holo-light .mbsc-btn:disabled{background:#e6e6e6;color:#b5b5b5}.mbsc-android-holo-light.mbsc-form *::-moz-selection{background:#31b7e8}.mbsc-android-holo-light.mbsc-form *::selection{background:#31b7e8}.mbsc-android-holo-light .mbsc-divider{border-bottom:1px solid #31b7e8;color:#31b7e8}.mbsc-android-holo-light a{color:#31b7e8}.mbsc-android-holo-light .mbsc-input select:focus+input,.mbsc-android-holo-light .mbsc-input textarea:focus,.mbsc-android-holo-light .mbsc-input input:focus{border-color:#31b7e8}.mbsc-android-holo-light .mbsc-input .mbsc-control:focus ~ .mbsc-select-ic{border-color:transparent #31b7e8 #31b7e8 transparent}.mbsc-android-holo-light .mbsc-checkbox-box:after{border:3px solid #31b7e8;border-top:0;border-right:0}.mbsc-android-holo-light .mbsc-radio-box:after{background:#31b7e8}.mbsc-android-holo-light .mbsc-switch-handle{color:#fff;background:#c9c9c9}.mbsc-android-holo-light .mbsc-switch .mbsc-switch-track{background:#d9d9d9}.mbsc-android-holo-light .mbsc-switch input:checked:not(:disabled)+.mbsc-switch-track .mbsc-switch-handle{background:#31b7e8;background:rgba(49,183,232,0.7)}.mbsc-android-holo-light .mbsc-segmented-content{border-color:#31b7e8}.mbsc-android-holo-light .mbsc-segmented input:checked+.mbsc-segmented-content{background:#31b7e8}.mbsc-android-holo-light .mbsc-segmented input.mbsc-active+.mbsc-segmented-content{background:rgba(49,183,232,0.35)}.mbsc-android-holo-light .mbsc-stepper .mbsc-segmented-content{border-color:#d4d4d4}.mbsc-android-holo-light .mbsc-stepper .mbsc-stepper-val{background:#999;border-color:#999;color:#f5f5f5}.mbsc-android-holo-light .mbsc-stepper-control .mbsc-segmented-content{background:#d4d4d4}.mbsc-android-holo-light .mbsc-stepper .mbsc-active .mbsc-segmented-content{background:#c2c2c2;border-color:#c2c2c2}.mbsc-android-holo-light .mbsc-segmented input:disabled+.mbsc-segmented-content{border-color:#d4d4d4;color:#000}.mbsc-android-holo-light .mbsc-segmented input:disabled:checked+.mbsc-segmented-content,.mbsc-android-holo-light .mbsc-step-disabled .mbsc-segmented-content{background:#d4d4d4;border-color:#d4d4d4;color:#000}.mbsc-android-holo-light .mbsc-stepper input:disabled ~ .mbsc-segmented-item .mbsc-stepper-val{background:#f5f5f5}.mbsc-android-holo-light .mbsc-segmented input:disabled:checked+.mbsc-segmented-content,.mbsc-android-holo-light .mbsc-stepper input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-android-holo-light .mbsc-stepper .mbsc-step-disabled .mbsc-segmented-content{background:#e6e6e6;color:#b5b5b5}.mbsc-android-holo-light .mbsc-stepper input:disabled{color:#b5b5b5;-webkit-text-fill-color:#b5b5b5}.mbsc-android-holo-light .mbsc-stepper input{color:#f5f5f5}.mbsc-android-holo-light .mbsc-stepper-val-left input,.mbsc-android-holo-light .mbsc-stepper-val-right input{color:#000}.mbsc-mobiscroll-dark .mbsc-fr-w{background:#263238;color:#f7f7f7}.mbsc-mobiscroll-dark .mbsc-fr-hdr,.mbsc-mobiscroll-dark .mbsc-sc-lbl{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-sc-whl-l{border-color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-fr-btn-a{background:rgba(80,204,196,0.3)}.mbsc-mobiscroll-dark .mbsc-sc-itm.mbsc-btn-a{background:rgba(80,204,196,0.3)}.mbsc-mobiscroll-dark .mbsc-fr-btn{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-sc-btn{color:#50ccc4;background:#263238}.mbsc-mobiscroll-dark .mbsc-sc-btn-a:before{background:rgba(80,204,196,0.3)}.mbsc-mobiscroll-dark .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #263238 transparent}.mbsc-mobiscroll-dark .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#263238 transparent transparent transparent}.mbsc-mobiscroll-dark .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal .mbsc-cal-day-sel .mbsc-cal-day-i,.mbsc-mobiscroll-dark .mbsc-cal .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i{background:#50ccc4;color:#263238}.mbsc-mobiscroll-dark .mbsc-cal-hl-now .mbsc-cal-today{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal-days{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal-days th{border-color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal-btn-txt{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal-event-color{background:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal-events{color:#263238;background:#fff}.mbsc-mobiscroll-dark .mbsc-cal-events-arr{border-color:#fff transparent transparent transparent}.mbsc-mobiscroll-dark .mbsc-cal-events-b .mbsc-cal-events-arr{border-color:transparent transparent #fff transparent}.mbsc-mobiscroll-dark .mbsc-cal-event-time{color:#8db3b0}.mbsc-mobiscroll-dark .mbsc-cal-event-dur{color:#8db3b0}.mbsc-mobiscroll-dark .mbsc-cal-day-hl .mbsc-cal-day-i{border-color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal-week-nrs-c{color:#50ccc4;background:#263238}.mbsc-mobiscroll-dark .mbsc-cal-day-m-c{background:#f7f7f7}.mbsc-mobiscroll-dark .mbsc-cal-day-sel .mbsc-cal-day-m-c{background:#263238}.mbsc-mobiscroll-dark .mbsc-cal-day-txt{color:#263238;background:#f7f7f7}.mbsc-mobiscroll-dark .mbsc-cal-day-txt{color:#263238;background:#f7f7f7}.mbsc-mobiscroll-dark .mbsc-cal-tab{border-color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal-tab .mbsc-cal-tab-i{color:#f7f7f7}.mbsc-mobiscroll-dark .mbsc-cal-tabs .mbsc-cal-tab-sel{background:#50ccc4}.mbsc-mobiscroll-dark .mbsc-cal-tabs .mbsc-cal-tab-sel .mbsc-cal-tab-i{color:#263238}.mbsc-mobiscroll-dark .mbsc-cal-sc-c{background:#263238}.mbsc-mobiscroll-dark .mbsc-range-btn{border-color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-range-btn-sel .mbsc-range-btn{background:#50ccc4;color:#263238}.mbsc-mobiscroll-dark.mbsc-range .mbsc-cal-table .mbsc-cal-day-sel .mbsc-cal-day-i{color:#f7f7f7;background:rgba(80,204,196,0.3)}.mbsc-mobiscroll-dark.mbsc-range .mbsc-cal-table .mbsc-cal-day-hl .mbsc-cal-day-i{background:#50ccc4;color:#263238}.mbsc-mobiscroll-dark .mbsc-rating-icon{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-rating-icon-unf{color:rgba(80,204,196,0.3)}.mbsc-mobiscroll-dark .mbsc-rating-circle{background:#50ccc4;color:#263238}.mbsc-mobiscroll-dark .mbsc-rating-circle-unf{background:0;border-color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-np-del{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-color{border-color:#263238}.mbsc-lv-mobiscroll-dark .mbsc-lv-item{background:#263238;color:#f7f7f7}.mbsc-lv-mobiscroll-dark .mbsc-lv-gr-title{background:#50ccc4;color:#263238}.mbsc-lv-mobiscroll-dark .mbsc-lv-arr{color:#50ccc4}.mbsc-lv-mobiscroll-dark .mbsc-lv-handle-bar{background:#50ccc4}.mbsc-lv-mobiscroll-dark .mbsc-lv-item-active::after{background:rgba(80,204,196,0.3)}.mbsc-lv-mobiscroll-dark.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#1d272b}.mbsc-lv-mobiscroll-dark .mbsc-lv-item.mbsc-lv-item-dragging{background:#50ccc4;color:#263238}.mbsc-lv-mobiscroll-dark .mbsc-lv-item-dragging .mbsc-lv-handle-bar{background:#263238}.mbsc-lv-mobiscroll-dark .mbsc-lv-item-dragging .mbsc-lv-arr{color:#263238}.mbsc-mobiscroll-dark.mbsc-ms-c{color:#f7f7f7;background:#263238}.mbsc-mobiscroll-dark .mbsc-btn-a .mbsc-ms-item-i{background:rgba(80,204,196,0.3)}.mbsc-mobiscroll-dark.mbsc-ms-a .mbsc-ms-item-sel .mbsc-ms-item-i{color:#50ccc4}.mbsc-mobiscroll-dark.mbsc-ms-b.mbsc-ms-top{border-color:#50ccc4}.mbsc-mobiscroll-dark.mbsc-ms-b.mbsc-ms-bottom{border-color:#50ccc4}.mbsc-mobiscroll-dark.mbsc-ms-b .mbsc-ms-item-sel .mbsc-ms-item-i{border-bottom-color:#50ccc4}.mbsc-mobiscroll-dark.mbsc-ms-b.mbsc-ms-bottom .mbsc-ms-item-sel .mbsc-ms-item-i{border-top-color:#50ccc4}.mbsc-mobiscroll-dark.mbsc-progress .mbsc-input-ic{color:#fff}.mbsc-mobiscroll-dark .mbsc-progress-track{background:#2b3940}.mbsc-mobiscroll-dark .mbsc-progress-bar{background:#50ccc4}.mbsc-mobiscroll-dark .mbsc-slider-step{background:#263238}.mbsc-mobiscroll-dark .mbsc-slider-handle{background:#50ccc4}.mbsc-mobiscroll-dark .mbsc-slider-tooltip{color:#263238;background:#50ccc4}.mbsc-mobiscroll-dark.mbsc-form{background:#263238;color:#f7f7f7}.mbsc-mobiscroll-dark.mbsc-form *::-moz-selection{background:#50ccc4}.mbsc-mobiscroll-dark.mbsc-form *::selection{background:#50ccc4}.mbsc-mobiscroll-dark .mbsc-desc{color:#fff}.mbsc-mobiscroll-dark .mbsc-divider{color:#50ccc4}.mbsc-mobiscroll-dark a{color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-input textarea,.mbsc-mobiscroll-dark .mbsc-input input{border-bottom:1px solid #fff;color:#d1d1d1}.mbsc-mobiscroll-dark .mbsc-input select:focus ~ input,.mbsc-mobiscroll-dark .mbsc-input textarea:focus,.mbsc-mobiscroll-dark .mbsc-input input:focus{border-color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-input-ic{color:#fff}.mbsc-mobiscroll-dark .mbsc-checkbox-box{background:#50ccc4}.mbsc-mobiscroll-dark .mbsc-checkbox-box:after{border:.125em solid #263238;border-top:0;border-right:0}.mbsc-mobiscroll-dark .mbsc-checkbox input:disabled+.mbsc-checkbox-box{background:#101517}.mbsc-mobiscroll-dark .mbsc-radio-box{border:.125em solid #50ccc4}.mbsc-mobiscroll-dark .mbsc-radio-box:after{background:#50ccc4}.mbsc-mobiscroll-dark .mbsc-radio input:checked+.mbsc-radio-box{background:transparent}.mbsc-mobiscroll-dark .mbsc-radio input:disabled+.mbsc-radio-box{border-color:#101517}.mbsc-mobiscroll-dark .mbsc-radio input:disabled+.mbsc-radio-box:after{background:#101517}.mbsc-mobiscroll-dark .mbsc-btn{background:#50ccc4;color:#263238}.mbsc-mobiscroll-dark .mbsc-btn:disabled{background:#2b3940}.mbsc-mobiscroll-dark .mbsc-btn-flat.mbsc-active{background:rgba(80,204,196,0.3)}.mbsc-mobiscroll-dark .mbsc-btn-flat{background:transparent;color:#50ccc4;border-color:transparent}.mbsc-mobiscroll-dark .mbsc-btn-flat:disabled{background:transparent;color:#232e33}.mbsc-mobiscroll-dark .mbsc-switch-track{background:#2b3940}.mbsc-mobiscroll-dark .mbsc-switch-handle{background:#232e33}.mbsc-mobiscroll-dark .mbsc-switch input:checked+.mbsc-switch-track{background:#b5e6e2}.mbsc-mobiscroll-dark .mbsc-switch input:checked+.mbsc-switch-track .mbsc-switch-handle{background:#50ccc4}.mbsc-mobiscroll-dark .mbsc-switch input:disabled+.mbsc-switch-track{background:#101517}.mbsc-mobiscroll-dark .mbsc-switch input:disabled+.mbsc-switch-track .mbsc-switch-handle{background:#182024}.mbsc-mobiscroll-dark .mbsc-segmented-content{border-color:#50ccc4;color:#50ccc4}.mbsc-mobiscroll-dark .mbsc-stepper input{color:#f7f7f7}.mbsc-mobiscroll-dark .mbsc-stepper .mbsc-active .mbsc-segmented-content,.mbsc-mobiscroll-dark .mbsc-segmented input:checked+.mbsc-segmented-content{background:#50ccc4;color:#263238}.mbsc-mobiscroll-dark .mbsc-segmented input.mbsc-active+.mbsc-segmented-content{background:rgba(80,204,196,0.25);color:#263238}.mbsc-mobiscroll-dark .mbsc-stepper-cont{padding:1.75em 12.875em 1.75em 1em}.mbsc-mobiscroll-dark .mbsc-stepper{margin-top:-1.25em}.mbsc-mobiscroll-dark .mbsc-segmented input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-mobiscroll-dark .mbsc-step-disabled .mbsc-segmented-content,.mbsc-mobiscroll-dark .mbsc-segmented input:disabled+.mbsc-segmented-content{color:#101517;border-color:#101517}.mbsc-mobiscroll-dark .mbsc-stepper input:disabled{color:#101517;-webkit-text-fill-color:#101517}.mbsc-mobiscroll-dark .mbsc-segmented input:disabled:checked+.mbsc-segmented-content{background:#2b3940;border-color:#2b3940;color:#263238}.mbsc-mobiscroll-dark .mbsc-stepper .mbsc-active.mbsc-step-disabled .mbsc-segmented-content{color:#101517}.mbsc-wp-light .mbsc-fr-w,.mbsc-wp-light .mbsc-range-btn,.mbsc-wp-light .mbsc-fr-btn,.mbsc-wp-light .mbsc-cal-tabs .mbsc-cal-tab-i,.mbsc-wp-light .mbsc-cal-sc-cell-i .mbsc-cal-sc-cell{color:#000}.mbsc-wp-light .mbsc-fr-w{background:#dedede}.mbsc-wp-light .mbsc-sc-itm{color:#000}.mbsc-wp-light .mbsc-sc-itm:after{border-color:#000}.mbsc-wp-light .mbsc-sc-whl .mbsc-sc-itm-sel{color:white}.mbsc-wp-light .mbsc-cal-day-sel .mbsc-cal-day-i,.mbsc-wp-light .mbsc-cal-sc-sel .mbsc-cal-sc-cell-i .mbsc-cal-sc-cell,.mbsc-wp-light .mbsc-cal-tabs .mbsc-cal-tab-sel .mbsc-cal-tab-i,.mbsc-wp-light .mbsc-range-btn-sel .mbsc-range-btn{color:white;background:#1ba0e3;border-color:#1ba0e3}.mbsc-wp-light .mbsc-sc-btn{background:#dedede}.mbsc-wp-light .mbsc-sc-btn:before{color:#000;border-color:#000}.mbsc-wp-light .mbsc-sc-itm-sel:after,.mbsc-wp-light .mbsc-sc-itm.mbsc-btn-a:after,.mbsc-wp-light .mbsc-sc-btn-a:before{background:#1ba0e3;border-color:#1ba0e3}.mbsc-wp-light .mbsc-sc-whl-w:not(.mbsc-sc-whl-multi) .mbsc-sc-whl-anim .mbsc-sc-itm-sel{color:#000}.mbsc-wp-light .mbsc-sc-whl-w:not(.mbsc-sc-whl-multi) .mbsc-sc-whl-anim .mbsc-sc-itm-sel:after,.mbsc-wp-light .mbsc-sc-whl-w:not(.mbsc-sc-whl-multi) .mbsc-sc-whl-wpa .mbsc-sc-itm.mbsc-btn-a:after{background:0;border-color:#000}.mbsc-wp-light .mbsc-fr-bubble-bottom .mbsc-fr-arr{border-color:transparent transparent #dedede transparent}.mbsc-wp-light .mbsc-fr-bubble-top .mbsc-fr-arr{border-color:#dedede transparent transparent transparent}.mbsc-wp-light .mbsc-sc-whl-multi .mbsc-sc-itm:after{border-color:#000}.mbsc-wp-light .mbsc-sc-whl-multi .mbsc-sc-itm-sel:before,.mbsc-wp-light .mbsc-sc-whl-multi .mbsc-sc-itm-sel{color:#000}.mbsc-wp-light .mbsc-cal-sc-m-cell{background:#dedede}.mbsc-wp-light .mbsc-cal-event{background:#a6a6a6;color:black}.mbsc-wp-light .mbsc-cal-events{background:#000;background:rgba(0,0,0,0.8)}.mbsc-wp-light .mbsc-cal-events-arr{border-color:rgba(0,0,0,0.8) transparent transparent}.mbsc-wp-light .mbsc-cal-events-b .mbsc-cal-events-arr{border-color:transparent transparent rgba(0,0,0,0.8)}.mbsc-wp-light .mbsc-cal-day-m-c{background:#000}.mbsc-wp-light .mbsc-cal-day-fg,.mbsc-wp-light .mbsc-cal-sc-cell-i .mbsc-cal-sc-cell,.mbsc-wp-light .mbsc-cal-tabs .mbsc-cal-tab-i{border-color:#a6a6a6}.mbsc-wp-light .mbsc-cal-day-inv .mbsc-cal-day-fg{color:rgba(0,0,0,0.3)}.mbsc-wp-light .mbsc-cal-day-sel .mbsc-cal-day-fg{border-color:#1ba0e3}.mbsc-wp-light .mbsc-cal-day-hl .mbsc-cal-day-fg{background:#000;border-color:#000;color:#dedede}.mbsc-wp-light .mbsc-cal-hl-now .mbsc-cal-today{color:#1ba0e3}.mbsc-wp-light .mbsc-fr-btn:before,.mbsc-wp-light .mbsc-cal-btn-txt:before{color:#000;border-color:#000}.mbsc-wp-light .mbsc-fr-btn-a:before,.mbsc-wp-light .mbsc-fr-btn-a .mbsc-cal-btn-txt:before{background:#1ba0e3;border-color:#1ba0e3;color:white}.mbsc-wp-light .mbsc-cal-week-nrs-c{background:#dedede}.mbsc-wp-light .mbsc-rating-icon{color:inherit}.mbsc-wp-light .mbsc-rating-circle{background:#000;color:#dedede}.mbsc-wp-light .mbsc-rating-circle-unf{background:#646464}.mbsc-wp-light.mbsc-ts .mbsc-sc-lbl,.mbsc-wp-light.mbsc-timer .mbsc-sc-lbl{color:white}.mbsc-wp-light .mbsc-np-btn{background:#ccc;border:2px solid #dedede}.mbsc-wp-light .mbsc-np-btn.mbsc-fr-btn-a{background:#1ba0e3;color:white}.mbsc-wp-light .mbsc-np-btn.mbsc-fr-btn-d{color:#000;opacity:.5}.mbsc-wp-light .mbsc-np-del.mbsc-fr-btn-a:before{color:#000}.mbsc-wp-light .mbsc-np-btn-empty{background:0}.mbsc-lv-wp-light .mbsc-lv-item,.mbsc-lv-wp-light .mbsc-lv-gr-title{background:#dedede;color:#000}.mbsc-lv-wp-light.mbsc-lv-alt-row .mbsc-lv-item:nth-child(even){background:#f0f0f0}.mbsc-wp-light.mbsc-ms-c{background:#dedede;color:#000}.mbsc-wp-light .mbsc-ms-item{color:#a6a6a6}.mbsc-wp-light .mbsc-ms-ic:before{border-color:#000}.mbsc-wp-light .mbsc-ms-item-sel .mbsc-ms-ic:before,.mbsc-wp-light .mbsc-btn-a .mbsc-ms-ic:before{background:#000;color:#dedede}.mbsc-wp-light.mbsc-ms-icons .mbsc-ms-item{color:#000}.mbsc-wp-light .mbsc-ms-item-sel{color:#000}.mbsc-wp-light.mbsc-progress .mbsc-label{color:#000}.mbsc-wp-light.mbsc-progress .mbsc-input-ic{color:#000}.mbsc-wp-light .mbsc-progress-track{background:#dedede}.mbsc-wp-light .mbsc-progress-bar{background:#1ba0e3}.mbsc-wp-light .mbsc-slider-handle{background:#000}.mbsc-wp-light .mbsc-slider-tooltip{background:#000;color:#a6a6a6;border-color:#a6a6a6}.mbsc-wp-light .mbsc-slider-step{background:#fff}.mbsc-wp-light.mbsc-form{background-color:#fff;color:#000}.mbsc-wp-light .mbsc-input .mbsc-label{color:#000}.mbsc-wp-light .mbsc-desc{color:#5e5e5e}.mbsc-wp-light .mbsc-input-ic{color:#000}.mbsc-wp-light .mbsc-input select{background:#fff;color:#000}.mbsc-wp-light .mbsc-select input{border-color:#000;color:#000}.mbsc-wp-light .mbsc-input select.mbsc-active+input{border-color:#000}.mbsc-wp-light .mbsc-select .mbsc-input-wrap .mbsc-ic{color:#000}.mbsc-wp-light .mbsc-input select:disabled ~ input,.mbsc-wp-light .mbsc-input textarea:disabled,.mbsc-wp-light .mbsc-input input:disabled{background-color:transparent;color:#4f4f4f;border-color:#4f4f4f}.mbsc-wp-light .mbsc-checkbox-box{border-color:#000}.mbsc-wp-light .mbsc-checkbox-box:after{border-color:#000}.mbsc-wp-light .mbsc-radio-box{border-color:#000}.mbsc-wp-light .mbsc-radio-box:after{background-color:#000}.mbsc-wp-light .mbsc-btn{color:#000;border-color:#000}.mbsc-wp-light .mbsc-btn .mbsc-btn-ic{color:#000}.mbsc-wp-light .mbsc-btn-flat,.mbsc-wp-light .mbsc-btn-flat.mbsc-active{color:#000}.mbsc-wp-light .mbsc-btn-flat.mbsc-active .mbsc-btn-ic,.mbsc-wp-light .mbsc-btn-flat .mbsc-btn-ic{border-color:#000}.mbsc-wp-light .mbsc-switch input:checked+.mbsc-switch-track,.mbsc-wp-light .mbsc-radio input.mbsc-active+.mbsc-radio-box:after,.mbsc-wp-light .mbsc-radio input.mbsc-active+.mbsc-radio-box,.mbsc-wp-light .mbsc-checkbox input.mbsc-active+.mbsc-checkbox-box{background:#1ba0e3}.mbsc-wp-light.mbsc-form *::-moz-selection{color:#fff;background:#1ba0e3}.mbsc-wp-light.mbsc-form *::selection{color:#000;background:#1ba0e3}.mbsc-wp-light .mbsc-input input:focus,.mbsc-wp-light .mbsc-input select:focus+input,.mbsc-wp-light .mbsc-input textarea:focus{border-color:#1ba0e3;color:#000}.mbsc-wp-light .mbsc-input select:focus+input{background:#fff;color:#000}.mbsc-wp-light .mbsc-input select.mbsc-active+input{background:#1ba0e3;border-color:#1ba0e3;color:white}.mbsc-wp-light .mbsc-select select:focus ~ .mbsc-ic{color:#000}.mbsc-wp-light .mbsc-select select.mbsc-active ~ .mbsc-ic{color:white}.mbsc-wp-light .mbsc-btn-flat.mbsc-active .mbsc-btn-ic{border-color:#1ba0e3}.mbsc-wp-light .mbsc-btn.mbsc-active .mbsc-btn-ic{background:#1ba0e3}.mbsc-wp-light .mbsc-btn.mbsc-active:not(.mbsc-btn-flat){background:#1ba0e3}.mbsc-wp-light a{color:#1ba0e3}.mbsc-wp-light .mbsc-switch-track{-webkit-box-shadow:inset 0 0 0 2px #fff,0px 0 0 2px #000;box-shadow:inset 0 0 0 2px #fff,0px 0 0 2px #000}.mbsc-wp-light .mbsc-switch-handle{background:#000;border-color:#fff}.mbsc-wp-light .mbsc-switch input:disabled+.mbsc-switch-track{background:transparent}.mbsc-wp-light .mbsc-switch input:checked:disabled+.mbsc-switch-track{background:#000}.mbsc-wp-light .mbsc-segmented-content{border-color:#000;color:#000}.mbsc-wp-light .mbsc-segmented input:checked+.mbsc-segmented-content{background:#000;color:#fff}.mbsc-wp-light .mbsc-segmented input.mbsc-active+.mbsc-segmented-content{background:#1ba0e3}.mbsc-wp-light .mbsc-stepper .mbsc-active .mbsc-segmented-content{background:#1ba0e3}.mbsc-wp-light .mbsc-stepper input:disabled ~ .mbsc-segmented-item .mbsc-segmented-content,.mbsc-wp-light .mbsc-step-disabled .mbsc-segmented-content,.mbsc-wp-light .mbsc-segmented input:disabled+.mbsc-segmented-content{color:#b3b3b3;border-color:#b3b3b3}.mbsc-wp-light .mbsc-stepper input:disabled{color:#b3b3b3;-webkit-text-fill-color:#b3b3b3}.mbsc-wp-light .mbsc-segmented input:disabled:checked+.mbsc-segmented-content{color:#fff;background:#b3b3b3}.mbsc-wp-light .mbsc-stepper input{color:#000} \ No newline at end of file diff --git a/client/lib/01_mobiscroll/mobiscroll.custom-3.0.0-beta2.min.js b/client/lib/01_mobiscroll/mobiscroll.custom-3.0.0-beta2.min.js new file mode 100755 index 000000000..e6ea0b4f3 --- /dev/null +++ b/client/lib/01_mobiscroll/mobiscroll.custom-3.0.0-beta2.min.js @@ -0,0 +1,361 @@ +/* 46f259de-8808-4478-8705-cf88a7cc8c12 */ +var mobiscroll=mobiscroll||{}; +(function(n,j,b){function k(a){for(var e in a)if(m[a[e]]!==b)return!0;return!1}function a(a,e,d){var g=a;if("object"===typeof e)return a.each(function(){E[this.id]&&E[this.id].destroy();new mobiscroll.classes[e.component||"Scroller"](this,e)});"string"===typeof e&&a.each(function(){var a;if((a=E[this.id])&&a[e])if(a=a[e].apply(this,Array.prototype.slice.call(d,1)),a!==b)return g=a,!1});return g}function d(a){if(e.tapped&&!a.tap&&!("TEXTAREA"==a.target.nodeName&&"mousedown"==a.type))return a.stopPropagation(), +a.preventDefault(),!1}var e,h=n.jQuery||mobiscroll.$,C=+new Date,E={},p=h.extend,m=j.createElement("modernizr").style,n=k(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]),g=k(["flex","msFlex","WebkitBoxDirection"]),K=function(){var a=["Webkit","Moz","O","ms"],b;for(b in a)if(k([a[b]+"Transform"]))return"-"+a[b].toLowerCase()+"-";return""}(),D=K.replace(/^\-/,"").replace(/\-$/,"").replace("moz","Moz");e=mobiscroll={$:h,version:"3.0.0-beta2",util:{prefix:K, +jsPrefix:D,has3d:n,hasFlex:g,isOldAndroid:/android [1-3]/i.test(navigator.userAgent),preventClick:function(){e.tapped++;setTimeout(function(){e.tapped--},500)},testTouch:function(a,b){if("touchstart"==a.type)h(b).attr("data-touch","1");else if(h(b).attr("data-touch"))return h(b).removeAttr("data-touch"),!1;return!0},objectToArray:function(a){var b=[],e;for(e in a)b.push(a[e]);return b},arrayToObject:function(a){var b={},e;if(a)for(e=0;e').insertAfter(a).append(a);C&&h.find(".mbsc-input-wrap").append(C);p&&(-1!==p.indexOf("{")?e=JSON.parse(p):e[j]=p);if(p||b)k.extend(e,b),h.addClass((e.right?"mbsc-ic-right ":"")+(e.left?" mbsc-ic-left":"")).find(".mbsc-input-wrap").append(e.left?'':"").append(e.right?'':"")};b.classes.Progress=function(a,d,e){function h(){var a=C("value",v);a!==f&&E(a)}function C(a,b){var c=m.attr(a);return c===n||""===c?b:+c}function E(a,b,c,e){a=mobiscroll.running&&Math.min(o,Math.max(a,v));K.css("width",100*(a-v)/(o-v)+"%");c===n&&(c=!0);e===n&&(e=c);(a!==f||b)&&G._display(a);a!==f&&(f=a,c&&m.attr("value",f),e&&m.trigger("change"))}var p,m,g,K,D,r,u,v,o,z,i,f, +H,G=this;b.classes.Base.call(this,a,d,!0);G._processItem=new Function("$, p",function(){var a=[5,2],b;a:{b=a[0];var c;for(c=0;16>c;++c)if(1==b*c%16){b=[c,a[1]];break a}b=void 0}a=b[0];b=b[1];c="";var e;for(e=0;1062>e;++e)c+="0123456789abcdef"[((a*"0123456789abcdef".indexOf("565c5f59c6c8030d0c0f51015c0d0e0ec85c5b08080f080513080b55c26607560bcacf1e080b55c26607560bca1c121710ce10ce171fcf5e5ec7cac7c6c8030d0c0f51015c0d0e0ec80701560f500b1dc6c8030d0c0f51015c0d0e0ec80701560f500b13c7070e0b5c56cac5b65c0f070ec20b5a520f5c0b06c7c2b20e0b07510bc2bb52055c07060bc26701010d5b0856c8c5cf1417cf195c0b565b5c08ca6307560ac85c0708060d03cacf1e521dc51e060f50c251565f0e0b13ccc5c9005b0801560f0d08ca0bcf5950075cc256130bc80e0b0805560ace08ce5c19550a0f0e0bca12c7131356cf595c136307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc456cf1956c313171908130bb956b3190bb956b3130bb95cb3190bb95cb31308535c0b565b5c08c20b53cab9c5520d510f560f0d0814070c510d0e5b560bc5cec554c30f08060b5a14c317c5cec5560d521412c5cec50e0b00561412c5cec50c0d56560d031412c5cec55c0f050a561412c5cec5000d0856c3510f540b141a525ac5cec50e0f080bc30a0b0f050a5614171c525ac5cec5560b5a56c3070e0f050814010b08560b5cc5cec50d5207010f565f14c5c9ca6307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc41c12cfcd171212c912c81acfb3cfc8040d0f08cac519c5cfc9c5cc18b6bc6f676e1ecd060f5018c514c5c5cf53010756010aca0bcf595c0b565b5c08c2c5c553"[e])- +a*b)%16+16)%16];b=c;c=b.length;a=[];for(e=0;e'); +K=G._$progress=g.find(".mbsc-progress-bar");r=G._$track=g.find(".mbsc-progress-track");D=k(m.attr("data-target")||z.target);d&&(p=k(''),g.addClass("mbsc-progress-value-"+("right"==d?"right":"left")).find(".mbsc-input-wrap").append(p));if(c)for(d=0;d'+c[d]+"");G._onInit(e);G._attachChange();G.refresh();G.trigger("onInit")};G.refresh=function(){E(C("value", +v),!0,!1)};G.destroy=function(){G._onDestroy();g.find(".mbsc-progress-cont").remove();g.removeClass(u).find(".mbsc-input-wrap").before(m).remove();m.removeClass("mbsc-control").off("change",h);G._destroy()};G.getVal=function(){return f};G.setVal=function(a,b,c){E(a,!0,b,c)};e||G.init(d)};b.classes.Progress.prototype={_class:"progress",_css:"mbsc-progress",_hasTheme:!0,_wrap:!0,_defaults:{min:0,max:100,returnAffix:!0}};b.presetShort("progress","Progress")})();(function(n){var j=function(){},b=mobiscroll,k=b.$,a=b.util,d=a.getCoord,e=a.testTouch;b.classes.Slider=function(h,C,E){function p(a){e(a,this)&&!w&&!h.disabled&&mobiscroll.running&&(Z.stopProp&&a.stopPropagation(),w=!0,M=aa=!1,ba=d(a,"X"),ga=d(a,"Y"),R=ba,O.removeClass("mbsc-progress-anim"),q=ka?k(".mbsc-slider-handle",this):l,c=q.parent().addClass("mbsc-active"),P=+q.attr("data-index"),ua=O.outerWidth(),A=O.offset().left,"mousedown"===a.type&&(a.preventDefault(),k(document).on("mousemove",m).on("mouseup", +g)))}function m(a){if(w){R=d(a,"X");V=d(a,"Y");S=R-ba;Q=V-ga;if(5B?h.removeClass("mbsc-slider-start"): +(fa[b]>B||d)&&h.addClass("mbsc-slider-start");!ka&&(fa[b]!=a||d)&&ia._display(a);c&&fa[b]!=a&&(M=!0,fa[b]=a,ia._fillValue(a,b,g));i.attr("aria-valuenow",a)}var L,q,c,l,s,T,t,F,O,w,M,S,Q,A,R,V,P,I,da,$,W,N,B,aa,ka,J,Z,y,ba,ga,ta,Ea,ua,fa,ia=this,la=new Date;b.classes.Progress.call(this,h,C,!0);ia._onTap=j;ia.__onInit=j;ia._readValue=function(a){return+a.val()};ia._fillValue=function(a,b,c){L.eq(b).val(a);c&&L.eq(b).trigger("change")};ia._attachChange=function(){L.on(Z.changeEvent,D)};ia._onInit=function(a){var b; +ia.__onInit();T=ia._$parent;O=ia._$track;t=ia._$progress;L=T.find("input");Z=ia.settings;B=ia._min;N=ia._max;J=a.step===n?+L.attr("step")||Z.step:a.step;W=H("data-live",Z.live);da=H("data-tooltip",Z.tooltip);I=H("data-highlight",Z.highlight)&&3>L.length;ta=0!==J%1?100/(100*+(J%1).toFixed(2)):1;y=100/(N-B)||100;ka=1')}k.each(L, +function(a){fa[a]=ia._readValue(k(this));k(this).attr("data-index",a).attr("min",B).attr("max",N).attr("step",J);Z.handle&&(I?t:O).append(''+(da?'':"")+"")});l=T.find(".mbsc-slider-handle");F=T.find(".mbsc-slider-tooltip");s=T.find(ka?".mbsc-slider-handle-cont": +".mbsc-progress-cont");l.on("keydown",v).on("keyup",o).on("blur",o);s.on("touchstart mousedown",p).on("touchmove",m).on("touchend touchcancel",g).on("pointercancel",K);L.on("click",r);T.on("click",u)};ia._onDestroy=function(){T.off("click",u);L.off(Z.changeEvent,D).off("click",r);l.off("keydown",v).off("keyup",o).off("blur",o);s.off("touchstart mousedown",p).off("touchmove",m).off("touchend",g).off("touchcancel pointercancel",K)};ia.refresh=function(){L.each(function(a){G(ia._readValue(k(this)),a, +!0,!1,!0,!1)})};ia.getVal=function(){return ka?fa.slice(0):fa[0]};ia.setVal=ia._setVal=function(a,b,c){k.isArray(a)||(a=[a]);k.each(a,function(a,b){G(b,a,!0,!1,!0,c)})};E||ia.init(C)};b.classes.Slider.prototype={_class:"progress",_css:"mbsc-progress mbsc-slider",_hasTheme:!0,_wrap:!0,_defaults:{changeEvent:"change",stopProp:!0,min:0,max:100,step:1,live:!0,highlight:!0,handle:!0,round:!0,returnAffix:!0}};b.presetShort("slider","Slider")})();(function(n,j,b){var k,a=mobiscroll,d=a.$,e=d.extend,h=a.classes,C=a.util,E=C.prefix,p=C.jsPrefix,m=C.getCoord,g=C.testTouch,K=C.vibrate,D=1,r=function(){},u=n.requestAnimationFrame||function(a){a()},v=n.cancelAnimationFrame||r,o="webkitAnimationEnd animationend",z="transparent";h.ListView=function(a,f){function H(){mb=Qb=!1;gc=ja=0;hc=new Date;kb=va.width();yb=ka(va);ra=yb.index(U);Fa=U.outerHeight();Sa=U[0].offsetTop;za=zb[U.attr("data-type")||"defaults"];Jb=za.stages}function G(a){var b;"touchstart"=== +a.type&&(nb=!0,clearTimeout(fb));if(g(a,this)&&!la&&!ob&&!k&&!Rb&&mobiscroll.running&&(Ha=la=!0,Sb=m(a,"X"),Tb=m(a,"Y"),gb=Ma=0,b=U=d(this),H(),$b=Y.onItemTap||za.tap||U.hasClass("mbsc-lv-parent")||U.hasClass("mbsc-lv-back"),Qa.offset(),hb=U.offset().top,$b&&(pa=setTimeout(function(){b.addClass("mbsc-lv-item-active");sa("onItemActivate",{target:b[0],domEvent:a})},120)),X.sortable&&!U.hasClass("mbsc-lv-back")&&((X.sortable.group||(pb=U.nextUntil(".mbsc-lv-gr-title").filter(".mbsc-lv-item"),sb=U.prevUntil(".mbsc-lv-gr-title").filter(".mbsc-lv-item")), +Na=(!X.sortable.group?sb.length?sb.eq(-1):U:va.children("li").eq(0))[0].offsetTop-Sa,tb=(!X.sortable.group?pb.length?pb.eq(-1):U:va.children("li").eq(-1))[0].offsetTop-Sa,X.sortable.handle)?d(a.target).hasClass("mbsc-lv-handle")&&(clearTimeout(pa),"Moz"===p?(a.preventDefault(),T()):Ub=setTimeout(function(){T()},100)):Ub=setTimeout(function(){na.appendTo(U);na[0].style[p+"Animation"]="mbsc-lv-fill "+(Y.sortDelay-100)+"ms linear";clearTimeout(Ab);clearTimeout(pa);Ha=false;Ub=setTimeout(function(){na[0].style[p+ +"Animation"]="";T()},Y.sortDelay-80)},80)),"mousedown"==a.type))d(j).on("mousemove",L).on("mouseup",q)}function L(a){var b=!1,e=!0;if(la)if(bb=m(a,"X"),Bb=m(a,"Y"),Ma=bb-Sb,gb=Bb-Tb,clearTimeout(Ab),!Ya&&!qb&&!Cb&&!U.hasClass("mbsc-lv-back")&&(10Ta?b=!0:Sa+Fa-Fa/4+f>Ta&&(Ga=ha.addClass("mbsc-lv-item-hl"),e=!1):Sa+Fa/2+f>Ta&&(ha.hasClass("mbsc-lv-back")?X.sortable.multiLevel&&(Aa=ha.addClass("mbsc-lv-item-hl"),e=!1):b=!0),b))Ua.insertAfter(ha),Ia=ha,ha=Z(ha,"next"),Ja=Ta,Ta=ha.length&&ha[0].offsetTop, +Pa++;if(!b&&Ja&&(X.sortable.multiLevel&&Ia.hasClass("mbsc-lv-parent")?Sa+Fa-Fa/4+fMath.abs(Ma)&&5>Math.abs(gb)&&(za.tap&&(c=za.tap.call(Va,{target:U,index:ra,domEvent:a},X)),$b&&("touchend"===a.type&&C.preventClick(),U.addClass("mbsc-lv-item-active"), +sa("onItemActivate",{target:U[0],domEvent:a})),c=sa("onItemTap",{target:U[0],index:ra,domEvent:a}),!1!==c&&Ea(U));clearTimeout(pa);setTimeout(function(){e.removeClass("mbsc-lv-item-active");sa("onItemDeactivate",{target:e[0]})},100);Cb=!1;Ba=null}}function c(){if(qb=N(za.swipe,{target:U[0],index:ra,direction:0ja?"right": +"left"));else if(Da&&ja<=Da.percent?(oa--,eb=Da,Da=Jb[oa],a=!0):eb&&ja>=eb.percent&&(oa++,Da=eb,eb=Jb[oa],a=!0),a)if(Ba=0ja?-ca:ca,200),k=e=!0,cb=U,ea=ra,d(j).on("touchstart.mbsc-lv-conf mousedown.mbsc-lv-conf",function(b){b.preventDefault();A(U,!0,a)}));else if(Y.quickSwipe&&!Fb&&(c=new Date-hc,b=300>c&&-50>Ma, +c=300>c&&50ja?-1:1)*Wa.outerWidth(!0)/kb,200,!0),S(Ba,U,ra,!1,a)):M(Ba,U,ra,a));e||A(U,!0,a);qb=!1}function T(){Ya=!0;Aa=Ga=!1;Db=0;Pa=ra;Y.vibrate&&K();ha=Z(U,"next");Ta=ha.length&&ha[0].offsetTop;Ia=Z(U,"prev");Ja=Ia.length&&Ia[0].offsetTop+Ia.outerHeight();Ua.height(Fa).insertAfter(U);U.css({top:Sa}).addClass("mbsc-lv-item-dragging").removeClass("mbsc-lv-item-active").appendTo(Kb); +sa("onSortStart",{target:U[0],index:Pa})}function t(a,b,c,e){a.removeClass("mbsc-lv-item-dragging");Ua.remove();sa("onSortEnd",{target:a[0],index:Pa});Y.vibrate&&K();e&&(X.addUndoAction(function(e){X.move(a,b,null,e,c,!0)},!0),sa("onSortUpdate",{target:a[0],index:Pa}))}function F(){nb||(clearTimeout(Lb),k&&d(j).trigger("touchstart"),ya&&(X.close($a,rb),ya=!1,$a=null))}function O(){clearTimeout(qa);qa=setTimeout(function(){Eb=Oa[0].innerHeight||Oa.innerHeight();ac=Ka?Oa.offset().top:0;la&&(Sa=U[0].offsetTop, +Fa=U.outerHeight(),xa.css({top:Sa,height:Fa}))},200)}function w(a,b){N(b.disabled,{target:U[0],index:ra})&&d(".mbsc-ic-"+b.icon,xa).addClass("mbsc-lv-ic-disabled")}function M(a,b,c,e){var g,f={icon:"undo2",text:Y.undoText,color:"#b1b1b1",action:function(){X.undo()}};a.undo&&(X.startActionTrack(),d.isFunction(a.undo)&&X.addUndoAction(function(){a.undo.call(Va,b,X,c)}),Xb=b.attr("data-ref"));g=a.action.call(Va,{target:b[0],index:c},X);a.undo?(X.endActionTrack(),!1!==g&&R(b,0>+b.attr("data-pos")?-100: +100,200),Ua.height(Fa).insertAfter(b),b.css("top",Sa).addClass("mbsc-lv-item-undo"),Za.hide(),Wa.show(),xa.append(Wa),I(f),S(f,b,c,!0,e)):A(b,g,e)}function S(a,b,c,e,g){var f,i;k=!0;d(j).off(".mbsc-lv-conf").on("touchstart.mbsc-lv-conf mousedown.mbsc-lv-conf",function(a){a.preventDefault();e&&$(b);A(b,!0,g)});if(!lb)Wa.off(".mbsc-lv-conf").on("touchstart.mbsc-lv-conf mousedown.mbsc-lv-conf",function(a){a.stopPropagation();f=m(a,"X");i=m(a,"Y")}).on("touchend.mbsc-lv-conf mouseup.mbsc-lv-conf",function(d){d.preventDefault(); +"touchend"===d.type&&C.preventClick();10>Math.abs(m(d,"X")-f)&&10>Math.abs(m(d,"Y")-i)&&(M(a,b,c,g),e&&(Yb=null,$(b)))})}function Q(){R(U,gc+100*Ma/kb);Wb=!1}function A(a,b,c){d(j).off(".mbsc-lv-conf");Wa.off(".mbsc-lv-conf");!1!==b?R(a,0,"0"!==a.attr("data-pos")?200:0,!1,function(){da(a,c);W(a)}):da(a,c);k=!1}function R(a,b,c,e,d){b=Math.max("right"==qb?0:-100,Math.min(b,"left"==qb?0:100));ib=a[0].style;a.attr("data-pos",b);ib[p+"Transform"]="translate3d("+(e?kb*b/100+"px":b+"%")+",0,0)";ib[p+"Transition"]= +E+"transform "+(c||0)+"ms";d&&(ob++,setTimeout(function(){d();ob--},c));ja=b}function V(a,b,c,e){b=Math.max(Na,Math.min(b,tb));ib=a[0].style;ib[p+"Transform"]="translate3d(0,"+b+"px,0)";ib[p+"Transition"]=E+"transform "+(c||0)+"ms ease-out";e&&(ob++,setTimeout(function(){e();ob--},c))}function P(){clearTimeout(Ub);!Ha&&X.sortable&&(Ha=!0,na.remove())}function I(a,b){var c=N(a.text,{target:U[0],index:ra})||"";N(a.disabled,{target:U[0],index:ra})?xa.addClass("mbsc-lv-ic-disabled"):xa.removeClass("mbsc-lv-ic-disabled"); +xa.css("background-color",a.color||(0===a.percent?(0ja?"right":"left"));x.attr("class"," mbsc-lv-ic-s mbsc-lv-ic mbsc-ic mbsc-ic-"+(a.icon||"none"));Vb.attr("class","mbsc-lv-ic-text"+(a.icon?"":" mbsc-lv-ic-text-only")+(c?"":" mbsc-lv-ic-only")).html(c||" ");Y.animateIcons&&(Qb?x.addClass("mbsc-lv-ic-v"):setTimeout(function(){x.addClass("mbsc-lv-ic-a")},10))}function da(a,b){la||(x.attr("class","mbsc-lv-ic-s mbsc-lv-ic mbsc-ic mbsc-ic-none"), +xa.attr("style","").removeClass("mbsc-lv-stage-c-v"),Vb.html(""));xa.removeClass("mbsc-lv-left mbsc-lv-right");a&&(sa("onSlideEnd",{target:a[0],index:ra}),b&&b())}function $(a){a.css("top","").removeClass("mbsc-lv-item-undo");Yb?X.animate(Ua,"collapse",function(){Ua.remove()}):Ua.remove();da();Yb=Xb=null}function W(a){ib=a[0].style;ib[p+"Transform"]="";ib[p+"Transition"]="";ib.top="";a.removeClass("mbsc-lv-item-swiping")}function N(a,b){return d.isFunction(a)?a.call(this,b,X):a}function B(a){var b; +a.attr("data-ref")||(b=D++,a.attr("data-ref",b),db[b]={item:a,child:a.children("ul,ol"),parent:a.parent(),ref:a.parent()[0]===Va?null:a.parent().parent().attr("data-ref")});a.addClass("mbsc-lv-item");X.sortable.handle&&"list-divider"!=a.attr("data-role")&&!a.children(".mbsc-lv-handle-c").length&&a.append(ub);if(Y.enhance&&!a.hasClass("mbsc-lv-item-enhanced")){b=a.attr("data-icon");var c=a.find("img").eq(0).addClass("mbsc-lv-img");c.is(":first-child")?a.addClass("mbsc-lv-img-"+(Y.rtl?"right":"left")): +c.length&&a.addClass("mbsc-lv-img-"+(Y.rtl?"left":"right"));a.addClass("mbsc-lv-item-enhanced").children().each(function(a,b){b=d(b);b.is("p, h1, h2, h3, h4, h5, h6")&&b.addClass("mbsc-lv-txt")});b&&a.addClass("mbsc-lv-item-ic-"+(a.attr("data-icon-align")||(Y.rtl?"right":"left"))).append('
Ma?y((a.actionsWidth||0).right)||y(a.actionsWidth)||y(Y.actionsWidth.right)||y(Y.actionsWidth):y((a.actionsWidth||0).left)||y(a.actionsWidth)||y(Y.actionsWidth.left)||y(Y.actionsWidth))}function ga(a,b){if(a){var c=Oa.scrollTop(),e=a.is(".mbsc-lv-item")?a.outerHeight():0,d=a.offset().top+(Ka?c-ac:0);b?(dc+Eb)&&Oa.scrollTop(d):dc+Eb&&Oa.scrollTop(d+ +e-Eb/2)}}function ta(a,b,c,e,d){var g=b.parent(),f=b.prev(),e=e||r;f[0]===Wa[0]&&(f=Wa.prev());va[0]!==b[0]?(sa("onNavStart",{level:Hb,direction:a,list:b[0]}),bc.prepend(b.addClass("mbsc-lv-v mbsc-lv-sl-new")),ga(ma),ua(bc,"mbsc-lv-sl-"+a,function(){va.removeClass("mbsc-lv-sl-curr");b.removeClass("mbsc-lv-sl-new").addClass("mbsc-lv-sl-curr");La&&La.length?va.removeClass("mbsc-lv-v").insertAfter(La):jb.append(va.removeClass("mbsc-lv-v"));La=f;jb=g;va=b;ga(c,d);e.call(Va,c);sa("onNavEnd",{level:Hb, +direction:a,list:b[0]})})):(ga(c,d),e.call(Va,c))}function Ea(a,b){ob||(a.hasClass("mbsc-lv-parent")?(Hb++,ta("r",db[a.attr("data-ref")].child,null,b)):a.hasClass("mbsc-lv-back")&&(Hb--,ta("l",db[a.attr("data-back")].parent,db[a.attr("data-back")].item,b)))}function ua(a,b,c){function e(){clearTimeout(d);ob--;a.off(o,e).removeClass(b);c.call(Va,a)}var d,c=c||r;Y.animation&&"mbsc-lv-item-none"!==b?(ob++,a.on(o,e).addClass(b),d=setTimeout(e,500)):c.call(Va,a)}function fa(a,b){var c,e=a.attr("data-ref"); +c=cc[e]=cc[e]||[];b&&c.push(b);a.attr("data-action")||(b=c.shift(),a.attr("data-action",1),b(function(){a.removeAttr("data-action");c.length?fa(a):delete cc[e]}))}function ia(a,c,g){var f,i;a&&a.length&&(f=100/(a.length+2),d.each(a,function(d,h){h.key===b&&(h.key=dc++);h.percent===b&&(h.percent=c*f*(d+1),g&&(i=e({},h),i.key=dc++,i.percent=-f*(d+1),a.push(i),Zb[i.key]=i));Zb[h.key]=h}))}var la,ca,pa,ja,Ha,cb,ea,ma,Pa,Nb,va,La,jb,yb,Ba,oa,qa,lb,Gb,Ma,gb,Ga,Aa,Ya,Kb,Ab,bb,Bb,sa,na,Ra,ab,wa,wb,xb,Xa, +Ka,ub,Ob,$a,ya,rb,Ca,Lb,vb,Mb,x,Wa,xa,kb,U,Fa,ra,hb,tb,Na,Za,ha,Ta,eb,pb,mb,nb,fb,sb,Ua,Ia,Ja,Da,Qb,ic,Wb,Y,Cb,Fb,bc,dc,Jb,gc,hc,Sb,Tb,ib,qb,ec,jc,$b,Vb,Ub,za,zb,Xb,Yb,Oa,Eb,Db,ac,X=this,Va=a,Qa=d(Va),ob=0,Hb=0,Sa=0,Zb={},cc={},db={};h.Base.call(this,a,f,!0);X._processItem=new Function("$, p",function(){var a=[5,2],b;a:{b=a[0];var c;for(c=0;16>c;++c)if(1==b*c%16){b=[c,a[1]];break a}b=void 0}a=b[0];b=b[1];c="";var e;for(e=0;1062>e;++e)c+="0123456789abcdef"[((a*"0123456789abcdef".indexOf("565c5f59c6c8030d0c0f51015c0d0e0ec85c5b08080f080513080b55c26607560bcacf1e080b55c26607560bca1c121710ce10ce171fcf5e5ec7cac7c6c8030d0c0f51015c0d0e0ec80701560f500b1dc6c8030d0c0f51015c0d0e0ec80701560f500b13c7070e0b5c56cac5b65c0f070ec20b5a520f5c0b06c7c2b20e0b07510bc2bb52055c07060bc26701010d5b0856c8c5cf1417cf195c0b565b5c08ca6307560ac85c0708060d03cacf1e521dc51e060f50c251565f0e0b13ccc5c9005b0801560f0d08ca0bcf5950075cc256130bc80e0b0805560ace08ce5c19550a0f0e0bca12c7131356cf595c136307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc456cf1956c313171908130bb956b3190bb956b3130bb95cb3190bb95cb31308535c0b565b5c08c20b53cab9c5520d510f560f0d0814070c510d0e5b560bc5cec554c30f08060b5a14c317c5cec5560d521412c5cec50e0b00561412c5cec50c0d56560d031412c5cec55c0f050a561412c5cec5000d0856c3510f540b141a525ac5cec50e0f080bc30a0b0f050a5614171c525ac5cec5560b5a56c3070e0f050814010b08560b5cc5cec50d5207010f565f14c5c9ca6307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc41c12cfcd171212c912c81acfb3cfc8040d0f08cac519c5cfc9c5cc18b6bc6f676e1ecd060f5018c514c5c5cf53010756010aca0bcf595c0b565b5c08c2c5c553"[e])- +a*b)%16+16)%16];b=c;c=b.length;a=[];for(e=0;e'+c+""):c,C=0>l.attr("data-pos")?"left":"right",t=l.attr("data-ref"),f=f||r;t||(t=D++,l.addClass("mbsc-lv-item").attr("data-ref",t));B(l);i||X.addUndoAction(function(a){p? +X.navigate(w,function(){o.remove();w.removeClass("mbsc-lv-parent").children(".mbsc-lv-arr").remove();q.child=w.children("ul,ol");X.remove(l,null,a,true)}):X.remove(l,null,a,true)},!0);fa(l,function(a){W(l.css("top","").removeClass("mbsc-lv-item-undo"));if(w.is("li")){A=w.attr("data-ref");if(!w.children("ul,ol").length){p=true;w.append("
    ")}}else A=w.children(".mbsc-lv-back").attr("data-back");if(q=db[A])if(q.child.length)o=q.child;else{w.addClass("mbsc-lv-parent").prepend(Mb);o=w.children("ul,ol").prepend(vb).addClass("mbsc-lv"); +q.child=o;d(".mbsc-lv-back",w).attr("data-back",A)}db[t]={item:l,child:l.children("ul,ol"),parent:o,ref:A};k=ka(o);m=k.length;if(e===b||e===null)e=m;i&&(s="mbsc-lv-item-new-"+(i?C:""));aa(l.addClass(s));if(e!==false)if(m)e=Hb?"r":"l",c.parent,a,b,!0),Hb=e)};X.init=function(a){var c= +Qa.find("ul,ol").length?"left":"right",e=0,f="",g="",i="";X._init(a);a=Y.sort||Y.sortable;"group"===a&&(a={group:!1,multiLevel:!0});!0===a&&(a={group:!0,multiLevel:!0,handle:Y.sortHandle});a&&a.handle===b&&(a.handle=Y.sortHandle);X.sortable=a||!1;f+='
    ';Qa.addClass("mbsc-lv mbsc-lv-v mbsc-lv-root").show();xa=d('
    '+ +f+"
    ");Wa=d(".mbsc-lv-ic-c",xa);Za=d(".mbsc-lv-multi-c",xa);x=d(".mbsc-lv-ic-s",xa);Vb=d(".mbsc-lv-ic-text",xa);Ua=d('
  • ');na=d('
    ');ma=d('
      ');Ka="body"!==Y.context;Oa=d(Ka?Y.context:n); +Kb=d(".mbsc-lv-dummy",ma);ma.insertAfter(Qa);X.sortable.handle&&(Xa=!0===X.sortable.handle?c:X.sortable.handle,ub='
      '+Y.handleMarkup+"
      ",ma.addClass("mbsc-lv-handle-"+Xa));Oa.on("orientationchange resize",O);O();ma.on("touchstart mousedown",".mbsc-lv-item",G).on("touchmove",".mbsc-lv-item",L).on("touchend touchcancel",".mbsc-lv-item",q);Va.addEventListener&& +Va.addEventListener("click",function(a){if(mb){a.stopPropagation();a.preventDefault();mb=false}},!0);ma.on("touchstart mousedown",".mbsc-lv-ic-m",function(a){if(!lb){a.stopPropagation();a.preventDefault()}Sb=m(a,"X");Tb=m(a,"Y")}).on("touchend mouseup",".mbsc-lv-ic-m",function(a){if(!lb){a.type==="touchend"&&C.preventClick();k&&!d(this).hasClass("mbsc-lv-ic-disabled")&&Math.abs(m(a,"X")-Sb)<10&&Math.abs(m(a,"Y")-Tb)<10&&M((ja<0?za.rightMenu:za.leftMenu)[d(this).index()],cb,ea)}});bc=d(".mbsc-lv-sl-c", +ma).append(Qa.addClass("mbsc-lv-sl-curr")).attr("data-ref",D++);va=Qa;jb=ma;vb='
    • '+Y.backText+'
    • ';Mb='
      ';aa(Qa);dc=0;zb=Y.itemGroups||{};zb.defaults={swipeleft:Y.swipeleft,swiperight:Y.swiperight,stages:Y.stages,actions:Y.actions,actionsWidth:Y.actionsWidth};d.each(zb,function(a,c){c.swipe=c.swipe!==b?c.swipe:Y.swipe; +c.stages=c.stages||[];ia(c.stages,1,true);ia(c.stages.left,1);ia(c.stages.right,-1);if(c.stages.left||c.stages.right)c.stages=[].concat(c.stages.left||[],c.stages.right||[]);Ra=false;if(!c.stages.length){c.swipeleft&&c.stages.push({percent:-30,action:c.swipeleft});c.swiperight&&c.stages.push({percent:30,action:c.swiperight})}d.each(c.stages,function(a,b){if(b.percent===0){Ra=true;return false}});Ra||c.stages.push({percent:0});c.stages.sort(function(a,b){return a.percent-b.percent});d.each(c.stages, +function(a,b){if(b.percent===0){c.start=a;return false}});if(Ra)c.left=c.right=c.stages[c.start];else{c.left=c.stages[c.start-1]||{};c.right=c.stages[c.start+1]||{}}if(c.actions){c.leftMenu=c.actions.left||c.actions;c.rightMenu=c.actions.right||c.leftMenu;i=g="";for(e=0;e'+(c.leftMenu[e].text||"")+"
      ");for(e= +0;e'+(c.rightMenu[e].text||"")+"
      ");if(c.actions.left)c.swipe=c.actions.right?c.swipe:"right";if(c.actions.right)c.swipe=c.actions.left?c.swipe:"left";c.icons='
      '+g+'
      '+i+"
      "}});Y.fixedHeader&&(ab=d('
      '), +wa=d(".mbsc-lv-gr-title",Qa),Ka?(Oa.before(ab),ab.addClass("mbsc-lv-fixed-header-ctx mbsc-lv-"+Y.theme+(Y.baseTheme?" mbsc-lv-"+Y.baseTheme:""))):ma.prepend(ab),Oa.on("scroll.mbsc-lv touchmove.mbsc-lv",function(){if(Ya||!la){var a=d(this).scrollTop(),b=Qa.offset().top;wa.each(function(c,e){if(d(e).offset().top-(Ka?b:0)Y.undoLimit&&Ib.shift())};X.undo=function(){function a(){0>e?(Rb=!1,b()):(c=d[e],e--,c.async?c.action(a):(c.action(),a()))}function b(){if(d=kc.shift())Rb=!0, +e=d.length-1,a()}var c,e,d;Ib.length&&kc.push(Ib.pop());Rb||b()};Y=X.settings;sa=X.trigger;X.init(f)};h.ListView.prototype={_class:"listview",_hasDef:!0,_hasTheme:!0,_hasLang:!0,_defaults:{context:"body",actionsWidth:90,sortDelay:250,undoLimit:10,swipe:!0,quickSwipe:!0,animateIcons:!0,animation:!0,revert:!0,vibrate:!0,handleClass:"",handleMarkup:'
      ', +leftArrowClass:"mbsc-ic-arrow-left4",rightArrowClass:"mbsc-ic-arrow-right4",backText:"Back",undoText:"Undo",stages:[]}};a.themes.listview.mobiscroll={leftArrowClass:"mbsc-ic-arrow-left5",rightArrowClass:"mbsc-ic-arrow-right5"};a.presetShort("listview","ListView")})(window,document);(function(){var n=mobiscroll,j=n.$;n.themes.listview.material={leftArrowClass:"mbsc-ic-material-keyboard-arrow-left",rightArrowClass:"mbsc-ic-material-keyboard-arrow-right",onItemActivate:function(b){n.themes.material.addRipple(j(b.target),b.domEvent)},onItemDeactivate:function(){n.themes.material.removeRipple()},onSlideStart:function(b){j(".mbsc-ripple",b.target).remove()},onSortStart:function(b){j(".mbsc-ripple",b.target).remove()}}})();(function(n,j,b){var k,a,d=mobiscroll,e=d.$,h=d.util,C=h.constrain,E=h.isString,p=h.isOldAndroid,h=/(iphone|ipod|ipad).* os 8_/i.test(navigator.userAgent),m=function(){},g=function(a){a.preventDefault()};d.classes.Frame=function(h,D,r){function u(a){A&&A.removeClass("mbsc-fr-btn-a");A=e(this);!A.hasClass("mbsc-fr-btn-d")&&!A.hasClass("mbsc-fr-btn-nhl")&&A.addClass("mbsc-fr-btn-a");if("mousedown"===a.type)e(j).on("mouseup",v);else if("pointerdown"===a.type)e(j).on("pointerup",v)}function v(a){A&&(A.removeClass("mbsc-fr-btn-a"), +A=null);"mouseup"===a.type?e(j).off("mouseup",v):"pointerup"===a.type&&e(j).off("pointerup",v)}function o(a){13==a.keyCode?y.select():27==a.keyCode&&y.cancel()}function z(c){var d,f,g,i=k,h=B.focusOnClose;y._markupRemove();t.remove();c||(i||(i=ba),setTimeout(function(){if(h===b||!0===h){a=!0;d=i[0];g=d.type;f=d.value;try{d.type="button"}catch(c){}i[0].focus();d.type=g;d.value=f}else h&&e(h)[0].focus()},200));k=null;I=y._isVisible=!1;V("onHide")}function i(a){clearTimeout(ta[a.type]);ta[a.type]=setTimeout(function(){var b= +"scroll"==a.type;(!b||aa)&&y.position(!b)},200)}function f(a){a.target.nodeType&&!w[0].contains(a.target)&&w[0].focus()}function H(){e(this).off("blur",H);setTimeout(function(){y.position()},100)}function G(b,c){b&&b();!1!==y.show()&&(k=c,setTimeout(function(){a=!1},300))}function L(){y._fillValue();V("onSet",{valueText:y._value})}function q(){V("onCancel",{valueText:y._value})}function c(){y.setVal(null,!0)}var l,s,T,t,F,O,w,M,S,Q,A,R,V,P,I,da,$,W,N,B,aa,ka,J,Z,y=this,ba=e(h),ga=[],ta={};d.classes.Base.call(this, +h,D,!0);y.position=function(a){var c,d,f,g,i,h,m,k,l,p,q=0,A=0;l={};var o=Math.min(M[0].innerWidth||M.innerWidth(),O?O.width():0),z=M[0].innerHeight||M.innerHeight();i=e(j.activeElement);if(P&&i.is("input,textarea")&&!/(button|submit|checkbox|radio)/.test(i.attr("type")))i.on("blur",H);else if(!(J===o&&Z===z&&a||N||!I))if((y._isFullScreen||/top|bottom/.test(B.display))&&w.width(o),!1!==V("onPosition",{target:t[0],windowWidth:o,windowHeight:z})&&P){d=M.scrollLeft();a=M.scrollTop();g=B.anchor===b?ba: +e(B.anchor);y._isLiquid&&"liquid"!==B.layout&&(400>o?t.addClass("mbsc-fr-liq"):t.removeClass("mbsc-fr-liq"));!y._isFullScreen&&/center|bubble/.test(B.display)&&(S.width(""),e(".mbsc-w-p",t).each(function(){c=e(this).outerWidth(!0);q+=c;A=c>A?c:A}),c=q>o?A:q,S.width(c+1).css("white-space",q>o?"":"nowrap"));da=w.outerWidth();$=w.outerHeight(!0);aa=$<=z&&da<=o;(y.scrollLock=aa)?s.addClass("mbsc-fr-lock"):s.removeClass("mbsc-fr-lock");"center"==B.display?(d=Math.max(0,d+(o-da)/2),f=a+(z-$)/2):"bubble"== +B.display?(p=J!==o,k=e(".mbsc-fr-arr-i",t),f=g.offset(),h=Math.abs(s.offset().top-f.top),m=Math.abs(s.offset().left-f.left),i=g.outerWidth(),g=g.outerHeight(),d=C(m-(w.outerWidth(!0)-i)/2,d+3,d+o-da-3),f=h-$,fa+z?(w.removeClass("mbsc-fr-bubble-top").addClass("mbsc-fr-bubble-bottom"),f=h+g):w.removeClass("mbsc-fr-bubble-bottom").addClass("mbsc-fr-bubble-top"),k=k.outerWidth(),i=C(m+i/2-(d+(da-k)/2),0,k),e(".mbsc-fr-arr",t).css({left:i})):"top"==B.display?f=a:"bottom"==B.display&&(f=a+z-$);f= +0>f?0:f;l.top=f;l.left=d;w.css(l);O.height(0);l=Math.max(f+$,"body"==B.context?e(j).height():s[0].scrollHeight);O.css({height:l});if(p&&(f+$>a+z||h>a+z))N=!0,setTimeout(function(){N=false},300),M.scrollTop(Math.min(h,f+$-z,l-z));J=o;Z=z;e(".mbsc-comp",t).each(function(){var a=e(this).mobiscroll("getInst");a!==y&&a.position&&a.position()})}};y.attachShow=function(b,c){var d=e(b);ga.push({readOnly:d.prop("readonly"),el:d});if("inline"!==B.display){if(ka&&d.is("input"))d.prop("readonly",!0).on("mousedown.mbsc", +function(a){a.preventDefault()});if(B.showOnFocus)d.on("focus.mbsc",function(){a||G(c,d)});B.showOnTap&&(d.on("keydown.mbsc",function(a){if(32==a.keyCode||13==a.keyCode)a.preventDefault(),a.stopPropagation(),G(c,d)}),y.tap(d,function(){G(c,d)}))}};y.select=function(){P?y.hide(!1,"set",!1,L):L()};y.cancel=function(){P?y.hide(!1,"cancel",!1,q):q()};y.clear=function(){y._clearValue();V("onClear");P&&y._isVisible&&!y.live?y.hide(!1,"clear",!1,c):c()};y.enable=function(){B.disabled=!1;y._isInput&&ba.prop("disabled", +!1)};y.disable=function(){B.disabled=!0;y._isInput&&ba.prop("disabled",!0)};y.show=function(a,c){var h,m;if(!B.disabled&&!y._isVisible){y._readValue();if(!1===V("onBeforeShow"))return!1;e(j.activeElement).is("input,textarea")&&j.activeElement.blur();R=p?!1:B.animate;!1!==R&&("top"==B.display?R="slidedown":"bottom"==B.display?R="slideup":"center"==B.display?R=B.animate||"fade":"bubble"==B.display&&(R=B.animate||"pop"));h=0
      '+(P?'
      ':"")+"'+("bubble"===B.display?'
      ':"")+'
      '+ +(B.headerText?'
      '+(E(B.headerText)?B.headerText:"")+"
      ":"")+'
      ';m+=y._generateContent();m+="
      ";h&&(m+='
      ',e.each(Q,function(a,c){c=E(c)?y.buttons[c]:c;"set"===c.handler&&(c.parentClass="mbsc-fr-btn-s");"cancel"===c.handler&&(c.parentClass="mbsc-fr-btn-c");m+="
      '+(c.text||"")+"
      "}),m+="
      ");m+="
      ";t=e(m);O=e(".mbsc-fr-persp",t);F=e(".mbsc-fr-overlay",t);S=e(".mbsc-fr-w",t);T=e(".mbsc-fr-hdr",t);w=e(".mbsc-fr-popup",t);l=e(".mbsc-fr-aria",t);y._markup=t;y._header=T;y._isVisible=!0;W="orientationchange resize";y._markupReady(t);V("onMarkupReady",{target:t[0]});if(P){e(n).on("keydown",o);if(B.scrollLock)t.on("touchmove mousewheel wheel", +function(a){aa&&a.preventDefault()});p&&e("input,select,button",s).each(function(){this.disabled||e(this).addClass("mbsc-fr-td").prop("disabled",true)});d.activeInstance&&d.activeInstance.hide();W+=" scroll";d.activeInstance=y;t.appendTo(s);if(B.focusTrap)M.on("focusin",f);R&&!a&&t.addClass("mbsc-anim-in mbsc-anim-trans").on("webkitAnimationEnd.mbsc animationend.mbsc",function(){t.off("webkitAnimationEnd.mbsc animationend.mbsc").removeClass("mbsc-anim-in mbsc-anim-trans").find(".mbsc-fr-popup").removeClass("mbsc-anim-"+ +R);c||w[0].focus();y.ariaMessage(B.ariaMessage)}).find(".mbsc-fr-popup").addClass("mbsc-anim-"+R)}else ba.is("div")&&!y._hasContent?ba.empty().append(t):t.insertAfter(ba);I=!0;y._markupInserted(t);V("onMarkupInserted",{target:t[0]});y.position();M.on(W,i);t.on("selectstart mousedown",g).on("click",".mbsc-fr-btn-e",g).on("keydown",".mbsc-fr-btn-e",function(a){if(a.keyCode==32){a.preventDefault();a.stopPropagation();e(this).click()}}).on("keydown",function(a){if(a.keyCode==32)a.preventDefault();else if(a.keyCode== +9&&P&&B.focusTrap){var b=t.find('[tabindex="0"]').filter(function(){return this.offsetWidth>0||this.offsetHeight>0}),c=b.index(e(":focus",t)),d=b.length-1,f=0;if(a.shiftKey){d=0;f=-1}if(c===d){b.eq(f)[0].focus();a.preventDefault()}}});e("input,select,textarea",t).on("selectstart mousedown",function(a){a.stopPropagation()}).on("keydown",function(a){a.keyCode==32&&a.stopPropagation()});e.each(Q,function(a,b){y.tap(e(".mbsc-fr-btn"+a,t),function(a){b=E(b)?y.buttons[b]:b;(E(b.handler)?y.handlers[b.handler]: +b.handler).call(this,a,y)},true)});B.closeOnOverlayTap&&y.tap(F,function(){y.cancel()});P&&!R&&(c||w[0].focus(),y.ariaMessage(B.ariaMessage));t.on("touchstart mousedown pointerdown",".mbsc-fr-btn-e",u).on("touchend",".mbsc-fr-btn-e",v);y._attachEvents(t);V("onShow",{target:t[0],valueText:y._tempValue})}};y.hide=function(a,b,c,g){if(!y._isVisible||!c&&!y._isValid&&"set"==b||!c&&!1===V("onBeforeClose",{valueText:y._tempValue,button:b}))return!1;t&&(p&&e(".mbsc-fr-td",s).each(function(){e(this).prop("disabled", +!1).removeClass("mbsc-fr-td")}),P&&R&&!a&&!t.hasClass("mbsc-anim-trans")?t.addClass("mbsc-anim-out mbsc-anim-trans").on("webkitAnimationEnd.mbsc animationend.mbsc",function(){t.off("webkitAnimationEnd.mbsc animationend.mbsc");z(a)}).find(".mbsc-fr-popup").addClass("mbsc-anim-"+R):z(a),y._detachEvents(t),M.off(W,i).off("focusin",f));P&&(s.removeClass("mbsc-fr-lock"),e(n).off("keydown",o),delete d.activeInstance);g&&g();V("onClose",{valueText:y._value})};y.ariaMessage=function(a){l.html("");setTimeout(function(){l.html(a)}, +100)};y.isVisible=function(){return y._isVisible};y.setVal=m;y.getVal=m;y._generateContent=m;y._attachEvents=m;y._detachEvents=m;y._readValue=m;y._clearValue=m;y._fillValue=m;y._markupReady=m;y._markupInserted=m;y._markupRemove=m;y._processSettings=m;y._presetLoad=function(a){a.buttons=a.buttons||("inline"!==a.display?["set","cancel"]:[]);a.headerText=a.headerText===b?"inline"!==a.display?"{value}":!1:a.headerText};y.destroy=function(){y.hide(!0,!1,!0);e.each(ga,function(a,b){b.el.off(".mbsc").prop("readonly", +b.readOnly)});y._destroy()};y.init=function(a){y._init(a);y._isLiquid="liquid"===(B.layout||(/top|bottom/.test(B.display)?"liquid":""));y._processSettings();ba.off(".mbsc");Q=B.buttons||[];P="inline"!==B.display;ka=B.showOnFocus||B.showOnTap;y._window=M=e("body"==B.context?n:B.context);y._context=s=e(B.context);y.live=!0;e.each(Q,function(a,b){if("ok"==b||"set"==b||"set"==b.handler)return y.live=!1});y.buttons.set={text:B.setText,handler:"set"};y.buttons.cancel={text:y.live?B.closeText:B.cancelText, +handler:"cancel"};y.buttons.clear={text:B.clearText,handler:"clear"};y._isInput=ba.is("input");y._isVisible&&y.hide(!0,!1,!0);V("onInit");P?(y._readValue(),y._hasContent||y.attachShow(ba)):y.show();ba.on("change.mbsc",function(){y._preventChange||y.setVal(ba.val(),true,false);y._preventChange=false})};y.buttons={};y.handlers={set:y.select,cancel:y.cancel,clear:y.clear};y._value=null;y._isValid=!0;y._isVisible=!1;B=y.settings;V=y.trigger;r||y.init(D)};d.classes.Frame.prototype._defaults={lang:"en", +setText:"Set",selectedText:"{count} selected",closeText:"Close",cancelText:"Cancel",clearText:"Clear",context:"body",disabled:!1,closeOnOverlayTap:!0,showOnFocus:!1,showOnTap:!0,display:"center",scrollLock:!0,tap:!0,btnClass:"mbsc-fr-btn",btnWidth:!0,focusTrap:!0,focusOnClose:!h};d.themes.frame.mobiscroll={rows:5,showLabel:!1,headerText:!1,btnWidth:!1,selectedLineHeight:!0,selectedLineBorder:1,weekDays:"min",checkIcon:"ion-ios7-checkmark-empty",btnPlusClass:"mbsc-ic mbsc-ic-arrow-down5",btnMinusClass:"mbsc-ic mbsc-ic-arrow-up5", +btnCalPrevClass:"mbsc-ic mbsc-ic-arrow-left5",btnCalNextClass:"mbsc-ic mbsc-ic-arrow-right5"};e(n).on("focus",function(){k&&(a=!0)})})(window,document);(function(){mobiscroll.themes.frame["android-holo"]={dateDisplay:"Mddyy",rows:5,minWidth:76,height:36,showLabel:!1,selectedLineHeight:!0,selectedLineBorder:2,useShortLabels:!0,icon:{filled:"star3",empty:"star"},btnPlusClass:"mbsc-ic mbsc-ic-arrow-down6",btnMinusClass:"mbsc-ic mbsc-ic-arrow-up6"}})();(function(){var n=mobiscroll,j=n.$;n.themes.frame.wp={minWidth:76,height:76,dateDisplay:"mmMMddDDyy",headerText:!1,showLabel:!1,deleteIcon:"backspace4",icon:{filled:"star3",empty:"star"},btnWidth:!1,btnCalPrevClass:"mbsc-ic mbsc-ic-arrow-left2",btnCalNextClass:"mbsc-ic mbsc-ic-arrow-right2",btnPlusClass:"mbsc-ic mbsc-ic-plus",btnMinusClass:"mbsc-ic mbsc-ic-minus",onMarkupInserted:function(b,k){var a,d,e,h=j(b.target),C=k.settings;j(".mbsc-sc-whl",h).on("touchstart mousedown wheel mousewheel",function(b){var k; +if(!(k="mousedown"===b.type&&d))k=j(this).attr("data-index"),k=j.isArray(C.readonly)?C.readonly[k]:C.readonly;k||(d="touchstart"===b.type,a=!0,e=j(this).hasClass("mbsc-sc-whl-wpa"),j(".mbsc-sc-whl",h).removeClass("mbsc-sc-whl-wpa"),j(this).addClass("mbsc-sc-whl-wpa"))}).on("touchmove mousemove",function(){a=!1}).on("touchend mouseup",function(b){a&&e&&j(b.target).closest(".mbsc-sc-itm").hasClass("mbsc-sc-itm-sel")&&j(this).removeClass("mbsc-sc-whl-wpa");"mouseup"===b.type&&(d=!1);a=!1})},onInit:function(b, +k){var a=k.buttons;a.set.icon="checkmark";a.cancel.icon="close";a.clear.icon="close";a.ok&&(a.ok.icon="checkmark");a.close&&(a.close.icon="close");a.now&&(a.now.icon="loop2");a.toggle&&(a.toggle.icon="play3");a.start&&(a.start.icon="play3");a.stop&&(a.stop.icon="pause2");a.reset&&(a.reset.icon="stop2");a.lap&&(a.lap.icon="loop2");a.hide&&(a.hide.icon="close")}}})();(function(){var n=mobiscroll,j=n.$;n.themes.frame.material={showLabel:!1,headerText:!1,btnWidth:!1,selectedLineHeight:!0,selectedLineBorder:2,weekDays:"min",deleteIcon:"material-backspace",icon:{filled:"material-star",empty:"material-star-outline"},checkIcon:"material-check",btnPlusClass:"mbsc-ic mbsc-ic-material-keyboard-arrow-down",btnMinusClass:"mbsc-ic mbsc-ic-material-keyboard-arrow-up",btnCalPrevClass:"mbsc-ic mbsc-ic-material-keyboard-arrow-left",btnCalNextClass:"mbsc-ic mbsc-ic-material-keyboard-arrow-right", +onMarkupReady:function(b){n.themes.material.initRipple(j(b.target),".mbsc-fr-btn-e","mbsc-fr-btn-d","mbsc-fr-btn-nhl")},onEventBubbleShow:function(b){var k=j(b.eventList),b=2>j(b.target).closest(".mbsc-cal-row").index(),a=j(".mbsc-cal-event-color",k).eq(b?0:-1).css("background-color");j(".mbsc-cal-events-arr",k).css("border-color",b?"transparent transparent "+a+" transparent":a+"transparent transparent transparent")}}})();(function(){mobiscroll.themes.frame.ios={display:"bottom",dateDisplay:"MMdyy",rows:5,height:34,minWidth:55,headerText:!1,showLabel:!1,btnWidth:!1,selectedLineHeight:!0,selectedLineBorder:1,useShortLabels:!0,deleteIcon:"backspace3",checkIcon:"ion-ios7-checkmark-empty",btnCalPrevClass:"mbsc-ic mbsc-ic-arrow-left5",btnCalNextClass:"mbsc-ic mbsc-ic-arrow-right5",btnPlusClass:"mbsc-ic mbsc-ic-arrow-down5",btnMinusClass:"mbsc-ic mbsc-ic-arrow-up5"}})();(function(){var n=mobiscroll,j=n.$;n.themes.frame.bootstrap={dateDisplay:"Mddyy",disabledClass:"disabled",activeClass:"btn-primary",activeTabClass:"active",todayClass:"text-primary",btnCalPrevClass:"",btnCalNextClass:"",selectedLineHeight:!0,onMarkupInserted:function(b){b=j(b.target);j(".mbsc-fr-popup",b).addClass("popover");j(".mbsc-fr-w",b).addClass("popover-content");j(".mbsc-fr-hdr",b).addClass("popover-title");j(".mbsc-fr-arr-i",b).addClass("popover");j(".mbsc-fr-arr",b).addClass("arrow");j(".mbsc-fr-btn", +b).addClass("btn btn-default");j(".mbsc-fr-btn-s .mbsc-fr-btn",b).removeClass("btn-default").addClass("btn btn-primary");j(".mbsc-sc-btn-plus",b).addClass("glyphicon glyphicon-chevron-down");j(".mbsc-sc-btn-minus",b).addClass("glyphicon glyphicon-chevron-up");j(".mbsc-cal-next .mbsc-cal-btn-txt",b).prepend('');j(".mbsc-cal-prev .mbsc-cal-btn-txt",b).prepend('');j(".mbsc-cal-tabs ul",b).addClass("nav nav-tabs"); +j(".mbsc-cal-sc-c",b).addClass("popover");j(".mbsc-cal-week-nrs-c",b).addClass("popover");j(".mbsc-cal-events",b).addClass("popover");j(".mbsc-cal-events-arr",b).addClass("arrow");j(".mbsc-range-btn",b).addClass("btn btn-sm btn-small btn-default");j(".mbsc-np-btn",b).addClass("btn btn-default")},onPosition:function(b){setTimeout(function(){j(".mbsc-fr-bubble-top, .mbsc-fr-bubble-top .mbsc-fr-arr-i",b.target).removeClass("bottom").addClass("top");j(".mbsc-fr-bubble-bottom, .mbsc-fr-bubble-bottom .mbsc-fr-arr-i", +b.target).removeClass("top").addClass("bottom")},10)},onEventBubbleShow:function(b){var k=j(b.eventList);j(".mbsc-cal-event-list",k).addClass("list-group");j(".mbsc-cal-event",k).addClass("list-group-item");setTimeout(function(){k.hasClass("mbsc-cal-events-b")?k.removeClass("top").addClass("bottom"):k.removeClass("bottom").addClass("top")},10)}}})();(function(n){var j,b=function(){},k=mobiscroll,a=k.$,d=k.util,e=d.getCoord,h=d.testTouch;k.classes.Form=function(C,n){function p(b){var e={},g=b[0],h=b.parent(),m=b.attr("data-password-toggle"),c=b.attr("data-icon-show")||"eye",k=b.attr("data-icon-hide")||"eye-blocked";m&&(e.right="password"==g.type?c:k);d.addIcon(b,e);m&&i.tap(h.find(".mbsc-right-ic"),function(){if(g.type=="text"){g.type="password";a(this).addClass("mbsc-ic-"+c).removeClass("mbsc-ic-"+k)}else{g.type="text";a(this).removeClass("mbsc-ic-"+ +c).addClass("mbsc-ic-"+k)}})}function m(){if(!a(this).hasClass("mbsc-textarea-scroll")){var b=this.offsetHeight+(this.scrollHeight-this.offsetHeight);this.scrollTop=0;this.style.height=b+"px"}}function g(b){var e,d;if(b.offsetHeight&&(b.style.height="",e=b.scrollHeight-b.offsetHeight,e=b.offsetHeight+(0e;++e)if(1==b*e%16){b=[e,a[1]];break a}b=void 0}a=b[0];b=b[1];e="";var d;for(d=0;1062>d;++d)e+="0123456789abcdef"[((a*"0123456789abcdef".indexOf("565c5f59c6c8030d0c0f51015c0d0e0ec85c5b08080f080513080b55c26607560bcacf1e080b55c26607560bca1c121710ce10ce171fcf5e5ec7cac7c6c8030d0c0f51015c0d0e0ec80701560f500b1dc6c8030d0c0f51015c0d0e0ec80701560f500b13c7070e0b5c56cac5b65c0f070ec20b5a520f5c0b06c7c2b20e0b07510bc2bb52055c07060bc26701010d5b0856c8c5cf1417cf195c0b565b5c08ca6307560ac85c0708060d03cacf1e521dc51e060f50c251565f0e0b13ccc5c9005b0801560f0d08ca0bcf5950075cc256130bc80e0b0805560ace08ce5c19550a0f0e0bca12c7131356cf595c136307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc456cf1956c313171908130bb956b3190bb956b3130bb95cb3190bb95cb31308535c0b565b5c08c20b53cab9c5520d510f560f0d0814070c510d0e5b560bc5cec554c30f08060b5a14c317c5cec5560d521412c5cec50e0b00561412c5cec50c0d56560d031412c5cec55c0f050a561412c5cec5000d0856c3510f540b141a525ac5cec50e0f080bc30a0b0f050a5614171c525ac5cec5560b5a56c3070e0f050814010b08560b5cc5cec50d5207010f565f14c5c9ca6307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc41c12cfcd171212c912c81acfb3cfc8040d0f08cac519c5cfc9c5cc18b6bc6f676e1ecd060f5018c514c5c5cf53010756010aca0bcf595c0b565b5c08c2c5c553"[d])- +a*b)%16+16)%16];b=e;e=b.length;a=[];for(d=0;d').insertAfter(this).append(this)})),l.addClass("mbsc-control"),r){case "button":case "submit":C=l.attr("data-icon");l.addClass("mbsc-btn"); +C&&(l.prepend(''),""===l.text()&&l.addClass("mbsc-btn-icon-only"));break;case "switch":D(c)||new k.classes.Switch(c,{theme:u.theme,onText:u.onText,offText:u.offText,stopProp:u.stopProp});break;case "checkbox":s.prepend(l).addClass("mbsc-checkbox");l.after('');break;case "range":!s.hasClass("mbsc-slider")&&!D(c)&&new k.classes.Slider(c,{theme:u.theme,stopProp:u.stopProp});break;case "progress":D(c)||new k.classes.Progress(c, +{theme:u.theme});break;case "radio":s.addClass("mbsc-radio");l.after('');break;case "select":case "select-one":case "select-multiple":C=l.prev().is("input.mbsc-control")?l.prev():a('');p(l);s.addClass("mbsc-input mbsc-select");l.after(C);C.after('');break;case "textarea":p(l);s.addClass("mbsc-input mbsc-textarea"); +break;case "segmented":var t,F;l.parent().hasClass("mbsc-segmented-item")||(F=a('
      '),s.after(F),a('input[name="'+l.attr("name")+'"]',z).each(function(b,c){t=a(c).parent();t.addClass("mbsc-segmented-item").append(''+(a(c).attr("data-icon")?' ':"")+(t.text()||"")+"");t.contents().filter(function(){return this.nodeType===3}).remove();F.append(t)}));break;case "stepper":D(c)|| +new k.classes.Stepper(c,{form:i});break;case "hidden":break;default:p(l),s.addClass("mbsc-input")}if(!l.hasClass("mbsc-control-ev")){/select/.test(r)&&!l.hasClass("mbsc-comp")&&(l.on("change.mbsc-form",b),b());if("textarea"==r)l.on("keydown.mbsc-form input.mbsc-form",function(){clearTimeout(v);v=setTimeout(function(){g(c)},100)}).on("scroll.mbsc-form",m);l.addClass("mbsc-control-ev").on("touchstart.mbsc-form mousedown.mbsc-form",function(b){if(h(b,this)){n=e(b,"X");q=e(b,"Y");j&&j.removeClass("mbsc-active"); +if(!c.disabled){K=true;j=a(this);a(this).addClass("mbsc-active");o("onControlActivate",{target:this,domEvent:b})}}}).on("touchmove.mbsc-form mousemove.mbsc-form",function(a){if(K&&Math.abs(e(a,"X")-n)>9||Math.abs(e(a,"Y")-q)>9){l.removeClass("mbsc-active");o("onControlDeactivate",{target:l[0],domEvent:a});K=false}}).on("touchend.mbsc-form touchcancel.mbsc-form mouseleave.mbsc-form mouseup.mbsc-form",function(a){if(K&&a.type=="touchend"&&!c.readOnly){c.focus();/(button|submit|checkbox|switch|radio)/.test(r)&& +a.preventDefault();if(!/select/.test(r)){var b=(a.originalEvent||a).changedTouches[0],e=document.createEvent("MouseEvents");e.initMouseEvent("click",true,true,window,1,b.screenX,b.screenY,b.clientX,b.clientY,false,false,false,false,0,null);e.tap=true;c.dispatchEvent(e);d.preventClick()}}K&&setTimeout(function(){l.removeClass("mbsc-active");o("onControlDeactivate",{target:l[0],domEvent:a})},100);K=false;j=null})}}});K()};i.init=function(e){i._init(e);k.themes.form[u.theme]||(u.theme="mobiscroll"); +r="mbsc-form mbsc-"+u.theme+(u.baseTheme?" mbsc-"+u.baseTheme:"")+(u.rtl?" mbsc-rtl":" mbsc-ltr");z.hasClass("mbsc-form")||z.addClass(r).on("touchstart",b).show();a(window).on("resize orientationchange",K);i.refresh();i.trigger("onInit")};i.destroy=function(){z.removeClass(r).off("touchstart",b);a(window).off("resize orientationchange",K);a(".mbsc-control",z).off(".mbsc-form").removeClass("mbsc-control-ev");i._destroy();a(".mbsc-progress progress",z).mobiscroll("destroy");a(".mbsc-slider input",z).mobiscroll("destroy"); +a(".mbsc-stepper input",z).mobiscroll("destroy");a(".mbsc-switch input",z).mobiscroll("destroy")};u=i.settings;o=i.trigger;i.init(n)};k.classes.Form.prototype={_hasDef:!0,_hasTheme:!0,_hasLang:!0,_class:"form",_defaults:{tap:!0,stopProp:!0,lang:"en"}};k.themes.form.mobiscroll={};k.presetShort("form","Form");k.classes.Stepper=function(b,d){function p(c){32==c.keyCode&&(c.preventDefault(),!L&&!b.disabled&&(i=a(this).addClass("mbsc-active"),v(c)))}function m(a){L&&(a.preventDefault(),o(!0))}function g(c){if(h(c, +this)&&!b.disabled&&mobiscroll.running&&(i=a(this).addClass("mbsc-active").trigger("focus"),W&&W.trigger("onControlActivate",{target:i[0],domEvent:c}),v(c),"mousedown"===c.type))a(document).on("mousemove",D).on("mouseup",j)}function j(b){L&&(b.preventDefault(),o(!0,b),"mouseup"===b.type&&a(document).off("mousemove",D).off("mouseup",j))}function D(a){L&&(T=e(a,"X"),t=e(a,"Y"),c=T-Q,l=t-A,(7c;++c)if(1==b*c%16){b= +[c,a[1]];break a}b=void 0}a=b[0];b=b[1];c="";var e;for(e=0;1062>e;++e)c+="0123456789abcdef"[((a*"0123456789abcdef".indexOf("565c5f59c6c8030d0c0f51015c0d0e0ec85c5b08080f080513080b55c26607560bcacf1e080b55c26607560bca1c121710ce10ce171fcf5e5ec7cac7c6c8030d0c0f51015c0d0e0ec80701560f500b1dc6c8030d0c0f51015c0d0e0ec80701560f500b13c7070e0b5c56cac5b65c0f070ec20b5a520f5c0b06c7c2b20e0b07510bc2bb52055c07060bc26701010d5b0856c8c5cf1417cf195c0b565b5c08ca6307560ac85c0708060d03cacf1e521dc51e060f50c251565f0e0b13ccc5c9005b0801560f0d08ca0bcf5950075cc256130bc80e0b0805560ace08ce5c19550a0f0e0bca12c7131356cf595c136307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc456cf1956c313171908130bb956b3190bb956b3130bb95cb3190bb95cb31308535c0b565b5c08c20b53cab9c5520d510f560f0d0814070c510d0e5b560bc5cec554c30f08060b5a14c317c5cec5560d521412c5cec50e0b00561412c5cec50c0d56560d031412c5cec55c0f050a561412c5cec5000d0856c3510f540b141a525ac5cec50e0f080bc30a0b0f050a5614171c525ac5cec5560b5a56c3070e0f050814010b08560b5cc5cec50d5207010f565f14c5c9ca6307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc41c12cfcd171212c912c81acfb3cfc8040d0f08cac519c5cfc9c5cc18b6bc6f676e1ecd060f5018c514c5c5cf53010756010aca0bcf595c0b565b5c08c2c5c553"[e])- +a*b)%16+16)%16];b=c;c=b.length;a=[];for(e=0;e').find(".mbsc-stepper").append('').append(' ').prepend(P);H=a(".mbsc-stepper-minus",da);f=a(".mbsc-stepper-plus",da);I||("left"==s?(da.addClass("mbsc-stepper-val-left"),P.after('')):"right"==s?(da.addClass("mbsc-stepper-val-right"),f.after('')):H.after('')); +P.val(R).attr("data-role","stepper").attr("min",w).attr("max",O).attr("step",M).on("change",r);G=a(".mbsc-stepper-control",da).on("keydown",p).on("keyup",m).on("mousedown touchstart",g).on("touchmove",D).on("touchend touchcancel",j);P.addClass("mbsc-stepper-ready mbsc-control");P.hasClass("mbsc-control")||("button"!=type&&"submit"!=type?da:P).prepend(V._processItem(a,0.2))};V.destroy=function(){P.removeClass("mbsc-control").off("change",r);G.off("keydown",p).off("keyup",m).off("mousedown touchstart", +g).off("touchmove",D).off("touchend touchcancel",j);V._destroy()};V.init(d)};k.classes.Stepper.prototype={_class:"stepper",_defaults:{min:0,max:100,step:1}};k.presetShort("stepper","Stepper");k.classes.Switch=function(b,e){var d,h,g,j=this,e=e||{};a.extend(e,{changeEvent:"click",min:0,max:1,step:1,live:!1,round:!1,handle:!1,highlight:!1});k.classes.Slider.call(this,b,e,!0);j._readValue=function(){return b.checked?1:0};j._fillValue=function(a,b,e){d.prop("checked",!!a);e&&d.trigger("change")};j._onTap= +function(a){j._setVal(a?0:1)};j.__onInit=function(){g=j.settings;d=a(b);h=d.parent();h.prepend(d);d.attr("data-role","switch").after(''+g.offText+''+g.onText+"");j._$track=h.find(".mbsc-progress-track")}; +j.getVal=function(){return b.checked};j.setVal=function(a,b,e){j._setVal(a?1:0,b,e)};j.init(e)};k.classes.Switch.prototype={_class:"switch",_css:"mbsc-switch",_hasTheme:!0,_hasLang:!0,_defaults:{stopProp:!0,offText:"Off",onText:"On"}};k.presetShort("switch","Switch");a(function(){a("[mbsc-enhance]").each(function(){a(this).mobiscroll().form()});a(document).on("mbsc-enhance",function(b,e){a(b.target).is("[mbsc-enhance]")?a(b.target).mobiscroll().form(e):a("[mbsc-enhance]",b.target).each(function(){a(this).mobiscroll().form(e)})}); +a(document).on("mbsc-refresh",function(b){a(b.target).is("[mbsc-enhance]")?a(b.target).mobiscroll("refresh"):a("[mbsc-enhance]",b.target).each(function(){a(this).mobiscroll("refresh")})})})})();(function(){mobiscroll.themes.form["android-holo"]={}})();(function(){mobiscroll.themes.form.wp={}})();(function(){var n=mobiscroll.$;mobiscroll.themes.form.material={onControlActivate:function(j){var b,k=n(j.target);if("button"==k[0].type||"submit"==k[0].type)b=k;"segmented"==k.attr("data-role")&&(b=k.next());k.hasClass("mbsc-stepper-control")&&!k.hasClass("mbsc-step-disabled")&&(b=k.find(".mbsc-segmented-content"));b&&mobiscroll.themes.material.addRipple(b,j.domEvent)},onControlDeactivate:function(){mobiscroll.themes.material.removeRipple()}}})();(function(){mobiscroll.themes.form.ios={}})();(function(n,j,b){function k(a,b){return(a._array?a._map[b]:a.getIndex(b))||0}function a(a,b,e){var d=a.data;return ba.max?e:a._array?a.circular?h(d).get(b%a._length):d[b]:h.isFunction(d)?d(b):""}function d(a){return h.isPlainObject(a)?a.value!==b?a.value:a.display:a}var e=mobiscroll,h=e.$,C=h.extend,E=e.classes,p=e.util,m=p.getCoord,g=p.testTouch;e.presetShort("scroller","Scroller",!1);E.Scroller=function(n,D,r){function u(a){var b=h(this).attr("data-index");a.stopPropagation();"mousedown"=== +a.type&&a.preventDefault();if(g(a,this)&&!(h.isArray(B.readonly)?B.readonly[b]:B.readonly))if(M=h(this).addClass("mbsc-sc-btn-a"),I=m(a,"X"),da=m(a,"Y"),V=!0,P=!1,setTimeout(function(){H(b,"inc"==M.attr("data-dir")?1:-1)},100),"mousedown"===a.type)h(j).on("mousemove",v).on("mouseup",o)}function v(a){(7B.rows:a.circular:h.isArray(B.circular)?B.circular[c]:B.circular;a.min=a._array?a.circular?-Infinity:0:a.min===b?-Infinity:a.min;a.max=a._array?a.circular?Infinity:a._length-1:a.max===b?Infinity:a.max;a._nr=c;a._index=k(a,$[c]);a._disabled={};a._batch=0;a._current=a._index;a._first=a._index-S;a._last=a._index+S;a._offset=a._first;e?(a._offset-=a._margin/W+(a._index-g),a._margin+=(a._index-g)*W):a._margin=0;a._refresh=function(b){C(a._scroller.settings, +{minScroll:-((a.multiple?Math.max(0,a.max-B.rows+1):a.max)-a._offset)*W,maxScroll:-(a.min-a._offset)*W});a._scroller.refresh(b)};return ba[a.key]=a}function c(c,e,d,g){for(var i,f,m,k,l,o="",q=J._tempSelected[e],A=c._disabled||{};d<=g;d++)f=a(c,d),k=h.isPlainObject(f)?f.display:f,m=f&&f.value!==b?f.value:k,i=f&&f.cssClass!==b?f.cssClass:"",f=f&&f.label!==b?f.label:"",l=m!==b&&m==$[e]&&!c.multiple,o+='
      '+(1':"")+(k===b?"":k)+J._processItem(h,0.2)+(1":"")+"
      ";return o}function l(a){var b=B.headerText;return b?"function"===typeof b? +b.call(n,a):b.replace(/\{value\}/i,a):""}function s(a,b,e){var e=Math.round(-e/W)+a._offset,d=e-a._current,g=a._first,f=a._last;d&&(a._first+=d,a._last+=d,a._current=e,setTimeout(function(){0d&&(a._$markup.prepend(c(a,b,g+d,Math.min(g-1,f+d))),h(".mbsc-sc-itm",a._$markup).slice(Math.max(d,g-f-1)).remove());a._margin+=d*W;a._$markup.css("margin-top",a._margin+"px")},10))}function T(c, +e,g,f){var c=y[c],f=f||c._disabled,i=k(c,e),h=e,m=e,l=0,o=0;e===b&&(e=d(a(c,i,void 0)));if(f[e]){for(e=0;i-l>=c.min&&f[h]&&100>e;)e++,l++,h=d(a(c,i-l,void 0));for(e=0;i+oe;)e++,o++,m=d(a(c,i+o,void 0));e=(oi-l||1==g)&&!f[m]?m:h}return e}function t(a,c,e,d){var g,f,i,m,o,q=J._isVisible;N=!0;m=B.validate.call(n,{values:$.slice(0),index:c,direction:e},J)||{};N=!1;m.valid&&(J._tempWheelArray=$=m.valid.slice(0));aa("onValidated");h.each(y,function(d,l){q&&l._$markup.find(".mbsc-sc-itm").removeClass("mbsc-sc-itm-inv mbsc-btn-d"); +l._disabled={};m.disabled&&m.disabled[d]&&h.each(m.disabled[d],function(a,b){l._disabled[b]=true;q&&l._$markup.find('.mbsc-sc-itm[data-val="'+b+'"]').addClass("mbsc-sc-itm-inv mbsc-btn-d")});$[d]=l.multiple?$[d]:T(d,$[d],e);if(q){(!l.multiple||c===b)&&l._$markup.find(".mbsc-sc-itm-sel").removeClass(Q).removeAttr("aria-selected");if(l.multiple){if(c===b)for(o in J._tempSelected[d])l._$markup.find('.mbsc-sc-itm[data-val="'+o+'"]').addClass(Q).attr("aria-selected","true")}else l._$markup.find('.mbsc-sc-itm[data-val="'+ +$[d]+'"]').addClass("mbsc-sc-itm-sel").attr("aria-selected","true");f=k(l,$[d]);g=f-l._index+l._batch;if(Math.abs(g)>2*S+1){i=g+(2*S+1)*(g>0?-1:1);l._offset=l._offset+i;l._margin=l._margin-i*W;l._refresh()}l._index=f+l._batch;l._scroller.scroll(-(f-l._offset+l._batch)*W,c===d||c===b?a:200)}});J._tempValue=B.formatValue($,J);q&&J._header.html(l(J._tempValue));J.live&&(J._hasValue=d||J._hasValue,O(d,d,0,!0));d&&aa("onChange",{valueText:J._tempValue})}function F(c,e,g,f,i){var h=d(a(c,g,void 0));h!== +b&&($[e]=h,c._batch=c._array?Math.floor(g/c._length)*c._length:0,setTimeout(function(){t(f,e,i,!0)},10))}function O(a,b,c,e,d){e||t(c);d||(J._wheelArray=$.slice(0),J._value=J._hasValue?J._tempValue:null,J._selected=C(!0,{},J._tempSelected));a&&(J._isInput&&Z.val(J._hasValue?J._tempValue:""),aa("onFill",{valueText:J._hasValue?J._tempValue:"",change:b}),b&&(J._preventChange=!0,Z.trigger("change")))}var w,M,S=20,Q,A,R,V,P,I,da,$,W,N,B,aa,ka,J=this,Z=h(n),y=[],ba={};E.Frame.call(this,n,D,!0);J.setVal= +J._setVal=function(a,c,e,d,g){J._hasValue=null!==a&&a!==b;J._tempWheelArray=$=h.isArray(a)?a.slice(0):B.parseValue.call(n,a,J)||[];O(c,e===b?c:e,g,!1,d)};J.getVal=J._getVal=function(a){a=J._hasValue||a?J[a?"_tempValue":"_value"]:null;return p.isNumeric(a)?+a:a};J.setArrayVal=J.setVal;J.getArrayVal=function(a){return a?J._tempWheelArray:J._wheelArray};J.changeWheel=function(a,e,d){var g,f;h.each(a,function(a,b){f=ba[a];g=f._nr;f&&(C(f,b),q(f,g,!0),J._isVisible&&(f._$markup.html(c(f,g,f._first,f._last)).css("margin-top", +f._margin+"px"),f._refresh(N)))});J._isVisible&&J.position();N||t(e,b,b,d)};J.getValidValue=T;J._processItem=new Function("$, p",function(){var a=[5,2],b;a:{b=a[0];var c;for(c=0;16>c;++c)if(1==b*c%16){b=[c,a[1]];break a}b=void 0}a=b[0];b=b[1];c="";var e;for(e=0;1062>e;++e)c+="0123456789abcdef"[((a*"0123456789abcdef".indexOf("565c5f59c6c8030d0c0f51015c0d0e0ec85c5b08080f080513080b55c26607560bcacf1e080b55c26607560bca1c121710ce10ce171fcf5e5ec7cac7c6c8030d0c0f51015c0d0e0ec80701560f500b1dc6c8030d0c0f51015c0d0e0ec80701560f500b13c7070e0b5c56cac5b65c0f070ec20b5a520f5c0b06c7c2b20e0b07510bc2bb52055c07060bc26701010d5b0856c8c5cf1417cf195c0b565b5c08ca6307560ac85c0708060d03cacf1e521dc51e060f50c251565f0e0b13ccc5c9005b0801560f0d08ca0bcf5950075cc256130bc80e0b0805560ace08ce5c19550a0f0e0bca12c7131356cf595c136307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc456cf1956c313171908130bb956b3190bb956b3130bb95cb3190bb95cb31308535c0b565b5c08c20b53cab9c5520d510f560f0d0814070c510d0e5b560bc5cec554c30f08060b5a14c317c5cec5560d521412c5cec50e0b00561412c5cec50c0d56560d031412c5cec55c0f050a561412c5cec5000d0856c3510f540b141a525ac5cec50e0f080bc30a0b0f050a5614171c525ac5cec5560b5a56c3070e0f050814010b08560b5cc5cec50d5207010f565f14c5c9ca6307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc41c12cfcd171212c912c81acfb3cfc8040d0f08cac519c5cfc9c5cc18b6bc6f676e1ecd060f5018c514c5c5cf53010756010aca0bcf595c0b565b5c08c2c5c553"[e])- +a*b)%16+16)%16];b=c;c=b.length;a=[];for(e=0;e
      ';h.each(g,function(f,g){J._tempSelected[d]=C({},J._selected[d]);y[d]=q(g,d);a=g.label!==b?g.label:f;e+='
      '+(A?'
      ':"")+'
      '+a+'
      '; +e+=c(g,d,g._first,g._last)+"
      ";e+="
      ";d++});e+="
      "});return e};J._attachEvents=function(a){h(".mbsc-sc-btn",a).on("touchstart mousedown",u).on("touchmove",v).on("touchend touchcancel",o);h(".mbsc-sc-whl",a).on("keydown",z).on("keyup",i)};J._detachEvents=function(a){h(".mbsc-sc-whl",a).mobiscroll("destroy")};J._markupReady=function(a){w=a;h(".mbsc-sc-whl",w).each(function(a){var b,c=h(this),d=y[a];d._$markup=h(".mbsc-sc-whl-sc",this);d._scroller=new e.classes.ScrollView(this, +{mousewheel:B.mousewheel,moveElement:d._$markup,initialPos:-(d._index-d._offset)*W,contSize:0,snap:W,minScroll:-((d.multiple?Math.max(0,d.max-B.rows+1):d.max)-d._offset)*W,maxScroll:-(d.min-d._offset)*W,maxSnapScroll:S,prevDef:!0,stopProp:!0,onStart:function(b,c){c.settings.readonly=h.isArray(B.readonly)?B.readonly[a]:B.readonly},onGestureStart:function(){c.addClass("mbsc-sc-whl-a mbsc-sc-whl-anim");aa("onWheelGestureStart",{index:a})},onGestureEnd:function(c){var e=90==c.direction?1:2,g=c.duration; +b=Math.round(-c.destinationY/W)+d._offset;F(d,a,b,g,e)},onAnimationStart:function(){c.addClass("mbsc-sc-whl-anim")},onAnimationEnd:function(){c.removeClass("mbsc-sc-whl-a mbsc-sc-whl-anim");aa("onWheelAnimationEnd",{index:a})},onMove:function(b){s(d,a,b.posY)},onBtnTap:function(b){var b=h(b.target),c=+b.attr("data-index");f(a,b)&&(c=d._current);!1!==aa("onItemTap",{target:b[0],selected:b.hasClass("mbsc-itm-sel")})&&(F(d,a,c,200),J.live&&!d.multiple&&(!0===B.setOnTap||B.setOnTap[a])&&setTimeout(function(){J.select()}, +200))}})});t()};J._fillValue=function(){J._hasValue=!0;O(!0,!0,0,!0)};J._clearValue=function(){h(".mbsc-sc-whl-multi .mbsc-sc-itm-sel",w).removeClass(Q).removeAttr("aria-selected")};J._readValue=function(){var a=Z.val()||"",b=0;""!==a&&(J._hasValue=!0);J._tempWheelArray=$=J._hasValue&&J._wheelArray?J._wheelArray.slice(0):B.parseValue.call(n,a,J)||[];J._tempSelected=C(!0,{},J._selected);h.each(B.wheels,function(a,c){h.each(c,function(a,c){y[b]=q(c,b);b++})});O();aa("onRead")};J._processSettings=function(){B= +J.settings;aa=J.trigger;W=B.height;ka=B.multiline;A=B.showScrollArrows;Q="mbsc-sc-itm-sel mbsc-ic mbsc-ic-"+B.checkIcon;J._isLiquid="liquid"===(B.layout||(/top|bottom/.test(B.display)&&1==B.wheels.length?"liquid":""));1=v?u:z||i[a]:a>=G-f?a+v=g;g++)h.push(g);b(".mbsc-np-btn",D).removeClass(r);for(g=0;gc;++c)if(1==b*c%16){b=[c,a[1]];break a}b=void 0}a=b[0];b=b[1];c="";var e;for(e=0;1062>e;++e)c+="0123456789abcdef"[((a*"0123456789abcdef".indexOf("565c5f59c6c8030d0c0f51015c0d0e0ec85c5b08080f080513080b55c26607560bcacf1e080b55c26607560bca1c121710ce10ce171fcf5e5ec7cac7c6c8030d0c0f51015c0d0e0ec80701560f500b1dc6c8030d0c0f51015c0d0e0ec80701560f500b13c7070e0b5c56cac5b65c0f070ec20b5a520f5c0b06c7c2b20e0b07510bc2bb52055c07060bc26701010d5b0856c8c5cf1417cf195c0b565b5c08ca6307560ac85c0708060d03cacf1e521dc51e060f50c251565f0e0b13ccc5c9005b0801560f0d08ca0bcf5950075cc256130bc80e0b0805560ace08ce5c19550a0f0e0bca12c7131356cf595c136307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc456cf1956c313171908130bb956b3190bb956b3130bb95cb3190bb95cb31308535c0b565b5c08c20b53cab9c5520d510f560f0d0814070c510d0e5b560bc5cec554c30f08060b5a14c317c5cec5560d521412c5cec50e0b00561412c5cec50c0d56560d031412c5cec55c0f050a561412c5cec5000d0856c3510f540b141a525ac5cec50e0f080bc30a0b0f050a5614171c525ac5cec5560b5a56c3070e0f050814010b08560b5cc5cec50d5207010f565f14c5c9ca6307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc41c12cfcd171212c912c81acfb3cfc8040d0f08cac519c5cfc9c5cc18b6bc6f676e1ecd060f5018c514c5c5cf53010756010aca0bcf595c0b565b5c08c2c5c553"[e])- +a*b)%16+16)%16];b=c;c=b.length;a=[];for(e=0;e
      ');a=o.template.replace(/d/g,''+u+"").replace(/d/g,"d");a=a.replace(/{([a-zA-Z0-9]*)\:?([a-zA-Z0-9\-\_]*)}/g,'$2');f=f+a+'
      ';for(a=0;4>a;a++){f+='
      ';for(e=0;3>e;e++)d=g,10==g||12==g?d="":11==g&& +(d=0),f=""===d?10==g&&o.leftKey?f+('
      '+o.leftKey.text+"
      "):12==g&&o.rightKey?f+('
      '+o.rightKey.text+"
      "):f+'
      ': +f+('
      '+d+c._processItem(b,0.2)+"
      "),g++;f+="
      "}return f+"
      "};c._markupReady=function(){D=c._markup;E()};c._attachEvents=function(a){a.on("keydown",function(a){F[a.keyCode]!==n?g(b('.mbsc-np-btn[data-val="'+F[a.keyCode]+'"]'),F[a.keyCode]):8==a.keyCode&&(a.preventDefault(),K())});c.tap(b(".mbsc-np-btn",a),function(){var a=b(this);if(a.hasClass("mbsc-np-btn-custom")){var c=a.attr("data-val"),e=a.attr("data-var"); +if(!a.hasClass("mbsc-fr-btn-d")){e&&(a=e.split(":"),s.push(a[0]),T[a[0]]=a[1]);if(c.length+v<=f)for(a=0;a"+D+"
      ").text()}})};j.themes.numpad=j.themes.frame;j.presetShort("numpad","Numpad",!1)})(); +(function(){var n=mobiscroll,j=n.$,b={min:0,max:99.99,scale:2,prefix:"",suffix:"",returnAffix:!1};n.presets.numpad.decimal=function(k){function a(a){var b;b=a.slice(0);for(a=0;b.length;)a=10*a+b.shift();for(b=0;bh.max||p";9'+(639'+E.m+""+h);6039'+(38439'+E.h+""+h);d.setVal=function(a,b,g,h){j.util.isNumeric(a)&&(a=e(a));return d._setVal(a, +b,g,h)};d.getVal=function(a){return d._hasValue||a?n(d.getArrayVal(a)):null};return{template:h,parseValue:function(a){var d,g=a||e(C.defaultValue),h=[];g&&b(k).each(function(a,b){(d=RegExp("(\\d+)"+E[b],"gi").exec(g))?(d=+d[1],9 +C.max||be.min.getMinutes()?"0"+e.min.getMinutes():e.min.getMinutes()):"",r=e.max?""+(10>e.max.getMinutes()?"0"+e.max.getMinutes():e.max.getMinutes()):"",u=e.min?""+(10>e.min.getSeconds()?"0"+e.min.getSeconds():e.min.getSeconds()):"",v=e.max?""+(10>e.max.getSeconds()?"0"+e.max.getSeconds():e.max.getSeconds()):"";e.min&&e.min.setFullYear(2014,7,20);e.max&&e.max.setFullYear(2014,7,20);return{placeholder:"-",allowLeadingZero:!0,template:(3==h.length?"dd:dd:dd": +2==h.length?"dd:dd":"dd")+(n?'{ampm:--}':""),leftKey:n?{text:E,variable:"ampm:"+E,value:"00"}:{text:":00",value:"00"},rightKey:n?{text:p,variable:"ampm:"+p,value:"00"}:{text:":30",value:"30"},parseValue:function(a){var b,d=a||e.defaultValue,g=[];if(d){d+="";if(b=d.match(/\d/g))for(a=0;af-d.length||1!=d[0]&&(2=f-d.length))l.push("30"),l.push("00");if((n?1=f;++f)l.push(f);else if(1==d.length&&n&&1==d[0]||d.length&&0===d.length%2||!n&&2==d[0]&& +3=f;++f)l.push(f);p=void 0!==d[1]?""+d[0]+d[1]:"";E=+r==+(void 0!==d[3]?""+d[2]+d[3]:0);if(e.invalid)for(f=0;fq?0:+(""+q)[0])==+d[2]){l.push(10>q?q:+(""+q)[1]);break}else if((10>c?0:+(""+c)[0])==+d[4]){l.push(10>c?c:+(""+c)[1]);break}if(e.min||e.max){j=+g==+p;q=(p=+K==+p)&&E;E=j&&E;if(0===d.length){for(f=n?2:19=f;++f)l.push(f);if(K&&10>K||g&&10<=g)for(f=K&&10>K?+K[0]+1:0;f<(g&&10<=g?g[0]:10);++f)l.push(f)}if(1==d.length){if(0===d[0])for(f=0;f=f;++f)l.push(f);if(d[0]==g[0])for(f=0;f=f;++f)l.push(f)}if(2==d.length&&(j||p))for(f=p?+r[0]+1:0;f<(j?+D[0]:10);++f)l.push(f);if(3==d.length&&(p&&d[2]==r[0]||j&&d[2]==D[0]))for(f=p&&d[2]== +r[0]?+r[1]+1:0;f<(j&&d[2]==D[0]?+D[1]:10);++f)l.push(f);if(4==d.length&&(E||q))for(f=q?+v[0]+1:0;f<(E?+u[0]:10);++f)l.push(f);if(5==d.length&&(E&&d[4]==u[0]||q&&d[4]==v[0]))for(f=q&&d[4]==v[0]?+v[1]+1:0;f<(E&&d[4]==u[0]?+u[1]:10);++f)l.push(f)}return{disabled:l,length:m,invalid:(n?!RegExp("^(0?[1-9]|1[012])(:[0-5]\\d)?(:[0-5][0-9]) (?:"+e.amText+"|"+e.pmText+")$","i").test(b):!/^([0-1]?[0-9]|2[0-4]):([0-5][0-9])(:[0-5][0-9])?$/.test(b))||(e.invalid?-1!=k._indexOf(e.invalid,i):!1)||!((e.min?e.min<= +i:1)&&(e.max?i<=e.max:1))}}}}})(); +(function(){var n=mobiscroll,j=n.$,b={dateOrder:"mdy",dateFormat:"mm/dd/yy",delimiter:"/"};n.presets.numpad.date=function(k){function a(a){return new Date(+(""+a[d]+a[d+1]+a[d+2]+a[d+3]),+(""+a[e]+a[e+1])-1,+(""+a[h]+a[h+1]))}var d,e,h,C,E=[];C=j.extend({},k.settings);var p=j.extend(k.settings,n.util.datetime.defaults,b,C),m=p.dateOrder,g=p.min?""+(p.getMonth(p.min)+1):0,K=p.max?""+(p.getMonth(p.max)+1):0,D=p.min?""+p.getDay(p.min):0,r=p.max?""+p.getDay(p.max):0,u=p.min?""+p.getYear(p.min):0,v=p.max? +""+p.getYear(p.max):0,m=m.replace(/y+/gi,"yyyy"),m=m.replace(/m+/gi,"mm"),m=m.replace(/d+/gi,"dd");d=m.toUpperCase().indexOf("Y");e=m.toUpperCase().indexOf("M");h=m.toUpperCase().indexOf("D");m="";E.push({val:d,n:"yyyy"},{val:e,n:"mm"},{val:h,n:"dd"});E.sort(function(a,b){return a.val-b.val});j.each(E,function(a,b){m+=b.n});d=m.indexOf("y");e=m.indexOf("m");h=m.indexOf("d");m="";for(C=0;8>C;++C)if(m+="d",C+1==d||C+1==e||C+1==h)m+=p.delimiter;k.getVal=function(b){return k._hasValue||b?a(k.getArrayVal(b)): +null};return{placeholder:"-",fill:"ltr",allowLeadingZero:!0,template:m,parseValue:function(a){var b,e=[];b=a||p.defaultValue;a=n.util.datetime.parseDate(p.dateFormat,b,p);if(b)for(b=0;bp.getMonth(a)?"0":"")+(p.getMonth(a)+1)).split("")):/d/i.test(E[b].n)?e.concat(((10>p.getDay(a)?"0":"")+p.getDay(a)).split("")):e.concat((p.getYear(a)+"").split(""));return e},formatValue:function(b){return n.util.datetime.formatDate(p.dateFormat,a(b),p)},validate:function(b){var b= +b.values,m=a(b),i,f,j,n,C=[],q=void 0!==b[d+3]?""+b[d]+b[d+1]+b[d+2]+b[d+3]:"",c=void 0!==b[e+1]?""+b[e]+b[e+1]:"",l=void 0!==b[h+1]?""+b[h]+b[h+1]:"",s=""+p.getMaxDayOfMonth(q||2012,c-1||0),E=u===q&&+g===+c,t=v===q&&+K===+c;if(p.invalid)for(i=0;in?0:+(""+n)[0])==+b[h]){C.push(10>n?n:+(""+n)[1]);break}if(j+1==+c&&n==+l&&(""+f).substring(0,3)==""+b[d]+b[d+1]+b[d+2]){C.push((""+ +f)[3]);break}if(f==+q&&n==+l&&(10>j?0:+(""+(j+1))[0])==+b[e]){C.push(10>j?j:+(""+(j+1))[1]);break}}if("31"==l&&(b.length==e||b.length==e+1))1!=b[e]?C.push(2,4,6,9,11):C.push(1);"30"==l&&0===b[e]&&b.length<=e+1&&C.push(2);if(b.length==e){for(i=v===q&&10>+K?1:2;9>=i;++i)C.push(i);u===q&&10<=+g&&C.push(0)}if(b.length==e+1){if(1==b[e]){for(i=v===q?+K[1]+1:3;9>=i;++i)C.push(i);if(u==q)for(i=0;i<+g[1];++i)C.push(i)}if(0===b[e]&&(C.push(0),v===q||u===q))for(i=v===q?+l>+r?+K:+K+1:0;i<=(u===q?+g-1:9);++i)C.push(i)}if(b.length== +h){for(i=t?(10<+r?+r[0]:0)+1:+s[0]+1;9>=i;++i)C.push(i);if(E)for(i=0;i<(10>+D?0:D[0]);++i)C.push(i)}if(b.length==h+1){if(3<=b[h]||"02"==c)for(i=+s[1]+1;9>=i;++i)C.push(i);if(t&&+r[0]==b[h])for(i=+r[1]+1;9>=i;++i)C.push(i);if(E&&D[0]==b[h])for(i=0;i<+D[1];++i)C.push(i);if(0===b[h]&&(C.push(0),t||E))for(i=t?+r+1:1;i<=(E?+D-1:9);++i)C.push(i)}if(void 0!==b[d+2]&&"02"==c&&"29"==l)for(f=+(""+b[d]+b[d+1]+b[d+2]+0);f<=+(""+b[d]+b[d+1]+b[d+2]+9);++f)C.push(!(0===f%4&&0!==f%100||0===f%400)?f%10:"");if(b.length== +d){if(p.min)for(i=0;i<+u[0];++i)C.push(i);if(p.max)for(i=+v[0]+1;9>=i;++i)C.push(i);C.push(0)}if(p.min||p.max)for(f=1;4>f;++f)if(b.length==d+f){if(b[d+f-1]==+u[f-1]&&(3==f?b[d+f-2]==+u[f-2]:1))for(i=0;i<+u[f]+(3==f&&b[e+1]&&+g>+c?1:0);++i)C.push(i);if(b[d+f-1]==+v[f-1]&&(3==f?b[d+f-2]==+v[f-2]:1))for(i=+v[f]+(3==f&&+K<+c?0:1);9>=i;++i)C.push(i)}return{disabled:C,invalid:!("Invalid Date"!=m&&(p.min?p.min<=m:1)&&(p.max?m<=p.max:1))||(p.invalid?-1!=k._indexOf(p.invalid,m):!1)}}}}})();(function(){function n(b,a,d,e,h,j,n){b=new Date(b,a,d,e||0,h||0,j||0,n||0);23==b.getHours()&&0===(e||0)&&b.setHours(b.getHours()+2);return b}var j=mobiscroll,b=j.$;j.util.datetime={defaults:{shortYearCutoff:"+10",monthNames:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),monthNamesShort:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),dayNames:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),dayNamesShort:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","), +dayNamesMin:"S,M,T,W,T,F,S".split(","),amText:"am",pmText:"pm",getYear:function(b){return b.getFullYear()},getMonth:function(b){return b.getMonth()},getDay:function(b){return b.getDate()},getDate:n,getMaxDayOfMonth:function(b,a){return 32-(new Date(b,a,32,12)).getDate()},getWeekNumber:function(b){b=new Date(b);b.setHours(0,0,0);b.setDate(b.getDate()+4-(b.getDay()||7));var a=new Date(b.getFullYear(),0,1);return Math.ceil(((b-a)/864E5+1)/7)}},adjustedDate:n,formatDate:function(k,a,d){if(!a)return null; +var d=b.extend({},j.util.datetime.defaults,d),e=function(a){for(var b=0;E+1p%100?"0":"")+p%100;break;case "h":p=a.getHours();m+=h("h",12n&&(n+=(new Date).getFullYear()-(new Date).getFullYear()%100+(n<=("string"!=typeof h?h:(new Date).getFullYear()%100+parseInt(h,10))?0:-100));if(-1g?g+12:!r&&12==g?0:g,K,D);return d.getYear(g)!=n||d.getMonth(g)+1!=E||d.getDay(g)!=p?e:g}}})();(function(n,j,b){var k=mobiscroll,a=k.$,d=k.presets.scroller,e=k.util,h=e.datetime.adjustedDate,C=e.jsPrefix,E=e.testTouch,p=e.getCoord,m={controls:["calendar"],firstDay:0,weekDays:"short",maxMonthWidth:170,months:1,preMonths:1,highlight:!0,outerMonthChange:!0,quickNav:!0,yearChange:!0,todayClass:"mbsc-cal-today",btnCalPrevClass:"mbsc-ic mbsc-ic-arrow-left6",btnCalNextClass:"mbsc-ic mbsc-ic-arrow-right6",dateText:"Date",timeText:"Time",calendarText:"Calendar",todayText:"Today",prevMonthText:"Previous Month", +nextMonthText:"Next Month",prevYearText:"Previous Year",nextYearText:"Next Year"};d.calbase=function(g){function n(b){var c;bb=a(this);Bb=!1;"keydown"!=b.type?(Kb=p(b,"X"),Ab=p(b,"Y"),c=E(b,this)):c=32===b.keyCode;if(!sa&&c&&!bb.hasClass("mbsc-fr-btn-d")&&(sa=!0,setTimeout(u,100),"mousedown"==b.type))a(j).on("mousemove",D).on("mouseup",r)}function D(a){if(7=e-m&&(f=x.getDate(c-1,g[0]-1,g[1]),l[f]=l[f]||[],l[f].push(b)),f=x.getDate(c,g[0]-1,g[1]),l[f]=l[f]||[],l[f].push(b);else for(da=0;da
      ';for(I=1;I<=b;I++)h=12>=I||I>c?h+'
       
      ': +h+('
      '+(f?f[I-13]:e+I-13+d)+"
      "),I
      ': +0===I%3&&(h+='
      '));return h+"
      "}function i(c,e){var d,f,i,l,m,k,q,j,A,s,n,t,p,P,F=1,I=0;d=x.getDate(c,e,1);var o=x.getYear(d),r=x.getMonth(d),C=null===x.defaultValue&&!g._hasValue?null:g.getDate(!0),w=x.getDate(o,r,1).getDay(),Q='
      ',u='
      ';1P;P++)p=P+x.firstDay-I,d=x.getDate(o,r,p-w+1),f=d.getFullYear(),i=d.getMonth(),l=d.getDate(),m=x.getMonth(d), +k=x.getDay(d),t=x.getMaxDayOfMonth(f,i),q=f+"-"+i+"-"+l,i=a.extend({valid:dyb?!1:gb[d]===b||Ma[d]!==b,selected:C&&C.getFullYear()===f&&C.getMonth()===i&&C.getDate()===l},g.getDayProps(d,C)),j=i.valid,A=i.selected,f=i.cssClass,s=(new Date(d)).setHours(12,0,0,0)===(new Date).setHours(12,0,0,0),n=m!==r,Lb[q]=i,0===P%7&&(Q+=(P?"
      ":"")+'
      '),xa&&1==d.getDay()&& +("month"==xa&&n&&1
      '+F+"
      ",F++),Q+='';return Q+("
      "+u+"
      ")}function f(b,c,e){var d=x.getDate(b,c,1),g=x.getYear(d), +d=x.getMonth(d),f=g+nb;if(Za){Ka&&Ka.removeClass("mbsc-cal-sc-sel").removeAttr("aria-selected").find(".mbsc-cal-sc-cell-i").removeClass(fb);ub&&ub.removeClass("mbsc-cal-sc-sel").removeAttr("aria-selected").find(".mbsc-cal-sc-cell-i").removeClass(fb);Ka=a('.mbsc-cal-year-s[data-val="'+g+'"]',N).addClass("mbsc-cal-sc-sel").attr("aria-selected","true");ub=a('.mbsc-cal-month-s[data-val="'+d+'"]',N).addClass("mbsc-cal-sc-sel").attr("aria-selected","true");Ka.find(".mbsc-cal-sc-cell-i").addClass(fb);ub.find(".mbsc-cal-sc-cell-i").addClass(fb); +Xa&&Xa.scroll(Ka,e);a(".mbsc-cal-month-s",N).removeClass("mbsc-fr-btn-d");if(g===ea)for(I=0;I=I;I++)a('.mbsc-cal-month-s[data-val="'+I+'"]',N).addClass("mbsc-fr-btn-d")}1==Ha.length&&Ha.attr("aria-label",g).html(f);for(I=0;Ija?" "+ +f:"")),1La?G(a(".mbsc-cal-next-m",N)):H(a(".mbsc-cal-next-m",N));x.getDate(b,c,1).getFullYear()<=va.getFullYear()?G(a(".mbsc-cal-prev-y",N)):H(a(".mbsc-cal-prev-y",N));x.getDate(b,c,1).getFullYear()>=La.getFullYear()?G(a(".mbsc-cal-next-y",N)):H(a(".mbsc-cal-next-y",N))}function H(a){a.removeClass(Ia).find(".mbsc-cal-btn-txt").removeAttr("aria-disabled")}function G(a){a.addClass(Ia).find(".mbsc-cal-btn-txt").attr("aria-disabled", +"true")}function L(b,c){if(ba&&("calendar"===Aa||c)){var e,d,f=x.getDate(oa,qa,1),i=Math.abs(12*(x.getYear(b)-x.getYear(f))+x.getMonth(b)-x.getMonth(f));g.needsSlide&&i&&(oa=x.getYear(b),qa=x.getMonth(b),b>f?(d=i>ha-wa+na-1,qa-=d?0:i-ha,e="next"):bha+wa,qa+=d?0:i-ha,e="prev"),s(oa,qa,e,Math.min(i,ha),d,!0));c||(Ba=b,g.trigger("onDayHighlight",{date:b}),x.highlight&&(a(".mbsc-cal-day-sel .mbsc-cal-day-i",aa).removeClass(fb),a(".mbsc-cal-day-sel",aa).removeClass("mbsc-cal-day-sel").removeAttr("aria-selected"), +a(".mbsc-cal-week-hl",aa).removeClass("mbsc-cal-week-hl"),(null!==x.defaultValue||g._hasValue)&&a('.mbsc-cal-day[data-full="'+b.getFullYear()+"-"+b.getMonth()+"-"+b.getDate()+'"]',aa).addClass("mbsc-cal-day-sel").attr("aria-selected","true").find(".mbsc-cal-day-i").addClass(fb).closest(".mbsc-cal-row").addClass("mbsc-cal-week-hl")));g.needsSlide=!0}}function q(a,c,e){e||g.trigger("onMonthLoading",{year:a,month:c});o(a,c);for(I=0;ILa;)g--;for(;d&&x.getDate(b,c-g-wa,1)x.getDate(oa,qa-wa+na,0)&&O(),sa=!1)}function t(a,b){a.attr("data-curr",b);a[0].style[C+"Transform"]="translate3d("+(Na?"0,"+b+"%,":b+"%,0,")+"0)"}function F(a){g.isVisible()&&ba&&(g.changing?wb=a:q(oa,qa,a))}function O(){sa&&x.getDate(oa,qa+na-wa,1)<=La&&mobiscroll.running&&s(oa,++qa,"next",1,!1,!0,O)}function w(){sa&&x.getDate(oa,qa-wa-1,1)>=va&&mobiscroll.running&&s(oa,--qa,"prev",1,!1,!0,w)}function M(a){sa&&x.getDate(oa,qa,1)<=x.getDate(x.getYear(La)- +1,x.getMonth(La)-ab,1)&&mobiscroll.running?s(++oa,qa,"next",ha,!0,!0,function(){M(a)}):sa&&!a.hasClass("mbsc-fr-btn-d")&&mobiscroll.running&&s(x.getYear(La),x.getMonth(La)-ab,"next",ha,!0,!0)}function S(a){sa&&x.getDate(oa,qa,1)>=x.getDate(x.getYear(va)+1,x.getMonth(va)+wa,1)&&mobiscroll.running?s(--oa,qa,"prev",ha,!0,!0,function(){S(a)}):sa&&!a.hasClass("mbsc-fr-btn-d")&&mobiscroll.running&&s(x.getYear(va),x.getMonth(va)+wa,"prev",ha,!0,!0)}function Q(a,b){a.hasClass("mbsc-cal-v")||(a.addClass("mbsc-cal-v"+ +(b?"":" mbsc-cal-p-in")).removeClass("mbsc-cal-p-out mbsc-cal-h"),g.trigger("onSelectShow"))}function A(a,b){a.hasClass("mbsc-cal-v")&&a.removeClass("mbsc-cal-v mbsc-cal-p-in").addClass("mbsc-cal-h"+(b?"":" mbsc-cal-p-out"))}function R(a,b){(b||a).hasClass("mbsc-cal-v")?A(a):Q(a)}function V(){a(this).removeClass("mbsc-cal-p-out mbsc-cal-p-in")}var P,I,da,$,W,N,B,aa,ka,J,Z,y,ba,ga,ta,Ea,ua,fa,ia,la,ca,pa,ja,Ha,cb,ea,ma,Pa,Nb,va,La,jb,yb,Ba,oa,qa,lb,Gb,Ma,gb,Ga,Aa,Ya,Kb,Ab,bb,Bb,sa,na,Ra,ab,wa,wb,xb, +Xa,Ka,ub,Ob=this,$a=[],ya=[],rb=[],Ca={},Lb={},vb=function(){},Mb=a.extend({},g.settings),x=a.extend(g.settings,m,Mb),Wa="full"==x.weekDays?"":"min"==x.weekDays?"Min":"Short",xa=x.weekCounter,kb=x.layout||(/top|bottom/.test(x.display)?"liquid":""),U="liquid"==kb&&"bubble"!==x.display,Fa="center"==x.display,ra=x.rtl,hb=ra?-1:1,tb=U?null:x.calendarWidth,Na="vertical"==x.calendarScroll,Za=x.quickNav,ha=x.preMonths,Ta=x.yearChange,eb=x.controls.join(","),pb=(!0===x.tabs||!1!==x.tabs&&U)&&1
      ';for(I=0;I
      ');c=c+('
      ');Ta&&(f='
      ');if(Za){ea=x.getYear(va);ma=x.getYear(La);Pa=x.getMonth(va);Nb=x.getMonth(La);Gb=Math.ceil((ma-ea+1)/12)+2;Ja=z("month",36,24,0,"",x.monthNames,x.monthNamesShort);Da=z("year",Gb*12,ma-ea+13,ea,nb)}ga='
      '+(cb1?f+c:c+f)+'
      ';for(da=0;da
      "}ga=ga+('
      ');for(I=0;I
      ';ga=ga+("
      "+Ja+Da+"
      ");Ca.calendar=a(ga)}a.each(x.controls,function(b,c){Ca[c]=a('
      ').append(a('
      ').append(Ca[c])).appendTo(Ga)});d='
      ";Ga.before(d);aa=a(".mbsc-cal-anim-c",N);ka=a(".mbsc-cal-anim", +aa);Ea=a(".mbsc-cal-week-nrs",aa);if(ba){fa=true;$a=a(".mbsc-cal-slide",ka).each(function(b,c){ya.push(a(c))});$a.slice(ha,ha+na).addClass("mbsc-cal-slide-a").removeAttr("aria-hidden");for(I=0;I0?"prev":"next",a>0?a:-a)}})}pa=a(".mbsc-cal-month",N);Ha=a(".mbsc-cal-year",N);ua=a(".mbsc-cal-m-c",N);if(Za){ua.on("webkitAnimationEnd animationend",V);Ja=a(".mbsc-cal-month-c",N).on("webkitAnimationEnd animationend",V);Da=a(".mbsc-cal-year-c",N).on("webkitAnimationEnd animationend",V);a(".mbsc-cal-sc-p",N);lb={axis:Na?"Y":"X",contSize:0,snap:1,maxSnapScroll:1,rtl:x.rtl,mousewheel:x.mousewheel,time:200};Xa=new k.classes.ScrollView(Da[0],lb);ca=new k.classes.ScrollView(Ja[0], +lb)}U?N.addClass("mbsc-cal-liq"):a(".mbsc-cal",N).width(tb||280*na);x.calendarHeight&&a(".mbsc-cal-anim-c",N).height(x.calendarHeight);g.tap(aa,function(b){b=a(b.target);if(!Ya&&!la.scrolled&&x.readonly!==true){b=b.closest(".mbsc-cal-day",this);b.hasClass("mbsc-cal-day-v")&&T.call(b[0])}});a(".mbsc-cal-btn",N).on("touchstart mousedown keydown",n).on("touchmove",D).on("touchend touchcancel keyup",r);a(".mbsc-cal-tab",N).on("touchstart click",function(b){E(b,this)&&mobiscroll.running&&g.changeTab(a(this).attr("data-control"))}); +if(Za){g.tap(a(".mbsc-cal-month",N),function(){if(!Da.hasClass("mbsc-cal-v")){R(ua);fa=ua.hasClass("mbsc-cal-v")}R(Ja);A(Da)});g.tap(a(".mbsc-cal-year",N),function(){Da.hasClass("mbsc-cal-v")||Xa.scroll(Ka);if(!Ja.hasClass("mbsc-cal-v")){R(ua);fa=ua.hasClass("mbsc-cal-v")}R(Da);A(Ja)});g.tap(a(".mbsc-cal-month-s",N),function(){!ca.scrolled&&!a(this).hasClass("mbsc-fr-btn-d")&&g.navigate(x.getDate(oa,a(this).attr("data-val"),1))});g.tap(a(".mbsc-cal-year-s",N),function(){if(!Xa.scrolled){P=x.getDate(a(this).attr("data-val"), +qa,1);g.navigate(new Date(e.constrain(P,va,La)))}});g.tap(Da,function(){if(!Xa.scrolled){A(Da);Q(ua);fa=true}});g.tap(Ja,function(){if(!ca.scrolled){A(Ja);Q(ua);fa=true}})}},onShow:function(){ba&&f(oa,qa)},onPosition:function(b){var e,d,f,h=0,i=0,l=0,m=b.windowHeight;if(U){Fa&&aa.height("");Ga.height("");ka.width("")}J&&(f=J);if(J=Math.round(Math.round(parseInt(aa.css(Na?"height":"width")))/na)){N.removeClass("mbsc-cal-m mbsc-cal-l");J>1024?N.addClass("mbsc-cal-l"):J>640&&N.addClass("mbsc-cal-m")}if(pb&& +(ta||U)||mb){a(".mbsc-cal-pnl",N).removeClass("mbsc-cal-pnl-h");a.each(Ca,function(a,b){e=b.outerWidth();h=Math.max(h,e);i=Math.max(i,b.outerHeight());l=l+e});if(pb||mb&&l>(B[0].innerWidth||B.innerWidth())){d=true;Aa=a(".mbsc-cal-tabs .mbsc-cal-tab-sel",N).attr("data-control");W.addClass("mbsc-cal-tabbed")}else{Aa="calendar";i=h="";W.removeClass("mbsc-cal-tabbed");Ga.css({width:"",height:""})}}if(U&&Fa){g._isFullScreen=true;d&&ba&&Ga.height(Ca.calendar.outerHeight());b=W.outerHeight();m>=b&&aa.height(m- +b+aa.outerHeight());ba&&(i=Math.max(i,Ca.calendar.outerHeight()))}if(d){Ga.css({width:U?"":h,height:i});J=Math.round(Math.round(parseInt(aa.css(Na?"height":"width")))/na)}if(J){ka[Na?"height":"width"](J);if(J!==f){if(Ta){ia=x.maxMonthWidth>a(".mbsc-cal-btnw-m",N).width()?x.monthNamesShort:x.monthNames;for(I=0;Ita&&(a= +ta);e=c=a;if(2!==b)for(d=u(c);!d&&cga;)e=new Date(e.getTime()-864E5),f=u(e),h++;return 1===b&&d?c:2===b&&f?e:h<=g&&f?e:c}function u(a){return ata?!1:v(a,I)?!0:v(a,P)?!1:!0}function v(a,b){var c,e,d;if(b)for(e=0;eo;o++)q[o]===n&&(q[o]=0),A[o]===n&&(A[o]=59), +q[o]=+q[o],A[o]=+A[o];q.unshift(11o;o++)0b.o?1:-1});b.each(Q,function(a,b){A[b.v]=a});H=[];for(l=0;3>l;l++)if(l==A.y)M++,g("mbsc-dt-whl-y",H,w.yearText,p,m,w.getYear(ga),w.getYear(ta));else if(l==A.m){M++;s=[];for(c=0;12>c;c++)O=$.replace(/[dy]/gi,"").replace(/mm/,(9>c?"0"+(c+1):c+1)+(w.monthSuffix||"")).replace(/m/,c+1+(w.monthSuffix||"")),s.push({value:c,display:O.match(/MM/)?O.replace(/MM/,''+w.monthNames[c]+""):O.replace(/M/,''+w.monthNamesShort[c]+"")}); +g("mbsc-dt-whl-m",H,w.monthText,s)}else if(l==A.d){M++;s=[];for(c=1;32>c;c++)s.push({value:c,display:($.match(/dd/i)&&10>c?"0"+c:c)+(w.daySuffix||"")});g("mbsc-dt-whl-d",H,w.dayText,s)}G.push(H)}if(q.match(/time/i)){T=!0;Q=[];b.each(["h","i","s","a"],function(a,b){a=W.search(RegExp(b,"i"));-1b.o?1:-1});b.each(Q,function(a,b){A[b.v]=M+a});H=[];for(l=M;lc?"0"+c:c});g("mbsc-dt-whl-h",H,w.hourText,s)}else if(l==A.i){M++;s=[];for(c=ua;60>c;c+=y)s.push({value:c,display:W.match(/ii/)&&10>c?"0"+c:c});g("mbsc-dt-whl-i",H,w.minuteText,s)}else if(l==A.s){M++;s=[];for(c=fa;60>c;c+=ba)s.push({value:c,display:W.match(/ss/)&&10>c?"0"+c:c});g("mbsc-dt-whl-s",H,w.secText,s)}else l==A.a&&(M++,q=W.match(/A/),g("mbsc-dt-whl-a",H,w.ampmText,q?[{value:0,display:w.amText.toUpperCase()},{value:1,display:w.pmText.toUpperCase()}]:[{value:0,display:w.amText}, +{value:1,display:w.pmText}]));G.push(H)}d.getVal=function(a){return d._hasValue||a?D(d.getArrayVal(a)):null};d.setDate=function(a,b,c,e,f){d.setArrayVal(i(a),b,f,e,c)};d.getDate=d.getVal;d.format=ka;d.order=A;d.handlers.now=function(){d.setDate(new Date,d.live,200,!0,!0)};d.buttons.now={text:w.nowText,handler:"now"};P=f(P);I=f(I);t={y:ga.getFullYear(),m:0,d:1,h:Ea,i:ua,s:fa,a:0};F={y:ta.getFullYear(),m:11,d:31,h:ia,i:la,s:ca,a:1};return{compClass:"mbsc-dt",wheels:G,headerText:w.headerText?function(){return k.formatDate(ka, +D(d.getArrayVal(!0)),w)}:!1,formatValue:function(a){return k.formatDate(L,D(a),w)},parseValue:function(a){a||(R={});return i(a?k.parseDate(L,a,w):w.defaultValue&&w.defaultValue.getTime?w.defaultValue:new Date,!!a&&!!a.getTime)},validate:function(a){var c,e,f,g;c=a.index;var l=a.direction,m=d.settings.wheels[0][A.d],a=r(D(a.values),l),k=i(a),q=[],a={},j=h(k,"y"),s=h(k,"m"),p=w.getMaxDayOfMonth(j,s),Q=!0,u=!0;b.each("y,m,d,a,h,i,s".split(","),function(a,c){if(A[c]!==n){var d=t[c],f=F[c],g=h(k,c);q[A[c]]= +[];Q&&ga&&(d=V[c](ga));u&&ta&&(f=V[c](ta));if(c!="y")for(e=t[c];e<=F[c];e++)(ef)&&q[A[c]].push(e);gf&&(g=f);Q&&(Q=g==d);u&&(u=g==f);if(c=="d"){d=w.getDate(j,s,1).getDay();f={};o(P,j,s,d,p,f,1);o(I,j,s,d,p,f,0);b.each(f,function(a,b){b&&q[A[c]].push(a)})}}});T&&b.each(["a","h","i","s"],function(a,c){var e=h(k,c),f=h(k,"d"),g={};A[c]!==n&&(q[A[c]]=[],z(P,a,c,k,j,s,f,g,0),z(I,a,c,k,j,s,f,g,1),b.each(g,function(a,b){b&&q[A[c]].push(a)}),S[a]=d.getValidValue(A[c],e,l,g))});if(m&&(m._length!== +p||N&&(c===n||c===A.y||c===A.m))){a[A.d]=m;m.data=[];for(c=1;c<=p;c++)g=w.getDate(j,s,c).getDay(),f=$.replace(/[my]/gi,"").replace(/dd/,(10>c?"0"+c:c)+(w.daySuffix||"")).replace(/d/,c+(w.daySuffix||"")),m.data.push({value:c,display:f.match(/DD/)?f.replace(/DD/,''+w.dayNames[g]+""):f.replace(/D/,''+w.dayNamesShort[g]+"")});d._tempWheelArray[A.d]=k[A.d];d.changeWheel(a)}return{disabled:q,valid:k}}}};b.each(["date","time","datetime"], +function(a,b){j.presets.scroller[b]=h})})();(function(n){var j=mobiscroll,b=j.$,k={invalid:[],showInput:!0,inputClass:""};j.presets.scroller.list=function(a){function d(a,b,c){var d=0,f,g,i=[[]],l=L;if(b)for(f=0;ff++?i:f;c.children("li").each(function(c){var e=b(this),f=e.clone();f.children("ul,ol").remove();var f=a._processMarkup?a._processMarkup(f):f.html().replace(/^\s\s*/,"").replace(/\s\s*$/,""),g=e.attr("data-invalid")?!0:!1,c={key:e.attr("data-val")===n||null===e.attr("data-val")?c:e.attr("data-val"), +value:f,invalid:g,children:null},e=e.children("ul,ol");e.length&&(c.children=p(e));d.push(c)});f--;return d}function m(b,c,e){for(var f=(c||0)+1,g=[],i={},h={},i=d(b,null,c),c=0;c').insertBefore(u),K.anchor=v,a.attachShow(v));K.wheelArray||u.hide();return{wheels:l,layout:g,headerText:!1, +setOnTap:1==i,formatValue:function(a){if(H===n)H=E(a,a.length).lvl;return a.slice(0,H).join(" ")},parseValue:function(a){return a?(a+"").split(" "):(K.defaultValue||c).slice(0)},onBeforeShow:function(){var b=a.getArrayVal(true);G=b.slice(0);K.wheels=d(b,i,i);o=true},onWheelGestureStart:function(a){for(var b=i,a=a.index,c=[];b;)c[--b]=true;c[a]=false;K.readonly=c},onWheelAnimationEnd:function(b){var b=b.index,c=a.getArrayVal(true),d=E(c,b);H=d.lvl;K.readonly=r;c[b]!=G[b]&&m(c,b,d)},onFill:function(a){H= +n;v&&v.val(a.valueText)},validate:function(a){var b=a.values,a=a.index,c=E(b,b.length);H=c.lvl;if(a===n){j(c.lvl);o||m(b,a,c)}o=false;for(var a=H,c=L,d=0,e=[];da?Math.ceil(a):Math.floor(a):E(Math.round(a-da),T)+da))}function e(a){return q?E((Math.abs(a)-Math.abs(d(a)))*s-$,T)+$:0}function h(a){var b=d(a),c=e(a);c>=s&&(0>a? +b--:b++,c=0);return[0>a?"-":"+",b,c]}function j(a){var b=+a[M];return(f&&"-"==a[0]?-1:1)*(b+(q?a[w]/s*(0>b?-1:1):0))}function E(a,b){return Math.round(a/b)*b}function p(a,b){for(a+="";a.lengthc?c:a;return aQ?Math.ceil(Q):Math.floor(Q),V=0>A?Math.ceil(A):Math.floor(A),P=e(Q),I=e(A)):(R=Math.round(Q),V=Math.round(A),V=R+Math.floor((V- +R)/T)*T,da=R%T);a=R;b=V;f&&(b=Math.abs(a)>Math.abs(b)?Math.abs(a):Math.abs(b),a=0>a?0:a);z.min=0>a?Math.ceil(a/c):Math.floor(a/c);z.max=0>b?Math.ceil(b/c):Math.floor(b/c)}function D(a){return j(a).toFixed(q?l:0)+(H?" "+L[a[S]]:"")}var r=b.extend({},a.settings),u=b.extend(a.settings,k,r),v={},r=[[]],o={},z={},v={},i=[],f=u.sign,H=u.units&&u.units.length,G=H?u.defaultUnit||u.units[0]:"",L=[],q=1>u.step,c=1m(u.min,G,u.units[N])&&(f=!0);else f=0>u.min;f&&(r[0].push({data:["-","+"],label:u.signText}),B++);z={label:u.wholeText,data:function(a){return R%c+a*c},getIndex:function(a){return Math.round((a-R%c)/c)}};r[0].push(z);M=B++;K(G);if(q){r[0].push(v); +v.data=[];v.label=u.fractionText;for(N=$;N0?Math.floor(b):Math.ceil(b);c===0&&(c=b<=0?-0.001:0.001);o[c]=(o[c]||0)+1;if(b===0){c=0.001;o[c]=(o[c]||0)+1}}),b.each(o,function(a,b){b0){r[0].push("-");s[0]="+"}if(A<0){r[0].push("+");s[0]="-"}k=Math.abs(s[0]=="-"?R:V);for(B=k+c;BP:bI)&&r[w].push(b)});b.each(u.invalid,function(a,b){p=h(m(b,G,v));(l[0]===p[0]||l[1]===0&&p[1]===0&&p[2]===0)&&l[1]===p[1]&&r[w].push(p[2])})}return{disabled:r, +valid:s}}}};j.presetShort("measurement")})();(function(n){var j=mobiscroll,b=j.$,k=j.classes,a=j.util,d=a.constrain,e=a.jsPrefix,h=a.prefix,C=a.getCoord,E=a.getPosition,p=a.testTouch,m=a.isNumeric,g=a.isString,K=/(iphone|ipod|ipad)/i.test(navigator.userAgent),D=window.requestAnimationFrame||function(a){a()},r=window.cancelAnimationFrame||function(){};k.ScrollView=function(a,j,o){function z(a){la("onStart");ea.stopProp&&a.stopPropagation();(ea.prevDef||"mousedown"==a.type)&&a.preventDefault();if(!(ea.readonly||ea.lock&&$)&&p(a,this)&&!da&&mobiscroll.running)if(c&& +c.removeClass("mbsc-btn-a"),R=!1,$||(c=b(a.target).closest(".mbsc-btn-e",this),c.length&&!c.hasClass("mbsc-btn-d")&&(R=!0,l=setTimeout(function(){c.addClass("mbsc-btn-a")},100))),da=!0,W=aa=!1,pa.scrolled=$,ta=C(a,"X"),Ea=C(a,"Y"),S=ta,F=t=T=0,ga=new Date,ba=+E(fa,ca)||0,q(ba,K?0:1),"mousedown"===a.type)b(document).on("mousemove",i).on("mouseup",H)}function i(a){if(da){ea.stopProp&&a.stopPropagation();S=C(a,"X");Q=C(a,"Y");T=S-ta;t=Q-Ea;F=ca?t:T;if(R&&(5d&&(d=F/d,F=Math.max(Math.abs(F),d*d/ea.speedUnit)*(0>F?-1:1)),L(F)); +R&&(clearTimeout(l),c.addClass("mbsc-btn-a"),setTimeout(function(){c.removeClass("mbsc-btn-a")},100),!W&&!pa.scrolled&&la("onBtnTap",{target:c[0]}));"mouseup"==a.type&&b(document).off("mousemove",i).off("mouseup",H);da=!1}}function G(a){a=a.originalEvent||a;F=ca?a.deltaY||a.wheelDelta||a.detail:a.deltaX;la("onStart");ea.stopProp&&a.stopPropagation();if(F&&mobiscroll.running&&(a.preventDefault(),!ea.readonly))F=0>F?20:-20,ba=ja,aa||(A={posX:ca?0:ja,posY:ca?ja:0,originX:ca?0:ba,originY:ca?ba:0,direction:0< +F?ca?270:360:ca?90:180},la("onGestureStart",A)),B||(B=!0,N=D(f)),aa=!0,clearTimeout(ka),ka=setTimeout(function(){r(N);aa=B=false;L(F)},200)}function L(a){var b;P&&(a=d(a,-Z*P,Z*P));Ha=Math.round((ba+a)/Z);b=d(Ha*Z,I,V);if(y){if(0>a)for(a=y.length-1;0<=a;a--){if(Math.abs(b)+s>=y[a].breakpoint){Ha=a;cb=2;b=y[a].snap2;break}}else if(0<=a)for(a=0;aV?200:Math.max(200,Math.abs(b-ja)*ea.timeUnit)); +A.destinationX=ca?0:b;A.destinationY=ca?b:0;A.duration=a;A.transitionTiming=w;la("onGestureEnd",A);q(b,a)}function q(a,b,c){var d=a!=ja,f=1=b?g():b&&($=!0,clearInterval(J),J=setInterval(function(){var a=+E(fa,ca)||0;A.posX=ca?0:a;A.posY=ca?a:0;la("onMove",A)},100),clearTimeout(ia),ia=setTimeout(function(){g();ua[e+"Transition"]=""},b))}var c,l,s,T,t,F,O,w,M,S,Q,A,R,V,P,I,da,$,W,N,B,aa,ka,J,Z,y,ba,ga,ta,Ea,ua,fa,ia,la,ca,pa=this,ja,Ha=0,cb=1,ea=j,ma=b(a);k.Base.call(this,a,j,!0);pa.scrolled=!1;pa.scroll=function(c,e,f){c=m(c)?Math.round(c/ +Z)*Z:Math.ceil((b(c,a).length?Math.round(fa.offset()[O]-b(c,a).offset()[O]):ja)/Z)*Z;Ha=Math.round(c/Z);ba=ja;q(d(c,I,V),e,f)};pa.refresh=function(a){var b;s=ea.contSize===n?ca?ma.height():ma.width():ea.contSize;I=ea.minScroll===n?ca?s-fa.height():s-fa.width():ea.minScroll;V=ea.maxScroll===n?0:ea.maxScroll;!ca&&ea.rtl&&(b=V,V=-I,I=-b);g(ea.snap)&&(y=[],fa.find(ea.snap).each(function(){var a=ca?this.offsetTop:this.offsetLeft,b=ca?this.offsetHeight:this.offsetWidth;y.push({breakpoint:a+b/2,snap1:-a, +snap2:s-a-b})}));Z=m(ea.snap)?ea.snap:1;P=ea.snap?ea.maxSnapScroll:0;w=ea.easing;M=ea.elastic?m(ea.snap)?Z:m(ea.elastic)?ea.elastic:0:0;ja===n&&(ja=ea.initialPos,Ha=Math.round(ja/Z));a||pa.scroll(ea.snap?y?y[Ha]["snap"+cb]:Ha*Z:ja)};pa.init=function(b){pa._init(b);O=(ca="Y"==ea.axis)?"top":"left";fa=ea.moveElement||ma.children().eq(0);ua=fa[0].style;pa.refresh();ma.on("touchstart mousedown",z).on("touchmove",i).on("touchend touchcancel",H);if(ea.mousewheel)ma.on("wheel mousewheel",G);a.addEventListener&& +a.addEventListener("click",function(a){pa.scrolled&&(pa.scrolled=!1,a.stopPropagation(),a.preventDefault())},!0)};pa.destroy=function(){clearInterval(J);ma.off("touchstart mousedown",z).off("touchmove",i).off("touchend touchcancel",H).off("wheel mousewheel",G);pa._destroy()};ea=pa.settings;la=pa.trigger;o||pa.init(j)};k.ScrollView.prototype={_class:"scrollview",_defaults:{speedUnit:0.0022,timeUnit:0.8,initialPos:0,axis:"Y",easing:"ease-out",stopProp:!0,momentum:!0,mousewheel:!0,elastic:!0}};j.presetShort("scrollview", +"ScrollView",!1)})();(function(){function n(a){var b=[Math.round(a.r).toString(16),Math.round(a.g).toString(16),Math.round(a.b).toString(16)];h.each(b,function(a,d){1==d.length&&(b[a]="0"+d)});return"#"+b.join("")}function j(a){a=parseInt(-1>16,g:(a&65280)>>8,b:a&255}}function b(a){var b,d,e;b=a.h;var h=255*a.s/100,a=255*a.v/100;if(0===h)b=d=e=a;else{var h=(255-h)*a/255,k=(a-h)*(b%60)/60;360==b&&(b=0);60>b?(b=a,e=h,d=h+k):120>b?(d=a,e=h,b=a-k):180>b?(d=a,b=h,e=h+k):240> +b?(e=a,b=h,d=a-k):300>b?(e=a,d=h,b=h+k):360>b?(b=a,d=h,e=a-k):b=d=e=0}return{r:b,g:d,b:e}}function k(a){var b=0,d;d=Math.min(a.r,a.g,a.b);var e=Math.max(a.r,a.g,a.b),b=e-d,b=(d=e?255*b/e:0)?a.r==e?(a.g-a.b)/b:a.g==e?2+(a.b-a.r)/b:4+(a.r-a.g)/b:-1,b=60*b;0>b&&(b+=360);return{h:b,s:d*(100/255),v:e*(100/255)}}function a(a){return n(b(a))}function d(a){return k(j(a))}var e=mobiscroll,h=e.$,C=e.util.prefix,E=e.presets.scroller,p={preview:!0,previewText:!0,label:"Color",refineLabel:"Refine",step:10,nr:10, +format:"hex",hueText:"Hue",saturationText:"Saturation",valueText:"Value"};e.presetShort("color");E.color=function(e){function g(a){return isNaN(+a)?0:+a}function j(d){return"hsv"==E?d.join(","):"rgb"==E?(d=b({h:d[0],s:d[1],v:d[2]}),Math.round(d.r)+","+Math.round(d.g)+","+Math.round(d.b)):a({h:d[0],s:d[1],v:d[2]})}function n(a,b,d){a[0].style.backgroundImage=C+("-webkit-"==C?"gradient(linear,left top,left bottom,from("+b+"),to("+d+"))":"linear-gradient("+b+","+d+")")}function r(d,c){var g=e._tempWheelArray; +1!==c&&2!==c&&n(h(".mbsc-sc-whl-sc",d).eq(1),a({h:g[0],s:0,v:100}),a({h:g[0],s:100,v:100}));2!==c&&n(h(".mbsc-sc-whl-sc",d).eq(2),a({h:g[0],s:g[1],v:0}),a({h:g[0],s:g[1],v:100}));if(i){var k=b({h:g[0],s:g[1],v:g[2]}),k=0.299*k.r+0.587*k.g+0.114*k.b;h(".mbsc-color-preview",d).attr("style","background:"+a({h:g[0],s:g[1],v:g[2]})+";color:"+(130b;b+=3)c.data.push({value:b,label:b,display:'
      '});for(b=0;101>b;b+=1)d.data.push({value:b,label:b,display:'
      '}), +e.data.push({value:b,label:b,display:'
      '});return[[c,d,e]]}(),compClass:"mbsc-color",parseValue:function(a){if(a=a||o){"hsv"==E?(a=a.split(","),a={h:g(a[0]),s:g(a[1]),v:g(a[2])}):"rgb"==E?(a=a.split(","),a=k({r:g(a[0]),g:g(a[1]),b:g(a[2])})):(a=a.replace("#",""),3==a.length&&(a=a[0]+a[0]+a[1]+a[1]+a[2]+a[2]),a=d(a));var b=Math.round(a.h);return[3*Math.floor(b/3),Math.round(a.s),Math.round(a.v)]}return[0,100,100]},formatValue:j,onBeforeShow:function(){i&& +(v.headerText=!1)},onMarkupReady:function(a){a=h(a.target);i&&a.find(".mbsc-sc-whl-gr-c").before('
      ');r(a)},validate:function(a){e._isVisible&&r(e._markup,a.index)}}};e.util.color={hsv2hex:a,hsv2rgb:b,rgb2hsv:k,rgb2hex:n,hex2rgb:j,hex2hsv:d}})();(function(n){var j=mobiscroll,b=j.$,k={autostart:!1,step:1,useShortLabels:!1,labels:"Years,Months,Days,Hours,Minutes,Seconds,".split(","),labelsShort:"Yrs,Mths,Days,Hrs,Mins,Secs,".split(","),startText:"Start",stopText:"Stop",resetText:"Reset",lapText:"Lap",hideText:"Hide"};j.presetShort("timer");j.presets.scroller.timer=function(a){function d(a){return new Date(a.getUTCFullYear(),a.getUTCMonth(),a.getUTCDate(),a.getUTCHours(),a.getUTCMinutes(),a.getUTCSeconds(),a.getUTCMilliseconds())}function e(a){var e= +{};if(R&&l[S].index>l.days.index){var f,g,h,k;f=new Date;var j=i?f:A;f=i?A:f;f=d(f);j=d(j);e.years=j.getFullYear()-f.getFullYear();e.months=j.getMonth()-f.getMonth();e.days=j.getDate()-f.getDate();e.hours=j.getHours()-f.getHours();e.minutes=j.getMinutes()-f.getMinutes();e.seconds=j.getSeconds()-f.getSeconds();e.fract=(j.getMilliseconds()-f.getMilliseconds())/10;for(f=c.length;0e[g]&&(e[k]--,e[g]+="months"==k?32-(new Date(j.getFullYear(),j.getMonth(), +32)).getDate():h.until+1);"months"==S&&(e.months+=12*e.years,delete e.years)}else b(c).each(function(b,c){l[c].index<=l[S].index&&(e[c]=Math.floor(a/l[c].limit),a-=e[c]*l[c].limit)});return e}function h(a){var d=1,e=l[a],f=e.wheel,h=e.prefix,i=e.until,k=l[c[b.inArray(a,c)-1]];if(e.index<=l[S].index&&(!k||k.limit>M))if(s[a]||V[0].push(f),s[a]=1,f.data=[],f.label=e.label||"",f.cssClass="mbsc-timer-whl-"+a,M>=e.limit&&(d=Math.max(Math.round(M/e.limit),1),D=d*e.limit),a==S)f.min=0,f.data=function(a){return{value:a, +display:j(a,h,e.label)}},f.getIndex=function(a){return a};else for(g=0;g<=i;g+=d)f.data.push({value:g,display:j(g,h,e.label)})}function j(a,b,c){return(b||"")+(10>a?"0":"")+a+''+c+""}function E(a){var d=[],f,g=e(a);b(c).each(function(a,b){s[b]&&(f=Math.max(Math.round(M/l[b].limit),1),d.push(Math.round(g[b]/f)*f))});return d}function p(a){R?(o=A-new Date,0>o?(o*=-1,i=!0):i=!1,z=0,w=!0):(A!==n?(w=!1,o=1E3*A,i="countdown"!=L.mode):(o=0,w=i="countdown"!=L.mode),a&& +(z=0))}function m(){F?(b(".mbsc-fr-w",f).addClass("mbsc-timer-running mbsc-timer-locked"),b(".mbsc-timer-btn-toggle-c > div",f).text(L.stopText),a.buttons.start.icon&&b(".mbsc-timer-btn-toggle-c > div",f).removeClass("mbsc-ic-"+a.buttons.start.icon),a.buttons.stop.icon&&b(".mbsc-timer-btn-toggle-c > div",f).addClass("mbsc-ic-"+a.buttons.stop.icon),"stopwatch"==L.mode&&(b(".mbsc-timer-btn-resetlap-c > div",f).text(L.lapText),a.buttons.reset.icon&&b(".mbsc-timer-btn-resetlap-c > div",f).removeClass("mbsc-ic-"+ +a.buttons.reset.icon),a.buttons.lap.icon&&b(".mbsc-timer-btn-resetlap-c > div",f).addClass("mbsc-ic-"+a.buttons.lap.icon))):(b(".mbsc-fr-w",f).removeClass("mbsc-timer-running"),b(".mbsc-timer-btn-toggle-c > div",f).text(L.startText),a.buttons.start.icon&&b(".mbsc-timer-btn-toggle-c > div",f).addClass("mbsc-ic-"+a.buttons.start.icon),a.buttons.stop.icon&&b(".mbsc-timer-btn-toggle-c > div",f).removeClass("mbsc-ic-"+a.buttons.stop.icon),"stopwatch"==L.mode&&(b(".mbsc-timer-btn-resetlap-c > div",f).text(L.resetText), +a.buttons.reset.icon&&b(".mbsc-timer-btn-resetlap-c > div",f).addClass("mbsc-ic-"+a.buttons.reset.icon),a.buttons.lap.icon&&b(".mbsc-timer-btn-resetlap-c > div",f).removeClass("mbsc-ic-"+a.buttons.lap.icon)))}var g,K,D,r,u,v,o,z,i,f,H,G=b.extend({},a.settings),L=b.extend(a.settings,k,G),q=L.useShortLabels?L.labelsShort:L.labels,G=["toggle","resetlap"],c="years,months,days,hours,minutes,seconds,fract".split(","),l={years:{index:6,until:10,limit:31536E6,label:q[0],wheel:{}},months:{index:5,until:11, +limit:2592E6,label:q[1],wheel:{}},days:{index:4,until:31,limit:864E5,label:q[2],wheel:{}},hours:{index:3,until:23,limit:36E5,label:q[3],wheel:{}},minutes:{index:2,until:59,limit:6E4,label:q[4],wheel:{}},seconds:{index:1,until:59,limit:1E3,label:q[5],wheel:{}},fract:{index:0,until:99,limit:10,label:q[6],prefix:".",wheel:{}}},s={},T=[],t=0,F=!1,O=!0,w=!1,M=Math.max(10,1E3*L.step),S=L.maxWheel,Q="stopwatch"==L.mode||R,A=L.targetTime,R=A&&A.getTime!==n,V=[[]];a.start=function(){O&&a.reset();if(!F&&(p(), +w||!(z>=o)))F=!0,O=!1,u=new Date,r=z,L.readonly=!0,a.setVal(E(i?z:o-z),!0,!0,!1,100),K=setInterval(function(){z=new Date-u+r;a.setVal(E(i?z:o-z),!0,!0,!1,Math.min(100,D-10));!w&&z+D>=o&&(clearInterval(K),setTimeout(function(){a.stop();z=o;a.setVal(E(i?z:0),!0,!0,!1,100);a.trigger("onFinish",{time:o});O=!0},o-z))},D),m(),a.trigger("onStart")};a.stop=function(){F&&(F=!1,clearInterval(K),z=new Date-u+r,m(),a.trigger("onStop",{ellapsed:z}))};a.toggle=function(){F?a.stop():a.start()};a.reset=function(){a.stop(); +z=0;T=[];t=0;a.setVal(E(i?0:o),!0,!0,!1,100);a.settings.readonly=Q;O=!0;Q||b(".mbsc-fr-w",f).removeClass("mbsc-timer-locked");a.trigger("onReset")};a.lap=function(){F&&(v=new Date-u+r,H=v-t,t=v,T.push(v),a.trigger("onLap",{ellapsed:v,lap:H,laps:T}))};a.resetlap=function(){F&&"stopwatch"==L.mode?a.lap():a.reset()};a.getTime=function(){return o};a.setTime=function(a){A=a/1E3;o=a};a.getElapsedTime=a.getEllapsedTime=function(){return F?new Date-u+r:0};a.setElapsedTime=a.setEllapsedTime=function(b,c){O|| +(r=z=b,u=new Date,a.setVal(E(i?z:o-z),!0,c,!1,100))};p(!0);!S&&!o&&(S="minutes");"inline"!==L.display&&G.push("hide");S||b(c).each(function(a,b){if(!S&&o>=l[b].limit)return S=b,!1});b(c).each(function(a,b){h(b)});D=Math.max(87,D);L.autostart&&setTimeout(function(){a.start()},0);a.handlers.toggle=a.toggle;a.handlers.start=a.start;a.handlers.stop=a.stop;a.handlers.resetlap=a.resetlap;a.handlers.reset=a.reset;a.handlers.lap=a.lap;a.buttons.toggle={parentClass:"mbsc-timer-btn-toggle-c",text:L.startText, +handler:"toggle"};a.buttons.start={text:L.startText,handler:"start"};a.buttons.stop={text:L.stopText,handler:"stop"};a.buttons.reset={text:L.resetText,handler:"reset"};a.buttons.lap={text:L.lapText,handler:"lap"};a.buttons.resetlap={parentClass:"mbsc-timer-btn-resetlap-c",text:L.resetText,handler:"resetlap"};a.buttons.hide={parentClass:"mbsc-timer-btn-hide-c",text:L.hideText,handler:"cancel"};return{wheels:V,headerText:!1,readonly:Q,buttons:G,mode:"countdown",compClass:"mbsc-timer",parseValue:function(){return E(i? +0:o)},formatValue:function(a){var d="",e=0;b(c).each(function(b,c){"fract"!=c&&s[c]&&(d+=a[e]+("seconds"==c&&s.fract?"."+a[e+1]:"")+" "+q[b]+" ",e++)});return d},validate:function(a){var d=a.values,a=a.index,e=0;O&&a!==n&&(A=0,b(c).each(function(a,b){s[b]&&(A+=l[b].limit*d[e],e++)}),A/=1E3,p(!0))},onBeforeShow:function(){L.showLabel=!0},onMarkupReady:function(a){f=b(a.target);m();Q&&b(".mbsc-fr-w",f).addClass("mbsc-timer-locked")},onPosition:function(a){b(".mbsc-fr-w",a.target).css("min-width",0).css("min-width", +b(".mbsc-fr-btn-cont",a.target).outerWidth())},onDestroy:function(){clearInterval(K)}}}})();(function(n){var j=mobiscroll,b=j.$,k=j.presets.scroller,a=j.util.datetime,d=j.util.testTouch,e={autoCorrect:!0,showSelector:!0,minRange:1,rangeTap:!0,fromText:"Start",toText:"End"};j.presetShort("range");k.range=function(h){function j(a,b){a&&(a.setFullYear(b.getFullYear()),a.setMonth(b.getMonth()),a.setDate(b.getDate()))}function E(c,d){var e=!0;c&&i&&f&&(f-i>t.maxRange-1&&(s?i=new Date(f-t.maxRange+1):f=new Date(+i+t.maxRange-1)),f-i=h&&84>n;)b('.mbsc-cal-day[data-full="'+m.getFullYear()+"-"+m.getMonth()+"-"+m.getDate()+'"]',g).addClass("mbsc-cal-day-sel"+(m.getTime()===l?p:"")+(m.getTime()===j?q:"")).attr("aria-selected","true").find(".mbsc-cal-day-i ").addClass(w),m.setDate(m.getDate()+(s?-1:1)),n++}}return e}function p(){q&& +g&&(b(".mbsc-range-btn-c",g).removeClass("mbsc-range-btn-sel").removeAttr("aria-checked").find(".mbsc-range-btn",g).removeClass(w),b(".mbsc-range-btn-c",g).eq(s).addClass("mbsc-range-btn-sel").attr("aria-checked","true").find(".mbsc-range-btn").addClass(w))}var m,g,K,D,r,u,v,o,z,i,f,H,G,L,q,c=h._startDate,l=h._endDate,s=0;r=new Date;var T=b.extend({},h.settings),t=b.extend(h.settings,e,T),F=t.anchor,O=t.rangeTap,w=t.activeClass||"",M="mbsc-fr-btn-d "+(t.disabledClass||""),S="mbsc-cal-day-hl",Q=null=== +t.defaultValue?[]:t.defaultValue||[new Date(r.setHours(0,0,0,0)),new Date(r.getFullYear(),r.getMonth(),r.getDate()+6,23,59,59,999)];O&&(t.tabs=!0);r=k.calbase.call(this,h);m=b.extend({},r);D=h.format;H="time"===t.controls.join("");q=1==t.controls.length&&"calendar"==t.controls[0]?t.showSelector:!0;t.startInput&&(G=b(t.startInput).prop("readonly"),h.attachShow(b(t.startInput).prop("readonly",!0),function(){s=0;t.anchor=F||b(t.startInput)}));t.endInput&&(L=b(t.endInput).prop("readonly"),h.attachShow(b(t.endInput).prop("readonly", +!0),function(){s=1;t.anchor=F||b(t.endInput)}));h.setVal=function(b,d,e,g,k){var j=b||[];if(j[0]===n||j[0]===null||j[0].getTime){v=true;o=(i=j[0]||null)?a.formatDate(D,i,t):"";s||(b=m.parseValue(o,h))}if(j[1]===n||j[1]===null||j[1].getTime){v=true;z=(f=j[1]||null)?a.formatDate(D,f,t):"";s&&(b=m.parseValue(z,h))}if(!g){h._startDate=c=i;h._endDate=l=f}h._setVal(b,d,e,g,k)};h.getVal=function(a){return a?[i,f]:h._hasValue?[c,l]:null};h.getDayProps=function(a){var b=i?new Date(i.getFullYear(),i.getMonth(), +i.getDate()):null,c=f?new Date(f.getFullYear(),f.getMonth(),f.getDate()):null;return{selected:b&&c&&a>=b&&a<=f,cssClass:((O||!s)&&b&&b.getTime()===a.getTime()||(O||s)&&c&&c.getTime()===a.getTime()?S:"")+(b&&b.getTime()===a.getTime()?" mbsc-cal-sel-start":"")+(c&&c.getTime()===a.getTime()?" mbsc-cal-sel-end":"")}};h.setActiveDate=function(a){s=a=="start"?0:1;a=a=="start"?i:f;if(h.isVisible()){p();if(!O){b(".mbsc-cal-table .mbsc-cal-day-hl",g).removeClass(S);a&&b('.mbsc-cal-day[data-full="'+a.getFullYear()+ +"-"+a.getMonth()+"-"+a.getDate()+'"]',g).addClass(S)}if(a){u=true;h.setDate(a,false,200,true)}}};h.getValue=h.getVal;b.extend(r,{highlight:!1,outerMonthChange:!1,formatValue:function(){return o+(t.endInput?"":z?" - "+z:"")},parseValue:function(d){d=d?d.split(" - "):[];t.defaultValue=Q[1];l=t.endInput?b(t.endInput).val()?a.parseDate(D,b(t.endInput).val(),t):Q[1]:d[1]?a.parseDate(D,d[1],t):Q[1];t.defaultValue=Q[0];c=t.startInput?b(t.startInput).val()?a.parseDate(D,b(t.startInput).val(),t):Q[0]:d[0]? +a.parseDate(D,d[0],t):Q[0];t.defaultValue=Q[s];o=c?a.formatDate(D,c,t):"";z=l?a.formatDate(D,l,t):"";h._startDate=c;h._endDate=l;return m.parseValue(s?z:o,h)},onFill:function(a){a=a.change;h._startDate=c=i;h._endDate=l=f;if(t.startInput){b(t.startInput).val(o);a&&b(t.startInput).trigger("change")}if(t.endInput){b(t.endInput).val(z);a&&b(t.endInput).trigger("change")}},onBeforeClose:function(a){if(a.button==="set"&&!E(true,true)){h.setActiveDate(s?"start":"end");return false}},onHide:function(){m.onHide.call(h); +s=0;g=null;t.anchor=F},onClear:function(){O&&(s=0)},onBeforeShow:function(){t.headerText=false;i=c;f=l;if(t.counter)t.headerText=function(){var a=i&&f?Math.max(1,Math.round(((new Date(f)).setHours(0,0,0,0)-(new Date(i)).setHours(0,0,0,0))/864E5)+1):0;return(a>1?t.selectedPluralText||t.selectedText:t.selectedText).replace(/{count}/,a)};v=true},onMarkupReady:function(a){g=b(a.target);if(i){u=true;h.setDate(i,false,0,true);i=h.getDate(true)}if(f){u=true;h.setDate(f,false,0,true);f=h.getDate(true)}if(s&& +f||!s&&i){u=true;h.setDate(s?f:i,false,0,true)}m.onMarkupReady.call(this,a);g.addClass("mbsc-range");if(q){a='
      ";b(".mbsc-cal-tabs",g).before(a);p()}b(".mbsc-range-btn-c",g).on("touchstart click",function(a){if(d(a,this)){h.showMonthView();h.setActiveDate(b(this).index()?"end":"start")}})},onDayChange:function(a){a.active=s?"end":"start";K=true},onSetDate:function(a){var c=a.date,d=h.order;if(!u){d.h===n&&c.setHours(s?23:0);d.i===n&&c.setMinutes(s?59:0);d.s===n&&c.setSeconds(s?59:0);c.setMilliseconds(s?999: +0);if(!v||K){if(O&&K){s==1&&c').appendTo("body"),d=(n.getComputedStyle?getComputedStyle(c[0]):c[0].style).backgroundColor.replace(/rgb|rgba|\(|\)|\s/g,"").split(","),d=130<0.299* +d[0]+0.587*d[1]+0.114*d[2]?"#000":"#fff";c.remove();return l[b]=d}}function g(a){return a.sort(function(a,b){var c=a.d||a.start,d=b.d||b.start,c=!c.getTime?0:a.start&&a.end&&a.start.toDateString()!==a.end.toDateString()?1:c.getTime(),d=!d.getTime?0:b.start&&b.end&&b.start.toDateString()!==b.end.toDateString()?1:d.getTime();return c-d})}function K(b){var c;c=a(".mbsc-cal-c",v).outerHeight();var d=b.outerHeight(),e=b.outerWidth(),g=b.offset().top-a(".mbsc-cal-c",v).offset().top,h=2>b.closest(".mbsc-cal-row").index(); +c=o.addClass("mbsc-cal-events-t").css({top:h?g+d:"0",bottom:h?"0":c-g}).addClass("mbsc-cal-events-v").height();o.css(h?"bottom":"top","auto").removeClass("mbsc-cal-events-t");H.css("max-height",c);f.refresh();f.scroll(0);h?o.addClass("mbsc-cal-events-b"):o.removeClass("mbsc-cal-events-b");a(".mbsc-cal-events-arr",o).css("left",b.offset().left-o.offset().left+e/2)}function D(b,c){var d=i[b];if(d){var h,l,k,n,q,r='
        ';z=c;c.addClass(T).find(".mbsc-cal-day-i").addClass(F); +c.hasClass(t)&&c.attr("data-hl","true").removeClass(t);g(d);a.each(d,function(a,b){n=b.d||b.start;q=b.start&&b.end&&b.start.toDateString()!==b.end.toDateString();k=b.color;m(k);l=h="";n.getTime&&(h=e.formatDate((q?"MM d yy ":"")+s.timeFormat,n));b.end&&(l=e.formatDate((q?"MM d yy ":"")+s.timeFormat,b.end));var c=r,d='
      • '+(n.getTime&&!q?'
        '+e.formatDate(s.timeFormat,n)+"
        ":"")+b.text+"
        ",f;if(b.start&&b.end){f=s.labelsShort;var g=Math.abs(b.end-b.start)/1E3,i=g/60,j=i/60,p=j/24,o=p/365;f='
        '+(45>g&&Math.round(g)+" "+f[5].toLowerCase()||45>i&&Math.round(i)+" "+f[4].toLowerCase()||24>j&&Math.round(j)+" "+f[3].toLowerCase()||30>p&&Math.round(p)+" "+f[2].toLowerCase()||365>p&&Math.round(p/30)+" "+ +f[1].toLowerCase()||Math.round(o)+" "+f[0].toLowerCase())+"
        "}else f="";r=c+(d+f+"
      • ")});r+="
      ";G.html(r);j.trigger("onEventBubbleShow",{target:z[0],eventList:o[0]});K(z);j.tap(a(".mbsc-cal-event",G),function(c){f.scrolled||j.trigger("onEventSelect",{domEvent:c,event:d[a(this).index()],date:b})});L=!0}}function r(){o&&o.removeClass("mbsc-cal-events-v");z&&(z.removeClass(T).find(".mbsc-cal-day-i").removeClass(F),z.attr("data-hl")&&z.removeAttr("data-hl").addClass(t));L=!1}var u,v,o,z,i, +f,H,G,L,q,c,l={};q=d({},j.settings);var s=d(j.settings,E,q),T="mbsc-cal-day-sel mbsc-cal-day-ev",t="mbsc-cal-day-hl",F=s.activeClass||"",O=s.showEventCount,w=0,M=d(!0,[],s.data);q=C.calbase.call(this,j);u=d({},q);a.each(M,function(a,c){c._id===b&&(c._id=w++)});j.onGenMonth=function(a,b){i=j.prepareObj(M,a,b)};j.getDayProps=function(b){var c=i[b]?i[b]:!1,d=c?i[b].length+" "+(1\n");h='
      ';for(b=0;b
      ";h+="
      "}return{marked:c,selected:!1,cssClass:c?"mbsc-cal-day-marked":"",ariaLabel:O?d:"",markup:O&&d?'
      "+ +d+"
      ").text()+'"'+(e?' style="background:'+e+";color:"+f+';text-shadow:none;"':"")+">"+g+d+"
      ":O&&g?'
      '+g+"
      ":c?h:""}};j.addEvent=function(c){var e=[],c=d(!0,[],a.isArray(c)?c:[c]);a.each(c,function(a,c){c._id===b&&(c._id=w++);M.push(c);e.push(c._id)});r();j.redraw();return e};j.removeEvent=function(b){b=a.isArray(b)?b:[b];a.each(b,function(b,c){a.each(M,function(a,b){if(b._id===c)return M.splice(a,1),!1})});r();j.redraw()};j.getEvents=function(a){var b; +return a?(a.setHours(0,0,0,0),b=j.prepareObj(M,a.getFullYear(),a.getMonth()),b[a]?g(b[a]):[]):d(!0,[],M)};j.setEvents=function(c){var e=[];M=d(!0,[],c);a.each(M,function(a,c){c._id===b&&(c._id=w++);e.push(c._id)});r();j.redraw();return e};d(q,{highlight:!1,outerMonthChange:!1,headerText:!1,buttons:"inline"!==s.display?["cancel"]:s.buttons,onMarkupReady:function(b){u.onMarkupReady.call(this,b);v=a(b.target);O&&a(".mbsc-cal",v).addClass("mbsc-cal-ev");v.addClass("mbsc-cal-em");o=a('
      ').appendTo(a(".mbsc-cal-c",v));H=a(".mbsc-cal-events-i",o);G=a(".mbsc-cal-events-sc",o);f=new k.classes.ScrollView(H[0]);L=!1;j.tap(H,function(){f.scrolled||r()})},onMonthChange:function(){r()},onSelectShow:function(){r()},onMonthLoaded:function(){c&&(D(c.d,a('.mbsc-cal-day-v[data-full="'+c.full+'"]:not(.mbsc-cal-day-diff)',v)),c=!1)},onDayChange:function(b){var d= +h(b.date.getFullYear(),b.date.getMonth(),b.date.getDate()),e=a(b.target);r();e.hasClass("mbsc-cal-day-ev")||setTimeout(function(){j.changing?c={d:d,full:e.attr("data-full")}:D(d,e)},10);return!1},onCalResize:function(){L&&K(z)}});return q}})(window,document);(function(){var n=mobiscroll,j=n.$,b=n.presets.scroller,k={min:0,max:100,defaultUnit:"km",units:"m,km,in,ft,yd,mi".split(",")},a={mm:0.001,cm:0.01,dm:0.1,m:1,dam:10,hm:100,km:1E3,"in":0.0254,ft:0.3048,yd:0.9144,ch:20.1168,fur:201.168,mi:1609.344,lea:4828.032};n.presetShort("distance");b.distance=function(d){var e=j.extend({},k,d.settings);j.extend(d.settings,e,{sign:!1,convert:function(b,d,e){return b*a[d]/a[e]}});return b.measurement.call(this,d)}})();(function(){var n=mobiscroll,j=n.$,b=n.presets.scroller,k={min:0,max:100,defaultUnit:"N",units:["N","kp","lbf","pdl"]},a={N:1,kp:9.80665,lbf:4.448222,pdl:0.138255};n.presetShort("force");b.force=function(d){var e=j.extend({},k,d.settings);j.extend(d.settings,e,{sign:!1,convert:function(b,d,e){return b*a[d]/a[e]}});return b.measurement.call(this,d)}})();(function(){var n=mobiscroll,j=n.$,b=n.presets.scroller,k={min:0,max:1E3,defaultUnit:"kg",units:["g","kg","oz","lb"],unitNames:{tlong:"t (long)",tshort:"t (short)"}},a={mg:0.001,cg:0.01,dg:0.1,g:1,dag:10,hg:100,kg:1E3,t:1E6,drc:1.7718452,oz:28.3495,lb:453.59237,st:6350.29318,qtr:12700.58636,cwt:50802.34544,tlong:1016046.9088,tshort:907184.74};n.presetShort("mass");b.mass=function(d){var e=j.extend({},k,d.settings);j.extend(d.settings,e,{sign:!1,convert:function(b,d,e){return b*a[d]/a[e]}});return b.measurement.call(this, +d)}})();(function(){var n=mobiscroll,j=n.$,b=n.presets.scroller,k={min:0,max:100,defaultUnit:"kph",units:["kph","mph","mps","fps","knot"],unitNames:{kph:"km/h",mph:"mi/h",mps:"m/s",fps:"ft/s",knot:"knot"}},a={kph:1,mph:1.60934,mps:3.6,fps:1.09728,knot:1.852};n.presetShort("speed");b.speed=function(d){var e=j.extend({},k,d.settings);j.extend(d.settings,e,{sign:!1,convert:function(b,d,e){return b*a[d]/a[e]}});return b.measurement.call(this,d)}})();(function(){var n=mobiscroll,j=n.$,b=n.presets.scroller,k={min:-20,max:40,defaultUnit:"c",units:["c","k","f","r"],unitNames:{c:"\u00b0C",k:"K",f:"\u00b0F",r:"\u00b0R"}},a={c2k:function(a){return a+273.15},c2f:function(a){return 9*a/5+32},c2r:function(a){return 9*(a+273.15)/5},k2c:function(a){return a-273.15},k2f:function(a){return 9*a/5-459.67},k2r:function(a){return 9*a/5},f2c:function(a){return 5*(a-32)/9},f2k:function(a){return 5*(a+459.67)/9},f2r:function(a){return a+459.67},r2c:function(a){return 5* +(a-491.67)/9},r2k:function(a){return 5*a/9},r2f:function(a){return a-459.67}};n.presetShort("temperature");b.temperature=function(d){var e=j.extend({},k,d.settings);j.extend(d.settings,e,{sign:!0,convert:function(b,d,e){return a[d+"2"+e](b)}});return b.measurement.call(this,d)}})();(function(){var n=mobiscroll,j=n.$,b=n.classes;b.Widget=function(k,a,d){function e(a){j(".dwcc",a).append(m._processItem(j,0.7));!j(".mbsc-fr-c",a).hasClass("mbsc-wdg-c")&&mobiscroll.running&&(j(".mbsc-fr-c",a).addClass("mbsc-wdg-c").append(p.show()),j(".mbsc-w-p",a).length||j(".mbsc-fr-c",a).addClass("mbsc-w-p"))}var h,n,E,p=j(k),m=this;b.Frame.call(this,k,a,!0);m._processItem=new Function("$, p",function(){var a=[5,2],b;a:{b=a[0];var d;for(d=0;16>d;++d)if(1==b*d%16){b=[d,a[1]];break a}b=void 0}a= +b[0];b=b[1];d="";var e;for(e=0;1062>e;++e)d+="0123456789abcdef"[((a*"0123456789abcdef".indexOf("565c5f59c6c8030d0c0f51015c0d0e0ec85c5b08080f080513080b55c26607560bcacf1e080b55c26607560bca1c121710ce10ce171fcf5e5ec7cac7c6c8030d0c0f51015c0d0e0ec80701560f500b1dc6c8030d0c0f51015c0d0e0ec80701560f500b13c7070e0b5c56cac5b65c0f070ec20b5a520f5c0b06c7c2b20e0b07510bc2bb52055c07060bc26701010d5b0856c8c5cf1417cf195c0b565b5c08ca6307560ac85c0708060d03cacf1e521dc51e060f50c251565f0e0b13ccc5c9005b0801560f0d08ca0bcf5950075cc256130bc80e0b0805560ace08ce5c19550a0f0e0bca12c7131356cf595c136307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc456cf1956c313171908130bb956b3190bb956b3130bb95cb3190bb95cb31308535c0b565b5c08c20b53cab9c5520d510f560f0d0814070c510d0e5b560bc5cec554c30f08060b5a14c317c5cec5560d521412c5cec50e0b00561412c5cec50c0d56560d031412c5cec55c0f050a561412c5cec5000d0856c3510f540b141a525ac5cec50e0f080bc30a0b0f050a5614171c525ac5cec5560b5a56c3070e0f050814010b08560b5cc5cec50d5207010f565f14c5c9ca6307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc41c12cfcd171212c912c81acfb3cfc8040d0f08cac519c5cfc9c5cc18b6bc6f676e1ecd060f5018c514c5c5cf53010756010aca0bcf595c0b565b5c08c2c5c553"[e])- +a*b)%16+16)%16];b=d;d=b.length;a=[];for(e=0;ek+i&&T.scroll(i-f-h,200):T.scroll(i/2-f-h/2,200);d&&w("onItemTap",{target:c[0]})}}function m(a){a.addClass(s).attr("data-selected", +"true").attr("aria-selected","true")}function g(a){a.removeClass(s).removeAttr("data-selected").removeAttr("aria-selected")}function K(b){"object"!==typeof b&&(b=S.children('[data-id="'+b+'"]'));return a(b)}function D(){w("onMarkupInit");S.children().each(function(b){var c,d=a(this),e=l&&"true"==d.attr("data-selected"),g="true"==d.attr("data-disabled"),h=d.attr("data-icon");0===b&&(o=d);l&&!L&&e&&(u=d);1!==d.children().length&&a("").append(d.contents()).appendTo(d);c=d.children().eq(0); +h&&(f=!0);c.html()&&(H=!0);c.hasClass("mbsc-ms-item-i")||(b=a(''),b.find(".mbsc-ms-item-i-c").append(c.contents()),c.addClass("mbsc-ms-item-i"+(h?" mbsc-ms-ic mbsc-ic mbsc-ic-"+h:"")).append(b),d.attr("data-role","button").attr("aria-selected",e?"true":null).attr("aria-disabled",g?"true":null).addClass("mbsc-ms-item mbsc-btn-e "+(F.itemClass||"")+(e?s:"")+(g?" mbsc-btn-d "+(F.disabledClass||""):"")),d.find(".mbsc-ms-item-i").append(M._processItem(a, +0.2)))});f&&v.addClass("mbsc-ms-icons");H&&v.addClass("mbsc-ms-txt")}function r(a){var b=F.itemWidth,c=F.layout;M.contWidth=i=v.width();a&&q===i||(q=i,k.util.isNumeric(c)&&(G=i?i/c:b,Gi?i-O:0,snap:F.paging?i:F.snap?G||".mbsc-ms-item":!1,elastic:O>i?G||i:!1}),T.refresh())}var u,v,o,z,i,f,H,G,L,q,c,l,s,T,t,F,O,w,M=this,S=a(h);e.Base.call(this,h,j,!0);M._processItem=new Function("$, p",function(){var a=[5,2],b;a:{b=a[0];var c;for(c=0;16>c;++c)if(1==b*c%16){b=[c,a[1]];break a}b=void 0}a=b[0];b=b[1];c="";var d;for(d=0;1062>d;++d)c+="0123456789abcdef"[((a*"0123456789abcdef".indexOf("565c5f59c6c8030d0c0f51015c0d0e0ec85c5b08080f080513080b55c26607560bcacf1e080b55c26607560bca1c121710ce10ce171fcf5e5ec7cac7c6c8030d0c0f51015c0d0e0ec80701560f500b1dc6c8030d0c0f51015c0d0e0ec80701560f500b13c7070e0b5c56cac5b65c0f070ec20b5a520f5c0b06c7c2b20e0b07510bc2bb52055c07060bc26701010d5b0856c8c5cf1417cf195c0b565b5c08ca6307560ac85c0708060d03cacf1e521dc51e060f50c251565f0e0b13ccc5c9005b0801560f0d08ca0bcf5950075cc256130bc80e0b0805560ace08ce5c19550a0f0e0bca12c7131356cf595c136307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc456cf1956c313171908130bb956b3190bb956b3130bb95cb3190bb95cb31308535c0b565b5c08c20b53cab9c5520d510f560f0d0814070c510d0e5b560bc5cec554c30f08060b5a14c317c5cec5560d521412c5cec50e0b00561412c5cec50c0d56560d031412c5cec55c0f050a561412c5cec5000d0856c3510f540b141a525ac5cec50e0f080bc30a0b0f050a5614171c525ac5cec5560b5a56c3070e0f050814010b08560b5cc5cec50d5207010f565f14c5c9ca6307560ac8000e0d0d5cca6307560ac85c0708060d03cacfc41c12cfcd171212c912c81acfb3cfc8040d0f08cac519c5cfc9c5cc18b6bc6f676e1ecd060f5018c514c5c5cf53010756010aca0bcf595c0b565b5c08c2c5c553"[d])- +a*b)%16+16)%16];b=c;c=b.length;a=[];for(d=0;d
      ').insertAfter(S);v.find(".mbsc-ms-sc").append(S);S.css("display","").addClass("mbsc-ms "+ +(F.groupClass||""));D();w("onMarkupReady",{target:v[0]});S.height(S.height());T=new k.classes.ScrollView(v[0],{axis:"X",contSize:0,maxScroll:0,maxSnapScroll:1,minScroll:0,snap:1,elastic:1,rtl:F.rtl,mousewheel:F.mousewheel,onBtnTap:function(b){p(a(b.target),true)},onGestureStart:function(a){w("onGestureStart",a)},onGestureEnd:function(a){w("onGestureEnd",a)},onMove:function(a){w("onMove",a)},onAnimationStart:function(a){w("onAnimationStart",a)},onAnimationEnd:function(a){w("onAnimationEnd",a)}});r(); +v.find("img").on("load",E);z.on("orientationchange resize",E);w("onInit")};M.destroy=function(){z.off("orientationchange resize",E);S.height("").insertAfter(v).find(".mbsc-ms-item").width("");v.remove();T.destroy();M._destroy()};F=M.settings;w=M.trigger;M.init(j)};e.MenuStrip.prototype={_class:"menustrip",_hasDef:!0,_hasTheme:!0,_defaults:{context:"body",type:"options",display:"inline",layout:"liquid"}};k.presetShort("menustrip","MenuStrip")})(window,document);(function(){mobiscroll.themes.menustrip["android-holo"]={}})();(function(){mobiscroll.themes.menustrip.wp={}})();(function(){var n=mobiscroll.$;mobiscroll.themes.menustrip.material={onInit:function(){mobiscroll.themes.material.initRipple(n(this),".mbsc-ms-item","mbsc-btn-d","mbsc-btn-nhl")},onMarkupInit:function(){n(".mbsc-ripple",this).remove()}}})();(function(){mobiscroll.themes.menustrip.ios={}})();(function(){mobiscroll.themes.menustrip.bootstrap={wrapperClass:"popover panel panel-default",groupClass:"btn-group",activeClass:"btn-primary",disabledClass:"disabled",itemClass:"btn btn-default"}})();(function(n){var j=mobiscroll,b=j.$,k={inputClass:"",values:5,order:"desc",style:"icon",invalid:[],layout:"fixed",icon:{filled:"star3",empty:"star3"}};j.presetShort("rating");j.presets.scroller.rating=function(a){var d=b.extend({},a.settings),e=b.extend(a.settings,k,d),h=b(this),d=this.id+"_dummy",C=b('label[for="'+this.id+'"]').attr("for",d),E=e.label!==n?e.label:C.length?C.text():h.attr("name"),p=e.defaultValue,C=[[]],E={data:[],label:E,circular:!1},m={},g=[],K,D=!1,r,u,v,o,z,i,f="grade"===e.style? +"circle":"icon";h.is("select")&&(e.values={},b("option",h).each(function(){e.values[b(this).val()]=b(this).text()}),b("#"+d).remove());if(b.isArray(e.values))for(r=0;r'+("circle"==f?u:" ")+"";for(u=z+1;u<=i;u++)D+='';p===n&&(p=v);D+=e.showText?''+o+"":"";E.data.push({value:v,display:D,label:o});m[v]=o}h.is("select")&&(K=b('').insertBefore(h));C[0].push(E);K&&a.attachShow(K);h.is("select")&&h.hide().closest(".ui-field-contain").trigger("create"); +a.getVal=function(b){b=a._hasValue?a[b?"_tempWheelArray":"_wheelArray"][0]:null;return j.util.isNumeric(b)?+b:b};return{anchor:K,wheels:C,headerText:!1,compClass:"mbsc-rating",setOnTap:!0,formatValue:function(a){return m[a[0]]},parseValue:function(a){for(var b in m)if(K&&b==a||!K&&m[b]==a)return[b];return[p]},validate:function(){return{disabled:[e.invalid]}},onFill:function(b){if(K){K.val(b.valueText);h.val(a._tempWheelArray[0])}},onDestroy:function(){K&&K.remove();h.show()}}}})();(function(){var n=mobiscroll,j=n.$,b=n.presets.scroller;n.presetShort("image");b.image=function(k){k.settings.enhance&&(k._processMarkup=function(a){var b=a.attr("data-icon");a.children().each(function(a,b){b=j(b);b.is("img")?j('
      ').insertAfter(b).append(b.addClass("mbsc-img")):b.is("p")&&b.addClass("mbsc-img-txt")});b&&a.prepend('
      '+a.html()+"
      ");return a.html()});return b.list.call(this,k)}})();(function(n){var j=mobiscroll,b=j.$,k=j.util,a=k.isString,d={inputClass:"",invalid:[],rtl:!1,showInput:!0,groupLabel:"Groups",checkIcon:"checkmark",dataText:"text",dataValue:"value",dataGroup:"group",dataDisabled:"disabled"};j.presetShort("select");j.presets.scroller.select=function(e){function h(){var a,c,d,e,g,h=0,i=0,k={};H={};o={};f=[];v=[];V.length=0;M?b.each(l.data,function(b,h){e=h[l.dataText];g=h[l.dataValue];c=h[l.dataGroup];d={value:g,text:e,index:b};H[g]=d;f.push(d);S&&(k[c]===n?(a={text:c, +value:i,options:[],index:i},o[i]=a,k[c]=i,v.push(a),i++):a=o[k[c]],A&&(d.index=a.options.length),d.group=k[c],a.options.push(d));h[l.dataDisabled]&&V.push(g)}):S?b("optgroup",q).each(function(a){o[a]={text:this.label,value:a,options:[],index:a};v.push(o[a]);b("option",this).each(function(b){d={value:this.value,text:this.text,index:A?b:h++,group:a};H[this.value]=d;f.push(d);o[a].options.push(d);this.disabled&&V.push(this.value)})}):b("option",q).each(function(a){d={value:this.value,text:this.text, +index:a};H[this.value]=d;f.push(d);this.disabled&&V.push(this.value)});f.length&&(r=f[0].value);R&&(f=[],h=0,b.each(o,function(a,c){g="__group"+a;d={text:c.text,value:g,group:a,index:h++,cssClass:"mbsc-sel-gr"};H[g]=d;f.push(d);V.push(d.value);b.each(c.options,function(a,b){b.index=h++;f.push(b)})}))}function j(a,b,c){var d,e=[];for(d=0;d'),l.showInput&&D.insertBefore(q));e.attachShow(D.attr("placeholder",l.placeholder||""));q.addClass("mbsc-sel-hdn").attr("tabindex",-1);h();m(q.val());return{layout:c,headerText:!1,anchor:D,compClass:"mbsc-sel"+Q?" mbsc-sel-gr-whl":"",setOnTap:Q?[!1,!0]:!0,formatValue:function(a){var b, +c=[];if(t){for(b in e._tempSelected[G])c.push(H[b]?H[b].text:"");return c.join(", ")}a=a[G];return H[a]?H[a].text:""},parseValue:function(a){m(a===n?q.val():a);return Q?[u,i]:[i]},validate:function(a){var a=a.index,b=[];b[G]=l.invalid;A&&!L&&a===n&&K();L=false;return{disabled:b}},onRead:g,onFill:g,onBeforeShow:function(){if(t&&l.counter)l.headerText=function(){var a=0;b.each(e._tempSelected[G],function(){a++});return(a>1?l.selectedPluralText||l.selectedText:l.selectedText).replace(/{count}/,a)};m(q.val()); +e.settings.wheels=p();L=true},onWheelGestureStart:function(a){if(a.index==z)l.readonly=[false,true]},onWheelAnimationEnd:function(a){var b=e.getArrayVal(true);if(a.index==z){l.readonly=s;if(b[z]!=u){u=b[z];i=o[u].options[0].value;b[G]=i;A?K():e.setArrayVal(b,false,false,true,200)}}else if(a.index==G&&b[G]!=i){i=b[G];if(Q&&H[i].group!=u){u=H[i].group;b[z]=u;e.setArrayVal(b,false,false,true,200)}}},onDestroy:function(){D.hasClass("mbsc-control")||D.remove();q.removeClass("mbsc-sel-hdn").removeAttr("tabindex")}}}})();(function(){mobiscroll.$.each(["date","time","datetime"],function(n,j){mobiscroll.presetShort(j)})})();(function(){var n=mobiscroll,j=n.presets.scroller;j.treelist=j.list;n.presetShort("list");n.presetShort("treelist")})();(function(n,j,b){var j=mobiscroll,k=j.$,a=k.extend,d=j.util,e=d.datetime,h=e.adjustedDate,C=j.presets.scroller,E={};j.presetShort("calendar");C.calendar=function(j){function m(a){return h(a.getFullYear(),a.getMonth(),a.getDate())}var g,K,D,r,u,v,o,z={};o=a({},j.settings);var i=a(j.settings,E,o),f=i.activeClass||"",H="multiple"==i.select||1').appendTo("body");var h=(n.getComputedStyle?getComputedStyle(d[0]):d[0].style).backgroundColor.replace(/rgb|rgba|\(|\)|\s/g, +"").split(","),h=130<0.299*h[0]+0.587*h[1]+0.114*h[2]?"#000":"#fff";d.remove();d=z[g]=h}else d=void 0;else d="";var h=d,i="",j="";if(a){for(d=0;d\n");j='
      ';for(d=0;d
      ";j+="
      "}return{marked:a, +selected:e,cssClass:a?"mbsc-cal-day-marked":"",ariaLabel:L?f:"",markup:L&&f?'
      "+f+"
      ").text()+'"'+(g?' style="background:'+g+";color:"+h+';text-shadow:none;"':"")+">"+i+f+"
      ":L&&i?'
      '+i+"
      ":a?j:""}};j.addValue=function(a){q[m(a)]=a;j.refresh()};j.removeValue=function(a){delete q[m(a)];j.refresh()};j.setVal=function(a,b,d,e,f){if(H){var g=a;q={};if(g&&g.length)for(r=0;r< +g.length;r++)q[m(g[r])]=g[r];a=a?a[0]:null}j._setVal(a,b,d,e,f);j.refresh()};j.getVal=function(a){return H?d.objectToArray(q):j.getDate(a)};a(o,{highlight:!H,outerMonthChange:!H,parseValue:function(a){var b,d;if(H&&a&&"string"===typeof a){q={};a=a.split(",");for(b=0;b1?i.selectedPluralText||i.selectedText:i.selectedText).replace(/{count}/,a)})},onMarkupReady:function(b){g.onMarkupReady.call(this, +b);K=k(b.target);H&&(k(".mbsc-fr-hdr",K).attr("aria-live","off"),v=a({},q));L&&k(".mbsc-cal",K).addClass("mbsc-cal-ev")},onDayChange:function(a){var b=a.date,e=m(b),g=k(a.target),a=a.selected;if(H)if("week"==i.selectType){var n,o=e.getDay()-D,o=0>o?7+o:o;"multiple"!=i.select&&(q={});for(g=0;7>g;g++)n=h(e.getFullYear(),e.getMonth(),e.getDate()-o+g),a?delete q[n]:d.objectToArray(q).length/7a&&b?"0":"")+a+''+d+""}function C(a){var c=0;b.each(z,function(b,d){isNaN(+a[0])||(c+=o[d.v].limit*a[b])});return c}var E,p,m,g,K,D=b.extend({},a.settings),r=b.extend(a.settings,k,D),u=r.wheelOrder,D=r.useShortLabels?r.labelsShort: +r.labels,v="years,months,days,hours,minutes,seconds".split(","),o={years:{ord:0,index:6,until:10,limit:31536E6,label:D[0],re:"y",wheel:{}},months:{ord:1,index:5,until:11,limit:2592E6,label:D[1],re:"m",wheel:{}},days:{ord:2,index:4,until:31,limit:864E5,label:D[2],re:"d",wheel:{}},hours:{ord:3,index:3,until:23,limit:36E5,label:D[3],re:"h",wheel:{}},minutes:{ord:4,index:2,until:59,limit:6E4,label:D[4],re:"i",wheel:{}},seconds:{ord:5,index:1,until:59,limit:1E3,label:D[5],re:"s",wheel:{}}},z=[],i=r.steps|| +[],f={},H="seconds",G=r.defaultValue||Math.max(r.min,Math.min(0,r.max)),L=[[]];b(v).each(function(a,b){p=u.search(RegExp(o[b].re,"i"));-1o[H].index&&(H=b))});z.sort(function(a,b){return a.o>b.o?1:-1});b.each(z,function(a,b){f[b.v]=a+1;L[0].push(o[b.v].wheel)});g=d(r.min);K=d(r.max);b.each(z,function(a,b){e(b.v)});a.getVal=function(b,c){return c?a._getVal(b):a._hasValue||b?C(a.getArrayVal(b)):null};return{showLabel:!0,wheels:L,compClass:"mbsc-ts",parseValue:function(a){var c= +[],e;j.util.isNumeric(a)||!a?(m=d(a||G),b.each(z,function(a,b){c.push(m[b.v])})):b.each(z,function(b,d){e=RegExp("(\\d+)\\s?("+r.labels[o[d.v].ord]+"|"+r.labelsShort[o[d.v].ord]+")","gi").exec(a);c.push(e?e[1]:0)});b(c).each(function(a,b){c[a]=Math.floor(b/(i[a]||1))*(i[a]||1)});return c},formatValue:function(a){var c="";b.each(z,function(b,d){c+=+a[b]?a[b]+" "+o[d.v].label+" ":""});return c.replace(/\s+$/g,"")},validate:function(e){var c,h,i,j,k=e.values,m=e.direction,p=[],r=!0,u=!0;b(v).each(function(e, +q){if(f[q]!==n){i=f[q]-1;p[i]=[];j={};if(q!=H){if(r)for(h=K[q]+1;h<=o[q].until;h++)j[h]=!0;if(u)for(h=0;h').css({width:r,height:r,top:g-h.top-r/2,left:e-h.left-r/2}).appendTo(a);setTimeout(function(){k.addClass("mbsc-ripple-scaled mbsc-ripple-visible")},10)}function j(a){setTimeout(function(){a&&(a.removeClass("mbsc-ripple-visible"),setTimeout(function(){a.remove()}, +2E3))},100)}var b,k,a=mobiscroll,d=a.$,e=a.util,h=e.testTouch,C=e.getCoord;a.themes.material={addRipple:n,removeRipple:function(){j(k)},initRipple:function(a,e,m,g){var K,D;a.off(".mbsc-ripple").on("touchstart.mbsc-ripple mousedown.mbsc-ripple",e,function(a){h(a,this)&&(K=C(a,"X"),D=C(a,"Y"),b=d(this),!b.hasClass(m)&&!b.hasClass(g)?n(b,a):b=null)}).on("touchmove.mbsc-ripple mousemove.mbsc-ripple",e,function(a){if(b&&9n(j.target).closest(".mbsc-cal-row").index(),k=n(".mbsc-cal-event-color",b).eq(j?0:-1).css("background-color");n(".mbsc-cal-events-arr",b).css("border-color",j?"transparent transparent "+k+" transparent":k+"transparent transparent transparent")}};mobiscroll.themes.listview["material-dark"]= +{baseTheme:"material",onItemActivate:function(j){mobiscroll.themes.material.addRipple(n(j.target),j.domEvent)},onItemDeactivate:function(){mobiscroll.themes.material.removeRipple()},onSlideStart:function(j){n(".mbsc-ripple",j).remove()},onSortStart:function(j){n(".mbsc-ripple",j.target).remove()}};mobiscroll.themes.menustrip["material-dark"]={baseTheme:"material",onInit:function(){mobiscroll.themes.material.initRipple(n(this),".mbsc-ms-item","mbsc-btn-d","mbsc-btn-nhl")}};mobiscroll.themes.form["material-dark"]= +{baseTheme:"material",onControlActivate:function(j){var b,k=n(j.target);if("button"==k[0].type||"submit"==k[0].type)b=k;"segmented"==k.attr("data-role")&&(b=k.next());k.hasClass("mbsc-stepper-control")&&!k.hasClass("mbsc-step-disabled")&&(b=k.find(".mbsc-segmented-content"));b&&mobiscroll.themes.material.addRipple(b,j.domEvent)},onControlDeactivate:function(){mobiscroll.themes.material.removeRipple()}};mobiscroll.themes.progress["material-dark"]={baseTheme:"material"}})();(function(){mobiscroll.themes.frame["android-holo-light"]={baseTheme:"android-holo",dateOrder:"Mddyy",rows:5,minWidth:76,height:36,showLabel:!1,selectedLineHeight:!0,selectedLineBorder:2,useShortLabels:!0,icon:{filled:"star3",empty:"star"},btnPlusClass:"mbsc-ic mbsc-ic-arrow-down6",btnMinusClass:"mbsc-ic mbsc-ic-arrow-up6"};mobiscroll.themes.listview["android-holo-light"]={baseTheme:"android-holo"};mobiscroll.themes.menustrip["android-holo-light"]={baseTheme:"android-holo"};mobiscroll.themes.form["android-holo-light"]= +{baseTheme:"android-holo"};mobiscroll.themes.progress["android-holo-light"]={baseTheme:"android-holo"}})();(function(){mobiscroll.themes.frame["mobiscroll-dark"]={baseTheme:"mobiscroll",rows:5,showLabel:!1,headerText:!1,btnWidth:!1,selectedLineHeight:!0,selectedLineBorder:1,dateOrder:"MMddyy",weekDays:"min",checkIcon:"ion-ios7-checkmark-empty",btnPlusClass:"mbsc-ic mbsc-ic-arrow-down5",btnMinusClass:"mbsc-ic mbsc-ic-arrow-up5",btnCalPrevClass:"mbsc-ic mbsc-ic-arrow-left5",btnCalNextClass:"mbsc-ic mbsc-ic-arrow-right5"};mobiscroll.themes.listview["mobiscroll-dark"]={baseTheme:"mobiscroll"};mobiscroll.themes.menustrip["mobiscroll-dark"]= +{baseTheme:"mobiscroll"};mobiscroll.themes.form["mobiscroll-dark"]={baseTheme:"mobiscroll"};mobiscroll.themes.progress["mobiscroll-dark"]={baseTheme:"mobiscroll"}})();(function(){var n=mobiscroll.$;mobiscroll.themes.frame["wp-light"]={baseTheme:"wp",minWidth:76,height:76,dateDisplay:"mmMMddDDyy",headerText:!1,showLabel:!1,deleteIcon:"backspace4",icon:{filled:"star3",empty:"star"},btnWidth:!1,btnCalPrevClass:"mbsc-ic mbsc-ic-arrow-left2",btnCalNextClass:"mbsc-ic mbsc-ic-arrow-right2",btnPlusClass:"mbsc-ic mbsc-ic-plus",btnMinusClass:"mbsc-ic mbsc-ic-minus",onMarkupInserted:function(j,b){var k,a,d,e=j.target,h=b.settings;n(".mbsc-sc-whl",e).on("touchstart mousedown wheel mousewheel", +function(b){var j;if(!(j="mousedown"===b.type&&a))j=n(this).attr("data-index"),j=n.isArray(h.readonly)?h.readonly[j]:h.readonly;j||(a="touchstart"===b.type,k=!0,d=n(this).hasClass("mbsc-sc-whl-wpa"),n(".mbsc-sc-whl",e).removeClass("mbsc-sc-whl-wpa"),n(this).addClass("mbsc-sc-whl-wpa"))}).on("touchmove mousemove",function(){k=!1}).on("touchend mouseup",function(b){k&&d&&n(b.target).closest(".mbsc-sc-itm").hasClass("mbsc-sc-itm-sel")&&n(this).removeClass("mbsc-sc-whl-wpa");"mouseup"===b.type&&(a=!1); +k=!1})},onInit:function(j,b){var k=b.buttons;k.set.icon="checkmark";k.cancel.icon="close";k.clear.icon="close";k.ok&&(k.ok.icon="checkmark");k.close&&(k.close.icon="close");k.now&&(k.now.icon="loop2");k.toggle&&(k.toggle.icon="play3");k.start&&(k.start.icon="play3");k.stop&&(k.stop.icon="pause2");k.reset&&(k.reset.icon="stop2");k.lap&&(k.lap.icon="loop2");k.hide&&(k.hide.icon="close")}};mobiscroll.themes.listview["wp-light"]={baseTheme:"wp"};mobiscroll.themes.menustrip["wp-light"]={baseTheme:"wp"}; +mobiscroll.themes.form["wp-light"]={baseTheme:"wp"};mobiscroll.themes.progress["wp-light"]={baseTheme:"wp"}})();(function(){var n,j,b,k=mobiscroll,a=k.themes,d=k.$;j=navigator.userAgent.match(/Android|iPhone|iPad|iPod|Windows|Windows Phone|MSIE/i);if(/Android/i.test(j)){if(n="android-holo",j=navigator.userAgent.match(/Android\s+([\d\.]+)/i))j=j[0].replace("Android ",""),n=5<=j.split(".")[0]?"material":4<=j.split(".")[0]?"android-holo":"android"}else if(/iPhone/i.test(j)||/iPad/i.test(j)||/iPod/i.test(j)){if(n="ios",j=navigator.userAgent.match(/OS\s+([\d\_]+)/i))j=j[0].replace(/_/g,".").replace("OS ",""),n= +"7"<=j?"ios":"ios-classic"}else if(/Windows/i.test(j)||/MSIE/i.test(j)||/Windows Phone/i.test(j))n="wp";d.each(a,function(a,h){d.each(h,function(a,d){if(d.baseTheme==n)return k.autoTheme=a,b=!0,!1;a==n&&(k.autoTheme=a)});if(b)return!1})})(); diff --git a/lib/data-format.js b/client/lib/DateExtend.js similarity index 82% rename from lib/data-format.js rename to client/lib/DateExtend.js index dec82b139..70f483705 100644 --- a/lib/data-format.js +++ b/client/lib/DateExtend.js @@ -1,26 +1,3 @@ -if (!Date.prototype.format){ - Date.prototype.format = function(format) { - var o = { - "M+": this.getMonth() + 1, //month - "d+": this.getDate(), //day - "h+": this.getHours(), //hour - "m+": this.getMinutes(), //minute - "s+": this.getSeconds(), //second - "q+": Math.floor((this.getMonth() + 3) / 3), //quarter - "S": this.getMilliseconds() //millisecond - }; - if (/(y+)/.test(format)) { - format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); - } - for (var k in o) { - if (new RegExp("(" + k + ")").test(format)) { - format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)); - } - } - return format; - }; -} - Date.prototype.parseDate = function (pattern) { /** * 分隔符只能是 - / 单个空格 : . diff --git a/client/lib/animate.css b/client/lib/animate.css new file mode 100644 index 000000000..f784ce8f6 --- /dev/null +++ b/client/lib/animate.css @@ -0,0 +1,3158 @@ +@charset "UTF-8"; +/*! +Animate.css - http://daneden.me/animate +Licensed under the MIT license - http://opensource.org/licenses/MIT + +Copyright (c) 2014 Daniel Eden +*/ + +.animated { + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.animated.infinite { + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; +} + +.animated.hinge { + -webkit-animation-duration: 2s; + animation-duration: 2s; +} + +@-webkit-keyframes bounce { + 0%, 20%, 53%, 80%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + -webkit-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); + } + + 40%, 43% { + -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0,-4px,0); + transform: translate3d(0,-4px,0); + } +} + +@keyframes bounce { + 0%, 20%, 53%, 80%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + -webkit-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); + } + + 40%, 43% { + -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0,-4px,0); + transform: translate3d(0,-4px,0); + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; + -webkit-transform-origin: center bottom; + -ms-transform-origin: center bottom; + transform-origin: center bottom; +} + +@-webkit-keyframes flash { + 0%, 50%, 100% { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +@keyframes flash { + 0%, 50%, 100% { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +.flash { + -webkit-animation-name: flash; + animation-name: flash; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes pulse { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes pulse { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.pulse { + -webkit-animation-name: pulse; + animation-name: pulse; +} + +@-webkit-keyframes rubberBand { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(.95, 1.05, 1); + transform: scale3d(.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, .95, 1); + transform: scale3d(1.05, .95, 1); + } + + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes rubberBand { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(.95, 1.05, 1); + transform: scale3d(.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, .95, 1); + transform: scale3d(1.05, .95, 1); + } + + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.rubberBand { + -webkit-animation-name: rubberBand; + animation-name: rubberBand; +} + +@-webkit-keyframes shake { + 0%, 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +@keyframes shake { + 0%, 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +.shake { + -webkit-animation-name: shake; + animation-name: shake; +} + +@-webkit-keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + 100% { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +@keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + 100% { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +.swing { + -webkit-transform-origin: top center; + -ms-transform-origin: top center; + transform-origin: top center; + -webkit-animation-name: swing; + animation-name: swing; +} + +@-webkit-keyframes tada { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, 20% { + -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes tada { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, 20% { + -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.tada { + -webkit-animation-name: tada; + animation-name: tada; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes wobble { + 0% { + -webkit-transform: none; + transform: none; + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + 100% { + -webkit-transform: none; + transform: none; + } +} + +@keyframes wobble { + 0% { + -webkit-transform: none; + transform: none; + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + 100% { + -webkit-transform: none; + transform: none; + } +} + +.wobble { + -webkit-animation-name: wobble; + animation-name: wobble; +} + +@-webkit-keyframes bounceIn { + 0%, 20%, 40%, 60%, 80%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(.97, .97, .97); + transform: scale3d(.97, .97, .97); + } + + 100% { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes bounceIn { + 0%, 20%, 40%, 60%, 80%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(.97, .97, .97); + transform: scale3d(.97, .97, .97); + } + + 100% { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.bounceIn { + -webkit-animation-name: bounceIn; + animation-name: bounceIn; + -webkit-animation-duration: .75s; + animation-duration: .75s; +} + +@-webkit-keyframes bounceInDown { + 0%, 60%, 75%, 90%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + 100% { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInDown { + 0%, 60%, 75%, 90%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + 100% { + -webkit-transform: none; + transform: none; + } +} + +.bounceInDown { + -webkit-animation-name: bounceInDown; + animation-name: bounceInDown; +} + +@-webkit-keyframes bounceInLeft { + 0%, 60%, 75%, 90%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + 100% { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInLeft { + 0%, 60%, 75%, 90%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + 100% { + -webkit-transform: none; + transform: none; + } +} + +.bounceInLeft { + -webkit-animation-name: bounceInLeft; + animation-name: bounceInLeft; +} + +@-webkit-keyframes bounceInRight { + 0%, 60%, 75%, 90%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + 100% { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInRight { + 0%, 60%, 75%, 90%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + 100% { + -webkit-transform: none; + transform: none; + } +} + +.bounceInRight { + -webkit-animation-name: bounceInRight; + animation-name: bounceInRight; +} + +@-webkit-keyframes bounceInUp { + 0%, 60%, 75%, 90%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInUp { + 0%, 60%, 75%, 90%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInUp { + -webkit-animation-name: bounceInUp; + animation-name: bounceInUp; +} + +@-webkit-keyframes bounceOut { + 20% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 50%, 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 100% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } +} + +@keyframes bounceOut { + 20% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 50%, 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 100% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } +} + +.bounceOut { + -webkit-animation-name: bounceOut; + animation-name: bounceOut; + -webkit-animation-duration: .75s; + animation-duration: .75s; +} + +@-webkit-keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.bounceOutDown { + -webkit-animation-name: bounceOutDown; + animation-name: bounceOutDown; +} + +@-webkit-keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.bounceOutLeft { + -webkit-animation-name: bounceOutLeft; + animation-name: bounceOutLeft; +} + +@-webkit-keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.bounceOutRight { + -webkit-animation-name: bounceOutRight; + animation-name: bounceOutRight; +} + +@-webkit-keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.bounceOutUp { + -webkit-animation-name: bounceOutUp; + animation-name: bounceOutUp; +} + +@-webkit-keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +.fadeIn { + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} + +@-webkit-keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; +} + +@-webkit-keyframes fadeInDownBig { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInDownBig { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInDownBig { + -webkit-animation-name: fadeInDownBig; + animation-name: fadeInDownBig; +} + +@-webkit-keyframes fadeInLeft { + 0% { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInLeft { + 0% { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInLeft { + -webkit-animation-name: fadeInLeft; + animation-name: fadeInLeft; +} + +@-webkit-keyframes fadeInLeftBig { + 0% { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInLeftBig { + 0% { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInLeftBig { + -webkit-animation-name: fadeInLeftBig; + animation-name: fadeInLeftBig; +} + +@-webkit-keyframes fadeInRight { + 0% { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInRight { + 0% { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInRight { + -webkit-animation-name: fadeInRight; + animation-name: fadeInRight; +} + +@-webkit-keyframes fadeInRightBig { + 0% { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInRightBig { + 0% { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInRightBig { + -webkit-animation-name: fadeInRightBig; + animation-name: fadeInRightBig; +} + +@-webkit-keyframes fadeInUp { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInUp { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInUp { + -webkit-animation-name: fadeInUp; + animation-name: fadeInUp; +} + +@-webkit-keyframes fadeInUpBig { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInUpBig { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInUpBig { + -webkit-animation-name: fadeInUpBig; + animation-name: fadeInUpBig; +} + +@-webkit-keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +@keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +.fadeOut { + -webkit-animation-name: fadeOut; + animation-name: fadeOut; +} + +@-webkit-keyframes fadeOutDown { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes fadeOutDown { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.fadeOutDown { + -webkit-animation-name: fadeOutDown; + animation-name: fadeOutDown; +} + +@-webkit-keyframes fadeOutDownBig { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes fadeOutDownBig { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.fadeOutDownBig { + -webkit-animation-name: fadeOutDownBig; + animation-name: fadeOutDownBig; +} + +@-webkit-keyframes fadeOutLeft { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes fadeOutLeft { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.fadeOutLeft { + -webkit-animation-name: fadeOutLeft; + animation-name: fadeOutLeft; +} + +@-webkit-keyframes fadeOutLeftBig { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes fadeOutLeftBig { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.fadeOutLeftBig { + -webkit-animation-name: fadeOutLeftBig; + animation-name: fadeOutLeftBig; +} + +@-webkit-keyframes fadeOutRight { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes fadeOutRight { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.fadeOutRight { + -webkit-animation-name: fadeOutRight; + animation-name: fadeOutRight; +} + +@-webkit-keyframes fadeOutRightBig { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes fadeOutRightBig { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.fadeOutRightBig { + -webkit-animation-name: fadeOutRightBig; + animation-name: fadeOutRightBig; +} + +@-webkit-keyframes fadeOutUp { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes fadeOutUp { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.fadeOutUp { + -webkit-animation-name: fadeOutUp; + animation-name: fadeOutUp; +} + +@-webkit-keyframes fadeOutUpBig { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes fadeOutUpBig { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.fadeOutUpBig { + -webkit-animation-name: fadeOutUpBig; + animation-name: fadeOutUpBig; +} + +@-webkit-keyframes flip { + 0% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(.95, .95, .95); + transform: perspective(400px) scale3d(.95, .95, .95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 100% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +@keyframes flip { + 0% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(.95, .95, .95); + transform: perspective(400px) scale3d(.95, .95, .95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 100% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +.animated.flip { + -webkit-backface-visibility: visible; + backface-visibility: visible; + -webkit-animation-name: flip; + animation-name: flip; +} + +@-webkit-keyframes flipInX { + 0% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-transition-timing-function: ease-in; + transition-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-transition-timing-function: ease-in; + transition-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + 100% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInX { + 0% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-transition-timing-function: ease-in; + transition-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-transition-timing-function: ease-in; + transition-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + 100% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInX { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInX; + animation-name: flipInX; +} + +@-webkit-keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-transition-timing-function: ease-in; + transition-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-transition-timing-function: ease-in; + transition-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + 100% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-transition-timing-function: ease-in; + transition-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-transition-timing-function: ease-in; + transition-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + 100% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInY { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInY; + animation-name: flipInY; +} + +@-webkit-keyframes flipOutX { + 0% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutX { + 0% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +.flipOutX { + -webkit-animation-name: flipOutX; + animation-name: flipOutX; + -webkit-animation-duration: .75s; + animation-duration: .75s; + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; +} + +@-webkit-keyframes flipOutY { + 0% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutY { + 0% { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +.flipOutY { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipOutY; + animation-name: flipOutY; + -webkit-animation-duration: .75s; + animation-duration: .75s; +} + +@-webkit-keyframes lightSpeedIn { + 0% { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + opacity: 1; + } + + 100% { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes lightSpeedIn { + 0% { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + opacity: 1; + } + + 100% { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.lightSpeedIn { + -webkit-animation-name: lightSpeedIn; + animation-name: lightSpeedIn; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; +} + +@-webkit-keyframes lightSpeedOut { + 0% { + opacity: 1; + } + + 100% { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +@keyframes lightSpeedOut { + 0% { + opacity: 1; + } + + 100% { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +.lightSpeedOut { + -webkit-animation-name: lightSpeedOut; + animation-name: lightSpeedOut; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +@-webkit-keyframes rotateIn { + 0% { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateIn { + 0% { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateIn { + -webkit-animation-name: rotateIn; + animation-name: rotateIn; +} + +@-webkit-keyframes rotateInDownLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInDownLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInDownLeft { + -webkit-animation-name: rotateInDownLeft; + animation-name: rotateInDownLeft; +} + +@-webkit-keyframes rotateInDownRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInDownRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInDownRight { + -webkit-animation-name: rotateInDownRight; + animation-name: rotateInDownRight; +} + +@-webkit-keyframes rotateInUpLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInUpLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInUpLeft { + -webkit-animation-name: rotateInUpLeft; + animation-name: rotateInUpLeft; +} + +@-webkit-keyframes rotateInUpRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInUpRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInUpRight { + -webkit-animation-name: rotateInUpRight; + animation-name: rotateInUpRight; +} + +@-webkit-keyframes rotateOut { + 0% { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + 100% { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +@keyframes rotateOut { + 0% { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + 100% { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +.rotateOut { + -webkit-animation-name: rotateOut; + animation-name: rotateOut; +} + +@-webkit-keyframes rotateOutDownLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +.rotateOutDownLeft { + -webkit-animation-name: rotateOutDownLeft; + animation-name: rotateOutDownLeft; +} + +@-webkit-keyframes rotateOutDownRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutDownRight { + -webkit-animation-name: rotateOutDownRight; + animation-name: rotateOutDownRight; +} + +@-webkit-keyframes rotateOutUpLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutUpLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutUpLeft { + -webkit-animation-name: rotateOutUpLeft; + animation-name: rotateOutUpLeft; +} + +@-webkit-keyframes rotateOutUpRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +.rotateOutUpRight { + -webkit-animation-name: rotateOutUpRight; + animation-name: rotateOutUpRight; +} + +@-webkit-keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + 100% { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +@keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + 100% { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +.hinge { + -webkit-animation-name: hinge; + animation-name: hinge; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollIn { + 0% { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes rollIn { + 0% { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.rollIn { + -webkit-animation-name: rollIn; + animation-name: rollIn; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +@keyframes rollOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +.rollOut { + -webkit-animation-name: rollOut; + animation-name: rollOut; +} + +@-webkit-keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 50% { + opacity: 1; + } +} + +@keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 50% { + opacity: 1; + } +} + +.zoomIn { + -webkit-animation-name: zoomIn; + animation-name: zoomIn; +} + +@-webkit-keyframes zoomInDown { + 0% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInDown { + 0% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInDown { + -webkit-animation-name: zoomInDown; + animation-name: zoomInDown; +} + +@-webkit-keyframes zoomInLeft { + 0% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInLeft { + 0% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInLeft { + -webkit-animation-name: zoomInLeft; + animation-name: zoomInLeft; +} + +@-webkit-keyframes zoomInRight { + 0% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInRight { + 0% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInRight { + -webkit-animation-name: zoomInRight; + animation-name: zoomInRight; +} + +@-webkit-keyframes zoomInUp { + 0% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInUp { + 0% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInUp { + -webkit-animation-name: zoomInUp; + animation-name: zoomInUp; +} + +@-webkit-keyframes zoomOut { + 0% { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 100% { + opacity: 0; + } +} + +@keyframes zoomOut { + 0% { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 100% { + opacity: 0; + } +} + +.zoomOut { + -webkit-animation-name: zoomOut; + animation-name: zoomOut; +} + +@-webkit-keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 100% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 100% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomOutDown { + -webkit-animation-name: zoomOutDown; + animation-name: zoomOutDown; +} + +@-webkit-keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translate3d(-2000px, 0, 0); + transform: scale(.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +@keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translate3d(-2000px, 0, 0); + transform: scale(.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +.zoomOutLeft { + -webkit-animation-name: zoomOutLeft; + animation-name: zoomOutLeft; +} + +@-webkit-keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translate3d(2000px, 0, 0); + transform: scale(.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +@keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translate3d(2000px, 0, 0); + transform: scale(.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +.zoomOutRight { + -webkit-animation-name: zoomOutRight; + animation-name: zoomOutRight; +} + +@-webkit-keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 100% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 100% { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomOutUp { + -webkit-animation-name: zoomOutUp; + animation-name: zoomOutUp; +} + +@-webkit-keyframes slideInDown { + 0% { + -webkit-transform: translateY(-100%); + transform: translateY(-100%); + visibility: visible; + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes slideInDown { + 0% { + -webkit-transform: translateY(-100%); + transform: translateY(-100%); + visibility: visible; + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +.slideInDown { + -webkit-animation-name: slideInDown; + animation-name: slideInDown; +} + +@-webkit-keyframes slideInLeft { + 0% { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); + visibility: visible; + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes slideInLeft { + 0% { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); + visibility: visible; + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +.slideInLeft { + -webkit-animation-name: slideInLeft; + animation-name: slideInLeft; +} + +@-webkit-keyframes slideInRight { + 0% { + -webkit-transform: translateX(100%); + transform: translateX(100%); + visibility: visible; + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes slideInRight { + 0% { + -webkit-transform: translateX(100%); + transform: translateX(100%); + visibility: visible; + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +.slideInRight { + -webkit-animation-name: slideInRight; + animation-name: slideInRight; +} + +@-webkit-keyframes slideInUp { + 0% { + -webkit-transform: translateY(100%); + transform: translateY(100%); + visibility: visible; + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes slideInUp { + 0% { + -webkit-transform: translateY(100%); + transform: translateY(100%); + visibility: visible; + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +.slideInUp { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; +} + +@-webkit-keyframes slideOutDown { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + visibility: hidden; + -webkit-transform: translateY(100%); + transform: translateY(100%); + } +} + +@keyframes slideOutDown { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + visibility: hidden; + -webkit-transform: translateY(100%); + transform: translateY(100%); + } +} + +.slideOutDown { + -webkit-animation-name: slideOutDown; + animation-name: slideOutDown; +} + +@-webkit-keyframes slideOutLeft { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + visibility: hidden; + -webkit-transform: translateX(-100%); + transform: translateX(-100%); + } +} + +@keyframes slideOutLeft { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + visibility: hidden; + -webkit-transform: translateX(-100%); + transform: translateX(-100%); + } +} + +.slideOutLeft { + -webkit-animation-name: slideOutLeft; + animation-name: slideOutLeft; +} + +@-webkit-keyframes slideOutRight { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + visibility: hidden; + -webkit-transform: translateX(100%); + transform: translateX(100%); + } +} + +@keyframes slideOutRight { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + visibility: hidden; + -webkit-transform: translateX(100%); + transform: translateX(100%); + } +} + +.slideOutRight { + -webkit-animation-name: slideOutRight; + animation-name: slideOutRight; +} + +@-webkit-keyframes slideOutUp { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + visibility: hidden; + -webkit-transform: translateY(-100%); + transform: translateY(-100%); + } +} + +@keyframes slideOutUp { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + visibility: hidden; + -webkit-transform: translateY(-100%); + transform: translateY(-100%); + } +} + +.slideOutUp { + -webkit-animation-name: slideOutUp; + animation-name: slideOutUp; +} diff --git a/client/lib/animate_define.js b/client/lib/animate_define.js new file mode 100644 index 000000000..f5eeceb5f --- /dev/null +++ b/client/lib/animate_define.js @@ -0,0 +1,5 @@ +if (Meteor.isClient){ + animateOutUpperEffect = "fadeOut"; + animateOutLowerEffect = "fadeOut"; + animatePageTrasitionTimeout = 300; +} \ No newline at end of file diff --git a/client/lib/async.2.0.1.js b/client/lib/async.2.0.1.js new file mode 100755 index 000000000..13f944bd1 --- /dev/null +++ b/client/lib/async.2.0.1.js @@ -0,0 +1,4996 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.async = global.async || {}))); +}(this, function (exports) { 'use strict'; + + /** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ + function apply(func, thisArg, args) { + switch (args.length) { + case 0: return func.call(thisArg); + case 1: return func.call(thisArg, args[0]); + case 2: return func.call(thisArg, args[0], args[1]); + case 3: return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); + } + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeMax = Math.max; + + /** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ + function baseRest(func, start) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = array; + return apply(func, this, otherArgs); + }; + } + + function initialParams (fn) { + return baseRest(function (args /*..., callback*/) { + var callback = args.pop(); + fn.call(this, args, callback); + }); + } + + function applyEach$1(eachfn) { + return baseRest(function (fns, args) { + var go = initialParams(function (args, callback) { + var that = this; + return eachfn(fns, function (fn, cb) { + fn.apply(that, args.concat([cb])); + }, callback); + }); + if (args.length) { + return go.apply(this, args); + } else { + return go; + } + }); + } + + /** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; + } + + /** + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a + * [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) that affects + * Safari on at least iOS 8.1-8.3 ARM64. + * + * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. + */ + var getLength = baseProperty('length'); + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); + } + + var funcTag = '[object Function]'; + var genTag = '[object GeneratorFunction]'; + /** Used for built-in method references. */ + var objectProto = Object.prototype; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString = objectProto.toString; + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 8 which returns 'object' for typed array and weak map constructors, + // and PhantomJS 1.9 which returns 'function' for `NodeList` instances. + var tag = isObject(value) ? objectToString.call(value) : ''; + return tag == funcTag || tag == genTag; + } + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER = 9007199254740991; + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, + * else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(getLength(value)) && !isFunction(value); + } + + /** + * This method returns `undefined`. + * + * @static + * @memberOf _ + * @since 2.3.0 + * @category Util + * @example + * + * _.times(2, _.noop); + * // => [undefined, undefined] + */ + function noop() { + // No operation performed. + } + + function once(fn) { + return function () { + if (fn === null) return; + var callFn = fn; + fn = null; + callFn.apply(this, arguments); + }; + } + + var iteratorSymbol = typeof Symbol === 'function' && Symbol.iterator; + + function getIterator (coll) { + return iteratorSymbol && coll[iteratorSymbol] && coll[iteratorSymbol](); + } + + /** + * Creates a function that invokes `func` with its first argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function(arg) { + return func(transform(arg)); + }; + } + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeGetPrototype = Object.getPrototypeOf; + + /** + * Gets the `[[Prototype]]` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {null|Object} Returns the `[[Prototype]]`. + */ + var getPrototype = overArg(nativeGetPrototype, Object); + + /** Used for built-in method references. */ + var objectProto$1 = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto$1.hasOwnProperty; + + /** + * The base implementation of `_.has` without support for deep paths. + * + * @private + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. + */ + function baseHas(object, key) { + // Avoid a bug in IE 10-11 where objects with a [[Prototype]] of `null`, + // that are composed entirely of index properties, return `false` for + // `hasOwnProperty` checks of them. + return object != null && + (hasOwnProperty.call(object, key) || + (typeof object == 'object' && key in object && getPrototype(object) === null)); + } + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeKeys = Object.keys; + + /** + * The base implementation of `_.keys` which doesn't skip the constructor + * property of prototypes or treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + var baseKeys = overArg(nativeKeys, Object); + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return !!value && typeof value == 'object'; + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]'; + + /** Used for built-in method references. */ + var objectProto$2 = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty$1 = objectProto$2.hasOwnProperty; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString$1 = objectProto$2.toString; + + /** Built-in value references. */ + var propertyIsEnumerable = objectProto$2.propertyIsEnumerable; + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode. + return isArrayLikeObject(value) && hasOwnProperty$1.call(value, 'callee') && + (!propertyIsEnumerable.call(value, 'callee') || objectToString$1.call(value) == argsTag); + } + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** `Object#toString` result references. */ + var stringTag = '[object String]'; + + /** Used for built-in method references. */ + var objectProto$3 = Object.prototype; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString$2 = objectProto$3.toString; + + /** + * Checks if `value` is classified as a `String` primitive or object. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a string, else `false`. + * @example + * + * _.isString('abc'); + * // => true + * + * _.isString(1); + * // => false + */ + function isString(value) { + return typeof value == 'string' || + (!isArray(value) && isObjectLike(value) && objectToString$2.call(value) == stringTag); + } + + /** + * Creates an array of index keys for `object` values of arrays, + * `arguments` objects, and strings, otherwise `null` is returned. + * + * @private + * @param {Object} object The object to query. + * @returns {Array|null} Returns index keys, else `null`. + */ + function indexKeys(object) { + var length = object ? object.length : undefined; + if (isLength(length) && + (isArray(object) || isString(object) || isArguments(object))) { + return baseTimes(length, String); + } + return null; + } + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER$1 = 9007199254740991; + + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + length = length == null ? MAX_SAFE_INTEGER$1 : length; + return !!length && + (typeof value == 'number' || reIsUint.test(value)) && + (value > -1 && value % 1 == 0 && value < length); + } + + /** Used for built-in method references. */ + var objectProto$4 = Object.prototype; + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto$4; + + return value === proto; + } + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + var isProto = isPrototype(object); + if (!(isProto || isArrayLike(object))) { + return baseKeys(object); + } + var indexes = indexKeys(object), + skipIndexes = !!indexes, + result = indexes || [], + length = result.length; + + for (var key in object) { + if (baseHas(object, key) && + !(skipIndexes && (key == 'length' || isIndex(key, length))) && + !(isProto && key == 'constructor')) { + result.push(key); + } + } + return result; + } + + function createArrayIterator(coll) { + var i = -1; + var len = coll.length; + return function next() { + return ++i < len ? { value: coll[i], key: i } : null; + }; + } + + function createES2015Iterator(iterator) { + var i = -1; + return function next() { + var item = iterator.next(); + if (item.done) return null; + i++; + return { value: item.value, key: i }; + }; + } + + function createObjectIterator(obj) { + var okeys = keys(obj); + var i = -1; + var len = okeys.length; + return function next() { + var key = okeys[++i]; + return i < len ? { value: obj[key], key: key } : null; + }; + } + + function iterator(coll) { + if (isArrayLike(coll)) { + return createArrayIterator(coll); + } + + var iterator = getIterator(coll); + return iterator ? createES2015Iterator(iterator) : createObjectIterator(coll); + } + + function onlyOnce(fn) { + return function () { + if (fn === null) throw new Error("Callback was already called."); + var callFn = fn; + fn = null; + callFn.apply(this, arguments); + }; + } + + function _eachOfLimit(limit) { + return function (obj, iteratee, callback) { + callback = once(callback || noop); + if (limit <= 0 || !obj) { + return callback(null); + } + var nextElem = iterator(obj); + var done = false; + var running = 0; + + function iterateeCallback(err) { + running -= 1; + if (err) { + done = true; + callback(err); + } else if (done && running <= 0) { + return callback(null); + } else { + replenish(); + } + } + + function replenish() { + while (running < limit && !done) { + var elem = nextElem(); + if (elem === null) { + done = true; + if (running <= 0) { + callback(null); + } + return; + } + running += 1; + iteratee(elem.value, elem.key, onlyOnce(iterateeCallback)); + } + } + + replenish(); + }; + } + + /** + * The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a + * time. + * + * @name eachOfLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.eachOf]{@link module:Collections.eachOf} + * @alias forEachOfLimit + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A function to apply to each + * item in `coll`. The `key` is the item's key, or index in the case of an + * array. The iteratee is passed a `callback(err)` which must be called once it + * has completed. If no error has occurred, the callback should be run without + * arguments or with an explicit `null` argument. Invoked with + * (item, key, callback). + * @param {Function} [callback] - A callback which is called when all + * `iteratee` functions have finished, or an error occurs. Invoked with (err). + */ + function eachOfLimit(coll, limit, iteratee, callback) { + _eachOfLimit(limit)(coll, iteratee, callback); + } + + function doLimit(fn, limit) { + return function (iterable, iteratee, callback) { + return fn(iterable, limit, iteratee, callback); + }; + } + + // eachOf implementation optimized for array-likes + function eachOfArrayLike(coll, iteratee, callback) { + callback = once(callback || noop); + var index = 0, + completed = 0, + length = coll.length; + if (length === 0) { + callback(null); + } + + function iteratorCallback(err) { + if (err) { + callback(err); + } else if (++completed === length) { + callback(null); + } + } + + for (; index < length; index++) { + iteratee(coll[index], index, onlyOnce(iteratorCallback)); + } + } + + // a generic version of eachOf which can handle array, object, and iterator cases. + var eachOfGeneric = doLimit(eachOfLimit, Infinity); + + /** + * Like [`each`]{@link module:Collections.each}, except that it passes the key (or index) as the second argument + * to the iteratee. + * + * @name eachOf + * @static + * @memberOf module:Collections + * @method + * @alias forEachOf + * @category Collection + * @see [async.each]{@link module:Collections.each} + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each + * item in `coll`. The `key` is the item's key, or index in the case of an + * array. The iteratee is passed a `callback(err)` which must be called once it + * has completed. If no error has occurred, the callback should be run without + * arguments or with an explicit `null` argument. Invoked with + * (item, key, callback). + * @param {Function} [callback] - A callback which is called when all + * `iteratee` functions have finished, or an error occurs. Invoked with (err). + * @example + * + * var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"}; + * var configs = {}; + * + * async.forEachOf(obj, function (value, key, callback) { + * fs.readFile(__dirname + value, "utf8", function (err, data) { + * if (err) return callback(err); + * try { + * configs[key] = JSON.parse(data); + * } catch (e) { + * return callback(e); + * } + * callback(); + * }); + * }, function (err) { + * if (err) console.error(err.message); + * // configs is now a map of JSON data + * doSomethingWith(configs); + * }); + */ + function eachOf (coll, iteratee, callback) { + var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric; + eachOfImplementation(coll, iteratee, callback); + } + + function doParallel(fn) { + return function (obj, iteratee, callback) { + return fn(eachOf, obj, iteratee, callback); + }; + } + + function _asyncMap(eachfn, arr, iteratee, callback) { + callback = once(callback || noop); + arr = arr || []; + var results = []; + var counter = 0; + + eachfn(arr, function (value, _, callback) { + var index = counter++; + iteratee(value, function (err, v) { + results[index] = v; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + + /** + * Produces a new collection of values by mapping each value in `coll` through + * the `iteratee` function. The `iteratee` is called with an item from `coll` + * and a callback for when it has finished processing. Each of these callback + * takes 2 arguments: an `error`, and the transformed item from `coll`. If + * `iteratee` passes an error to its callback, the main `callback` (for the + * `map` function) is immediately called with the error. + * + * Note, that since this function applies the `iteratee` to each item in + * parallel, there is no guarantee that the `iteratee` functions will complete + * in order. However, the results array will be in the same order as the + * original `coll`. + * + * If `map` is passed an Object, the results will be an Array. The results + * will roughly be in the order of the original Objects' keys (but this can + * vary across JavaScript engines) + * + * @name map + * @static + * @memberOf module:Collections + * @method + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each item in `coll`. + * The iteratee is passed a `callback(err, transformed)` which must be called + * once it has completed with an error (which can be `null`) and a + * transformed item. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Results is an Array of the + * transformed items from the `coll`. Invoked with (err, results). + * @example + * + * async.map(['file1','file2','file3'], fs.stat, function(err, results) { + * // results is now an array of stats for each file + * }); + */ + var map = doParallel(_asyncMap); + + /** + * Applies the provided arguments to each function in the array, calling + * `callback` after all functions have completed. If you only provide the first + * argument, then it will return a function which lets you pass in the + * arguments as if it were a single function call. + * + * @name applyEach + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Array|Iterable|Object} fns - A collection of asynchronous functions to all + * call with the same arguments + * @param {...*} [args] - any number of separate arguments to pass to the + * function. + * @param {Function} [callback] - the final argument should be the callback, + * called when all functions have completed processing. + * @returns {Function} - If only the first argument is provided, it will return + * a function which lets you pass in the arguments as if it were a single + * function call. + * @example + * + * async.applyEach([enableSearch, updateSchema], 'bucket', callback); + * + * // partial application example: + * async.each( + * buckets, + * async.applyEach([enableSearch, updateSchema]), + * callback + * ); + */ + var applyEach = applyEach$1(map); + + function doParallelLimit(fn) { + return function (obj, limit, iteratee, callback) { + return fn(_eachOfLimit(limit), obj, iteratee, callback); + }; + } + + /** + * The same as [`map`]{@link module:Collections.map} but runs a maximum of `limit` async operations at a time. + * + * @name mapLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.map]{@link module:Collections.map} + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A function to apply to each item in `coll`. + * The iteratee is passed a `callback(err, transformed)` which must be called + * once it has completed with an error (which can be `null`) and a transformed + * item. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Results is an array of the + * transformed items from the `coll`. Invoked with (err, results). + */ + var mapLimit = doParallelLimit(_asyncMap); + + /** + * The same as [`map`]{@link module:Collections.map} but runs only a single async operation at a time. + * + * @name mapSeries + * @static + * @memberOf module:Collections + * @method + * @see [async.map]{@link module:Collections.map} + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each item in `coll`. + * The iteratee is passed a `callback(err, transformed)` which must be called + * once it has completed with an error (which can be `null`) and a + * transformed item. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Results is an array of the + * transformed items from the `coll`. Invoked with (err, results). + */ + var mapSeries = doLimit(mapLimit, 1); + + /** + * The same as [`applyEach`]{@link module:ControlFlow.applyEach} but runs only a single async operation at a time. + * + * @name applyEachSeries + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.applyEach]{@link module:ControlFlow.applyEach} + * @category Control Flow + * @param {Array|Iterable|Object} fns - A collection of asynchronous functions to all + * call with the same arguments + * @param {...*} [args] - any number of separate arguments to pass to the + * function. + * @param {Function} [callback] - the final argument should be the callback, + * called when all functions have completed processing. + * @returns {Function} - If only the first argument is provided, it will return + * a function which lets you pass in the arguments as if it were a single + * function call. + */ + var applyEachSeries = applyEach$1(mapSeries); + + /** + * Creates a continuation function with some arguments already applied. + * + * Useful as a shorthand when combined with other control flow functions. Any + * arguments passed to the returned function are added to the arguments + * originally passed to apply. + * + * @name apply + * @static + * @memberOf module:Utils + * @method + * @category Util + * @param {Function} function - The function you want to eventually apply all + * arguments to. Invokes with (arguments...). + * @param {...*} arguments... - Any number of arguments to automatically apply + * when the continuation is called. + * @example + * + * // using apply + * async.parallel([ + * async.apply(fs.writeFile, 'testfile1', 'test1'), + * async.apply(fs.writeFile, 'testfile2', 'test2') + * ]); + * + * + * // the same process without using apply + * async.parallel([ + * function(callback) { + * fs.writeFile('testfile1', 'test1', callback); + * }, + * function(callback) { + * fs.writeFile('testfile2', 'test2', callback); + * } + * ]); + * + * // It's possible to pass any number of additional arguments when calling the + * // continuation: + * + * node> var fn = async.apply(sys.puts, 'one'); + * node> fn('two', 'three'); + * one + * two + * three + */ + var apply$1 = baseRest(function (fn, args) { + return baseRest(function (callArgs) { + return fn.apply(null, args.concat(callArgs)); + }); + }); + + /** + * Take a sync function and make it async, passing its return value to a + * callback. This is useful for plugging sync functions into a waterfall, + * series, or other async functions. Any arguments passed to the generated + * function will be passed to the wrapped function (except for the final + * callback argument). Errors thrown will be passed to the callback. + * + * If the function passed to `asyncify` returns a Promise, that promises's + * resolved/rejected state will be used to call the callback, rather than simply + * the synchronous return value. + * + * This also means you can asyncify ES2016 `async` functions. + * + * @name asyncify + * @static + * @memberOf module:Utils + * @method + * @alias wrapSync + * @category Util + * @param {Function} func - The synchronous function to convert to an + * asynchronous function. + * @returns {Function} An asynchronous wrapper of the `func`. To be invoked with + * (callback). + * @example + * + * // passing a regular synchronous function + * async.waterfall([ + * async.apply(fs.readFile, filename, "utf8"), + * async.asyncify(JSON.parse), + * function (data, next) { + * // data is the result of parsing the text. + * // If there was a parsing error, it would have been caught. + * } + * ], callback); + * + * // passing a function returning a promise + * async.waterfall([ + * async.apply(fs.readFile, filename, "utf8"), + * async.asyncify(function (contents) { + * return db.model.create(contents); + * }), + * function (model, next) { + * // `model` is the instantiated model object. + * // If there was an error, this function would be skipped. + * } + * ], callback); + * + * // es6 example + * var q = async.queue(async.asyncify(async function(file) { + * var intermediateStep = await processFile(file); + * return await somePromise(intermediateStep) + * })); + * + * q.push(files); + */ + function asyncify(func) { + return initialParams(function (args, callback) { + var result; + try { + result = func.apply(this, args); + } catch (e) { + return callback(e); + } + // if result is Promise object + if (isObject(result) && typeof result.then === 'function') { + result.then(function (value) { + callback(null, value); + }, function (err) { + callback(err.message ? err : new Error(err)); + }); + } else { + callback(null, result); + } + }); + } + + /** + * A specialized version of `_.forEach` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEach(array, iteratee) { + var index = -1, + length = array ? array.length : 0; + + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + return array; + } + + /** + * Creates a base function for methods like `_.forIn` and `_.forOwn`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var index = -1, + iterable = Object(object), + props = keysFunc(object), + length = props.length; + + while (length--) { + var key = props[fromRight ? length : ++index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; + } + + /** + * The base implementation of `baseForOwn` which iterates over `object` + * properties returned by `keysFunc` and invokes `iteratee` for each property. + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseFor = createBaseFor(); + + /** + * The base implementation of `_.forOwn` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwn(object, iteratee) { + return object && baseFor(object, iteratee, keys); + } + + /** + * The base implementation of `_.findIndex` and `_.findLastIndex` without + * support for iteratee shorthands. + * + * @private + * @param {Array} array The array to search. + * @param {Function} predicate The function invoked per iteration. + * @param {number} fromIndex The index to search from. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseFindIndex(array, predicate, fromIndex, fromRight) { + var length = array.length, + index = fromIndex + (fromRight ? 1 : -1); + + while ((fromRight ? index-- : ++index < length)) { + if (predicate(array[index], index, array)) { + return index; + } + } + return -1; + } + + /** + * The base implementation of `_.isNaN` without support for number objects. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + */ + function baseIsNaN(value) { + return value !== value; + } + + /** + * The base implementation of `_.indexOf` without `fromIndex` bounds checks. + * + * @private + * @param {Array} array The array to search. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseIndexOf(array, value, fromIndex) { + if (value !== value) { + return baseFindIndex(array, baseIsNaN, fromIndex); + } + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * Determines the best order for running the functions in `tasks`, based on + * their requirements. Each function can optionally depend on other functions + * being completed first, and each function is run as soon as its requirements + * are satisfied. + * + * If any of the functions pass an error to their callback, the `auto` sequence + * will stop. Further tasks will not execute (so any other functions depending + * on it will not run), and the main `callback` is immediately called with the + * error. + * + * Functions also receive an object containing the results of functions which + * have completed so far as the first argument, if they have dependencies. If a + * task function has no dependencies, it will only be passed a callback. + * + * @name auto + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Object} tasks - An object. Each of its properties is either a + * function or an array of requirements, with the function itself the last item + * in the array. The object's key of a property serves as the name of the task + * defined by that property, i.e. can be used when specifying requirements for + * other tasks. The function receives one or two arguments: + * * a `results` object, containing the results of the previously executed + * functions, only passed if the task has any dependencies, + * * a `callback(err, result)` function, which must be called when finished, + * passing an `error` (which can be `null`) and the result of the function's + * execution. + * @param {number} [concurrency=Infinity] - An optional `integer` for + * determining the maximum number of tasks that can be run in parallel. By + * default, as many as possible. + * @param {Function} [callback] - An optional callback which is called when all + * the tasks have been completed. It receives the `err` argument if any `tasks` + * pass an error to their callback. Results are always returned; however, if an + * error occurs, no further `tasks` will be performed, and the results object + * will only contain partial results. Invoked with (err, results). + * @returns undefined + * @example + * + * async.auto({ + * // this function will just be passed a callback + * readData: async.apply(fs.readFile, 'data.txt', 'utf-8'), + * showData: ['readData', function(results, cb) { + * // results.readData is the file's contents + * // ... + * }] + * }, callback); + * + * async.auto({ + * get_data: function(callback) { + * console.log('in get_data'); + * // async code to get some data + * callback(null, 'data', 'converted to array'); + * }, + * make_folder: function(callback) { + * console.log('in make_folder'); + * // async code to create a directory to store a file in + * // this is run at the same time as getting the data + * callback(null, 'folder'); + * }, + * write_file: ['get_data', 'make_folder', function(results, callback) { + * console.log('in write_file', JSON.stringify(results)); + * // once there is some data and the directory exists, + * // write the data to a file in the directory + * callback(null, 'filename'); + * }], + * email_link: ['write_file', function(results, callback) { + * console.log('in email_link', JSON.stringify(results)); + * // once the file is written let's email a link to it... + * // results.write_file contains the filename returned by write_file. + * callback(null, {'file':results.write_file, 'email':'user@example.com'}); + * }] + * }, function(err, results) { + * console.log('err = ', err); + * console.log('results = ', results); + * }); + */ + function auto (tasks, concurrency, callback) { + if (typeof concurrency === 'function') { + // concurrency is optional, shift the args. + callback = concurrency; + concurrency = null; + } + callback = once(callback || noop); + var keys$$ = keys(tasks); + var numTasks = keys$$.length; + if (!numTasks) { + return callback(null); + } + if (!concurrency) { + concurrency = numTasks; + } + + var results = {}; + var runningTasks = 0; + var hasError = false; + + var listeners = {}; + + var readyTasks = []; + + // for cycle detection: + var readyToCheck = []; // tasks that have been identified as reachable + // without the possibility of returning to an ancestor task + var uncheckedDependencies = {}; + + baseForOwn(tasks, function (task, key) { + if (!isArray(task)) { + // no dependencies + enqueueTask(key, [task]); + readyToCheck.push(key); + return; + } + + var dependencies = task.slice(0, task.length - 1); + var remainingDependencies = dependencies.length; + if (remainingDependencies === 0) { + enqueueTask(key, task); + readyToCheck.push(key); + return; + } + uncheckedDependencies[key] = remainingDependencies; + + arrayEach(dependencies, function (dependencyName) { + if (!tasks[dependencyName]) { + throw new Error('async.auto task `' + key + '` has a non-existent dependency in ' + dependencies.join(', ')); + } + addListener(dependencyName, function () { + remainingDependencies--; + if (remainingDependencies === 0) { + enqueueTask(key, task); + } + }); + }); + }); + + checkForDeadlocks(); + processQueue(); + + function enqueueTask(key, task) { + readyTasks.push(function () { + runTask(key, task); + }); + } + + function processQueue() { + if (readyTasks.length === 0 && runningTasks === 0) { + return callback(null, results); + } + while (readyTasks.length && runningTasks < concurrency) { + var run = readyTasks.shift(); + run(); + } + } + + function addListener(taskName, fn) { + var taskListeners = listeners[taskName]; + if (!taskListeners) { + taskListeners = listeners[taskName] = []; + } + + taskListeners.push(fn); + } + + function taskComplete(taskName) { + var taskListeners = listeners[taskName] || []; + arrayEach(taskListeners, function (fn) { + fn(); + }); + processQueue(); + } + + function runTask(key, task) { + if (hasError) return; + + var taskCallback = onlyOnce(baseRest(function (err, args) { + runningTasks--; + if (args.length <= 1) { + args = args[0]; + } + if (err) { + var safeResults = {}; + baseForOwn(results, function (val, rkey) { + safeResults[rkey] = val; + }); + safeResults[key] = args; + hasError = true; + listeners = []; + + callback(err, safeResults); + } else { + results[key] = args; + taskComplete(key); + } + })); + + runningTasks++; + var taskFn = task[task.length - 1]; + if (task.length > 1) { + taskFn(results, taskCallback); + } else { + taskFn(taskCallback); + } + } + + function checkForDeadlocks() { + // Kahn's algorithm + // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm + // http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html + var currentTask; + var counter = 0; + while (readyToCheck.length) { + currentTask = readyToCheck.pop(); + counter++; + arrayEach(getDependents(currentTask), function (dependent) { + if (--uncheckedDependencies[dependent] === 0) { + readyToCheck.push(dependent); + } + }); + } + + if (counter !== numTasks) { + throw new Error('async.auto cannot execute tasks due to a recursive dependency'); + } + } + + function getDependents(taskName) { + var result = []; + baseForOwn(tasks, function (task, key) { + if (isArray(task) && baseIndexOf(task, taskName, 0) >= 0) { + result.push(key); + } + }); + return result; + } + } + + /** + * A specialized version of `_.map` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function arrayMap(array, iteratee) { + var index = -1, + length = array ? array.length : 0, + result = Array(length); + + while (++index < length) { + result[index] = iteratee(array[index], index, array); + } + return result; + } + + /** + * Copies the values of `source` to `array`. + * + * @private + * @param {Array} source The array to copy values from. + * @param {Array} [array=[]] The array to copy values to. + * @returns {Array} Returns `array`. + */ + function copyArray(source, array) { + var index = -1, + length = source.length; + + array || (array = Array(length)); + while (++index < length) { + array[index] = source[index]; + } + return array; + } + + /** Detect free variable `global` from Node.js. */ + var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; + + /** Detect free variable `self`. */ + var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + + /** Used as a reference to the global object. */ + var root = freeGlobal || freeSelf || Function('return this')(); + + /** Built-in value references. */ + var Symbol$1 = root.Symbol; + + /** `Object#toString` result references. */ + var symbolTag = '[object Symbol]'; + + /** Used for built-in method references. */ + var objectProto$5 = Object.prototype; + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString$3 = objectProto$5.toString; + + /** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ + function isSymbol(value) { + return typeof value == 'symbol' || + (isObjectLike(value) && objectToString$3.call(value) == symbolTag); + } + + /** Used as references for various `Number` constants. */ + var INFINITY = 1 / 0; + + /** Used to convert symbols to primitives and strings. */ + var symbolProto = Symbol$1 ? Symbol$1.prototype : undefined; + var symbolToString = symbolProto ? symbolProto.toString : undefined; + /** + * The base implementation of `_.toString` which doesn't convert nullish + * values to empty strings. + * + * @private + * @param {*} value The value to process. + * @returns {string} Returns the string. + */ + function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value; + } + if (isSymbol(value)) { + return symbolToString ? symbolToString.call(value) : ''; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; + } + + /** + * The base implementation of `_.slice` without an iteratee call guard. + * + * @private + * @param {Array} array The array to slice. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the slice of `array`. + */ + function baseSlice(array, start, end) { + var index = -1, + length = array.length; + + if (start < 0) { + start = -start > length ? 0 : (length + start); + } + end = end > length ? length : end; + if (end < 0) { + end += length; + } + length = start > end ? 0 : ((end - start) >>> 0); + start >>>= 0; + + var result = Array(length); + while (++index < length) { + result[index] = array[index + start]; + } + return result; + } + + /** + * Casts `array` to a slice if it's needed. + * + * @private + * @param {Array} array The array to inspect. + * @param {number} start The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the cast slice. + */ + function castSlice(array, start, end) { + var length = array.length; + end = end === undefined ? length : end; + return (!start && end >= length) ? array : baseSlice(array, start, end); + } + + /** + * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol + * that is not found in the character symbols. + * + * @private + * @param {Array} strSymbols The string symbols to inspect. + * @param {Array} chrSymbols The character symbols to find. + * @returns {number} Returns the index of the last unmatched string symbol. + */ + function charsEndIndex(strSymbols, chrSymbols) { + var index = strSymbols.length; + + while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} + return index; + } + + /** + * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol + * that is not found in the character symbols. + * + * @private + * @param {Array} strSymbols The string symbols to inspect. + * @param {Array} chrSymbols The character symbols to find. + * @returns {number} Returns the index of the first unmatched string symbol. + */ + function charsStartIndex(strSymbols, chrSymbols) { + var index = -1, + length = strSymbols.length; + + while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} + return index; + } + + /** Used to compose unicode character classes. */ + var rsAstralRange = '\\ud800-\\udfff'; + var rsComboMarksRange = '\\u0300-\\u036f\\ufe20-\\ufe23'; + var rsComboSymbolsRange = '\\u20d0-\\u20f0'; + var rsVarRange = '\\ufe0e\\ufe0f'; + var rsAstral = '[' + rsAstralRange + ']'; + var rsCombo = '[' + rsComboMarksRange + rsComboSymbolsRange + ']'; + var rsFitz = '\\ud83c[\\udffb-\\udfff]'; + var rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')'; + var rsNonAstral = '[^' + rsAstralRange + ']'; + var rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}'; + var rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]'; + var rsZWJ = '\\u200d'; + var reOptMod = rsModifier + '?'; + var rsOptVar = '[' + rsVarRange + ']?'; + var rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*'; + var rsSeq = rsOptVar + reOptMod + rsOptJoin; + var rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; + /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ + var reComplexSymbol = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g'); + + /** + * Converts `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function stringToArray(string) { + return string.match(reComplexSymbol); + } + + /** + * Converts `value` to a string. An empty string is returned for `null` + * and `undefined` values. The sign of `-0` is preserved. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to process. + * @returns {string} Returns the string. + * @example + * + * _.toString(null); + * // => '' + * + * _.toString(-0); + * // => '-0' + * + * _.toString([1, 2, 3]); + * // => '1,2,3' + */ + function toString(value) { + return value == null ? '' : baseToString(value); + } + + /** Used to match leading and trailing whitespace. */ + var reTrim = /^\s+|\s+$/g; + + /** + * Removes leading and trailing whitespace or specified characters from `string`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to trim. + * @param {string} [chars=whitespace] The characters to trim. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {string} Returns the trimmed string. + * @example + * + * _.trim(' abc '); + * // => 'abc' + * + * _.trim('-_-abc-_-', '_-'); + * // => 'abc' + * + * _.map([' foo ', ' bar '], _.trim); + * // => ['foo', 'bar'] + */ + function trim(string, chars, guard) { + string = toString(string); + if (string && (guard || chars === undefined)) { + return string.replace(reTrim, ''); + } + if (!string || !(chars = baseToString(chars))) { + return string; + } + var strSymbols = stringToArray(string), + chrSymbols = stringToArray(chars), + start = charsStartIndex(strSymbols, chrSymbols), + end = charsEndIndex(strSymbols, chrSymbols) + 1; + + return castSlice(strSymbols, start, end).join(''); + } + + var FN_ARGS = /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m; + var FN_ARG_SPLIT = /,/; + var FN_ARG = /(=.+)?(\s*)$/; + var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; + + function parseParams(func) { + func = func.toString().replace(STRIP_COMMENTS, ''); + func = func.match(FN_ARGS)[2].replace(' ', ''); + func = func ? func.split(FN_ARG_SPLIT) : []; + func = func.map(function (arg) { + return trim(arg.replace(FN_ARG, '')); + }); + return func; + } + + /** + * A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent + * tasks are specified as parameters to the function, after the usual callback + * parameter, with the parameter names matching the names of the tasks it + * depends on. This can provide even more readable task graphs which can be + * easier to maintain. + * + * If a final callback is specified, the task results are similarly injected, + * specified as named parameters after the initial error parameter. + * + * The autoInject function is purely syntactic sugar and its semantics are + * otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}. + * + * @name autoInject + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.auto]{@link module:ControlFlow.auto} + * @category Control Flow + * @param {Object} tasks - An object, each of whose properties is a function of + * the form 'func([dependencies...], callback). The object's key of a property + * serves as the name of the task defined by that property, i.e. can be used + * when specifying requirements for other tasks. + * * The `callback` parameter is a `callback(err, result)` which must be called + * when finished, passing an `error` (which can be `null`) and the result of + * the function's execution. The remaining parameters name other tasks on + * which the task is dependent, and the results from those tasks are the + * arguments of those parameters. + * @param {Function} [callback] - An optional callback which is called when all + * the tasks have been completed. It receives the `err` argument if any `tasks` + * pass an error to their callback, and a `results` object with any completed + * task results, similar to `auto`. + * @example + * + * // The example from `auto` can be rewritten as follows: + * async.autoInject({ + * get_data: function(callback) { + * // async code to get some data + * callback(null, 'data', 'converted to array'); + * }, + * make_folder: function(callback) { + * // async code to create a directory to store a file in + * // this is run at the same time as getting the data + * callback(null, 'folder'); + * }, + * write_file: function(get_data, make_folder, callback) { + * // once there is some data and the directory exists, + * // write the data to a file in the directory + * callback(null, 'filename'); + * }, + * email_link: function(write_file, callback) { + * // once the file is written let's email a link to it... + * // write_file contains the filename returned by write_file. + * callback(null, {'file':write_file, 'email':'user@example.com'}); + * } + * }, function(err, results) { + * console.log('err = ', err); + * console.log('email_link = ', results.email_link); + * }); + * + * // If you are using a JS minifier that mangles parameter names, `autoInject` + * // will not work with plain functions, since the parameter names will be + * // collapsed to a single letter identifier. To work around this, you can + * // explicitly specify the names of the parameters your task function needs + * // in an array, similar to Angular.js dependency injection. + * + * // This still has an advantage over plain `auto`, since the results a task + * // depends on are still spread into arguments. + * async.autoInject({ + * //... + * write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) { + * callback(null, 'filename'); + * }], + * email_link: ['write_file', function(write_file, callback) { + * callback(null, {'file':write_file, 'email':'user@example.com'}); + * }] + * //... + * }, function(err, results) { + * console.log('err = ', err); + * console.log('email_link = ', results.email_link); + * }); + */ + function autoInject(tasks, callback) { + var newTasks = {}; + + baseForOwn(tasks, function (taskFn, key) { + var params; + + if (isArray(taskFn)) { + params = copyArray(taskFn); + taskFn = params.pop(); + + newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn); + } else if (taskFn.length === 1) { + // no dependencies, use the function as-is + newTasks[key] = taskFn; + } else { + params = parseParams(taskFn); + if (taskFn.length === 0 && params.length === 0) { + throw new Error("autoInject task functions require explicit parameters."); + } + + params.pop(); + + newTasks[key] = params.concat(newTask); + } + + function newTask(results, taskCb) { + var newArgs = arrayMap(params, function (name) { + return results[name]; + }); + newArgs.push(taskCb); + taskFn.apply(null, newArgs); + } + }); + + auto(newTasks, callback); + } + + var hasSetImmediate = typeof setImmediate === 'function' && setImmediate; + var hasNextTick = typeof process === 'object' && typeof process.nextTick === 'function'; + + function fallback(fn) { + setTimeout(fn, 0); + } + + function wrap(defer) { + return baseRest(function (fn, args) { + defer(function () { + fn.apply(null, args); + }); + }); + } + + var _defer; + + if (hasSetImmediate) { + _defer = setImmediate; + } else if (hasNextTick) { + _defer = process.nextTick; + } else { + _defer = fallback; + } + + var setImmediate$1 = wrap(_defer); + + // Simple doubly linked list (https://en.wikipedia.org/wiki/Doubly_linked_list) implementation + // used for queues. This implementation assumes that the node provided by the user can be modified + // to adjust the next and last properties. We implement only the minimal functionality + // for queue support. + function DLL() { + this.head = this.tail = null; + this.length = 0; + } + + function setInitial(dll, node) { + dll.length = 1; + dll.head = dll.tail = node; + } + + DLL.prototype.removeLink = function (node) { + if (node.prev) node.prev.next = node.next;else this.head = node.next; + if (node.next) node.next.prev = node.prev;else this.tail = node.prev; + + node.prev = node.next = null; + this.length -= 1; + return node; + }; + + DLL.prototype.empty = DLL; + + DLL.prototype.insertAfter = function (node, newNode) { + newNode.prev = node; + newNode.next = node.next; + if (node.next) node.next.prev = newNode;else this.tail = newNode; + node.next = newNode; + this.length += 1; + }; + + DLL.prototype.insertBefore = function (node, newNode) { + newNode.prev = node.prev; + newNode.next = node; + if (node.prev) node.prev.next = newNode;else this.head = newNode; + node.prev = newNode; + this.length += 1; + }; + + DLL.prototype.unshift = function (node) { + if (this.head) this.insertBefore(this.head, node);else setInitial(this, node); + }; + + DLL.prototype.push = function (node) { + if (this.tail) this.insertAfter(this.tail, node);else setInitial(this, node); + }; + + DLL.prototype.shift = function () { + return this.head && this.removeLink(this.head); + }; + + DLL.prototype.pop = function () { + return this.tail && this.removeLink(this.tail); + }; + + function queue(worker, concurrency, payload) { + if (concurrency == null) { + concurrency = 1; + } else if (concurrency === 0) { + throw new Error('Concurrency must not be zero'); + } + + function _insert(data, insertAtFront, callback) { + if (callback != null && typeof callback !== 'function') { + throw new Error('task callback must be a function'); + } + q.started = true; + if (!isArray(data)) { + data = [data]; + } + if (data.length === 0 && q.idle()) { + // call drain immediately if there are no tasks + return setImmediate$1(function () { + q.drain(); + }); + } + arrayEach(data, function (task) { + var item = { + data: task, + callback: callback || noop + }; + + if (insertAtFront) { + q._tasks.unshift(item); + } else { + q._tasks.push(item); + } + }); + setImmediate$1(q.process); + } + + function _next(tasks) { + return baseRest(function (args) { + workers -= 1; + + arrayEach(tasks, function (task) { + arrayEach(workersList, function (worker, index) { + if (worker === task) { + workersList.splice(index, 1); + return false; + } + }); + + task.callback.apply(task, args); + + if (args[0] != null) { + q.error(args[0], task.data); + } + }); + + if (workers <= q.concurrency - q.buffer) { + q.unsaturated(); + } + + if (q.idle()) { + q.drain(); + } + q.process(); + }); + } + + var workers = 0; + var workersList = []; + var q = { + _tasks: new DLL(), + concurrency: concurrency, + payload: payload, + saturated: noop, + unsaturated: noop, + buffer: concurrency / 4, + empty: noop, + drain: noop, + error: noop, + started: false, + paused: false, + push: function (data, callback) { + _insert(data, false, callback); + }, + kill: function () { + q.drain = noop; + q._tasks.empty(); + }, + unshift: function (data, callback) { + _insert(data, true, callback); + }, + process: function () { + while (!q.paused && workers < q.concurrency && q._tasks.length) { + var tasks = [], + data = []; + var l = q._tasks.length; + if (q.payload) l = Math.min(l, q.payload); + for (var i = 0; i < l; i++) { + var node = q._tasks.shift(); + tasks.push(node); + data.push(node.data); + } + + if (q._tasks.length === 0) { + q.empty(); + } + workers += 1; + workersList.push(tasks[0]); + + if (workers === q.concurrency) { + q.saturated(); + } + + var cb = onlyOnce(_next(tasks)); + worker(data, cb); + } + }, + length: function () { + return q._tasks.length; + }, + running: function () { + return workers; + }, + workersList: function () { + return workersList; + }, + idle: function () { + return q._tasks.length + workers === 0; + }, + pause: function () { + q.paused = true; + }, + resume: function () { + if (q.paused === false) { + return; + } + q.paused = false; + var resumeCount = Math.min(q.concurrency, q._tasks.length); + // Need to call q.process once per concurrent + // worker to preserve full concurrency after pause + for (var w = 1; w <= resumeCount; w++) { + setImmediate$1(q.process); + } + } + }; + return q; + } + + /** + * A cargo of tasks for the worker function to complete. Cargo inherits all of + * the same methods and event callbacks as [`queue`]{@link module:ControlFlow.queue}. + * @typedef {Object} CargoObject + * @memberOf module:ControlFlow + * @property {Function} length - A function returning the number of items + * waiting to be processed. Invoke like `cargo.length()`. + * @property {number} payload - An `integer` for determining how many tasks + * should be process per round. This property can be changed after a `cargo` is + * created to alter the payload on-the-fly. + * @property {Function} push - Adds `task` to the `queue`. The callback is + * called once the `worker` has finished processing the task. Instead of a + * single task, an array of `tasks` can be submitted. The respective callback is + * used for every task in the list. Invoke like `cargo.push(task, [callback])`. + * @property {Function} saturated - A callback that is called when the + * `queue.length()` hits the concurrency and further tasks will be queued. + * @property {Function} empty - A callback that is called when the last item + * from the `queue` is given to a `worker`. + * @property {Function} drain - A callback that is called when the last item + * from the `queue` has returned from the `worker`. + * @property {Function} idle - a function returning false if there are items + * waiting or being processed, or true if not. Invoke like `cargo.idle()`. + * @property {Function} pause - a function that pauses the processing of tasks + * until `resume()` is called. Invoke like `cargo.pause()`. + * @property {Function} resume - a function that resumes the processing of + * queued tasks when the queue is paused. Invoke like `cargo.resume()`. + * @property {Function} kill - a function that removes the `drain` callback and + * empties remaining tasks from the queue forcing it to go idle. Invoke like `cargo.kill()`. + */ + + /** + * Creates a `cargo` object with the specified payload. Tasks added to the + * cargo will be processed altogether (up to the `payload` limit). If the + * `worker` is in progress, the task is queued until it becomes available. Once + * the `worker` has completed some tasks, each callback of those tasks is + * called. Check out [these](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) [animations](https://camo.githubusercontent.com/f4810e00e1c5f5f8addbe3e9f49064fd5d102699/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130312f38346339323036362d356632392d313165322d383134662d3964336430323431336266642e676966) + * for how `cargo` and `queue` work. + * + * While [`queue`]{@link module:ControlFlow.queue} passes only one task to one of a group of workers + * at a time, cargo passes an array of tasks to a single worker, repeating + * when the worker is finished. + * + * @name cargo + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.queue]{@link module:ControlFlow.queue} + * @category Control Flow + * @param {Function} worker - An asynchronous function for processing an array + * of queued tasks, which must call its `callback(err)` argument when finished, + * with an optional `err` argument. Invoked with `(tasks, callback)`. + * @param {number} [payload=Infinity] - An optional `integer` for determining + * how many tasks should be processed per round; if omitted, the default is + * unlimited. + * @returns {module:ControlFlow.CargoObject} A cargo object to manage the tasks. Callbacks can + * attached as certain properties to listen for specific events during the + * lifecycle of the cargo and inner queue. + * @example + * + * // create a cargo object with payload 2 + * var cargo = async.cargo(function(tasks, callback) { + * for (var i=0; i true + */ + function identity(value) { + return value; + } + + function _createTester(eachfn, check, getResult) { + return function (arr, limit, iteratee, cb) { + function done(err) { + if (cb) { + if (err) { + cb(err); + } else { + cb(null, getResult(false)); + } + } + } + function wrappedIteratee(x, _, callback) { + if (!cb) return callback(); + iteratee(x, function (err, v) { + if (cb) { + if (err) { + cb(err); + cb = iteratee = false; + } else if (check(v)) { + cb(null, getResult(true, x)); + cb = iteratee = false; + } + } + callback(); + }); + } + if (arguments.length > 3) { + cb = cb || noop; + eachfn(arr, limit, wrappedIteratee, done); + } else { + cb = iteratee; + cb = cb || noop; + iteratee = limit; + eachfn(arr, wrappedIteratee, done); + } + }; + } + + function _findGetResult(v, x) { + return x; + } + + /** + * Returns the first value in `coll` that passes an async truth test. The + * `iteratee` is applied in parallel, meaning the first iteratee to return + * `true` will fire the detect `callback` with that result. That means the + * result might not be the first item in the original `coll` (in terms of order) + * that passes the test. + * If order within the original `coll` is important, then look at + * [`detectSeries`]{@link module:Collections.detectSeries}. + * + * @name detect + * @static + * @memberOf module:Collections + * @method + * @alias find + * @category Collections + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The iteratee is passed a `callback(err, truthValue)` which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called as soon as any + * iteratee returns `true`, or after all the `iteratee` functions have finished. + * Result will be the first item in the array that passes the truth test + * (iteratee) or the value `undefined` if none passed. Invoked with + * (err, result). + * @example + * + * async.detect(['file1','file2','file3'], function(filePath, callback) { + * fs.access(filePath, function(err) { + * callback(null, !err) + * }); + * }, function(err, result) { + * // result now equals the first file in the list that exists + * }); + */ + var detect = _createTester(eachOf, identity, _findGetResult); + + /** + * The same as [`detect`]{@link module:Collections.detect} but runs a maximum of `limit` async operations at a + * time. + * + * @name detectLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.detect]{@link module:Collections.detect} + * @alias findLimit + * @category Collections + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The iteratee is passed a `callback(err, truthValue)` which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called as soon as any + * iteratee returns `true`, or after all the `iteratee` functions have finished. + * Result will be the first item in the array that passes the truth test + * (iteratee) or the value `undefined` if none passed. Invoked with + * (err, result). + */ + var detectLimit = _createTester(eachOfLimit, identity, _findGetResult); + + /** + * The same as [`detect`]{@link module:Collections.detect} but runs only a single async operation at a time. + * + * @name detectSeries + * @static + * @memberOf module:Collections + * @method + * @see [async.detect]{@link module:Collections.detect} + * @alias findSeries + * @category Collections + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The iteratee is passed a `callback(err, truthValue)` which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called as soon as any + * iteratee returns `true`, or after all the `iteratee` functions have finished. + * Result will be the first item in the array that passes the truth test + * (iteratee) or the value `undefined` if none passed. Invoked with + * (err, result). + */ + var detectSeries = _createTester(eachOfSeries, identity, _findGetResult); + + function consoleFunc(name) { + return baseRest(function (fn, args) { + fn.apply(null, args.concat([baseRest(function (err, args) { + if (typeof console === 'object') { + if (err) { + if (console.error) { + console.error(err); + } + } else if (console[name]) { + arrayEach(args, function (x) { + console[name](x); + }); + } + } + })])); + }); + } + + /** + * Logs the result of an `async` function to the `console` using `console.dir` + * to display the properties of the resulting object. Only works in Node.js or + * in browsers that support `console.dir` and `console.error` (such as FF and + * Chrome). If multiple arguments are returned from the async function, + * `console.dir` is called on each argument in order. + * + * @name dir + * @static + * @memberOf module:Utils + * @method + * @category Util + * @param {Function} function - The function you want to eventually apply all + * arguments to. + * @param {...*} arguments... - Any number of arguments to apply to the function. + * @example + * + * // in a module + * var hello = function(name, callback) { + * setTimeout(function() { + * callback(null, {hello: name}); + * }, 1000); + * }; + * + * // in the node repl + * node> async.dir(hello, 'world'); + * {hello: 'world'} + */ + var dir = consoleFunc('dir'); + + /** + * The post-check version of [`during`]{@link module:ControlFlow.during}. To reflect the difference in + * the order of operations, the arguments `test` and `fn` are switched. + * + * Also a version of [`doWhilst`]{@link module:ControlFlow.doWhilst} with asynchronous `test` function. + * @name doDuring + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.during]{@link module:ControlFlow.during} + * @category Control Flow + * @param {Function} fn - A function which is called each time `test` passes. + * The function is passed a `callback(err)`, which must be called once it has + * completed with an optional `err` argument. Invoked with (callback). + * @param {Function} test - asynchronous truth test to perform before each + * execution of `fn`. Invoked with (...args, callback), where `...args` are the + * non-error args from the previous callback of `fn`. + * @param {Function} [callback] - A callback which is called after the test + * function has failed and repeated execution of `fn` has stopped. `callback` + * will be passed an error if one occured, otherwise `null`. + */ + function doDuring(fn, test, callback) { + callback = onlyOnce(callback || noop); + + var next = baseRest(function (err, args) { + if (err) return callback(err); + args.push(check); + test.apply(this, args); + }); + + function check(err, truth) { + if (err) return callback(err); + if (!truth) return callback(null); + fn(next); + } + + check(null, true); + } + + /** + * The post-check version of [`whilst`]{@link module:ControlFlow.whilst}. To reflect the difference in + * the order of operations, the arguments `test` and `iteratee` are switched. + * + * `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript. + * + * @name doWhilst + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.whilst]{@link module:ControlFlow.whilst} + * @category Control Flow + * @param {Function} iteratee - A function which is called each time `test` + * passes. The function is passed a `callback(err)`, which must be called once + * it has completed with an optional `err` argument. Invoked with (callback). + * @param {Function} test - synchronous truth test to perform after each + * execution of `iteratee`. Invoked with Invoked with the non-error callback + * results of `iteratee`. + * @param {Function} [callback] - A callback which is called after the test + * function has failed and repeated execution of `iteratee` has stopped. + * `callback` will be passed an error and any arguments passed to the final + * `iteratee`'s callback. Invoked with (err, [results]); + */ + function doWhilst(iteratee, test, callback) { + callback = onlyOnce(callback || noop); + var next = baseRest(function (err, args) { + if (err) return callback(err); + if (test.apply(this, args)) return iteratee(next); + callback.apply(null, [null].concat(args)); + }); + iteratee(next); + } + + /** + * Like ['doWhilst']{@link module:ControlFlow.doWhilst}, except the `test` is inverted. Note the + * argument ordering differs from `until`. + * + * @name doUntil + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.doWhilst]{@link module:ControlFlow.doWhilst} + * @category Control Flow + * @param {Function} fn - A function which is called each time `test` fails. + * The function is passed a `callback(err)`, which must be called once it has + * completed with an optional `err` argument. Invoked with (callback). + * @param {Function} test - synchronous truth test to perform after each + * execution of `fn`. Invoked with the non-error callback results of `fn`. + * @param {Function} [callback] - A callback which is called after the test + * function has passed and repeated execution of `fn` has stopped. `callback` + * will be passed an error and any arguments passed to the final `fn`'s + * callback. Invoked with (err, [results]); + */ + function doUntil(fn, test, callback) { + doWhilst(fn, function () { + return !test.apply(this, arguments); + }, callback); + } + + /** + * Like [`whilst`]{@link module:ControlFlow.whilst}, except the `test` is an asynchronous function that + * is passed a callback in the form of `function (err, truth)`. If error is + * passed to `test` or `fn`, the main callback is immediately called with the + * value of the error. + * + * @name during + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.whilst]{@link module:ControlFlow.whilst} + * @category Control Flow + * @param {Function} test - asynchronous truth test to perform before each + * execution of `fn`. Invoked with (callback). + * @param {Function} fn - A function which is called each time `test` passes. + * The function is passed a `callback(err)`, which must be called once it has + * completed with an optional `err` argument. Invoked with (callback). + * @param {Function} [callback] - A callback which is called after the test + * function has failed and repeated execution of `fn` has stopped. `callback` + * will be passed an error, if one occured, otherwise `null`. + * @example + * + * var count = 0; + * + * async.during( + * function (callback) { + * return callback(null, count < 5); + * }, + * function (callback) { + * count++; + * setTimeout(callback, 1000); + * }, + * function (err) { + * // 5 seconds have passed + * } + * ); + */ + function during(test, fn, callback) { + callback = onlyOnce(callback || noop); + + function next(err) { + if (err) return callback(err); + test(check); + } + + function check(err, truth) { + if (err) return callback(err); + if (!truth) return callback(null); + fn(next); + } + + test(check); + } + + function _withoutIndex(iteratee) { + return function (value, index, callback) { + return iteratee(value, callback); + }; + } + + /** + * Applies the function `iteratee` to each item in `coll`, in parallel. + * The `iteratee` is called with an item from the list, and a callback for when + * it has finished. If the `iteratee` passes an error to its `callback`, the + * main `callback` (for the `each` function) is immediately called with the + * error. + * + * Note, that since this function applies `iteratee` to each item in parallel, + * there is no guarantee that the iteratee functions will complete in order. + * + * @name each + * @static + * @memberOf module:Collections + * @method + * @alias forEach + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each item + * in `coll`. The iteratee is passed a `callback(err)` which must be called once + * it has completed. If no error has occurred, the `callback` should be run + * without arguments or with an explicit `null` argument. The array index is not + * passed to the iteratee. Invoked with (item, callback). If you need the index, + * use `eachOf`. + * @param {Function} [callback] - A callback which is called when all + * `iteratee` functions have finished, or an error occurs. Invoked with (err). + * @example + * + * // assuming openFiles is an array of file names and saveFile is a function + * // to save the modified contents of that file: + * + * async.each(openFiles, saveFile, function(err){ + * // if any of the saves produced an error, err would equal that error + * }); + * + * // assuming openFiles is an array of file names + * async.each(openFiles, function(file, callback) { + * + * // Perform operation on file here. + * console.log('Processing file ' + file); + * + * if( file.length > 32 ) { + * console.log('This file name is too long'); + * callback('File name too long'); + * } else { + * // Do work to process file here + * console.log('File processed'); + * callback(); + * } + * }, function(err) { + * // if any of the file processing produced an error, err would equal that error + * if( err ) { + * // One of the iterations produced an error. + * // All processing will now stop. + * console.log('A file failed to process'); + * } else { + * console.log('All files have been processed successfully'); + * } + * }); + */ + function eachLimit(coll, iteratee, callback) { + eachOf(coll, _withoutIndex(iteratee), callback); + } + + /** + * The same as [`each`]{@link module:Collections.each} but runs a maximum of `limit` async operations at a time. + * + * @name eachLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.each]{@link module:Collections.each} + * @alias forEachLimit + * @category Collection + * @param {Array|Iterable|Object} coll - A colleciton to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A function to apply to each item in `coll`. The + * iteratee is passed a `callback(err)` which must be called once it has + * completed. If no error has occurred, the `callback` should be run without + * arguments or with an explicit `null` argument. The array index is not passed + * to the iteratee. Invoked with (item, callback). If you need the index, use + * `eachOfLimit`. + * @param {Function} [callback] - A callback which is called when all + * `iteratee` functions have finished, or an error occurs. Invoked with (err). + */ + function eachLimit$1(coll, limit, iteratee, callback) { + _eachOfLimit(limit)(coll, _withoutIndex(iteratee), callback); + } + + /** + * The same as [`each`]{@link module:Collections.each} but runs only a single async operation at a time. + * + * @name eachSeries + * @static + * @memberOf module:Collections + * @method + * @see [async.each]{@link module:Collections.each} + * @alias forEachSeries + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each + * item in `coll`. The iteratee is passed a `callback(err)` which must be called + * once it has completed. If no error has occurred, the `callback` should be run + * without arguments or with an explicit `null` argument. The array index is + * not passed to the iteratee. Invoked with (item, callback). If you need the + * index, use `eachOfSeries`. + * @param {Function} [callback] - A callback which is called when all + * `iteratee` functions have finished, or an error occurs. Invoked with (err). + */ + var eachSeries = doLimit(eachLimit$1, 1); + + /** + * Wrap an async function and ensure it calls its callback on a later tick of + * the event loop. If the function already calls its callback on a next tick, + * no extra deferral is added. This is useful for preventing stack overflows + * (`RangeError: Maximum call stack size exceeded`) and generally keeping + * [Zalgo](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony) + * contained. + * + * @name ensureAsync + * @static + * @memberOf module:Utils + * @method + * @category Util + * @param {Function} fn - an async function, one that expects a node-style + * callback as its last argument. + * @returns {Function} Returns a wrapped function with the exact same call + * signature as the function passed in. + * @example + * + * function sometimesAsync(arg, callback) { + * if (cache[arg]) { + * return callback(null, cache[arg]); // this would be synchronous!! + * } else { + * doSomeIO(arg, callback); // this IO would be asynchronous + * } + * } + * + * // this has a risk of stack overflows if many results are cached in a row + * async.mapSeries(args, sometimesAsync, done); + * + * // this will defer sometimesAsync's callback if necessary, + * // preventing stack overflows + * async.mapSeries(args, async.ensureAsync(sometimesAsync), done); + */ + function ensureAsync(fn) { + return initialParams(function (args, callback) { + var sync = true; + args.push(function () { + var innerArgs = arguments; + if (sync) { + setImmediate$1(function () { + callback.apply(null, innerArgs); + }); + } else { + callback.apply(null, innerArgs); + } + }); + fn.apply(this, args); + sync = false; + }); + } + + function notId(v) { + return !v; + } + + /** + * Returns `true` if every element in `coll` satisfies an async test. If any + * iteratee call returns `false`, the main `callback` is immediately called. + * + * @name every + * @static + * @memberOf module:Collections + * @method + * @alias all + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in the + * collection in parallel. The iteratee is passed a `callback(err, truthValue)` + * which must be called with a boolean argument once it has completed. Invoked + * with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Result will be either `true` or `false` + * depending on the values of the async tests. Invoked with (err, result). + * @example + * + * async.every(['file1','file2','file3'], function(filePath, callback) { + * fs.access(filePath, function(err) { + * callback(null, !err) + * }); + * }, function(err, result) { + * // if result is true then every file exists + * }); + */ + var every = _createTester(eachOf, notId, notId); + + /** + * The same as [`every`]{@link module:Collections.every} but runs a maximum of `limit` async operations at a time. + * + * @name everyLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.every]{@link module:Collections.every} + * @alias allLimit + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A truth test to apply to each item in the + * collection in parallel. The iteratee is passed a `callback(err, truthValue)` + * which must be called with a boolean argument once it has completed. Invoked + * with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Result will be either `true` or `false` + * depending on the values of the async tests. Invoked with (err, result). + */ + var everyLimit = _createTester(eachOfLimit, notId, notId); + + /** + * The same as [`every`]{@link module:Collections.every} but runs only a single async operation at a time. + * + * @name everySeries + * @static + * @memberOf module:Collections + * @method + * @see [async.every]{@link module:Collections.every} + * @alias allSeries + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in the + * collection in parallel. The iteratee is passed a `callback(err, truthValue)` + * which must be called with a boolean argument once it has completed. Invoked + * with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Result will be either `true` or `false` + * depending on the values of the async tests. Invoked with (err, result). + */ + var everySeries = doLimit(everyLimit, 1); + + function _filter(eachfn, arr, iteratee, callback) { + callback = once(callback || noop); + var results = []; + eachfn(arr, function (x, index, callback) { + iteratee(x, function (err, v) { + if (err) { + callback(err); + } else { + if (v) { + results.push({ index: index, value: x }); + } + callback(); + } + }); + }, function (err) { + if (err) { + callback(err); + } else { + callback(null, arrayMap(results.sort(function (a, b) { + return a.index - b.index; + }), baseProperty('value'))); + } + }); + } + + /** + * Returns a new array of all the values in `coll` which pass an async truth + * test. This operation is performed in parallel, but the results array will be + * in the same order as the original. + * + * @name filter + * @static + * @memberOf module:Collections + * @method + * @alias select + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The `iteratee` is passed a `callback(err, truthValue)`, which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Invoked with (err, results). + * @example + * + * async.filter(['file1','file2','file3'], function(filePath, callback) { + * fs.access(filePath, function(err) { + * callback(null, !err) + * }); + * }, function(err, results) { + * // results now equals an array of the existing files + * }); + */ + var filter = doParallel(_filter); + + /** + * The same as [`filter`]{@link module:Collections.filter} but runs a maximum of `limit` async operations at a + * time. + * + * @name filterLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.filter]{@link module:Collections.filter} + * @alias selectLimit + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The `iteratee` is passed a `callback(err, truthValue)`, which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Invoked with (err, results). + */ + var filterLimit = doParallelLimit(_filter); + + /** + * The same as [`filter`]{@link module:Collections.filter} but runs only a single async operation at a time. + * + * @name filterSeries + * @static + * @memberOf module:Collections + * @method + * @see [async.filter]{@link module:Collections.filter} + * @alias selectSeries + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The `iteratee` is passed a `callback(err, truthValue)`, which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Invoked with (err, results) + */ + var filterSeries = doLimit(filterLimit, 1); + + /** + * Calls the asynchronous function `fn` with a callback parameter that allows it + * to call itself again, in series, indefinitely. + * If an error is passed to the + * callback then `errback` is called with the error, and execution stops, + * otherwise it will never be called. + * + * @name forever + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Function} fn - a function to call repeatedly. Invoked with (next). + * @param {Function} [errback] - when `fn` passes an error to it's callback, + * this function will be called, and execution stops. Invoked with (err). + * @example + * + * async.forever( + * function(next) { + * // next is suitable for passing to things that need a callback(err [, whatever]); + * // it will result in this function being called again. + * }, + * function(err) { + * // if next is called with a value in its first parameter, it will appear + * // in here as 'err', and execution will stop. + * } + * ); + */ + function forever(fn, errback) { + var done = onlyOnce(errback || noop); + var task = ensureAsync(fn); + + function next(err) { + if (err) return done(err); + task(next); + } + next(); + } + + /** + * Logs the result of an `async` function to the `console`. Only works in + * Node.js or in browsers that support `console.log` and `console.error` (such + * as FF and Chrome). If multiple arguments are returned from the async + * function, `console.log` is called on each argument in order. + * + * @name log + * @static + * @memberOf module:Utils + * @method + * @category Util + * @param {Function} function - The function you want to eventually apply all + * arguments to. + * @param {...*} arguments... - Any number of arguments to apply to the function. + * @example + * + * // in a module + * var hello = function(name, callback) { + * setTimeout(function() { + * callback(null, 'hello ' + name); + * }, 1000); + * }; + * + * // in the node repl + * node> async.log(hello, 'world'); + * 'hello world' + */ + var log = consoleFunc('log'); + + /** + * The same as [`mapValues`]{@link module:Collections.mapValues} but runs a maximum of `limit` async operations at a + * time. + * + * @name mapValuesLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.mapValues]{@link module:Collections.mapValues} + * @category Collection + * @param {Object} obj - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A function to apply to each value in `obj`. + * The iteratee is passed a `callback(err, transformed)` which must be called + * once it has completed with an error (which can be `null`) and a + * transformed value. Invoked with (value, key, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Result is an object of the + * transformed values from the `obj`. Invoked with (err, result). + */ + function mapValuesLimit(obj, limit, iteratee, callback) { + callback = once(callback || noop); + var newObj = {}; + eachOfLimit(obj, limit, function (val, key, next) { + iteratee(val, key, function (err, result) { + if (err) return next(err); + newObj[key] = result; + next(); + }); + }, function (err) { + callback(err, newObj); + }); + } + + /** + * A relative of [`map`]{@link module:Collections.map}, designed for use with objects. + * + * Produces a new Object by mapping each value of `obj` through the `iteratee` + * function. The `iteratee` is called each `value` and `key` from `obj` and a + * callback for when it has finished processing. Each of these callbacks takes + * two arguments: an `error`, and the transformed item from `obj`. If `iteratee` + * passes an error to its callback, the main `callback` (for the `mapValues` + * function) is immediately called with the error. + * + * Note, the order of the keys in the result is not guaranteed. The keys will + * be roughly in the order they complete, (but this is very engine-specific) + * + * @name mapValues + * @static + * @memberOf module:Collections + * @method + * @category Collection + * @param {Object} obj - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each value and key in + * `coll`. The iteratee is passed a `callback(err, transformed)` which must be + * called once it has completed with an error (which can be `null`) and a + * transformed value. Invoked with (value, key, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Results is an array of the + * transformed items from the `obj`. Invoked with (err, result). + * @example + * + * async.mapValues({ + * f1: 'file1', + * f2: 'file2', + * f3: 'file3' + * }, function (file, key, callback) { + * fs.stat(file, callback); + * }, function(err, result) { + * // results is now a map of stats for each file, e.g. + * // { + * // f1: [stats for file1], + * // f2: [stats for file2], + * // f3: [stats for file3] + * // } + * }); + */ + + var mapValues = doLimit(mapValuesLimit, Infinity); + + /** + * The same as [`mapValues`]{@link module:Collections.mapValues} but runs only a single async operation at a time. + * + * @name mapValuesSeries + * @static + * @memberOf module:Collections + * @method + * @see [async.mapValues]{@link module:Collections.mapValues} + * @category Collection + * @param {Object} obj - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each value in `obj`. + * The iteratee is passed a `callback(err, transformed)` which must be called + * once it has completed with an error (which can be `null`) and a + * transformed value. Invoked with (value, key, callback). + * @param {Function} [callback] - A callback which is called when all `iteratee` + * functions have finished, or an error occurs. Result is an object of the + * transformed values from the `obj`. Invoked with (err, result). + */ + var mapValuesSeries = doLimit(mapValuesLimit, 1); + + function has(obj, key) { + return key in obj; + } + + /** + * Caches the results of an `async` function. When creating a hash to store + * function results against, the callback is omitted from the hash and an + * optional hash function can be used. + * + * If no hash function is specified, the first argument is used as a hash key, + * which may work reasonably if it is a string or a data type that converts to a + * distinct string. Note that objects and arrays will not behave reasonably. + * Neither will cases where the other arguments are significant. In such cases, + * specify your own hash function. + * + * The cache of results is exposed as the `memo` property of the function + * returned by `memoize`. + * + * @name memoize + * @static + * @memberOf module:Utils + * @method + * @category Util + * @param {Function} fn - The function to proxy and cache results from. + * @param {Function} hasher - An optional function for generating a custom hash + * for storing results. It has all the arguments applied to it apart from the + * callback, and must be synchronous. + * @returns {Function} a memoized version of `fn` + * @example + * + * var slow_fn = function(name, callback) { + * // do something + * callback(null, result); + * }; + * var fn = async.memoize(slow_fn); + * + * // fn can now be used as if it were slow_fn + * fn('some name', function() { + * // callback + * }); + */ + function memoize(fn, hasher) { + var memo = Object.create(null); + var queues = Object.create(null); + hasher = hasher || identity; + var memoized = initialParams(function memoized(args, callback) { + var key = hasher.apply(null, args); + if (has(memo, key)) { + setImmediate$1(function () { + callback.apply(null, memo[key]); + }); + } else if (has(queues, key)) { + queues[key].push(callback); + } else { + queues[key] = [callback]; + fn.apply(null, args.concat([baseRest(function (args) { + memo[key] = args; + var q = queues[key]; + delete queues[key]; + for (var i = 0, l = q.length; i < l; i++) { + q[i].apply(null, args); + } + })])); + } + }); + memoized.memo = memo; + memoized.unmemoized = fn; + return memoized; + } + + /** + * Calls `callback` on a later loop around the event loop. In Node.js this just + * calls `setImmediate`. In the browser it will use `setImmediate` if + * available, otherwise `setTimeout(callback, 0)`, which means other higher + * priority events may precede the execution of `callback`. + * + * This is used internally for browser-compatibility purposes. + * + * @name nextTick + * @static + * @memberOf module:Utils + * @method + * @alias setImmediate + * @category Util + * @param {Function} callback - The function to call on a later loop around + * the event loop. Invoked with (args...). + * @param {...*} args... - any number of additional arguments to pass to the + * callback on the next tick. + * @example + * + * var call_order = []; + * async.nextTick(function() { + * call_order.push('two'); + * // call_order now equals ['one','two'] + * }); + * call_order.push('one'); + * + * async.setImmediate(function (a, b, c) { + * // a, b, and c equal 1, 2, and 3 + * }, 1, 2, 3); + */ + var _defer$1; + + if (hasNextTick) { + _defer$1 = process.nextTick; + } else if (hasSetImmediate) { + _defer$1 = setImmediate; + } else { + _defer$1 = fallback; + } + + var nextTick = wrap(_defer$1); + + function _parallel(eachfn, tasks, callback) { + callback = callback || noop; + var results = isArrayLike(tasks) ? [] : {}; + + eachfn(tasks, function (task, key, callback) { + task(baseRest(function (err, args) { + if (args.length <= 1) { + args = args[0]; + } + results[key] = args; + callback(err); + })); + }, function (err) { + callback(err, results); + }); + } + + /** + * Run the `tasks` collection of functions in parallel, without waiting until + * the previous function has completed. If any of the functions pass an error to + * its callback, the main `callback` is immediately called with the value of the + * error. Once the `tasks` have completed, the results are passed to the final + * `callback` as an array. + * + * **Note:** `parallel` is about kicking-off I/O tasks in parallel, not about + * parallel execution of code. If your tasks do not use any timers or perform + * any I/O, they will actually be executed in series. Any synchronous setup + * sections for each task will happen one after the other. JavaScript remains + * single-threaded. + * + * It is also possible to use an object instead of an array. Each property will + * be run as a function and the results will be passed to the final `callback` + * as an object instead of an array. This can be a more readable way of handling + * results from {@link async.parallel}. + * + * @name parallel + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Array|Iterable|Object} tasks - A collection containing functions to run. + * Each function is passed a `callback(err, result)` which it must call on + * completion with an error `err` (which can be `null`) and an optional `result` + * value. + * @param {Function} [callback] - An optional callback to run once all the + * functions have completed successfully. This function gets a results array + * (or object) containing all the result arguments passed to the task callbacks. + * Invoked with (err, results). + * @example + * async.parallel([ + * function(callback) { + * setTimeout(function() { + * callback(null, 'one'); + * }, 200); + * }, + * function(callback) { + * setTimeout(function() { + * callback(null, 'two'); + * }, 100); + * } + * ], + * // optional callback + * function(err, results) { + * // the results array will equal ['one','two'] even though + * // the second function had a shorter timeout. + * }); + * + * // an example using an object instead of an array + * async.parallel({ + * one: function(callback) { + * setTimeout(function() { + * callback(null, 1); + * }, 200); + * }, + * two: function(callback) { + * setTimeout(function() { + * callback(null, 2); + * }, 100); + * } + * }, function(err, results) { + * // results is now equals to: {one: 1, two: 2} + * }); + */ + function parallelLimit(tasks, callback) { + _parallel(eachOf, tasks, callback); + } + + /** + * The same as [`parallel`]{@link module:ControlFlow.parallel} but runs a maximum of `limit` async operations at a + * time. + * + * @name parallelLimit + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.parallel]{@link module:ControlFlow.parallel} + * @category Control Flow + * @param {Array|Collection} tasks - A collection containing functions to run. + * Each function is passed a `callback(err, result)` which it must call on + * completion with an error `err` (which can be `null`) and an optional `result` + * value. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} [callback] - An optional callback to run once all the + * functions have completed successfully. This function gets a results array + * (or object) containing all the result arguments passed to the task callbacks. + * Invoked with (err, results). + */ + function parallelLimit$1(tasks, limit, callback) { + _parallel(_eachOfLimit(limit), tasks, callback); + } + + /** + * A queue of tasks for the worker function to complete. + * @typedef {Object} QueueObject + * @memberOf module:ControlFlow + * @property {Function} length - a function returning the number of items + * waiting to be processed. Invoke with `queue.length()`. + * @property {boolean} started - a boolean indicating whether or not any + * items have been pushed and processed by the queue. + * @property {Function} running - a function returning the number of items + * currently being processed. Invoke with `queue.running()`. + * @property {Function} workersList - a function returning the array of items + * currently being processed. Invoke with `queue.workersList()`. + * @property {Function} idle - a function returning false if there are items + * waiting or being processed, or true if not. Invoke with `queue.idle()`. + * @property {number} concurrency - an integer for determining how many `worker` + * functions should be run in parallel. This property can be changed after a + * `queue` is created to alter the concurrency on-the-fly. + * @property {Function} push - add a new task to the `queue`. Calls `callback` + * once the `worker` has finished processing the task. Instead of a single task, + * a `tasks` array can be submitted. The respective callback is used for every + * task in the list. Invoke with `queue.push(task, [callback])`, + * @property {Function} unshift - add a new task to the front of the `queue`. + * Invoke with `queue.unshift(task, [callback])`. + * @property {Function} saturated - a callback that is called when the number of + * running workers hits the `concurrency` limit, and further tasks will be + * queued. + * @property {Function} unsaturated - a callback that is called when the number + * of running workers is less than the `concurrency` & `buffer` limits, and + * further tasks will not be queued. + * @property {number} buffer - A minimum threshold buffer in order to say that + * the `queue` is `unsaturated`. + * @property {Function} empty - a callback that is called when the last item + * from the `queue` is given to a `worker`. + * @property {Function} drain - a callback that is called when the last item + * from the `queue` has returned from the `worker`. + * @property {Function} error - a callback that is called when a task errors. + * Has the signature `function(error, task)`. + * @property {boolean} paused - a boolean for determining whether the queue is + * in a paused state. + * @property {Function} pause - a function that pauses the processing of tasks + * until `resume()` is called. Invoke with `queue.pause()`. + * @property {Function} resume - a function that resumes the processing of + * queued tasks when the queue is paused. Invoke with `queue.resume()`. + * @property {Function} kill - a function that removes the `drain` callback and + * empties remaining tasks from the queue forcing it to go idle. Invoke with `queue.kill()`. + */ + + /** + * Creates a `queue` object with the specified `concurrency`. Tasks added to the + * `queue` are processed in parallel (up to the `concurrency` limit). If all + * `worker`s are in progress, the task is queued until one becomes available. + * Once a `worker` completes a `task`, that `task`'s callback is called. + * + * @name queue + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Function} worker - An asynchronous function for processing a queued + * task, which must call its `callback(err)` argument when finished, with an + * optional `error` as an argument. If you want to handle errors from an + * individual task, pass a callback to `q.push()`. Invoked with + * (task, callback). + * @param {number} [concurrency=1] - An `integer` for determining how many + * `worker` functions should be run in parallel. If omitted, the concurrency + * defaults to `1`. If the concurrency is `0`, an error is thrown. + * @returns {module:ControlFlow.QueueObject} A queue object to manage the tasks. Callbacks can + * attached as certain properties to listen for specific events during the + * lifecycle of the queue. + * @example + * + * // create a queue object with concurrency 2 + * var q = async.queue(function(task, callback) { + * console.log('hello ' + task.name); + * callback(); + * }, 2); + * + * // assign a callback + * q.drain = function() { + * console.log('all items have been processed'); + * }; + * + * // add some items to the queue + * q.push({name: 'foo'}, function(err) { + * console.log('finished processing foo'); + * }); + * q.push({name: 'bar'}, function (err) { + * console.log('finished processing bar'); + * }); + * + * // add some items to the queue (batch-wise) + * q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) { + * console.log('finished processing item'); + * }); + * + * // add some items to the front of the queue + * q.unshift({name: 'bar'}, function (err) { + * console.log('finished processing bar'); + * }); + */ + function queue$1 (worker, concurrency) { + return queue(function (items, cb) { + worker(items[0], cb); + }, concurrency, 1); + } + + /** + * The same as [async.queue]{@link module:ControlFlow.queue} only tasks are assigned a priority and + * completed in ascending priority order. + * + * @name priorityQueue + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.queue]{@link module:ControlFlow.queue} + * @category Control Flow + * @param {Function} worker - An asynchronous function for processing a queued + * task, which must call its `callback(err)` argument when finished, with an + * optional `error` as an argument. If you want to handle errors from an + * individual task, pass a callback to `q.push()`. Invoked with + * (task, callback). + * @param {number} concurrency - An `integer` for determining how many `worker` + * functions should be run in parallel. If omitted, the concurrency defaults to + * `1`. If the concurrency is `0`, an error is thrown. + * @returns {module:ControlFlow.QueueObject} A priorityQueue object to manage the tasks. There are two + * differences between `queue` and `priorityQueue` objects: + * * `push(task, priority, [callback])` - `priority` should be a number. If an + * array of `tasks` is given, all tasks will be assigned the same priority. + * * The `unshift` method was removed. + */ + function priorityQueue (worker, concurrency) { + // Start with a normal queue + var q = queue$1(worker, concurrency); + + // Override push to accept second parameter representing priority + q.push = function (data, priority, callback) { + if (callback == null) callback = noop; + if (typeof callback !== 'function') { + throw new Error('task callback must be a function'); + } + q.started = true; + if (!isArray(data)) { + data = [data]; + } + if (data.length === 0) { + // call drain immediately if there are no tasks + return setImmediate$1(function () { + q.drain(); + }); + } + + priority = priority || 0; + var nextNode = q._tasks.head; + while (nextNode && priority >= nextNode.priority) { + nextNode = nextNode.next; + } + + arrayEach(data, function (task) { + var item = { + data: task, + priority: priority, + callback: callback + }; + + if (nextNode) { + q._tasks.insertBefore(nextNode, item); + } else { + q._tasks.push(item); + } + }); + setImmediate$1(q.process); + }; + + // Remove unshift function + delete q.unshift; + + return q; + } + + /** + * Runs the `tasks` array of functions in parallel, without waiting until the + * previous function has completed. Once any the `tasks` completed or pass an + * error to its callback, the main `callback` is immediately called. It's + * equivalent to `Promise.race()`. + * + * @name race + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Array} tasks - An array containing functions to run. Each function + * is passed a `callback(err, result)` which it must call on completion with an + * error `err` (which can be `null`) and an optional `result` value. + * @param {Function} callback - A callback to run once any of the functions have + * completed. This function gets an error or result from the first function that + * completed. Invoked with (err, result). + * @returns undefined + * @example + * + * async.race([ + * function(callback) { + * setTimeout(function() { + * callback(null, 'one'); + * }, 200); + * }, + * function(callback) { + * setTimeout(function() { + * callback(null, 'two'); + * }, 100); + * } + * ], + * // main callback + * function(err, result) { + * // the result will be equal to 'two' as it finishes earlier + * }); + */ + function race(tasks, callback) { + callback = once(callback || noop); + if (!isArray(tasks)) return callback(new TypeError('First argument to race must be an array of functions')); + if (!tasks.length) return callback(); + arrayEach(tasks, function (task) { + task(callback); + }); + } + + var slice = Array.prototype.slice; + + /** + * Same as [`reduce`]{@link module:Collections.reduce}, only operates on `array` in reverse order. + * + * @name reduceRight + * @static + * @memberOf module:Collections + * @method + * @see [async.reduce]{@link module:Collections.reduce} + * @alias foldr + * @category Collection + * @param {Array} array - A collection to iterate over. + * @param {*} memo - The initial state of the reduction. + * @param {Function} iteratee - A function applied to each item in the + * array to produce the next step in the reduction. The `iteratee` is passed a + * `callback(err, reduction)` which accepts an optional error as its first + * argument, and the state of the reduction as the second. If an error is + * passed to the callback, the reduction is stopped and the main `callback` is + * immediately called with the error. Invoked with (memo, item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Result is the reduced value. Invoked with + * (err, result). + */ + function reduceRight(array, memo, iteratee, callback) { + var reversed = slice.call(array).reverse(); + reduce(reversed, memo, iteratee, callback); + } + + /** + * Wraps the function in another function that always returns data even when it + * errors. + * + * The object returned has either the property `error` or `value`. + * + * @name reflect + * @static + * @memberOf module:Utils + * @method + * @category Util + * @param {Function} fn - The function you want to wrap + * @returns {Function} - A function that always passes null to it's callback as + * the error. The second argument to the callback will be an `object` with + * either an `error` or a `value` property. + * @example + * + * async.parallel([ + * async.reflect(function(callback) { + * // do some stuff ... + * callback(null, 'one'); + * }), + * async.reflect(function(callback) { + * // do some more stuff but error ... + * callback('bad stuff happened'); + * }), + * async.reflect(function(callback) { + * // do some more stuff ... + * callback(null, 'two'); + * }) + * ], + * // optional callback + * function(err, results) { + * // values + * // results[0].value = 'one' + * // results[1].error = 'bad stuff happened' + * // results[2].value = 'two' + * }); + */ + function reflect(fn) { + return initialParams(function reflectOn(args, reflectCallback) { + args.push(baseRest(function callback(err, cbArgs) { + if (err) { + reflectCallback(null, { + error: err + }); + } else { + var value = null; + if (cbArgs.length === 1) { + value = cbArgs[0]; + } else if (cbArgs.length > 1) { + value = cbArgs; + } + reflectCallback(null, { + value: value + }); + } + })); + + return fn.apply(this, args); + }); + } + + function reject$1(eachfn, arr, iteratee, callback) { + _filter(eachfn, arr, function (value, cb) { + iteratee(value, function (err, v) { + if (err) { + cb(err); + } else { + cb(null, !v); + } + }); + }, callback); + } + + /** + * The opposite of [`filter`]{@link module:Collections.filter}. Removes values that pass an `async` truth test. + * + * @name reject + * @static + * @memberOf module:Collections + * @method + * @see [async.filter]{@link module:Collections.filter} + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The `iteratee` is passed a `callback(err, truthValue)`, which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Invoked with (err, results). + * @example + * + * async.reject(['file1','file2','file3'], function(filePath, callback) { + * fs.access(filePath, function(err) { + * callback(null, !err) + * }); + * }, function(err, results) { + * // results now equals an array of missing files + * createFiles(results); + * }); + */ + var reject = doParallel(reject$1); + + /** + * A helper function that wraps an array or an object of functions with reflect. + * + * @name reflectAll + * @static + * @memberOf module:Utils + * @method + * @see [async.reflect]{@link module:Utils.reflect} + * @category Util + * @param {Array} tasks - The array of functions to wrap in `async.reflect`. + * @returns {Array} Returns an array of functions, each function wrapped in + * `async.reflect` + * @example + * + * let tasks = [ + * function(callback) { + * setTimeout(function() { + * callback(null, 'one'); + * }, 200); + * }, + * function(callback) { + * // do some more stuff but error ... + * callback(new Error('bad stuff happened')); + * }, + * function(callback) { + * setTimeout(function() { + * callback(null, 'two'); + * }, 100); + * } + * ]; + * + * async.parallel(async.reflectAll(tasks), + * // optional callback + * function(err, results) { + * // values + * // results[0].value = 'one' + * // results[1].error = Error('bad stuff happened') + * // results[2].value = 'two' + * }); + * + * // an example using an object instead of an array + * let tasks = { + * one: function(callback) { + * setTimeout(function() { + * callback(null, 'one'); + * }, 200); + * }, + * two: function(callback) { + * callback('two'); + * }, + * three: function(callback) { + * setTimeout(function() { + * callback(null, 'three'); + * }, 100); + * } + * }; + * + * async.parallel(async.reflectAll(tasks), + * // optional callback + * function(err, results) { + * // values + * // results.one.value = 'one' + * // results.two.error = 'two' + * // results.three.value = 'three' + * }); + */ + function reflectAll(tasks) { + var results; + if (isArray(tasks)) { + results = arrayMap(tasks, reflect); + } else { + results = {}; + baseForOwn(tasks, function (task, key) { + results[key] = reflect.call(this, task); + }); + } + return results; + } + + /** + * The same as [`reject`]{@link module:Collections.reject} but runs a maximum of `limit` async operations at a + * time. + * + * @name rejectLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.reject]{@link module:Collections.reject} + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The `iteratee` is passed a `callback(err, truthValue)`, which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Invoked with (err, results). + */ + var rejectLimit = doParallelLimit(reject$1); + + /** + * The same as [`reject`]{@link module:Collections.reject} but runs only a single async operation at a time. + * + * @name rejectSeries + * @static + * @memberOf module:Collections + * @method + * @see [async.reject]{@link module:Collections.reject} + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in `coll`. + * The `iteratee` is passed a `callback(err, truthValue)`, which must be called + * with a boolean argument once it has completed. Invoked with (item, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Invoked with (err, results). + */ + var rejectSeries = doLimit(rejectLimit, 1); + + /** + * Creates a function that returns `value`. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Util + * @param {*} value The value to return from the new function. + * @returns {Function} Returns the new constant function. + * @example + * + * var objects = _.times(2, _.constant({ 'a': 1 })); + * + * console.log(objects); + * // => [{ 'a': 1 }, { 'a': 1 }] + * + * console.log(objects[0] === objects[1]); + * // => true + */ + function constant$1(value) { + return function() { + return value; + }; + } + + /** + * Attempts to get a successful response from `task` no more than `times` times + * before returning an error. If the task is successful, the `callback` will be + * passed the result of the successful task. If all attempts fail, the callback + * will be passed the error and result (if any) of the final attempt. + * + * @name retry + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - Can be either an + * object with `times` and `interval` or a number. + * * `times` - The number of attempts to make before giving up. The default + * is `5`. + * * `interval` - The time to wait between retries, in milliseconds. The + * default is `0`. The interval may also be specified as a function of the + * retry count (see example). + * * If `opts` is a number, the number specifies the number of times to retry, + * with the default interval of `0`. + * @param {Function} task - A function which receives two arguments: (1) a + * `callback(err, result)` which must be called when finished, passing `err` + * (which can be `null`) and the `result` of the function's execution, and (2) + * a `results` object, containing the results of the previously executed + * functions (if nested inside another control flow). Invoked with + * (callback, results). + * @param {Function} [callback] - An optional callback which is called when the + * task has succeeded, or after the final failed attempt. It receives the `err` + * and `result` arguments of the last attempt at completing the `task`. Invoked + * with (err, results). + * @example + * + * // The `retry` function can be used as a stand-alone control flow by passing + * // a callback, as shown below: + * + * // try calling apiMethod 3 times + * async.retry(3, apiMethod, function(err, result) { + * // do something with the result + * }); + * + * // try calling apiMethod 3 times, waiting 200 ms between each retry + * async.retry({times: 3, interval: 200}, apiMethod, function(err, result) { + * // do something with the result + * }); + * + * // try calling apiMethod 10 times with exponential backoff + * // (i.e. intervals of 100, 200, 400, 800, 1600, ... milliseconds) + * async.retry({ + * times: 10, + * interval: function(retryCount) { + * return 50 * Math.pow(2, retryCount); + * } + * }, apiMethod, function(err, result) { + * // do something with the result + * }); + * + * // try calling apiMethod the default 5 times no delay between each retry + * async.retry(apiMethod, function(err, result) { + * // do something with the result + * }); + * + * // It can also be embedded within other control flow functions to retry + * // individual methods that are not as reliable, like this: + * async.auto({ + * users: api.getUsers.bind(api), + * payments: async.retry(3, api.getPayments.bind(api)) + * }, function(err, results) { + * // do something with the results + * }); + */ + function retry(opts, task, callback) { + var DEFAULT_TIMES = 5; + var DEFAULT_INTERVAL = 0; + + var options = { + times: DEFAULT_TIMES, + intervalFunc: constant$1(DEFAULT_INTERVAL) + }; + + function parseTimes(acc, t) { + if (typeof t === 'object') { + acc.times = +t.times || DEFAULT_TIMES; + + acc.intervalFunc = typeof t.interval === 'function' ? t.interval : constant$1(+t.interval || DEFAULT_INTERVAL); + } else if (typeof t === 'number' || typeof t === 'string') { + acc.times = +t || DEFAULT_TIMES; + } else { + throw new Error("Invalid arguments for async.retry"); + } + } + + if (arguments.length < 3 && typeof opts === 'function') { + callback = task || noop; + task = opts; + } else { + parseTimes(options, opts); + callback = callback || noop; + } + + if (typeof task !== 'function') { + throw new Error("Invalid arguments for async.retry"); + } + + var attempt = 1; + function retryAttempt() { + task(function (err) { + if (err && attempt++ < options.times) { + setTimeout(retryAttempt, options.intervalFunc(attempt)); + } else { + callback.apply(null, arguments); + } + }); + } + + retryAttempt(); + } + + /** + * A close relative of [`retry`]{@link module:ControlFlow.retry}. This method wraps a task and makes it + * retryable, rather than immediately calling it with retries. + * + * @name retryable + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.retry]{@link module:ControlFlow.retry} + * @category Control Flow + * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - optional + * options, exactly the same as from `retry` + * @param {Function} task - the asynchronous function to wrap + * @returns {Functions} The wrapped function, which when invoked, will retry on + * an error, based on the parameters specified in `opts`. + * @example + * + * async.auto({ + * dep1: async.retryable(3, getFromFlakyService), + * process: ["dep1", async.retryable(3, function (results, cb) { + * maybeProcessData(results.dep1, cb); + * })] + * }, callback); + */ + function retryable (opts, task) { + if (!task) { + task = opts; + opts = null; + } + return initialParams(function (args, callback) { + function taskFn(cb) { + task.apply(null, args.concat([cb])); + } + + if (opts) retry(opts, taskFn, callback);else retry(taskFn, callback); + }); + } + + /** + * Run the functions in the `tasks` collection in series, each one running once + * the previous function has completed. If any functions in the series pass an + * error to its callback, no more functions are run, and `callback` is + * immediately called with the value of the error. Otherwise, `callback` + * receives an array of results when `tasks` have completed. + * + * It is also possible to use an object instead of an array. Each property will + * be run as a function, and the results will be passed to the final `callback` + * as an object instead of an array. This can be a more readable way of handling + * results from {@link async.series}. + * + * **Note** that while many implementations preserve the order of object + * properties, the [ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6) + * explicitly states that + * + * > The mechanics and order of enumerating the properties is not specified. + * + * So if you rely on the order in which your series of functions are executed, + * and want this to work on all platforms, consider using an array. + * + * @name series + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Array|Iterable|Object} tasks - A collection containing functions to run, each + * function is passed a `callback(err, result)` it must call on completion with + * an error `err` (which can be `null`) and an optional `result` value. + * @param {Function} [callback] - An optional callback to run once all the + * functions have completed. This function gets a results array (or object) + * containing all the result arguments passed to the `task` callbacks. Invoked + * with (err, result). + * @example + * async.series([ + * function(callback) { + * // do some stuff ... + * callback(null, 'one'); + * }, + * function(callback) { + * // do some more stuff ... + * callback(null, 'two'); + * } + * ], + * // optional callback + * function(err, results) { + * // results is now equal to ['one', 'two'] + * }); + * + * async.series({ + * one: function(callback) { + * setTimeout(function() { + * callback(null, 1); + * }, 200); + * }, + * two: function(callback){ + * setTimeout(function() { + * callback(null, 2); + * }, 100); + * } + * }, function(err, results) { + * // results is now equal to: {one: 1, two: 2} + * }); + */ + function series(tasks, callback) { + _parallel(eachOfSeries, tasks, callback); + } + + /** + * Returns `true` if at least one element in the `coll` satisfies an async test. + * If any iteratee call returns `true`, the main `callback` is immediately + * called. + * + * @name some + * @static + * @memberOf module:Collections + * @method + * @alias any + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in the array + * in parallel. The iteratee is passed a `callback(err, truthValue)` which must + * be called with a boolean argument once it has completed. Invoked with + * (item, callback). + * @param {Function} [callback] - A callback which is called as soon as any + * iteratee returns `true`, or after all the iteratee functions have finished. + * Result will be either `true` or `false` depending on the values of the async + * tests. Invoked with (err, result). + * @example + * + * async.some(['file1','file2','file3'], function(filePath, callback) { + * fs.access(filePath, function(err) { + * callback(null, !err) + * }); + * }, function(err, result) { + * // if result is true then at least one of the files exists + * }); + */ + var some = _createTester(eachOf, Boolean, identity); + + /** + * The same as [`some`]{@link module:Collections.some} but runs a maximum of `limit` async operations at a time. + * + * @name someLimit + * @static + * @memberOf module:Collections + * @method + * @see [async.some]{@link module:Collections.some} + * @alias anyLimit + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - A truth test to apply to each item in the array + * in parallel. The iteratee is passed a `callback(err, truthValue)` which must + * be called with a boolean argument once it has completed. Invoked with + * (item, callback). + * @param {Function} [callback] - A callback which is called as soon as any + * iteratee returns `true`, or after all the iteratee functions have finished. + * Result will be either `true` or `false` depending on the values of the async + * tests. Invoked with (err, result). + */ + var someLimit = _createTester(eachOfLimit, Boolean, identity); + + /** + * The same as [`some`]{@link module:Collections.some} but runs only a single async operation at a time. + * + * @name someSeries + * @static + * @memberOf module:Collections + * @method + * @see [async.some]{@link module:Collections.some} + * @alias anySeries + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A truth test to apply to each item in the array + * in parallel. The iteratee is passed a `callback(err, truthValue)` which must + * be called with a boolean argument once it has completed. Invoked with + * (item, callback). + * @param {Function} [callback] - A callback which is called as soon as any + * iteratee returns `true`, or after all the iteratee functions have finished. + * Result will be either `true` or `false` depending on the values of the async + * tests. Invoked with (err, result). + */ + var someSeries = doLimit(someLimit, 1); + + /** + * Sorts a list by the results of running each `coll` value through an async + * `iteratee`. + * + * @name sortBy + * @static + * @memberOf module:Collections + * @method + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {Function} iteratee - A function to apply to each item in `coll`. + * The iteratee is passed a `callback(err, sortValue)` which must be called once + * it has completed with an error (which can be `null`) and a value to use as + * the sort criteria. Invoked with (item, callback). + * @param {Function} callback - A callback which is called after all the + * `iteratee` functions have finished, or an error occurs. Results is the items + * from the original `coll` sorted by the values returned by the `iteratee` + * calls. Invoked with (err, results). + * @example + * + * async.sortBy(['file1','file2','file3'], function(file, callback) { + * fs.stat(file, function(err, stats) { + * callback(err, stats.mtime); + * }); + * }, function(err, results) { + * // results is now the original array of files sorted by + * // modified date + * }); + * + * // By modifying the callback parameter the + * // sorting order can be influenced: + * + * // ascending order + * async.sortBy([1,9,3,5], function(x, callback) { + * callback(null, x); + * }, function(err,result) { + * // result callback + * }); + * + * // descending order + * async.sortBy([1,9,3,5], function(x, callback) { + * callback(null, x*-1); //<- x*-1 instead of x, turns the order around + * }, function(err,result) { + * // result callback + * }); + */ + function sortBy(coll, iteratee, callback) { + map(coll, function (x, callback) { + iteratee(x, function (err, criteria) { + if (err) return callback(err); + callback(null, { value: x, criteria: criteria }); + }); + }, function (err, results) { + if (err) return callback(err); + callback(null, arrayMap(results.sort(comparator), baseProperty('value'))); + }); + + function comparator(left, right) { + var a = left.criteria, + b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + } + } + + /** + * Sets a time limit on an asynchronous function. If the function does not call + * its callback within the specified milliseconds, it will be called with a + * timeout error. The code property for the error object will be `'ETIMEDOUT'`. + * + * @name timeout + * @static + * @memberOf module:Utils + * @method + * @category Util + * @param {Function} asyncFn - The asynchronous function you want to set the + * time limit. + * @param {number} milliseconds - The specified time limit. + * @param {*} [info] - Any variable you want attached (`string`, `object`, etc) + * to timeout Error for more information.. + * @returns {Function} Returns a wrapped function that can be used with any of + * the control flow functions. + * @example + * + * async.timeout(function(callback) { + * doAsyncTask(callback); + * }, 1000); + */ + function timeout(asyncFn, milliseconds, info) { + var originalCallback, timer; + var timedOut = false; + + function injectedCallback() { + if (!timedOut) { + originalCallback.apply(null, arguments); + clearTimeout(timer); + } + } + + function timeoutCallback() { + var name = asyncFn.name || 'anonymous'; + var error = new Error('Callback function "' + name + '" timed out.'); + error.code = 'ETIMEDOUT'; + if (info) { + error.info = info; + } + timedOut = true; + originalCallback(error); + } + + return initialParams(function (args, origCallback) { + originalCallback = origCallback; + // setup timer and call original function + timer = setTimeout(timeoutCallback, milliseconds); + asyncFn.apply(null, args.concat(injectedCallback)); + }); + } + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeCeil = Math.ceil; + var nativeMax$1 = Math.max; + /** + * The base implementation of `_.range` and `_.rangeRight` which doesn't + * coerce arguments. + * + * @private + * @param {number} start The start of the range. + * @param {number} end The end of the range. + * @param {number} step The value to increment or decrement by. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Array} Returns the range of numbers. + */ + function baseRange(start, end, step, fromRight) { + var index = -1, + length = nativeMax$1(nativeCeil((end - start) / (step || 1)), 0), + result = Array(length); + + while (length--) { + result[fromRight ? length : ++index] = start; + start += step; + } + return result; + } + + /** + * The same as [times]{@link module:ControlFlow.times} but runs a maximum of `limit` async operations at a + * time. + * + * @name timesLimit + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.times]{@link module:ControlFlow.times} + * @category Control Flow + * @param {number} count - The number of times to run the function. + * @param {number} limit - The maximum number of async operations at a time. + * @param {Function} iteratee - The function to call `n` times. Invoked with the + * iteration index and a callback (n, next). + * @param {Function} callback - see [async.map]{@link module:Collections.map}. + */ + function timeLimit(count, limit, iteratee, callback) { + mapLimit(baseRange(0, count, 1), limit, iteratee, callback); + } + + /** + * Calls the `iteratee` function `n` times, and accumulates results in the same + * manner you would use with [map]{@link module:Collections.map}. + * + * @name times + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.map]{@link module:Collections.map} + * @category Control Flow + * @param {number} n - The number of times to run the function. + * @param {Function} iteratee - The function to call `n` times. Invoked with the + * iteration index and a callback (n, next). + * @param {Function} callback - see {@link module:Collections.map}. + * @example + * + * // Pretend this is some complicated async factory + * var createUser = function(id, callback) { + * callback(null, { + * id: 'user' + id + * }); + * }; + * + * // generate 5 users + * async.times(5, function(n, next) { + * createUser(n, function(err, user) { + * next(err, user); + * }); + * }, function(err, users) { + * // we should now have 5 users + * }); + */ + var times = doLimit(timeLimit, Infinity); + + /** + * The same as [times]{@link module:ControlFlow.times} but runs only a single async operation at a time. + * + * @name timesSeries + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.times]{@link module:ControlFlow.times} + * @category Control Flow + * @param {number} n - The number of times to run the function. + * @param {Function} iteratee - The function to call `n` times. Invoked with the + * iteration index and a callback (n, next). + * @param {Function} callback - see {@link module:Collections.map}. + */ + var timesSeries = doLimit(timeLimit, 1); + + /** + * A relative of `reduce`. Takes an Object or Array, and iterates over each + * element in series, each step potentially mutating an `accumulator` value. + * The type of the accumulator defaults to the type of collection passed in. + * + * @name transform + * @static + * @memberOf module:Collections + * @method + * @category Collection + * @param {Array|Iterable|Object} coll - A collection to iterate over. + * @param {*} [accumulator] - The initial state of the transform. If omitted, + * it will default to an empty Object or Array, depending on the type of `coll` + * @param {Function} iteratee - A function applied to each item in the + * collection that potentially modifies the accumulator. The `iteratee` is + * passed a `callback(err)` which accepts an optional error as its first + * argument. If an error is passed to the callback, the transform is stopped + * and the main `callback` is immediately called with the error. + * Invoked with (accumulator, item, key, callback). + * @param {Function} [callback] - A callback which is called after all the + * `iteratee` functions have finished. Result is the transformed accumulator. + * Invoked with (err, result). + * @example + * + * async.transform([1,2,3], function(acc, item, index, callback) { + * // pointless async: + * process.nextTick(function() { + * acc.push(item * 2) + * callback(null) + * }); + * }, function(err, result) { + * // result is now equal to [2, 4, 6] + * }); + * + * @example + * + * async.transform({a: 1, b: 2, c: 3}, function (obj, val, key, callback) { + * setImmediate(function () { + * obj[key] = val * 2; + * callback(); + * }) + * }, function (err, result) { + * // result is equal to {a: 2, b: 4, c: 6} + * }) + */ + function transform(coll, accumulator, iteratee, callback) { + if (arguments.length === 3) { + callback = iteratee; + iteratee = accumulator; + accumulator = isArray(coll) ? [] : {}; + } + callback = once(callback || noop); + + eachOf(coll, function (v, k, cb) { + iteratee(accumulator, v, k, cb); + }, function (err) { + callback(err, accumulator); + }); + } + + /** + * Undoes a [memoize]{@link module:Utils.memoize}d function, reverting it to the original, + * unmemoized form. Handy for testing. + * + * @name unmemoize + * @static + * @memberOf module:Utils + * @method + * @see [async.memoize]{@link module:Utils.memoize} + * @category Util + * @param {Function} fn - the memoized function + * @returns {Function} a function that calls the original unmemoized function + */ + function unmemoize(fn) { + return function () { + return (fn.unmemoized || fn).apply(null, arguments); + }; + } + + /** + * Repeatedly call `fn`, while `test` returns `true`. Calls `callback` when + * stopped, or an error occurs. + * + * @name whilst + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Function} test - synchronous truth test to perform before each + * execution of `fn`. Invoked with (). + * @param {Function} iteratee - A function which is called each time `test` passes. + * The function is passed a `callback(err)`, which must be called once it has + * completed with an optional `err` argument. Invoked with (callback). + * @param {Function} [callback] - A callback which is called after the test + * function has failed and repeated execution of `fn` has stopped. `callback` + * will be passed an error and any arguments passed to the final `fn`'s + * callback. Invoked with (err, [results]); + * @returns undefined + * @example + * + * var count = 0; + * async.whilst( + * function() { return count < 5; }, + * function(callback) { + * count++; + * setTimeout(function() { + * callback(null, count); + * }, 1000); + * }, + * function (err, n) { + * // 5 seconds have passed, n = 5 + * } + * ); + */ + function whilst(test, iteratee, callback) { + callback = onlyOnce(callback || noop); + if (!test()) return callback(null); + var next = baseRest(function (err, args) { + if (err) return callback(err); + if (test()) return iteratee(next); + callback.apply(null, [null].concat(args)); + }); + iteratee(next); + } + + /** + * Repeatedly call `fn` until `test` returns `true`. Calls `callback` when + * stopped, or an error occurs. `callback` will be passed an error and any + * arguments passed to the final `fn`'s callback. + * + * The inverse of [whilst]{@link module:ControlFlow.whilst}. + * + * @name until + * @static + * @memberOf module:ControlFlow + * @method + * @see [async.whilst]{@link module:ControlFlow.whilst} + * @category Control Flow + * @param {Function} test - synchronous truth test to perform before each + * execution of `fn`. Invoked with (). + * @param {Function} fn - A function which is called each time `test` fails. + * The function is passed a `callback(err)`, which must be called once it has + * completed with an optional `err` argument. Invoked with (callback). + * @param {Function} [callback] - A callback which is called after the test + * function has passed and repeated execution of `fn` has stopped. `callback` + * will be passed an error and any arguments passed to the final `fn`'s + * callback. Invoked with (err, [results]); + */ + function until(test, fn, callback) { + whilst(function () { + return !test.apply(this, arguments); + }, fn, callback); + } + + /** + * Runs the `tasks` array of functions in series, each passing their results to + * the next in the array. However, if any of the `tasks` pass an error to their + * own callback, the next function is not executed, and the main `callback` is + * immediately called with the error. + * + * @name waterfall + * @static + * @memberOf module:ControlFlow + * @method + * @category Control Flow + * @param {Array} tasks - An array of functions to run, each function is passed + * a `callback(err, result1, result2, ...)` it must call on completion. The + * first argument is an error (which can be `null`) and any further arguments + * will be passed as arguments in order to the next task. + * @param {Function} [callback] - An optional callback to run once all the + * functions have completed. This will be passed the results of the last task's + * callback. Invoked with (err, [results]). + * @returns undefined + * @example + * + * async.waterfall([ + * function(callback) { + * callback(null, 'one', 'two'); + * }, + * function(arg1, arg2, callback) { + * // arg1 now equals 'one' and arg2 now equals 'two' + * callback(null, 'three'); + * }, + * function(arg1, callback) { + * // arg1 now equals 'three' + * callback(null, 'done'); + * } + * ], function (err, result) { + * // result now equals 'done' + * }); + * + * // Or, with named functions: + * async.waterfall([ + * myFirstFunction, + * mySecondFunction, + * myLastFunction, + * ], function (err, result) { + * // result now equals 'done' + * }); + * function myFirstFunction(callback) { + * callback(null, 'one', 'two'); + * } + * function mySecondFunction(arg1, arg2, callback) { + * // arg1 now equals 'one' and arg2 now equals 'two' + * callback(null, 'three'); + * } + * function myLastFunction(arg1, callback) { + * // arg1 now equals 'three' + * callback(null, 'done'); + * } + */ + function waterfall (tasks, callback) { + callback = once(callback || noop); + if (!isArray(tasks)) return callback(new Error('First argument to waterfall must be an array of functions')); + if (!tasks.length) return callback(); + var taskIndex = 0; + + function nextTask(args) { + if (taskIndex === tasks.length) { + return callback.apply(null, [null].concat(args)); + } + + var taskCallback = onlyOnce(baseRest(function (err, args) { + if (err) { + return callback.apply(null, [err].concat(args)); + } + nextTask(args); + })); + + args.push(taskCallback); + + var task = tasks[taskIndex++]; + task.apply(null, args); + } + + nextTask([]); + } + + var index = { + applyEach: applyEach, + applyEachSeries: applyEachSeries, + apply: apply$1, + asyncify: asyncify, + auto: auto, + autoInject: autoInject, + cargo: cargo, + compose: compose, + concat: concat, + concatSeries: concatSeries, + constant: constant, + detect: detect, + detectLimit: detectLimit, + detectSeries: detectSeries, + dir: dir, + doDuring: doDuring, + doUntil: doUntil, + doWhilst: doWhilst, + during: during, + each: eachLimit, + eachLimit: eachLimit$1, + eachOf: eachOf, + eachOfLimit: eachOfLimit, + eachOfSeries: eachOfSeries, + eachSeries: eachSeries, + ensureAsync: ensureAsync, + every: every, + everyLimit: everyLimit, + everySeries: everySeries, + filter: filter, + filterLimit: filterLimit, + filterSeries: filterSeries, + forever: forever, + log: log, + map: map, + mapLimit: mapLimit, + mapSeries: mapSeries, + mapValues: mapValues, + mapValuesLimit: mapValuesLimit, + mapValuesSeries: mapValuesSeries, + memoize: memoize, + nextTick: nextTick, + parallel: parallelLimit, + parallelLimit: parallelLimit$1, + priorityQueue: priorityQueue, + queue: queue$1, + race: race, + reduce: reduce, + reduceRight: reduceRight, + reflect: reflect, + reflectAll: reflectAll, + reject: reject, + rejectLimit: rejectLimit, + rejectSeries: rejectSeries, + retry: retry, + retryable: retryable, + seq: seq, + series: series, + setImmediate: setImmediate$1, + some: some, + someLimit: someLimit, + someSeries: someSeries, + sortBy: sortBy, + timeout: timeout, + times: times, + timesLimit: timeLimit, + timesSeries: timesSeries, + transform: transform, + unmemoize: unmemoize, + until: until, + waterfall: waterfall, + whilst: whilst, + + // aliases + all: every, + any: some, + forEach: eachLimit, + forEachSeries: eachSeries, + forEachLimit: eachLimit$1, + forEachOf: eachOf, + forEachOfSeries: eachOfSeries, + forEachOfLimit: eachOfLimit, + inject: reduce, + foldl: reduce, + foldr: reduceRight, + select: filter, + selectLimit: filterLimit, + selectSeries: filterSeries, + wrapSync: asyncify + }; + + exports['default'] = index; + exports.applyEach = applyEach; + exports.applyEachSeries = applyEachSeries; + exports.apply = apply$1; + exports.asyncify = asyncify; + exports.auto = auto; + exports.autoInject = autoInject; + exports.cargo = cargo; + exports.compose = compose; + exports.concat = concat; + exports.concatSeries = concatSeries; + exports.constant = constant; + exports.detect = detect; + exports.detectLimit = detectLimit; + exports.detectSeries = detectSeries; + exports.dir = dir; + exports.doDuring = doDuring; + exports.doUntil = doUntil; + exports.doWhilst = doWhilst; + exports.during = during; + exports.each = eachLimit; + exports.eachLimit = eachLimit$1; + exports.eachOf = eachOf; + exports.eachOfLimit = eachOfLimit; + exports.eachOfSeries = eachOfSeries; + exports.eachSeries = eachSeries; + exports.ensureAsync = ensureAsync; + exports.every = every; + exports.everyLimit = everyLimit; + exports.everySeries = everySeries; + exports.filter = filter; + exports.filterLimit = filterLimit; + exports.filterSeries = filterSeries; + exports.forever = forever; + exports.log = log; + exports.map = map; + exports.mapLimit = mapLimit; + exports.mapSeries = mapSeries; + exports.mapValues = mapValues; + exports.mapValuesLimit = mapValuesLimit; + exports.mapValuesSeries = mapValuesSeries; + exports.memoize = memoize; + exports.nextTick = nextTick; + exports.parallel = parallelLimit; + exports.parallelLimit = parallelLimit$1; + exports.priorityQueue = priorityQueue; + exports.queue = queue$1; + exports.race = race; + exports.reduce = reduce; + exports.reduceRight = reduceRight; + exports.reflect = reflect; + exports.reflectAll = reflectAll; + exports.reject = reject; + exports.rejectLimit = rejectLimit; + exports.rejectSeries = rejectSeries; + exports.retry = retry; + exports.retryable = retryable; + exports.seq = seq; + exports.series = series; + exports.setImmediate = setImmediate$1; + exports.some = some; + exports.someLimit = someLimit; + exports.someSeries = someSeries; + exports.sortBy = sortBy; + exports.timeout = timeout; + exports.times = times; + exports.timesLimit = timeLimit; + exports.timesSeries = timesSeries; + exports.transform = transform; + exports.unmemoize = unmemoize; + exports.until = until; + exports.waterfall = waterfall; + exports.whilst = whilst; + exports.all = every; + exports.allLimit = everyLimit; + exports.allSeries = everySeries; + exports.any = some; + exports.anyLimit = someLimit; + exports.anySeries = someSeries; + exports.find = detect; + exports.findLimit = detectLimit; + exports.findSeries = detectSeries; + exports.forEach = eachLimit; + exports.forEachSeries = eachSeries; + exports.forEachLimit = eachLimit$1; + exports.forEachOf = eachOf; + exports.forEachOfSeries = eachOfSeries; + exports.forEachOfLimit = eachOfLimit; + exports.inject = reduce; + exports.foldl = reduce; + exports.foldr = reduceRight; + exports.select = filter; + exports.selectLimit = filterLimit; + exports.selectSeries = filterSeries; + exports.wrapSync = asyncify; + +})); \ No newline at end of file diff --git a/client/lib/banner.css b/client/lib/banner.css new file mode 100644 index 000000000..68916131b --- /dev/null +++ b/client/lib/banner.css @@ -0,0 +1,17 @@ +#connection-lost-banner { + position: fixed; + z-index: 2000; + text-align: center; + vertical-align: baseline; + width: 100%; + top: 0; + padding-top:10px; + padding-bottom:10px; + border-radius: 0px; +} + +.alert-connection-lost { + border: 0px; + color: black; + background-color: #ffc7c4; +} \ No newline at end of file diff --git a/client/lib/banner.html b/client/lib/banner.html new file mode 100644 index 000000000..503e26fde --- /dev/null +++ b/client/lib/banner.html @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/client/lib/banner.js b/client/lib/banner.js new file mode 100644 index 000000000..cd9c42b24 --- /dev/null +++ b/client/lib/banner.js @@ -0,0 +1,116 @@ +Meteor.startup(function(){ + Template.connectionBanner.events({ + 'click #connection-try-reconnect': function(event, template){ + event.preventDefault(); + Session.set('MeteorConnection-isConnecting', true); + Meteor.reconnect(); + } + }); + + Template.connectionBanner.helpers({ + 'wasConnected': function(event, template){ + return Session.equals('MeteorConnection-wasConnected', true); + }, + 'isDisconnected': function(event, template){ + return Session.equals('MeteorConnection-isConnected', false) && Meteor.status().retryCount > 1; + }, + 'retryTimeSeconds': function(event, template){ + return Session.get('MeteorConnection-retryTimeSeconds'); + }, + 'failedReason': function(event, template){ + return Session.get('MeteorConnection-failedReason'); + }, + 'isConnecting': function(event, template){ + return Session.get('MeteorConnection-isConnecting'); + }, + + 'connectioningText': function(event, template){ + var defaultText = "正在连接中"; + if(Meteor.settings && Meteor.settings.public && Meteor.settings.public.connectionBanner && Meteor.settings.public.connectionBanner.connectioningText) + return Meteor.settings.public.connectionBanner.connectionLostText; + else + return defaultText; + }, + 'connectionLostText': function(event, template){ + var defaultText = "世界上最遥远的距离就是没网,请检查你的网络设置!"; + if(Meteor.settings && Meteor.settings.public && Meteor.settings.public.connectionBanner && Meteor.settings.public.connectionBanner.connectionLostText) + return Meteor.settings.public.connectionBanner.connectionLostText; + else + return defaultText; + }, + 'tryReconnectText': function(event, template){ + var defaultText = "点击重新连接"; + if(Meteor.settings && Meteor.settings.public && Meteor.settings.public.connectionBanner && Meteor.settings.public.connectionBanner.tryReconnectText) + return Meteor.settings.public.connectionBanner.tryReconnectText; + else + return defaultText; + }, + 'reconnectBeforeCountdownText': function(event, template){ + var defaultText = "(未连接)尝试自动重连"; + if(Meteor.settings && Meteor.settings.public && Meteor.settings.public.connectionBanner && Meteor.settings.public.connectionBanner.reconnectBeforeCountdownText) + return Meteor.settings.public.connectionBanner.reconnectBeforeCountdownText; + else + return defaultText; + }, + 'reconnectAfterCountdownText': function(event, template){ + var defaultText = "秒后."; + if(Meteor.settings && Meteor.settings.public && Meteor.settings.public.connectionBanner && Meteor.settings.public.connectionBanner.reconnectAfterCountdownText) + return Meteor.settings.public.connectionBanner.reconnectAfterCountdownText; + else + return defaultText; + } + }); + + Session.setDefault('MeteorConnection-isConnected', true); + Session.setDefault('MeteorConnection-wasConnected', false); + Session.setDefault('MeteorConnection-retryTimeSeconds', 0); + Session.setDefault('MeteorConnection-failedReason', null); + Session.setDefault('MeteorConnection-isConnecting', false); + var connectionRetryUpdateInterval; + + Deps.autorun(function(){ + var isConnected = Meteor.status().connected; + if(isConnected){ + Session.set('MeteorConnection-wasConnected', true); + Meteor.clearInterval(connectionRetryUpdateInterval); + connectionRetryUpdateInterval = undefined; + Session.set('MeteorConnection-retryTimeSeconds', 0); + Session.set('MeteorConnection-failedReason', null); + Session.set('MeteorConnection-isConnecting', false); + }else{ + if(Session.equals('MeteorConnection-wasConnected', true)){ + if(!connectionRetryUpdateInterval) + connectionRetryUpdateInterval = Meteor.setInterval(function(){ + var retryIn = Math.round((Meteor.status().retryTime - (new Date()).getTime())/1000); + if(isNaN(retryIn)) + retryIn = 0; + if (retryIn == 0){ + Session.set('MeteorConnection-isConnecting', true); + } + Session.set('MeteorConnection-retryTimeSeconds', retryIn); + Session.set('MeteorConnection-failedReason', Meteor.status().reason); + },500); + + + }else { + Meteor.setTimeout(function(){ + Session.set('MeteorConnection-wasConnected', true); + }, 5000); + } + if(Session.equals('MeteorConnection-isConnecting', true)){ + + Meteor.setTimeout(function(){ + var retryIn = Math.round((Meteor.status().retryTime - (new Date()).getTime())/1000); + if(isNaN(retryIn)) + retryIn = 0; + + if (retryIn != 0){ + Session.set('MeteorConnection-isConnecting', false); + } + }, 1000); + } + } + Session.set('MeteorConnection-isConnected', isConnected); + }); +}); + \ No newline at end of file diff --git a/client/lib/compare-versions_3_4_0.js b/client/lib/compare-versions_3_4_0.js new file mode 100644 index 000000000..753d150de --- /dev/null +++ b/client/lib/compare-versions_3_4_0.js @@ -0,0 +1,75 @@ +/* global define */ +(function (root, factory) { + /* istanbul ignore next */ + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.compareVersions = factory(); + } +}(this, function () { + + var semver = /^v?(?:\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+))?(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i; + + function indexOrEnd(str, q) { + return str.indexOf(q) === -1 ? str.length : str.indexOf(q); + } + + function split(v) { + var c = v.replace(/^v/, '').replace(/\+.*$/, ''); + var patchIndex = indexOrEnd(c, '-'); + var arr = c.substring(0, patchIndex).split('.'); + arr.push(c.substring(patchIndex + 1)); + return arr; + } + + function tryParse(v) { + return isNaN(Number(v)) ? v : Number(v); + } + + function validate(version) { + if (typeof version !== 'string') { + throw new TypeError('Invalid argument expected string'); + } + if (!semver.test(version)) { + throw new Error('Invalid argument not valid semver (\''+version+'\' received)'); + } + } + + return function compareVersions(v1, v2) { + [v1, v2].forEach(validate); + + var s1 = split(v1); + var s2 = split(v2); + + for (var i = 0; i < Math.max(s1.length - 1, s2.length - 1); i++) { + var n1 = parseInt(s1[i] || 0, 10); + var n2 = parseInt(s2[i] || 0, 10); + + if (n1 > n2) return 1; + if (n2 > n1) return -1; + } + + var sp1 = s1[s1.length - 1]; + var sp2 = s2[s2.length - 1]; + + if (sp1 && sp2) { + var p1 = sp1.split('.').map(tryParse); + var p2 = sp2.split('.').map(tryParse); + + for (i = 0; i < Math.max(p1.length, p2.length); i++) { + if (p1[i] === undefined || typeof p2[i] === 'string' && typeof p1[i] === 'number') return -1; + if (p2[i] === undefined || typeof p1[i] === 'string' && typeof p2[i] === 'number') return 1; + + if (p1[i] > p2[i]) return 1; + if (p2[i] > p1[i]) return -1; + } + } else if (sp1 || sp2) { + return sp1 ? -1 : 1; + } + + return 0; + }; + +})); diff --git a/client/lib/cordova-exif.js b/client/lib/cordova-exif.js new file mode 100644 index 000000000..6a128357e --- /dev/null +++ b/client/lib/cordova-exif.js @@ -0,0 +1,54 @@ + +get_image_size_from_URI = function(imageURI,callback) { + // This function is called once an imageURI is rerturned from PhoneGap's camera or gallery function + window.resolveLocalFileSystemURL(imageURI, function(fileEntry) { + fileEntry.file(function(fileObject){ + // Create a reader to read the file + var reader = new FileReader(); + + // Create a function to process the file once it's read + reader.onloadend = function(evt) { + //console.log('Create an image element that we will load the data into '); + var image = new Image(); + image.onerror = function(){ + image = null; + if (callback){ + callback(0,0) + } + }; + image.onload = function(evt) { + // The image has been loaded and the data is ready + var image_width = this.width; + var image_height = this.height; + console.log("IMAGE HEIGHT: " + image_height + " IMAGE WIDTH: " + image_width); + // We don't need the image element anymore. Get rid of it. + image = null; + if (callback){ + callback(image_width,image_height) + } + }; + // Load the read data into the image source. It's base64 data + image.src = evt.target.result + }; + reader.onabort = function(){ + console.log("reader.onabort"); + if(callback){ + callback(0,0) + } + }; + reader.onerror = function(){ + console.log("reader.onerror"); + if(callback){ + callback(0,0) + } + }; + // Read from disk the data as base64 + reader.readAsDataURL(fileObject) + }, function(){ + console.log("There was an error reading or processing this file."); + if(callback){ + callback(0,0) + } + }) + }) +}; diff --git a/client/lib/extract.coffee b/client/lib/extract.coffee new file mode 100644 index 000000000..54a652d11 --- /dev/null +++ b/client/lib/extract.coffee @@ -0,0 +1,559 @@ +# +# This file is based on readabiity.js: +# http://code.google.com/p/arc90labs-readability/ +# +# Copyright (c) 2010 Arc90 Inc +# Copyright (c) 2011 MORITA Hajime +# This software is licensed under the Apache License, Version 2.0. +# +keepImagesForSpecialMobileSite = false +removeStyle = true +class Log + this.print = (message) -> console.log(message) + this.error = (message) -> console.log(message) + this.log = (message) -> console.log(message) + this.debug = (message) -> console.log(message) + + +class Score + constructor: (node) -> + this.value = Score.initialScoreFor(node.tagName) + + add: (n) -> this.value += n + scale: (s) -> this.value *= s + + this.initialScoreFor = (tagName) -> + switch tagName + when 'DIV' then 5 + when 'IFRAME' then 15 + when 'BLOCKQUOTE' then 15 + when 'PRE', 'TD' then 3 + when 'ADDRESS', 'OL', 'UL', 'DL', 'DD', 'DT', 'LI', 'FORM' then -3 + when 'H2', 'H3', 'H4', 'H5' then -5 + else 0 + +REGEXPS = + unlikelyCandidates: /combx|comment|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|ad-break|agegate|pagination|pager|popup|tweet|twitter/i, + okMaybeItsACandidate: /and|article|body|column|main|shadow/i, okMaybeItsACandidate: /and|article|body|column|main|shadow/i, + positive: /iframe|article|body|content|entry|hentry|main|page|pagination|post|text|blog|story|rich_media_content/i, + negative: /combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|shopping|tags|tool|widget|js_profile_qrcode|rich_media_meta_list|profile_inner/i, + extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single/i, + divToPElements: /<(a|blockquote|dl|div|img|ol|p|pre|table|ul)/i, + replaceBrs: /(]*>[ \n\r\t]*){2,}/gi, + replaceFonts: /<(\/?)font[^>]*>/gi, + trim: /^\s+|\s+$/g, + normalize: /\s{2,}/g, + killBreaks: /((\s| ?)*){1,}/g, + videos: /http:\/\/(www\.)?(youtube|vimeo)\.com/i, + skipFootnoteLink: /^\s*(\[?[a-z0-9]{1,2}\]?|^|edit|citation needed)\s*$/i, + nextLink: /(next|weiter|continue|>([^\|]|$)|ツサ([^\|]|$))/i, # Match: next, continue, >, >>, ツサ but not >|, ツサ| as those usually mean last. + prevLink: /(prev|earl|old|new|<|ツォ)/i, + specialTags: /blockquote|section/i, + possibleVideoTags: /iframe|data-video/i, + specialClass: /note-content|rich_media_content|WBA_content/i + +specialClassNameForPopularMobileSite = [ + '.note-content' # Douban + '.rich_media_content' # Wechat + '.WBA_content' # Weibo + '#cont-wrapper' # 豆瓣FM + '#j-body' # 企鹅FM + '.article' # 悦读FM + '.main_box' # QQ 音乐 + '#page-content' #xueqiu + '#BODYCON' #tripadvisor + '.yaow > p' #news.ifeng.com + #'.pulse-article' #Linkedin + '.page-wrap' # businessinsider.com +] + +specialClassNameExcludeMobileSites = [ + 'techcrunch.com' +] + +specialMobileSiteForImages = [ + 'medium.com' + 'techcrunch.com' +] + +beforeExtractConfig = [ + # 微信凤凰读书诗歌的例外处理。 + # { + # enable: (url, data)-> + # $page = $(data.body) + # if $page.find('.rich_media_content').length <= 0 + # return false + # if $page.find(".rich_media_content > section > section:last > section > section:last > section > section > section").length <= 0 + # return false + # return true + # extract: (url, data)-> + # $page = $(data.body) + # $sction = $page.find(".rich_media_content > section > section:last > section > section:last > section > section") + # $sction.each ()-> + # $(this).html('

      '+$(this).text()+'

      ') + + # data.body = '' + # _.map $page[0].parentNode.childNodes, (node)-> + # data.body += node.outerHTML + # data.bodyLength = data.body.length + # } +] + +@onBeforeExtract = (url, data)-> + _.map beforeExtractConfig, (config)-> + if config.enable(url, data) + config.extract(url, data) + +textContentFor = (node, normalizeWs = true) -> + return "" unless node.textContent + text = node.textContent.replace(REGEXPS.trim, "") + text = text.replace(REGEXPS.normalize, " ") if normalizeWs + text + +countChars = (node, ch) -> + textContentFor(node).split(ch).length - 1 + +linkDensityFor = (node) -> + linkLength = _.reduce($(node).find("a"), ((len, n) -> len + n.innerHTML.length), 0) + if (node and node.innerHTML and node.innerHTML.length) + textLength = node.innerHTML.length + else + return 1 + linkLength/textLength + +classWeight = (node) -> + weight = 0 + if node.className + weight -= 25 if -1 < node.className.search(REGEXPS.negative) + weight += 25 if -1 < node.className.search(REGEXPS.positive) + if node.id + weight -= 25 if -1 < node.id.search(REGEXPS.negative) + weight += 25 if -1 < node.id.search(REGEXPS.positive) + weight + +fishy = (node) -> + if node.tagName == 'iframe' || $(node).find('iframe').length > 0 + console.log('fishy on iframe') + return false + theNode = if $(node).parent() then $(node).parent() else $(node) + if (node.id is 'mediaPlayer' and node.getAttribute('data-video') isnt null) or theNode.find('#mediaPlayer').length > 0 + console.log('fishy on mediaPlayer') + return false + weight = classWeight(node) + contentScore = if node.score then node.score.value else 0 + return false if 10 <= countChars(node, ',') + nj = $(node) + p = nj.find("p").length + img = nj.find("img").length + li = nj.find("li").length - 100 + input = nj.find("input").length + embed = nj.find("embed").length + linkDensity = linkDensityFor(node) + contentLength = textContentFor(node).length + + if keepImagesForSpecialMobileSite + return false if img > 0 + return true if weight + contentScore < 0 + #return true if img > p + return true if li > p and node.tagName != "ul" and node.tagName != "ol" + return true if input > Math.floor(p/3) + return true if contentLength < 25 and (img == 0 || img > 2) + return true if weight < 25 && linkDensity > 0.2 + return true if weight >= 25 && linkDensity > 0.5 + return true if (embed == 1 && contentLength < 75) || embed > 1 + false + +deepInsideThislayer = (node,tagName)-> + childrenNumber=$(node).children().length + if childrenNumber > 0 and childrenNumber <=2 + firstChild = $(node).find(':first-child').get(0) + if firstChild and firstChild.tagName is tagName + return deepInsideThislayer(firstChild,tagName) + return node +# Turn all divs that don't have children block level elements into p's +# TODO(omo): support experimental parify text node +parify = (node) -> + ### + if node.tagName.search(REGEXPS.specialTags) > -1 + newNode = deepInsideThislayer(node,node.tagName) + if newNode + console.log('we got you') + node.parentNode.replaceChild(newNode, node) + return newNode + ### + return node if node.tagName != "DIV" + return node if node.innerHTML.search(REGEXPS.divToPElements) > -1 + p = $("

      ").html(node.innerHTML)[0] + node.parentNode.replaceChild(p, node) + p + +ensureScore = (array, node) -> + return if !node or typeof(node.tagName) == 'undefined' + if not node.score + node.score = new Score(node) + console.log('Node tag ' + node.tagName + ' class ' + node.className + ' id ' + node.id + ' score value ' + node.score.value) + array.push(node) + +propagateScore = (node, scoredList, score) -> + parent = node.parentNode + if parent + ensureScore(scoredList, parent) + parent.score.add(score) + grandParent = parent.parentNode + if grandParent and grandParent.score + ensureScore(scoredList, grandParent) + grandParent.score.add(score/2) + +scoreNode = (node) -> + #if node.tagName == "IFRAME" + # console.log('scoreNode on IFRAME +15') + # return 15 + #if node.className && node.className.search(REGEXPS.specialClass) + # console.log('the main class of mainstream web for mobile. bingo') + # return 250 + if node.tagName == "IMG" + if $(node).parent() and $(node).parent().hasClass('article-cover') + if $(node).parent().parent() and $(node).parent().parent().hasClass('article-header') + return 99999 + unlikely = node.className + node.id + if unlikely.search(REGEXPS.unlikelyCandidates) != -1 and \ + unlikely.search(REGEXPS.okMaybeItsACandidate) == -1 and \ + node.tagName != "BODY" + return 0 + unless node.tagName == "P" || node.tagName == "TD" || node.tagName == "PRE" + return 0 + text = textContentFor(node) + return 0 if text.length < 25 + # Add points for any commas within this paragraph + # For every 100 characters in this paragraph, add another point. Up to 3 points. + 1 + text.split(',').length + Math.min(Math.floor(text.length / 100), 3) + +reduceScorable = (scoredList, node) -> + score = scoreNode(node) + if node.tagName == 'IFRAME' + console.log('reduceScoreable on IFRAME ' + score) + propagateScore(node, scoredList, score) if 0 < score + scoredList + +isAcceptableSibling = (top, sib) -> + return true if top == sib + if sib.innerHTML.search(REGEXPS.specialTags) > -1 + console.log('Sib of ' + sib.tagName + ' has specialTags') + return true + if sib.innerHTML.search(REGEXPS.possibleVideoTags) > -1 + console.log('sib contain video tags ' + sib.tagName) + return true + threshold = Math.max(10, top.score.value * 0.2) + return true if threshold <= scoreSibling(top, sib) + return false if "P" != sib.tagName + #density = linkDensityFor(sib) + text = textContentFor(sib) + textLen = text.length + return true if 80 < textLen #and density < 0.25 + #return true if textLen < 80 and density == 0 and text.search(/\.( |$)/) != -1 + false + +scoreSibling = (top, sib) -> + return 0 if !sib.score + if sib.className == sib.className && sib.className != "" + sib.score.value + (top.score.value * 0.2) + else + sib.score.value + +removeFragments = (node) -> + jn = $(node) + jn.html(jn.html().replace(REGEXPS.killBreaks,'
      ')) + jn.find("h1,h2,h3").find( + (n) -> classWeight(n) < 0 #or linkDensityFor(n) > 0.33 + ).remove() + if removeStyle + jn.find("*").removeAttr("style") + jn.find("p").filter(-> \ + 0 == $(this).find("img").length and \ + 0 == $(this).find("embed").length and \ + 0 == $(this).find("object").length and \ + 0 == $(this).find("iframe").length and \ + 0 == textContentFor(this, false).length).remove() + jn.find("form").filter(-> fishy(this)).remove() + jn.find("table").filter(-> fishy(this)).remove() + jn.find("ul").filter(-> fishy(this)).remove() + jn.find("div").filter(-> fishy(this)).remove() + jn.find("noscript").filter(-> fishy(this)).remove() + if removeStyle + jn.find("object,h1,script,link,iframe,style").remove() + else + jn.find("object,h1,script,iframe,link").remove() + jn.find("h2").remove() if jn.find("h2").length == 1 + node.innerHTML = node.innerHTML.replace(/]*>\s*

      + jn = $(node) + jn.html(jn.html().replace(REGEXPS.killBreaks,'
      ')) + if removeStyle + jn.find("*").removeAttr("style") + if removeStyle + jn.find("object,h1,script,link,style").remove() + else + jn.find("object,h1,script,link").remove() + #node.innerHTML = node.innerHTML.replace(/]*>\s*

      + Log.log("not found. using page element") + page.score = new Score(page) + page + +scoreAndSelectTop = (nodes) -> + scored = _.reduce(nodes, reduceScorable, []) + #_.each(scored, (n) => n.score.scale(1 - linkDensityFor(n))) + #_.sortBy(scored, (n) -> n.score.value)[scored.length-1] + sortArray = _.sortBy(scored, (n) -> n.score.value) + id = scored.length-1 + while id >= 0 and sortArray[id].score.value == 99999 + id-- + topId = scored.length-1 + if id >= 0 and id != scored.length-1 + $(sortArray[id]).prepend($(sortArray[scored.length-1])) + topId = id + sortArray[topId] + +collectSiblings = (top) -> + _.reduce( + if top.parentNode then $(top.parentNode).children() else $(top).children() + ((root, s) => + root.appendChild(s) if isAcceptableSibling(top, s) + root), + document.createElement("div")) +collectNodeSibling=(node)-> + el=node.nextSibling + count=0 + while (el) + console.log(count+' Self.nextSibling Tag is '+el.tagName+' my text '+ + textContentFor(node)+' siblingNode text'+textContentFor(el)+ + ' siblingNode is text node '+(el.nodeType is Node.TEXT_NODE)) + next=el.nextSibling + if el.tagName is 'BR' + console.log('Has BR') + node.textContent=node.textContent+'\n' + node.parentNode.removeChild(el) + else if el.tagName is 'SPAN' + text=textContentFor(el) + if text + console.log('Hit SPAN'+text) + node.textContent=node.textContent+text + node.parentNode.removeChild(el) + else if el.nodeType is Node.TEXT_NODE + text=textContentFor(el) + console.log('Hit TEXT_NODE'+text) + if text + node.textContent=node.textContent+text + node.parentNode.removeChild(el) + else + console.log('Stop processing') + return false + el = next; + count++ + return true +getCalculatedStyle=(node,prop)-> + try + $node=$(node) + while($node.parent().length>0) + attr=$node.parent().css(prop) + if attr and attr isnt '' + return attr + $node=$node.parent() + catch error + return null + return null +getSpecialTag=(node,specialTagName)-> + try + $node=$(node) + while($node.parent().length>0) + tagName=$node.parent().get(0).tagName + if tagName and tagName isnt '' and tagName.toLowerCase() is specialTagName + return true + $node=$node.parent() + catch error + return false + return false +cloneWithoutSibling=(parentNode, node)-> + if parentNode is null or parentNode is undefined + return null + $parentNode=$(parentNode) + $cloneParent=$parentNode.clone().empty() + $cloneParent.append($(node).clone()) + return $cloneParent.get(0) +@extractScript = (page, getMusic)-> + parified = _.map($('

      '+page.innerHTML+'
      ').find('*'), parify) + for item in $(parified) + if(item.tagName is 'SCRIPT') + musicInfo = getMusic(item) + if musicInfo + console.log('Got Music Info '+JSON.stringify(musicInfo)) + musicElement = document.createElement("musicExtracted") + musicElement.setAttribute('playUrl', musicInfo.playUrl) + musicElement.setAttribute('image', musicInfo.image) + musicElement.setAttribute('songName', musicInfo.songName) + musicElement.setAttribute('singerName', musicInfo.singerName) + + newRoot = document.createElement("div") + newRoot.appendChild(musicElement) + newRoot.id = 'hotshare_special_tag_will_not_hit_other' + return newRoot + + newRoot = document.createElement("div") + newRoot.id = 'hotshare_special_tag_will_not_hit_other' + return newRoot +@extract = (page) -> + parified = _.map($(page).find('*'), parify) + documentBody = document.createElement('body') + documentBody.innerHTML = page.innerHTML + bodyParified = _.map($(documentBody).find('*'), parify) # -> body + + keepImagesForSpecialMobileSite = false + console.log(' page.host = '+page.host) + if page.host is specialMobileSiteForImages + keepImagesForSpecialMobileSite = true + + for tag in specialClassNameForPopularMobileSite + if page.host in specialClassNameExcludeMobileSites + break + rootNode = null + + if($(bodyParified).find(tag).length > 0) # 无法查找body下的第一层 + #item = $(bodyParified).find(tag)[0] + #if item.tagName and item.tagName.toUpperCase() is 'IMG' + # continue + rootNode = $(bodyParified).find(tag)[0] + else + for item in bodyParified + if tag.indexOf('#') is 0 + if item.id is tag.substr(1) + rootNode = item + break + else if item.parentNode.id is tag.substr(1) + rootNode = item.parentNode + break + else if tag.indexOf('.') is 0 + if item.className is tag.substr(1) + rootNode = item + break + else if item.parentNode.className is tag.substr(1) + rootNode = item.parentNode + break + else + if item.tagName is tag.toUpperCase() + rootNode = item + break + else if item.tagName is tag.toUpperCase() + rootNode = item.parentNode + break + + console.log("rootNode =" + rootNode) + if rootNode isnt null + treeWalker = document.createTreeWalker( + rootNode, + NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT, + { + acceptNode : (node)-> + try + try + #if (node.nodeType isnt Node.TEXT_NODE) + musicInfo = getMusicFromNode(node, documentBody) + if musicInfo + return NodeFilter.FILTER_ACCEPT + catch error + console.log('getMusicFromNode Exception: ' + error) + try + if $(node).css("display") is 'none' + return NodeFilter.FILTER_REJECT + if $(node).parent() and $(node).parent().css("display") is 'none' + return NodeFilter.FILTER_REJECT + catch error + console.log('Get Display Exception') + unless node.hasChildNodes() + if node.nodeType is Node.TEXT_NODE + if $(node).parent().length > 0 + alignstyle=getCalculatedStyle(node,'text-align') + #console.log('Get parent style '+alignstyle); + if alignstyle and alignstyle isnt '' + storeStyleInItem(node.parentNode,'textAlign',alignstyle) + if collectNodeSibling(node) is false + return NodeFilter.FILTER_ACCEPT + #if node.parentNode and node.parentNode.tagName is 'SPAN' + #if node.parentNode.nextSibling + # console.log('Parent nextSibling is '+node.parentNode.nextSibling.tagName+' my text '+ + # textContentFor(node)+' next text'+textContentFor(node.parentNode.nextSibling)+ + # ' next is text node '+(node.parentNode.nextSibling.nodeType is Node.TEXT_NODE)) + # collectNodeSibling(node.parentNode) + return NodeFilter.FILTER_ACCEPT + catch e + return NodeFilter.FILTER_REJECT + return NodeFilter.FILTER_REJECT + }, + false + ) + newRoot = document.createElement("div") + nodeList = [] + while(treeWalker.nextNode()) + nodeList.push(treeWalker.currentNode) + parentPNode = null; + for node in nodeList + unless node.hasChildNodes() + if node.nodeType is Node.TEXT_NODE + flag = 0 + level = 0 + tmpNode = node.parentNode; + if parentPNode + while level < 6 and tmpNode + if tmpNode is parentPNode + flag = 1 + break + tmpNode = tmpNode.parentNode + level++ + if flag + if node.parentNode + alignstyle=$(node).parent().attr('hotshare-textAlign') + if alignstyle and alignstyle isnt '' + storeStyleInItem(parentPNode,'textAlign',alignstyle) + if !node.parentNode or node.parentNode is parentPNode + parentPNode.appendChild(node) + else + parentPNode.appendChild(node.parentNode) + else + if node.parentNode + #newRoot.appendChild(node.parentNode) + tmpParentNode = cloneWithoutSibling(node.parentNode, node) + if getSpecialTag(node, "strong") or getSpecialTag(node, "h1") or getSpecialTag(node, "h2") or getSpecialTag(node, "h3") + storeStyleInItem(tmpParentNode, 'fontWeight', "bold") + newRoot.appendChild(tmpParentNode) + else + p = document.createElement("P") + p.appendChild(node) + ### + if node.parentNode + if node.parentNode.tagName is 'P' + span = document.createElement("SPAN") + span.appendChild(node) + alignstyle=getCalculatedStyle(node.parentNode,'text-align') + if alignstyle and alignstyle isnt '' + storeStyleInItem(span, 'textAlign', alignstyle) + newRoot.appendChild(span) + else + newRoot.appendChild(node.parentNode) + else + p = document.createElement("P") + p.appendChild(node) + ### + else + newRoot.appendChild(node) + else if node.tagName is 'P' + #p = document.createElement("P"); + newRoot.appendChild(node); + parentPNode = node + console.log('node length ' + nodeList.length) + removeUnwanted(newRoot) + newRoot.id = 'hotshare_special_tag_will_not_hit_other' + return newRoot + top = scoreAndSelectTop(parified) or asTop(page) + root = collectSiblings(top) + removeFragments(root) + root + diff --git a/client/lib/file_uploader.js b/client/lib/file_uploader.js new file mode 100644 index 000000000..0bf2348e6 --- /dev/null +++ b/client/lib/file_uploader.js @@ -0,0 +1,598 @@ + +if (Meteor.isCordova){ + uploadingFilesInfo = {filesCount:0, files:[]}; + abortuploader = function(){} + var showDebug=false + var uploadToAliyun_new = function(filename,URI, callback){ + Meteor.call('getAliyunWritePolicy',filename,URI,function(error,result){ + if(error) { + showDebug && console.log('getAliyunWritePolicy error: ' + error); + if(callback){ + callback(null); + } + } + showDebug && console.log('File URI is ' + result.orignalURI); + var options = new FileUploadOptions(); + options.mimeType ="image/jpeg"; + options.chunkedMode = false; + options.httpMethod = "PUT"; + options.fileName = filename; + + var uri = encodeURI(result.acceccURI); + + var headers = { + "Content-Type": "image/jpeg", + "Content-Md5":"", + "Authorization": result.auth, + "Date": result.date + }; + options.headers = headers; + + var ft = new FileTransfer(); + ft.onprogress = function(progressEvent) { + if (progressEvent && progressEvent.lengthComputable) { + if (callback){ + showDebug && console.log('Loaded ' + progressEvent.loaded + ' Total ' + progressEvent.total); + callback('uploading',progressEvent) + } + } else { + showDebug && console.log('Upload ++'); + } + }; + ft.upload(result.orignalURI, uri, function(e){ + if(callback){ + callback('done',result.readURI); + } + }, function(e){ + showDebug && console.log('upload error' + e.code ); + if (callback) { + callback('error',null); + } + }, options,true); + + return ft; + }); + } + var uploadToAliyun = function(filename,URI, callback, errCallback){ + Meteor.call('getAliyunWritePolicy',filename,URI,function(error,result){ + if(error) { + showDebug && console.log('getAliyunWritePolicy error: ' + error); + if(callback){ + callback(null); + } + } + showDebug && console.log('File URI is ' + result.orignalURI); + var options = new FileUploadOptions(); + options.mimeType ="image/jpeg"; + options.chunkedMode = false; + options.httpMethod = "PUT"; + options.fileName = filename; + + var uri = encodeURI(result.acceccURI); + + var headers = { + "Content-Type": "image/jpeg", + "Content-Md5":"", + "Authorization": result.auth, + "Date": result.date + }; + options.headers = headers; + + var ft = new FileTransfer(); + ft.onprogress = function(progressEvent) { + if (progressEvent.lengthComputable) { + showDebug && console.log('Loaded ' + progressEvent.loaded + ' Total ' + progressEvent.total); + computeProgressBar(filename, 60*(progressEvent.loaded/progressEvent.total)); + showDebug && console.log('Uploaded Progress ' + 60* (progressEvent.loaded / progressEvent.total ) + '%'); + } else { + showDebug && console.log('Upload ++'); + } + }; + ft.upload(result.orignalURI, uri, function(e){ + if(callback){ + computeProgressBar(filename, 100); + callback(result.acceccURI); + } + }, function(e){ + showDebug && console.log('upload error' + e.code ); + if (errCallback) { + errCallback(filename); + } else { + if(callback){ + callback(null); + } + } + }, options,true); + + return ft; + }); + } + var uploadToS3 = function(filename,URI,callback){ + Meteor.call('getS3WritePolicy',filename,URI,function(error,result){ + if(error) { + showDebug && console.log('getS3WritePolice error: ' + error); + if(callback){ + callback(null); + } + } + showDebug && console.log('File URI is ' + result.orignalURI); + var options = new FileUploadOptions(); + options.fileKey="file"; + var time = new Date().getTime(); + options.fileName = filename; + options.mimeType ="image/jpeg"; + options.chunkedMode = false; + + var uri = encodeURI("https://travelers-bucket.s3.amazonaws.com/"); + + var policyDoc = result.s3PolicyBase64; + var signature = result.s3Signature ; + var params = { + "key": filename, + "AWSAccessKeyId": 'AKIAJY2UYZVD3WWOF4JA', + "acl": "public-read", + "policy": policyDoc, + "signature": signature, + "Content-Type": "image/jpeg" + }; + options.params = params; + + var ft = new FileTransfer(); + ft.onprogress = function(progressEvent) { + if (progressEvent.lengthComputable) { + showDebug && console.log('Uploaded Progress ' + 100* (progressEvent.loaded / progressEvent.total ) + '%'); + } else { + showDebug && console.log('Upload ++'); + } + }; + ft.upload(result.orignalURI, uri, function(e){ + if(callback){ + callback('https://travelers-bucket.s3.amazonaws.com/' + filename); + } + }, function(e){ + showDebug && console.log('upload error' + e.code ) + if(callback){ + callback(null); + } + }, options,true); + }); + } + var uploadToBCS = function(filename,URI,callback,errCallback){ + Meteor.call('getBCSSigniture',filename,URI,function(error,result){ + if(error) { + showDebug && console.log('getBCSSigniture error: ' + error); + if(callback){ + callback(null); + } + return; + } + showDebug && console.log('File URI is ' + result.orignalURI); + showDebug && console.log('Result is ' + JSON.stringify(result)); + var options = new FileUploadOptions(); + var time = new Date().getTime(); + options.mimeType ="image/jpeg"; + options.chunkedMode = false; + options.httpMethod = "PUT"; + + var uri = encodeURI("http://bcs.duapp.com/travelers-km/"+filename)+"?sign="+result.signture; + + var headers = { + "x-bs-acl": "public-read", + "Content-Type": "image/jpeg" + }; + options.headers = headers; + + var ft = new FileTransfer(); + ft.onprogress = function(progressEvent) { + if (progressEvent.lengthComputable) { + computeProgressBar(filename, 100*(progressEvent.loaded/progressEvent.total)); + showDebug && console.log('Uploaded Progress ' + 100* (progressEvent.loaded / progressEvent.total ) + '%'); + } else { + showDebug && console.log('Upload ++'); + } + }; + ft.upload(result.orignalURI, uri, function(e){ + if(callback){ + callback('http://bcs.duapp.com/travelers-km/' + filename); + } + }, function(e){ + showDebug && console.log('upload error' + e.code ) + if (errCallback) { + errCallback(filename); + } else { + if(callback){ + callback(null); + } + } + }, options,true); + }); + } + + var FileDownloadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) { + this.headers = headers || null; + }; + downloadFromBCS = function(source, callback){ + function fail(error) { + showDebug && console.log(error) + if(callback){ + callback(null, source); + } + } + function onFileSystemSuccess(fileSystem) { + var timestamp = new Date().getTime(); + var hashOnUrl = Math.abs(source.hashCode()); + var filename = Meteor.userId()+'_'+timestamp+ '_' + hashOnUrl; + fileSystem.root.getFile(filename, {create: true, exclusive: false}, + function(fileEntry){ + showDebug && console.log("filename = "+filename+", fileEntry.toURL()="+fileEntry.toURL()); + //var target = "cdvfile://localhost/temporary/"+filename + var target = fileEntry.toURL(); + showDebug && console.log("target = "+target); + + var options = new FileDownloadOptions(); + var headers = { + "x-bs-acl": "public-read", + "Content-Type": "image/jpeg" + //"Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA==" + }; + options.headers = headers; + var ft = new FileTransfer(); + ft.download(source, target, function(theFile){ + //showDebug && console.log('download suc, theFile.toURL='+theFile.toURL()); + if(callback){ + callback(theFile.toURL(),source,theFile); + } + }, function(e){ + showDebug && console.log('download error: ' + e.code) + if(callback){ + callback(null, source); + } + }, true, options); + + }, fail); + } + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFileSystemSuccess, fail); + } + + /** + * upload file in cordova with plugin for select/resize file to S3 + * + * @method uploadFileInCordova + * @param {Function} callback + * @return {Object} url in callback + */ + var uploadFileInCordova = function(ImageWidth, ImageHeight, ImageQuality, callback){ + if(device.platform === 'Android'){ + pictureSource = navigator.camera.PictureSourceType; + destinationType = navigator.camera.DestinationType; +// var cameraOptions = { +// width: 400, +// height: 400, +// destinationType: destinationType.NATIVE_URI, +// sourceType: pictureSource.SAVEDPHOTOALBUM, +// quality: 60 +// }; + navigator.camera.getPicture(function(s){ + console.info(s); + //判断是否图片 + if(s.indexOf("file:///")==0){ + if(s.lastIndexOf('.')<=0){ + PUB.toast('您选取的文件不是图片!'); + return; + }else{ + var ext = s.substring(s.lastIndexOf('.')).toUpperCase(); + if(!(ext.indexOf('.PNG')==0||ext.indexOf('.JPG')==0||ext.indexOf('.JPEG')==0||ext.indexOf('.GIF')==0)){ + PUB.toast('您选取的文件不是图片!'); + return; + } + } + } + var timestamp = new Date().getTime(); + var filename = Meteor.userId()+'_'+timestamp+'.jpg'; + showDebug && console.log('File name ' + filename); + //uploadToS3(filename,results[i],callback); + uploadToAliyun_new(filename,s,callback); + }, function(s){ + console.info(s); + }, { + quality: ImageQuality, + targetWidth: ImageWidth, + targetHeight: ImageHeight, + destinationType: destinationType.NATIVE_URI, + sourceType: pictureSource.SAVEDPHOTOALBUM + }); + + }else{ + window.imagePicker.getPictures( + function(results) { + if(results == undefined) + return; + var length = 0; + try{ + length=results.length; + } + catch (error){ + length=results.length; + } + if (length == 0) + return; + for (var i = 0; i < length; i++) { + var timestamp = new Date().getTime(); + var originalFilename = results[i].replace(/^.*[\\\/]/, ''); + var filename = Meteor.userId()+'_'+timestamp+ '_' + originalFilename; + showDebug && console.log('File name ' + filename); + //uploadToS3(filename,results[i],callback); + uploadToAliyun_new(filename,results[i],callback); + } + }, function (error){ + showDebug && console.log('Pick Image Error ' + error); + if(callback){ + callback(null); + } + }, { + maximumImagesCount: 1, + width: ImageWidth, + height: ImageHeight, + quality: ImageQuality, + storage: 'persistent' + }); + } + } + + var fileUploader = function (item,callback){ + console.log('uploading ' + JSON.stringify(item)); + if (Session.get('terminateUpload')) { + if (Session.get('flag')){ + return; + } + Session.set('flag',true); + return callback(new Error('aboutUpload'),item) + } + var self = this; + if ($.isEmptyObject(item)) { + self.uploaded++; + Session.set('progressBarWidth', parseInt(100*self.uploaded/self.total)); + return callback(null,item); + } + var filename = ''; + var URI = '' + if (item.type === 'music') { + filename = item.musicInfo.filename + URI = item.musicInfo.URI + } else if (item.type === 'video') { + filename = item.videoInfo.filename + URI = item.videoInfo.URI + } else { + filename = item.filename; + URI = item.URI + } + var ft = uploadToAliyun_new(filename, URI, function(status,param){ + if (Session.get('terminateUpload')) { + if (Session.get('flag')){ + return; + } + Session.set('flag',true); + return callback(new Error('aboutUpload'),item) + } + if (status === 'uploading' && param){ + var progressBarWidth = parseInt(100*(self.uploaded/self.total + (param.loaded / param.total)/self.total)); + if(progressBarWidth-Session.get('progressBarWidth')>=1){ + Session.set('progressBarWidth',progressBarWidth); + } + //Session.set('progressBarWidth', parseInt(100*(self.uploaded/self.total + (param.loaded / param.total)/self.total))); + } else if (status === 'done'){ + self.uploaded++; + var progressBarWidth1 = parseInt(100*self.uploaded/self.total); + if(progressBarWidth1-Session.get('progressBarWidth')>=1){ + Session.set('progressBarWidth',progressBarWidth1); + } + //Session.set('progressBarWidth', parseInt(100*self.uploaded/self.total)); + if ( item.type === 'music'){ + item.musicInfo.playUrl = param; + } else if ( item.type === 'video'){ + item.videoInfo.imageUrl = param; + } else { + item.imgUrl = param; + } + item.uploaded = true; + callback(null,item) + } else if (status === 'error'){ + item.uploaded = false; + Meteor.setTimeout( function() { + fileUploader(item, callback) + },1000); + } + }); + }; + + var asyncCallback = function (err,result){ + console.log('async processing done ' + JSON.stringify(result)); + Template.progressBar.__helpers.get('close')(); + if (err){ + console.log('err is ' + err); + if (this.finalCallback) { + console.log('result is '+ result); + this.finalCallback('error',result); + } + } else { + console.log('no err result ') + console.log(result) + if (this.finalCallback) { + this.finalCallback(null,result); + } + } + }; + multiThreadUploadFile_new = function(draftData, maxThreads, callback) { + var uploadObj = { + fileUploader : fileUploader, + draftData : draftData, + finalCallback: callback, + asyncCallback: asyncCallback, + uploaded : 0, + total : draftData.length + }; + console.log('draft data is ' + JSON.stringify(draftData)); + + Session.set('aboutUpload', false); + Session.set('flag',false); + async.mapLimit(draftData,maxThreads,uploadObj.fileUploader.bind(uploadObj),uploadObj.asyncCallback.bind(uploadObj)); + }; + multiThreadUploadFileWhenPublishInCordova = function(draftData, postId, callback){ + //showDebug && console.log("draftData="+JSON.stringify(draftData)); + if (draftData.length > 0) { + Template.progressBar.__helpers.get('show')(); + } else { + callback('failed'); + } + + var multiThreadUploadFileCallback = function(err,result){ + if (!err) { + // console.log('gooooooooooooood ') + callback(null, result); + } else { + Template.progressBar.__helpers.get('close')(); + showDebug && console.log("Jump to post page..."); + PUB.pagepop();//Pop addPost page, it was added by PUB.page('/progressBar'); + callback('failed', result); + showDebug && console.log("multiThreadUploadFile, failed"); + } + }; + + multiThreadUploadFile_new(draftData, 1, multiThreadUploadFileCallback); + return; + }; + uploadFileWhenPublishInCordova = function(draftData, postId){ + if(device.platform === 'testAndroid' ){ + Router.go('/posts/'+postId); + return; + } + var uploadedCount = 0; + //showDebug && console.log("draftData="+JSON.stringify(draftData)); + if (draftData.length > 0) { + $('.addProgress').css('display',"block"); + } + uploadingFilesInfo.filesCount = draftData.length; + uploadingFilesInfo.files = []; + for (var i=0; i= 0 || draftImageData[i].imgUrl.toLowerCase().indexOf("https://") >= 0) { + continue; + } + window.resolveLocalFileSystemURL(URI, function (fileEntry) { + fileEntry.remove(function () { + console.log("Removal succeeded"); + }, function (e) { + console.log('Error removing file: ' + e); + }); + }, function (error) { + console.log("fileEntry.file Error = " + error.code); + }); + } + + }, function () { + console.log('Request file system error'); + }); + + }; + uploadFile = uploadFileInCordova; + } diff --git a/client/lib/get_base64.js b/client/lib/get_base64.js new file mode 100644 index 000000000..d52f068e7 --- /dev/null +++ b/client/lib/get_base64.js @@ -0,0 +1,63 @@ +/** + * Created by simba on 4/10/15. + */ +if(Meteor.isCordova){ + window.getBase64OfImage = function(filename,originalFilename,URI,callback){ + //var params = {filename:filename, originalFilename:originalFilename, URI:URI, smallImage:''}; + if(withNewFilePath && device.platform === 'iOS'){ + var libIndex = URI.indexOf('Library'); + var filePath = URI.substring(libIndex); + URI = cordova.file.applicationStorageDirectory+filePath; + console.log("new file path: " + URI); + } + var fileExt = filename.split('.').pop(); + //retArray.push(params); + if(fileExt.toUpperCase()==='GIF'){ + ImageBase64.base64({ + uri: URI, + quality: 90, + width: 600, + height: 600 + }, + function(a) { + smallImage = "data:image/jpg;base64,"+a.base64; + if (callback){ + callback(URI,smallImage); + } + }, + function(e) { + console.log("error" + e); + if (callback){ + callback(URI,null); + } + }); + }else{ + window.resolveLocalFileSystemURL(URI, function(fileEntry) { + fileEntry.file(function(file) { + var reader = new FileReader(); + reader.onloadend = function(event) { + var localURL = event.target._localURL; + //retCount++; + smallImage = event.target.result; + if (callback){ + callback(URI,smallImage); + } + }; + reader.readAsDataURL(file); + }, function(e) { + console.log('fileEntry.file Error = ' + e); + if (callback){ + callback(URI,null); + } + }); + + }, function(e) { + console.log('resolveLocalFileSystemURL Error = ' + e); + if (callback){ + callback(URI,null); + } + }); + } + + } +} \ No newline at end of file diff --git a/client/lib/get_diff_time.js b/client/lib/get_diff_time.js index 3294809f7..0e778c718 100644 --- a/client/lib/get_diff_time.js +++ b/client/lib/get_diff_time.js @@ -16,23 +16,40 @@ GetTime0 = function(dateM){ var seconds=Math.round(leave3/1000); var prefix; - if(dateM > DyMilli) - prefix = days+"天前"; - else if (dateM > HrMilli) - prefix = hours+"小时前"; - else if (dateM > MinMilli) - prefix = minutes+"分钟前"; - else if (dateM <= MinMilli){ - if (seconds <= 0) - prefix = "刚刚"; - else - prefix = seconds+"秒前"; - } else - prefix = ""; - return prefix -} + if(Session.equals('display-lang','en')){ + if(dateM > DyMilli) + prefix = days+" Days"; + else if (dateM > HrMilli) + prefix = hours+" Hours"; + else if (dateM > MinMilli) + prefix = minutes+" Minutes"; + else if (dateM <= MinMilli){ + if (seconds <= 0) + prefix = " Now"; + else + prefix = seconds+" Seconds"; + } else + prefix = ""; + return prefix + } else { + if(dateM > DyMilli) + prefix = days+"天 前"; + else if (dateM > HrMilli) + prefix = hours+"小时 前"; + else if (dateM > MinMilli) + prefix = minutes+"分钟 前"; + else if (dateM <= MinMilli){ + if (seconds <= 0) + prefix = "刚刚"; + else + prefix = seconds+"秒 前"; + } else + prefix = ""; + return prefix + } +}; -get_diff_time = function (dateTimeStamp) { +get_diff_time = function(dateTimeStamp){ var minute = 1000 * 60; var hour = minute * 60; var day = hour * 24; @@ -40,30 +57,30 @@ get_diff_time = function (dateTimeStamp) { var month = day * 30; var now = new Date().getTime(); var diffValue = now - dateTimeStamp; - if (diffValue < 0) { return; } - var monthC = diffValue / month; - var weekC = diffValue / (7 * day); - var dayC = diffValue / day; - var hourC = diffValue / hour; - var minC = diffValue / minute; - if (monthC >= 1) { - if (parseInt(monthC) >= 12) - result = "1 年前"; + if(diffValue < 0){return;} + var monthC =diffValue/month; + var weekC =diffValue/(7*day); + var dayC =diffValue/day; + var hourC =diffValue/hour; + var minC =diffValue/minute; + if(monthC>=1){ + if(parseInt(monthC) >= 12) + result="1 年前"; else - result = "" + parseInt(monthC) + " 月前"; + result="" + parseInt(monthC) + " 月前"; } - else if (weekC >= 1) { - result = "" + parseInt(weekC) + " 周前"; + else if(weekC>=1){ + result="" + parseInt(weekC) + " 周前"; } - else if (dayC >= 1) { - result = "" + parseInt(dayC) + " 天前"; + else if(dayC>=1){ + result=""+ parseInt(dayC) +" 天前"; } - else if (hourC >= 1) { - result = "" + parseInt(hourC) + " 小时前"; + else if(hourC>=1){ + result=""+ parseInt(hourC) +" 小时前"; } - else if (minC >= 1) { - result = "" + parseInt(minC) + " 分钟前"; - } else - result = "刚刚"; + else if(minC>=1){ + result=""+ parseInt(minC) +" 分钟前"; + }else + result="刚刚"; return result; -} \ No newline at end of file +}; \ No newline at end of file diff --git a/client/lib/hashOnString.js b/client/lib/hashOnString.js new file mode 100644 index 000000000..87a433122 --- /dev/null +++ b/client/lib/hashOnString.js @@ -0,0 +1,15 @@ + +if(Meteor.isClient){ + if (!String.prototype.hashCode ){ + String.prototype.hashCode = function() { + var hash = 0, i, chr, len; + if (this.length == 0) return hash; + for (i = 0, len = this.length; i < len; i++) { + chr = this.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; + }; + } +} \ No newline at end of file diff --git a/client/lib/jquery-near-viewport.min.js b/client/lib/jquery-near-viewport.min.js new file mode 100644 index 000000000..54258344e --- /dev/null +++ b/client/lib/jquery-near-viewport.min.js @@ -0,0 +1 @@ +require=function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gd&&f>h}},{}],2:[function(a){(function(b){var c=a("jquery"),d=a("./near-viewport.js");c.expr[":"]["near-viewport"]=function(a,c,e){var f=b.parseInt(e[3])||0;return d(a,f)}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./near-viewport.js":1,jquery:"jquery"}],jquery:[function(a,b){(function(a){b.exports=a.jQuery}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[2]); \ No newline at end of file diff --git a/client/lib/jquery.bpopup.0.11.0.js b/client/lib/jquery.bpopup.0.11.0.js index 1586abe50..763162c48 100644 --- a/client/lib/jquery.bpopup.0.11.0.js +++ b/client/lib/jquery.bpopup.0.11.0.js @@ -133,7 +133,8 @@ if (o.modal) { $('.b-modal.'+$popup.data('id')) .fadeTo(o.speed, 0, function() { - $(this).remove(); + // 如果使用remove方法后,在点击下一次router.go时出现了黑屏情况,故使用hide + $(this).hide(); }); } // Clean up diff --git a/client/lib/jquery.gridster.0.5.6.js b/client/lib/jquery.gridster.0.5.6.js new file mode 100644 index 000000000..16a3c77bf --- /dev/null +++ b/client/lib/jquery.gridster.0.5.6.js @@ -0,0 +1,4222 @@ +/*! gridster.js - v0.5.6 - 2014-09-25 +* http://gridster.net/ +* Copyright (c) 2014 ducksboard; Licensed MIT */ + +;(function(root, factory) { + + if (typeof define === 'function' && define.amd) { + define('gridster-coords', ['jquery'], factory); + } else { + root.GridsterCoords = factory(root.$ || root.jQuery); + } + +}(this, function($) { + /** + * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height) + * to simulate DOM elements on the screen. + * Coords is used by Gridster to create a faux grid with any DOM element can + * collide. + * + * @class Coords + * @param {HTMLElement|Object} obj The jQuery HTMLElement or a object with: left, + * top, width and height properties. + * @return {Object} Coords instance. + * @constructor + */ + function Coords(obj) { + if (obj[0] && $.isPlainObject(obj[0])) { + this.data = obj[0]; + }else { + this.el = obj; + } + + this.isCoords = true; + this.coords = {}; + this.init(); + return this; + } + + + var fn = Coords.prototype; + + + fn.init = function(){ + this.set(); + this.original_coords = this.get(); + }; + + + fn.set = function(update, not_update_offsets) { + var el = this.el; + + if (el && !update) { + this.data = el.offset(); + this.data.width = el.width(); + this.data.height = el.height(); + } + + if (el && update && !not_update_offsets) { + var offset = el.offset(); + this.data.top = offset.top; + this.data.left = offset.left; + } + + var d = this.data; + + typeof d.left === 'undefined' && (d.left = d.x1); + typeof d.top === 'undefined' && (d.top = d.y1); + + this.coords.x1 = d.left; + this.coords.y1 = d.top; + this.coords.x2 = d.left + d.width; + this.coords.y2 = d.top + d.height; + this.coords.cx = d.left + (d.width / 2); + this.coords.cy = d.top + (d.height / 2); + this.coords.width = d.width; + this.coords.height = d.height; + this.coords.el = el || false ; + + return this; + }; + + + fn.update = function(data){ + if (!data && !this.el) { + return this; + } + + if (data) { + var new_data = $.extend({}, this.data, data); + this.data = new_data; + return this.set(true, true); + } + + this.set(true); + return this; + }; + + + fn.get = function(){ + return this.coords; + }; + + fn.destroy = function() { + this.el.removeData('coords'); + delete this.el; + }; + + //jQuery adapter + $.fn.coords = function() { + if (this.data('coords') ) { + return this.data('coords'); + } + + var ins = new Coords(this, arguments[0]); + this.data('coords', ins); + return ins; + }; + + return Coords; + +})); + +;(function(root, factory) { + + if (typeof define === 'function' && define.amd) { + define('gridster-collision', ['jquery', 'gridster-coords'], factory); + } else { + root.GridsterCollision = factory(root.$ || root.jQuery, + root.GridsterCoords); + } + +}(this, function($, Coords) { + + var defaults = { + colliders_context: document.body, + overlapping_region: 'C' + // ,on_overlap: function(collider_data){}, + // on_overlap_start : function(collider_data){}, + // on_overlap_stop : function(collider_data){} + }; + + + /** + * Detects collisions between a DOM element against other DOM elements or + * Coords objects. + * + * @class Collision + * @uses Coords + * @param {HTMLElement} el The jQuery wrapped HTMLElement. + * @param {HTMLElement|Array} colliders Can be a jQuery collection + * of HTMLElements or an Array of Coords instances. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {String} [options.overlapping_region] Determines when collision + * is valid, depending on the overlapped area. Values can be: 'N', 'S', + * 'W', 'E', 'C' or 'all'. Default is 'C'. + * @param {Function} [options.on_overlap_start] Executes a function the first + * time each `collider ` is overlapped. + * @param {Function} [options.on_overlap_stop] Executes a function when a + * `collider` is no longer collided. + * @param {Function} [options.on_overlap] Executes a function when the + * mouse is moved during the collision. + * @return {Object} Collision instance. + * @constructor + */ + function Collision(el, colliders, options) { + this.options = $.extend(defaults, options); + this.$element = el; + this.last_colliders = []; + this.last_colliders_coords = []; + this.set_colliders(colliders); + + this.init(); + } + + Collision.defaults = defaults; + + var fn = Collision.prototype; + + + fn.init = function() { + this.find_collisions(); + }; + + + fn.overlaps = function(a, b) { + var x = false; + var y = false; + + if ((b.x1 >= a.x1 && b.x1 <= a.x2) || + (b.x2 >= a.x1 && b.x2 <= a.x2) || + (a.x1 >= b.x1 && a.x2 <= b.x2) + ) { x = true; } + + if ((b.y1 >= a.y1 && b.y1 <= a.y2) || + (b.y2 >= a.y1 && b.y2 <= a.y2) || + (a.y1 >= b.y1 && a.y2 <= b.y2) + ) { y = true; } + + return (x && y); + }; + + + fn.detect_overlapping_region = function(a, b){ + var regionX = ''; + var regionY = ''; + + if (a.y1 > b.cy && a.y1 < b.y2) { regionX = 'N'; } + if (a.y2 > b.y1 && a.y2 < b.cy) { regionX = 'S'; } + if (a.x1 > b.cx && a.x1 < b.x2) { regionY = 'W'; } + if (a.x2 > b.x1 && a.x2 < b.cx) { regionY = 'E'; } + + return (regionX + regionY) || 'C'; + }; + + + fn.calculate_overlapped_area_coords = function(a, b){ + var x1 = Math.max(a.x1, b.x1); + var y1 = Math.max(a.y1, b.y1); + var x2 = Math.min(a.x2, b.x2); + var y2 = Math.min(a.y2, b.y2); + + return $({ + left: x1, + top: y1, + width : (x2 - x1), + height: (y2 - y1) + }).coords().get(); + }; + + + fn.calculate_overlapped_area = function(coords){ + return (coords.width * coords.height); + }; + + + fn.manage_colliders_start_stop = function(new_colliders_coords, start_callback, stop_callback){ + var last = this.last_colliders_coords; + + for (var i = 0, il = last.length; i < il; i++) { + if ($.inArray(last[i], new_colliders_coords) === -1) { + start_callback.call(this, last[i]); + } + } + + for (var j = 0, jl = new_colliders_coords.length; j < jl; j++) { + if ($.inArray(new_colliders_coords[j], last) === -1) { + stop_callback.call(this, new_colliders_coords[j]); + } + + } + }; + + + fn.find_collisions = function(player_data_coords){ + var self = this; + var overlapping_region = this.options.overlapping_region; + var colliders_coords = []; + var colliders_data = []; + var $colliders = (this.colliders || this.$colliders); + var count = $colliders.length; + var player_coords = self.$element.coords() + .update(player_data_coords || false).get(); + + while(count--){ + var $collider = self.$colliders ? + $($colliders[count]) : $colliders[count]; + var $collider_coords_ins = ($collider.isCoords) ? + $collider : $collider.coords(); + var collider_coords = $collider_coords_ins.get(); + var overlaps = self.overlaps(player_coords, collider_coords); + + if (!overlaps) { + continue; + } + + var region = self.detect_overlapping_region( + player_coords, collider_coords); + + //todo: make this an option + if (region === overlapping_region || overlapping_region === 'all') { + + var area_coords = self.calculate_overlapped_area_coords( + player_coords, collider_coords); + var area = self.calculate_overlapped_area(area_coords); + var collider_data = { + area: area, + area_coords : area_coords, + region: region, + coords: collider_coords, + player_coords: player_coords, + el: $collider + }; + + if (self.options.on_overlap) { + self.options.on_overlap.call(this, collider_data); + } + colliders_coords.push($collider_coords_ins); + colliders_data.push(collider_data); + } + } + + if (self.options.on_overlap_stop || self.options.on_overlap_start) { + this.manage_colliders_start_stop(colliders_coords, + self.options.on_overlap_start, self.options.on_overlap_stop); + } + + this.last_colliders_coords = colliders_coords; + + return colliders_data; + }; + + + fn.get_closest_colliders = function(player_data_coords){ + var colliders = this.find_collisions(player_data_coords); + + colliders.sort(function(a, b) { + /* if colliders are being overlapped by the "C" (center) region, + * we have to set a lower index in the array to which they are placed + * above in the grid. */ + if (a.region === 'C' && b.region === 'C') { + if (a.coords.y1 < b.coords.y1 || a.coords.x1 < b.coords.x1) { + return - 1; + }else{ + return 1; + } + } + + if (a.area < b.area) { + return 1; + } + + return 1; + }); + return colliders; + }; + + + fn.set_colliders = function(colliders) { + if (typeof colliders === 'string' || colliders instanceof $) { + this.$colliders = $(colliders, + this.options.colliders_context).not(this.$element); + }else{ + this.colliders = $(colliders); + } + }; + + + //jQuery adapter + $.fn.collision = function(collider, options) { + return new Collision( this, collider, options ); + }; + + return Collision; + +})); + +;(function(window, undefined) { + + /* Delay, debounce and throttle functions taken from underscore.js + * + * Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and + * Investigative Reporters & Editors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + window.delay = function(func, wait) { + var args = Array.prototype.slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(null, args); }, wait); + }; + + window.debounce = function(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + if (immediate && !timeout) func.apply(context, args); + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + }; + + window.throttle = function(func, wait) { + var context, args, timeout, throttling, more, result; + var whenDone = debounce( + function(){ more = throttling = false; }, wait); + return function() { + context = this; args = arguments; + var later = function() { + timeout = null; + if (more) func.apply(context, args); + whenDone(); + }; + if (!timeout) timeout = setTimeout(later, wait); + if (throttling) { + more = true; + } else { + result = func.apply(context, args); + } + whenDone(); + throttling = true; + return result; + }; + }; + +})(window); + +;(function(root, factory) { + + if (typeof define === 'function' && define.amd) { + define('gridster-draggable', ['jquery'], factory); + } else { + root.GridsterDraggable = factory(root.$ || root.jQuery); + } + +}(this, function($) { + + var defaults = { + items: 'li', + distance: 1, + limit: true, + offset_left: 0, + autoscroll: true, + ignore_dragging: ['INPUT', 'SELECT', 'BUTTON'], // or function + handle: null, + container_width: 0, // 0 == auto + move_element: true, + helper: false, // or 'clone' + remove_helper: true, + long_press: true + // drag: function(e) {}, + // start : function(e, ui) {}, + // stop : function(e) {} + }; + + var $window = $(window); + var dir_map = { x : 'left', y : 'top' }; + var isTouch = !!('ontouchstart' in window); + + var capitalize = function(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + }; + + var idCounter = 0; + var uniqId = function() { + return ++idCounter + ''; + } + + /** + * Basic drag implementation for DOM elements inside a container. + * Provide start/stop/drag callbacks. + * + * @class Draggable + * @param {HTMLElement} el The HTMLelement that contains all the widgets + * to be dragged. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.items] Define who will + * be the draggable items. Can be a CSS Selector String or a + * collection of HTMLElements. + * @param {Number} [options.distance] Distance in pixels after mousedown + * the mouse must move before dragging should start. + * @param {Boolean} [options.limit] Constrains dragging to the width of + * the container + * @param {Object|Function} [options.ignore_dragging] Array of node names + * that sould not trigger dragging, by default is `['INPUT', 'TEXTAREA', + * 'SELECT', 'BUTTON']`. If a function is used return true to ignore dragging. + * @param {offset_left} [options.offset_left] Offset added to the item + * that is being dragged. + * @param {Number} [options.drag] Executes a callback when the mouse is + * moved during the dragging. + * @param {Number} [options.start] Executes a callback when the drag + * starts. + * @param {Number} [options.stop] Executes a callback when the drag stops. + * @return {Object} Returns `el`. + * @constructor + */ + function Draggable(el, options) { + this.options = $.extend({}, defaults, options); + this.$document = $(document); + this.$container = $(el); + this.$dragitems = $(this.options.items, this.$container); + this.is_dragging = false; + this.player_min_left = 0 + this.options.offset_left; + this.id = uniqId(); + this.ns = '.gridster-draggable-' + this.id; + this.init(); + } + + Draggable.defaults = defaults; + + var fn = Draggable.prototype; + + fn.init = function() { + var pos = this.$container.css('position'); + this.calculate_dimensions(); + this.$container.css('position', pos === 'static' ? 'relative' : pos); + this.disabled = false; + this.events(); + + $(window).bind(this.nsEvent('resize'), + throttle($.proxy(this.calculate_dimensions, this), 200)); + }; + + //Added added_widget hook, to process added longpress event. + fn.added_widget = function($el){ + if(this.options.long_press === false) { + return; + } + $el.longpress($.proxy(function(e){ + //force only pressed item can be draggable + if($(e.currentTarget).hasClass('pressed') == false) { + $(e.currentTarget).trigger( "click" ) + //return false; + } + if (typeof global_disable_longpress !== "undefined" && global_disable_longpress != null){ + if (global_disable_longpress) { + return; + } + } + + $el.animate({ + 'marginLeft' : "+=5px", //moves Right + 'marginTop' : "-=5px" + },{ + duration : 200, + complete : function(){ + $el.animate({ + 'marginLeft' : "-=5px", //moves Left + 'marginTop' : "+=5px" + },{ + duration:200 + }); + } + }); + + this.drag_handler(e); + },this)); + }; + + + fn.nsEvent = function(ev) { + return (ev || '') + this.ns; + }; + + fn.events = function() { + this.pointer_events = { + start: this.nsEvent('touchstart') + ' ' + this.nsEvent('mousedown'), + move: this.nsEvent('touchmove') + ' ' + this.nsEvent('mousemove'), + end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup') + ' ' + this.nsEvent('touchcancel') + }; + + this.$container.on(this.nsEvent('selectstart'), + $.proxy(this.on_select_start, this)); + + + if(this.options.from_resiable){ + this.$container.on(this.pointer_events.start, this.options.items, + $.proxy(this.drag_handler, this)); + } else { + if(this.options.long_press === false) { + return; + } + that = this; + var duration = 200; + var mouse_down_time; + var timeout; + + // mousedown or touchstart callback + function mousedown_callback(e) { + + + mouse_down_time = new Date().getTime(); + var context = $(this); + + // set a timeout to call the longpress callback when time elapses + timeout = setTimeout(function() { + //console.log("taphold called"); + + if($(e.currentTarget).hasClass('pressed') == false) { + $(e.currentTarget).trigger( "click" ) + //return false; + } + if (typeof global_disable_longpress !== "undefined" && global_disable_longpress != null){ + if (global_disable_longpress) { + return; + } + } + + $(e.currentTarget).animate({ + 'marginLeft' : "+=5px", //moves Right + 'marginTop' : "-=5px" + },{ + duration : 200, + complete : function(){ + $(e.currentTarget).animate({ + 'marginLeft' : "-=5px", //moves Left + 'marginTop' : "+=5px" + },{ + duration:200 + }); + } + }); + + + that.drag_handler(e); + }, duration); + } + + // mouseup or touchend callback + function mouseup_callback(e) { + var press_time = new Date().getTime() - mouse_down_time; + if (press_time < duration) { + // cancel the timeout + clearTimeout(timeout); + } + } + + // cancel long press event if the finger or mouse was moved + function move_callback(e) { + clearTimeout(timeout); + } + + this.$container.on("touchstart", this.options.items,mousedown_callback); + this.$container.on("touchmove", this.options.items,move_callback); + this.$container.on("touchend", this.options.items,mouseup_callback); + + /* + that = this; + $(this.options.items).each($.proxy(function(index,element){ + var $el = $(element); + this.added_widget($el); + },this)); + */ + } + + + //this.$container.on(this.pointer_events.start, this.options.items, + // $.proxy(this.drag_handler, this)); + + this.$document.on(this.pointer_events.end, $.proxy(function(e) { + this.is_dragging = false; + if (this.disabled) { return; } + this.$document.off(this.pointer_events.move); + if (this.drag_start) { + this.on_dragstop(e); + } + }, this)); + }; + + fn.get_actual_pos = function($el) { + var pos = $el.position(); + return pos; + }; + + + fn.get_mouse_pos = function(e) { + if (e.originalEvent && e.originalEvent.touches) { + var oe = e.originalEvent; + e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0]; + } + + return { + left: e.clientX, + top: e.clientY + }; + }; + + + fn.get_offset = function(e) { + e.preventDefault(); + var mouse_actual_pos = this.get_mouse_pos(e); + var diff_x = Math.round( + mouse_actual_pos.left - this.mouse_init_pos.left); + var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top); + + var left = Math.round(this.el_init_offset.left + + diff_x - this.baseX + $(window).scrollLeft() - this.win_offset_x); + var top = Math.round(this.el_init_offset.top + + diff_y - this.baseY + $(window).scrollTop() - this.win_offset_y); + + if (this.options.limit) { + if (left > this.player_max_left) { + left = this.player_max_left; + } else if(left < this.player_min_left) { + left = this.player_min_left; + } + } + + return { + position: { + left: left, + top: top + }, + pointer: { + left: mouse_actual_pos.left, + top: mouse_actual_pos.top, + diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x), + diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y) + } + }; + }; + + + fn.get_drag_data = function(e) { + var offset = this.get_offset(e); + offset.$player = this.$player; + offset.$helper = this.helper ? this.$helper : this.$player; + + return offset; + }; + + + fn.set_limits = function(container_width) { + container_width || (container_width = this.$container.width()); + this.player_max_left = (container_width - this.player_width + + - this.options.offset_left); + + this.options.container_width = container_width; + + return this; + }; + + + fn.scroll_in = function(axis, data) { + var dir_prop = dir_map[axis]; + + var area_size = 50; + var scroll_inc = 30; + + var is_x = axis === 'x'; + var window_size = is_x ? this.window_width : this.window_height; + var doc_size = is_x ? $(document).width() : $(document).height(); + var player_size = is_x ? this.$player.width() : this.$player.height(); + + var next_scroll; + var scroll_offset = $window['scroll' + capitalize(dir_prop)](); + var min_window_pos = scroll_offset; + var max_window_pos = min_window_pos + window_size; + + var mouse_next_zone = max_window_pos - area_size; // down/right + var mouse_prev_zone = min_window_pos + area_size; // up/left + + var abs_mouse_pos = min_window_pos + data.pointer[dir_prop]; + + var max_player_pos = (doc_size - window_size + player_size); + + if (abs_mouse_pos >= mouse_next_zone) { + next_scroll = scroll_offset + scroll_inc; + if (next_scroll < max_player_pos) { + $window['scroll' + capitalize(dir_prop)](next_scroll); + this['scroll_offset_' + axis] += scroll_inc; + } + } + + if (abs_mouse_pos <= mouse_prev_zone) { + next_scroll = scroll_offset - scroll_inc; + if (next_scroll > 0) { + $window['scroll' + capitalize(dir_prop)](next_scroll); + this['scroll_offset_' + axis] -= scroll_inc; + } + } + + return this; + }; + + + fn.manage_scroll = function(data) { + this.scroll_in('x', data); + this.scroll_in('y', data); + }; + + + fn.calculate_dimensions = function(e) { + this.window_height = $window.height(); + this.window_width = $window.width(); + }; + + + fn.drag_handler = function(e) { + var node = e.target.nodeName; + // skip if drag is disabled, or click was not done with the mouse primary button + if (this.disabled || e.which !== 1 && !isTouch) { + return; + } + if ($(e.currentTarget).parent()) { + if ($(e.currentTarget).hasClass('gs-resize-handle') + && !$(e.currentTarget).parent().hasClass('pressed')) { + return; + } + } + + if (this.ignore_drag(e)) { + return; + } + + var self = this; + var first = true; + this.$player = $(e.currentTarget); + + this.el_init_pos = this.get_actual_pos(this.$player); + this.mouse_init_pos = this.get_mouse_pos(e); + this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top; + + this.$document.on(this.pointer_events.move, function(mme) { + var mouse_actual_pos = self.get_mouse_pos(mme); + var diff_x = Math.abs( + mouse_actual_pos.left - self.mouse_init_pos.left); + var diff_y = Math.abs( + mouse_actual_pos.top - self.mouse_init_pos.top); + if (!(diff_x > self.options.distance || + diff_y > self.options.distance) + ) { + return false; + } + + if (first) { + first = false; + self.on_dragstart.call(self, mme); + return false; + } + + if (self.is_dragging === true) { + self.on_dragmove.call(self, mme); + } + + return false; + }); + + if (!isTouch) { return false; } + }; + + + fn.on_dragstart = function(e) { + e.preventDefault(); + + if (this.is_dragging) { return this; } + + this.drag_start = this.is_dragging = true; + var offset = this.$container.offset(); + this.baseX = Math.round(offset.left); + this.baseY = Math.round(offset.top); + this.initial_container_width = this.options.container_width || this.$container.width(); + + if (this.options.helper === 'clone') { + this.$helper = this.$player.clone() + .appendTo(this.$container).addClass('helper'); + this.helper = true; + } else { + this.helper = false; + } + + this.win_offset_y = $(window).scrollTop(); + this.win_offset_x = $(window).scrollLeft(); + this.scroll_offset_y = 0; + this.scroll_offset_x = 0; + this.el_init_offset = this.$player.offset(); + this.player_width = this.$player.width(); + this.player_height = this.$player.height(); + + this.set_limits(this.options.container_width); + + if (this.options.start) { + this.options.start.call(this.$player, e, this.get_drag_data(e)); + } + return false; + }; + + + fn.on_dragmove = function(e) { + var data = this.get_drag_data(e); + + this.options.autoscroll && this.manage_scroll(data); + + if (this.options.move_element) { + (this.helper ? this.$helper : this.$player).css({ + 'position': 'absolute', + 'left' : data.position.left, + 'top' : data.position.top + }); + } + + var last_position = this.last_position || data.position; + data.prev_position = last_position; + + if (this.options.drag) { + this.options.drag.call(this.$player, e, data); + } + + this.last_position = data.position; + return false; + }; + + + fn.on_dragstop = function(e) { + var data = this.get_drag_data(e); + this.drag_start = false; + + if (this.options.stop) { + this.options.stop.call(this.$player, e, data); + } + + if (this.helper && this.options.remove_helper) { + this.$helper.remove(); + } + + return false; + }; + + fn.on_select_start = function(e) { + if (this.disabled) { return; } + + if (this.ignore_drag(e)) { + return; + } + + return false; + }; + + fn.enable = function() { + this.disabled = false; + }; + + fn.disable = function() { + this.disabled = true; + }; + + fn.destroy = function() { + this.disable(); + + this.$container.off(this.ns); + this.$document.off(this.ns); + $(window).off(this.ns); + + $.removeData(this.$container, 'drag'); + }; + + fn.ignore_drag = function(event) { + if (this.options.handle) { + return !$(event.target).is(this.options.handle); + } + + if ($.isFunction(this.options.ignore_dragging)) { + return this.options.ignore_dragging(event); + } + + return $(event.target).is(this.options.ignore_dragging.join(', ')); + }; + + //jQuery adapter + $.fn.drag = function ( options ) { + return new Draggable(this, options); + }; + + return Draggable; + +})); + +;(function(root, factory) { + + if (typeof define === 'function' && define.amd) { + define(['jquery', 'gridster-draggable', 'gridster-collision'], factory); + } else { + root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable, + root.GridsterCollision); + } + + }(this, function($, Draggable, Collision) { + + var defaults = { + namespace: '', + widget_selector: 'li', + widget_margins: [10, 10], + widget_base_dimensions: [400, 225], + extra_rows: 0, + extra_cols: 0, + min_cols: 1, + max_cols: Infinity, + min_rows: 15, + max_size_x: false, + autogrow_cols: false, + autogenerate_stylesheet: true, + avoid_overlapped_widgets: true, + auto_init: true, + serialize_params: function($w, wgd) { + return { + col: wgd.col, + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + }, + collision: {}, + draggable: { + items: '.gs-w', + distance: 4, + ignore_dragging: Draggable.defaults.ignore_dragging.slice(0) + }, + resize: { + enabled: false, + axes: ['x','y','both'], + handle_append_to: '', + handle_class: 'gs-resize-handle', + max_size: [Infinity, Infinity], + min_size: [1, 1] + } + }; + + /** + * @class Gridster + * @uses Draggable + * @uses Collision + * @param {HTMLElement} el The HTMLelement that contains all the widgets. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.widget_selector] Define who will + * be the draggable widgets. Can be a CSS Selector String or a + * collection of HTMLElements + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @param {Number} [options.extra_cols] Add more columns in addition to + * those that have been calculated. + * @param {Number} [options.extra_rows] Add more rows in addition to + * those that have been calculated. + * @param {Number} [options.min_cols] The minimum required columns. + * @param {Number} [options.max_cols] The maximum columns possible (set to null + * for no maximum). + * @param {Number} [options.min_rows] The minimum required rows. + * @param {Number} [options.max_size_x] The maximum number of columns + * that a widget can span. + * @param {Boolean} [options.autogenerate_stylesheet] If true, all the + * CSS required to position all widgets in their respective columns + * and rows will be generated automatically and injected to the + * `` of the document. You can set this to false, and write + * your own CSS targeting rows and cols via data-attributes like so: + * `[data-col="1"] { left: 10px; }` + * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded + * from the DOM can be overlapped. It is helpful if the positions were + * bad stored in the database or if there was any conflict. + * @param {Boolean} [options.auto_init] Automatically call gridster init + * method or not when the plugin is instantiated. + * @param {Function} [options.serialize_params] Return the data you want + * for each widget in the serialization. Two arguments are passed: + * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid + * coords object (`col`, `row`, `size_x`, `size_y`). + * @param {Object} [options.collision] An Object with all options for + * Collision class you want to overwrite. See Collision docs for + * more info. + * @param {Object} [options.draggable] An Object with all options for + * Draggable class you want to overwrite. See Draggable docs for more + * info. + * @param {Object|Function} [options.draggable.ignore_dragging] Note that + * if you use a Function, and resize is enabled, you should ignore the + * resize handlers manually (options.resize.handle_class). + * @param {Object} [options.resize] An Object with resize config options. + * @param {Boolean} [options.resize.enabled] Set to true to enable + * resizing. + * @param {Array} [options.resize.axes] Axes in which widgets can be + * resized. Possible values: ['x', 'y', 'both']. + * @param {String} [options.resize.handle_append_to] Set a valid CSS + * selector to append resize handles to. + * @param {String} [options.resize.handle_class] CSS class name used + * by resize handles. + * @param {Array} [options.resize.max_size] Limit widget dimensions + * when resizing. Array values should be integers: + * `[max_cols_occupied, max_rows_occupied]` + * @param {Array} [options.resize.min_size] Limit widget dimensions + * when resizing. Array values should be integers: + * `[min_cols_occupied, min_rows_occupied]` + * @param {Function} [options.resize.start] Function executed + * when resizing starts. + * @param {Function} [otions.resize.resize] Function executed + * during the resizing. + * @param {Function} [options.resize.stop] Function executed + * when resizing stops. + * + * @constructor + */ + function Gridster(el, options) { + this.options = $.extend(true, {}, defaults, options); + this.$el = $(el); + this.$wrapper = this.$el.parent(); + this.$widgets = this.$el.children( + this.options.widget_selector).addClass('gs-w'); + this.widgets = []; + this.$changed = $([]); + this.wrapper_width = this.$wrapper.width(); + this.min_widget_width = (this.options.widget_margins[0] * 2) + + this.options.widget_base_dimensions[0]; + this.min_widget_height = (this.options.widget_margins[1] * 2) + + this.options.widget_base_dimensions[1]; + + this.generated_stylesheets = []; + this.$style_tags = $([]); + + this.options.auto_init && this.init(); + } + + Gridster.defaults = defaults; + Gridster.generated_stylesheets = []; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in ascending way. + * + * @method sort_by_row_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (!a.row) { + a = $(a).coords().grid; + b = $(b).coords().grid; + } + + if (a.row > b.row) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) placing first the empty cells upper left. + * + * @method sort_by_row_and_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_and_col_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.row > b.row || a.row === b.row && a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects by column (representing the grid + * coords of each widget) in ascending way. + * + * @method sort_by_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_col_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method sort_by_row_desc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_desc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.row + a.size_y < b.row + b.size_y) { + return 1; + } + return -1; + }); + return widgets; + }; + + + + /** Instance Methods **/ + + var fn = Gridster.prototype; + + fn.init = function() { + this.options.resize.enabled && this.setup_resize(); + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.set_dom_grid_width(); + this.$wrapper.addClass('ready'); + this.draggable(); + this.options.resize.enabled && this.resizable(); + + $(window).bind('resize.gridster', throttle( + $.proxy(this.recalculate_faux_grid, this), 200)); + }; + + + /** + * Disables dragging. + * + * @method disable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.disable = function() { + this.$wrapper.find('.player-revert').removeClass('player-revert'); + this.drag_api.disable(); + return this; + }; + + + /** + * Enables dragging. + * + * @method enable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.enable = function() { + this.drag_api.enable(); + return this; + }; + + + + /** + * Disables drag-and-drop widget resizing. + * + * @method disable + * @return {Class} Returns instance of gridster Class. + */ + fn.disable_resize = function() { + this.$el.addClass('gs-resize-disabled'); + if (this.resize_api){ + this.resize_api.disable(); + } + return this; + }; + + + /** + * Enables drag-and-drop widget resizing. + * + * @method enable + * @return {Class} Returns instance of gridster Class. + */ + fn.enable_resize = function() { + this.$el.removeClass('gs-resize-disabled'); + if (this.resize_api){ + this.resize_api.enable(); + } + return this; + }; + + + /** + * Add a new widget to the grid. + * + * @method add_widget + * @param {String|HTMLElement} html The string representing the HTML of the widget + * or the HTMLElement. + * @param {Number} [size_x] The nº of rows the widget occupies horizontally. + * @param {Number} [size_y] The nº of columns the widget occupies vertically. + * @param {Number} [col] The column the widget should start in. + * @param {Number} [row] The row the widget should start in. + * @param {Array} [max_size] max_size Maximun size (in units) for width and height. + * @param {Array} [min_size] min_size Minimum size (in units) for width and height. + * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. + * the widget that was just created. + */ + fn.add_widget = function(html, size_x, size_y, col, row, max_size, min_size) { + var pos; + size_x || (size_x = 1); + size_y || (size_y = 1); + + if (!col & !row) { + pos = this.next_position(size_x, size_y); + } else { + pos = { + col: col, + row: row, + size_x: size_x, + size_y: size_y + }; + + this.empty_cells(col, row, size_x, size_y); + } + + var $w = $(html).attr({ + 'data-col': pos.col, + 'data-row': pos.row, + 'data-sizex' : size_x, + 'data-sizey' : size_y + }).addClass('gs-w').appendTo(this.$el).hide(); + + this.$widgets = this.$widgets.add($w); + + this.add_faux_rows(pos.size_y); + this.register_widget($w); + + //this.rows+=pos.size_y; + + //this.add_faux_rows(pos.size_y); + //this.add_faux_cols(pos.size_x); + + if (max_size) { + this.set_widget_max_size($w, max_size); + } + + if (min_size) { + this.set_widget_min_size($w, min_size); + } + + this.set_dom_grid_width(); + this.set_dom_grid_height(); + + this.drag_api.set_limits(this.cols * this.min_widget_width); + + return $w.fadeIn(); + }; + + + /** + * Change widget size limits. + * + * @method set_widget_min_size + * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement + * representing the widget or an index representing the desired widget. + * @param {Array} min_size Minimum size (in units) for width and height. + * @return {HTMLElement} Returns instance of gridster Class. + */ + fn.set_widget_min_size = function($widget, min_size) { + $widget = typeof $widget === 'number' ? + this.$widgets.eq($widget) : $widget; + + if (!$widget.length) { return this; } + + var wgd = $widget.data('coords').grid; + wgd.min_size_x = min_size[0]; + wgd.min_size_y = min_size[1]; + + return this; + }; + + + /** + * Change widget size limits. + * + * @method set_widget_max_size + * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement + * representing the widget or an index representing the desired widget. + * @param {Array} max_size Maximun size (in units) for width and height. + * @return {HTMLElement} Returns instance of gridster Class. + */ + fn.set_widget_max_size = function($widget, max_size) { + $widget = typeof $widget === 'number' ? + this.$widgets.eq($widget) : $widget; + + if (!$widget.length) { return this; } + + var wgd = $widget.data('coords').grid; + wgd.max_size_x = max_size[0]; + wgd.max_size_y = max_size[1]; + + return this; + }; + + + /** + * Append the resize handle into a widget. + * + * @method add_resize_handle + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @return {HTMLElement} Returns instance of gridster Class. + */ + fn.add_resize_handle = function($w) { + if ($w.hasClass('hastextarea') == true){ + return; + } + var append_to = this.options.resize.handle_append_to; + $(this.resize_handle_tpl).appendTo( append_to ? $(append_to, $w) : $w); + + return this; + }; + + + /** + * Change the size of a widget. Width is limited to the current grid width. + * + * @method resize_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} size_x The number of columns that will occupy the widget. + * By default size_x is limited to the space available from + * the column where the widget begins, until the last column to the right. + * @param {Number} size_y The number of rows that will occupy the widget. + * @param {Function} [callback] Function executed when the widget is removed. + * @return {HTMLElement} Returns $widget. + */ + fn.resize_widget = function($widget, size_x, size_y, callback) { + var wgd = $widget.coords().grid; + var col = wgd.col; + var max_cols = this.options.max_cols; + var old_size_y = wgd.size_y; + var old_col = wgd.col; + var new_col = old_col; + + size_x || (size_x = wgd.size_x); + size_y || (size_y = wgd.size_y); + + if (max_cols !== Infinity) { + size_x = Math.min(size_x, max_cols - col + 1); + } + + if (size_y > old_size_y) { + this.add_faux_rows(Math.max(size_y - old_size_y, 0)); + } + + var player_rcol = (col + size_x - 1); + if (player_rcol > this.cols) { + this.add_faux_cols(player_rcol - this.cols); + } + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + + /** + * Mutate widget dimensions and position in the grid map. + * + * @method mutate_widget_in_gridmap + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget to mutate. + * @param {Object} wgd Current widget grid data (col, row, size_x, size_y). + * @param {Object} new_wgd New widget grid data. + * @return {HTMLElement} Returns instance of gridster Class. + */ + fn.mutate_widget_in_gridmap = function($widget, wgd, new_wgd) { + var old_size_x = wgd.size_x; + var old_size_y = wgd.size_y; + + var old_cells_occupied = this.get_cells_occupied(wgd); + var new_cells_occupied = this.get_cells_occupied(new_wgd); + + var empty_cols = []; + $.each(old_cells_occupied.cols, function(i, col) { + if ($.inArray(col, new_cells_occupied.cols) === -1) { + empty_cols.push(col); + } + }); + + var occupied_cols = []; + $.each(new_cells_occupied.cols, function(i, col) { + if ($.inArray(col, old_cells_occupied.cols) === -1) { + occupied_cols.push(col); + } + }); + + var empty_rows = []; + $.each(old_cells_occupied.rows, function(i, row) { + if ($.inArray(row, new_cells_occupied.rows) === -1) { + empty_rows.push(row); + } + }); + + var occupied_rows = []; + $.each(new_cells_occupied.rows, function(i, row) { + if ($.inArray(row, old_cells_occupied.rows) === -1) { + occupied_rows.push(row); + } + }); + + this.remove_from_gridmap(wgd); + + if (occupied_cols.length) { + var cols_to_empty = [ + new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget + ]; + this.empty_cells.apply(this, cols_to_empty); + } + + if (occupied_rows.length) { + var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget]; + this.empty_cells.apply(this, rows_to_empty); + } + + // not the same that wgd = new_wgd; + wgd.col = new_wgd.col; + wgd.row = new_wgd.row; + wgd.size_x = new_wgd.size_x; + wgd.size_y = new_wgd.size_y; + + this.add_to_gridmap(new_wgd, $widget); + + $widget.removeClass('player-revert'); + + //update coords instance attributes + $widget.data('coords').update({ + width: (new_wgd.size_x * this.options.widget_base_dimensions[0] + + ((new_wgd.size_x - 1) * this.options.widget_margins[0]) * 2), + height: (new_wgd.size_y * this.options.widget_base_dimensions[1] + + ((new_wgd.size_y - 1) * this.options.widget_margins[1]) * 2) + }); + + $widget.attr({ + 'data-col': new_wgd.col, + 'data-row': new_wgd.row, + 'data-sizex': new_wgd.size_x, + 'data-sizey': new_wgd.size_y + }); + + if (empty_cols.length) { + var cols_to_remove_holes = [ + empty_cols[0], new_wgd.row, + empty_cols.length, + Math.min(old_size_y, new_wgd.size_y), + $widget + ]; + + this.remove_empty_cells.apply(this, cols_to_remove_holes); + } + + if (empty_rows.length) { + var rows_to_remove_holes = [ + new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget + ]; + this.remove_empty_cells.apply(this, rows_to_remove_holes); + } + + this.move_widget_up($widget); + + return this; + }; + + + /** + * Move down widgets in cells represented by the arguments col, row, size_x, + * size_y + * + * @method empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} $exclude Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells = function(col, row, size_x, size_y, $exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row - size_y, + size_x: size_x, + size_y: size_y + }); + + $nexts.not($exclude).each($.proxy(function(i, w) { + var wgd = $(w).coords().grid; + if ( !(wgd.row <= (row + size_y - 1))) { return; } + var diff = (row + size_y) - wgd.row; + this.move_widget_down($(w), diff); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Move up widgets below cells represented by the arguments col, row, size_x, + * size_y. + * + * @method remove_empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} exclude Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row, + size_x: size_x, + size_y: size_y + }); + + $nexts.not(exclude).each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), size_y ); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Get the most left column below to add a new widget. + * + * @method next_position + * @param {Number} size_x The nº of rows the widget occupies horizontally. + * @param {Number} size_y The nº of columns the widget occupies vertically. + * @return {Object} Returns a grid coords object representing the future + * widget coords. + */ + fn.next_position = function(size_x, size_y) { + size_x || (size_x = 1); + size_y || (size_y = 1); + var ga = this.gridmap; + var cols_l = ga.length; + var valid_pos = []; + var rows_l; + + for (var c = 1; c < cols_l; c++) { + rows_l = ga[c].length; + for (var r = 1; r <= rows_l; r++) { + var can_move_to = this.can_move_to({ + size_x: size_x, + size_y: size_y + }, c, r); + + if (can_move_to) { + valid_pos.push({ + col: c, + row: r, + size_y: size_y, + size_x: size_x + }); + } + } + } + + if (valid_pos.length) { + return Gridster.sort_by_row_and_col_asc(valid_pos)[0]; + } + return false; + }; + + + /** + * Remove a widget from the grid. + * + * @method remove_widget + * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. + * @param {Boolean|Function} silent If true, widgets below the removed one + * will not move up. If a Function is passed it will be used as callback. + * @param {Function} callback Function executed when the widget is removed. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_widget = function(el, silent, callback) { + var $el = el instanceof $ ? el : $(el); + var wgd = $el.coords().grid; + + // if silent is a function assume it's a callback + if ($.isFunction(silent)) { + callback = silent; + silent = false; + } + + this.cells_occupied_by_placeholder = {}; + this.$widgets = this.$widgets.not($el); + + var $nexts = this.widgets_below($el); + + this.remove_from_gridmap(wgd); + + $el.fadeOut($.proxy(function() { + $el.remove(); + + if (!silent) { + $nexts.each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), wgd.size_y ); + }, this)); + } + + this.set_dom_grid_height(); + + if (callback) { + callback.call(this, el); + } + }, this)); + + return this; + }; + + fn.remove_widget2 = function(el, silent, callback) { + var $el = el instanceof $ ? el : $(el); + var wgd = $el.coords().grid; + + // if silent is a function assume it's a callback + if ($.isFunction(silent)) { + callback = silent; + silent = false; + } + + this.cells_occupied_by_placeholder = {}; + this.$widgets = this.$widgets.not($el); + + var $nexts = this.widgets_below($el); + + this.remove_from_gridmap(wgd); + + $el.fadeOut($.proxy(function() { + //$el.remove(); + + if (!silent) { + $nexts.each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), wgd.size_y ); + }, this)); + } + + this.set_dom_grid_height(); + + if (callback) { + callback.call(this, el); + } + }, this)); + + return this; + }; + + /** + * Remove all widgets from the grid. + * + * @method remove_all_widgets + * @param {Function} callback Function executed for each widget removed. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_all_widgets = function(callback) { + this.$widgets.each($.proxy(function(i, el){ + this.remove_widget(el, true, callback); + }, this)); + + return this; + }; + + + /** + * Returns a serialized array of the widgets in the grid. + * + * @method serialize + * @param {HTMLElement} [$widgets] The collection of jQuery wrapped + * HTMLElements you want to serialize. If no argument is passed all widgets + * will be serialized. + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize = function($widgets) { + $widgets || ($widgets = this.$widgets); + + return $widgets.map($.proxy(function(i, widget) { + var $w = $(widget); + return this.options.serialize_params($w, $w.coords().grid); + }, this)).get(); + }; + + + /** + * Returns a serialized array of the widgets that have changed their + * position. + * + * @method serialize_changed + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize_changed = function() { + return this.serialize(this.$changed); + }; + + + /** + * Convert widgets from DOM elements to "widget grid data" Objects. + * + * @method dom_to_coords + * @param {HTMLElement} $widget The widget to be converted. + */ + fn.dom_to_coords = function($widget) { + return { + 'col': parseInt($widget.attr('data-col'), 10), + 'row': parseInt($widget.attr('data-row'), 10), + 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1, + 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1, + 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false, + 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false, + 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false, + 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false, + 'el': $widget + }; + }; + + + /** + * Creates the grid coords object representing the widget an add it to the + * mapped array of positions. + * + * @method register_widget + * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing + * the widget, or an "widget grid data" Object with (col, row, el ...). + * @return {Boolean} Returns true if the widget final position is different + * than the original. + */ + fn.register_widget = function($el) { + var isDOM = $el instanceof jQuery; + var wgd = isDOM ? this.dom_to_coords($el) : $el; + var posChanged = false; + isDOM || ($el = wgd.el); + + var empty_upper_row = this.can_go_widget_up(wgd); + if (empty_upper_row) { + wgd.row = empty_upper_row; + $el.attr('data-row', empty_upper_row); + this.$el.trigger('gridster:positionchanged', [wgd]); + posChanged = true; + } + + if (this.options.avoid_overlapped_widgets && + !this.can_move_to( + {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) + ) { + $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y)); + $el.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + posChanged = true; + } + + // attach Coord object to player data-coord attribute + $el.data('coords', $el.coords()); + // Extend Coord object with grid position info + $el.data('coords').grid = wgd; + + this.add_to_gridmap(wgd, $el); + + this.options.resize.enabled && this.add_resize_handle($el); + + return posChanged; + }; + + + /** + * Update in the mapped array of positions the value of cells represented by + * the grid coords object passed in the `grid_data` param. + * + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped + * HTMLElement, depends if you want to delete an existing position or add + * a new one. + * @method update_widget_position + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.update_widget_position = function(grid_data, value) { + this.for_each_cell_occupied(grid_data, function(col, row) { + if (!this.gridmap[col]) { return this; } + this.gridmap[col][row] = value; + }); + return this; + }; + + + /** + * Remove a widget from the mapped array of positions. + * + * @method remove_from_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_from_gridmap = function(grid_data) { + return this.update_widget_position(grid_data, false); + }; + + + /** + * Add a widget to the mapped array of positions. + * + * @method add_to_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value The value to set in the specified + * position . + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.add_to_gridmap = function(grid_data, value) { + this.update_widget_position(grid_data, value || grid_data.el); + + if (grid_data.el) { + var $widgets = this.widgets_below(grid_data.el); + $widgets.each($.proxy(function(i, widget) { + this.move_widget_up( $(widget)); + }, this)); + } + }; + + + /** + * Make widgets draggable. + * + * @uses Draggable + * @method draggable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.draggable = function() { + var self = this; + var draggable_options = $.extend(true, {}, this.options.draggable, { + offset_left: this.options.widget_margins[0], + offset_top: this.options.widget_margins[1], + container_width: this.cols * this.min_widget_width, + limit: true, + start: function(event, ui) { + self.$widgets.filter('.player-revert') + .removeClass('player-revert'); + + self.$player = $(this); + self.$helper = $(ui.$helper); + + self.helper = !self.$helper.is(self.$player); + + self.on_start_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstart'); + }, + stop: function(event, ui) { + self.on_stop_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstop'); + }, + drag: throttle(function(event, ui) { + self.on_drag.call(self, event, ui); + self.$el.trigger('gridster:drag'); + }, 60) + }); + + this.drag_api = this.$el.drag(draggable_options); + return this; + }; + + + /** + * Bind resize events to get resize working. + * + * @method resizable + * @return {Class} Returns instance of gridster Class. + */ + fn.resizable = function() { + this.resize_api = this.$el.drag({ + from_resiable: true, + items: '.' + this.options.resize.handle_class, + offset_left: this.options.widget_margins[0], + container_width: this.container_width, + move_element: false, + resize: true, + limit: this.options.autogrow_cols ? false : true, + start: $.proxy(this.on_start_resize, this), + stop: $.proxy(function(event, ui) { + delay($.proxy(function() { + this.on_stop_resize(event, ui); + }, this), 120); + }, this), + drag: throttle($.proxy(this.on_resize, this), 60) + }); + + return this; + }; + + + /** + * Setup things required for resizing. Like build templates for drag handles. + * + * @method setup_resize + * @return {Class} Returns instance of gridster Class. + */ + fn.setup_resize = function() { + this.resize_handle_class = this.options.resize.handle_class; + var axes = this.options.resize.axes; + var handle_tpl = ''; + + this.resize_handle_tpl = $.map(axes, function(type) { + return handle_tpl.replace('{type}', type); + }).join(''); + + if ($.isArray(this.options.draggable.ignore_dragging)) { + this.options.draggable.ignore_dragging.push( + '.' + this.resize_handle_class); + } + + return this; + }; + + + /** + * This function is executed when the player begins to be dragged. + * + * @method on_start_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_start_drag = function(event, ui) { + // hide toolbar at here + $('.tool-container :visible').parent('.tool-container').addClass('element-deagging').css('opacity',0); + this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); + + this.highest_col = this.get_highest_occupied_cell().col; + + this.$player.addClass('player'); + this.player_grid_data = this.$player.coords().grid; + this.placeholder_grid_data = $.extend({}, this.player_grid_data); + + this.set_dom_grid_height(this.$el.height() + + (this.player_grid_data.size_y * this.min_widget_height)); + + this.set_dom_grid_width(this.cols); + + var pgd_sizex = this.player_grid_data.size_x; + var cols_diff = this.cols - this.highest_col; + + if (this.options.autogrow_cols && cols_diff <= pgd_sizex) { + this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1)); + } + + var colliders = this.faux_grid; + var coords = this.$player.data('coords').coords; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.last_cols = []; + this.last_rows = []; + + // see jquery.collision.js + this.collision_api = this.$helper.collision( + colliders, this.options.collision); + + this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', { + 'class': 'preview-holder', + 'data-row': this.$player.attr('data-row'), + 'data-col': this.$player.attr('data-col'), + css: { + width: coords.width, + height: coords.height + } + }).appendTo(this.$el); + + + if (this.options.draggable.start) { + this.options.draggable.start.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player is being dragged. + * + * @method on_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_drag = function(event, ui) { + //break if dragstop has been fired + if (this.$player === null) { + return false; + } + + var abs_offset = { + left: ui.position.left + this.baseX, + top: ui.position.top + this.baseY + }; + + // auto grow cols + if (this.options.autogrow_cols) { + var prcol = this.placeholder_grid_data.col + + this.placeholder_grid_data.size_x - 1; + + // "- 1" due to adding at least 1 column in on_start_drag + if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) { + this.add_faux_cols(1); + this.set_dom_grid_width(this.cols + 1); + this.drag_api.set_limits(this.container_width); + } + + this.collision_api.set_colliders(this.faux_grid); + } + + this.colliders_data = this.collision_api.get_closest_colliders( + abs_offset); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, this.on_stop_overlapping_column); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, this.on_stop_overlapping_row); + + + if (this.helper && this.$player) { + this.$player.css({ + 'left': ui.position.left, + 'top': ui.position.top + }); + } + + if (this.options.draggable.drag) { + this.options.draggable.drag.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player stops being dragged. + * + * @method on_stop_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_stop_drag = function(event, ui) { + setTimeout(function(){ + if($('.tool-container.element-deagging').hasClass('tool-bottom')){ + $('.tool-container.element-deagging').removeClass('tool-bottom').addClass('tool-top'); + } + $('.tool-container.element-deagging').removeClass('element-deagging').css({ + 'opacity':1, + 'top':($('li.dragging').offset().top+ $('#add_posts_content').scrollTop() - 80)+ 'px' + }); + $('li.dragging').removeClass('dragging'); + },300); + + // this.$helper.add(this.$player).add(this.$wrapper) + // .removeClass('dragging'); + + ui.position.left = ui.position.left + this.baseX; + ui.position.top = ui.position.top + this.baseY; + this.colliders_data = this.collision_api.get_closest_colliders( + ui.position); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, + this.on_stop_overlapping_column + ); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, + this.on_stop_overlapping_row + ); + + this.$player.addClass('player-revert').removeClass('player') + .attr({ + 'data-col': this.placeholder_grid_data.col, + 'data-row': this.placeholder_grid_data.row + }).css({ + 'left': '', + 'top': '' + }); + + this.$changed = this.$changed.add(this.$player); + + this.cells_occupied_by_player = this.get_cells_occupied( + this.placeholder_grid_data); + this.set_cells_player_occupies( + this.placeholder_grid_data.col, this.placeholder_grid_data.row); + + this.$player.coords().grid.row = this.placeholder_grid_data.row; + this.$player.coords().grid.col = this.placeholder_grid_data.col; + + if (this.options.draggable.stop) { + this.options.draggable.stop.call(this, event, ui); + } + + this.$preview_holder.remove(); + + this.$player = null; + this.$helper = null; + this.placeholder_grid_data = {}; + this.player_grid_data = {}; + this.cells_occupied_by_placeholder = {}; + this.cells_occupied_by_player = {}; + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (this.options.autogrow_cols) { + this.drag_api.set_limits(this.cols * this.min_widget_width); + } + }; + + + /** + * This function is executed every time a widget starts to be resized. + * + * @method on_start_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_start_resize = function(event, ui) { + this.$resized_widget = ui.$player.closest('.gs-w'); + + if(this.$resized_widget.hasClass('pressed') == false + || this.$resized_widget.hasClass('resizing') == true + ) { + return; + } + + this.resize_coords = this.$resized_widget.coords(); + this.resize_wgd = this.resize_coords.grid; + this.resize_initial_width = this.resize_coords.coords.width; + this.resize_initial_height = this.resize_coords.coords.height; + this.resize_initial_sizex = this.resize_coords.grid.size_x; + this.resize_initial_sizey = this.resize_coords.grid.size_y; + this.resize_initial_col = this.resize_coords.grid.col; + this.resize_last_sizex = this.resize_initial_sizex; + this.resize_last_sizey = this.resize_initial_sizey; + + this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x || + this.options.resize.max_size[0], + this.options.max_cols - this.resize_initial_col + 1); + this.resize_max_size_y = this.resize_wgd.max_size_y || + this.options.resize.max_size[1]; + + this.resize_min_size_x = (this.resize_wgd.min_size_x || + this.options.resize.min_size[0] || 1); + this.resize_min_size_y = (this.resize_wgd.min_size_y || + this.options.resize.min_size[1] || 1); + + this.resize_initial_last_col = this.get_highest_occupied_cell().col; + + this.set_dom_grid_width(this.cols); + + this.resize_dir = { + right: ui.$player.is('.' + this.resize_handle_class + '-x'), + bottom: ui.$player.is('.' + this.resize_handle_class + '-y') + }; + + this.$resized_widget.css({ + 'min-width': this.options.widget_base_dimensions[0], + 'min-height': this.options.widget_base_dimensions[1] + }); + + var nodeName = this.$resized_widget.get(0).tagName; + + this.$resize_preview_holder = $('<' + nodeName + ' />', { + 'class': 'preview-holder resize-preview-holder', + 'data-row': this.$resized_widget.attr('data-row'), + 'data-col': this.$resized_widget.attr('data-col'), + 'css': { + 'width': this.resize_initial_width, + 'height': this.resize_initial_height + } + }).appendTo(this.$el); + //console.log("add resize_preview_holder"); + this.$resized_widget.addClass('resizing'); + + if (this.options.resize.start) { + this.options.resize.start.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resizestart'); + }; + + + /** + * This function is executed every time a widget stops being resized. + * + * @method on_stop_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_stop_resize = function(event, ui) { + if(this.$resized_widget.hasClass('pressed') == false) { + //return; + } + this.$resized_widget + .css({ + 'width': '', + 'height': '' + }); + if (device.platform == 'Android'){ + var x = parseInt(this.$resized_widget.attr('data-sizex'),10) + var width = (x * this.options.widget_base_dimensions[0] + + (x - 1) * (this.options.widget_margins[0] * 2)); + + var y = parseInt(this.$resized_widget.attr('data-sizey'),10) + var height = (y * this.options.widget_base_dimensions[1] + + (y - 1) * (this.options.widget_margins[1] * 2)); + + //console.log("#10 resized_widget.width: width:"+ this.$resized_widget.width()) + this.$resized_widget + .css({ + 'min-width': width, + 'min-height': height + }); + //console.log("#11 resized_widget.width: width:"+ this.$resized_widget.width()) + + if (this.options.resize.resize) { + this.options.resize.resize.call(this, event, ui, this.$resized_widget); + } + //console.log("#12 resized_widget.width: width:"+ this.$resized_widget.width()) + } + + + delay($.proxy(function() { + if (this.$resize_preview_holder == null){ + return; + } + this.$resize_preview_holder + .remove() + .css({ + 'min-width': '', + 'min-height': '' + }); + //console.log("remove resize_preview_holder"); + if (device.platform == 'Android'){ + this.$resized_widget + .css({ + 'min-width': '', + 'min-height': '' + }); + //console.log("#13 resized_widget.width: width:"+ this.$resized_widget.width()) + } + + if (this.options.resize.stop) { + this.options.resize.stop.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resizestop'); + this.$resized_widget.removeClass('resizing') + }, this), 300); + + this.set_dom_grid_width(); + + if (this.options.autogrow_cols) { + this.drag_api.set_limits(this.cols * this.min_widget_width); + } + }; + + + /** + * This function is executed when a widget is being resized. + * + * @method on_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_resize = function(event, ui) { + if(this.$resized_widget.hasClass('pressed') == false) { + return; + } + var rel_x = (ui.pointer.diff_left); + var rel_y = (ui.pointer.diff_top); + var wbd_x = this.options.widget_base_dimensions[0]; + var wbd_y = this.options.widget_base_dimensions[1]; + var margin_x = this.options.widget_margins[0]; + var margin_y = this.options.widget_margins[1]; + var max_size_x = this.resize_max_size_x; + var min_size_x = this.resize_min_size_x; + var max_size_y = this.resize_max_size_y; + var min_size_y = this.resize_min_size_y; + var autogrow = this.options.autogrow_cols; + var width; + var max_width = Infinity; + var max_height = Infinity; + + var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2); + var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2); + + var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x); + var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y); + + var max_cols = (this.container_width / this.min_widget_width) - + this.resize_initial_col + 1; + var limit_width = ((max_cols * this.min_widget_width) - margin_x * 2); + + size_x = Math.max(Math.min(size_x, max_size_x), min_size_x); + size_x = Math.min(max_cols, size_x); + width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x * 2); + max_width = Math.min(width, limit_width); + min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x * 2); + + size_y = Math.max(Math.min(size_y, max_size_y), min_size_y); + max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y * 2); + min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y * 2); + + if (this.resize_dir.right) { + size_y = this.resize_initial_sizey; + } else if (this.resize_dir.bottom) { + size_x = this.resize_initial_sizex; + } + + if (autogrow) { + var last_widget_col = this.resize_initial_col + size_x - 1; + if (autogrow && this.resize_initial_last_col <= last_widget_col) { + this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols)); + + if (this.cols < last_widget_col) { + this.add_faux_cols(last_widget_col - this.cols); + } + } + } + + var css_props = {}; + !this.resize_dir.bottom && (css_props.width = Math.max(Math.min( + this.resize_initial_width + rel_x, max_width), min_width)); + !this.resize_dir.right && (css_props.height = Math.max(Math.min( + this.resize_initial_height + rel_y, max_height), min_height)); + + this.$resized_widget.css(css_props); + + if (size_x !== this.resize_last_sizex || + size_y !== this.resize_last_sizey) { + + this.resize_widget(this.$resized_widget, size_x, size_y); + this.set_dom_grid_width(this.cols); + + this.$resize_preview_holder.css({ + 'width': '', + 'height': '' + }).attr({ + 'data-row': this.$resized_widget.attr('data-row'), + 'data-sizex': size_x, + 'data-sizey': size_y + }); + } + + if (this.options.resize.resize) { + this.options.resize.resize.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resize'); + + this.resize_last_sizex = size_x; + this.resize_last_sizey = size_y; + }; + + + /** + * Executes the callbacks passed as arguments when a column begins to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new column + * begins to be overlapped. The column is passed as first argument. + * @param {Function} stop_callback Function executed when a column stops + * being overlapped. The column is passed as first argument. + * @method on_overlapped_column_change + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_column_change = function(start_callback, stop_callback) { + if (!this.colliders_data.length) { + return this; + } + var cols = this.get_targeted_columns( + this.colliders_data[0].el.data.col); + + var last_n_cols = this.last_cols.length; + var n_cols = cols.length; + var i; + + for (i = 0; i < n_cols; i++) { + if ($.inArray(cols[i], this.last_cols) === -1) { + (start_callback || $.noop).call(this, cols[i]); + } + } + + for (i = 0; i< last_n_cols; i++) { + if ($.inArray(this.last_cols[i], cols) === -1) { + (stop_callback || $.noop).call(this, this.last_cols[i]); + } + } + + this.last_cols = cols; + + return this; + }; + + + /** + * Executes the callbacks passed as arguments when a row starts to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} end_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method on_overlapped_row_change + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_row_change = function(start_callback, end_callback) { + if (!this.colliders_data.length) { + return this; + } + var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); + var last_n_rows = this.last_rows.length; + var n_rows = rows.length; + var i; + + for (i = 0; i < n_rows; i++) { + if ($.inArray(rows[i], this.last_rows) === -1) { + (start_callback || $.noop).call(this, rows[i]); + } + } + + for (i = 0; i < last_n_rows; i++) { + if ($.inArray(this.last_rows[i], rows) === -1) { + (end_callback || $.noop).call(this, this.last_rows[i]); + } + } + + this.last_rows = rows; + }; + + + /** + * Sets the current position of the player + * + * @param {Number} col + * @param {Number} row + * @param {Boolean} no_player + * @method set_player + * @return {object} + */ + fn.set_player = function(col, row, no_player) { + var self = this; + if (!no_player) { + this.empty_cells_player_occupies(); + } + var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; + var to_col = cell.col; + var to_row = row || cell.row; + + this.player_grid_data = { + col: to_col, + row: to_row, + size_y : this.player_grid_data.size_y, + size_x : this.player_grid_data.size_x + }; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + + var $overlapped_widgets = this.get_widgets_overlapped( + this.player_grid_data); + + var constraints = this.widgets_constraints($overlapped_widgets); + + this.manage_movements(constraints.can_go_up, to_col, to_row); + this.manage_movements(constraints.can_not_go_up, to_col, to_row); + + /* if there is not widgets overlapping in the new player position, + * update the new placeholder position. */ + if (!$overlapped_widgets.length) { + var pp = this.can_go_player_up(this.player_grid_data); + if (pp !== false) { + to_row = pp; + } + this.set_placeholder(to_col, to_row); + } + + return { + col: to_col, + row: to_row + }; + }; + + + /** + * See which of the widgets in the $widgets param collection can go to + * a upper row and which not. + * + * @method widgets_contraints + * @param {jQuery} $widgets A jQuery wrapped collection of + * HTMLElements. + * @return {object} Returns a literal Object with two keys: `can_go_up` & + * `can_not_go_up`. Each contains a set of HTMLElements. + */ + fn.widgets_constraints = function($widgets) { + var $widgets_can_go_up = $([]); + var $widgets_can_not_go_up; + var wgd_can_go_up = []; + var wgd_can_not_go_up = []; + + $widgets.each($.proxy(function(i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + if (this.can_go_widget_up(wgd)) { + $widgets_can_go_up = $widgets_can_go_up.add($w); + wgd_can_go_up.push(wgd); + } else { + wgd_can_not_go_up.push(wgd); + } + }, this)); + + $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); + + return { + can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up), + can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up) + }; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method manage_movements + * @param {jQuery} $widgets A jQuery collection of HTMLElements + * representing the widgets you want to move. + * @param {Number} to_col The column to which we want to move the widgets. + * @param {Number} to_row The row to which we want to move the widgets. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.manage_movements = function($widgets, to_col, to_row) { + $.each($widgets, $.proxy(function(i, w) { + var wgd = w; + var $w = wgd.el; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + //target CAN go up + //so move widget up + this.move_widget_to($w, can_go_widget_up); + this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); + + } else { + //target can't go up + var can_go_player_up = this.can_go_player_up( + this.player_grid_data); + + if (!can_go_player_up) { + // target can't go up + // player cant't go up + // so we need to move widget down to a position that dont + // overlaps player + var y = (to_row + this.player_grid_data.size_y) - wgd.row; + + this.move_widget_down($w, y); + this.set_placeholder(to_col, to_row); + } + } + }, this)); + + return this; + }; + + /** + * Determines if there is a widget in the row and col given. Or if the + * HTMLElement passed as first argument is the player. + * + * @method is_player + * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of + * HTMLElements. + * @param {Number} [row] The column to which we want to move the widgets. + * @return {Boolean} Returns true or false. + */ + fn.is_player = function(col_or_el, row) { + if (row && !this.gridmap[col_or_el]) { return false; } + var $w = row ? this.gridmap[col_or_el][row] : col_or_el; + return $w && ($w.is(this.$player) || $w.is(this.$helper)); + }; + + + /** + * Determines if the widget that is being dragged is currently over the row + * and col given. + * + * @method is_player_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_player_in = function(col, row) { + var c = this.cells_occupied_by_player || {}; + return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the row and col given. + * + * @method is_placeholder_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in = function(col, row) { + var c = this.cells_occupied_by_placeholder || {}; + return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the column given. + * + * @method is_placeholder_in_col + * @param {Number} col The column to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in_col = function(col) { + var c = this.cells_occupied_by_placeholder || []; + return $.inArray(col, c.cols) >= 0; + }; + + + /** + * Determines if the cell represented by col and row params is empty. + * + * @method is_empty + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_empty = function(col, row) { + if (typeof this.gridmap[col] !== 'undefined') { + if(typeof this.gridmap[col][row] !== 'undefined' && + this.gridmap[col][row] === false + ) { + return true; + } + return false; + } + return true; + }; + + + /** + * Determines if the cell represented by col and row params is occupied. + * + * @method is_occupied + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_occupied = function(col, row) { + if (!this.gridmap[col]) { + return false; + } + + if (this.gridmap[col][row]) { + return true; + } + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row params. + * + * @method is_widget + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean|HTMLElement} Returns false if there is no widget, + * else returns the jQuery HTMLElement + */ + fn.is_widget = function(col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + return cell; + } + + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row + * params and if this is under the widget that is being dragged. + * + * @method is_widget_under_player + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_widget_under_player = function(col, row) { + if (this.is_widget(col, row)) { + return this.is_player_in(col, row); + } + return false; + }; + + + /** + * Get widgets overlapping with the player or with the object passed + * representing the grid cells. + * + * @method get_widgets_under_player + * @return {HTMLElement} Returns a jQuery collection of HTMLElements + */ + fn.get_widgets_under_player = function(cells) { + cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); + var $widgets = $([]); + + $.each(cells.cols, $.proxy(function(i, col) { + $.each(cells.rows, $.proxy(function(i, row) { + if(this.is_widget(col, row)) { + $widgets = $widgets.add(this.gridmap[col][row]); + } + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * Put placeholder at the row and column specified. + * + * @method set_placeholder + * @param {Number} col The column to which we want to move the + * placeholder. + * @param {Number} row The row to which we want to move the + * placeholder. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_placeholder = function(col, row) { + var phgd = $.extend({}, this.placeholder_grid_data); + var $nexts = this.widgets_below({ + col: phgd.col, + row: phgd.row, + size_y: phgd.size_y, + size_x: phgd.size_x + }); + + // Prevents widgets go out of the grid + var right_col = (col + phgd.size_x - 1); + if (right_col > this.cols) { + col = col - (right_col - col); + } + + var moved_down = this.placeholder_grid_data.row < row; + var changed_column = this.placeholder_grid_data.col !== col; + + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.$preview_holder.attr({ + 'data-row' : row, + 'data-col' : col + }); + + if (moved_down || changed_column) { + $nexts.each($.proxy(function(i, widget) { + this.move_widget_up( + $(widget), this.placeholder_grid_data.col - col + phgd.size_y); + }, this)); + } + + var $widgets_under_ph = this.get_widgets_under_player( + this.cells_occupied_by_placeholder); + + if ($widgets_under_ph.length) { + $widgets_under_ph.each($.proxy(function(i, widget) { + var $w = $(widget); + this.move_widget_down( + $w, row + phgd.size_y - $w.data('coords').grid.row); + }, this)); + } + + }; + + + /** + * Determines whether the player can move to a position above. + * + * @method can_go_player_up + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @return {Number|Boolean} If the player can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_player_up = function(widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + var $widgets_under_player = this.get_widgets_under_player(); + + /* generate an array with columns as index and array with upper rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = this.gridmap[tcol]; + var r = p_bottom_row + 1; + upper_rows[tcol] = []; + + while (--r > 0) { + if (this.is_empty(tcol, r) || this.is_player(tcol, r) || + this.is_widget(tcol, r) && + grid_col[r].is($widgets_under_player) + ) { + upper_rows[tcol].push(r); + min_row = r < min_row ? r : min_row; + } else { + break; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(function(a, b) { + return a - b; + }); + }); + + if (!result) { return false; } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Determines whether a widget can move to a position above. + * + * @method can_go_widget_up + * @param {Object} widget_grid_data The actual grid coords object of the + * widget we want to check. + * @return {Number|Boolean} If the widget can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_widget_up = function(widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + + /* generate an array with columns as index and array with topmost rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = this.gridmap[tcol]; + upper_rows[tcol] = []; + + var r = p_bottom_row + 1; + // iterate over each row + while (--r > 0) { + if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { + if (!grid_col[r].is(widget_grid_data.el)) { + break; + } + } + + if (!this.is_player(tcol, r) && + !this.is_placeholder_in(tcol, r) && + !this.is_player_in(tcol, r)) { + upper_rows[tcol].push(r); + } + + if (r < min_row) { + min_row = r; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(function(a, b) { + return a - b; + }); + }); + + if (!result) { return false; } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Search a valid row for the widget represented by `widget_grid_data' in + * the `upper_rows` array. Iteration starts from row specified in `min_row`. + * + * @method get_valid_rows + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @param {Array} upper_rows An array with columns as index and arrays + * of valid rows as values. + * @param {Number} min_row The upper row from which the iteration will start. + * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` + * for the widget in question. + */ + fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) { + var p_top_row = widget_grid_data.row; + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var size_y = widget_grid_data.size_y; + var r = min_row - 1; + var valid_rows = []; + + while (++r <= p_bottom_row ) { + var common = true; + $.each(upper_rows, function(col, rows) { + if ($.isArray(rows) && $.inArray(r, rows) === -1) { + common = false; + } + }); + + if (common === true) { + valid_rows.push(r); + if (valid_rows.length === size_y) { + break; + } + } + } + + var new_row = false; + if (size_y === 1) { + if (valid_rows[0] !== p_top_row) { + new_row = valid_rows[0] || false; + } + } else { + if (valid_rows[0] !== p_top_row) { + new_row = this.get_consecutive_numbers_index( + valid_rows, size_y); + } + } + + return new_row; + }; + + + fn.get_consecutive_numbers_index = function(arr, size_y) { + var max = arr.length; + var result = []; + var first = true; + var prev = -1; // or null? + + for (var i=0; i < max; i++) { + if (first || arr[i] === prev + 1) { + result.push(i); + if (result.length === size_y) { + break; + } + first = false; + } else { + result = []; + first = true; + } + + prev = arr[i]; + } + + return result.length >= size_y ? arr[result[0]] : false; + }; + + + /** + * Get widgets overlapping with the player. + * + * @method get_widgets_overlapped + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.get_widgets_overlapped = function() { + var $w; + var $widgets = $([]); + var used = []; + var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); + rows_from_bottom.reverse(); + + $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) { + $.each(rows_from_bottom, $.proxy(function(i, row) { + // if there is a widget in the player position + if (!this.gridmap[col]) { return true; } //next iteration + var $w = this.gridmap[col][row]; + if (this.is_occupied(col, row) && !this.is_player($w) && + $.inArray($w, used) === -1 + ) { + $widgets = $widgets.add($w); + used.push($w); + } + + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * This callback is executed when the player begins to collide with a column. + * + * @method on_start_overlapping_column + * @param {Number} col The collided column. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_column = function(col) { + this.set_player(col, false); + }; + + + /** + * A callback executed when the player begins to collide with a row. + * + * @method on_start_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_row = function(row) { + this.set_player(false, row); + }; + + + /** + * A callback executed when the the player ends to collide with a column. + * + * @method on_stop_overlapping_column + * @param {Number} col The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_column = function(col) { + this.set_player(col, false); + + var self = this; + this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], + function(tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + }; + + + /** + * This callback is executed when the player ends to collide with a row. + * + * @method on_stop_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_row = function(row) { + this.set_player(false, row); + + var self = this; + var cols = this.cells_occupied_by_player.cols; + for (var c = 0, cl = cols.length; c < cl; c++) { + this.for_each_widget_below(cols[c], row, function(tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + }; + + + /** + * Move a widget to a specific row. The cell or cells must be empty. + * If the widget has widgets below, all of these widgets will be moved also + * if they can. + * + * @method move_widget_to + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_to = function($widget, row) { + var self = this; + var widget_grid_data = $widget.coords().grid; + var diff = row - widget_grid_data.row; + var $next_widgets = this.widgets_below($widget); + + var can_move_to_new_cell = this.can_move_to( + widget_grid_data, widget_grid_data.col, row, $widget); + + if (can_move_to_new_cell === false) { + return false; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + this.$changed = this.$changed.add($widget); + + + $next_widgets.each(function(i, widget) { + var $w = $(widget); + var wgd = $w.coords().grid; + var can_go_up = self.can_go_widget_up(wgd); + if (can_go_up && can_go_up !== wgd.row) { + self.move_widget_to($w, can_go_up); + } + }); + + return this; + }; + + + /** + * Move up the specified widget and all below it. + * + * @method move_widget_up + * @param {HTMLElement} $widget The widget you want to move. + * @param {Number} [y_units] The number of cells that the widget has to move. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_up = function($widget, y_units) { + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + var can_go_up = true; + y_units || (y_units = 1); + + if (!this.can_go_up($widget)) { return false; } //break; + + this.for_each_column_occupied(el_grid_data, function(col) { + // can_go_up + if ($.inArray($widget, moved) === -1) { + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row - y_units; + next_row = this.can_go_up_to_row( + widget_grid_data, col, next_row); + + if (!next_row) { + return true; + } + + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = next_row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + + $next_widgets.each($.proxy(function(i, widget) { + this.move_widget_up($(widget), y_units); + }, this)); + } + }); + + }; + + + /** + * Move down the specified widget and all below it. + * + * @method move_widget_down + * @param {jQuery} $widget The jQuery object representing the widget + * you want to move. + * @param {Number} y_units The number of cells that the widget has to move. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_down = function($widget, y_units) { + var el_grid_data, actual_row, moved, y_diff; + + if (y_units <= 0) { return false; } + + el_grid_data = $widget.coords().grid; + actual_row = el_grid_data.row; + moved = []; + y_diff = y_units; + + if (!$widget) { return false; } + + if ($.inArray($widget, moved) === -1) { + + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row + y_units; + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + + $next_widgets.each($.proxy(function(i, widget) { + var $w = $(widget); + var wd = $w.coords().grid; + var tmp_y = this.displacement_diff( + wd, widget_grid_data, y_diff); + + if (tmp_y > 0) { + this.move_widget_down($w, tmp_y); + } + }, this)); + + widget_grid_data.row = next_row; + this.update_widget_position(widget_grid_data, $widget); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + } + }; + + + /** + * Check if the widget can move to the specified row, else returns the + * upper row possible. + * + * @method can_go_up_to_row + * @param {Number} widget_grid_data The current grid coords object of the + * widget. + * @param {Number} col The target column. + * @param {Number} row The target row. + * @return {Boolean|Number} Returns the row number if the widget can move + * to the target position, else returns false. + */ + fn.can_go_up_to_row = function(widget_grid_data, col, row) { + var ga = this.gridmap; + var result = true; + var urc = []; // upper_rows_in_columns + var actual_row = widget_grid_data.row; + var r; + + /* generate an array with columns as index and array with + * upper rows empty in the column */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = ga[tcol]; + urc[tcol] = []; + + r = actual_row; + while (r--) { + if (this.is_empty(tcol, r) && + !this.is_placeholder_in(tcol, r) + ) { + urc[tcol].push(r); + } else { + break; + } + } + + if (!urc[tcol].length) { + result = false; + return true; + } + + }); + + if (!result) { return false; } + + /* get common rows starting from upper position in all the columns + * that widget occupies */ + r = row; + for (r = 1; r < actual_row; r++) { + var common = true; + + for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { + if (urc[uc] && $.inArray(r, urc[uc]) === -1) { + common = false; + } + } + + if (common === true) { + result = r; + break; + } + } + + return result; + }; + + + fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) { + var actual_row = widget_grid_data.row; + var diffs = []; + var parent_max_y = parent_bgd.row + parent_bgd.size_y; + + this.for_each_column_occupied(widget_grid_data, function(col) { + var temp_y_units = 0; + + for (var r = parent_max_y; r < actual_row; r++) { + if (this.is_empty(col, r)) { + temp_y_units = temp_y_units + 1; + } + } + + diffs.push(temp_y_units); + }); + + var max_diff = Math.max.apply(Math, diffs); + y_units = (y_units - max_diff); + + return y_units > 0 ? y_units : 0; + }; + + + /** + * Get widgets below a widget. + * + * @method widgets_below + * @param {HTMLElement} $el The jQuery wrapped HTMLElement. + * @return {jQuery} A jQuery collection of HTMLElements. + */ + fn.widgets_below = function($el) { + var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; + var self = this; + var ga = this.gridmap; + var next_row = el_grid_data.row + el_grid_data.size_y - 1; + var $nexts = $([]); + + this.for_each_column_occupied(el_grid_data, function(col) { + self.for_each_widget_below(col, next_row, function(tcol, trow) { + if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { + $nexts = $nexts.add(this); + return true; // break + } + }); + }); + + return Gridster.sort_by_row_asc($nexts); + }; + + + /** + * Update the array of mapped positions with the new player position. + * + * @method set_cells_player_occupies + * @param {Number} col The new player col. + * @param {Number} col The new player row. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_cells_player_occupies = function(col, row) { + this.remove_from_gridmap(this.placeholder_grid_data); + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + this.add_to_gridmap(this.placeholder_grid_data, this.$player); + return this; + }; + + + /** + * Remove from the array of mapped positions the reference to the player. + * + * @method empty_cells_player_occupies + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells_player_occupies = function() { + this.remove_from_gridmap(this.placeholder_grid_data); + return this; + }; + + + fn.can_go_up = function($el) { + var el_grid_data = $el.coords().grid; + var initial_row = el_grid_data.row; + var prev_row = initial_row - 1; + var ga = this.gridmap; + var upper_rows_by_column = []; + + var result = true; + if (initial_row === 1) { return false; } + + this.for_each_column_occupied(el_grid_data, function(col) { + var $w = this.is_widget(col, prev_row); + + if (this.is_occupied(col, prev_row) || + this.is_player(col, prev_row) || + this.is_placeholder_in(col, prev_row) || + this.is_player_in(col, prev_row) + ) { + result = false; + return true; //break + } + }); + + return result; + }; + + + /** + * Check if it's possible to move a widget to a specific col/row. It takes + * into account the dimensions (`size_y` and `size_x` attrs. of the grid + * coords object) the widget occupies. + * + * @method can_move_to + * @param {Object} widget_grid_data The grid coords object that represents + * the widget. + * @param {Object} col The col to check. + * @param {Object} row The row to check. + * @param {Number} [max_row] The max row allowed. + * @return {Boolean} Returns true if all cells are empty, else return false. + */ + fn.can_move_to = function(widget_grid_data, col, row, max_row) { + var ga = this.gridmap; + var $w = widget_grid_data.el; + var future_wd = { + size_y: widget_grid_data.size_y, + size_x: widget_grid_data.size_x, + col: col, + row: row + }; + var result = true; + + //Prevents widgets go out of the grid + var right_col = col + widget_grid_data.size_x - 1; + if (right_col > this.cols) { + return false; + } + + if (max_row && max_row < row + widget_grid_data.size_y - 1) { + return false; + } + + this.for_each_cell_occupied(future_wd, function(tcol, trow) { + var $tw = this.is_widget(tcol, trow); + if ($tw && (!widget_grid_data.el || $tw.is($w))) { + result = false; + } + }); + + return result; + }; + + + /** + * Given the leftmost column returns all columns that are overlapping + * with the player. + * + * @method get_targeted_columns + * @param {Number} [from_col] The leftmost column. + * @return {Array} Returns an array with column numbers. + */ + fn.get_targeted_columns = function(from_col) { + var max = (from_col || this.player_grid_data.col) + + (this.player_grid_data.size_x - 1); + var cols = []; + for (var col = from_col; col <= max; col++) { + cols.push(col); + } + return cols; + }; + + + /** + * Given the upper row returns all rows that are overlapping with the player. + * + * @method get_targeted_rows + * @param {Number} [from_row] The upper row. + * @return {Array} Returns an array with row numbers. + */ + fn.get_targeted_rows = function(from_row) { + var max = (from_row || this.player_grid_data.row) + + (this.player_grid_data.size_y - 1); + var rows = []; + for (var row = from_row; row <= max; row++) { + rows.push(row); + } + return rows; + }; + + /** + * Get all columns and rows that a widget occupies. + * + * @method get_cells_occupied + * @param {Object} el_grid_data The grid coords object of the widget. + * @return {Object} Returns an object like `{ cols: [], rows: []}`. + */ + fn.get_cells_occupied = function(el_grid_data) { + var cells = { cols: [], rows: []}; + var i; + if (arguments[1] instanceof $) { + el_grid_data = arguments[1].coords().grid; + } + + for (i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + cells.cols.push(col); + } + + for (i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + cells.rows.push(row); + } + + return cells; + }; + + + /** + * Iterate over the cells occupied by a widget executing a function for + * each one. + * + * @method for_each_cell_occupied + * @param {Object} el_grid_data The grid coords object that represents the + * widget. + * @param {Function} callback The function to execute on each column + * iteration. Column and row are passed as arguments. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_cell_occupied = function(grid_data, callback) { + this.for_each_column_occupied(grid_data, function(col) { + this.for_each_row_occupied(grid_data, function(row) { + callback.call(this, col, row); + }); + }); + return this; + }; + + + /** + * Iterate over the columns occupied by a widget executing a function for + * each one. + * + * @method for_each_column_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The column number is passed as first argument. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_column_occupied = function(el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + callback.call(this, col, el_grid_data); + } + }; + + + /** + * Iterate over the rows occupied by a widget executing a function for + * each one. + * + * @method for_each_row_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The row number is passed as first argument. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_row_occupied = function(el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + callback.call(this, row, el_grid_data); + } + }; + + + + fn._traversing_widgets = function(type, direction, col, row, callback) { + var ga = this.gridmap; + if (!ga[col]) { return; } + + var cr, max; + var action = type + '/' + direction; + if (arguments[2] instanceof $) { + var el_grid_data = arguments[2].coords().grid; + col = el_grid_data.col; + row = el_grid_data.row; + callback = arguments[3]; + } + var matched = []; + var trow = row; + + + var methods = { + 'for_each/above': function() { + while (trow--) { + if (trow > 0 && this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { break; } + } + } + }, + 'for_each/below': function() { + for (trow = row + 1, max = ga[col].length; trow < max; trow++) { + if (this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { break; } + } + } + } + }; + + if (methods[action]) { + methods[action].call(this); + } + }; + + + /** + * Iterate over each widget above the column and row specified. + * + * @method for_each_widget_above + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery + * wrapped HTMLElement. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_above = function(col, row, callback) { + this._traversing_widgets('for_each', 'above', col, row, callback); + return this; + }; + + + /** + * Iterate over each widget below the column and row specified. + * + * @method for_each_widget_below + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery wrapped + * HTMLElement. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_below = function(col, row, callback) { + this._traversing_widgets('for_each', 'below', col, row, callback); + return this; + }; + + + /** + * Returns the highest occupied cell in the grid. + * + * @method get_highest_occupied_cell + * @return {Object} Returns an object with `col` and `row` numbers. + */ + fn.get_highest_occupied_cell = function() { + var r; + var gm = this.gridmap; + var rl = gm[1].length; + var rows = [], cols = []; + var row_in_col = []; + for (var c = gm.length - 1; c >= 1; c--) { + for (r = rl - 1; r >= 1; r--) { + if (this.is_widget(c, r)) { + rows.push(r); + cols.push(c); + break; + } + } + } + + return { + col: Math.max.apply(Math, cols), + row: Math.max.apply(Math, rows) + }; + }; + + + fn.get_widgets_from = function(col, row) { + var ga = this.gridmap; + var $widgets = $(); + + if (col) { + $widgets = $widgets.add( + this.$widgets.filter(function() { + var tcol = $(this).attr('data-col'); + return (tcol === col || tcol > col); + }) + ); + } + + if (row) { + $widgets = $widgets.add( + this.$widgets.filter(function() { + var trow = $(this).attr('data-row'); + return (trow === row || trow > row); + }) + ); + } + + return $widgets; + }; + + + /** + * Set the current height of the parent grid. + * + * @method set_dom_grid_height + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_height = function(height) { + if (typeof height === 'undefined') { + var r = this.get_highest_occupied_cell().row; + height = r * this.min_widget_height; + } + + this.container_height = height; + this.$el.css('height', this.container_height); + return this; + }; + + /** + * Set the current width of the parent grid. + * + * @method set_dom_grid_width + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_width = function(cols) { + if (typeof cols === 'undefined') { + cols = this.get_highest_occupied_cell().col; + } + + var max_cols = (this.options.autogrow_cols ? this.options.max_cols : + this.cols); + + cols = Math.min(max_cols, Math.max(cols, this.options.min_cols)); + this.container_width = cols * this.min_widget_width; + this.$el.css('width', this.container_width); + return this; + }; + + + /** + * It generates the neccessary styles to position the widgets. + * + * @method generate_stylesheet + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_stylesheet = function(opts) { + var styles = ''; + var max_size_x = this.options.max_size_x || this.cols; + var max_rows = 0; + var max_cols = 0; + var i; + var rules; + + opts || (opts = {}); + opts.cols || (opts.cols = this.cols); + opts.rows || (opts.rows = this.rows); + opts.namespace || (opts.namespace = this.options.namespace); + opts.widget_base_dimensions || + (opts.widget_base_dimensions = this.options.widget_base_dimensions); + opts.widget_margins || + (opts.widget_margins = this.options.widget_margins); + opts.min_widget_width = (opts.widget_margins[0] * 2) + + opts.widget_base_dimensions[0]; + opts.min_widget_height = (opts.widget_margins[1] * 2) + + opts.widget_base_dimensions[1]; + + // don't duplicate stylesheets for the same configuration + var serialized_opts = $.param(opts); + if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { + return false; + } + + this.generated_stylesheets.push(serialized_opts); + Gridster.generated_stylesheets.push(serialized_opts); + + /* generate CSS styles for cols */ + for (i = opts.cols; i >= 0; i--) { + styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' + + ((i * opts.widget_base_dimensions[0]) + + (i * opts.widget_margins[0]) + + ((i + 1) * opts.widget_margins[0])) + 'px; }\n'); + } + + /* generate CSS styles for rows */ + for (i = opts.rows; i >= 0; i--) { + styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' + + ((i * opts.widget_base_dimensions[1]) + + (i * opts.widget_margins[1]) + + ((i + 1) * opts.widget_margins[1]) ) + 'px; }\n'); + } + + for (var y = 1; y <= opts.rows; y++) { + styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + + (y * opts.widget_base_dimensions[1] + + (y - 1) * (opts.widget_margins[1] * 2)) + 'px; }\n'); + } + + for (var x = 1; x <= max_size_x; x++) { + styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + + (x * opts.widget_base_dimensions[0] + + (x - 1) * (opts.widget_margins[0] * 2)) + 'px; }\n'); + } + + this.remove_style_tags(); + + return this.add_style_tag(styles); + }; + + + /** + * Injects the given CSS as string to the head of the document. + * + * @method add_style_tag + * @param {String} css The styles to apply. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_style_tag = function(css) { + var d = document; + var tag = d.createElement('style'); + + d.getElementsByTagName('head')[0].appendChild(tag); + tag.setAttribute('type', 'text/css'); + + if (tag.styleSheet) { + tag.styleSheet.cssText = css; + } else { + tag.appendChild(document.createTextNode(css)); + } + + this.$style_tags = this.$style_tags.add(tag); + + return this; + }; + + + /** + * Remove the style tag with the associated id from the head of the document + * + * @method remove_style_tag + * @return {Object} Returns the instance of the Gridster class. + */ + fn.remove_style_tags = function() { + var all_styles = Gridster.generated_stylesheets; + var ins_styles = this.generated_stylesheets; + + this.$style_tags.remove(); + + Gridster.generated_stylesheets = $.map(all_styles, function(s) { + if ($.inArray(s, ins_styles) === -1) { return s; } + }); + }; + + + /** + * Generates a faux grid to collide with it when a widget is dragged and + * detect row or column that we want to go. + * + * @method generate_faux_grid + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_faux_grid = function(rows, cols) { + this.faux_grid = []; + this.gridmap = []; + var col; + var row; + for (col = cols; col > 0; col--) { + this.gridmap[col] = []; + for (row = rows; row > 0; row--) { + this.add_faux_cell(row, col); + } + } + return this; + }; + + + /** + * Add cell to the faux grid. + * + * @method add_faux_cell + * @param {Number} row The row for the new faux cell. + * @param {Number} col The col for the new faux cell. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cell = function(row, col) { + var coords = $({ + left: this.baseX + ((col - 1) * this.min_widget_width), + top: this.baseY + (row -1) * this.min_widget_height, + width: this.min_widget_width, + height: this.min_widget_height, + col: col, + row: row, + original_col: col, + original_row: row + }).coords(); + + if (!$.isArray(this.gridmap[col])) { + this.gridmap[col] = []; + } + + this.gridmap[col][row] = false; + this.faux_grid.push(coords); + + return this; + }; + + + /** + * Add rows to the faux grid. + * + * @method add_faux_rows + * @param {Number} rows The number of rows you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_rows = function(rows) { + var actual_rows = this.rows; + var max_rows = actual_rows + (rows || 1); + + for (var r = max_rows; r > actual_rows; r--) { + for (var c = this.cols; c >= 1; c--) { + this.add_faux_cell(r, c); + } + } + + this.rows = max_rows; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + /** + * Add cols to the faux grid. + * + * @method add_faux_cols + * @param {Number} cols The number of cols you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cols = function(cols) { + var actual_cols = this.cols; + var max_cols = actual_cols + (cols || 1); + max_cols = Math.min(max_cols, this.options.max_cols); + + for (var c = actual_cols + 1; c <= max_cols; c++) { + for (var r = this.rows; r >= 1; r--) { + this.add_faux_cell(r, c); + } + } + + this.cols = max_cols; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + + /** + * Recalculates the offsets for the faux grid. You need to use it when + * the browser is resized. + * + * @method recalculate_faux_grid + * @return {Object} Returns the instance of the Gridster class. + */ + fn.recalculate_faux_grid = function() { + var aw = this.$wrapper.width(); + this.baseX = ($(window).width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + $.each(this.faux_grid, $.proxy(function(i, coords) { + this.faux_grid[i] = coords.update({ + left: this.baseX + (coords.data.col -1) * this.min_widget_width, + top: this.baseY + (coords.data.row -1) * this.min_widget_height + }); + }, this)); + + return this; + }; + + + /** + * Get all widgets in the DOM and register them. + * + * @method get_widgets_from_DOM + * @return {Object} Returns the instance of the Gridster class. + */ + fn.get_widgets_from_DOM = function() { + var widgets_coords = this.$widgets.map($.proxy(function(i, widget) { + var $w = $(widget); + return this.dom_to_coords($w); + }, this)); + + widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); + + var changes = $(widgets_coords).map($.proxy(function(i, wgd) { + return this.register_widget(wgd) || null; + }, this)); + + if (changes.length) { + this.$el.trigger('gridster:positionschanged'); + } + + return this; + }; + + + /** + * Calculate columns and rows to be set based on the configuration + * parameters, grid dimensions, etc ... + * + * @method generate_grid_and_stylesheet + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_grid_and_stylesheet = function() { + var aw = this.$wrapper.width(); + var max_cols = this.options.max_cols; + + var cols = Math.floor(aw / this.min_widget_width) + + this.options.extra_cols; + + var actual_cols = this.$widgets.map(function() { + return $(this).attr('data-col'); + }).get(); + + //needed to pass tests with phantomjs + actual_cols.length || (actual_cols = [0]); + + var min_cols = Math.max.apply(Math, actual_cols); + + this.cols = Math.max(min_cols, cols, this.options.min_cols); + + if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) { + this.cols = max_cols; + } + + // get all rows that could be occupied by the current widgets + var max_rows = this.options.extra_rows; + this.$widgets.each(function(i, w) { + max_rows += (+$(w).attr('data-sizey')); + }); + + this.rows = Math.max(max_rows, this.options.min_rows); + + this.baseX = ($(window).width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this.generate_faux_grid(this.rows, this.cols); + }; + + /** + * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks + * + * @method destroy + * @param {Boolean} remove If true, remove gridster from DOM. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.destroy = function(remove) { + this.$el.removeData('gridster'); + + // remove bound callback on window resize + $(window).unbind('.gridster'); + + if (this.drag_api) { + this.drag_api.destroy(); + } + + this.remove_style_tags(); + + remove && this.$el.remove(); + + return this; + }; + + + //jQuery adapter + $.fn.gridster = function(options) { + return this.each(function() { + if (! $(this).data('gridster')) { + $(this).data('gridster', new Gridster( this, options )); + } + }); + }; + + return Gridster; + +})); diff --git a/client/lib/jquery.gridster.css b/client/lib/jquery.gridster.css new file mode 100644 index 000000000..5f6c3e343 --- /dev/null +++ b/client/lib/jquery.gridster.css @@ -0,0 +1,121 @@ +/*! gridster.js - v0.5.1 - 2014-03-05 +* http://gridster.net/ +* Copyright (c) 2014 ducksboard; Licensed MIT */ + +.gridster { + position:relative; +} + +.gridster > * { + margin: 0 auto; + -webkit-transition: height .4s, width .4s; + -moz-transition: height .4s, width .4s; + -o-transition: height .4s, width .4s; + -ms-transition: height .4s, width .4s; + transition: height .4s, width .4s; +} + +.gridster .gs-w { + z-index: 2; + position: absolute; +} + +.ready .gs-w:not(.preview-holder) { + -webkit-transition: opacity .3s, left .3s, top .3s; + -moz-transition: opacity .3s, left .3s, top .3s; + -o-transition: opacity .3s, left .3s, top .3s; + transition: opacity .3s, left .3s, top .3s; +} + +.ready .gs-w:not(.preview-holder), +.ready .resize-preview-holder { + -webkit-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -moz-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -o-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; +} + +.gridster .preview-holder { + z-index: 1; + position: absolute; + background-color: #fff; + border-color: #fff; + opacity: 0.3; +} + +.gridster .player-revert { + z-index: 10; + -webkit-transition: left .3s, top .3s!important; + -moz-transition: left .3s, top .3s!important; + -o-transition: left .3s, top .3s!important; + transition: left .3s, top .3s!important; +} + +.gridster .dragging, +.gridster .resizing { + z-index: 10!important; + -webkit-transition: all 0s !important; + -moz-transition: all 0s !important; + -o-transition: all 0s !important; + transition: all 0s !important; +} + + +.gs-resize-handle { + position: absolute; + z-index: 1; +} + +.gs-resize-handle-both { + width: 40px; + height: 40px; + bottom: -8px; + right: -8px; + /*background-image: url('/img/layer_resize.png'); */ + background-position: top left; + background-repeat: no-repeat; + cursor: se-resize; + z-index: 20; +} + +.gs-resize-handle-x { + top: 0; + bottom: 13px; + right: -5px; + width: 30px; + cursor: e-resize; +} + +.gs-resize-handle-y { + left: 0; + right: 13px; + bottom: -5px; + height: 20px; + cursor: s-resize; +} + +.gs-w:hover .gs-resize-handle, +.resizing .gs-resize-handle { + opacity: 1; +} + +.gs-resize-handle, +.gs-w.dragging .gs-resize-handle { + opacity: 0; +} + +.gs-resize-disabled .gs-resize-handle { + display: none!important; +} + +[data-max-sizex="1"] .gs-resize-handle-x, +[data-max-sizey="1"] .gs-resize-handle-y, +[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle { + display: none !important; +} + +/* Uncomment this if you set helper : "clone" in draggable options */ +/*.gridster .player { + opacity:0; +} +*/ diff --git a/client/lib/jquery.linkify.js b/client/lib/jquery.linkify.js index 3032de6a6..cbc129425 100644 --- a/client/lib/jquery.linkify.js +++ b/client/lib/jquery.linkify.js @@ -83,6 +83,8 @@ }); }), $("body").on("click", ".linkified", function() { var $link = $(this), url = $link.attr("href"), isEmail = /^mailto:/i.test(url), target = $link.attr("target"); - return isEmail ? window.location.href = url : window.open(url, target), !1; + cordova.InAppBrowser.open(url, '_system'); + return false; + // return isEmail ? window.location.href = url : window.open(url, target), !1; }); }(jQuery, window, document); \ No newline at end of file diff --git a/client/lib/jquery.scrollevent.js b/client/lib/jquery.scrollevent.js new file mode 100644 index 000000000..6f86398f7 --- /dev/null +++ b/client/lib/jquery.scrollevent.js @@ -0,0 +1,902 @@ +/* +* jQuery Mobile v1.4.5 +* http://jquerymobile.com +* +* Copyright 2010, 2014 jQuery Foundation, Inc. and other contributors +* Released under the MIT license. +* http://jquery.org/license +* +*/ + +(function ( root, doc, factory ) { + if ( typeof define === "function" && define.amd ) { + // AMD. Register as an anonymous module. + define( [ "jquery" ], function ( $ ) { + factory( $, root, doc ); + return $.mobile; + }); + } else { + // Browser globals + factory( root.jQuery, root, doc ); + } +}( this, document, function ( jQuery, window, document, undefined ) { + // throttled resize event + (function( $ ) { + $.event.special.throttledresize = { + setup: function() { + $( this ).bind( "resize", handler ); + }, + teardown: function() { + $( this ).unbind( "resize", handler ); + } + }; + + var throttle = 250, + handler = function() { + curr = ( new Date() ).getTime(); + diff = curr - lastCall; + + if ( diff >= throttle ) { + + lastCall = curr; + $( this ).trigger( "throttledresize" ); + + } else { + + if ( heldCall ) { + clearTimeout( heldCall ); + } + + // Promise a held call will still execute + heldCall = setTimeout( handler, throttle - diff ); + } + }, + lastCall = 0, + heldCall, + curr, + diff; + })( jQuery ); + +// This plugin is an experiment for abstracting away the touch and mouse +// events so that developers don't have to worry about which method of input +// the device their document is loaded on supports. +// +// The idea here is to allow the developer to register listeners for the +// basic mouse events, such as mousedown, mousemove, mouseup, and click, +// and the plugin will take care of registering the correct listeners +// behind the scenes to invoke the listener at the fastest possible time +// for that device, while still retaining the order of event firing in +// the traditional mouse environment, should multiple handlers be registered +// on the same element for different events. +// +// The current version exposes the following virtual events to jQuery bind methods: +// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel" + +(function( $, window, document, undefined ) { + +var dataPropertyName = "virtualMouseBindings", + touchTargetPropertyName = "virtualTouchID", + virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ), + touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ), + mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [], + mouseEventProps = $.event.props.concat( mouseHookProps ), + activeDocHandlers = {}, + resetTimerID = 0, + startX = 0, + startY = 0, + didScroll = false, + clickBlockList = [], + blockMouseTriggers = false, + blockTouchTriggers = false, + eventCaptureSupported = "addEventListener" in document, + $document = $( document ), + nextTouchID = 1, + lastTouchID = 0, threshold, + i; + +$.vmouse = { + moveDistanceThreshold: 10, + clickDistanceThreshold: 10, + resetTimerDuration: 1500 +}; + +function getNativeEvent( event ) { + + while ( event && typeof event.originalEvent !== "undefined" ) { + event = event.originalEvent; + } + return event; +} + +function createVirtualEvent( event, eventType ) { + + var t = event.type, + oe, props, ne, prop, ct, touch, i, j, len; + + event = $.Event( event ); + event.type = eventType; + + oe = event.originalEvent; + props = $.event.props; + + // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280 + // https://github.com/jquery/jquery-mobile/issues/3280 + if ( t.search( /^(mouse|click)/ ) > -1 ) { + props = mouseEventProps; + } + + // copy original event properties over to the new event + // this would happen if we could call $.event.fix instead of $.Event + // but we don't have a way to force an event to be fixed multiple times + if ( oe ) { + for ( i = props.length, prop; i; ) { + prop = props[ --i ]; + event[ prop ] = oe[ prop ]; + } + } + + // make sure that if the mouse and click virtual events are generated + // without a .which one is defined + if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) { + event.which = 1; + } + + if ( t.search(/^touch/) !== -1 ) { + ne = getNativeEvent( oe ); + t = ne.touches; + ct = ne.changedTouches; + touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined ); + + if ( touch ) { + for ( j = 0, len = touchEventProps.length; j < len; j++) { + prop = touchEventProps[ j ]; + event[ prop ] = touch[ prop ]; + } + } + } + + return event; +} + +function getVirtualBindingFlags( element ) { + + var flags = {}, + b, k; + + while ( element ) { + + b = $.data( element, dataPropertyName ); + + for ( k in b ) { + if ( b[ k ] ) { + flags[ k ] = flags.hasVirtualBinding = true; + } + } + element = element.parentNode; + } + return flags; +} + +function getClosestElementWithVirtualBinding( element, eventType ) { + var b; + while ( element ) { + + b = $.data( element, dataPropertyName ); + + if ( b && ( !eventType || b[ eventType ] ) ) { + return element; + } + element = element.parentNode; + } + return null; +} + +function enableTouchBindings() { + blockTouchTriggers = false; +} + +function disableTouchBindings() { + blockTouchTriggers = true; +} + +function enableMouseBindings() { + lastTouchID = 0; + clickBlockList.length = 0; + blockMouseTriggers = false; + + // When mouse bindings are enabled, our + // touch bindings are disabled. + disableTouchBindings(); +} + +function disableMouseBindings() { + // When mouse bindings are disabled, our + // touch bindings are enabled. + enableTouchBindings(); +} + +function startResetTimer() { + clearResetTimer(); + resetTimerID = setTimeout( function() { + resetTimerID = 0; + enableMouseBindings(); + }, $.vmouse.resetTimerDuration ); +} + +function clearResetTimer() { + if ( resetTimerID ) { + clearTimeout( resetTimerID ); + resetTimerID = 0; + } +} + +function triggerVirtualEvent( eventType, event, flags ) { + var ve; + + if ( ( flags && flags[ eventType ] ) || + ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) { + + ve = createVirtualEvent( event, eventType ); + + $( event.target).trigger( ve ); + } + + return ve; +} + +function mouseEventCallback( event ) { + var touchID = $.data( event.target, touchTargetPropertyName ), + ve; + + if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) { + ve = triggerVirtualEvent( "v" + event.type, event ); + if ( ve ) { + if ( ve.isDefaultPrevented() ) { + event.preventDefault(); + } + if ( ve.isPropagationStopped() ) { + event.stopPropagation(); + } + if ( ve.isImmediatePropagationStopped() ) { + event.stopImmediatePropagation(); + } + } + } +} + +function handleTouchStart( event ) { + + var touches = getNativeEvent( event ).touches, + target, flags, t; + + if ( touches && touches.length === 1 ) { + + target = event.target; + flags = getVirtualBindingFlags( target ); + + if ( flags.hasVirtualBinding ) { + + lastTouchID = nextTouchID++; + $.data( target, touchTargetPropertyName, lastTouchID ); + + clearResetTimer(); + + disableMouseBindings(); + didScroll = false; + + t = getNativeEvent( event ).touches[ 0 ]; + startX = t.pageX; + startY = t.pageY; + + triggerVirtualEvent( "vmouseover", event, flags ); + triggerVirtualEvent( "vmousedown", event, flags ); + } + } +} + +function handleScroll( event ) { + if ( blockTouchTriggers ) { + return; + } + + if ( !didScroll ) { + triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) ); + } + + didScroll = true; + startResetTimer(); +} + +function handleTouchMove( event ) { + if ( blockTouchTriggers ) { + return; + } + + var t = getNativeEvent( event ).touches[ 0 ], + didCancel = didScroll, + moveThreshold = $.vmouse.moveDistanceThreshold, + flags = getVirtualBindingFlags( event.target ); + + didScroll = didScroll || + ( Math.abs( t.pageX - startX ) > moveThreshold || + Math.abs( t.pageY - startY ) > moveThreshold ); + + if ( didScroll && !didCancel ) { + triggerVirtualEvent( "vmousecancel", event, flags ); + } + + triggerVirtualEvent( "vmousemove", event, flags ); + startResetTimer(); +} + +function handleTouchEnd( event ) { + if ( blockTouchTriggers ) { + return; + } + + disableTouchBindings(); + + var flags = getVirtualBindingFlags( event.target ), + ve, t; + triggerVirtualEvent( "vmouseup", event, flags ); + + if ( !didScroll ) { + ve = triggerVirtualEvent( "vclick", event, flags ); + if ( ve && ve.isDefaultPrevented() ) { + // The target of the mouse events that follow the touchend + // event don't necessarily match the target used during the + // touch. This means we need to rely on coordinates for blocking + // any click that is generated. + t = getNativeEvent( event ).changedTouches[ 0 ]; + clickBlockList.push({ + touchID: lastTouchID, + x: t.clientX, + y: t.clientY + }); + + // Prevent any mouse events that follow from triggering + // virtual event notifications. + blockMouseTriggers = true; + } + } + triggerVirtualEvent( "vmouseout", event, flags); + didScroll = false; + + startResetTimer(); +} + +function hasVirtualBindings( ele ) { + var bindings = $.data( ele, dataPropertyName ), + k; + + if ( bindings ) { + for ( k in bindings ) { + if ( bindings[ k ] ) { + return true; + } + } + } + return false; +} + +function dummyMouseHandler() {} + +function getSpecialEventObject( eventType ) { + var realType = eventType.substr( 1 ); + + return { + setup: function(/* data, namespace */) { + // If this is the first virtual mouse binding for this element, + // add a bindings object to its data. + + if ( !hasVirtualBindings( this ) ) { + $.data( this, dataPropertyName, {} ); + } + + // If setup is called, we know it is the first binding for this + // eventType, so initialize the count for the eventType to zero. + var bindings = $.data( this, dataPropertyName ); + bindings[ eventType ] = true; + + // If this is the first virtual mouse event for this type, + // register a global handler on the document. + + activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1; + + if ( activeDocHandlers[ eventType ] === 1 ) { + $document.bind( realType, mouseEventCallback ); + } + + // Some browsers, like Opera Mini, won't dispatch mouse/click events + // for elements unless they actually have handlers registered on them. + // To get around this, we register dummy handlers on the elements. + + $( this ).bind( realType, dummyMouseHandler ); + + // For now, if event capture is not supported, we rely on mouse handlers. + if ( eventCaptureSupported ) { + // If this is the first virtual mouse binding for the document, + // register our touchstart handler on the document. + + activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1; + + if ( activeDocHandlers[ "touchstart" ] === 1 ) { + $document.bind( "touchstart", handleTouchStart ) + .bind( "touchend", handleTouchEnd ) + + // On touch platforms, touching the screen and then dragging your finger + // causes the window content to scroll after some distance threshold is + // exceeded. On these platforms, a scroll prevents a click event from being + // dispatched, and on some platforms, even the touchend is suppressed. To + // mimic the suppression of the click event, we need to watch for a scroll + // event. Unfortunately, some platforms like iOS don't dispatch scroll + // events until *AFTER* the user lifts their finger (touchend). This means + // we need to watch both scroll and touchmove events to figure out whether + // or not a scroll happenens before the touchend event is fired. + + .bind( "touchmove", handleTouchMove ) + .bind( "scroll", handleScroll ); + } + } + }, + + teardown: function(/* data, namespace */) { + // If this is the last virtual binding for this eventType, + // remove its global handler from the document. + + --activeDocHandlers[ eventType ]; + + if ( !activeDocHandlers[ eventType ] ) { + $document.unbind( realType, mouseEventCallback ); + } + + if ( eventCaptureSupported ) { + // If this is the last virtual mouse binding in existence, + // remove our document touchstart listener. + + --activeDocHandlers[ "touchstart" ]; + + if ( !activeDocHandlers[ "touchstart" ] ) { + $document.unbind( "touchstart", handleTouchStart ) + .unbind( "touchmove", handleTouchMove ) + .unbind( "touchend", handleTouchEnd ) + .unbind( "scroll", handleScroll ); + } + } + + var $this = $( this ), + bindings = $.data( this, dataPropertyName ); + + // teardown may be called when an element was + // removed from the DOM. If this is the case, + // jQuery core may have already stripped the element + // of any data bindings so we need to check it before + // using it. + if ( bindings ) { + bindings[ eventType ] = false; + } + + // Unregister the dummy event handler. + + $this.unbind( realType, dummyMouseHandler ); + + // If this is the last virtual mouse binding on the + // element, remove the binding data from the element. + + if ( !hasVirtualBindings( this ) ) { + $this.removeData( dataPropertyName ); + } + } + }; +} + +// Expose our custom events to the jQuery bind/unbind mechanism. + +for ( i = 0; i < virtualEventNames.length; i++ ) { + $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] ); +} + +// Add a capture click handler to block clicks. +// Note that we require event capture support for this so if the device +// doesn't support it, we punt for now and rely solely on mouse events. +if ( eventCaptureSupported ) { + document.addEventListener( "click", function( e ) { + var cnt = clickBlockList.length, + target = e.target, + x, y, ele, i, o, touchID; + + if ( cnt ) { + x = e.clientX; + y = e.clientY; + threshold = $.vmouse.clickDistanceThreshold; + + // The idea here is to run through the clickBlockList to see if + // the current click event is in the proximity of one of our + // vclick events that had preventDefault() called on it. If we find + // one, then we block the click. + // + // Why do we have to rely on proximity? + // + // Because the target of the touch event that triggered the vclick + // can be different from the target of the click event synthesized + // by the browser. The target of a mouse/click event that is synthesized + // from a touch event seems to be implementation specific. For example, + // some browsers will fire mouse/click events for a link that is near + // a touch event, even though the target of the touchstart/touchend event + // says the user touched outside the link. Also, it seems that with most + // browsers, the target of the mouse/click event is not calculated until the + // time it is dispatched, so if you replace an element that you touched + // with another element, the target of the mouse/click will be the new + // element underneath that point. + // + // Aside from proximity, we also check to see if the target and any + // of its ancestors were the ones that blocked a click. This is necessary + // because of the strange mouse/click target calculation done in the + // Android 2.1 browser, where if you click on an element, and there is a + // mouse/click handler on one of its ancestors, the target will be the + // innermost child of the touched element, even if that child is no where + // near the point of touch. + + ele = target; + + while ( ele ) { + for ( i = 0; i < cnt; i++ ) { + o = clickBlockList[ i ]; + touchID = 0; + + if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) || + $.data( ele, touchTargetPropertyName ) === o.touchID ) { + // XXX: We may want to consider removing matches from the block list + // instead of waiting for the reset timer to fire. + e.preventDefault(); + e.stopPropagation(); + return; + } + } + ele = ele.parentNode; + } + } + }, true); +} +})( jQuery, window, document ); + +(function( $ ) { + $.mobile = {}; +}( jQuery )); + + (function( $, undefined ) { + var support = { + touch: "ontouchend" in document + }; + + $.mobile.support = $.mobile.support || {}; + $.extend( $.support, support ); + $.extend( $.mobile.support, support ); + }( jQuery )); + + +(function( $, window, undefined ) { + var $document = $( document ), + supportTouch = $.mobile.support.touch, + scrollEvent = "touchmove scroll", + touchStartEvent = supportTouch ? "touchstart" : "mousedown", + touchStopEvent = supportTouch ? "touchend" : "mouseup", + touchMoveEvent = supportTouch ? "touchmove" : "mousemove"; + + // setup new event shortcuts + $.each( ( "touchstart touchmove touchend " + + "tap taphold " + + "swipe swipeleft swiperight " + + "scrollstart scrollstop" ).split( " " ), function( i, name ) { + + $.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + // jQuery < 1.8 + if ( $.attrFn ) { + $.attrFn[ name ] = true; + } + }); + + function triggerCustomEvent( obj, eventType, event, bubble ) { + var originalType = event.type; + event.type = eventType; + if ( bubble ) { + $.event.trigger( event, undefined, obj ); + } else { + $.event.dispatch.call( obj, event ); + } + event.type = originalType; + } + + // also handles scrollstop + $.event.special.scrollstart = { + + enabled: true, + setup: function() { + + var thisObject = this, + $this = $( thisObject ), + scrolling, + timer; + + function trigger( event, state ) { + scrolling = state; + triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event ); + } + + // iPhone triggers scroll after a small delay; use touchmove instead + $this.bind( scrollEvent, function( event ) { + + if ( !$.event.special.scrollstart.enabled ) { + return; + } + + if ( !scrolling ) { + trigger( event, true ); + } + + clearTimeout( timer ); + timer = setTimeout( function() { + trigger( event, false ); + }, 50 ); + }); + }, + teardown: function() { + $( this ).unbind( scrollEvent ); + } + }; + + // also handles taphold + $.event.special.tap = { + tapholdThreshold: 750, + emitTapOnTaphold: true, + setup: function() { + var thisObject = this, + $this = $( thisObject ), + isTaphold = false; + + $this.bind( "vmousedown", function( event ) { + isTaphold = false; + if ( event.which && event.which !== 1 ) { + return false; + } + + var origTarget = event.target, + timer; + + function clearTapTimer() { + clearTimeout( timer ); + } + + function clearTapHandlers() { + clearTapTimer(); + + $this.unbind( "vclick", clickHandler ) + .unbind( "vmouseup", clearTapTimer ); + $document.unbind( "vmousecancel", clearTapHandlers ); + } + + function clickHandler( event ) { + clearTapHandlers(); + + // ONLY trigger a 'tap' event if the start target is + // the same as the stop target. + if ( !isTaphold && origTarget === event.target ) { + triggerCustomEvent( thisObject, "tap", event ); + } else if ( isTaphold ) { + event.preventDefault(); + } + } + + $this.bind( "vmouseup", clearTapTimer ) + .bind( "vclick", clickHandler ); + $document.bind( "vmousecancel", clearTapHandlers ); + + timer = setTimeout( function() { + if ( !$.event.special.tap.emitTapOnTaphold ) { + isTaphold = true; + } + triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) ); + }, $.event.special.tap.tapholdThreshold ); + }); + }, + teardown: function() { + $( this ).unbind( "vmousedown" ).unbind( "vclick" ).unbind( "vmouseup" ); + $document.unbind( "vmousecancel" ); + } + }; + + // Also handles swipeleft, swiperight + $.event.special.swipe = { + + // More than this horizontal displacement, and we will suppress scrolling. + scrollSupressionThreshold: 30, + + // More time than this, and it isn't a swipe. + durationThreshold: 1000, + + // Swipe horizontal displacement must be more than this. + horizontalDistanceThreshold: 30, + + // Swipe vertical displacement must be less than this. + verticalDistanceThreshold: 30, + + getLocation: function ( event ) { + var winPageX = window.pageXOffset, + winPageY = window.pageYOffset, + x = event.clientX, + y = event.clientY; + + if ( event.pageY === 0 && Math.floor( y ) > Math.floor( event.pageY ) || + event.pageX === 0 && Math.floor( x ) > Math.floor( event.pageX ) ) { + + // iOS4 clientX/clientY have the value that should have been + // in pageX/pageY. While pageX/page/ have the value 0 + x = x - winPageX; + y = y - winPageY; + } else if ( y < ( event.pageY - winPageY) || x < ( event.pageX - winPageX ) ) { + + // Some Android browsers have totally bogus values for clientX/Y + // when scrolling/zooming a page. Detectable since clientX/clientY + // should never be smaller than pageX/pageY minus page scroll + x = event.pageX - winPageX; + y = event.pageY - winPageY; + } + + return { + x: x, + y: y + }; + }, + + start: function( event ) { + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : event, + location = $.event.special.swipe.getLocation( data ); + return { + time: ( new Date() ).getTime(), + coords: [ location.x, location.y ], + origin: $( event.target ) + }; + }, + + stop: function( event ) { + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : event, + location = $.event.special.swipe.getLocation( data ); + return { + time: ( new Date() ).getTime(), + coords: [ location.x, location.y ] + }; + }, + + handleSwipe: function( start, stop, thisObject, origTarget ) { + if ( stop.time - start.time < $.event.special.swipe.durationThreshold && + Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && + Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { + var direction = start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight"; + + triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop }), true ); + triggerCustomEvent( thisObject, direction,$.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ), true ); + return true; + } + return false; + + }, + + // This serves as a flag to ensure that at most one swipe event event is + // in work at any given time + eventInProgress: false, + + setup: function() { + var events, + thisObject = this, + $this = $( thisObject ), + context = {}; + + // Retrieve the events data for this element and add the swipe context + events = $.data( this, "mobile-events" ); + if ( !events ) { + events = { length: 0 }; + $.data( this, "mobile-events", events ); + } + events.length++; + events.swipe = context; + + context.start = function( event ) { + + // Bail if we're already working on a swipe event + if ( $.event.special.swipe.eventInProgress ) { + return; + } + $.event.special.swipe.eventInProgress = true; + + var stop, + start = $.event.special.swipe.start( event ), + origTarget = event.target, + emitted = false; + + context.move = function( event ) { + if ( !start || event.isDefaultPrevented() ) { + return; + } + + stop = $.event.special.swipe.stop( event ); + if ( !emitted ) { + emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget ); + if ( emitted ) { + + // Reset the context to make way for the next swipe event + $.event.special.swipe.eventInProgress = false; + } + } + // prevent scrolling + if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { + event.preventDefault(); + } + }; + + context.stop = function() { + emitted = true; + + // Reset the context to make way for the next swipe event + $.event.special.swipe.eventInProgress = false; + $document.off( touchMoveEvent, context.move ); + context.move = null; + }; + + $document.on( touchMoveEvent, context.move ) + .one( touchStopEvent, context.stop ); + }; + $this.on( touchStartEvent, context.start ); + }, + + teardown: function() { + var events, context; + + events = $.data( this, "mobile-events" ); + if ( events ) { + context = events.swipe; + delete events.swipe; + events.length--; + if ( events.length === 0 ) { + $.removeData( this, "mobile-events" ); + } + } + + if ( context ) { + if ( context.start ) { + $( this ).off( touchStartEvent, context.start ); + } + if ( context.move ) { + $document.off( touchMoveEvent, context.move ); + } + if ( context.stop ) { + $document.off( touchStopEvent, context.stop ); + } + } + } + }; + $.each({ + scrollstop: "scrollstart", + taphold: "tap", + swipeleft: "swipe.left", + swiperight: "swipe.right" + }, function( event, sourceEvent ) { + + $.event.special[ event ] = { + setup: function() { + $( this ).bind( sourceEvent, $.noop ); + }, + teardown: function() { + $( this ).unbind( sourceEvent ); + } + }; + }); + +})( jQuery, this ); + + +})); diff --git a/client/lib/jquery.swipebox.1.3.0.2.js b/client/lib/jquery.swipebox.1.3.0.2.js index e55ccfa6e..760075f32 100644 --- a/client/lib/jquery.swipebox.1.3.0.2.js +++ b/client/lib/jquery.swipebox.1.3.0.2.js @@ -328,7 +328,10 @@ $( this ).addClass( 'touching' ); //scale - var img_style = $('#swipebox-slider .current img')[0].style['-webkit-transform']; + var $swipebox_slider = $('#swipebox-slider .current img'); + var img_style = null;//$('#swipebox-slider .current img')[0].style['-webkit-transform']; + if($swipebox_slider.length > 0 && $swipebox_slider[0].style) + img_style = $swipebox_slider[0].style['-webkit-transform']; if(img_style && img_style != '' && img_style != 'scale(1)'){ var transform_style = $('#swipebox-slider .current .img-box')[0].style['-webkit-transform']; if(transform_style != ''){ @@ -386,7 +389,10 @@ endCoords = event.originalEvent.targetTouches[0]; //scale - var img_style = $('#swipebox-slider .current img')[0].style['-webkit-transform']; + var $swipebox_slider = $('#swipebox-slider .current img'); + var img_style = null;//$('#swipebox-slider .current img')[0].style['-webkit-transform']; + if($swipebox_slider.length > 0 && $swipebox_slider[0].style) + img_style = $swipebox_slider[0].style['-webkit-transform']; if(img_style && img_style != '' && img_style != 'scale(1)'){ var pageX = event.originalEvent.targetTouches[0].pageX - scaleCoords.pageX + scaleCoords.x; var pageY = event.originalEvent.targetTouches[0].pageY - scaleCoords.pageY + scaleCoords.y; @@ -489,7 +495,10 @@ event.preventDefault(); event.stopPropagation(); - var img_style = $('#swipebox-slider .current img')[0].style['-webkit-transform']; + var $swipebox_slider = $('#swipebox-slider .current img'); + var img_style = null;//$('#swipebox-slider .current img')[0].style['-webkit-transform']; + if($swipebox_slider.length > 0 && $swipebox_slider[0].style) + img_style = $swipebox_slider[0].style['-webkit-transform']; if(img_style && img_style != '' && img_style != 'scale(1)'){ //console.log(img_style.split('(')[1].split(')')[0]); var scale = parseInt(img_style.split('(')[1].split(')')[0]); @@ -1086,11 +1095,40 @@ */ loadMedia : function ( src, callback ) { if ( ! this.isVideo( src ) ) { - var img = $( '' ).on( 'load', function() { - callback.call( img ); - } ); + var $img = $( '' ); + var timeInterval = null; + var isReload = false; + + $img.error(function(){ + isReload = false; + if(timeInterval === null){ + timeInterval = setInterval(function(){ + if($('#swipebox-overlay').length <= 0){ + clearInterval(timeInterval); + return timeInterval = null; + } + + if(isReload) + return; + + $img.attr( 'src', src ); + console.log('reload image:', src); + isReload = true; + }, 1000); + } + }); + $img.on( 'load', function(){ + if(timeInterval != null){ + clearInterval(timeInterval); + timeInterval = null; + } + callback.call( $img ); + }); + // var img = $( '' ).on( 'load', function() { + // callback.call( img ); + // } ); - img.attr( 'src', src ); + $img.attr( 'src', src ); } }, @@ -1196,4 +1234,4 @@ }; -}( window, document, jQuery ) ); +}( window, document, jQuery ) ); \ No newline at end of file diff --git a/client/lib/jquery.textarea_autosize.0.4.1.js b/client/lib/jquery.textarea_autosize.0.4.1.js new file mode 100644 index 000000000..2801b379c --- /dev/null +++ b/client/lib/jquery.textarea_autosize.0.4.1.js @@ -0,0 +1,54 @@ +/*! + * jQuery Textarea AutoSize plugin + * Author: Javier Julio + * Licensed under the MIT license + */ +;(function ($, window, document, undefined) { + + var pluginName = "textareaAutoSize"; + var pluginDataName = "plugin_" + pluginName; + + var containsText = function (value) { + return (value.replace(/\s/g, '').length > 0); + }; + + function Plugin(element, options) { + this.element = element; + this.$element = $(element); + this.init(); + } + + Plugin.prototype = { + init: function() { + var height = this.$element.outerHeight(); + var diff = parseInt(this.$element.css('paddingBottom')) + + parseInt(this.$element.css('paddingTop')) || 0; + + if (containsText(this.element.value)) { + this.$element.height(this.element.scrollHeight - diff); + } + + // keyup is required for IE to properly reset height when deleting text + this.$element.on('input keyup', function(event) { + var $window = $(window); + var currentScrollPosition = $window.scrollTop(); + + $(this) + .height(0) + .height(this.scrollHeight - diff); + + $window.scrollTop(currentScrollPosition); + }); + } + }; + + $.fn[pluginName] = function (options) { + this.each(function() { + if (!$.data(this, pluginDataName)) { + $.data(this, pluginDataName, new Plugin(this, options)); + } + }); + return this; + }; + +})(jQuery, window, document); diff --git a/client/lib/jquery.toolbar.js b/client/lib/jquery.toolbar.js index 9e400c516..b63fd1a04 100755 --- a/client/lib/jquery.toolbar.js +++ b/client/lib/jquery.toolbar.js @@ -265,7 +265,8 @@ if ( typeof Object.create !== 'function' ) { } } self.$elem.addClass('pressed').trigger('beSelected'); - //console.log("click toobar pressed :" + self.$elem.attr("id") + " visible:" + self.toolbar.is(":visible")); +// console.log("click toobar pressed :" + self.$elem.attr("id") + " visible:" + self.toolbar.is(":visible")); + $("#"+self.$elem.attr("id")+"TextArea").attr("placeholder", "点击笔添加文字") self.calculatePosition(); switch(self.options.position) { diff --git a/client/lib/jquery.toolbars.css b/client/lib/jquery.toolbars.css index 72c1ce223..65a352d53 100755 --- a/client/lib/jquery.toolbars.css +++ b/client/lib/jquery.toolbars.css @@ -13,7 +13,7 @@ } .tool-container {background:#00c4ff;border:none;} -.tool-item i{width: 100%;margin-left:auto;margin-right: auto ;margin-top:auto;margin-bottom:auto ;color: white;font-size:150%;} +.tool-item i{width: 100%;margin-left:auto;margin-right: auto ;margin-top:5px;margin-bottom:auto ;color: white;font-size:150%;} .tool-container.tool-top, .tool-container.tool-bottom, .tool-container.tool-center,{ height: 34px; diff --git a/client/lib/pinyin.js b/client/lib/pinyin.js new file mode 100644 index 000000000..ee0cd18c7 --- /dev/null +++ b/client/lib/pinyin.js @@ -0,0 +1,56 @@ +// 汉字拼音首字母列表 本列表包含了20902个汉字,用于配合 ToChineseSpell +//函数使用,本表收录的字符的Unicode编码范围为19968至40869, XDesigner 整理 +var strChineseFirstPY = "YDYQSXMWZSSXJBYMGCCZQPSSQBYCDSCDQLDYLYBSSJGYZZJJFKCCLZDHWDWZJLJPFYYNWJJTMYHZWZHFLZPPQHGSCYYYNJQYXXGJHHSDSJNKKTMOMLCRXYPSNQSECCQZGGLLYJLMYZZSECYKYYHQWJSSGGYXYZYJWWKDJHYCHMYXJTLXJYQBYXZLDWRDJRWYSRLDZJPCBZJJBRCFTLECZSTZFXXZHTRQHYBDLYCZSSYMMRFMYQZPWWJJYFCRWFDFZQPYDDWYXKYJAWJFFXYPSFTZYHHYZYSWCJYXSCLCXXWZZXNBGNNXBXLZSZSBSGPYSYZDHMDZBQBZCWDZZYYTZHBTSYYBZGNTNXQYWQSKBPHHLXGYBFMJEBJHHGQTJCYSXSTKZHLYCKGLYSMZXYALMELDCCXGZYRJXSDLTYZCQKCNNJWHJTZZCQLJSTSTBNXBTYXCEQXGKWJYFLZQLYHYXSPSFXLMPBYSXXXYDJCZYLLLSJXFHJXPJBTFFYABYXBHZZBJYZLWLCZGGBTSSMDTJZXPTHYQTGLJSCQFZKJZJQNLZWLSLHDZBWJNCJZYZSQQYCQYRZCJJWYBRTWPYFTWEXCSKDZCTBZHYZZYYJXZCFFZZMJYXXSDZZOTTBZLQWFCKSZSXFYRLNYJMBDTHJXSQQCCSBXYYTSYFBXDZTGBCNSLCYZZPSAZYZZSCJCSHZQYDXLBPJLLMQXTYDZXSQJTZPXLCGLQTZWJBHCTSYJSFXYEJJTLBGXSXJMYJQQPFZASYJNTYDJXKJCDJSZCBARTDCLYJQMWNQNCLLLKBYBZZSYHQQLTWLCCXTXLLZNTYLNEWYZYXCZXXGRKRMTCNDNJTSYYSSDQDGHSDBJGHRWRQLYBGLXHLGTGXBQJDZPYJSJYJCTMRNYMGRZJCZGJMZMGXMPRYXKJNYMSGMZJYMKMFXMLDTGFBHCJHKYLPFMDXLQJJSMTQGZSJLQDLDGJYCALCMZCSDJLLNXDJFFFFJCZFMZFFPFKHKGDPSXKTACJDHHZDDCRRCFQYJKQCCWJDXHWJLYLLZGCFCQDSMLZPBJJPLSBCJGGDCKKDEZSQCCKJGCGKDJTJDLZYCXKLQSCGJCLTFPCQCZGWPJDQYZJJBYJHSJDZWGFSJGZKQCCZLLPSPKJGQJHZZLJPLGJGJJTHJJYJZCZMLZLYQBGJWMLJKXZDZNJQSYZMLJLLJKYWXMKJLHSKJGBMCLYYMKXJQLBMLLKMDXXKWYXYSLMLPSJQQJQXYXFJTJDXMXXLLCXQBSYJBGWYMBGGBCYXPJYGPEPFGDJGBHBNSQJYZJKJKHXQFGQZKFHYGKHDKLLSDJQXPQYKYBNQSXQNSZSWHBSXWHXWBZZXDMNSJBSBKBBZKLYLXGWXDRWYQZMYWSJQLCJXXJXKJEQXSCYETLZHLYYYSDZPAQYZCMTLSHTZCFYZYXYLJSDCJQAGYSLCQLYYYSHMRQQKLDXZSCSSSYDYCJYSFSJBFRSSZQSBXXPXJYSDRCKGJLGDKZJZBDKTCSYQPYHSTCLDJDHMXMCGXYZHJDDTMHLTXZXYLYMOHYJCLTYFBQQXPFBDFHHTKSQHZYYWCNXXCRWHOWGYJLEGWDQCWGFJYCSNTMYTOLBYGWQWESJPWNMLRYDZSZTXYQPZGCWXHNGPYXSHMYQJXZTDPPBFYHZHTJYFDZWKGKZBLDNTSXHQEEGZZYLZMMZYJZGXZXKHKSTXNXXWYLYAPSTHXDWHZYMPXAGKYDXBHNHXKDPJNMYHYLPMGOCSLNZHKXXLPZZLBMLSFBHHGYGYYGGBHSCYAQTYWLXTZQCEZYDQDQMMHTKLLSZHLSJZWFYHQSWSCWLQAZYNYTLSXTHAZNKZZSZZLAXXZWWCTGQQTDDYZTCCHYQZFLXPSLZYGPZSZNGLNDQTBDLXGTCTAJDKYWNSYZLJHHZZCWNYYZYWMHYCHHYXHJKZWSXHZYXLYSKQYSPSLYZWMYPPKBYGLKZHTYXAXQSYSHXASMCHKDSCRSWJPWXSGZJLWWSCHSJHSQNHCSEGNDAQTBAALZZMSSTDQJCJKTSCJAXPLGGXHHGXXZCXPDMMHLDGTYBYSJMXHMRCPXXJZCKZXSHMLQXXTTHXWZFKHCCZDYTCJYXQHLXDHYPJQXYLSYYDZOZJNYXQEZYSQYAYXWYPDGXDDXSPPYZNDLTWRHXYDXZZJHTCXMCZLHPYYYYMHZLLHNXMYLLLMDCPPXHMXDKYCYRDLTXJCHHZZXZLCCLYLNZSHZJZZLNNRLWHYQSNJHXYNTTTKYJPYCHHYEGKCTTWLGQRLGGTGTYGYHPYHYLQYQGCWYQKPYYYTTTTLHYHLLTYTTSPLKYZXGZWGPYDSSZZDQXSKCQNMJJZZBXYQMJRTFFBTKHZKBXLJJKDXJTLBWFZPPTKQTZTGPDGNTPJYFALQMKGXBDCLZFHZCLLLLADPMXDJHLCCLGYHDZFGYDDGCYYFGYDXKSSEBDHYKDKDKHNAXXYBPBYYHXZQGAFFQYJXDMLJCSQZLLPCHBSXGJYNDYBYQSPZWJLZKSDDTACTBXZDYZYPJZQSJNKKTKNJDJGYYPGTLFYQKASDNTCYHBLWDZHBBYDWJRYGKZYHEYYFJMSDTYFZJJHGCXPLXHLDWXXJKYTCYKSSSMTWCTTQZLPBSZDZWZXGZAGYKTYWXLHLSPBCLLOQMMZSSLCMBJCSZZKYDCZJGQQDSMCYTZQQLWZQZXSSFPTTFQMDDZDSHDTDWFHTDYZJYQJQKYPBDJYYXTLJHDRQXXXHAYDHRJLKLYTWHLLRLLRCXYLBWSRSZZSYMKZZHHKYHXKSMDSYDYCJPBZBSQLFCXXXNXKXWYWSDZYQOGGQMMYHCDZTTFJYYBGSTTTYBYKJDHKYXBELHTYPJQNFXFDYKZHQKZBYJTZBXHFDXKDASWTAWAJLDYJSFHBLDNNTNQJTJNCHXFJSRFWHZFMDRYJYJWZPDJKZYJYMPCYZNYNXFBYTFYFWYGDBNZZZDNYTXZEMMQBSQEHXFZMBMFLZZSRXYMJGSXWZJSPRYDJSJGXHJJGLJJYNZZJXHGXKYMLPYYYCXYTWQZSWHWLYRJLPXSLSXMFSWWKLCTNXNYNPSJSZHDZEPTXMYYWXYYSYWLXJQZQXZDCLEEELMCPJPCLWBXSQHFWWTFFJTNQJHJQDXHWLBYZNFJLALKYYJLDXHHYCSTYYWNRJYXYWTRMDRQHWQCMFJDYZMHMYYXJWMYZQZXTLMRSPWWCHAQBXYGZYPXYYRRCLMPYMGKSJSZYSRMYJSNXTPLNBAPPYPYLXYYZKYNLDZYJZCZNNLMZHHARQMPGWQTZMXXMLLHGDZXYHXKYXYCJMFFYYHJFSBSSQLXXNDYCANNMTCJCYPRRNYTYQNYYMBMSXNDLYLYSLJRLXYSXQMLLYZLZJJJKYZZCSFBZXXMSTBJGNXYZHLXNMCWSCYZYFZLXBRNNNYLBNRTGZQYSATSWRYHYJZMZDHZGZDWYBSSCSKXSYHYTXXGCQGXZZSHYXJSCRHMKKBXCZJYJYMKQHZJFNBHMQHYSNJNZYBKNQMCLGQHWLZNZSWXKHLJHYYBQLBFCDSXDLDSPFZPSKJYZWZXZDDXJSMMEGJSCSSMGCLXXKYYYLNYPWWWGYDKZJGGGZGGSYCKNJWNJPCXBJJTQTJWDSSPJXZXNZXUMELPXFSXTLLXCLJXJJLJZXCTPSWXLYDHLYQRWHSYCSQYYBYAYWJJJQFWQCQQCJQGXALDBZZYJGKGXPLTZYFXJLTPADKYQHPMATLCPDCKBMTXYBHKLENXDLEEGQDYMSAWHZMLJTWYGXLYQZLJEEYYBQQFFNLYXRDSCTGJGXYYNKLLYQKCCTLHJLQMKKZGCYYGLLLJDZGYDHZWXPYSJBZKDZGYZZHYWYFQYTYZSZYEZZLYMHJJHTSMQWYZLKYYWZCSRKQYTLTDXWCTYJKLWSQZWBDCQYNCJSRSZJLKCDCDTLZZZACQQZZDDXYPLXZBQJYLZLLLQDDZQJYJYJZYXNYYYNYJXKXDAZWYRDLJYYYRJLXLLDYXJCYWYWNQCCLDDNYYYNYCKCZHXXCCLGZQJGKWPPCQQJYSBZZXYJSQPXJPZBSBDSFNSFPZXHDWZTDWPPTFLZZBZDMYYPQJRSDZSQZSQXBDGCPZSWDWCSQZGMDHZXMWWFYBPDGPHTMJTHZSMMBGZMBZJCFZWFZBBZMQCFMBDMCJXLGPNJBBXGYHYYJGPTZGZMQBQTCGYXJXLWZKYDPDYMGCFTPFXYZTZXDZXTGKMTYBBCLBJASKYTSSQYYMSZXFJEWLXLLSZBQJJJAKLYLXLYCCTSXMCWFKKKBSXLLLLJYXTYLTJYYTDPJHNHNNKBYQNFQYYZBYYESSESSGDYHFHWTCJBSDZZTFDMXHCNJZYMQWSRYJDZJQPDQBBSTJGGFBKJBXTGQHNGWJXJGDLLTHZHHYYYYYYSXWTYYYCCBDBPYPZYCCZYJPZYWCBDLFWZCWJDXXHYHLHWZZXJTCZLCDPXUJCZZZLYXJJTXPHFXWPYWXZPTDZZBDZCYHJHMLXBQXSBYLRDTGJRRCTTTHYTCZWMXFYTWWZCWJWXJYWCSKYBZSCCTZQNHXNWXXKHKFHTSWOCCJYBCMPZZYKBNNZPBZHHZDLSYDDYTYFJPXYNGFXBYQXCBHXCPSXTYZDMKYSNXSXLHKMZXLYHDHKWHXXSSKQYHHCJYXGLHZXCSNHEKDTGZXQYPKDHEXTYKCNYMYYYPKQYYYKXZLTHJQTBYQHXBMYHSQCKWWYLLHCYYLNNEQXQWMCFBDCCMLJGGXDQKTLXKGNQCDGZJWYJJLYHHQTTTNWCHMXCXWHWSZJYDJCCDBQCDGDNYXZTHCQRXCBHZTQCBXWGQWYYBXHMBYMYQTYEXMQKYAQYRGYZSLFYKKQHYSSQYSHJGJCNXKZYCXSBXYXHYYLSTYCXQTHYSMGSCPMMGCCCCCMTZTASMGQZJHKLOSQYLSWTMXSYQKDZLJQQYPLSYCZTCQQPBBQJZCLPKHQZYYXXDTDDTSJCXFFLLCHQXMJLWCJCXTSPYCXNDTJSHJWXDQQJSKXYAMYLSJHMLALYKXCYYDMNMDQMXMCZNNCYBZKKYFLMCHCMLHXRCJJHSYLNMTJZGZGYWJXSRXCWJGJQHQZDQJDCJJZKJKGDZQGJJYJYLXZXXCDQHHHEYTMHLFSBDJSYYSHFYSTCZQLPBDRFRZTZYKYWHSZYQKWDQZRKMSYNBCRXQBJYFAZPZZEDZCJYWBCJWHYJBQSZYWRYSZPTDKZPFPBNZTKLQYHBBZPNPPTYZZYBQNYDCPJMMCYCQMCYFZZDCMNLFPBPLNGQJTBTTNJZPZBBZNJKLJQYLNBZQHKSJZNGGQSZZKYXSHPZSNBCGZKDDZQANZHJKDRTLZLSWJLJZLYWTJNDJZJHXYAYNCBGTZCSSQMNJPJYTYSWXZFKWJQTKHTZPLBHSNJZSYZBWZZZZLSYLSBJHDWWQPSLMMFBJDWAQYZTCJTBNNWZXQXCDSLQGDSDPDZHJTQQPSWLYYJZLGYXYZLCTCBJTKTYCZJTQKBSJLGMGZDMCSGPYNJZYQYYKNXRPWSZXMTNCSZZYXYBYHYZAXYWQCJTLLCKJJTJHGDXDXYQYZZBYWDLWQCGLZGJGQRQZCZSSBCRPCSKYDZNXJSQGXSSJMYDNSTZTPBDLTKZWXQWQTZEXNQCZGWEZKSSBYBRTSSSLCCGBPSZQSZLCCGLLLZXHZQTHCZMQGYZQZNMCOCSZJMMZSQPJYGQLJYJPPLDXRGZYXCCSXHSHGTZNLZWZKJCXTCFCJXLBMQBCZZWPQDNHXLJCTHYZLGYLNLSZZPCXDSCQQHJQKSXZPBAJYEMSMJTZDXLCJYRYYNWJBNGZZTMJXLTBSLYRZPYLSSCNXPHLLHYLLQQZQLXYMRSYCXZLMMCZLTZSDWTJJLLNZGGQXPFSKYGYGHBFZPDKMWGHCXMSGDXJMCJZDYCABXJDLNBCDQYGSKYDQTXDJJYXMSZQAZDZFSLQXYJSJZYLBTXXWXQQZBJZUFBBLYLWDSLJHXJYZJWTDJCZFQZQZZDZSXZZQLZCDZFJHYSPYMPQZMLPPLFFXJJNZZYLSJEYQZFPFZKSYWJJJHRDJZZXTXXGLGHYDXCSKYSWMMZCWYBAZBJKSHFHJCXMHFQHYXXYZFTSJYZFXYXPZLCHMZMBXHZZSXYFYMNCWDABAZLXKTCSHHXKXJJZJSTHYGXSXYYHHHJWXKZXSSBZZWHHHCWTZZZPJXSNXQQJGZYZYWLLCWXZFXXYXYHXMKYYSWSQMNLNAYCYSPMJKHWCQHYLAJJMZXHMMCNZHBHXCLXTJPLTXYJHDYYLTTXFSZHYXXSJBJYAYRSMXYPLCKDUYHLXRLNLLSTYZYYQYGYHHSCCSMZCTZQXKYQFPYYRPFFLKQUNTSZLLZMWWTCQQYZWTLLMLMPWMBZSSTZRBPDDTLQJJBXZCSRZQQYGWCSXFWZLXCCRSZDZMCYGGDZQSGTJSWLJMYMMZYHFBJDGYXCCPSHXNZCSBSJYJGJMPPWAFFYFNXHYZXZYLREMZGZCYZSSZDLLJCSQFNXZKPTXZGXJJGFMYYYSNBTYLBNLHPFZDCYFBMGQRRSSSZXYSGTZRNYDZZCDGPJAFJFZKNZBLCZSZPSGCYCJSZLMLRSZBZZLDLSLLYSXSQZQLYXZLSKKBRXBRBZCYCXZZZEEYFGKLZLYYHGZSGZLFJHGTGWKRAAJYZKZQTSSHJJXDCYZUYJLZYRZDQQHGJZXSSZBYKJPBFRTJXLLFQWJHYLQTYMBLPZDXTZYGBDHZZRBGXHWNJTJXLKSCFSMWLSDQYSJTXKZSCFWJLBXFTZLLJZLLQBLSQMQQCGCZFPBPHZCZJLPYYGGDTGWDCFCZQYYYQYSSCLXZSKLZZZGFFCQNWGLHQYZJJCZLQZZYJPJZZBPDCCMHJGXDQDGDLZQMFGPSYTSDYFWWDJZJYSXYYCZCYHZWPBYKXRYLYBHKJKSFXTZJMMCKHLLTNYYMSYXYZPYJQYCSYCWMTJJKQYRHLLQXPSGTLYYCLJSCPXJYZFNMLRGJJTYZBXYZMSJYJHHFZQMSYXRSZCWTLRTQZSSTKXGQKGSPTGCZNJSJCQCXHMXGGZTQYDJKZDLBZSXJLHYQGGGTHQSZPYHJHHGYYGKGGCWJZZYLCZLXQSFTGZSLLLMLJSKCTBLLZZSZMMNYTPZSXQHJCJYQXYZXZQZCPSHKZZYSXCDFGMWQRLLQXRFZTLYSTCTMJCXJJXHJNXTNRZTZFQYHQGLLGCXSZSJDJLJCYDSJTLNYXHSZXCGJZYQPYLFHDJSBPCCZHJJJQZJQDYBSSLLCMYTTMQTBHJQNNYGKYRQYQMZGCJKPDCGMYZHQLLSLLCLMHOLZGDYYFZSLJCQZLYLZQJESHNYLLJXGJXLYSYYYXNBZLJSSZCQQCJYLLZLTJYLLZLLBNYLGQCHXYYXOXCXQKYJXXXYKLXSXXYQXCYKQXQCSGYXXYQXYGYTQOHXHXPYXXXULCYEYCHZZCBWQBBWJQZSCSZSSLZYLKDESJZWMYMCYTSDSXXSCJPQQSQYLYYZYCMDJDZYWCBTJSYDJKCYDDJLBDJJSODZYSYXQQYXDHHGQQYQHDYXWGMMMAJDYBBBPPBCMUUPLJZSMTXERXJMHQNUTPJDCBSSMSSSTKJTSSMMTRCPLZSZMLQDSDMJMQPNQDXCFYNBFSDQXYXHYAYKQYDDLQYYYSSZBYDSLNTFQTZQPZMCHDHCZCWFDXTMYQSPHQYYXSRGJCWTJTZZQMGWJJTJHTQJBBHWZPXXHYQFXXQYWYYHYSCDYDHHQMNMTMWCPBSZPPZZGLMZFOLLCFWHMMSJZTTDHZZYFFYTZZGZYSKYJXQYJZQBHMBZZLYGHGFMSHPZFZSNCLPBQSNJXZSLXXFPMTYJYGBXLLDLXPZJYZJYHHZCYWHJYLSJEXFSZZYWXKZJLUYDTMLYMQJPWXYHXSKTQJEZRPXXZHHMHWQPWQLYJJQJJZSZCPHJLCHHNXJLQWZJHBMZYXBDHHYPZLHLHLGFWLCHYYTLHJXCJMSCPXSTKPNHQXSRTYXXTESYJCTLSSLSTDLLLWWYHDHRJZSFGXTSYCZYNYHTDHWJSLHTZDQDJZXXQHGYLTZPHCSQFCLNJTCLZPFSTPDYNYLGMJLLYCQHYSSHCHYLHQYQTMZYPBYWRFQYKQSYSLZDQJMPXYYSSRHZJNYWTQDFZBWWTWWRXCWHGYHXMKMYYYQMSMZHNGCEPMLQQMTCWCTMMPXJPJJHFXYYZSXZHTYBMSTSYJTTQQQYYLHYNPYQZLCYZHZWSMYLKFJXLWGXYPJYTYSYXYMZCKTTWLKSMZSYLMPWLZWXWQZSSAQSYXYRHSSNTSRAPXCPWCMGDXHXZDZYFJHGZTTSBJHGYZSZYSMYCLLLXBTYXHBBZJKSSDMALXHYCFYGMQYPJYCQXJLLLJGSLZGQLYCJCCZOTYXMTMTTLLWTGPXYMZMKLPSZZZXHKQYSXCTYJZYHXSHYXZKXLZWPSQPYHJWPJPWXQQYLXSDHMRSLZZYZWTTCYXYSZZSHBSCCSTPLWSSCJCHNLCGCHSSPHYLHFHHXJSXYLLNYLSZDHZXYLSXLWZYKCLDYAXZCMDDYSPJTQJZLNWQPSSSWCTSTSZLBLNXSMNYYMJQBQHRZWTYYDCHQLXKPZWBGQYBKFCMZWPZLLYYLSZYDWHXPSBCMLJBSCGBHXLQHYRLJXYSWXWXZSLDFHLSLYNJLZYFLYJYCDRJLFSYZFSLLCQYQFGJYHYXZLYLMSTDJCYHBZLLNWLXXYGYYHSMGDHXXHHLZZJZXCZZZCYQZFNGWPYLCPKPYYPMCLQKDGXZGGWQBDXZZKZFBXXLZXJTPJPTTBYTSZZDWSLCHZHSLTYXHQLHYXXXYYZYSWTXZKHLXZXZPYHGCHKCFSYHUTJRLXFJXPTZTWHPLYXFCRHXSHXKYXXYHZQDXQWULHYHMJTBFLKHTXCWHJFWJCFPQRYQXCYYYQYGRPYWSGSUNGWCHKZDXYFLXXHJJBYZWTSXXNCYJJYMSWZJQRMHXZWFQSYLZJZGBHYNSLBGTTCSYBYXXWXYHXYYXNSQYXMQYWRGYQLXBBZLJSYLPSYTJZYHYZAWLRORJMKSCZJXXXYXCHDYXRYXXJDTSQFXLYLTSFFYXLMTYJMJUYYYXLTZCSXQZQHZXLYYXZHDNBRXXXJCTYHLBRLMBRLLAXKYLLLJLYXXLYCRYLCJTGJCMTLZLLCYZZPZPCYAWHJJFYBDYYZSMPCKZDQYQPBPCJPDCYZMDPBCYYDYCNNPLMTMLRMFMMGWYZBSJGYGSMZQQQZTXMKQWGXLLPJGZBQCDJJJFPKJKCXBLJMSWMDTQJXLDLPPBXCWRCQFBFQJCZAHZGMYKPHYYHZYKNDKZMBPJYXPXYHLFPNYYGXJDBKXNXHJMZJXSTRSTLDXSKZYSYBZXJLXYSLBZYSLHXJPFXPQNBYLLJQKYGZMCYZZYMCCSLCLHZFWFWYXZMWSXTYNXJHPYYMCYSPMHYSMYDYSHQYZCHMJJMZCAAGCFJBBHPLYZYLXXSDJGXDHKXXTXXNBHRMLYJSLTXMRHNLXQJXYZLLYSWQGDLBJHDCGJYQYCMHWFMJYBMBYJYJWYMDPWHXQLDYGPDFXXBCGJSPCKRSSYZJMSLBZZJFLJJJLGXZGYXYXLSZQYXBEXYXHGCXBPLDYHWETTWWCJMBTXCHXYQXLLXFLYXLLJLSSFWDPZSMYJCLMWYTCZPCHQEKCQBWLCQYDPLQPPQZQFJQDJHYMMCXTXDRMJWRHXCJZYLQXDYYNHYYHRSLSRSYWWZJYMTLTLLGTQCJZYABTCKZCJYCCQLJZQXALMZYHYWLWDXZXQDLLQSHGPJFJLJHJABCQZDJGTKHSSTCYJLPSWZLXZXRWGLDLZRLZXTGSLLLLZLYXXWGDZYGBDPHZPBRLWSXQBPFDWOFMWHLYPCBJCCLDMBZPBZZLCYQXLDOMZBLZWPDWYYGDSTTHCSQSCCRSSSYSLFYBFNTYJSZDFNDPDHDZZMBBLSLCMYFFGTJJQWFTMTPJWFNLBZCMMJTGBDZLQLPYFHYYMJYLSDCHDZJWJCCTLJCLDTLJJCPDDSQDSSZYBNDBJLGGJZXSXNLYCYBJXQYCBYLZCFZPPGKCXZDZFZTJJFJSJXZBNZYJQTTYJYHTYCZHYMDJXTTMPXSPLZCDWSLSHXYPZGTFMLCJTYCBPMGDKWYCYZCDSZZYHFLYCTYGWHKJYYLSJCXGYWJCBLLCSNDDBTZBSCLYZCZZSSQDLLMQYYHFSLQLLXFTYHABXGWNYWYYPLLSDLDLLBJCYXJZMLHLJDXYYQYTDLLLBUGBFDFBBQJZZMDPJHGCLGMJJPGAEHHBWCQXAXHHHZCHXYPHJAXHLPHJPGPZJQCQZGJJZZUZDMQYYBZZPHYHYBWHAZYJHYKFGDPFQSDLZMLJXKXGALXZDAGLMDGXMWZQYXXDXXPFDMMSSYMPFMDMMKXKSYZYSHDZKXSYSMMZZZMSYDNZZCZXFPLSTMZDNMXCKJMZTYYMZMZZMSXHHDCZJEMXXKLJSTLWLSQLYJZLLZJSSDPPMHNLZJCZYHMXXHGZCJMDHXTKGRMXFWMCGMWKDTKSXQMMMFZZYDKMSCLCMPCGMHSPXQPZDSSLCXKYXTWLWJYAHZJGZQMCSNXYYMMPMLKJXMHLMLQMXCTKZMJQYSZJSYSZHSYJZJCDAJZYBSDQJZGWZQQXFKDMSDJLFWEHKZQKJPEYPZYSZCDWYJFFMZZYLTTDZZEFMZLBNPPLPLPEPSZALLTYLKCKQZKGENQLWAGYXYDPXLHSXQQWQCQXQCLHYXXMLYCCWLYMQYSKGCHLCJNSZKPYZKCQZQLJPDMDZHLASXLBYDWQLWDNBQCRYDDZTJYBKBWSZDXDTNPJDTCTQDFXQQMGNXECLTTBKPWSLCTYQLPWYZZKLPYGZCQQPLLKCCYLPQMZCZQCLJSLQZDJXLDDHPZQDLJJXZQDXYZQKZLJCYQDYJPPYPQYKJYRMPCBYMCXKLLZLLFQPYLLLMBSGLCYSSLRSYSQTMXYXZQZFDZUYSYZTFFMZZSMZQHZSSCCMLYXWTPZGXZJGZGSJSGKDDHTQGGZLLBJDZLCBCHYXYZHZFYWXYZYMSDBZZYJGTSMTFXQYXQSTDGSLNXDLRYZZLRYYLXQHTXSRTZNGZXBNQQZFMYKMZJBZYMKBPNLYZPBLMCNQYZZZSJZHJCTZKHYZZJRDYZHNPXGLFZTLKGJTCTSSYLLGZRZBBQZZKLPKLCZYSSUYXBJFPNJZZXCDWXZYJXZZDJJKGGRSRJKMSMZJLSJYWQSKYHQJSXPJZZZLSNSHRNYPZTWCHKLPSRZLZXYJQXQKYSJYCZTLQZYBBYBWZPQDWWYZCYTJCJXCKCWDKKZXSGKDZXWWYYJQYYTCYTDLLXWKCZKKLCCLZCQQDZLQLCSFQCHQHSFSMQZZLNBJJZBSJHTSZDYSJQJPDLZCDCWJKJZZLPYCGMZWDJJBSJQZSYZYHHXJPBJYDSSXDZNCGLQMBTSFSBPDZDLZNFGFJGFSMPXJQLMBLGQCYYXBQKDJJQYRFKZTJDHCZKLBSDZCFJTPLLJGXHYXZCSSZZXSTJYGKGCKGYOQXJPLZPBPGTGYJZGHZQZZLBJLSQFZGKQQJZGYCZBZQTLDXRJXBSXXPZXHYZYCLWDXJJHXMFDZPFZHQHQMQGKSLYHTYCGFRZGNQXCLPDLBZCSCZQLLJBLHBZCYPZZPPDYMZZSGYHCKCPZJGSLJLNSCDSLDLXBMSTLDDFJMKDJDHZLZXLSZQPQPGJLLYBDSZGQLBZLSLKYYHZTTNTJYQTZZPSZQZTLLJTYYLLQLLQYZQLBDZLSLYYZYMDFSZSNHLXZNCZQZPBWSKRFBSYZMTHBLGJPMCZZLSTLXSHTCSYZLZBLFEQHLXFLCJLYLJQCBZLZJHHSSTBRMHXZHJZCLXFNBGXGTQJCZTMSFZKJMSSNXLJKBHSJXNTNLZDNTLMSJXGZJYJCZXYJYJWRWWQNZTNFJSZPZSHZJFYRDJSFSZJZBJFZQZZHZLXFYSBZQLZSGYFTZDCSZXZJBQMSZKJRHYJZCKMJKHCHGTXKXQGLXPXFXTRTYLXJXHDTSJXHJZJXZWZLCQSBTXWXGXTXXHXFTSDKFJHZYJFJXRZSDLLLTQSQQZQWZXSYQTWGWBZCGZLLYZBCLMQQTZHZXZXLJFRMYZFLXYSQXXJKXRMQDZDMMYYBSQBHGZMWFWXGMXLZPYYTGZYCCDXYZXYWGSYJYZNBHPZJSQSYXSXRTFYZGRHZTXSZZTHCBFCLSYXZLZQMZLMPLMXZJXSFLBYZMYQHXJSXRXSQZZZSSLYFRCZJRCRXHHZXQYDYHXSJJHZCXZBTYNSYSXJBQLPXZQPYMLXZKYXLXCJLCYSXXZZLXDLLLJJYHZXGYJWKJRWYHCPSGNRZLFZWFZZNSXGXFLZSXZZZBFCSYJDBRJKRDHHGXJLJJTGXJXXSTJTJXLYXQFCSGSWMSBCTLQZZWLZZKXJMLTMJYHSDDBXGZHDLBMYJFRZFSGCLYJBPMLYSMSXLSZJQQHJZFXGFQFQBPXZGYYQXGZTCQWYLTLGWSGWHRLFSFGZJMGMGBGTJFSYZZGZYZAFLSSPMLPFLCWBJZCLJJMZLPJJLYMQDMYYYFBGYGYZMLYZDXQYXRQQQHSYYYQXYLJTYXFSFSLLGNQCYHYCWFHCCCFXPYLYPLLZYXXXXXKQHHXSHJZCFZSCZJXCPZWHHHHHAPYLQALPQAFYHXDYLUKMZQGGGDDESRNNZLTZGCHYPPYSQJJHCLLJTOLNJPZLJLHYMHEYDYDSQYCDDHGZUNDZCLZYZLLZNTNYZGSLHSLPJJBDGWXPCDUTJCKLKCLWKLLCASSTKZZDNQNTTLYYZSSYSSZZRYLJQKCQDHHCRXRZYDGRGCWCGZQFFFPPJFZYNAKRGYWYQPQXXFKJTSZZXSWZDDFBBXTBGTZKZNPZZPZXZPJSZBMQHKCYXYLDKLJNYPKYGHGDZJXXEAHPNZKZTZCMXCXMMJXNKSZQNMNLWBWWXJKYHCPSTMCSQTZJYXTPCTPDTNNPGLLLZSJLSPBLPLQHDTNJNLYYRSZFFJFQWDPHZDWMRZCCLODAXNSSNYZRESTYJWJYJDBCFXNMWTTBYLWSTSZGYBLJPXGLBOCLHPCBJLTMXZLJYLZXCLTPNCLCKXTPZJSWCYXSFYSZDKNTLBYJCYJLLSTGQCBXRYZXBXKLYLHZLQZLNZCXWJZLJZJNCJHXMNZZGJZZXTZJXYCYYCXXJYYXJJXSSSJSTSSTTPPGQTCSXWZDCSYFPTFBFHFBBLZJCLZZDBXGCXLQPXKFZFLSYLTUWBMQJHSZBMDDBCYSCCLDXYCDDQLYJJWMQLLCSGLJJSYFPYYCCYLTJANTJJPWYCMMGQYYSXDXQMZHSZXPFTWWZQSWQRFKJLZJQQYFBRXJHHFWJJZYQAZMYFRHCYYBYQWLPEXCCZSTYRLTTDMQLYKMBBGMYYJPRKZNPBSXYXBHYZDJDNGHPMFSGMWFZMFQMMBCMZZCJJLCNUXYQLMLRYGQZCYXZLWJGCJCGGMCJNFYZZJHYCPRRCMTZQZXHFQGTJXCCJEAQCRJYHPLQLSZDJRBCQHQDYRHYLYXJSYMHZYDWLDFRYHBPYDTSSCNWBXGLPZMLZZTQSSCPJMXXYCSJYTYCGHYCJWYRXXLFEMWJNMKLLSWTXHYYYNCMMCWJDQDJZGLLJWJRKHPZGGFLCCSCZMCBLTBHBQJXQDSPDJZZGKGLFQYWBZYZJLTSTDHQHCTCBCHFLQMPWDSHYYTQWCNZZJTLBYMBPDYYYXSQKXWYYFLXXNCWCXYPMAELYKKJMZZZBRXYYQJFLJPFHHHYTZZXSGQQMHSPGDZQWBWPJHZJDYSCQWZKTXXSQLZYYMYSDZGRXCKKUJLWPYSYSCSYZLRMLQSYLJXBCXTLWDQZPCYCYKPPPNSXFYZJJRCEMHSZMSXLXGLRWGCSTLRSXBZGBZGZTCPLUJLSLYLYMTXMTZPALZXPXJTJWTCYYZLBLXBZLQMYLXPGHDSLSSDMXMBDZZSXWHAMLCZCPJMCNHJYSNSYGCHSKQMZZQDLLKABLWJXSFMOCDXJRRLYQZKJMYBYQLYHETFJZFRFKSRYXFJTWDSXXSYSQJYSLYXWJHSNLXYYXHBHAWHHJZXWMYLJCSSLKYDZTXBZSYFDXGXZJKHSXXYBSSXDPYNZWRPTQZCZENYGCXQFJYKJBZMLJCMQQXUOXSLYXXLYLLJDZBTYMHPFSTTQQWLHOKYBLZZALZXQLHZWRRQHLSTMYPYXJJXMQSJFNBXYXYJXXYQYLTHYLQYFMLKLJTMLLHSZWKZHLJMLHLJKLJSTLQXYLMBHHLNLZXQJHXCFXXLHYHJJGBYZZKBXSCQDJQDSUJZYYHZHHMGSXCSYMXFEBCQWWRBPYYJQTYZCYQYQQZYHMWFFHGZFRJFCDPXNTQYZPDYKHJLFRZXPPXZDBBGZQSTLGDGYLCQMLCHHMFYWLZYXKJLYPQHSYWMQQGQZMLZJNSQXJQSYJYCBEHSXFSZPXZWFLLBCYYJDYTDTHWZSFJMQQYJLMQXXLLDTTKHHYBFPWTYYSQQWNQWLGWDEBZWCMYGCULKJXTMXMYJSXHYBRWFYMWFRXYQMXYSZTZZTFYKMLDHQDXWYYNLCRYJBLPSXCXYWLSPRRJWXHQYPHTYDNXHHMMYWYTZCSQMTSSCCDALWZTCPQPYJLLQZYJSWXMZZMMYLMXCLMXCZMXMZSQTZPPQQBLPGXQZHFLJJHYTJSRXWZXSCCDLXTYJDCQJXSLQYCLZXLZZXMXQRJMHRHZJBHMFLJLMLCLQNLDXZLLLPYPSYJYSXCQQDCMQJZZXHNPNXZMEKMXHYKYQLXSXTXJYYHWDCWDZHQYYBGYBCYSCFGPSJNZDYZZJZXRZRQJJYMCANYRJTLDPPYZBSTJKXXZYPFDWFGZZRPYMTNGXZQBYXNBUFNQKRJQZMJEGRZGYCLKXZDSKKNSXKCLJSPJYYZLQQJYBZSSQLLLKJXTBKTYLCCDDBLSPPFYLGYDTZJYQGGKQTTFZXBDKTYYHYBBFYTYYBCLPDYTGDHRYRNJSPTCSNYJQHKLLLZSLYDXXWBCJQSPXBPJZJCJDZFFXXBRMLAZHCSNDLBJDSZBLPRZTSWSBXBCLLXXLZDJZSJPYLYXXYFTFFFBHJJXGBYXJPMMMPSSJZJMTLYZJXSWXTYLEDQPJMYGQZJGDJLQJWJQLLSJGJGYGMSCLJJXDTYGJQJQJCJZCJGDZZSXQGSJGGCXHQXSNQLZZBXHSGZXCXYLJXYXYYDFQQJHJFXDHCTXJYRXYSQTJXYEFYYSSYYJXNCYZXFXMSYSZXYYSCHSHXZZZGZZZGFJDLTYLNPZGYJYZYYQZPBXQBDZTZCZYXXYHHSQXSHDHGQHJHGYWSZTMZMLHYXGEBTYLZKQWYTJZRCLEKYSTDBCYKQQSAYXCJXWWGSBHJYZYDHCSJKQCXSWXFLTYNYZPZCCZJQTZWJQDZZZQZLJJXLSBHPYXXPSXSHHEZTXFPTLQYZZXHYTXNCFZYYHXGNXMYWXTZSJPTHHGYMXMXQZXTSBCZYJYXXTYYZYPCQLMMSZMJZZLLZXGXZAAJZYXJMZXWDXZSXZDZXLEYJJZQBHZWZZZQTZPSXZTDSXJJJZNYAZPHXYYSRNQDTHZHYYKYJHDZXZLSWCLYBZYECWCYCRYLCXNHZYDZYDYJDFRJJHTRSQTXYXJRJHOJYNXELXSFSFJZGHPZSXZSZDZCQZBYYKLSGSJHCZSHDGQGXYZGXCHXZJWYQWGYHKSSEQZZNDZFKWYSSTCLZSTSYMCDHJXXYWEYXCZAYDMPXMDSXYBSQMJMZJMTZQLPJYQZCGQHXJHHLXXHLHDLDJQCLDWBSXFZZYYSCHTYTYYBHECXHYKGJPXHHYZJFXHWHBDZFYZBCAPNPGNYDMSXHMMMMAMYNBYJTMPXYYMCTHJBZYFCGTYHWPHFTWZZEZSBZEGPFMTSKFTYCMHFLLHGPZJXZJGZJYXZSBBQSCZZLZCCSTPGXMJSFTCCZJZDJXCYBZLFCJSYZFGSZLYBCWZZBYZDZYPSWYJZXZBDSYUXLZZBZFYGCZXBZHZFTPBGZGEJBSTGKDMFHYZZJHZLLZZGJQZLSFDJSSCBZGPDLFZFZSZYZYZSYGCXSNXXCHCZXTZZLJFZGQSQYXZJQDCCZTQCDXZJYQJQCHXZTDLGSCXZSYQJQTZWLQDQZTQCHQQJZYEZZZPBWKDJFCJPZTYPQYQTTYNLMBDKTJZPQZQZZFPZSBNJLGYJDXJDZZKZGQKXDLPZJTCJDQBXDJQJSTCKNXBXZMSLYJCQMTJQWWCJQNJNLLLHJCWQTBZQYDZCZPZZDZYDDCYZZZCCJTTJFZDPRRTZTJDCQTQZDTJNPLZBCLLCTZSXKJZQZPZLBZRBTJDCXFCZDBCCJJLTQQPLDCGZDBBZJCQDCJWYNLLZYZCCDWLLXWZLXRXNTQQCZXKQLSGDFQTDDGLRLAJJTKUYMKQLLTZYTDYYCZGJWYXDXFRSKSTQTENQMRKQZHHQKDLDAZFKYPBGGPZREBZZYKZZSPEGJXGYKQZZZSLYSYYYZWFQZYLZZLZHWCHKYPQGNPGBLPLRRJYXCCSYYHSFZFYBZYYTGZXYLXCZWXXZJZBLFFLGSKHYJZEYJHLPLLLLCZGXDRZELRHGKLZZYHZLYQSZZJZQLJZFLNBHGWLCZCFJYSPYXZLZLXGCCPZBLLCYBBBBUBBCBPCRNNZCZYRBFSRLDCGQYYQXYGMQZWTZYTYJXYFWTEHZZJYWLCCNTZYJJZDEDPZDZTSYQJHDYMBJNYJZLXTSSTPHNDJXXBYXQTZQDDTJTDYYTGWSCSZQFLSHLGLBCZPHDLYZJYCKWTYTYLBNYTSDSYCCTYSZYYEBHEXHQDTWNYGYCLXTSZYSTQMYGZAZCCSZZDSLZCLZRQXYYELJSBYMXSXZTEMBBLLYYLLYTDQYSHYMRQWKFKBFXNXSBYCHXBWJYHTQBPBSBWDZYLKGZSKYHXQZJXHXJXGNLJKZLYYCDXLFYFGHLJGJYBXQLYBXQPQGZTZPLNCYPXDJYQYDYMRBESJYYHKXXSTMXRCZZYWXYQYBMCLLYZHQYZWQXDBXBZWZMSLPDMYSKFMZKLZCYQYCZLQXFZZYDQZPZYGYJYZMZXDZFYFYTTQTZHGSPCZMLCCYTZXJCYTJMKSLPZHYSNZLLYTPZCTZZCKTXDHXXTQCYFKSMQCCYYAZHTJPCYLZLYJBJXTPNYLJYYNRXSYLMMNXJSMYBCSYSYLZYLXJJQYLDZLPQBFZZBLFNDXQKCZFYWHGQMRDSXYCYTXNQQJZYYPFZXDYZFPRXEJDGYQBXRCNFYYQPGHYJDYZXGRHTKYLNWDZNTSMPKLBTHBPYSZBZTJZSZZJTYYXZPHSSZZBZCZPTQFZMYFLYPYBBJQXZMXXDJMTSYSKKBJZXHJCKLPSMKYJZCXTMLJYXRZZQSLXXQPYZXMKYXXXJCLJPRMYYGADYSKQLSNDHYZKQXZYZTCGHZTLMLWZYBWSYCTBHJHJFCWZTXWYTKZLXQSHLYJZJXTMPLPYCGLTBZZTLZJCYJGDTCLKLPLLQPJMZPAPXYZLKKTKDZCZZBNZDYDYQZJYJGMCTXLTGXSZLMLHBGLKFWNWZHDXUHLFMKYSLGXDTWWFRJEJZTZHYDXYKSHWFZCQSHKTMQQHTZHYMJDJSKHXZJZBZZXYMPAGQMSTPXLSKLZYNWRTSQLSZBPSPSGZWYHTLKSSSWHZZLYYTNXJGMJSZSUFWNLSOZTXGXLSAMMLBWLDSZYLAKQCQCTMYCFJBSLXCLZZCLXXKSBZQCLHJPSQPLSXXCKSLNHPSFQQYTXYJZLQLDXZQJZDYYDJNZPTUZDSKJFSLJHYLZSQZLBTXYDGTQFDBYAZXDZHZJNHHQBYKNXJJQCZMLLJZKSPLDYCLBBLXKLELXJLBQYCXJXGCNLCQPLZLZYJTZLJGYZDZPLTQCSXFDMNYCXGBTJDCZNBGBQYQJWGKFHTNPYQZQGBKPBBYZMTJDYTBLSQMPSXTBNPDXKLEMYYCJYNZCTLDYKZZXDDXHQSHDGMZSJYCCTAYRZLPYLTLKXSLZCGGEXCLFXLKJRTLQJAQZNCMBYDKKCXGLCZJZXJHPTDJJMZQYKQSECQZDSHHADMLZFMMZBGNTJNNLGBYJBRBTMLBYJDZXLCJLPLDLPCQDHLXZLYCBLCXZZJADJLNZMMSSSMYBHBSQKBHRSXXJMXSDZNZPXLGBRHWGGFCXGMSKLLTSJYYCQLTSKYWYYHYWXBXQYWPYWYKQLSQPTNTKHQCWDQKTWPXXHCPTHTWUMSSYHBWCRWXHJMKMZNGWTMLKFGHKJYLSYYCXWHYECLQHKQHTTQKHFZLDXQWYZYYDESBPKYRZPJFYYZJCEQDZZDLATZBBFJLLCXDLMJSSXEGYGSJQXCWBXSSZPDYZCXDNYXPPZYDLYJCZPLTXLSXYZYRXCYYYDYLWWNZSAHJSYQYHGYWWAXTJZDAXYSRLTDPSSYYFNEJDXYZHLXLLLZQZSJNYQYQQXYJGHZGZCYJCHZLYCDSHWSHJZYJXCLLNXZJJYYXNFXMWFPYLCYLLABWDDHWDXJMCXZTZPMLQZHSFHZYNZTLLDYWLSLXHYMMYLMBWWKYXYADTXYLLDJPYBPWUXJMWMLLSAFDLLYFLBHHHBQQLTZJCQJLDJTFFKMMMBYTHYGDCQRDDWRQJXNBYSNWZDBYYTBJHPYBYTTJXAAHGQDQTMYSTQXKBTZPKJLZRBEQQSSMJJBDJOTGTBXPGBKTLHQXJJJCTHXQDWJLWRFWQGWSHCKRYSWGFTGYGBXSDWDWRFHWYTJJXXXJYZYSLPYYYPAYXHYDQKXSHXYXGSKQHYWFDDDPPLCJLQQEEWXKSYYKDYPLTJTHKJLTCYYHHJTTPLTZZCDLTHQKZXQYSTEEYWYYZYXXYYSTTJKLLPZMCYHQGXYHSRMBXPLLNQYDQHXSXXWGDQBSHYLLPJJJTHYJKYPPTHYYKTYEZYENMDSHLCRPQFDGFXZPSFTLJXXJBSWYYSKSFLXLPPLBBBLBSFXFYZBSJSSYLPBBFFFFSSCJDSTZSXZRYYSYFFSYZYZBJTBCTSBSDHRTJJBYTCXYJEYLXCBNEBJDSYXYKGSJZBXBYTFZWGENYHHTHZHHXFWGCSTBGXKLSXYWMTMBYXJSTZSCDYQRCYTWXZFHMYMCXLZNSDJTTTXRYCFYJSBSDYERXJLJXBBDEYNJGHXGCKGSCYMBLXJMSZNSKGXFBNBPTHFJAAFXYXFPXMYPQDTZCXZZPXRSYWZDLYBBKTYQPQJPZYPZJZNJPZJLZZFYSBTTSLMPTZRTDXQSJEHBZYLZDHLJSQMLHTXTJECXSLZZSPKTLZKQQYFSYGYWPCPQFHQHYTQXZKRSGTTSQCZLPTXCDYYZXSQZSLXLZMYCPCQBZYXHBSXLZDLTCDXTYLZJYYZPZYZLTXJSJXHLPMYTXCQRBLZSSFJZZTNJYTXMYJHLHPPLCYXQJQQKZZSCPZKSWALQSBLCCZJSXGWWWYGYKTJBBZTDKHXHKGTGPBKQYSLPXPJCKBMLLXDZSTBKLGGQKQLSBKKTFXRMDKBFTPZFRTBBRFERQGXYJPZSSTLBZTPSZQZSJDHLJQLZBPMSMMSXLQQNHKNBLRDDNXXDHDDJCYYGYLXGZLXSYGMQQGKHBPMXYXLYTQWLWGCPBMQXCYZYDRJBHTDJYHQSHTMJSBYPLWHLZFFNYPMHXXHPLTBQPFBJWQDBYGPNZTPFZJGSDDTQSHZEAWZZYLLTYYBWJKXXGHLFKXDJTMSZSQYNZGGSWQSPHTLSSKMCLZXYSZQZXNCJDQGZDLFNYKLJCJLLZLMZZNHYDSSHTHZZLZZBBHQZWWYCRZHLYQQJBEYFXXXWHSRXWQHWPSLMSSKZTTYGYQQWRSLALHMJTQJSMXQBJJZJXZYZKXBYQXBJXSHZTSFJLXMXZXFGHKZSZGGYLCLSARJYHSLLLMZXELGLXYDJYTLFBHBPNLYZFBBHPTGJKWETZHKJJXZXXGLLJLSTGSHJJYQLQZFKCGNNDJSSZFDBCTWWSEQFHQJBSAQTGYPQLBXBMMYWXGSLZHGLZGQYFLZBYFZJFRYSFMBYZHQGFWZSYFYJJPHZBYYZFFWODGRLMFTWLBZGYCQXCDJYGZYYYYTYTYDWEGAZYHXJLZYYHLRMGRXXZCLHNELJJTJTPWJYBJJBXJJTJTEEKHWSLJPLPSFYZPQQBDLQJJTYYQLYZKDKSQJYYQZLDQTGJQYZJSUCMRYQTHTEJMFCTYHYPKMHYZWJDQFHYYXWSHCTXRLJHQXHCCYYYJLTKTTYTMXGTCJTZAYYOCZLYLBSZYWJYTSJYHBYSHFJLYGJXXTMZYYLTXXYPZLXYJZYZYYPNHMYMDYYLBLHLSYYQQLLNJJYMSOYQBZGDLYXYLCQYXTSZEGXHZGLHWBLJHEYXTWQMAKBPQCGYSHHEGQCMWYYWLJYJHYYZLLJJYLHZYHMGSLJLJXCJJYCLYCJPCPZJZJMMYLCQLNQLJQJSXYJMLSZLJQLYCMMHCFMMFPQQMFYLQMCFFQMMMMHMZNFHHJGTTHHKHSLNCHHYQDXTMMQDCYZYXYQMYQYLTDCYYYZAZZCYMZYDLZFFFMMYCQZWZZMABTBYZTDMNZZGGDFTYPCGQYTTSSFFWFDTZQSSYSTWXJHXYTSXXYLBYQHWWKXHZXWZNNZZJZJJQJCCCHYYXBZXZCYZTLLCQXYNJYCYYCYNZZQYYYEWYCZDCJYCCHYJLBTZYYCQWMPWPYMLGKDLDLGKQQBGYCHJXY"; +//此处收录了375个多音字,数据来自于http://www.51window.net/page/pinyin +var oMultiDiff={"19969":"DZ","19975":"WM","19988":"QJ","20048":"YL","20056":"SC","20060":"NM","20094":"QG","20127":"QJ","20167":"QC","20193":"YG","20250":"KH","20256":"ZC","20282":"SC","20285":"QJG","20291":"TD","20314":"YD","20340":"NE","20375":"TD","20389":"YJ","20391":"CZ","20415":"PB","20446":"YS","20447":"SQ","20504":"TC","20608":"KG","20854":"QJ","20857":"ZC","20911":"PF","20504":"TC","20608":"KG","20854":"QJ","20857":"ZC","20911":"PF","20985":"AW","21032":"PB","21048":"XQ","21049":"SC","21089":"YS","21119":"JC","21242":"SB","21273":"SC","21305":"YP","21306":"QO","21330":"ZC","21333":"SDC","21345":"QK","21378":"CA","21397":"SC","21414":"XS","21442":"SC","21477":"JG","21480":"TD","21484":"ZS","21494":"YX","21505":"YX","21512":"HG","21523":"XH","21537":"PB","21542":"PF","21549":"KH","21571":"E","21574":"DA","21588":"TD","21589":"O","21618":"ZC","21621":"KHA","21632":"ZJ","21654":"KG","21679":"LKG","21683":"KH","21710":"A","21719":"YH","21734":"WOE","21769":"A","21780":"WN","21804":"XH","21834":"A","21899":"ZD","21903":"RN","21908":"WO","21939":"ZC","21956":"SA","21964":"YA","21970":"TD","22003":"A","22031":"JG","22040":"XS","22060":"ZC","22066":"ZC","22079":"MH","22129":"XJ","22179":"XA","22237":"NJ","22244":"TD","22280":"JQ","22300":"YH","22313":"XW","22331":"YQ","22343":"YJ","22351":"PH","22395":"DC","22412":"TD","22484":"PB","22500":"PB","22534":"ZD","22549":"DH","22561":"PB","22612":"TD","22771":"KQ","22831":"HB","22841":"JG","22855":"QJ","22865":"XQ","23013":"ML","23081":"WM","23487":"SX","23558":"QJ","23561":"YW","23586":"YW","23614":"YW","23615":"SN","23631":"PB","23646":"ZS","23663":"ZT","23673":"YG","23762":"TD","23769":"ZS","23780":"QJ","23884":"QK","24055":"XH","24113":"DC","24162":"ZC","24191":"GA","24273":"QJ","24324":"NL","24377":"TD","24378":"QJ","24439":"PF","24554":"ZS","24683":"TD","24694":"WE","24733":"LK","24925":"TN","25094":"ZG","25100":"XQ","25103":"XH","25153":"PB","25170":"PB","25179":"KG","25203":"PB","25240":"ZS","25282":"FB","25303":"NA","25324":"KG","25341":"ZY","25373":"WZ","25375":"XJ","25384":"A","25457":"A","25528":"SD","25530":"SC","25552":"TD","25774":"ZC","25874":"ZC","26044":"YW","26080":"WM","26292":"PB","26333":"PB","26355":"ZY","26366":"CZ","26397":"ZC","26399":"QJ","26415":"ZS","26451":"SB","26526":"ZC","26552":"JG","26561":"TD","26588":"JG","26597":"CZ","26629":"ZS","26638":"YL","26646":"XQ","26653":"KG","26657":"XJ","26727":"HG","26894":"ZC","26937":"ZS","26946":"ZC","26999":"KJ","27099":"KJ","27449":"YQ","27481":"XS","27542":"ZS","27663":"ZS","27748":"TS","27784":"SC","27788":"ZD","27795":"TD","27812":"O","27850":"PB","27852":"MB","27895":"SL","27898":"PL","27973":"QJ","27981":"KH","27986":"HX","27994":"XJ","28044":"YC","28065":"WG","28177":"SM","28267":"QJ","28291":"KH","28337":"ZQ","28463":"TL","28548":"DC","28601":"TD","28689":"PB","28805":"JG","28820":"QG","28846":"PB","28952":"TD","28975":"ZC","29100":"A","29325":"QJ","29575":"SL","29602":"FB","30010":"TD","30044":"CX","30058":"PF","30091":"YSP","30111":"YN","30229":"XJ","30427":"SC","30465":"SX","30631":"YQ","30655":"QJ","30684":"QJG","30707":"SD","30729":"XH","30796":"LG","30917":"PB","31074":"NM","31085":"JZ","31109":"SC","31181":"ZC","31192":"MLB","31293":"JQ","31400":"YX","31584":"YJ","31896":"ZN","31909":"ZY","31995":"XJ","32321":"PF","32327":"ZY","32418":"HG","32420":"XQ","32421":"HG","32438":"LG","32473":"GJ","32488":"TD","32521":"QJ","32527":"PB","32562":"ZSQ","32564":"JZ","32735":"ZD","32793":"PB","33071":"PF","33098":"XL","33100":"YA","33152":"PB","33261":"CX","33324":"BP","33333":"TD","33406":"YA","33426":"WM","33432":"PB","33445":"JG","33486":"ZN","33493":"TS","33507":"QJ","33540":"QJ","33544":"ZC","33564":"XQ","33617":"YT","33632":"QJ","33636":"XH","33637":"YX","33694":"WG","33705":"PF","33728":"YW","33882":"SR","34067":"WM","34074":"YW","34121":"QJ","34255":"ZC","34259":"XL","34425":"JH","34430":"XH","34485":"KH","34503":"YS","34532":"HG","34552":"XS","34558":"YE","34593":"ZL","34660":"YQ","34892":"XH","34928":"SC","34999":"QJ","35048":"PB","35059":"SC","35098":"ZC","35203":"TQ","35265":"JX","35299":"JX","35782":"SZ","35828":"YS","35830":"E","35843":"TD","35895":"YG","35977":"MH","36158":"JG","36228":"QJ","36426":"XQ","36466":"DC","36710":"JC","36711":"ZYG","36767":"PB","36866":"SK","36951":"YW","37034":"YX","37063":"XH","37218":"ZC","37325":"ZC","38063":"PB","38079":"TD","38085":"QY","38107":"DC","38116":"TD","38123":"YD","38224":"HG","38241":"XTC","38271":"ZC","38415":"YE","38426":"KH","38461":"YD","38463":"AE","38466":"PB","38477":"XJ","38518":"YT","38551":"WK","38585":"ZC","38704":"XS","38739":"LJ","38761":"GJ","38808":"SQ","39048":"JG","39049":"XJ","39052":"HG","39076":"CZ","39271":"XT","39534":"TD","39552":"TD","39584":"PB","39647":"SB","39730":"LG","39748":"TPB","40109":"ZQ","40479":"ND","40516":"HG","40536":"HG","40583":"QJ","40765":"YQ","40784":"QJ","40840":"YK","40863":"QJG"}; +//参数,中文字符串 +//返回值:拼音首字母串数组 +window.makePy = function(str){ +if(typeof(str) != "string") +throw new Error(-1,"函数makePy需要字符串类型参数!"); +var arrResult = new Array(); //保存中间结果的数组 +for(var i=0,len=str.length;i 40869 || uni < 19968) +return ch; //dealWithOthers(ch); +//检查是否是多音字,是按多音字处理,不是就直接在strChineseFirstPY字符串中找对应的首字母 +return (oMultiDiff[uni]?oMultiDiff[uni]:(strChineseFirstPY.charAt(uni-19968))); +} +function mkRslt(arr){ +var arrRslt = [""]; +for(var i=0,len=arr.length;idiv,#topicList>li{ + position: relative; + overflow: hidden; + cursor: pointer; -webkit-tap-highlight-color: transparent; } - -.ripple-effect{ +.ripple-btn:after,.ripple-label:after,.leftButton:after,.rightButton:after,.chatFooter>div:after,#topicList>li:after{ + content: ""; + background: rgba(0, 0, 0, .15); + display: block; position: absolute; + top: 50%; + left: 50%; border-radius: 50%; - width: 50px; - height: 50px; - /*background: white;*/ - background: #bdbdbd; /* 默认的颜色效果 */ - animation: ripple-animation 1.25s ease-in; + padding-top: 480%; + padding-left: 480%; + margin-top: -240%; + margin-left: -240%; + opacity: 0; + -webkit-transition: all .75s ease-out; + -moz-transition: all .75s ease-out; + transition: all .75s ease-out; } +.ripple-label:after{top: 100%; left: 0;} +.ripple-btn:active:after,.ripple-label:active:after,.leftButton:active:after,.rightButton:active:after,.chatFooter>div:active:after,#topicList>li:active:after{ + padding-top: 0; + padding-left: 0; + margin-top: 0; + margin-left: 0; + border-radius: 50%; + opacity: 1; + -webkit-transition: 0s; + -moz-transition: 0s; + transition: 0s; +} -@keyframes ripple-animation { - from { - transform: scale(1); - opacity: 0.4; - } - to { - transform: scale(100); - opacity: 0; - } -} \ No newline at end of file diff --git a/client/lib/ripple_effects.js b/client/lib/ripple_effects.js index 5392e247c..6017eb400 100644 --- a/client/lib/ripple_effects.js +++ b/client/lib/ripple_effects.js @@ -2,7 +2,7 @@ $.extend({ rippleButton:function() { - $('.ripple').on('click', function (event) { + $('.ripple-elem').on('click', function (event) { event.preventDefault(); var $div = $('
      '), diff --git a/client/lib/string_startswith.js b/client/lib/string_startswith.js new file mode 100644 index 000000000..5f93f08d3 --- /dev/null +++ b/client/lib/string_startswith.js @@ -0,0 +1,5 @@ +if ( typeof String.prototype.startsWith != 'function' ) { + String.prototype.startsWith = function( str ) { + return str.length > 0 && this.substring( 0, str.length ) === str; + } +}; \ No newline at end of file diff --git a/client/lib/take_photo.js b/client/lib/take_photo.js new file mode 100644 index 000000000..3c28d321e --- /dev/null +++ b/client/lib/take_photo.js @@ -0,0 +1,34 @@ +if (Meteor.isCordova) { + function replaceAll(find, replace, str) { + return str.replace(new RegExp(find, 'g'), replace); + } + window.takePhoto = function(callback){ + var pictureSource = navigator.camera.PictureSourceType; + var destinationType = navigator.camera.DestinationType; + + navigator.camera.getPicture(function (imageURI) { + //var returnURI = imageURI; + var timestamp = new Date().getTime(); + var retVal = {filename:'', URI:'', smallImage:''}; + retVal.filename = Meteor.userId()+'_'+timestamp+ '_'+imageURI.replace(/^.*[\\\/]/, ''); + retVal.URI = imageURI; + //ios "file:///var/mobile/Containers/Data/Application/748449D2-3F45-4057-9630-F12065B1C0C8/tmp/cdv_photo_002.jpg" + console.log('image uri is ' + imageURI); + retVal.smallImage = 'cdvfile://localhost/temporary/' + imageURI.replace(/^.*[\\\/]/, ''); + if(callback){ + callback(retVal); + } + }, function(){ + console.log('take photo failed'); + if(callback){ + callback(null); + } + }, { quality: 20, + destinationType: destinationType.FILE_URI, + sourceType: pictureSource.CAMERA, + targetWidth: 1900, + targetHeight: 1900, + correctOrientation: true, + saveToPhotoAlbum: false}); + } +} diff --git a/client/lib/updateAPP.css b/client/lib/updateAPP.css new file mode 100644 index 000000000..3d4e8f4d2 --- /dev/null +++ b/client/lib/updateAPP.css @@ -0,0 +1,67 @@ +.newVersion { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.31); + z-index: 999999; +} +.nw-box { + top: 50%; + left: 50%; + width: 80%; + border-radius: 8px; + transform: translateX(-50%) translateY(-50%); + position: absolute; + color: #404040; + background: #fff; +} + +.nw-box-head { + height: 64px; + line-height: 64px; + text-align: center; + font-size: 16px; + font-weight: bold; + border-radius: 8px 8px 0 0; +} + +.nw-box-body { + height: 200px; + padding: 10px 20px; + white-space: pre-line; + overflow-x: hidden; + overflow-y: auto; + text-align: justify; + letter-spacing: 1px; + line-height: 26px; + font-size: 14px; + border-radius: 8px 8px 0 0; + -webkit-overflow-scrolling: touch; +} + +.nw-box-foot { + border-radius: 0 0 8px 8px; + text-align: center; + padding: 16px; +} +.nw-btn { + width: 40%; + height: 36px; + line-height: 36px; + text-align: center; + border: 1px solid; + border-radius: 12px; + display: inline-block; + margin: 0 10px; + cursor: pointer; +} + +.nw-btn.nw--btn-update { + color: deepskyblue; +} + +.nw-btn.nw-btn-later { + color: #404040; +} \ No newline at end of file diff --git a/client/lib/url_analyser.coffee b/client/lib/url_analyser.coffee new file mode 100644 index 000000000..c10768b9c --- /dev/null +++ b/client/lib/url_analyser.coffee @@ -0,0 +1,1191 @@ +if Meteor.isClient + showDebug=true + importColor=false + titleRules = [ + # link string, class name + {prefix:'view.inews.qq.com',titleClass:'title'}, + {prefix:'buluo.qq.com',titleClass:'post-title'}, + {prefix:'wap.koudaitong.com',titleClass:'custom-title.wx_template .title'} + ] + hostnameMapping = [ + {hostname:'mp.weixin.qq.com',displayName:'微信公众号'}, + {hostname:'m.toutiao.com',displayName:'头条'}, + {hostname:'news.shou.com',displayName:'搜狐新闻'}, + {hostname:'m.shou.com',displayName:'搜狐'}, + {hostname:'www.zhihu.com',displayName:'知乎'}, + {hostname:'card.weibo.com',displayName:'微博'}, + {hostname:'mil.sohu.com',displayName:'搜狐军事'}, + {hostname:'wap.koudaitong.com',displayName:'罗辑思维'} + ] + musicExtactorMapping = [ + { + musicClass:'.qqmusic_area', + musicUrlSelector:'.qqmusic_area .qqmusic_thumb', + musicUrlAttr:'data-autourl', + musicImgSelector:'.qqmusic_area .qqmusic_thumb', + musicImgAttr:'src', + musicSongNameSelector:'.qqmusic_area .qqmusic_songname', + musicSingerNameSelector:'.qqmusic_area .qqmusic_singername' + }, + { + musicClass:'mpvoice', + musicUrlSelector:'mpvoice', + musicUrlAttr:'voice_encode_fileid', + prefixToMusicUrl:'http://res.wx.qq.com/voice/getvoice?mediaid=', + musicImgSelector:'', + musicImgAttr:'', + musicSongNameSelector:'.audio_area .audio_info_area .audio_title', + musicSingerNameSelector:'.audio_area .audio_info_area .audio_source' + } + ] + musicExtactorMappingV2 = [ + { + nodeSelector: 'QQMUSIC' + parentSelector: '' + getMusicUrl:(node, body)-> + return $(node).attr('audiourl') + getMusicThumbImageURL:(node, body)-> + nodes = $(node.parentNode).find('.qqmusic_area .play_area img') + if (nodes.length > 1) + return $(nodes[1]).attr('src') + else + return $(nodes[0]).attr('src') + getMusicSongName:(node, body)-> + return $(body).find('.qqmusic_area .qqmusic_songname').text() + getMusicSingerName:(node, body)-> + return $(body).find('.qqmusic_area .qqmusic_singername').text() + cleanUp:(node, body)-> + $(node.parentNode).remove('.qqmusic_area') + }, + { + nodeSelector: 'MPVOICE' + parentSelector: '' + getMusicUrl:(node)-> + playUrl = node.getAttribute('voice_encode_fileid'); + if playUrl and playUrl isnt '' + playUrl = 'http://res.wx.qq.com/voice/getvoice?mediaid='+playUrl + console.log('getMusicUrl '+playUrl) + return playUrl + '' + getMusicThumbImageURL:(node)-> + console.log('getMusicThumbImageURL None') + '' + getMusicSongName:(node)-> + console.log('GetMusicSongName') + if node and node.parentNode + return $(node.parentNode).find('.audio_area .audio_info_area .audio_title').text() + '' + getMusicSingerName:(node)-> + console.log('getMusicSingerName') + if node and node.parentNode + return $(node.parentNode).find('.audio_area .audio_info_area .audio_source').text() + '' + cleanUp:(node)-> + if node and node.parentNode + return $(node.parentNode).remove('.audio_area') + }, + { + nodeSelector: '#playerbox' + parentSelector: '' + getMusicInfo:(node, body)-> + for item in $(body).find('script') + text = item.innerText + if text.indexOf('window.__bootstrap_data') isnt -1 + eval(text) + if window.__bootstrap_data.song + return { + playUrl: window.__bootstrap_data.song.url + image: window.__bootstrap_data.song.picture + songName: window.__bootstrap_data.song.title + singerName: window.__bootstrap_data.song.artist + } + return {} + cleanUp:(node)-> + if node and node.parentNode + return $(node).html('') + } + { + nodeSelector: '.j-orientation-0' + parentSelector: '' + getMusicInfo:(node, body)-> + console.log(node) + for item in $(body).find('#j-body script') + text = item.innerText + if text.indexOf('showData:{"') isnt -1 + text = text.substr(text.indexOf('showData:{"') + 'showData:'.length) + text = text.substring(0, text.indexOf('"}},')+3) + obj = JSON.parse(text) + return { + playUrl: obj.data.url + image: obj.data.cover + songName: obj.data.name + singerName: obj.data.ownername + } + return {} + cleanUp:(node)-> + } + { + nodeSelector: '' + parentSelector: '.article' + getMusicInfo:(node, body)-> + for item in $(body).find('script') + text = item.innerText + if text.indexOf('mp3:"/') isnt -1 + text = text.substr(text.indexOf('mp3:"/') + 'mp3:"/'.length) + text = text.substring(0, text.indexOf('.mp3"')+4) + player = $(body).find('#jp_container_1') + + return { + playUrl: 'http://yuedu.fm' + text + image: 'http://yuedu.fm' + player.find('.cover img').attr('src') + songName: player.find('.item-title h1').text() + singerName: player.find('.item-title p').text() + } + return {} + cleanUp:(node)-> + } + { + nodeSelector: '.song_infosong_info' # QQ 音乐 + parentSelector: '' + getMusicInfo:(node, body)-> + return { + playUrl: $(body).find('#h5audio_media').attr('src') + image: $(node).find('.album_cover__img').attr('src') + songName: $(node).find('.song_name__text').text() + singerName: $(node).find('.singer_name__text').text() + } + cleanUp:(node)-> + $(node).html() + } + ] + @getMusicFromNode = (node, body) -> + for s in musicExtactorMappingV2 + isExist = false + findNone = node + + if s.nodeSelector isnt '' and node.nodeType isnt Node.TEXT_NODE + if s.nodeSelector.indexOf('#') is 0 + if node.id is s.nodeSelector.substr(1) + isExist = true + else if s.nodeSelector.indexOf('.') is 0 + if node.className is s.nodeSelector.substr(1) + isExist = true + else + if node.tagName is s.nodeSelector.toUpperCase() + isExist = true + else if s.parentSelector isnt '' and $(node.parentNode).find('musicExtracted').length <= 0 + if s.parentSelector.indexOf('#') is 0 + if node.parentNode.id is s.parentSelector.substr(1) + findNone = node.parentNode + isExist = true + else if s.parentSelector.indexOf('.') is 0 + if node.parentNode.className is s.parentSelector.substr(1) + findNone = node.parentNode + isExist = true + else + if node.parentNode.tagName is s.parentSelector.toUpperCase() + findNone = node.parentNode + isExist = true + + if isExist is true + musicInfo = {} + if s.getMusicInfo + musicInfo = s.getMusicInfo(findNone, body) + else + if typeof(s.getMusicUrl) is 'function' + musicInfo.playUrl = s.getMusicUrl(findNone, body) + if typeof(s.getMusicThumbImageURL) is 'function' + musicInfo.image = s.getMusicThumbImageURL(findNone, body) + if typeof(s.getMusicSongName) is 'function' + musicInfo.songName = s.getMusicSongName(findNone, body) + if typeof(s.getMusicSingerName) is 'function' + musicInfo.singerName = s.getMusicSingerName(findNone, body) + if typeof(s.cleanUp) is 'function' + s.cleanUp(findNone, body) + if musicInfo.playUrl + musicElement = document.createElement("musicExtracted") + musicElement.setAttribute('playUrl', musicInfo.playUrl) + musicElement.setAttribute('image', musicInfo.image) + musicElement.setAttribute('songName', musicInfo.songName) + musicElement.setAttribute('singerName', musicInfo.singerName) + + findNone.appendChild(musicElement) + console.log('Got Music Info '+JSON.stringify(musicInfo)) + return musicInfo + else if s.parentSelector isnt '' and $(node.parentNode).find('musicExtracted').length > 0 + musicInfo = {} + musicInfo.playUrl = $(node.parentNode).find('musicExtracted').attr('playUrl') + musicInfo.image = $(node.parentNode).find('musicExtracted').attr('image') + musicInfo.songName = $(node.parentNode).find('musicExtracted').attr('songName') + musicInfo.singerName = $(node.parentNode).find('musicExtracted').attr('singerName') + return musicInfo + return null + @getMusicFromScript = (url, page)-> + for mapping in musicExtactorScriptMapping + if (url.toLowerCase().indexOf(mapping.url) is 0) + return extractScript(page, mapping.getMusic) + + return null + getMusicFromPage = (page) -> + for s in musicExtactorMapping + if $(page).find(s.musicClass).length > 0 + playUrl = $(page).find(s.musicUrlSelector).attr(s.musicUrlAttr) + if s.prefixToMusicUrl and s.prefixToMusicUrl isnt '' + playUrl = s.prefixToMusicUrl + playUrl + if s.musicImgSelector and s.musicImgSelector isnt '' + image = $(page).find(s.musicImgSelector).attr(s.musicImgAttr) + songName = $(page).find(s.musicSongNameSelector).text() + singerName = $(page).find(s.musicSingerNameSelector).text() + console.log('found music element ' + playUrl + ' image ' + image + ' song name ' + songName + ' singer ' + singerName) + $(page).find(s.musicClass).remove() + if playUrl + return { + playUrl : playUrl, + image : image, + songName : songName, + singerName: singerName + } + return null + videoExtactorMapping = [ + { + videoClass: '.f-video', + videoUrlSelector: '#mediaPlayer', + videoUrlAttr: 'data-video', + videoImgSelector: '#fVideoImg', + videoImgAttr: 'src' + }, + { + videoClass: '.jwvideo', + videoUrlSelector: '#container_media', + videoUrlAttr: 'src', + videoImgSelector: '.live-bg', + videoImgAttr: 'src' + } + ] + getPossibleVideo = (elem,data)-> + showDebug&&console.log 'data is --------------' + if device.platform isnt 'iOS' + data = data[0] + showDebug&&console.log data.scripts + # showDebug&&console.log data.host + if data.host is "www.meerlive.com" and device.platform is 'iOS' + console.log 'iOS device' + playUrlArr = data.body.match(/file":\["(\S*)\"],"user"/) + playUrl = playUrlArr[1].replace(/\\/g,"") + if playUrl + imageUrlArr = data.body.match(/image":"(\S*)\","cover"/) + imageUrl = imageUrlArr[1].replace(/\\/g,"") + if playUrl and imageUrl + return {playUrl: playUrl, imageUrl: imageUrl} + else if data.host is "www.meerlive.com" and device.platform isnt 'iOS' and data.scripts + html = data.scripts + playUrlArr = html.match(/file":\["(\S*)\"],"user"/) + console.log playUrlArr + playUrl = playUrlArr[1].replace(/\\/g,"") + console.log playUrl + if playUrl + imageUrlArr = html.match(/image":"(\S*)\","cover"/) + console.log imageUrlArr + imageUrl = imageUrlArr[1].replace(/\\/g,"") + console.log imageUrl + if playUrl and imageUrl + console.log 'onSuccess' + return {playUrl: playUrl, imageUrl: imageUrl} + # try + # alert(data.body) + # reg = new RegExp(/var media_info = {[\s\S]*};/gim) + # if !reg.test(data.body) + # return null + # regResult = data.body.match(reg) + # alert(regResult) + # if !regResult or regResult.length <= 0 + # return null + # script = regResult[0].substr('var media_info = '.length) + # script = script.substr(0, script.length-1) + # alert(script) + # media_info = JSON.parse(script) + # if !media_info or !media_info.file or media_info.file.length <= 0 + # return null + # return { + # playUrl: media_info.file[0] + # imageUrl: media_info.image + # } + # catch + # return null + else + console.log 'not match meerlive' + for s in videoExtactorMapping + if '#'+elem.id is s.videoUrlSelector + node = if elem.parentNode then elem.parentNode else elem + if $(node).find(s.videoClass).length > 0 + playUrl = $(node).find(s.videoUrlSelector).attr(s.videoUrlAttr) + if s.videoImgSelector and s.videoImgSelector isnt '' + imageUrl = $(node).find(s.videoImgSelector).attr(s.videoImgAttr) + console.log('found video element:' + playUrl + ', imageUrl=' + imageUrl) + $(node).find(s.videoUrlSelector).remove() + if playUrl + return {playUrl: playUrl, imageUrl: imageUrl} + return null + + ### + http://stackoverflow.com/a/1634841/3380894 + To remove the width/height parameter in url, center the video play icon + ### + `function removeURLParameter(url, parameter) { + //prefer to use l.search if you have a location/link object + var urlparts= url.split('?'); + if (urlparts.length>=2) { + + var prefix= encodeURIComponent(parameter)+'='; + var pars= urlparts[1].split(/[&;]/g); + + //reverse iteration as may be destructive + for (var i= pars.length; i-- > 0;) { + //idiom for string.startsWith + if (pars[i].lastIndexOf(prefix, 0) !== -1) { + pars.splice(i, 1); + } + } + + url= urlparts[0]+'?'+pars.join('&'); + return url; + } else { + return url; + } + }` + @seekSuitableImageFromArray = (imageArray,callback,minimal,onlyOne)-> + @imageCounter = 0 + @foundImages = 0 + if minimal + minimalWidthAndHeight = minimal + else + minimalWidthAndHeight = 150 + unless @imageResolver + @imageResolver = new Image() + imageResolver.onload = -> + height = imageResolver.height + width = imageResolver.width + showDebug&&console.log imageArray[imageCounter] + ' width is ' + width + ' height is ' + height + if height >= minimalWidthAndHeight and width >= minimalWidthAndHeight + showDebug&&console.log 'This image can be used ' + imageArray[imageCounter] + ' width is ' + width + ' height is ' + height + callback imageArray[imageCounter],width,height, ++foundImages,imageCounter,imageArray.length + if onlyOne + return + if ++imageCounter < imageArray.length + imageResolver.src = imageArray[imageCounter] + else + callback null,0,0,foundImages,imageCounter,imageArray.length + imageResolver.onerror = -> + showDebug&&console.log 'image resolve url got error' + if ++imageCounter < imageArray.length + imageResolver.src = imageArray[imageCounter] + else + callback null,0,0,foundImages,imageCounter,imageArray.length + imageResolver.src = imageArray[imageCounter] + @seekSuitableImageFromArrayAndDownloadToLocal = (imageArray,callback,minimal,onlyOne)-> + @imageCounter = 0 + @foundImages = 0 + if minimal + minimalWidthAndHeight = minimal + else + minimalWidthAndHeight = 150 + downloadHandler = (downloadedUrl,source,file)-> + #showDebug&&console.log('Got downloaded URL ' + downloadedUrl) + if downloadedUrl + onSuccess(downloadedUrl,source,file) + else + onError(source) + onSuccess = (url,source,file)-> + #showDebug&&console.log('To call get_image_size_from_URI on ' + url) + get_image_size_from_URI(url,(width,height)-> + #showDebug&&console.log url + ' width is ' + width + ' height is ' + height + if height >= minimalWidthAndHeight and width >= minimalWidthAndHeight + #showDebug&&console.log 'This image can be used ' + imageArray[imageCounter] + ' width is ' + width + ' height is ' + height + callback file,width,height, ++foundImages,imageCounter,imageArray.length,source + if onlyOne + return + if ++imageCounter < imageArray.length + #showDebug&&console.log('imageCounter ' + imageCounter + ' imageArray.length ' + imageArray.length) + downloadFromBCS(imageArray[imageCounter],downloadHandler) + else + callback null,0,0,foundImages,imageCounter,imageArray.length,source + ) + onError = (source)-> + showDebug&&console.log 'image resolve url got error' + if ++imageCounter < imageArray.length + downloadFromBCS(imageArray[imageCounter],downloadHandler) + else + callback null,0,0,foundImages,imageCounter,imageArray.length,null,source + downloadFromBCS(imageArray[imageCounter],downloadHandler) + @analyseUrl = (url,callback)-> + @iabRef = window.open(url, '_blank', 'hidden=yes') + iabRef.addEventListener 'loadstop', ()-> + showDebug&&console.log 'load stop' + getImagesListFromUrl(iabRef,url,callback) + iabRef.addEventListener 'loaderror', ()-> + showDebug&&console.log 'load error' + if callback + callback(null,0,0) + @reAnalyseUrl = (url,callback)-> + unless iabRef + callback(null,0,0) + return + getImagesListFromUrl(iabRef,url,callback) + @clearLastUrlAnalyser = ()-> + if iabRef + iabRef.close() + iabRef = undefined + @seekOneUsableMainImage = (data,callback,minimal)-> + imageArray = [] + showDebug&&console.log 'Url Analyse result is ' + JSON.stringify(data) + if data.imageArray + for img in data.imageArray + if img and img.startsWith("http") + imageArray.push img + if data.bgArray + for bgImg in data.bgArray + imageUrl = (bgImg.match( /url\([^\)]+\)/gi ) ||[""])[0].split(/[()'"]+/)[1] + if imageUrl and imageUrl.startsWith("http") + imageArray.push imageUrl + showDebug&&console.log 'Got images to be anylised ' + JSON.stringify(imageArray) + if imageArray.length > 0 + seekSuitableImageFromArrayAndDownloadToLocal imageArray,(file,w,h,found,index,length,source)-> + if file + showDebug&&console.log('Original source:'+source+'Got local url '+ JSON.stringify(file)+' w:'+w+' h:'+h) + callback(file,w,h,found,index,length,source) + else + showDebug&&console.log('No local url '+' w:'+w+' h:'+h) + callback(null,0,0,found,index,length,source) + ,minimal,true + else + callback(null,0,0,0,0,0,null) + @processInAppInjectionData = (data,callback,minimal)-> + imageArray = [] + showDebug&&console.log 'Url Analyse result is ' + JSON.stringify(data) + if data.imageArray + for img in data.imageArray + if img and img.startsWith("http") + imageArray.push img + if data.bgArray + for bgImg in data.bgArray + imageUrl = (bgImg.match( /url\([^\)]+\)/gi ) ||[""])[0].split(/[()'"]+/)[1] + if imageUrl and imageUrl.startsWith("http") + imageArray.push imageUrl + showDebug&&console.log 'Got images to be anylised ' + JSON.stringify(imageArray) + if imageArray.length > 0 + seekSuitableImageFromArray imageArray,(url,w,h,found,index,length)-> + if url + callback(url,w,h,found,index,length) + else + callback(null,0,0,found,index,length) + ,minimal,false + else + callback(null,0,0,0,0,0) + # data.body, the data to be analyse. (string) + # return value + # data.bgArray, the background images + # data.imageArray, the image in the element + grabImagesInHTMLString = (data)-> + documentBody = $.parseHTML( data.body ) + documentBody.innerHTML = data.body + $(documentBody).find('img').each ()-> + dataSrc = $(this).attr('data-src') + dataLISrc = $(this).attr('data-li-src') + if dataSrc and dataSrc isnt '' + src = dataSrc + else if dataLISrc and dataLISrc isnt '' + src = dataLISrc + else + src = $(this).attr('src') + if src and src isnt '' + src = src.replace(/&/g, '&').replace("tp=webp","tp=jpeg") + unless src.startsWith('http') + if src.startsWith('//') + src = data.protocol + src + else if src.startsWith('/') + src = data.protocol + '//' + data.host + '/' + src + showDebug&&console.log 'Image Src: ' + src + if (data.imageArray.indexOf src) <0 + data.imageArray.push src + $(documentBody).find('input').each ()-> + src = $(this).attr('src') + if src and src isnt '' and src.startsWith('http') + src = src.replace(/&/g, '&').replace("tp=webp","tp=jpeg") + if (data.imageArray.indexOf src) <0 + showDebug&&console.log 'Got src is ' + src + data.imageArray.push src + $(documentBody).find('div').each ()-> + bg_url = $(this).css('background-image') + # ^ Either "none" or url("...urlhere..") + if bg_url and bg_url isnt '' + bg_url = bg_url.replace(/&/g, '&').replace("tp=webp","tp=jpeg") + bg_url = /^url\((['"]?)(.*)\1\)$/.exec(bg_url) + # If matched, retrieve url, otherwise "" + if bg_url + bg_url = bg_url[2] + if bg_url and bg_url isnt '' + unless bg_url.startsWith('http') + bg_url = data.protocol + '//' + data.host + '/' + bg_url + showDebug&&console.log 'Background Image: ' + bg_url + if (data.bgArray.indexOf bg_url) <0 + data.bgArray.push bg_url + pattern = /img src=\"([\s\S]*?)(?=\")/g + result = data.body.match(pattern) + if result and result.length > 0 + showDebug&&console.log 'result ' + JSON.stringify(result) + for subString in result + dataSrc = subString.substring(9, subString.length).replace(/&/g, '&').replace("tp=webp","tp=jpeg") + if (data.imageArray.indexOf dataSrc) <0 and (data.bgArray.indexOf dataSrc) <0 + data.imageArray.push(dataSrc) + showDebug&&console.log 'push dataSrc: ' + dataSrc + pattern = /data-src=\"([\s\S]*?)(?=\")/g + result = data.body.match(pattern) + if result and result.length > 0 + showDebug&&console.log 'result ' + JSON.stringify(result) + for subString in result + dataSrc = subString.substring(10, subString.length).replace(/&/g, '&').replace("tp=webp","tp=jpeg") + if (data.imageArray.indexOf dataSrc) <0 and (data.bgArray.indexOf dataSrc) <0 + data.imageArray.push(dataSrc) + showDebug&&console.log 'push dataSrc: ' + dataSrc + pattern = /data-url=\"([\s\S]*?)(?=\")/g + result = data.body.match(pattern) + if result and result.length > 0 + showDebug&&console.log 'result ' + JSON.stringify(result) + for subString in result + dataSrc = subString.substring(10, subString.length).replace(/&/g, '&').replace("tp=webp","tp=jpeg") + if (data.imageArray.indexOf dataSrc) <0 and (data.bgArray.indexOf dataSrc) <0 + data.imageArray.push(dataSrc) + showDebug&&console.log 'push dataSrc: ' + dataSrc + @getImagesListFromUrl = (inappBrowser,url,callback)-> + inappBrowser.executeScript { + code: ' + var returnJson = {}; + if(document.title){ + returnJson["title"] = document.title; + } + if(location.host){ + returnJson["host"] = location.host; + } + if(document.body){ + returnJson["body"] = document.body.innerHTML; + returnJson["bodyLength"] = document.body.innerHTML.length; + } + if(window.location.protocol){ + returnJson["protocol"] = window.location.protocol; + } + + if(location.host === "m.youku.com"){ + try{returnJson["_video_src"] = BuildVideoInfo._videoInfo._videoSegsDic.streams.default.mp4[0].src;}catch(e){} + } + + returnJson; + '} + ,(data)-> + if data[0] + showDebug&&console.log 'data0 is ' + JSON.stringify(data[0]) + data = data[0] + data.bgArray = [] + data.imageArray = [] + grabImagesInHTMLString(data) + documentBody = $.parseHTML( data.body ) + documentBody.innerHTML = data.body + extracted = extract(documentBody) + data.fullText = $(extracted).text() + #showDebug&&console.log data.body + callback data + _html2data = (url, data, callback)-> + Meteor.defer ()-> + onBeforeExtract(url, data) + + if data[0] + showDebug&&console.log 'data0 is ' + JSON.stringify(data[0]) + data = data[0] + data.bgArray = [] + data.imageArray = [] + documentBody = $.parseHTML( data.body ) + documentBody.innerHTML = data.body + documentBody.host = data.host + + for titleRule in titleRules + if url.indexOf(titleRule.prefix) > -1 + realTitle = $(documentBody).find('.'+titleRule.titleClass).text().replace(/^\s+|\s+$/g, "") + if realTitle and realTitle isnt '' + #data.host = data.title + data.title = realTitle + break + for item in hostnameMapping + if data.host is item.hostname + data.host = '摘自 ' + item.displayName + break + + #musicInfo = getMusicFromPage documentBody + extracted = extract(documentBody) + toBeInsertedText = '' + toBeInsertedStyleAlign='' + previousIsImage = false + resortedArticle = [] + sortedImages = 0 + +# musics = getMusicFromScript(url, documentBody) +# if(musics.length > 0) +# for musicInfo in musics +# resortedArticle.push {type:'music', musicInfo: musicInfo} + + if extracted.id is 'hotshare_special_tag_will_not_hit_other' + toBeProcessed = extracted + else + toBeProcessed = extracted.innerHTML + previousIsSpan = false + $(toBeProcessed).children().each (index,node)-> + info = {} + info.bgArray = [] + info.imageArray = [] + info.body = node.innerHTML + nodeColor = $(node).css('color') + nodeBackgroundColor = $(node).css('background-color') + #iframeNumber = $(node).find('iframe').length + console.log(' Node['+index+'] tagName '+node.tagName+' text '+node.textContent) + styleAlign=getStyleInItem(node,'textAlign') + console.log(' Got style '+styleAlign); + if node.tagName is 'BR' + if toBeInsertedText.length > 0 + resortedArticle.push {type:'text',text:toBeInsertedText,layout:{align:toBeInsertedStyleAlign}} + toBeInsertedText = '' + toBeInsertedStyleAlign = '' + previousIsSpan = false + return true + else if node.tagName is 'MUSICEXTRACTED' + if toBeInsertedText and toBeInsertedText isnt '' + resortedArticle.push {type:'text',text:toBeInsertedText} + toBeInsertedText = '' + + playUrl=node.getAttribute('playUrl') + image=node.getAttribute('image') + songName=node.getAttribute('songName') + singerName=node.getAttribute('singerName') + resortedArticle.push {type:'music', musicInfo: { + playUrl:playUrl + image:image + songName:songName + singerName:singerName + }} + previousIsSpan = false + return true + text = $(node).text() + if text and text isnt '' + text = text.replace(/\s\s\s+/g, '') + console.log('text '+text) + if node.tagName == 'IFRAME' + previousIsSpan = false + node.width = '100%' + node.width = '100%' + node.height = '100%' + node.src = removeURLParameter(node.src,'width') + node.src = removeURLParameter(node.src,'height') + node.src = node.src.replace(/https:\/\//g, 'http://') + node.removeAttribute("style") + dataSrc = node.getAttribute('data-src') + if dataSrc + dataSrc = removeURLParameter(dataSrc,'width') + dataSrc = removeURLParameter(dataSrc,'height') + dataSrc = dataSrc.replace(/https:\/\//g, 'http://') + dataSrc = dataSrc.replace(/v.qq.com\/iframe\/preview.html/g, 'v.qq.com/iframe/player.html') + node.setAttribute('data-src',dataSrc) + node.src = dataSrc + showDebug&&console.log(node.outerHTML) + if toBeInsertedText and toBeInsertedText isnt '' + resortedArticle.push {type:'text',text:toBeInsertedText} + toBeInsertedText = '' + resortedArticle.push {type:'iframe',iframe:node.outerHTML} + else if node.tagName == 'IMG' + previousIsSpan = false + dataSrc = $(node).attr('data-src') + dataLISrc = $(node).attr('data-li-src') + if dataSrc and dataSrc isnt '' + src = dataSrc + else if dataLISrc and dataLISrc isnt '' + src = dataLISrc + else + src = $(node).attr('src') + if src and src isnt '' + src = src.replace(/&/g, '&').replace("tp=webp","tp=jpeg") + unless src.startsWith('http') + if src.startsWith('//') + src = data.protocol + src + else if src.startsWith('/') + src = data.protocol + '//' + documentBody.host + '/' + src + showDebug&&console.log 'Image Src: ' + src + previousIsImage = true + if toBeInsertedText and toBeInsertedText isnt '' + resortedArticle.push {type:'text',text:toBeInsertedText} + toBeInsertedText = '' + sortedImages++; + resortedArticle.push {type:'image',imageUrl:src} + data.imageArray.push src + else if info.body + grabImagesInHTMLString(info) + if info.imageArray.length > 0 + showDebug&&console.log(' Got image') + previousIsImage = true + previousIsSpan = false + if toBeInsertedText and toBeInsertedText isnt '' + resortedArticle.push {type:'text',text:toBeInsertedText} + toBeInsertedText = '' + for imageUrl in info.imageArray + if imageUrl.startsWith('http://') or imageUrl.startsWith('https://') + showDebug&&console.log(' save imageUrl ' + imageUrl) + sortedImages++; + resortedArticle.push {type:'image',imageUrl:imageUrl} + data.imageArray.push imageUrl + else if info.bgArray.length > 0 + showDebug&&console.log(' Got Background image') + previousIsImage = true + previousIsSpan = false + if toBeInsertedText and toBeInsertedText isnt '' + resortedArticle.push {type:'text',text:toBeInsertedText} + toBeInsertedText = '' + for imageUrl in info.bgArray + if imageUrl.startsWith('http://') or imageUrl.startsWith('https://') + showDebug&&console.log(' save background imageUrl ' + imageUrl) + sortedImages++ + resortedArticle.push {type:'image',imageUrl:imageUrl} + data.imageArray.push imageUrl + if text and text isnt '' + previousIsImage = false + showDebug&&console.log ' Got text in this element('+toBeInsertedText.length+') '+text + showDebug&&console.log 'Text ['+text+'] color is '+nodeColor+' nodeBackgroundColor is '+nodeBackgroundColor + ### + if importColor and nodeColor and nodeColor isnt '' + if toBeInsertedText.length > 0 + toBeInsertedText += '\n' + resortedArticle.push {type:'text',text:toBeInsertedText} + toBeInsertedText = '' + resortedArticle.push {type:'text',text:text,color:nodeColor,backgroundColor:nodeBackgroundColor} + else + ### + #console.log('Get style '+$(node).attr('style')); + if toBeInsertedText.length is 0 + toBeInsertedStyleAlign = styleAlign + if node.tagName is 'SPAN' + toBeInsertedText +=text + previousIsSpan = true + else if previousIsSpan is true + toBeInsertedText += text + previousIsSpan = false + text = '' + else if toBeInsertedText.length < 20 and styleAlign is toBeInsertedStyleAlign + if toBeInsertedText.length > 0 + toBeInsertedText += '\n' + toBeInsertedText += text + else + if toBeInsertedText.length > 0 + resortedArticle.push {type:'text',text:toBeInsertedText,layout:{align:toBeInsertedStyleAlign}} + toBeInsertedText = text; + toBeInsertedStyleAlign = styleAlign; + if toBeInsertedText and toBeInsertedText isnt '' + resortedArticle.push {type:'text',text:toBeInsertedText} + if sortedImages < 1 + console.log('no image ?') + grabImagesInHTMLString(data) + if data.imageArray.length > 0 + for imageUrl in data.imageArray + if imageUrl.startsWith('http://') or imageUrl.startsWith('https://') + showDebug&&console.log(' save imageUrl ' + imageUrl) + resortedArticle.push {type:'image',imageUrl:imageUrl} + else if data.bgArray.length > 0 + for imageUrl in data.bgArray + if imageUrl.startsWith('http://') or imageUrl.startsWith('https://') + showDebug&&console.log(' save background imageUrl ' + imageUrl) + resortedArticle.push {type:'image',imageUrl:imageUrl} + data.resortedArticle = resortedArticle + # showDebug&&console.log('Resorted Article is ' + JSON.stringify(data.resortedArticle)) + callback data + _html2data2 = (url, data, callback)-> + htmldata = data + Meteor.defer ()-> + onBeforeExtract(url, data) + + pageInnerText = '' + previousParagraph = '' + paragraphArray = [] + paragraphArrayTmp = [] + initParagraphArray = (extracted)-> + divElement = document.createElement("div"); + divElement.style.display = '' + divElement.style.height = 0 + divElement.style.width = 0 + divElement.style.left = -100; + divElement.style.position = 'absolute' + divElement.appendChild(extracted); + document.body.appendChild(divElement); + pageInnerText = divElement.innerText + console.log("pageInnerText = "+pageInnerText) + console.log("divElement.innerHTML = "+divElement.innerHTML) + paragraphArrayTmp = pageInnerText.split('\n') + document.body.removeChild(divElement) + if paragraphArrayTmp.length > 0 + for i in [0..paragraphArrayTmp.length-1] + unless (paragraphArrayTmp[i].length == 0 or paragraphArrayTmp[i] == ' ') + paragraphArray.push(paragraphArrayTmp[i]) + if paragraphArray.length > 0 + console.log("paragraphArray.length="+paragraphArray.length) + for i in [0..paragraphArray.length-1] + console.log('paragraphArray['+i+']='+paragraphArray[i]) + appendParagraph = (resortedArticle, text, styleAlign)-> + isShortParagraph = false + appendTextWithStyleAlign = ()-> + if !isShortParagraph + if text.trim() is '' or text.trim() is '\n' + return + if styleAlign is undefined + resortedArticle.push {type:'text',text:text} + else + if styleAlign.textAlign and styleAlign.fontWeight + resortedArticle.push {type:'text',text:text,layout:{align:styleAlign.textAlign, weight:styleAlign.fontWeight}} + else if styleAlign.textAlign + resortedArticle.push {type:'text',text:text,layout:{align:styleAlign.textAlign}} + else if styleAlign.fontWeight + resortedArticle.push {type:'text',text:text,layout:{weight:styleAlign.fontWeight}} + else + resortedArticle.push {type:'text',text:text} + if resortedArticle.length > 0 + lastArtical = resortedArticle[resortedArticle.length-1] + if lastArtical.type is 'text' + textArray = lastArtical.text.split('\n') + if textArray[textArray.length-1].length < 20 and styleAlign is (if lastArtical.layout then lastArtical.layout else undefined) + lastArtical.text += '\n' + text + if textArray[textArray.length-1].trim() isnt '' and textArray[textArray.length-1].trim() isnt '\n' + isShortParagraph = true + else + appendTextWithStyleAlign() + else + appendTextWithStyleAlign() + else + appendTextWithStyleAlign() + + if data[0] + showDebug&&console.log 'data0 is ' + JSON.stringify(data[0]) + data = data[0] + data.bgArray = [] + data.imageArray = [] + documentBody = $.parseHTML( data.body ) + documentBody.innerHTML = data.body + documentBody.host = data.host + console.log('documentBody.host = '+documentBody.host) + + for titleRule in titleRules + if url.indexOf(titleRule.prefix) > -1 + realTitle = $(documentBody).find('.'+titleRule.titleClass).text().replace(/^\s+|\s+$/g, "") + if realTitle and realTitle isnt '' + #data.host = data.title + data.title = realTitle + break + for item in hostnameMapping + if data.host is item.hostname + data.host = '摘自 ' + item.displayName + break + + #musicInfo = getMusicFromPage documentBody + extracted = extract(documentBody) + initParagraphArray(extracted) + console.log('extracted:') + console.log(extracted) + + toBeInsertedText = '' + toBeInsertedStyleAlign={} + previousIsImage = false + resortedArticle = [] + sortedImages = 0 + sortedVideos = 0 + +# musics = getMusicFromScript(url, documentBody) +# if(musics.length > 0) +# for musicInfo in musics +# resortedArticle.push {type:'music', musicInfo: musicInfo} + + if extracted.id is 'hotshare_special_tag_will_not_hit_other' + toBeProcessed = extracted + else if data.host is "www.meerlive.com" + divv = document.createElement('div') + divv.appendChild(document.createElement('div')) + toBeProcessed = divv + else + toBeProcessed = extracted.innerHTML + previousIsSpan = false + $(toBeProcessed).children().each (index,node)-> + info = {} + info.bgArray = [] + info.imageArray = [] + info.body = node.outerHTML + nodeColor = $(node).css('color') + nodeBackgroundColor = $(node).css('background-color') + #iframeNumber = $(node).find('iframe').length + console.log(' Node['+index+'] tagName '+node.tagName+' text '+node.textContent) + styleAlign={textAlign:getStyleInItem(node,'textAlign'), fontWeight:getStyleInItem(node,'fontWeight')} + # console.log(' Got style '+JSON.stringify(styleAlign)); + if node.tagName is 'BR' + if toBeInsertedText.length > 0 + appendParagraph(resortedArticle, toBeInsertedText, toBeInsertedStyleAlign) + toBeInsertedText = '' + toBeInsertedStyleAlign = {} + previousIsSpan = false + return true + else if node.tagName is 'MUSICEXTRACTED' + if toBeInsertedText and toBeInsertedText isnt '' + appendParagraph(resortedArticle, toBeInsertedText, undefined) + toBeInsertedText = '' + + playUrl=node.getAttribute('playUrl') + image=node.getAttribute('image') + songName=node.getAttribute('songName') + singerName=node.getAttribute('singerName') + resortedArticle.push {type:'music', musicInfo: { + playUrl:playUrl + image:image + songName:songName + singerName:singerName + }} + previousIsSpan = false + return true + else + # console.log 'get htmldata is ----------' + # console.log htmldata + videoInfo = getPossibleVideo(node,htmldata) + if videoInfo + sortedVideos++ + resortedArticle.push({type:'video', videoInfo:videoInfo}) + if videoInfo.imageUrl + data.imageArray.push videoInfo.imageUrl + return true + text = $(node).text() + if text and text isnt '' + # text = text.replace(/\s\s\s+/g, '') + text = text.replace(/\s{3,}/g, '\r\n\r\n').trim() # 保证换行时至少有一行空行 + console.log('text '+text) + if node.tagName == 'IFRAME' + previousIsSpan = false + node.width = '100%' + node.width = '100%' + node.height = '100%' + node.src = removeURLParameter(node.src,'width') + node.src = removeURLParameter(node.src,'height') + node.src = node.src.replace(/https:\/\//g, 'http://') + node.removeAttribute("style") + dataSrc = node.getAttribute('data-src') + if dataSrc + dataSrc = removeURLParameter(dataSrc,'width') + dataSrc = removeURLParameter(dataSrc,'height') + if dataSrc.indexOf('/') is 0 + dataSrc = data.protocol + '//' + documentBody.host + dataSrc + dataSrc = dataSrc.replace(/https:\/\//g, 'http://') + dataSrc = dataSrc.replace(/v.qq.com\/iframe\/preview.html/g, 'v.qq.com/iframe/player.html') + node.setAttribute('data-src',dataSrc) + node.src = dataSrc + showDebug&&console.log(node.outerHTML) + if toBeInsertedText and toBeInsertedText isnt '' + appendParagraph(resortedArticle, toBeInsertedText, undefined) + toBeInsertedText = '' + console.log("Frank.iframe: node.outerHTML="+node.outerHTML); + resortedArticle.push {type:'iframe',iframe:node.outerHTML} + else if node.tagName == 'IMG' + previousIsSpan = false + dataSrc = $(node).attr('data-src') + dataLISrc = $(node).attr('data-li-src') + if dataSrc and dataSrc isnt '' + src = dataSrc + else if dataLISrc and dataLISrc isnt '' + src = dataLISrc + else + src = $(node).attr('src') + if src and src isnt '' + src = src.replace(/&/g, '&').replace("tp=webp","tp=jpeg") + unless src.startsWith('http') + if src.startsWith('//') + src = data.protocol + src + else if src.startsWith('/') + src = data.protocol + '//' + documentBody.host + '/' + src + showDebug&&console.log 'Image Src: ' + src + previousIsImage = true + if toBeInsertedText and toBeInsertedText isnt '' + appendParagraph(resortedArticle, toBeInsertedText, undefined) + toBeInsertedText = '' + sortedImages++; + resortedArticle.push {type:'image',imageUrl:src} + data.imageArray.push src + else if info.body + grabImagesInHTMLString(info) + if info.imageArray.length > 0 + showDebug&&console.log(' Got image') + previousIsImage = true + previousIsSpan = false + if toBeInsertedText and toBeInsertedText isnt '' + appendParagraph(resortedArticle, toBeInsertedText, undefined) + toBeInsertedText = '' + for imageUrl in info.imageArray + if imageUrl.startsWith('http://') or imageUrl.startsWith('https://') + showDebug&&console.log(' save imageUrl ' + imageUrl) + sortedImages++; + resortedArticle.push {type:'image',imageUrl:imageUrl} + data.imageArray.push imageUrl + else if info.bgArray.length > 0 + showDebug&&console.log(' Got Background image') + previousIsImage = true + previousIsSpan = false + if toBeInsertedText and toBeInsertedText isnt '' + appendParagraph(resortedArticle, toBeInsertedText, undefined) + toBeInsertedText = '' + for imageUrl in info.bgArray + if imageUrl.startsWith('http://') or imageUrl.startsWith('https://') + showDebug&&console.log(' save background imageUrl ' + imageUrl) + sortedImages++ + resortedArticle.push {type:'image',imageUrl:imageUrl} + data.imageArray.push imageUrl + if node.tagName is 'P' + previousIsSpan = false + if toBeInsertedText.length > 0 + appendParagraph(resortedArticle, toBeInsertedText, toBeInsertedStyleAlign) + if text and text isnt '' + toBeInsertedText = text + else + toBeInsertedText = '' + toBeInsertedStyleAlign = styleAlign + else if text and text isnt '' + if node.tagName is 'OL' + textArray = text.split('\n') + if textArray.length > 0 + count = 0 + $(node).children().each(()-> + console.log("this.value = "+this.value) + ) + for i in [0..textArray.length-1] + if toBeInsertedText.length < 20 + if toBeInsertedText.length > 0 + toBeInsertedText += '\n' + if textArray[i].length > 0 + toBeInsertedText += ' '+(parseInt(count++,10)+1).toString()+'. '+textArray[i] + else + if textArray[i].length > 0 + appendParagraph(resortedArticle, toBeInsertedText, toBeInsertedStyleAlign) + toBeInsertedText = ' '+(parseInt(count++,10)+1).toString()+'. '+textArray[i] + toBeInsertedStyleAlign = styleAlign + return + previousIsImage = false + showDebug&&console.log ' Got text in this element('+toBeInsertedText.length+') '+text + showDebug&&console.log 'Text ['+text+'] color is '+nodeColor+' nodeBackgroundColor is '+nodeBackgroundColor + ### + if importColor and nodeColor and nodeColor isnt '' + if toBeInsertedText.length > 0 + toBeInsertedText += '\n' + resortedArticle.push {type:'text',text:toBeInsertedText} + toBeInsertedText = '' + resortedArticle.push {type:'text',text:text,color:nodeColor,backgroundColor:nodeBackgroundColor} + else + ### + #console.log('Get style '+$(node).attr('style')); + if toBeInsertedText.length is 0 + toBeInsertedStyleAlign = styleAlign + if node.tagName is 'SPAN' or node.tagName is 'STRONG' + toBeInsertedText +=text + previousIsSpan = true + else if previousIsSpan is true + toBeInsertedText += text + previousIsSpan = false + text = '' + else if toBeInsertedText.length < 20 and styleAlign is toBeInsertedStyleAlign + if toBeInsertedText.length > 0 + toBeInsertedText += '\n' + toBeInsertedText += text + else + if toBeInsertedText.length > 0 + appendParagraph(resortedArticle, toBeInsertedText, toBeInsertedStyleAlign) + toBeInsertedText = text; + toBeInsertedStyleAlign = styleAlign; + if toBeInsertedText and toBeInsertedText isnt '' + appendParagraph(resortedArticle, toBeInsertedText, undefined) + if sortedImages < 1 and sortedVideos < 1 + console.log('no image ?') + grabImagesInHTMLString(data) + if data.imageArray.length > 0 + for imageUrl in data.imageArray + if imageUrl.startsWith('http://') or imageUrl.startsWith('https://') + showDebug&&console.log(' save imageUrl ' + imageUrl) + resortedArticle.push {type:'image',imageUrl:imageUrl} + else if data.bgArray.length > 0 + for imageUrl in data.bgArray + if imageUrl.startsWith('http://') or imageUrl.startsWith('https://') + showDebug&&console.log(' save background imageUrl ' + imageUrl) + resortedArticle.push {type:'image',imageUrl:imageUrl} + data.resortedArticle = resortedArticle + # showDebug&&console.log('Resorted Article is ' + JSON.stringify(data.resortedArticle)) + callback data + @getContentListsFromUrl = (inappBrowser,url,callback)-> + inappBrowser.executeScript { + code: ' + var returnJson = {}; + if(document.title){ + returnJson["title"] = document.title; + } + if(location.host){ + returnJson["host"] = location.host; + } + if(location.host == "www.meerlive.com"){ + returnJson["scripts"] = document.scripts[11].innerHTML; + } + if(document.body){ + returnJson["body"] = document.body.innerHTML; + returnJson["bodyLength"] = document.body.innerHTML.length; + } + if(window.location.protocol){ + returnJson["protocol"] = window.location.protocol; + } + if(location.host === "m.youku.com"){ + try{returnJson["_video_src"] = BuildVideoInfo._videoInfo._videoSegsDic.streams.default.mp4[0].src;}catch(e){} + } + returnJson; + '} + ,(data)-> + unless data.host + a = document.createElement('a') + a.href = url + data.host = a.hostname + + if data.body + data.body = data.body.replace(/()/gim, '$1$2') + data.bodyLength = data.body.length + + console.log 'getContentListsFromUrl _html2data2 data is ' + console.log data + console.log "scripts is " + data.scripts + _html2data2(url, data, callback) + @_getContentListsFromUrl_test = (url, callback)-> + headers = { + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' + 'Content-Type': 'text/html; charset=utf-8' + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4' + } + Meteor.call 'http_get', url , headers, (error, result)-> + if(error) + console.log(error) + else + returnJson = {} + html = document.createElement('html') + html.innerHTML = result.content + + if(html.getElementsByTagName('title').length > 0) + returnJson["title"] = html.getElementsByTagName('title')[0].innerText + if(html.getElementsByTagName('body').length > 0) + returnJson["body"] = html.getElementsByTagName('body')[0].innerHTML + returnJson["bodyLength"] = returnJson["body"].length + showDebug && console.log(returnJson) +# treeWalker = document.createTreeWalker( +# html, NodeFilter.SHOW_TEXT +# { +# acceptNode : (node)-> +# console.log(node) +# return NodeFilter.FILTER_REJECT +# } +# false +# ) + console.log '_getContentListsFromUrl_test _html2data2 data is ' + console.log JSON.stringify returnJson + _html2data2(url, returnJson, callback) diff --git a/client/location.coffee b/client/location.coffee index 728dfbb0a..bd7d1660f 100644 --- a/client/location.coffee +++ b/client/location.coffee @@ -16,6 +16,7 @@ updateFromThirdPartWebsite = ()-> Meteor.users.update Meteor.userId(),{$set:{'profile.location':address}} console.log 'Set address to ' + address window.updateMyOwnLocationAddress = ()-> + return updateFromThirdPartWebsite() console.log('Update location now') url = "http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js" $.getScript url, (data, textStatus, jqxhr)-> @@ -37,10 +38,4 @@ window.updateMyOwnLocationAddress = ()-> else updateFromThirdPartWebsite() else - updateFromThirdPartWebsite() -Accounts.onLogin(()-> - Meteor.setTimeout ()-> - console.log("Accounts.onLogin") - window.updateMyOwnLocationAddress(); - ,3000 -) \ No newline at end of file + updateFromThirdPartWebsite() \ No newline at end of file diff --git a/client/main.js b/client/main.js index f2ff91324..c0eae4362 100644 --- a/client/main.js +++ b/client/main.js @@ -1,11 +1,383 @@ +Template.registerHelper('isIOS',function(){ + return ( navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false ); +}); + +Template.registerHelper('isAndroid',function(){ + return navigator.userAgent.toLowerCase().indexOf("android") > -1; +}); + +// --- +// generated by coffee-script 1.9.2 + +if (Meteor.isCordova) { + getHotPostsData = function() { + Meteor.call('getHottestPosts',function(err, res){ + if (!err) { + console.log('-------------------getHottestPosts') + console.log(res) + return Session.set('hottestPosts', res) + } + }); + } + Deps.autorun(function(){ + if( Session.get('persistentLoginStatus') && !Meteor.userId() && !Meteor.loggingIn()){ + Session.setPersistent('persistentLoginStatus',false); + window.plugins.toast.showLongCenter("登录超时,需要重新登录~"); + + var pages = ['/user', '/bell', '/search']; + if(pages.indexOf(location.pathname) != -1) + PUB.page('/'); + } + }); + + window.updatePushNotificationToken = function(type,token){ + Deps.autorun(function(){ + if(Meteor.user()){ + if(token != Session.get("token")) + { + console.log("type:"+type+";token:"+token); + Meteor.users.update({_id: Meteor.user()._id}, {$set: {type: type, token: token}}); + Meteor.call('updatePushToken' ,{type: type, token: token,userId:Meteor.user()._id}); + // Meteor.call('refreshAssociatedUserToken' ,{type: type, token: token}); + Session.set("token", token); + } + } else { + Session.set("token", ''); + } + }); + }; + window.checkNotificationServicesEnabled = function(){ + var callbackHandle = function(data){ + console.log('data.isEnabled:'+data.isEnabled); + var status = data.isEnabled ? 'on' :'off'; + Meteor.call('update_WorkAI_PushNotifacaton_Status',Meteor.userId(),status); + var hasShow = Session.get('notificationConfim'); + + if (!data.isEnabled && !hasShow) { + navigator.notification.confirm('能及时收到出现提醒',function(index){ + Session.set('notificationConfim','showed'); + if (index == 2) { + if (device.platform === 'iOS') { + window.plugins.appsetup.openSettings(); + } + else{ + window.plugins.jPushPlugin.goToSet(); + } + } + },'开启推送',['以后再说','马上开启']); + } + else if (data.isEnabled){ + Session.set('notificationConfim',null); + } + }; + if (device.platform === 'iOS') { + PushNotification.hasPermission(callbackHandle); + } + else{ + window.plugins.jPushPlugin.getUserNotificationSettings(function(result) { + var data = {}; + if(result == 0) { + // 系统设置中已关闭应用推送。 + data.isEnabled = false; + } else if(result > 0) { + // 系统设置中打开了应用推送。 + data.isEnabled = true; + } + callbackHandle(data); + }); + } + }; + Meteor.startup(function(){ + Session.setDefault('hottestPosts', []) + getUserLanguage = function() { + var lang; + lang = void 0; + if (navigator && navigator.userAgent && (lang = navigator.userAgent.match(/android.*\W(\w\w)-(\w\w)\W/i))) { + lang = lang[1]; + } + if (!lang && navigator) { + if (navigator.language) { + lang = navigator.language; + } else if (navigator.browserLanguage) { + lang = navigator.browserLanguage; + } else if (navigator.systemLanguage) { + lang = navigator.systemLanguage; + } else { + if (navigator.userLanguage) { + lang = navigator.userLanguage; + } + } + lang = lang.substr(0, 2); + } + return lang; + }; + document.addEventListener("deviceready", onDeviceReady, false); + // PhoneGap加载完毕 + function onDeviceReady() { + // 按钮事件 + // console.log('<------- onDeviceReady ----->'); + checkShareExtension(); + // getHotPostsData(); + navigator.splashscreen.hide(); + document.addEventListener("backbutton", eventBackButton, false); // 返回键 + document.addEventListener("pause", eventPause, false);//挂起 + document.addEventListener("resume", eventResume, false); + + //checkNewVersion2(); + + TAPi18n.precacheBundle = true; + // if(isUSVersion){ + // Session.set("display_lang",'en'); + // Cookies.set("display-lang","en",360); + // AppRate.preferences.useLanguage = 'en'; + // } + if(Cookies.check("display-lang")){ + var displayLang = Cookies.get("display-lang"); + Session.set("display_lang",displayLang) + // if(displayLang === 'en'){ + // AppRate.preferences.useLanguage = 'en'; + // } + // else if(displayLang ==='zh') + // { + AppRate.preferences.useLanguage = 'zh-Hans'; + // } + TAPi18n.setLanguage("zh") + .done(function () { + console.log("zh"); + }) + .fail(function (error_message) { + // Handle the situation + console.log(error_message); + }); + } else { + Session.set("display_lang","zh") + AppRate.preferences.useLanguage = 'zh-Hans'; + TAPi18n.setLanguage("zh") + .done(function () { + console.log("en"); + }) + .fail(function (error_message) { + // Handle the situation + console.log(error_message); + }); + } + TAPi18n.setLanguage("zh") + //当用户第八次使用该软件时提示评价app + AppRate.preferences.usesUntilPrompt = 7; + AppRate.preferences.storeAppURL.ios = '957024953'; + AppRate.preferences.storeAppURL.android = 'http://a.app.qq.com/o/simple.jsp?pkgname=org.hotshare.everywhere'; + AppRate.promptForRating(false); + //universalLinks.subscribe('openSimpleChatGroup',onSimpleChatPageRequested); + zeroconfWatch(); + window.checkNotificationServicesEnabled(); + } + + // openNewsDetailedPage Event Handler + function onSimpleChatPageRequested(eventData) { + console.log('Showing to user details page for some news'); + // do some work to show detailed page + } + + function checkShareExtension(){ + if(device.platform === 'iOS') { + window.plugins.shareExtension.getShareData(function(data) { + if(data){ + CustomDialog.show(data); + } + }, function() {Session.set('wait_import_count',false);}); + } + } + var lastPauseDate = null; + function restartApplication() { + var initialHref = window.location.href; + // Show splash screen (useful if your app takes time to load) + navigator.splashscreen.show(); + // Reload original app url (ie your index.html file) + window.location = initialHref; + } + function zeroconfWatch(){ + var zeroconf = cordova.plugins.zeroconf; + zeroconf.watch('_DeepEye._tcp', 'local.', function(result) { + var action = result.action; + var service = result.service; + /* service : { + 'domain' : 'local.', + 'type' : '_zhifa._tcp.', + 'name': 'cloudrouter', + 'port' : 4000, + 'hostname' : 'Android.local.', + 'ipv4Addresses' : [ '192.168.31.103' ], + 'ipv6Addresses' : [ '2001:0:5ef5:79fb:10cb:1dbf:3f57:feb0' ], + 'txtRecord' : { + 'foo' : 'bar' + } + } */ + /* + Meteor.call('upsetDeepVideoDevices', result); + if (action == 'added') { + console.log('service added', JSON.stringify(service)); + Deps.autorun(function(){ + if(Meteor.userId()){ + Session.set('canShowDeepVideoAnalysisEnter', true); + PUB.Toptip(' 发现一台新设备',{autohide:true, timeout:10000, service: service},function(event, options){ + console.log(event); + console.log(JSON.stringify(options)); + PUB.page('/deepVideoAnalysis'); + }); + } + }); + } else { + console.log('service removed', JSON.stringify(service)); + }*/ + }); + } + + function eventResume(){ + if ($('body').text().length === 0 || $('body').text().indexOf("Oops, looks like there's no route on the client or the server for url:") > -1 ) { + //restartApplication(); + //location.reload(); + } + if (Meteor.status().connected !== true) + Meteor.reconnect(); + //checkNewVersion2(); + if (Meteor.user()) { + setTimeout(function(){ + console.log('Refresh Main Data Source when resume'); + if (Meteor.isCordova) { + window.refreshMainDataSource(); + window.checkNotificationServicesEnabled(); + if(Meteor.user().profile.waitReadCount > 0){ + Meteor.users.update({_id: Meteor.user()._id}, {$set: {'profile.waitReadCount': 0}}); + } + } + },1*1000) + } + //mqttEventResume(); + if (lastPauseDate != null) { + var now = new Date(); + if (now.getTime() - lastPauseDate.getTime() > 5*60*1000) { + //restartApplication(); + } + } + try{ + console.log('try reconnect mqtt') + // mqtt_connection._reconnect(); + mqttEventResume(); + } catch (error) { + console.log('mqtt reconnect Error=',error); + } + } + function eventPause(){ + mqttEventPause(); + lastPauseDate = new Date(); + } + + function eventBackButton(){ + // 显示tips时 + if(Tips.isShow()) + return Tips.close(); + + // if on add hyperlink page, just disappear that page + if ($('#show_hyperlink').css('display') == 'block') { + console.log('##RDBG hide add hyperlink page'); + $('#add_posts_content').show(); + $('#show_hyperlink').hide(); + return; + } + + // 编辑post时回退 + if(withAutoSavedOnPaused) { + if (location.pathname === '/add') { + Template.addPost.__helpers.get('saveDraft')() + } + } + + if ($('#swipebox-overlay').length > 0) { + $.swipebox.close(); + return; + } + + // 阅读私信时返回 + if(Session.equals('inPersonalLetterView',true)) { + Session.set('inPersonalLetterView',false); + $('body').css('overflow-y','auto'); + $('.personalLetterContent,.bellAlertBackground').fadeOut(300); + return; + } + var currentRoute = Router.current().route.getName(); + if (currentRoute == 'myPosts'){ + if (isHotPostsChanged()) { + PUB.confirm("您改变了热门帖子, 要保存吗?", function(){ + console.log('##RDBG confirm callback'); + saveHotPosts() + }); + } + PUB.back(); + } + else if (currentRoute == 'deal_page'){ + if (Session.get("dealBack") == "register"){ + Router.go('/signupForm'); + } else if (Session.get("dealBack") == "anonymous"){ + Router.go('/loginForm'); + Meteor.setTimeout(function(){ + $('.agreeDeal').css('display',"block") + },10); + } + } else if (currentRoute == "checkInOutMsgList"){ + var msgSession = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId:sysMsgToUserId}); + if (msgSession) + SimpleChat.MsgSession.update({_id:msgSession._id},{$set:{count:0}}); + PUB.back(); + } + else if (currentRoute == "recoveryForm"){ + Router.go('/loginForm'); + } else if (currentRoute == undefined || currentRoute =="search" || currentRoute =="add" || currentRoute =="bell" || currentRoute =="user" || currentRoute == "authOverlay") { + window.plugins.toast.showShortBottom('再点击一次退出!'); + document.removeEventListener("backbutton", eventBackButton, false); // 注销返回键 + document.addEventListener("backbutton", exitApp, false);// 绑定退出事件 + // 3秒后重新注册 + var intervalID = window.setInterval(function() { + window.clearInterval(intervalID); + document.removeEventListener("backbutton", exitApp, false); // 注销返回键 + document.addEventListener("backbutton", eventBackButton, false); // 返回键 + }, 3000); + }else{ + //history.back(); + if($('.customerService,.customerServiceBackground').is(":visible")){ + $('.customerService,.customerServiceBackground').fadeOut(300); + } else { + var v = SimpleChat.simple_chat_page_stack.pop(); + if (v) { + return Blaze.remove(v); + } + PUB.back(); + } + } + } + + function exitApp() { + navigator.app.exitApp(); + } + }); +} + if (Meteor.isClient) { - Session.set("DocumentTitle",'点圈'); + Session.set("DocumentTitle",'来了吗'); Deps.autorun(function(){ + if(Meteor.userId()){ + //Meteor.subscribe("topics"); + //Meteor.subscribe("topicposts"); + Meteor.subscribe('get-workai-user-relation',Meteor.userId()); + Meteor.subscribe('getPushFollow') + // getHotPostsData(); + } document.title = Session.get("DocumentTitle"); }); } -Tracker.autorun(function(){ - if(Meteor.userId()) - Meteor.subscribe('loginFeeds'); -}); \ No newline at end of file +/*Reload._onMigrate(function (retry) { + if (Meteor.isCordova) { + cordova.exec(callback, console.error, 'WebAppLocalServer', 'switchPendingVersion', []); + } + return [true, {}]; +});*/ diff --git a/client/main.less b/client/main.less new file mode 100644 index 000000000..56d7a5cc7 --- /dev/null +++ b/client/main.less @@ -0,0 +1,38 @@ +@import "{}/imports/ui/stylesheets/addPost.less"; +@import "{}/imports/ui/stylesheets/addTopicComment.less"; +@import "{}/imports/ui/stylesheets/bell.less"; +@import "{}/imports/ui/stylesheets/chatContent.less"; +@import "{}/imports/ui/stylesheets/chatGroups.less"; +@import "{}/imports/ui/stylesheets/color.less"; +@import "{}/imports/ui/stylesheets/comFadeIn.less"; +@import "{}/imports/ui/stylesheets/commentBar.less"; +@import "{}/imports/ui/stylesheets/contactsList.less"; +@import "{}/imports/ui/stylesheets/dashboard.less"; +@import "{}/imports/ui/stylesheets/discover.less"; +@import "{}/imports/ui/stylesheets/followers.less"; +@import "{}/imports/ui/stylesheets/footer.less"; +@import "{}/imports/ui/stylesheets/header.less"; +@import "{}/imports/ui/stylesheets/home.less"; +@import "{}/imports/ui/stylesheets/hotPosts.less"; +@import "{}/imports/ui/stylesheets/loadingpost.less"; +@import "{}/imports/ui/stylesheets/loginForm.less"; +@import "{}/imports/ui/stylesheets/mainImagesList.less"; +@import "{}/imports/ui/stylesheets/management.less"; +@import "{}/imports/ui/stylesheets/me.less"; +@import "{}/imports/ui/stylesheets/messageDialog.less"; +@import "{}/imports/ui/stylesheets/messageDialogInfo.less"; +@import "{}/imports/ui/stylesheets/messageGroup.less"; +@import "{}/imports/ui/stylesheets/newPostsMsg.less"; +@import "{}/imports/ui/stylesheets/padding.less"; +@import "{}/imports/ui/stylesheets/postNotFound.less"; +@import "{}/imports/ui/stylesheets/progressBar.less"; +@import "{}/imports/ui/stylesheets/reportPost.less"; +@import "{}/imports/ui/stylesheets/search.less"; +@import "{}/imports/ui/stylesheets/showPosts.less"; +@import "{}/imports/ui/stylesheets/signupForm.less"; +@import "{}/imports/ui/stylesheets/socialBar.less"; +@import "{}/imports/ui/stylesheets/splashScreen.less"; +@import "{}/imports/ui/stylesheets/thanksReport.less"; +@import "{}/imports/ui/stylesheets/unpublish.less"; +@import "{}/imports/ui/stylesheets/user.less"; +@import "{}/imports/ui/stylesheets/userProfile.less"; diff --git a/client/mqtt_mq_client.js b/client/mqtt_mq_client.js new file mode 100644 index 000000000..2b771d092 --- /dev/null +++ b/client/mqtt_mq_client.js @@ -0,0 +1,474 @@ +/** + * Created by simba on 5/12/16. + */ +if(Meteor.isClient && !withNativeMQTTLIB){ + var myMqtt = Paho.MQTT; + var undeliveredMessages = []; + var unsendMessages = []; + var uninsertMessages = []; + var uninsertMessages_msgKey = []; + var init_timer = null; + mqtt_connection = null; + Session.set('history_message',false); + var noMessageTimer = null; + + //mqtt_connected = false; + var onMessageArrived = function(message, msgKey,len, mqttCallback) { + console.log("onMessageArrived:"+message.payloadString); + console.log('message.destinationName= '+message.destinationName); + console.log('message= ', msgKey, JSON.stringify(message)); + var history = Session.get('history_message'); + if(noMessageTimer){ + Meteor.clearTimeout(noMessageTimer); + noMessageTimer = null; + } + if(history && len == 0){ + console.log('sync finish'); + Session.set('history_message',false); + } + function reciveMsg(message, msgKey){ + try { + var topic = message.destinationName; + console.log('on mqtt message topic: ' + topic + ', message: ' + message.payloadString); + if (topic.startsWith('/msg/g/') || topic.startsWith('/msg/u/')) + { + SimpleChat.onMqttMessage(topic, message.payloadString, msgKey, mqttCallback); + var isTesting = Session.get('isStarting'); + if(isTesting && (topic == '/msg/g/'+isTesting.group_id) && isTesting.isTesting){ + GroupInstallTest(message.payloadString); + } + } + else if (topic.startsWith('/msg/l/')) + SimpleChat.onMqttLabelMessage(topic, message.payloadString, msgKey, mqttCallback); + } catch (ex) { + console.log('exception onMqttMessage: ' + ex); + } + } + if (Session.equals('GroupUsersLoaded',true)) { + setImmediateWrap(function() { + reciveMsg(message, msgKey); + }); + } + else{ + console.log('subscribe get my group!'); + uninsertMessages.push(message); + uninsertMessages_msgKey.push(msgKey) + Meteor.subscribe('get-my-group', Meteor.userId(),{ + onReady:function(){ + Session.set('GroupUsersLoaded',true); + console.log('GroupUsersLoaded!!'); + if (uninsertMessages.length > 0) { + for (var i = 0; i < uninsertMessages.length; i++) { + reciveMsg(uninsertMessages[i], uninsertMessages_msgKey[i]); + } + uninsertMessages = []; + uninsertMessages_msgKey = []; + } + } + }); + } + }; + initMQTT = function(clientId){ + if(!mqtt_connection){ + var pahoMqttOptions = { + timeout: 30, + keepAliveInterval:60, + cleanSession: false, + onSuccess:onConnect, + onFailure:onFailure, + reconnect: true + }; + //mqtt_connection=myMqtt.connect('ws://tmq.tiegushi.com:80',mqttOptions); + mqtt_connection=new Paho.MQTT.Client('mq.tiegushi.com', Number(80), clientId); + //mqtt_connection=new Paho.MQTT.Client('183.136.238.174', Number(8083), clientId); + mqtt_connection.onConnectionLost = onConnectionLost; + mqtt_connection.onMessageArrived = onMessageArrived; + mqtt_connection.onMessageDelivered = onMessageDelivered; + mqtt_connection.connect(pahoMqttOptions); + function clearUndeliveredMessages() { + console.log('clearUndeliveredMessages: undeliveredMessages.length='+undeliveredMessages.length); + while (undeliveredMessages.length > 0) { + console.log('undeliveredMessages.length='+undeliveredMessages.length); + var undeliveredMessage = undeliveredMessages.shift(); + var topic = undeliveredMessage.topic; + var message = undeliveredMessage.message; + var onMessageDeliveredCallback = undeliveredMessage.onMessageDeliveredCallback; + addToUnsendMessaages(topic, message, onMessageDeliveredCallback, 10*1000); + } + }; + + function onConnect() { + // Once a connection has been made, make a subscription and send a message. + console.log("mqtt onConnect"); + // get MQTT_TIME_DIFF + var url = 'http://'+server_domain_name+'/restapi/date/'; + $.get(url,function(data){ + if(data){ + MQTT_TIME_DIFF = Number(data) - Date.now(); + console.log('MQTT_TIME_DIFF===',MQTT_TIME_DIFF) + } + }); + console.log('Connected to mqtt server'); + noMessageTimer = Meteor.setTimeout(function(){ + console.log('no message to receive'); + Session.set('history_message',false); + },2*1000); + //mqtt_connection.subscribe('workai'); + subscribeMyChatGroups(); + subscribeMqttUser(Meteor.userId()); + + setTimeout(function(){ + console.log("sendMqttMessage /presence") + sendMqttMessage('/presence/'+Meteor.userId(),{online:true}) + // if (unsendMessages.length > 0) { + // var unsendMsg; + // var fifo = unsendMessages.reverse(); + // // Send all queued messages down socket connection + // console.log('onConnect: Send all unsendMessages message: '+unsendMessages.length); + // var len = unsendMessages.length + // var i = 0; + // while ((unsendMsg = fifo.pop())) { + // var topic = unsendMsg.topic; + // var message = unsendMsg.message; + // var callback = unsendMsg.callback; + // var timeoutTimer = unsendMsg.timer; + // clearTimeout(timeoutTimer); + // timeoutTimer = null; + // sendMqttMessage(topic, message, callback); + // console.log('unsendMessages send message='+JSON.stringify(message)); + // i++ + // if (i >= len){ + // break; + // } + // } + // } + }, 20*1000) + }; + function onFailure(msg) { + console.log('mqtt onFailure: errorCode='+msg.errorCode); + clearUndeliveredMessages(); + // setTimeout(function(){ + console.log('MQTT onFailure, reconnecting...'); + mqtt_connection.connect(pahoMqttOptions); + // }, 1000); + }; + function onConnectionLost(responseObject) { + //mqtt_connected = false; + console.log('MQTT connection lost.') + clearUndeliveredMessages(); + if (responseObject.errorCode !== 0) { + console.log("onConnectionLost: "+responseObject.errorMessage); + } + // setTimeout(function(){ + console.log('MQTT onConnectionLost, reconnecting...'); + mqtt_connection.connect(pahoMqttOptions); + // }, 1000); + }; + function onMessageDelivered(message) { + console.log('MQTT onMessageDelivered: "' + message.payloadString + '" delivered'); + try { + var messageObj = JSON.parse(message.payloadString); + var msgId = messageObj.msgId; + for (var i=0; i>> this is admin, send group message to myself') + onMessageOld("/msg/g/" + group_id, JSON.stringify(message),callback); + } + else { + sendMqttMessage("/msg/g/" + group_id, message,callback); + } + }; + sendMqttUserMessage=function(user_id, message,callback) { + // console.log('sendMqttUserMessage:', message); + sendMqttMessage("/msg/u/" + user_id, message,callback); + }; + sendMqttGroupLabelMessage=function(group_id, message,callback) { + message.create_time = new Date(Date.now() + MQTT_TIME_DIFF); + if(Meteor.user() && Meteor.user().profile && Meteor.user().profile.userType == 'admin') { + console.log('>>> this is admin, send label message to myself') + onMessageOld("/msg/l/" + group_id, JSON.stringify(message)); + console.log('====sraita===='+JSON.stringify(message)); + if(message.is_admin_relay){ + sendMqttMessage("/msg/l/" + group_id, JSON.stringify(message),callback); + } + } + else { + sendMqttMessage("/msg/l/" + group_id, message,callback); + } + }; + } + } + uninitMQTT = function() { + try { + if (mqtt_connection) { + mqtt_connection.disconnect(); + //mqtt_connected = false; + mqtt_connection = null; + } + } catch (error) { + console.log(error) + } + } + subscribeMyChatGroups = function() { + Meteor.subscribe('get-my-group', Meteor.userId(),{ + onReady:function(){ + Session.set('GroupUsersLoaded',true); + } + }); + + if(Meteor.user() && Meteor.user().profile && Meteor.user().profile.userType == 'admin') { + if (mqtt_connection) { + console.log('sub all groups mqtt'); + mqtt_connection.subscribe('/msg/g/#', {qos:1, onSuccess:onSuccess, onFailure:onFailure}); + mqtt_connection.subscribe('/msg/l/#', {qos:1, onSuccess:onSuccess, onFailure:onFailure}); // label 消息 + function onSuccess() { + console.log('mqtt subscribe group msg successfully.'); + } + function onFailure() { + console.log('mqtt subscribe group msg failed.'); + } + } + } + else { + SimpleChat.GroupUsers.find({user_id: Meteor.userId()}).observe({ + added: function(document) { + subscribeMqttGroup(document.group_id); + }, + changed: function(newDocument, oldDocument){ + if (oldDocument.group_id === newDocument.group_id) + return; + + unsubscribeMqttGroup(oldDocument.group_id); + subscribeMqttGroup(newDocument.group_id); + }, + removed: function(document){ + unsubscribeMqttGroup(document.group_id); + } + }); + } + } + getMqttClientID = function() { + var client_id = window.localStorage.getItem('mqtt_client_id'); + if (!client_id) { + client_id = 'WorkAIC_' + (new Mongo.ObjectID())._str; + window.localStorage.setItem('mqtt_client_id', client_id); + } + console.log("##RDBG getMqttClientID: " + client_id); + return client_id; + }; + function startMQTT() { + if (SimpleChat.checkMsgSessionLoaded()) { + console.log("GroundDB all loaded!"); + initMQTT(Meteor.userId()); + } else { + console.log("Waiting for loading GroundDB..."); + if (init_timer) { + clearTimeout(init_timer); + init_timer = null; + } + init_timer = setTimeout(function(){ + startMQTT(); + },500); + } + } + mqttEventResume = function() { + console.log('##RDBG, mqttEventResume, reestablish mqtt connection'); + setTimeout(function() { + if(Meteor.userId()){ + //initMQTT(getMqttClientID()); + //initMQTT(Meteor.userId()); + startMQTT(); + } + }, 1000); + /*try { + if (mqtt_connection) { + console.log('try reconnect mqtt'); + mqtt_connection._reconnect(); + } + } + catch (ex) { console.log('mqtt reconnect ex=', ex); }*/ + }; + mqttEventPause = function() { + console.log('##RDBG, mqttEventPause, disconnect mqtt'); + uninitMQTT(); + }; + Deps.autorun(function(){ + if(Meteor.userId()){ + startMQTT(); + } else { + uninitMQTT(); + } + }); +} diff --git a/client/mqtt_mq_client_native.js b/client/mqtt_mq_client_native.js new file mode 100644 index 000000000..672a69ec6 --- /dev/null +++ b/client/mqtt_mq_client_native.js @@ -0,0 +1,547 @@ +/** + * Created by simba on 5/12/16. + */ + +function isJSON(message) { + if (typeof (message) == 'object' && + Object.prototype.toString.call(message).toLowerCase() == '[object object]' && !message.length) { + return true; + } else { + return false; + } +} + +if (Meteor.isClient && withNativeMQTTLIB) { + var network_status = ''; + + Meteor.startup(function () { + var undeliveredMessages = []; + var unsendMessages = []; + var uninsertMessages = []; + var uninsertMessages_msgKey = []; + var init_timer = null; + var connected = false; + var noMessageTimer = null; + Session.set('history_message', false); + Session.set('offlineMsgOverflow', false); + + //mqtt_connected = false; + function check_if_message_sent_byself(message) { + if (Meteor.userId() && message && message.form && message.form.id) { + return message.form.id === Meteor.userId(); + } + return false; + } + + initMQTT = function (clientId) { + // 初始化MQTT时,检查queue队列是否超过emq的queue_len配置,当前配置是2000 + Meteor.call('getMqttSessionInfo', Meteor.userId(), function(err, result) { + if (err) { + console.log(err); + return; + } + + if (result && result['mqueue_len'] > 1999) { + Session.set('offlineMsgOverflow', true); + } + }); + + if (mqtt.host) { + console.log('already inited'); + //if(!connected){ + mqtt.disconnect(function () { + setTimeout(function () { + mqtt.connect(); + }, 1 * 1000); + }); + //} + return; + } + + var mqttOptions = { + username: clientId, + password: localStorage.getItem('Meteor.loginToken'), + host: 'mq.tiegushi.com', + port: 8080, + timeout: 30, + keepAlive: 10, + cleanSession: false, + qos: 1, + clientId: clientId + }; + //mqtt_connection=new Paho.MQTT.Client('mq.tiegushi.com', Number(80), clientId); + + //if(mqtt.isOnline()){ + // console.log('mqtt is already connected, skip mqtt init') + //} else { + mqtt.init(mqttOptions); + //} + //mqtt_connection.onConnectionLost = onConnectionLost; + //mqtt_connection.onMessageArrived = onMessageArrived; + //mqtt_connection.onMessageDelivered = onMessageDelivered; + mqtt.on('init', function (re) { + //mqtt.disconnect(function(){ + // double call of connect will cause app crash, just disconnect then connect + //}); + }, function () { + console.log('init failed'); + }); + + setTimeout(function () { + mqtt.connect(); + }, 1 * 1000); + + mqtt.on('connect', onConnect, onFailure); + /*mqtt.on('publish', onMessageDelivered,function(errorMessage){ + console.log('publish failed, ', errorMessage) + })*/ + mqtt.on('message', onMessageArrived); + + function clearUndeliveredMessages() { + console.log('clearUndeliveredMessages: undeliveredMessages.length=' + undeliveredMessages.length); + while (undeliveredMessages.length > 0) { + console.log('undeliveredMessages.length=' + undeliveredMessages.length); + var undeliveredMessage = undeliveredMessages.shift(); + var topic = undeliveredMessage.topic; + var message = undeliveredMessage.message; + var onMessageDeliveredCallback = undeliveredMessage.onMessageDeliveredCallback; + addToUnsendMessaages(topic, message, onMessageDeliveredCallback, 10 * 1000); + } + } + + function onConnect(conact) { + // Once a connection has been made, make a subscription and send a message. + console.log('mqtt onConnect'); + connected = true; + // get MQTT_TIME_DIFF + // TODO: 跨域问题跨域问题导致该段代码无效,后期需要处理 + // var url = 'http://' + server_domain_name + '/restapi/date/'; + // $.get(url, function (data) { + // if (data) { + // MQTT_TIME_DIFF = Number(data) - Date.now(); + // console.log('MQTT_TIME_DIFF===', MQTT_TIME_DIFF); + // } + // }); + console.log('Connected to mqtt server'); + + noMessageTimer = Meteor.setTimeout(function () { + console.log('no message to receive'); + Session.set('history_message', false); + }, 2 * 1000); + //mqtt_connection.subscribe('workai'); + subscribeMyChatGroups(); + subscribeMqttUser(Meteor.userId()); + + setTimeout(function () { + console.log('sendMqttMessage /presence'); + sendMqttMessage('/presence/' + Meteor.userId(), { + online: true + }); + }, 20 * 1000); + } + + function onFailure() { + console.log('mqtt onFailure: errorCode='); + connected = false; + clearUndeliveredMessages(); + setTimeout(function () { + //console.log('MQTT onFailure, reconnecting...'); + //mqtt_connection.connect(pahoMqttOptions); + initMQTT(); + }, 5 * 1000); + } + + function onMessageArrived(message) { + if (!isJSON(message)) { + message = JSON.parse(message); + } + console.log('onMessageArrived:' + message.message); + console.log('message.destinationName= ' + message.topic); + //console.log('message= ', msgKey, JSON.stringify(message)); + var history = Session.get('history_message'); + var msgKey = null; + var mqttCallback = null; + try { + var messageObj = JSON.parse(message.message); + if (check_if_message_sent_byself(messageObj)) { + console.log('self sent message from broker'); + onMessageDelivered(message); + return; + } + } catch (e) { + console.log('exception by JSON.parsh and check_if_message_sent_byself ', e); + } + if (noMessageTimer) { + Meteor.clearTimeout(noMessageTimer); + noMessageTimer = null; + } + /*if(history && len == 0){ + console.log('sync finish'); + Session.set('history_message',false); + }*/ + function reciveMsg(message, msgKey) { + try { + var topic = message.topic; + console.log('on mqtt message topic: ' + topic + ', message: ' + message.message); + if (topic.startsWith('/msg/g/') || topic.startsWith('/msg/u/')) { + SimpleChat.onMqttMessage(topic, message.message, msgKey, mqttCallback); + var isTesting = Session.get('isStarting'); + if (isTesting && (topic == '/msg/g/' + isTesting.group_id) && isTesting.isTesting) { + GroupInstallTest(message.message); + } + } else if (topic.startsWith('/msg/l/')) + SimpleChat.onMqttLabelMessage(topic, message.message, msgKey, mqttCallback); + } catch (ex) { + console.log('exception onMqttMessage: ' + ex); + } + } + + if (Session.equals('GroupUsersLoaded', true)) { + setImmediateWrap(function () { + reciveMsg(message, msgKey); + }); + } else { + console.log('subscribe get my group!'); + uninsertMessages.push(message); + uninsertMessages_msgKey.push(msgKey); + /** + * TODO: 该处订阅影响首页打开速度(一定要等到mqtt连接成功才能订阅到groupuser数据), + * 该处方法暂时保留,首页数据直接在首页订阅 + */ + Meteor.subscribe('get-my-group', Meteor.userId(), { + onReady: function () { + Session.set('GroupUsersLoaded', true); + console.log('GroupUsersLoaded!!'); + if (uninsertMessages.length > 0) { + for (var i = 0; i < uninsertMessages.length; i++) { + reciveMsg(uninsertMessages[i], uninsertMessages_msgKey[i]); + } + uninsertMessages = []; + uninsertMessages_msgKey = []; + } + } + }); + } + } + + function onMessageDelivered(message) { + try { + if (!isJSON(message)) { + message = JSON.parse(message); + } + var messageObj = JSON.parse(message.message); + console.log('MQTT onMessageDelivered: "' + messageObj + '" delivered'); + var msgId = messageObj.msgId; + // for (var i = 0; i < undeliveredMessages.length; i++) { + // console.log(i + ': ' + JSON.stringify(undeliveredMessages[i])); + // } + for (var i = 0; i < undeliveredMessages.length; i++) { + console.log(i + ': ' + JSON.stringify(undeliveredMessages[i])); + var undeliveredMessage = undeliveredMessages[i]; + if (undeliveredMessage && undeliveredMessage.message && (undeliveredMessage.message.msgId == msgId)) { + console.log('Found message in undeliveredMessages!'); + if (undeliveredMessage.message) { + console.log('Shift undeliveredMessage: ' + JSON.stringify(undeliveredMessage.message)); + } + if (undeliveredMessage.onMessageDeliveredCallback) { + console.log('onMessageDelivered: Call calback'); + undeliveredMessage.onMessageDeliveredCallback(null, message.message); + } + undeliveredMessages.splice(i, 1); + break; + } + } + } catch (error) { + console.log('JSON parse failed. Message should be a JSON string.'); + } + } + + function addToUnsendMessaages(topic, message, callback, timeout) { + var id; + if (typeof Mongo != 'undefined') { + id = (new Mongo.ObjectID())._str; + } else { + var dt = new Date(); + var str = (dt.getTime() + dt.getMilliseconds() + Math.random() * 1000).toString(); + id = MD5(str); + } + var timeoutTimer = setTimeout(function () { + for (var i = 0; i < unsendMessages.length; i++) { + if (unsendMessages[i].id == id) { + console.log('unsendMessages timeout: message=' + JSON.stringify(unsendMessages[i].message)); + callback && callback('failed', JSON.stringify(message)); + unsendMessages.splice(i, 1); + return; + } + } + }, timeout ? timeout : 15 * 1000); + + var unsendMsg = { + id: id, + topic: topic, + message: message, + callback: callback, + timer: timeoutTimer + }; + unsendMessages.push(unsendMsg); + console.log('unsendMessages push: message=' + JSON.stringify(message)); + } + + var conn_time = new Date().getTime(); + var onMessageOld = function (topic, message) { + var cur_time = new Date().getTime(); + if (cur_time - conn_time > 3000) { + try { + console.log('on mqtt message topic: ' + topic + ', message: ' + message); + if (topic.startsWith('/msg/g/') || topic.startsWith('/msg/u/')) + SimpleChat.onMqttMessage(topic, message); + else if (topic.startsWith('/msg/l/')) + SimpleChat.onMqttLabelMessage(topic, message); + } catch (ex) { + console.log('exception onMqttMessage: ' + ex); + } + } else { + Meteor.setTimeout(function () { + try { + console.log('on mqtt message topic: ' + topic + ', message: ' + message); + if (topic.startsWith('/msg/g/') || topic.startsWith('/msg/u/')) + SimpleChat.onMqttMessage(topic, message); + else if (topic.startsWith('/msg/l/')) + SimpleChat.onMqttLabelMessage(topic, message); + } catch (ex) { + console.log('exception onMqttMessage: ' + ex); + } + }, 3000); + } + }; + + sendMqttMessage = function (topic, message, callback) { + var msgId; + + if (typeof Mongo != 'undefined') { + msgId = (new Mongo.ObjectID())._str; + } else { + var dt = new Date(); + var str = (dt.getTime() + dt.getMilliseconds() + Math.random() * 1000).toString(); + msgId = MD5(str); + } + + if (isJSON(message)) { + var newMessage = {}; + newMessage.msgId = msgId; + for (var key in message) { // Looping through all values of the old object + newMessage[key] = message[key]; + } + message = newMessage; + } + + undeliveredMessages.push({ + topic: topic, + message: message, + onMessageDeliveredCallback: callback + }); + + console.log('sendMqttMessage:', topic, JSON.stringify(message)); + + mqtt.publish({ + topic: topic, + message: JSON.stringify(message), + qos: 1 + }, function (sentMsg) { + console.log('publish succ, ', sentMsg); + }, function (err) { + console.log('publish failed ', err); + }); + return; + + //addToUnsendMessaages(topic, message, callback); + }; + + subscribeMqttGroup = function (group_id) { + if (group_id) { + console.log('sub mqtt:' + group_id); + mqtt.subscribe({ + topic: '/msg/g/' + group_id, + qos: 1 + }); + mqtt.subscribe({ + topic: '/msg/l/' + group_id, + qos: 1 + }); // label 消息 + } + }; + + unsubscribeMqttGroup = function (group_id) { + if (mqtt) { + if (group_id) { + mqtt.unsubscribe({ + topic: '/msg/g/' + group_id + }); + mqtt.unsubscribe({ + topic: '/msg/l/' + group_id + }); + } + } + }; + + subscribeMqttUser = function (user_id) { + if (mqtt && user_id) { + console.log('sub mqtt:' + user_id); + mqtt.subscribe({ + topic: '/msg/u/' + user_id, + qos: 1 + }); + } + }; + + unsubscribeMqttUser = function (user_id) { + if (mqtt && user_id) { + mqtt.unsubscribe({ + topic: '/msg/u/' + user_id + }); + } + }; + + sendMqttGroupMessage = function (group_id, message, callback) { + message.create_time = new Date(Date.now() + MQTT_TIME_DIFF); + if (Meteor.user() && Meteor.user().profile && Meteor.user().profile.userType == 'admin') { + console.log('>>> this is admin, send group message to myself'); + onMessageOld('/msg/g/' + group_id, JSON.stringify(message), callback); + } else { + sendMqttMessage('/msg/g/' + group_id, message, callback); + } + }; + + sendMqttUserMessage = function (user_id, message, callback) { + // console.log('sendMqttUserMessage:', message); + sendMqttMessage('/msg/u/' + user_id, message, callback); + }; + + sendMqttGroupLabelMessage = function (group_id, message, callback) { + message.create_time = new Date(Date.now() + MQTT_TIME_DIFF); + if (Meteor.user() && Meteor.user().profile && Meteor.user().profile.userType == 'admin') { + console.log('>>> this is admin, send label message to myself'); + onMessageOld('/msg/l/' + group_id, JSON.stringify(message)); + console.log('====sraita====' + JSON.stringify(message)); + if (message.is_admin_relay) { + sendMqttMessage('/msg/l/' + group_id, JSON.stringify(message), callback); + } + } else { + sendMqttMessage('/msg/l/' + group_id, message, callback); + } + }; + }; + + MQTTDisconnect = function (cb) { + try { + mqtt.disconnect(cb); + connected = false; + } catch (error) { + console.log(error); + cb && cb(); + } + }; + + subscribeMyChatGroups = function () { + /** + * TODO: 该处订阅影响首页打开速度(一定要等到mqtt连接成功才能订阅到groupuser数据), + * 该处方法暂时保留,首页数据直接在首页订阅 + */ + Meteor.subscribe('get-my-group', Meteor.userId(), { + onReady: function () { + Session.set('GroupUsersLoaded', true); + } + }); + + SimpleChat.GroupUsers.find({ + user_id: Meteor.userId() + }).observe({ + added: function (document) { + subscribeMqttGroup(document.group_id); + }, + changed: function (newDocument, oldDocument) { + if (oldDocument.group_id === newDocument.group_id) + return; + + unsubscribeMqttGroup(oldDocument.group_id); + subscribeMqttGroup(newDocument.group_id); + }, + removed: function (document) { + unsubscribeMqttGroup(document.group_id); + } + }); + }; + + getMqttClientID = function () { + var client_id = window.localStorage.getItem('mqtt_client_id'); + if (!client_id) { + client_id = 'WorkAIC_' + (new Mongo.ObjectID())._str; + window.localStorage.setItem('mqtt_client_id', client_id); + } + console.log('##RDBG getMqttClientID: ' + client_id); + return client_id; + }; + + function startMQTT() { + if (SimpleChat.checkMsgSessionLoaded()) { + console.log('GroundDB all loaded!'); + initMQTT(Meteor.userId()); + } else { + console.log('Waiting for loading GroundDB...'); + if (init_timer) { + clearTimeout(init_timer); + init_timer = null; + } + init_timer = setTimeout(function () { + startMQTT(); + }, 500); + } + } + + mqttEventResume = function () { + console.log('##RDBG, mqttEventResume, reestablish mqtt connection'); + setTimeout(function () { + if (Meteor.userId()) { + startMQTT(); + } + }, 1000); + /*try { + if (mqtt_connection) { + console.log('try reconnect mqtt'); + mqtt_connection._reconnect(); + } + } + catch (ex) { console.log('mqtt reconnect ex=', ex); }*/ + }; + + mqttEventPause = function () { + console.log('##RDBG, mqttEventPause, disconnect mqtt'); + MQTTDisconnect(); + }; + + Deps.autorun(function () { + if (Meteor.userId()) { + startMQTT(); + } else { + MQTTDisconnect(); + } + }); + + document.addEventListener('offline', function () { + console.log('device get offline'); + MQTTDisconnect(); + network_status = navigator.connection.type; + }, false); + + document.addEventListener('online', function () { + console.log('device get online'); + //startMQTT() + if (network_status !== navigator.connection.type) { + MQTTDisconnect(function () { + network_status = navigator.connection.type; + startMQTT(); + }); + } + }, false); + }); +} \ No newline at end of file diff --git a/client/piwik.coffee b/client/piwik.coffee index 9c34fdfa3..39a2281f4 100644 --- a/client/piwik.coffee +++ b/client/piwik.coffee @@ -1,11 +1,30 @@ window.trackEvent=(category, action)-> try console.log('Track Event') - unless typeof(piwik) is 'undefined' + if typeof(piwik) isnt 'undefined' piwik.trackEvent(category, action) - piwik1.trackEvent(category, action) + else + $.getScript('http://piwik.tiegushi.com/piwik.js' ,()-> + console.log('Got piwik') + window.piwik = Piwik.getTracker( 'http://piwik.tiegushi.com/piwik.php', 14 ) + piwik.trackEvent(category, action) + ) catch error console.log('trackevent exception') + +window.trackImportEvent=(url)-> + try + console.log('Track Event') + if typeof(piwik) isnt 'undefined' + piwik.trackEvent('logs', 'import', 'URL', url) + else + $.getScript('http://piwik.tiegushi.com/piwik.js' ,()-> + console.log('Got piwik') + window.piwik = Piwik.getTracker( 'http://piwik.tiegushi.com/piwik.php', 14 ) + piwik.trackEvent('logs', 'import', 'URL', url) + ) + catch error + console.log('trackevent exception') window.trackPage=(url,title)-> try @@ -17,38 +36,18 @@ window.trackPage=(url,title)-> piwik.setReferrerUrl(url) piwik.setDocumentTitle(title) piwik.trackPageView() - - piwik1.setCustomUrl(url) - piwik1.setReferrerUrl(url) - piwik1.setDocumentTitle(title) - piwik1.trackPageView() catch error console.log('trackpage exception') initPiwik=(url,title)-> - - loadScript = (url, callback)-> - jQuery.ajax({ - url: url, - dataType: 'script', - success: callback, - async: true, - cache: true - }); if typeof(Piwik) isnt 'undefined' console.log('Has piwik'); else - loadScript('http://piwik.tiegushi.com/piwik.js' ,()-> + $.getScript('http://piwik.tiegushi.com/piwik.js' ,()-> console.log('Got piwik') - window.piwik = Piwik.getTracker( 'http://piwik.tiegushi.com/piwik.php', 1 ) + window.piwik = Piwik.getTracker( 'http://piwik.tiegushi.com/piwik.php', 14 ) piwik.setCustomUrl(url) piwik.setReferrerUrl(url) piwik.setDocumentTitle(title) piwik.trackPageView() - - window.piwik1 = Piwik.getTracker( 'http://piwik.tiegushi.com/piwik.php', 10 ) - piwik1.setCustomUrl(url) - piwik1.setReferrerUrl(url) - piwik1.setDocumentTitle(title) - piwik1.trackPageView() - ) + ) \ No newline at end of file diff --git a/client/public.coffee b/client/public.coffee index da09f6e75..894c71e11 100644 --- a/client/public.coffee +++ b/client/public.coffee @@ -1,20 +1,83 @@ postPageArr = [] +pages = ['/user', '/bell', '/search'] + #公共函数 @PUB = - 'openPost':(postId)-> - window.open '/posts/'+postId + 'showWaitLoading':(text)-> + this.hideWaitLoading() + text = text || '加载中...' + $('body').append('

      '+text+'

      ') + 'hideWaitLoading':()-> + $('.actionWaitLoading').remove() + 'Toptip': (text, options, callback)-> + if !text + return + if Session.equals('can_show_toptip', false) + return + + this.hideTopTip() + config = _.extend({ + timeout: 5000, + autohide: true + }, options) + + $("tool_tp").remove() + + div = document.createElement('div') + div.classList += '_top_tip' + div.innerHTML = text + + window.ToptipTimeout = null + if window.ToptipTimeout + Meteor.clearTimeout(window.ToptipTimeout) + if config.autohide + window.ToptipTimeout = Meteor.setTimeout(-> + PUB.hideTopTip() + ,config.timeout) + + div.addEventListener('click', (e)-> + PUB.hideTopTip() + callback and callback(e, options) + ) + $('body').append(div) + 'hideTopTip':-> + $('._top_tip').remove() + if window.ToptipTimeout + Meteor.clearTimeout(window.ToptipTimeout) + 'isUrl':(str_url)-> + ` + var strRegex = '^((https|http|ftp|rtsp|mms)?://)' + + '?(([0-9a-zA-Z_!~*\'().&=+$%-]+: )?[0-9a-zA-Z_!~*\'().&=+$%-]+@)?' + + '(([0-9]{1,3}.){3}[0-9]{1,3}' + + '|' + + '([0-9a-zA-Z_!~*\'()-]+.)*' + + '([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].' + + '[a-zA-Z]{2,6})' + + '(:[0-9]{1,4})?' + + '((/?)|' + + '(/[0-9a-zA-Z_!~*\'().;?:@&=+$,%#-]+)+/?)$'; + ` + re=new RegExp(strRegex) + if re.test(str_url) + return true + else + return false # 该方法实现页面切换 'page':(pageName)-> + if Session.get('persistentLoginStatus') and !Meteor.userId() and !Meteor.loggingIn() and pages.indexOf(pageName) isnt -1 + window.plugins.toast.showLongCenter("登录超时,需要重新登录~"); + return Router.go('/') + history = Session.get("history_view") view = Session.get("channel") - if history is undefined or history is "" + if history is null or history is undefined or history is "" history = new Array() #footerPages = ['/home', '/search', '/addPost', '/bell', '/user'] - footerPages = ['home', 'search', 'addPost', 'bell', 'user'] + footerPages = ['home', 'timeline', 'message', 'user'] #if current view is one of footer pages, and record the position of these pages for page in footerPages if view is page - Session.set 'document_body_scrollTop_'+view, document.body.scrollTop + Session.set 'document_body_scrollTop_'+view, $('.content').scrollTop() break #if pageName is one of footer pages, we will clear history and need to return back to the last position Session.set 'document_body_scrollTop', 0 @@ -37,12 +100,15 @@ postPageArr = [] Session.set 'document_body_scrollTop', value break unless view is undefined or view is "" + scroll_top = document.body.scrollTop + if _.contains(footerPages,view) + scroll_top = $('.content').scrollTop() if history.length > 0 and view is history[history.length-1].view - history[history.length-1].scrollTop = document.body.scrollTop + history[history.length-1].scrollTop = scroll_top else history.push { view: view - scrollTop: document.body.scrollTop + scrollTop: scroll_top } Session.set "history_view", history #if Session.get('view') isnt 'partner_detail' and Session.get('view') isnt 'add_partner' @@ -54,19 +120,27 @@ postPageArr = [] for tmpPage in history console.log "Frank.PUB: page, tmpPage = "+JSON.stringify(tmpPage) console.log "pageName is :"+pageName + if pageName is '/bell' + Session.set('canClearUnreadMessage',true) + else + if Session.equals('canClearUnreadMessage',true) + Session.set('canClearUnreadMessage',false) + Session.set('updataFeedsWithMe',true) + Meteor.call 'updataFeedsWithMe', Meteor.userId() Router.go(pageName) return # 返回上一页 'back':-> try if typeof PopUpBox isnt "undefined" - PopUpBox.close() + PopUpBox.close() + # $('.popUpBox, .b-modal').hide() catch error console.log error history = Session.get("history_view") - for tmpPage in history - console.log "Frank.PUB: back, tmpPage = "+JSON.stringify(tmpPage) - unless history is undefined or history is "" + unless history is null or history is undefined or history is "" + for tmpPage in history + console.log "Frank.PUB: back, tmpPage = "+JSON.stringify(tmpPage) if history.length > 0 page = history.pop() if Session.get("postContent") @@ -74,7 +148,8 @@ postPageArr = [] else currentPostView='' if page.view is currentPostView and history.length >0 - page = history.pop() + unless page.parent and page.parent is 'postItem' + page = history.pop() Session.set "document_body_scrollTop", page.scrollTop Session.set "history_view", history #Session.set "view", page.view @@ -82,10 +157,15 @@ postPageArr = [] Router.go('/add') else if page.view is 'home' Router.go('/') + else if page.view is 'message' and Session.get('_timelineAlbumFromGroupId') + Router.go('/simple-chat/to/group?id=' + Session.get('_timelineAlbumFromGroupId')) + Session.set('_timelineAlbumFromGroupId', '') else Router.go('/'+page.view) else Router.go('/') + else + Router.go('/') #nowPage = Session.get('view') #Session.set 'view',Session.get('referrer') #if nowPage isnt 'partner_detail' and nowPage isnt 'add_partner' @@ -111,8 +191,8 @@ postPageArr = [] '确定' ) catch error - if confirm(msg) - callback + alert(msg) + callback "confirm":(msg, callback)-> try navigator.notification.confirm( @@ -126,7 +206,7 @@ postPageArr = [] catch error if confirm(msg) callback() - + # 可以浏览图片,放大,缩小,下一张 # items 格式 # items = [ @@ -143,26 +223,17 @@ postPageArr = [] } postPageArr.push(postIdJson) "postPageBack":-> - if postPageArr.length is 1 - Session.set 'displayShowPostLeftBackBtn',false - post = postPageArr.pop() - postId = post.postId - if post.scrollTop is undefined - postPageScrollTop = 0 - else - postPageScrollTop = post.scrollTop - Session.set("postPageScrollTop", postPageScrollTop) - Router.go '/posts/'+postId -# $(window).children().off() -# $(window).unbind('scroll') -# $('.showPosts').addClass('animated ' + animateOutUpperEffect) -# $('.showPostsFooter').addClass('animated ' + animateOutUpperEffect) -# Meteor.setTimeout ()-> -# PUB.back() -# if Session.get("Social.LevelOne.Menu") is 'userProfile' -# Session.set("Social.LevelOne.Menu",'contactsList') -# return -# ,animatePageTrasitionTimeout + if postPageArr.length is 0 + $(window).children().off() + $(window).unbind('scroll') + $('.showPosts').addClass('animated ' + animateOutUpperEffect) + $('.showPostsFooter').addClass('animated ' + animateOutUpperEffect) + setTimeout ()-> + PUB.back() + if Session.get("Social.LevelOne.Menu") is 'userProfile' + Session.set("Social.LevelOne.Menu",'contactsList') + return + ,animatePageTrasitionTimeout else post = postPageArr.pop() postId = post.postId @@ -172,4 +243,25 @@ postPageArr = [] postPageScrollTop = post.scrollTop Session.set("postPageScrollTop", postPageScrollTop) Router.go '/posts/'+postId - + "actionSheet": (menuArray, title, callback)-> + if Meteor.isCordova + if title + options = { + 'androidTheme': window.plugins.actionsheet.ANDROID_THEMES.THEME_HOLO_LIGHT, + 'title': title, + 'buttonLabels': menuArray, + 'androidEnableCancelButton' : true, + 'winphoneEnableCancelButton' : true, + 'addCancelButtonWithLabel': '取消', + 'position': [20, 40] + } + else + options = { + 'androidTheme': window.plugins.actionsheet.ANDROID_THEMES.THEME_HOLO_LIGHT, + 'buttonLabels': menuArray, + 'androidEnableCancelButton' : true, + 'winphoneEnableCancelButton' : true, + 'addCancelButtonWithLabel': '取消', + 'position': [20, 40] + } + window.plugins.actionsheet.show(options,callback) diff --git a/client/public.less b/client/public.less new file mode 100644 index 000000000..5301f5f2a --- /dev/null +++ b/client/public.less @@ -0,0 +1,50 @@ +.actionWaitLoading{ + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + text-align: center; + z-index: 999999; + .loadingContainer{ + color: #fff; + border-radius: 8px; + background: rgba(0, 0, 0, 0.9); + // top: 50%; + // margin-top: -32px; + // position: relative; + display: inline-block; + padding: 10px 16px; + text-align: center; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 120px; + height:80px; + margin: auto; + p{margin: 8px 0 0 0;} + } +} + +// top tip +._top_tip { + position: fixed; + top: 0; + z-index: 999990; + background: rgba(33, 150, 243, 0.9); + left: 0; + right: 0; + padding: 0 16px; + font-size: 12px; + color: #fff; + height: 40px; + line-height: 40px; + i { + font-size: 16px; + line-height: 40px; + margin-right: 5px; + vertical-align: middle; + } +} \ No newline at end of file diff --git a/client/registration/authOverlay/authOverlay.css b/client/registration/authOverlay/authOverlay.css index 31f2fac4c..7a5a1f413 100644 --- a/client/registration/authOverlay/authOverlay.css +++ b/client/registration/authOverlay/authOverlay.css @@ -1,37 +1,33 @@ /* CSS declarations go here */ .authOverlay{background-repeat: no-repeat;background-size: 100% auto; position: relative;} - +.webHome{background-image: url(webbg.jpg);background-repeat: no-repeat;background-size: cover; position: relative;} .authOverlay .agreeDeal{position: absolute;bottom: 45%;left: 5%;right: 5%;width: 90%;background-color: #FFF;color: #111; text-align: center;border-radius: 5px;padding: 15px;} .authOverlay .title{font-weight:bold;} -.authOverlay .loginActionArea{ - position: absolute; - left: 10%; - right: 10%; - bottom: 50px; - height: 50%; - color: #1a1a1a; - text-align: center; -} -.authOverlay #register{ - text-align: center; - padding: 10px; - background-color: rgba(255,255,255,0.2); - font-size: 16px; - position: relative !important; - border: 1px solid #37a7fe !important; - width: 100% !important; - border-radius: 16px !important; - color: #37a7fe !important; -} -.authOverlay #login{ - text-align: center; - font-size: 16px; - position: relative !important; - padding: 10px; - background-color: #37a7fe; - color: #ffffff !important; - border-radius: 16px; - width: 100% !important; - margin-bottom: 12px;} +.webHome #register{position: absolute;bottom: 25%;left: 5%;right: 5%;width: 90%;text-align: center;border: none;border-radius: 8px;padding: 10px;background-color: rgba(255,255,255,0.2);color:#FFF;font-size: 16px;} +.authOverlay #register{text-align: center;padding: 10px;background-color: rgba(255,255,255,0.2);font-size: 16px;} +.authOverlay #anonymous{position: absolute;bottom: 15%;left: 5%;right: 5%;width: 90%;text-align: center;border: none;border-radius: 8px;padding: 10px;background-color: rgba(255,255,255,0.2);color:#FFF;font-size: 16px;} +.authOverlay #wechat,.webHome #wechat{position: absolute;bottom: 15%;left: 5%;right: 5%;width: 90%;text-align: center;border: none;border-radius: 8px;padding: 10px;font-size: 16px;background-color: rgba(255,255,255,0.2); color: #37a7fe; cursor: pointer;} +.authOverlay #wechat i,.webHome #wechat i {font-size: 26px; vertical-align: middle;} +.authOverlay #weibo,.webHome #weibo{position: absolute;bottom: 25%;left: 5%;right: 5%;width: 90%;text-align: center;border: thin solid;border-radius: 8px;padding: 10px;font-size: 16px;} +.authOverlay #qq,.webHome #qq{position: absolute;bottom: 15%;left: 5%;right: 5%;width: 90%;text-align: center;border: thin solid;border-radius: 8px;padding: 10px;font-size: 16px;} +.webHome #login{position: absolute;bottom: 6%;left: 30%;right: 30%;text-align: center;font-size: 16px;} +.authOverlay #login{text-align: center;font-size: 16px;} .authOverlay .company{position: absolute;bottom: 0;left: 30%;right: 30%;text-align: center;font-size: 12px;text-shadow: 0 1px 0 black} +.webHome .webFooter{position: absolute;bottom: 30px;} +.webHome .downloadBtn{text-align: center;} +.webHome .downloadBtn a{position: relative;padding-left: 45px;} +.webHome .download{text-shadow: 0 1px 0 #ccc;color: #eee;background-color: #aebdc9;padding: 15px;border-radius: 10px;} +.webHome .downloadBtn a i{font-size: xx-large;position: absolute;top: 8px;left: 15px;} +.webHome .webFooter .company{font-size: 12px;color:#aebdc9; } +.webHome .webFooter .miibeian{font-size: 12px;color:#aebdc9;text-align: center} +.webHome .webFooter .miibeian a{color:#aebdc9;} +.register{position: absolute;top:0px} .authOverlay #loggingIn{position: absolute;bottom: 15%;left: 5%;right: 5%;width: 90%;text-align: center;border: none;border-radius: 8px;padding: 10px;background-color: #00c4ff;font-size: 16px;} +.resetPwdTopbar{opacity: 1;width:100%;margin-left: 0px;height: 6%;background: #454546 ;font-size: 120%;vertical-align: middle;padding-left: 10%;line-height: 120%;} +.resetPwddiv{opacity: 0.8;width:80%;margin-left: 10%;background: white;margin-top: 10%;color:black;} +.sureReset{width:100%;background:#f0f0f0;height:40px;text-align:center;margin-top:20px;padding-top:3px;} +.sureReset input, .sureReset button{width:80px;height:30px;background:#44b549; margin-left: 50%; transform:translate(-50%, 0);} + +/* .iconArea{margin: 9% auto 10%;width: 50% } */ +.iconArea{left:0;right:0;width: 50%;position: fixed;top:9%;margin-left: auto;margin-right: auto;} +#wechatBtn{margin: 20px;font-size:14px;display: inline-block} \ No newline at end of file diff --git a/client/registration/authOverlay/authOverlay.html b/client/registration/authOverlay/authOverlay.html index 31282f120..07ae53242 100644 --- a/client/registration/authOverlay/authOverlay.html +++ b/client/registration/authOverlay/authOverlay.html @@ -1,15 +1,149 @@ + diff --git a/client/registration/authOverlay/authOverlay.js b/client/registration/authOverlay/authOverlay.js index 2c250a079..b749c9cc6 100644 --- a/client/registration/authOverlay/authOverlay.js +++ b/client/registration/authOverlay/authOverlay.js @@ -1,8 +1,231 @@ if (Meteor.isClient) { + window.ScanBarcodeByBarcodeScanner = function() { + cordova.plugins.barcodeScanner.scan( + function(result) { + console.log("We got a barcode\n" + + "Result: " + result.text + "\n" + + "Format: " + result.format + "\n" + + "Cancelled: " + result.cancelled); + var gotoPage = '/'; + //var requiredStr = rest_api_url+'/simple-chat/to/group?id=' + if (result.text) { + if (Session.get('addHomeAIBox') === true) { + Router.go('/scanFailPrompt'); + Session.set('addHomeAIBox',false); + return; + } + //if (result.text.indexOf(requiredStr)=== 0) { + if (result.text.indexOf('http://workaicdn.tiegushi.com/simple-chat/to/group?id=') >= 0 || result.text.indexOf('http://testworkai.tiegushi.com/simple-chat/to/group?id=') >= 0){ + //var groupid = result.text.substring(requiredStr.length); + var groupid = result.text.substr(result.text.lastIndexOf('?id=')+'?id='.length); + console.log('groupid==='+groupid); + if (groupid && groupid.length > 0) { + Meteor.call('add-group-urser', groupid, [Meteor.userId()], function(err, result) { + if (err) { + console.log(err); + return PUB.toast('添加失败,请重试~'); + } + if (result === 'succ') { + PUB.toast('添加成功'); + gotoPage = '/simple-chat/to/group?id='+ groupid; + Meteor.subscribe('get-group',groupid, { + onReady: function() { + var group, msgObj, user; + group = SimpleChat.Groups.findOne({ + _id: groupid + }); + var msgSession = SimpleChat.MsgSession.findOne({userId: Meteor.userId(), toUserId: group._id}); + if (msgSession) { + return; + } + user = Meteor.user(); + msgObj = { + toUserId: group._id, + toUserName: group.name, + toUserNames: group.name, + toUserIcon: group.icon, + sessionType: 'group', + userId: user._id, + userName: user.profile.fullname || user.username, + userIcon: user.profile.icon || '/userPicture.png', + lastText: '', + createAt: new Date(), + updateAt: new Date(), + }; + return SimpleChat.MsgSession.insert(msgObj); + } + }); + var relations = WorkAIUserRelations.findOne({'app_user_id':Meteor.userId()}); + if (!relations) { + // Meteor.setTimeout(function(){ + return Router.go('/timeline'); + // },500); + } + return Router.go(gotoPage); + } + if (result === 'not find group') { + PUB.toast('二维码格式错误或该群组已被删除'); + return Router.go(gotoPage); + } + }); + } + else{ + Router.go(gotoPage); + PUB.toast('二维码格式错误或该群组已被删除') + } + + } + else{ + Router.go(gotoPage); + PUB.toast('你可能扫描了错误的二维码,请检查......') + } + } + if (result.cancelled) { + Router.go(gotoPage); + return; + } + if (result.alumTapped) { + DecodeImageFromAlum(); + return; + } + }, + function(error) { + alert("Scanning failed: " + error); + }, { + preferFrontCamera: false, // iOS and Android + showFlipCameraButton: true, // iOS and Android + showTorchButton: true, // iOS and Android + torchOn: false, // Android, launch with the torch switched on (if available) + prompt: "Place a barcode inside the scan area", // Android + resultDisplayDuration: 500, // Android, display scanned text for X ms. 0 suppresses it entirely, default 1500 + formats: "QR_CODE,PDF_417", // default: all but PDF_417 and RSS_EXPANDED + orientation: "landscape", // Android only (portrait|landscape), default unset so it rotates with the device + //disableAnimations: true, // iOS + //disableSuccessBeep: false // iOS + } + ); + } + window.DecodeImageFromAlum = function(){ + function decodecallback(result){ + var gotoPage = '/'; + if (result && (result.indexOf('http://workaicdn.tiegushi.com/simple-chat/to/group?id=') >= 0 || result.indexOf('http://testworkai.tiegushi.com/simple-chat/to/group?id=') >= 0)){ + var groupid = result.substr(result.lastIndexOf('?id=')+'?id='.length); + console.log('groupid==='+groupid); + if (groupid && groupid.length > 0) { + Meteor.call('add-group-urser', groupid, [Meteor.userId()], function(err, result) { + if (err) { + console.log(err); + return PUB.toast('添加失败,请重试~'); + } + if (result === 'succ') { + PUB.toast('添加成功'); + gotoPage = '/simple-chat/to/group?id='+ groupid; + Meteor.subscribe('get-group',groupid, { + onReady: function() { + var group, msgObj, user; + group = SimpleChat.Groups.findOne({ + _id: groupid + }); + if (group) { + var msgSession = SimpleChat.MsgSession.findOne({userId: Meteor.userId(), toUserId: group._id}); + if (msgSession) { + return; + } + user = Meteor.user(); + msgObj = { + toUserId: group._id, + toUserName: group.name, + toUserIcon: group.icon, + sessionType: 'group', + userId: user._id, + userName: user.profile.fullname || user.username, + userIcon: user.profile.icon || '/userPicture.png', + lastText: '', + createAt: new Date(), + updateAt: new Date(), + }; + SimpleChat.MsgSession.insert(msgObj); + } + } + }); + return Router.go(gotoPage); + } + if (result === 'not find group') { + PUB.toast('二维码格式错误或该群组已被删除'); + return Router.go(gotoPage); + } + }); + } + else{ + Router.go(gotoPage); + PUB.toast('二维码格式错误或该群组已被删除') + } + } + else{ + Router.go(gotoPage); + PUB.toast('你可能扫描了错误的二维码,请检查......') + } + } + if(device.platform === 'Android' ){ + pictureSource = navigator.camera.PictureSourceType; + destinationType = navigator.camera.DestinationType; + encodingType = navigator.camera.EncodingType; + + navigator.camera.getPicture(function(s){ + console.log('##RDBG pic get: ' + s); + + localFile = s.substring(7); + console.log('##RDBG local file: ' + localFile); + questionMark = localFile.indexOf('?'); + if (questionMark > 0) { + localFile = localFile.substring(0, questionMark); + console.log('##RDBG local file: ' + localFile); + } + + cordova.plugins.barcodeScanner.decodeImage(localFile, function (result) { + console.log("##RDBG decodeImage suc: " + result); + decodecallback(result); + }, function (err) { + console.log('##RDBG decodeImage err: ' + err); + }); + }, function(err) { + console.log('##RDBG pic get fail: ' + err); + }, { + quality: 20, + targetWidth: 1900, + targetHeight: 1900, + destinationType: destinationType.FILE_URI, + sourceType: pictureSource.SAVEDPHOTOALBUM + }); + } + else { + window.imagePicker.getPictures(function (results) { + for (var i = 0; i < results.length; i++) { + localFile = results[i].substring(7); + console.log('##RDBG local file: ' + localFile); + + cordova.plugins.barcodeScanner.decodeImage(localFile, function (result) { + console.log("##RDBG decodeImage suc: " + result); + decodecallback(result); + }, function (err) { + console.log('##RDBG decodeImage err: ' + err); + PUB.toast('二维码格式错误或该群组已被删除'); + }); + } + }, function (error) { + console.log('getPictures Error: ' + error); + }, { + maximumImagesCount: 1 + }); + } + } Meteor.startup(function(){ if (Accounts._resetPasswordToken) { Session.set('resetPassword', Accounts._resetPasswordToken); } + WechatShare.isWXAppInstalled(function(result){ + Session.set('isWXAppInstalled', result); + }, function(){}); }); Template.authOverlay.onRendered(function () { // StatusBar.backgroundColorByHexString("#ffffff"); @@ -11,23 +234,224 @@ if (Meteor.isClient) { // $('.authOverlay').css('height', $(window).height()); if (Meteor.user()) Meteor.subscribe("follows"); - document.getElementById("authOverlaybg").style.backgroundImage = "url(theme_blue/loginbg1.jpg)"; + if (isUSVersion == true) { + // document.getElementById("authOverlaybg").style.backgroundImage = "url(loginbg1en.jpg)"; + } else { + // document.getElementById("authOverlaybg").style.backgroundImage = "url(loginbg1.png)"; + // document.getElementById("authOverlaybg").style.backgroundImage = "url(theme_blue/loginbg1.jpg)"; + } }); - + // Template.authOverlay.onDestroyed(function () { + // StatusBar.backgroundColorByHexString("#37a7fe"); + // StatusBar.styleLightContent(); + // }); Template.authOverlay.helpers({ isLoggingIn:function() { return Meteor.loggingIn(); }, + isWXAppInstalled:function(){ + if(device.platform === 'iOS'){ + return Session.get('isWXAppInstalled'); + } + return true; + } }); Template.authOverlay.events({ + 'click #anonymous': function () { + console.log ('UUID is ' + device.uuid); + if (device.uuid){ + Meteor.loginWithPassword(device.uuid,'123456',function(error){ + console.log('Login Error is ' + JSON.stringify(error)); + if(error && error.reason && error.reason ==='User not found'){ + console.log('User Not Found, need create'); + $('.agreeDeal').css('display',"block"); + Session.set("dealBack","anonymous"); + } + if (!error){ + Router.go ('/'); + checkShareUrl(); + } + }); + } else { + PUB.toast ('您的设备不支持匿名使用,请和我们联系'); + } + }, + 'click #cancle': function () { + $('.agreeDeal').css('display',"none"); + }, + 'click #agree': function () { + Accounts.createUser({ + 'username':device.uuid, + 'password':'123456', + 'profile':{ + fullname:'匿名', + icon:'/userPicture.png', + anonymous:true + } + }, + function(error){ + console.log('Registration Error is ' + JSON.stringify(error)); + if (!error){ + console.log('Registration Succ, goto Follow page'); + //Router.go('/registerFollow'); + var flag = window.localStorage.getItem("isSecondUse") === 'true'; + if (flag) { + Router.go('/'); + } + else{ + Router.go('/introductoryPage'); + } + + } else { + $('.agreeDeal').css('display',"none"); + PUB.toast ('匿名服务暂时不可用,请稍后重试'); + } + }); + }, 'click #register': function () { PUB.page('/signupForm'); - Session.set("dealBack","register"); + // $('.register').css('display',"block") + // $('#register').css('display',"none") + // $('#weibo').css('display',"none") + // $('#login').css('display',"none") + // $('.recovery').css('display',"none") + // $('.agreeDeal').css('display',"none"); + Session.set("dealBack","register"); +// $('.authOverlay').css('-webkit-filter',"blur(10px)") }, 'click #login': function () { PUB.page('/loginForm'); + // $('.login').css('display',"block") + // $('#register').css('display',"none") + // $('#weibo').css('display',"none") + // $('#login').css('display',"none") + // $('.recovery').css('display',"none") + // $('.agreeDeal').css('display',"none"); +// $('.authOverlay').css('-webkit-filter',"blur(10px)") + }, + 'click #weibo': function () { + Meteor.loginWithWeibo({ + loginStyle: 'popup' + //loginStyle: 'redirect' + //loginStyle: 'redirect' you can use redirect for mobile web app + }, function () { + console.log('in call back', arguments); + }); }, + 'click #wechatBtn': function (e,t) { + if (Meteor.status().connected !== true) { + PUB.toast('当前为离线状态,请检查网络连接'); + return; + } + if(device.platform === 'iOS' && !Session.get('isWXAppInstalled')){ + PUB.toast('当前没有安装微信'); + return; + } + Meteor.loginWithWeixin(function(err, result) { + if (err) { + PUB.toast('微信登陆失败'); + return console.log(err); + } else { + PUB.toast('微信登陆成功'); + if(Meteor.user().profile.new === undefined || Meteor.user().profile.new === true) + { + Meteor.users.update({_id: Meteor.userId()}, {$set: {"profile.new": false}}); + //return Router.go('/registerFollow'); + // ScanBarcodeByBarcodeScanner(); + var flag = window.localStorage.getItem("isSecondUse") === 'true'; + if (flag) { + Router.go('/'); + } + else{ + Router.go('/introductoryPage'); + } + } + else + return Router.go('/'); + } + }); + }, + 'click #qq': function () { + Meteor.loginWithQq({ + loginStyle: 'popup' + //loginStyle: 'redirect' + //loginStyle: 'redirect' you can use redirect for mobile web app + }, function () { + console.log('in call back', arguments); + }); + } }); -} + Template.webHome.rendered = function() { + $('.webHome').css('height', $(window).height()); + $('.webFooter').css('left', $(window).width()*0.5-105); + Session.set("resetPasswordSuccess", false); + }; + Template.webHome.helpers({ + resetPassword: function(){ + return Session.get('resetPassword'); + }, + pwdErrorInfo: function(){ + return Session.get("pwdErrorInfo"); + }, + resetPasswordSuccess: function(){ + return Session.get("resetPasswordSuccess"); + } + }); + Template.webHome.events({ + 'submit #new-password':function(e,t){ + e.preventDefault(); + var newPass=t.find('#new-password-password').value; + var repPass=t.find('#new-password-repeat').value; + if(newPass!==repPass) + { + Session.set("pwdErrorInfo", "两次填写的密码不一致"); + $('.errorInfo').show(); + Meteor.setTimeout(function(){ + $('.errorInfo').hide(); + },3000); + return false; + } + if(newPass.length<6 || newPass.length>16) + { + Session.set("pwdErrorInfo", "您输入的密码不符合规则"); + $('.errorInfo').show(); + Meteor.setTimeout(function(){ + $('.errorInfo').hide(); + },3000); + return false; + } + Accounts.resetPassword(Session.get("resetPassword"), newPass,function(error){ + if(error){ + if(error.error===403 && error.reason==="Token expired"){ + Session.set("pwdErrorInfo", "密码重设链接已经过期,请从手机端再次发起重设请求"); + $('.errorInfo').show(); + Meteor.setTimeout(function(){ + $('.errorInfo').hide(); + },3000);} + else{ + Session.set("pwdErrorInfo", "未能成功重设密码,请稍后重试或从手机端再次发起重设请求"); + $('.errorInfo').show(); + Meteor.setTimeout(function(){ + $('.errorInfo').hide(); + },3000); + } + } + else{ + Session.set("resetPasswordSuccess", true); + } + }); + return false; + }, + 'click #finishReset' :function(){ + Session.set('resetPassword', false); + } + }); + Meteor.startup(function() { + $(window).resize(function() { + $('.webHome').css('height', $(window).height()); + $('.webFooter').css('left', $(window).width()*0.5-105); + }); + }); +} diff --git a/client/registration/loginForm/loginForm.coffee b/client/registration/loginForm/loginForm.coffee index 68edd0605..7d728179c 100644 --- a/client/registration/loginForm/loginForm.coffee +++ b/client/registration/loginForm/loginForm.coffee @@ -1,15 +1,27 @@ +# id1 = null +# id2 = null Template.loginForm.events - 'focus input':(e,t)-> - Meteor.setTimeout -> - $('.company').css('display','none') - ,10 - 'blur input':(e,t)-> - Meteor.setTimeout -> - $('.company').css('display','block') - ,10 + 'click #toSignup':(e,t)-> + return PUB.page('/signupForm'); + # 'focus input':(e,t)-> + # if id2 isnt null + # Meteor.clearTimeout id2 + # id2 = null + # id1 = Meteor.setTimeout -> + # $('.company').css('display','none') + # $('.bottom-img').css('display','none') + # ,10 + # 'blur input':(e,t)-> + # if id1 isnt null + # Meteor.clearTimeout id1 + # id1 = null + # id2 = Meteor.setTimeout -> + # $('.company').css('display','block') + # $('.bottom-img').css('display','block') + # ,500 'click #btn_back' :-> $('input').blur() - Router.go '/authOverlay' + PUB.back() # Router.go '/authOverlay' # $('.login').css('display',"none") # $('#register').css('display',"block") @@ -18,7 +30,20 @@ Template.loginForm.events # $('.recovery').css('display',"none") # $('.authOverlay').css('-webkit-filter',"none") 'click .forgetPwdBtn': (e)-> - $('.loginProblemsPromptPage').fadeIn(300) + menus = ['忘记密码?','联系客服'] + menuTitle = '' + callback = (buttonIndex)-> + if buttonIndex is 1 + # $('.login').css('display',"none") + # $('#register').css('display',"none") + # $('#weibo').css('display',"none") + # $('#login').css('display',"none") + # $('.recovery').css('display',"block") + # $('.agreeDeal').css('display',"none") + Router.go '/recoveryForm' + else if buttonIndex is 2 + $('.customerService,.customerServiceBackground').fadeIn(300) + PUB.actionSheet(menus, menuTitle, callback) 'click .btnClose' :-> $('.customerService,.customerServiceBackground').hide() 'click #sendEmailBtn' :(e,t)-> @@ -63,18 +88,29 @@ Template.loginForm.events PUB.toast '请输入密码!' return t.find('#sub-login').disabled = true - t.find('#sub-login').value = '正在登录...' + t.find('#sub-login').innerText = '正在登录...' Meteor.loginWithPassword name, pass,(error)-> if error PUB.toast '帐号或密码有误!' t.find('#sub-login').disabled = false - t.find('#sub-login').value = '登 录' + t.find('#sub-login').innerText = '登 录' else - page = Session.get('routerWillRenderPage') - if page - Router.go page + Router.go '/' + ### + if window.localStorage.getItem("isSecondUse") == 'true' + Router.go('/') else - Router.go '/' + if window.localStorage.getItem("enableHomeAI") == 'true' + Router.go('/scene') + else + Meteor.call 'enableHomeAI',(err,res)-> + if !err and res is true + window.localStorage.setItem("enableHomeAI",'true') + Router.go('/scene') + else + Router.go('/introductoryPage') + ### + checkShareUrl() return false Template.recoveryForm.events @@ -102,35 +138,52 @@ Template.recoveryForm.events email = t.find('#recovery-email').value if email is '' return - t.find('#sub-recovery').disabled = true - t.find('#sub-recovery').value = '正在重设...' - Accounts.forgotPassword {email:email},(error)-> - t.find('#sub-recovery').disabled = false - t.find('#sub-recovery').value = '重设' - if error - if error.error is 403 and error.reason is 'User not found' - PUB.toast '您填写的邮件地址不存在!' - else - PUB.toast '暂时无法处理您的请求,请稍后重试!' - else - #PUB.toast '请访问邮件中给出的网页链接地址,根据页面提示完成密码重设。' - navigator.notification.confirm('请访问邮件中给出的网页链接地址,根据页面提示完成密码重设。', (r)-> - if r is 1 - $('#recovery-email').val(''); - , '提示信息', ['确定']); + qqValueReg = RegExp(/^[1-9][0-9]{4,9}$/) + mailValueReg = RegExp(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/) + if !mailValueReg.test(email) and !qqValueReg.test(email) + PUB.toast('请输入正确的QQ号或Email') + return false + if qqValueReg.test(email) + email += '@qq.com' + # if Meteor.call('checkUserByEmail', email) is 'undefined' + # PUB.toast '未检测到与您输入邮箱匹配的帐号,请检查输入的邮箱' + Meteor.call('checkUserByEmail', email,(error,result)-> + if result isnt undefined and result isnt null + Meteor.call('sendResetPasswordEmail', result._id, result.emails[0].address) + console.log result + PUB.toast('密码重置邮件已发送到您的注册邮箱,请查收邮件后继续操作。') Router.go '/loginForm' - # $('.login').css('display',"none") - # $('#register').css('display',"block") - # $('#weibo').css('display',"block") - # $('#login').css('display',"block") - # $('.recovery').css('display',"none") + else + PUB.toast('未检测到与您输入邮箱匹配的帐号,请检查输入的邮箱。') + ); + return + + t.find('#sub-recovery').disabled = true + t.find('#sub-recovery').innerText = '正在重设...' + subject = '用户' + email + '需要重置密码!' + content = "来了吗APP收到新的重置密码申请,请尽快处理!\n\n申请信息――\n\n用户账户邮箱:" + email + "\n\n\n本邮件为系统自动发送,请不要直接回复,谢谢!" + Meteor.call('sendEmailToAdmin', email,subject ,content) + PUB.toast('重置密码请求已经提交客服,请等待客服与您联系。') + Router.go '/loginForm' -Template.loginProblemsPrompt.events - 'click .forgetPasswordBtn':(e,t)-> - $('.loginProblemsPromptPage').hide() - Router.go '/recoveryForm' - 'click .customerServiceBtn':(e,t)-> - $('.loginProblemsPromptPage').hide() - $('.customerService,.customerServiceBackground').fadeIn(300) - 'click .bg, click .cancleBtn':-> - $('.loginProblemsPromptPage').hide() \ No newline at end of file +### + Accounts.forgotPassword {email:email},(error)-> + t.find('#sub-recovery').disabled = false + t.find('#sub-recovery').innerText = '重设' + if error + if error.error is 403 and error.reason is 'User not found' + PUB.toast '您填写的邮件地址不存在!' + else + PUB.toast '暂时无法处理您的请求,请稍后重试!' + else + PUB.toast '请访问邮件中给出的网页链接地址,根据页面提示完成密码重设。' + navigator.notification.confirm('请访问邮件中给出的网页链接地址,根据页面提示完成密码重设。', (r)-> + if r is 1 + $('#recovery-email').val(''); + , '提示信息', ['确定']); + Router.go '/loginForm' + $('.login').css('display',"none") + $('#register').css('display',"block") + $('#weibo').css('display',"block") + $('#login').css('display',"block") + $('.recovery').css('display',"none")### diff --git a/client/registration/loginForm/loginForm.html b/client/registration/loginForm/loginForm.html index d27f3bf39..ddc21df6f 100644 --- a/client/registration/loginForm/loginForm.html +++ b/client/registration/loginForm/loginForm.html @@ -1,29 +1,40 @@ - - diff --git a/client/registration/registerFollow/registerFollow.coffee b/client/registration/registerFollow/registerFollow.coffee new file mode 100644 index 000000000..4ff2f7b7d --- /dev/null +++ b/client/registration/registerFollow/registerFollow.coffee @@ -0,0 +1,59 @@ +#用户 space2 +Template.follow_user.helpers + follows: -> + Follows.find({},{sort: {index: 1}}) +Template.follow_user_list.helpers + isFollowed:(follow)-> + fcount = Follower.find({"userId":Meteor.userId(),"followerId":follow.userId}).count() + if fcount > 0 + true + else + false +Template.registerFollow.onCreated ()-> + Meteor.subscribe("follows") + Meteor.subscribe("follower") +Template.registerFollow.helpers + followCount: -> + Follower.find({"userId":Meteor.userId()}).count() + NeedMoreCount: -> + 4 - Follower.find({"userId":Meteor.userId()}).count() + larger:(a,b)-> + if a > b + true + else + false +Template.registerFollow.events + 'click #continue':-> + if Meteor.user().profile.new isnt undefined and Meteor.user().profile.new is true + Meteor.users.update({_id: Meteor.userId()}, {$set: {"profile.new": false}}) + Session.setPersistent('persistentLoginStatus',true) + Router.go('/') + 'click .layer':(e)-> + fcount = Follower.find({"userId":Meteor.userId(),"followerId":@userId}).count() + if fcount > 0 + followerId = Follower.findOne({ + userId: Meteor.userId() + followerId: @userId + })._id + Follower.remove(followerId) + else + #匿名用户刚注册,系统就已经分配随机全名 + if Meteor.user().profile and Meteor.user().profile.fullname + username = Meteor.user().profile.fullname + else + username = Meteor.user().username + Follower.insert { + userId: Meteor.userId() + #用户更新fullname后,这里存放fullname + userName: username + #刚注册,用户还没有设置头像和个性签名 + #注册时,头像用默认头像,desc用'' + userIcon: Meteor.user().profile.icon + userDesc: Meteor.user().profile.desc + followerId: @userId + #这里存放fullname + followerName: @fullname + followerIcon: @icon + followerDesc: @desc + createAt: new Date() + } diff --git a/client/registration/registerFollow/registerFollow.css b/client/registration/registerFollow/registerFollow.css new file mode 100644 index 000000000..26a5b6264 --- /dev/null +++ b/client/registration/registerFollow/registerFollow.css @@ -0,0 +1,9 @@ +.registerFollow h5{color: gray} +.registerFollow hr{border-color: gray} +.registerFollow .user_head{position: relative} +.registerFollow .user_head i{font-size: 38px; position: absolute;top: 30px;right: 20px;} +.registerFollow .user_head img{border: none;border-radius:50%;} +.registerFollow .follow_user{font-size: 19px;} +.registerFollow .container{border:none} +.registerFollow .container #continue{width: 100%;text-align: center;border: none;border-radius: 8px;padding: 5px;background-color: #00c4ff;font-size: 18px;} +.registerFollow #continue{margin-top: 8px;} diff --git a/client/registration/registerFollow/registerFollow.html b/client/registration/registerFollow/registerFollow.html new file mode 100644 index 000000000..772d5c87c --- /dev/null +++ b/client/registration/registerFollow/registerFollow.html @@ -0,0 +1,49 @@ + + + + diff --git a/client/registration/signupForm/deal.coffee b/client/registration/signupForm/deal.coffee index bd11bd1f4..cdfe05d44 100644 --- a/client/registration/signupForm/deal.coffee +++ b/client/registration/signupForm/deal.coffee @@ -9,9 +9,9 @@ Template.deal_page.events # $('#login').css('display',"none") # ,10 else if Session.get("dealBack") is "anonymous" - Router.go '/authOverlay' + Router.go '/loginForm' Meteor.setTimeout -> $('.agreeDeal').css('display',"block") ,10 else - Router.go '/authOverlay' \ No newline at end of file + Router.go '/loginForm' \ No newline at end of file diff --git a/client/registration/signupForm/deal.html b/client/registration/signupForm/deal.html index 879e4864d..e959497a6 100644 --- a/client/registration/signupForm/deal.html +++ b/client/registration/signupForm/deal.html @@ -1,11 +1,14 @@