+ 监控组
+ -
+ {{#each officialImages}}
+
- + + + {{/each}} + {{#if mainImage}} +
- + + + {{/if}} +
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 [](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 @@
+
+ {{>connectionBanner}}
+ 自动标注 标注陌生人 请输入要标注人的名称 标注处理中 标注成功 标注失败
+ 监控组
+
+ 设备
+
+ {{followerName}}
+
+ {{else}}
+
+ {{/if}}
+ {{group_name}}
+
+
+
+
+
+
+
+



+ 出现提醒
+ {{#if showRedSpot}}
+
+ {{/if}}
+ {{name}}
+
加载中...
+
加载中...
+
加载中...
+
加载中...
+
+
+
+
+ {{#if name}}
+ {{name}}
+
+ {{else}}
+ 未知设备
+
+ {{/if}}
+
+
加载中...
+ 点击头像进行操作
+
+ {{else}}
+ {{#if isSuccess}}
+
+ {{else}}
+
+ {{/if}}
+ {{/if}}
+ 失败原因
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 = '您的设备未接通,请检查设备连接后再次进行部署评测
+ | 姓名 | +状态 | +进 | +出 | +||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
+ {{person_name}}
+
+ {{#if app_user_id}}
+
+ {{#if enable_push app_notifaction_status}}
+
+ {{else}}
+
+ {{/if}}
+ {{else}}
+
+ {{/if}}
+
+ |
+ + {{#if isStatusIN status}} + + {{else}} + + {{/if}} + {{#if isHistoryNoOut group_id}} + — + {{else}} + {{InComTimeLen group_id}} + {{/if}} + | + + {{#if isInStatusNormal in_status}} +
+ |
+ {{/if}}
+ {{#if isInStatusWarning in_status}}
+
+ |
+ {{/if}}
+ {{#if isInStatusError in_status}}
+ {{#if isMySelf app_user_id}}
+
+ |
+ {{else}}
+
+ |
+ {{/if}}
+ {{/if}}
+ {{#if isInStatusUnknown in_status}}
+ {{#if isMySelf app_user_id}}
+ — | + {{else}} +— | + {{/if}} + {{/if}} + + {{#if isCurrentStatusIN status}} + {{#if isMySelf app_user_id}} +— | + {{else}} +— | + {{/if}} + {{else}} + {{#if isOutStatusNormal out_status}} +
+ |
+ {{/if}}
+ {{#if isOutStatusWarning out_status}}
+
+ |
+ {{/if}}
+ {{#if isOutStatusError out_status}}
+ {{#if isMySelf app_user_id}}
+
+ |
+ {{else}}
+
+ |
+ {{/if}}
+ {{/if}}
+ {{#if isOutStatusUnknown out_status}}
+ {{#if isMySelf app_user_id}}
+ — | + {{else}} +— | + {{/if}} + {{/if}} + {{/if}} + +
|
+ {{#if whats_up}}
+
+ {{#each whatsUpLists}}
+ {{person_name}}[{{getShortTime ts ../group_id}}]: {{content}} + {{/each}} + {{else}} + 今天还没有工作安排... + {{/if}} + |
+ |||||||||||||||
加载中...
+ 1.点击头像进行操作
+ +
{{/if}}
+ + + {{imageCount}} 张照片 + {{#if isNeedLabelMore}}标记{{/if}} +
+
加载中...
+ 请选择通知方式
APP推送通知
+ {{#if getIsShow this}} + {{/if}} +邮件通知
+ {{#if getIsShow this}} + {{/if}} +以下事件发生时,通知您
动静报告
+ {{#if getIsShow this}} + {{/if}} +不熟悉的人
+ {{#if getIsShow this}} + {{/if}} +以下熟人出现时,通知您
{{person_name}}
+当使用严格匹配时,会将部分熟人当作陌生人,需要更多的照片标注来避免此类情况
+ + {{else}} + +当使用宽松匹配时,会将部分陌生人识别为熟人。
+ + {{/if}} +新人观察
+是否认识这个人
+NO
+YES
+
加载中...
+ 您还没有加入任何监控组!
+
加载中...
+ 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

加载中
{{name}}
+{{person_name}}
+ {{getTime}} +{{data.in_company_tlen}}
+ {{#if data.in_time}} +上班时间: {{data.in_time}}
+ {{/if}} + {{#if data.out_time}} +最近活动时间: {{data.out_time}}
+ {{/if}} +


+ 当前没有合辑, 马上创建一个
+ 加载中...
+ {{else}} + + {{#if data}} +暂无数据
+ {{/if}} + + {{/if}} +{{formatDate}}
+{{device_name}}
+你认识这个人吗?
+{{formatDate}}
+{{device_name}}
+你认识这个人吗?
+
小贴士控制您的Home AI家庭系统
+通过手机APP随时随地
+了解家中情况,让家近在眼前
+即智能又简便
+与多款智能摄像头适配,告别繁复
+安装与设置。嵌入式AI精准识别,不错过任何一个动静
+





+
+
+ | 姓名 | +状态 | +进 | +出 | + +||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
+ {{person_name}}
+
+ {{#if app_user_id}}
+
+ {{#if enable_push app_notifaction_status}}
+
+ {{else}}
+
+ {{/if}}
+ {{else}}
+
+ {{/if}}
+
+ |
+ + {{#if isStatusIN status}} + + {{else}} + + {{/if}} + {{#if isHistoryNoOut group_id}} + — + {{else}} + {{InComTimeLen group_id}} + {{/if}} + | + + {{#if isInStatusNormal in_status}} +
+ |
+ {{/if}}
+ {{#if isInStatusWarning in_status}}
+
+ |
+ {{/if}}
+ {{#if isInStatusError in_status}}
+ {{#if isMySelf app_user_id}}
+
+ |
+ {{else}}
+
+ |
+ {{/if}}
+ {{/if}}
+ {{#if isInStatusUnknown in_status}}
+ {{#if isMySelf app_user_id}}
+ — | + {{else}} +— | + {{/if}} + {{/if}} + + {{#if isCurrentStatusIN status}} + {{#if isMySelf app_user_id}} +— | + {{else}} +— | + {{/if}} + {{else}} + {{#if isOutStatusNormal out_status}} +
+ |
+ {{/if}}
+ {{#if isOutStatusWarning out_status}}
+
+ |
+ {{/if}}
+ {{#if isOutStatusError out_status}}
+ {{#if isMySelf app_user_id}}
+
+ |
+ {{else}}
+
+ |
+ {{/if}}
+ {{/if}}
+ {{#if isOutStatusUnknown out_status}}
+ {{#if isMySelf app_user_id}}
+ — | + {{else}} +— | + {{/if}} + {{/if}} + {{/if}} + +
|
+ {{#if whats_up}}
+
+ {{#each whatsUpLists}}
+ {{person_name}}[{{getShortTime ts ../group_id}}]: {{content}} + {{/each}} + {{else}} + 今天还没有工作安排... + {{/if}} + |
+
+ |||||||||||||||
| + | 在监控组 | ++ | 不在监控组 | +
| + | 已绑定App | ++ | 未绑定App | +
| + | 允许通知 | ++ | 关闭通知 | +
|
+
+ |
+ 出现时间大于8小时 或 9:00am前出现 | +||
|
+
+ |
+ 出现时间小于8小时 或 9:00am后出现 | +||
|
+
+ |
+ 记录到出但没记录到进 或 记录到两次进但没记录到出 | +||
+

加载中
上班时间: {{getTime 'in'}}
+最近活动: {{getTime 'out'}}
++ 工作安排: + + + +
+{{person_name}}[{{getShortTime}}]: {{content}}
+ {{/each}} + {{else}} + 暂无工作安排... + {{/if}} +
暂无数据
{{day_title}}
+ +
加载中
上班时间: {{getTime 'in'}}
+最近活动: {{getTime 'out'}}
++ 工作安排: + + + +
+{{name}}[{{getShortTime}}]: {{content}}
+ {{/each}} + {{else}} + 暂无工作安排... + {{/if}} +
暂无数据
+ n&&(n+=(new Date).getFullYear()-(new Date).getFullYear()%100+(n<=("string"!=typeof h?h:(new Date).getFullYear()%100+parseInt(h,10))?0:-100));if(-1
o;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++)(e f)&&q[A[c]].push(e);g f&&(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=1 m(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=$;N 0?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;B P:b I)&&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&&(5
d&&(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;a V?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:"+(130 b;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=''+t.fromText+''+(o||" ")+''+ +t.toText+''+(z||" ")+"';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='
";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='- ")});r+="
'+(n.getTime&&!q?'",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=''+e.formatDate(s.timeFormat,n)+"":"")+b.text+"'+(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+""}return{marked:c,selected:!1,cssClass:c?"mbsc-cal-day-marked":"",ariaLabel:O?d:"",markup:O&&d?'';for(b=0;b";h+="":O&&g?'"+ +d+"").text()+'"'+(e?' style="background:'+e+";color:"+f+';text-shadow:none;"':"")+">"+g+d+"'+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?'":"";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=' "}return{marked:a, +selected:e,cssClass:a?"mbsc-cal-day-marked":"",ariaLabel:L?f:"",markup:L&&f?'';for(d=0;d";j+="":L&&i?'"+f+"").text()+'"'+(g?' style="background:'+g+";color:"+h+';text-shadow:none;"':"")+">"+i+f+"'+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/7 a&&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"));-1 o[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&&9 n(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 @@ + + {{#if wasConnected}} + {{#if isDisconnected}} + + {{/if}} + {{else}} + + {{/if}} + \ 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;g d&&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_xis 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;i div,#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(' ') + '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 @@ + {{>connectionBanner}}'+text+'
+ + {{#if isLoggingIn}}+ + +{{>spinner}}{{_ "funnyLogin"}}{{else}} + + +++
-+ + + + + + + {{/if}} + +登录-注册- +{{_ "theme_blue_loginWithAccount"}}+{{_ "theme_blue_regWithEmail"}}+ ++ {{#if resetPassword}} + ++ 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 @@ +{{>connectionBanner}}++ {{else}} +++ +{{_ "resetPassword"}}
+
+
+
++ {{_ "gst"}}
++ {{_ "gstAdWord"}}
+
+
+ + {{/if}} + +-+- 登录 + + + 登录 ++-++
昆明讯动科技有限公司+ +- {{>loginProblemsPrompt}}-联系点圈客服
+联系来了吗客服
×@@ -37,35 +48,23 @@联系点圈客服
发送- --- 重设密码 + {{_ "resetPassword"}}昆明讯动科技有限公司+{{_ "kmxdkj"}}- -- - 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 @@ + +--忘记密码?-联系客服-取消-++ + + ++ {{_ "followPeople"}}
+ {{_ "knowActivity"}}
+ {{> follow_user}} +
+ +++ + ++++ {{#each follows}} + {{> follow_user_list}} + {{/each}} +++++++ {{fullname}} + + {{#if isFollowed this}} + + {{else}} + + {{/if}} +
{{desc}}++++
+
+
+ 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 @@-- “点圈”服务告知 - ++ “来了吗”用户协议 ++ + 返回 +- {{#if hasRecommends}} --{{> recommends}} @@ -32,15 +32,12 @@+
@@ -13,7 +16,7 @@ -
【首部及导言】 +
【首部及导言】
@@ -21,7 +24,7 @@ -欢迎您使用点圈手机应用! +
欢迎您使用来了吗手机应用!
@@ -29,7 +32,7 @@ -点圈手机应用(以下简称“本服务”),您应当阅读并遵守《点圈服务告知》(以下简称“本告知”)。请您务必审慎阅读、充分理解各条款内容,特别是限制或免除责任的相应条款,以及开通或使用某项服务的单独文件,并选择接受或不接受。限制或免除责任条款可能以加粗形式提示您注意。 +
来了吗手机应用(以下简称“本服务”),您应当阅读并遵守《来了吗服务告知》(以下简称“本告知”)。请您务必审慎阅读、充分理解各条款内容,特别是限制或免除责任的相应条款,以及开通或使用某项服务的单独文件,并选择接受或不接受。限制或免除责任条款可能以加粗形式提示您注意。
@@ -53,7 +56,7 @@ -一、【告知的范围】 +
一、【告知的范围】
@@ -61,14 +64,14 @@ -本告知内容为您与点圈之间关于使用本服务所应遵守的事宜。 +
本告知内容为您与来了吗之间关于使用本服务所应遵守的事宜。
-二、【用户个人信息保护】 +
二、【用户个人信息保护】
@@ -76,21 +79,21 @@ -2.1 您在申请本服务过程中,需要填写一些必要的信息,请保持这些信息的及时更新,以便点圈向您提供服务。若国家法律法规有特殊规定的,您需要填写真实的身份信息。若您填写的信息不完整或不准确,则可能无法使用服务或在使用过程中受到限制。 +
2.1 您在申请本服务过程中,需要填写一些必要的信息,请保持这些信息的及时更新,以便来了吗向您提供服务。若国家法律法规有特殊规定的,您需要填写真实的身份信息。若您填写的信息不完整或不准确,则可能无法使用服务或在使用过程中受到限制。
-2.2 本服务与用户一同致力于个人信息的保护,保护用户个人信息是的一项基本原则。未经您的同意,点圈不会向点圈平台商户、点圈以外的任何公司、组织和个人披露您的个人信息,但您违反本告知约定导致他人投诉或者主管机关追究责任,以及法律法规另有规定的除外。 +
2.2 本服务与用户一同致力于个人信息的保护,保护用户个人信息是的一项基本原则。未经您的同意,来了吗不会向来了吗平台商户、来了吗以外的任何监控组、组织和个人披露您的个人信息,但您违反本告知约定导致他人投诉或者主管机关追究责任,以及法律法规另有规定的除外。
-2.3 您理解并同意:鉴于本服务为网络信息服务,为改善用户体验,点圈可以: +
2.3 您理解并同意:鉴于本服务为网络信息服务,为改善用户体验,来了吗可以:
@@ -106,7 +109,7 @@ -(2)对您的昵称、头像以及在本服务中的相关操作信息等信息进行使用,并可通过点圈服务或点圈服务中的第三方提供的服务向您本人、其他用户展示。 +
(2)对您的昵称、头像以及在本服务中的相关操作信息等信息进行使用,并可通过来了吗服务或来了吗服务中的第三方提供的服务向您本人、其他用户展示。
@@ -122,7 +125,7 @@ -您理解并同意:为改善用户体验,点圈可能会将您的个人信息的公开范围默认设置为公开,该默认设置会导致他人接触或获取您的个人信息;如您希望变更默认设置,请您在相关服务页面予以变更。 +
您理解并同意:为改善用户体验,来了吗可能会将您的个人信息的公开范围默认设置为公开,该默认设置会导致他人接触或获取您的个人信息;如您希望变更默认设置,请您在相关服务页面予以变更。
@@ -137,7 +140,7 @@ -三、【使用规范】 +
三、【使用规范】
@@ -145,7 +148,7 @@ -本服务仅供您个人以非商业目的使用,除非经点圈书面许可,您不得进行以下行为: +
本服务仅供您个人以非商业目的使用,除非经来了吗书面许可,您不得进行以下行为:
@@ -173,14 +176,14 @@ -3.4 其他未经点圈书面许可的行为。 +
3.4 其他未经来了吗书面许可的行为。
-四、【第三方产品和服务】 +
四、【第三方产品和服务】
@@ -195,7 +198,7 @@ -4.2 如您在使用第三方产品或服务时发生任何纠纷的,请您与第三方直接联系,点圈不承担任何责任,但可根据需要会依法提供必要的协助。 +
4.2 如您在使用第三方产品或服务时发生任何纠纷的,请您与第三方直接联系,来了吗不承担任何责任,但可根据需要会依法提供必要的协助。
@@ -203,7 +206,7 @@ -五、【数据的储存】 +
五、【数据的储存】
@@ -211,7 +214,7 @@ -5.1 点圈不对您在本服务中相关数据的删除或储存失败负责,您应对重要数据在本服务之外自行进行备份。 +
5.1 来了吗不对您在本服务中相关数据的删除或储存失败负责,您应对重要数据在本服务之外自行进行备份。
@@ -219,14 +222,14 @@ -5.2 点圈有权根据实际情况自行决定在服务器上对您在本服务中的数据的最长储存期限、为单个用户分配的最大存储使用空间等。您可根据自己的需要自行备份本服务中的相关数据。 +
5.2 来了吗有权根据实际情况自行决定在服务器上对您在本服务中的数据的最长储存期限、为单个用户分配的最大存储使用空间等。您可根据自己的需要自行备份本服务中的相关数据。
-5.3 如果您的服务被终止或取消,点圈可以从服务器上永久地删除您的数据。服务终止或取消后,点圈没有义务向您返还或恢复任何数据。 +
5.3 如果您的服务被终止或取消,来了吗可以从服务器上永久地删除您的数据。服务终止或取消后,来了吗没有义务向您返还或恢复任何数据。
@@ -234,7 +237,7 @@ -六、【用户发布的内容】 +
六、【用户发布的内容】
@@ -273,7 +276,7 @@ -6.3 点圈特别提醒您注意,鉴于本服务的社交服务特性及用户的社交需求,您发布的部分内容的公开范围,本服务可能默认设置为公开。该默认设置,会允许使用本服务的其他用户浏览、转发、评论等,若您想改变该默认设置,请您在相关设置中进行改变。 +
6.3 来了吗特别提醒您注意,鉴于本服务的社交服务特性及用户的社交需求,您发布的部分内容的公开范围,本服务可能默认设置为公开。该默认设置,会允许使用本服务的其他用户浏览、转发、评论等,若您想改变该默认设置,请您在相关设置中进行改变。
@@ -281,7 +284,7 @@ -七、【用户行为规范】 +
七、【用户行为规范】
@@ -331,7 +334,7 @@ -7.2 如果点圈发现或收到他人举报您发布的信息违反本告知约定的,点圈有权进行独立判断并采取技术手段予以删除、屏蔽或断开链接。同时,点圈有权视用户的行为性质,采取包括但不限于限制、暂停或终止您使用本服务的全部或部分功能,追究法律责任等措施。 +
7.2 如果来了吗发现或收到他人举报您发布的信息违反本告知约定的,来了吗有权进行独立判断并采取技术手段予以删除、屏蔽或断开链接。同时,来了吗有权视用户的行为性质,采取包括但不限于限制、暂停或终止您使用本服务的全部或部分功能,追究法律责任等措施。
@@ -339,7 +342,7 @@ -若您违反本告知及本服务相关协议、规则或相关法规政策,被投诉多次的,点圈有权不经您同意而直接解除您的好友关系。 +
若您违反本告知及本服务相关协议、规则或相关法规政策,被投诉多次的,来了吗有权不经您同意而直接解除您的好友关系。
@@ -347,7 +350,7 @@ -7.3 您理解并同意,若您的单向或双向好友(简称“违规用户”)违反《点圈服务告知》或相关法规政策的,点圈将依照相关文件、规则对违规用户进行处理,由此可能影响您与违规用户之间的信息交流、好友关系、好友动态等数据或您对本服务的使用。 +
7.3 您理解并同意,若您的单向或双向好友(简称“违规用户”)违反《来了吗服务告知》或相关法规政策的,来了吗将依照相关文件、规则对违规用户进行处理,由此可能影响您与违规用户之间的信息交流、好友关系、好友动态等数据或您对本服务的使用。
@@ -355,7 +358,7 @@ -您理解并同意这是点圈公司为了维护健康良好的网络环境而采取的必要措施,若由于点圈按照前述约定对违规用户采取措施而对您产生影响或任何损失的,您同意不追究点圈的任何责任或不向点圈主张任何权利。 +
您理解并同意这是来了吗监控组为了维护健康良好的网络环境而采取的必要措施,若由于来了吗按照前述约定对违规用户采取措施而对您产生影响或任何损失的,您同意不追究来了吗的任何责任或不向来了吗主张任何权利。
@@ -363,14 +366,14 @@ -7.4 您违反本条约定,导致任何第三方损害的,您应当独立承担责任;点圈因此遭受损失的,您也应当一并赔偿。 +
7.4 您违反本条约定,导致任何第三方损害的,您应当独立承担责任;来了吗因此遭受损失的,您也应当一并赔偿。
-八、【风险与免责】 +
八、【风险与免责】
@@ -378,7 +381,7 @@ -基于互联网的开放性和社交网络服务的传播特殊性,点圈特别提醒您谨慎注意以下风险: +
基于互联网的开放性和社交网络服务的传播特殊性,来了吗特别提醒您谨慎注意以下风险:
@@ -386,7 +389,7 @@ -8.1 本服务仅提供一个在线资讯服务及社交的平台,您应当对其他用户使用本服务所发布的内容进行独立判断并承担因依赖该内容而引起的所有风险,包括但不限于因对内容的正确性、完整性或实用性的依赖而产生的风险以及因您个人信息被其他用户获知而带来的风险。您了解并确认,点圈不对本服务用户之间的纠纷承担任何责任。 +
8.1 本服务仅提供一个在线资讯服务及社交的平台,您应当对其他用户使用本服务所发布的内容进行独立判断并承担因依赖该内容而引起的所有风险,包括但不限于因对内容的正确性、完整性或实用性的依赖而产生的风险以及因您个人信息被其他用户获知而带来的风险。您了解并确认,来了吗不对本服务用户之间的纠纷承担任何责任。
@@ -394,7 +397,7 @@ -8.2 您在使用本服务过程中所发布的内容有可能会被第三方复制、转载、修改或做其他用途,脱离您的预期和控制,您必须充分意识此类风险的存在,点圈对由此产生的纠纷不予负责。 +
8.2 您在使用本服务过程中所发布的内容有可能会被第三方复制、转载、修改或做其他用途,脱离您的预期和控制,您必须充分意识此类风险的存在,来了吗对由此产生的纠纷不予负责。
@@ -410,7 +413,7 @@ -8.4 由于您或其他用户违反本服务相关规则导致被点圈依约处理的,可能会对您在本服务中的发布信息、好友信息等数据造成影响。 +
8.4 由于您或其他用户违反本服务相关规则导致被来了吗依约处理的,可能会对您在本服务中的发布信息、好友信息等数据造成影响。
@@ -418,7 +421,7 @@ -8.5 点圈不能对用户发表的文章和评论的正确性进行保证。用户在点圈发表的内容仅表明其个人的立场和观点,并不代表点圈的立场或观点。作为内容的发表者,需自行对所发表内容负责,因所发表内容引发的一切纠纷,由该内容的发表者承担全部法律及连带责任。点圈不承担任何法律及连带责任。 +
8.5 来了吗不能对用户发表的文章和评论的正确性进行保证。用户在来了吗发表的内容仅表明其个人的立场和观点,并不代表来了吗的立场或观点。作为内容的发表者,需自行对所发表内容负责,因所发表内容引发的一切纠纷,由该内容的发表者承担全部法律及连带责任。来了吗不承担任何法律及连带责任。
@@ -426,7 +429,7 @@ -8.6 点圈不保证网络服务一定能满足用户的要求,也不保证网络服务不会中断,对网络服务的及时性、安全性、准确性也都不作保证。 +
8.6 来了吗不保证网络服务一定能满足用户的要求,也不保证网络服务不会中断,对网络服务的及时性、安全性、准确性也都不作保证。
@@ -434,7 +437,7 @@ -九、【本服务的软件形式】 +
九、【本服务的软件形式】
@@ -442,35 +445,35 @@ -点圈和您还应遵守本条款的以下约定。 +
来了吗和您还应遵守本条款的以下约定。
-9.1 点圈可能为不同的终端设备开发不同的软件版本,您应当根据实际情况选择下载合适的版本进行安装。 +
9.1 来了吗可能为不同的终端设备开发不同的软件版本,您应当根据实际情况选择下载合适的版本进行安装。
-9.2 您可以直接从点圈的网站上获取软件,也可以从得到点圈授权的第三方获取。如果您从未经点圈授权的第三方获取软件或与软件名称相同的安装程序,点圈无法保证该软件能够正常使用,并对因此给您造成的损失不予负责。 +
9.2 您可以直接从来了吗的网站上获取软件,也可以从得到来了吗授权的第三方获取。如果您从未经来了吗授权的第三方获取软件或与软件名称相同的安装程序,来了吗无法保证该软件能够正常使用,并对因此给您造成的损失不予负责。
-9.3 为了增进用户体验、完善服务内容,点圈将不时提供软件更新版本升级。 +
9.3 为了增进用户体验、完善服务内容,来了吗将不时提供软件更新版本升级。
-为了改善用户体验,并保证服务的安全性和功能的一致性,点圈有权不经向您特别通知而对软件进行更新,或者对软件的部分功能效果进行改变或限制。 +
为了改善用户体验,并保证服务的安全性和功能的一致性,来了吗有权不经向您特别通知而对软件进行更新,或者对软件的部分功能效果进行改变或限制。
@@ -478,7 +481,7 @@ -9.4 软件新版本发布后,旧版软件可能无法使用。点圈不保证旧版软件继续可用及相应的客户服务,请您随时核对并下载最新版本。 +
9.4 软件新版本发布后,旧版软件可能无法使用。来了吗不保证旧版软件继续可用及相应的客户服务,请您随时核对并下载最新版本。
@@ -486,7 +489,7 @@ -9.5 除非法律允许或点圈书面许可,您不得从事下列行为: +
9.5 除非法律允许或来了吗书面许可,您不得从事下列行为:
@@ -507,14 +510,14 @@ -(3)对点圈拥有知识产权的内容进行使用、出租、出借、复制、修改、链接、转载、汇编、发表、出版、建立镜像站点等; +
(3)对来了吗拥有知识产权的内容进行使用、出租、出借、复制、修改、链接、转载、汇编、发表、出版、建立镜像站点等;
-(4)对软件或者软件运行过程中释放到任何终端内存中的数据、软件运行过程中客户端与服务器端的交互数据,以及软件运行所必需的系统数据,进行复制、修改、增加、删除、挂接运行或创作任何衍生作品,形式包括但不限于使用插件、外挂或非经点圈授权的第三方工具/服务接入软件和相关系统; +
(4)对软件或者软件运行过程中释放到任何终端内存中的数据、软件运行过程中客户端与服务器端的交互数据,以及软件运行所必需的系统数据,进行复制、修改、增加、删除、挂接运行或创作任何衍生作品,形式包括但不限于使用插件、外挂或非经来了吗授权的第三方工具/服务接入软件和相关系统;
@@ -528,28 +531,28 @@ -(6)通过非点圈开发、授权的第三方软件、插件、外挂、系统,登录或使用点圈软件及服务,或制作、发布、传播上述工具; +
(6)通过非来了吗开发、授权的第三方软件、插件、外挂、系统,登录或使用来了吗软件及服务,或制作、发布、传播上述工具;
-(7)其他未经点圈明示授权的行为。 +
(7)其他未经来了吗明示授权的行为。
-如果您实施了前款约定的行为,点圈将保留依法追究法律责任的权利。 +
如果您实施了前款约定的行为,来了吗将保留依法追究法律责任的权利。
-十、【其他】 +
十、【其他】
@@ -557,14 +560,14 @@ -10.1 您使用本服务即视为您已阅读并同意受本告知的约束。点圈有权在必要时修改本告知条款。您可以在相关页面中查阅最新的协议条款。本告知条款变更后,如果您继续使用本服务,即视为您已接受修改后的协议。如果您不接受修改后的协议,应当停止使用本服务。 +
10.1 您使用本服务即视为您已阅读并同意受本告知的约束。来了吗有权在必要时修改本告知条款。您可以在相关页面中查阅最新的协议条款。本告知条款变更后,如果您继续使用本服务,即视为您已接受修改后的协议。如果您不接受修改后的协议,应当停止使用本服务。
-10.2点圈可能不断发布的关于本服务的相关协议、业务规则等内容。上述内容一经正式发布,即为本告知不可分割的组成部分,您同样应当遵守。 +
10.2来了吗可能不断发布的关于本服务的相关协议、业务规则等内容。上述内容一经正式发布,即为本告知不可分割的组成部分,您同样应当遵守。
diff --git a/client/registration/signupForm/signupForm.coffee b/client/registration/signupForm/signupForm.coffee index f28f13279..f3e747e50 100644 --- a/client/registration/signupForm/signupForm.coffee +++ b/client/registration/signupForm/signupForm.coffee @@ -1,4 +1,6 @@ userNameLength = signUpUserNameLength || 16 +# id1 = null +# id2 = null Template.signupForm.onRendered ()-> $('#signup-username').bind('propertychange input',(e)-> names = $(e.target).val().trim() @@ -15,16 +17,21 @@ Template.signupForm.helpers '' namesMaxLength:-> return userNameLength - email:-> - if Session.get("signUpMail") - Session.get("signUpMail") - else - '' + # email:-> + # if Session.get("signUpMail") + # Session.get("signUpMail") + # else + # '' pwd:-> if Session.get("signUpPwd") Session.get("signUpPwd") else '' + repwd:-> + if Session.get('signUpRepwd') + Session.get('signUpRepwd') + else + '' Template.signupForm.events # 'keyup #signup-username': (e,t)-> # names = t.find('#signup-username').value @@ -32,29 +39,39 @@ Template.signupForm.events # html += names.length # html += '/'+userNameLength # $('#signup-username-help').html(html) - 'focus input':(e,t)-> - Meteor.setTimeout -> - $('.company').css('display','none') - ,10 - 'blur input':(e,t)-> - Meteor.setTimeout -> - $('.company').css('display','block') - ,10 + # '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 .term_notice' :(e,t)-> names = t.find('#signup-username').value - email = t.find('#signup-email').value.toLowerCase() + # email = t.find('#signup-email').value.toLowerCase() pwd = t.find('#signup-password').value + repwd = t.find('#signup-repassword').value Session.set("signUpName", names) - Session.set("signUpMail", email) + # Session.set("signUpMail", email) + Session.set('signUpRepwd',repwd) Session.set("signUpPwd", pwd) - Session.set("dealBack",'register') Router.go '/deal_page' 'click #btn_back' :-> $('input').blur() Session.set("signUpName", '') Session.set("signUpMail", '') + Session.set('signUpRepwd','') Session.set("signUpPwd", '') - Router.go '/authOverlay' + PUB.back() # $('.register').css('display',"none") # $('#login').css('display',"block") # $('#weibo').css('display',"block") @@ -71,16 +88,19 @@ Template.signupForm.events email = t.find('#signup-email').value.toLowerCase() Session.set 'userName',names pass1 = t.find('#signup-password').value + pass2 = t.find('#signup-repassword').value myRegExp = /[a-z0-9-]{1,30}@[a-z0-9-]{1,65}.[a-z]{2,6}/ ; if names is '' - PUB.toast '请输入姓名!' + PUB.toast '用户名不能为空!' else if myRegExp.test(email) is false - PUB.toast '你的邮箱有误!' + PUB.toast '邮箱格式有误,请重新输入.' + else if pass1 != pass2 + PUB.toast '密码输入不一致,请重新输入.' else if pass1.length < 6 PUB.toast '密码至少要6位!' else t.find('#sub-registered').disabled = true - t.find('#sub-registered').value = '正在提交信息...' + t.find('#sub-registered').innerText = '正在提交信息...' Accounts.createUser username:Session.get('userName') email:email @@ -92,15 +112,32 @@ Template.signupForm.events (err)-> if err console.log err - PUB.toast '注册失败,邮箱或姓名可能已经存在!' + trackEvent("signupuser","user signup failure.") + PUB.toast '注册失败,用户名或邮箱已经注册!' t.find('#sub-registered').disabled = false - t.find('#sub-registered').value = '创建帐户' + t.find('#sub-registered').innerText = '创建帐户' else - page = Session.get('routerWillRenderPage') - if page - Router.go page + trackEvent("signupuser","user signup succeed.") + window.plugins.userinfo.setUserInfo Meteor.user()._id, -> + console.log 'setUserInfo was succeed!' + return + , -> + console.log 'setUserInfo was Error!' + return + #Router.go '/registerFollow' + #ScanBarcodeByBarcodeScanner() + # if window.localStorage.getItem("isSecondUse") == 'true' + # Router.go('/') + # else + if window.localStorage.getItem("enableHomeAI") == 'true' + Router.go('/scene') else - Router.go '/' + Meteor.call 'enableHomeAI',(err,res)-> + if !err and res is true + window.localStorage.setItem("enableHomeAI",'true') + Router.go('/scene') + else + Router.go('/introductoryPage') return false diff --git a/client/registration/signupForm/signupForm.html b/client/registration/signupForm/signupForm.html index f00d8f2b0..309f24565 100644 --- a/client/registration/signupForm/signupForm.html +++ b/client/registration/signupForm/signupForm.html @@ -1,31 +1,39 @@ + {{>connectionBanner}}diff --git a/client/scannerAddDevice/scannerAddDevice.html b/client/scannerAddDevice/scannerAddDevice.html new file mode 100644 index 000000000..123700c9a --- /dev/null +++ b/client/scannerAddDevice/scannerAddDevice.html @@ -0,0 +1,61 @@ + +-- 创建账户 + {{_ "createAccount"}}昆明讯动科技有限公司+ +++ ++ ++ +++ + + ++ + + + + + + + ++++ + diff --git a/client/scannerAddDevice/scannerAddDevice.js b/client/scannerAddDevice/scannerAddDevice.js new file mode 100644 index 000000000..ed421d52f --- /dev/null +++ b/client/scannerAddDevice/scannerAddDevice.js @@ -0,0 +1,140 @@ +var zeroconf; +if(Meteor.isCordova){ + Meteor.startup(function(){ + document.addEventListener("deviceready", function(){ + zeroconf = cordova.plugins.zeroconf; + }, false); + }); +} +// 扫描到的设备列表 +var scanLists = new ReactiveVar([]); +var scanIds = new ReactiveVar([]); + +Template.scannerAddDevice.helpers({ + isScanning: function() { + return isScanning.get(); + }, + scanLists: function() { + return scanLists.get(); + }, + isInDB: function() { + return this.isInDB ? this.isInDB : false; + }, + getIp: function() { + var ipv4 = this.ipv4Addresses; + if(ipv4 && ipv4.length > 0) { + return ipv4[0]; + } + }, + formatDate: function(date) { + if (!date) { + return '' + } + var d = new Date(date); + return d.parseDate('YYYY/MM/DD hh:ss'); + } +}); + +Template.scannerAddDevice.events({ + 'click .leftButton': function(e) { + return PUB.page('/'); + }, + /** + * add device + * 1. select or create a group + * 2. add device to group + */ + 'click .scanListItem': function(e) { + var self = this; + console.log("self = "+JSON.stringify(self)); + return window.SELECT_CREATE_GROUP.show(self, function() { + var lists = scanLists.get(); + var ids = scanIds.get(); + + var uuid = (self.txtRecord && self.txtRecord.uuid) ? self.txtRecord.uuid:''; + var index = ids.indexOf(uuid); + if(index > -1){ + ids.splice(index,1); + lists.splice(index,1); + } + scanLists.set(lists); + scanIds.set(ids); + }); // see selectCreateGroup.js + } +}); + +Template.scannerAddDevice.onRendered(function() { + console.log("Template.scannerAddDevice.onRendered") + scanLists.set([]); + scanIds.set([]); + var watchStr = '_DeepEye._tcp.'; + if (device.platform === 'iOS') + watchStr = '_DeepEye._tcp'; + // 页面初始化完成后, 自动开始扫描设备 + zeroconf && zeroconf.watch(watchStr, 'local.',function(result) { + console.log("zeroconf.watch in"); + console.log("zeroconf.watch in, result="+JSON.stringify(result)); + var lists = scanLists.get(); + var ids = scanIds.get(); + + var action = result.action; + var service = result.service; + + var uuid = (service.txtRecord && service.txtRecord.uuid) ? service.txtRecord.uuid:''; + var index = ids.indexOf(uuid); + + Meteor.call('upsetDeepVideoDevices', result); + + if( action == 'added' ) { + console.log('service added', JSON.stringify(service)); + if(index < 0 && service && service.name && service.ipv4Addresses && service.ipv4Addresses.length > 0){ + // check is device in db + console.log("service.ipv4Addresses="+service.ipv4Addresses); + Meteor.call('isDeviceInDB', uuid, function(error, result){ + console.log('isDeviceInDB result = ', JSON.stringify(result)) + function isUuidInList(uuid, lists) { + for (var i = 0; i < lists.length; i++) { + if (lists[i].uuid == uuid) + return true; + } + return false; + } + if(!error/* && !result*/) { + service.uuid = uuid; + if (result && result.length > 0) { + service.isInDB = true; + service._id = result[0]._id; + service.groupId = result[0].groupId; + } else { + service.isInDB = false; + } + if (!isUuidInList(uuid, lists)) { + lists.push(service); + ids.push(uuid); + } + } + scanLists.set(lists); + scanIds.set(ids); + console.log('scanLists is ', JSON.stringify(scanLists.get())); + }); + } + + } else { + if(index > -1){ + ids.splice(index,1); + lists.splice(index,1); + } + scanLists.set(lists); + scanIds.set(ids); + console.log('scanLists is ', JSON.stringify(scanLists.get())); + console.log('service removed', JSON.stringify(service)); + } + }); +}); + +Template.scannerAddDevice.onDestroyed(function (){ + var watchStr = '_DeepEye._tcp.'; + if (device.platform === 'iOS') + watchStr = '_DeepEye._tcp'; + zeroconf && zeroconf.unwatch(watchStr, 'local.'); +}); diff --git a/client/scannerAddDevice/scannerAddDevice.less b/client/scannerAddDevice/scannerAddDevice.less new file mode 100644 index 000000000..61966b150 --- /dev/null +++ b/client/scannerAddDevice/scannerAddDevice.less @@ -0,0 +1,70 @@ +/* +scannerAddDevice Page +*/ +.boxDeviceLists { + margin: 0; padding: 0; + .ul-line{ + list-style: none; + line-height: 20px; + padding: 10px 5px; + background: #efefef; + font-size: 12px; + } + .boxDeviceItem{ + list-style: none; overflow: hidden;padding: 10px 0; border-bottom: 1px solid #efefef; margin: 0 5px; + h2 {margin: 0; font-size: 14px; line-height: 20px;} + p{margin: 0; font-size: 12px; line-height: 20px;} + } + .boxDeviceIcon { + height: 40px; + width: 40px; + margin-right: 10px; + float: left; + text-align: center; + line-height: 40px; + i { font-size: 40px;} + } + .boxDeviceCheckedIcon { + height: 40px; + width: 40px; + right: 0px; + line-height: 40px; + position: absolute; + i.fa-plus { font-size: 20px; color: red;} + i.fa-pencil-square-o { font-size: 20px;} + } +} + + +// selectCreateGroup +.selectCreateGroup{ + background: #efefef; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 99990; + overflow: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + -webkit-touch-callout: none; // 禁止长按菜单 +} + +// deviceSetting + +.deviceSetting{ + background: #efefef; min-height: 100%; +} +.deleteDevice{ + position: relative; + left: 2.5%; + width: 95%; + text-align: center; + font-size: 16px; + background-color: #f56262; + color: #ffffff !important; + height: 40px; + border-radius: 5px; + padding-top: 10px; +} \ No newline at end of file diff --git a/client/scannerAddDevice/selectCreateGroup.html b/client/scannerAddDevice/selectCreateGroup.html new file mode 100644 index 000000000..f1c001468 --- /dev/null +++ b/client/scannerAddDevice/selectCreateGroup.html @@ -0,0 +1,51 @@ + ++ +++ {{#if isCreating}} ++ \ No newline at end of file diff --git a/client/scannerAddDevice/selectCreateGroup.js b/client/scannerAddDevice/selectCreateGroup.js new file mode 100644 index 000000000..47fb420b7 --- /dev/null +++ b/client/scannerAddDevice/selectCreateGroup.js @@ -0,0 +1,195 @@ +var view = null; +var d = null; +var cb = null; + +SELECT_CREATE_GROUP = { + show: function(data, callback){ + SELECT_CREATE_GROUP.close(); + d = data; + if (callback) { + cb = callback; + } + view = Blaze.renderWithData(Template.selectCreateGroup, data, document.body ); + }, + close: function(){ + if(view){ + Blaze.remove(view); + view = null; + d = null; + cb = null; + } + }, + isShow: function(){ + return view != null; + } +}; +window.SELECT_CREATE_GROUP = SELECT_CREATE_GROUP; + +var isCreating = new ReactiveVar(false); +var addDeviceToGroup = function(group_id, group_name) { + d.groupId = group_id; + d.groupName = group_name; + + var uuid = d.uuid; + console.log(d); + PUB.showWaitLoading('正在添加设备'); + Meteor.subscribe('devices-by-uuid',uuid, function() { + if ( Devices.find({uuid: uuid}).count() > 0 ) { + PUB.hideWaitLoading() + // return PUB.toast('该设备已被其他用户绑定'); + changeDeviceGroup(group_id,group_name); + } else { + var user = Meteor.user(); + d.userId = Meteor.userId(); + d.userName = user.profile.fullname ? user.profile.fullname: user.username; + d.userIcon = user.profile.icon; + d.latestUpdateAt = new Date(); + d.status = 'online'; + + Devices.insert(d, function(error , result){ + PUB.hideWaitLoading(); + if(error){ + console.log(error); + return PUB.toast('添加设备失败~'); + } + cb && cb(); + //$.post("http://workaihost.tiegushi.com/restapi/workai-join-group", {uuid: uuid, group_id: group_id, name: uuid, in_out: "in"}, function(data) { + // var msgBody = {_id: new Mongo.ObjectID()._str, uuid: uuid, type: 'text', text: 'groupchanged'}; + // sendMqttMessage('/msg/d/'+uuid, msgBody); + //}); + + Meteor.call('join-group',uuid, group_id, uuid, "in",function(err,result){ + console.log('meteor call result:',result) + //var msgBody = {_id: new Mongo.ObjectID()._str, group_id: group_id, uuid: uuid, type: 'text', text: 'groupchanged'}; + //sendMqttMessage('/msg/d/'+uuid, msgBody); + }); + SELECT_CREATE_GROUP.close(); + //return PUB.toast('添加设备成功'); + $('#addDeviceResultText').html('添加设备成功'); + $('#addDeviceResult').modal('show'); + }); + } + }); +}; + +var changeDeviceGroup = function(group_id,group_name){ + PUB.showWaitLoading('正在处理'); + var uuid = (d.txtRecord && d.txtRecord.uuid) ? d.txtRecord.uuid:''; + console.log("d._id="+d._id+", uuid="+uuid); + Devices.update({_id:d._id}, { + $set:{ + groupId: group_id, + groupName: group_name, + uuid: uuid + } + },function(error , result){ + PUB.hideWaitLoading(); + if(error){ + console.log(error); + return PUB.toast('请重试~'); + } + cb && cb(); + + Meteor.call('join-group',uuid, group_id, uuid, "in",function(err,result){ + console.log('meteor call result:',result) + //var msgBody = {_id: new Mongo.ObjectID()._str, group_id:group_id , uuid: uuid, type: 'text', text: 'groupchanged'}; + //sendMqttMessage('/msg/d/'+uuid, msgBody); + }); + //$.post("http://workaihost.tiegushi.com/restapi/workai-join-group", {uuid: uuid, group_id: group_id, name: uuid, in_out: "in"}, function(data) { + // var msgBody = {_id: new Mongo.ObjectID()._str, uuid: uuid, type: 'text', text: 'groupchanged'}; + // sendMqttMessage('/msg/d/'+uuid, msgBody); + //}); + SELECT_CREATE_GROUP.close(); + //return PUB.toast('群组已更改'); + $('#addDeviceResultText').html('添加设备成功'); + $('#addDeviceResult').modal('show'); + }) +} + +Template.selectCreateGroup.onRendered(function () { + Meteor.subscribe('get-my-group',Meteor.userId()); +}); + +Template.selectCreateGroup.helpers({ + groups: function() { + return SimpleChat.GroupUsers.find({user_id: Meteor.userId()}).fetch(); + }, + isCreating: function() { + return isCreating.get(); + } +}); + +Template.selectCreateGroup.events({ + 'click .back': function (e) { + return SELECT_CREATE_GROUP.close(); + }, + 'click .createNewGroup': function (e) { + isCreating.set(true); + }, + 'click .cancel': function (e) { + isCreating.set(false); + }, + 'click .save': function (e) { + return $('.setGroupname-form').submit(); + }, + 'submit .setGroupname-form': function(e) { + e.preventDefault(); + if (e.target.text.value == '') { + return PUB.toast('请输入群组名'); + } + var name = e.target.text.value; + var offsetTimeZone = (new Date().getTimezoneOffset())/-60; + + var id = new Mongo.ObjectID()._str; + var user = Meteor.user(); + SimpleChat.Groups.insert({ + _id: id, + name: name, + icon: '', + describe: '', + create_time: new Date(), + template:null, + offsetTimeZone: offsetTimeZone, + last_text: '', + last_time: new Date(), + barcode: rest_api_url + '/restapi/workai-group-qrcode?group_id=' + id, + //建群的人 + creator:{ + id:user._id, + name:user.profile && user.profile.fullname ? user.profile.fullname : user.username + } + }, function(error, result){ + if(error) { + console.log(error); + return PUB.toast('创建群组失败'); + } + SimpleChat.GroupUsers.insert({ + group_id: id, + group_name: name, + group_icon: '', + user_id: user._id, + user_name: user.profile && user.profile.fullname ? user.profile.fullname : user.username, + user_icon: user.profile && user.profile.icon ? user.profile.icon : '/userPicture.png', + create_time: new Date() + }); + if(d.get_group_only){ + cb && cb(id, name) + return + } + if(d.groupId) { // 如果有group_id, 设备更换新的群组 + return changeDeviceGroup(id, name); + } + return addDeviceToGroup(id, name); + }); + }, + 'click .selectGroup': function (e) { + if(d.get_group_only){ + cb && cb(this.group_id,this.group_name) + return + } + if(d.groupId) { // 如果有group_id, 设备更换新的群组 + return changeDeviceGroup(this.group_id,this.group_name); + } + return addDeviceToGroup(this.group_id, this.group_name); + } +}) diff --git a/client/scene/scene.coffee b/client/scene/scene.coffee new file mode 100644 index 000000000..8f672eafd --- /dev/null +++ b/client/scene/scene.coffee @@ -0,0 +1,11 @@ +if Meteor.isClient + # Template.introductoryPage.rendered=-> + # $('.content').css 'min-height',$(window).height() + + Template.scene.events + 'click #workaiScene':(event)-> + window.localStorage.setItem("isWorkAIScene",'true'); + Router.go('/introductoryPage'); + 'click #homeaiScene':(event)-> + window.localStorage.setItem("isWorkAIScene",'false'); + Router.go('/addHomeAIBox'); \ No newline at end of file diff --git a/client/scene/scene.html b/client/scene/scene.html new file mode 100644 index 000000000..bde0e9e2a --- /dev/null +++ b/client/scene/scene.html @@ -0,0 +1,16 @@ + + +++取消+保存+ 新建监控组 +++ {{else}} ++
+- +
++ ++++ ++ ++ 选择监控组 +++ {{/if}} ++
+ +- + +
++监控组列表++ {{#each groups}} +
+- +
+ {{/each}} ++ + + +
+++ \ No newline at end of file diff --git a/client/scene/scene.less b/client/scene/scene.less new file mode 100644 index 000000000..935db691e --- /dev/null +++ b/client/scene/scene.less @@ -0,0 +1,41 @@ +.scene{ + // 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: 7.5%; + right: 7.5%; + top: 20%; + height: 70%; + text-align: center; + color: #fff; + font-size: 16px; + } + .bg_img{ + height: 32%; + border-radius: 10px; + background-repeat: no-repeat; + background-size: cover !important; + } + .content_btn{ + top: 40%; + border-bottom-style: solid; + border-bottom-width: 1px; + } + #otherScene{ + border-bottom-style: solid; + border-bottom-width: 1px; + color: #37a7fe; + margin-left: 50%; + margin-top: 12%; + display: inline-block; + } +} diff --git a/client/search/search.coffee b/client/search/search.coffee new file mode 100644 index 000000000..e77d27c30 --- /dev/null +++ b/client/search/search.coffee @@ -0,0 +1,387 @@ +if Meteor.isClient + Template.search.onRendered ()-> + Meteor.subscribe("topics") + Meteor.subscribe("topicposts") + topics = Topics.find({type:"topic"}, {sort: {posts: -1},limit:20}) + themes = Topics.find({type:"theme"}, {sort: {posts: -1},limit:5}) + if topics.count() > 0 + Meteor.defer ()-> + Session.setPersistent('persistentTopics',topics.fetch()) + if themes.count() > 0 + Meteor.defer ()-> + Session.setPersistent('persistentThemes',themes.fetch()) + Template.search.helpers + theme:()-> + Session.get('persistentThemes') + topic:()-> + Session.get('persistentTopics') + Template.search.events + 'focus #search-box': (event)-> + PUB.page '/searchPeopleAndTopic' + 'click #follow': (event)-> + PUB.page '/searchFollow' + 'click .themeBtn': (event)-> + Session.set "topicId", @_id + Session.set "topicTitle", "#"+ @text + "#" + PUB.page '/topicPosts' + 'click .topic': (event)-> + Session.set "topicId", @_id + Session.set "topicTitle", "#"+ @text + "#" + PUB.page '/topicPosts' + Template.searchFollow.onRendered ()-> + Session.set('isSearching', false) + Session.set('is_fullname', true) + Meteor.subscribe 'follows' + $('#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 + options = {is_fullname:Session.get('is_fullname')} + FollowUsersSearch.search text,options + ) + Template.searchFollow.events + 'focus #search-box':-> + console.log("#search get focus"); + $('#footer').css('display',"none") + 'blur #search-box':-> + console.log("#search lost focus"); + $('#footer').css('display',"") + 'click #search_people_fullname':(event)-> + Session.set("is_fullname",true) + text = $('#search-box').val().trim() + if text is "" + Session.set 'isSearching', false + $('#search-box').trigger('focus') + return + Session.set 'isSearching', true + Session.set 'noSearchResult',false + Session.set 'searchLoading', true + options = {is_fullname:Session.get('is_fullname')} + FollowUsersSearch.search text,options + $('#search-box').trigger('focus') + + 'click #search_people_username':(event)-> + Session.set("is_fullname",false) + text = $('#search-box').val().trim() + if text is "" + Session.set 'isSearching', false + $('#search-box').trigger('focus') + return + Session.set 'isSearching', true + Session.set 'noSearchResult',false + Session.set 'searchLoading', true + options = {is_fullname:Session.get('is_fullname')} + FollowUsersSearch.search text,options + $('#search-box').trigger('focus') + + 'click .back': (event)-> + Session.set('is_fullname', true) + history.back() + 'click .delFollow':(e)-> + FollowerId = Follower.findOne({ + userId: Meteor.userId() + followerId: @_id + })._id + Follower.remove(FollowerId) + 'click .addFollow':(e)-> + if Meteor.user().profile.fullname + username = Meteor.user().profile.fullname + else + username = Meteor.user().username + if @profile.fullname + followername = @profile.fullname + else + followername = @username + insertObj = { + userId: Meteor.userId() + #这里存放fullname + userName: username + userIcon: Meteor.user().profile.icon + userDesc: Meteor.user().profile.desc + followerId: @_id + #这里存放fullname + followerName: followername + followerIcon: @profile.icon + followerDesc: @desc + createAt: new Date() + } + addFollower(insertObj) + 'click .del':(e)-> + followerId = e.currentTarget.id + FollowerId = Follower.findOne({ + userId: Meteor.userId() + followerId: followerId + })._id + Follower.remove(FollowerId) + 'click .add':(e)-> + if Meteor.user().profile.fullname + username = Meteor.user().profile.fullname + else + username = Meteor.user().username + insertObj = { + userId: Meteor.userId() + #这里存放fullname + userName: username + userIcon: Meteor.user().profile.icon + userDesc: Meteor.user().profile.desc + followerId: @userId + #这里存放fullname + followerName: @fullname + followerIcon: @icon + followerDesc: @desc + createAt: new Date() + } + addFollower(insertObj) + Template.searchFollow.helpers + isSearching:-> + if Session.get('isSearching') is false + false + else + true + is_fullname:-> + if Session.get('is_fullname') + "昵称" + else + "用户名" + noSearchResult:-> + return Session.get("noSearchResult") + searchLoading:-> + return Session.get('searchLoading') + + follows: -> + Follows.find({},{sort: {index: 1}}) + isFollowed:(follow)-> + Meteor.subscribe("friendFollower",Meteor.userId(),follow.userId) + fcount = Follower.find({"userId":Meteor.userId(),"followerId":follow.userId}).count() + if fcount > 0 + true + else + false + isFollowedUser:(follow)-> + Meteor.subscribe("friendFollower",Meteor.userId(),follow._id) + fcount = Follower.find({"userId":Meteor.userId(),"followerId":follow._id}).count() + if fcount > 0 + true + else + false + isSelf:(follow)-> + if follow.userId is Meteor.userId() + true + else + false + notSelf:(follow)-> + if follow._id is Meteor.userId() + false + else + true + Template.searchPeopleAndTopic.onCreated ()-> + Meteor.subscribe("topics") + Meteor.subscribe('follows') + Meteor.subscribe("topicposts") + Template.searchPeopleAndTopic.onRendered ()-> + Session.setDefault('is_people', true) + Session.setDefault('is_fullname', true) + if(Session.get("searchContent") isnt undefined) + $("#search-box").val(Session.get("searchContent")) + if Session.get("noSearchResult") is true + Session.set("searchLoading", false) + if($("#search-box").val() is "") + Session.set("showSearchStatus", false) + Session.set("showSearchItems", false) + Session.set("noSearchResult", false) + $('#search-box').bind('propertychange input',(e)-> + text = $(e.target).val().trim() + Session.set("showSearchStatus", true) + Session.set("showSearchItems", true) + Session.set("searchLoading", true) + Session.set("noSearchResult", false) + if text is "" + Session.set("showSearchStatus", false) + Session.set("showSearchItems", false) + Session.set("searchLoading", false) + Session.set("noSearchResult", false) + return + + if Session.get('is_people') + options = {is_fullname:Session.get('is_fullname')} + FollowUsersSearch.search text,options + else + TopicsSearch.search text + ) + $('#search-box').trigger('focus') + Template.searchPeopleAndTopic.helpers + is_people:-> + Session.get('is_people') + is_fullname:-> + if Session.get('is_fullname') + "昵称" + else + "用户名" + showSearchStatus:-> + return Session.get('showSearchStatus') + noSearchResult:-> + return Session.get('noSearchResult') + searchLoading:-> + return Session.get('searchLoading') + showSearchItems:-> + return Session.get('showSearchItems') + placeHolder:-> + if Session.get('is_people') + "搜索作者" + else + "搜索话题" + isFollowedUser:(follow)-> + fcount = Follower.find({"userId":Meteor.userId(),"followerId":follow._id}).count() + if fcount > 0 + true + else + false + notSelf:(follow)-> + if follow._id is Meteor.userId() + false + else + true + Template.searchPeopleAndTopic.events + 'focus #search-box':-> + console.log("#search get focus"); + $('#footer').css('display',"none") + 'blur #search-box':-> + console.log("#search lost focus"); + $('#footer').css('display',"") + 'click .topicTitle': (event)-> + Session.set "topicId", @_id + Session.set "topicTitle", "#"+ @text + "#" + Router.go '/topicPosts' + 'click #search_people': (event)-> + Session.set('is_people', true) + text = $('#search-box').val().trim() + if text is "" + Session.set("showSearchStatus", false) + Session.set("showSearchItems", false) + Session.set("searchLoading", false) + Session.set("noSearchResult", false) + $('#search-box').trigger('focus') + return + Session.set("showSearchStatus", true) + Session.set("showSearchItems", true) + Session.set("searchLoading", true) + Session.set("noSearchResult", false) + options = {is_fullname:Session.get('is_fullname')} + FollowUsersSearch.search text,options + $('#search-box').trigger('focus') + + 'click #search_topic': (event)-> + Session.set('is_people', false) + text = $('#search-box').val().trim() + if text is "" + Session.set("showSearchStatus", false) + Session.set("showSearchItems", false) + Session.set("searchLoading", false) + Session.set("noSearchResult", false) + $('#search-box').trigger('focus') + return + Session.set("showSearchStatus", true) + Session.set("showSearchItems", true) + Session.set("searchLoading", true) + Session.set("noSearchResult", false) + TopicsSearch.search text + $('#search-box').trigger('focus') + + 'click #search_people_fullname':(event)-> + Session.set("is_fullname",true) + text = $('#search-box').val().trim() + if text is "" + Session.set("showSearchStatus", false) + Session.set("showSearchItems", false) + Session.set("searchLoading", false) + Session.set("noSearchResult", false) + $('#search-box').trigger('focus') + return + Session.set("showSearchStatus", true) + Session.set("showSearchItems", true) + Session.set("searchLoading", true) + Session.set("noSearchResult", false) + options = {is_fullname:Session.get('is_fullname')} + FollowUsersSearch.search text,options + $('#search-box').trigger('focus') + 'click #search_people_username':(event)-> + Session.set("is_fullname",false) + text = $('#search-box').val().trim() + if text is "" + Session.set("showSearchStatus", false) + Session.set("showSearchItems", false) + Session.set("searchLoading", false) + Session.set("noSearchResult", false) + $('#search-box').trigger('focus') + return + Session.set("showSearchStatus", true) + Session.set("showSearchItems", true) + Session.set("searchLoading", true) + Session.set("noSearchResult", false) + options = {is_fullname:Session.get('is_fullname')} + FollowUsersSearch.search text,options + $('#search-box').trigger('focus') + 'click .back': (event)-> + Session.set("searchContent","") + Session.set('is_fullname', true) + history.back() + 'click .delFollow':(e)-> + FollowerId = Follower.findOne({ + userId: Meteor.userId() + followerId: @_id + })._id + Follower.remove(FollowerId) + 'click .addFollow':(e)-> + if Meteor.user().profile.fullname + username = Meteor.user().profile.fullname + else + username = Meteor.user().username + if @profile.fullname + followername = @profile.fullname + else + followername = @username + insertObj = { + userId: Meteor.userId() + #这里存放fullname + userName: username + userIcon: Meteor.user().profile.icon + userDesc: Meteor.user().profile.desc + followerId: @_id + #这里存放fullname + followerName: followername + followerIcon: @profile.icon + followerDesc: @desc + createAt: new Date() + } + addFollower(insertObj) + 'click .del':(e)-> + followerId = e.currentTarget.id + FollowerId = Follower.findOne({ + userId: Meteor.userId() + followerId: followerId + })._id + Follower.remove(FollowerId) + 'click .add':(e)-> + if Meteor.user().profile.fullname + username = Meteor.user().profile.fullname + else + username = Meteor.user().username + insertObj = { + userId: Meteor.userId() + #这里存放fullname + userName: username + userIcon: Meteor.user().profile.icon + userDesc: Meteor.user().profile.desc + followerId: @userId + #这里存放fullname + followerName: @fullname + followerIcon: @icon + followerDesc: @desc + createAt: new Date() + } + addFollower(insertObj) diff --git a/client/search/search.html b/client/search/search.html new file mode 100644 index 000000000..082096111 --- /dev/null +++ b/client/search/search.html @@ -0,0 +1,227 @@ + ++++++ WorkAI场景选择 +++ 家用AI场景选择 ++ +++ + + ++ {{_ "searchTopic"}} ++++++ + +++ {{#each theme}} +++ ++ {{/each}} +{{text}}
+{{_ "popTopics"}}++ {{#each topic}} +
+- #{{text}}#
+ {{/each}} +++ + + ++++
+ {{#if is_people}} ++ ++ ++ {{#if is_people}} +++ {{is_fullname}} + ++ {{else}} + + {{/if}} + ++ +{{_ "cancel"}}++
+- 作者
+- #{{_ "plainTopic"}}#
++ {{#if showSearchStatus}} + {{#if noSearchResult}} ++ {{else}} +{{_ "NoSearchResults"}} + {{/if}} + {{#if searchLoading}} +{{_ "Searching"}} + {{/if}} + {{/if}} + {{#if showSearchItems}} + {{#each getFollowUsers}} + ++ {{#if profile.fullname}} +
{{profile.fullname}}+ {{else}} +{{username}}+ {{/if}} + {{#if notSelf this}} + {{#if isFollowedUser this}} ++ {{else}} ++ {{/if}} + {{/if}} +{{desc}}+ ++ ++ {{/each}} + {{else}} +{{_ "recommandUser"}}+ {{#each follows}} + ++
{{fullname}}+ {{#unless isSelf this}} + {{#if isFollowed this}} ++ {{else}} ++ {{/if}} + {{/unless}} +{{desc}}+ ++ ++ {{/each}} + {{/if}} ++
+- {{_ "plainPeople"}}
+- #{{_ "plainTopic"}}#
++ {{#if showSearchStatus}} + {{#if noSearchResult}} ++ {{/if}} +{{_ "NoSearchResults"}} + {{/if}} + {{#if searchLoading}} +{{_ "Searching"}} + {{/if}} + {{/if}} + {{#if showSearchItems}} + {{#each getTopics}} +#{{text}}#{{posts}}{{_ "numOfStory"}}++ ++ {{/each}} + {{else}} +#热门话题#+ {{#each topic}} + +#{{text}}#{{posts}}{{_ "numOfStory"}}++ ++ {{/each}} + {{/if}} +++ + diff --git a/client/search/search.js b/client/search/search.js new file mode 100644 index 000000000..6ef835948 --- /dev/null +++ b/client/search/search.js @@ -0,0 +1,143 @@ +Template.searchFollow.helpers({ + getFollowUsers: function() { + var followUsersSearchData = FollowUsersSearch.getData({ + transform: function(matchText, regExp) { + //return matchText.replace(regExp, "$&") + return matchText + }, + sort: {createdAt: -1} + }); + if (FollowUsersSearch.getStatus().loaded == true) + { + if (followUsersSearchData.length == 0) { + Meteor.setTimeout (function(){ + Session.set("noSearchResult", true); + Session.set("searchLoading", false); + },500); + } else { + Session.set("noSearchResult", false); + Session.set("searchLoading", false); + } + } + return followUsersSearchData; + }, + + isLoading: function() { + return FollowUsersSearch.getStatus().loading; + } +}); +Template.searchPeopleAndTopic.helpers({ + getTopics: function() { + var topicsSearchData = TopicsSearch.getData({ + transform: function(matchText, regExp) { + //return matchText.replace(regExp, "$&") + return matchText + }, + sort: {createdAt: -1} + }); + if (TopicsSearch.getStatus().loaded == true && Session.get('is_people') == false) { + if (topicsSearchData.length == 0) { + Meteor.setTimeout (function(){ + Session.set("searchLoading", false); + Session.set("noSearchResult", true); + },500); + } else { + Session.set("showSearchStatus", false); + Session.set("searchLoading", false); + Session.set("noSearchResult", false); + } + } + return topicsSearchData; + }, + + getFollowUsers: function() { + var followUsersSearchData = FollowUsersSearch.getData({ + transform: function(matchText, regExp) { + //return matchText.replace(regExp, "$&") + return matchText + }, + sort: {createdAt: -1} + }); + if (FollowUsersSearch.getStatus().loaded == true && Session.get('is_people') == true) { + if (followUsersSearchData.length == 0) { + Meteor.setTimeout (function(){ + Session.set("searchLoading", false); + Session.set("noSearchResult", true); + },500); + } else { + Session.set("showSearchStatus", false); + Session.set("searchLoading", false); + Session.set("noSearchResult", false); + } + } + return followUsersSearchData; + }, + + follows: function() { + return Follows.find({}, { + sort: { + index: 1 + } + }); + }, + + isFollowed: function(follow) { + var fcount; + Meteor.subscribe("friendFollower", Meteor.userId(), follow.userId); + fcount = Follower.find({ + "userId": Meteor.userId(), + "followerId": follow.userId + }).count(); + if (fcount > 0) { + return true; + } else { + return false; + } + }, + + isFollowedUser: function(follow) { + var fcount; + Meteor.subscribe("friendFollower", Meteor.userId(), follow._id); + fcount = Follower.find({ + "userId": Meteor.userId(), + "followerId": follow._id + }).count(); + if (fcount > 0) { + return true; + } else { + return false; + } + }, + + isSelf: function(follow) { + if (follow.userId === Meteor.userId()) { + return true; + } else { + return false; + } + }, + + notSelf: function(follow) { + if (follow._id === Meteor.userId()) { + return false; + } else { + return true; + } + }, + + topic: function() { + return Session.get('persistentTopics'); + }, + + isLoading: function() { + return FollowUsersSearch.getStatus().loading; + } +}); + +Template.searchFollow.events({ + 'click .topic': function(event) { + Session.set("topicId", this._id); + Session.set("topicTitle", "#" + this.text + "#"); + return PUB.page('/topicPosts'); + } +}) diff --git a/client/simpleUserProfile/simpleUserProfile.coffee b/client/simpleUserProfile/simpleUserProfile.coffee new file mode 100644 index 000000000..d2bc309c4 --- /dev/null +++ b/client/simpleUserProfile/simpleUserProfile.coffee @@ -0,0 +1,208 @@ +if Meteor.isClient + getLocation = (userId)-> + userInfo = Meteor.users.findOne({_id:userId}) + console.log('Get location for user '+ userId + JSON.stringify(userInfo)) + if userInfo and userInfo.profile + if userInfo.profile.location and userInfo.profile.location isnt '' + return userInfo.profile.location + else if userInfo.profile.lastLogonIP and userInfo.profile.lastLogonIP isnt '' + unless Session.get('userLocation_'+userId) + console.log 'Get Address from ' + userInfo.profile.lastLogonIP + Session.set('userLocation_'+userId,'加载中...') + url = "http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js&ip="+userInfo.profile.lastLogonIP + $.getScript url, (data, textStatus, jqxhr)-> + console.log 'status is ' + textStatus + address = '' + if textStatus is 'success' and remote_ip_info and remote_ip_info.ret is 1 + console.log 'Remote IP Info is ' + JSON.stringify(remote_ip_info) + if remote_ip_info.country and remote_ip_info.country isnt '' and remote_ip_info.country isnt '中国' + address += remote_ip_info.country + address += ' ' + if remote_ip_info.province and remote_ip_info.province isnt '' + address += remote_ip_info.province + address += ' ' + if remote_ip_info.city and remote_ip_info.city isnt '' and remote_ip_info.city isnt remote_ip_info.province + address += remote_ip_info.city + console.log 'Address is ' + address + if address isnt '' + Session.set('userLocation_'+userId,address) + else + Session.set('userLocation_'+userId,'未知') + else + Session.set('userLocation_'+userId,'未知') + return Session.get('userLocation_'+userId) + Template.simpleUserProfile.rendered=-> + $('.simpleUserProfile').css('min-height', $(window).height() - 40) + $('.viewPostImages ul li').css('height',$(window).width()*0.168) + # offSettop = $('.userProfileBottom').offset().top + # $('.userProfileBottom').css('height',$(window).height()-40-offSettop) + $('.page').addClass('scrollable') + groupid = Session.get('groupsId') + Meteor.subscribe("get-group",groupid) + Meteor.subscribe('loginuser-in-group',groupid,Session.get("simpleUserProfileUserId")); + Meteor.subscribe('usersById',Session.get("simpleUserProfileUserId")) + $(document).scrollTop(0) + Template.simpleUserProfile.helpers + isFromChat:()-> + return Router.current().params.query && Router.current().params.query.from is 'chat' + isMale:(sex)-> + sex is 'male' + isFemale:(sex)-> + sex is 'female' + profile:-> + Meteor.users.findOne {_id: Session.get("simpleUserProfileUserId")} + location:-> + getLocation(Session.get("simpleUserProfileUserId")) + isFollowed:()-> + fcount = Follower.find({"followerId":Session.get("simpleUserProfileUserId")}).count() + if fcount > 0 + true + else + false + compareViewsCount:(value)-> + if (ViewLists.find({userId:Session.get("simpleUserProfileUserId")}, {sort: {createdAt: -1}, limit:3}).count() > value) + true + else + false + viewLists:()-> + ViewLists.find({userId:Session.get("simpleUserProfileUserId")},{sort: {createdAt: -1}, limit:3}) + inBlackList:()-> + if BlackList.find({blackBy: Meteor.userId(), blacker:{$in: [Session.get("simpleUserProfileUserId")]}}).count() > 0 + return true + else + return false + 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 + return true + userIsGroupAdmin:()-> + groupUser = SimpleChat.GroupUsers.findOne({group_id:Session.get('groupsId'), user_id: Session.get("simpleUserProfileUserId")}) + if groupUser and groupUser.isGroupAdmin + return true + return false + + Template.simpleUserProfile.events + 'click #setAsGroupAdmin':()-> + user_id = Session.get("simpleUserProfileUserId") + group_id = Session.get('groupsId') + Meteor.call('modifyGroupUserAdmin', group_id, user_id, true) + 'click #unSetGroupAdmin':()-> + user_id = Session.get("simpleUserProfileUserId") + group_id = Session.get('groupsId') + Meteor.call('modifyGroupUserAdmin', group_id, user_id, false) + 'click #removeFormGroup':()-> + user_id = Session.get("simpleUserProfileUserId") + group_id = Session.get('groupsId') + Meteor.call('removeGroupUser', group_id, user_id) + return PUB.back() + 'click .simpleUserProfile .back':()-> + historyArr = Session.get("history_view") + if (historyArr and historyArr.length > 0) + PUB.back() + else + history.go(-1) + 'click #removeFormBlacklist':()-> + blackId = Session.get("simpleUserProfileUserId") + BlackList.update({_id: blackId}, {$pull: {blacker: id}}) + blacker = Meteor.users.findOne({_id: id}) + blackerName = if blacker.profile.fullname then blacker.profile.fullname else blacker.username + Follower.insert { + userId: Meteor.userId() + userName: Meteor.user().profile.fullname || Meteor.user().username + userIcon: Meteor.user().profile.icon || '/userPicture.png' + userDesc: Meteor.user().profile.desc + + followerId: blacker._id + followerName: blackerName + followerIcon: blacker.profile.icon || '/userPicture.png' + followerDesc: blacker.profile.desc + + createAt: new Date() + } + 'click #addToBlacklist':()-> + blackerId = Session.get("simpleUserProfileUserId") + FollowerId = Follower.findOne({userId: Meteor.userId(),followerId: blackerId}) + MsgSessionId = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId: blackerId}) + if BlackList.find({blackBy: Meteor.userId(), blacker:{$in: [blackerId]}}).count() is 0 + if BlackList.find({blackBy: Meteor.userId()}).count() is 0 + #Meteor.call('addBlackList', blackerId, Meteor.userId()) + BlackList.insert({blacker: [blackerId],blackBy: Meteor.userId()}) + if FollowerId + Follower.remove(FollowerId._id) + if MsgSessionId + SimpleChat.MsgSession.remove(MsgSessionId._id) + Session.set('fromeaddblacllist', true) + Router.go '/my_blacklist' + else + id = BlackList.findOne({blackBy: Meteor.userId()})._id + BlackList.update({_id: id}, {$addToSet: {blacker: blackerId}}) + if FollowerId + Follower.remove(FollowerId._id) + if MsgSessionId + SimpleChat.MsgSession.remove(MsgSessionId._id) + Session.set('fromeaddblacllist', true) + Router.go '/my_blacklist' + else + id = BlackList.findOne({blackBy: Meteor.userId(), blacker:{$in: [blackerId]}})._id + BlackList.update({_id: id}, {$pull: {blacker: blackerId}}) + 'click #deleteUser':()-> + FollowerId = Follower.findOne({ + userId: Meteor.userId() + followerId: Session.get('simpleUserProfileUserId') + })._id + Follower.remove(FollowerId) + MsgSessionId = SimpleChat.MsgSession.findOne({userId: Meteor.userId(),toUserId: Session.get('simpleUserProfileUserId')}) + if MsgSessionId + SimpleChat.MsgSession.remove(MsgSessionId._id) + 'click .addToAddressbook':()-> + follow = Meteor.users.findOne({_id:Session.get('simpleUserProfileUserId')}) + if follow.profile.fullname + followerName = follow.profile.fullname + else + followerName = follow.username + if Meteor.user().profile.fullname + username = Meteor.user().profile.fullname + else + username = Meteor.user().username + insertObj = { + userId: Meteor.userId() + #这里存放fullname + userName: username + userIcon: Meteor.user().profile.icon + userDesc: Meteor.user().profile.desc + followerId: follow._id + #这里存放fullname + followerName: followerName + followerIcon: follow.profile.icon + followerDesc: follow.profile.desc + createAt: new Date() + } + addFollower(insertObj) + 'click .sendMesssage':()-> + page = '/simple-chat/to/user?id='+Session.get('simpleUserProfileUserId') + Router.go page + + 'click .postImages ul li':(e)-> + PUB.openPost e.currentTarget.id + ### + postId = e.currentTarget.id + $(window).children().off() + $(window).unbind('scroll') + currentPostId = Session.get("postContent")._id + postBack = Session.get("postBack") + postBack.push(currentPostId) + Session.set("postForward",[]) + Session.set("postBack",postBack) + if PopUpBox + PopUpBox.close() + Meteor.setTimeout ()-> + Session.set("Social.LevelOne.Menu",'contactsList') + Router.go '/posts/'+postId + ,300 + ### diff --git a/client/simpleUserProfile/simpleUserProfile.html b/client/simpleUserProfile/simpleUserProfile.html new file mode 100644 index 000000000..464c7d4c8 --- /dev/null +++ b/client/simpleUserProfile/simpleUserProfile.html @@ -0,0 +1,98 @@ + ++ {{_ "discoverPeople"}} ++++++ +++ {{is_fullname}} + ++ ++ {{#if isSearching}} + {{#if noSearchResult}} + ++{{_ "NoSearchResults"}} + {{/if}} + {{#if searchLoading}} + +{{_ "Searching"}} + {{/if}} + {{#each getFollowUsers}} + ++ {{#if profile.fullname}} +
{{profile.fullname}}+ {{else}} +{{username}}+ {{/if}} + {{#if notSelf this}} + {{#if isFollowedUser this}} ++ {{else}} ++ {{/if}} + {{/if}} +{{desc}}+ ++ ++ {{/each}} + {{else}} + + {{/if}} +++ + diff --git a/client/simpleUserProfile/simpleUserProfile.less b/client/simpleUserProfile/simpleUserProfile.less new file mode 100644 index 000000000..437c5e53f --- /dev/null +++ b/client/simpleUserProfile/simpleUserProfile.less @@ -0,0 +1,120 @@ +.simpleUserProfile{ + background-color:#efeff4; + color: black; + .head{ + background:#37a7fe !important; + div{font-size: 16px;font-weight: bold;color:white;} + } + .userProfileTop{ + background: #fff; + padding: 10px; + position: relative; + .icon{ + border-radius: 5px; + margin-right: 10px; + } + .userName{ + font-size: large; + position: absolute; + display: inline-block; + left: 90px; + right: 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + img{margin-left: 5px;} + } + .location{position: absolute;top: 38px;color: grey;} + .desc{position: absolute;bottom: 18px;color: grey;} + } + .post{ + position: relative; + margin:0 !important; + padding: 10px 0; + .postLeft{margin: 0 20px;color: grey;} + .postRight{float: right;margin: 0 20px;color: deepskyblue} + } + .postImages{ + padding-bottom: 15px; + ul{ + margin: 0; + padding: 0; + overflow: hidden; + list-style-type: none; + li{ + color: grey; + line-height: 24px; + text-align: left; + margin-left: 2%; + float: left; + width: 30%; + margin-bottom: 8px; + padding-bottom: 8px; + box-shadow: 1px 1px 5px gray, 1px 1px 3px gray; + word-break: break-all; + overflow:hidden; + able-layout:fixed; + .postMainImage{height: 120px;margin: 8px;background-repeat: no-repeat;background-position: center;background-size: cover;} + .title{margin-left: 10px;margin-right: 0;margin-top: 0;margin-bottom: 0;} + .addontitle{margin-left:10px;margin-bottom: 0;margin-top: 0;} + .postInfo{ + font-size: 12px; + margin-left: 10px; + i{margin-right: 5px;} + } + } + } + } + .viewPostImages ul{ + margin: 0; + padding: 0 2%; + overflow: hidden; + list-style-type: none; + li{margin-left: 2%;float: left;width: 17.5%;margin-bottom: 8px;padding-bottom: 8px;background-position: center;background-size: cover;} + } + .userProfileBottom{ + background: #efeff4; + .botton_btn{ + position: relative; + left: 2.5%; + width: 95%; + text-align: center; + font-size: 18px; + background-color: #37a7fe; + color: #ffffff !important; + height: 45px; + border-radius: 5px; + /* padding-left: 5%; */ + padding-top: 10px; + } + } + .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; + } + } + } +} + +.userProfileMarginTop {margin-top: 40px;} + +.userProfileCenter{ + background: #fff; +} \ No newline at end of file diff --git a/client/social/contactsList.coffee b/client/social/contactsList.coffee index d90c110bd..3a881ce06 100644 --- a/client/social/contactsList.coffee +++ b/client/social/contactsList.coffee @@ -10,7 +10,12 @@ if Meteor.isClient Meteor.subscribe 'followToWithLimit', 9999 ### onUserProfile = -> - #Meteor.subscribe("userfavouriteposts", Session.get("ProfileUserId"), 10) + #Router.go '/userProfilePage' + Session.set("momentsitemsLimit", 10) + #Meteor.subscribe("userfavouriteposts", Session.get("ProfileUserId"), Session.get("momentsitemsLimit")) + #Meteor.subscribe("userfavouriteposts", Session.get("ProfileUserId1"), Session.get("momentsitemsLimit")) + #Meteor.subscribe("userfavouriteposts", Session.get("ProfileUserId2"), Session.get("momentsitemsLimit")) + #Meteor.subscribe("userfavouriteposts", Session.get("ProfileUserId3"), Session.get("momentsitemsLimit")) @PopUpBox = $('.popUpBox').bPopup positionStyle: 'fixed' position: [0, 0] @@ -28,6 +33,11 @@ if Meteor.isClient else false Template.contactsList.events + 'click .contactsList .back' :-> + $(window).children().off() + $(window).unbind('scroll') + Meteor.setTimeout ()-> + PUB.postPageBack() "click #addNewFriends":()-> Session.set("Social.LevelOne.Menu",'addNewFriends') "click .oldFriends":(e)-> @@ -60,14 +70,14 @@ if Meteor.isClient target = $("#showMorePostFriendsResults"); POSTFRIENDS_ITEMS_INCREMENT = 10; console.log "target.length: " + target.length - if $('#newFriendRedSpotReal').is(":hidden") and parseInt($('#newFriendRedSpotReal').html()) > 0 - $('#newFriendRedSpotReal').show() - $('#newFriendRedSpot').hide() if (!target.length) return; threshold = $(window).scrollTop() + $(window).height() - target.height(); console.log "threshold: " + threshold console.log "target.top: " + target.offset().top + if $('#newFriendRedSpotReal').is(":hidden") and parseInt($('#newFriendRedSpotReal').html()) > 0 + $('#newFriendRedSpotReal').show() + $('#newFriendRedSpot').hide() if target.offset().top < threshold if (!target.data("visible")) target.data("visible", true); @@ -76,7 +86,6 @@ if Meteor.isClient if (target.data("visible")) target.data("visible", false); Template.addNewFriends.helpers - hasFriendMeet:()-> meeter:()-> PostFriends.find({meetOnPostId:Session.get("postContent")._id,ta:{$ne:null}},{sort:{createdAt:-1}}) isMyself:()-> @@ -104,7 +113,8 @@ if Meteor.isClient else true moreResults:()-> - !(PostFriends.find({meetOnPostId:Session.get("postContent")._id}).count()+1 < Session.get("postfriendsitemsLimit")) + return PostFriendsCount.findOne({_id:Meteor.userId()+'_'+Session.get("postContent")._id})?.count > Session.get("postfriendsitemsLimit") + # !(PostFriends.find({meetOnPostId:Session.get("postContent")._id}).count()+1 < Session.get("postfriendsitemsLimit")) loading:()-> Session.equals('postfriendsCollection','loading') loadError:()-> @@ -157,7 +167,8 @@ if Meteor.isClient username = Meteor.user().profile.fullname else username = Meteor.user().username - Follower.insert { + console.log 'contactsList addFollow!' + insertObj = { userId: Meteor.userId() #这里存放fullname userName: username @@ -169,3 +180,4 @@ if Meteor.isClient followerIcon: this.userIcon createAt: new Date() } + addFollower(insertObj) diff --git a/client/social/contactsList.html b/client/social/contactsList.html index 2dc2c31c9..d59ce5b01 100644 --- a/client/social/contactsList.html +++ b/client/social/contactsList.html @@ -1,20 +1,21 @@+++ {{#if profile.profile.fullname}} +{{profile.profile.fullname}}+ {{else}} +{{profile.username}}+ {{/if}} + {{#if isFromChat}} + + {{else}} + {{#if isGroupCreator}} +++ {{/if}} + {{/if}} + +++ ++
++ + + + ++ + {{#if profile.profile.fullname}} + {{profile.profile.fullname}} + {{else}} + {{profile.username}} + {{/if}} + {{#if isMale profile.profile.sex}} +
+ {{/if}} + {{#if isFemale profile.profile.sex}} +
+ {{/if}} + + {{location}} + {{profile.profile.desc}} +
++ +发消息+ +diff --git a/client/social/discover.coffee b/client/social/discover.coffee index 7cf685692..7cf32c311 100644 --- a/client/social/discover.coffee +++ b/client/social/discover.coffee @@ -9,17 +9,7 @@ if Meteor.isClient # if withDiscover # spanOuterWidth = $(".discover .discover-top .discover-con span").outerWidth() || 0 # $(".discover .discover-top .discover-con").css({'width': (spanOuterWidth + 40) + 'px'}); - - Template.discover.events - 'click .clear-discover-msg':(e,t)-> - Meteor.call 'clearDiscoverMSG',Meteor.userId(),Session.get("postContent")._id, (err,res)-> - if !err and res and res.msg is 'success' - toastr.remove() - toastr.info('已全部标记为已读') - else - toastr.remove() - toastr.info('操作失败请重试~') - console.table(res) + Template.discover.helpers showSuggestPosts:()-> if Session.get("showSuggestPosts") is true @@ -108,53 +98,36 @@ if Meteor.isClient Template.moments.events 'click .readpost':(e)-> postId = this.readPostId + scrollTop = $(window).scrollTop() if postId is undefined postId = this._id - PUB.openPost postId - ### - Session.set("historyForwardDisplay", false) $(window).children().off() $(window).unbind('scroll') - currentPostId = Session.get("postContent")._id - postBack = Session.get("postBack") - postBack.push(currentPostId) - Session.set("postForward",[]) - Session.set("postBack",postBack) + id = Session.get("postContent")._id + #PUB.postPage(id,scrollTop) Meteor.setTimeout ()-> - #Session.set("lastPost",postId) + Session.set("Social.LevelOne.Menu",'contactsList') Router.go '/posts/'+postId ,300 - ### 'click .masonry_element':(e)-> postId = $(e.currentTarget).find('.readPost')[0].id + scrollTop = $(window).scrollTop() if postId is undefined postId = this._id - PUB.openPost postId - ### - Session.set("historyForwardDisplay", false) $(window).children().off() $(window).unbind('scroll') - currentPostId = Session.get("postContent")._id - postBack = Session.get("postBack") - postBack.push(currentPostId) - Session.set("postForward",[]) - Session.set("postBack",postBack) + id = Session.get("postContent")._id + #PUB.postPage(id,scrollTop) Meteor.setTimeout ()-> - #Session.set("lastPost",postId) + Session.set("Social.LevelOne.Menu",'contactsList') Router.go '/posts/'+postId ,300 - ### Template.lpcomments.helpers - isCommentShare:-> - if this.eventType is "pcommentShare" - true - else - false isShareFeed:-> if this.eventType is "share" true else - false + false withSuggestAlreadyRead:()-> withSuggestAlreadyRead description:-> @@ -162,15 +135,15 @@ if Meteor.isClient "点评了您的故事" else "也点评了此故事" + commentReply:-> + if this.eventType is "pcommentReply" + true + else + false hasLpcoments:()-> Feeds.find({followby:Meteor.userId(),checked:false, eventType: {$nin: ['share','personalletter']}, createdAt:{$gt:new Date((new Date()).getTime() - 7 * 24 * 3600 * 1000)}},{sort: {createdAt: -1}, limit:20}).count() > 0 lpcomments:()-> Feeds.find({followby:Meteor.userId(),checked:false, eventType: {$nin: ['share','personalletter']}, createdAt:{$gt:new Date((new Date()).getTime() - 7 * 24 * 3600 * 1000)}},{sort: {createdAt: -1}, limit:20}) - commentReply:()-> - if this.eventType is "pcommentReply" - return true - else - return false time_diff: (created)-> GetTime0(new Date() - created) Template.lpcomments.events @@ -181,7 +154,6 @@ if Meteor.isClient postId = this.postId scrollTop = $(window).scrollTop() Session.set("pcurrentIndex",this.pindex) - Session.set("historyForwardDisplay", false) Session.set("pcommetsId",this.owner) Session.set("pcommentsName",this.ownerName) Session.set "toasted",false @@ -189,41 +161,32 @@ if Meteor.isClient Session.set "isPcommetReply",true else Session.set "isPcommetReply",false - Session.set "NoUpdateShare",true Feeds.update({_id:this._id},{$set: {checked:true}}) id = Session.get("postContent")._id if postId isnt id - #$(window).children().off() - #$(window).unbind('scroll') - postBack = Session.get("postBack") - postBack.push(id) - Session.set("postForward",[]) - Session.set("postBack",postBack) + Session.set('displayDiscoverContent',false) + $(window).children().off() + $(window).unbind('scroll') + #PUB.postPage(id,scrollTop) Meteor.setTimeout ()-> - Session.set("lastPost",postId) + Session.set("Social.LevelOne.Menu",'contactsList') + Session.set("needBindScroll", true) Router.go '/posts/'+postId ,300 else document.body.scrollTop = 0 Template.recommends.helpers - hasRecommends: ()-> - Meteor.subscribe('list_recommends', Session.get("postContent")._id); - Recommends.find({relatedPostId: Session.get("postContent")._id}).count() > 0 recommends: ()-> - # Meteor.subscribe('list_recommends', Session.get("postContent")._id); + Meteor.subscribe('list_recommends', Session.get("postContent")._id); Recommends.find({relatedPostId: Session.get("postContent")._id}) time_diff: (created)-> - GetTime0(new Date() - created) + GetTime0(new Date() - created) Template.recommends.events - 'click .elementBox': (e)-> + 'click .elementBox':(e)-> + Session.set("historyForwardDisplay", false) postId = e.currentTarget.id scrollTop = $(window).scrollTop() - currentPostId = Session.get("postContent")._id - postBack = Session.get("postBack") - postBack.push(currentPostId) - Session.set("postBack",postBack) Session.set("lastPost",postId) - Session.set('postContentTwo', postId) $(window).children().off() $(window).unbind('scroll') userLists = [] @@ -239,4 +202,4 @@ if Meteor.isClient Session.set("needBindScroll", true) Router.go '/posts/'+postId ,300 - # Router.go '/posts/' + postId + # Router.go '/posts/'+postId diff --git a/client/social/discover.html b/client/social/discover.html index aa01bc835..6c7995b04 100644 --- a/client/social/discover.html +++ b/client/social/discover.html @@ -6,11 +6,11 @@+{{> addNewFriends}}- 新的朋友 + {{_ "NewFriendsList"}}
{{displayName}} -
缘分啊,我们已偶遇{{count}}次了!+{{_ "FateAh"}}{{count}}{{_ "times1"}}{{#if showRedSpot}} {{/if}} @@ -81,7 +82,7 @@ {{/each}} {{#if moreResults}}- 加载中... + {{_ "loading"}}{{/if}}@@ -22,7 +22,7 @@-朋友圈
+{{_ "Moments"}}
{{#if showSuggestPosts}} -看过当前帖子的朋友看过的帖子,您都看过了,特意为您推荐以下您没看过帖子...
+{{_ "RecommendPosts"}}
{{else}} -看过当前帖子的朋友,还看过...
+{{_ "AlsoRead"}}
{{/if}}- 看过该帖的朋友还看过... + {{_ "RecommendReadMore"}}
全部标为已读- {{/if}}-+
{{#each recommends}}
- 删除 + {{_ "delete"}}@@ -48,16 +45,16 @@- + + + +{{recommendUserName}}
-看过{{targetPostTitle}}后,推荐您阅读
+{{_ "recommendA"}}{{targetPostTitle}}{{_ "recommendB"}}
@@ -68,7 +65,7 @@
- -
![]()
- +
{{recommendPostTitle}}
-发表:{{time_diff recommendPostCreatedAt}}
+{{_ "publish"}}:{{time_diff recommendPostCreatedAt}}
{{recommendPostTitle}}
{{/each}}- +
+ \ No newline at end of file diff --git a/client/user/collect_list.js b/client/user/collect_list.js new file mode 100644 index 000000000..e2ab4d432 --- /dev/null +++ b/client/user/collect_list.js @@ -0,0 +1,76 @@ +var limit = 10; +var pageSize = 10; + +Template.collectItemWrap.helpers({ + collectList: function () { + var list = SimpleChat.CollectMessages.find({}, {sort: {collectDate: -1}, limit: limit}).fetch(); + return list; + } +}); + +Template.collectItem.helpers({ + name: function() { + if (this.form.id !== Meteor.userId) { + return this.form.name; + } else { + return this.to.name; + } + }, + collectDate: function() { + var date = new Date(this.collectDate); + var year = date.getFullYear(); + var month = date.getMonth() + 1; + var day = date.getDate(); + var dateStr = year + '/' + month + '/' + day; + return dateStr; + } +}); + +Template.collectList.events({ + 'click .back': function(e) { + return Router.go('/user'); + }, +}); + +Template.collectItem.events({ + 'click .delBtn': function(e) { + SimpleChat.CollectMessages.remove({_id: this._id}); + }, + 'click img.swipebox': function(e) { + var initialIndex; + var parentItemData = Blaze.getData(Template.collectItem.view); + var originImages = parentItemData.images || [{url: parentItemData.url}]; + var images = originImages.map(function(item, index) { + if (item.url === e.target.src) { + initialIndex = index; + } + return { + href: item.url, + title: '' + }; + }); + $.swipebox(images, { + initialIndexOnArray: initialIndex, + hideCloseButtonOnMobile : true, + loopAtEnd: false + }); + } +}); + +var loadMore = function() { + if ($(window).scrollTop() + $(window).height() >= $(document).height()) { + var loadedCount = SimpleChat.CollectMessages.find({}, {sort: {collectDate: -1}, limit: limit}).count(); + if (loadedCount !== limit) return; + limit += pageSize; + Meteor.subscribe('collectedMessages', {sort: {collectDate: -1}, limit: limit}); + $('.collect-page .content').empty(); + Blaze.render(Template.collectItemWrap, $('.collect-page .content')[0]); + } +}; + +window.addEventListener('scroll', function() { + loadMore(); +}, false); + + + diff --git a/client/user/collect_list.less b/client/user/collect_list.less new file mode 100644 index 000000000..08e160387 --- /dev/null +++ b/client/user/collect_list.less @@ -0,0 +1,62 @@ +.collect-page { + min-height: 100%; + background: #eeeef3; + padding-bottom: 10px; + .content { + padding-top: 40px; + + } + .collect-list { + padding: 0 !important; + overflow: hidden; + margin-bottom: 0; + > li { + background: #fff; + padding: 15px; + border-radius: 3px; + margin: 5px; + word-wrap: break-word; + position: relative; + .delBtn-wrap { + width: 100px; + text-align: center; + position: absolute; + right: -106px; + background: red; + box-sizing: border-box; + top: 0px; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + .delBtn { + width: 36px; + height: 36px; + line-height: 36px; + border-radius: 50%; + font-size: 16px; + color:#ffffff; + // background: url(delete-2.png) no-repeat; + background-size: contain; + } + } + } + .item-content { + img { + max-width: 80px; + margin-bottom: 5px; + } + } + .item-summary { + font-size: 12px; + color: #555; + margin-top: 5px; + span { + margin-right: 10px; + } + span:last-child { + margin-right: 0; + } + } + } +} \ No newline at end of file diff --git a/client/user/dashboard/dashboard.coffee b/client/user/dashboard/dashboard.coffee new file mode 100644 index 000000000..547e7e241 --- /dev/null +++ b/client/user/dashboard/dashboard.coffee @@ -0,0 +1,354 @@ +#space 2 +if Meteor.isClient + Template.dashboard.rendered=-> + if Session.get('dashboardHeight') is undefined + Session.set('dashboardHeight', $(window).height()) + $('.dashboard').css 'min-height', Session.get('dashboardHeight') + $('body').css('height', 'auto') + Template.dashboard.onDestroyed ()-> + $('body').css('height', '100%') + Template.dashboard.helpers + showFollowTips: -> + if Meteor.user() and Meteor.user().profile and Meteor.user().profile.followTips + return Meteor.user().profile.followTips isnt false + else + false + userEmail :-> + address = '' + if Meteor.user() and Meteor.user().emails + if Meteor.user().emails[0] and Meteor.user().emails[0].address + address = Meteor.user().emails[0].address + return address + anonymous :-> + if Meteor.user() + Meteor.user().profile.anonymous + else + '' + allowLanguageSetting:-> + if withLanguageSetting + return true + else + return false + isEnglish: -> + if Cookies.check("display-lang") + return Cookies.get("display-lang") is 'en' + else + return false + newVersion: -> + version_of_build + inDevMode: -> + devMode = false + ldev = Session.get('inDevMode') + if ldev is null or ldev is undefined + ldev = localStorage.getItem('inDevMode') + if ldev is true or ldev is 'true' + devMode = true + Session.set('inDevMode', devMode); + return devMode + isLatestVersion: -> + # version = Versions.findOne({}) + if checkNewVersion() + return false + else + return true + addDashboardIntoHistory = ()-> + history = [] + history.push { + view: 'dashboard' + scrollTop: 0 + } + Session.set "history_view", history + Template.dashboard.events + 'click .readFollowTips': -> + Meteor.users.update( + {_id: Meteor.userId()} + {$set: {'profile.followTips': !(Meteor.user().profile.followTips isnt false)}} + ) + 'click .email' :-> + addDashboardIntoHistory() + Router.go '/my_email' + 'click .accounts-management' :-> + addDashboardIntoHistory() + Router.go '/my_accounts_management' + 'click .changePasswd' :-> + addDashboardIntoHistory() + Router.go '/my_password' + 'click .blacklist' :-> + addDashboardIntoHistory() + Router.go '/my_blacklist' + 'click .notice' :-> + addDashboardIntoHistory() + Router.go '/my_notice' + 'click .language' :-> + addDashboardIntoHistory() + Router.go '/display_lang' + 'click .devmode' :-> + old_dev = false + old_val = Session.get("inDevMode") + if old_val is false + old_dev = true + Session.set("inDevMode", old_dev) + localStorage.setItem('inDevMode', old_dev) + 'click .update' :-> + console.log '##RDBG update clicked' + #$('#updateToLatestVersion').modal('show') + 'click #updateToLatestVersion .btn-primary' :-> + window.location.href = 'http://180.153.105.143/imtt.dd.qq.com/16891/346CBF0E04862CA542EA8AD714643FB6.apk?mkey=57d128030673c190&f=188a&c=0&fsname=org.hotshare.everywhere_1.3.10_103102.apk&hsr=4d5s&p=.apkhttp://a.app.qq.com/o/simple.jsp?pkgname=org.hotshare.everywhere' + setTimeout(()-> + $("#updateToLatestVersion .btn-default").trigger('click'); + , 200) + 'click .about' :-> + addDashboardIntoHistory() + Router.go '/my_about' + 'click .back' :-> + Router.go '/user' + 'click .logout':(e)-> + e.target.innerText="正在退出登录..." + thisUser = Meteor.user() + Meteor.call('updatePushToken' ,{type: thisUser.type, token: thisUser.token,userId:''}); +# Meteor.users.update({_id: thisUser._id}, {$set: {type: '', token: ''}}) + Meteor.logout (msg)-> + Session.setPersistent('persistentLoginStatus',false) + Router.go '/loginForm' + Template.my_email.rendered=-> + $('.dashboard').css 'min-height', $(window).height() + return + Template.my_email.helpers + userEmail :-> + Meteor.user().emails[0].address + Template.my_email.events + 'click #btn_save' :-> + Users = Meteor.users + #正则验证邮箱格式 + my_edit_email = $('#my_edit_email').val() + ret = my_edit_email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) + if not ret + PUB.toast '无效邮箱地址!' + return + new_email = [{address: $('#my_edit_email').val(), verified: false}] + Meteor.subscribe('allUsers'); + userExist = Users.find({emails: new_email}).fetch()[0] + if userExist != undefined + PUB.toast "邮箱地址未修改!" +# PUB.toast "邮箱地址已存在!" + else + Users.update {_id: Meteor.user()._id}, {$set: {emails: new_email}}, (error, result) -> + if error + PUB.toast "邮箱地址已存在!" + return + else + PUB.toast "邮箱修改成功!" + Router.go '/dashboard' + + 'click #btn_back' :-> + Router.go '/dashboard' + + Template.my_password.rendered=-> + $('.dashboard').css 'min-height', $(window).height() + return + Template.my_password.helpers + showSaveBtn :-> + if Session.get('changePasswordSaveBtnClicked') is true + false + else + true + hasnick :-> + if Meteor.user() and Meteor.user().profile and Meteor.user().profile.fullname and Meteor.user().profile.fullname isnt '' + true + else + false + hasemail :-> + if Meteor.user() and Meteor.user().emails and Meteor.user().emails[0] and Meteor.user().emails[0].address + true + else + false + userEmail :-> + Meteor.user().emails[0].address + newpassword :-> + $("#my_edit_password").val() + currentuser :-> + Meteor.user() + Template.my_password.events + 'click #pass_btn_save' :-> + Session.set('changePasswordSaveBtnClicked', true) + old_pass = $("#my_old_password").val() + new_pass = $("#my_edit_password").val() + new_pass_confirm = $("#my_edit_password_confirm").val() + if new_pass != new_pass_confirm + Session.set('changePasswordSaveBtnClicked', false) + PUB.toast "两次填写的密码不一致!" + return + else if new_pass.length<6 + Session.set('changePasswordSaveBtnClicked', false) + PUB.toast "密码至少要6位"; + return + if new_pass + navigator.notification.confirm('', (r)-> + if r is 1 + console.log 'changePassword !!' + $(".shownewpassword").html(new_pass) + Accounts.changePassword old_pass, new_pass, (error) -> + console.log 'changePassword error ' + error + if error + Meteor.setTimeout ()-> + Session.set('changePasswordSaveBtnClicked', false) + ,5000 + PUB.toast '输入密码有误,请重试!' + else + Session.set('changePasswordSaveBtnClicked', false) + $('.afterchangepassword').fadeOut 300 + $('.show-change-userinfo').fadeIn 300 + return + # Meteor.call "changeMyPassword", new_pass, (error, result) -> + # if error + # Meteor.setTimeout ()-> + # Session.set('changePasswordSaveBtnClicked', false) + # ,5000 + # PUB.toast '修改密码失败,请重试!' + # else + # Session.set('changePasswordSaveBtnClicked', false) + # PUB.toast '修改密码成功!' + # Router.go '/authOverlay' + # return + , '修改密码并重新登录!', ['确定']); + else + Session.set('changePasswordSaveBtnClicked', false) + PUB.toast "密码不能为空!" + 'click #pass_btn_back' :-> + Session.set('changePasswordSaveBtnClicked', false) + Router.go '/dashboard' + 'click #save-user-info-btn' :-> + Meteor.logout (msg)-> + Session.set("searchContent","") + #PostsSearch.cleanHistory() + Session.setPersistent('persistentLoginStatus',false) + Session.setPersistent('persistentFeedsForMe',null) + Session.setPersistent('persistentMyFollowedPosts',null) + Session.setPersistent('myFollowedByCount',0) + Session.setPersistent('mySavedDraftsCount',0) + Session.setPersistent('myPostsCount',0) + Session.setPersistent('myFollowToCount',0) + Session.setPersistent('persistentProfileIcon',null) + Session.setPersistent('persistentProfileName',null) + Session.setPersistent('persistentMySavedDrafts',null) + Session.setPersistent('persistentMyOwnPosts',null) + #console.log msg + window.plugins.userinfo.setUserInfo '', -> + console.log 'setUserInfo was succeed!' + return + , -> + console.log 'setUserInfo was Error!' + return + Router.go '/loginForm' + + Template.my_notice.rendered=-> + $('.dashboard').css 'min-height', $(window).height() + return + Template.my_notice.helpers + isIOS :-> + if device.platform is 'iOS' + true + else + false + Template.my_notice.events + 'click #about_btn_back' :-> + Router.go '/dashboard' + + Template.my_blacklist.rendered=-> + $('.dashboard').css 'min-height', $(window).height() + Meteor.subscribe("allBlackList") + # Meteor.subscribe('allUsers') + return + Template.my_blacklist.helpers + myBlackers :-> + blackList = BlackList.findOne({blackBy: Meteor.userId()}) || {} + if blackList + blackList.blacker + Template.my_blacklist_item.helpers + profile :-> + id = this.toString() + Meteor.subscribe('usersById', id) + return Meteor.users.findOne({_id: id}).profile + thisUserName:-> + id = this.toString() + if Meteor.users.findOne({_id: id}).profile.fullname + return username = Meteor.users.findOne({_id: id}).profile.fullname + else + return username = Meteor.users.findOne({_id: id}).username + Template.my_blacklist_item.events + 'click .remove' :(e)-> + id = this.toString() + blackId = BlackList.findOne({blackBy: Meteor.userId()})._id + menus = ['从黑名单中移除'] + menuTitle = '' + callback = (buttonIndex)-> + if buttonIndex is 1 + BlackList.update({_id: blackId}, {$pull: {blacker: id}}) + blacker = Meteor.users.findOne({_id: id}) + blackerName = if blacker.profile.fullname then blacker.profile.fullname else blacker.username + Follower.insert { + userId: Meteor.userId() + userName: Meteor.user().profile.fullname || Meteor.user().username + userIcon: Meteor.user().profile.icon || '/userPicture.png' + userDesc: Meteor.user().profile.desc + + followerId: blacker._id + followerName: blackerName + followerIcon: blacker.profile.icon || '/userPicture.png' + followerDesc: blacker.profile.desc + + createAt: new Date() + } + PUB.actionSheet(menus, menuTitle, callback) + e.preventDefault() + e.stopPropagation() + Template.my_blacklist.events + 'click #about_btn_back' :-> + if Session.get('fromeaddblacllist') is true + Session.set('fromeaddblacllist', false) + Router.go '/' + else + Router.go '/dashboard' + Template.my_about.helpers + version:-> + if isIOS.true is false + return version_of_build + if Meteor.isCordova and isIOS and window.plugins.appsetup + window.plugins.appsetup.getVersion((version)-> + if version and version isnt '' + Session.set('AppVersion',version) + else + Session.set('AppVersion',version_of_build) + ()-> + Session.set('AppVersion',version_of_build) + ) + return Session.get('AppVersion') + version_of_build + Template.my_about.rendered=-> + $('.dashboard').css 'min-height', $(window).height() + return + Template.my_about.events + 'click #about_btn_back' :-> + Router.go '/dashboard' + + Template.display_lang.helpers + isEnglish: -> + if Cookies.check("display-lang") + return Cookies.get("display-lang") is 'en' + else + return false + Template.display_lang.events + 'click #about_btn_back' :-> + Router.go '/dashboard' + 'click #english': -> + Session.set("display_lang","en") + Cookies.set("display-lang","en",360) + TAPi18n.setLanguage("en") + Meteor.call 'updateUserLanguage', Meteor.userId(), 'en' + Router.go '/dashboard' + 'click #chinese': -> + Session.set("display_lang","zh") + Cookies.set("display-lang","zh",360) + TAPi18n.setLanguage("zh") + Meteor.call 'updateUserLanguage', Meteor.userId(), 'zh' + Router.go '/dashboard' diff --git a/client/user/dashboard/dashboard.html b/client/user/dashboard/dashboard.html new file mode 100644 index 000000000..58739d0a9 --- /dev/null +++ b/client/user/dashboard/dashboard.html @@ -0,0 +1,272 @@ + ++ {{#if text}} ++{{{text}}}
+ {{/if}} + {{#each images}} ++ {{/each}} +
+ {{name}} + {{collectDate}} +
+++删除+++++{{_ "setting"}}+{{_ "done"}}+++++{{_ "Account"}}+ {{#unless anonymous}} + +{{_ "email"}}{{userEmail}}+{{_ "changePass"}}+ + {{/unless}} + ++ + + ++ +{{_ "notification"}}+ + + {{#if allowLanguageSetting}} +{{_ "language"}} + {{#if isEnglish}} + English + {{else}} + 中文 + {{/if}} + ++ {{/if}} + {{#unless isLatestVersion}} +{{_ "version"}} + + {{_ "Newversion"}} + ++ {{/unless}} + {{#if inDevMode}} +{{_ "devMode"}}+ {{else}} +{{_ "devMode"}}+ {{/if}} +{{_ "about"}}+{{_ "logOut"}}+++ + ++ ++++ + +++{{_ "email"}}++{{_ "save"}}+++++{{_ "email"}}+ + +++ + + +++{{_ "changePass"}}++ {{#if showSaveBtn}} +{{_ "save"}}+ {{else}} +{{_ "save"}}+ {{/if}} +++ + +++{{_ "currentPass"}}+{{_ "newPass"}}+{{_ "confirmPass"}}+ +++ + +++{{_ "notification"}}+++ {{#if isIOS}} ++++{{_ "notifiButton"}}+如果你要关闭或开启来了吗的新消息通知,请在“设置”-“通知”功能中,找到应用程序“来了吗”更改。+ + {{else}} +++{{_ "notifiButton"}}+如果你要关闭或开启来了吗的新消息通知,请在系统设置中,找到应用程序“来了吗”更改}+ + {{/if}} +++ + +++{{_ "blacklist"}}+++++ {{#each myBlackers}} + {{> my_blacklist_item}} + {{/each}} +
+- +
++
{{thisUserName}}+移除黑名单++ ++ + + +++ + + +++{{_ "aboutTitle"}}+++ ++来了吗
+V{{version}}+第一款开放式深度学习APP,提供手机上所见所得的深度学习训练环境,一点一圈,完成训练;远程对接嵌入式AI深瞳集群,运行结果一目了然;独创多人协作的AI训练群,协同工作,互相激励;灵活可配置的人工智能视觉出现,提高效率,圈住商机。+ + + +++ + + +++{{_ "language"}}+++++ {{#if isEnglish}} ++ +{{_ "English"}}+{{_ "Chinese"}}+ {{else}} +{{_ "English"}}+{{_ "Chinese"}}+ {{/if}} ++ ++ diff --git a/client/user/dayTasks.html b/client/user/dayTasks.html new file mode 100644 index 000000000..81336a7e2 --- /dev/null +++ b/client/user/dayTasks.html @@ -0,0 +1,20 @@ + + ++++++
想获取该作者的最新动态吗?
+++ +一键关注,您将实时收到作者最新文章
+++ \ No newline at end of file diff --git a/client/user/dayTasks.js b/client/user/dayTasks.js new file mode 100644 index 000000000..2eb4a84a5 --- /dev/null +++ b/client/user/dayTasks.js @@ -0,0 +1,50 @@ +var todayUTC = new ReactiveVar(null); + +Template.dayTasks.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); + + todayUTC.set(date); + + var group_id = Router.current().params._id; + Meteor.subscribe('group_workstatus',group_id, date); +}); + +Template.dayTasks.helpers({ + data: function() { + var group_id = Router.current().params._id; + + return WorkStatus.findOne({group_id: group_id,date: todayUTC.get(),app_user_id:Meteor.userId()}); + } +}); + +Template.dayTasks.events({ + 'click .back': function(e) { + return PUB.back(); + }, + 'click .save': function(e) { + var text = $('#wahtsUpTextContent').val(); + + var group_id = Router.current().params._id; + var date = todayUTC.get(); + + var work_status = WorkStatus.findOne({group_id: group_id,date: todayUTC.get(),app_user_id:Meteor.userId()}); + if (work_status) { + var whats_up = work_status.whats_up || []; + whats_up.push({ + content: text, + person_name: Meteor.user().username, + ts: Date.now() + }); + WorkStatus.update({_id: work_status._id},{$set:{whats_up: whats_up}}, function(error, result){ + if(error) { + console.log('==sr==. update WorkStatus Err=', error); + return PUB.tosat('更新今日简述失败~') + } + return PUB.back(); + }); + } + } +}); \ No newline at end of file diff --git a/client/user/follow/followers.coffee b/client/user/follow/followers.coffee new file mode 100644 index 000000000..8358e0f1f --- /dev/null +++ b/client/user/follow/followers.coffee @@ -0,0 +1,135 @@ +#space 2 +if Meteor.isClient + @addFollower = (data)-> + followerId = data.followerId + callback = ()-> + blackId = BlackList.findOne({blackBy: Meteor.userId()})._id + BlackList.update({_id: blackId}, {$pull: {blacker: followerId}}) + Follower.insert data + #对方在黑名单中 + if BlackList.find({blackBy: Meteor.userId(), blacker:{$in: [followerId]}}).count() > 0 + navigator.notification.confirm( + '你已将对方加入黑名单,是否解除?' + (index)-> + if index is 2 + callback() + '提示' + ['暂不','解除'] + ) + else + Follower.insert data + Template.followers.rendered=-> + $('.content').css 'min-height',$(window).height() + $(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); + if Session.get('followers_tag') + Session.set("followersitemsLimit", + Session.get("followersitemsLimit") + FOLLOWS_ITEMS_INCREMENT); + else + Session.set("followeesitemsLimit", + Session.get("followeesitemsLimit") + FOLLOWS_ITEMS_INCREMENT); + else + if (target.data("visible")) + target.data("visible", false); + Template.followers.helpers + followers:-> + #true 列出偶像列表,false 列出粉丝列表 + #Follower存放用户间关注记录, Follows是推荐偶像列表 + #followerId是偶像userId, userId是粉丝userId + if Session.get('followers_tag') + #粉丝是自己的; true 列出偶像 + Follower.find({"userId":Meteor.userId()}, {sort: {createdAt: -1}}, {limit:Session.get("followersitemsLimit")}) + else + #偶像id是自己的; false 列出粉丝 + Follower.find({"followerId":Meteor.userId()}, {sort: {createdAt: -1}}, {limit:Session.get("followeesitemsLimit")}) + isFollowers:-> + if Session.get('followers_tag') + true + else + false + page_title:-> + #true 列出偶像列表,false 列出粉丝列表 + if Session.get('followers_tag') + #'正在关注' + TAPi18n.__("following") + else + #'关注者' + TAPi18n.__("follower") + isFollowed:(follow)-> + if Session.get('followers_tag') + #follow.userId是自己 + #follow.followerId是偶像 + #这个页面可以取消关注,所以要重新检查是否还有关注 + fcount = Follower.find({"userId":Meteor.userId(),"followerId":follow.followerId}).count() + else + #follow.userId是粉丝 + #找followerId是follow.userId,是否互粉 + Meteor.subscribe("friendFollower",Meteor.userId(),follow.userId) + fcount = Follower.find({"userId":Meteor.userId(),"followerId":follow.userId,"userEmail": {$exists: false}}).count() + if fcount > 0 + true + else + false + moreResults:-> + if Session.get('followers_tag') + !(Follower.find({"userId":Meteor.userId()}).count() < Session.get("followersitemsLimit")) + else + !(Follower.find({"followerId":Meteor.userId()}).count() < Session.get("followeesitemsLimit")) + loading:-> + if Session.get('followers_tag') + Session.equals('followersCollection','loading') + else + Session.equals('followeesCollection','loading') + loadError:-> + if Session.get('followers_tag') + Session.equals('followersCollection','error') + else + Session.equals('followeesCollection','error') + Template.followers.events + 'click .back' :-> + Router.go '/user' + 'click .del':(e)-> + followerId = e.currentTarget.id + FollowerId = Follower.findOne({ + userId: Meteor.userId() + followerId: followerId + })._id + Follower.remove(FollowerId) + 'click .add':(e)-> + #true 列出偶像列表,false 列出粉丝列表 + if Session.get('followers_tag') + followerId = @followerId + followerName = @followerName + followerIcon = @followerIcon + followerDesc = @followerDesc + else + followerId = @userId + followerName = @userName + followerIcon = @userIcon + followerDesc = @userDesc + + if Meteor.user().profile.fullname + username = Meteor.user().profile.fullname + else + username = Meteor.user().username + insertObj = { + userId: Meteor.userId() + #这里存放fullname + userName: username + userIcon: Meteor.user().profile.icon + userDesc: Meteor.user().profile.desc + followerId: followerId + #这里存放fullname + followerName: followerName + followerIcon: followerIcon + followerDesc: followerDesc + createAt: new Date() + } + addFollower(insertObj) diff --git a/client/user/follow/followers.html b/client/user/follow/followers.html new file mode 100644 index 000000000..3853402b3 --- /dev/null +++ b/client/user/follow/followers.html @@ -0,0 +1,52 @@ + ++++保存+ 今日简述 ++ ++++ {{> footer}} + diff --git a/client/user/management/management.coffee b/client/user/management/management.coffee new file mode 100644 index 000000000..453606970 --- /dev/null +++ b/client/user/management/management.coffee @@ -0,0 +1,172 @@ +Meteor['_unsubscribeAll'] = _.bind(Meteor.connection['_unsubscribeAll'], Meteor.connection); +is_loading = new ReactiveVar([]) +loginFn = (id)-> + Meteor._unsubscribeAll() + Meteor.loginWithUserId id, false, (err)-> + # 切换帐号时清空PostSearch history + Session.set("searchContent","") + #PostsSearch.cleanHistory() + if err is 'RESET_LOGIN' + return navigator.notification.confirm('切换帐号失败~' + (index)-> + if index is 1 then loginFn id + '提示', ['知道了', '重新切换'] + ) + else if err is 'NOT_LOGIN' + return navigator.notification.confirm('切换帐号时发生异常,需要重新登录您的帐号!' + ()-> + return Router.go '/loginForm' + '提示', ['重新登录'] + ) + else if err is 'WAIT_TIME' + return navigator.notification.confirm '切换帐号太频繁了(间隔至少10秒),请稍后再试!', null, '提示', ['知道了'] + + window.plugins.userinfo.setUserInfo( + Meteor.userId() + ()-> + console.log("setUserInfo was success ") + ()-> + console.log("setUserInfo was Error!") + ) + Router.go '/my_accounts_management' + Meteor.defer ()-> + Session.setPersistent('persistentMySavedDrafts', SavedDrafts.find({},{sort: {createdAt: -1},limit:2}).fetch()) + Session.setPersistent('persistentMyOwnPosts', Posts.find({owner: Meteor.userId(),publish:{"$ne":false}}, {sort: {createdAt: -1},limit:4}).fetch()) + Session.setPersistent('myFollowedByCount',Counts.get('myFollowedByCount')) + Session.setPersistent('mySavedDraftsCount',Counts.get('mySavedDraftsCount')) + Session.setPersistent('myPostsCount',Counts.get('myPostsCount')) + Session.setPersistent('myFollowToCount',Counts.get('myFollowToCount')) + Session.setPersistent('myFollowToCount',Counts.get('myEmailFollowerCount')) + + is_loading.set([]) + navigator.notification.confirm '切换帐号成功~', null, '提示', ['知道了'] + +Template.accounts_management.rendered=-> + is_loading = new ReactiveVar([]) + Tracker.autorun ()-> + if Meteor.status().connected && is_loading.get().length > 0 + loginFn is_loading.get().pop() + is_loading.set([]) + + + $('.dashboard').css 'min-height', $(window).height() + + # userIds = [] + # AssociatedUsers.find({}).forEach((item)-> + # if Meteor.userId() isnt item.userIdA and !~ userIds.indexOf(item.userIdA) + # userIds.push(item.userIdA) + + # if Meteor.userId() isnt item.userIdB and !~ userIds.indexOf(item.userIdB) + # userIds.push(item.userIdB) + # ) + + # Meteor.subscribe('associateduserdetails', userIds) + + # return + +Template.accounts_management.helpers + is_me: (id)-> + return id is Meteor.userId() + connecting: -> + return is_loading.get().length > 0 + loging: -> + return Meteor.loggingIn() + accountList :-> + UserRelation.find({userId: Meteor.userId()}) + # userIds = [] + # AssociatedUsers.find({}).forEach((item)-> + # if Meteor.userId() isnt item.userIdA and !~ userIds.indexOf(item.userIdA) + # userIds.push(item.userIdA) + + # if Meteor.userId() isnt item.userIdB and !~ userIds.indexOf(item.userIdB) + # userIds.push(item.userIdB) + # ) + + # return Meteor.users.find({_id: {'$in': userIds}}) + +Template.accounts_management.events + 'click dl.my_account': -> + if is_loading.get().length > 0 + return navigator.notification.confirm '正在切换中,请稍后在试~', null, '提示', ['知道了'] + slef = this + unless Meteor.status().connected + is_loading.set([@toUserId]) + return Meteor.reconnect() + loginFn(@toUserId) + 'click .add-new' :-> + history = Session.get("history_view") + history.push { + view: 'my_accounts_management' + scrollTop: document.body.scrollTop + } + Session.set "history_view", history + Router.go '/my_accounts_management_addnew' + + 'click .remove': (e, t)-> + e.stopPropagation() + id = @toUserId + #console.log(this._id) + #console.log(e.currentTarget) + PUB.confirm( + '确定要删除吗?' + ()-> + Meteor.call( + 'removeAssociatedUserNew' + id + ) + ) + + 'click .leftButton' :-> + Router.go '/dashboard' + + + + +Template.accounts_management_addnew.rendered=-> + $('.dashboard').css 'min-height', $(window).height() + return + +Template.accounts_management_addnew.events + 'click .leftButton' :-> + PUB.back() + 'submit #form-addnew': (e, t)-> + e.preventDefault() + # need wait method response + $(e.target).find('input[type=submit]').attr('disabled','').removeClass('active').val('添加中...') + userInfo = { + username: $(e.target).find('input[name=username]').val(), + password: Package.sha.SHA256($(e.target).find('input[name=password]').val()), + type: Meteor.user().type, + token: Meteor.user().token + } + + Meteor.call('addAssociatedUserNew', userInfo, (err, data)-> + $(e.target).find('input[type=submit]').removeAttr('disabled').addClass('active').val('添加') + if data and data.status is 'ERROR' + if data.message is 'Invalid Username' + PUB.toast('用户不存在') + else if data.message is 'Can not add their own' + PUB.toast('不能添加自己') + else if data.message is 'Exist Associate User' + PUB.toast('该用户已关联') + else if data.message is 'Invalid Password' + PUB.toast('密码不正确') + else + PUB.toast('用户名或密码不正确') + else + Router.go '/my_accounts_management' + ); + +Template.accounts_management_prompt.rendered=-> + $(".spinner .spinner-blade").css({"width":"0.104em","height":"0.4777em","transform-origin":"center -0.4222em"}) + $("body,html").css({"overflow":"hidden"}) + return + +Template.accounts_management_prompt.events + 'click .prompt-close' :-> + $('.page-accounts-management-prompt').remove() + +Template.accounts_management_prompt.destroyed=-> + $(".spinner .spinner-blade").css({"width":"0.074em","height":"0.2777em","transform-origin":"center -0.2222em"}) + $("body,html").css({"overflow":""}) + return diff --git a/client/user/management/management.html b/client/user/management/management.html new file mode 100644 index 000000000..41033244b --- /dev/null +++ b/client/user/management/management.html @@ -0,0 +1,95 @@ + +++{{page_title}}+++ {{#if loading}} ++正在加载中...+ {{/if}} + {{#if loadError}} +加载失败,请检查网络设置或稍后重试+ {{/if}} + {{#each followers}} + {{#if isFollowers}} + + ++
{{followerName}}+ {{#if isFollowed this}} ++ {{else}} ++ {{/if}} +{{followerDesc}}+ + {{else}} + + ++
{{userName}}+ {{#if isFollowed this}} ++ {{else}} ++ {{/if}} +{{userDesc}}+ + {{/if}} ++ ++ {{/each}} + {{#if moreResults}} ++ 加载中... ++ {{/if}} +++ + + ++ {{#if connecting}} ++ {{#if currentUser}} +重新连接中...+ {{else}} + +{{_ "Account"}}+ + {{/if}} ++++ {{/if}} ++++
+ {{#each accountList}} + {{> accounts_management_item}} + {{/each}} +- +
- + {{#if currentUser.profile.fullname}} + {{currentUser.profile.fullname}} + {{else}} + {{currentUser.username}} + {{/if}} +
++
+- +
- {{_ "addAccount"}}
+退出当前帐号+ {{#if loging}} + {{> accounts_management_prompt}} + {{/if}} ++
+ + + +- +
- + {{toName}} + +
+++ + + +++{{_ "addAccount"}}+++ ++++ diff --git a/client/user/onLogin.js b/client/user/onLogin.js new file mode 100644 index 000000000..c5bbbece0 --- /dev/null +++ b/client/user/onLogin.js @@ -0,0 +1,80 @@ +if (Meteor.isClient) { + Meteor.startup(function() { + return Accounts.onLogin(function() { + if (Meteor.user().profile["new"] === true) { + Session.setPersistent('persistentLoginStatus', false); + } else { + Session.setPersistent('persistentLoginStatus', true); + } + return Meteor.setTimeout(function() { + if(isUSVersion){ + Meteor.call('updateUserLanguage', Meteor.userId(), 'en'); + } else { + Meteor.call('updateUserLanguage', Meteor.userId(), 'zh'); + } + console.log("Accounts.onLogin"); + Session.set("token", ''); + Meteor.subscribe("pcomments"); + checkShareUrl(); + if(device.platform === 'Android'){ + window.plugins.shareExtension.getShareData(function(data) { + console.log("##RDBG getShareData: " + JSON.stringify(data)); + if(data){ + editFromShare(data); + } + }, function() {}); + window.plugins.shareExtension.emptyData(function(result) {}, function(err) {}); + } + window.updateMyOwnLocationAddress(); + if (device.platform === 'iOS' && localStorage.getItem('registrationID') == null ) { + var registerInterval1 = window.setInterval( function(){ + console.log('on push notification init'); + 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) { + console.log('Got message'); + if (data.count) { + Session.set('waitReadCount', data.count); + } + 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) { + return console.log('No Push Notification support in this build error = ' + e.message); + }); + },20000 ); + } + if (Session.get('registrationID') && localStorage.getItem('registrationID') && device.platform === 'iOS') { + console.log(localStorage.getItem('registrationID')); + return window.updatePushNotificationToken('iOS', localStorage.getItem('registrationID')); + } + }, 3000); + }); + }); +} diff --git a/client/user/user.coffee b/client/user/user.coffee new file mode 100644 index 000000000..7982fd583 --- /dev/null +++ b/client/user/user.coffee @@ -0,0 +1,633 @@ +#space 2 +if Meteor.isClient + now = new Date(); + today = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate(), 0, 0, 0, 0); + + userGroupIndex = new ReactiveVar(0) + + Meteor.startup ()-> + ### + Session.setDefault('myFollowedByCount',0) + Session.setDefault('mySavedDraftsCount',0) + Session.setDefault('myPostsCount',0) + Session.setDefault('myFollowToCount',0) + ### + Tracker.autorun ()-> + ### + Meteor.subscribe "myCounter",{ + onReady:()-> + Session.set('myCounterCollection','loaded') + } + ### + if Meteor.user() and Session.equals('channel','user') + Session.set('gotMyProfileData',false) + Meteor.setTimeout ()-> + Meteor.call('getMyProfileData',(err,json)-> + if(!err && json) + Session.set('gotMyProfileData',true) + console.log(json) + Session.setPersistent('myPostsCount',json['myPostsCount']) + Session.setPersistent('mySavedDraftsCount',json['mySavedDraftsCount']) + Session.setPersistent('myFollowedByCount',json['myFollowedByCount']) + Session.setPersistent('myFollowedByCount-'+Meteor.userId(),json['myFollowedByCount-'+Meteor.userId()]) + Session.setPersistent('myFollowToCount',json['myFollowToCount']) + Session.setPersistent('myEmailFollowerCount',json['myEmailFollowerCount']) + Session.setPersistent('myEmailFollowerCount-'+Meteor.userId(),json['myEmailFollowerCount-'+Meteor.userId()]) + console.log('Issue on getMyProfileData') + ) + ,100 + + Tracker.autorun ()-> + if Meteor.user() and Session.equals('channel','user') + Session.set('postsWithLimitCollection','loading') + Session.set('savedDraftsWithLimitCollection','loading') + Session.set('followedByWithLimitCollection','loading') + Session.set('followToWithLimitCollection','loading') + Session.set('myCounterCollection','loading') + # Meteor.subscribe("postsWithLimit",4,{ + # onReady:()-> + # Session.set('postsWithLimitCollection','loaded') + # }) + # Meteor.subscribe("savedDraftsWithLimit",20,{ + # onReady:()-> + # Session.set('savedDraftsWithLimitCollection','loaded') + # }) + # Meteor.subscribe("followedByWithLimit",10,{ + # onReady:()-> + # Session.set('followedByWithLimitCollection','loaded') + # }) + # Meteor.subscribe("followToWithLimit",10,{ + # onReady:()-> + # Session.set('followToWithLimitCollection','loaded') + # }) + Meteor.subscribe("userRelation") + Meteor.subscribe("userGroups") + Meteor.subscribe("group_devices") + # Meteor.subscribe('myCounter',{ + # onReady:()-> + # Session.set('myCounterCollection','loaded') + # }) + Template.user.onRendered(-> + userGroupIndex.set(0) + ) + + Template.user.helpers + followedOnly: () -> + if Meteor.userId() + followDoc = NotificationFollowList.findOne({_id: Meteor.userId()}) + if followDoc && followDoc['followedOnly'] + Session.set('push_followed_only',true) + return 'checked' + + Session.set('push_followed_only',false) + return '' + getShortTime: (ts,group_id)-> + time_offset = 8 + group = SimpleChat.Groups.findOne({_id: group_id}) + if group and group.offsetTimeZone + time_offset = group.offsetTimeZone + time = new Date(this.ts) + return time.shortTime(time_offset,true) + isLoading:-> + if Session.get('myPostsCount') isnt undefined + return false + if ( + Session.get('persistentProfileIcon') is undefined or + Session.get('persistentProfileName') is undefined or + Session.get('myFollowedByCount') is undefined or + Session.get('myEmailFollowerCount') is undefined or + Session.get('mySavedDraftsCount') is undefined or + Session.get('persistentMySavedDrafts') is undefined or + Session.get('myPostsCount') is undefined or + Session.get('persistentMyOwnPosts') is undefined or + Session.get('myFollowToCount') is undefined + ) and ( + Session.get('gotMyProfileData') is false or + Session.get('postsWithLimitCollection') is 'loading' or + Session.get('savedDraftsWithLimitCollection') is 'loading' or + Session.get('followedByWithLimitCollection') is 'loading' or + Session.get('followToWithLimitCollection') is 'loading' + ) + return true + else + return false + myProfileIcon:-> + me = Meteor.user() + if me and me.profile and me.profile.icon + Session.setPersistent('persistentProfileIcon',me.profile.icon) + Session.get('persistentProfileIcon') + myProfileName:-> + me = Meteor.user() + if me and me.profile and me.profile.fullname + Session.setPersistent('persistentProfileName',me.profile.fullname) + else if me and me.username + Session.setPersistent('persistentProfileName',me.username) + Session.get('persistentProfileName') + followers:-> + #Follower存放用户间关注记录, Follows是推荐偶像列表 + #followerId是偶像userId, userId是粉丝userId + myFollowedByCount = Session.get('myEmailFollowerCount-'+Meteor.userId()) + Session.get('myFollowedByCount-'+Meteor.userId()) + if Session.equals('myCounterCollection','loaded') + myFollowedByCount = Counts.get('myEmailFollowerCount-'+Meteor.userId()) + Counts.get('myFollowedByCount-'+Meteor.userId()) + if myFollowedByCount + myFollowedByCount + else + 0 + + emailFollowerCount:-> + myEmailFollowedByCount = Session.get('myEmailFollowerCount-'+Meteor.userId()) + if Session.equals('myCounterCollection','loaded') + myEmailFollowedByCount = Counts.get('myEmailFollowerCount-'+Meteor.userId()) + if myEmailFollowedByCount + myEmailFollowedByCount + else + 0 + + appFollowerCount:-> + myFollowedByCount = Session.get('myFollowedByCount-'+Meteor.userId()) + if Session.equals('myCounterCollection','loaded') + myFollowedByCount = Counts.get('myFollowedByCount-'+Meteor.userId()) + + if myFollowedByCount + myFollowedByCount + else + 0 + + draftsCount:-> + mySavedDraftsCount = Session.get('mySavedDraftsCount') + if Session.equals('myCounterCollection','loaded') + mySavedDraftsCount = Counts.get('mySavedDraftsCount') + if mySavedDraftsCount + mySavedDraftsCount + else + 0 + compareDraftsCount:(value)-> + if (Session.get('mySavedDraftsCount')> value) + true + else + false + items:()-> + mySavedDrafts = SavedDrafts.find({owner: Meteor.userId()},{sort: {createdAt: -1},limit:2}) + if mySavedDrafts.count() > 0 + Meteor.defer ()-> + Session.setPersistent('persistentMySavedDrafts',mySavedDrafts.fetch()) + return mySavedDrafts + else + Session.get('persistentMySavedDrafts') + return mySavedDrafts + gtValue: (value1, value2)-> + return value1 > value2 + gtZero: (value)-> + return value > 0 + showGrayZone:(draftsCount, postsCount)-> + if draftsCount > 0 or postsCount > 0 + return true + else + return false + postsCount:-> + #return Posts.find({owner: Meteor.userId(), publish: {$ne: false}}).count() + myPostsCount = Session.get('myPostsCount') + if Session.equals('myCounterCollection','loaded') + myPostsCount = Counts.get('myPostsCount') + if myPostsCount + myPostsCount + else + 0 + comparePostsCount:(value)-> + if (Session.get('myPostsCount') > value) + true + else + false + postItems:()-> + myOwnPosts = Posts.find({owner: Meteor.userId(),publish:{"$ne":false}}, {sort: {createdAt: -1},limit:4}) + if myOwnPosts.count() > 0 + Meteor.defer ()-> + Session.setPersistent('persistentMyOwnPosts',myOwnPosts.fetch()) + return myOwnPosts + else + Session.get('persistentMyOwnPosts') + followCount:-> + myFollowToCount = Session.get('myFollowToCount') + if Session.equals('myCounterCollection','loaded') + myFollowToCount = Counts.get('myFollowToCount') + if myFollowToCount + myFollowToCount + else + 0 + getmainImage:()-> + mImg = this.mainImage + if (mImg.indexOf('file:///') >= 0) + if Session.get(mImg) is undefined + ProcessImage = (URI,smallImage)-> + if smallImage + Session.set(mImg, smallImage) + else + Session.set(mImg, '/noimage.png') + getBase64OfImage('','',mImg,ProcessImage) + Session.get(mImg) + else + this.mainImage + hasTwoMoreGroup:()-> + workstatus = WorkStatus.find({app_user_id:Meteor.userId(),date: today}); + if workstatus and workstatus.count() > 1 + return true + return false + myGroupWorkStatus:()-> + lists = []; + SimpleChat.GroupUsers.find({user_id:Meteor.userId()},{sort:{create_time:-1}}).forEach((item)-> + lists.push({ + group_id: item.group_id, + group_name: item.group_name + }); + ); + return lists; + workstatus: (group_id)-> + if(group_id) + return WorkStatus.find({ + group_id: group_id, + app_user_id:Meteor.userId(), + date: today + }).fetch(); + return []; + hasIntime:(in_time)-> + if in_time and in_time > 0 + return true + return false + inTime:(in_time,group_id)-> + time_offset = 8 + intime = in_time + # if (!in_time) + # workstatus = WorkStatus.findOne({app_user_id:Meteor.userId(),date: today}) + # if (workstatus and workstatus.in_time) + # intime = workstatus.in_time + # if (workstatus and workstatus.group_id) + # group_id = workstatus.group_id + + group = SimpleChat.Groups.findOne({_id: group_id}) + if (group and group.offsetTimeZone) + time_offset = group.offsetTimeZone + + if (intime and intime isnt 0) + inDate = new Date(intime); + if (inDate.toString() isnt 'Invalid Date' ) + return inDate.shortTime(time_offset) + return intime; + return ''; + hasOutTime:(out_time)-> + if out_time and out_time > 0 + return true + return false + outTime:(out_time, group_id)-> + time_offset = 8 + outtime = out_time + # if (!out_time) + # workstatus = WorkStatus.findOne({app_user_id:Meteor.userId(),date: today}) + # if (workstatus and workstatus.out_time) + # outtime = workstatus.out_time + # if (workstatus and workstatus.group_id) + # group_id = workstatus.group_id + + group = SimpleChat.Groups.findOne({_id: group_id}) + if (group and group.offsetTimeZone) + time_offset = group.offsetTimeZone + + if (outtime and outtime isnt 0) + outDate = new Date(outtime); + if (outDate.toString() isnt 'Invalid Date' ) + return outDate.shortTime(time_offset) + return outtime + return ''; + devices: ()-> + group_id = Session.get('modifyMyStatus_group_id'); + in_out = Session.get('modifyMyStatus_in_out'); + return Devices.find({groupId: group_id,in_out:in_out},{sort:{createAt:-1}}).fetch(); + hasJoinGroup:()-> + SimpleChat.GroupUsers.find({user_id:Meteor.userId()}).count() > 0 + hasTwoMore:()-> + SimpleChat.GroupUsers.find({user_id:Meteor.userId()}).count() > 2 + group:()-> + lists = [] + SimpleChat.GroupUsers.find({user_id:Meteor.userId()},{sort:{create_time:-1}}).forEach((item)-> + workstatus = WorkStatus.findOne({group_id: item.group_id, app_user_id:Meteor.userId(), date: today}) + if workstatus + group = { + group_id:item.group_id, + group_name: item.group_name + } + group = _.extend(group,workstatus) + lists.push(group) + ) + index = userGroupIndex.get() + group = lists[index] + return group + isFirstGroup: ()-> + return userGroupIndex.get() < 1 + isLastGroup: ()-> + lists = [] + SimpleChat.GroupUsers.find({user_id:Meteor.userId()},{sort:{create_time:-1}}).forEach((item)-> + workstatus = WorkStatus.findOne({group_id: item.group_id, app_user_id:Meteor.userId(), date: today}) + if workstatus + group = { + group_id:item.group_id, + group_name: item.group_name + } + lists.push(group) + ) + return userGroupIndex.get() >= (lists.length - 1) + groupList:()-> + SimpleChat.GroupUsers.find({user_id:Meteor.userId()}, {limit:2, sort: {create_time: -1}}).fetch() + Template.user.events + # bind group user + 'click input':(e) -> + isChecked = false + if Session.equals('push_followed_only',true) + isChecked = true + if isChecked + NotificationFollowList.update({_id:Meteor.userId()},{ $unset: {followedOnly:1}}) + else + NotificationFollowList.update({_id:Meteor.userId()},{ $set: {followedOnly:1}}) + 'click .bindGroupUser':(e)-> + PUB.page('/bindGroupUser') + 'click .collect':(e)-> + PUB.page('/collectList') + # edit day Tasks + 'click .editDayTasks': (e)-> + group_id = $(e.currentTarget).data('groupid') + PUB.page('/dayTasks/'+group_id) + # change to next Group + 'click #changeToNextGroup': (e)-> + index = userGroupIndex.get() + index += 1 + userGroupIndex.set(index) + 'click #changeToPrevGroup': (e)-> + index = userGroupIndex.get() + index -= 1 + userGroupIndex.set(index) + 'focus #search-box': (event)-> + PostsSearch.cleanHistory() + PUB.page '/searchMyPosts' + 'click #follow': (event)-> + $('.user').addClass('animated ' + animateOutLowerEffect); + Meteor.setTimeout ()-> + Router.go '/searchFollow' + ,animatePageTrasitionTimeout + 'click .icon':(e)-> + val = e.currentTarget.innerHTML + uploadFile 160, 160, 60, (status,result)-> + e.currentTarget.innerHTML = '' + if status is 'done' and result + e.currentTarget.innerHTML = '++ ++++
+ {{>spinner}} ++++帐号切换中,请稍候...
+' + Meteor.users.update Meteor.userId(),{$set:{'profile.icon':result}} + Meteor.call 'updateFollower',Meteor.userId(),{icon:result} + console.log '头像上传成功:' + result + else + e.currentTarget.innerHTML = val + return + return + 'click #setting' :-> + $('.user').addClass('animated ' + animateOutLowerEffect); + Meteor.setTimeout ()-> + Router.go '/dashboard' + ,animatePageTrasitionTimeout + 'click .follower' :-> + Session.set('followersitemsLimit', 10); + Session.set('followeesitemsLimit', 10); + #true 列出偶像列表,false 列出粉丝列表 + Session.set 'followers_tag', false + $('.user').addClass('animated ' + animateOutLowerEffect); + Meteor.setTimeout ()-> + Router.go '/followers' + ,animatePageTrasitionTimeout + 'click .following' :-> + Session.set('followeesitemsLimit', 10); + Session.set('followersitemsLimit', 10); + #true 列出偶像列表,false 列出粉丝列表 + Session.set 'followers_tag', true + $('.user').addClass('animated ' + animateOutLowerEffect); + Meteor.setTimeout ()-> + Router.go '/followers' + ,animatePageTrasitionTimeout + 'click .draftImages ul li':(e)-> + Session.set('pubImages', []) + #Use for if user discard change on Draft + Session.set('backtopageuser', true) + TempDrafts + .find {owner: Meteor.userId()} + .forEach (drafts)-> + TempDrafts.remove drafts._id + #Clear draft first + Drafts + .find {owner: Meteor.userId()} + .forEach (drafts)-> + Drafts.remove drafts._id + #Prepare data + savedDraftData = SavedDrafts.findOne({_id: e.currentTarget.id}) + if withDirectDraftShow + if savedDraftData and savedDraftData.pub and savedDraftData._id + Session.set('postContent',savedDraftData) + PUB.page('/draftposts/'+savedDraftData._id) + return + else + toastr.error('got wrong') + return + + TempDrafts.insert { + _id:savedDraftData._id, + pub:savedDraftData.pub, + title:savedDraftData.title, + addontitle:savedDraftData.addontitle, + fromUrl:savedDraftData.fromUrl, + mainImage: savedDraftData.mainImage, + mainText: savedDraftData.mainText, + owner:savedDraftData.owner, + createdAt: savedDraftData.createdAt, + } + pub = savedDraftData.pub + deferedProcessAddPostItemsWithEditingProcessBar(pub) + Session.set 'isReviewMode','1' + PUB.page('/add') + + 'click .draftRight':(e)-> + $('.user').addClass('animated ' + animateOutLowerEffect); + Meteor.setTimeout ()-> + PUB.page('/allDrafts') + ,animatePageTrasitionTimeout + 'click .postImages ul li':(e)-> + Session.set("postPageScrollTop", 0) + postId = e.currentTarget.id + $('.user').addClass('animated ' + animateOutLowerEffect); + # history = [] + # history.push { + # view: 'user' + # scrollTop: document.body.scrollTop + # } + # Session.set "history_view", history + #Session.set('backtopageuser', true) + Meteor.setTimeout ()-> + PUB.page '/posts/'+postId + ,animatePageTrasitionTimeout + 'click .postRight':(e)-> + $('.user').addClass('animated ' + animateOutLowerEffect); + Meteor.setTimeout ()-> + PUB.page('/myPosts') + ,animatePageTrasitionTimeout + 'click .checkInTime, click .reReckInTime':(e)-> + group_id = $(e.currentTarget).data('groupid') + Session.set('wantModify',true); + if (group_id) + modifyStatusFun(group_id,'in') + return; + workstatus = WorkStatus.findOne({app_user_id:Meteor.userId(),date:today}) + if (workstatus && workstatus.group_id) + modifyStatusFun(workstatus.group_id,'in') + else + Session.set('fromUserInfomation',true); + PUB.page('/timeline') + 'click .checkOutTime, click .reCheckOutTime':(e)-> + group_id = $(e.currentTarget).data('groupid') + Session.set('wantModify',true) + if (group_id) + modifyStatusFun(group_id,'out') + return + workstatus = WorkStatus.findOne({app_user_id:Meteor.userId(),date:today}); + if (workstatus && workstatus.group_id) + modifyStatusFun(workstatus.group_id,'out') + else + Session.set('fromUserInfomation',true); + PUB.page('/timeline') + 'click .deviceItem': (e)-> + $('#selectDevicesInOut').modal('hide'); + $('.user .content').removeClass('content_box'); + setTimeout(()-> + PUB.page('/timelineAlbum/'+e.currentTarget.id); + ,1000); + 'click .groupItem':(e)-> + console.log 'click .groupItem' + $('.user').addClass('animated ' + animateOutLowerEffect); + console.log this.group_id + url = '/simple-chat/to/group?id='+this.group_id + setTimeout ()-> + PUB.page(url) + ,animatePageTrasitionTimeout + 'click .check_all':(e)-> + $('.user').addClass('animated ' + animateOutLowerEffect); + setTimeout ()-> + PUB.page('/groupsList'); + ,animatePageTrasitionTimeout + + Template.searchMyPosts.rendered=-> +# $('.content').css 'min-height',$(window).height() + if(Session.get("searchContent") isnt undefined) + $("#search-box").val(Session.get("searchContent")) + if(Session.get("showBigImage") == undefined) + Session.set("showBigImage",true) + if Session.get("noSearchResult") is true + Session.set("searchLoading", false) + if($("#search-box").val() is "") + Session.set("showSearchStatus", false) + Session.set("showSearchItems", false) + Session.set("noSearchResult", false) + $(window).scroll (event)-> + console.log "myPosts window scroll event: "+event + target = $("#showMoreMyPostsResults"); + MYPOSTS_ITEMS_INCREMENT = 300; + if (!target.length) + return; + threshold = $(window).scrollTop() + $(window).height() - target.height(); + + if target.offset().top < threshold + if (!target.data("visible")) + if Session.get("mypostsitemsLimit") < Session.get('myPostsCount') + target.data("visible", true); + Next_Limit = Session.get("mypostsitemsLimit") + MYPOSTS_ITEMS_INCREMENT + if Next_Limit > Session.get('myPostsCount') + Next_Limit = Session.get('myPostsCount') + Session.set('myPostsCollection','loading') + Session.set("mypostsitemsLimit", Next_Limit); + else + if (target.data("visible")) + target.data("visible", false); + $('#search-box').bind('propertychange input',(e)-> + text = $(e.target).val().trim() + Session.set("showSearchStatus", true) + Session.set("showSearchItems", true) + Session.set("searchLoading", true) + Session.set("noSearchResult", false) + if text is "" + Session.set("showSearchStatus", false) + Session.set("showSearchItems", false) + Session.set("searchLoading", false) + Session.set("noSearchResult", false) + return + options = {userId: Meteor.userId()} + PostsSearch.search text,options + ) +# if PostsSearch.getStatus().loaded is true +# Session.set("searchLoading", false) + $('#search-box').trigger('focus') + Template.searchMyPosts.helpers + showSearchItems:()-> + return Session.get('showSearchItems') + showSearchStatus:()-> + return Session.get('showSearchStatus') + searchLoading:()-> + return Session.get('searchLoading') + noSearchResult:()-> + return Session.get('noSearchResult') + showBigImage:()-> + return Session.get("showBigImage") + showRightIcon:()-> + if(Session.get("showBigImage")) + return "fa fa-list fa-fw" + else + return "fa fa-th-large" + getBrowseCount:(browse)-> + if (browse) + browse + else + 0 + moreResults:-> + if (Posts.find({owner:Meteor.userId()}).count() < Session.get('myPostsCount')) or (Session.equals('myPostsCollection','loading')) + true + else + false + loading:-> + Session.equals('myPostsCollection','loading') + loadError:-> + Session.equals('myPostsCollection','error') + Template.searchMyPosts.events + 'click #search-box':()-> + PostsSearch.cleanHistory() + 'click .back':(event)-> + $('.home').addClass('animated ' + animateOutUpperEffect); + Meteor.setTimeout ()-> +# PUB.back() + Session.set("searchContent","") + Router.go('/user') + ,animatePageTrasitionTimeout + 'click .mainImage':(e)-> + content = $("#search-box").val() + Session.set("searchContent",content) + Session.set("postPageScrollTop", 0) + if isIOS + if (event.clientY + $('#footer').height()) >= $(window).height() + console.log 'should be triggered in scrolling' + return false + postId = this._id + $('.home').addClass('animated ' + animateOutUpperEffect); + Meteor.setTimeout ()-> + PUB.page '/posts/'+postId + history = [] + history.push { + view: 'searchMyPosts' + scrollTop: document.body.scrollTop + } + Session.set "history_view", history + ,animatePageTrasitionTimeout + Session.set 'FollowPostsId',this._id + return + 'click .listView':()-> + if(Session.get("showBigImage")) + Session.set("showBigImage",false) + else + Session.set("showBigImage",true) diff --git a/client/user/user.html b/client/user/user.html new file mode 100644 index 000000000..1ed32fde8 --- /dev/null +++ b/client/user/user.html @@ -0,0 +1,323 @@ + +
++ {{> footer}} + + + ++ +++ {{_ "mySelf"}} ++++++ ++ ++ {{#if myProfileIcon}} ++ {{myProfileName}} ++ {{else}} + {{_ "uploadFigure"}} + {{/if}} +
+
+ + {{#if group}} +- + +
+将帐号与对应群组成员绑定,实时查看、更正自己的出现信息+- + +
+ +查看收藏的消息+- + +
++ ++只有出现您关注的用户才发推送消息(陌生人仍然通知您)+++ {{/if}} + +++ ++ {{#if hasTwoMore}} + {{#if isFirstGroup}} + + {{else}} + + {{/if}} + {{#if isLastGroup}} + + {{else}} + + {{/if}} + {{/if}} +++
++ {{group.group_name}} +
+++ + {{#if hasIntime group.in_time}} ++++ + 更新打卡 + + {{else}} +上班时间:09:00+打卡时间:{{inTime group.in_time group.group_id}}+++ 上班打卡 + {{/if}} +上班时间:09:00+打卡时间: 未打卡++ + {{#if hasOutTime group.out_time}} ++++ + 更新打卡 + + {{else}} +下班时间:18:00+打卡时间:{{outTime group.out_time group.group_id}}+++ 下班打卡 + {{/if}} +下班时间:18:00+打卡时间: 未打卡++ {{#if group.whats_up}} + {{#each group.whats_up}} ++{{getShortTime ts group.group_id}} {{content}}
+ {{/each}} + {{else}} + 添加今日简述... + {{/if}} +++ + + ++ ++++ {{> footer}} + diff --git a/client/user/user.js b/client/user/user.js new file mode 100644 index 000000000..0f98b7525 --- /dev/null +++ b/client/user/user.js @@ -0,0 +1,24 @@ + +Template.searchMyPosts.helpers({ + items: function () { + var postsSearchData = PostsSearch.getData({ + transform: function(matchText, regExp) { + return matchText + }, + sort: {createdAt: -1} + }); + if (PostsSearch.getStatus().loaded == true) { + if (postsSearchData.length == 0) { + Meteor.setTimeout (function(){ + Session.set("searchLoading", false); + Session.set("noSearchResult", true); + },2000); + } else { + Session.set("showSearchStatus", false); + Session.set("searchLoading", false); + Session.set("noSearchResult", false); + } + } + return postsSearchData; + } +}); diff --git a/client/user/user.less b/client/user/user.less new file mode 100644 index 000000000..937d49712 --- /dev/null +++ b/client/user/user.less @@ -0,0 +1,63 @@ +.user{ + .set-up{ + .right-btn{ + float: right; + background: #00c4ff; + color: #fff; + border-radius: 4px; + display: inline-block; + height: 28px; + line-height: 28px; + margin-top: -15px; + width: 50px; + text-align: center; + font-size: 12px; + } + + .switch{ + position: relative; + width: 52px; + height: 22px; + border: 1px solid #dfdfdf; + outline: 0; + border-radius: 11px; + 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: 20px; + border-radius: 10px; + 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: 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); + } + .switch:checked:after{transform: translateX(20px);} + a,a:hover,a:focus{ + color: #1a1a1a + } + } +} diff --git a/client/wookmark/wookmark.coffee b/client/wookmark/wookmark.coffee index 181673a3d..50c22cc43 100644 --- a/client/wookmark/wookmark.coffee +++ b/client/wookmark/wookmark.coffee @@ -126,7 +126,7 @@ class @newLayout wookmark_debug&&console.log('image outside width is ' + img.offsetHeight) if img.offsetHeight is 0 - Meteor.setTimeout ()-> + setTimeout ()-> wookmark_debug&&console.log('Got error layout ' + img.offsetHeight); if img.offsetHeight is 0 $element.remove() @@ -155,18 +155,14 @@ Template.newLayoutContainer.events = if postId is undefined postId = this._id scrollTop = $(window).scrollTop() - currentPostId = Session.get("postContent")._id - postBack = Session.get("postBack") - postBack.push(currentPostId) - Session.set("postForward",[]) - Session.set("postBack",postBack) Session.set("lastPost",postId) - Session.set('postContentTwo', postId) $(window).children().off() $(window).unbind('scroll') if typeof PopUpBox isnt "undefined" PopUpBox.close() - PUB.openPost postId + # $('.popUpBox, .b-modal').hide() + Session.set("readMomentsPost",true); + Router.go '/posts/'+postId Template.newLayoutContainer.helpers = displayId:()-> if this.data and this.data.displayId @@ -227,7 +223,7 @@ Template.newLayoutElement.onDestroyed ()-> if instance wookmark_debug&&console.log('Need remove item'); $('.newLayout_element_'+ this.data.src + '_' + this.data.layoutId + '#' + this.data.displayId).removeClass('loaded'); - Meteor.setTimeout ()-> + setTimeout ()-> instance.initItems(); instance.layout(true); ,1000 diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index cfbcf4156..cc1f32682 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -33,6 +33,7 @@ "me": "Me", "selectPicFromGallery": "Library", "importFromPaster": "Import from Web", + "importFromShareExtension":"Import from Share", "takePicture": "Photo", //end //bell.html @@ -40,9 +41,6 @@ "newInfo": "Moment", "loadFailNotification": "Fail to load. Please check your network connection and retry again.", "commentYourStory": "commented your story.", - "alsoCommentThisStory": "comments your story", - "likeThisStory": "likes your story", - "dislikeThisStory": "dislikes your story", "replyStoryYouJoin": "also commented on the stroies you have commented.", "replyYourStory": "replied on your story", "publishNewStory": "post a new story", @@ -54,6 +52,7 @@ "loading": "Loading...", "publishTo": "recommend HotShare to these readers", "readerUnit": "readers", + "noMessages": "No any messages", //end //signupForm.html "createAccount": "Sign up", @@ -65,16 +64,20 @@ //loginForm.html "logIn": "Log in", "forgotPassword": "Forgot your password?", + "forgetPwd": "Forgot password?", + "noAccount": "No account yet?", + "registNow": "Sign up now", + "passwordAgain": "Confirm password", //end //registerFollow.html - // "chooseAtLeast": "Choose at least ", - // "toFollow": " story tellers to follow.", + "chooseAtLeast": "Choose at least ", + "toFollow": " story tellers to follow.", //end //registerFollow.html "followPeople": "Follow people", "knowActivity": "Read their latest stories", - "chooseAtLeast": "Follow at least ", - "toFollow": " story tellers to continue.", + "chooseAtLeast1": "Follow at least ", + "toFollow1": " story tellers to continue.", "conTinue": "Continue", //end //user.html @@ -88,6 +91,8 @@ "draft": " drafts", "checkAll": "Read all ", "numOfStory": " stories", + "follow":"follow", + "unFollow": "unFollow", //end //savedDrafts.html "draftTxt": "Draft", @@ -132,7 +137,7 @@ "gossip": "Chat", "gossipAnonymous": "Chat to anonymous", "send": "Send", - "saySomething": "Write a comment...", + "saySomething": "Say something", "exchange": "Change", //end //help.html @@ -172,6 +177,7 @@ //end //progressBar.html "publishLater": "publish later", + "saveLater": "save later", //end //reportPost.html "report": "complain", @@ -228,10 +234,16 @@ "notifiTipsIos": "如果你要关闭或开启故事贴的新消息通知,请在“设置”-“通知”功能中,找到应用程序“故事贴”更改。", "changePass": "Change PassWord", "newPass": "New PassWord", + "currentPass": "Current Password", "confirmPass": "Confirm", "language": "Language", "English": "English", "Chinese": "中文", + + "version":"New version testing ", + "latestversion":"Latest version ", + "Newversion":"A new version", + "about": "About", "aboutTitle": "About hotShare", "ver": "Ver.", @@ -251,22 +263,220 @@ "NewFriendsList": "New friends", "FailToLoad": "Failed to load, check the network settings or try again later", "FateAh": "Fate ah, we have encounter ", + "times1": " times.", "Moments": "Moments", "RecommendPosts": "Read the current posts ago friend posts, you have seen, specially recommended for you that you did not read the following posts ...", "AlsoRead": "friends who read the current post, also read ...", "RecommendReadMore": "Friends who read the current post also read ...", "PersonReadYourForward": " persons read your forwarding", "AlsoComment": "also commented on this story", - "recommend": "read the the current post, recommend you to read:", + "recommendA": "read the", + "recommendB": "recommend you to read", "theStoryGroupMsg": "Story Group Messages", "readed": "read", "postedastory": "posted a story", "readedastory": "readed a story", "recentlyviewedstories": "Recently viewed stories", + "readFollowTips": "阅读超过3次后提示读者关注", + // end + + // introductoryPage.html + "joinGroup":"Join Group", + "createGroup":"Create Group", + "skipToHome":"Skip to Home", + "scanQRCode":"Scan QRCode", + "scanFromAlbum":"Scan Album", + "quickDemo":"Quick Demo", + // end + + // homePage.html + "headMonitorGroup":"Groups", + "createGroup":"Create Group", + "joinGroup":"Scan QR code to join group", + "scanPicture":"Scan picture to join group", + "scanToAddDevice":"Scan QR code to add device", + "scanNetworkToAddDevice":"Scan network to add device", + // end + + // footer.html + "monitorGroup":"Groups", + "timeLine":"Timeline", + "message":"Message", + "profile":"Profile", + // end + + + "myDevices":"My Devices", + "offline":"Offline", + "online":"Online", + "unknownDevice":"Unknown Device", + "box":"Box", + "camera":"Camera", + "noDevice":"No Device", + "setServer":"Set", + "currentAddress":"Current Server Address:", + "switchServerURL":"Changing to new server address:", + + // user.html + "bindFaceID":"Bind to Group Face ID", + "detailBindFaceID":"Bind your account to Face ID of group to check your appearence", + "favoriteDetail":"Check your favorite messages", + "followedOnlyPushNotification":"Subscribe only followed users", + "detealFollowedOnlyPushNotification":"Subscribe only followed users' push notification(will notify you unknown person's notification)", + // end + + // messageGroup.html + "theGroupName":"Group Name", + "inputGroupName":"Please input group name", + "inputNewGroupName":"Please input new group name", // end + // groupsProfile.html + "groupManagement":"Manage Group", + // end + + // homePage.html + "checkIns":"Check-in of Today", + "checkInsPrefix":"", + "checkInsPerson":"", + "evaluateDeployment":"Evaluate Deployment", + "memberManagement":"Manage Members", + "checkInRecords":"Check-in History", + "label":"Name Unknown", + "tabelAppearance":"Check-in Report Table", + // end + + "groupInfo":"Group Informationn", + "groupQRCode":"Group QRCode", + "groupDeviceList":"Group Device List", + "recognitionCounts":"Recognition Status", + "checkInDuration":"Check-in Duration", + "notifyToFillSchedule":"Notify to Fill in Daily Schedule", + "unknownNotification":"Notify You When See Unknown Person", + "manageNotification":"Manage Notification Rules", + "groupReportEmail":"Email to Receive Status Report", + "cleanUpTrainingRecord":"Cleanup Training Dataset", + "exit":"Exit", + "showAllMembers":"Show All Members", + "choiceNotificationWay":"Choice The Way to Send Notification", + "byPushNotification":"By Mobile Push Notification", + "byEmail":"By Email", + "whenToSendNotification":"When to Send Notification", + "activityReport":"Activity", + "unfamiliarFaces":"Unfamiliar Face", + "knownFaces":"Send Notification When See Following Faces", + "deviceList":"Device Lists", + "updateConfiguration":"Auto Update Configuration", + "unknownDevice":"Unknown Device", + "cameraConfiguration":"Config Camera", + "hitSetupCamera":"Please wait for 15s after setup", + "deviceName":"Device Name", + "inputDeviceName":"Input Device Name", + "name":"Name", + "recogCounts":"Counts", + "recogCountsStatistics":"Recognition Statistics", + "recogRecord":"Recognition Records", + "addEmail":"Add Email", + "removeEmail":"Remove Email", + "back":"Back", + "setToAdmin":"Set as Admin", + "removeFromAdmin":"Remove from Admin", + "removeFromGroup":"Remove from Group", + "sendMessage":"Send Message", + "checkedIn":"OnSite", + "notYetCheckedIn":"NotYet", + "wrongRecognition":"Hints", + "noDataForNow":"No Records For Now", + "checkIn":"Checked In:", + "shape":"Shape", + "face":"Face", + "labelHints":"Tap on face to label", + "tapToCheck-InHints":" Tap on face to manually check-in", + "tapToLabelHints":" Tap on face to name", + "workItems":"Work Items", + "noItemYet":"No Item Yet", + "changeCheckInTime":"Change CheckIn", + "changeLatestTime":"Change Latest Activity", + "editWorkItem":"Edit Work Items", + "noInformationYet":"No Information Yet", + "switchToUnknownPerson":"Switch to Unfamiliar Faces", + "chat":"Chat", + "noDeviceToFrontPage":"No device in group, use + to add device on the front page please", + "toFrontPage":"To Frontpage", + "whatToSearch":"Input text to search", + "messageCounts":"new messages", + "ok":"Ok", + "aiSaw":"AI saw", + "aiSawSomeone":"AI saw some activities", + "times":"times", + "reselect":"Reset", + "multiSelection":"Multiple", + "recogStatus":"Recognition Status", + "unknownFacesStatus":"Unfamiliar", + "knownFacesStatus":"Familiar", + "blury":"Blury", + "faceType":"Face Type", + "front":"Front", + "showAll":"Show All", + "apiServer":"API Server", + "mqttBroker":"MQTT Broker", + "mqttBrokerSetup":"Setup MQTT Broker", + "apiServerSetup":"Setup API Server", + "serverIP":"Server IP", + "serverPort":"Server Port", + "enterIP":"Input IP", + "enterPort":"Input Port", + "serverURL":"Server URL", + "isThis":"Is this", + "ma":"", + "yes":"Yes", + "no":"No", + "choiceDevice":"Choice Device", + "labelHints":"Hints", + "selected":"Selected", + + // TAPi18n.__() //dynamic js file contents + "today":"Today", + "yesterday":"Yesterday", + "earlyMorning":"Morning", + "morning":"Morning", + "noon":"Noon", + "afternoon":"Afternoon", + "earlyEvening":"Afternoon", + "evening":"Evening", + "labelFailed":"Label Failed", + "savedToCheckInRecords":"Saved to check-in records", + "restartAPPAfterMqttSetup":"MQTT Broker setup successfully, please researt APP to take effect", + "justNow":"Just Now", + "minAgo":"Min Ago", + "minsAgo":"Mins Ago", + "hourAgo":"Hr Ago", + "hoursAgo":"Hrs Ago", + "dayAgo":"Day Ago", + "daysAgo":"Days Ago", + "weekAgo":"Week Ago", + "weeksAgo":"Weeks Ago", + "monthAgo":"Month Ago", + "monthsAgo":"Months Ago", + "yearAgo":"Year Ago", + "tryAgain":"Try again please~", + "Mon":"Mon", + "Tue":"Tue", + "Wed":"Wed", + "Thu":"Thu", + "Fri":"Fri", + "Sat":"Sat", + "Sun":"Sun", + "unknown":"Unknown Position", + "hint":"Hint", + "hintLoginTimeout":"Login timeout, please retry later.", + "confirmToDeleteDatasets":"Do you really want to delete dataset?", + "fromTextCheckin":"CheckIn Start", + "toTextCheckin":"CheckIn End", + "hintEndStart":"End is earlier than start. Please set again.", "hotShareQQZoneShare": "Share to QQZone from hotShare", + "shareStoryGroup": "Share to Story Group", "wechatNotInstalled": "wechat not installed, fail to share", "failToShare": "fail to share", "qqNotInstalled": "QQ not installed, fail to share", @@ -276,8 +486,29 @@ "Account": "Account", "addAccount": "Add Account", "add": "Add", + "registing":"Signing up", + "noConnection":"No network connection, please check your configuration.", + "userNamePlease":"Please input username", + "incorrectUsernameOrPassword":"Username or password is not the same as in record.", + "contactUs":"Contact us", + "exiting":"Exiting", + "hintTimeline":"Tap on TIMELINE to show online devices", + "hintMessage":"Tap on MESSAGE to show recognition results", + "hintAdding":"Tap to manage groups/devices", + "hintToCreateGroup":"Tap to create group", + "hintToAddDevice":"Tap to add device", //end "readMore": "Read More", - "readingRoom": "ReadingRoom" + "readingRoom": "ReadingRoom", + + "emailFollow": "Email", + "appFollow": "App", + + + // theme blue + "theme_blue_anonymous":"Anonymously", + "theme_blue_regWithEmail": "Register", + "theme_blue_loginWithAccount": "Login", + "theme_blue_socialLoginTips": "Or Login With" } diff --git a/i18n/new_en.i18n.json_bkup b/i18n/new_en.i18n.json_bkup index 5ff0e1abe..749d5b690 100644 --- a/i18n/new_en.i18n.json_bkup +++ b/i18n/new_en.i18n.json_bkup @@ -1,4 +1,5 @@ -{ +{ + //authOverlay.html "anonymous": "Continue anonymously", "anonymousAgree": "User accepts", "gstServiceSpec": "Terms and Privacy", @@ -24,13 +25,18 @@ "linkDownloadAndroid": "Android version download", "gstPrivacy": "© 2015 HotShare", "gstProductSerial": "滇 ICP 备 14007324号", + //end + //footer.html "homePage": "Home", "discovery": "Discover", "message": "Messages", "me": "Me", "selectPicFromGallery": "Library", "importFromPaster": "Import from Web", + "importFromShareExtension":"Import from Share", "takePicture": "Photo", + //end + //bell.html "receiving": "Receiving...", "newInfo": "Moment", "loadFailNotification": "Fail to load. Please check your network connection and retry again.", @@ -38,12 +44,16 @@ "replyStoryYouJoin": "also commented on the stroies you have commented.", "replyYourStory": "replied on your story", "publishNewStory": "post a new story", - "recommendAStory": "recommand you a new story", + "recommendAStory": "recommend you a new story", "askFriends": "wants to add you as a friend", "alreadyAdded": "Already Added", "acceptInvitation": "Accept", "alreadySendInvitation": "Already Sent", "loading": "Loading...", + "publishTo": "recommend HotShare to these readers", + "readerUnit": "readers", + "noMessages": "No any messages", + //end //signupForm.html "createAccount": "Sign up", "signUpMeansWhat": "Sign up means you accept our terms and privacy.", @@ -54,6 +64,7 @@ //loginForm.html "logIn": "Log in", "forgotPassword": "Forgot your password?", + "forgetPwd": "Forgot password?", //end //registerFollow.html "chooseAtLeast": "Choose at least ", @@ -62,8 +73,8 @@ //registerFollow.html "followPeople": "Follow people", "knowActivity": "Read their latest stories", - "chooseAtLeast": "Follow at least ", - "toFollow": " story tellers to continue.", + "chooseAtLeast1": "Follow at least ", + "toFollow1": " story tellers to continue.", "conTinue": "Continue", //end //user.html @@ -71,11 +82,14 @@ "mySelf": "Me", "uploadFigure": "Figure image", "followers": " followers", + "follower": "followers", "ppl": "", "followin": "Following ", "draft": " drafts", "checkAll": "Read all ", "numOfStory": " stories", + "follow":"follow", + "unFollow": "unFollow", //end //savedDrafts.html "draftTxt": "Draft", @@ -143,11 +157,150 @@ "plainTopic": "Topics", "discoverPeople": "Discover interesting people", "plainPeople": "People", - "recommandUser": "Recommand users", + "recommandUser": "Recommend users", + //end + + //listPosts.html + "isLoading": "Loading...", + //"loadFailNotification" :"加载失败,请检查网络设置或稍后重试", duplicate + //end + //myPosts.html + "story": "story", + "browse" :"browse", + "times": "times", + //end + //postNotFound.html + "storyNotFound": "This story does not exist or was deleted", //end - - // language + //progressBar.html + "publishLater": "publish later", + "saveLater": "save later", + //end + //reportPost.html + "report": "complain", + "reportAt": "complain@", + "storyRef1": "story《", + "storyRef2": "》", + "addReportContent": "add complaints", + //end + //showPosts.html + "clickMeToCreate": "click me to create", + "operate": "operate", + "share": "share", + "cancelPublish": "Cancel publishing", + "shareWechatFriend": "shareWechatFriend", + "shareWechatFriendField": "shareWechatFriendField", + "shareQQ": "shareQQ", + "shareQQZone": "shareQQZone", + "shareMore": "shareMore", + "fromNumOf": "――from paragraph", + "segment": " ", + //"readOriginalText": " 阅读原文", duplicate + "hotStores": "hot stories", + "downloadGSTAndReadMore": "(download HotShare and read more stories)", + "weTogetherThatYears": "we are together those days", + "fromBestAPP": "this story is edited by the excellent App", + "supportCreate": "", + "forMorePayAttentionTo": "for more, click following", + "wechatPublicAccount": "wechat public account", + "forMoreSearch": "For more, please search and follow", + "noPrivacyExposed": "HotShare has no access to your wechat information", + "comment": "comment", + "commentAnonymous": "comment anonymously", + "forwardSegment": "forward this paragraph", + "readLoudly": "read", + "writeComment": "write comment", + "comeon": "come and take a position~~( ̄▽ ̄)", + //end + //thanksReport.html + "thanksReport": "Thanks", + "reportSuccess": "complaint is submitted", + "reportDeclaration": "Thank you! HotShare resists pornography, violence and cheating, we will take your complaints seriously, and maintain a green network environment.", + //end + + // dashboard.html + "blacklist": "Blacklist", + "setting": "Settings", + "done": "Done", + "save": "Save", + "email": "Email", + "enterEmail": "Enter Email Address", + "notification": "Notification", + "notifiButton": "Get new Notification", + "notifiTips": "如果你要关闭或开启故事贴的新消息通知,请在系统设置中,找到应用程序“故事贴”更改", + "notifiTipsIos": "如果你要关闭或开启故事贴的新消息通知,请在“设置”-“通知”功能中,找到应用程序“故事贴”更改。", + "changePass": "Change PassWord", + "newPass": "New PassWord", + "currentPass": "Current Password", + "confirmPass": "Confirm", "language": "Language", - "lang": "English" + "English": "English", + "Chinese": "中文", + + "version":"New version testing ", + "latestversion":"Latest version ", + "Newversion":"A new version", + + "about": "About", + "aboutTitle": "About hotShare", + "ver": "Ver.", + "logOut": "Logout", + "NewFriends": "NewFriends", + "NickName": "NickName", + "Gender": "Gender", + "Save": "Save", + "Male": "Male", + "Female": "Female", + "enteranewnickname": "Enter a new nickname", + "FavoriteStory": "Favorite Story", + "Publish": "Publish", + "NoSearchResults": "No Search Results", + "Searching": "Searching...", + "Contacts": "Contacts", + "NewFriendsList": "New friends", + "FailToLoad": "Failed to load, check the network settings or try again later", + "FateAh": "Fate ah, we have encounter ", + "times1": " times.", + "Moments": "Moments", + "RecommendPosts": "Read the current posts ago friend posts, you have seen, specially recommended for you that you did not read the following posts ...", + "AlsoRead": "friends who read the current post, also read ...", + "RecommendReadMore": "Friends who read the current post also read ...", + "PersonReadYourForward": " persons read your forwarding", + "AlsoComment": "also commented on this story", + "recommendA": "read the", + "recommendB": "recommend you to read", + "theStoryGroupMsg": "Story Group Messages", + "readed": "read", + "postedastory": "posted a story", + "readedastory": "readed a story", + "recentlyviewedstories": "Recently viewed stories", + "readFollowTips": "阅读超过3次后提示读者关注", // end + + //dynamic js file contents + "hotShareQQZoneShare": "Share to QQZone from hotShare", + "shareStoryGroup": "Share to Story Group", + "wechatNotInstalled": "wechat not installed, fail to share", + "failToShare": "fail to share", + "qqNotInstalled": "QQ not installed, fail to share", + "preparePicAndWait": "preparing topic picture, please wait", + "failToGetPicAndTryAgain": "could not get topic picture, try later please!", + "loginByWechat": "Login by Wechat", + "Account": "Account", + "addAccount": "Add Account", + "add": "Add", + //end + + "readMore": "Read More", + "readingRoom": "ReadingRoom", + + "emailFollow": "Email", + "appFollow": "App", + + + // theme blue + "theme_blue_anonymous":"Anonymously", + "theme_blue_regWithEmail": "Register", + "theme_blue_loginWithAccount": "Login", + "theme_blue_socialLoginTips": "Or Login With" } diff --git a/i18n/zh.i18n.json b/i18n/zh.i18n.json index 75f3c4e34..44d8a54b8 100644 --- a/i18n/zh.i18n.json +++ b/i18n/zh.i18n.json @@ -2,28 +2,28 @@ //authOverlay.html "anonymous": "匿名使用", "anonymousAgree": "匿名使用表示已经同意", - "gstServiceSpec": "点圈服务告知", + "gstServiceSpec": "《来了吗用户协议》", "anonymousWarn": "友情提示:匿名账户的数据在程序卸载后可能会无法继续使用,如需永久保存数据,请注册或登录使用。", "kownIt": "知道了", "funnyLogin": "很卖萌的登录中~~~", "regWithEmail": "使用电子邮件注册", "loginWithAccount": "已经有帐户?登录", "kmxdkj": "昆明讯动科技有限公司", - "gstSecurity": "点圈 - 安全", + "gstSecurity": "来了吗 - 安全", "resetPassword": "重设密码", - "resetPasswordSuc": "你的点圈密码已经重置成功", - "loginWithNewPassword": "现在可以用新密码登录你的点圈帐号。", - "canSetNewPassword": "现在你可以重新设定您的点圈密码。", + "resetPasswordSuc": "你的来了吗密码已经重置成功", + "loginWithNewPassword": "现在可以用新密码登录你的来了吗帐号。", + "canSetNewPassword": "现在你可以重新设定您的来了吗密码。", "newPassword": "新密码:", "sixToSixteenCh": "6到16个字符。", "inputPasswordAgain": "重复输入新密码:", "OK": "完成", "confirm": "确定", - "gst": "点圈", + "gst": "故事贴", "gstAdWord": "一帖 一故事,分享 朋友圈", "linkDownloadIPhone": "Iphone用户下载", "linkDownloadAndroid": "Android用户下载", - "gstPrivacy": "© 2015 点圈", + "gstPrivacy": "© 2015 故事贴", "gstProductSerial": "滇 ICP 备 14007324号", //end //footer.html @@ -31,8 +31,10 @@ "discovery": "探索", "message": "消息", "me": "我", + "favorite": "收藏夹", "selectPicFromGallery": "从相册选取照片", "importFromPaster": "从粘贴板导入链接", + "importFromShareExtension":"从系统分享导入链接", "takePicture": "拍照", //end //bell.html @@ -41,8 +43,8 @@ "loadFailNotification": "加载失败,请检查网络设置或稍后重试", "commentYourStory": "点评了您的故事", "alsoCommentThisStory": "也点评了此故事", - "likeThisStory": "赞了", - "dislikeThisStory": "踩了", + "pCommentReplyThisStory":"回复了您在", + "pCommentReplyThisStoryEnd":"的评论", "alsoFavouriteThisStory": "也赞了此故事", "replyStoryYouJoin": "回复了您参与讨论的故事", "replyYourStory": "回复了您的故事", @@ -53,30 +55,36 @@ "acceptInvitation": "接受邀请", "alreadySendInvitation": "已发送邀请", "loading": "加载中...", - "publishTo": "把故事推荐给这些读者们", + "publishTo": "同读每一篇故事的朋友们都有一个朋友圈", "readerUnit": "位读者", + "noMessages": "暂无任何消息:(", //end //signupForm.html - "createAccount": "创建账户", + "createAccount": "注册", "signUpMeansWhat": "如果注册就意味着同意了用户协议", - "userName": "用户名", - "passWord": "密码", + "userName": "请输入用户名", + "passWord": "请输入密码", + "passwordAgain": "再次确认密码", "emailAddress": "邮箱", //end //loginForm.html "logIn": "登录", "forgotPassword": "忘了密码?", + "forgetPwd": "登录遇到问题?", + "noAccount": "没有账号?", + "registNow": "立即注册", //end //registerFollow.html "followPeople": "关注您喜欢的人", "knowActivity": "及时了解对方的动态", - "chooseAtLeast": "至少再关注", - "toFollow": "个讲故事者可以继续", + "chooseAtLeast": "再关注", + "chooseAtLeast1": "再关注", + "toFollow1": "位作者,订阅喜欢的文章", "conTinue": "继续", //end //user.html "searchMyPosts": "搜索我的帖子", - "mySelf": "个人信息", + "mySelf": "我的", "uploadFigure": "上传头像", "followers": " 名关注者", "follower": "关注者", @@ -85,6 +93,8 @@ "draft": " 份草稿", "checkAll": "查看全部 ", "numOfStory": " 个故事", + "follow":"关注", + "unFollow": "已关注", //end //savedDrafts.html "draftTxt": "草稿", @@ -133,8 +143,8 @@ "exchange": "换", //end //help.html - "useTips": "点圈使用攻略", - "howToRegister": "如何注册点圈?", + "useTips": "来了吗使用攻略", + "howToRegister": "如何注册来了吗?", "howToPublish": "如何发帖", "changePicture": "图片调整", "addTextAndEdit": "添加文字&排版", @@ -151,11 +161,11 @@ //search.html "searchTopic": "探索", "popTopics" :"流行话题", - "searchPeople": "搜索人和#话题#", + "searchPeople": "搜索作者和#话题#", "plainTopic": "话题", - "discoverPeople": "查找值得关注的人", - "plainPeople": "人", - "recommandUser": "建议用户", + "discoverPeople": "查找值得关注的作者", + "plainPeople": "作者", + "recommandUser": "热门作者", //end //listPosts.html "isLoading": "正在加载中...", @@ -171,6 +181,7 @@ //end //progressBar.html "publishLater": "稍后发表", + "saveLater": "稍后保存", //end //reportPost.html "report": "举报", @@ -186,6 +197,7 @@ "cancelPublish": "取消发表", "shareWechatFriend": "分享给微信好友", "shareWechatFriendField": "分享到微信朋友圈", + "shareStoryGroup": "分享到故事贴群", "shareQQ": "分享到QQ", "shareQQZone": "分享到QQ空间", "shareMore": "分享到更多应用", @@ -193,14 +205,14 @@ "segment": "段 ", //"readOriginalText": " 阅读原文", duplicate "hotStores": "热门文章", - "downloadGSTAndReadMore": "(下载“点圈”查看更多精彩内容)", + "downloadGSTAndReadMore": "(下载“来了吗”查看更多精彩内容)", "weTogetherThatYears": "哪些年,我们在一起", "fromBestAPP": "本文由史上最好用的图文排版APP", "supportCreate": "支持创作", "forMorePayAttentionTo": "更多精彩内容,敬请点击关注", "wechatPublicAccount": "微信公众号", "forMoreSearch": "更多精彩内容,请搜索关注", - "noPrivacyExposed": "点圈无法获得微信信息,请放心使用", + "noPrivacyExposed": "故事贴无法获得微信信息,请放心使用", "comment": "评论", "commentAnonymous": "匿名评论", "forwardSegment": "转发本段", @@ -211,7 +223,7 @@ //thanksReport.html "thanksReport": "感谢举报", "reportSuccess": "举报成功", - "reportDeclaration": "谢谢您!点圈坚决反对色情、暴力、欺诈信息,我们会认真处理您的举报,维护绿色、健康的网络环境。", + "reportDeclaration": "谢谢您!来了吗坚决反对色情、暴力、欺诈信息,我们会认真处理您的举报,维护绿色、健康的网络环境。", //end // dashboard.html @@ -223,16 +235,23 @@ "enterEmail": "输入电子邮件", "notification": "通知", "notifiButton": "接收新消息通知", - "notifiTips": "如果你要关闭或开启点圈的新消息通知,请在系统设置中,找到应用程序“点圈”更改", - "notifiTipsIos": "如果你要关闭或开启点圈的新消息通知,请在“设置”-“通知”功能中,找到应用程序“点圈”更改。", + "notifiTips": "如果你要关闭或开启来了吗的新消息通知,请在系统设置中,找到应用程序“来了吗”更改", + "notifiTipsIos": "如果你要关闭或开启来了吗的新消息通知,请在“设置”-“通知”功能中,找到应用程序“来了吗”更改。", "changePass": "修改密码", + "currentPass": "当前密码", "newPass": "新密码", "confirmPass": "确认", "language": "语言", "English": "English", "Chinese": "中文", - "about": "关于点圈", - "aboutTitle": "关于 点圈", + + "version":"新版本检测", + "latestversion":"当前已是最新版本", + "Newversion":"有新版本", + + "about": "关于来了吗", + "devMode": "开发者模式", + "aboutTitle": "关于 来了吗", "ver": "版本", "logOut": "退出登录", "NewFriends": "新朋友", @@ -250,23 +269,222 @@ "NewFriendsList": "新的朋友", "FailToLoad": "加载失败,请检查网络设置或稍后重试", "FateAh": "缘分啊,我们已偶遇", - "times": "次了!", + "times1": "次了!", "Moments": "朋友圈", "RecommendPosts": "看过当前帖子的朋友看过的帖子,您都看过了,特意为您推荐以下您没看过帖子...", "AlsoRead": "看过当前帖子的朋友,还看过...", "RecommendReadMore": "看过该帖的朋友还看过...", "PersonReadYourForward": "人读过您的转发", "AlsoComment": "也点评了此故事", - "recommend": "看过该帖后,推荐您阅读:", + "CommentPeply":"回复了您的评论", + "recommendA": "看过", + "recommendB": "后,推荐您阅读", "theStoryGroupMsg": "朋友圈消息", "readed": "读过", "postedastory": "发布了一个故事", "readedastory": "阅读了一个故事", "recentlyviewedstories": "最近浏览的故事", + "readFollowTips": "阅读超过3次后提示读者关注", + // end + + // introductoryPage.html + "joinGroup":"加入监控组", + "createGroup":"创建监控组", + "skipToHome":"下次再说,进入主页", + "scanQRCode":"扫描二维码", + "scanFromAlbum":"相册载入二维码", + "quickDemo":"快速体验", + // end + + // homePage.html + "headMonitorGroup":"监控组", + "createGroup":"创建监控组", + "joinGroup":"扫码加入监控组", + "scanPicture":"扫图片", + "scanToAddDevice":"扫码添加脸脸盒", + "scanNetworkToAddDevice":"网络扫描脸脸盒", + // end + + // footer.html + "monitorGroup":"监控组", + "timeLine":"时间轴", + "message":"消息", + "profile":"我的", + // end + + "myDevices":"我的设备", + "offline":"离线", + "online":"在线", + "unknownDevice":"未知设备", + "box":"盒子", + "camera":"摄像头", + "noDevice":"未找到设备", + "setServer":"设置", + "currentAddress":"当前服务器地址", + "switchServerURL":"正在切换到新服务器地址:", + + // user.html + "bindFaceID":"群组Face ID绑定", + "detailBindFaceID":"将帐号与对应群组成员绑定,实时查看、更正自己的出现信息", + "favoriteDetail":"查看收藏的消息", + "followedOnlyPushNotification":"只通知关注用户", + "detealFollowedOnlyPushNotification":"只有出现您关注的用户才发推送消息(陌生人仍然通知您)", // end + // messageGroup.html + "theGroupName":"监控组名称", + "inputGroupName":"输入监控组名称", + "inputNewGroupName":"输入新的监控组名称", + // end + + // groupsProfile.html + "groupManagement":"监控组管理", + // end + + + // homePage.html + "checkIns":"今日出现信息", + + "checkInsPrefix":"已出现", + "checkInsPerson":"人", + "evaluateDeployment":"部署评测", + "checkInRecords":"出现记录", + "memberManagement":"成员管理", + "label":"标注", + "tabelAppearance":"出现时间报表", + // end + + "groupInfo":"监控组信息", + "groupQRCode":"监控组二维码", + "groupDeviceList":"监控组设备列表", + "recognitionCounts":"识别统计", + "checkInDuration":"监控时间设置", + "notifyToFillSchedule":"提醒填写工作安排", + "unknownNotification":"接收未识别成员信息", + "manageNotification":"通知管理", + "groupReportEmail":"报告接收邮箱", + "cleanUpTrainingRecord":"清空训练记录", + "exit":"退出", + "showAllMembers":"查看全部成员", + "choiceNotificationWay":"请选择通知方式", + "byPushNotification":"APP推送通知", + "byEmail":"邮件通知", + "whenToSendNotification":"以下事件发生时,通知您", + "activityReport":"动静报告", + "unfamiliarFaces":"不熟悉的人", + "knownFaces":"以下熟人出现时,通知您", + "deviceList":"设备列表", + "updateConfiguration":"自动升级控制", + "unknownDevice":"未知设备", + "cameraConfiguration":"摄像头配置", + "hitSetupCamera":"设置完成后,大约15秒后生效", + "deviceName":"设备名称", + "inputDeviceName":"请输入设备名", + "name":"姓名", + "recogCounts":"识别次数", + "recogCountsStatistics":"识别次数统计", + "recogRecord":"识别记录", + "addEmail":"添加邮箱", + "removeEmail":"删除邮箱", + "back":"返回", + "setToAdmin":"设置为管理员", + "removeFromAdmin":"取消管理权限", + "removeFromGroup":"移除该成员", + "sendMessage":"发消息", + "checkedIn":"已出现", + "notYetCheckedIn":"未出现", + "wrongRecognition":"识别错了?", + "noDataForNow":"暂无出现记录", + "checkIn":"上班时间", + "latestActivity":"最近活动", + "shape":"人形", + "face":"人脸", + "labelHints":"点击头像进行操作", + "tapToCheck-InHints":" 点击头像,调整您的出现时间", + "tapToLabelHints":" 点击头像,进行标注", + "workItems":"工作安排", + "noItemYet":"暂无工作安排", + "changeCheckInTime":"修改上班时间", + "changeLatestTime":"修改最近活动", + "editWorkItem":"编辑今日简述", + "noInformationYet":"暂无数据", + "switchToUnknownPerson":"查看未标注的人", + "chat":"聊天", + "noDeviceToFrontPage":"组里暂无设备,请到首页点击+号添加脸脸盒", + "toFrontPage":"去首页", + "whatToSearch":"请输入搜索内容", + "messageCounts":"条新消息", + "ok":"Ok", + "aiSaw":"AI观察到", + "aiSawSomeone":"AI观察到有人在活动", + "times":"次", + "reselect":"重选", + "multiSelection":"多选", + "recogStatus":"识别状态", + "unknownFacesStatus":"未识别", + "knownFacesStatus":"已识别", + "blury":"模糊度", + "faceType":"人脸类型", + "front":"正脸", + "showAll":"显示全部", + "apiServer":"API服务器", + "mqttBroker":"MQTT Broker", + "mqttBrokerSetup":"设置MQTT Broker", + "apiServerSetup":"设置API服务器地址", + "serverIP":"服务器地址", + "serverPort":"服务器端口", + "enterIP":"输入地址", + "enterPort":"输入端口", + "serverURL":"服务器URL", + "isThis":"这是", + "ma":"吗", + "yes":"对", + "no":"错", + "choiceDevice":"选择设备", + "labelHints":"标注助手", + "selected":"已选", + + // TAPi18n.__() //dynamic js file contents - "hotShareQQZoneShare": "点圈分享QQ空间", + "today":"今天", + "yesterday":"昨天", + "earlyMorning":"凌晨", + "morning":"上午", + "noon":"中午", + "afternoon":"下午", + "earlyEvening":"傍晚", + "evening":"晚上", + "labelFailed":"标注失败", + "restartAPPAfterMqttSetup":"MQTT Broker信息设置成功,您需要重新启动APP使设置生效", + "savedToCheckInRecords":"已记录到每日出勤报告", + "justNow":"刚刚", + "minAgo":"分钟前", + "minsAgo":"分钟前", + "hourAgo":"小时前", + "hoursAgo":"小时前", + "dayAgo":"天前", + "daysAgo":"天前", + "weekAgo":"周前", + "weeksAgo":"周前", + "monthAgo":"月前", + "monthsAgo":"月前", + "yearAgo":"年前", + "tryAgain":"'请重试~", + "Mon":"周一", + "Tue":"周二", + "Wed":"周三", + "Thu":"周四", + "Fri":"周五", + "Sat":"周六", + "Sun":"周日", + "unknown":"未知", + "hint":"提示", + "hintLoginTimeout":"登录超时,需要重新登录~", + "confirmToDeleteDatasets":"确定要清空训练记录吗?", + "fromTextCheckin":"上班时间", + "toTextCheckin":"下班时间", + "hintEndStart":"下班时间早于上班时间,请重试", + "hotShareQQZoneShare": "来了吗分享QQ空间", "wechatNotInstalled": "未安装微信客户端,分享失败", "failToShare": "分享失败", "qqNotInstalled": "未安装QQ客户端,分享失败", @@ -276,8 +494,29 @@ "Account": "帐号管理", "addAccount": "添加帐号", "add": "添加", + "registing":"正在提交信息...", + "noConnection":"当前为离线状态,请检查网络连接", + "userNamePlease":"请输入用户名!", + "passwordPlease":"请输入密码!", + "incorrectUsernameOrPassword":"帐号或密码有误!", + "contactUs":"联系客服", + "exiting":"正在退出登录...", + "hintTimeline":"点击时间轴查看设备在线状态", + "hintMessage":"点击消息查看识别动态", + "hintAdding":"点击加号", + "hintToCreateGroup":"点击此处创建监控组", + "hintToAddDevice":"点击此处扫码添加脸脸盒", //end "readMore": "继续阅读", - "readingRoom": "阅览室" + "readingRoom": "阅览室", + + "emailFollow": "邮件", + "appFollow": "应用", + + // theme blue + "theme_blue_anonymous":"匿名使用", + "theme_blue_regWithEmail": "注册", + "theme_blue_loginWithAccount": "登录", + "theme_blue_socialLoginTips": "或用其它方式登录" } diff --git a/imports/ui/stylesheets/addPost.less b/imports/ui/stylesheets/addPost.less new file mode 100644 index 000000000..0585e6b6b --- /dev/null +++ b/imports/ui/stylesheets/addPost.less @@ -0,0 +1,150 @@ +.addPost{background-color:white;overflow-x: hidden} +.addPost .head{background-color: #F0F0F0;color: gray !important;opacity: 0.9;} +.addPost .head i{color: gray !important;} +.addPost .content{position:relative;top: 40px;} +.addPost .content .gridster {padding-left:5%; padding-right:5%;} +.addPost .content #display{} +.addPost .content #display .resortitem .gs-resize-handle-both{background-image: none} +.addPost .content #display .resortitem .imgContainer{width: 100%;height: 100%;position: absolute;overflow: hidden;} +.addPost .content #display .pressed .isImage{border: 2px solid #00c4ff;position: relative} +.addPost .content #display .pressed .isImage i{font-size: 18px;color: #00c4ff;opacity: 10 !important;} +.addPost .content .mainImage{position: relative;overflow: hidden;margin-bottom: 5px;} +.addPost .content .title{margin: 0;text-shadow: 0 1px 0 black;color: white;font-weight:bold;white-space: pre-line;word-wrap: break-word;font-size:30px ;background:none;border:1px dotted #fff;overflow-y: hidden;width: 100%; height:50px} +.addPost .content .addontitle{margin: 0;text-shadow: 0 1px 0 black;color: white;white-space: pre-line;word-wrap: break-word;font-size: 18px;background:none;border:1px dotted #fff;overflow-y: hidden;width: 100%; height:30px} +.addPost .contentList{position:relative;} +.addPost .content .img{clear: both; display: block; margin:auto;} +.addPost .content #addText{position: absolute; left: 50%;margin-left: -30px;font-size: 80px;bottom: 1px;color: #00c4ff} +.addPost .content #addmore{position: absolute; left: 25%;margin-left: -30px;font-size: 80px;bottom: 1px;color: #00c4ff} +.addPost .content #addLink{position: absolute; left: 75%;margin-left: -30px;font-size: 80px;bottom: 1px;color: #00c4ff} +.addPost .content .remove{position: absolute;right: 15px;font-size: 20px;bottom: 2px;color: yellow} +.addPost .content .gridster .textdiv{height:100%; width:100%; } +.addPost .content .gridster .textdiv textarea{-webkit-appearance: none; vertical-align: middle; display: inline-block; font-size:large; margin: 0;color: black;word-wrap: break-word;font-size:medium ;background :none; line-height: 30px; overflow-y: hidden; resize:vertical;height:100%;width:100%;box-sizing:border-box; border: 1px solid white;} +.addPost #show_hyperlink{ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: #fff; + z-index: 9999; + display: none; + div.hyperlink_content{ + padding-top: 100px; + text-align: center; + div.hyperlink_input{ + background-color: #fff; + padding-bottom: 10px; + color: #7d7d7d; + border: none; + span{font-weight: bold;} + label{ + border-bottom: 1px solid #eee; + font-weight: normal; + input{ + border: none; + color: #7d7d7d; + } + } + } + div#add_hyperlink_btn{ + background-color: #37a7fe; + width: 30%; + margin: auto; + padding: 8px; + margin-top: 25px; + border-radius: 5px; + font-weight: bold; + } + } + +} +.addPost #postFooter { + width:100%; + height:40px; + font-size:16px; + line-height:40px; + position:fixed; + left:0; + bottom:0; + border:none; + text-align:center; + background: #F0F0F0; + z-index: 999; + opacity: 0.9; + div{ + width: 25%; + height: 40px; + line-height: 40px; + float: left; + text-align: center; + i{ + display: block; + width: 100%; + height: 40px; + line-height: 40px; + font-size: 26px; + color: #aeaeae; + } + } + div.clear{clear: both;} + div#addHyperlink{ + position: relative; + top: -75px; + font-size: .6em; + float: right; + border-radius: 20px; + background-color: #37a7fe; + height: 30px; + margin-right: 5px; + line-height: 30px; + + i{ + font-size: 1.2em; + height: 30px; + line-height: 30px; + color: #fff; + } + } + +} +.addPost #postFooter{width:100%; height:40px;font-size:16px; line-height:40px; position:fixed; left:0; bottom:0; border:none; text-align:center; background: #F0F0F0; z-index: 999;opacity: 0.9;} +.addPost #postFooter div{width: 20%; height: 40px; line-height: 40px; float: left; text-align: center;} +.addPost #postFooter div i{display: block; width: 100%; height: 40px; line-height: 40px; font-size: 26px;color: #aeaeae;} +.addPost #postFooter div.clear{clear: both;} + +.addPost .content #display .pressed .textdiv textarea{border: 1px dotted #00c4ff;position: relative} +.addPost .content #display .edit .textdiv textarea{border: 1px dotted #00c4ff;position: relative} + +.addPost .linkInputBox { display:none; min-width: 100%;position: absolute;background-color: #fff;} + +.addPost .imgMask{position: absolute; top: 0; left: 0; height: 100%; width: 100%; z-index: 1;} +.addPost .content #display .resortitem .imgMask {border: 2px solid #00c4ff;} + +.addPost .head .leftButton { padding-top: 0;} +.addPost .head .pullright { + position: absolute; + right: 10px; + top: 0; + + .secondRightButton { + position: relative; + display: inline-block; + // margin-right: 3px; + right: 0; + float: left; + } + + .rightButton { + // position: static; + position: relative !important; + display: inline-block; + padding: 0; + text-align: center; + } +} + +.addPost #text-toolbar-options #delEnd img, .tool-container .tool-items #delEnd img { + position: relative; + top: 4.5px; + height: 18px; +} \ No newline at end of file diff --git a/imports/ui/stylesheets/addTopicComment.less b/imports/ui/stylesheets/addTopicComment.less new file mode 100644 index 000000000..e5f5214fa --- /dev/null +++ b/imports/ui/stylesheets/addTopicComment.less @@ -0,0 +1,88 @@ +.addTopicComment{ + background-color:white;overflow-x: hidden; + .waterfall{ + -moz-column-count: 2; + -webkit-column-count: 2; + column-count: 2; + -moz-column-width: 8em; + -webkit-column-width: 8em; + column-width: 8em; + -webkit-column-gap: 8px; + column-gap: 8px; + font-size: 14px; + .pin{ + padding: 5px; + -webkit-column-break-inside: avoid; + break-inside: avoid; + margin-top: 8px; + box-shadow: -1px 3px 13px #ccc; + .img_placeholder { + width: 100%; padding-bottom: 10px; margin-bottom: 2px; position: relative; + .selectHelper{width: 26px; height: 26px; position: absolute; z-index: 99; right: 0;margin-top: 8px; margin-right: 8px;} + // .img-container{} + } + img{width: 100%;} + } + } + .content{ + overflow-y: auto; + background-color:#fbfbfb; + color: #333; + border-radius: 0px; + position: absolute; + bottom: 0px; + top: 0px; + width: 100%; + padding-top: 30px; + padding-bottom:30px; + #title{ + font-size: 18px; + font-weight:bold; + color:#000000; + text-align: center; + } + #comment{ + -webkit-user-select: auto !important; + position: absolute; + left:5%; + right: 5%; + color:#000000; + background-color:#ffffff; + width:90%; + height:180px; + margin-top: 30px; + margin-bottom:30px; + border-color: #f0f0f0; + border-radius:6px; + } + #save{ + position: absolute; + margin-top:230px; + left: 5%; + right: 5%; + width: 90%; + text-align: center; + border: none; + border-radius: 50px; + padding: 10px; + background-color: #00c4ff; + color:white; + font-size: 16px; + } + #topics{ + position: absolute; + margin-top:280px; + left: 2%; + right: 2%; + width: 95%; + border: none; + color:black; + font-size: 16px; + } + #topic{ + border: none; + color:#2F96B4; + font-size: 18px; + } + } +} diff --git a/imports/ui/stylesheets/bell.less b/imports/ui/stylesheets/bell.less new file mode 100644 index 000000000..b9dfe598b --- /dev/null +++ b/imports/ui/stylesheets/bell.less @@ -0,0 +1,110 @@ +.bell .content{position:relative;padding-top: 40px;padding-bottom: 55px;-webkit-touch-callout: none;} +.bell .content .contentList{ + display: block;position:relative;min-height: 50px; + .readTips{position: absolute; left: 30px; top: 10px; border-radius:50%; height: 10px; width: 10px; background: red; z-index: 1;} +} +.bell .content .icon{position: absolute;top: 10px;left: 10px;border: none;border-radius:50%;} +.bell .content .button{position: absolute;top: 10px;right: 10px;border: 2px;} +.bell .content .noIcon{position: absolute;top: 10px;left: 10px;border: none;border-radius:50%;box-shadow: 0 0 12px #fff, 0 0 2px #fff} +.bell .content .alarm{padding-left: 50px;padding-right: 10px; color: #bbb; overflow-x: hidden; text-overflow: ellipsis;} +.bell .content .createAt{padding-left: 50px; color: gray;} +.bell .line{text-align:center;width: 100%; height: 1px; padding: 5px 0px;} +.bell .line span{background: #333; width: 100%; height: 1px; display: block;} +.bell .content a,a:hover,a:focus{text-decoration: none;color: #bbb;} + + +//朋友圈 +.bellTop{width:100%;height:500px; overflow: hidden;position:relative;} +.bellBg{width:100%; height:460px;background: url("./img/bellBg.jpg") no-repeat center top; background-size: cover} +.bellRight{width:300px; position:absolute;bottom:0;right:0;} +.bellpic{width:120px;height:120px;border: 1px solid #fff;overflow:hidden;float:right;margin-right:15px;} +.bellname{float:left;font-weight:bold;color:#fff;font-size:18px;margin: 26px 0 0 0;text-shadow: -1px 2px 3px #ffb69a} +.bellrelease{width:100%;padding: 10px 0;border-bottom:1px solid #f0ad4e;overflow:hidden;} +.bellrelease-left{width:48px;height:48px;float:left;} +.bellrelease-right{width:90%;float:left;padding-left:10px;} +.bellrelease-right p{width:100%;line-height: 22px;overflow:hidden;} +.bellrelease-right p a{float:left;display:block} +.bellrelease-right p span{float:right;margin-right:15px;} +.bellrelease-right .bellImg{width:260px;height:350px;overflow: hidden;background-color:#fff;margin-bottom: 10px;} +.dz{width:90%;background-color: #000;padding:10px 0; text-indent: 5px} +.bod{border-bottom:1px solid #f0ad4e} + +.bellAlertBackground{ + width:100%; + height:100%; + position:fixed; + top:0px; + left:0px; + z-index: 99998; + display:none; + background: rgba(130,130,130,0); +} +.personalLetterContent{ + position: fixed; + z-index: 99999; + top: 0; + left: 0; + right: 0; + bottom: 0; + text-align: center; + color: #000; + background: #fff; + border-radius: 0; + display: none; + .LetterHead{ + position: absolute; + top: 0; + left: 0; + right: 0; + height: 48px; + line-height: 48px; + font-size: 16px; + background: #282828; + box-shadow:0 1px 13px #ccc; + color: #fff; + } + .closePersonalLetter{ + position: absolute; + top: 0; + left: 0; + height: 48px; + text-align: left; + font-size: 32px; + width: 64px; + padding: 0; + font-size: 32px; + color: #fff; + + } + .LetterContent{ + position: absolute; + top: 48px; + left: 0; + right: 0; + bottom: 96px; + line-height: 22px; + font-size: 14px; + overflow: hidden; + overflow-y: auto; + padding: 15px; + white-space: pre-wrap; + } + .LetterFooter{ + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 80px; + line-height: 40px; + font-size: 16px; + background: #fff; + div{ + border-top: solid 1px #ccc; + text-align: center; + line-height: 80px; + color: deepskyblue; + font-size: 16px; + a{color: deepskyblue;} + } + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/chatContent.less b/imports/ui/stylesheets/chatContent.less new file mode 100644 index 000000000..9b60ec138 --- /dev/null +++ b/imports/ui/stylesheets/chatContent.less @@ -0,0 +1,5 @@ +.chatContent{ + .eachChat{ + .waitRead{width: 18px; height: 18px; line-height: 18px; text-align: center; font-size: 10px; position: absolute; right: 10px; bottom: 13px;border-radius: 50%;background-color: #F43531;color:#fff;} + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/chatGroups.less b/imports/ui/stylesheets/chatGroups.less new file mode 100644 index 000000000..6642225f1 --- /dev/null +++ b/imports/ui/stylesheets/chatGroups.less @@ -0,0 +1,14 @@ +.chatGroups #wrapper{position:relative;-webkit-touch-callout: none;} +.chatGroups #wrapper .eachViewer{ + background-color: white; +} +.chatGroups #wrapper .icon{width: 50px;height: 50px;margin: 10px;border-radius: 6px;} +.chatGroups #wrapper .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;} +.chatGroups #wrapper .groupsName{color: black;} +.chatGroups .line{ + text-align:center; + width: 100%; + height: 1px; + //padding: 5px 0px; + } +.chatGroups .line span{background: #333; width: 100%; height: 1px; display: block;} \ No newline at end of file diff --git a/imports/ui/stylesheets/color.less b/imports/ui/stylesheets/color.less new file mode 100644 index 000000000..9662b4b5b --- /dev/null +++ b/imports/ui/stylesheets/color.less @@ -0,0 +1,300 @@ +@colorBlue: #37a7fe; +@colorBlueDeep1: #2196f3; +@colorWhite: #ffffff; +@colorGray: #7d7d7d; +@colorGrayLight: #eeeeee; +@colorBlack: #1a1a1a; +@username: #333333; +@userdesc: #666666; + +// backgroud blue +div.head, .dashboard div.head{background-color: @colorBlue;} +.bell { + .personalLetterContent .LetterHead {background-color: @colorBlue;} + .personalLetterContent .LetterFooter {color: @colorBlue;} +} +// background white + +body,#footer, #footer .btn{background-color: @colorWhite !important;} + +// footer +#footer .btn:focus, #footer .focus{color: @colorBlueDeep1 !important;} +#footer{box-shadow: 0 1px 13px rgba(0, 0, 0, 0.36), 0 1px 13px rgba(0,0,0,0.12) !important;} + +// color black +.content,.user{color: @colorBlack !important;} + +.home .content,.timeline .content,.homePage .content,.explore .content,.user .content { + position: absolute; + top: 40px; + bottom: 55px; + left: 0px; + right: 0px; + overflow-y: auto; + padding: 0 !important; + -webkit-overflow-scrolling: touch; + #connection-lost-banner{ + top: 40px; + } +} + +.content_box { + -webkit-overflow-scrolling: auto !important; +} + + +// user +.user .line{ + padding:5px 0 !important; +} +.user .draftLeft,.user .draftRight,.user .postLeft, .user .postRight{padding-top: 5px;} +.user .draftImages{padding-bottom: 0 !important;} +.user .draftImages ul li{margin-bottom: 24px !important;} + +// search +.searchForm{ + background-color: @colorGrayLight; + color: @colorGray !important; + border: none !important; + input,input::-webkit-input-placeholder{color: @colorGray !important;} +} +// search people and topic +.searchPeopleAndTopic div.content { + .searchForm{ + min-height: 30px;background-color: @colorWhite; padding-top: 0; + label{height: 30px;} + input{height: 30px;} + i {padding: 9px 12px;} + } + padding-top:0 !important; + table{ + height: 48px; + background-color: @colorBlue !important; + color: @colorWhite !important; + } + ul{ + background-color: @colorWhite !important; + margin-top: 0 !important; + border-color: @colorGrayLight !important; + li.on{color: @colorBlue !important;background-color: @colorWhite !important;} + li{background-color: @colorBlue !important; color: @colorWhite !important;} + } + .userName{color: @username !important;} + .follow{color: @colorBlue !important;} +} + +// search my posts +.searchMyPosts .content table{margin-top: 0 !important; top: 0 !important; height: 48px; background-color: @colorBlue !important; color: @colorWhite !important;} +.searchMyPosts .content div.searchForm {min-height: 30px;background-color: @colorWhite;} +.searchMyPosts .content div.searchForm label{height: 30px;padding-left: 30px;} +.searchMyPosts .content div.searchForm input{height: 30px;} +.searchMyPosts .content div.searchForm i {padding: 9px 12px;} + +.top .searchForm i.fa{padding: 14px;} +.top .icon img{border: 4px solid #e6e6e6 !important;} +// ul li +ul li{ + border-style: @colorGray !important; +} + +// line +.line span{background-color: @colorGrayLight !important;} + +// dashboard +.dashboard .head{ + .leftButton, .leftButton i ,.rightButton, div{color: @colorWhite !important;} + div{text-shadow: none !important;} +} +.dashboard .boxCenter,.dashboard .fa-toggle-on,.dashboard .update .right{color: @colorBlue !important;} + +// discover +.content #themeList{ + color: @colorWhite !important; +} +.content #topicList .topic{ + color: @colorBlueDeep1 !important; + border-color: @colorBlueDeep1 !important; +} + +// help +.help h3,.help h3 a{ + color: @colorBlack !important; +} + +// bell +.bell .content{ + .alarm{ color: @colorGray !important;} +} + +// home , post lists +.home .content{ + background-color: @colorGrayLight !important; + div#list{ + width: 100%; margin: 12px 0; + .mainImage{margin-bottom: 0;} + .footer{margin-bottom:12px; padding: 12px; background-color: @colorWhite;} + .mainImageListItem{background-color: @colorWhite; padding: 0 12px;} + .mainImage dl{position: absolute; right: 12px; left: 12px;background-color: @colorWhite !important;} + .mainImage dd{color: @colorGray !important;} + } +} + +// followers +.followers .content { + .follow{color: @colorBlue !important;} + .userName{color: @username !important;} + .desc{color: @userdesc !important;} +} +// login page +.authOverlay #anonymous{ + bottom: 0 !important; + left: 0 !important; + right: 0 !important; + width: 100% !important; + background-color: @colorBlue !important; + border-radius: 0 !important; + height: 50px; + line-height: 50px; + padding: 0 !important; +} +.authOverlay #register{ + position: relative !important; + border: 1px solid @colorBlue !important; + width: 100% !important; + border-radius: 16px !important; + color: @colorBlue !important; +} +.authOverlay #login{ + position: relative !important; + padding: 10px; + background-color: @colorBlue; + color: @colorWhite !important; + border-radius: 16px; + width: 100% !important; + margin-bottom: 12px; +} +.authOverlay .loginActionArea{ + position: fixed; + left: 10%; + right: 10%; + bottom: 50px; + height: 50%; + color: @colorBlack; + text-align: center; + .loginWithSocial{ + position: absolute; + bottom: 0; + left: 0; + right: 0; + color: #999999; + } + #wechat{ + position: static !important; + bottom: 0 !important; + padding: 18px 0 !important; + width: 100% !important; + } + #wechat .fa-stack{color: #4caf50;} +} + + +.loginForm,.signupForm,.recoveryForm{ + // background-image: url(/theme_blue/loginbg2.jpg) !important; + // background-size: cover !important; + color: @colorBlack !important; +} +.loginForm .company,.signupForm .company,.recoveryForm .company{ + position: fixed; + bottom: 25px; + text-align: center; + width: 100%; + color: #2f2f2f; + text-shadow: -1px 1px #ffffff; +} +.signupForm .signupBody .input-group,.loginForm .loginBody .input-group,.recoveryForm .recoveryBody .input-group{ + display: block; + input{ + background-color: #e6eff6; + border-radius: 12px ; + padding: 5px 16px ; + height: 40px; + } + input,input::-webkit-input-placeholder{color: #555555 !important;} +} +.signupForm .signupBody .form-control,.loginForm .loginBody .form-control,.recoveryForm .recoveryBody .form-control{background-image: none !important;} +.signupHeader,.signupHeader .leftButton i{ + color: @colorWhite !important; +} +.signupForm .signupBody,.loginForm .loginBody,.recoveryForm .recoveryBody{top: 80px !important;} +.signupForm #sub-registered,.loginForm #sub-login,.recoveryForm #sub-recovery{ + border-radius: 16px; + background-color: @colorBlue !important; +} +// .signupForm .signupBody span{color: @colorBlack !important;} +#authOverlaybg .agreeDeal{z-index: 9; box-shadow: 1px 1px 5px @colorGray, -1px 0 5px @colorGrayLight;} + +// register Follow +.registerFollow{color: @colorBlack; padding: 0 16px;} +.registerFollow hr{border-color: @colorGrayLight !important;} +.registerFollow .user_head i{color: @colorBlue;top: 14px !important; right: 0 !important;} +.registerFollow .user_head .user_pic{box-shadow: 1px 1px 3px 1px @colorGray; width: 64px; height: 64px; margin: 0 8px 8px 0;} +.registerFollow .navbar-default.navbar{background-color: @colorBlue;} +.registerFollow .container #continue{background: none;} + +// search Follow +.searchFollow .content{ + .userName{color: @username !important;} + .desc{color: @userdesc !important;} + .follow{color: @colorBlue !important;} +} + +// splashScreen +// .swiper-container div.swiper-pagination{bottom: 120px !important;} +span.swiper-pagination-bullet { + background: gray !important; +} +span.swiper-pagination-bullet:nth-child(1).swiper-pagination-bullet-active{ + background: #00bcd4 !important; +} +span.swiper-pagination-bullet:nth-child(2).swiper-pagination-bullet-active{ + background: #1a4e82 !important; +} +span.swiper-pagination-bullet:nth-child(3).swiper-pagination-bullet-active{ + background: #ff5722 !important; +} +span.swiper-pagination-bullet:nth-child(4).swiper-pagination-bullet-active{ + background: #c2deeb !important; +} + +//processBar +.progressBarBg { + background-image: url(/processbarbg.jpg) !important; + background-repeat: no-repeat; + background-size: cover; + .progressBar { + left: 10% !important; + width:80% !important; + .progressBarIcon{ + height: 20%; + width: 20%; + background-image: url(/processbaricon.png); + position: absolute; + top: 0px; + background-repeat: no-repeat; + background-size: 100%; + text-align: center; + bottom: 0; + padding-top: 15%; + color: #7fcef2; + } + .progress{ + margin-top: 12px; + #cancelImport{ + background-color: #7fcef2 !important; + } + } + #delayPublish{ + background-color: #7fcef2 !important; + } + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/comFadeIn.less b/imports/ui/stylesheets/comFadeIn.less new file mode 100644 index 000000000..34d8e182e --- /dev/null +++ b/imports/ui/stylesheets/comFadeIn.less @@ -0,0 +1,55 @@ +.fade-up-out{opacity: 0;transform: scale(0.8) translate3d(0, -10%, 0);transition: all 300ms cubic-bezier(0.165, 0.840, 0.440, 1.000);} +@-webkit-keyframes comFadeInUp { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes comFadeInUp { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.comFadeInUp { + -webkit-animation-duration: 1s; + animation-duration: 1s; +// -webkit-animation-fill-mode: both; +// animation-fill-mode: both; + -webkit-animation-name: comFadeInUp; + animation-name: comFadeInUp; +} +@-webkit-keyframes comFadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@keyframes comFadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +.comFadeIn { + -webkit-animation-duration: 1s; + animation-duration: 1s; +// -webkit-animation-fill-mode: both; +// animation-fill-mode: both; + -webkit-animation-name: comFadeIn; + animation-name: comFadeIn; +} \ No newline at end of file diff --git a/imports/ui/stylesheets/commentBar.less b/imports/ui/stylesheets/commentBar.less new file mode 100644 index 000000000..4607b5c10 --- /dev/null +++ b/imports/ui/stylesheets/commentBar.less @@ -0,0 +1,131 @@ +.commentBar{overflow-y: auto;width: 100%;position: absolute;top:0;z-index: 20000;background-color: #fff} +.commentBar .commentBg{position: fixed;background-color: #fff;top:0;bottom: 0;width: 100%} +.commentBarBackground{width: 100%;height:100%;position: absolute;top:0;z-index: 10000;background-color: #fff} +.commentBar .content{background-color:#fff;color: #333;position: absolute;top: 0;width: 100%; padding-top: 40px;padding-bottom: 40px;} +.commentBar .content #comment{width: 100%;border: none;} +.commentBar .content header{background-color: #fff;width: 100%;height: 40px;font-size: 16px;line-height: 40px;position: fixed;left: 0;top: 0;text-align: center;z-index: 999;} +.commentBar .content form{background-color: #fff;font-size: 16px;z-index: 999;} +.commentBar .content form textarea{line-height: 15px; font-size:15px; word-wrap: break-word;position:fixed;} +.commentBar .content form .change{bottom:0px; position: fixed;right:10px; height: 28px;line-height: 28px; margin: 4px;padding: 1px 5px;color: #fff;background-color: #0078e7;border-radius: 5px;} +.commentBar .content form .submit{bottom:0px; position: absolute;right:0%;height: 28px;line-height: 28px; margin: 4px;padding: 1px 5px;color: #fff;background-color: #0078e7;border-radius: 5px;} +.commentBar .content header .rightButton{ position:absolute; right: 10px; top:0; cursor:pointer;text-align:right;color: #57cccc} +.commentBar .content header .leftButton{ position:absolute; left: 10px; top:0; cursor:pointer;text-align:left;color: #57cccc} +.commentBar .content .boxTitle{position: relative;padding: 8px 15px} +.commentBar .content .boxTitle span{position: absolute;right: 10px;} +.commentBar .content .boxTitle span i{color: #aaa;font-size: 24px;} +.commentBar .content .eachComment{min-height: 90px;display: inline-block;width: 100%;} +.commentBar .content .eachComment .icon{border-radius: 50%;margin: 10px;margin-top: 20px;} +.commentBar .content .eachComment .userName{display: inline-block;font-weight: bold;color: #000} +.commentBar .content .eachComment .createAt{display: inline-block;position:relative; color: #000;margin-right:-100px;} +.commentBar .content .eachComment .commentDetail{margin-right: 20px;margin-bottom: 5px;margin-left: 50px;white-space: normal;word-break: break-all;} +.commentBar .bottomdirection{position: absolute;bottom: 24px;left: 50px;width:0;height:0;line-height:0;border-width:8px;border-style:solid;border-color: #fff transparent transparent transparent;} +.topBorder{border: 1px solid #c8c7cc;border-left: 0px;border-right: 0px;border-bottom: 0px} +.bottomBorder{border: 1px solid #c8c7cc;border-left: 0px;border-right: 0px;border-top: 0px} +.commentBar .content .eachComment .personName { padding-left:0px; color:black; font-weight:600;} +.commentBar .content .eachComment .personSay { padding-left:0px; color:#000; font-weight:300;word-wrap: break-word;} +.commentBar .content .eachComment .round { -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } +.commentBar .content .eachComment .time { color:#000; font-weight:300;position: absolute;right:5px;vertical-align: top;} +.commentBar .content .eachComment .bubble{ + background-color: #F2F2F2; + border-radius: 5px; + box-shadow: 0 0 6px #B2B2B2; + display: inline-block; + padding: 10px 18px; + position: relative; + vertical-align: top; + float: left; + margin: 5px 45px 5px 20px; + border-color: #cdecb0; +} +.commentBar .content .eachComment .bubble::before { + background-color: #F2F2F2; + content: "\00a0"; + display: block; + height: 16px; + position: absolute; + top: 11px; + transform: rotate( 29deg ) skew( -35deg ); + -moz-transform: rotate( 29deg ) skew( -35deg ); + -ms-transform: rotate( 29deg ) skew( -35deg ); + -o-transform: rotate( 29deg ) skew( -35deg ); + -webkit-transform: rotate( 29deg ) skew( -35deg ); + width: 20px; + box-shadow: -2px 2px 2px 0 rgba( 178, 178, 178, .4 ); + left: -9px; + } +.commentBar .content .eachComment .bubble{ + background-color: #F2F2F2; + border-radius: 5px; + box-shadow: 0 0 6px #B2B2B2; + display: inline-block; + padding: 10px 18px; + position: relative; + vertical-align: top; + margin: 10px 10px; + border-color: #cdecb0; + width: 75%; + } + + +.commentContent{position: relative;background-color: #fff} +.commentContent .content{background-color:#fff;color: #333;padding-top: 10px;width: 100%;min-height: 100%;} +.commentContent .content header{background-color: #fff;width: 100%;height: 40px;font-size: 16px;line-height: 40px;position: fixed;left: 0;top: 0;text-align: center;z-index: 999;} +.commentContent .content form{background-color: #fff; max-height: 100%; width: 100%;height: 40px;font-size: 16px;line-height: 40px;position: fixed;left: 0;bottom: 0px;z-index: 999;} +.commentContent .content form textarea{height: 30px;line-height: 15px; margin: 5px 15px;border: thin solid #c8c7cc;border-radius: 4px;font-size:15px; word-wrap: break-word;overflow-y: scroll;position:absolute;left:20px;right:35px;} +.commentContent .content form .change{bottom:0px; position: absolute;right:10px; height: 28px;line-height: 28px; margin: 4px;padding: 1px 5px;color: #fff;background-color: #0078e7;border-radius: 5px;} +.commentContent .content form .submit{bottom:0px; position: absolute;right:0%;height: 28px;line-height: 28px; margin: 4px;padding: 1px 5px;color: #fff;background-color: #0078e7;border-radius: 5px;} +.commentContent .content header .rightButton{ position:absolute; right: 5px; top:0; cursor:pointer;text-align:right;color: #00c4ff} +.commentContent .content header .leftButton{ position:absolute; left: 0; top:0; cursor:pointer;text-align:left;font-size:2em;} +.commentContent .content .boxTitle{position: relative;padding: 8px 15px} +.commentContent .content .boxTitle span{position: absolute;right: 10px;} +.commentContent .content .boxTitle span i{color: #aaa;font-size: 24px;} +.commentContent .content .eachComment{min-height: 90px;display: inline-block;width: 100%;} +.commentContent .content .eachComment .icon{border-radius: 50%;margin: 10px;margin-top: 20px;} +.commentContent .content .eachComment .userName{display: inline-block;font-weight: bold;color: #000} +.commentContent .content .eachComment .createAt{display: inline-block;position:relative; color: #000;margin-right:-100px;} +.commentContent .content .eachComment .commentDetail{margin-right: 20px;margin-bottom: 5px;margin-left: 50px;white-space: normal;word-break: break-all;} +.commentContent .bottomdirection{position: absolute;bottom: 24px;left: 50px;width:0;height:0;line-height:0;border-width:8px;border-style:solid;border-color: #fff transparent transparent transparent;} +.commentContent .content .eachComment .personName { padding-left:0px; color:black; font-weight:600;} +.commentContent .content .eachComment .personSay { padding-left:0px; color:#000; font-weight:300;word-wrap: break-word;} +.commentContent .content .eachComment .round { -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } +.commentContent .content .eachComment .time { color:#000; font-weight:300;position: absolute;right:5px;vertical-align: top;} +.commentContent .content .eachComment .bubble{ + background-color: #F2F2F2; + border-radius: 5px; + box-shadow: 0 0 6px #B2B2B2; + display: inline-block; + padding: 10px 18px; + position: relative; + vertical-align: top; + float: left; + margin: 5px 45px 5px 20px; + border-color: #cdecb0; +} +.commentContent .content .eachComment .bubble::before { + background-color: #F2F2F2; + content: "\00a0"; + display: block; + height: 16px; + position: absolute; + top: 11px; + transform: rotate( 29deg ) skew( -35deg ); + -moz-transform: rotate( 29deg ) skew( -35deg ); + -ms-transform: rotate( 29deg ) skew( -35deg ); + -o-transform: rotate( 29deg ) skew( -35deg ); + -webkit-transform: rotate( 29deg ) skew( -35deg ); + width: 20px; + box-shadow: -2px 2px 2px 0 rgba( 178, 178, 178, .4 ); + left: -9px; + } +.commentContent .content .eachComment .bubble{ + background-color: #F2F2F2; + border-radius: 5px; + box-shadow: 0 0 6px #B2B2B2; + display: inline-block; + padding: 10px 18px; + position: relative; + vertical-align: top; + margin: 10px 10px; + border-color: #cdecb0; + width: 75%; + } diff --git a/imports/ui/stylesheets/contactsList.less b/imports/ui/stylesheets/contactsList.less new file mode 100644 index 000000000..5278bfd2a --- /dev/null +++ b/imports/ui/stylesheets/contactsList.less @@ -0,0 +1,14 @@ +.contactsList .head{display: none;} +.contactsList .head .socialTitle{position: relative;width: 100%;margin: auto;height: 40px;} +.contactsList .head .socialTitle .Contacts{font-size: 16px;font-weight: bold;position: relative;color:white;} +.contactsList .head .socialTitle .right-btn{ + position: absolute; right: 10px; top: 0px;color: #04BE02; + i{color: #fff;} +} +.contactsList .head .socialTitle .left-btn{position: absolute; left: 10px; top: 0px;color: #fff;} +.contactsList .head .socialTitle .disable{color:#ccc;} +.contactsList .showPostsLine{line-height: 20px; font-size: 10px;color: #8E8E93; padding-left: 10px;} +.addNewFriends .userName{display: inline-block;position: absolute; left: 50px; + right: 175px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;top:15px; } +.addNewFriends .meet_count{z-index: 20; width: 155px; line-height: 18px;position: absolute;right:20px; top:12px;border-radius: 5%;font-size:12px; text-align: right;color: #000; } +.addNewFriends .red_spot{z-index: 20; width: 12px; height: 12px; line-height: 12px;position: absolute;left:30px; top:2px;border-radius: 50%;font-size:8px; text-align: center;background: #FF0000; color: #fff; } diff --git a/imports/ui/stylesheets/dashboard.less b/imports/ui/stylesheets/dashboard.less new file mode 100644 index 000000000..a8befe33d --- /dev/null +++ b/imports/ui/stylesheets/dashboard.less @@ -0,0 +1,248 @@ +.dashboard{background-color: #efeff4;color: #000} +.dashboard .head{background-color: #f7f7f7;color: #111;box-shadow: 0 1px 4px #ccc; } +.dashboard .head div{color: #5f5f5f; font-weight: bold;text-shadow: 0 1px 1px #fff;} +.dashboard .head .rightButton{color: #57CCCC} +.dashboard .head .leftButton{color: #57CCCC} +.dashboard .head .leftButton i{color: #57CCCC} +.dashboard .content{position:relative;padding-top: 40px;padding-bottom: 50px;} + +//dashboard全局影响 +.dashboard .content .smalltitle{margin-top: 30px;margin-bottom: -30px;padding-left: 15px;font-size: 12px;color: #aaa;} +.dashboard .content .smallcontent{padding: 10px 10px 0px;font-size: 12px;color: #aaa;} +.dashboard .content span i{color: #c8c7cc;font-size: 20px;} +.dashboard .content span input{border: none;background-color: #FFF;width: 100%;height: 38px;line-height: 40px;} +.dashboard .box{background-color: #FFF;border: 1px solid #e2e2e2; border-left: 0px;border-right: 0px;margin-top: 20px;} +.dashboard .boxTitle{position: relative;padding: 0 15px; height: 40px; line-height: 40px; font-weight: bold; color: #656565;text-shadow: 0 1px 1px #fff;} +.dashboard .boxTitle span{position: absolute;right: 10px;} +.dashboard .boxTitle .input{position: absolute;left: 35%;} +.dashboard .bottomBorder {border: 1px solid #e6e6e6; border-left: 0px; border-right: 0px; border-top: 0px;} +.dashboard .boxCenter{text-align: center;color: #57CCCC} +.switchkk { + height: 25px; + width: 50px; + position: absolute; + top: 20%; + right: 10px; + + -moz-border-radius: 40px; + -webkit-border-radius: 40px; + border-radius: 40px; + background-color: #d1d1d1; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFD1D1D1', endColorstr='#FFFEFBF7'); + background-image: -moz-linear-gradient(top, #d1d1d1 0%, #fefbf7 100%); + background-image: -webkit-linear-gradient(top, #d1d1d1 0%, #fefbf7 100%); + background-image: linear-gradient(to bottom, #d1d1d1 0%, #fefbf7 100%); +} + +.checkboxkk { + height: 25px; + width: 50px; + position: absolute; + top: 0; + left: 0; + z-index: 10000; + -moz-border-radius: 35px; + -webkit-border-radius: 35px; + border-radius: 35px; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFF28A00', endColorstr='#FFD86517'); + background-image: -moz-linear-gradient(top, #57cccc 0%, #57cccc 67%, #57cccc 100%); + background-image: -webkit-linear-gradient(top, #57cccc 0%, #57cccc 67%, #57cccc 100%); + background-image: linear-gradient(to bottom, #57cccc 0%, #57cccc 67%, #57cccc 100%); +} +.checkboxkk:after { + height: 25px; + width: 25px; + position: absolute; + top: 0; + left: 0; + content: ''; + z-index: 9999; + -moz-border-radius: 35px 0 0 35px; + -webkit-border-radius: 35px; + border-radius: 35px 0 0 35px; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFB1DD00', endColorstr='#FF84AD00'); + background-image: -moz-linear-gradient(top, #57cccc 0%, #57cccc 67%, #57cccc 100%); + background-image: -webkit-linear-gradient(top, #57cccc 0%, #57cccc 67%, #57cccc 100%); + background-image: linear-gradient(to bottom, #57cccc 0%, #57cccc 67%, #57cccc 100%); +} + +.controlkk { + outline: 0; + position: absolute; + margin-top: 0px !important; + right: 1px; + z-index: 10001; + -webkit-appearance: none; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFEFEFEF', endColorstr='#FFBCB9B8'); + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.controlkk:checked { + left: 25px; +} +.controlkk:after { + height: 25px; + width: 25px; + position: absolute; + right: 0; + top: 0; + content: ''; + z-index: 10001; + -moz-border-radius: 50%; + -webkit-border-radius: 50%; + border-radius: 50%; + background-color: #c2c0be; + *zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFC2C0BE', endColorstr='#FFD7D7D7'); + background-image: -moz-linear-gradient(top, #c2c0be 0%, #d7d7d7 72%); + background-image: -webkit-linear-gradient(top, #c2c0be 0%, #d7d7d7 72%); + background-image: linear-gradient(to bottom, #c2c0be 0%, #d7d7d7 72%); +} + +//dashboard局部影响 +.dashboard .content .email span{color: #c8c7cc} +.dashboard .content .email span i{margin-left: 5px; vertical-align: middle;} +.dashboard .content .blackListUl{margin: 0;padding: 0;} +.dashboard .content .blackListUl li{display: block;position: relative;min-height: 50px;} +.dashboard .content .blackListUl .icon{position: absolute;left: 0px;margin: 10px;border: none; border-radius: 5px;} +.dashboard .content .blackListUl .userName{position: absolute;top: 15px;left: 50px;} +.dashboard .content .blackListUl .line span{ background: #c8c7cc; + width: 100%; + height: 1px; + display: block;} +.dashboard .content .blackListUl .line{ text-align: center; + width: 100%; + height: 1px; + padding: 5px 0px;} +.dashboard .content .blackListUl .remove{ position: absolute; + top: 10px; + right: 10px; + padding: 5px; + color: #57cccc; + border: solid 1px #57cccc; + border-radius: 5px;} +.dashboard .showChangePwdInfo .banner .btnYes{ + width: 50%; + position: absolute; + font-weight: bold; + right: 0; + background: rgba(255, 255, 255, 0.99); + padding: 8px 0; + border-bottom-right-radius: 10px; +} + +.dashboard .showChangePwdInfo .banner .btnNo{ + position: absolute; + width: 50%; + border-right: 1px solid #ccc; + background: rgba(255, 255, 255, 0.99); + padding: 8px; + left: 0; + border-bottom-left-radius: 10px; +} + +.dashboard .showChangePwdInfo .user-info-content div{ + padding: 5px; +} +.dashboard .showChangePwdInfo .user-info-content{ + text-align: left; + padding: 10px; +} +.dashboard .showChangePwdInfo .banner{ + position: relative; + color: #0078e7; +} +.dashboard .showChangePwdInfo .title{ + background: rgba(255, 255, 255, 0.99); + border-bottom: 1px solid #ccc; + font-weight: bold; + padding: 14px 0; + // margin-bottom: 1px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} +.dashboard .showChangePwdInfo #save-user-info-btn{ + padding: 5px; + margin: 10px; + border: 1px solid #57cccc; + border-radius: 5px; + background: #57cccc; + color: #fff; +} +.dashboard .showChangePwdInfo{ + position: fixed; + z-index: 99999; + top: 10%; + left: 10%; + right: 10%; + text-align: center; + color: #000; + background: rgba(255, 255, 255, 0.99); +} +.dashboard .changePwdInfoBackground{ + width:100%; + height:100%; + position:fixed; + top:0px; + left:0px; + z-index: 99998; + display:none; +} + +#updateToLatestVersion .modal-dialog { + top: 15%; +} + +#updateToLatestVersion .modal-header div { + margin-left: auto; + margin-right: auto; + width: 155px; +} + +#updateToLatestVersion .modal-body div { + width: 100%; + text-align: center; +} + +#updateToLatestVersion .modal-body h5 { + top: 10px; +} + + +// about +.dashboard .content { + .about-icon{ + width: 114px; + height: 114px; + margin: auto; + background-image: url('/icon.png'); + background-size: cover; + background-repeat: no-repeat; + margin-top: 20px; + } + .app-name{ + text-align: center; + } + .ver{ + text-align: center; + margin-bottom: 15px; + color: #444; + } + .textpadding{ + padding: 0px 15px; + } + .bottom-image{ + width: 100%; + height: 15%; + position: fixed; + bottom: 0; + background-image: url('aboutBg.png'); + background-size: cover; + background-repeat: no-repeat; + } +} diff --git a/imports/ui/stylesheets/discover.less b/imports/ui/stylesheets/discover.less new file mode 100644 index 000000000..3fd5e42dc --- /dev/null +++ b/imports/ui/stylesheets/discover.less @@ -0,0 +1,220 @@ +//朋友圈 +.discover{width:100%;position:absolute;top: 0px;background:#ffffff;} +.disTop{width:100%;height:280px; overflow: hidden;position:relative; margin-top:20px;} +.disBg{width:100%; height:280px;background: url("./img/readMore.gif") no-repeat center top; background-size: cover} +.disRight{width:300px; position:absolute;bottom:0;right:0;} +.disLeft{width:100%; position:absolute;bottom:0;left:0;} +.dispic{width:120px;height:120px;border: 1px solid #fff;overflow:hidden;float:right;margin-right:15px;} +.disname{float:left;font-weight:bold;color:#fff;font-size:28px;margin: 26px 0 50px 20px;text-shadow: -1px 2px 3px #ffb69a} +.disdesc{float:left;font-weight:bold;color:#fff;font-size:18px;margin: 5px 25px 75px 125px;text-shadow: -1px 2px 3px #ffb69a} +.disrelease{width:100%;padding: 10px 0;border-bottom:1px solid #f0ad4e;overflow:hidden;} +.disrelease-left{width:48px;height:48px;float:left;} +.disrelease-right{width:90%;float:left;padding-left:10px;} +.disrelease-right p{width:100%;line-height: 22px;overflow:hidden;} +.disrelease-right p a{float:left;display:block} +.disrelease-right p span{float:right;margin-right:15px;} +.disrelease-right .disImg{width:260px;height:350px;overflow: hidden;background-color:#fff;margin-bottom: 10px;} +.dz{width:90%;background-color: #000;padding:10px 0; text-indent: 5px} +.bod{border-bottom:1px solid #f0ad4e} +.moments #wrapper { min-width:300px; padding:0 10px; margin-top:40px; margin-bottom:50px; background-color:#fff;} +.moments #wrapper #list { border-bottom:1px solid #efefef; padding-bottom:10px; margin-top:15px; width:100%; position:relative;} +.moments .icon { position:absolute;} + +.moments .user_name { position:relative; margin-left:50px;} +.moments .user_name h2 { font-size:16px; margin-top:4px; color:#07519a;} +.moments .user_name span { margin-left:15px; color:#999; } +.moments .username button{padding-left:30px;position:relative;float: right; margin-right: 5px;top:-5px;border: none; background-color: rgba(250, 250, 250, 0);} +.moments .username i{color:gray;font-size:22px;} +.moments .user_name .readpost { + font-size:15px; + width: 100%; + float: left; + margin-right: 2%; + height: 56px; + overflow: hidden; + position: relative;} +.moments .user_name dl { background:#efefef; padding:5px; margin-top:6px; min-height:50px;margin-bottom:1px;} +.moments .user_name dl dt {float:left; margin-right:10px;} +.moments .user_name dl dd { + color: black; + font-size:15px; +} +.moments .user_name p { color:#999; margin-top:5px;} +.moments .clear {clear: both;} +.moments {background:#fff;} +.moments .pin_content .alreadyReadBtn{bottom: 1px;float: right;margin-right: 10px;border: none;position: relative;height: 16px;width: 16px;} + +.lpcomments #wrapper { min-width:300px; padding:10px; margin-top:10px; margin-bottom:10px; background-color:#fff;} +.lpcomments #wrapper #list { border-bottom:1px solid #efefef; padding-bottom:10px; margin-top:15px; width:100%; position:relative;background-color: #fff;} +.lpcomments .icon { position:absolute;} + +.lpcomments .user_name { position:relative; margin-left:50px;background-color: #fff;border-bottom: 1px solid #efefef;} +.lpcomments .user_name h2 { font-size:16px; margin-top:4px; color:#07519a;} +.lpcomments .user_name span { margin-left:15px; color:#999; } + +.lpcomments .user_name .readpost { + font-size:15px; + width: 100%; + float: left; + margin-right: 2%; + height: 56px; + overflow: hidden; + position: relative;} +.lpcomments .user_name .readpost .red_spot{ + z-index: 20; + width: 12px; + height: 12px; + line-height: 12px; + position: absolute; + left: 40px; + top: 1px; + border-radius: 50%; + font-size: 8px; + text-align: center; + background: #FF0000; + color: #fff;} +.lpcomments .user_name dl { background:#efefef; padding:5px; margin-top:6px; min-height:50px;margin-bottom:1px;} +.lpcomments .user_name dl dt {float:left; margin-right:10px;} +.lpcomments .user_name dl dd { + color: black; + font-size:15px; +} +.lpcomments .user_name p { color:#999; margin-top:5px;} +.lpcomments .clear {clear: both;} +.lpcomments {background:#fff;} +.lpcomments #wrapper .elementBox { + position: relative; +} +.lpcomments #wrapper .behind { + width: 100%; + height: 100%; + position: absolute; + top: 0; + right: 0; + line-height: 50px; +// font-size: x-large; + border-bottom: 1px solid #efefef; +// padding-bottom: 10px; + } +.lpcomments #wrapper .behind a.ui-btn { + height: 50px; + margin-top: 32px; + width: 100px; +// margin: 0px; + float: right; + border: none; + text-align: center; + text-overflow: ellipsis; + position: absolute; + bottom: 21px; + right: 0; +} +.lpcomments #wrapper .behind a.delete-btn, .behind a.delete-btn:active, .behind a.delete-btn:visited, .behind a.delete-btn:focus, .behind a.delete-btn:hover { + color: white; + background-color: #f15353; + text-shadow: none; +} +.lpcomments #wrapper .behind a.ui-btn.pull-left { + float: left; +} + +.discover .discover-top { + position: relative; + margin-top: 20px; + width: 100%; + overflow: hidden; + text-align: center; + + .discover-bg { + width: 100%; + height: 15px; + border-bottom: 1px solid #d7d7df; + } + + .discover-con { + position: relative; + top: -12px; + background-color: #fff; + padding: 0 5px; + // margin-left: auto; + // margin-right: auto; + font-size: 14px; + font-weight: bold; + display: inline-block; + + img { + position: relative; + top: -1px; + width: 24px; + margin-right: 2px; + } + + span { + color: #a0a0a6; + } + } +} + +.recommends #wrapper { min-width:300px; padding:0 10px; margin-top:20px; margin-bottom:30px; background-color:#efeff4;font-family: "RobotoDraft", "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif;} +.recommends #wrapper #list { border-bottom:1px solid #efefef; padding-bottom:10px; margin-top:15px; width:100%; position:relative;background-color: #fff;letter-spacing: 1px;} +.recommends .icon { position:absolute;} +.recommends img {object-fit: cover;} +.recommends .user_name { position:relative; margin-left:50px;background-color: #fff;border-bottom: 1px solid #efefef; margin-top: 10px;} +.recommends .user_name h2 { font-size:16px; margin: 0;margin-top:4px; color:#07519a;font-weight: bold;} +.recommends .user_name h3 { font-size:14px; line-height: 20px; margin-top:4px; color: black; } +.recommends .user_name span { margin-left:4px; margin-right: 4px; color:#07519a; } + +.recommends .user_name .readpost { + font-size:15px; + width: 100%; + // float: left; + margin-right: 10px; + height: 80px; + overflow: hidden; + position: relative;} +.recommends .user_name dl { background:none; padding:0; margin:0; min-height:80px;} +.recommends .user_name dl dt {float:left; margin-right:10px;} +.recommends .user_name dl dd { + color: #07519a; + font-size:14px; + font-weight: bold; + h2{max-height: 64px; color: #07519a;font-size:14px; line-height: 22px; overflow: hidden;overflow : hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;} +} +.recommends .user_name p { color:black; font-weight: normal; line-height: 18px; margin: 4px;} +.recommends .clear {clear: both;} +.recommends {background:#fff;} +.recommends #wrapper .elementBox { + position: relative; +} +.recommends #wrapper .behind { + width: 100%; + height: 100%; + position: absolute; + top: 0; + right: 0; + line-height: 50px; +// font-size: x-large; + border-bottom: 1px solid #efefef; +// padding-bottom: 10px; + } +.recommends #wrapper .behind a.ui-btn { + height: 50px; + margin-top: 32px; + width: 100px; +// margin: 0px; + float: right; + border: none; + text-align: center; + text-overflow: ellipsis; + position: absolute; + bottom: 21px; + right: 0; +} +.recommends #wrapper .behind a.delete-btn, .behind a.delete-btn:active, .behind a.delete-btn:visited, .behind a.delete-btn:focus, .behind a.delete-btn:hover { + color: white; + background-color: #f15353; + text-shadow: none; +} +.recommends #wrapper .behind a.ui-btn.pull-left { + float: left; +} \ No newline at end of file diff --git a/imports/ui/stylesheets/followers.less b/imports/ui/stylesheets/followers.less new file mode 100644 index 000000000..2242ee914 --- /dev/null +++ b/imports/ui/stylesheets/followers.less @@ -0,0 +1,10 @@ +.followers .content {position:relative;padding-top: 45px;padding-bottom: 55px;} +.followers .content .contentList{display: block;position:relative;min-height: 50px;} +.followers .content .icon{margin: 10px;border: none;border-radius:50%;object-fit: cover;} +.followers .content .userName{position: absolute;top:15px;left: 50px;color: #bbb} +.followers .content .follow{position: absolute;top: 10px;right: 10px;width: 30px;height: 30px;text-align: center; display: table;border-radius: 50%;border: thin solid #fff;background-color: #fff;color: #000;} +.followers .content .follow span{position: relative;bottom: 8px;font-size:8px;padding-top: 12px} +.followers .content .desc{padding: 0 15px;} +.followers .line{text-align:center;width: 100%; height: 1px; padding: 5px 0px;} +.followers .line span{background: #333; width: 100%; height: 1px; display: block;} +.followers .content a,a:hover,a:focus{text-decoration: none;color: #bbb;} \ No newline at end of file diff --git a/imports/ui/stylesheets/footer.less b/imports/ui/stylesheets/footer.less new file mode 100644 index 000000000..8a35ddba5 --- /dev/null +++ b/imports/ui/stylesheets/footer.less @@ -0,0 +1,80 @@ +#footer .btn{height: 50px;line-height:50px;background-color: #282828;padding: 0 0;margin: 0 0;width: 100%;} + +.navbar-default.navbar {background-color: #282828;} +#footer{border: none;opacity: 0.99;} +#footer .footerName{font-size: 10px;position: absolute;bottom: 5px;line-height: 10px;width: 100%;} +#footer .btn{border:0px;color: #888;font-size: 14px;} +#footer .btn:focus,#footer .focus{color: #FFF;} +#footer .focus i{font-size: 24px} +#footer .btn.focus,#footer .btn:active.focus,#footer .btn.active.focus{outline:none;border: 0;} +#footer .btn-group{position: relative;} +#footer .btn-group .msg_count{z-index: 99999; width: 18px; height: 18px; line-height: 18px;position: absolute;right:20px; top:5px;border-radius: 50%;font-size:8px; text-align: center;background: #FF0000; color: #fff; } +#level2-popup-menu .modal-content button { + width: 100%; +} +.modal-dialog { + margin: 0; + position: fixed; + bottom: 50px; + color: black; +} +.modal.fade:not(.in).bottom .modal-dialog { + -webkit-transform: translate3d(0, 125%, 0); + transform: translate3d(0, 125%, 0); +} +.modal-footer { + margin-top: 0; +} +@media (max-width: 767px) { + .modal-dialog { + left: 10%; + right: 10%; + width: 80%; + } +} + +#mask { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 9990; + background: rgba(0,0,0,.28); +} +.selectImportWay{ + position: fixed; + background-color: #fff; + border-radius: 4px; + height: 200px; + width: 80%; + left: 10%; + bottom: 20%; + z-index: 9999; + .importWayheader{ + background-color: #77a9d8; + color: #fff; + height: 130px; + padding: 16px; + border-radius: 4px 4px 0 0 ; + .importWayTitle{ padding-left: 6px; text-align: center; font-size: 18px; letter-spacing:2px;} + .importWayContent{margin-top: 16px;} + } + .importWayFooter { padding: 16px; border-radius: 4px;} + .importWayBtn { + display: inline-block; + text-align: center; + width: 45%; + line-height: 22px; + padding: 8px 16px; + border-radius: 8px; + border: 1px solid #77a9d8; + color: #77a9d8; + } + .importWayBtnLight{ + right: 16px; + color: #fff; + position: absolute; + background: #77a9d8; + } +} diff --git a/imports/ui/stylesheets/header.less b/imports/ui/stylesheets/header.less new file mode 100644 index 000000000..363fba7de --- /dev/null +++ b/imports/ui/stylesheets/header.less @@ -0,0 +1,24 @@ +.head{width:100%; height:40px;font-size:16px; line-height:40px; position:fixed; left:0; top:0; border:none; text-align:center; background: #37a7fe; z-index: 999;opacity: 0.99;} +.head strong{font-size: 16px; color: #fff} +.head strong.top-home-btn,.head strong.top-series-btn{font-size: 14px; color: #e4f3ff;line-height: 40px; height: 39px; width: 64px;display: inline-block; vertical-align: middle;position: relative;font-weight: normal;} +.head strong.active{ font-size: 16px; color: #fff;} +// .head strong.active:after{position: absolute; content: ""; bottom: 2px; height: 2px; width: 12px; left: 50%;margin-left: -6px; background: #fff;} + +.head .leftButton span{position: relative;bottom: 8px;font-size:8px;color:#fff} +.head .leftButton{ position:absolute;z-index: 1000; left: 10px;top:0;cursor:pointer;margin-top: 3px;text-align:left;} +.head .leftButton i{font-size: 26px;padding-right: 80px;color: #fff;} +.head .leftButton .fa-angle-left{font-size: 2em;} +.head .rightButton{ position:absolute; right: 10px; top:0; cursor:pointer;margin-top: 3px; text-align:right;} +.head .rightButton i{font-size: 26px;color: #fff;} +.head .secondRightButton{ position:absolute; right: 50px; top:0; cursor:pointer;margin-top: 3px; text-align:right;} + +.head .user{position: relative;width: 60%;margin: auto;height: 40px;} +.head .user .icon{border: none;border-radius:50%; position: absolute; margin-left: -25px; margin-top: 8px;} +.head .user .name{font-size: 14px;font-weight: bold;position: relative;top: -6px;padding-left: 8px;display: inline-block;max-width: 80%;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;} +.head .user .createAtBox{position: relative;text-align: center} +.head .user .createAt{font-size: 12px;position: absolute;top: 25px; line-height: 12px; width: 100%;} +// .head .user .createAt{font-size: 12px;position: relative;top: -30px;left: 18px;} + +.head .leftButton,.head .rightButton{padding-left: 10px; padding-right: 10px; min-width: 55px; margin-top: 0px; padding-top: 3px; bottom: 0px;} +.head .leftButton{left: 0px; } +.head .rightButton{ right: 0px;} \ No newline at end of file diff --git a/imports/ui/stylesheets/home.less b/imports/ui/stylesheets/home.less new file mode 100644 index 000000000..7310816ba --- /dev/null +++ b/imports/ui/stylesheets/home.less @@ -0,0 +1,125 @@ +body{background-color: #111;min-height: 100%;height: auto;color: #fff} +.home{min-height: 100%;height: auto;} +.home .content{padding-top: 40px;padding-bottom:50px;background-color: #3F3F3F} +.home .head .dropdown { + cursor: pointer; + #add-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; + } + } +} + +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; + } + } +} + +.dailyReporterTip { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: none; + z-index: 999999; +} \ No newline at end of file diff --git a/imports/ui/stylesheets/hotPosts.less b/imports/ui/stylesheets/hotPosts.less new file mode 100644 index 000000000..57457bbe6 --- /dev/null +++ b/imports/ui/stylesheets/hotPosts.less @@ -0,0 +1,60 @@ +.hot-posts{ + position: absolute; + z-index: 111; + padding: 10px; + min-height: 100%; + background: #fff; + color: #333; + .head{color: #fff;} + .content{margin-top: 60px;} + .waterfall{ + -moz-column-count: 2; + -webkit-column-count: 2; + column-count: 2; + -moz-column-width: 8em; + -webkit-column-width: 8em; + column-width: 8em; + -webkit-column-gap: 8px; + column-gap: 8px; + .pin{ + padding: 5px; + -webkit-column-break-inside: avoid; + break-inside: avoid; + margin-top: 8px; + box-shadow: -1px 3px 13px #ccc; + .img_placeholder { + width: 100%; padding-bottom: 10px; margin-bottom: 2px; position: relative; + .selectHelper{width: 32px; height: 32px; position: absolute; z-index: 99; right: 0;margin-top: 8px; margin-right: 8px;} + } + img{width: 100%;} + } + } + .leftButton{ + // position: relative; + .fa-angle-left{ + font-size: 2em; + } + span{ + position: absolute; + top:5px; + padding-left: 5px; + color: white; + } + } + h1{font-size: 14px; margin-bottom: 10px; font-weight: bold;} + ul,li{padding: 0; list-style: none;} + ul{ padding-left: 10px;} + .submit{width: 100%; height: 48px; line-height: 48px; border:1px solid #CCCCCC; border-radius: 5px; text-align: center; margin-top: 10px;} + .hotposts-toast-banner{ + position: fixed; + bottom: 10%; + left: 0; + right: 0; + margin: auto; + text-align: center; + .toast{ + display: none; + color: white;opacity: 0.8;font-weight: normal;padding: 12px;border-radius: 50px;position: relative;bottom: 10%;text-align: center;margin: auto; + } + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/loadingpost.less b/imports/ui/stylesheets/loadingpost.less new file mode 100644 index 000000000..d76ff91dd --- /dev/null +++ b/imports/ui/stylesheets/loadingpost.less @@ -0,0 +1,33 @@ +.glyphicon-refresh-animate { + -animation: spin .7s infinite linear; + -webkit-animation: spin2 .7s infinite linear; +} + +@-webkit-keyframes spin2 { + from { -webkit-transform: rotate(0deg);} + to { -webkit-transform: rotate(360deg);} +} + +@keyframes spin { + from { transform: scale(1) rotate(0deg);} + to { transform: scale(1) rotate(360deg);} +} + +.showPosts .content .loading { + height: 0px; + position: absolute; + top:0; + bottom: 0; + left: 0; + right: 0; + + margin: auto; + text-align: center; + i{ + background-color: rgba(240,240,240,0); + span{ + color: #333; + } + } + +} \ No newline at end of file diff --git a/imports/ui/stylesheets/loginForm.less b/imports/ui/stylesheets/loginForm.less new file mode 100644 index 000000000..1a6773471 --- /dev/null +++ b/imports/ui/stylesheets/loginForm.less @@ -0,0 +1,206 @@ +.loginForm{ + // background-image: url(loginbg2.jpg); + // background-repeat: no-repeat; + // background-size: 100% auto; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1000; + .customerServiceBackground{ + width:100%; + height:100%; + position:fixed; + top:0px; + left:0px; + z-index: 99998; + display:none; + background: #777777; + opacity: 0.4; + } + .customerService{ + position: fixed; + z-index: 99999; + top: 20%; + left: 25px; + right: 25px; + text-align: center; + color: #000; + background: #fff; + border-radius: 10px; + display: none; + .customerService-head{ + position: relative; + text-align: center; + border-bottom: solid 1px #f1f2f4; + .title{ + font-weight: bold; + padding: 15px; + margin: 0; + margin-bottom: 1px; + text-align: left; + span{color: #0084ee;} + } + .btnClose{ + position: absolute; + top: 0px; + right: 10px; + z-index: 250; + color: #ccc; + font-weight: normal; + font-size: 2em; + } + } + .customer-email{ + position: relative; + margin: 10px 15px; + border: 1px solid #ccc; + padding: 10px; + margin-bottom: 0; + input{ + width: 100%; + border: none; + line-height: 1.5em; + resize: none; + outline: none; + color: #777; + font-size: 14px; + } + } + .alert-email{ + text-align: right; + margin-right: 15px; + font-size: 10px; + margin-bottom: 10px; + } + .content{ + position: relative; + margin: 10px 15px; + border: 1px solid #ccc; + padding: 10px; + background:#fff; + textarea{ + -webkit-user-select: auto; + -khtml-user-select: auto; + -moz-user-select: auto; + -ms-user-select: auto; + user-select: auto; + width: 100%; + border: none; + line-height: 1.5em; + resize: none; + outline: none; + color: #777; + font-size: 14px; + } + } + .btn-banner{ + text-align: right; + margin: 15px; + font-size: 18px; + position: relative; + .send-email-btn{ + color: white; + background: #00c4ff; + padding: 4px 14px; + font-weight: normal; + } + } + } + .avatar{ + display: flex; + justify-content: center; + align-items: center; + background: #37a7fe; + margin-top: 39px; + height: 30%; + .circle{ + width: 100px; + height: 100px; + border-radius: 50%; + background: #ffffff; + display: flex; + justify-content: center; + align-items: center; + } + } +} +.loginForm .loginBody{margin-top: 50px;} +.loginForm .loginBody input{background-color:rgba(255,255,255,0.4);color: #FFF;border: none;} +.loginForm .loginBody #sub-login{ + border-radius: 2px !important; + margin-top: 10px; + margin-left:10%; + width: 80%; + height: 64px; + font-size: 16px; +} +.loginForm .loginBody .forgetPwdBtn{ + color: #555555; + font-size: 14px; + margin-top: 10px; + margin-right: 10%; + text-decoration: underline; + text-align: right; +} +.loginForm .loginBody input::-webkit-input-placeholder{color: #FFF;} +.loginForm .loginBody input:focus{outline:none;} +.loginForm .loginBody .newinput,.signupForm .signupBody .newinput{ + margin-left: 10%; + width: 80%; + height: 40px !important; + line-height: 40px; + background: none !important; + border: 1px solid #cccccc; + padding: 5px 16px; + border-radius: 2px !important; + font-size: 15px; + color: #555555; +} +.loginForm .loginBody .noRegister{ + text-align: center; + font-size: 14px; + padding-top: 10px; + color: #555555; + .signup-text{ + color: #37a7fe; + text-decoration: underline; + } +} + +.loginForm .loginBody div{border-radius:5px;text-align:left;overflow: hidden; margin-bottom: 10px;} +.loginForm .loginBody s{display: block; overflow: hidden; padding: 5px 0} +.loginForm .loginBody div label span{padding: 6px 12px;font-size: 24px;color:#ccc;font-weight: normal;line-height: 1;text-align: center;width: 1%;white-space: nowrap;vertical-align: middle;display: table-cell;} +.loginForm .loginBody div label{border-bottom: solid 1px #ccc;height:45px;margin-bottom: 3px;width:98%;margin-left: 3px;} +.loginForm .loginBody div label input{z-index: 2;float: left;width: 93%;height: 30px;padding: 2px 4px;font-size: 14px;line-height: 1.42857143;border: none;margin-top:5px;} +.loginForm .loginBody div label textarea{z-index: 2;float: left;width: 93%;height: 60px;padding: 2px 4px;font-size: 14px;line-height: 1.42857143;border: none;margin-top:5px;} +.loginForm .loginBody ol {margin: 0; padding:10px 0 20px; list-style-type: none} +.loginForm .loginBody ol li{float: left;width:33%; text-align: left; font-size: 1em} +.loginForm .loginBody ol li:first-letter{font-size: 1.1em} +.form-control {height: 34px !important;} +.loginForm .loginBody .box{text-align: center;} +.loginForm .loginBody .text-box{font-size: 2em;border:1px solid #74daf3;display: inline;color: #74daf3;padding: 2px 5px} + + + + +.recoveryForm{/*background-image: url(loginbg2.jpg);background-repeat: no-repeat;background-size: 100% auto; */ position: fixed;top: 0;bottom: 0;left: 0;right: 0;z-index: 1000} +.recoveryForm .recoveryBody{background-color:rgba(255,255,255,0);position: fixed;top: 60px;left: 5%;right: 5%;} +.recoveryForm .recoveryBody input{background-color:rgba(255,255,255,0.4);color: #FFF;border: none;} +.recoveryForm .recoveryBody #sub-recovery{position: absolute;width: 100%;text-align: center;border: none;border-radius: 8px;padding: 10px;background-color: #00c4ff;margin-top: 10px;font-size: 16px;} +.recoveryForm .recoveryBody input::-webkit-input-placeholder{color: #FFF;} +.recoveryForm .recoveryBody input:focus{outline:none;} + + +.recoveryForm .recoveryBody div{border-radius:5px;text-align:left;overflow: hidden; margin-bottom: 10px;} +.recoveryForm .recoveryBody s{display: block; overflow: hidden; padding: 5px 0} +.recoveryForm .recoveryBody div label span{padding: 6px 12px;font-size: 24px;color:#ccc;font-weight: normal;line-height: 1;text-align: center;width: 1%;white-space: nowrap;vertical-align: middle;display: table-cell;} +.recoveryForm .recoveryBody div label{border-bottom: solid 1px #ccc;height:45px;margin-bottom: 3px;width:98%;margin-left: 3px;} +.recoveryForm .recoveryBody div label input{z-index: 2;float: left;width: 93%;height: 30px;padding: 2px 4px;font-size: 14px;line-height: 1.42857143;border: none;margin-top:5px;} +.recoveryForm .recoveryBody div label textarea{z-index: 2;float: left;width: 93%;height: 60px;padding: 2px 4px;font-size: 14px;line-height: 1.42857143;border: none;margin-top:5px;} +.recoveryForm .recoveryBody span{margin-right:5px;color: #FFF;background-color: rgba(255, 255, 255, 0.4);border: none;font-size: 1.4em;padding: 6px 7px;} +.recoveryForm .recoveryBody ol {margin: 0; padding:10px 0 20px; list-style-type: none} +.recoveryForm .recoveryBody ol li{float: left;width:33%; text-align: left; font-size: 1em} +.recoveryForm .recoveryBody ol li:first-letter{font-size: 1.1em} + diff --git a/imports/ui/stylesheets/mainImagesList.less b/imports/ui/stylesheets/mainImagesList.less new file mode 100644 index 000000000..99d770f89 --- /dev/null +++ b/imports/ui/stylesheets/mainImagesList.less @@ -0,0 +1,27 @@ +.mainImagesList{ + display: none; + background: #fff; + .content{ + .mainImageListUl{ + position: relative; + width: 99%; + margin: auto; + top: 45px; + padding: 0; + li{ + position: relative; + width: 31%; + height: 100px; + background-repeat: no-repeat; + background-size: cover; + display: inline-block; + margin: 2px; + .mainImageListInput{ + position: absolute; + right: 5px; + zoom:2; + } + } + } + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/management.less b/imports/ui/stylesheets/management.less new file mode 100644 index 000000000..75fd73ee1 --- /dev/null +++ b/imports/ui/stylesheets/management.less @@ -0,0 +1,302 @@ +.page-accounts-management { + .accounts-list { + dl { + position: relative; + margin-bottom: 0; + line-height: 50px; + border-bottom: 1px solid #c8c7cc; + + dt { + position: relative; + display: inline-block; + margin-left: 10px; + + img { + width: 35px; + height: 35px; + border-radius: 50px; + } + + i.fa.fa-check-circle { + display: none; + position: absolute; + bottom: 0; + right: -2px; + height: 12px; + border-radius: 50px; + color: #35CE0F; + background-color: #fff; + } + + i.fa.fa-check-circle:before { + position: relative; + top: -1px; + } + } + + dt.active { + i.fa.fa-check-circle { display: inline-block; } + } + + dd { + display: inline-block; + margin-left: 10px; + + width: 65%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + .remove { + position: absolute; + right: 10px; + } + } + .remove{ + width: 55px; height: 55px; line-height: 55px; text-align: center; margin-top: -10px; + } + } + + dl.add-new { + > dt { + position: relative; + vertical-align: middle; + height: 35px; + width: 35px; + border-radius: 50px; + background-color: #e7e7e7; + + i.fa.fa-plus { + position: absolute; + display: inline-block; + top: 6px; + left: 7.5px; + font-size: 25px; + color: #afafaf; + } + } + } + + > dl:last-of-type { + border-bottom: none; + } + } + + .logout { + display: none; + } +} + +.page-accounts-management-addnew { + dl.add-new { + margin-bottom: 0; + min-height: 50px; + line-height: 50px; + border-bottom: 1px solid #c8c7cc; + clear: both; + dt, dd { + float: left; + } + + dt { + width: 100px; + padding-top: 15px; + padding-right: 10px; + text-align: right; + } + + dd { + padding-top: 10px; + padding-left: 10px; + } + } + + dl:last-of-type { + border-bottom: none; + } + + div.logout { + padding: 0; + height: 40px; + } + + #form-addnew { + dt > label { + color: #929292; + } + + dd > input { + border: 1px solid #b7b7b7; + } + + input[type=submit] { + display: inline-block; + height: 100%; + width: 100%; + border: none; + border-radius: 0; + } + input[type=submit].active { + background-color: #fff; + } + } +} + + +#chooseAssociatedUser { + .modal-body { + dl { + position: relative; + margin-bottom: 0; + line-height: 50px; + border-bottom: 1px solid #c8c7cc; + + dt { + position: relative; + display: inline-block; + margin-left: 10px; + + img { + width: 35px; + height: 35px; + border-radius: 50px; + } + + i.fa.fa-check-circle { + display: none; + position: absolute; + bottom: 0; + right: -2px; + height: 12px; + border-radius: 50px; + color: #35CE0F; + background-color: #fff; + } + + i.fa.fa-check-circle:before { + position: relative; + top: -1px; + } + } + + dt.active { + i.fa.fa-check-circle { display: inline-block; } + } + + dd { + display: inline-block; + margin-left: 10px; + width: 65%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + } +} + +.server-import-select-user { + color: #000; + .h{ + height: 48px; line-height: 48px; + background-color: whitesmoke; + box-shadow: 0 1px 11px #adadad; + text-align: center; + } + .f{ + padding: 15px; + button{width: 80%;margin: 0 auto;display: table;} + } + .c { + padding: 15px; + dl { + position: relative; + margin-bottom: 0; + line-height: 50px; + border-bottom: 1px solid #c8c7cc; + + dt { + position: relative; + display: inline-block; + margin-left: 10px; + + img { + width: 35px; + height: 35px; + border-radius: 50px; + } + + i.fa.fa-check-circle { + display: none; + position: absolute; + bottom: 0; + right: -2px; + height: 12px; + border-radius: 50px; + color: #35CE0F; + background-color: #fff; + } + + i.fa.fa-check-circle:before { + position: relative; + top: -1px; + } + } + + dt.active { + i.fa.fa-check-circle { display: inline-block; } + } + + dd { + display: inline-block; + margin-left: 10px; + width: 65%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + } +} + +.page-accounts-management-prompt{ + + .management-prompt-content { + width: 70%; + height: 20%; + position: fixed; + left: 15%; + top: 40%; + background: white; + border-radius: 10px; + z-index: 99999; + } + .management-prompt-content .prompt-close{ + width: 40px; + height: 40px; + right: 0px; + position: absolute + } + + .management-prompt-content .prompt-text{ + width: 100%; + height: auto; + position: absolute; + left: 50%; + margin-left: -30%; + bottom: 0px; + font-size: 16px; + font-weight: 400; + } + + + .management-prompt-background{ + width:100%; + height:100%; + position:fixed; + top:0px; + left:0px; + z-index: 99998; + background: rgba(130,130,130,0); + } +} diff --git a/imports/ui/stylesheets/me.less b/imports/ui/stylesheets/me.less new file mode 100644 index 000000000..9ce7344ce --- /dev/null +++ b/imports/ui/stylesheets/me.less @@ -0,0 +1,75 @@ + +.me{ + background-color: #EFEFF4; margin-top: 40px; margin-bottom: -20px;padding-top: 10px; + .setNickname{ + .head{ + 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;} + } + } + .set-up{margin-top: 10px;} + } + .removefavp { + position:relative;float: right; margin-right: 5px;top:-5px;border: none; background-color: rgba(250, 250, 250, 0); + + // i{color:gray;font-size:22px;} + } + + .loading-spinner{position:fixed;bottom:60px;left:50%;height:46px;width:46px;margin-left: -23px;} +} +ul.set-up{ + background-color: #FFFFFF;padding-left: 10px; margin-bottom: 20px;border-top: 1px solid #f5f5f5;border-bottom: 1px solid #f5f5f5; + li{ + 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;} + } + } + li:first-child{border-top: none;} +} +.setNickname{ + background-color: #EFEFF4;height: 100%; + .head{ + 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: #04BE02;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;} + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/messageDialog.less b/imports/ui/stylesheets/messageDialog.less new file mode 100644 index 000000000..4edcefe06 --- /dev/null +++ b/imports/ui/stylesheets/messageDialog.less @@ -0,0 +1,65 @@ +.messageDialog{ + .clear{clear: both;} + .message{ + background-color: #EBEBEB; + .time{ + text-align: center; margin: 0 auto; + span{width: auto;display:inline-block; margin: 0 auto; background-color: #CECECE; color: #fff; padding: 3px 5px;border-radius: 5px;} + } + > div{ + position: relative; padding: 10px 10px; + .avatar img{width: 50px; height: 50px; border-radius: 5px; position: absolute;left: 10px; top: 10px; border:1px solid #CDD6CB;background-color: #fff;} + .text{ + min-height: 50px; float: left;border: 1px solid #CDD6CB; border-radius: 5px;background-color: #fff;color: #000;padding: 8px;font-size: 16px; + img{max-width: 100%;} + } + } + > div.left{ + padding-left: 80px; + .name{padding-bottom: 5px; margin-top: -5px; color: #737373;} + .text:after{width: 16px; height: 25px;position: absolute; left: 65px; top: 22px; background-image: url(/chat_left.png); content:"\00a0";} + .text.group{min-height: 30px;} + .text.group:after{top: 37px;} + } + > div.right{ + padding-right: 80px; + .avatar img{left: auto;right: 10px;} + .text{float: right;background-color: #A2E65B;} + .text:after{width: 16px; height: 25px;position: absolute; right: 66px; top: 22px; background-image: url(/chat_right.png); content:"\00a0";} + } + } + .messageDialogInput{ + width: 100%; height: 52px; line-height: 52px;background-color: #fff; + position: fixed;left: 0;bottom: 0;border-top: 1px solid #C4C4C6;z-index: 9999999999999; + padding-left: 50px; padding-right: 5px; + .img{ + position: absolute;left: 5px; top: 5px;text-align: center; + i{width: 40px; height: 40px; line-height: 40px; display: block;font-size: 18px; border-radius: 50%;border: 1px solid #D2D2D2;color: #999999;} + } + .input{ + height: 40px; line-height: 40px; margin-top: 5px;padding: 0px 5px; border: 1px solid #D2D2D2;border-radius: 5px;color: #999999; + input{height: 40px !important; padding: 3px 5px; border: 1px solid #D2D2D2;border-radius: 5px;border-bottom: 0;} + } + .send{ + width: 50px; height: 40px; position: absolute;right: 5px;top: 5px; + .submit{width: 100%; height: 38px; line-height: 38px; border: 1px solid #D2D2D2; text-align: center;background-color: #fff; color: #999999; border-radius: 5px;} + } + } + .messageDialogInputForm{ + margin-top: -10px; + .head{ + 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: #04BE02;font-weight: normal; + i{color: #fff;} + } + } + form{ + padding: 10px; + .text{ + border-radius: 5px; padding: 5px; width: 100%;border: 1px solid #D2D2D2;height: 60px; + } + } + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/messageDialogInfo.less b/imports/ui/stylesheets/messageDialogInfo.less new file mode 100644 index 000000000..3e09f34c0 --- /dev/null +++ b/imports/ui/stylesheets/messageDialogInfo.less @@ -0,0 +1,20 @@ +.messageDialogInfo{ + background-color: #EFEFF4; margin-bottom: -20px; + .set-up{ + .users{ + .item{ + width: 60px;height: 80px; display: inline-block;margin: 5px;position: relative; + img{width: 60px; height: 60px; border-radius: 5px; margin-bottom:3px;} + span{display: block;width: 100%; text-align: center;} + .add{ + width: 60px; height: 60px; text-align: center;border:1px solid #D9D9D9;border-radius: 5px; + i{font-size: 40px;height: 60px; line-height: 60px;color: #D9D9D9;} + } + } + } + } + .buttons{ + margin-top: -20px; padding: 0px 10px; + button{width: 100%;} + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/messageGroup.less b/imports/ui/stylesheets/messageGroup.less new file mode 100644 index 000000000..78d2b1801 --- /dev/null +++ b/imports/ui/stylesheets/messageGroup.less @@ -0,0 +1,15 @@ +.messageGroupCreateName{ + margin-top: -10px; + .head{ + position: relative; text-align: center; color: #fff; font-weight: bold; margin-bottom: 10px; + .left-btn{position: absolute; left: 10px; top: 0px;color: #fff;font-weight: normal;} + .right-btn{ + position: absolute; right: 10px; top: 0px;color: #04BE02;font-weight: normal; + i{color: #fff;} + } + } + form{ + padding: 0px 10px; + input{} + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/newPostsMsg.less b/imports/ui/stylesheets/newPostsMsg.less new file mode 100644 index 000000000..cbca695b3 --- /dev/null +++ b/imports/ui/stylesheets/newPostsMsg.less @@ -0,0 +1,20 @@ +.new-posts-msg{ + padding-top: 40px; + ul,li{margin: 0; padding: 0;} + ul{padding: 0px 10px;} + li{ + padding: 10px 0px; position: relative; padding-left: 60px; padding-right: 40px; box-sizing: border-box; border-top: 1px solid #ccc; + .icon{ + width: 45px; height: 48px; position: absolute; left: 0px; top: 15px; + img{width: 100%; height: 100%; border-radius: 5px;} + } + .name,.time{color: gray;} + .time{ + margin-top: 10px; font-size: 14px; + i{padding-right: 5px;} + } + .text{font-size: 16px; color: gray;} + .more{width: 14px; height: 14px; line-height: 14px; position: absolute; right: 10px; top: 50%; margin-top: -7px; color: gray;} + } + li:first-child{border-top: none;} +} \ No newline at end of file diff --git a/imports/ui/stylesheets/padding.less b/imports/ui/stylesheets/padding.less new file mode 100644 index 000000000..1ba4f53af --- /dev/null +++ b/imports/ui/stylesheets/padding.less @@ -0,0 +1,11 @@ +.padding-overlay{ + height: 0; + width: 0; +} +.padding { + font-size: 25px; + position: relative; + display: inline-block; + width: 1em; + height: 1em; +} \ No newline at end of file diff --git a/imports/ui/stylesheets/postNotFound.less b/imports/ui/stylesheets/postNotFound.less new file mode 100644 index 000000000..89f0b5830 --- /dev/null +++ b/imports/ui/stylesheets/postNotFound.less @@ -0,0 +1,28 @@ +.showPosts .content .loading { + .banner-img{ + background-image: url("/reviewing.png"); + height: 25%; + width: 100%; + background-repeat: no-repeat; + margin: auto; + background-position-x: center; + background-size: auto 100%; + position: relative; + margin-bottom: 20px; + } + p{ + color: grey; + } +} +.showPosts .content .empty { + min-height: 100%; + height: 100%; + position: absolute; + top:0; + bottom: 0; + left: 0; + right: 0; + background-color: #111; + margin: auto; + text-align: center; +} \ No newline at end of file diff --git a/imports/ui/stylesheets/progressBar.less b/imports/ui/stylesheets/progressBar.less new file mode 100644 index 000000000..a6695e17b --- /dev/null +++ b/imports/ui/stylesheets/progressBar.less @@ -0,0 +1,17 @@ +.progressBarBg {position: fixed;top: 0;background-color: #fff;bottom: 0;left: 0;right: 0;z-index: 999999999999;} +.progressBar { + position:absolute; + left: 5%; + width:90%; + height:50%; + top:45%; + .progress{ + height: 10%; + font-size: 100%; + } + .btnDelayPublish{ + width:100%; + position:absolute; + bottom:20px; + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/reportPost.less b/imports/ui/stylesheets/reportPost.less new file mode 100644 index 000000000..682c6ced3 --- /dev/null +++ b/imports/ui/stylesheets/reportPost.less @@ -0,0 +1,49 @@ +.reportPost{ + background-color:white; + overflow-x: hidden; + .content{ + overflow-y: auto; + background-color:#fbfbfb; + color: #333; + border-radius: 0px; + position: absolute; + bottom: 0px; + top: 0px; + width: 100%; + padding-top: 80px; + padding-bottom:30px; + #title{ + font-size: 18px; + font-weight:bold; + color:#000000; + text-align: center; + } + #reason{ + position: absolute; + left:5%; + right: 5%; + color:#000000; + background-color:#ffffff; + width:90%; + height:180px; + margin-top: 30px; + margin-bottom:30px; + border-color: #f0f0f0; + border-radius:6px; + } + #save{ + position: absolute; + margin-top:230px; + left: 5%; + right: 5%; + width: 90%; + text-align: center; + border: none; + border-radius: 50px; + padding: 10px; + background-color: #00c4ff; + color:white; + font-size: 16px; + } + } +} diff --git a/imports/ui/stylesheets/search.less b/imports/ui/stylesheets/search.less new file mode 100644 index 000000000..786fe8db8 --- /dev/null +++ b/imports/ui/stylesheets/search.less @@ -0,0 +1,147 @@ +.search .content{position:relative;padding-bottom: 60px;} +.search .content .searchForm{position:fixed;z-index:11;margin:0;padding:0;top:50px;left:10px;right:10px;color:#fff; font-weight: normal;border: solid;border-radius: 20px;border-color: #888;border-width: thin;padding: auto;padding-top: 5px;} +.search .content .searchForm label{display:block;width:72%;float: left; overflow: hidden} +.search .content .searchForm input{border:none; background-color:transparent;color:#fff;width:100%} +.search .content .searchForm input:focus{outline:none;} +.search .content .searchForm i{float:left;padding: 5px 15px;font-size: 12px;} +.search .content #themeList{margin-top: 100px;} +.search .content #themeList .block{position: relative} +.search .content #themeList .block h3{position: absolute;height: 150px;margin: 0px;width: 100%;text-align: center;top: 0;padding-top: 70px;background-color: rgba(0,0,0,0.3);} +.search .content .theme{height: 150px;background-position: center center;background-size: 100% auto;text-align: center; background-repeat: no-repeat;padding-top: 50px;} +.search .content #topic{color: #888;text-align: center;padding: 20px;} +.search .content #topicList{margin:0;padding:0;overflow:hidden;list-style-type:none;text-align: center} +.search .content #topicList li{float:left;width:46%;margin-bottom:8px;padding: 4px;border: solid;border-radius: 20px;border-color: #888;border-width: thin;} +.search .content #topicList li:nth-child(n){margin-right: 1%;margin-left: 3%;} +.search .content #topicList li:nth-child(2n){margin-left: 1%;margin-right: 3%;} + +.searchFollow .content {position:relative;padding-top: 50px;padding-bottom: 65px;} +.searchFollow .content .searchForm{position:relative; margin:0; padding:0; margin-left:10px;margin-right:10px;color:#fff; font-weight: normal;border: solid;border-radius: 20px;border-color: #888;border-width: thin;padding: auto;padding-top: 5px; min-height: 35px;} +.searchFollow .content .searchForm label{display:block;width:72%;float: left; overflow: hidden} +.searchFollow .content .searchForm input{border:none; background-color:transparent;color:#fff;width:100%} +.searchFollow .content .searchForm input:focus{outline:none;} +.searchFollow .content .searchForm i{float:left;padding: 5px 15px;font-size: 12px;} +.searchFollow .content .contentList{display: block;position:relative;min-height: 50px;} +.searchFollow .content .icon{position: absolute; left:0px; margin: 10px;border: none;border-radius:50%;} +.searchFollow .content .userName{position: absolute;top:15px;left: 50px;color: #FFF} +.searchFollow .content .follow{position: absolute;top: 10px;right: 10px;width: 30px;height: 30px;text-align: center; display: table;border-radius: 50%;border: thin solid #fff;background-color: #fff;color: #000;} +.searchFollow .content .follow span{position: relative;bottom: 8px;font-size:8px;padding-top: 12px} +.searchFollow .content .desc{padding: 0 15px;padding-top: 50px} +.searchFollow .line{text-align:center;width: 100%; height: 1px; padding: 5px 0px;} +.searchFollow .line span{background: #444; width: 100%; height: 1px; display: block;} +.searchFollow .content a,a:hover,a:focus{text-decoration: none;color: #bbb;} +.searchFollow .content .stitle{position: relative;margin-top:10px; margin-left:10px;color: #bbb} +.searchFollow .content .searchForm .dropdown{ + cursor:pointer; + #search-people-menu{ + float: left; + margin-left: 9px; + margin-top: 3px; + color: black; + } + .search-people-dropdown-menu{ + position: absolute; + padding: 0; + top: 46px; + font-size: 16px; + background: rgba(51, 51, 51, 1) !important; + border-radius: 5px; + li{ + background-color:transparent !important; + font-size: 16px; + } + .dropdown-menu-item{ + padding: 8px; + text-align: center; + } + .divider{ + background-color: rgba(65, 65, 65, 1)!important; + height: 1px; + margin: 0px; + } + } + .search-people-dropdown-menu:before{ + content: "\f0d8"; + position: absolute; + top: -18px; + left: 20px; + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: 26px; + color: rgba(51, 51, 51, 1); + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } +} +.searchPeopleAndTopic .content {position:relative;padding-top: 5px;padding-bottom: 55px;} +.searchPeopleAndTopic .content .searchForm{position:relative; margin:0; padding:0; margin-top:5px; margin-left:10px;margin-right:0px;color:#fff; font-weight: normal;border: solid;border-radius: 20px;border-color: #444;border-width: thin;padding: auto;padding-top: 5px; min-height: 35px;} +.searchPeopleAndTopic .content .searchForm label{display:block;width:72%;float: left; overflow: hidden} +.searchPeopleAndTopic .content .searchForm input{border:none; background-color:transparent;color:#fff;width:100%} +.searchPeopleAndTopic .content .searchForm input:focus{outline:none;} +.searchPeopleAndTopic .content .searchForm i{float:left;padding: 5px 15px;font-size: 12px;} +.searchPeopleAndTopic .content table{width: 100%;} +.searchPeopleAndTopic .content table td{text-align: center; vertical-align: middle; padding: 0 3px;} +.searchPeopleAndTopic .content table td .submit{} + +.searchPeopleAndTopic .content .contentList{display: block;position:relative;min-height: 50px;} +.searchPeopleAndTopic .content .icon{position: relative; left:0px; margin: 10px;border: none;border-radius:50%;} +.searchPeopleAndTopic .content .userName{position: absolute;top:15px;left: 50px;color: #FFF} +.searchPeopleAndTopic .content .follow{position: absolute;top: 10px;right: 10px;width: 30px;height: 30px;text-align: center; display: table;border-radius: 50%;border: thin solid #fff;background-color: #fff;color: #000;} +.searchPeopleAndTopic .content .follow span{position: relative;bottom: 8px;font-size:8px;padding-top: 12px} +.searchPeopleAndTopic .content .desc{padding: 0 15px;} +.searchPeopleAndTopic .line{text-align:center;width: 100%; height: 1px; padding: 5px 0px;} +.searchPeopleAndTopic .line span{background: #444; width: 100%; height: 1px; display: block;} +.searchPeopleAndTopic .content a,a:hover,a:focus{text-decoration: none;color: #bbb;} +.searchPeopleAndTopic .content .stitle{position: relative;margin-top:10px; margin-left:10px;color: #bbb} + +.searchPeopleAndTopic .content .my_btns{margin: 0; margin-top:5px; margin-bottom:5px; padding:0; list-style-type: none; overflow: hidden;border-top: 1px solid #444;border-bottom: 1px solid #444; background-color: #000} +.searchPeopleAndTopic .content .my_btns li{float: left; padding:10px 0;width:33%; text-align: center; color:#444} +.searchPeopleAndTopic .content .my_btns li.on{background-color: #444; color:#fff} +.searchPeopleAndTopic .content .my_btn2 li{width: 50%} + +.searchPeopleAndTopic .content .topicTitle{position: relative;padding: 8px 15px;} +.searchPeopleAndTopic .content .topicTitle span{position: absolute;right: 10px; color: #bbb;} +.searchPeopleAndTopic .content .topicTitle span i{margin-left: 5px; color:#ffffff;} +.searchPeopleAndTopic .content .searchForm .dropdown{ + cursor:pointer; + #search-people-menu{ + float: left; + margin-left: 9px; + margin-top: 5px; + color: black; + } + .search-people-dropdown-menu{ + position: absolute; + padding: 0; + top: 46px; + font-size: 16px; + background: rgba(51, 51, 51, 1) !important; + border-radius: 5px; + li{ + background-color:transparent !important; + font-size: 16px; + } + .dropdown-menu-item{ + padding: 8px; + text-align: center; + } + .divider{ + background-color: rgba(65, 65, 65, 1)!important; + height: 1px; + margin: 0px; + } + } + .search-people-dropdown-menu:before{ + content: "\f0d8"; + position: absolute; + top: -18px; + left: 20px; + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: 26px; + color: rgba(51, 51, 51, 1); + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } +} diff --git a/imports/ui/stylesheets/showPosts.less b/imports/ui/stylesheets/showPosts.less new file mode 100644 index 000000000..100176feb --- /dev/null +++ b/imports/ui/stylesheets/showPosts.less @@ -0,0 +1,852 @@ +.hidden{display: none} +.showBgColor{background-color:white;width:100% !important;} +.showPosts{background-color:white;overflow-x: hidden;} +.showPosts .head{background-color:#F0F0F0;color: gray !important;opacity: 0.95;} +.showPosts .head i{color: gray !important;} +.showPosts .head .dropdown{position:absolute; right: 0px; top:0px; cursor:pointer;text-align:right;} +.showPosts .head .dropdown.open{width:100%;right:0px;} +.showPosts .head .dropdown-menu{width:100%;font-size:120%;} +.showPosts .head .dropdown .btn{color:gray;margin-bottom: 0px;margin-top:0px;padding-left:3%;padding-right: 3%;} +.showPosts .head .dropdown i{color:black !important;} +.showPosts .head .dropdown-menu .divider{background-color: rgba(229, 229, 229, 1);} +.showPosts .content{padding-top: 40px; padding-bottom: 30px;} +.showPosts .content .mainImage{position: relative;overflow: hidden;margin-bottom: 5px;} +.showPosts .contentList{position:relative;} +.showPosts .content .img{clear: both; display: block; margin:auto;} +.showPosts .content .text{word-break: break-all;white-space: pre-line;width: 65%;text-align: center;margin:5% auto;font-size: 20px;font-weight:bold;background:none;border:none;height:20px} + +// post-head +.post-head{margin:0; height: 40px;} +.post-head-left,.post-head-right{position:relative;float:left; height: 40px;line-height: 40px;width:64px;margin:0 -64px 0 0;} +.post-head-right{float:right;width:64px;margin:0 0 0 -64px;} +.post-user{float:left;width:100%; text-align: center;height: 40px;line-height: 40px;} +.post-user-content{margin:0 64px 0 64px;} +.post-user-help{display: table; margin: 0 auto;} +.post-user-right-content{margin: 2px 0 2px 40px;} +.post-user-left{position:relative;float:left;width:40px;margin-right:-40px;} +.post-user-right{float:right;width:100%;} + +.post-user-name,.post-user-create{height: 18px; line-height: 18px;text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + font-size: 12px; + word-break: break-all; +} +.post-user-name{font-size: 14px; font-weight: bold;} + +.post-user-icon{margin: 5px; object-fit: cover; border: none; border-radius: 50%;} + +.showPosts .content .post_header{ + overflow: hidden; + background: #f6f6f6; +} +.showPosts .content .post_abstract { + position: relative; + padding: 32px 16px 35px 16px; + margin: 30px 32px; + border: 1px solid #dbdbdb; +} +.showPosts .content .post_abstract:before{ + content: ""; + position:absolute; + top: 3px; + right: 3px; + bottom:3px; + left: 3px; + border: 1px solid #dbdbdb; +} +.showPosts .content .post_abstract .abstract_sentence{ + position: relative; + padding-right: 33px; + margin-bottom: 14px; + font-size: 16px; + color: #000; + line-height: 26px; +} +.showPosts .content .post_abstract .abstract_sentence:before{ + position: absolute; + font-size: 0; + color: #797979; + width: 9px; + height: 8px; + background: no-repeat; + background-size: 100%; + content: ""; + top: 3px; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAOBAMAAAA7w+qHAAAALVBMVEUAAAA/Pz8/Pz8/Pz9AQEA/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz9AQEBAQEBAQECjVS6AAAAADnRSTlMAUOXdDu111cK3poJYPFYfJCwAAAA/SURBVAjXY2BgcCuAEjxBAlBi3xIGCMH2ggFK3HsII+Y9gRFx7x4LQIh3794pwIkHxBB6794ZQAjhd48EwAQAOew9Z9cQjFEAAAAASUVORK5CYII="); +} +.showPosts .content .post_abstract .abstract_sentence span{ + position: relative; + left: 20px; +} +.showPosts .content .post_abstract .abstract_sentence:after{ + position: absolute; + font-size: 0; + color: #797979; + width: 9px; + height: 8px; + background: no-repeat; + background-size: 100%; + content: ""; + bottom: 3px; + right: 0px; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAOBAMAAAA7w+qHAAAAKlBMVEUAAAA/Pz8/Pz8/Pz8+Pj4/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz9AQEBAQEDWzkgUAAAADXRSTlMAUeXdD+111cK3poI8ZYuCQgAAAEBJREFUCNdjELp7WQBC2N69qwAh7t69e4EYQhdOCN+9GwAhGFjuToASjHcPQAmOawwQgmXtBighFMgAJcocoAQAAgc5gXBc0bQAAAAASUVORK5CYII="); +} +.showPosts .content .abstract_chapter{ + text-align: right; + font-size: 14px; + color: #9a9a9a; +} +.tts-stoper{ + position: fixed; + right: 9px; + bottom: 59px; + height: 59px; + width: 59px; + z-index: 100; +} + +.popUpBox { display:none; min-width: 100%;height:100%;position: absolute;background-color: #fff;top:0;left:0;} +.commentInputBox { min-width: 100%;min-height: 620px;height:100%;position: fixed;background-color: #fff;top:0;left:0;} +.userProfileBox { min-width: 100%;height:100%;position: absolute;background-color: #fff;top:0;left:0;} +.commentInputBox header{color: #111;background-color: #fff;width: 100%;height: 40px;font-size: 16px;line-height: 40px;position: absolute;left: 0;top: 0;text-align: center;z-index: 999;} +.commentInputBox header .rightButton{ position:absolute; right: 10px; top:0; cursor:pointer;text-align:right;color: grey} +.commentInputBox header .leftButton{ position:absolute; left: 10px; top:0; cursor:pointer;text-align:left;color: grey} +.commentInputBox #new-reply{ height: 40%} +.commentInputBox form .footer{position: relative;height: 30px;width: 100%;top: 0;background-color: #fff;} +.commentInputBox form .change{position: absolute;right:10px; height: 28px;line-height: 28px; padding: 1px 5px;color: #fff;background-color: #0078e7;border-radius: 5px;font-size:20px;} +.commentInputBox .commentArea{-webkit-user-select: auto !important; padding-top: 40px;border: 1px solid #c8c7cc;width: 100%;height:80%;color:black;font-size:16px} +.showPostsFollowMe{color:black;width:100%;font-size:16px;text-align:left;} +.showPostsFollowMe span{color:blue;width:100%;height:18px;font-size:20px;text-align:left;} +.showPostsFollowMe span i{color:green;} +.showPostsFollowMe span a:link {color:blue;font-size:20px;} +.showPostsFollowMe span a:visited {color:blue;font-size:20px;} +.showPostsFollowMe span a:hover {color:blue;font-size:20px;} +.showPostsFollowMe span a:active {color:blue;font-size:20px;} + +.tool-container .tool-items .section-forward {width: 100px;} +.tool-container .tool-items .post-tts {width: 60px;} + +.superChatIntroduce p{color:black;width:100%;font-size:16px;text-align:left;} +#report{color:#888;width:100%;height:20px;font-size:16px;text-align:right;} +.showPostsLine{width: 100%;background-color: #efeff4;height: 20px;} +#postFooter{width:100%; height:40px;font-size:16px; line-height:40px; position:fixed; left:0; bottom:0; border:none; background: #F0F0F0; z-index: 999;opacity: 0.99;color: gray !important;} +#postFooter .commentList{position: relative;font-size: 12px;margin-left: 10px;} +#postFooter .commentList i{padding: 0 3px;} +#postFooter .commentList .minHeart{position: absolute;} +#postFooter .commentList .minRetweet{position: absolute;top: -6px;left: 50px;} +#postFooter .commentList .minComment{position: absolute;left:30px;} +#postFooter .buttons{position: absolute;right: 5px;top: 0px;font-size: 20px;} +#postFooter .buttons .heart,.blueHeart,.retweet,.blueRetweet,.comment,.refresh,.toWarning,.toCommit{margin: 0 10px 0 20px} +.write{width: 100%; height: 210px; background: url("/img/bg.png") no-repeat center top; } +.write .showPhoto{overflow:hidden;display: block;text-align:center} +.write .showPhoto img{border-radius:120px;border:4px solid #fff;} +.author{font-family:"BorderWeb"; text-align:center;color:#000;font-size:20px;} +.author .spc{color:#FF9200} +.nav{width:100%; height: 39px; background: url("/img/nav.jpg") no-repeat center top } +.hotitle{width:100%; height: 44px;overflow:hidden;line-height:44px;padding-left:5%;} +.hotitle h2{font-family: "微软雅黑";padding-left:38px;color:#000;background: url("/img/icon-1.png") no-repeat +10px center;margin: 0;font-size:20px; height:30px;line-height:30px; overflow:hidden} +.hotitle h2 span{color:#7D7D7D; margin: 0;font-size:14px;} +.hotcont{width:100%; height:186px; overflow: hidden; text-align:center} +.hotcont dl{height:186px;overflow:hidden;display: inline-block} +.hotcont dl dt{height:127px;overflow:hidden;} +.hotcont dl dd{width:100px;line-height:40px;margin:0;text-align:center;color:#000;white-space:nowrap;text-overflow:ellipsis;} +.button-1{height: 101px;overflow:hidden;display: block;text-align:center;padding-top:28px;} +.writeBottom{width:100%; height:245px;background:url("/img/bg2.jpg")no-repeat center bottom;background-size:contain} +.writeBottom p{color:#727272; line-height: 30px;text-align:center;font-size:16px;padding-top:20px;color:#c2c0be;} +.writeBottom p span{color:#383838;font-size:18px;} + +#ViewOnWeb{ + width: 40%; + position: relative; + z-index: 9; + padding: 5px; + color: rgba(8, 101, 255, 0.93); + text-align: left; + letter-spacing: 1px; + font-size: 18px; + margin: 10px auto; + margin-left: 5%; +} + +/*Mobile*/ +@media screen + and (max-width: 768px) { + .like_img{width:25px;} + .like_div{width:32px;} + .count_div{right: 40px;font-size:8px;bottom: 6px;} + .big_like{width:50px !important;} + .count_like{width:16px !important;} + .like_count{bottom:-17px;} + .count{bottom:-12px; right:35px;height:90px;} + .count p{font-size: 12px;} +} + +/*Pad*/ +@media screen + and (min-width: 768px) { + .like_img{width:60px;} + .like_div{width:76px;} + .count_div{right: 90px;font-size:25px;bottom: 6px;} + .big_like{width:100px !important;} + .count_like{width:28px !important;} + .like_count{bottom:-46px;} + .count{bottom:-28px; right:50px;height:80px;} + .count p{font-size: 20px;} +} + +.full_like{height:100px;position:fixed;bottom:10px;left:50%;z-index:99999 !important;transform: translate(-50%, 0);} +.like_count{height:100px;position:fixed;right:15px;z-index:99999 !important;min-width: 1% !important;} +.count{position:fixed;z-index:99999 !important;min-width: 1% !important;} +.pcommentsList{ + position: fixed; + max-height: 90%; + background-color: #f2f2f2; + color: black; + right: 2%; + left: 2%; + z-index: 99999; + overflow-y: scroll; + border-radius: 5px; + -webkit-border-radius: 5px; + border: 1px #fff solid; + bottom: 40px; + -webkit-overflow-scrolling: touch; +} +.pcommentsList .pcomment .banner{ + text-align: center; + padding-bottom: 25px; + color: #aaa; +} +.pcommentsList .pcomment{ + position: relative; + background-color: #f2f2f2; + padding-top: 10px; + padding-bottom: 30px; +} + +.pcommentsList .pcomment .eachComment { + min-height: 90px; + display: inline-block; + width: 100%; +} +.authorInfos{ + .icon{ position: relative; margin: 32px auto; margin-bottom: 16px; border-radius: 50%; width: 70px; height: 70px; box-shadow: 1px 3px 13px #ccc; + img{border-radius: 50%;} + } + .authorNike{margin: 16px auto; margin-bottom: 8px; text-align: center; font-size: 16px;} + .subscribe{margin:8px auto; text-align: center;} + .tipAuthorHotPosts{margin:32px auto; text-align: center;color: #333;} + ul{ text-align: center; color: deepskyblue; margin: 0 auto; margin-bottom: 16px; padding: 0;} + dd{margin: 0;} +} +.postSeries{ + .postSeriesIntro{margin:32px auto; text-align: center;color: #333;} + .postSeriesTitle{ text-align: center; color: deepskyblue; margin: 0 auto; margin-bottom: 16px; padding: 0;} +} +.pcommentsList .pcomment .eachComment .icon{border-radius: 50%;margin: 10px;margin-top: 20px;} +.pcommentsList .pcomment .eachComment .userName{display: inline-block;font-weight: bold;color: #000} +.pcommentsList .pcomment .eachComment .createAt{display: inline-block;position:relative; color: #000;margin-right:-100px;} +.pcommentsList .pcomment .eachComment .commentDetail{margin-right: 20px;margin-bottom: 5px;margin-left: 50px;white-space: normal;word-break: break-all;} +.pcommentsList .pcomment .eachComment .personName { padding-left:0px; color:black; font-weight:600;} +.pcommentsList .pcomment .eachComment .personSay { padding-left:0px; color:#000; font-weight:300;word-wrap: break-word;} +.pcommentsList .pcomment .eachComment .round { -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } +.pcommentsList .pcomment .eachComment .time { color:#000; font-weight:300;position: absolute;right:5px;vertical-align: top;} +.pcommentsList .pcomment .eachComment .bubble{ + background-color: #FFFFFF; + border-radius: 5px; + box-shadow: 0 0 6px #B2B2B2; + display: inline-block; + padding: 10px 18px; + position: relative; + vertical-align: top; + float: left; + margin: 5px 45px 5px 20px; + border-color: #cdecb0; +} +.pcommentsList .pcomment .eachComment .bubble::before { + background-color: #FFFFFF; + content: "\00a0"; + display: block; + height: 16px; + position: absolute; + top: 11px; + transform: rotate( 29deg ) skew( -35deg ); + -moz-transform: rotate( 29deg ) skew( -35deg ); + -ms-transform: rotate( 29deg ) skew( -35deg ); + -o-transform: rotate( 29deg ) skew( -35deg ); + -webkit-transform: rotate( 29deg ) skew( -35deg ); + width: 20px; + box-shadow: -2px 2px 2px 0 rgba( 178, 178, 178, .4 ); + left: -9px; + } +.pcommentsList .pcomment .eachComment .bubble{ + background-color: #FFFFFF; + border-radius: 5px; + box-shadow: 0 0 6px #B2B2B2; + display: inline-block; + padding: 10px 18px; + position: relative; + vertical-align: top; + margin: 10px 10px; + border-color: #cdecb0; + width: 75%; + } + +.pcommentsList .input-group{ + background-color: rgba(255, 255, 255, 0.6); + font-size: larger; + border: none; + border-radius: 5px; + bottom: 40px; + position: fixed; + width: 91%; + } + .pcommentsList .input-group #pcommitReport{ + border-radius: 4px !important; + padding-left: 10px !important; + width: 80%; + float: left; + padding: 6px 12px; + } + .pcommentsList .input-group #pcommitReportBtn{ + width: 18%; + height: 30px; + float: right; + margin-top: 2px; + color: black; + } + .pcommentsList .accountIcon{ + width: 30px; + position: relative; + top: 5px; + } +.shareTheReadingRoom .banner .btnYes{ + width: 50%; + position: absolute; + font-weight: bold; + right: 0; + background: rgba(255, 255, 255, 0.99); + padding: 8px 0; + border-bottom-right-radius: 10px; +} + +.shareTheReadingRoom .banner .btnNo{ + position: absolute; + width: 50%; + border-right: 1px solid #ccc; + background: rgba(255, 255, 255, 0.99); + padding: 8px; + left: 0; + border-bottom-left-radius: 10px; +} + +.shareTheReadingRoom .title h4{ + color: #349fe2; +} +.shareTheReadingRoom .banner{ + position: relative; + color: #349fe2; +} +.share-room{ + position: fixed; + z-index: 99999; + top: 42px; + right: 30px; + text-align: center; + color: #fff; +} +.shareTheReadingRoom .title{ + border-bottom: 1px solid #ccc; + padding: 30px 0; + padding-top: 55%; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} +.shareTheReadingRoom{ + position: fixed; + z-index: 99999; + font-size: 16px; + top: 15%; + left: 5%; + right: 5%; + text-align: center; + color: #656565; + background-image: url('/sharereadingroombg.png'); + background-size: 100%; + background-repeat: no-repeat; +} + +.shareReaderClub{ + position: fixed; + z-index: 99999; + top: 30%; + left: 25px; + right: 25px; + text-align: center; + color: #000; + background: #fff; + border-radius: 10px; + display: none; +} + +.shareReaderClub .reader-club-head{ + position: relative; + text-align: center; + border-bottom: solid 1px #f1f2f4; +} + +.shareReaderClub .title{ + font-weight: bold; + padding: 10px; + margin: 0; + margin-bottom: 1px; +} + +.shareReaderClub .btnYes .fa-angle-right{ + color: #f1f2f4; + float: right; + font-size: 2em; +} + +.shareReaderClub .btnYes{ + padding: 10px; + padding-top: 0px; + text-align: left; + color: #03bb07; + position: relative; + line-height: 30px; +} + +.shareReaderClub .btnNo{ + position: absolute; + top: 10px; + color: #03bb07; + left: 10px; +} + +.shareReaderClub .contant .mini-main-title{ + padding-top: 10px; + padding-bottom: 35px; + margin-bottom: 10px; + text-align: left; + width: 100%; + padding-left: 65px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-bottom: solid 1px #f1f2f4; +} + +.shareReaderClub .contant .mini-main-image{ + margin: 10px; + position: absolute; + left: 0; +} + +.shareReaderClubBackground{ + width: 100%; + height: 100%; + position: fixed; + top: 0px; + left: 0px; + z-index: 99998; + background: #111; + display: none; + opacity: 0.6; +} + +.pcommentInput{ + position: fixed; + z-index: 99999; +} +.pcommentInput .pcomment .banner{ + text-align: center; + padding-bottom: 25px; + color: #aaa; +} +.pcommentInput .pcomment{ + position: relative; + background-color: #f2f2f2; + padding-top: 10px; + padding-bottom: 30px; +} + +.pcommentInput .pcomment .eachComment { + min-height: 90px; + display: inline-block; + width: 100%; +} + +.pcommentInput .pcomment .eachComment .icon{border-radius: 50%;margin: 10px;margin-top: 20px;} +.pcommentInput .pcomment .eachComment .userName{display: inline-block;font-weight: bold;color: #000} +.pcommentInput .pcomment .eachComment .createAt{display: inline-block;position:relative; color: #000;margin-right:-100px;} +.pcommentInput .pcomment .eachComment .commentDetail{margin-right: 20px;margin-bottom: 5px;margin-left: 50px;white-space: normal;word-break: break-all;} +.pcommentInput .pcomment .eachComment .personName { padding-left:0px; color:black; font-weight:600;} +.pcommentInput .pcomment .eachComment .personSay { padding-left:0px; color:#000; font-weight:300;word-wrap: break-word;} +.pcommentInput .pcomment .eachComment .round { -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } +.pcommentInput .pcomment .eachComment .time { color:#000; font-weight:300;position: absolute;right:5px;vertical-align: top;} +.pcommentInput .pcomment .eachComment .bubble{ + background-color: #FFFFFF; + border-radius: 5px; + box-shadow: 0 0 6px #B2B2B2; + display: inline-block; + padding: 10px 18px; + position: relative; + vertical-align: top; + float: left; + margin: 5px 45px 5px 20px; + border-color: #cdecb0; +} +.pcommentInput .pcomment .eachComment .bubble::before { + background-color: #FFFFFF; + content: "\00a0"; + display: block; + height: 16px; + position: absolute; + top: 11px; + transform: rotate( 29deg ) skew( -35deg ); + -moz-transform: rotate( 29deg ) skew( -35deg ); + -ms-transform: rotate( 29deg ) skew( -35deg ); + -o-transform: rotate( 29deg ) skew( -35deg ); + -webkit-transform: rotate( 29deg ) skew( -35deg ); + width: 20px; + box-shadow: -2px 2px 2px 0 rgba( 178, 178, 178, .4 ); + left: -9px; + } +.pcommentInput .pcomment .eachComment .bubble{ + background-color: #FFFFFF; + border-radius: 5px; + box-shadow: 0 0 6px #B2B2B2; + display: inline-block; + padding: 10px 18px; + position: relative; + vertical-align: top; + margin: 10px 10px; + border-color: #cdecb0; + width: 75%; + } + +.pcommentInput .input-group{ + font-size: larger; + bottom: 0px; + z-index: 999999; + position: fixed; + width: 100%; + left: 0; + height: 50px; + border: 1px solid #bfbfbf !important; + background-color: #fafafa !important; + } + .pcommentInput .input-group #pcommitReport{ + border-radius: 4px !important; + padding-left: 10px !important; + width: 80%; + float: left; + margin-left: 10px; + margin-top: 5px; + border: 1px solid #9b9b9b; + } + .pcommentInput .input-group #pcommitReportBtn{ + color: black; + text-align: center; + padding-top: 5px; + margin-top: 4px; + background-color: #9b9b9b; + margin-right: 5px; + width: 15%; + height: 36px; + float: right; + border: 1px solid #9b9b9b; + border-radius: 5px !important; + } + .pcommentInput .accountIcon{ + width: 30px; + position: relative; + top: 5px; + } + +.alertBackground{ + width:100%; + height:100%; + position:fixed; + top:0px; + left:0px; + z-index: 99998; + display:none; + background: rgba(130,130,130,0); +} + +.shareAlertBackground{ + .alertBackground(); +} + +.share-room-background{ + width:100%; + height:100%; + position:fixed; + top:0px; + left:0px; + z-index: 99998; + background: rgba(130,130,130,0); +} +.showPosts .content .abstract_comment {float: right;margin-top: 10px;font-size: 20px;text-align: center;width: 100%;border-top: 1px solid #ebebeb;} +.showPosts .content .abstract_comment .abstract_thumbsDown{position: relative;top: 8px;} +.showPosts .content .abstract_comment .abstract_thumbsUp{margin: 0 25%; position: relative; top: 3px;} +.showPosts .content .abstract_comment .abstract_commentGray{position: relative;top: 8px;} + +.showPostsBox .readmore { + position: relative; + z-index: 9; + padding: 5px; + color: rgba(8, 101, 255, 0.93); + box-shadow: 0px -12px 25px 12px rgba(255, 255, 255, 1); + width: 100%; + margin: 0px auto; + text-align: center; + letter-spacing: 1px; + .readMoreContent{ + background-color: #166fff; + color: white; + width: 90%; + border-radius: 15px; + text-align: center; + font-size: 20px; + margin: 0 auto; + i.fa { + margin-left: 3px; + padding: 10px; + } + } +} +.importing{background-color: #13C0F5; color: #fff; font-size: 12px; text-align: center; padding: 10px;} + +.pcommentInputPromptPage{ + z-index: 9999; position: fixed;top: 0; right: 0; bottom: 0; left: 0;display: none; background: none; background-color: rgba(0,0,0,.36); + .bg{z-index: 9;position: fixed;top: 0; right: 0; bottom: 0; left: 0;background: none;cursor: pointer;} + .content { + background: white; + position: absolute; + bottom: 0px; + width: 100%; + height: auto; + border-radius: 4px; + z-index: 99; + .header{ + line-height: 58px; + font-size: 13px; + text-align: center; + color: lightgray; + border-radius: 4px 4px 0 0; + } + .deleteComment{ + line-height: 48px; + font-size: 16px; + text-align: center; + color:red; + border-top: 1px solid #f1f1f1; + cursor: pointer; + } + .cancleBtn{ + line-height: 48px; + font-size: 16px; + text-align: center; + border-top: 6px solid #f1f1f1; + color: black; + cursor: pointer; + } + } +} +.subscribeAutorPage{ + z-index: 9999; position: fixed;top: 0; right: 0; bottom: 0; left: 0;display: none; background: none; background-color: rgba(0,0,0,.36); + .subscribeAutorPage_bg{z-index: 9;position: fixed;top: 0; right: 0; bottom: 0; left: 0;background: none;} + .subscribeAutorPage_content { + background: white; + position: absolute; + top: 0; + right:0; + bottom: 0; + left: 0; + margin: auto; + width: 240px; + height: 240px; + border-radius: 8px; + z-index: 99; + text-align: center; + .subscribeAutorPage_header{ + line-height: 48px; + font-size: 16px; + font-weight: 600; + border-radius: 4px 4px 0 0; + color: #00c4ff; + } + .subscribeAutorPage_body{ + font-size: 12px; + color: #909090; + textarea{ width: 100%; border: none;line-height: 1.5em;resize:none;outline:none; color: #777; font-size: 14px;} + .subscribeAutorPage_help-block{ margin: 4px 0; color: #909090;} + } + .subscribeAutorPage_footer{ + position: absolute; + width: 100%; + height: 48px; + box-sizing: content-box; + bottom: 0; + left: 0; + border-top: 1px solid #ccc; + .subscribeAutorPage_btn{ + width: 50%; + height: 48px; + line-height: 48px; + text-align: center; + color: #00c4ff; + float: left; + cursor: pointer; + box-shadow: none; + -webkit-tap-highlight-color:rgba(0,196,255,.16); + } + .subscribeAutorPage_okBtn{border-bottom-right-radius: 8px; border-left: 1px solid #ccc; background-color: #00c4ff; color: #fff;} + .subscribeAutorPage_cannelBtn{border-bottom-left-radius: 8px;} + } + } +} + +.show-post-new-message{ + padding: 4px 0px; + .msg-box{ + width: 150px; height: 48px; margin: 0px auto; background-color: #333; color: #fff; border-radius: 3px; padding: 5px; box-sizing: border-box; + .icon{ + width: 38px; height: 38px; float: left; + img{width: 100%; height: 100%;} + } + .tips{ width: 98px; height: 38px; line-height: 38px; float: left; text-align: center;} + } +} +.recommendstory{ + .description{ + text-align: center; + font-size: 16px; + margin: 32px 16px; + font-weight: bold; + letter-spacing: 1px; + line-height: 32px; + color: #4a4a4a; + cursor: pointer; + .shareStoryBtn{ + display: inline-block; + font-size: 14px; + color: #fff; + border: 1px solid #166FFF; + border-radius: 4px; + padding: 6px 12px; + background: #166FFF; + line-height: 16px; + } + } +} +.recommendStory { + background: #fff; height: 100%; + .fastImport{ + padding-top: 40px; + .shareNewStory{ + padding-left: 10px; + font-size: 16px; + font-family: "BorderWeb"; + font-weight: bold; + padding:10px; + color: #166fff; + } + .input-group{ + padding:10px; width: 100%; + #importUrl{ + display: inline-block; + width: 70%; + height: 48px !important; + padding: 10px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + } + #importBtn{ + display: inline-block; + margin-top: 6px; + padding: 8px 12px; + background-color: #166fff; + color: #fff; + border-radius: 6px; + float: right; + cursor: pointer; + border: 0px; + } + } + } + .storySource{ + padding: 16px; + label{ + font-size: 16px; + font-weight: bold; + font-family: "BorderWeb"; + padding-right: 6px;color: #5d5d5d; + display: inline-block; + } + .storyLists{ + padding: 0 !important; + font-size: 14px; + overflow-y: auto; + position: absolute; + bottom: 0; + left: 16px; + right: 0; + top: 240px; + li{list-style: none; margin-bottom: 16px; margin-right:16px; position: relative;} + li::after { + content: '分享'; + position: absolute; + bottom: 4px; + right: 0; + border-left: 1px dashed #808080; + color: #166FFF; + padding-left: 10px; + background: #fff; + line-height: 40px; + width: 50px; + text-align: center; + } + .imgPlaceHolder{ float: left;} + .postContent{margin-left: 120px;} + h2 {font-size: 14px; font-weight: bold; height: 30px; line-height: 30px; overflow: hidden; white-space: nowrap;text-overflow: ellipsis;margin: 0;color: #000;} + p{height: 50px;line-height: 25px;font-size:12px;overflow: hidden;margin: 0; color: #5d5d5d; margin-right: 50px; display: -webkit-box; -webkit-line-clamp:2; -webkit-box-orient: vertical;} + img{height: 80px;width: 110px;object-fit: cover;} + } + } + .importing-mask,.importing{ + position: absolute; + top: 0; right: 0; bottom: 0; left: 0; background: rgba(0,0,0,.36); + z-index: 9; + display: none; + } + .importing{ + background: white; + color: #6B6A6A; + width: 200px; + height: 200px; + top: 50%; + padding: 0; + margin-top: -100px; + left: 50%; + margin-left: -100px; + text-align: center; + border-radius: 4px; + } +} +.storySource input[type="radio"]{display: inline-block;} +.storySource input[type="radio"]:checked + label{color: #166fff;} diff --git a/imports/ui/stylesheets/signupForm.less b/imports/ui/stylesheets/signupForm.less new file mode 100644 index 000000000..f5f61c6c0 --- /dev/null +++ b/imports/ui/stylesheets/signupForm.less @@ -0,0 +1,56 @@ +.signupForm{ /*background-image: url(loginbg2.jpg);background-repeat: no-repeat;background-size: 100% auto; */ position: fixed;top: 0;bottom: 0;left: 0;right: 0;z-index: 1000} +.signupForm .deal{position: fixed;bottom: 25px;left: 20%;text-align: center;right: 20%;font-size: 12px;} +.signupForm .signupBody{ + padding-top:80px; + + .term_notice{ + color: #37a7fe; text-decoration: underline;margin-left: 5px; + } + #deal_check{ + background-color:#FFF; + border: solid 1px #FFF; + } + .input-group{ + width: 100%; + } +} +// .signupForm .signupBody input{background-color:rgba(255,255,255,0.4);color: #FFF;border: none;} +// .signupForm .signupBody input::-webkit-input-placeholder{color: #FFF;} +// .signupForm .signupBody input:focus{outline:none;} +.signupForm .signupBody #sub-registered{ + border-radius: 2px !important; + margin-top: 10px; + margin-left:10%; + width: 80%; + height: 64px; + font-size: 16px; +} + + + +.signupForm .signupBody div{border-radius:5px;text-align:left;overflow: hidden; margin-bottom: 10px;} +.signupForm .signupBody s{display: block; overflow: hidden; padding: 5px 0} +.signupForm .signupBody div label span{padding: 6px 12px;font-size: 24px;color:#ccc;font-weight: normal;line-height: 1;text-align: center;width: 1%;white-space: nowrap;vertical-align: middle;display: table-cell;} +.signupForm .signupBody div label input{z-index: 2;float: left;width: 93%;height: 30px;padding: 2px 4px;font-size: 14px;line-height: 1.42857143;border: none;margin-top:5px;} +.signupForm .signupBody div label textarea{z-index: 2;float: left;width: 93%;height: 60px;padding: 2px 4px;font-size: 14px;line-height: 1.42857143;border: none;margin-top:5px;} +.signupForm .signupBody ol {margin: 0; padding:10px 0 20px; list-style-type: none} +.signupForm .signupBody ol li{float: left;width:33%; text-align: left; font-size: 1em} +.signupForm .signupBody ol li:first-letter{font-size: 1.1em} +.signupForm .signupBody .term_notice_block {text-align: center;margin-top: 10px;font-size: 14px;} +.signupForm .signupBody .box{text-align: center;} +.signupForm .signupBody .text-box{font-size: 2em;border:1px solid #74daf3;display: inline;color: #74daf3;padding: 2px 5px} + +.signupHeader{width:100%; height:40px;font-size:16px; line-height:40px; position:fixed; left:0; top:0; border:none; text-align:center; z-index: 999;opacity: 0.99;background: #37a7fe} +.signupHeader .leftButton{ position:absolute; left: 5%;top:0;cursor:pointer;margin-top: 3px;text-align:left;} +.signupHeader .leftButton i{font-size: 2em;padding-right: 80px;color: #fff;} + +.bottom-img{ + position: fixed; + bottom: 10%; + left: 0; + right: 0; + width: 50%; + text-align: center; + margin-left: auto; + margin-right:auto; +} \ No newline at end of file diff --git a/imports/ui/stylesheets/socialBar.less b/imports/ui/stylesheets/socialBar.less new file mode 100644 index 000000000..77711927f --- /dev/null +++ b/imports/ui/stylesheets/socialBar.less @@ -0,0 +1,88 @@ +.socialContent{position: relative;background-color: #fff; z-index: 9;} +.socialContent .content{background-color:#fff;color: #333;padding-top: 10px;width: 100%;min-height: 100%;padding-bottom: 50px;} +.socialContent .content .chatBox .chatContentLine{background-color: #efeff4;height: 1px;margin-left: 10px;} +.socialContent .content .chatBox .chatBoxContent .eachViewer{position: relative} +.socialContent .content .chatBox .chatBoxContent .eachViewer .icon{margin: 10px;} +.socialContent .content .chatBox .chatBoxContent .eachChat{position: relative} +.socialContent .content .chatBox .chatBoxContent .eachChat .icon{margin: 10px;border-radius: 4px;} +.socialContent .content .chatBox .chatBoxContent .eachChat .userName{position: absolute;top:10px} +.socialContent .content .chatBox .chatBoxContent .eachChat .recentRecoveryTime{position: absolute;top: 10px;right: 10px; font-size: .8em;color: #9b9b9b;} +.socialContent .content .chatBox .chatBoxContent .eachChat .chatPreview{position: absolute;bottom: 10px;font-size: .98em;color: #9b9b9b;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;width: 75%;} +.socialContent .content .chatBox .chatFooter{position:fixed; left:0; bottom:0;height: 50px;border-top: solid 1px #999;background: #F0F0F0;width:100%; z-index: 99;} +.socialContent .content .chatBox .chatFooter .focusColor{color: #00c4ff;} +.socialContent .content .chatBox .chatFooter .chatBtn{position: relative;/*margin-left: 2%;*/width: 20%;text-align: center;display: inline-block; top: -18px;} +.socialContent .content .chatBox .chatFooter .chatBtn i{font-size: 22px} +.socialContent .content .chatBox .chatFooter .chatBtn div{font-size: 10px} +.socialContent .content .chatBox .chatFooter .chatBtn .red_spot{z-index: 99999; width: 18px; height: 18px; line-height: 18px;position: absolute;right:4px; top:3px;border-radius: 50%;font-size:12px; text-align: center;background: #FF0000; color: #fff; } +.socialContent .content .chatBox .chatFooter .postBtn{position: relative;width: 19%;text-align: center;display: inline-block;padding: 5px;} +.socialContent .content .chatBox .chatFooter .postBtn i{font-size: 22px} +.socialContent .content .chatBox .chatFooter .postBtn div{font-size: 10px} +.socialContent .content .chatBox .chatFooter .contactsBtn{position: relative;width: 19%;text-align: center;display: inline-block;padding: 5px;} +.socialContent .content .chatBox .chatFooter .contactsBtn i{font-size: 22px; position: relative;} +.socialContent .content .chatBox .chatFooter .contactsBtn div{font-size: 10px} +.socialContent .content .chatBox .chatFooter .contactsBtn .red_spot{z-index: 99999; width: 18px; height: 18px; line-height: 18px;position: absolute;right:-10px; top:-5px;border-radius: 50%;font-size:12px; text-align: center;background: #FF0000; color: #fff; } +.socialContent .content .chatBox .chatFooter .discoverBtn{position: relative;width: 19%;text-align: center;display: inline-block;padding: 5px;} +.socialContent .content .chatBox .chatFooter .discoverBtn i{font-size: 22px; position: relative;} +.socialContent .content .chatBox .chatFooter .discoverBtn div{font-size: 10px} +.socialContent .content .chatBox .chatFooter .discoverBtn .red_spot{z-index: 99999; width: 18px; height: 18px; line-height: 18px;position: absolute;right:-10px; top:-5px;border-radius: 50%;font-size:12px; text-align: center;background: #FF0000; color: #fff; } +.socialContent .content .chatBox .chatFooter .meBtn{position: relative;width: 19%;float: right;text-align: center;display: inline-block;padding: 5px;} +.socialContent .content .chatBox .chatFooter .meBtn i{font-size: 22px} +.socialContent .content .chatBox .chatFooter .meBtn div{font-size: 10px} +//addNewFriends +.socialContent .content .follow{position: absolute;top: 10px;right: 10px;width: 30px;height: 30px;text-align: center; display: table;border-radius: 50%;border: thin solid grey;background-color: grey;color: #fff;} +.socialContent .content .follow span{position: relative;bottom: 8px;font-size:8px;padding-top: 12px} + +.socialContent .content .chatBox .chatFooter{ + > div{ + position: relative; + .wait_read{width: 8px; height: 8px; background-color: #F43531; border-radius: 50%;position: absolute;right: 50%; top: 6px; margin-right: -15px;} + } +} +.socialContent .content .chatBox .chatBoxContent .eachViewer{ + position: relative; + .choose{ + text-align: right;position: absolute; right: 10px; top: 15px; + i{font-size: 20px; color: #C9C9C9;} + } +} + + +@-webkit-keyframes twinkling{ + 0% { + opacity:0.4; + } + 100%{ + opacity:1; + } +} + +.socialContent .content .chatBox .chatFooter .chatBtn { + .chat-icon-wrap { + position: relative; + // top: -18px; + width: 60px; + margin-left: auto; + margin-right: auto; + background-color: #F0F0F0; + // padding: 6px 6px 0px 6px; + border-top: 2px #999 solid; + border-top-left-radius: 50px; + border-top-right-radius: 50px; + } + + .chat-icon-img { + height: 43px; + padding-top: 6px; + } + + .chat-icon-img.twinkling { + animation: twinkling 1s infinite ease-in-out; + -webkit-animation: twinkling 1s infinite ease-in-out; + } +} + +.socialContent .content .chatBox .chatFooter .chatBtn.twinking { + i, div { + animation: twinkling 1s infinite ease-in-out; + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/splashScreen.less b/imports/ui/stylesheets/splashScreen.less new file mode 100644 index 000000000..c33d5cbdb --- /dev/null +++ b/imports/ui/stylesheets/splashScreen.less @@ -0,0 +1,37 @@ +html,body{ + height:100%; + margin:0; + width: 100%; +} +.swiper-container { + width: 100%; + height: 100%; +} + .swiper-slide { + text-align: center; + font-size: 18px; + background: #fff; + display: -webkit-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; +} +.swiper-slide img{ + width:100%; +} +.swiper-pagination-bullet-active { + background: white !important; +} +.swiper-pagination-bullet { + width: 10px !important; + height: 10px !important; + background: #ffa !important; + } \ No newline at end of file diff --git a/imports/ui/stylesheets/thanksReport.less b/imports/ui/stylesheets/thanksReport.less new file mode 100644 index 000000000..41140ee0b --- /dev/null +++ b/imports/ui/stylesheets/thanksReport.less @@ -0,0 +1,35 @@ +.thanksReport{ + background-color:white; + overflow-x: hidden; + .content{ + overflow-y: auto; + background-color:#fbfbfb; + color: #333; + border-radius: 0px; + position: absolute; + bottom: 0px; + top: 0px; + width: 100%; + padding-top: 100px; + padding-bottom:30px; + #title{ + font-size: 18px; + font-weight:bold; + color:#000000; + text-align: center; + } + #comment{ + position: absolute; + left:5%; + right: 5%; + color:#000000; + background-color:#ffffff; + width:90%; + height:180px; + margin-top: 30px; + margin-bottom:30px; + border-color: #f0f0f0; + border-radius:6px; + } + } +} diff --git a/imports/ui/stylesheets/unpublish.less b/imports/ui/stylesheets/unpublish.less new file mode 100644 index 000000000..fca5d6a65 --- /dev/null +++ b/imports/ui/stylesheets/unpublish.less @@ -0,0 +1,22 @@ +.templateunpublish{ + position: relative; + height: 100%; + padding-top: 40px; + padding-bottom: 30px; + .content{ + padding-top: 40%; + color: grey; + position: relative; + height: 100%; + .banner-img{ + background-image: url("/delete.png"); + height: 25%; + width: 100%; + background-repeat: no-repeat; + margin: auto; + background-position-x: center; + background-size: auto 100%; + margin-bottom: 20px; + } + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/user.less b/imports/ui/stylesheets/user.less new file mode 100644 index 000000000..4a7b2faec --- /dev/null +++ b/imports/ui/stylesheets/user.less @@ -0,0 +1,115 @@ +// .top{padding-top:80px; } +.top .icon{clear: both; display: block; margin:auto;width: 80px;height: 80px;border-radius:50%;box-shadow: 0 0 15px #fff, 0 0 3px #fff;} +.top .icon img{border: none;border-radius:50%;object-fit: cover;} +.top .icon i{display: block; text-align: center; font-size:24px; color: #939f3e;padding:10px 0 0 0;} +.top .userName{clear: both; display: block; margin:auto;margin-top: 20px;margin-bottom: 0px;font-weight: bold;font-size: 24px; height: 24px; line-height: 24px; width: 90%; text-align: center;overflow: hidden; word-break: break-all;} +.top .searchForm{position:absolute;height:40px;z-index:11;margin:0;padding:0;top:55px;left:10px;right:10px;color:#fff; font-weight: normal;border: solid;border-radius: 20px;border-color: #888;border-width: thin;padding: auto;padding-top: 0px;} +.top .searchForm label{display:block;width:100%;float: left; overflow: hidden} +.top .searchForm input{position: absolute; height:40px; padding-left: 40px;border:none; background-color:transparent;color:#fff;width:100%;} +.top .searchForm input:focus{outline:none;} +.top .searchForm i{position:absolute;float:left;padding: 12px 15px;font-size: 12px;} +#userPage{padding-bottom: 50px; overflow-x: hidden;} +.user .line{text-align:center;width: 100%; height: 1px; padding: 5px 25px;} +.user .line span{background: #888; width: 100%; height: 1px; display: block;} +.user .follow,.good{display: block;text-align: center;} +.user .good{margin: 20px auto;} +.user .follower,.praise{display: inline-block;} +.user .following,.dot{display: inline-block;} +.user .forward{display: inline-block;margin: auto 5px;} +.user .vertical{text-align:center;width: 1px; height: 10px; display: inline-block;margin: auto 5px;} +.user .vertical span{background: #bbb; width: 1px; height:10px ; display: block;} +.user .draft{margin: 0px 0px;} +.user .draftLeft{display: inline-block; position:absolute; left: 10px;cursor:pointer; text-align:left;} +.user .draftRight{display: inline-block; position:absolute; right: 5px; cursor:pointer; text-align:right;} +.user .draftImages{padding: 35px 0px;padding-top:35px;} +.user .draftImages ul {margin:0;padding:0;overflow:hidden;list-style-type:none;} +.user .draftImages ul li{position: relative; height:144px;float:left;width:46%;margin-bottom:8px;background-repeat: no-repeat;background-position:left center;background-size:cover;able-layout:fixed; word-break: break-all; overflow:hidden;} +.user .draftImages ul li{color:#fff;line-height:24px;padding-right:2%;padding-top:80px;text-shadow:2px 2px 2px #000, 0px 0px 10px #000, 0px 0px 20px #000;text-align:left;margin-right: 2%;margin-left:2%;} +.user .draftImages ul li:nth-child(2n){background-position:right center;} +.user .draftImages ul li .title{margin-left:10px;font-weight:bold;} +.user .draftImages ul li .addontitle{margin-left:10px;} +.user .post{margin: 0px 0px;} +.user .postLeft{display: inline-block; position:absolute; left: 10px;cursor:pointer; text-align:left;} +.user .postRight{display: inline-block; position:absolute; right: 5px; cursor:pointer; text-align:right;} +.user .postImages{padding-top:35px;} +.user .postImages ul {margin:0;padding:0;overflow:hidden;list-style-type:none;} +.user .postImages ul li{position: relative; height:144px;float:left;width:46%;margin-bottom:8px;background-repeat: no-repeat;background-position:left center;background-size:cover;} +.user .postImages ul li{color:#fff;line-height:24px;padding-right:2%;padding-top:80px;text-shadow:2px 2px 2px #000, 0px 0px 10px #000, 0px 0px 20px #000;text-align:left;margin-right: 2%;margin-left:2%;} +.user .postImages ul li:nth-child(2n){background-position:right center;} +.user .postImages ul li .title{margin-left:10px;font-weight:bold;} +.user .postImages ul li .addontitle{margin-left:10px;} +.user .rightButton #login-buttons a{text-decoration: blink;} +.user .rightButton #login-buttons #login-dropdown-list{background-color: #333;color: #fff;} +.user .css-post-title{position: absolute; bottom: 0;width: 100%;} + +.searchMyPosts .content {position:relative;padding-top: 5px;padding-bottom: 55px;} +.searchMyPosts .content .searchForm{position:relative; margin:0; padding:0; margin-top:0; margin-left:10px;margin-right:0px;color:#fff; font-weight: normal;border: solid;border-radius: 20px;border-color: #444;border-width: thin;padding: auto;padding-top: 0; min-height: 40px;} +.searchMyPosts .content .searchForm label{position:absolute; padding-left:40px; height:40px;display:block;width:100%;float: left; overflow: hidden;} +.searchMyPosts .content .searchForm input{border:none; background-color:transparent;color:#fff;width:100%; height: 40px;} +.searchMyPosts .content .searchForm input:focus{outline:none;} +.searchMyPosts .content .searchForm i{float:left;padding: 12px 15px;font-size: 12px;} +.searchMyPosts .content table{width: 100%; top: 10px;position: fixed;z-index: 9999;} +.searchMyPosts .content table td{text-align: center; vertical-align: middle; padding: 0 3px;} + +.searchMyPosts .content .contentList{display: block;position:relative;min-height: 50px;} +.searchMyPosts .content .icon{position: relative; left:0px; margin: 10px;border: none;border-radius:50%;} +.searchMyPosts .content .userName{position: absolute;top:15px;left: 50px;color: #FFF;} +.searchMyPosts .content .follow{position: absolute;top: 10px;right: 10px;width: 30px;height: 30px;text-align: center; display: table;border-radius: 50%;border: thin solid #fff;background-color: #fff;color: #000;} +.searchMyPosts .content .follow span{position: relative;bottom: 8px;font-size:8px;padding-top: 12px;} +.searchMyPosts .content .desc{padding: 0 15px;} +.searchMyPosts .line{text-align:center;width: 100%; height: 1px; padding: 5px 0px;} +.searchMyPosts .line span{background: #444; width: 100%; height: 1px; display: block;} +.searchMyPosts .content a,a:hover,a:focus{text-decoration: none;color: #bbb;} +.searchMyPosts .content .stitle{position: relative;margin-top:10px; margin-left:10px;color: #bbb} + +.searchMyPosts .content .my_btns{margin: 0; margin-top:5px; margin-bottom:5px; padding:0; list-style-type: none; overflow: hidden;border-top: 1px solid #444;border-bottom: 1px solid #444; background-color: #000;} +.searchMyPosts .content .my_btns li{float: left; padding:10px 0;width:33%; text-align: center; color:#444;} +.searchMyPosts .content .my_btns li.on{background-color: #444; color:#fff;} +.searchMyPosts .content .my_btn2 li{width: 50%} + +.searchMyPosts .content .topicTitle{position: relative;padding: 8px 15px;} +.searchMyPosts .content .topicTitle span{position: absolute;right: 10px; color: #bbb;} +.searchMyPosts .content .topicTitle span i{margin-left: 5px; color:#ffffff;} + + +.user .groupsList{ + padding: 10px; + .item_head{ + padding-bottom: 10px; + .check_all{ + right: 5px; + position: absolute; + } + } + i.fa-users{ + color: #37a7fe; + font-size: 16px; + margin: 10px; + } + +} + +.user .top{ + background: #fff; + border-radius: 4px; + padding: 32px 0; +} +.user .content{ + background: #efefef; +} + +.userCheckinBox{ + margin: 10px 0;background: #fff;padding: 10px; + .checkInOutStatus{ + position: relative; margin: 16px; + } + .checkInOutStatus:before{ + position: absolute; + content: ""; + top: 20px; + bottom: 20px; + left: 6px; + width: 1px; + background: #ccc; + } +} \ No newline at end of file diff --git a/imports/ui/stylesheets/userProfile.less b/imports/ui/stylesheets/userProfile.less new file mode 100644 index 000000000..4df74ee57 --- /dev/null +++ b/imports/ui/stylesheets/userProfile.less @@ -0,0 +1,40 @@ +.userProfile{background-color:#FFF;color: black;} +.userProfile .head div{font-size: 16px;font-weight: bold;color:white;} + +.userProfile .userProfileTop{padding: 10px;position: relative} +.userProfile .userProfileTop .icon{border-radius: 5px;margin-right: 10px;object-fit: cover;} +.userProfile .userProfileTop .userName{font-size: large;position: absolute; display: inline-block; margin-right: 80px; height: 22px; overflow: hidden; text-overflow:ellipsis; } +.userProfile .userProfileTop .userName img{margin-left: 5px; object-fit: cover;} +.userProfile .userProfileTop .location{position: absolute;top: 38px;color: grey;} +.userProfile .userProfileTop .desc{position: absolute;bottom: 18px;color: grey;} +.userProfile .userProfileTop .followMe{ + display: block;position: absolute; top: 0; right: 0;height: 100%; + .followBtn{ + display:inline-block;border: 1px solid deepskyblue;margin: 10px; + min-width: 64px;height: 26px;line-height: 25px; text-align: center; font-size: 14px; color: deepskyblue; border-radius: 4px; + } +} +#unFollowAuthor .followBtn{border: none;line-height: 26px;} + +.userProfile .post{position: relative;margin: 10px 0;} +.userProfile .post .postLeft{margin: 0 20px;color: grey;} +.userProfile .post .postRight{float: right;margin: 0 20px;color: deepskyblue} +.userProfile .postImages{padding-bottom: 15px;} +.userProfile .postImages ul{margin: 0;padding: 0;overflow: hidden;list-style-type: none;} +.userProfile .postImages ul li{color: grey;line-height: 24px;text-align: left;margin-left: 2%;float: left;width: 30%;margin-bottom: 8px;padding-bottom: 8px;box-shadow: 1px 1px 5px gray, 1px 1px 3px gray;table-layout:fixed; word-break: break-all; overflow:hidden;} +.userProfile .postImages ul li .postMainImage{height: 120px;margin: 8px;background-repeat: no-repeat;background-position: center;background-size: cover;} +.userProfile .postImages ul li .title{margin-left: 10px;margin-right: 0;margin-top: 0;margin-bottom: 0;} +.userProfile .postImages ul li .addontitle{margin-left:10px;margin-bottom: 0;margin-top: 0;} +.userProfile .postImages ul li .postInfo{font-size: 12px;margin-left: 10px;} +.userProfile .postImages ul li .postInfo i{margin-right: 5px;} + +.userProfile .viewPostImages ul{margin: 0;padding: 0 2%;overflow: hidden;list-style-type: none;} +.userProfile .viewPostImages ul li{margin-left: 2%;float: left;width: 17.5%;margin-bottom: 8px;padding-bottom: 8px;background-position: center;background-size: cover;} + +.userProfile #sendChatMessage{background-color: #0be00b;color: white;width: 90%;border-radius: 5px;text-align: center;font-size: 16px;padding: 10px;margin: 20px auto;} +.userProfile #addToContactList{background-color: #0be00b;color: white;width: 90%;border-radius: 5px;text-align: center;font-size: 16px;padding: 10px;margin: 20px auto;} +.userProfile #suggestCurrentPost{background-color: #0be00b;color: white;width: 90%;border-radius: 5px;text-align: center;font-size: 16px;padding: 10px;margin: 20px auto;} +.userProfile #suggestedCurrentPost{background-color: gray;color: white;width: 90%;border-radius: 5px;text-align: center;font-size: 16px;padding: 10px;margin: 20px auto;} + +.userProfileMarginTop {margin-top: 40px;} + diff --git a/lib b/lib new file mode 120000 index 000000000..73f59f565 --- /dev/null +++ b/lib @@ -0,0 +1 @@ +../SharpAIMobileApp/lib \ No newline at end of file diff --git a/lib/0_production_setup.coffee b/lib/0_production_setup.coffee deleted file mode 100644 index 17a23d822..000000000 --- a/lib/0_production_setup.coffee +++ /dev/null @@ -1,44 +0,0 @@ - -@withSocialBar = false -@withAfterPostIntroduce = false -@withChat = false -@withDiscover = true -@withAutoSavedOnPaused = true -@withPostSuggestionToUser = false -@withNewLayoutMoment = false -@withMusicSharing = true -@withSectionMenu = false -@withSectionShare = false -@withPostTTS = false -@withWeChatSignatureServer = false -@withLocalBase64 = true -@withSuggestAlreadyRead = true -@withSponserLinkAds = false -@isUSVersion = false -@withZhiFaCDN = false -@withServerImport = true -@withShareStoryGroup = false -@withDirectDraftShow = true -@withMobileBackendUploading = false -@withStaticPosts = false -@withNewFilePath=true -@withLanguageSetting=false -@withImportToEdit=false -@signUpUserNameLength = 16 -@withPostTitleMaxLength = 180 #byte -@withPostSubTitleMaxLength = 120 #byte -@withShowGroupsUserMaxCount = 10 -@withThumbInTextSection = false -@withUserInfoShowInPostHeader = false -@withInlineComment = false -@withInPostMassInformation = false -@withSetTopicAndShareAfterPost = false -@withThirdPartSocialSharing = false -@withFirstScreenTip = false -@withReadMoreButtonOnPost = false -@withEnableHaveReadMsg = false #已读消息 -@withSwitchNormalLabelMsg = true -@MQTT_TIME_DIFF = 0 -@withAccuracyFuzzinesssInTimeLine = true -@hideLableButtonWithNormalGroupUser = true # 群: 普通成员不显示 标记 相关按钮 -@withFollowFeature = true \ No newline at end of file diff --git a/lib/1_production_setup.coffee b/lib/1_production_setup.coffee deleted file mode 100644 index 3ea1077f9..000000000 --- a/lib/1_production_setup.coffee +++ /dev/null @@ -1,26 +0,0 @@ - -@withSocialBar = true -@withAfterPostIntroduce = false -@withChat = false -@withDiscover = true -@withAutoSavedOnPaused = false -@withPostSuggestionToUser = false -@withNewLayoutMoment = true -@withMusicSharing = true -@withSectionMenu = true -@withSectionShare = true -@withPostTTS = false -@withWeChatSignatureServer = false #(Meteor.isServer and process.env.ENABLE_WECHAT_SIGN_SERVER) || 0 -@withSuggestAlreadyRead = true -@withForcePopupSectionReview = false -@withSponserLinkAds = false -@withServerImport = true -@withSlackReporter = false -@withSkypeWebReporter = true -@withPostTitleMaxLength = 90 #byte -@withPostSubTitleMaxLength = 60 #byte -@signUpUserNameLength = 16 -@withShowGroupsUserMaxCount = 10 -@withEnableHomeAI = false -@withDefaultAccuracy = 0.85 #默认最低识别准确度 -@withNativeMQTTLIB = true diff --git a/lib/1_serverUrl.js b/lib/1_serverUrl.js deleted file mode 100644 index 43fb92da5..000000000 --- a/lib/1_serverUrl.js +++ /dev/null @@ -1,18 +0,0 @@ -server_domain_name = (typeof process !== "undefined" && process !== null ? process.env.SERVER_DOMAIN_NAME : void 0) || "workaicdn.tiegushi.com"; -//测试版 -//server_domain_name = "testworkai.tiegushi.com"; -if (withZhiFaCDN) { - server_domain_name = "cdcdn.tiegushi.com:8080"; -} -chat_server_url = 'chat.tiegushi.com'; -sign_server_url = 'http://sign.tiegushi.com:8080/sign/'; -import_server_url = 'http://urlanalyser.tiegushi.com:8080/import'; -import_cancel_url = 'http://urlanalyser.tiegushi.com:8080/import-cancel'; -ddp_alter_url = (typeof process !== "undefined" && process !== null ? process.env.DDP_ALTER_URL : void 0) || 'ws://localhost:5000/websocket'; -// import_server_url = 'http://192.168.1.84:8080/import'; -// import_cancel_url = 'http://192.168.1.84:8080/import-cancel'; -rest_api_url = "http://"+server_domain_name; -version_host_url = 'http://data.tiegushi.com/versions/workaiversion.json?t='+(Date.now()); -deepVideoServer = 'http://192.168.0.117:8000'; -review_post_url = "http://"+server_domain_name + '/restapi/postInsertHook/'; -//review_post_url = 'http://192.168.1.65:5000/restapi/postInsertHook/'; diff --git a/lib/2_collections.js b/lib/2_collections.js deleted file mode 100755 index 1464728ea..000000000 --- a/lib/2_collections.js +++ /dev/null @@ -1,966 +0,0 @@ -// Mongo.setConnectionOptions({server: {reconnectTries:Infinity}}); -Posts = new Meteor.Collection('posts'); -RePosts = new Meteor.Collection('rePosts'); -FollowPosts = new Meteor.Collection('followposts'); -Feeds = new Meteor.Collection('feeds'); -Drafts = new Meteor.Collection(null); -TempDrafts = new Meteor.Collection(null); -SavedDrafts = new Meteor.Collection('saveddrafts'); -Follows = new Meteor.Collection('follows'); -Follower = new Meteor.Collection('follower'); -Topics = new Meteor.Collection('topics'); -TopicPosts = new Meteor.Collection('topicposs'); -Comment = new Meteor.Collection('comment'); -Viewers = new Meteor.Collection('viewers'); -RefComments = new Meteor.Collection("refcomments"); -ReComment = new Meteor.Collection('recomment'); -Reports = new Meteor.Collection('reports'); -Meets = new Meteor.Collection('meets'); -Versions = new Meteor.Collection('versions'); -Moments = new Meteor.Collection('moments'); -BlackList = new Meteor.Collection('blackList'); -AssociatedUsers = new Meteor.Collection('associatedusers'); -UserRelation = new Meteor.Collection('userrelation'); // 用户关系,为了不和以前的产生冲突,使用新表 -PushMessages = new Meteor.Collection('pushmessages'); -UserCheckoutEndLog = new Meteor.Collection('usercheckoutendlog');//用户下班的最后一条消息 - -Recommends = new Meteor.Collection('recommends'); -Series = new Meteor.Collection('series'); -SeriesFollow = new Meteor.Collection('seriesfollow'); - -LogonIPLogs = new Meteor.Collection('loginiplogs'); - -Configs = new Meteor.Collection('configs'); - -// 删除帖子 -LockedUsers = new Meteor.Collection('lockedUsers'); -BackUpPosts = new Meteor.Collection('backUpPosts'); -reporterLogs = new Meteor.Collection('reporterLogs'); - -People = new Meteor.Collection('people'); -PeopleHis = new Meteor.Collection('peopleHis'); -Devices = new Meteor.Collection('devices'); - -Person = new Meteor.Collection('person'); -PersonNames = new Meteor.Collection('personNames'); -ClusterPerson = new Meteor.Collection('clusterPerson'); -AiMessages = new Meteor.Collection('ai_messages'); -/*Person = { - id:+++++
++ ++ ++ + +++ +{{_ "cancel"}}++++ {{#if showSearchStatus}} + {{#if noSearchResult}} ++{{_ "NoSearchResults"}} + {{/if}} + {{#if searchLoading}} +{{_ "Searching"}} + {{/if}} + {{/if}} + {{#if showSearchItems}} + {{#each items}} + {{#if showBigImage}} ++ {{#if mainImage}} + {{>padding noRandomBackgroundColor=true}} + {{/if}} ++ + {{else}} ++
+ +{{title}}
+{{addontitle}}
+++ {{/if}} + {{/each}} + {{/if}} ++ + +
+, - uuid: , - faceId: , - url: , - name: , - faces: [{id: , url: }] - deviceId: , - DeviceName: , - createAt: , - updateAt: -}*/ - -// 记录所有人的活动信息 -Activity = new Meteor.Collection('activity'); - -NLPTextClassName = new Meteor.Collection('nlpTextClassName'); -/* -NLPTextClassName = { - class_id: , - class_name: , - group_id: , - createAt: -} - */ - -//来了吗用户和平板识别出的人的关系表 -WorkAIUserRelations = new Meteor.Collection('workaiUserRelations'); - -/* -WorkAIUserRelations = { - app_user_id: //来了吗用户 - app_user_name: //来了吗用户名 - ai_persons:[{id:}] //平板识别的人 - ai_in_time: //平板检测到这个人的进门时间 - ai_out_time: //平板检测到这个人的出门时间 - checkin_time: //app标记进门时间 - checkout_time: //app标记出门的时间 - in_uuid: //进门UUID - out_uuid: //出门UUID - group_id: //组id -} - */ - -// WorkStatus -WorkStatus = new Meteor.Collection('workStatus'); -ClusterWorkStatus = new Meteor.Collection('clusterWorkStatus'); -/* -{ - app_user_id: //来了吗用户 - app_user_name: //来了吗用户名 - group_id: , // 组id - date: , // 20170810 - person_id: , // - person_name: , - status: , // in || out - in_status: , // normal || warning || error || unknown - out_status: , // normal || warning || error || unknown - whats_up: -} -*/ - -DeviceTimeLine = new Meteor.Collection('device_timeline'); -/* -{ - "hour":"", - "uuid":"", - "group_id":"", - "perMin":{ - "01":[{ - "person_id":"", - "person_name":"",// 允许为空 - "img_url":"", - "app_user_id":"",// 关联过有,未关联没有 - "app_user_name":"",// 关联过有,未关联没有 - "sqlid": "sqlid", - "style": "style", - "ts":"" - }], - "11":[{ - - }] - } -} -*/ - - -LableDadaSet = new Meteor.Collection('label_dataset'); -ClusterLableDadaSet = new Meteor.Collection('cluster_label_dataset'); -/* -{ - "id":"", - "name":"", - "group_id":"", - "url":'', - "sqlId":"", - "style": "left_side", 侧脸 || 正脸 - "createAt":'', - "operator":[{ - user_name:操作的人的app名字 - user_id:app的userid, - ts:操作时间 - action:具体的动作(标记,删除等) - }, - ... - ] -} -*/ - -Clustering = new Meteor.Collection('clustering'); -/* -{ - group_id: 'xx', - faceId: Id_xx, - totalFaces: 1, - url: 'http: //xx', - rawfilepath: '/dataset/A/1.png', - isOneSelf: true -} -*/ - - -//不可用的邮箱账号 -UnavailableEmails = new Meteor.Collection('unavailableEmails'); -/* -{ - address:'xx', - createAt:, - reason:'xx', -} - */ -Strangers = new Meteor.Collection('strangers') - -/*{ - "_id": "hnmnvA4pn4jdZXDSe", - "imgs": [{ - "faceid": "15330142402500000", - "url": "http://workaiossqn.tiegushi.com/fffc7fd4-9480-11e8-8abe-0242ac130006", - "img_type": "face", - "accuracy": 0, - "fuzziness": 92.90688987914469, - "sqlid": "0", - "style": "front" - }, { - "faceid": "15330142402500000", - "url": "http://workaiossqn.tiegushi.com/00744370-9481-11e8-8abe-0242ac130006", - "img_type": "face", - "accuracy": 0, - "fuzziness": 212.15898400936524, - "sqlid": "0", - "style": "front" - }], - "img_gif": "http://cdn.workaioss.tiegushi.com/Lorex_1_1533014240250.gif", - "group_id": "7e7013139ccafbbc369785d3", - "camera_id": "Lorex_1", - "uuid": "78c2c095d150", - "trackerId": 1533014240250, - "isStrange": true, - "createTime": ISODate("2018-07-31T05:17:25.007Z"), - "avatar": "http://workaiossqn.tiegushi.com/fffc7fd4-9480-11e8-8abe-0242ac130006" -}*/ - - // 陌生人 -NotificationFollowList = new Meteor.Collection('notification_follow_list'); -/* - _id : Meteor User ID - followed_to_user_id : 1 - ... -*/ -if (Meteor.isServer) { - Meteor.methods({ - getStrangers: function(group_id){ - var stranges = Strangers.find({}).fetch() - return stranges - }, - removeStrangers: function(id) { - Strangers.remove({_id: id}) - } - }) -} - - - -Cameras = new Meteor.Collection('cameras'); - -Faces = new Meteor.Collection('faces'); - -ModelParam = new Meteor.Collection('modelParam'); - - - -if(Meteor.isServer){ - MsgAlertLimit = new Mongo.Collection("alertlimit_app_reco_msg") - - // only send recognition msg (for one person) once within 2mins. - var expiresTime = 2*60 - MsgAlertLimit._ensureIndex({group_id: 1, uuid: 1, person_id: 1}, {expireAfterSeconds: expiresTime}); - MsgAlertLimit._ensureIndex({createdAt: 1}, {expireAfterSeconds: expiresTime}); - - checkIfSendRecoMsg = function(groupd_id,uuid,person_id){ - if(MsgAlertLimit.findOne({group_id: groupd_id, uuid: uuid, person_id: person_id})){ - console.log(MsgAlertLimit.findOne({group_id: groupd_id, uuid: uuid, person_id:person_id})) - return false; - } else { - MsgAlertLimit.insert({group_id: groupd_id, uuid: uuid, person_id:person_id, createdAt: new Date()}) - return true - } - } - - - KnownUnknownAlertLimit = new Mongo.Collection("alertlimit_known_unknown"); - - // 30分钟之内不要做重复的推送,什么是重复推送呢: - // 1 相同的人,同一个组里 - // 2 陌生人,同一个组里 - // Don't allow TTL less than 30 minutes so we don't break synchronization - var expiresAfterSeconds = 30*60; - - KnownUnknownAlertLimit._ensureIndex({group_id: 1, uuid: 1}, { expireAfterSeconds: expiresAfterSeconds }); - KnownUnknownAlertLimit._ensureIndex({createdAt: 1 }, { expireAfterSeconds: expiresAfterSeconds }); - - checkIfSendKnownUnknownPushNotification = function(groupd_id,uuid){ - if(KnownUnknownAlertLimit.findOne({group_id: groupd_id, uuid: uuid})){ - console.log(KnownUnknownAlertLimit.findOne({group_id: groupd_id, uuid: uuid})) - return false; - } else { - KnownUnknownAlertLimit.insert({group_id: groupd_id, uuid: uuid,createdAt: new Date()}) - return true - } - } - - Cameras.allow({ - insert: function(userId, doc){ - return true; - }, - update: function (userId, doc, fields, modifier) { - return true; - }, - remove: function(userId, doc){ - return true; - } - }) - - Devices.allow({ - insert: function(userId, doc){ - return true; - }, - update: function(userId,doc, fields, modifier) { - //return userId == doc.userId; - return true; - }, - remove: function(userId, doc){ - return true; - } - }); - - WorkAIUserRelations.allow({ - insert: function (userId, doc) { - return userId == doc.app_user_id; - }, - update: function(userId, doc, fields, modifier) { - if(userId && modifier['$set'].hide_it !== undefined){ - return true; - } - if(userId && modifier['$set'].app_user_id == userId ) { - return true; - } - return false; - } - }); - - PeopleHis.allow({ - update: function (userId, doc, fields, modifier) { - var user = Meteor.users.findOne({_id: userId}) - - if(modifier['$set'].fix_name){ - PERSON.setName(doc.uuid, doc.id, doc.aliyun_url, modifier['$set'].fix_name); - var people = People.find({id: doc.id, uuid: doc.uuid}); - if(people && people.name) - People.update({name: people.name}, {$set: {name: modifier['$set'].fix_name, updateTime: new Date()}}, {multi: true}); - else - People.update({id: doc.id, uuid: doc.uuid}, {$set: {name: modifier['$set'].fix_name, updateTime: new Date()}}); - } - return true; - } - }); - - Clustering.allow({ - update: function (userId, doc, fields, modifier) { - var user = Meteor.users.findOne({_id: userId}) - if(user && user.profile && user.profile.userType && user.profile.userType == 'admin'){ - return true; - } - return false; - } - }); - NotificationFollowList.allow({ - update: function(userId, doc, fields, modifier){ - return userId === doc._id - }, - insert: function(userId,doc){ - return userId === doc._id; - } - }); - Strangers.allow({ - remove: function(userId, doc) { - return true; - } - }); - Meteor.publish('getPushFollow', function () { - if(!this.userId){ - return this.ready(); - } - - return NotificationFollowList.find({_id: this.userId}); - }) - Meteor.publish('getStrangersByGroupId', function (group_id) { - if(!this.userId){ - return this.ready(); - } - - var limit = 50; - - return Strangers.find({group_id: group_id}, {sort: {createTime: -1}, limit: limit}); - }) - - Meteor.publish('getFaces', function (limit) { - if(!this.userId){ - return this.ready(); - } - - var limit = limit || 10; - var groupIds = []; - SimpleChat.GroupUsers.find({user_id: this.userId}).forEach(function (item) { - groupIds.push(item.group_id); - }); - - return [ - SimpleChat.GroupUsers.find({user_id: this.userId}), - Faces.find({group_id: {$in: groupIds}},{sort: {createdAt: -1}, limit: limit}) - ]; - }) - - // 发布:纠错 - Meteor.publish('clusteringLists', function(group_id, faceId, limit){ - if(!this.userId || !group_id || !faceId){ - return this.ready(); - } - var limit = limit || 30; - return Clustering.find({group_id: group_id, faceId: faceId, marked: {$ne: true}},{limit: limit}) - }); - - // 发布 group 已经标注的person 信息 - Meteor.publish('group_person', function(group_id, limit){ - if(!this.userId || !group_id){ - return this.ready(); - } - var limit = limit || 50; - return Person.find({group_id: group_id},{limit: limit,sort:{name: 1}}); - }); - - // 发布 group 已经标注的person 信息 - Meteor.publish('group_person_info', function(group_id){ - if(!this.userId || !group_id){ - return this.ready(); - } - return Person.find({group_id: group_id},{fields:{ - faceId:1,name:1,group_id:1,_id:1,url:1}}); - }); - Meteor.publish('group_cluster_person', function(group_id){ - if(!this.userId || !group_id){ - return this.ready(); - } - var limit = limit || 50; - return ClusterPerson.find({group_id: group_id},{limit: limit,sort:{createAt: -1}}); - }); - - Meteor.publish('cluster_person', function(group_id, limit){ - if(!this.userId || !group_id){ - return this.ready(); - } - var limit = limit || 50; - return ClusterPerson.find({group_id: group_id},{limit: limit,sort:{createAt: -1}}); - }); - - Meteor.publish('person_labelDataset',function(group_id,name,limit){ - if (!group_id || !name) { - return this.ready(); - } - var limit = limit || 50; - return LableDadaSet.find({group_id: group_id,name:name},{limit: limit,sort:{createAt: -1}}); - }); - - Meteor.publish('cluster_person_labelDataset',function(group_id,name,limit){ - if (!group_id || !name) { - return this.ready(); - } - var limit = limit || 50; - return ClusterLableDadaSet.find({group_id: group_id,name:name},{limit: limit,sort:{createAt: -1}}); - }); - - Meteor.publish('people_new', function(){ - return People.find({}, {sort: {updateTime: -1}, limit: 50}); - }); - - Meteor.publish('userGroups', function(){ - if(!this.userId){ - return this.ready(); - } - var groupIds = []; - var groups = SimpleChat.GroupUsers.find({user_id:this.userId}).fetch(); - for(var i = 0;i< groups.length; i++){ - groupIds.push(groups[i].group_id); - } - if(groupIds){ - return SimpleChat.Groups.find({_id: {$in: groupIds}}); - } - return this.ready(); - }); - - Meteor.publish('WorkStatus',function(date){ - if(!date){ - return this.ready(); - } - var groupIds = []; - var groups = SimpleChat.GroupUsers.find({user_id:this.userId}).fetch(); - for(var i = 0;i< groups.length; i++){ - groupIds.push(groups[i].group_id); - } - if(groupIds){ - return [ - WorkStatus.find({date: date,group_id:{$in:groupIds}}), - SimpleChat.Groups.find({_id: {$in: groupIds}}) - ]; - } - return this.ready(); - }); - - Meteor.publish('groupWorkStatusHistory', function(group_id, dates){ - if(!group_id || !dates){ - return this.ready(); - } - return WorkStatus.find({ date:{$in: dates},group_id: group_id},{sort:{date:-1}}) - }); - - Meteor.publish('userGroupsWorkstatusLists', function(date, limit) { - - if(!this.userId || !date) { - return this.ready(); - } - - var limit = limit || 3; - - var dates = []; - var groupIds = []; - SimpleChat.GroupUsers.find({user_id: this.userId},{limit: limit}).forEach(function (item) { - groupIds.push(item.group_id); - }); - - for(var i = 0; i < 30 ; i++){ - var d = date - (i * 24 * 60 * 60 * 1000); - dates.push(d); - }; - return WorkStatus.find({ date: {$in: dates},group_id:{$in:groupIds}}); - }); - - Meteor.publish('WorkStatusByGroup', function(date, group_id, status){ - if(!date || !group_id){ - return this.ready(); - } - var selector = { - date: date, - group_id: group_id - }; - selector.$or = [ - {in_image:{$nin:[null,'']}},{out_image:{$nin:[null,'']}} - ] - - if(status){ - selector.status = status - } - console.log(selector) - return WorkStatus.find(selector); - }); - - Meteor.publish('WorkStatusListsByGroup', function(date,group_id){ - if(!date || !group_id){ - return this.ready(); - } - var dates = []; - for(var i = 0; i < 30 ; i++){ - var d = date - (i * 24 * 60 * 60 * 1000); - dates.push(d); - }; - return WorkStatus.find({ date: {$in: dates},group_id:group_id}); - }); - - WorkStatus.allow({ - update: function(userId, doc, fields, modifier){ - //if(userId === doc.app_user_id){ - return true; - //} - //return false; - } - }); - - Meteor.publish('group_devices',function(){ - if(this.userId){ - var groupIds = [] - var groups = SimpleChat.GroupUsers.find({user_id:this.userId}).fetch(); - for(var i = 0;i< groups.length; i++){ - groupIds.push(groups[i].group_id); - } - if(groupIds){ - return [ - Devices.find({groupId: {$in:groupIds}}), - SimpleChat.GroupUsers.find({user_id:this.userId}) - ]; - } - } - return this.ready(); - }); - - Meteor.publish('device_by_groupId', function(groupId) { - if (!this.userId || !groupId) { - return this.ready(); - } - return Devices.find({groupId: groupId}); - }); - - Meteor.publish('group_workstatus', function(group_id, date){ - if (!this.userId) { - return this.ready(); - } - return [ - Devices.find({groupId: group_id}), - WorkStatus.find({date: date,group_id:group_id}) - ]; - }); - Meteor.publish('group_clusterworkstatus', function(group_id, date){ - if (!this.userId) { - return this.ready(); - } - return [ - Devices.find({groupId: group_id}), - ClusterWorkStatus.find({date: date,group_id:group_id}) - ]; - }); - Meteor.publish('devices-by-uuid',function(uuid){ - if(!this.userId || !uuid){ - return this.ready(); - } - return [ - Devices.find({uuid:uuid}), - Meteor.users.find({username: uuid}) - ]; - }); - Meteor.publish('commands', function (client_id){ - return Commands.find({client_id:client_id,done : false},{limit: 5,sort:{createdAt:1}}); - }); - Meteor.publish('latestboxversion', function (){ - return BoxVersion.find({}, {limit: 1}); - }); - - Meteor.publish('group-device-timeline', function(group_id,timeRange){ - if(!this.userId || !group_id || !timeRange) { - return this.ready(); - } - var selector = { - group_id: group_id, - hour: { - $gte: timeRange[0], - $lte: timeRange[1] - } - }; - - return DeviceTimeLine.find(selector, {sort:{hour: -1}}); - }); - - Meteor.publish('device-timeline', function(uuid,limit){ - var limit = limit || 10; - if(!this.userId || !uuid){ - return this.ready(); - } - var device = Devices.findOne({uuid: uuid}); - return [ - DeviceTimeLine.find({uuid: uuid},{sort:{hour:-1},limit: limit}), - Meteor.users.find({username: uuid}), - SimpleChat.Groups.find({_id: device.groupId}) - ]; - }); - - Meteor.publish('device-timeline2', function(uuid,selector,limit){ - var limit = limit || 10; - if(!this.userId || !uuid){ - return this.ready(); - } - var device = Devices.findOne({uuid: uuid}); - return [ - DeviceTimeLine.find(selector,{sort:{hour:-1},limit: limit}), - Meteor.users.find({username: uuid}), - SimpleChat.Groups.find({_id: device.groupId}) - ]; - }); - - Meteor.publish('device-timeline-with-hour', function(uuid,options,sort,limit){ - var limit = limit || 10; - if(!this.userId || !uuid){ - return this.ready(); - } - // console.log('publish device-timeline-with-hour'); - // console.log('uuid:'+uuid); - // console.log('options:'+JSON.stringify(options)); - // console.log('sort:'+sort); - // console.log('limit:'+limit); - var device = Devices.findOne({uuid: uuid}); - return [ - DeviceTimeLine.find({uuid: uuid,hour: options},{sort:{hour:sort},limit: limit}), - SimpleChat.Groups.find({_id: device.groupId}) - ]; - }); - - Meteor.methods({ - getPeopleIdByName: function(name, uuid){ - var people = People.findOne({name: name, uuid: uuid}, {sort: {updateTime: -1}}); - if(!people) - return ''; - - return {uuid: people.uuid, id: people.id}; - }, - //获取group下的设备列表 - getDeviceListByGroupId:function(group_id){ - var deviceList = Devices.find({groupId: group_id}).fetch(); - return deviceList; - } - }); - var Fiber = Meteor.npmRequire('fibers'); - deferSetImmediate = function(func){ - var runFunction = function () { - return func.apply(null); - } - if (typeof setImmediate == 'function') { - setImmediate(function () { - Fiber(runFunction).run(); - }); - } else { - setTimeout(function () { - Fiber(runFunction).run(); - }, 0); - } - } -} - -GetStringByteLength = function(str){ - return str ? str.replace(/[^\x00-\xff]/g, 'xx').length : 0; -} -if(Meteor.isServer) - PushSendLogs = new Meteor.Collection('pushSendLogs'); - -ReaderPopularPosts = new Meteor.Collection('readerpopularposts'); - -FavouritePosts = new Meteor.Collection('favouriteposts'); - -ShareURLs = new Meteor.Collection('shareURLs'); - -//推送设备token(同一手机只绑定最近一次登录的用户) -PushTokens = new Meteor.Collection('pushTokens'); - -if(Meteor.isClient){ - PostFriends = new Meteor.Collection("postfriends"); - PostFriendsCount = new Meteor.Collection("postfriendsCount"); - Newfriends = new Meteor.Collection("newfriends"); - ViewLists = new Meteor.Collection("viewlists"); - //User detail has duplicated information with postfriends, so only leave one to save traffic - //UserDetail = new Meteor.Collection("userDetail"); - DynamicMoments = new Meteor.Collection('dynamicmoments'); - NewDynamicMoments = new Meteor.Collection('newdynamicmoments'); - SuggestPosts = new Meteor.Collection('suggestposts'); -} - -if(Meteor.isServer){ - Rnd = 0; - try{ - suggestPostsUserId = Meteor.users.findOne({'username': 'suggestPosts' })._id; - } - catch(error) - { - //创建公共粉丝用户,关注所有推荐用户,从公共粉丝用户的FollowPosts里提取推荐帖子,加快推荐帖子速度 - suggestPostsUserId = Accounts.createUser({ - username:'suggestPosts', - password:'actiontec123', - email:'suggestposts@ggmail.com', - profile:{ - icon:'/follows/icon1.png', - desc:"留下美好的瞬间!就看我的!", - fullname:'伊人' - } - }); - } - Meteor.publish('allBlackList', function () { - return BlackList.find({blackBy:this.userId},{limit: 1}); - }); - Meteor.publish("usersById", function (userId) { - return Meteor.users.find({_id: userId}, { - fields: { - 'username': 1, - 'profile.fullname': 1, - 'profile.icon': 1, - 'is_device':1 - } - }); - }); - Meteor.publish("reports", function(postId) { - if(!Match.test(postId, String)) - return this.ready(); - else - return Reports.find({postId: postId},{limit:5}); - }); - Meteor.publish('versions', function() { - return Versions.find({}); - }); - - Meteor.publish('associatedusers', function() { - if(!this.userId){ - return this.ready() - } - var self = this; - var pub = this; - - var userA_Handle=AssociatedUsers.find({userIdA: self.userId}).observeChanges({ - added: function(_id, record){ - if(record.userIdB){ - Meteor.defer(function(){ - var userInfo=Meteor.users.findOne({_id: record.userIdB}, {fields: {username: 1, 'profile.icon': 1, 'profile.fullname': 1}}) - if(userInfo){ - pub.added('associatedusers', _id, record); - var userId=userInfo._id - delete userInfo['_id'] - pub.added('users', userId, userInfo); - } - }) - } - }, - changed: function(_id, record){ - try { - pub.changed('associatedusers', _id, record); - } - catch (e) { - } - - }, - removed: function(_id, record){ - pub.removed('associatedusers', _id, record); - } - }); - var userB_Handle=AssociatedUsers.find({userIdB: self.userId}).observeChanges({ - added: function(_id, record){ - if(record.userIdA){ - Meteor.defer(function(){ - var userInfo=Meteor.users.findOne({_id: record.userIdA}, {fields: {username: 1, 'profile.icon': 1, 'profile.fullname': 1}}) - if(userInfo){ - pub.added('associatedusers', _id, record); - var userId=userInfo._id - delete userInfo['_id'] - pub.added('users', userId, userInfo); - } - }) - } - }, - changed: function(_id, record){ - try { - pub.changed('associatedusers', _id, record); - } - catch (e) { - } - }, - removed: function(_id, record){ - pub.removed('associatedusers', _id, record); - } - }); - this.ready(); - this.onStop(function(){ - userA_Handle.stop(); - userB_Handle.stop(); - }); - return - }); - - Meteor.publish('userRelation', function() { - if(!this.userId) - return this.ready(); - - return UserRelation.find({userId: this.userId}); - }); - - Meteor.publish('workaiUserRelationsByGroup', function (group_id) { - if(!this.userId || !group_id){ - return this.ready(); - } - - return WorkAIUserRelations.find({group_id: group_id}); - }); - - Meteor.publish('associateduserdetails', function(userIds) { - if(userIds) { - return Meteor.users.find({_id: {"$in": userIds}}, {fields: {username: 1, 'profile.icon': 1, 'profile.fullname': 1}}); - } - else { - return this.ready(); - } - }); - - Meteor.publish('aiMessages.group.unread', function(groupId) { - console.log('aiMessages.group.unread',groupId); - return AiMessages.find({ groupId:groupId,isRead:false },{sort:{createdAt:-1}}); - }); - - LogonIPLogs.allow({ - insert: function (userId, doc) { - return doc.userId === userId; - }, - update: function (userId, doc, fields, modifier) { - return doc.userId === userId; - }, - remove: function (userId, doc) { - return doc.userId === userId; - } - }); - - ShareURLs.allow({ - insert: function(userId, doc) { - // if(ShareURLs.findOne({userId:doc.userId,url:doc.url})){ - // return false; - // } - return true; - }, - update: function(userId, doc) { - return true; - }, - remove: function(userId, doc) { - return true; - } - }) - BlackList.allow({ - insert: function(userId) { - return !! userId; - }, - update: function (userId) { - return !! userId; - }, - remove: function (userId) { - return !! userId; - } - }); - - - Reports.allow({ - insert: function (userId, doc) { - return doc.username !== null; - } - }); - Meteor.users.deny({ - //A profile object that is completely writeable by default, even after you return false in Meteor.users.allow(). - update: function (userId, doc, fieldNames, modifier) { - if(fieldNames.toString() === 'profile' && doc._id === userId && modifier.$set["profile.fullname"] !== undefined && doc.profile.fullname !== modifier.$set["profile.fullname"]) - { - Meteor.defer(function(){ - try{ - UserRelation.update({toUserId:userId},{$set:{'toName': modifier.$set["profile.fullname"]}},{ multi: true}); - } - catch(error){ - //console.log("update Posts and FollowPost get error:"+error); - } - }); - } - if(fieldNames.toString() === 'profile' && doc._id === userId && modifier.$set["profile.icon"] !== undefined && doc.profile.icon !== modifier.$set["profile.icon"]) - { - Meteor.defer(function(){ - try{ - UserRelation.update({toUserId:userId},{$set:{'toIcon': modifier.$set["profile.icon"]}},{ multi: true}); - } - catch(error){} - }); - } - return doc._id !== userId - } - }); - Meteor.users.allow({ - update: function (userId, doc, fieldNames, modifier) { - return doc._id === userId - } - }); - function buildRegExp(searchText) { - // this is a dumb implementation - var parts = searchText.trim().split(/[ \-\:]+/); - return new RegExp("(" + parts.join('|') + ")", "ig"); - } -} - -if(Meteor.isClient){ - if(Meteor.isCordova){ - Tracker.autorun(function(){ - if (Meteor.userId()){ - Meteor.subscribe('associatedusers', { - onReady: function() { - - } - }); - } - - Meteor.subscribe('versions'); - }); - //To prevent method not defined exception. - window.refreshMainDataSource = function(){ - //Meteor.subscribe('waitreadcount'); - }; - - } -} diff --git a/lib/3_pushnotification_trigger.coffee b/lib/3_pushnotification_trigger.coffee deleted file mode 100644 index aad659b6c..000000000 --- a/lib/3_pushnotification_trigger.coffee +++ /dev/null @@ -1,482 +0,0 @@ -if Meteor.isServer - Fiber = Meteor.npmRequire('fibers') - Meteor.startup ()-> - @JPush = Meteor.npmRequire "jpush-sdk" - @client = @JPush.buildClient 'c84d336dc527ce926b44a815', '497c9e7d894cfd1db3a13ade' - URL = Meteor.npmRequire('url') - http = Meteor.npmRequire('http') - Fiber = Meteor.npmRequire('fibers') - - `var httppost = function(url, data, callback){ - var uri = URL.parse(url); - var receiveData = ''; - var req = http.request({ - hostname: uri.hostname, - port: uri.port, - method: 'POST', - path: uri.pathname, - // handers: {} - }, function(res){ - res.setEncoding('utf8'); - res.on('data', function(result){ - receiveData += result; - console.log('httppost suc: url='+url+', data:', result); - console.log('------- End --------'); - }); - res.on('end', () => { - console.log('No more data in response.'); - if (receiveData.indexOf('502 Bad Gateway') > 0) { - callback && callback('failed', receiveData); - } else { - callback && callback(null, receiveData); - } - }); - }); - req.on('error',function(e){ - callback && callback(e, null); - console.log('httppost failed: url='+url+', error: '+JSON.stringify(e)); - console.log('------- End --------'); - }); - Meteor.setTimeout(function(){ - req.write(JSON.stringify(data)); - req.end(); - }, 0); - };` - - @_pushnotification = (type, doc, userId)-> - if type is "palsofavourite" - content = '有人也赞了此公告:《' + doc.title + '》' - extras = { - type: "palsofavourite" - postId: doc._id - } - toUserId = userId - else if type is "palsocomment" - content = '有人也点评了此公告:《' + doc.title + '》' - extras = { - type: "palsocomment" - postId: doc._id - } - toUserId = userId - else if type is "palsocommentReply" - content = '有人回复了您在:《' + doc.title + '》的评论' - extras = { - type: "palsocommentReply" - postId: doc._id - } - toUserId = userId - else if type is "pcommentowner" - content = '有人点评了您的公告:《' + doc.title + '》' - extras = { - type: "pcommentowner" - postId: doc._id - } - toUserId = doc.owner - else if type is "comment" - post = Posts.findOne({_id: doc.postId}); - if post.owner == userId - #console.log "comment self post" - return - commentText = doc.content; - content = '您收到了新回复:'+commentText - extras = { - type: "comment" - postId: doc.postId - } - toUserId = post.owner - else if type is "personalletter" - post = Posts.findOne({_id: doc.postId}) - commentText = doc.content; - content = '您收到了一条来自' + doc.userName + '的私信' - extras = { - type: "personalletter" - postId: doc.postId - } - toUserId = userId - else if type is "read" - if doc.owner == userId - #console.log "read self post" - return - content = '有人正在阅读您的公告:《' + doc.title + '》' - extras = { - type: "read" - postId: doc._id - } - toUserId = doc.owner - else if type is "recommand" - content = doc.recommander + '推荐您阅读' + doc.ownerName + '的公告《' + doc.title + '》' - extras = { - type: "recommand" - postId: doc.postId - } - toUserId = doc.followby - else if type is "getrequest" - content = doc.requester + '邀请您加为好友!' - extras = { - type: "getrequest" - requesterId: doc.requesterId - } - toUserId = doc.followby - else if type is "newpost" - content = doc.ownerName + '发布了新公告:《' + doc.title + '》' - extras = { - type: "newpost" - postId: doc._id - } - toUserId = userId - else - post = Posts.findOne({_id: doc.postId}); - commentText = doc.content; - content = '您参与讨论的公告有新回复:'+commentText - extras = { - type: "recomment" - postId: doc.postId - } - if userId is null or userId is undefined - return; - toUserId = userId - toUserToken = Meteor.users.findOne({_id: toUserId}) - - unless toUserToken is undefined or toUserToken.type is undefined or toUserToken.token is undefined - pushTokenObj = PushTokens.findOne({type:toUserToken.type,token:toUserToken.token}) - if pushTokenObj is undefined or pushTokenObj.userId isnt toUserId - return - waitReadCount = 0 - pushToken = {type: toUserToken.type, token: toUserToken.token} - - if type is "newpost" - # push send logs - removeTime = new Date((new Date()).getTime() - 1000*60*60*48) # 48 hour - expireTime = new Date((new Date()).getTime() - 1000*60*10) # 10 minute - - PushSendLogs.remove({createAt: {$lt: removeTime}}) - if(PushSendLogs.find({ - type: toUserToken.type - token: toUserToken.token - message: content - 'extras.type': extras.type - 'extras.postId': extras.postId - 'extras.requesterId': extras.requesterId - createAt: {$gte: expireTime} - }).count() > 0) - return - - pushReq = { - toUserId: toUserId - type: toUserToken.type - token: toUserToken.token - message: content - extras: extras - createAt: new Date() - } - PushSendLogs.insert pushReq - - if pushToken.type is 'iOS' - #console.log 'Server PN to iOS ' - token = pushToken.token - waitReadCount = Meteor.users.findOne({_id:toUserId}).profile.waitReadCount - if waitReadCount is undefined or isNaN(waitReadCount) - waitReadCount = 1 - Meteor.users.update({_id: toUserId}, {$set: {'profile.waitReadCount': waitReadCount}}); - pushServerAddr = 'http://pushserver.tiegushi.com/pushnotification/' + (new Mongo.ObjectID()._str) - #pushServerAddr += '?fromserver='+encodeURIComponent(Meteor.absoluteUrl()) - #pushServerAddr += '&eventType='+type - #pushServerAddr += '&doc='+JSON.stringify(doc) - #pushServerAddr += '&userId='+userId - - tidyDoc = {_id:doc._id} - if doc.owner - tidyDoc.owner = doc.owner - if doc.ownerName - tidyDoc.ownerName = doc.ownerName - if doc.title - tidyDoc.title = doc.title - if doc.postId - tidyDoc.postId = doc.postId - if doc.content - tidyDoc.content = doc.content - if doc.userName - tidyDoc.userName = doc.userName - if doc.followby - tidyDoc.followby = doc.followby - if doc.requester - tidyDoc.requester = doc.requester - if doc.requesterId - tidyDoc.requesterId = doc.requesterId - dataObj = { - fromserver:encodeURIComponent(Meteor.absoluteUrl()), - eventType:type, - doc:tidyDoc, - userId:userId, - content:content, - extras:extras, - toUserId:toUserId, - pushToken:pushToken, - waitReadCount:waitReadCount - } - dataArray = [] - dataArray.push(dataObj) - console.log("Will send http POST to push server. pushServerAddr="+pushServerAddr+", dataArray="+JSON.stringify(dataArray)) - PushMessages.insert({pushMessage: dataArray, createAt: new Date()}) - # httppost(pushServerAddr, dataArray, (error, result)-> - # if error - # console.log("ERROR: httppost failed, let's try to send notification directly...") - # #_pushnotification(type, doc, userId) - # Fiber( - # ()-> - # PushMessages.insert({pushMessage: dataArray}) - # ).run() - # ) - - @pushnotification = (type, doc, userId)-> - return _pushnotification(type, doc, userId) - console.log "type:"+type - if type is "palsofavourite" - content = '有人也赞了此公告:《' + doc.title + '》' - extras = { - type: "palsofavourite" - postId: doc._id - } - toUserId = userId - else if type is "palsocomment" - content = '有人也点评了此公告:《' + doc.title + '》' - extras = { - type: "palsocomment" - postId: doc._id - } - toUserId = userId - else if type is "palsocommentReply" - content = '有人回复了您在:《' + doc.title + '》的评论' - extras = { - type: "palsocommentReply" - postId: doc._id - } - toUserId = userId - else if type is "pcommentowner" - content = '有人点评了您的公告:《' + doc.title + '》' - extras = { - type: "pcommentowner" - postId: doc._id - } - toUserId = doc.owner - else if type is "comment" - post = Posts.findOne({_id: doc.postId}); - if post.owner == userId - #console.log "comment self post" - return - commentText = doc.content; - content = '您收到了新回复:'+commentText - extras = { - type: "comment" - postId: doc.postId - } - toUserId = post.owner - else if type is "personalletter" - commentText = doc.content; - content = '您收到了一条来自' + doc.userName + '的私信' - extras = { - type: "personalletter" - postId: doc.postId - } - toUserId = userId - else if type is "read" - if doc.owner == userId - #console.log "read self post" - return - content = '有人正在阅读您的公告:《' + doc.title + '》' - extras = { - type: "read" - postId: doc._id - } - toUserId = doc.owner - else if type is "recommand" - content = doc.recommander + '推荐您阅读' + doc.ownerName + '的公告《' + doc.title + '》' - extras = { - type: "recommand" - postId: doc.postId - } - toUserId = doc.followby - else if type is "getrequest" - content = doc.requester + '邀请您加为好友!' - extras = { - type: "getrequest" - requesterId: doc.requesterId - } - toUserId = doc.followby - else if type is "newpost" - content = doc.ownerName + '发布了新公告:《' + doc.title + '》' - extras = { - type: "newpost" - postId: doc._id - } - toUserId = userId - else - post = Posts.findOne({_id: doc.postId}); - commentText = doc.content; - content = '您参与讨论的公告有新回复:'+commentText - extras = { - type: "recomment" - postId: doc.postId - } - if userId is null or userId is undefined - return; - toUserId = userId - - toUserToken = Meteor.users.findOne({_id: toUserId}) - - unless toUserToken is undefined or toUserToken.type is undefined or toUserToken.token is undefined - pushTokenObj = PushTokens.findOne({type:toUserToken.type,token:toUserToken.token}) - if pushTokenObj is undefined or pushTokenObj.userId isnt toUserId - return - if type is "newpost" - # push send logs - removeTime = new Date((new Date()).getTime() - 1000*60*60*48) # 48 hour - expireTime = new Date((new Date()).getTime() - 1000*60*10) # 10 minute - - PushSendLogs.remove({createAt: {$lt: removeTime}}) - if(PushSendLogs.find({ - type: toUserToken.type - token: toUserToken.token - message: content - 'extras.type': extras.type - 'extras.postId': extras.postId - 'extras.requesterId': extras.requesterId - createAt: {$gte: expireTime} - }).count() > 0) - return - - pushReq = { - toUserId: toUserId - type: toUserToken.type - token: toUserToken.token - message: content - extras: extras - createAt: new Date() - } - PushSendLogs.insert pushReq - - pushToken = {type: toUserToken.type, token: toUserToken.token} - #console.log "toUserToken.type:"+toUserToken.type+";toUserToken.token:"+toUserToken.token - try - if pushToken.type is 'JPush' - token = pushToken.token - #console.log 'JPUSH to ' + pushToken.token - client.push().setPlatform 'ios', 'android' - .setAudience JPush.registration_id(token) - .setNotification '回复通知',JPush.ios(content,null,null,null,extras),JPush.android(content, null, 1,extras) - #.setMessage(commentText) - .setOptions null, 60 - .send (err, res)-> - #if err - # console.log err.message - #else - # console.log 'Sendno: ' + res.sendno - # console.log 'Msg_id: ' + res.msg_id - else if pushToken.type is 'iOS' - #console.log 'Server PN to iOS ' - token = pushToken.token - waitReadCount = Meteor.users.findOne({_id:toUserId}).profile.waitReadCount - if waitReadCount is undefined or isNaN(waitReadCount) - waitReadCount = 1 - Meteor.users.update({_id: toUserId}, {$set: {'profile.waitReadCount': waitReadCount}}); - #console.log 'waitReadCount>>>>>>>'+waitReadCount - @pushServer.sendIOS 'me', token , '', content, waitReadCount - else if pushToken.type is 'GCM' - #console.log 'Server PN to GCM ' - token = pushToken.token - @pushServer.sendAndroid 'me', token , '',content, 1 - catch err - console.log("Exception in pushnotification: err="+err); - - @sharpai_pushnotification = (type, doc, userId, ai_person_id)-> - group_notify = false - console.log "sharpai_pushnotification: type:"+type - if type is "notify_stranger" - group_notify = true - group_name = if doc.group_name then doc.group_name else "监控组" - content = 'SharpAI '+'在'+group_name+'发现了'+'陌生人。' + '\n30分钟内不重复推送' - extras = { - type: "notify_stranger" - } - toUserId = userId - else if type is "notify_knownPeople" - group_notify = true - group_name = if doc.group_name then doc.group_name else "监控组" - person_name = if doc.person_name then doc.person_name else "多个人" - content = 'SharpAI '+'在'+group_name+'看到了'+person_name+'。' + '\n30分钟内不重复推送' - extras = { - type: "notify_known_people" - } - toUserId = userId - else if type is "device_offline" - group_notify = true - group_name = if doc.group_name then doc.group_name else "监控组" - content = group_name+':您的设备('+userId+')已经离线,请检查' - extras = { - type: "device_offline" - } - else if type is "device_online" - group_notify = true - group_name = if doc.group_name then doc.group_name else "监控组" - content = group_name+':您的设备('+userId+')已经在线' - extras = { - type: "device_online" - } - else - content = 'SharpAI欢迎您!' - extras = { - type: "recomment" - } - if userId is null or userId is undefined - return; - toUserId = userId - - console.log("notification content="+content) - Fiber(()-> - if group_notify - allUsersCursor = SimpleChat.GroupUsers.find({group_id:doc.group_id}) - console.log("allUsersCursor.count()="+allUsersCursor.count()) - if allUsersCursor.count() > 0 - allUsersCursor.forEach((oneUser)-> - needPushToThisUser = true - - if type is "notify_knownPeople" - if ai_person_id - pushList = NotificationFollowList.findOne({_id:oneUser.user_id}) - if pushList and pushList.hasOwnProperty('followedOnly') - if pushList.hasOwnProperty(ai_person_id) is false - needPushToThisUser = false - console.log(pushList) - console.log('not send push notification for '+ai_person_id) - if needPushToThisUser is true - toUserToken = Meteor.users.findOne({_id: oneUser.user_id}) - #console.log("toUserToken="+JSON.stringify(toUserToken)) - unless toUserToken is undefined or toUserToken.type is undefined or toUserToken.token is undefined - pushToken = {type: toUserToken.type, token: toUserToken.token} - #console.log "toUserToken.type:"+toUserToken.type+";toUserToken.token:"+toUserToken.token - if pushToken.type is 'JPush' - token = pushToken.token - console.log 'JPUSH to ' + pushToken.token - client.push().setPlatform 'ios', 'android' - .setAudience JPush.registration_id(token) - .setNotification 'SharpAI',JPush.ios(content,null,null,null,extras),JPush.android(content, null, 1,extras) - #.setMessage(commentText) - .setOptions null, 60 - .send (err, res)-> - if err - console.log 'err: '+err.message+", "+pushToken.token - else - console.log 'Sendno: '+res.sendno - console.log 'Msg_id: '+res.msg_id+', '+pushToken.token - else if pushToken.type is 'iOS' - console.log 'Server PN to iOS '+pushToken.token - token = pushToken.token - if token is "2e8da18650ffb952823a6690c6257a877d5b6338932b77fbe7031435e4404a60" - return - @pushServer.sendIOS 'me', token , '', content - else if pushToken.type is 'GCM' - console.log 'Server PN to GCM ' - token = pushToken.token - @pushServer.sendAndroid 'me', token , '',content, 1 - ) - ).run() diff --git a/lib/4_0_server_side_template.coffee b/lib/4_0_server_side_template.coffee deleted file mode 100644 index b067a6413..000000000 --- a/lib/4_0_server_side_template.coffee +++ /dev/null @@ -1,76 +0,0 @@ - -if Meteor.isServer - Meteor.startup ()-> - postFontStyleDefault='font-size:large;'; - postFontStyleNormal='font-size:large;'; - postFontStyleQuota='font-size:15px;background:#F5F5F5;padding-left:3%;padding-right:3%;color:grey;'; - - calcTextItemStyle = (layoutObj)-> - fontStyle = postFontStyleDefault - alignStyle = 'text-align:left;' - if layoutObj - if layoutObj.font - if layoutObj.font is 'normal' - fontStyle=postFontStyleNormal - else if layoutObj.font is 'quota' - fontStyle=postFontStyleQuota - if layoutObj.align - if layoutObj.align is 'right' - alignStyle = "text-align:right;" - else if layoutObj.align is 'center' - alignStyle = "text-align:center;" - if layoutObj.weight - alignStyle = "font-weight:"+layoutObj.weight+";" - fontStyle+alignStyle - storeStyleInItem = (node,type,value)-> - $(node).attr('hotshare-'+type,value) - getStyleInItem = (node,type,value)-> - $(node).attr('hotshare-'+type) - - - GetTime0 = (dateM)-> - MinMilli = 1000 * 60; #初始化变量。 - HrMilli = MinMilli * 60; - DyMilli = HrMilli * 24; - #计算出相差天数 - days=Math.floor(dateM/(DyMilli)); - - #计算出小时数 - leave1=dateM%(DyMilli); #计算天数后剩余的毫秒数 - hours=Math.floor(leave1/(HrMilli)); - #计算相差分钟数 - leave2=leave1%(HrMilli); #计算小时数后剩余的毫秒数 - minutes=Math.floor(leave2/(MinMilli)); - #计算相差秒数 - leave3=leave2%(MinMilli); #计算分钟数后剩余的毫秒数 - seconds=Math.round(leave3/1000); - - 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+"秒 前" - - return prefix - String::SymbolEscape = -> - strArr = this.split('') - htmlChar = '&<>' - i = 0 - while i < strArr.length - if htmlChar.indexOf(this.charAt(i)) != -1 - switch this.charAt(i) - when '<' - strArr.splice i, 1, '<' - when '>' - strArr.splice i, 1, '>' - when '&' - strArr.splice i, 1, '&' - i++ - strArr.join '' diff --git a/lib/6_version.js b/lib/6_version.js deleted file mode 100644 index c069d05b3..000000000 --- a/lib/6_version.js +++ /dev/null @@ -1 +0,0 @@ -version_of_build = '2.5.0'; diff --git a/lib/7_cookies.js b/lib/7_cookies.js deleted file mode 100644 index ea5d061f1..000000000 --- a/lib/7_cookies.js +++ /dev/null @@ -1,29 +0,0 @@ -Cookies = { - check:function(name){ - var c_name = this.get(name); - if(c_name!=null && c_name != ""){ - return true; - } else { - return false; - } - }, - set: function(c_name,value,expiredays){ - var exdate=new Date() - exdate.setDate(exdate.getDate()+expiredays) - document.cookie=c_name+ "=" +escape(value)+ - ((expiredays==null) ? "" : ";expires="+exdate.toGMTString()) - }, - get: function(c_name){ - if (document.cookie.length>0){ - var c_start=document.cookie.indexOf(c_name + "=") - if (c_start!=-1) - { - c_start=c_start + c_name.length+1 - var c_end=document.cookie.indexOf(";",c_start) - if (c_end==-1) c_end=document.cookie.length - return unescape(document.cookie.substring(c_start,c_end)) - } - } - return "" - } -} \ No newline at end of file diff --git a/lib/7_public.js b/lib/7_public.js deleted file mode 100644 index 3b13c8f1f..000000000 --- a/lib/7_public.js +++ /dev/null @@ -1,44 +0,0 @@ -PUB = { - formatTime: function(time, str) { - var UTC = 8; // 启用北京时间 - var date = new Date(time); - if(UTC){ - date.setUTCHours(date.getUTCHours()+UTC); - } - var Y,M,D,h,m,s,result; - var addZero = function(val) { - val = val.toString(); - if(val.length < 2){ - return '0'+val; - } else { - return val; - } - }; - Y = date.getUTCFullYear(); - M = addZero(date.getUTCMonth() + 1); - D = addZero(date.getUTCDate()); - h = addZero(date.getUTCHours()); - m = addZero(date.getUTCMinutes()); - s = addZero(date.getUTCSeconds()); - - switch (str) { - case 'yyyy-mm-dd H:m:s': - result = Y+'-'+M+'-'+D+' '+h+':'+m+':'+s; - break; - case 'yyyy-mm-dd': - result = Y+'-'+M+'-'+D; - break; - default: - result = Y+'-'+M+'-'+D+' '+h+':'+m+':'+s; - } - if(UTC){ - result += '(UTC'+UTC+')'; - } - return result; - } -} - -// js 原生a扩展 -String.prototype.replaceAll = function(s1,s2) { - return this.replace(new RegExp(s1,"gm"),s2); -} \ No newline at end of file diff --git a/lib/8_sendMsgToChatroom.js b/lib/8_sendMsgToChatroom.js deleted file mode 100644 index ba5edf738..000000000 --- a/lib/8_sendMsgToChatroom.js +++ /dev/null @@ -1,198 +0,0 @@ -if(Meteor.isServer){ - Meteor.startup(function(){ - var allPerson=[ - { - //lambda - "id": "897423", - "name": "lambda", - "imgurl":[ - "http://onm4mnb4w.bkt.clouddn.com/b50cf316-2b02-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/e07ff9a8-2b02-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/fba20d86-2b05-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/16e5888e-2b06-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/b6f34158-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/b644cebe-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/f402621c-2bf3-11e7-a7cc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/c0313062-2bf3-11e7-a7cc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/c2bca06e-2bf3-11e7-a7cc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/b3666432-2b18-11e7-9bfc-d065caa81a04"] - }, - { - //zxs - "id": "67124", - "name": "zxs", - "imgurl":[ - "http://onm4mnb4w.bkt.clouddn.com/7818661a-2b03-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/fc9f9876-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/fbd0df22-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/fd6ced4e-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/10b2dd0a-2b19-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/fc9f9876-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/ffd65534-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/ff02a5ea-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/56f1df92-2bf4-11e7-a7cc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/fe34e632-2b18-11e7-9bfc-d065caa81a04"] - }, - { - //lfw - "id": "906765", - "name": "lfw", - "imgurl":[ - "http://onm4mnb4w.bkt.clouddn.com/3df82d2c-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/3fb92fd0-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/4624b98e-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/ab16d0fa-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/27dea1e2-2bc0-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/28bcf1e0-2bc0-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/298ac61a-2bc0-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/2a42d26e-2bc0-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/2bc36a68-2bc0-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/ab16d0fa-2b18-11e7-9bfc-d065caa81a04"] - }, - { - //zj - "id": "12967", - "name": "zj", - "imgurl":[ - "http://onm4mnb4w.bkt.clouddn.com/8855772a-2b0d-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/ad3d6c28-2b0d-11e7-9bfc-d065caa81a04"] - }, - { - //gfb - "id": "902834", - "name": "gfb", - "imgurl":[ - "http://onm4mnb4w.bkt.clouddn.com/618de51a-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/667917ac-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/6e4c35ea-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/6d6a5e40-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/6c8a9a08-2b11-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/dee2ea22-2bbd-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/ecef1fb4-2bbd-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/3a8418ec-2bbe-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/ecef1fb4-2bbd-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/5cfde7b8-2bbe-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/8e4f83f6-2b11-11e7-9bfc-d065caa81a04"] - }, - { - //srp - "id": "63276", - "name": "srp", - "imgurl":[ - "http://workaiossqn.tiegushi.com/00b3c326-15eb-11e7-8cfb-0242ac11000a", - "http://onm4mnb4w.bkt.clouddn.com/6fb1b2f6-2b12-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/da577630-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/e1d96328-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/e2e26e22-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/e07c37d0-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/e1293df4-2b18-11e7-9bfc-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/cd02e6c8-2bbc-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/cfd83eb6-2bbc-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/c3f38d76-2bbc-11e7-bd74-d065caa81a04", - "http://onm4mnb4w.bkt.clouddn.com/da577630-2b18-11e7-9bfc-d065caa81a04"] - } - ] - - function getRandom1000() { - var ret = Math.ceil(1000*Math.random()) - return ret; - } - - function initPeopleInOut(uuidIn, uuidOut) { - var timeLine=[]; - var duration=(60*60); //1hour - - for(var i=0; i = minDuration)) { - var imgurl = allPerson[k].imgurl; - var len = imgurl.length; - var urlIn = imgurl[random % len]; - var urlOut = imgurl[(random + 1) % len]; - var accuracy = 0.85 + (random/1000) % 0.12 - var fuzziness = 5 + random%5 - - accuracy = accuracy.toFixed(2) - timeLine[i].people.push({id: allPerson[k].id, uuid: uuidIn, imgurl: urlIn, accuracy: accuracy, fuzziness: fuzziness}); - timeLine[nextOut].people.push({id: allPerson[k].id, uuid: uuidOut, imgurl: urlOut, accuracy: accuracy, fuzziness: fuzziness}); - } - i = nextOut + skip; - } - } - return timeLine; - } - - function sendMessage2Group(idx, timeline) { - if(idx >= timeline.length) - return; - - item = timeline[idx]; - if(item && item.people && item.people.length && item.people.length >0 && item.sent == false) { - var people = item.people; - //console.log('>>> ' + idx + ' ' + JSON.stringify(item.people)); - for(var i=0; i 7 && day !== 6 && day !==0){ - //console.log('Working hour') - } else { - //console.log('Not working hour') - return; - } - } - if (!timeline || idx >= timeline.length) { - var uuidIn = 'ZTEBA510' - var uuidOut = '7249c9d4' - timeline = []; - timeline = initPeopleInOut(uuidIn, uuidOut); - idx = 0; - } - - for(var i=0; i< speed; i++) { - sendMessage2Group(idx, timeline); - idx++; - } - speed = (speed <=1)? 1:(speed -1); - }, 1000*60); // 10 分钟 - }*/ - }); -} - diff --git a/lib/accounts-userid/userid_client.js b/lib/accounts-userid/userid_client.js deleted file mode 100644 index 18e561d3e..000000000 --- a/lib/accounts-userid/userid_client.js +++ /dev/null @@ -1,52 +0,0 @@ -if (Meteor.isClient) { - Meteor.loginWithUserId = function (userId, isExtension, callback) { - if(!userId) - return callback && callback('RESET_LOGIN'); - if(isExtension) - return Accounts.callLoginMethod({ - methodArguments: [{userId: userId, isExtension: isExtension}], - userCallback: function (err) { - if (err) - return callback && callback(err); - - return callback && callback(); - } - }); - - var lastLoginWithUserIdTime = localStorage.getItem('login-with-user-id-last-time'); - if(lastLoginWithUserIdTime && (new Date()).getTime() - (new Date(lastLoginWithUserIdTime)).getTime() <= 10000) - return callback && callback('WAIT_TIME'); - - var loginUserId = Meteor.userId(); - if(!loginUserId) - return callback && callback('NOT_LOGIN'); - - Accounts.callLoginMethod({ - methodArguments: [{userId: userId, loginUserId: loginUserId, version: '2.0'}], - userCallback: function (err) { - if (err) - return callback && callback('NOT_LOGIN'); - - localStorage.setItem('login-with-user-id-last-time', new Date()); - return callback && callback(); - } - }); - // Meteor.logout(function (err) { - // if(err && Meteor.userId()) - // return callback && callback('RESET_LOGIN'); - // // else if(err && !Meteor.userId()) - // // return callback && callback('NOT_LOGIN'); - - // Accounts.callLoginMethod({ - // methodArguments: [{userId: userId, loginUserId: loginUserId, version: '2.0'}], - // userCallback: function (err) { - // if (err) - // return callback && callback('NOT_LOGIN'); - - // localStorage.setItem('login-with-user-id-last-time', new Date()); - // return callback && callback(); - // } - // }); - // }); - }; -} \ No newline at end of file diff --git a/lib/accounts-userid/userid_server.js b/lib/accounts-userid/userid_server.js deleted file mode 100644 index 4566bed5b..000000000 --- a/lib/accounts-userid/userid_server.js +++ /dev/null @@ -1,20 +0,0 @@ -if (Meteor.isServer) { - Accounts.registerLoginHandler('userId', function(options) { - if(!options.userId) - return undefined; - if(!Meteor.users.findOne({_id: options.userId})) - throw new Meteor.Error(403, 'User not found'); - if(options.isExtension) - return {userId: options.userId}; - - options.loginUserId = options.loginUserId || this.userId; - if(options.version && options.version === '2.0'){ - if(UserRelation.find({userId: options.loginUserId, toUserId: options.userId}).count() > 0) - return {userId: options.userId}; - } - if(AssociatedUsers.find({$or: [{userIdA: options.userId, userIdB: options.loginUserId}, {userIdA: options.loginUserId, userIdB: options.userId}]}).count() > 0) - return {userId: options.userId}; - - throw new Meteor.Error(403, 'User not found'); - }); -} \ No newline at end of file diff --git a/lib/activity.js b/lib/activity.js deleted file mode 100644 index 34708ec86..000000000 --- a/lib/activity.js +++ /dev/null @@ -1,243 +0,0 @@ -var checkIsToday = function (checktime, group_id) { - var isToday = true; - var time_offset = 8; //US is -7, China is +8 - var group = SimpleChat.Groups.findOne({ - _id: group_id - }); - if (group && group.offsetTimeZone) { - time_offset = group.offsetTimeZone; - } - - function DateTimezone(date, offset) { - //var d = new Date(); - var utc = date.getTime() + (date.getTimezoneOffset() * 60000); - var local_now = new Date(utc + (3600000 * offset)); - var today_now = new Date(local_now.getFullYear(), local_now.getMonth(), local_now.getDate(), - local_now.getHours(), local_now.getMinutes()); - return today_now; - } - var now = DateTimezone(new Date(), time_offset); - checktime = DateTimezone(new Date(checktime), time_offset); - - var DayDiff = now.getDate() - checktime.getDate(); - - if (DayDiff != 0) { - console.log('ai_checkin_out: not today out/in '); - isToday = false; - } - return isToday; -}; - -activity_update_time = function (person_id, create_time, image_url) { - console.log("activity_update_time", person_id, create_time.getTime(), image_url); - - var act_time = create_time.getTime(); - var ai_in_time, ai_lastest_in_time, ai_out_time, checkin_time, checkout_time; - var first_in = false; - var in_image_url, lastin_image_url, out_image_url; - - var relation = WorkAIUserRelations.findOne({ - 'ai_persons.id': person_id - }); - - if (relation == null) { - return; - } - - console.log("relation", relation); - - relation.ai_in_time = (!relation.ai_in_time) ? 0 : (!checkIsToday(relation.ai_in_time, relation.group_id)) ? 0 : relation.ai_in_time; - relation.ai_out_time = (!relation.ai_out_time) ? 0 : (!checkIsToday(relation.ai_out_time, relation.group_id)) ? 0 : relation.ai_out_time; - relation.ai_lastest_in_time = (!relation.ai_lastest_in_time) ? 0 : (!checkIsToday(relation.ai_lastest_in_time, relation.group_id)) ? 0 : relation.ai_lastest_in_time; - - console.log("relation2", relation); - if (relation.ai_in_time == 0) { - first_in = true; - } - - if (!relation.ai_in_time || - relation.ai_in_time > act_time) { - ai_in_time = act_time; - in_image_url = image_url; - } else { - ai_in_time = relation.ai_in_time; - in_image_url = relation.ai_in_image; - } - /* - if (relation.checkin_time > act_time){ - checkin_time = act_time - }else { - checkin_time = relation.checkin_time - } - */ - - if (!relation.ai_lastest_in_time || - relation.ai_lastest_in_time < act_time) { - ai_lastest_in_time = act_time; - lastin_image_url = image_url; - } else { - ai_lastest_in_time = relation.ai_lastest_in_time; - lastin_image_url = relation.ai_lastest_in_image; - } - - if (first_in) { - ai_out_time = relation.ai_out_time; - out_image_url = image_url; - } else { - if (!relation.ai_out_time || - relation.ai_out_time < act_time) { - ai_out_time = act_time; - out_image_url = image_url; - } else { - ai_out_time = relation.ai_out_time; - out_image_url = relation.ai_out_image; - } - } - - /* - if (relation.checkout_time){ - if (relation.checkout_time > act_time){ - checkout_time = relation.checkout_time - }else { - checkout_time = act_time - } - } - */ - - var setObj = { - ai_in_time: ai_in_time, - ai_in_image: in_image_url, - - ai_lastest_in_time: ai_lastest_in_time, - ai_lastest_in_image: lastin_image_url, - - ai_out_time: ai_out_time, - ai_out_image: out_image_url - }; - - console.log("setObj", setObj); - WorkAIUserRelations.update({ - _id: relation._id - }, { - $set: setObj - }); - - var time_offset = 8; //US is -7, China is +8 - var group = SimpleChat.Groups.findOne({ - _id: relation.group_id - }); - if (group && group.offsetTimeZone) { - time_offset = group.offsetTimeZone; - } - console.log("time_offset", time_offset); - - function DateTimezone(offset) { - var d = new Date(); - var utc = d.getTime() + (d.getTimezoneOffset() * 60000); - var local_now = new Date(utc + (3600000 * offset)); - - return local_now; - } - - var now = DateTimezone(time_offset); - var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); - var day_utc = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), - 0, 0, 0, 0); - - console.log('day_utc:' + day_utc); - - var workstatus = null; - if (relation.app_user_id) { - workstatus = WorkStatus.findOne({ - 'group_id': relation.group_id, - 'app_user_id': relation.app_user_id, - 'date': day_utc - }); - } - if (!workstatus && relation.person_name) { - workstatus = WorkStatus.findOne({ - 'group_id': relation.group_id, - 'person_name': relation.person_name, - 'date': day_utc - }); - } - - if (!image_url) { - PUB.toast("头像不能为空"); - return; - } - console.log("workstatus", workstatus); - if (!workstatus) { - var intime = act_time; - var in_image = image_url; - var in_video = ''; - var outtime = 0; - var out_image = image_url; - var out_video = ''; - var now_status = 'in'; - var in_status = 'normal'; - var out_status = 'unknown'; - - console.log("workstatus insert"); - - WorkStatus.insert({ - "app_user_id": relation.app_user_id, - "app_notifaction_status": relation.app_notifaction_status, - "group_id": relation.group_id, - "date": day_utc, - "person_id": relation.ai_persons, - "person_name": relation.person_name, - "status": now_status, - "in_status": in_status, - "out_status": out_status, - "in_uuid": relation.in_uuid, - "out_uuid": relation.out_uuid, - "whats_up": "", - "in_time": intime, - "in_image": in_image, - "in_video": in_video, - "out_image": out_image, - "out_time": outtime, - "out_video": out_video, - "hide_it": relation.hide_it ? relation.hide_it : false - }); - } else { - if (!workstatus.in_time || workstatus.in_time > act_time) { - intime = act_time; - in_image = image_url; - } else { - intime = workstatus.in_time; - in_image = workstatus.in_image; - } - - if (!workstatus.out_time || - workstatus.out_time < act_time) { - outtime = act_time; - out_image = image_url; - } else { - outtime = workstatus.out_time; - out_image = workstatus.out_image; - } - - if (first_in || intime == outtime) { - now_status = 'in'; - } else { - now_status = 'out'; - } - - setObj = { - status: now_status, - in_time: intime, - in_image: in_image, - out_time: outtime, - out_image: out_image, - }; - - console.log("setObj", setObj); - WorkStatus.update({ - _id: workstatus._id - }, { - $set: setObj - }); - } -}; \ No newline at end of file diff --git a/lib/collections_doc/drafts.txt b/lib/collections_doc/drafts.txt deleted file mode 100644 index bc9e1ecd1..000000000 --- a/lib/collections_doc/drafts.txt +++ /dev/null @@ -1,13 +0,0 @@ -Drafts = { - type: // 类型,可选值:image - isImage: // 是否图片 - owner: // 所有人 - imgUrl: // 本地图片地址 - filename: // 服务器文件名 - URI: { - url: // 原始图片地址 - } - layout: [String] // 图片的布局 -} - -// 注:1、第一条为标题图。2、saveDrafts保存的用一条数据保存以上数据,且ID为第一条数据的ID \ No newline at end of file diff --git a/lib/collections_doc/messages.txt b/lib/collections_doc/messages.txt deleted file mode 100644 index cf8de0c23..000000000 --- a/lib/collections_doc/messages.txt +++ /dev/null @@ -1,74 +0,0 @@ -messages = { - userId: - userName: - userIcon: - - // 一对一聊天 - toUserId: - toUserName: - toUserIcon: - - // 群聊天 - toGroupId: - toUsers: [ - { - userId: - userName: - userIcon: - } - ] - - // 文本消息 - text: - - // 图片消息 - image: - - isRead: - readTime: [Date] - msgType: 'text/image' - sesType: 'singleChat/groupChat/chatNotify' - createTime: -} - -msgsession = { - userId: - userName: - userIcon: - - // 一对一聊天 - toUserId: - toUserName: - toUserIcon: - - // 群聊天 - toGroupId: - toGroupName: - toGroupIcon: - - text: - isRead: - readTime: [Date] - waitRead: - msgType: 'text/image' - sesType: 'singleChat/groupChat/chatNotify' - updateTime: -} - -msggroup = { - name: - users: [ - { - userId: - userName: - userIcon: - isManager: - } - ] - create: { - userId: - userName: - userIcon: - createTime: - } -} \ No newline at end of file diff --git a/lib/format-post-pub.js b/lib/format-post-pub.js deleted file mode 100644 index dc588dea4..000000000 --- a/lib/format-post-pub.js +++ /dev/null @@ -1,29 +0,0 @@ -formatPostPub = function(pub){ - for(var i=0;i 0) { - pub[i].data_row = pub[i-1].data_row + pub[i-1].data_sizey; - } - } - - return pub; -} \ No newline at end of file diff --git a/lib/mqtt_mq_server.js b/lib/mqtt_mq_server.js deleted file mode 100644 index 2e13dac89..000000000 --- a/lib/mqtt_mq_server.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Created by simba on 5/12/16. - */ -if(Meteor.isServer){ - sendMqttUserMessage = null; - sendMqttGroupMessage= null; - - initMQTT = function(clientId){ - var mqttOptions = { - clean:true, - keepalive:30, - reconnectPeriod:20*1000, - clientId:clientId - } - mqtt_connection=mqtt.connect('mqtt://mq.tiegushi.com:8080',mqttOptions); - mqtt_connection.on('connect',function(){ - console.log('Connected to mqtt server'); - }); - mqtt_connection.on('message', function (topic, message) { - // message is Buffer - var keyword = '/msg/autogroup/'; - if (topic.indexOf(keyword) == 0) { - var group_id = topic.substring(keyword.length); - console.log("/msg/autogroup/: "+group_id+", message="+message.toString()); - //results.append({"opt":'mv', "url":url, "from_faceId":from_faceId, "to_faceId":to_faceId}) - CLUSTER_PERSON.updateAutogroupResult(group_id, message); - } - }); - mqtt_connection.subscribe('/msg/autogroup/#'); - sendMqttMessage=function(topic,message){ - Meteor.defer(function(){ - mqtt_connection.publish(topic,JSON.stringify(message),{qos:1}) - }) - } - mqttPostViewHook=function(userId,postId){ - try{ - sendMqttMessage('postView',{userId:userId,postId:postId}) - }catch(e){} - } - mqttInsertNewPostHook=function(ownerId,postId,title,addonTitle,ownerName,mainImage){ - try{ - sendMqttMessage('publishPost',{ - ownerId:ownerId, - postId:postId, - title:title, - addonTitle:addonTitle, - ownerName:ownerName, - mainImage:mainImage - }) - }catch(e){} - } - mqttUserCreateHook=function(userId,fullname,username){ - try{ - sendMqttMessage('newUser',{ - userId:userId, - fullname:fullname, - username:username - }) - }catch(e){} - } - sendMqttUserMessage=function(user_id, message) { - // console.log('sendMqttUserMessage:', message); - sendMqttMessage("/msg/u/" + user_id, message); - }; - sendMqttGroupMessage=function(group_id, message) { - sendMqttMessage("/msg/g/" + group_id, message); - }; - } - - - Meteor.startup(function(){ - initMQTT(null); - }) -} diff --git a/lib/setImmediate.js b/lib/setImmediate.js deleted file mode 100644 index f8dd02763..000000000 --- a/lib/setImmediate.js +++ /dev/null @@ -1,187 +0,0 @@ -(function (global, undefined) { - "use strict"; - - if (global.setImmediate) { - console.log("has global.setImmediate"); - return; - } - - var nextHandle = 1; // Spec says greater than zero - var tasksByHandle = {}; - var currentlyRunningATask = false; - var doc = global.document; - var registerImmediate; - - function setImmediate(callback) { - // Callback can either be a function or a string - if (typeof callback !== "function") { - callback = new Function("" + callback); - } - // Copy function arguments - var args = new Array(arguments.length - 1); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i + 1]; - } - // Store and register the task - var task = { callback: callback, args: args }; - tasksByHandle[nextHandle] = task; - registerImmediate(nextHandle); - return nextHandle++; - } - - function clearImmediate(handle) { - delete tasksByHandle[handle]; - } - - function run(task) { - var callback = task.callback; - var args = task.args; - switch (args.length) { - case 0: - callback(); - break; - case 1: - callback(args[0]); - break; - case 2: - callback(args[0], args[1]); - break; - case 3: - callback(args[0], args[1], args[2]); - break; - default: - callback.apply(undefined, args); - break; - } - } - - function runIfPresent(handle) { - // From the spec: "Wait until any invocations of this algorithm started before this one have completed." - // So if we're currently running a task, we'll need to delay this invocation. - if (currentlyRunningATask) { - // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a - // "too much recursion" error. - setTimeout(runIfPresent, 0, handle); - } else { - var task = tasksByHandle[handle]; - if (task) { - currentlyRunningATask = true; - try { - run(task); - } finally { - clearImmediate(handle); - currentlyRunningATask = false; - } - } - } - } - - function installNextTickImplementation() { - registerImmediate = function(handle) { - process.nextTick(function () { runIfPresent(handle); }); - }; - } - - function canUsePostMessage() { - // The test against `importScripts` prevents this implementation from being installed inside a web worker, - // where `global.postMessage` means something completely different and can't be used for this purpose. - if (global.postMessage && !global.importScripts) { - var postMessageIsAsynchronous = true; - var oldOnMessage = global.onmessage; - global.onmessage = function() { - postMessageIsAsynchronous = false; - }; - global.postMessage("", "*"); - global.onmessage = oldOnMessage; - return postMessageIsAsynchronous; - } - } - - function installPostMessageImplementation() { - // Installs an event handler on `global` for the `message` event: see - // * https://developer.mozilla.org/en/DOM/window.postMessage - // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages - - var messagePrefix = "setImmediate$" + Math.random() + "$"; - var onGlobalMessage = function(event) { - if (event.source === global && - typeof event.data === "string" && - event.data.indexOf(messagePrefix) === 0) { - runIfPresent(+event.data.slice(messagePrefix.length)); - } - }; - - if (global.addEventListener) { - global.addEventListener("message", onGlobalMessage, false); - } else { - global.attachEvent("onmessage", onGlobalMessage); - } - - registerImmediate = function(handle) { - global.postMessage(messagePrefix + handle, "*"); - }; - } - - function installMessageChannelImplementation() { - var channel = new MessageChannel(); - channel.port1.onmessage = function(event) { - var handle = event.data; - runIfPresent(handle); - }; - - registerImmediate = function(handle) { - channel.port2.postMessage(handle); - }; - } - - function installReadyStateChangeImplementation() { - var html = doc.documentElement; - registerImmediate = function(handle) { - // Create a - - - - - --- --- 我的消息 ---- ---- - - - - ------- - -点我创作-- {{ownerName}} -- -{{time_diff createdAt}}-- {{#if getAbstractSentence}} ---- {{/if}} ---{{{getAbstractSentence}}}- ------
- -{{title}}
-{{addontitle}}
-- {{#if mainText}} -- -{{mainText}}- {{/if}} ---- {{#each getPub}} - {{>postItem}} - {{/each}} -- -- {{#if fromUrl}} -- {{#if browse}} - - {{/if}} -阅读原文- {{else}} - - {{/if}} -私信作者----- - - - - - - - - - {{#if withSectionMenu}} - - {{/if}} -更多精彩内容,请搜索关注故事贴微信公众号
-- -- - - - ---关注作者-- -- -想实时阅读该作者最新文章吗?留下您的QQ号或者邮箱地址,然后选择“确定”完成关注。我们将把它们送给您!
--- - - - - - - - - - - - --- -给{{ownerName}}发私信
-×-QQ号或邮箱地址用于作者联系你,只有作者能看到,请放心使用-- -- --- - - - - - - - ----
-- - - - - diff --git a/private/static/postItem.html b/private/static/postItem.html deleted file mode 100644 index ca0c6d094..000000000 --- a/private/static/postItem.html +++ /dev/null @@ -1,74 +0,0 @@ - {{#if isImage}} - {{#if inIframe}} ----- -- 创作故事 -- {{{iframe}}} -- {{else}} -- -- {{/if}} - {{else}} - {{#if musicInfo}} --
- - - - -- {{else}} - {{#if hasVideoInfo videoInfo}} --
- - - - {{musicInfo.songName}} - {{musicInfo.singerName}} - - - -
- - - - - - - -- {{else}} - {{#if adv}} -- - - - -
-- {{else}} ---- 如何改写喜欢的文章呢 ----故事贴 提供 ---- {{/if}} - {{/if}} - {{/if}} - {{/if}} diff --git a/private/template/post.html b/private/template/post.html deleted file mode 100644 index 7923c9d34..000000000 --- a/private/template/post.html +++ /dev/null @@ -1,36 +0,0 @@ - - - -- {{{getText text}}} {{#if isTextLength text}}-{{#if myselfClickedUp}}{{plike}} {{else}}{{plike}} {{/if}}{{#if myselfClickedDown}}{{pdislike}} {{else}}{{pdislike}} {{/if}}{{/if}} -{{title}} - 故事贴 - - - - - - - - - - - --- \ No newline at end of file diff --git a/private/workai-motion-imgs.html b/private/workai-motion-imgs.html deleted file mode 100644 index 8c88fd0cb..000000000 --- a/private/workai-motion-imgs.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - --{{title}}
-{{addontitle}}
- {{#each pub}} - {{#if isImage}} -- {{else}} -{{text}}
- {{/if}} - {{/each}} -{{imgs}}
- \ No newline at end of file diff --git a/public/1.gif b/public/1.gif new file mode 100755 index 000000000..b504b7660 Binary files /dev/null and b/public/1.gif differ diff --git a/public/2.gif b/public/2.gif new file mode 100755 index 000000000..7b4351596 Binary files /dev/null and b/public/2.gif differ diff --git a/public/3.gif b/public/3.gif new file mode 100755 index 000000000..76e2a66fe Binary files /dev/null and b/public/3.gif differ diff --git a/public/aboutBg.png b/public/aboutBg.png new file mode 100644 index 000000000..2979f7325 Binary files /dev/null and b/public/aboutBg.png differ diff --git a/public/active_report.png b/public/active_report.png new file mode 100644 index 000000000..fd45f1c75 Binary files /dev/null and b/public/active_report.png differ diff --git a/public/add-series.png b/public/add-series.png new file mode 100755 index 000000000..305d07d4b Binary files /dev/null and b/public/add-series.png differ diff --git a/public/addBtn.jpg b/public/addBtn.jpg new file mode 100755 index 000000000..4c684bfd3 Binary files /dev/null and b/public/addBtn.jpg differ diff --git a/public/addDevice.png b/public/addDevice.png new file mode 100644 index 000000000..46c7dcfaa Binary files /dev/null and b/public/addDevice.png differ diff --git a/public/addUser.png b/public/addUser.png new file mode 100644 index 000000000..d49f242c1 Binary files /dev/null and b/public/addUser.png differ diff --git a/public/add_more.png b/public/add_more.png new file mode 100755 index 000000000..94948ebee Binary files /dev/null and b/public/add_more.png differ diff --git a/public/addressbook.png b/public/addressbook.png new file mode 100755 index 000000000..a8417c373 Binary files /dev/null and b/public/addressbook.png differ diff --git a/public/addressbook_s.png b/public/addressbook_s.png new file mode 100755 index 000000000..f2a9113c5 Binary files /dev/null and b/public/addressbook_s.png differ diff --git a/public/arrow_top_right.png b/public/arrow_top_right.png new file mode 100644 index 000000000..85914eba7 Binary files /dev/null and b/public/arrow_top_right.png differ diff --git a/public/autolabel.png b/public/autolabel.png new file mode 100755 index 000000000..9e40d85fc Binary files /dev/null and b/public/autolabel.png differ diff --git a/public/biaozhu.png b/public/biaozhu.png new file mode 100644 index 000000000..d1e3d555d Binary files /dev/null and b/public/biaozhu.png differ diff --git a/public/bubbleTip/bubblehint1.png b/public/bubbleTip/bubblehint1.png new file mode 100644 index 000000000..f32141731 Binary files /dev/null and b/public/bubbleTip/bubblehint1.png differ diff --git a/public/bubbleTip/bubblehint2.png b/public/bubbleTip/bubblehint2.png new file mode 100644 index 000000000..4ddec349a Binary files /dev/null and b/public/bubbleTip/bubblehint2.png differ diff --git a/public/bubbleTip/bubblehint3.png b/public/bubbleTip/bubblehint3.png new file mode 100644 index 000000000..f46216660 Binary files /dev/null and b/public/bubbleTip/bubblehint3.png differ diff --git a/public/camera_offline.svg b/public/camera_offline.svg new file mode 100644 index 000000000..00082c8c7 --- /dev/null +++ b/public/camera_offline.svg @@ -0,0 +1,10 @@ ++ diff --git a/public/camera_online.svg b/public/camera_online.svg new file mode 100644 index 000000000..d2ccb6be0 --- /dev/null +++ b/public/camera_online.svg @@ -0,0 +1,10 @@ ++ + ++ + diff --git a/public/check_canlder.png b/public/check_canlder.png new file mode 100755 index 000000000..d42c095d7 Binary files /dev/null and b/public/check_canlder.png differ diff --git a/public/createGroup2.jpg b/public/createGroup2.jpg new file mode 100644 index 000000000..5665636a0 Binary files /dev/null and b/public/createGroup2.jpg differ diff --git a/public/createGroup3.png b/public/createGroup3.png new file mode 100644 index 000000000..64dbee334 Binary files /dev/null and b/public/createGroup3.png differ diff --git a/public/delete-2.png b/public/delete-2.png new file mode 100755 index 000000000..bbbe7b2b8 Binary files /dev/null and b/public/delete-2.png differ diff --git a/public/delete-all.png b/public/delete-all.png new file mode 100644 index 000000000..b524331d9 Binary files /dev/null and b/public/delete-all.png differ diff --git a/public/delete.png b/public/delete.png new file mode 100644 index 000000000..bc9ae06ba Binary files /dev/null and b/public/delete.png differ diff --git a/public/device.png b/public/device.png new file mode 100644 index 000000000..7def65e1f Binary files /dev/null and b/public/device.png differ diff --git a/public/device_icon_192.png b/public/device_icon_192.png index 3048a9e52..f47469786 100644 Binary files a/public/device_icon_192.png and b/public/device_icon_192.png differ diff --git a/public/electric_quantity.png b/public/electric_quantity.png new file mode 100755 index 000000000..64c6a462f Binary files /dev/null and b/public/electric_quantity.png differ diff --git a/public/email.png b/public/email.png new file mode 100644 index 000000000..03b42a506 Binary files /dev/null and b/public/email.png differ diff --git a/public/exit-cancel.jpg b/public/exit-cancel.jpg new file mode 100644 index 000000000..ac50de9c0 Binary files /dev/null and b/public/exit-cancel.jpg differ diff --git a/public/face_box_offline.svg b/public/face_box_offline.svg new file mode 100644 index 000000000..cdbe23f9a --- /dev/null +++ b/public/face_box_offline.svg @@ -0,0 +1,13 @@ ++ + ++ + diff --git a/public/face_box_online.svg b/public/face_box_online.svg new file mode 100644 index 000000000..1e0a4e5b4 --- /dev/null +++ b/public/face_box_online.svg @@ -0,0 +1,13 @@ ++ + ++ ++ + + diff --git a/public/failed.png b/public/failed.png new file mode 100644 index 000000000..fb291f901 Binary files /dev/null and b/public/failed.png differ diff --git a/public/follows/11.jpg b/public/follows/11.jpg new file mode 100644 index 000000000..6b389d6f0 Binary files /dev/null and b/public/follows/11.jpg differ diff --git a/public/follows/12.jpg b/public/follows/12.jpg new file mode 100644 index 000000000..2592eddc8 Binary files /dev/null and b/public/follows/12.jpg differ diff --git a/public/follows/13.jpg b/public/follows/13.jpg new file mode 100644 index 000000000..34d838849 Binary files /dev/null and b/public/follows/13.jpg differ diff --git a/public/follows/21.jpg b/public/follows/21.jpg new file mode 100644 index 000000000..55262760b Binary files /dev/null and b/public/follows/21.jpg differ diff --git a/public/follows/22.jpg b/public/follows/22.jpg new file mode 100644 index 000000000..c67883fad Binary files /dev/null and b/public/follows/22.jpg differ diff --git a/public/follows/23.jpg b/public/follows/23.jpg new file mode 100644 index 000000000..004a67704 Binary files /dev/null and b/public/follows/23.jpg differ diff --git a/public/follows/31.jpg b/public/follows/31.jpg new file mode 100644 index 000000000..07395630b Binary files /dev/null and b/public/follows/31.jpg differ diff --git a/public/follows/32.jpg b/public/follows/32.jpg new file mode 100644 index 000000000..2046068e5 Binary files /dev/null and b/public/follows/32.jpg differ diff --git a/public/follows/33.jpg b/public/follows/33.jpg new file mode 100644 index 000000000..abc6bd26e Binary files /dev/null and b/public/follows/33.jpg differ diff --git a/public/follows/41.jpg b/public/follows/41.jpg new file mode 100644 index 000000000..7be4e84c7 Binary files /dev/null and b/public/follows/41.jpg differ diff --git a/public/follows/42.jpg b/public/follows/42.jpg new file mode 100644 index 000000000..6b8c8f792 Binary files /dev/null and b/public/follows/42.jpg differ diff --git a/public/follows/43.jpg b/public/follows/43.jpg new file mode 100644 index 000000000..58ea90c85 Binary files /dev/null and b/public/follows/43.jpg differ diff --git a/public/follows/51.jpg b/public/follows/51.jpg new file mode 100644 index 000000000..c260f8400 Binary files /dev/null and b/public/follows/51.jpg differ diff --git a/public/follows/52.jpg b/public/follows/52.jpg new file mode 100644 index 000000000..41395c32a Binary files /dev/null and b/public/follows/52.jpg differ diff --git a/public/follows/53.jpg b/public/follows/53.jpg new file mode 100644 index 000000000..cdd408df7 Binary files /dev/null and b/public/follows/53.jpg differ diff --git a/public/follows/61.jpg b/public/follows/61.jpg new file mode 100644 index 000000000..ca29f3b06 Binary files /dev/null and b/public/follows/61.jpg differ diff --git a/public/follows/62.jpg b/public/follows/62.jpg new file mode 100644 index 000000000..8ff1d2c47 Binary files /dev/null and b/public/follows/62.jpg differ diff --git a/public/follows/63.jpg b/public/follows/63.jpg new file mode 100644 index 000000000..45f0566a6 Binary files /dev/null and b/public/follows/63.jpg differ diff --git a/public/follows/71.jpg b/public/follows/71.jpg new file mode 100644 index 000000000..5f616db88 Binary files /dev/null and b/public/follows/71.jpg differ diff --git a/public/follows/72.jpg b/public/follows/72.jpg new file mode 100644 index 000000000..4c9c6f17f Binary files /dev/null and b/public/follows/72.jpg differ diff --git a/public/follows/73.jpg b/public/follows/73.jpg new file mode 100644 index 000000000..7802d78bf Binary files /dev/null and b/public/follows/73.jpg differ diff --git a/public/follows/icon7.png b/public/follows/icon7.png new file mode 100644 index 000000000..eb8b0a19c Binary files /dev/null and b/public/follows/icon7.png differ diff --git a/public/group_set.png b/public/group_set.png new file mode 100755 index 000000000..0e3624247 Binary files /dev/null and b/public/group_set.png differ diff --git a/public/group_test.png b/public/group_test.png new file mode 100644 index 000000000..5b13fac05 Binary files /dev/null and b/public/group_test.png differ diff --git a/public/group_users.png b/public/group_users.png new file mode 100755 index 000000000..f29856682 Binary files /dev/null and b/public/group_users.png differ diff --git a/public/helpImgs/dr_01en.jpg b/public/helpImgs/dr_01en.jpg new file mode 100644 index 000000000..7b751f5a5 Binary files /dev/null and b/public/helpImgs/dr_01en.jpg differ diff --git a/public/helpImgs/dr_02en.jpg b/public/helpImgs/dr_02en.jpg new file mode 100644 index 000000000..93dc75592 Binary files /dev/null and b/public/helpImgs/dr_02en.jpg differ diff --git a/public/helpImgs/dr_03en.jpg b/public/helpImgs/dr_03en.jpg new file mode 100644 index 000000000..95687c969 Binary files /dev/null and b/public/helpImgs/dr_03en.jpg differ diff --git a/public/helpImgs/dr_04en.jpg b/public/helpImgs/dr_04en.jpg new file mode 100644 index 000000000..6cdfd27fd Binary files /dev/null and b/public/helpImgs/dr_04en.jpg differ diff --git a/public/helpImgs/dr_05en.jpg b/public/helpImgs/dr_05en.jpg new file mode 100644 index 000000000..2b0d65f65 Binary files /dev/null and b/public/helpImgs/dr_05en.jpg differ diff --git a/public/helpImgs/dr_06en.jpg b/public/helpImgs/dr_06en.jpg new file mode 100644 index 000000000..2c4d23ef8 Binary files /dev/null and b/public/helpImgs/dr_06en.jpg differ diff --git a/public/helpImgs/dr_07en.jpg b/public/helpImgs/dr_07en.jpg new file mode 100644 index 000000000..37b9ad7ed Binary files /dev/null and b/public/helpImgs/dr_07en.jpg differ diff --git a/public/helpImgs/dr_08en.jpg b/public/helpImgs/dr_08en.jpg new file mode 100644 index 000000000..375832e3f Binary files /dev/null and b/public/helpImgs/dr_08en.jpg differ diff --git a/public/helpImgs/dr_09en.jpg b/public/helpImgs/dr_09en.jpg new file mode 100755 index 000000000..2309f2ce2 Binary files /dev/null and b/public/helpImgs/dr_09en.jpg differ diff --git a/public/helpImgs/dr_10en.jpg b/public/helpImgs/dr_10en.jpg new file mode 100755 index 000000000..169612ebb Binary files /dev/null and b/public/helpImgs/dr_10en.jpg differ diff --git a/public/helpImgs/dr_11en.jpg b/public/helpImgs/dr_11en.jpg new file mode 100755 index 000000000..46119fda1 Binary files /dev/null and b/public/helpImgs/dr_11en.jpg differ diff --git a/public/helpImgs/top_00en.jpg b/public/helpImgs/top_00en.jpg new file mode 100644 index 000000000..6c941beb3 Binary files /dev/null and b/public/helpImgs/top_00en.jpg differ diff --git a/public/hint.jpg b/public/hint.jpg new file mode 100644 index 000000000..ff9361f7d Binary files /dev/null and b/public/hint.jpg differ diff --git a/public/homeAI/addHomeAIBoxBg.png b/public/homeAI/addHomeAIBoxBg.png new file mode 100644 index 000000000..b4a24190e Binary files /dev/null and b/public/homeAI/addHomeAIBoxBg.png differ diff --git a/public/homeAI/homeIcon.png b/public/homeAI/homeIcon.png new file mode 100644 index 000000000..c333b827c Binary files /dev/null and b/public/homeAI/homeIcon.png differ diff --git a/public/homeAI/smartIcon.png b/public/homeAI/smartIcon.png new file mode 100644 index 000000000..d237d1e9c Binary files /dev/null and b/public/homeAI/smartIcon.png differ diff --git a/public/homeai.png b/public/homeai.png new file mode 100644 index 000000000..4b9ab33a6 Binary files /dev/null and b/public/homeai.png differ diff --git a/public/i18n/tap-i18n.json b/public/i18n/tap-i18n.json new file mode 100644 index 000000000..e0e61f40f --- /dev/null +++ b/public/i18n/tap-i18n.json @@ -0,0 +1 @@ +{"zh":{"project":{"anonymous":"匿名使用","anonymousAgree":"匿名使用表示已经同意","gstServiceSpec":"《来了吗用户协议》","anonymousWarn":"友情提示:匿名账户的数据在程序卸载后可能会无法继续使用,如需永久保存数据,请注册或登录使用。","kownIt":"知道了","funnyLogin":"很卖萌的登录中~~~","regWithEmail":"使用电子邮件注册","loginWithAccount":"已经有帐户?登录","kmxdkj":"昆明讯动科技有限公司","gstSecurity":"来了吗 - 安全","resetPassword":"重设密码","resetPasswordSuc":"你的来了吗密码已经重置成功","loginWithNewPassword":"现在可以用新密码登录你的来了吗帐号。","canSetNewPassword":"现在你可以重新设定您的来了吗密码。","newPassword":"新密码:","sixToSixteenCh":"6到16个字符。","inputPasswordAgain":"重复输入新密码:","OK":"完成","confirm":"确定","gst":"故事贴","gstAdWord":"一帖 一故事,分享 朋友圈","linkDownloadIPhone":"Iphone用户下载","linkDownloadAndroid":"Android用户下载","gstPrivacy":"© 2015 故事贴","gstProductSerial":"滇 ICP 备 14007324号","homePage":"首页","discovery":"探索","message":"消息","me":"我","favorite":"收藏夹","selectPicFromGallery":"从相册选取照片","importFromPaster":"从粘贴板导入链接","importFromShareExtension":"从系统分享导入链接","takePicture":"拍照","receiving":"收取中...","newInfo":"动态","loadFailNotification":"加载失败,请检查网络设置或稍后重试","commentYourStory":"点评了您的故事","alsoCommentThisStory":"也点评了此故事","pCommentReplyThisStory":"回复了您在","pCommentReplyThisStoryEnd":"的评论","alsoFavouriteThisStory":"也赞了此故事","replyStoryYouJoin":"回复了您参与讨论的故事","replyYourStory":"回复了您的故事","publishNewStory":"发布了新故事","recommendAStory":"推荐您一个新故事","askFriends":"邀请您加为好友!","alreadyAdded":"已添加","acceptInvitation":"接受邀请","alreadySendInvitation":"已发送邀请","loading":"加载中...","publishTo":"同读每一篇故事的朋友们都有一个朋友圈","readerUnit":"位读者","noMessages":"暂无任何消息:(","createAccount":"注册","signUpMeansWhat":"如果注册就意味着同意了用户协议","userName":"请输入用户名","passWord":"请输入密码","emailAddress":"邮箱","logIn":"登录","forgotPassword":"忘了密码?","forgetPwd":"登录遇到问题?","followPeople":"关注您喜欢的人","knowActivity":"及时了解对方的动态","chooseAtLeast":"再关注","chooseAtLeast1":"再关注","toFollow1":"位作者,订阅喜欢的文章","conTinue":"继续","searchMyPosts":"搜索我的帖子","mySelf":"我的","uploadFigure":"上传头像","followers":" 名关注者","follower":"关注者","ppl":"人","following":"正在关注","draft":" 份草稿","checkAll":"查看全部 ","numOfStory":" 个故事","follow":"关注","unFollow":"已关注","draftTxt":"草稿","clearDraftBox":"清空草稿箱","cancel":"取消","inflateAndDeflate":"缩放和裁剪","store":"存储","edit":"编辑","delete":"删除","saveDraft":"存储草稿","publish":"发表","plsAddImageAndText":"请添加图文","readOriginalText":" 阅读原文","pasteLink":"黏贴链接","insertLink":"插入链接","normal":"正常","reference":"引用","tooBigAndWait":"帖子那么大,让我分析下","plsWait":"请稍候...","interruptAndIgnore":"中断,跳过未导入内容","total":"总计 ","numOfContent":" 段内容,","numOfProcessed":" 段已处理,请稍候...","mainTitle":"主标题","subTitle":"副标题","yourStoryPublished":"您的故事已发表","hotTopics":"热门话题:","addTopicOrComment":"添加#话题#或评论","gossip":"闲聊","gossipAnonymous":"匿名闲聊:","send":"发送","saySomething":"说点什么...","exchange":"换","useTips":"来了吗使用攻略","howToRegister":"如何注册来了吗?","howToPublish":"如何发帖","changePicture":"图片调整","addTextAndEdit":"添加文字&排版","oneKeyImport":"一键导入","shareFrindField":"分享朋友圈?","forwardInWechat":"微信中使用段落转发","howToResetPassword":"如何找回密码","howToSearchMyPost":"如何搜索自己的文章","howToPostAnonymous":"如何匿名发帖","howToPutPostInFavourite":"如何收藏喜欢的文章","welcomeContactUs":"如果您对使用攻略有任何建议或问题,随时欢迎联系我们","adminEmail":"联系邮箱:admin@tiegushi.com","searchTopic":"探索","popTopics":"流行话题","searchPeople":"搜索作者和#话题#","plainTopic":"话题","discoverPeople":"查找值得关注的作者","plainPeople":"作者","recommandUser":"热门作者","isLoading":"正在加载中...","story":"故事","browse":"浏览","times":"次","storyNotFound":"该贴不存在或已被删除","publishLater":"稍后发表","saveLater":"稍后保存","report":"举报","reportAt":"举报@","storyRef1":"的故事《","storyRef2":"》","addReportContent":"添加举报理由","clickMeToCreate":"点我创作","operate":"操作","share":"分享","cancelPublish":"取消发表","shareWechatFriend":"分享给微信好友","shareWechatFriendField":"分享到微信朋友圈","shareStoryGroup":"分享到故事贴群","shareQQ":"分享到QQ","shareQQZone":"分享到QQ空间","shareMore":"分享到更多应用","fromNumOf":"――节选自 第","segment":"段 ","hotStores":"热门文章","downloadGSTAndReadMore":"(下载“来了吗”查看更多精彩内容)","weTogetherThatYears":"哪些年,我们在一起","fromBestAPP":"本文由史上最好用的图文排版APP","supportCreate":"支持创作","forMorePayAttentionTo":"更多精彩内容,敬请点击关注","wechatPublicAccount":"微信公众号","forMoreSearch":"更多精彩内容,请搜索关注","noPrivacyExposed":"故事贴无法获得微信信息,请放心使用","comment":"评论","commentAnonymous":"匿名评论","forwardSegment":"转发本段","readLoudly":"朗读","writeComment":"写评论","comeon":"快来抢沙发~~( ̄▽ ̄)","thanksReport":"感谢举报","reportSuccess":"举报成功","reportDeclaration":"谢谢您!来了吗坚决反对色情、暴力、欺诈信息,我们会认真处理您的举报,维护绿色、健康的网络环境。","blacklist":"黑名单","setting":"设置","done":"完成","save":"存储","email":"电子邮件","enterEmail":"输入电子邮件","notification":"通知","notifiButton":"接收新消息通知","notifiTips":"如果你要关闭或开启来了吗的新消息通知,请在系统设置中,找到应用程序“来了吗”更改","notifiTipsIos":"如果你要关闭或开启来了吗的新消息通知,请在“设置”-“通知”功能中,找到应用程序“来了吗”更改。","changePass":"修改密码","currentPass":"当前密码","newPass":"新密码","confirmPass":"确认","language":"语言","English":"English","Chinese":"中文","version":"新版本检测","latestversion":"当前已是最新版本","Newversion":"有新版本","about":"关于来了吗","devMode":"开发者模式","aboutTitle":"关于 来了吗","ver":"版本","logOut":"退出登录","NewFriends":"新朋友","NickName":"昵称","Gender":"性别","Save":"保存","Male":"男","Female":"女","enteranewnickname":"输入新昵称","FavoriteStory":"喜欢的故事","Publish":"发布","NoSearchResults":"查无结果","Searching":"查找中...","Contacts":"联系人","NewFriendsList":"新的朋友","FailToLoad":"加载失败,请检查网络设置或稍后重试","FateAh":"缘分啊,我们已偶遇","times1":"次了!","Moments":"朋友圈","RecommendPosts":"看过当前帖子的朋友看过的帖子,您都看过了,特意为您推荐以下您没看过帖子...","AlsoRead":"看过当前帖子的朋友,还看过...","RecommendReadMore":"看过该帖的朋友还看过...","PersonReadYourForward":"人读过您的转发","AlsoComment":"也点评了此故事","CommentPeply":"回复了您的评论","recommendA":"看过","recommendB":"后,推荐您阅读","theStoryGroupMsg":"朋友圈消息","readed":"读过","postedastory":"发布了一个故事","readedastory":"阅读了一个故事","recentlyviewedstories":"最近浏览的故事","readFollowTips":"阅读超过3次后提示读者关注","hotShareQQZoneShare":"来了吗分享QQ空间","wechatNotInstalled":"未安装微信客户端,分享失败","failToShare":"分享失败","qqNotInstalled":"未安装QQ客户端,分享失败","preparePicAndWait":"准备故事的主题图片,请稍等","failToGetPicAndTryAgain":"无法获取故事标题图片,请稍后重试!","loginByWechat":"使用微信登录","Account":"帐号管理","addAccount":"添加帐号","add":"添加","readMore":"继续阅读","readingRoom":"阅览室","emailFollow":"邮件","appFollow":"应用","theme_blue_anonymous":"匿名使用","theme_blue_regWithEmail":"注册","theme_blue_loginWithAccount":"登录","theme_blue_socialLoginTips":"或用其它方式登录"}}} \ No newline at end of file diff --git a/public/i18n/zh.json b/public/i18n/zh.json new file mode 100644 index 000000000..1ead051e0 --- /dev/null +++ b/public/i18n/zh.json @@ -0,0 +1 @@ +{"project":{"anonymous":"匿名使用","anonymousAgree":"匿名使用表示已经同意","gstServiceSpec":"《来了吗用户协议》","anonymousWarn":"友情提示:匿名账户的数据在程序卸载后可能会无法继续使用,如需永久保存数据,请注册或登录使用。","kownIt":"知道了","funnyLogin":"很卖萌的登录中~~~","regWithEmail":"使用电子邮件注册","loginWithAccount":"已经有帐户?登录","kmxdkj":"昆明讯动科技有限公司","gstSecurity":"来了吗 - 安全","resetPassword":"重设密码","resetPasswordSuc":"你的来了吗密码已经重置成功","loginWithNewPassword":"现在可以用新密码登录你的来了吗帐号。","canSetNewPassword":"现在你可以重新设定您的来了吗密码。","newPassword":"新密码:","sixToSixteenCh":"6到16个字符。","inputPasswordAgain":"重复输入新密码:","OK":"完成","confirm":"确定","gst":"故事贴","gstAdWord":"一帖 一故事,分享 朋友圈","linkDownloadIPhone":"Iphone用户下载","linkDownloadAndroid":"Android用户下载","gstPrivacy":"© 2015 故事贴","gstProductSerial":"滇 ICP 备 14007324号","homePage":"首页","discovery":"探索","message":"消息","me":"我","favorite":"收藏夹","selectPicFromGallery":"从相册选取照片","importFromPaster":"从粘贴板导入链接","importFromShareExtension":"从系统分享导入链接","takePicture":"拍照","receiving":"收取中...","newInfo":"动态","loadFailNotification":"加载失败,请检查网络设置或稍后重试","commentYourStory":"点评了您的故事","alsoCommentThisStory":"也点评了此故事","pCommentReplyThisStory":"回复了您在","pCommentReplyThisStoryEnd":"的评论","alsoFavouriteThisStory":"也赞了此故事","replyStoryYouJoin":"回复了您参与讨论的故事","replyYourStory":"回复了您的故事","publishNewStory":"发布了新故事","recommendAStory":"推荐您一个新故事","askFriends":"邀请您加为好友!","alreadyAdded":"已添加","acceptInvitation":"接受邀请","alreadySendInvitation":"已发送邀请","loading":"加载中...","publishTo":"同读每一篇故事的朋友们都有一个朋友圈","readerUnit":"位读者","noMessages":"暂无任何消息:(","createAccount":"注册","signUpMeansWhat":"如果注册就意味着同意了用户协议","userName":"请输入用户名","passWord":"请输入密码","emailAddress":"邮箱","logIn":"登录","forgotPassword":"忘了密码?","forgetPwd":"登录遇到问题?","followPeople":"关注您喜欢的人","knowActivity":"及时了解对方的动态","chooseAtLeast":"再关注","chooseAtLeast1":"再关注","toFollow1":"位作者,订阅喜欢的文章","conTinue":"继续","searchMyPosts":"搜索我的帖子","mySelf":"我的","uploadFigure":"上传头像","followers":" 名关注者","follower":"关注者","ppl":"人","following":"正在关注","draft":" 份草稿","checkAll":"查看全部 ","numOfStory":" 个故事","follow":"关注","unFollow":"已关注","draftTxt":"草稿","clearDraftBox":"清空草稿箱","cancel":"取消","inflateAndDeflate":"缩放和裁剪","store":"存储","edit":"编辑","delete":"删除","saveDraft":"存储草稿","publish":"发表","plsAddImageAndText":"请添加图文","readOriginalText":" 阅读原文","pasteLink":"黏贴链接","insertLink":"插入链接","normal":"正常","reference":"引用","tooBigAndWait":"帖子那么大,让我分析下","plsWait":"请稍候...","interruptAndIgnore":"中断,跳过未导入内容","total":"总计 ","numOfContent":" 段内容,","numOfProcessed":" 段已处理,请稍候...","mainTitle":"主标题","subTitle":"副标题","yourStoryPublished":"您的故事已发表","hotTopics":"热门话题:","addTopicOrComment":"添加#话题#或评论","gossip":"闲聊","gossipAnonymous":"匿名闲聊:","send":"发送","saySomething":"说点什么...","exchange":"换","useTips":"来了吗使用攻略","howToRegister":"如何注册来了吗?","howToPublish":"如何发帖","changePicture":"图片调整","addTextAndEdit":"添加文字&排版","oneKeyImport":"一键导入","shareFrindField":"分享朋友圈?","forwardInWechat":"微信中使用段落转发","howToResetPassword":"如何找回密码","howToSearchMyPost":"如何搜索自己的文章","howToPostAnonymous":"如何匿名发帖","howToPutPostInFavourite":"如何收藏喜欢的文章","welcomeContactUs":"如果您对使用攻略有任何建议或问题,随时欢迎联系我们","adminEmail":"联系邮箱:admin@tiegushi.com","searchTopic":"探索","popTopics":"流行话题","searchPeople":"搜索作者和#话题#","plainTopic":"话题","discoverPeople":"查找值得关注的作者","plainPeople":"作者","recommandUser":"热门作者","isLoading":"正在加载中...","story":"故事","browse":"浏览","times":"次","storyNotFound":"该贴不存在或已被删除","publishLater":"稍后发表","saveLater":"稍后保存","report":"举报","reportAt":"举报@","storyRef1":"的故事《","storyRef2":"》","addReportContent":"添加举报理由","clickMeToCreate":"点我创作","operate":"操作","share":"分享","cancelPublish":"取消发表","shareWechatFriend":"分享给微信好友","shareWechatFriendField":"分享到微信朋友圈","shareStoryGroup":"分享到故事贴群","shareQQ":"分享到QQ","shareQQZone":"分享到QQ空间","shareMore":"分享到更多应用","fromNumOf":"――节选自 第","segment":"段 ","hotStores":"热门文章","downloadGSTAndReadMore":"(下载“来了吗”查看更多精彩内容)","weTogetherThatYears":"哪些年,我们在一起","fromBestAPP":"本文由史上最好用的图文排版APP","supportCreate":"支持创作","forMorePayAttentionTo":"更多精彩内容,敬请点击关注","wechatPublicAccount":"微信公众号","forMoreSearch":"更多精彩内容,请搜索关注","noPrivacyExposed":"故事贴无法获得微信信息,请放心使用","comment":"评论","commentAnonymous":"匿名评论","forwardSegment":"转发本段","readLoudly":"朗读","writeComment":"写评论","comeon":"快来抢沙发~~( ̄▽ ̄)","thanksReport":"感谢举报","reportSuccess":"举报成功","reportDeclaration":"谢谢您!来了吗坚决反对色情、暴力、欺诈信息,我们会认真处理您的举报,维护绿色、健康的网络环境。","blacklist":"黑名单","setting":"设置","done":"完成","save":"存储","email":"电子邮件","enterEmail":"输入电子邮件","notification":"通知","notifiButton":"接收新消息通知","notifiTips":"如果你要关闭或开启来了吗的新消息通知,请在系统设置中,找到应用程序“来了吗”更改","notifiTipsIos":"如果你要关闭或开启来了吗的新消息通知,请在“设置”-“通知”功能中,找到应用程序“来了吗”更改。","changePass":"修改密码","currentPass":"当前密码","newPass":"新密码","confirmPass":"确认","language":"语言","English":"English","Chinese":"中文","version":"新版本检测","latestversion":"当前已是最新版本","Newversion":"有新版本","about":"关于来了吗","devMode":"开发者模式","aboutTitle":"关于 来了吗","ver":"版本","logOut":"退出登录","NewFriends":"新朋友","NickName":"昵称","Gender":"性别","Save":"保存","Male":"男","Female":"女","enteranewnickname":"输入新昵称","FavoriteStory":"喜欢的故事","Publish":"发布","NoSearchResults":"查无结果","Searching":"查找中...","Contacts":"联系人","NewFriendsList":"新的朋友","FailToLoad":"加载失败,请检查网络设置或稍后重试","FateAh":"缘分啊,我们已偶遇","times1":"次了!","Moments":"朋友圈","RecommendPosts":"看过当前帖子的朋友看过的帖子,您都看过了,特意为您推荐以下您没看过帖子...","AlsoRead":"看过当前帖子的朋友,还看过...","RecommendReadMore":"看过该帖的朋友还看过...","PersonReadYourForward":"人读过您的转发","AlsoComment":"也点评了此故事","CommentPeply":"回复了您的评论","recommendA":"看过","recommendB":"后,推荐您阅读","theStoryGroupMsg":"朋友圈消息","readed":"读过","postedastory":"发布了一个故事","readedastory":"阅读了一个故事","recentlyviewedstories":"最近浏览的故事","readFollowTips":"阅读超过3次后提示读者关注","hotShareQQZoneShare":"来了吗分享QQ空间","wechatNotInstalled":"未安装微信客户端,分享失败","failToShare":"分享失败","qqNotInstalled":"未安装QQ客户端,分享失败","preparePicAndWait":"准备故事的主题图片,请稍等","failToGetPicAndTryAgain":"无法获取故事标题图片,请稍后重试!","loginByWechat":"使用微信登录","Account":"帐号管理","addAccount":"添加帐号","add":"添加","readMore":"继续阅读","readingRoom":"阅览室","emailFollow":"邮件","appFollow":"应用","theme_blue_anonymous":"匿名使用","theme_blue_regWithEmail":"注册","theme_blue_loginWithAccount":"登录","theme_blue_socialLoginTips":"或用其它方式登录"}} \ No newline at end of file diff --git a/public/icon-2.png b/public/icon-2.png new file mode 100644 index 000000000..f92a36b23 Binary files /dev/null and b/public/icon-2.png differ diff --git a/public/icon.png b/public/icon.png new file mode 100755 index 000000000..f47469786 Binary files /dev/null and b/public/icon.png differ diff --git a/public/icon/close.svg b/public/icon/close.svg new file mode 100755 index 000000000..c470996b7 --- /dev/null +++ b/public/icon/close.svg @@ -0,0 +1 @@ ++ + ++ ++ + \ No newline at end of file diff --git a/public/icon/icon-head-handle.png b/public/icon/icon-head-handle.png new file mode 100644 index 000000000..d064ab5c5 Binary files /dev/null and b/public/icon/icon-head-handle.png differ diff --git a/public/icon/icon-no.png b/public/icon/icon-no.png new file mode 100644 index 000000000..defaf8a11 Binary files /dev/null and b/public/icon/icon-no.png differ diff --git a/public/icon/icon-yes.png b/public/icon/icon-yes.png new file mode 100644 index 000000000..ddcc684aa Binary files /dev/null and b/public/icon/icon-yes.png differ diff --git a/public/icon/right.svg b/public/icon/right.svg new file mode 100755 index 000000000..1dda2b0b2 --- /dev/null +++ b/public/icon/right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/48X48-icon_54.png b/public/img/48X48-icon_54.png new file mode 100755 index 000000000..fde107a96 Binary files /dev/null and b/public/img/48X48-icon_54.png differ diff --git a/public/img/48X48create_32.png b/public/img/48X48create_32.png new file mode 100755 index 000000000..79481f983 Binary files /dev/null and b/public/img/48X48create_32.png differ diff --git a/public/img/48X48follow_20.png b/public/img/48X48follow_20.png new file mode 100755 index 000000000..d36306fb8 Binary files /dev/null and b/public/img/48X48follow_20.png differ diff --git a/public/img/48X48follow_60.png b/public/img/48X48follow_60.png new file mode 100755 index 000000000..536e62fdd Binary files /dev/null and b/public/img/48X48follow_60.png differ diff --git a/public/img/48X48home_12.png b/public/img/48X48home_12.png new file mode 100755 index 000000000..8eca65f84 Binary files /dev/null and b/public/img/48X48home_12.png differ diff --git a/public/img/48X48home_52.png b/public/img/48X48home_52.png new file mode 100755 index 000000000..f64e31159 Binary files /dev/null and b/public/img/48X48home_52.png differ diff --git a/public/img/48X48imassage_56.png b/public/img/48X48imassage_56.png new file mode 100755 index 000000000..f854bfb29 Binary files /dev/null and b/public/img/48X48imassage_56.png differ diff --git a/public/img/48X48lock_76.png b/public/img/48X48lock_76.png new file mode 100755 index 000000000..4e86822cb Binary files /dev/null and b/public/img/48X48lock_76.png differ diff --git a/public/img/48X48massage_16.png b/public/img/48X48massage_16.png new file mode 100755 index 000000000..4538a042d Binary files /dev/null and b/public/img/48X48massage_16.png differ diff --git a/public/img/48X48me_18.png b/public/img/48X48me_18.png new file mode 100755 index 000000000..c56fdb9fa Binary files /dev/null and b/public/img/48X48me_18.png differ diff --git a/public/img/48X48me_58.png b/public/img/48X48me_58.png new file mode 100755 index 000000000..559aea605 Binary files /dev/null and b/public/img/48X48me_58.png differ diff --git a/public/img/48X48screen_34.png b/public/img/48X48screen_34.png new file mode 100755 index 000000000..934804c67 Binary files /dev/null and b/public/img/48X48screen_34.png differ diff --git a/public/img/48X48search_14.png b/public/img/48X48search_14.png new file mode 100755 index 000000000..12f8f77e6 Binary files /dev/null and b/public/img/48X48search_14.png differ diff --git a/public/img/admin-portal.png b/public/img/admin-portal.png new file mode 100755 index 000000000..059487a7e Binary files /dev/null and b/public/img/admin-portal.png differ diff --git a/public/img/arrow-down.png b/public/img/arrow-down.png new file mode 100755 index 000000000..05db2c7b6 Binary files /dev/null and b/public/img/arrow-down.png differ diff --git a/public/img/arrow-up.png b/public/img/arrow-up.png new file mode 100755 index 000000000..5c3b8765a Binary files /dev/null and b/public/img/arrow-up.png differ diff --git a/public/img/b_like.png b/public/img/b_like.png new file mode 100644 index 000000000..b2e6d06ad Binary files /dev/null and b/public/img/b_like.png differ diff --git a/public/img/b_unlike.png b/public/img/b_unlike.png new file mode 100644 index 000000000..1c3863597 Binary files /dev/null and b/public/img/b_unlike.png differ diff --git a/public/img/bellBg.jpg b/public/img/bellBg.jpg new file mode 100644 index 000000000..bdf6c2f10 Binary files /dev/null and b/public/img/bellBg.jpg differ diff --git a/public/img/bg.jpg b/public/img/bg.jpg new file mode 100644 index 000000000..f716977e3 Binary files /dev/null and b/public/img/bg.jpg differ diff --git a/public/img/bg.png b/public/img/bg.png new file mode 100644 index 000000000..8bdef111f Binary files /dev/null and b/public/img/bg.png differ diff --git a/public/img/bg2.jpg b/public/img/bg2.jpg new file mode 100644 index 000000000..f4ff77f46 Binary files /dev/null and b/public/img/bg2.jpg differ diff --git a/public/img/closebtn.png b/public/img/closebtn.png new file mode 100644 index 000000000..79d035f03 Binary files /dev/null and b/public/img/closebtn.png differ diff --git a/public/img/company-loading.gif b/public/img/company-loading.gif new file mode 100755 index 000000000..a259e55a9 Binary files /dev/null and b/public/img/company-loading.gif differ diff --git a/public/img/company-nodata.png b/public/img/company-nodata.png new file mode 100755 index 000000000..0d0f5522c Binary files /dev/null and b/public/img/company-nodata.png differ diff --git a/public/img/creator-portal.png b/public/img/creator-portal.png new file mode 100755 index 000000000..86c3770f8 Binary files /dev/null and b/public/img/creator-portal.png differ diff --git a/public/img/default-company-logo.png b/public/img/default-company-logo.png new file mode 100755 index 000000000..cb9beb58e Binary files /dev/null and b/public/img/default-company-logo.png differ diff --git a/public/img/demo-1.jpg b/public/img/demo-1.jpg new file mode 100644 index 000000000..8dc57fadb Binary files /dev/null and b/public/img/demo-1.jpg differ diff --git a/public/img/gif/a.png b/public/img/gif/a.png new file mode 100644 index 000000000..1c3863597 Binary files /dev/null and b/public/img/gif/a.png differ diff --git a/public/img/gif/b.png b/public/img/gif/b.png new file mode 100644 index 000000000..39b099298 Binary files /dev/null and b/public/img/gif/b.png differ diff --git a/public/img/gif/c.png b/public/img/gif/c.png new file mode 100644 index 000000000..9f9b427d9 Binary files /dev/null and b/public/img/gif/c.png differ diff --git a/public/img/gif/d.png b/public/img/gif/d.png new file mode 100644 index 000000000..f9cdd06b9 Binary files /dev/null and b/public/img/gif/d.png differ diff --git a/public/img/gif/e.png b/public/img/gif/e.png new file mode 100644 index 000000000..661f62abd Binary files /dev/null and b/public/img/gif/e.png differ diff --git a/public/img/gif/f.png b/public/img/gif/f.png new file mode 100644 index 000000000..b6c15f886 Binary files /dev/null and b/public/img/gif/f.png differ diff --git a/public/img/gif/g.png b/public/img/gif/g.png new file mode 100644 index 000000000..f05699c4b Binary files /dev/null and b/public/img/gif/g.png differ diff --git a/public/img/gif/h.png b/public/img/gif/h.png new file mode 100644 index 000000000..c4c25ba8e Binary files /dev/null and b/public/img/gif/h.png differ diff --git a/public/img/gif/i.png b/public/img/gif/i.png new file mode 100644 index 000000000..fcdaa0426 Binary files /dev/null and b/public/img/gif/i.png differ diff --git a/public/img/gif/j.png b/public/img/gif/j.png new file mode 100644 index 000000000..1c3863597 Binary files /dev/null and b/public/img/gif/j.png differ diff --git a/public/img/gif/k.png b/public/img/gif/k.png new file mode 100644 index 000000000..0df8bde18 Binary files /dev/null and b/public/img/gif/k.png differ diff --git a/public/img/gif/l.png b/public/img/gif/l.png new file mode 100644 index 000000000..6b7e47089 Binary files /dev/null and b/public/img/gif/l.png differ diff --git a/public/img/gif/m.png b/public/img/gif/m.png new file mode 100644 index 000000000..13754c629 Binary files /dev/null and b/public/img/gif/m.png differ diff --git a/public/img/gif/n.png b/public/img/gif/n.png new file mode 100644 index 000000000..6cc9a777f Binary files /dev/null and b/public/img/gif/n.png differ diff --git a/public/img/gif/o.png b/public/img/gif/o.png new file mode 100644 index 000000000..fc2201c60 Binary files /dev/null and b/public/img/gif/o.png differ diff --git a/public/img/gif/p.png b/public/img/gif/p.png new file mode 100644 index 000000000..135569cb0 Binary files /dev/null and b/public/img/gif/p.png differ diff --git a/public/img/gif/q.png b/public/img/gif/q.png new file mode 100644 index 000000000..7f0dc9815 Binary files /dev/null and b/public/img/gif/q.png differ diff --git a/public/img/gif/r.png b/public/img/gif/r.png new file mode 100644 index 000000000..cbc0c4aff Binary files /dev/null and b/public/img/gif/r.png differ diff --git a/public/img/gif/s.png b/public/img/gif/s.png new file mode 100644 index 000000000..e07223e21 Binary files /dev/null and b/public/img/gif/s.png differ diff --git a/public/img/gif/t.png b/public/img/gif/t.png new file mode 100644 index 000000000..95465728d Binary files /dev/null and b/public/img/gif/t.png differ diff --git a/public/img/gif/u.png b/public/img/gif/u.png new file mode 100644 index 000000000..6c52d5770 Binary files /dev/null and b/public/img/gif/u.png differ diff --git a/public/img/gif/v.png b/public/img/gif/v.png new file mode 100644 index 000000000..b2e6d06ad Binary files /dev/null and b/public/img/gif/v.png differ diff --git a/public/img/home_clock_green.png b/public/img/home_clock_green.png new file mode 100755 index 000000000..a2f45a543 Binary files /dev/null and b/public/img/home_clock_green.png differ diff --git a/public/img/home_clock_orangered.png b/public/img/home_clock_orangered.png new file mode 100755 index 000000000..ca877e223 Binary files /dev/null and b/public/img/home_clock_orangered.png differ diff --git a/public/img/home_clock_red.png b/public/img/home_clock_red.png new file mode 100755 index 000000000..3460701aa Binary files /dev/null and b/public/img/home_clock_red.png differ diff --git a/public/img/hotpost_disable.png b/public/img/hotpost_disable.png new file mode 100644 index 000000000..ad4f28a2c Binary files /dev/null and b/public/img/hotpost_disable.png differ diff --git a/public/img/hotpost_enable.png b/public/img/hotpost_enable.png new file mode 100644 index 000000000..f32da604f Binary files /dev/null and b/public/img/hotpost_enable.png differ diff --git a/public/img/icon-1.png b/public/img/icon-1.png new file mode 100644 index 000000000..ca6702573 Binary files /dev/null and b/public/img/icon-1.png differ diff --git a/public/img/icon-2.jpg b/public/img/icon-2.jpg new file mode 100644 index 000000000..712741d91 Binary files /dev/null and b/public/img/icon-2.jpg differ diff --git a/public/img/label_new_person.svg b/public/img/label_new_person.svg new file mode 100755 index 000000000..02b3b3f98 --- /dev/null +++ b/public/img/label_new_person.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/layer_resize.png b/public/img/layer_resize.png new file mode 100644 index 000000000..a910f6e36 Binary files /dev/null and b/public/img/layer_resize.png differ diff --git a/public/img/like.gif b/public/img/like.gif new file mode 100644 index 000000000..a075017e4 Binary files /dev/null and b/public/img/like.gif differ diff --git a/public/img/like.png b/public/img/like.png new file mode 100644 index 000000000..b37542b05 Binary files /dev/null and b/public/img/like.png differ diff --git a/public/img/likecount.png b/public/img/likecount.png new file mode 100644 index 000000000..99657f403 Binary files /dev/null and b/public/img/likecount.png differ diff --git a/public/img/move.png b/public/img/move.png new file mode 100644 index 000000000..acd2ab515 Binary files /dev/null and b/public/img/move.png differ diff --git a/public/img/nav.jpg b/public/img/nav.jpg new file mode 100644 index 000000000..14c1bcdd4 Binary files /dev/null and b/public/img/nav.jpg differ diff --git a/public/img/rateicon.png b/public/img/rateicon.png new file mode 100644 index 000000000..3039e4db3 Binary files /dev/null and b/public/img/rateicon.png differ diff --git a/public/img/unlike.png b/public/img/unlike.png new file mode 100644 index 000000000..8660d90d8 Binary files /dev/null and b/public/img/unlike.png differ diff --git a/public/import-tip.png b/public/import-tip.png new file mode 100755 index 000000000..29a83a7e5 Binary files /dev/null and b/public/import-tip.png differ diff --git a/public/install-test.png b/public/install-test.png new file mode 100644 index 000000000..16feb64f2 Binary files /dev/null and b/public/install-test.png differ diff --git a/public/introductoryPage.png b/public/introductoryPage.png new file mode 100644 index 000000000..c499e5c98 Binary files /dev/null and b/public/introductoryPage.png differ diff --git a/public/intros/arrow-down.png b/public/intros/arrow-down.png new file mode 100755 index 000000000..65e0189ed Binary files /dev/null and b/public/intros/arrow-down.png differ diff --git a/public/intros/home-page-tip1.png b/public/intros/home-page-tip1.png new file mode 100755 index 000000000..b851f5875 Binary files /dev/null and b/public/intros/home-page-tip1.png differ diff --git a/public/intros/km-demo-tip.png b/public/intros/km-demo-tip.png new file mode 100755 index 000000000..c9f2e8e3c Binary files /dev/null and b/public/intros/km-demo-tip.png differ diff --git a/public/intros/seriesList-hint1.png b/public/intros/seriesList-hint1.png new file mode 100644 index 000000000..c1a138ef0 Binary files /dev/null and b/public/intros/seriesList-hint1.png differ diff --git a/public/intros/seriesList-hint2.png b/public/intros/seriesList-hint2.png new file mode 100644 index 000000000..94e627642 Binary files /dev/null and b/public/intros/seriesList-hint2.png differ diff --git a/public/intros/seriesList-hint3.png b/public/intros/seriesList-hint3.png new file mode 100644 index 000000000..4d0a6a73d Binary files /dev/null and b/public/intros/seriesList-hint3.png differ diff --git a/public/intros/view-daily-tip.png b/public/intros/view-daily-tip.png new file mode 100755 index 000000000..f07f62045 Binary files /dev/null and b/public/intros/view-daily-tip.png differ diff --git a/public/isLoading.gif b/public/isLoading.gif new file mode 100644 index 000000000..12b1af608 Binary files /dev/null and b/public/isLoading.gif differ diff --git a/public/islatest.png b/public/islatest.png new file mode 100644 index 000000000..06029bead Binary files /dev/null and b/public/islatest.png differ diff --git a/public/label_failure.png b/public/label_failure.png new file mode 100755 index 000000000..4f19187c4 Binary files /dev/null and b/public/label_failure.png differ diff --git a/public/label_new_person.png b/public/label_new_person.png new file mode 100644 index 000000000..7d3c5999f Binary files /dev/null and b/public/label_new_person.png differ diff --git a/public/label_stranger.png b/public/label_stranger.png new file mode 100755 index 000000000..825bdbd74 Binary files /dev/null and b/public/label_stranger.png differ diff --git a/public/label_success.png b/public/label_success.png new file mode 100755 index 000000000..b87e8d7cf Binary files /dev/null and b/public/label_success.png differ diff --git a/public/labeling.png b/public/labeling.png new file mode 100755 index 000000000..49f509c65 Binary files /dev/null and b/public/labeling.png differ diff --git a/public/laile.png b/public/laile.png new file mode 100644 index 000000000..70882ed3d Binary files /dev/null and b/public/laile.png differ diff --git a/public/loadError.png b/public/loadError.png new file mode 100644 index 000000000..b39fcdd31 Binary files /dev/null and b/public/loadError.png differ diff --git a/public/loading.gif b/public/loading.gif new file mode 100644 index 000000000..cc07c4056 Binary files /dev/null and b/public/loading.gif differ diff --git a/public/loginbg1.png b/public/loginbg1.png new file mode 100755 index 000000000..aa75b262b Binary files /dev/null and b/public/loginbg1.png differ diff --git a/public/loginbg1en.jpg b/public/loginbg1en.jpg new file mode 100644 index 000000000..33ace9a48 Binary files /dev/null and b/public/loginbg1en.jpg differ diff --git a/public/loginbg2.jpg b/public/loginbg2.jpg new file mode 100644 index 000000000..fed9f1b15 Binary files /dev/null and b/public/loginbg2.jpg differ diff --git a/public/logo.png b/public/logo.png new file mode 100755 index 000000000..f47469786 Binary files /dev/null and b/public/logo.png differ diff --git a/public/more.png b/public/more.png new file mode 100644 index 000000000..97dd88555 Binary files /dev/null and b/public/more.png differ diff --git a/public/moshengren.png b/public/moshengren.png new file mode 100644 index 000000000..2d0104907 Binary files /dev/null and b/public/moshengren.png differ diff --git a/public/new_logo.png b/public/new_logo.png new file mode 100644 index 000000000..70b2e4f63 Binary files /dev/null and b/public/new_logo.png differ diff --git a/public/nogroup_bg.png b/public/nogroup_bg.png new file mode 100644 index 000000000..b33dbf80b Binary files /dev/null and b/public/nogroup_bg.png differ diff --git a/public/noimage.png b/public/noimage.png new file mode 100644 index 000000000..f74c38a0d Binary files /dev/null and b/public/noimage.png differ diff --git a/public/notallow.png b/public/notallow.png new file mode 100644 index 000000000..b7afdd5e9 Binary files /dev/null and b/public/notallow.png differ diff --git a/public/offline_face_box.svg b/public/offline_face_box.svg new file mode 100644 index 000000000..42fbfdd4b --- /dev/null +++ b/public/offline_face_box.svg @@ -0,0 +1,10 @@ + + diff --git a/public/online_face_box.svg b/public/online_face_box.svg new file mode 100644 index 000000000..b2f207453 --- /dev/null +++ b/public/online_face_box.svg @@ -0,0 +1,10 @@ ++ + ++ + diff --git a/public/other.png b/public/other.png new file mode 100644 index 000000000..bf34fbb67 Binary files /dev/null and b/public/other.png differ diff --git a/public/processbarbg.jpg b/public/processbarbg.jpg new file mode 100644 index 000000000..ef252429c Binary files /dev/null and b/public/processbarbg.jpg differ diff --git a/public/processbaricon.png b/public/processbaricon.png new file mode 100644 index 000000000..c99d7b435 Binary files /dev/null and b/public/processbaricon.png differ diff --git a/public/prompt_close.png b/public/prompt_close.png new file mode 100644 index 000000000..4e3ee51f8 Binary files /dev/null and b/public/prompt_close.png differ diff --git a/public/push.png b/public/push.png new file mode 100644 index 000000000..d277af9ff Binary files /dev/null and b/public/push.png differ diff --git a/public/recently.png b/public/recently.png new file mode 100755 index 000000000..cff0c2078 Binary files /dev/null and b/public/recently.png differ diff --git a/public/reviewing.png b/public/reviewing.png new file mode 100644 index 000000000..4d74669fe Binary files /dev/null and b/public/reviewing.png differ diff --git a/public/scan_b.png b/public/scan_b.png new file mode 100644 index 000000000..267e2d17e Binary files /dev/null and b/public/scan_b.png differ diff --git a/public/scanbarcode.png b/public/scanbarcode.png new file mode 100644 index 000000000..3cccbc2d6 Binary files /dev/null and b/public/scanbarcode.png differ diff --git a/public/scanlancamera.png b/public/scanlancamera.png new file mode 100644 index 000000000..e01770ac0 Binary files /dev/null and b/public/scanlancamera.png differ diff --git a/public/scantip/scanhint1.png b/public/scantip/scanhint1.png new file mode 100644 index 000000000..1ec1e72d7 Binary files /dev/null and b/public/scantip/scanhint1.png differ diff --git a/public/scantip/scanhint2.png b/public/scantip/scanhint2.png new file mode 100644 index 000000000..fe9224a03 Binary files /dev/null and b/public/scantip/scanhint2.png differ diff --git a/public/scantip/scanhint3.png b/public/scantip/scanhint3.png new file mode 100755 index 000000000..86c56df6b Binary files /dev/null and b/public/scantip/scanhint3.png differ diff --git a/public/select.svg b/public/select.svg new file mode 100644 index 000000000..7bc72b17e --- /dev/null +++ b/public/select.svg @@ -0,0 +1 @@ ++ + ++ \ No newline at end of file diff --git a/public/select_n.svg b/public/select_n.svg new file mode 100644 index 000000000..3daef76d1 --- /dev/null +++ b/public/select_n.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/public/select_p.svg b/public/select_p.svg new file mode 100644 index 000000000..015570abe --- /dev/null +++ b/public/select_p.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/public/series-select-n.png b/public/series-select-n.png new file mode 100644 index 000000000..5f17a2a6c Binary files /dev/null and b/public/series-select-n.png differ diff --git a/public/series-select-p.png b/public/series-select-p.png new file mode 100644 index 000000000..7f33d832f Binary files /dev/null and b/public/series-select-p.png differ diff --git a/public/seriesList_blank.png b/public/seriesList_blank.png new file mode 100644 index 000000000..6a74fc69b Binary files /dev/null and b/public/seriesList_blank.png differ diff --git a/public/share/share_more.png b/public/share/share_more.png new file mode 100644 index 000000000..fdbc7fd4d Binary files /dev/null and b/public/share/share_more.png differ diff --git a/public/share/share_qq_friends.png b/public/share/share_qq_friends.png new file mode 100644 index 000000000..b012ecf09 Binary files /dev/null and b/public/share/share_qq_friends.png differ diff --git a/public/share/share_qq_qzone.png b/public/share/share_qq_qzone.png new file mode 100644 index 000000000..3bfe20b81 Binary files /dev/null and b/public/share/share_qq_qzone.png differ diff --git a/public/share/share_weibo.png b/public/share/share_weibo.png new file mode 100644 index 000000000..75bf2fd08 Binary files /dev/null and b/public/share/share_weibo.png differ diff --git a/public/share/share_weixin_friends.png b/public/share/share_weixin_friends.png new file mode 100644 index 000000000..d5485e50a Binary files /dev/null and b/public/share/share_weixin_friends.png differ diff --git a/public/share/share_weixin_timeline.png b/public/share/share_weixin_timeline.png new file mode 100644 index 000000000..134f2ac94 Binary files /dev/null and b/public/share/share_weixin_timeline.png differ diff --git a/public/sharereadingroombg.png b/public/sharereadingroombg.png new file mode 100755 index 000000000..611459e27 Binary files /dev/null and b/public/sharereadingroombg.png differ diff --git a/public/sign.png b/public/sign.png new file mode 100644 index 000000000..f925622ee Binary files /dev/null and b/public/sign.png differ diff --git a/public/splashScreen/m1.jpg b/public/splashScreen/m1.jpg new file mode 100755 index 000000000..640e0c26a Binary files /dev/null and b/public/splashScreen/m1.jpg differ diff --git a/public/splashScreen/m2.jpg b/public/splashScreen/m2.jpg new file mode 100755 index 000000000..aad6b9fcd Binary files /dev/null and b/public/splashScreen/m2.jpg differ diff --git a/public/splashScreen/m3.jpg b/public/splashScreen/m3.jpg new file mode 100755 index 000000000..f8beb2e67 Binary files /dev/null and b/public/splashScreen/m3.jpg differ diff --git a/public/splashScreen/m4.jpg b/public/splashScreen/m4.jpg new file mode 100755 index 000000000..02708e7c0 Binary files /dev/null and b/public/splashScreen/m4.jpg differ diff --git a/public/stop_play.png b/public/stop_play.png new file mode 100755 index 000000000..96052224b Binary files /dev/null and b/public/stop_play.png differ diff --git a/public/stranger.png b/public/stranger.png new file mode 100644 index 000000000..d31e5753d Binary files /dev/null and b/public/stranger.png differ diff --git a/public/swiper.gif b/public/swiper.gif new file mode 100644 index 000000000..d517b46b8 Binary files /dev/null and b/public/swiper.gif differ diff --git a/public/sysMessages.png b/public/sysMessages.png new file mode 100755 index 000000000..8a3e81361 Binary files /dev/null and b/public/sysMessages.png differ diff --git a/public/test_fail.png b/public/test_fail.png new file mode 100644 index 000000000..6dbb00101 Binary files /dev/null and b/public/test_fail.png differ diff --git a/public/test_success.png b/public/test_success.png new file mode 100644 index 000000000..a9082d3e2 Binary files /dev/null and b/public/test_success.png differ diff --git a/public/theme/theme1.jpg b/public/theme/theme1.jpg new file mode 100644 index 000000000..ae88d4f08 Binary files /dev/null and b/public/theme/theme1.jpg differ diff --git a/public/theme/theme2.jpg b/public/theme/theme2.jpg new file mode 100644 index 000000000..ebba6470f Binary files /dev/null and b/public/theme/theme2.jpg differ diff --git a/public/theme/theme3.jpg b/public/theme/theme3.jpg new file mode 100644 index 000000000..ee149e45b Binary files /dev/null and b/public/theme/theme3.jpg differ diff --git a/public/theme/theme4.jpg b/public/theme/theme4.jpg new file mode 100644 index 000000000..2f54c462d Binary files /dev/null and b/public/theme/theme4.jpg differ diff --git a/public/theme/theme5.jpg b/public/theme/theme5.jpg new file mode 100644 index 000000000..f7737561c Binary files /dev/null and b/public/theme/theme5.jpg differ diff --git a/public/theme_blue/loginbg1.jpg b/public/theme_blue/loginbg1.jpg index 81d6e92eb..c73242f9e 100644 Binary files a/public/theme_blue/loginbg1.jpg and b/public/theme_blue/loginbg1.jpg differ diff --git a/public/tips.png b/public/tips.png new file mode 100644 index 000000000..4a51593fb Binary files /dev/null and b/public/tips.png differ diff --git a/public/tips_addPost1.png b/public/tips_addPost1.png new file mode 100755 index 000000000..1fae308b6 Binary files /dev/null and b/public/tips_addPost1.png differ diff --git a/public/tips_addPost2.png b/public/tips_addPost2.png new file mode 100755 index 000000000..d4571fa2f Binary files /dev/null and b/public/tips_addPost2.png differ diff --git a/public/tips_addPost3.png b/public/tips_addPost3.png new file mode 100755 index 000000000..7dcb972ca Binary files /dev/null and b/public/tips_addPost3.png differ diff --git a/public/user_new.png b/public/user_new.png new file mode 100755 index 000000000..141950424 Binary files /dev/null and b/public/user_new.png differ diff --git a/public/webbg.jpg b/public/webbg.jpg new file mode 100644 index 000000000..da38df53b Binary files /dev/null and b/public/webbg.jpg differ diff --git a/public/wechat.png b/public/wechat.png new file mode 100644 index 000000000..58482a514 Binary files /dev/null and b/public/wechat.png differ diff --git a/public/what_offline.svg b/public/what_offline.svg new file mode 100644 index 000000000..df6752a38 --- /dev/null +++ b/public/what_offline.svg @@ -0,0 +1,17 @@ + + diff --git a/public/workai.png b/public/workai.png new file mode 100644 index 000000000..1af6b004b Binary files /dev/null and b/public/workai.png differ diff --git a/resource/device_icon_192.png b/resource/device_icon_192.png new file mode 100755 index 000000000..f47469786 Binary files /dev/null and b/resource/device_icon_192.png differ diff --git a/resource/icon.png b/resource/icon.png new file mode 100755 index 000000000..0d8a5d510 Binary files /dev/null and b/resource/icon.png differ diff --git a/resource/icon_120.png b/resource/icon_120.png new file mode 100755 index 000000000..7b507f6f4 Binary files /dev/null and b/resource/icon_120.png differ diff --git a/resource/icon_152.png b/resource/icon_152.png new file mode 100755 index 000000000..acd01a35a Binary files /dev/null and b/resource/icon_152.png differ diff --git a/resource/icon_180.png b/resource/icon_180.png new file mode 100755 index 000000000..8a5df2221 Binary files /dev/null and b/resource/icon_180.png differ diff --git a/resource/icon_192.png b/resource/icon_192.png new file mode 100755 index 000000000..f47469786 Binary files /dev/null and b/resource/icon_192.png differ diff --git a/resource/icon_29.png b/resource/icon_29.png new file mode 100755 index 000000000..efb47adba Binary files /dev/null and b/resource/icon_29.png differ diff --git a/resource/icon_36.png b/resource/icon_36.png new file mode 100755 index 000000000..19a4072b4 Binary files /dev/null and b/resource/icon_36.png differ diff --git a/resource/icon_48.png b/resource/icon_48.png new file mode 100755 index 000000000..00d069f89 Binary files /dev/null and b/resource/icon_48.png differ diff --git a/resource/icon_57.png b/resource/icon_57.png new file mode 100755 index 000000000..c143cc074 Binary files /dev/null and b/resource/icon_57.png differ diff --git a/resource/icon_58.png b/resource/icon_58.png new file mode 100755 index 000000000..63aecf0a2 Binary files /dev/null and b/resource/icon_58.png differ diff --git a/resource/icon_72.png b/resource/icon_72.png new file mode 100755 index 000000000..96aa5c606 Binary files /dev/null and b/resource/icon_72.png differ diff --git a/resource/icon_76.png b/resource/icon_76.png new file mode 100755 index 000000000..4d07e73c7 Binary files /dev/null and b/resource/icon_76.png differ diff --git a/resource/icon_87.png b/resource/icon_87.png new file mode 100755 index 000000000..21dfcf473 Binary files /dev/null and b/resource/icon_87.png differ diff --git a/resource/icon_96.png b/resource/icon_96.png new file mode 100755 index 000000000..4b004a117 Binary files /dev/null and b/resource/icon_96.png differ diff --git a/resource/splash_theme_1080_1440.png b/resource/splash_theme_1080_1440.png new file mode 100755 index 000000000..4b06346d9 Binary files /dev/null and b/resource/splash_theme_1080_1440.png differ diff --git a/resource/splash_theme_1080_1920.png b/resource/splash_theme_1080_1920.png new file mode 100755 index 000000000..3e0519580 Binary files /dev/null and b/resource/splash_theme_1080_1920.png differ diff --git a/resource/splash_theme_1242_2208.png b/resource/splash_theme_1242_2208.png new file mode 100755 index 000000000..bbdbe893f Binary files /dev/null and b/resource/splash_theme_1242_2208.png differ diff --git a/resource/splash_theme_1536_2048.png b/resource/splash_theme_1536_2048.png new file mode 100755 index 000000000..6b67929ee Binary files /dev/null and b/resource/splash_theme_1536_2048.png differ diff --git a/resource/splash_theme_320_470.png b/resource/splash_theme_320_470.png new file mode 100755 index 000000000..c73255b9c Binary files /dev/null and b/resource/splash_theme_320_470.png differ diff --git a/resource/splash_theme_480_640.png b/resource/splash_theme_480_640.png new file mode 100755 index 000000000..7d086488a Binary files /dev/null and b/resource/splash_theme_480_640.png differ diff --git a/resource/splash_theme_640_1136.png b/resource/splash_theme_640_1136.png new file mode 100755 index 000000000..1b2e3f91c Binary files /dev/null and b/resource/splash_theme_640_1136.png differ diff --git a/resource/splash_theme_640_960.png b/resource/splash_theme_640_960.png new file mode 100755 index 000000000..f611827f1 Binary files /dev/null and b/resource/splash_theme_640_960.png differ diff --git a/resource/splash_theme_720_960.png b/resource/splash_theme_720_960.png new file mode 100755 index 000000000..b16adeeeb Binary files /dev/null and b/resource/splash_theme_720_960.png differ diff --git a/resource/splash_theme_750_1334.png b/resource/splash_theme_750_1334.png new file mode 100755 index 000000000..53035a5e7 Binary files /dev/null and b/resource/splash_theme_750_1334.png differ diff --git a/resource/splash_theme_768_1024.png b/resource/splash_theme_768_1024.png new file mode 100755 index 000000000..5019daa78 Binary files /dev/null and b/resource/splash_theme_768_1024.png differ diff --git a/server b/server new file mode 120000 index 000000000..98da7718c --- /dev/null +++ b/server @@ -0,0 +1 @@ +../SharpAIMobileApp/server \ No newline at end of file diff --git a/server/07_get_profile_data.js b/server/07_get_profile_data.js deleted file mode 100644 index 271f8f7c4..000000000 --- a/server/07_get_profile_data.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Created by simba on 11/10/16. - */ - - -if(Meteor.isServer){ - Meteor.startup(function() { - Meteor.methods({ - "getMyProfileData": function () { - if (this.userId === null) { - return false; - } - this.unblock(); - - var myProfileData = {}; - - myProfileData['myPostsCount'] = Posts.find({owner: this.userId,publish: {$ne: false}}).count(); - myProfileData['mySavedDraftsCount'] = SavedDrafts.find({owner: this.userId}).count(); - myProfileData['myFollowedByCount'] = Follower.find({followerId:this.userId}).count(); - myProfileData['myFollowedByCount-'+this.userId] = Follower.find({followerId:this.userId, userEmail: {$exists: false}}).count(); - myProfileData['myFollowToCount'] = Follower.find({userId:this.userId}).count(); - myProfileData['myEmailFollowerCount'] = Follower.find({followerId:this.userId, userEmail: {$exists: true}}).count(); - myProfileData['myEmailFollowerCount-'+this.userId] = Follower.find({followerId:this.userId, userEmail: {$exists: true}}).count(); - return myProfileData; - } - }) - }) -} \ No newline at end of file diff --git a/server/4_router_server.coffee b/server/4_router_server.coffee deleted file mode 100644 index a83da632e..000000000 --- a/server/4_router_server.coffee +++ /dev/null @@ -1,2852 +0,0 @@ -subs = new SubsManager({ - #maximum number of cache subscriptions - cacheLimit: 999, - # any subscription will be expire after 30 days, if it's not subscribed again - expireIn: 60*24*30 -}); - -if Meteor.isServer - request = Meteor.npmRequire('request') - Fiber = Meteor.npmRequire('fibers') - QRImage = Meteor.npmRequire('qr-image') - - ### - Router.route '/posts/:_id', { - waitOn: -> - [subs.subscribe("publicPosts",this.params._id), - subs.subscribe "pcomments"] - fastRender: true - } - ### - injectSignData = (req,res)-> - try - console.log(req.url) - if req.url - signature=generateSignature('http://'+server_domain_name+req.url) - if signature - console.log(signature) - InjectData.pushData(res, "wechatsign", signature); - catch error - return null - # 获取对应时区的时间 - getLocalTimeByOffset = (i)-> - if typeof i isnt 'number' - return - d = new Date() - len = d.getTime() - offset = d.getTimezoneOffset() * 60000 - utcTime = len + offset - return new Date(utcTime + 3600000 * i) - - Router.configure { - waitOn: ()-> - if this and this.path - path=this.path - if path.indexOf('/posts/') is 0 - if path.indexOf('?') > 0 - path = path.split('?')[0] - params=path.replace('/posts/','') - params=params.split('/') - if params.length > 0 - return [subs.subscribe("publicPosts",params[0]), - subs.subscribe("postsAuthor",params[0]), - subs.subscribe "pcomments"] - fastRender: true - } - - Router.route('/download-reporter-logs', (req, res, next)-> - data = reporterLogs.find({},{sort:{createdAt:-1}}).fetch() - fields = [ - { - key:'postId', - title:'帖子Id', - }, - { - key:'postTitle', - title:'帖子标题', - }, - { - key:'postCreatedAt', - title:'帖子创建时间', - transform: (val, doc)-> - d = new Date(val) - return d.toLocaleString() - }, - { - key: 'userId', - title: '用户Id(涉及帖子操作时,为帖子Owner)' - }, - { - key:'userName', - title:'用户昵称' - }, - { - key:'userEmails', - title:'用户Email', - transform: (val, doc)-> - emails = '' - if val and val isnt null - val.forEach (item)-> - emails += item.address + '\r\n' - return emails; - }, - { - key:'eventType', - title: '操作类型' - }, - { - key:'loginUser', - title: '操作人员', - transform: (val, doc)-> - user = Meteor.users.findOne({_id: val}) - userInfo = '_id: '+val+'\r\n username: '+user.username - return userInfo - }, - { - key: 'createdAt', - title: '操作时间', - transform: (val, doc)-> - d = new Date(val) - return d.toLocaleString() - }, - - ] - - title = 'hotShareReporterLogs-'+ (new Date()).toLocaleDateString() - file = Excel.export(title, fields, data) - headers = { - 'Content-type': 'application/vnd.openxmlformats', - 'Content-Disposition': 'attachment; filename=' + title + '.xlsx' - } - - this.response.writeHead(200, headers) - this.response.end(file, 'binary') - , { where: 'server' }) - - # Router.route('/apple-app-site-association', (req, res, next)-> - # #name = 'apple-app-site-association' - # #name = 'import-server' - # fs = Npm.require("fs") - # path = Npm.require('path') - # base = path.resolve('.'); - # filepath = path.resolve('.') + '/app/lib/apple-app-site-association'; - # #filepath = path.join(__dirname,'../server/import-server.js') - # file = fs.readFileSync(filepath, 'binary'); - # headers = { - # 'Content-type': 'application/vnd.openxmlformats', - # 'Content-Disposition': 'attachment; apple-app-site-association' - # } - - # this.response.writeHead(200, headers) - # this.response.end(file, 'binary') - # , { where: 'server' }) - -if Meteor.isServer - workaiId = 'Lh4JcxG7CnmgR3YXe' - workaiName = 'Actiontec' - fomat_greeting_text = (time,time_offset)-> - DateTimezone = (d, time_offset)-> - if (time_offset == undefined) - if (d.getTimezoneOffset() == 420) - time_offset = -7 - else - time_offset = 8 - # 取得 UTC time - utc = d.getTime() + (d.getTimezoneOffset() * 60000); - local_now = new Date(utc + (3600000*time_offset)) - today_now = new Date(local_now.getFullYear(), local_now.getMonth(), local_now.getDate(), - local_now.getHours(), local_now.getMinutes()); - - return today_now; - - self = time; - now = new Date(); - result = ''; - self = DateTimezone(time, time_offset); - - # DayDiff = now.getDate() - self.getDate(); - Minutes = self.getHours() * 60 + self.getMinutes(); - # if (DayDiff === 0) { - # result += '今天 ' - # } else if (DayDiff === 1) { - # result += '昨天 ' - # } else { - # result += self.parseDate('YYYY-MM-DD') + ' '; - # } - if (Minutes >= 0 && Minutes < 360) - result += '凌晨 '; - if (Minutes >= 360 && Minutes < 660) - result += '上午 '; - if (Minutes >= 660 && Minutes < 780) - result += '中午 '; - if (Minutes >= 780 && Minutes < 960) - result += '下午 '; - if (Minutes >= 960 && Minutes < 1080) - result += '傍晚 '; - if (Minutes >= 1080 && Minutes < 1440) - result += '晚上 '; - result += self.parseDate('h:mm'); - result = '您的上班时间是 ' + result; - return result; - - @send_greeting_msg = (data)-> - console.log 'try send_greeting_msg~ with data:'+JSON.stringify(data) - if !data || !data.images ||data.images.img_type isnt 'face' - #console.log 'invalid params' - return - if !data.in_out - device = Devices.findOne({uuid:data.people_uuid}) - if !device || !device.in_out - #console.log ' not found device or in_device' - return - data.in_out = device.in_out - person = Person.findOne({group_id:data.group_id,'faces.id':data.images.id},{sort: {createAt: 1}}) - if !person - console.log 'not find person with faceid is:'+data.images.id - return - relation = WorkAIUserRelations.findOne({'ai_persons.id':person._id}) - create_time = new Date(data.create_time); - if !relation - #console.log 'not find workai user relations' - WorkAIUserRelations.insert({ - group_id:data.group_id, - person_name:person.name, - in_uuid:data.people_uuid, - ai_in_time:create_time.getTime(), - ai_lastest_in_time:create_time.getTime(), - ai_in_image:data.images.url, - ai_lastest_in_image:data.images.url, - ai_persons: [ - { - id : person._id - } - ] - }); - return - if data.in_out is 'out' - WorkAIUserRelations.update({_id:relation._id},{$set:{ai_out_time:create_time.getTime(), ai_out_image: data.images.url}}); - return - WorkAIUserRelations.update({_id:relation._id},{$set:{ai_lastest_in_time:create_time.getTime(),ai_lastest_in_image:data.images.url}});#平板最新拍到的时间 - #再次拍到进门需要把下班的提示移除 - if relation.app_user_id - endlog = UserCheckoutEndLog.findOne({userId:relation.app_user_id}); - if endlog - outtime = endlog.params.person_info.ts; - if outtime and PERSON.checkIsToday(outtime,data.group_id) and outtime < create_time.getTime() - UserCheckoutEndLog.remove({_id:endlog._id}); - - if relation.ai_in_time and PERSON.checkIsToday(relation.ai_in_time,data.group_id) - # ai_in_time = new Date(relation.ai_in_time); - # today = new Date(create_time.getFullYear(), create_time.getMonth(), create_time.getDate()).getTime(); #凌晨 - # if ai_in_time.getTime() > today - console.log 'today greeting_msg had send' - #WorkAIUserRelations.update({_id:relation._id},{$set:{ai_in_time:create_time.getTime()}}); - return - WorkAIUserRelations.update({_id:relation._id},{$set:{ai_in_time:create_time.getTime(), ai_in_image: data.images.url}}); - if !relation.app_user_id - return - deviceUser = Meteor.users.findOne({username: data.people_uuid}); - time_offset = 8; - group = SimpleChat.Groups.findOne({_id: data.group_id}); - if (group && group.offsetTimeZone) - time_offset = group.offsetTimeZone; - - sendMqttMessage('/msg/u/'+ relation.app_user_id, { - _id: new Mongo.ObjectID()._str - # form: { - # id: "fTnmgpdDN4hF9re8F", - # name: "workAI", - # icon: "http://data.tiegushi.com/fTnmgpdDN4hF9re8F_1493176458747.jpg" - # } - form:{ - id: deviceUser._id, - name: deviceUser.profile.fullname, - icon: deviceUser.profile.icon - } - to: { - id: relation.app_user_id - name: relation.app_user_name - icon: '' - } - images: [data.images] - to_type: "user" - type: "text" - text: fomat_greeting_text(create_time,time_offset) - create_time: new Date() - checkin_time:create_time - is_agent_check:true - offsetTimeZone:time_offset - group_id:data.group_id - people_uuid: data.people_uuid - is_read: false - checkin_out:'in' - }) - - - # 对data 进行处理, data 必须是数组 - insertFaces = (face)-> - device = Devices.findOne({uuid: face.uuid}) - if device and device.name - face.device_name = device.name - face.createdAt = new Date() - - Faces.insert(face) - - insert_msg2 = (id, url, uuid, img_type, accuracy, fuzziness, sqlid, style,img_ts,current_ts,tracker_id,p_ids)-> - #people = People.findOne({id: id, uuid: uuid}) - name = null - #device = PERSON.upsetDevice(uuid, null) - create_time = new Date() - console.log("insert_msg2: img_ts="+img_ts+", current_ts="+current_ts+", create_time="+create_time) - ### - if img_ts and current_ts - img_ts = Number(img_ts) - current_ts = Number(current_ts) - time_diff = img_ts + (create_time.getTime() - current_ts) - create_time = new Date(time_diff) - ### - create_time = new Date() - if img_ts and current_ts - img_ts = Number(img_ts) - current_ts = Number(current_ts) - time_diff = img_ts + getTimeZoneDiffByMs(create_time.getTime(), current_ts) - create_time = new Date(time_diff) - - #if !people - # people = {_id: new Mongo.ObjectID()._str, id: id, uuid: uuid,name: name,embed: null,local_url: null,aliyun_url: url} - # People.insert(people) - #else - # People.update({_id: people._id}, {$set: {aliyun_url: url}}) - - device = Devices.findOne({uuid: uuid}) - PeopleHis.insert {id: id,uuid: uuid,name: name, people_id: id, embed: null,local_url: null,aliyun_url: url}, (err, _id)-> - if err or !_id - return - - user = Meteor.users.findOne({username: uuid}) - unless user - return - userGroups = SimpleChat.GroupUsers.find({user_id: user._id}) - unless userGroups - return - userGroups.forEach((userGroup)-> - group = SimpleChat.Groups.findOne({_id:userGroup.group_id}); - if group.template and group.template._id - if group.template.img_type != img_type - return - name = null - name = PERSON.getName(null, userGroup.group_id,id) - p_ids_name = []; - #存在可能性最大的三个人的id - # if p_ids and p_ids.length > 0 - # for pid in p_ids - # person = Person.findOne({group_id:userGroup.group_id,'faces.id':pid},{sort:{createAt:1}}); - # if person - # p_person = { - # name:person.name, - # id:pid, - # url:person.url, - # p_id:person._id - # } - # if(_.pluck(p_ids_name, 'p_id').indexOf(person._id) is -1) - # p_ids_name.push(p_person) - - #没有准确度的人一定是没有识别出来的 - name = if accuracy then name else null - #没有识别的人的准确度清0 - Accuracy = if name then accuracy else false - Fuzziness = fuzziness - if true #name and checkIfSendRecoMsg(userGroup.group_id, uuid, id) - console.log('--------send reco msg to --------', uuid, '----', id, '----', name) - sendMqttMessage('/msg/g/'+ userGroup.group_id, { - _id: new Mongo.ObjectID()._str - form: { - id: user._id - name: if user.profile and user.profile.fullname then user.profile.fullname else user.username - icon: user.profile.icon - } - to: { - id: userGroup.group_id - name: userGroup.group_name - icon: userGroup.group_icon - } - images: [ - {_id: new Mongo.ObjectID()._str, id: id, people_his_id: _id, url: url, label: name, img_type: img_type, accuracy: Accuracy, fuzziness: Fuzziness, sqlid: sqlid, style: style,p_ids:p_ids_name} # 暂一次只能发一张图 - ] - to_type: "group" - type: "text" - text: if !name then 'Work AI发现有人在活动' else 'AI观察到 ' + name + ':' - create_time: create_time - people_id: id - people_uuid: uuid - people_his_id: _id - wait_lable: !name - is_people: true - is_read: false - tid:tracker_id - }) - - # update to DeviceTimeLine - timeObj = { - person_id: id, - person_name: name, - img_url: url, - sqlid: sqlid, - style: style, - accuracy: Accuracy, # 准确度(分数) - fuzziness: Fuzziness, # 模糊度 - ts:create_time.getTime(), - tid: tracker_id - } - PERSON.updateToDeviceTimeline(uuid,userGroup.group_id,timeObj) - #识别准确度在0.85以上才自动打卡 - #if name and accuracy >= withDefaultAccuracy - #if tablet recognizes this person, update it to mainpage. - if name - msg_data = { - group_id:userGroup.group_id, - create_time:create_time, - people_uuid:uuid, - in_out:userGroup.in_out, - images:{ - id: id, - people_his_id: _id, - url: url, - label: name, - img_type: img_type, - accuracy: Accuracy, - fuzziness: Fuzziness, - sqlid: sqlid, - style: style - } - } - person = Person.findOne({group_id: userGroup.group_id, name: name}, {sort: {createAt: 1}}); - person_info = null - if img_type == 'face' && person && person.faceId - #console.log('post person info to aixd.raidcdn') - person_info = { - 'id': person._id, - 'uuid': uuid, - 'name': name, - 'group_id': userGroup.group_id, - 'img_url': url, - 'type': img_type, - 'ts': create_time.getTime(), - 'accuracy': accuracy, - 'fuzziness': fuzziness - } - - relation = WorkAIUserRelations.findOne({'ai_persons.id': person._id}) - if (device.in_out is 'in' and relation and relation.app_user_id) - wsts = WorkStatus.findOne({group_id: userGroup.group_id, app_user_id: relation.app_user_id}, {sort: {date: -1}}) - if (wsts and !wsts.whats_up) - CreateSatsUpTipTask(relation.app_user_id, userGroup.group_id, device.uuid) - - if (device.in_out is 'out' and relation and relation.app_user_id) - checkout_msg = { - userId: relation.app_user_id, - userName: relation.app_user_name, - createAt: new Date(), - params: { - msg_data: msg_data, - person: person, - person_info: person_info - } - } - # UserCheckoutEndLog.remove({userId: relation.app_user_id}) - # UserCheckoutEndLog.insert(checkout_msg) - # sendUserCheckoutEvent(uuid, relation.app_user_id) - # return - # 到下班时间后,不终止后续处理 - group_outtime = '18:00' - time_offset = 8 - if (group and group.group_outtime) - group_outtime = group.group_outtime - - if group and group.offsetTimeZone - time_offset = group.offsetTimeZone - - group_outtime_H = parseInt(group_outtime.split(":")[0]) - group_outtime_M = parseInt(group_outtime.split(":")[1]) - group_out_minutes = group_outtime_H * 60 + group_outtime_M - - out_time_H = parseInt(create_time.getUTCHours() + time_offset) - out_time_M = create_time.getMinutes() - out_time_minutes = out_time_H * 60 + out_time_M - - # 到下班时间后,不自动 checkout(这里先取消, 目前:统一采用自动checkout的方式,但保留手动checkout) - # if (out_time_minutes < group_out_minutes) - # return - - send_greeting_msg(msg_data); - PERSON.updateWorkStatus(person._id) - if person_info - PERSON.sendPersonInfoToWeb(person_info) - console.log("device.in_out", device, relation) - if ((device.name.includes('inout') or device.in_out is 'inout' or device.uuid is '28D6R17505001070') and relation) - Devices.update({_id: device._id}, {$set: {in_out:'inout'}}); - console.log("activity_update_time") - activity_update_time(person._id, create_time, url) - ) - - @insert_msg2forTest = (id, url, uuid, accuracy, fuzziness)-> - insert_msg2(id, url, uuid, 'face', accuracy, fuzziness, 0, 0) - - # 平板 发现 某张图片为 错误识别(不涉及标记), 需要移除 或 修正 相应数据 - padCallRemove = (id, url, uuid, img_type, accuracy, fuzziness, sqlid, style,img_ts,current_ts,tracker_id,p_ids)-> - console.log("padCallRemove: id="+id+", url="+url+", uuid="+uuid+", img_type="+img_type+", accuracy="+accuracy+", fuzziness="+fuzziness+", sqlid="+sqlid+", style="+style+", img_ts="+img_ts+", current_ts="+current_ts+", tracker_id="+tracker_id+", p_ids="+p_ids) - - create_time = new Date() - if img_ts and current_ts - img_ts = Number(img_ts) - current_ts = Number(current_ts) - time_diff = img_ts + getTimeZoneDiffByMs(create_time.getTime(), current_ts) - create_time = new Date(time_diff) - - hour = new Date(create_time.getTime()) - hour.setMinutes(0) - hour.setSeconds(0) - hour.setMilliseconds(0) - console.log("hour="+hour) - - minutes = new Date(create_time.getTime()) - minutes = minutes.getMinutes() - console.log("minutes="+minutes) - - # Step 1. 修正出现记录, WorkAIUserRelations或workStatus - fixWorkStatus = (work_status,in_out)-> - today = new Date(create_time.getTime()) - today.setHours(0,0,0,0) - - console.log('hour='+hour+', today='+today+', uuid='+uuid) - timeline = DeviceTimeLine.findOne({hour:{$lte: hour, $gt: today},uuid: uuid},{sort: {hour: -1}}); - if timeline and timeline.perMin # 通过历史记录中的 数据 fix WorkStatus - time = null - imgUrl = null - - timelineArray = for mins of timeline.perMin - timeline.perMin[mins] - - for obj in timelineArray - if obj.img_url is url - time = Number(obj.ts) - imgUrl = obj.img_url - break - if in_out is 'in' - setObj = { - in_time: time, - in_image: imgUrl, - in_status: 'normal' - } - if !work_status.out_time - setObj.status = 'in' - else if time < work_status.out_time - setObj.status = 'out' - else if time >= work_status.out_time - setObj.status = 'in' - - if in_out is 'out' - setObj = { - out_time: time, - out_image: imgUrl, - out_status: 'normal' - } - if !work_status.in_time - setObj.status = 'out' - setObj.out_status = 'warning' - else if time <= work_status.in_time - setObj.status = 'in' - else if time > work_status.in_time - setObj.status = 'out' - - else - if in_out is 'in' - setObj = { - status: 'out', - in_uuid: null, - in_time: null, - in_image: null, - in_status: 'unknown' - } - if in_out is 'out' - setObj = { - status: 'in', - out_uuid: null, - out_time: null, - out_image: null, - out_status: 'unknown' - } - if !work_status.in_time - setObj.status = 'out' - - WorkStatus.update({_id: work_status._id},$set: setObj) - - work_status_in = WorkStatus.findOne({in_image: url}) - # 匹配到进的出现 - if work_status_in - console.log('padCallRemove Fix WorkStatus, 需要修正进的出现') - #fixWorkStatus(work_status_in,'in') - work_status_out = WorkStatus.findOne({out_image: url}) - # 匹配到出的出现 - if work_status_out - console.log('padCallRemove Fix WorkStatus, 需要修正出的出现') - #fixWorkStatus(work_status_out,'out') - # 删除出现 - WorkStatus.remove({$or:[{in_image: url},{out_image: url}]}); - # Step 2. 从设备时间轴中移除 - selector = { - hour: hour, - uuid: uuid - } - selector["perMin."+minutes+".img_url"] = url; - - ### - console.log('selector='+JSON.stringify(selector)) - timeline = DeviceTimeLine.findOne(selector) - console.log("timeline._id="+JSON.stringify(timeline._id)) - if timeline - minuteArray = timeline.perMin[""+minutes] - console.log("minuteArray="+JSON.stringify(minuteArray)) - minuteArray.splice(_.pluck(minuteArray, 'img_url').indexOf(url), 1) - console.log("2, minuteArray="+JSON.stringify(minuteArray)) - - modifier = { - $set:{} - } - - modifier.$set["perMin."+minutes] = minuteArray - - DeviceTimeLine.update({_id: timeline._id}, modifier, (err,res)-> - if err - console.log('padCallRemove DeviceTimeLine, update Err:'+err) - else - console.log('padCallRemove DeviceTimeLine, update Success') - ) - ### - console.log('selector='+JSON.stringify(selector)) - modifier = {$set:{}} - modifier.$set["perMin."+minutes+".$.person_name"] = null - modifier.$set["perMin."+minutes+".$.accuracy"] = false - DeviceTimeLine.update(selector, modifier, (err,res)-> - if err - console.log('padCallRemove DeviceTimeLine, update Err:'+err) - else - console.log('padCallRemove DeviceTimeLine, update Success') - ) - - # Step 3. 如果 person 表中 有此图片记录, 需要移除 - person = Person.findOne({'faces.id': id}) - if person - faces = person.faces - faces.splice(_.pluck(faces, 'id').indexOf(obj.face_id), 1) - Person.update({_id: person._id},{$set: {faces: faces}}) - - # Step 4. 向Group 发送一条 mqtt 消息, 告知需要移除 错误识别 的照片 - device = Devices.findOne({uuid: uuid}); - if device and device.groupId - group_id = device.groupId - group = SimpleChat.Groups.findOne({_id: group_id}) - - to = { - id: group._id, - name: group.name, - icon: group.icon - } - - device_user = Meteor.users.findOne({username: uuid}) - form = {} - if device_user - form = { - id: device_user._id, - name: if (device_user.profile and device_user.profile.fullname) then device_user.profile.fullname else device_user.username, - icon: device_user.profile.icon - } - - msg = { - _id: new Mongo.ObjectID()._str, - form: form, - to: to, - to_type: 'group', - type: 'remove_error_img', - id: id, - url: url, - uuid: uuid, - img_type: img_type, - img_ts: img_ts, - current_ts: current_ts, - tid: tracker_id, - pids: p_ids - } - - try - sendMqttGroupMessage(group_id,msg) - catch error - console.log('try sendMqttGroupMessage Err:',error) - - update_group_dataset = (group_id,dataset_url,uuid)-> - unless group_id and dataset_url and uuid - return - group = SimpleChat.Groups.findOne({_id:group_id}) - user = Meteor.users.findOne({username: uuid}) - if group and user - announcement = group.announcement; - unless announcement - announcement = [] - i = 0 - isExit = false - while i < announcement.length - if announcement[i].uuid is uuid - announcement[i].dataset_url = dataset_url - isExit = true - break; - i++ - unless isExit - announcementObj = { - uuid:uuid, - device_name:user.profile.fullname, - dataset_url:dataset_url - }; - announcement.push(announcementObj); - SimpleChat.Groups.update({_id:group_id},{$set:{announcement:announcement}}) - - - # test - # Meteor.startup ()-> - # insert_msg2( - # '7YRBBDB72200271715027668215821893', - # 'http://workaiossqn.tiegushi.com/8acb7f90-92e1-11e7-8070-d065caa7da61', - # '28DDU17602003551', - # 'face', - # '0.9', - # '100', - # '0', - # 'front', - # 1506588441021, - # 1506588441021, - # '' - # ) - - - # Faces API - # [{ - # 'id': id, - # 'uuid': uuid, - # 'group_id': current_groupid, - # 'img_url': url, - # 'position': position, - # 'type': img_type, - # 'current_ts': int(time.time()*1000), - # 'accuracy': accuracy, - # 'fuzziness': fuzziness, - # 'sqlid': sqlId, - # 'style': style, - # 'tid': tid, - # 'img_ts': img_ts, - # 'p_ids': p_ids, - # }] - Router.route('/restapi/workai/faces', {where: 'server'}).post(()-> - data = this.request.body - if (typeof data is 'object' and data.constructor is Array) - data.forEach((face)-> - insertFaces(face) - ) - this.response.end('{"result": "ok"}\n') - else - this.response.end('{"result": "ok", "reason": "params must be an Array"}\n') - ) - Router.route('/restapi/workai/faces_json', {where: 'server'}).post(()-> - json_data = this.request.body - data = [] - data.push(json_data) - if (typeof data is 'object') - data.forEach((face)-> - insertFaces(face) - ) - this.response.end('{"result": "ok"}\n') - else - this.response.end('{"result": "ok", "reason": "params must be an Array"}\n') - ) - Router.route('/restapi/list_device', {where: 'server'}).get(()-> - uuid = this.params.query.uuid - console.log(groups) - device = Devices.findOne({"uuid" : uuid}) - ret_str = '' - if device - user = {'_id': device._id, 'username': device.name, 'profile': {'icon': '/device_icon_192.png'}} - ret_str += 'Device['+uuid+'] in Devices deleted \n' - - if device.groupId - group_id = device.groupId - #SimpleChat.GroupUsers.remove({group_id:group_id,user_id:user._id}); - groups = SimpleChat.GroupUsers.find({group_id:group_id,user_name:uuid}).fetch(); - console.log(groups) - ret_str += groups.toString() - user = Meteor.users.findOne({username: uuid}) - if user - users = Meteor.users.find({username: uuid}).fetch() - console.log(users) - ret_str += users.toString() - if ret_str is '' - return this.response.end('nothing in db') - this.response.end(ret_str) - #userGroup = SimpleChat.GroupUsers.findOne({user_id: user._id, group_id: group_id}) - #unless userGroup or userGroup.group_id - # return this.response.end('{"result": "failed", "cause": "group not found"}\n') - ) - Router.route('/restapi/get_name_by_faceid', {where: 'server'}).get(()-> - face_id = this.params.query.face_id - group_id = this.params.query.group_id - if not face_id or not group_id - return this.response.end(JSON.stringify({result: 'invalid parameters'})) - person = Person.findOne({group_id: group_id, faceId: face_id}) - if not person - return this.response.end(JSON.stringify({result: 'no such item'})) - return this.response.end(JSON.stringify({result: 'success', name: person.name})) - ) - Router.route('/restapi/clean_device', {where: 'server'}).get(()-> - uuid = this.params.query.uuid - device = Devices.findOne({"uuid" : uuid}) - ret_str = '' - if device - Devices.remove({uuid : uuid}) - Devices.remove({_id:device._id}); - ret_str += 'Device['+uuid+'] in Devices deleted \n' - if device.groupId - group_id = device.groupId - #SimpleChat.GroupUsers.remove({group_id:group_id,user_id:user._id}); - SimpleChat.GroupUsers.remove({group_id:group_id,user_name:uuid}); - ret_str += 'Device in userGroup in SimpleChat.GroupUsers deleted \n' - user = Meteor.users.findOne({username: uuid}) - if user - Meteor.users.remove({username: uuid}) - ret_str += 'Device['+uuid+'] in Meteor.users deleted \n' - if ret_str is '' - return this.response.end('nothing to delete') - this.response.end(ret_str) - #userGroup = SimpleChat.GroupUsers.findOne({user_id: user._id, group_id: group_id}) - #unless userGroup or userGroup.group_id - # return this.response.end('{"result": "failed", "cause": "group not found"}\n') - ) - ### - payload = {'groupid': groupid, 'uuid': uuid,'fuzziness_from':fuzziness_from, 'fuzziness_to':fuzziness_to, - 'blury_threshold': blury_threshold, 'score_threshold_main': score_threshold_main, 'score_threshold_2nd': score_threshold_2nd} - ### - Router.route('/restapi/workai/model_param_update', {where: 'server'}).post(()-> - group_id = this.request.body.groupid - uuid = this.request.body.uuid - if group_id and uuid - ModelParam.upsert({groupid: group_id, uuid: uuid}, {$set: this.request.body}) - this.response.end('{"result": "ok"}\n') - else - this.response.end('{"result": "ok", "reason": "invalid params"}\n') - ) - - Router.route('/restapi/workai/group/:_id/last_time', {where: 'server'}).get(()-> - result = SimpleChat.Groups.findOne({_id: this.params._id}, {fields:{'last_time':1}}) - if (result and result != {}) - #console.log("Get last_time in group suc: "+this.params._id + ", last_time="+result.last_time) - last_time = new Date(result.last_time).getTime(); - return this.response.end(JSON.stringify({result: 'ok', last_time: last_time})) - else - console.log("Update last_time in group failed: "+this.params._id) - return this.response.end(JSON.stringify({result: 'failed'})) - ) - - Router.route('/restapi/workai/model_param', {where: 'server'}).post(()-> - group_id = this.request.body.groupid - uuid = this.request.body.uuid - if group_id and uuid - params = ModelParam.findOne({groupid: group_id, uuid: uuid}) - this.response.end(JSON.stringify(params) + '\n') - else - this.response.end('{"result": "ok", "reason": "invalid params"}\n') - ) - - Router.route('/restapi/workai', {where: 'server'}).get(()-> - id = this.params.query.id - img_url = this.params.query.img_url - uuid = this.params.query.uuid - img_type = this.params.query.type - tracker_id = this.params.query.tid - console.log '/restapi/workai get request, id:' + id + ', img_url:' + img_url + ',uuid:' + uuid - unless id or img_url or uuid - return this.response.end('{"result": "failed", "cause": "invalid params,check id,uuid,img_url"}\n') - accuracy = this.params.query.accuracy - sqlid = this.params.query.sqlid - style = this.params.query.style - fuzziness = this.params.query.fuzziness - img_ts = this.params.query.img_ts - current_ts = this.params.query.current_ts - if this.params.query.opt and this.params.query.opt is 'remove' - padCallRemove(id, img_url, uuid, img_type, accuracy, fuzziness, sqlid, style, img_ts, current_ts, tracker_id) - else - insert_msg2(id, img_url, uuid, img_type, accuracy, fuzziness, sqlid, style,img_ts,current_ts,tracker_id) - - this.response.end('{"result": "ok"}\n') - ).post(()-> - if this.request.body.hasOwnProperty('id') - id = this.request.body.id - if this.request.body.hasOwnProperty('img_url') - img_url = this.request.body.img_url - if this.request.body.hasOwnProperty('uuid') - uuid = this.request.body.uuid - if this.request.body.hasOwnProperty('type') - img_type = this.request.body.type - if this.request.body.hasOwnProperty('sqlid') - sqlid = this.request.body.sqlid - else - sqlid = 0 - - if this.request.body.hasOwnProperty('style') - style = this.request.body.style - else - style = 0 - if this.request.body.hasOwnProperty('img_ts') - img_ts = this.request.body.img_ts - if this.request.body.hasOwnProperty('current_ts') - current_ts = this.request.body.current_ts - - if this.request.body.hasOwnProperty('tid') - tracker_id = this.request.body.tid - - if this.request.body.hasOwnProperty('p_ids') #可能性最大的三个人的id - p_ids = this.request.body.p_ids - - console.log '/restapi/workai post request, id:' + id + ', img_url:' + img_url + ',uuid:' + uuid + ' img_type=' + img_type + ' sqlid=' + sqlid + ' style=' + style + 'img_ts=' + img_ts - unless id or img_url or uuid - return this.response.end('{"result": "failed", "cause": "invalid params,check id,uuid,img_url"}\n') - accuracy = this.params.query.accuracy - fuzziness = this.params.query.fuzziness - if this.params.query.opt and this.params.query.opt is 'remove' - padCallRemove(id, img_url, uuid, img_type, accuracy, fuzziness, sqlid, style, img_ts, current_ts, tracker_id) - else - insert_msg2(id, img_url, uuid, img_type, accuracy, fuzziness, sqlid, style,img_ts,current_ts, tracker_id,p_ids) - - this.response.end('{"result": "ok"}\n') - ) - - Router.route('restapi/workai_autolabel', {where: 'server'}).get(()-> - - ).post(()-> - person_id = '' - persons = [] - person_name = null - active_time = null - faceId = null - random_id = null - if this.request.body.hasOwnProperty('person_id') - person_id = this.request.body.person_id - else - console.log('restapi/workai_autolabel: no person_id, return.') - return - if this.request.body.hasOwnProperty('persons') - persons = this.request.body.persons - console.log("restapi/workai_autolabel post: person_id="+person_id+", persons="+JSON.stringify(persons)) - if (!(persons instanceof Array) or persons.length < 1) - console.log("restapi/workai_autolabel: this.request.body is not array.") - return this.response.end('{"result": "failed!", "cause": "this.request.body is not array."}\n') - - user = Meteor.users.findOne({username: persons[0].uuid}) - unless user - console.log("restapi/workai_autolabel: user is null") - return this.response.end('{"result": "failed!", "cause": "user is null."}\n') - userGroups = SimpleChat.GroupUsers.find({user_id: user._id}) - unless userGroups - console.log("restapi/workai_autolabel: userGroups is null") - return this.response.end('{"result": "failed!", "cause":"userGroups is null."}\n') - - faceId = if person_id != '' then person_id else new Mongo.ObjectID()._str - - #random_id = if person_id != '' then new Mongo.ObjectID()._str+"-autolabel" else faceId - #console.log("restapi/workai_autolabel: uuid = "+persons[0].uuid+", faceId="+faceId+", random_id="+random_id) - userGroups.forEach((userGroup)-> - console.log("restapi/workai_autolabel: userGroup.group_id="+userGroup.group_id) - person_name = if person_id != '' then PERSON.getName(null, userGroup.group_id, person_id) else null - if person_name == null - random_id = faceId - else - random_id = new Mongo.ObjectID()._str - person_name = if person_name != null then person_name else 'Guest_'+new Mongo.ObjectID()._str - console.log("restapi/workai_autolabel: person_name="+person_name) - for person in persons - console.log("person="+JSON.stringify(person)) - PERSON.setName( - userGroup.group_id, - person.uuid, - random_id, - person.img_url, - person_name - ) - LABLE_DADASET_Handle.insert({ - group_id:userGroup.group_id, - uuid:person.uuid, - id:random_id, - url:person.img_url, - name:person_name, - sqlid:person.sqlid, - style:person.style, - action:'AutoLabel', - faceId: faceId, - }) - - insert_msg2(random_id, person.img_url, person.uuid, person.type, person.accuracy, person.fuzziness, person.sqlid, person.style, person.img_ts, person.current_ts, person.tid) - ### - trainsetObj = { - group_id: userGroup.group_id, - type: 'trainset', - url: person.img_url, - #person_id: person_id, - device_id: person.uuid, - face_id: faceId, - drop: false, - img_type: 'face', - style:person.style, - sqlid:person.sqlid - }; - console.log("restapi/workai_autolabel: " + JSON.stringify(trainsetObj)); - sendMqttMessage('/device/'+userGroup.group_id, trainsetObj); - ### - ) - this.response.end('{"result": "ok"}\n') - ) - - Router.route('restapi/workai_autolabel/batch', {where: 'server'}).get(()-> - - ).post(()-> - person_id = '' - persons = [] - person_name = null - active_time = null - random_id = null - # if this.request.body.hasOwnProperty('person_id') - # person_id = this.request.body.person_id - # else - # console.log('restapi/workai_autolabel: no person_id, return.') - # return - if this.request.body.hasOwnProperty('persons') - persons = this.request.body.persons - console.log("restapi/workai_autolabel/batch post: person_id="+person_id+", persons="+JSON.stringify(persons)) - if (!(persons instanceof Array) or persons.length < 1) - console.log("restapi/workai_autolabel/batch: this.request.body is not array.") - return this.response.end('{"result": "failed!", "cause": "this.request.body is not array."}\n') - - user = Meteor.users.findOne({username: persons[0].uuid}) - unless user - console.log("restapi/workai_autolabel/batch: user is null") - return this.response.end('{"result": "failed!", "cause": "user is null."}\n') - userGroups = SimpleChat.GroupUsers.find({user_id: user._id}) - unless userGroups - console.log("restapi/workai_autolabel/batch: userGroups is null") - return this.response.end('{"result": "failed!", "cause":"userGroups is null."}\n') - - userGroups.forEach((userGroup)-> - console.log("restapi/workai_autolabel/batch: userGroup.group_id="+userGroup.group_id) - - for person in persons - console.log("person="+JSON.stringify(person)) - - random_id = new Mongo.ObjectID()._str - person_name = 'Guest_'+new Mongo.ObjectID()._str - - PERSON.setName( - userGroup.group_id, - person.uuid, - random_id, - person.img_url, - person_name - ) - - LABLE_DADASET_Handle.insert({ - group_id: userGroup.group_id, - uuid: person.uuid, - id: random_id, - url: person.img_url, - name: person_name, - sqlid: person.sqlid, - style: person.style, - action: 'AutoLabel', - faceId: random_id, - }) - - insert_msg2(random_id, person.img_url, person.uuid, person.type, person.accuracy, person.fuzziness, person.sqlid, person.style, person.img_ts, person.current_ts, person.tid) - ) - this.response.end('{"result": "ok"}\n') - ) - - Router.route('restapi/workai_unknown', {where: 'server'}).get(()-> - - ).post(()-> - person_id = '' - persons = [] - person_name = null - active_time = null - if this.request.body.hasOwnProperty('person_id') - person_id = this.request.body.person_id - if this.request.body.hasOwnProperty('persons') - persons = this.request.body.persons - console.log("restapi/workai_unknown post: person_id="+person_id+", persons="+JSON.stringify(persons)) - if (!(persons instanceof Array) or persons.length < 1) - console.log("restapi/workai_unknown: this.request.body is not array.") - return this.response.end('{"result": "failed!", "cause": "this.request.body is not array."}\n') - - console.log("restapi/workai_unknown: uuid = "+persons[0].uuid) - user = Meteor.users.findOne({username: persons[0].uuid}) - unless user - console.log("restapi/workai_unknown: user is null") - return this.response.end('{"result": "failed!", "cause": "user is null."}\n') - userGroups = SimpleChat.GroupUsers.find({user_id: user._id}) - unless userGroups - console.log("restapi/workai_unknown: userGroups is null") - return this.response.end('{"result": "failed!", "cause":"userGroups is null."}\n') - stranger_id = if person_id != '' then person_id else new Mongo.ObjectID()._str - #name = PERSON.getName(null, userGroup.group_id, person_id) - userGroups.forEach((userGroup)-> - console.log("restapi/workai_unknown: userGroup.group_id="+userGroup.group_id) - stranger_name = if person_id != '' then PERSON.getName(null, userGroup.group_id, person_id) else new Mongo.ObjectID()._str - console.log("stranger_name="+stranger_name) - for person in persons - console.log("person="+JSON.stringify(person)) - unless active_time - active_time = utilFormatTime(Number(person.img_ts)) - if !person_name && person_id != '' - person_name = PERSON.getName(null, userGroup.group_id, person_id) - console.log("person_name="+person_name) - - # update to DeviceTimeLine - create_time = new Date() - console.log("create_time.toString()="+create_time.toString()) - if person.img_ts and person.current_ts - img_ts = Number(person.img_ts) - current_ts = Number(person.current_ts) - time_diff = img_ts + getTimeZoneDiffByMs(create_time.getTime(), current_ts) - console.log("time_diff="+time_diff) - create_time = new Date(time_diff) - - timeObj = { - stranger_id: stranger_id, - stranger_name: stranger_name, - person_id: person.id, - person_name: person.name, - img_url: person.img_url, - sqlid: person.sqlid, - style: person.style, - accuracy: person.accuracy, # 准确度(分数) - fuzziness: person.fuzziness#, # 模糊度 - ts:create_time.getTime() - } - uuid = person.uuid - PERSON.updateValueToDeviceTimeline(uuid,userGroup.group_id,timeObj) - if person_name - console.log("restapi/workai_unknown: person_name="+person_name) - #Get Configuration from DB - people_config = WorkAIUserRelations.find({'group_id':userGroup.group_id}, {fields:{'person_name':1, 'hide_it':1}}).fetch() - isShow = people_config.some((elem) => elem.person_name == person_name && !elem.hide_it) - console.log("people_config="+JSON.stringify(people_config)) - if isShow and checkIfSendKnownUnknownPushNotification(userGroup.group_id,person_id) - group = SimpleChat.Groups.findOne({_id: userGroup.group_id}) - group_name = '监控组' - if group && group.name - group_name = group.name - console.log("group_id="+userGroup.group_id) - console.log("Will notify one known people") - - ai_person = Person.findOne({group_id:userGroup.group_id ,'faces.id': person_id}); - ai_person_id = if ai_person then ai_person._id else null - sharpai_pushnotification("notify_knownPeople", {active_time:active_time, group_id:userGroup.group_id, group_name:group_name, person_name:person_name}, null, ai_person_id) - else - group = SimpleChat.Groups.findOne({_id: userGroup.group_id}) - group_name = '监控组' - is_notify_stranger = true - if group && group.settings && group.settings.notify_stranger == false - is_notify_stranger = false - if group && group.name - group_name = group.name - console.log("group_id="+userGroup.group_id+", is_notify_stranger="+is_notify_stranger) - if is_notify_stranger and checkIfSendKnownUnknownPushNotification(userGroup.group_id,'0') - console.log("Will notify stranger") - sharpai_pushnotification("notify_stranger", {active_time:active_time, group_id:userGroup.group_id, group_name:group_name}, null, null) - ) - this.response.end('{"result": "ok"}\n') - ) - Router.route('restapi/workai_unknown_label', {where: 'server'}).get(()-> - - ).post(()-> - person_id = '' - persons = [] - person_name = null - active_time = null - is_person = false - if this.request.body.hasOwnProperty('person_id') - person_id = this.request.body.person_id - if this.request.body.hasOwnProperty('persons') - persons = this.request.body.persons - console.log("restapi/workai_unknown post: person_id="+person_id+", persons="+JSON.stringify(persons)) - if (!(persons instanceof Array) or persons.length < 1) - console.log("restapi/workai_unknown: this.request.body is not array.") - return this.response.end('{"result": "failed!", "cause": "this.request.body is not array."}\n') - user = Meteor.users.findOne({username: persons[0].uuid}) - unless user - console.log("restapi/workai_unknown: user is null") - return this.response.end('{"result": "failed!", "cause": "user is null."}\n') - userGroups = SimpleChat.GroupUsers.find({user_id: user._id}) - unless userGroups - console.log("restapi/workai_unknown: userGroups is null") - return this.response.end('{"result": "failed!", "cause":"userGroups is null."}\n') - if !persons[0].person_name - return this.response.end('{"result": "failed!", "cause":"person_name is null."}\n') - if person_id != '' - is_person = true - person_id = if person_id != '' then person_id else new Mongo.ObjectID()._str - userGroups.forEach((userGroup)-> - person_name = persons[0].person_name - person_data = PERSON.getIdByName(persons[0].uuid, person_name, userGroup.group_id) - console.log(person_name) - faceId = null - if person_data and person_data.faceId - faceId = person_data.faceId - else if persons[0].person_name - faceId = new Mongo.ObjectID()._str - else - faceId = person_id - for person in persons - trainsetObj = { - group_id: userGroup.group_id, - type: 'trainset', - url: person.img_url, - person_id: person_id, - device_id: person.uuid, - face_id: faceId, - drop: false, - img_type: 'face', - style:person.style, - sqlid:person.sqlid - } - console.log("==sr==. unknow_label: " + JSON.stringify(trainsetObj)) - sendMqttMessage('/device/'+userGroup.group_id, trainsetObj) - - setNames = [] - setNames.push({ - uuid: person.uuid, - id: faceId, - url: person.img_url, - name: person_name, - sqlid:person.sqlid, - style:person.style - }); - PERSON.updateLabelTimes(userGroup.group_id,setNames) - PERSON.setName( - userGroup.group_id, - person.uuid, - faceId, - person.img_url, - person_name - ) - LABLE_DADASET_Handle.insert({ - group_id:userGroup.group_id, - uuid:person.uuid, - id:faceId, - url:person.img_url, - name:person_name, - sqlid:person.sqlid, - style:person.style, - action:'rest_api标记'}) - - person_info = { - 'uuid': person.uuid, - 'name': person_name, - 'group_id':userGroup.group_id, - 'img_url': person.img_url, - 'type': 'face', - 'ts': person.img_ts, - 'accuracy': person.accuracy, - 'fuzziness': person.fuzziness, - 'sqlid':person.sqlid, - 'style':person.style - } - check_data = { - face_id: person_id, - person_info: person_info, - formLabel: true - }; - PERSON.aiCheckInOutHandle(check_data) - ) - this.response.end('{"result": "ok"}\n') - ) - Router.route('restapi/workai_multiple_people', {where: 'server'}).get(()-> - - ).post(()-> - person_id = '' - persons = [] - active_time = null - if this.request.body.hasOwnProperty('person_id') - person_id = this.request.body.person_id - if this.request.body.hasOwnProperty('persons') - persons = this.request.body.persons - console.log("restapi/workai_multiple_people post: person_id="+person_id+", persons="+JSON.stringify(persons)) - if (!(persons instanceof Array) or persons.length < 1) - console.log("restapi/workai_multiple_people: this.request.body is not array.") - return this.response.end('{"result": "failed!", "cause": "this.request.body is not array."}\n') - - console.log("restapi/workai_multiple_people: uuid = "+persons[0].uuid) - user = Meteor.users.findOne({username: persons[0].uuid}) - unless user - console.log("restapi/workai_multiple_people: user is null") - return this.response.end('{"result": "failed!", "cause": "user is null."}\n') - userGroups = SimpleChat.GroupUsers.find({user_id: user._id}) - unless userGroups - console.log("restapi/workai_multiple_people: userGroups is null") - return this.response.end('{"result": "failed!", "cause":"userGroups is null."}\n') - - - for person in persons - console.log("person="+JSON.stringify(person)) - unless active_time - active_time = utilFormatTime(Number(person.img_ts)) - - userGroups.forEach((userGroup)-> - console.log("restapi/workai_multiple_people: userGroup.group_id="+userGroup.group_id) - #Get Configuration from DB - people_config = WorkAIUserRelations.find({'group_id':userGroup.group_id}, {fields:{'person_name':1, 'hide_it':1}}).fetch() - console.log("people_config="+JSON.stringify(people_config)) - - multiple_persons = [] - for person in persons - if person.accuracy == 0 - continue - person_name = PERSON.getName(null, userGroup.group_id, person.id) - console.log("restapi/workai_multiple_people: person_name="+person_name) - isShow = people_config.some((elem) => elem.person_name == person_name && !elem.hide_it) - if isShow - if !multiple_persons.some((elem) => elem == person_name) - multiple_persons.push(person_name) - if multiple_persons.length == 0 - console.log("restapi/workai_multiple_people: No people in the request body.") - return - - group = SimpleChat.Groups.findOne({_id: userGroup.group_id}) - group_name = '监控组' - is_notify_stranger = true - if group && group.settings && group.settings.notify_stranger == false - is_notify_stranger = false - if group && group.name - group_name = group.name - console.log("group_id="+userGroup.group_id) - console.log("Will notify known multiple people") - sharpai_pushnotification("notify_knownPeople", {active_time:active_time, group_id:userGroup.group_id, group_name:group_name, person_name:multiple_persons.join(', ')}, null) - ) - this.response.end('{"result": "ok"}\n') - ) - - Router.route('/restapi/workai-group-qrcode', {where: 'server'}).get(()-> - group_id = this.params.query.group_id - console.log '/restapi/workai-group-qrcode get request, group_id: ', group_id - try - img = QRImage.image('http://' + server_domain_name + '/simple-chat/to/group?id=' + group_id, {size: 10}) - this.response.writeHead(200, {'Content-Type': 'image/png'}) - img.pipe(this.response) - catch - this.response.writeHead(414, {'Content-Type': 'text/html'}) - this.response.end('+ + ++ ++ + 414 Request-URI Too Large
') - ) - - device_join_group = (uuid,group_id,name,in_out)-> - device = PERSON.upsetDevice(uuid, group_id,name,in_out) - user = Meteor.users.findOne({username: uuid}) - if !user - userId = Accounts.createUser({username: uuid, password: '123456', profile: {fullname: device.name, icon: '/device_icon_192.png'},is_device:true}) - user = Meteor.users.findOne({_id: userId}) - else - Meteor.users.update({_id:user._id},{$set:{'profile.fullname':device.name, 'profile.icon':'/device_icon_192.png', is_device:true }}); - - group = SimpleChat.Groups.findOne({_id: group_id}) - - #一个设备只允许加入一个群 - groupUsers = SimpleChat.GroupUsers.find({user_id: user._id}) - hasBeenJoined = false - if groupUsers.count() > 0 - groupUsers.forEach((groupUser)-> - if groupUser.group_id is group_id - SimpleChat.GroupUsers.update({_id:groupUser._id},{$set:{is_device:true,in_out:in_out,user_name:device.name}}); - hasBeenJoined = true - else - _group = SimpleChat.Groups.findOne({_id: groupUser.group_id}) - SimpleChat.GroupUsers.remove(groupUser._id) - sendMqttMessage('/msg/g/'+ _group._id, { - _id: new Mongo.ObjectID()._str - form: { - id: user._id - name: if user.profile and user.profile.fullname then user.profile.fullname else user.username - icon: user.profile.icon - } - to: { - id: _group._id - name: _group.name - icon: _group.icon - } - images: [] - to_type: "group" - type: "text" - text: if user.profile and user.profile.fullname then user.profile.fullname + '[' +user.username + '] 已退出该群!' else '设备 ['+user.username+'] 已退出该群!' - create_time: new Date() - is_read: false - }) - ) - if hasBeenJoined is false - SimpleChat.GroupUsers.insert({ - group_id: group_id - group_name: group.name - group_icon: group.icon - user_id: user._id - user_name: if user.profile and user.profile.fullname then user.profile.fullname else user.username - user_icon: if user.profile and user.profile.icon then user.profile.icon else '/device_icon_192.png' - create_time: new Date() - is_device:true - in_out:in_out - }); - sendMqttMessage('/msg/g/'+ group_id, { - _id: new Mongo.ObjectID()._str - form: { - id: user._id - name: if user.profile and user.profile.fullname then user.profile.fullname else user.username - icon: user.profile.icon - } - to: { - id: group_id - name: group.name - icon: group.icon - } - images: [] - to_type: "group" - type: "text" - text: if user.profile and user.profile.fullname then user.profile.fullname + '[' +user.username + '] 已加入!' else '设备 ['+user.username+'] 已加入!' - create_time: new Date() - is_read: false - }) - - Meteor.call 'ai-system-register-devices',group_id,uuid, (err, result)-> - if err or result isnt 'succ' - return console.log('register devices to AI-system failed ! err=' + err); - if result == 'succ' - return console.log('register devices to AI-system succ'); - console.log('user:', user) - console.log('device:', device) - Meteor.methods { - "join-group":(uuid,group_id,name,in_out)-> - console.log("uuid = "+uuid+" group id= "+group_id+" name= "+name+" inout = "+in_out) - device_join_group(uuid,group_id,name,'inout') - sendMqttMessage('/msg/d/'+uuid, {text:'groupchanged'}); - return "ok" - 'getMqttSessionInfo': (clientId) -> - # API返回格式可参阅 http://www.emqtt.com/docs/v2/rest.html#id12 - url = 'http://mq.tiegushi.com:8081/api/v2/sessions/' + clientId - result = HTTP.call('GET', url, {auth: 'admin:public'}) - return result.data.result.objects[0] - } - - Router.route('/restapi/workai-join-group', {where: 'server'}).get(()-> - uuid = this.params.query.uuid - group_id = this.params.query.group_id - console.log '/restapi/workai-join-group get request, uuid:' + uuid + ', group_id:' + group_id - name = this.params.query.name - in_out = this.params.query.in_out - unless uuid or group_id or in_out or name - console.log '/restapi/workai-join-group get unless resturn' - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - device_join_group(uuid,group_id,name,'inout') - - sendMqttMessage('/msg/d/'+uuid, {text:'groupchanged'}); - this.response.end('{"result": "ok"}\n') - ).post(()-> - if this.request.body.hasOwnProperty('uuid') - uuid = this.request.body.uuid - if this.request.body.hasOwnProperty('group_id') - group_id = this.request.body.group_id - if this.request.body.hasOwnProperty('name') - name = this.request.body.name - if this.request.body.hasOwnProperty('in_out') - in_out = this.request.body.in_out - console.log '/restapi/workai-join-group post request, uuid:' + uuid + ', group_id:' + group_id - unless uuid or group_id or in_out or name - console.log '/restapi/workai-join-group get unless resturn' - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - device_join_group(uuid,group_id,name,in_out) - sendMqttMessage('/msg/d/'+uuid, {text:'groupchanged'}); - this.response.end('{"result": "ok"}\n') - ) - - Router.route('/restapi/workai-group-dataset', {where: 'server'}).get(()-> - group_id = this.params.query.group_id - value = this.params.query.value - uuid = this.params.query.uuid - console.log '/restapi/workai-group-dataset get request, group_id:' + group_id + ', value:' + value + ', uuid:' + uuid - unless value and group_id and uuid - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - # insert_msg2(id, img_url, uuid) - update_group_dataset(group_id,value,uuid) - this.response.end('{"result": "ok"}\n') - ).post(()-> - if this.request.body.hasOwnProperty('group_id') - group_id = this.request.body.id - if this.request.body.hasOwnProperty('value') - value = this.request.body.img_url - if this.request.body.hasOwnProperty('uuid') - uuid = this.request.body.uuid - console.log '/restapi/workai-group-dataset get request, group_id:' + group_id + ', value:' + value + ', uuid:' + uuid - unless value and group_id and uuid - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - #insert_msg2(id, img_url, uuid) - update_group_dataset(group_id,value,uuid) - this.response.end('{"result": "ok"}\n') - ) - - Router.route('/restapi/workai-getgroupid', {where: 'server'}).get(()-> - uuid = this.params.query.uuid - - #console.log '/restapi/workai-getgroupid get request, uuid:' + uuid - unless uuid - console.log '/restapi/workai-getgroupid get unless resturn' - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - user = Meteor.users.findOne({username: uuid}) - device_group = '' - if user - groupUser = SimpleChat.GroupUsers.find({user_id: user._id}) - groupUser.forEach((device)-> - if device.group_id - device_group += device.group_id - device_group += ',' - ) - this.response.end(device_group) - ) - - Router.route('/restapi/workai-send2group', {where: 'server'}).get(()-> - uuid = this.params.query.uuid - group_id = this.params.query.group_id - msg_type = this.params.query.type - msg_text = this.params.query.text - is_device_traing = this.params.query.is_device_traing - - unless uuid or group_id - console.log '/restapi/workai-send2group get unless resturn' - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - if (msg_type == 'text' and msg_text) - user = null - userGroup = null - - device = Devices.findOne({"uuid" : uuid}) - if device and device.groupId - user = {'_id': device._id, 'username': device.name, 'profile': {'icon': '/device_icon_192.png'}} - - userGroup = SimpleChat.GroupUsers.findOne({group_id: group_id}) - unless userGroup or userGroup.group_id - return this.response.end('{"result": "failed", "cause": "group not found"}\n') - else - user = Meteor.users.findOne({username: uuid}) - unless user - return this.response.end('{"result": "failed", "cause": "device not registered"}\n') - - userGroup = SimpleChat.GroupUsers.findOne({user_id: user._id, group_id: group_id}) - unless userGroup or userGroup.group_id - return this.response.end('{"result": "failed", "cause": "group not found"}\n') - - sendMqttMessage('/msg/g/'+ userGroup.group_id, { - _id: new Mongo.ObjectID()._str - form: { - id: user._id - name: if user.profile and user.profile.fullname then user.profile.fullname + '['+user.username+']' else user.username - icon: user.profile.icon - } - to: { - id: userGroup.group_id - name: userGroup.group_name - icon: userGroup.group_icon - } - images: [] - to_type: "group" - type: "text" - text: msg_text - create_time: new Date() - is_read: false - is_device_traing: is_device_traing - }) - - this.response.end('{"result": "ok"}\n') - ).post(()-> - if this.request.body.hasOwnProperty('uuid') - uuid = this.request.body.uuid - if this.request.body.hasOwnProperty('group_id') - group_id = this.request.body.group_id - if this.request.body.hasOwnProperty('type') - msg_type = this.request.body.type - if this.request.body.hasOwnProperty('text') - msg_text = this.request.body.text - if this.request.body.hasOwnProperty('is_device_traing') - is_device_traing = this.request.body.is_device_traing - - unless uuid or group_id - console.log '/restapi/workai-send2group get unless resturn' - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - if (msg_type == 'text' and msg_text) - user = null - userGroup = null - - device = Devices.findOne({"uuid" : uuid}) - if device and device.groupId - user = {'_id': device._id, 'username': device.name, 'profile': {'icon': '/device_icon_192.png'}} - - userGroup = SimpleChat.GroupUsers.findOne({group_id: group_id}) - unless userGroup or userGroup.group_id - return this.response.end('{"result": "failed", "cause": "group not found"}\n') - else - user = Meteor.users.findOne({username: uuid}) - unless user - return this.response.end('{"result": "failed", "cause": "device not registered"}\n') - - userGroup = SimpleChat.GroupUsers.findOne({user_id: user._id, group_id: group_id}) - unless userGroup or userGroup.group_id - return this.response.end('{"result": "failed", "cause": "group not found"}\n') - - sendMqttMessage('/msg/g/'+ userGroup.group_id, { - _id: new Mongo.ObjectID()._str - form: { - id: user._id - name: if user.profile and user.profile.fullname then user.profile.fullname + '['+user.username+']' else user.username - icon: user.profile.icon - } - to: { - id: userGroup.group_id - name: userGroup.group_name - icon: userGroup.group_icon - } - images: [] - to_type: "group" - type: "text" - text: msg_text - create_time: new Date() - is_read: false - is_device_traing: is_device_traing - }) - - this.response.end('{"result": "ok"}\n') - ) - - Router.route('/restapi/workai-group-template', {where: 'server'}).get(()-> - result = { - group_templates:[ - { - "_id" : new Mongo.ObjectID()._str, - "name": "Work AI工作效能模版", - "icon": rest_api_url + "/workAIGroupTemplate/efficiency.jpg", - "img_type": "face" - }, - { - "_id" : new Mongo.ObjectID()._str, - "name": "家庭安全模版", - "icon": rest_api_url + "/workAIGroupTemplate/safety.jpg", - "img_type": "object" - }, - { - "_id" : new Mongo.ObjectID()._str, - "name": "NLP情绪分析模版", - "icon": rest_api_url + "/workAIGroupTemplate/sentiment.jpg" - }, - { - "_id" : new Mongo.ObjectID()._str, - "name": "NLP通用文本分类模版", - "icon": rest_api_url + "/workAIGroupTemplate/classification.jpg", - "type":'nlp_classify' - }, - { - "_id" : new Mongo.ObjectID()._str, - "name": "ChatBot训练模版", - "icon": rest_api_url + "/workAIGroupTemplate/chatBot.jpg" - } - ]} - this.response.end(JSON.stringify(result)) - ) - - onNewHotSharePost = (postData)-> - console.log 'onNewHotSharePost:' , postData - nlp_group = SimpleChat.Groups.findOne({_id:'92bf785ddbe299bac9d1ca82'}); - nlp_user = Meteor.users.findOne({_id: 'xWA3KLXDprNe8Lczw'}); - #nlp_classname = NLP_CLASSIFY.getName() - nlp_classname = postData.classname - if nlp_classname - NLP_CLASSIFY.setName(nlp_group._id,nlp_classname) - sendMqttMessage('/msg/g/'+ nlp_group._id, { - _id: new Mongo.ObjectID()._str - form: { - id: nlp_user._id - name: if nlp_user.profile and nlp_user.profile.fullname then nlp_user.profile.fullname + '['+nlp_user.username+']' else nlp_user.username - icon: nlp_user.profile.icon - } - to: { - id: nlp_group._id - name: nlp_group.name - icon: nlp_group.icon - } - to_type: "group" - type: "url" - text: if !nlp_classname then '1 个链接需要标注' else nlp_className + ':' - urls:[{ - _id:new Mongo.ObjectID()._str, - label: nlp_classname, - #class_id:postData.classid, - url:postData.posturl, - title:postData.posttitle, - thumbData:postData.mainimage, - description:if postData.description then postData.description else postData.posturl - }] - create_time: new Date() - class_name: nlp_classname - wait_lable: !nlp_classname - is_read: false - }) - - Router.route('/restapi/workai-hotshare-newpost', {where: 'server'}).get(()-> - classname = this.params.query.classname - #username = this.params.query.username - posttitle = this.params.query.posttitle - posturl = this.params.query.posturl - mainimage = this.params.query.mainimage - description = this.params.query.description - - postData = { classname: classname, posttitle: posttitle, posturl: posturl ,mainimage:mainimage, description:description} - onNewHotSharePost(postData) - this.response.end('{"result": "ok"}\n') - ).post(()-> - if this.request.body.hasOwnProperty('classname') - classname = this.request.body.classname - if this.request.body.hasOwnProperty('posttitle') - posttitle = this.request.body.posttitle - if this.request.body.hasOwnProperty('posturl') - posturl = this.request.body.posturl - if this.request.body.hasOwnProperty('mainimage') - mainimage = this.request.body.mainimage - if this.request.body.hasOwnProperty('description') - description = this.request.body.description - - postData = { classname: classname, posttitle: posttitle, posturl: posturl, mainimage:mainimage, description:description} - onNewHotSharePost(postData) - this.response.end('{"result": "ok"}\n') - ) - - Router.route('/restapi/workai-motion-imgs/:id', {where: 'server'}).get(()-> - id = this.params.id - post = Posts.findOne({_id: id}) - html = Assets.getText('workai-motion-imgs.html'); - imgs = '' - post.docSource.imgs.forEach (img)-> - imgs += '- ' - html = html.replace('{{imgs}}', imgs) - - this.response.end(html) - ) - Router.route('/restapi/workai-motion', {where: 'server'}).post(()-> - payload = this.request.body || {} - deviceUser = Meteor.users.findOne({username: payload.uuid})|| {} - groupUser = SimpleChat.GroupUsers.findOne({user_id: deviceUser._id}) || {} # 一个平板只对应一个聊天群 - group = SimpleChat.Groups.findOne({_id: groupUser.group_id}) - - if (!group) - return this.response.end('{"result": "error"}\n') - if (payload.motion_gif) - imgs = [payload.motion_gif] - # if (payload.imgs) - # imgs = payload.imgs - if (!imgs or imgs.length <= 0) - return this.response.end('{"result": "error"}\n') - if (imgs.length > 10) - imgs = imgs.slice(0, 9) - deferSetImmediate ()-> - # update follow - SimpleChat.GroupUsers.find({group_id: group._id}).forEach (item)-> - if (Follower.find({userId: item.user_id, followerId: deviceUser._id}).count() <= 0) - console.log('insert follower:', item.user_id) - Follower.insert({ - userId: item.user_id - followerId: deviceUser._id - createAt: new Date() - }) - #一个设备一天的动作只放在一个帖子里 - devicePost = Posts.findOne({owner:deviceUser._id}) - isTodayPost = false; - if(devicePost) - today = new Date().toDateString() - isTodayPost = if devicePost.createdAt.toDateString() is today then true else false; - console.log 'isTodayPost:'+isTodayPost - name = PERSON.getName(payload.uuid, group._id, payload.id) - postId = if isTodayPost then devicePost._id else new Mongo.ObjectID()._str - deviceName = if deviceUser.profile and deviceUser.profile.fullname then deviceUser.profile.fullname else deviceUser.username - title = if name then "摄像头看到#{name} 在#{deviceName}" else (if payload.type is 'face' then "摄像头看到有人在#{deviceName}" else "摄像头看到#{deviceName}有动静") - time = '时间:' + new Date().toString() - post = { - pub: if isTodayPost then devicePost.pub else [] - title: title - addontitle: time - browse: 0 - heart: [] - retweet: [] - comment: [] - commentsCount: 0 - mainImage: if payload.motion_gif then payload.motion_gif else payload.img_url - publish: true - owner: deviceUser._id - ownerName: deviceName - ownerIcon: if deviceUser.profile and deviceUser.profile.icon then deviceUser.profile.icon else '/userPicture.png' - createdAt: new Date # new Date(payload.ts) - isReview: true - insertHook: true - import_status: 'done' - fromUrl: '' - docType: 'motion' - docSource: payload - } - newPub = [] - # newPub.push({ - # _id: new Mongo.ObjectID()._str - # type: 'text' - # isImage: false - # owner: deviceUser._id - # text: "类 型:#{if payload.type is 'face' then '人' else '对像'}\n准确度:#{payload.accuracy}\n模糊度:#{payload.fuzziness}\n设 备:#{deviceName}\n动 作:#{payload.mid}\n" - # style: '' - # data_row: 1 - # data_col: 1 - # data_sizex: 6 - # data_sizey: 1 - # data_wait_init: true - # }) - # newPub.push({ - # _id: new Mongo.ObjectID()._str - # type: 'text' - # isImage: false - # owner: deviceUser._id - # text: '以下为设备的截图:' - # style: '' - # data_row: 2 - # data_col: 1 - # data_sizex: 6 - # data_sizey: 1 - # data_wait_init: true - # }) - - data_row = 3 - imgs.forEach (img)-> - newPub.push({ - _id: new Mongo.ObjectID()._str - type: 'image' - isImage: true - # inIframe: true - owner: deviceUser._id - # text: '您当前程序不支持视频观看', - # iframe: '' - imgUrl: img, - data_row: data_row - data_col: 1 - data_sizex: 6 - data_sizey: 5 - data_wait_init: true - }) - data_row += 5 - newPub.push({ - _id: new Mongo.ObjectID()._str - type: 'text' - isImage: false - owner: deviceUser._id - text: time - style: '' - data_row: 1 - data_col: 1 - data_sizex: 6 - data_sizey: 1 - data_wait_init: true - isTime:true - }) - newPub.push({ - _id: new Mongo.ObjectID()._str - type: 'text' - isImage: false - owner: deviceUser._id - text: title - style: '' - data_row: 1 - data_col: 1 - data_sizex: 6 - data_sizey: 1 - data_wait_init: true - }) - Array.prototype.push.apply(newPub, post.pub) - post.pub = newPub - # formatPostPub(post.pub) - if isTodayPost - console.log('update motion post:', postId) - Posts.update({_id:postId},{$set:post}) - globalPostsUpdateHookDeferHandle(post.owner, postId,null,{$set:post}) - return; - post._id = postId - globalPostsInsertHookDeferHandle(post.owner, post._id) - Posts.insert(post) - console.log('insert motion post:', post._id) - this.response.end('{"result": "ok"}\n') - ) - Router.route('/restapi/date', (req, res, next)-> - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - this.response.end(Date.now().toString()) - , {where: 'server'}) - Router.route('/restapi/allgroupsid/:token/:skip/:limit', {where: 'server'}).get(()-> - token = this.params.token - limit = this.params.limit - skip = this.params.skip - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/allgroupsid get request, token:' + token + ' limit:' + limit + ' skip:' + skip - - allgroups = [] - groups = SimpleChat.Groups.find({}, {fields:{"_id": 1, "name": 1}, limit: parseInt(limit), skip: parseInt(skip)}) - unless groups - return this.response.end('[]\n') - groups.forEach((group)-> - allgroups.push({'id':group._id, 'name': group.name}) - ) - - this.response.end(JSON.stringify(allgroups)) - ) - Router.route('/restapi/groupusers/:token/:groupid/:skip/:limit', {where: 'server'}).get(()-> - token = this.params.token - limit = this.params.limit - skip = this.params.skip - groupid = this.params.groupid - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/groupusers get request, token:' + token + ' limit:' + limit + ' skip:' + skip + ' groupid:' + groupid - - group = SimpleChat.Groups.findOne({'_id': groupid}) - unless group - console.log 'no group found:' + groupid - return this.response.end('[]\n') - - #groupDevices = Devices.find({'groupId': groupid}).fetch() - #console.log 'no group found:' + groupDevices - - allUsers = [] - userGroups = SimpleChat.GroupUsers.find({group_id: groupid}, {fields:{"user_id": 1, "user_name": 1}, limit: parseInt(limit), skip: parseInt(skip)}) - unless userGroups - return this.response.end('[]\n') - userGroups.forEach((userGroup)-> - #if _.pluck(groupDevices, 'uuid').indexOf(userGroup.user_id) is -1 - allUsers.push({'user_id':userGroup.user_id, 'user_name': userGroup.user_name}) - ) - - this.response.end(JSON.stringify(allUsers)) - ) - Router.route('/restapi/activity/:token/:direction/:groupid/:ts/:skip/:limit', {where: 'server'}).get(()-> - token = this.params.token - groupid = this.params.groupid - limit = this.params.limit - skip = this.params.skip - direction = this.params.direction - starttime = this.params.ts - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/user get request, token:' + token + ' limit:' + limit + ' skip:' + skip + ' groupid:' + groupid + ' Direction:' + direction + ' starttime:' + starttime - - group = SimpleChat.Groups.findOne({'_id': groupid}) - unless group - console.log 'no group found:' + groupid - return this.response.end('[]\n') - - allActivity = [] - #Activity.id is id of person name - groupActivity = Activity.find( - {'group_id': groupid, 'ts': {$gt: parseInt(starttime)}, 'in_out': direction} - {fields:{'id': 1, 'name': 1, 'ts': 1, 'in_out': 1, 'img_url':1}, limit: parseInt(limit), skip: parseInt(skip)} - ) - unless groupActivity - return this.response.end('[]\n') - groupActivity.forEach((activity)-> - allActivity.push({'user_id': activity.id, 'user_name': activity.name, 'in_out': activity.in_out, 'img_url': activity.img_url}) - ) - - this.response.end(JSON.stringify(allActivity)) - ) - - Router.route('/restapi/active/:active/:token/:direction/:skip/:limit', {where: 'server'}).get(()-> - token = this.params.token # - limit = this.params.limit # - skip = this.params.skip # - direction = this.params.direction #'in'/'out' - active = this.params.active #'active'/'notactive' - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/:active get request, token:' + token + ' limit:' + limit + ' skip:' + skip + ' Direction:' + direction + ' active:' + active - - allnotActivity = [] - daytime = new Date() - daytime.setSeconds(0) - daytime.setMinutes(0) - daytime.setHours(0) - daytime = new Date(daytime).getTime() - - notActivity = WorkAIUserRelations.find({}, {limit: parseInt(limit), skip: parseInt(skip)}) - unless notActivity - return this.response.end('[]\n') - notActivity.forEach((item)-> - #console.log(item) - if !item.checkin_time - item.checkin_time = 0 - if !item.ai_in_time - item.ai_in_time= 0 - if !item.checkout_time - item.checkout_time = 0 - if !item.ai_out_time - item.ai_out_time = 0 - - if active is 'notactive' - if direction is 'in' and item.checkin_time < daytime and item.ai_in_time < daytime - allnotActivity.push({ - 'app_user_id': item.app_user_id - 'app_user_name': item.app_user_name - 'uuid': item.in_uuid - 'groupid': item.group_id - 'msgid': new Mongo.ObjectID()._str - }) - else if direction is 'out' and item.checkout_time < daytime and item.ai_out_time < daytime - allnotActivity.push({ - 'app_user_id': item.app_user_id - 'app_user_name': item.app_user_name - 'uuid': item.out_uuid - 'groupid': item.group_id - 'msgid': new Mongo.ObjectID()._str - }) - else if active is 'active' - if direction is 'in' and (item.checkin_time > daytime or item.ai_in_time > daytime) - allnotActivity.push({ - 'app_user_id': item.app_user_id - 'app_user_name': item.app_user_name - 'uuid': item.in_uuid - 'groupid': item.group_id - 'msgid': new Mongo.ObjectID()._str - }) - else if direction is 'out' and (item.checkout_time > daytime or item.ai_out_time > daytime) - allnotActivity.push({ - 'app_user_id': item.app_user_id - 'app_user_name': item.app_user_name - 'uuid': item.out_uuid - 'groupid': item.group_id - 'msgid': new Mongo.ObjectID()._str - }) - ) - - this.response.end(JSON.stringify(allnotActivity)) - ) - Router.route('/restapi/resetworkstatus/:token', {where: 'server'}).get(()-> - token = this.params.token # - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/resetworkstatus get request' - - date = Date.now(); - mod = 24*60*60*1000; - date = date - (date % mod) - nextday = date + mod - - relations = WorkAIUserRelations.find({}) - relations.forEach((fields)-> - if fields && fields.group_id - # 按 group 所在时区初始化 workStatus 数据 - timeOffset = 8 - shouldInitWorkStatus = false - group = SimpleChat.Groups.findOne({_id: fields.group_id}) - if (group and group.offsetTimeZone) - timeOffset = parseInt(group.offsetTimeZone) - - # 根据时区 获取 group 对应时区时间 - group_local_time = getLocalTimeByOffset(timeOffset) - group_local_time_hour = group_local_time.getHours() - if group_local_time_hour is 0 - shouldInitWorkStatus = true - console.log('now should init the offsetTimeZone: ', timeOffset) - #console.log('>>> ' + JSON.stringify(fields)) - workstatus = WorkStatus.findOne({'group_id': fields.group_id, 'date': nextday, 'person_name': fields.person_name}) - if !workstatus and shouldInitWorkStatus - newWorkStatus = { - "app_user_id" : fields.app_user_id - "group_id" : fields.group_id - "date" : nextday - "person_id" : fields.ai_persons - "person_name" : fields.person_name - "status" : "out" - "in_status" : "unknown" - "out_status" : "unknown" - "in_uuid" : fields.in_uuid - "out_uuid" : fields.out_uuid - "whats_up" : "" - "in_time" : 0 - "out_time" : 0 - "hide_it" : if fields.hide_it then fields.hide_it else false - } - #console.log('>>> new a WorkStatus ' + JSON.stringify(newWorkStatus)) - - WorkStatus.insert(newWorkStatus) - ) - - # 计算没有确认下班的数据 - docs = UserCheckoutEndLog.find({}).fetch() - docs.map (doc)-> - # remove - UserCheckoutEndLog.remove({_id: doc._id}) - - # 状态 - startUTC = Date.UTC(doc.params.msg_data.create_time.getUTCFullYear(), doc.params.msg_data.create_time.getUTCMonth(), doc.params.msg_data.create_time.getUTCDate(), 0, 0, 0, 0) - endUTC = Date.UTC(doc.params.msg_data.create_time.getUTCFullYear(), doc.params.msg_data.create_time.getUTCMonth(), doc.params.msg_data.create_time.getUTCDate(), 23, 59, 59, 0) - workstatus = WorkStatus.findOne({'group_id': doc.params.msg_data.group_id, 'date': {$gte: startUTC, $lte: endUTC}}) - - # local date - now = new Date() - group = SimpleChat.Groups.findOne({_id: doc.params.msg_data.group_id}) - if (group and group.offsetTimeZone) - now = new Date((now.getTime()+(now.getTimezoneOffset()*60000)) + (3600000*group.offsetTimeZone)) - else - now = new Date((now.getTime()+(now.getTimezoneOffset()*60000)) + (3600000*8)) - - # console.log('===1==', workstatus.status) - unless (workstatus and workstatus.status is 'out') - # console.log('===2==', doc.userName, doc.params.msg_data.create_time.getUTCDate(), now.getUTCDate()) - # 当天数据 - if (doc.params.msg_data.create_time.getUTCDate() is now.getUTCDate()) - # console.log('===3==', doc.userName) - send_greeting_msg(doc.params.msg_data); - PERSON.updateWorkStatus(doc.params.person._id) - if (doc.params.person_info) - PERSON.sendPersonInfoToWeb(doc.params.person_info) - else - # TODO: - - this.response.end(JSON.stringify({result: 'ok'})) - ) - - - # params = { - # uuid: 设备UUID, - # person_id: id, - # video_post: 视频封面图地址, - # video_src: 视频播放地址 - # ts: 时间戳 - # ts_offset: 时区 (eg : 东八区 是 -8); - # } - Router.route('/restapi/timeline/video/', {where: 'server'}).post(()-> - payload = this.request.body || {} - console.log('/restapi/timeline/video/ request body = ',JSON.stringify(payload)) - - if (!payload.uuid or !payload.person_id or !payload.video_post or !payload.video_src or !payload.ts or !payload.ts_offset) - return this.response.end('{"result": "error", "reson":"参数不全或格式错误!"}\n') - - # step 1. get group_id by uuid - device = Devices.findOne({uuid: payload.uuid}) - if device and device.groupId - group_id = device.groupId - - # step 2. get person_name by person_id - person_name = PERSON.getName(payload.uuid, group_id, payload.person_id) - if (!person_name) - person_name = "" - - PERSON.updateToDeviceTimeline(payload.uuid,group_id,{ - is_video: true, - person_id: payload.person_id, - person_name: person_name, - video_post: payload.video_post, - video_src: payload.video_src, - ts: Number(payload.ts), - ts_offset: Number(payload.ts_offset) - }) - - return this.response.end('{"result": "success"}\n') - ) - Router.route('/restapi/clustering', {where: 'server'}).get(()-> - group_id = this.params.query.group_id - faceId = this.params.query.faceId - totalFaces = this.params.query.totalFaces - url = this.params.query.url - rawfilepath = this.params.query.rawfilepath - isOneSelf = this.params.query.isOneSelf - if isOneSelf == 'true' - isOneSelf = true - else - isOneSelf = false - - console.log '/restapi/clustering get request, group_id:' + group_id + ', faceId:' + faceId + ',totalFaces:' + totalFaces + ',url' + url + ',rawfilepath' + rawfilepath + ',isOneSelf' + isOneSelf - unless group_id and isOneSelf - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - #取回这个组某个人所有正确/不正确的图片 - if group_id and faceId - dataset={'group_id': group_id, 'faceId': faceId, 'dataset': []} - allClustering = Clustering.find({'group_id': group_id, 'faceId': faceId, 'isOneSelf': isOneSelf}).fetch() - allClustering.forEach((fields)-> - dataset.dataset.push({url:fields.url, rawfilepath: fields.rawfilepath}) - ) - else - dataset={'group_id': group_id, 'dataset': []} - - #取回这个组某个人所有不正确的图片 - - #返回标注过的数据集 - this.response.end(JSON.stringify(dataset)) - ).post(()-> - if this.request.body.hasOwnProperty('group_id') - group_id = this.request.body.group_id - if this.request.body.hasOwnProperty('faceId') - faceId = this.request.body.faceId - if this.request.body.hasOwnProperty('totalFaces') - totalFaces = this.request.body.totalFaces - if this.request.body.hasOwnProperty('url') - url = this.request.body.url - if this.request.body.hasOwnProperty('rawfilepath') - rawfilepath = this.request.body.rawfilepath - if this.request.body.hasOwnProperty('isOneSelf') - isOneSelf = this.request.body.isOneSelf - - if isOneSelf == "true" - isOneSelf = true - - console.log '/restapi/clustering post request, group_id:' + group_id + ', faceId:' + faceId + ',totalFaces:' + totalFaces + ',url' + url + ',rawfilepath' + rawfilepath + ',isOneSelf' + isOneSelf - unless group_id and faceId and url - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - #插入数据库 - person = Person.findOne({group_id: group_id, 'faceId': faceId},{sort: {createAt: 1}}) - if !person - person = { - _id: new Mongo.ObjectID()._str, - id: 1, - group_id: group_id, - faceId: faceId, - url: url, - name: faceId, - faces: [], - deviceId: 'clustering', - DeviceName: 'clustering', - createAt: new Date(), - updateAt: new Date() - }; - Person.insert(person) - console.log('>>> new people' + faceId) - - clusteringObj = { - group_id: group_id, - faceId: faceId, - totalFaces: totalFaces, - url: url, - rawfilepath: rawfilepath, - isOneSelf: isOneSelf - } - Clustering.insert(clusteringObj) - #console.log('>>> ' + JSON.stringify(clusteringObj)) - this.response.end('{"result": "ok"}\n') - ) - Router.route('/restapi/get-group-users', {where: 'server'}).get(()-> - group_id = this.params.query.group_id - - resObj = [] - gUsers = Person.find({group_id: group_id}) - if gUsers - gUsers.forEach((user)-> - resObj.push(user) - ) - this.response.end(JSON.stringify(resObj)) - ).post(()-> - if this.request.body.hasOwnProperty('group_id') - group_id = this.request.body.group_id - - resObj = [] - gUsers = Person.find({group_id: group_id}) - if gUsers - gUsers.forEach((user)-> - resObj.push(user) - ) - this.response.end(JSON.stringify(resObj)) - ) - Router.route('/restapi/datasync/:token/:groupid', {where: 'server'}).get(()-> - token = this.params.token - groupid = this.params.groupid - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/datasync get request, token:' + token + ' groupid:' + groupid - - group = SimpleChat.Groups.findOne({'_id': groupid}) - unless group - console.log 'no group found:' + groupid - return this.response.end('[]\n') - - syncDateSet=[] - - #取出群相册里面所有已经标注的数据 - persons = Person.find({group_id: groupid},{fields:{name: 1, faceId:1}}).fetch() - persons.forEach((item)-> - urls=[] - if item and item.name - dataset = LableDadaSet.find({group_id: groupid ,name: item.name}, {fields:{url: 1,style:1,sqlid:1}}).fetch() - dataset.forEach((item2)-> - if item2 and item2.url - urls.push({ - url:item2.url, - style: item2.style || 'front', - sqlid: item2.sqlid || null - }) - ) - - if item and item.faceId - syncDateSet.push({faceId: item.faceId, urls: urls}) - ) - - this.response.end(JSON.stringify(syncDateSet)) - ) - Router.route('/restapi/humandatasync/:token/:groupid', {where: 'server'}).get(()-> - token = this.params.token - groupid = this.params.groupid - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/datasync get request, token:' + token + ' groupid:' + groupid - - group = SimpleChat.Groups.findOne({'_id': groupid}) - unless group - console.log 'no group found:' + groupid - return this.response.end('[]\n') - - syncDateSet=[] - - #取出群相册里面所有已经标注的数据 - persons = Person.find({group_id: groupid, human_shape: {$exists: true}},{fields:{name: 1, faceId: 1, human_shape: 1}}).fetch() - console.log('leon person', persons) - persons.forEach((item)-> - urls=[] - console.log('leon item', item) - item.human_shape.forEach((item2)-> - urls.push({ - url: item2.url, - style: 'human_shape', - sqlid: null - }) - ) - if item and item.faceId - syncDateSet.push({faceId: item.faceId, urls: urls}) - ) - this.response.end(JSON.stringify(syncDateSet)) - ) - Router.route('/restapi/groupdatasync/:token/:groupid', {where: 'server'}).get(()-> - token = this.params.token - groupid = this.params.groupid - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/datasync get request, token:' + token + ' groupid:' + groupid - - group = SimpleChat.Groups.findOne({'_id': groupid}) - unless group - console.log 'no group found:' + groupid - return this.response.end('[]\n') - - syncDateSet=[] - - #取出群相册里面所有已经标注的数据 - person = GroupPerson.find({group_id: groupid},{fields:{name: 1, faceId:1}}).fetch() - person.forEach((item)-> - urls=[] - if item and item.name - dataset = GroupLableDadaSet.find({group_id: groupid ,name: item.name}, {fields:{url: 1,style:1,sqlid:1}}).fetch() - dataset.forEach((item2)-> - if item2 and item2.url - urls.push({ - url:item2.url, - style: item2.style || 'front', - sqlid: item2.sqlid || null - }) - ) - - if item and item.faceId - syncDateSet.push({faceId: item.faceId, urls: urls}) - ) - - this.response.end(JSON.stringify(syncDateSet)) - ) - - - getInComTimeLen = (workstatus) -> - group_id = workstatus.group_id; - diff = 0; - out_time = workstatus.out_time; - today_end = workstatus.out_time; - time_offset = 8 - group = SimpleChat.Groups.findOne({_id: group_id}); - if (group && group.offsetTimeZone) - time_offset = group.offsetTimeZone; - DateTimezone = (d, time_offset) -> - if (time_offset == undefined) - if (d.getTimezoneOffset() == 420) - time_offset = -7 - else - time_offset = 8 - #取得 UTC time - utc = d.getTime() + (d.getTimezoneOffset() * 60000); - local_now = new Date(utc + (3600000*time_offset)) - 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(workstatus.in_time) - date = new Date(workstatus.in_time); - fomatDate = date.shortTime(time_offset); - isToday = PERSON.checkIsToday(workstatus.in_time,group_id) - #不是今天的时间没有out_time的或者是不是今天时间,最后一次拍到的是进门的状态的都计算到当天结束 - if((!out_time and !isToday) or (workstatus.status is 'in' and !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; - workstatus.in_time = date.getTime(); - #今天的时间(没有离开过监控组) - else if(!out_time and isToday) - now_time = Date.now(); - out_time = now_time; - #今天的时间(离开监控组又回到监控组) - else if(out_time and workstatus.status is 'in' and isToday) - now_time = Date.now(); - out_time = now_time; - - if(workstatus.in_time and out_time) - diff = out_time - workstatus.in_time; - - if(diff > 24*60*60*1000) - diff = 24*60*60*1000; - else if(diff < 0) - diff = 0; - - min = diff / 1000 / 60 ; - 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; - - getShortTime = (ts,group_id)-> - time_offset = 8 - group = SimpleChat.Groups.findOne({_id: group_id}); - if (group && group.offsetTimeZone) - time_offset = group.offsetTimeZone; - time = new Date(ts); - return time.shortTime(time_offset,true); - - sendEmailToGroupUsers = (group_id)-> - if !group_id - return - group = SimpleChat.Groups.findOne({_id:group_id}); - if !group - return - - date = Date.now(); - mod = 24*60*60*1000; - date = date - (date % mod) - yesterday = date - mod - console.log 'date:'+ yesterday - workstatus_content = '' - WorkStatus.find({'group_id': group_id, 'date': yesterday}).forEach((target)-> - unless target.hide_it - text = Assets.getText('email/work-status-content.html'); - text = text.replace('{{person_name}}', target.person_name); - # app_user_status_color = 'gray' - # app_notifaction_status = '' - # if target.app_user_id - # app_user_status_color = 'green' - # if target.app_notifaction_status is 'on' - # app_notifaction_status = '' - # app_user_status = ''+app_notifaction_status - # text = text.replace('{{app_user_status_color}}',app_user_status_color); - # text = text.replace('{{app_notifaction_status}}',app_notifaction_status); - isStatusIN_color = if target.status is 'in' then 'green' else 'gray' - # if target.in_time > 0 - # if PERSON.checkIsToday(target.in_time,group_id) - # isStatusIN_color = 'gray' - # text = text.replace('{{isStatusIN_color}}',isStatusIN_color); - - InComTimeLen = getInComTimeLen(target) - text = text.replace('{{InComTimeLen}}',InComTimeLen); - isInStatusNotUnknownStyle = if target.in_status is 'unknown' then 'display:none;' else 'display:table-cell;' - text = text.replace('{{isInStatusNotUnknownStyle}}',isInStatusNotUnknownStyle); - isInStatusUnknownStyle = if target.in_status is 'unknown' then 'display:table-cell;' else 'display:none;' - text = text.replace('{{isInStatusUnknownStyle}}',isInStatusUnknownStyle); - if target.in_status isnt 'unknown' - text = text.replace('{{in_image}}',target.in_image); - text = text.replace('{{in_time}}',getShortTime(target.in_time,group_id)) - in_time_Color = 'green' - if target.in_status is 'warning' - in_time_Color = 'orange' - else if target.in_status is 'error' - in_time_Color = 'red' - text = text.replace('{{in_time_Color}}',in_time_Color); - historyUnknownOutStyle = 'display:none;' - if isStatusIN_color is 'green' - historyUnknownOutStyle = 'display:table-cell;color:red;' - else - if target.out_status is 'unknown' - historyUnknownOutStyle = 'display:table-cell;' - text = text.replace('{{historyUnknownOutStyle}}',historyUnknownOutStyle); - - isOutStatusNotUnknownStyle = if historyUnknownOutStyle is 'display:none;' then 'display:table-cell;' else 'display:none;' - text = text.replace('{{isOutStatusNotUnknownStyle}}',isOutStatusNotUnknownStyle); - if historyUnknownOutStyle is 'display:none;' - text = text.replace('{{out_image}}',target.out_image); - text = text.replace('{{out_time}}',getShortTime(target.out_time,group_id)); - out_time_Color = 'green' - if target.out_status is 'warning' - out_time_Color = 'orange' - else if target.out_status is 'error' - out_time_Color = 'red' - text = text.replace('{{out_time_Color}}',out_time_Color); - whats_up = '' - if target.whats_up - whatsUpLists = []; - if typeof(target.whats_up) is 'string' - whatsUpLists.push({ - person_name:target.person_name, - content:target.whats_up, - ts:target.in_time - }) - # ... - else - whatsUpLists = target.whats_up - for item in whatsUpLists - whats_up = whats_up + '
'+item.person_name+'['+getShortTime(item.ts,group_id)+']'+item.content - else - whats_up = '今天还没有工作安排...' - text = text.replace('{{whats_up}}',whats_up); - - workstatus_content = workstatus_content + text - - ) - if workstatus_content.length > 0 - text = Assets.getText('email/work-status-report.html'); - text = text.replace('{{group.name}}', group.name); - y_date = new Date(yesterday) - year = y_date.getFullYear(); - month = y_date.getMonth() + 1; - y_date_title = '(' + year + '-' + month + '-' +y_date.getDate() + ')'; - text = text.replace('{{date.fomatStr}}',y_date_title) - text = text.replace('{{workStatus.content}}', workstatus_content); - subject = group.name + ' 每日出勤报告'+y_date_title - else - return - - #console.log 'html:'+ JSON.stringify(text) - SimpleChat.GroupUsers.find({group_id:group_id}).forEach( - (fields)-> - if fields and fields.user_id - user_id = fields.user_id - #user_id = 'GriTByu7MhRGhQdPD' - user = Meteor.users.findOne({_id:user_id}); - if user and user.emails and user.emails.length > 0 - email_address = user.emails[0].address - #email_address = 'dsun@actionteca.com' - isUnavailable = UnavailableEmails.findOne({address:email_address}); - unless isUnavailable - #email_address = user.emails[0].address - console.log 'groupuser : ' + user.profile.fullname + ' email address is :' + user.emails[0].address - - try - Email.send({ - to:email_address, - from:'点圈
', - subject:subject, - html : text, - envelope:{ - from:'点圈 ', - to:email_address + '<' + email_address + '>' - } - }) - console.log 'try send mail to:'+email_address - catch e - console.log 'exception:send mail error = %s, userEmail = %s',e,email_address - ### - unavailableEmail = UnavailableEmails.findOne({address:email_address}) - if unavailableEmail - UnavailableEmails.update({reason:e}); - else - UnavailableEmails.insert({address:email_address,reason:e,createAt:new Date()}); - ### - ) - - - Router.route('/restapi/sendReportByEmail/:token',{where:'server'}).get(()-> - token = this.params.token # - - headers = { - 'Content-type':'text/html;charest=utf-8', - 'Date': Date.now() - } - this.response.writeHead(200, headers) - console.log '/restapi/sendReportByEmail get request' - #sendEmailToGroupUsers('ae64c98bdff9b674fb5dad4b') - groups = SimpleChat.Groups.find({}) - groups.forEach((fields)-> - if fields - sendEmailToGroupUsers(fields._id) - ) - - this.response.end(JSON.stringify({result: 'ok'})) - ) - - # 定义相应的mailgun webhook, dropped,hardbounces,unsubscribe 下次不再向相应的邮件地址发信 - # docs: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks - @mailGunSendHooks = (address, type, reason)-> - if !address and !reason - return console.log('need email address and webhook reason') - - console.log('Email send Hooks, send to '+address + ' failed , reason: '+ reason) - - unavailableEmail = UnavailableEmails.findOne({address: address}) - if unavailableEmail - UnavailableEmails.update({_id: unavailableEmail._id},{$set:{ - reason:reason - }}) - else - UnavailableEmails.insert({ - address: address, - reason: reason - createAt: new Date() - }) - - # 发信失败跟踪 (Dropped Messages) - Router.route('/hooks/emails/dropped', {where: 'server'}).post(()-> - - data = this.request.body - mailGunSendHooks(emails,'dropped') - headers = { - 'Content-type': 'application/vnd.openxmlformats', - 'Content-Disposition': 'attachment; filename=' + title + '.xlsx' - } - - this.response.writeHead(200, headers) - this.response.end('{"result": "ok"}\n') - ) - - # 硬/软 退信跟踪 (Hard Bounces) - Router.route('/hooks/emails/bounced', {where: 'server'}).post(()-> - data = this.request.body - type = data.event || 'bounced' - mailGunSendHooks(data.recipient,type) - headers = { - 'Content-type': 'application/vnd.openxmlformats', - 'Content-Disposition': 'attachment; filename=' + title + '.xlsx' - } - - this.response.writeHead(200, headers) - this.response.end('{"result": "ok"}\n') - ) - - # 垃圾邮件跟踪 (Spam Complaints) - Router.route('/hooks/emails/complained', {where: 'server'}).post(()-> - data = this.request.body - type = data.event || 'complained' - mailGunSendHooks(data.recipient,type) - headers = { - 'Content-type': 'application/vnd.openxmlformats', - 'Content-Disposition': 'attachment; filename=' + title + '.xlsx' - } - - this.response.writeHead(200, headers) - this.response.end('{"result": "ok"}\n') - ) - - # 取消订阅跟踪 (Unsubscribes) - Router.route('/hooks/emails/unsubscribe', {where: 'server'}).post(()-> - data = this.request.body - type = data.event || 'unsubscribe' - mailGunSendHooks(data.recipient,type) - headers = { - 'Content-type': 'application/vnd.openxmlformats', - 'Content-Disposition': 'attachment; filename=' + title + '.xlsx' - } - - this.response.writeHead(200, headers) - this.response.end('{"result": "ok"}\n') - ) - #陌生人图片信息 - Router.route('/restapi/workai_autolabel/single', {where: 'server'}).post(()-> - #console.log('single labelling',this.request.body) - if this.request.body.hasOwnProperty('imgs') - imgs = this.request.body.imgs - if this.request.body.hasOwnProperty('img_gif') - img_gif = this.request.body.img_gif - if this.request.body.hasOwnProperty('isStrange') - isStrange = this.request.body.isStrange - if this.request.body.hasOwnProperty('createTime') - createTime = this.request.body.createTime - if this.request.body.hasOwnProperty('group_id') - group_id = this.request.body.group_id - if this.request.body.hasOwnProperty('camera_id') - cid = this.request.body.camera_id - if this.request.body.hasOwnProperty('uuid') - uuid = this.request.body.uuid - if this.request.body.hasOwnProperty('tid') - trackerId = this.request.body.tid - unless imgs and group_id and uuid - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - #console.log('username',uuid) - user = Meteor.users.findOne({username: uuid}) - unless user - console.log("restapi/workai_autolabel/single: user is null") - return this.response.end('{"result": "failed!", "cause": "user is null."}\n') - - #console.log("to label single on device",user) - userGroups = SimpleChat.GroupUsers.find({user_id: user._id}) - unless userGroups - console.log("restapi/workai_autolabel/single: userGroups is null") - return this.response.end('{"result": "failed!", "cause":"userGroups is null."}\n') - - faceId = new Mongo.ObjectID()._str - - userGroups.forEach((userGroup)-> - person_name = 'Guest_' + new Mongo.ObjectID()._str; - for img in imgs - PERSON.setName( - userGroup.group_id, - uuid, - faceId, - img, - person_name - ) - LABLE_DADASET_Handle.insert({ - group_id: userGroup.group_id, - uuid: uuid, - id: faceId, - url: img, - name: person_name, - sqlid: 0, - style: 'front', - action: 'Stranger', - faceId: faceId, - }) - #insert_msg2(faceId, img.url, uuid, img.img_type, img.accuracy, img.fuzziness, img.sqlid, img.style, null, null, trackerId) - ) - - #console.log(Strangers.find({}).fetch()) - this.response.end('{"result": "ok"}\n') - ) - - #陌生人图片信息 - Router.route('/restapi/updateStrangers', {where: 'server'}).post(()-> - if this.request.body.hasOwnProperty('imgs') - imgs = this.request.body.imgs - if this.request.body.hasOwnProperty('img_gif') - img_gif = this.request.body.img_gif - if this.request.body.hasOwnProperty('isStrange') - isStrange = this.request.body.isStrange - if this.request.body.hasOwnProperty('createTime') - createTime = this.request.body.createTime - if this.request.body.hasOwnProperty('group_id') - group_id = this.request.body.group_id - if this.request.body.hasOwnProperty('camera_id') - cid = this.request.body.camera_id - if this.request.body.hasOwnProperty('uuid') - uuid = this.request.body.uuid - if this.request.body.hasOwnProperty('tid') - trackerId = this.request.body.tid - - unless imgs and img_gif and group_id and uuid - return this.response.end('{"result": "failed", "cause": "invalid params"}\n') - - Strangers.insert({ - imgs: imgs, - img_gif: img_gif, - group_id: group_id, - camera_id: cid, - uuid: uuid, - trackerId: trackerId, - isStrange: isStrange, - createTime: new Date(), - avatar: imgs[0].url - }) - - user = Meteor.users.findOne({username: uuid}) - unless user - console.log("restapi/updateStrangers: user is null") - return this.response.end('{"result": "failed!", "cause": "user is null."}\n') - - userGroups = SimpleChat.GroupUsers.find({user_id: user._id}) - unless userGroups - console.log("restapi/updateStrangers: userGroups is null") - return this.response.end('{"result": "failed!", "cause":"userGroups is null."}\n') - - faceId = new Mongo.ObjectID()._str - - userGroups.forEach((userGroup)-> - person_name = 'Guest_' + new Mongo.ObjectID()._str; - for img in imgs - PERSON.setName( - userGroup.group_id, - uuid, - faceId, - img.url, - person_name - ) - LABLE_DADASET_Handle.insert({ - group_id: userGroup.group_id, - uuid: uuid, - id: faceId, - url: img.url, - name: person_name, - sqlid: img.sqlid, - style: img.style, - action: 'Stranger', - faceId: faceId, - }) - - insert_msg2(faceId, img.url, uuid, img.img_type, img.accuracy, img.fuzziness, img.sqlid, img.style, null, null, trackerId) - ) - - #console.log(Strangers.find({}).fetch()) - this.response.end('{"result": "ok"}\n') - ) diff --git a/server/auto_send_task_tip.js b/server/auto_send_task_tip.js deleted file mode 100644 index dc79fc59c..000000000 --- a/server/auto_send_task_tip.js +++ /dev/null @@ -1,88 +0,0 @@ -var timeouts = {}; -var addWhatsUpTip = function(userId, deviceId, groupId){ - if (!timeouts['_' + userId]) - timeouts['_' + userId] = {userId: userId, deviceId: deviceId, groupId: groupId, time: new Date().getTime(), count: 0}; - else - timeouts['_' + userId].time = new Date().getTime(); -}; -var removeWhatsUpTip = function(userId){ - eval('delete timeouts._' + userId); -}; -var sendPushMsg = function(userId, deviceId){ - var user = Meteor.users.findOne({_id: userId}); - var device = Meteor.users.findOne({username: deviceId}); - - if (device && user){ - sendMqttMessage('/msg/u/' + userId, { - _id: new Mongo.ObjectID()._str, - form:{ - id: device._id, - name: device.profile.fullname || device.username, - icon: device.profile.icon - }, - to: { - id: userId, - name: user.profile.fullname || user.username, - icon: user.profile.icon || '', - }, - to_type: "user", - type: "text", - text: "您今天计划做什么?", - is_read: false - }); - console.log('今天计划做什么?', user.profile.fullname || user.username); - } -} -CreateSatsUpTipTask = function(userId, groupId, deviceId){ - var group = SimpleChat.Groups.findOne({_id: groupId}); - if (group && group.whats_up_send) - return addWhatsUpTip(userId, deviceId, groupId); - removeWhatsUpTip(userId); -}; - -Meteor.startup(function(){ - var upsetWhatsUpTip = function(doc){ - if (!doc.whats_up && doc.in_time != 0){ - var group = SimpleChat.Groups.findOne({_id: doc.group_id}); - if (group && group.whats_up_send) - return addWhatsUpTip(doc.app_user_id, doc.in_uuid, doc.group_id); - } - removeWhatsUpTip(doc.app_user_id); - }; - // WorkStatus.after.insert(function (userId, doc) { - // upsetWhatsUpTip(doc); - // }); - // WorkStatus.after.update(function (userId, doc, fieldNames, modifier, options) { - // if (modifier['$set'].status === 'in'){ - // upsetWhatsUpTip(doc); - // } - // }); - - Meteor.setInterval(function(){ - for(var key in timeouts){ - if (timeouts[key].count >= 8) - continue; - - var now = new Date().getTime(); - if (now - timeouts[key].time >= 1000*60*30){ - var group = SimpleChat.Groups.findOne({_id: timeouts[key].groupId}); - var workStatus = WorkStatus.findOne({app_user_id: timeouts[key].userId, group_id: timeouts[key].groupId}, {sort: {date: -1}}); - if (group && group.whats_up_send && workStatus && !workStatus.whats_up){ - timeouts[key].count += 1; - timeouts[key].time = now; - sendPushMsg(timeouts[key].userId, timeouts[key].deviceId); - } - } - } - }, 1000*60); - - // test - // var workStatus = WorkStatus.findOne({app_user_id: 'L3mAjMWmxd9MfTFAF', group_id: 'd2bc4601dfc593888618e98f'}, {sort: {date: -1}}); - // WorkStatus.update({_id: workStatus._id}, {$set: {status: 'in'}}); -}); - -Meteor.methods({ - 'uGroupWhatsUp': function(id, value){ - return SimpleChat.Groups.update({_id: id}, {$set: {whats_up_send: value}}) - } -}); \ No newline at end of file diff --git a/server/daily_report.js b/server/daily_report.js deleted file mode 100644 index 869b48244..000000000 --- a/server/daily_report.js +++ /dev/null @@ -1,203 +0,0 @@ -if(Meteor.isServer){ - Meteor.startup(function(){ - //For local test purpose. also change the last line in Meteor.startup() to send email - //process.env.MAIL_URL = 'smtp://postmaster%40tiegushi.com:a7e104e236965118d8f1bd3268f36d8c@smtp.mailgun.org:587' - String.prototype.replaceAll = function(s1,s2) { - return this.replace(new RegExp(s1,"gm"),s2); - }; - - function calcTimeStamp23() { - var now = new Date(); - var millisTill23 = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 0, 0, 0) - now; - if (millisTill23 < 0) { - millisTill23 += 86400000; // it's after 10am, try 10am tomorrow. - } - console.log('millisTill23:', millisTill23); - return millisTill23; - } - - function sendGroupJobReport(group_user, emails) { - var group = SimpleChat.Groups.findOne({_id: group_user.group_id}); - if (!group) - return; - var group_id = group._id; - var time_offset = 8; - if (group && group.offsetTimeZone) { - time_offset = group.offsetTimeZone; - } - var now = DateTimezone(time_offset); - var date = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate(), 0, 0, 0, 0); - - var job_report = Assets.getText('email/job-report.html'); - job_report = job_report.replaceAll('{{company_name}}', group.name); - job_report = job_report.replaceAll('{{job_date}}', new Date(date).toISOString().split('T')[0]); - - var subject = "每日出现报告"; - var to = group.report_emails; - if (emails){ - to = emails; - } - - var job_content = ''; - - var checkin_content = '', - uncheckin_content = '', - checkin_count = 0, - uncheckin_count = 0; - - var check_names = []; - - var workStatus = WorkStatus.find({group_id: group_id, date: date}); - console.log('==sr==. date is '+date+' and time is '+new Date(date) +' and local time is '+ DateTimezone(time_offset)); - if (workStatus) { - workStatus.forEach(function(ws) { - var pContentCheck = Assets.getText('email/job-checkin-item.html'); - - var in_time = ws.in_time ? ws.in_time : ws.out_time; - var in_img = ws.in_image ? ws.in_image: ws.out_image; - if (in_time && in_img) { - var strInTime = new Date(in_time); - strInTime = strInTime.shortTime(time_offset, true); - - pContentCheck = pContentCheck.replaceAll('{{person_in_time}}', strInTime); - pContentCheck = pContentCheck.replace('{{person_name}}', ws.person_name); - pContentCheck = pContentCheck.replace('{{person_in_image}}', in_img); - - checkin_count += 1; - checkin_content += pContentCheck; - check_names.push(ws.person_name); - } - }); - } - - var persons = Person.find({group_id:group_id, name:{$nin: check_names}}); - persons.forEach( function (person) { - var pContentUnCheck = Assets.getText('email/job-uncheckin-item.html'); - if(person && person.url) { - pContentUnCheck = pContentUnCheck.replace('{{person_in_image}}', person.url); - pContentUnCheck = pContentUnCheck.replace('{{person_name}}', person.name); - uncheckin_count += 1; - uncheckin_content += pContentUnCheck; - } - }); - - job_report = job_report.replace('{{job_checkin_content}}', checkin_content); - job_report = job_report.replace('{{job_uncheckin_content}}', uncheckin_content); - job_report = job_report.replace('{{checkin_member_count}}', checkin_count); - job_report = job_report.replace('{{uncheckin_member_count}}', uncheckin_count); - - - try { - Email.send({ - to: to, - from: '来了吗 ', - subject: subject, - html: job_report, - envelope: { - from: "来了吗 ", - to: to + "<" + to + ">" - } - }); - - // console.log('send mail to:', notifyUser.userEmail); - } catch (_error) { - ex = _error; - //console.log("err is: ", ex); - console.log("Exception: sendEmail: error=%s, to=%s", ex, to); - } - } - - function delay3HourThenScheduleAgain() { - console.log('delay3HourThenScheduleAgain'); - Meteor.setTimeout(sendJobReport, calcTimeStamp23()); - } - - function DateTimezone(offset) { - var d = new Date(); - var utc = d.getTime() + (d.getTimezoneOffset() * 60000); - var local_now = new Date(utc + (3600000*offset)) - - return local_now; - } - - function sendJobReport() { - console.log("send email out"); - try { - var groups_users = SimpleChat.GroupUsers.find({report_emails: {$exists: true}}); - groups_users.forEach(function(group_user) { - var group = SimpleChat.Groups.findOne({_id: group_user.group_id}); - if (!group || !group_user.report_emails) - return; - var time_offset = 8; - if (group && group.offsetTimeZone) { - time_offset = group.offsetTimeZone; - } - var local_time = DateTimezone(time_offset); - console.log('Sending time email'+local_time.getHours()) - if(local_time.getHours() == 19) { // 群组本地时间 12 点 发送 - console.log('sendJobReport, and group_id is '+group._id+', and current timeOffsetZone is '+time_offset); - console.log(group._id, group_user.report_emails); - sendGroupJobReport(group_user, group_user.report_emails); - } - - }); - } - catch(ex) { - console.log("exception in sendJobReport", ex); - } - - //Meteor.setTimeout(delay3HourThenScheduleAgain, 3*60*60*1000); - } - - //Meteor.setTimeout(sendJobReport, calcTimeStamp23()); - // 邮件发送 - SyncedCron.add({ - name: 'send report email 12:00 am every day', - schedule: function(parser){ - // parser is later.parse pbject - // UTC -7 的12:00 am 也就是 UTC 8 下一天的 03:00 am - // 每小时执行一次 - return parser.text('every 1 hour'); - }, - job: function(){ - sendJobReport(); - return 1; - } - }); - - SyncedCron.start(); - - Meteor.methods({ - // for local test method - 'testGroupDailyReport': function (group_id, emails) { - var group = SimpleChat.Groups.findOne({_id: group_id}); - console.log(group._id, emails); - console.log(group._id, group.report_emails); - sendGroupJobReport(group, emails); - } - }); - - // Meteor.methods({ - // // for local test method - // 'testGroupDailyReport': function (group_id, emails) {SimpleChat.GroupUsers - // var group = SimpleChat.GroupUsers.findOne({group_id: group_id}); - // console.log(group._id, emails); - // console.log(group._id, group.report_emails); - // sendGroupJobReport(group, emails); - // } - // }); - //sendJobReport(); - - // function sendTestEmail() { - // var group = SimpleChat.Groups.findOne({_id: 'a5119193a661db15fc425f6c'}); - // console.log('send test emails'); - // console.log('group_id:', group._id, group.report_emails); - // sendGroupJobReport(group, 'zezhang@actiontec.com'); - // } - // sendTestEmail(); - - - - - }); -} diff --git a/server/ensure_index.coffee b/server/ensure_index.coffee deleted file mode 100644 index 38ddb7f66..000000000 --- a/server/ensure_index.coffee +++ /dev/null @@ -1,104 +0,0 @@ -if Meteor.isServer - Meteor.startup ()-> - Moments._ensureIndex({currentPostId:1}) - Moments._ensureIndex({currentPostId:1, readPostId:1}) - Moments._ensureIndex({currentPostId:1, createdAt: -1}) - Moments._ensureIndex({currentPostId:1, userId: 1}) - Moments._ensureIndex({currentPostId:1, userId:1, createdAt: -1}) - Moments._ensureIndex({readPostId:1}) - Viewers._ensureIndex({userId: 1, createdAt: -1}) - Viewers._ensureIndex({userId: 1}) - # {postId: 1, userId: 1, createdAt: -1} will create 3 indexs, - # {postId: 1},{postId: 1,userId: 1},{postId: 1, userId: 1, createdAt: -1} - #Viewers._ensureIndex({postId: 1}) - Viewers._ensureIndex({postId: 1,userId: 1}) - Viewers._ensureIndex({postId: 1, userId: 1, createdAt: -1}) - Viewers._ensureIndex({postId: 1, createdAt: -1}) - Follower._ensureIndex({userId: 1, followerId: 1, createAt:-1}) - Follower._ensureIndex({followerId: 1, createAt:-1}) - Follower._ensureIndex({userId: 1, createAt:-1}) - Follower._ensureIndex({userId: 1}) - Follower._ensureIndex({followerId: 1},{userEmail:1}) - Follows._ensureIndex({index: 1}) - TopicPosts._ensureIndex({postId: 1}) - TopicPosts._ensureIndex({postId: 1, owner: 1}) - ReComment._ensureIndex({postId: 1, commentUserId: 1}) - ReComment._ensureIndex({postId: 1}) - Meets._ensureIndex({me: 1, count: -1}) - Meets._ensureIndex({me: 1, createdAt: -1}) - #This one need to be deleted in mongodb. - #Meets._ensureIndex({me: 1, ta: 1, meetOnPostId: 1, count: -1, createdAt: -1}) - Meets._ensureIndex({me: 1, ta: 1}) - Meets._ensureIndex({me: 1, meetOnPostId: 1, createdAt: -1}) - Posts._ensureIndex({owner: 1, createdAt: -1}) - Posts._ensureIndex({owner: 1, publish: 1}) - Series._ensureIndex({owner: 1}) - #Posts._ensureIndex({title: 1, publish: 1}) - Posts._ensureIndex({owner: 1, publish: 1, createdAt: -1}) - Posts._ensureIndex({owner: 1, publish: 1, browse: -1}) - Posts._ensureIndex({createdAt: -1}) - Posts._ensureIndex({isReview:1,createdAt: -1}) - Posts._ensureIndex({hasPush: 1}) - RePosts._ensureIndex({createdAt: 1}) - FollowPosts._ensureIndex({followby: 1, createdAt: -1}) - FollowPosts._ensureIndex({followby: 1, postId: 1}) - FollowPosts._ensureIndex({postId: 1}) - FollowPosts._ensureIndex({owner: 1}) - FollowPosts._ensureIndex({owner: 1, followby: 1}) - SavedDrafts._ensureIndex({owner: 1, createdAt: -1}) - Feeds._ensureIndex({followby: 1, createdAt: -1}) - Feeds._ensureIndex({followby: 1, postId: 1, eventType: 1, recommanderId: 1, createdAt: -1}) - Comment._ensureIndex({postId: 1}) - Reports._ensureIndex({postId: 1}) - Feeds._ensureIndex({recommanderId: 1, recommander: 1, postId: 1, followby: 1}) - Feeds._ensureIndex({requesteeId: 1, requesterId: 1, followby: 1}) - Feeds._ensureIndex({owner:1,followby: 1, checked: 1, postId: 1, pindex: 1}) - Feeds._ensureIndex({postId:1,eventType: 1}) - Feeds._ensureIndex({followby:1,checked: 1}) - Feeds._ensureIndex({followby:1}) - Feeds._ensureIndex({followby:1,isRead:1}) - Feeds._ensureIndex({eventType:1}) - AssociatedUsers._ensureIndex({userIdA:1}) - AssociatedUsers._ensureIndex({userIdB:1}) - ReaderPopularPosts._ensureIndex({userId:1}) - FavouritePosts._ensureIndex({userId:1, createdAt:-1}) - # this one do not need index - # Topics.find({}) - # Topics._ensureIndex({text: 1, createdAt: -1}) - #RefComments._ensureIndex({text: 1}) - PushSendLogs._ensureIndex({createAt:-1}) - UserRelation._ensureIndex({userId:1}) - UserRelation._ensureIndex({toUserId:1}) - UserRelation._ensureIndex({userId:1, toUserId: 1}) - Recommends._ensureIndex({relatedUserId: 1}) - Recommends._ensureIndex({relatedPostId: 1}) - SeriesFollow._ensureIndex({owner: 1, seriesId: 1}) - WorkAIUserRelations._ensureIndex({app_user_id: 1}) - WorkAIUserRelations._ensureIndex({group_id: 1}) - WorkAIUserRelations._ensureIndex({'ai_persons.id': 1}) - DeviceTimeLine._ensureIndex({uuid: 1}) - DeviceTimeLine._ensureIndex({uuid: 1, group_id: 1}) - DeviceTimeLine._ensureIndex({hour: 1}) - DeviceTimeLine._ensureIndex({uuid: 1, group_id: 1,hour:-1}) - ModelParam._ensureIndex({groupid: 1, uuid: 1}) - WorkStatus._ensureIndex({in_image:1,out_image:1}) - WorkStatus._ensureIndex({date: 1,group_id:1}) - WorkStatus._ensureIndex({date: 1,group_id:1,status:1}) - WorkStatus._ensureIndex({group_id: 1, app_user_id: 1, date: 1}) - WorkStatus._ensureIndex({group_id: 1, person_name: 1, date: 1}) - WorkStatus._ensureIndex({app_user_id: 1}) - - ClusterWorkStatus._ensureIndex({date: 1,group_id:1}) - Person._ensureIndex({group_id: 1,'faces.id':1,createAt:1}) - Person._ensureIndex({group_id: 1, name: 1,createAt: 1}) - Person._ensureIndex({group_id: 1, createAt: 1}) - Person._ensureIndex({group_id: 1, faceId: 1}) - ClusterPerson._ensureIndex({group_id: 1,createAt: -1}); - Devices._ensureIndex({uuid:1}) - Devices._ensureIndex({groupId:1}) - LableDadaSet._ensureIndex({group_id: 1,name:1,createAt: -1}) - LableDadaSet._ensureIndex({createAt: -1}) - ClusterLableDadaSet._ensureIndex({group_id: 1,name:1,createAt: -1}) - Strangers._ensureIndex({group_id: 1,createTime: -1}) - Strangers._ensureIndex({createTime: -1}) - People._ensureIndex({updateTime: -1}) diff --git a/server/faces_server.js b/server/faces_server.js deleted file mode 100644 index fcee42cce..000000000 --- a/server/faces_server.js +++ /dev/null @@ -1,49 +0,0 @@ - -var generatePersonName = function (group_id) { - var count = PersonNames.find({group_id: group_id}).count(); - count += 1; - var person_name = 'Person '+count; - return person_name; -} - -Meteor.methods({ - 'faceLabelAsUnknown': function(_id){ - return Faces.remove({_id: _id}); - }, - 'faceLabelAsPerson': function(face){ - // Step 1. generate a person name - var name = generatePersonName(face.group_id); - // Step 2. add to group person name - PersonNames.insert({ - group_id: face.group_id, - uuid: face.uuid, - url: face.img_url, - id: face.id, - name: name, - createAt: new Date() - }); - // Step 3. add to group person - var device = Devices.findOne({uuid: face.uuid}); - Person.insert({ - id: 1, - group_id: face.group_id, - uuid: face.uuid, - faceId: face.id, - url: face.img_url, - name: name, - faces: [ - { - id: face.id, - url: face.img_url - } - ], - deviceId: device._id, - DeviceName: device.name, - createAt: new Date(), - updateAt: new Date(), - imgCount: 1 - }); - // Step 4. remove the face - Faces.remove({_id: face._id}); - } -}); \ No newline at end of file diff --git a/server/labelDataSet.js b/server/labelDataSet.js deleted file mode 100644 index 3a47f7bb0..000000000 --- a/server/labelDataSet.js +++ /dev/null @@ -1,325 +0,0 @@ -LABLE_DADASET_Handle = { - insert: function(doc) { - console.log('LableDadaSet insert with :' + JSON.stringify(doc)); - if (!doc || !doc.group_id || !doc.id || !doc.url || !doc.name) { - return; - } - var group_id = doc.group_id; - var id = doc.id; - var url = doc.url; - var name = doc.name; - var dataset = LableDadaSet.findOne({group_id:group_id,url:url}); - if (!dataset) { - var datasetObj = { - group_id:group_id, - id:id, - url:url, - name:name, - uuid:doc.uuid, - sqlid:doc.sqlid, - style: doc.style, - createAt:new Date(), - }; - if (doc.user_id) { - var user = Meteor.users.findOne({_id:doc.user_id}); - if (user) { - var operator = { - user_id:user._id, - user_name:user.profile && user.profile.fullname ? user.profile.fullname : user.username, - ts:new Date(), - label_name:name, //当前操作的人标记的名字 - action:doc.action - }; - datasetObj.operator = [operator]; - } - } - console.log('LableDadaSet is:', JSON.stringify(datasetObj)); - LableDadaSet.insert(datasetObj); - if (doc.faceId) { - trainsetObj = { - group_id: group_id, - type: 'trainset', - url: doc.url, - device_id: doc.uuid, - face_id: doc.faceId, - drop: false, - img_type: 'face', - style:doc.style, - sqlid:doc.sqlid - }; - console.log("LABLE_DADASET_Handle.insert: " + JSON.stringify(trainsetObj)); - sendMqttMessage('/device/'+group_id, trainsetObj); - } - } - else{ - LABLE_DADASET_Handle.update(doc); - } - Person.update({group_id:group_id, name: name}, { - $inc: {imgCount: 1} - }); - SimpleChat.Groups.update({_id: group_id},{$set:{last_time: new Date()}}, function(err,result) { - if (!err) - console.log("Update last_time in group suc: "+group_id) - else - console.log("Update last_time in group failed: "+group_id) - }); - }, - update:function(doc){ - console.log('LableDadaSet update with :' + JSON.stringify(doc)); - if (!doc || !doc.group_id || !doc.id || !doc.url) { - return; - } - var group_id = doc.group_id; - var id = doc.id; - var url = doc.url; - var dataset = LableDadaSet.findOne({group_id:group_id,url:url}); - if (!dataset) { - return; - } - var updateObj = {id:id}; - if (doc.name) { - updateObj.name = doc.name; - } - if (doc.user_id) { - var user = Meteor.users.findOne({_id:doc.user_id}); - if (user) { - var operator = { - user_id:user._id, - user_name:user.profile && user.profile.fullname ? user.profile.fullname : user.username, - ts:new Date(), - label_name:doc.name, - action:doc.action - }; - LableDadaSet.update({_id:dataset._id},{$set:updateObj,$push:{operator:operator}}); - } - } - else{ - LableDadaSet.update({_id:dataset._id},{$set:updateObj}); - } - console.log('wait update dataset: '+JSON.stringify(dataset)); - //标错后重标会存在id变化的情况 - if (dataset.id != id) { - //更新旧的id存在的person表 - doc.id = dataset.id; - LABLE_DADASET_Handle.updatePerson(doc); - } - // 同一张未识别的图片,两个人标记时选择了不同的人 - if (dataset.name != doc.name) { - doc.id = dataset.id; - doc.name = dataset.name; - LABLE_DADASET_Handle.updatePersonWithName(doc); - } - SimpleChat.Groups.update({_id: group_id},{$set:{last_time: new Date()}}, function(err,result){ - if (!err) - console.log("Update last_time in group suc: "+group_id) - else - console.log("Update last_time in group failed: "+group_id) - }); - }, - remove:function(doc){ - console.log('LableDadaSet remove with :' + JSON.stringify(doc)); - if (!doc || !doc.group_id || !doc.id || !doc.url) { - return; - } - var dataset = LableDadaSet.findOne({group_id:doc.group_id,url:doc.url}); - if (!dataset) { - return; - } - LableDadaSet.remove({group_id:doc.group_id,url:doc.url}); - LABLE_DADASET_Handle.updatePerson(doc); - if (dataset.faceId) { - trainsetObj = { - group_id: doc.group_id, - type: 'trainset', - url: doc.url, - device_id: doc.uuid, - face_id: dataset.faceId, - drop: true, - img_type: 'face', - style:dataset.style, - sqlid:dataset.sqlid - }; - console.log("LABLE_DADASET_Handle.remove: " + JSON.stringify(trainsetObj)); - sendMqttMessage('/device/'+doc.group_id, trainsetObj); - } - Person.update({group_id:doc.group_id, name: doc.name}, { - $inc: {imgCount: -1} - }); - var group_id = doc.group_id; - SimpleChat.Groups.update({_id: group_id},{$set:{last_time: new Date()}}, function(err,result) { - if (!err) - console.log("Update last_time in group suc: "+group_id) - else - console.log("Update last_time in group failed: "+group_id) - }); - }, - removePerson: function(doc){ - if (!doc || !doc.group_id || !doc.faceId || !doc.name) { - return; - } - var dataset = LableDadaSet.findOne({group_id:doc.group_id, name:doc.name}); - if (!dataset) { - return; - } - LableDadaSet.remove({group_id:doc.group_id, name: person.name}); - trainsetObj = { - group_id: doc.group_id, - face_id: doc.faceId, - drop: true, - drop_person:true - }; - console.log("LABLE_DADASET_Handle.remove: " + JSON.stringify(trainsetObj)); - sendMqttMessage('/device/'+doc.group_id, trainsetObj); - SimpleChat.Groups.update({_id: doc.group_id},{$set:{last_time: new Date()}}, function(err,result) { - if (!err) - console.log("Update last_time in group suc: "+group_id) - else - console.log("Update last_time in group failed: "+group_id) - }); - }, - updatePersonWithName:function(doc){ - console.log('try updatePersonWithName:'+JSON.stringify(doc)); - if (!doc || !doc.group_id || !doc.id || !doc.url || !doc.name) { - return; - } - var group_id = doc.group_id; - //如果此图片存在于person表中,需要替换 - var person = Person.findOne({group_id:group_id ,'name': doc.name}, {sort: {createAt: 1}}); - if (!person) { - return; - } - var url = doc.url; - - var index = _.pluck(person.faces, 'url').indexOf(url); - if (person.url == url || person.faceId == doc.id) {} - if(index != -1 || person.url == url || person.faceId == doc.id){ - if (index != -1 && person.faces[index].id === doc.id) { - person.faces.splice(_.pluck(person.faces, 'id').indexOf(doc.id), 1); - if (person.faces.length == 0) { - // 这里同时移除相应的personNames 记录 - var personName = PersonNames.findOne({group_id: group_id,id:doc.id, name:person.name}); - if(personName){ - PersonNames.remove({_id: personName._id}); - } - WorkAIUserRelations.remove({'ai_persons.id': person._id}); - WorkStatus.remove({'person_id.id': person._id}); - return Person.remove({_id:person._id}); - } - } - else if (index != -1) { - var newdataset = LableDadaSet.findOne({id:person.faces[index].id},{sort: {createAt: -1}}); - if (newdataset) { - person.faces[index].url = newdataset.url; - } - } - if (person.faces && person.faces.length > 0){ - person.faceId = person.faces[0].id; - person.url = person.faces[0].url; - } - Person.update({_id:person._id},{$set:{faceId:person.faceId,url:person.url,faces:person.faces}}); - } - }, - updatePerson:function(doc){ - console.log('try updatePerson:'+JSON.stringify(doc)); - if (!doc || !doc.group_id || !doc.id || !doc.url) { - return; - } - - var group_id = doc.group_id; - var id = doc.id; - var url = doc.url; - - //如果此图片存在于person表中,需要替换 - var person = Person.findOne({group_id:group_id ,'faces.id': id}, {sort: {createAt: 1}}); - if (!person) { - return; - } - var facesurl = person.faces[_.pluck(person.faces, 'id').indexOf(id)].url; - if (person.url == url || facesurl == url) { - // if(_.pluck(person.faces, 'id').indexOf(id) === -1){ - // person.faces.push({id: id, url: url}); - // } - // else{ - // person.faces[_.pluck(person.faces, 'id').indexOf(id)].url = url; - // } - var newdataset = LableDadaSet.findOne({id:id},{sort: {createAt: -1}}); - if (newdataset) { - if (person.url == url) { - person.url = newdataset.url; - } - if (facesurl == url) { - person.faces[_.pluck(person.faces, 'id').indexOf(id)].url = newdataset.url; - } - person.updateAt = new Date(); - Person.update({_id: person._id}, {$set: {url: person.url, updateAt: person.updateAt, faces: person.faces}}); - } - else{ - //数据集中没有此id更多的图片了 - person.faces.splice(_.pluck(person.faces, 'id').indexOf(id), 1); - if (person.faceId == id) { - if (person.faces.length == 0) { - // 这里同时移除相应的personNames 记录 - var personName = PersonNames.findOne({group_id: group_id,id:id, name:person.name}); - if(personName){ - PersonNames.remove({_id: personName._id}); - } - WorkAIUserRelations.remove({'ai_persons.id': person._id}); - WorkStatus.remove({'person_id.id': person._id}); - return Person.remove({_id:person._id}); - } - else{ - person.faceId = person.faces[0].id; - person.url = person.faces[0].url; - } - } - Person.update({_id:person._id},{$set:{faceId:person.faceId,url:person.url,faces:person.faces}}); - } - } - }, - /*initLableDataSet:function(){ - Person.find({},{sort: {updateAt:1}}).forEach(function(fields){ - if (fields.faces && fields.faces.length > 0) { - for (var i = 0; i < fields.faces.length; i++) { - LABLE_DADASET_Handle.insert({group_id:fields.group_id,name:fields.name,id:fields.faces[i].id,url:fields.faces[i].url,action:'Person表数据移植',user_id:'4jQXwnAuLnHcYJxwJ'}); - } - } - }); - },*/ - - // only local - updatePersonImgCount: function () { - Person.find({},{sort: {updateAt:1}}).forEach(function(fields){ - if(fields.group_id && fields.name){ - var counts = LableDadaSet.find({group_id: fields.group_id, name: fields.name}).count(); - Person.update({_id: fields._id}, { - $set: {imgCount: counts} - }); - } - }); - } -}; - -if (Meteor.isServer) { - function updateImgCount(group_id, name) { - Meteor.setTimeout(function(){ - var cnt = LableDadaSet.find({group_id: group_id, name: name}).count(); - Person.update({group_id: group_id, name: name}, {$set: {imgCount: cnt}}, function(err, ret){ - }); - }, 2000); - }; - - Meteor.startup(function(){ - LableDadaSet.find({createAt: {$gt: new Date()}}).observe({ - added(doc) { - if (doc.group_id && doc.name) { - updateImgCount(doc.group_id, doc.name); - } - }, - removed(doc) { - if (doc.group_id && doc.name) { - updateImgCount(doc.group_id, doc.name); - } - } - }); - }); -} diff --git a/server/lib/request-ip.js b/server/lib/request-ip.js deleted file mode 100644 index 038babef1..000000000 --- a/server/lib/request-ip.js +++ /dev/null @@ -1,128 +0,0 @@ -'use strict'; - -/** - * Get client IP address - * - * Will return 127.0.0.1 when testing locally - * Useful when you need the user ip for geolocation or serving localized content - * - * @method getClientIp - * @param req - * @returns {string} ip - */ -function getClientIp(req) { - - // the ipAddress we return - var ipAddress; - - // workaround to get real client IP - // most likely because our app will be behind a [reverse] proxy or load balancer - var clientIp = req.headers['x-client-ip']; - var forwardedForAlt = req.headers['x-forwarded-for']; - var realIp = req.headers['x-real-ip']; - - // more obsure ones below - var clusterClientIp = req.headers['x-cluster-client-ip']; - var forwardedAlt = req.headers['x-forwarded']; - var forwardedFor = req.headers['forwarded-for']; - var forwarded = req.headers['forwarded']; - - // remote address check - var reqConnectionRemoteAddress = req.connection ? req.connection.remoteAddress : null; - var reqSocketRemoteAddress = req.socket ? req.socket.remoteAddress : null; - var reqConnectionSocketRemoteAddress = (req.connection && req.connection.socket) ? req.connection.socket.remoteAddress : null; - var reqInfoRemoteAddress = req.info ? req.info.remoteAddress : null; - - // x-client-ip - if (clientIp) { - ipAddress = clientIp; - } - - // x-forwarded-for - else if (forwardedForAlt) { - // x-forwarded-for header is more common - // it may return multiple IP addresses in the format: - // "client IP, proxy 1 IP, proxy 2 IP" - // we pick the first one - var forwardedIps = forwardedForAlt.split(','); - ipAddress = forwardedIps[0]; - } - - // x-real-ip - // (default nginx proxy/fcgi) - else if (realIp) { - // alternative to x-forwarded-for - // used by some proxies - ipAddress = realIp; - } - - // x-cluster-client-ip - // (Rackspace LB and Riverbed's Stingray) - // http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address - // https://splash.riverbed.com/docs/DOC-1926 - else if (clusterClientIp) { - ipAddress = clusterClientIp; - } - - // x-forwarded - else if (forwardedAlt) { - ipAddress = forwardedAlt; - } - - // forwarded-for - else if (forwardedFor) { - ipAddress = forwardedFor; - } - - // forwarded - else if (forwarded) { - ipAddress = forwarded; - } - - // remote address checks - else if (reqConnectionRemoteAddress) { - ipAddress = reqConnectionRemoteAddress; - } - else if (reqSocketRemoteAddress) { - ipAddress = reqSocketRemoteAddress - } - else if (reqConnectionSocketRemoteAddress) { - ipAddress = reqConnectionSocketRemoteAddress - } - else if (reqInfoRemoteAddress) { - ipAddress = reqInfoRemoteAddress - } - - // return null if we cannot find an address - else { - ipAddress = null; - } - - return ipAddress; -} - -function mw(options) { - if (!options) options = {}; - var attr = options.attributeName || "clientIp"; - return function(req, res, next) { - req[attr] = getClientIp(req); - next(); - } -}; - -if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { - /** - * Expose mode public functions - */ - module.exports = getClientIp; - /** - * Expose a default implemtation for a connect middleware - * - * @options.attributeName - name of attribute to augment request object with - */ - module.exports = mw; -} -else { - this.getClientIp = getClientIp; - this.mw = mw; -} diff --git a/server/methods.js b/server/methods.js deleted file mode 100644 index 961a8410a..000000000 --- a/server/methods.js +++ /dev/null @@ -1,132 +0,0 @@ -Meteor.methods({ - //修改设备名 - change_device_name:function(deviceId,uuid,groupId,newName){ - //1.修改设备名 - //2.groupuser里的设备用户名字修改 - //3.user里对应名字修改 - Devices.update({_id:deviceId},{ - $set:{ - name:newName - } - }); - Meteor.users.update({username:uuid},{$set:{'profile.fullname':newName}}); - var user = Meteor.users.findOne({username:uuid}); - SimpleChat.GroupUsers.update({group_id:groupId,user_id:user._id},{$set:{user_name:newName}}); - return; - }, - //删除设备 - delete_device:function(deviceId,uuid,groupId){ - //1.从device里删除 - Devices.remove({_id:deviceId}); - //2.从GroupUsers里删除 - var user = Meteor.users.findOne({username:uuid}); - SimpleChat.GroupUsers.remove({group_id:groupId,user_id:user._id}); - //3.从users里移除 - Meteor.users.remove({username:uuid}); - }, - update_install_status:function(group_id,install_status){ - console.log(install_status); - SimpleChat.Groups.update({_id:group_id},{$set:install_status}); - }, - //自动标记 - autolabel:function(condition,uuid){ - console.log('autolabel',condition); - console.log('autolabel',uuid); - var device = Devices.findOne({uuid:uuid}); - var group_id = device.groupId; - var name = condition.person_name; - var selector = { - group_id:group_id, - uuid:uuid - }; - selector['hour'] = condition.start.hour; - var dt = DeviceTimeLine.findOne(selector); - var faceList = []; - if(condition.start.hour.getTime() == condition.end.hour.getTime()){ - for(var i=condition.start.min;i 0){ - console.log('Camera connected') - Devices.update({uuid: info.clientID},{$set:{camera_run:true}}) - } else if(info.total_tasks===0){ - console.log('Camera disconnected') - Devices.update({uuid: info.clientID},{$set:{camera_run:false}}) - } - - if(info && info.version) { - if(info.version.islatest) { - Devices.update({uuid: info.clientID},{$set:{islatest:true}}) - } - else { - Devices.update({uuid: info.clientID},{$set:{islatest:false}}) - } - } - } - }) -}) diff --git a/server/offline_notify/ensure_index.js b/server/offline_notify/ensure_index.js deleted file mode 100644 index 9db60c657..000000000 --- a/server/offline_notify/ensure_index.js +++ /dev/null @@ -1,10 +0,0 @@ -if(Meteor.isServer){ - Meteor.startup(function () { - peerCollection._ensureIndex({clientID:1,updateBy:-1}); - Commands._ensureIndex({clientID:1,done:1,updateBy:-1}); - offlineJobs._ensureIndex({'data.clientId':1}); - offlineJobs._ensureIndex({'data.clientId':1,status:1}); - offlineJobs._ensureIndex({status:1,expiresAfter:1}); - offlineJobs._ensureIndex({status:1,after:1}); - }); -} diff --git a/server/offline_notify/getClientIp.js b/server/offline_notify/getClientIp.js deleted file mode 100644 index bc44e1c81..000000000 --- a/server/offline_notify/getClientIp.js +++ /dev/null @@ -1,90 +0,0 @@ -getClientIp = function (req) { - - // the ipAddress we return - var ipAddress; - - // workaround to get real client IP - // most likely because our app will be behind a [reverse] proxy or load balancer - var clientIp = req.headers['x-client-ip']; - var forwardedForAlt = req.headers['x-forwarded-for']; - var realIp = req.headers['x-real-ip']; - - // more obsure ones below - var clusterClientIp = req.headers['x-cluster-client-ip']; - var forwardedAlt = req.headers['x-forwarded']; - var forwardedFor = req.headers['forwarded-for']; - var forwarded = req.headers['forwarded']; - - // remote address check - var reqConnectionRemoteAddress = req.connection ? req.connection.remoteAddress : null; - var reqSocketRemoteAddress = req.socket ? req.socket.remoteAddress : null; - var reqConnectionSocketRemoteAddress = (req.connection && req.connection.socket) ? req.connection.socket.remoteAddress : null; - var reqInfoRemoteAddress = req.info ? req.info.remoteAddress : null; - - // x-client-ip - if (clientIp) { - ipAddress = clientIp; - } - - // x-forwarded-for - else if (forwardedForAlt) { - // x-forwarded-for header is more common - // it may return multiple IP addresses in the format: - // "client IP, proxy 1 IP, proxy 2 IP" - // we pick the first one - var forwardedIps = forwardedForAlt.split(','); - ipAddress = forwardedIps[0]; - } - - // x-real-ip - // (default nginx proxy/fcgi) - else if (realIp) { - // alternative to x-forwarded-for - // used by some proxies - ipAddress = realIp; - } - - // x-cluster-client-ip - // (Rackspace LB and Riverbed's Stingray) - // http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address - // https://splash.riverbed.com/docs/DOC-1926 - else if (clusterClientIp) { - ipAddress = clusterClientIp; - } - - // x-forwarded - else if (forwardedAlt) { - ipAddress = forwardedAlt; - } - - // forwarded-for - else if (forwardedFor) { - ipAddress = forwardedFor; - } - - // forwarded - else if (forwarded) { - ipAddress = forwarded; - } - - // remote address checks - else if (reqConnectionRemoteAddress) { - ipAddress = reqConnectionRemoteAddress; - } - else if (reqSocketRemoteAddress) { - ipAddress = reqSocketRemoteAddress - } - else if (reqConnectionSocketRemoteAddress) { - ipAddress = reqConnectionSocketRemoteAddress - } - else if (reqInfoRemoteAddress) { - ipAddress = reqInfoRemoteAddress - } - - // return null if we cannot find an address - else { - ipAddress = null; - } - - return ipAddress; -} \ No newline at end of file diff --git a/server/offline_notify/offline_nofity_server.js b/server/offline_notify/offline_nofity_server.js deleted file mode 100644 index 70bc02528..000000000 --- a/server/offline_notify/offline_nofity_server.js +++ /dev/null @@ -1,39 +0,0 @@ -if (Meteor.isServer) { - offlineJobs = JobCollection('offline_notification_queue'); - - Meteor.startup(function () { - offlineJobs.startJobServer(); - }); - /* - * 如果clientId有需要发送的消息,也就是曾经掉线过,而且没通知过,则返回 true - * 如果clientId没有要发送的消息,则返回false - */ - cancel_offline_notification = function(clientId){ - var doc = offlineJobs.find({'data.clientId':clientId,status:'waiting'}).fetch() - console.log(doc) - if(!doc || doc.length <= 0){ - return false; - } - var cancelList = doc.map(function(item){ - return item._id - }) - offlineJobs.cancelJobs(cancelList) - offlineJobs.removeJobs(cancelList) - console.log(offlineJobs.find({'data.clientId':clientId,status:'waiting'}).fetch()) - return true; - //console.log(offlineJobs.find(docs)) - } - create_offline_notification = function(clientId,data){ - data['clientId'] = clientId; - var job = Job(offlineJobs, 'offline_notify', data); - - // Set some properties of the job and then submit it - // 离线消息等待5分钟再发,避免网络抖动或者服务器重启动带来的频繁报告 - job.priority('high') - .retry({ retries: 5, - wait: 1*60*1000 }) // 1 minutes between attempts - .delay(5*60*1000) // inMs - .save(); // Commit it to the server - console.log(job.doc) - } -} diff --git a/server/offline_notify/offline_notify_test.js b/server/offline_notify/offline_notify_test.js deleted file mode 100644 index bfb7bb57f..000000000 --- a/server/offline_notify/offline_notify_test.js +++ /dev/null @@ -1,10 +0,0 @@ -if (Meteor.isServer) { - Meteor.startup(function () { - create_offline_notification('test_client_id',{}); - Meteor.setTimeout(function(){ - cancel_offline_notification('test_client_id'); - },500) - - create_offline_notification('test_client_id_1111',{}); - }) -} diff --git a/server/offline_notify/offline_notify_worker.js b/server/offline_notify/offline_notify_worker.js deleted file mode 100644 index 25cfb36b0..000000000 --- a/server/offline_notify/offline_notify_worker.js +++ /dev/null @@ -1,38 +0,0 @@ -if (Meteor.isServer) { - Meteor.startup(function () { - var workers = Job.processJobs('offline_notification_queue', 'offline_notify', - function (job, cb) { - - console.log('send offline notification') - var data = job.data; - console.log(data) - var uuid = data.clientId - console.log('send offline notification to '+uuid) - user = Meteor.users.findOne({username: uuid}) - if(user){ - userGroups = SimpleChat.GroupUsers.findOne({user_id: user._id}) - device = Devices.findOne({"uuid" : uuid}) - if(userGroups && device){ - //sharpai_pushnotification("notify_knownPeople", {active_time:active_time, group_id:userGroup.group_id, group_name:group_name, person_name:person_name}, null, ai_person_id) - sharpai_pushnotification("device_offline",userGroups,uuid) - } - } - //group = SimpleChat.Groups.findOne({_id: userGroup.group_id}) - job.done(); - cb(); - }) - /* - function(err) { - if (err) { - job.log("Sending failed with error" + err, - {level: 'warning'}); - job.fail("" + err); - } else { - job.done(); - } - // Be sure to invoke the callback - // when work on this job has finished - cb(); - }*/ - }) -} diff --git a/server/offline_notify/register_handler.js b/server/offline_notify/register_handler.js deleted file mode 100644 index 592d4ad8d..000000000 --- a/server/offline_notify/register_handler.js +++ /dev/null @@ -1,88 +0,0 @@ -Meteor.startup(function () { - if (Meteor.isServer) { - var DEBUG_ON = false; - - Accounts.onLogin(function(info){ - DEBUG_ON && console.log('onLogin:') - if(info && info.user && info.user.profile && info.user.profile.device){ - console.log('Device: '+info.user.username+' --> login') - //user = Meteor.users.findOne({username: uuid) - userGroups = SimpleChat.GroupUsers.findOne({user_id: info.user._id}) - console.log(userGroups) - Devices.update({uuid: info.user.username},{$set:{online:true}}) - var not_yet_notified_offline = cancel_offline_notification(info.user.username); - /* - * 如果用户掉线后,掉线消息还没发,设备就已经上线了,就不再通知用户 - * 只有超过定义的时间如10分钟,离线消息才会发出。这样避免网络抖动带来的频繁通知 - * 也避免服务器重启动之后,所有设备都会被通知掉线 - */ - if(userGroups && !not_yet_notified_offline){ - sharpai_pushnotification("device_online",userGroups,info.user.username) - } - info.connection.onClose(function(){ - console.log('Device: '+info.user.username+' --> disconnected') - Meteor.users.update( - {username:info.user.username}, - {$unset:{"services.resume.loginTokens":[]} - }) - - Devices.update({uuid: info.user.username},{$set:{ - online:false, - camera_run:false - }}) - create_offline_notification(info.user.username,{}) - }) - } - if(info.user){ - if(info.connection && info.connection.clientAddress){ - Meteor.users.update({_id:info.user._id},{$set:{'profile.lastLogonIP':info.connection.clientAddress}}) - LogonIPLogs.insert({userid: info.user._id, ip: info.connection.clientAddress, createdAt: new Date()}) - } - } - }) - - Accounts.validateLoginAttempt(function(options) { - - if(options.user && options.user.token){ - if( LockedUsers.find({token: options.user.token}).count() > 0 ) { - throw new Meteor.Error(403, "设备被禁用") - } - } - if(options.allowed){ - return true; - } - DEBUG_ON && console.log(options); - var username = options.methodArguments[0].user.username - var password = options.methodArguments[0].password.digest - DEBUG_ON && console.log('username: ') - DEBUG_ON && console.log(username) - DEBUG_ON && console.log('password: ') - DEBUG_ON && console.log(password) - DEBUG_ON && console.log(options.methodArguments[0].password) - var real_pwd = CryptoJS.HmacSHA256(username, "sharp_ai98").toString() - var real_pwd_digest = CryptoJS.SHA256(real_pwd).toString() - if(real_pwd_digest === password){ - var userInfo = Meteor.users.findOne({username:username}); - if(userInfo){ - Accounts.setPassword(userInfo._id,real_pwd); - Meteor.users.update({_id:userInfo._id},{$set:{'profile.device':true}}) - } else { - DEBUG_ON && console.log('need create user') - var result = Accounts.createUser({ - username:username, - password:{ - digest: real_pwd_digest, - algorithm: 'sha-256' - }, - profile:{ - device: true - } - }) - console.log(result) - DEBUG_ON && console.log(result) - } - } - return true; - }) - } -}) diff --git a/server/on_create_user_server_side.coffee b/server/on_create_user_server_side.coffee deleted file mode 100644 index a7f1b8aff..000000000 --- a/server/on_create_user_server_side.coffee +++ /dev/null @@ -1,18 +0,0 @@ -if Meteor.isServer - Meteor.startup ()-> - # 禁止相关设备创建用户 - Accounts.validateNewUser (user)-> - if user.token - if LockedUsers.find({token: user.token}).count() > 0 - throw new Meteor.Error(403, "设备被禁用") - # 禁止匿名登录(only mobile) - ### - console.log('create='+JSON.stringify(user.profile)) - if user and user.profile and user.profile.anonymous is true - if user.profile.browser is true - return true - else - throw new Meteor.Error(403, "不能匿名登录") - else - ### - return true diff --git a/server/person.js b/server/person.js deleted file mode 100644 index 701fad54d..000000000 --- a/server/person.js +++ /dev/null @@ -1,1962 +0,0 @@ -cleanLeftRelationAndStatusDate = function(){ - // 清理,移除person后遗留的相关数据(仅在本地开发环境下使用) - WorkAIUserRelations.find({}).forEach(function(item){ - item.ai_persons.forEach(function(personIds){ - var person = null; - person = Person.findOne({_id: personIds.id}); - if(!person){ - console.log(item.person_name+' need remove from relations at group '+ item.group_id + ' and person id is '+ personIds.id); - WorkAIUserRelations.remove({_id: item._id}); - WorkStatus.remove({group_id: item.group_id, 'person_id.id': personIds.id}); - } - }) - }); -}; - - -var Fiber = Npm.require('fibers'); -var gLastTrainTimestamp = {}; - -PERSON = { - upsetDevice: function(uuid, group_id,name,in_out){ - var device = Devices.findOne({uuid: uuid}); - if (!device){ - device = { - _id: new Mongo.ObjectID()._str, - uuid: uuid, - name: name ? name : '设备 ' + (Devices.find({}).count() + 1), - in_out:in_out, - groupId: group_id, - createAt: new Date() - }; - Devices.insert(device); - } - else{ - if (group_id && name && in_out) { - Devices.update({uuid: uuid}, {$set: {groupId: group_id,name:name,in_out:in_out}}); - } - device = Devices.findOne({uuid: uuid}); - } - return device; - }, - removeName: function(group_id,uuid, id,url,is_video){ - console.log('try remove name'); - console.log('>>> id=' + id + ' url=' + url) - - /* 这张图片是误识别的,并且被人错误标注“对” */ - // Person.find({group_id:group_id ,'faces.url': url}).forEach(function(item){ - // console.log('>>> remove mistake url and id, person.name=' + item.name + ' id=' + id + ' url=' + url) - // Person.update({"_id": item._id}, {$pull: {'faces': {"url": url}}}) - // }); - - var person = null; - if (group_id && id) { - person = Person.findOne({group_id:group_id ,'faces.id': id}, {sort: {createAt: 1}}); - } - if (person){ - if (!is_video) { - PERSON.fixRelationOrWorkStatus(group_id, url,person._id); - } - // if (person.faceId === id){ - // if (person.faces.length <= 1) - // return Person.remove({_id: person._id}); - // Person.update({_id: person._id}, { - // $set: {faceId: person.faces[0].id, url: person.faces[0].url}, - // $pop: {faces: -1} - // }); - // } else { - // var faces = person.faces; - // faces.splice(_.pluck(faces, 'id').indexOf(id), 1); - // Person.update({_id: person._id}, { - // $set: {faces: faces} - // }); - // } - } - //PersonNames.remove({uuid: uuid, id: id}); [{id:'1'},{}] ['1','2'] - }, - removeFace: function(obj){ - console.log('try remove faces'); - var person = null; - var faces = []; - if(obj.group_id){ - person = Person.findOne({group_id: obj.group_id, faceId: obj.faceId}); - } - if(person){ - faces = person.faces; - var faceId = person.faceId; - if(faceId === obj.face_id){ - if(faces.length <= 1){ - return Person.remove({_id: person._id}); - } - Person.update({_id: person._id}, { - $set: {faceId: person.faces[0].id, url: person.faces[0].url}, - $pop: {faces: -1} - }); - } else { - faces.splice(_.pluck(faces, 'id').indexOf(obj.face_id), 1); - Person.update({_id: person._id},{$set: {faces: faces}}); - } - } - }, - updateLabelTimes: function(group_id, items) { - var names = []; - for (var i = 0; i < items.length; i++){ - if(items[i].name && names.indexOf(items[i].name) < 0) { - names.push(items[i].name); - } - } - - for (var x = 0; x < names.length; x++ ){ - var person = Person.findOne({group_id:group_id, name: names[x]}, {sort: {createAt: 1}}); - if ( person ) { - Person.update({_id: person._id}, { - $inc: {label_times: 1} - }); - } - } - console.log('==sr==. names =='+ JSON.stringify(names)); - }, - setName: function(group_id, uuid, id, url, name, is_video, is_human_shape, callback){ - var person = Person.findOne({group_id:group_id, name: name}, {sort: {createAt: 1}}); - var device = Devices.findOne({uuid: uuid}); - var personName = PersonNames.findOne({group_id: group_id, name: name}); - var trainCount = 1; - - if (!personName) - PersonNames.insert({group_id: group_id, url: url, id: id, name: name, createAt: new Date(), updateAt: new Date()}); - // else - // PersonNames.update({_id: name._id}, {$set: {name: name, url: url, id: id, updateAt: new Date()}}) - - if (person){ - if (is_video) { - return person; - } - person.url = url; - person.updateAt = new Date(); - if (is_human_shape) { - if(person.human_shape == undefined){ - person.human_shape = []; - } - if(_.pluck(person.human_shape, 'id').indexOf(id) === -1) - person.human_shape.push({id: id, url: url}); - else - person.human_shape[_.pluck(person.human_shape, 'id').indexOf(id)].url = url; - // Person.update({_id: person._id}, {$set: {name: name, url: person.url, updateAt: person.updateAt, faces: person.faces}}); - console.log("update person.humanshapes = "+JSON.stringify(person.human_shape)); - Person.update({_id: person._id}, {$set: {updateAt: person.updateAt, human_shape: person.human_shape}}); - } else { - if(person.faces == undefined){ - person.faces = []; - } - if(_.pluck(person.faces, 'id').indexOf(id) === -1) - person.faces.push({id: id, url: url}); - else - person.faces[_.pluck(person.faces, 'id').indexOf(id)].url = url; - // Person.update({_id: person._id}, {$set: {name: name, url: person.url, updateAt: person.updateAt, faces: person.faces}}); - console.log("update person.faces = "+JSON.stringify(person.faces)); - Person.update({_id: person._id}, {$set: {updateAt: person.updateAt, faces: person.faces}}); - } - - //标记,立即训练 - var obj = SimpleChat.Groups.findOne({_id: group_id}); - var to = { - id: obj._id, - name: obj.name, - icon: obj.icon - }; - var device_user = Meteor.users.findOne({username: uuid}) - var form = {}; - if (device_user) { - form = { - id: device_user._id, - name: device_user.profile && device_user.profile.fullname ? device_user.profile.fullname : device_user.username, - icon: device_user.profile.icon - }; - } - var msg = { - _id: new Mongo.ObjectID()._str, - form:form, - to: to, - to_type: 'group', - type: 'text', - text: 'train', - create_time: new Date(), - is_read: false, - is_trigger_train:true - }; - - do { - Meteor.setTimeout(function() { - try{ - var now = new Date().getTime(); - var groupLastTrain = gLastTrainTimestamp[group_id]; - if (groupLastTrain == undefined || groupLastTrain == null) - groupLastTrain = 0; - if (now - groupLastTrain > 10*1000) { - gLastTrainTimestamp[group_id] = now; - sendMqttGroupMessage(group_id,msg); - } - } catch (e){ - console.log('try sendMqttGroupMessage Err:',e); - } - }, 5 * 1000 * trainCount); - - ++trainCount; - } while (trainCount <= 3); - } - //此段代码会导致person表的名字会被篡改 - /* - else if (Person.find({group_id: group_id, faceId: id}).count() > 0){ - person = Person.findOne({group_id: group_id, faceId: id}, {sort: {createAt: 1}}); - person.name = name; - person.url = url; - person.updateAt = new Date(); - if (!is_video) { - if(_.pluck(person.faces, 'id').indexOf(id) === -1) - person.faces.push({id: id, url: url}); - else - person.faces[_.pluck(person.faces, 'id').indexOf(id)].url = url; - } - Person.update({_id: person._id}, {$set: {name: name, url: person.url, updateAt: person.updateAt, faces: person.faces}}); - }*/ - else { - if(is_human_shape){ - person = { - _id: new Mongo.ObjectID()._str, - id: Person.find({group_id: group_id, faceId: id}).count() + 1, - group_id:group_id, - faceId: id, - url: url, - name: name, - human_shape: [{id: id, url: url}], - deviceId: device._id, - DeviceName: device.name, - label_times: 1, - createAt: new Date(), - updateAt: new Date() - }; - }else{ - person = { - _id: new Mongo.ObjectID()._str, - id: Person.find({group_id: group_id, faceId: id}).count() + 1, - group_id:group_id, - faceId: id, - url: url, - name: name, - faces: [{id: id, url: url}], - deviceId: device._id, - DeviceName: device.name, - label_times: 1, - createAt: new Date(), - updateAt: new Date() - }; - } - if (is_video) { - delete person.faces; - delete person.faceId; - } - console.log("insert person = "+JSON.stringify(person)); - Person.insert(person); - //标记新人,立即训练 - var obj = SimpleChat.Groups.findOne({_id: group_id}); - var to = { - id: obj._id, - name: obj.name, - icon: obj.icon - }; - var device_user = Meteor.users.findOne({username: uuid}) - var form = {}; - if (device_user) { - form = { - id: device_user._id, - name: device_user.profile && device_user.profile.fullname ? device_user.profile.fullname : device_user.username, - icon: device_user.profile.icon - }; - } - var msg = { - _id: new Mongo.ObjectID()._str, - form:form, - to: to, - to_type: 'group', - type: 'text', - text: 'train', - create_time: new Date(), - is_read: false, - is_trigger_train:true - }; - - do { - Meteor.setTimeout(function() { - try{ - var now = new Date().getTime(); - var groupLastTrain = gLastTrainTimestamp[group_id]; - if (groupLastTrain == undefined || groupLastTrain == null) - groupLastTrain = 0; - if (now - groupLastTrain > 10*1000) { - gLastTrainTimestamp[group_id] = now; - sendMqttGroupMessage(group_id,msg); - } - } catch (e){ - console.log('try sendMqttGroupMessage Err:',e); - } - }, 5 * 1000 * trainCount); - - ++trainCount; - } while (trainCount <= 3); - } - callback && callback(); - return person; - }, - getName: function(uuid,group_id,id){ - var person = null; - if (id && group_id) { - person = Person.findOne({group_id: group_id, 'faces.id': id}, {sort: {createAt: 1}}); - } - if (person) - return person.name; - return null; - }, - getIdByName: function(uuid, name, group_id){ - var person = null; - if (group_id && name) { - person = Person.findOne({group_id: group_id, name: name}, {sort: {createAt: 1}}); - } - if (!person) - return null; - return { - _id:person._id, - id: person.id, - faceId: person.faceId - }; - }, - getIdByNames: function(uuid, names, group_id){ - var limit = names.length; - var persons = null; - var result = {}; - if(group_id && names) { - persons = Person.find({group_id: group_id, name: {$in: names}}, {sort: {createAt: 1}, limit: limit}).fetch() - } - - if (persons.length <= 0){ - for(var i=0;i 0 && workstatus.in_time < setObj.checkin_time) { - // delete setObj.checkin_time; - // delete setObj.checkin_image; - // delete setObj.checkin_video; - console.log('workstatus in_time earlier than setObj checkin_time'); - setObj.checkin_time = workstatus.in_time; - setObj.checkin_image = workstatus.in_image; - setObj.checkin_video = workstatus.in_video; - } - //当前出现下班时间大于标记时间 - if (workstatus.out_time && setObj.checkout_time && workstatus.out_time > setObj.checkout_time) { - console.log('workstatus out_time later than setObj checkout_time'); - setObj.checkout_time = workstatus.out_time; - setObj.checkout_image = workstatus.out_image; - setObj.checkout_video = workstatus.out_video; - } - } - } - if(isToday == true) - WorkAIUserRelations.update({_id:relation._id},{$set:setObj}); - else - if(data.user_id && !relation.app_user_id){ - var setObj2 = { - app_user_id:data.user_id, - app_user_name:user_name, - isWaitRelation:false, - app_notifaction_status:user && user.token ? 'on' : 'off' - }; - WorkAIUserRelations.update({_id:relation._id},{$set:setObj2}); - } - } - else{ - setObj.ai_persons = [{id:person._id}]; - if (!setObj.in_uuid) { - var device = SimpleChat.GroupUsers.findOne({group_id:setObj.group_id,in_out:'in'}); - if (device) { - setObj.in_uuid = device.username; - } - } - if (!setObj.out_uuid) { - var device = SimpleChat.GroupUsers.findOne({group_id:setObj.group_id,in_out:'out'}); - if (device) { - setObj.out_uuid = device.username; - } - } - setObj.checkin_image = data.checkin_image; - setObj.checkout_image = data.checkout_image; - WorkAIUserRelations.insert(setObj); - } - person_info.name = person.name; - person_info.id = person._id; - person_info.wantModify = data.wantModify; - if(isToday == true) - PERSON.updateWorkStatus(person._id); - else - PERSON.updateWorkStatusHistory(setObj); - PERSON.sendPersonInfoToWeb(person_info); - - var timeLineData = { - uuid:person_info.uuid, - group_id:person_info.group_id, - person_id:person_info.person_id ? person_info.person_id : null, - user_id: user ? user._id : null, - user_name:user_name, - person_name:person.name, - img_url:person_info.img_url, - ts:person_info.ts, - type:person_info.type, - accuracy: person_info.accuracy, - fuzziness: person_info.fuzziness, - sqlid: person_info.sqlid, - style: person_info.style - - }; - PERSON.updateToDeviceTimeline2(timeLineData); - return {result:'succ'}; - }, - //App用户关联过的成员,更新出现信息 - updateWorkStatus: function(ai_person_id){ - console.log('updateWorkStatus -->'+ai_person_id); - relation = WorkAIUserRelations.findOne({'ai_persons.id': ai_person_id}) - if(!relation || !relation.group_id || !relation.ai_persons || !relation.person_name) { - console.log("invalid arguments of updateWorkStatus") - return - } - if(!relation.checkin_image && !relation.checkout_image && !relation.ai_in_image && !relation.ai_out_image){ - console.log("invalid arguments of updateWorkStatus,check image") - return - } - - var time_offset = 8; //US is -7, China is +8 - - var group_intime = '09:00'; //默认上班时间9点 - var group_outtime = '18:00'; //默认下班时间18点 - - var group = SimpleChat.Groups.findOne({_id: relation.group_id}); - if (group && group.offsetTimeZone) { - time_offset = group.offsetTimeZone; - } - if (group && group.group_intime) { - group_intime = group.group_intime; - } - if (group && group.group_outtime) { - group_outtime = group.group_outtime; - } - - console.log('offsetTimeZone ' + time_offset); - function DateTimezone(offset) { - var d = new Date(); - var utc = d.getTime() + (d.getTimezoneOffset() * 60000); - var local_now = new Date(utc + (3600000*offset)) - - return local_now; - } - - var now = DateTimezone(time_offset); - var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); - var today_utc = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , - 0, 0, 0, 0); - - var group_intime_ary = group_intime.split(":"); - group_intime = new Date(now.getFullYear(), now.getMonth(), now.getDate(),group_intime_ary[0],group_intime_ary[1]).getTime(); - //console.log('group_intime:'+group_intime); - - var group_outtime_ary = group_outtime.split(":"); - group_outtime = new Date(now.getFullYear(), now.getMonth(), now.getDate(),group_outtime_ary[0],group_outtime_ary[1]).getTime(); - //console.log('group_outtime:'+group_intime); - - - //不是今天的记录都设置为0 - relation.checkout_time = (!relation.checkout_time) ? 0 : (!PERSON.checkIsToday(relation.checkout_time,relation.group_id)) ? 0 : relation.checkout_time; - relation.checkin_time = (!relation.checkin_time) ? 0 : (!PERSON.checkIsToday(relation.checkin_time,relation.group_id)) ? 0 : relation.checkin_time; - relation.ai_in_time = (!relation.ai_in_time) ? 0 : (!PERSON.checkIsToday(relation.ai_in_time,relation.group_id)) ? 0 : relation.ai_in_time; - relation.ai_out_time = (!relation.ai_out_time) ? 0 : (!PERSON.checkIsToday(relation.ai_out_time,relation.group_id)) ? 0 : relation.ai_out_time; - relation.ai_lastest_in_time = (!relation.ai_lastest_in_time) ? 0 : (!PERSON.checkIsToday(relation.ai_lastest_in_time,relation.group_id)) ? 0 : relation.ai_lastest_in_time; - - var outtime = 0; - outtime = (relation.checkout_time > relation.ai_out_time) ? relation.checkout_time : relation.ai_out_time; - outtime = (outtime > 0) ? ((PERSON.checkIsToday(outtime,relation.group_id)) ? outtime : 0 ): 0; - - //最新一次进门的时间 - var lastest_in_time = (relation.checkin_time > relation.ai_lastest_in_time) ? relation.checkin_time : relation.ai_lastest_in_time; - - var intime = 0; - if(relation.ai_in_time == 0 || relation.checkin_time == 0) { - //取出不等于0的就是intime - intime = (relation.ai_in_time > relation.checkin_time) ? relation.ai_in_time : relation.checkin_time; - } - else { - //取出较小的就是intime - intime = (relation.ai_in_time > relation.checkin_time) ? relation.checkin_time : relation.ai_in_time; - } - - // 如果存在 checkin_time , 那么 in_time 以 checkin_time - if(relation.checkin_time && relation.checkin_time !== 0) { - intime = relation.checkin_time; - } - - // var in_time = null;//上班时间 - // if (intime > 0) { - // var in_time_date = new Date(intime); - // in_time = in_time_date.getHours()+':'+in_time_date.getMinutes(); - - // } - // var out_time = null;//下班时间 - // if (outtime > 0) { - // var out_time_date = new Date(outtime); - // out_time = out_time_date.getHours()+':'+out_time_date.getMinutes(); - - // } - - // intime = (intime > today_utc)?intime:0 - intime = PERSON.checkIsToday(intime,relation.group_id)?intime:0; - outtime = (outtime >= intime)?outtime: 0; - - var in_image = ''; - var out_image = ''; - var now_status = "out"; //in/out - var in_status = "unknown"; - var out_status = "unknown"; - var in_video = ''; - var out_video = ''; - //normal 工作时间大于8小时 或 9:00am前上班 - - //in/out image - if(intime > today && intime == relation.checkin_time){ - in_image = relation.checkin_image; - in_video = relation.checkin_video; - } - else if(intime > today && intime == relation.ai_in_time) - in_image = relation.ai_in_image; - if(outtime > today && outtime == relation.checkout_time){ - out_image = relation.checkout_image; - out_video = relation.checkout_video; - } - else if(outtime > today && outtime == relation.ai_out_time) - out_image = relation.ai_out_image; - - //有in没有out就是绿色,其他是灰色 - if(lastest_in_time > today && lastest_in_time > outtime) - now_status = "in"; - - //9点以前上班是绿色, 之后是红色 - if(intime == 0){ - in_status = "unknown"; - } - else if(intime > 0 && intime <= group_intime){ - in_status = "normal"; - } - else if(intime > 0 && intime > group_intime){ - in_status = "warning"; - } - - if(outtime == 0) - out_status = "unknown"; - //没看到in却有out,或者先看到出后看到进 - else if(outtime > 0 && (intime ==0 || intime > outtime)) - out_status = "error" - //不足8小时 - else if(outtime > 0 && intime > 0 && outtime > intime && (outtime - intime) < (group_outtime - group_intime)) - out_status = "warning" - else if(outtime > 0 && intime > 0 && outtime > intime && (outtime - intime) >= (group_outtime - group_intime)) - out_status = "normal" - - var in_uuid = relation.in_uuid; - var out_uuid = relation.out_uuid; - - //var date = Date.now(); - //var mod = 24*60*60*1000; - //today2 = date - (date % mod); - - - var setObj = { - "status" : now_status, - "in_status" : in_status, - "out_status" : out_status, - "in_time" : intime, - "out_time" : outtime - }; - if(relation.in_uuid) - setObj.in_uuid = relation.in_uuid; - if(relation.out_uuid) - setObj.out_uuid = relation.out_uuid; - if(in_image) - setObj.in_image = in_image; - if(out_image) - setObj.out_image = out_image; - setObj.in_video = in_video; - setObj.out_video = out_video; - - var workstatus = null; - if (relation.app_user_id) { - workstatus = WorkStatus.findOne({'group_id': relation.group_id, 'app_user_id': relation.app_user_id, 'date': today_utc}); - } - if (!workstatus && relation.person_name) { - workstatus = WorkStatus.findOne({'group_id': relation.group_id, 'person_name': relation.person_name, 'date': today_utc}); - } - if (!workstatus) { - WorkStatus.insert({ - "app_user_id" : relation.app_user_id, - "app_notifaction_status":relation.app_notifaction_status, - "group_id" : relation.group_id, - "date" : today_utc, - "person_id" : relation.ai_persons, - "person_name" : relation.person_name, - "status" : now_status, - "in_status" : in_status, - "out_status" : out_status, - "in_uuid" : relation.in_uuid, - "out_uuid" : relation.out_uuid, - "whats_up" : "", - "in_time" : intime, - "in_image" : in_image, - "in_video" : in_video, - "out_image" : out_image, - "out_time" : outtime, - "out_video" : out_video, - "hide_it" : relation.hide_it? relation.hide_it: false - }); - } - else { - if (outtime > workstatus.out_time){ - - var deviceUser = Meteor.users.findOne({username: relation.out_uuid}); - - if (deviceUser) { - var msgObj = { - _id: new Mongo.ObjectID()._str, - form:{ - id: deviceUser._id, - name: deviceUser.profile.fullname, - icon: deviceUser.profile.icon - }, - to: { - id: relation.app_user_id, - name: relation.person_name, - icon: '' - }, - to_type: 'user', - type: 'text', - text: '你已经下班了吗?', - create_time: new Date(), - is_read: false, - }; - - if(relation.app_user_id){ - console.log(msgObj) - // sendMqttUserMessage(relation.app_user_id,msgObj); - } - } - } - - if (!workstatus.app_user_id && relation.app_user_id) { - setObj.app_user_id = relation.app_user_id; - } - setObj.app_notifaction_status = relation.app_notifaction_status; - WorkStatus.update({_id: workstatus._id}, {$set: setObj}); - } - }, - //更新历史出现信息 - updateWorkStatusHistory: function(workStatusObj){ - console.log('updateWorkStatusHistory --->'+JSON.stringify(workStatusObj)); - //{ - // "group_id": "cc30c1b5b49ea17c7145b270", - // "in_uuid": "7YRBBDB712001377", - // "checkin_time": 1503559323910, - // "checkin_image": "http://workaiossqn.tiegushi.com/ed506346-889c-11e7-bcbb-d065caa7da61", - // "app_user_id": "iXSQHnLkDqEQ9cZFC", - // "app_user_name": "天天向上", - // "isWaitRelation": false, - // "person_name": "lambda" - //} - if(!(workStatusObj && (workStatusObj.checkin_time || workStatusObj.checkout_time) && workStatusObj.group_id)) - return; - - var time_offset = 8; //US is -7, China is +8 - var group_intime = '09:00'; //默认上班时间9点 - var group_outtime = '18:00'; //默认下班时间18点 - - var group = SimpleChat.Groups.findOne({_id: workStatusObj.group_id}); - if (group && group.offsetTimeZone) { - time_offset = group.offsetTimeZone; - } - if (group && group.group_intime) { - group_intime = group.group_intime; - } - if (group && group.group_outtime) { - group_outtime = group.group_outtime; - } - - function DateTimezone(offset) { - var time = workStatusObj.checkin_time || workStatusObj.checkout_time; - var d = new Date(time); - var utc = d.getTime() + (d.getTimezoneOffset() * 60000); - var local_now = new Date(utc + (3600000*offset)) - - return local_now; - } - - var now = DateTimezone(time_offset); - var day = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); - var day_utc = Date.UTC(now.getFullYear(),now.getMonth(), now.getDate() , - 0, 0, 0, 0); - console.log('day_utc:'+day_utc); - - var group_intime_ary = group_intime.split(":"); - group_intime = new Date(now.getFullYear(), now.getMonth(), now.getDate(),group_intime_ary[0],group_intime_ary[1]).getTime(); - var group_outtime_ary = group_outtime.split(":"); - group_outtime = new Date(now.getFullYear(), now.getMonth(), now.getDate(),group_outtime_ary[0],group_outtime_ary[1]).getTime(); - - var workstatus = null; - if (workStatusObj.app_user_id) { - workstatus = WorkStatus.findOne({'group_id': workStatusObj.group_id, 'app_user_id': workStatusObj.app_user_id, 'date': day_utc}); - } - if (!workstatus && workStatusObj.person_name) { - workstatus = WorkStatus.findOne({'group_id': workStatusObj.group_id, 'person_name': workStatusObj.person_name, 'date': day_utc}); - } - - var in_image = ''; - var out_image = ''; - var in_video = ''; - var out_video = ''; - var now_status = "out"; //in/out - var in_status = "unknown"; - var out_status = "unknown"; - var intime = 0; - var outtime = 0; - var checkin_time = 0; - var checkout_time = 0; - - if(workStatusObj && workStatusObj.in_uuid && workStatusObj.checkin_time && workStatusObj.checkin_image) { - checkin_time = workStatusObj.checkin_time; - } - else if(workStatusObj && workStatusObj.out_uuid && workStatusObj.checkout_time && workStatusObj.checkout_image) { - checkout_time = workStatusObj.checkout_time; - } - - //这一天存在出现记录 - if (workstatus) { - intime = workstatus.in_time ? workstatus.in_time : 0; - outtime = workstatus.out_time ? workstatus.out_time : 0; - - //进 - if(checkin_time>0) { - intime = (intime>0 && intime 0 && intime>checkin_time) ? workstatus.in_image : workStatusObj.checkin_image; - in_video = (intime>0 && intime>checkin_time) ? workstatus.checkin_video : workStatusObj.checkin_video; - in_status = workstatus.in_status; - out_status = workstatus.out_status; - } - //出 - if(checkout_time>0) { - outtime = (outtime>0 && outtime>checkout_time) ? outtime : checkout_time; - out_image = (outtime>0 && outtime>checkout_time) ? workstatus.out_image : workStatusObj.checkout_image; - out_video = (outtime>0 && outtime>checkout_time) ? workstatus.out_video : workStatusObj.checkout_video; - in_status = workstatus.in_status; - out_status = workstatus.out_status; - } - } - else { - if(checkin_time>0) { - in_image = workstatus.in_image; - intime = checkin_time; - } - if(checkout_time>0) { - out_image = workstatus.out_image; - outtime = checkout_time; - } - } - - //9点以前上班是绿色, 之后是红色 - if(intime == 0) - in_status = "unknown"; - else if(intime > 0 && intime <= (group_intime)) - in_status = "normal"; - else if(intime > 0 && intime > (group_intime)) - in_status = "warning"; - - if(outtime == 0) - out_status = "unknown"; - //没看到in却有out,或者先看到出后看到进 - else if(outtime > 0 && (intime ==0 || intime > outtime)) - out_status = "error" - //不足8小时 - else if(outtime > 0 && intime > 0 && outtime > intime && (outtime - intime) < (group_outtime - group_intime)) - out_status = "warning" - else if(outtime > 0 && intime > 0 && outtime > intime && (outtime - intime) >= (group_outtime - group_intime)) - out_status = "normal" - - var setObj = { - "status" : now_status, - "in_status" : in_status, - "out_status" : out_status, - "in_time" : intime, - "out_time" : outtime - }; - if(workStatusObj.in_uuid) - setObj.in_uuid = workStatusObj.in_uuid; - if(workStatusObj.out_uuid) - setObj.out_uuid = workStatusObj.out_uuid; - if(in_image) - setObj.in_image = in_image; - if(out_image) - setObj.out_image = out_image; - setObj.in_video = in_video; - setObj.out_video = out_video; - setObj.app_notifaction_status = workStatusObj.app_notifaction_status; - - if (!workstatus) { - WorkStatus.insert({ - "app_user_id" : workStatusObj.app_user_id, - "app_notifaction_status":workStatusObj.app_notifaction_status, - "group_id" : workStatusObj.group_id, - "date" : day_utc, - "person_id" : workStatusObj.ai_persons, - "person_name" : workStatusObj.person_name, - "status" : now_status, - "in_status" : in_status, - "out_status" : out_status, - "in_uuid" : workStatusObj.in_uuid, - "out_uuid" : workStatusObj.out_uuid, - "whats_up" : "", - "in_time" : intime, - "in_image" : in_image, - "in_video" : workStatusObj.checkin_video, - "out_image" : out_image, - "out_time" : outtime, - "out_video" : workStatusObj.checkout_video, - "hide_it" : workStatusObj.hide_it? workStatusObj.hide_it: false - }); - } - else { - WorkStatus.update({_id: workstatus._id}, {$set: setObj}); - } - }, - // update Device TimeLine - updateToDeviceTimeline: function(uuid,group_id,obj){ - console.log('updateToDeviceTimeline= uuid:'+uuid+', group_id:'+group_id+' ,obj:'+JSON.stringify(obj)); - if(!uuid || !group_id || !obj){ - return; - } - var create_time = obj.ts || Date.now(); - var hour = new Date(create_time); - hour.setMinutes(0); - hour.setSeconds(0); - hour.setMilliseconds(0); - - var minutes = new Date(create_time); - minutes = minutes.getMinutes(); - - var selector = { - hour: hour, - uuid: uuid, - group_id: group_id - }; - var modifier = { - $push:{} - }; - if(!obj.ts){ - obj.ts = Date.now(); - } - modifier["$push"]["perMin."+minutes] = obj; - DeviceTimeLine.update(selector, modifier, {upsert: true},function(err,res){ - if(err){ - console.log('updateToDeviceTimeline Err:'+err); - } else { - console.log('updateToDeviceTimeline res',res) - console.log('updateToDeviceTimeline Success'); - } - }); - }, - updateToDeviceTimeline2: function(obj){ - console.log('updateToDeviceTimeline2= '+JSON.stringify(obj)); - if(!obj.uuid || !obj.group_id || !obj.ts){ - return; - } - var ts = obj.ts; - var person_name = obj.person_name || null; - - var hour = new Date(ts); - hour.setMinutes(0); - hour.setSeconds(0); - hour.setMilliseconds(0); - - var minutes = new Date(ts); - minutes = minutes.getMinutes(); - - var selector = { - hour: hour, - uuid: obj.uuid, - group_id: obj.group_id, - }; - var modifier = { - $set:{} - }; - selector["perMin."+minutes+".ts"] = ts; - if (obj.type === 'video') { - var deviceTimilineItem = DeviceTimeLine.findOne(selector); - if (deviceTimilineItem) { - var count = 1; - //有人选择过 - //console.log('deviceTimilineItem==='+JSON.stringify(deviceTimilineItem)); - var imgIndex = _.pluck(deviceTimilineItem.perMin[minutes], 'ts').indexOf(ts); - if (imgIndex == -1) { - return; - } - var imgItem = deviceTimilineItem.perMin[minutes][imgIndex]; - console.log('imageItem==='+JSON.stringify(imgItem)); - if (imgItem.relations) { - var index = _.pluck(imgItem.relations, 'person_name').indexOf(obj.person_name); - console.log('index==='+index); - if(index === -1){ - imgItem.relations.push({app_user_id: obj.user_id,app_user_name:obj.user_name,person_name:obj.person_name}); - count += imgItem.relations.length; - } - else{ - imgItem.relations[index].app_user_id = obj.user_id; - imgItem.relations[index].app_user_name = obj.user_name; - count = imgItem.relations.length; - } - } - //处理老版本的已选择的情况 - else if (imgItem.app_user_id) { - imgItem.relations = [{app_user_id: obj.user_id,app_user_name:obj.user_name,person_name:obj.person_name}]; - if (imgItem.app_user_id != obj.user_id) { - imgItem.relations.push({app_user_id: imgItem.app_user_id,app_user_name:imgItem.app_user_name,person_name:imgItem.person_name}); - count += 1; - } - } - else{ - imgItem.relations = [{app_user_id: obj.user_id,app_user_name:obj.user_name,person_name:obj.person_name}]; - } - modifier["$set"]["perMin."+minutes+".$.relations"] = imgItem.relations; - modifier["$set"]["perMin."+minutes+".$.relations_info"] = count + '人已选'; - - } - } - else{ - if(obj.user_id && obj.user_name){ - modifier["$set"]["perMin."+minutes+".$.app_user_id"] = obj.user_id; - modifier["$set"]["perMin."+minutes+".$.app_user_name"] = obj.user_name; - } - modifier["$set"]["perMin."+minutes+".$.person_name"] = person_name; - } - - // 更新标注次数 - modifier["$inc"] = {}; - modifier["$inc"]["perMin."+minutes+".$.label_times"] = 1; - - DeviceTimeLine.update(selector,modifier,function(err,res){ - if(err){ - console.log('updateToDeviceTimeline2 Err:'+err); - }else if(!res){ - // 无更新数据返回,插入新的时间轴数据 - PERSON.updateToDeviceTimeline(obj.uuid, obj.group_id, obj); - }else { - console.log('updateToDeviceTimeline2 Success'); - } - }); - - }, - updateValueToDeviceTimeline: function(uuid,group_id,obj){ - console.log('updateValueToDeviceTimeline= uuid:'+uuid+', group_id:'+group_id+' ,obj:'+JSON.stringify(obj)); - if(!uuid || !group_id || !obj){ - return; - } - var create_time = obj.ts || Date.now(); - var hour = new Date(create_time); - hour.setMinutes(0); - hour.setSeconds(0); - hour.setMilliseconds(0); - - var minutes = new Date(create_time); - minutes = minutes.getMinutes(); - console.log("create_time="+create_time+", hour="+hour) - - var selector = { - hour: hour, - uuid: uuid, - group_id: group_id}; - selector["perMin."+minutes+".img_url"] = obj.img_url; - if(!obj.ts){ - obj.ts = Date.now(); - } - //modifier["$push"]["perMin."+minutes] = obj; - console.log("selector="+JSON.stringify(selector)); - var deviceTimilineItem = DeviceTimeLine.findOne(selector); - if (deviceTimilineItem) { - //console.log('updateValueToDeviceTimeline find Success, deviceTimilineItem='+JSON.stringify(deviceTimilineItem)); - var stranger_id = "perMin."+minutes+".$.stranger_id"; - var stranger_name = "perMin."+minutes+".$.stranger_name"; - var modifier = {$set:{}};//{$set:{stranger_id:obj.stranger_id, stranger_name:obj.stranger_name}} - modifier.$set[stranger_id] = obj.stranger_id; - modifier.$set[stranger_name] = obj.stranger_name; - DeviceTimeLine.update(selector, modifier, function(err,res){ - if(err){ - console.log('updateValueToDeviceTimeline update Err:'+err+', obj.img_url='+obj.img_url); - } else { - console.log('updateValueToDeviceTimeline update Success, obj.img_url='+obj.img_url); - } - }); - } else { - console.log('updateValueToDeviceTimeline find failed, obj.img_url='+obj.img_url); - } - }, - // 标错时, 修正 WorkAIUserRelations或workStatus - fixRelationOrWorkStatus: function(group_id, img_url,person_id){ - console.log('try to fix Relation Or WorkStatus'); - var updateHandle = function(relation_id,time){ - var relation = WorkAIUserRelations.findOne({_id:relation_id}); - if (relation && time) { - var isToday = PERSON.checkIsToday(time,group_id); - if(isToday == true){ - PERSON.updateWorkStatus(person_id); - } - else{ - if ((!relation.checkin_time) || (relation.ai_in_time && relation.checkin_time && relation.ai_in_time < checkin_time)) { - relation.checkin_time = relation.ai_in_time; - relation.checkin_image = relation.ai_in_image; - relation.checkin_video = ''; - } - if ((!relation.checkout_time) || (relation.ai_out_time && relation.checkout_time && relation.ai_out_time > checkout_time)) { - relation.checkout_time = relation.ai_out_time; - relation.checkin_image = relation.ai_in_image; - relation.checkout_video = ''; - } - PERSON.updateWorkStatusHistory(relation); - } - } - }; - // 匹配到第一次进的图像 - var relation1 = WorkAIUserRelations.findOne({group_id: group_id,ai_in_image: img_url}); - if(relation1){ - var setObj = {}; - if(relation1.ai_lastest_in_time && relation1.ai_lastest_in_image){ - if(relation1.ai_in_time == relation1.ai_lastest_in_time){ - setObj = { - ai_in_time: null, - ai_in_image: '', - ai_lastest_in_time: null, - ai_lastest_in_image: '' - } - } else { - setObj = { - ai_in_time: relation1.ai_lastest_in_time, - ai_in_image: relation1.ai_lastest_in_image - } - } - return WorkAIUserRelations.update({_id: relation1._id},{ - $set:setObj - },function(error){ - if (!error) { - updateHandle(relation1._id,relation1.ai_in_time); - } - }); - } - } - - // 匹配到最后一次进的图像 - var relation2 = WorkAIUserRelations.findOne({group_id: group_id,ai_lastest_in_image: img_url}); - if(relation2){ - return WorkAIUserRelations.update({_id: relation2._id},{ - $set: { - ai_lastest_in_time: relation2.ai_in_time, - ai_lastest_in_image: relation2.ai_in_image - } - },function(error){ - if (!error) { - updateHandle(relation2._id,relation2.ai_lastest_in_time); - } - }); - } - - // 匹配到出的图像 - var relation3 = WorkAIUserRelations.findOne({group_id: group_id, ai_out_image: img_url}); - if(relation3){ - return WorkAIUserRelations.update({_id: relation3._id},{ - $set: { - ai_out_time: null, - ai_out_image: '' - } - },function(error){ - if (!error) { - updateHandle(relation3._id,relation3.ai_out_time); - } - }); - } - - //匹配历史出现表 - var workStatus1 = WorkStatus.findOne({group_id:group_id,in_image:img_url}); - if (workStatus1) { - return WorkStatus.update({_id:workStatus1._id},{ - $set:{ - in_time:0, - in_image:'', - in_status:'unknown', - status:'out' - } - }); - } - - var workStatus2 = WorkStatus.findOne({group_id:group_id,out_image:img_url}); - if (workStatus2) { - return WorkStatus.update({_id:workStatus2._id},{ - $set:{ - out_time:0, - out_image:'', - out_status:'unknown' - } - }); - } - } -}; - -CLUSTER_PERSON = { - /*_removeFace: function(obj){ - console.log('ClusterPerson: removeFace, try remove faces'); - var person = null; - var faces = []; - if(obj.group_id){ - person = ClusterPerson.findOne({group_id: obj.group_id, faceId: obj.faceId}); - } - if(person){ - faces = person.faces; - var faceId = person.faceId; - if(faceId === obj.face_id){ - if(faces.length <= 1){ - return ClusterPerson.remove({_id: person._id}); - } - ClusterPerson.update({_id: person._id}, { - $set: {faceId: person.faces[0].id, url: person.faces[0].url}, - $pop: {faces: -1} - }); - } else { - faces.splice(_.pluck(faces, 'id').indexOf(obj.face_id), 1); - ClusterPerson.update({_id: person._id},{$set: {faces: faces}}); - } - } - },*/ - removeFace: function(group_id, faceId, url){ - var person = null; - if(group_id){ - person = ClusterPerson.findOne({group_id: group_id, faceId: faceId}); - } - if(person){ - if(faceId === person.faceId){ - if(person.faces.length <= 1){ - return ClusterPerson.remove({_id: person._id}); - } - ClusterPerson.update({"_id": person._id}, {$pull: {'faces': {"url": url}}}) - } else { - console.log("ClusterPeople: removeFace failed, url="+url+", faceId="+faceId); - } - } - }, - removeFaceByUrl: function(group_id, url){ - var persons = null; - if(group_id){ - persons = ClusterPerson.find({group_id: group_id, "faces.url": url}).fetch(); - } - if(persons){ - console.log("url="+url+", persons="+JSON.stringify(persons)); - for(var i=0; i - #Files are placed in the `/private` folder: - apnsDevCert = Assets.getText 'ios/apn-development/WorkAI_PN_DEV_Cert.pem' - apnsDevKey = Assets.getText 'ios/apn-development/WorkAI_PN_DEV_Key.pem' - optionsDevelopment = - passphrase: '1234' - certData: apnsDevCert - keyData: apnsDevKey - gateway: 'gateway.sandbox.push.apple.com' - - apnsProductionCert = Assets.getText 'ios/apn-production/apns-dev-cert.pem' - apnsProductionKey = Assets.getText 'ios/apn-production/apns-dev-key.pem' - optionsProduction = - passphrase: '123456' - certData: apnsProductionCert - keyData: apnsProductionKey - gateway: 'gateway.push.apple.com' - - pushServer = new CordovaPush 'AIzaSyAeo0xEPBfrUJ5MztClvICNo-ZLIHcM8Zo', optionsProduction - - pushServer.initFeedback() - root.pushServer = pushServer - @pushServer = pushServer diff --git a/server/reset_password_template.coffee b/server/reset_password_template.coffee deleted file mode 100644 index 95ed1fad1..000000000 --- a/server/reset_password_template.coffee +++ /dev/null @@ -1,29 +0,0 @@ -if Meteor.isServer - Accounts.urls.resetPassword = (token)-> - return Meteor.absoluteUrl('reset/' + token); - Meteor.startup ()-> - Accounts.emailTemplates.from = '来了吗APP ' - Accounts.emailTemplates.siteName = '来了吗APP' - - #A Function that takes a user object and returns a String for the subject line of the email. - Accounts.emailTemplates.verifyEmail.subject = (user)-> - return '请验证您的邮件地址' - #A Function that takes a user object and a url, and returns the body text for the email. - #Note: if you need to return HTML instead, use Accounts.emailTemplates.verifyEmail.html - Accounts.emailTemplates.verifyEmail.text = (user, url)-> - return '请点击链接验证您的邮件地址: ' + url - Accounts.emailTemplates.resetPassword.subject = (user)-> - return '您请求重设来了吗APP的登录密码' - Accounts.emailTemplates.resetPassword.text = (user,url)-> - if user.profile and user.profile.fullname and user.profile.fullname isnt '' - displayName = user.profile.fullname - else - displayName = user.fullname - if displayName is undefined - displayName = "您好" - console.log url - token = url.substring(url.lastIndexOf('/')+1, url.length) - newUrl = Meteor.absoluteUrl('reset/' + token) - displayName = displayName + ':\n' +'忘记来了吗APP密码了吗?别着急,请点击以下链接,我们协助您重设密码:\n' - displayName = displayName + newUrl + '\n\n如果这不是您的邮件请忽略,很抱歉打扰您,请原谅。' - return displayName diff --git a/server/server_exception_protection.js b/server/server_exception_protection.js deleted file mode 100644 index ee5a5c5f3..000000000 --- a/server/server_exception_protection.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Created by simba on 9/27/16. - */ - -if (Meteor.isServer){ - process.addListener('uncaughtException', function (err) { - var msg = err.message; - if (err.stack) { - msg += '\n' + err.stack; - } - if (!msg) { - msg = JSON.stringify(err); - } - console.log(msg); - console.trace(); - }); -} \ No newline at end of file diff --git a/server/servermethod_register.coffee b/server/servermethod_register.coffee deleted file mode 100644 index 481307971..000000000 --- a/server/servermethod_register.coffee +++ /dev/null @@ -1,490 +0,0 @@ -if Meteor.isServer - myCrypto = Npm.require "crypto" - aliyun = Meteor.npmRequire "aliyun-sdk" - aliyun_access_key_id = process.env.ALIYUN_ACCESS_KEY_ID - aliyun_access_key_secret = process.env.ALIYUN_ACCESS_KEY_SECRET - @nodemailer = Meteor.npmRequire('nodemailer'); - #if (Meteor.absoluteUrl().toLowerCase().indexOf('host2.tiegushi.com') >= 0) - process.env['HTTP_FORWARDED_COUNT'] = 1 - console.log("process.env.HTTP_FORWARDED_COUNT="+process.env.HTTP_FORWARDED_COUNT); - # 权限验证 - @confirmReporterAuth = (userId)-> - console.log(userId) - user = Meteor.users.findOne({_id: userId}) - return user.profile.reporterSystemAuth - - # 删除阿里云图片 - @delectAliyunPictureObject = (postId)-> - # this.unblock() - oss = new aliyun.OSS({ - accessKeyId: aliyun_access_key_id || 'Vh0snNA4Orv3emBj', - secretAccessKey: aliyun_access_key_secret || 'd7p2eNO8GuMl1GtIZ0at4wPDyED4Nz', - endpoint: 'https://cdn.aliyuncs.com', - apiVersion: '2014-11-11' - }) - post = BackUpPosts.findOne({_id: postId}); - images = [] - if post and post.pub - post.pub.forEach (item)-> - if item.isImage - uri = item.imgUrl.split('/') - filename = uri[uri.length-1] - images.push {key:filename} - oss.deleteObjects({ - Bucket: 'tiegushi', - Delete: {images,Quiet:true} - }, (err,data)-> - console.log(err, data) - ) - # 发送关注成功邮件 - @sendSubscribeAutorEmail = (ownerName,email)-> - text = Assets.getText('email/follower-notify.html') - try - Email.send { - to: email, - from: '故事贴 ', - subject: '成功关注作者:'+ownerName + '', - body: '成功关注作者:'+ownerName + ',我们会不定期的为您推送关注作者的新文章!', - html: text - } - catch error - console.log("sendSubscribeAutorEmail Error===:"+error) - @addToUserFavPosts = (postId,userId)-> - favp = FavouritePosts.findOne({postId: postId, userId: userId}) - if favp - FavouritePosts.update({_id: favp._id}, {$set: {updateAt: new Date()}}) - else - FavouritePosts.insert({postId: postId, userId: userId, createdAt: new Date(), updateAt: new Date()}) - Meteor.startup ()-> - Meteor.methods - "isDeviceInDB": (uuid)-> - try - #return Devices.find({uuid: uuid}).count() > 0 - item = Devices.findOne({uuid: uuid}, {fields:{groupId:1}}) - console.log("isDeviceInDB: item="+JSON.stringify(item)) - if item - return [item] - else - return [] - catch error - console.log('checke device in db Err=', JSON.stringify(error)) - return false - "clusteringFixPersons": (ids, marked_ids)-> - try - result1 = Clustering.update({_id: {$in: ids}},{ - $set:{ - isOneSelf: false, - marked: true - } - },{multi: true}) - - result2 = Clustering.update({_id: {$in: marked_ids}},{ - $set:{marked: true} - },{multi: true}) - return '标记'+(result1 + result2) + '张(其中'+result1 + '为错,'+ result2 + '为对)' - catch error - console.log('clusteringFixPersons Err=',JSON.stringify(error)) - return false - 'clearDiscoverMSG': (userId,postId)-> - if !Match.test(userId, String) or !Match.test(postId, String) - return {msg: 'failed'} - console.log('user='+userId+', post=='+postId) - Feeds.update({followby:userId,checked:false, eventType: {$nin: ['share','personalletter']}},{$set: {checked: true}},{multi: true}) - recommendPosts = Recommends.find({relatedPostId: postId}).fetch() - if recommendPosts and recommendPosts.length > 0 - userLists = [] - recommendPosts.forEach (item)-> - if item.readUsers - userLists = item.readUsers - userLists.push(userId) - Recommends.update({_id:item._id},{$set: {readUsers: userLists}}) - return {msg: 'success'} - "isDeviceInDB": (uuid)-> - try - return Devices.find({uuid: uuid}).count() > 0 - catch error - console.log('checke device in db Err=', JSON.stringify(error)) - return false - 'clearUserBellWaitReadCount': (userId)-> - Feeds.update({followby: userId,isRead:{$exists:false}},{$set:{isRead: true,checked: true}},{multi: true}) - - 'getMoreFavouritePosts': (userId, skip, limit)-> - postIds = [] - FavouritePosts.find({userId: userId}).forEach((item) -> - if !~postIds.indexOf(item.postId) - postIds.push(item.postId) - ) - favouritePosts = Posts.find({_id: {$in: postIds}},{fields:{mainImage:1,addontitle:1,title:1},sort:{createdAt:-1},limit: limit,skip:skip}).fetch() - return favouritePosts - 'updateUserNike': (id, val)-> - Meteor.users.update({_id: id}, {$set: {'profile.fullname': val}}) - return; - 'updateUserSex': (id, val)-> - Meteor.users.update({_id: id}, {$set: {'profile.sex': val}}) - return; - 'updateFollower':(userId,options)-> - Meteor.defer ()-> - try - if options.name - Follower.update({userId:userId},{$set:{'userName':options.name}},{multi: true, upsert:false}) - Follower.update({followerId:userId},{$set:{'followerName':options.name}},{multi: true, upsert:false}) - SimpleChat.GroupUsers.update({user_id:userId},{$set:{'user_name':options.name}},{multi: true, upsert:false}) - if options.icon - Follower.update({userId:userId},{$set:{'userIcon':options.icon}},{multi: true, upsert:false}) - Follower.update({followerId:userId},{$set:{'followerIcon':options.icon}},{multi: true, upsert:false}) - SimpleChat.GroupUsers.update({user_id:userId},{$set:{'user_icon':options.icon}},{multi: true, upsert:false}) - catch error - console.log('Error on updateFollower' + error) - 'profileData': (userId)-> - if !Match.test(userId, String) - return [] - this.unblock() - viewPostIds = [] - viewers = Viewers.find({userId: userId},{limit:10}).forEach((item) -> - if !~viewPostIds.indexOf(item.postId) - viewPostIds.push(item.postId) - ) - console.log(JSON.stringify(viewers)) - recentViewPosts = Posts.find({_id: {$in: viewPostIds}},{fields:{mainImage:1,addontitle:1,title:1},sort:{createdAt:-1},limit: 3}).fetch() - postIds = [] - FavouritePosts.find({userId: userId},{limit:10}).forEach((item) -> - if !~postIds.indexOf(item.postId) - postIds.push(item.postId) - ) - favouritePosts = Posts.find({_id: {$in: postIds}},{fields:{mainImage:1,addontitle:1,title:1},sort:{createdAt:-1},limit: 10}).fetch() - console.log('favouritePosts=='+JSON.stringify(favouritePosts)) - profileData = { - recentViewPosts: recentViewPosts, - favouritePosts: favouritePosts, - mePosts: Posts.find({owner: userId},{fields:{mainImage:1,addontitle:1,title:1},sort:{createdAt:-1},limit: 10}).fetch() - } - return profileData - 'socialData': (postId)-> - if !Match.test(postId, String) - return [] - this.unblock(); - socialData = getSocialDataFromPostId(postId,this.userId) - socialData - - # Reporter START - 'isTrustedUser': (userId)-> - user = Meteor.users.findOne({_id: userId}) - if !user - return {hasUser: false} - else - if user.profile and user.profile.isTrusted - return {isTrusted:user.profile.isTrusted,hasUser: true} - else - return {isTrusted:false,hasUser: true} - # mark as trusted - 'markUserAsTrusted': (userId)-> - user = Meteor.users.findOne({_id: userId}) - if !user - return {noUser: true} - return Meteor.users.update {_id: userId},{ - $set:{'profile.isTrusted': true} - } - 'markUserAsMistrusted': (userId)-> - user = Meteor.users.findOne({_id: userId}) - if !user - return {noUser: true} - return Meteor.users.update {_id: userId},{ - $set:{'profile.isTrusted': false} - } - - 'restoreUser': (userA,userB)-> - if !confirmReporterAuth(userA) - return false - owner = Meteor.users.findOne({_id: userB}) - if owner and owner.profile and owner.profile.fullname - userName = owner.profile.fullname - else - userName = owner.username - reporterLogs.insert({ - userId:owner._id, - userName: userName, - userEmails: owner.emails, - postId: null, - postTitle: null, - postCreatedAt: null, - eventType: '恢复用户', - loginUser: userA, - createdAt: new Date() - }) - LockedUsers.remove({_id: userB}) - 'sendErrorReport':(to, from, subject, text)-> - console.log(to) - console.log(from) - console.log(subject) - console.log(text) - check([from, subject, text], [String]) - this.unblock() - Email.send ({ - to: to, - from: from, - subject: subject, - text: text - }) - "updateUserLanguage": (userId, lang)-> - Meteor.defer ()-> - Meteor.users.update({_id: userId},{$set: {'profile.language': lang}}) - 'httpCall': (method, url, options)-> - url += '?ip='+this.connection.clientAddress - if Meteor.absoluteUrl().toLowerCase().indexOf('host2.tiegushi.com') >= 0 - url += '&server='+Meteor.absoluteUrl(); - #url += '?ip=12.206.217.29' - console.log("Call httpCall, url="+url); - return HTTP.call(method, url, options) - "updataFeedsWithMe": (userId)-> - Meteor.defer ()-> - Feeds.update({followby: userId,isRead:{$exists:false}},{$set:{isRead: true}},{multi: true}) - "feedsMsgSetAsRead": (id)-> - Meteor.defer ()-> - Feeds.update({_id:id},{$set: {isRead:true}}) - 'sendEmailToAdmin':(from, subject, text)-> - to = 'admin@tiegushi.com' - check([from, subject, text], [String]) - this.unblock() - Email.send ({ - to: to, - from: from, - subject: subject, - text: text - }) - - 'updatePushToken':(data)-> - console.log 'updatePushToken with userId: '+data.userId+' token:'+data.token+' type:'+data.type - if data.token is undefined - return - token = data.token - type = data.type - userId = data.userId - pushTokenObj = PushTokens.findOne({type: type,token: token}) - if pushTokenObj is undefined - try - PushTokens.insert(data) - catch error - console.log error - else - try - if pushTokenObj.userId isnt userId and userId isnt '' - Meteor.users.update({_id:userId}, {$set: {'profile.waitReadCount': 0}}); - PushTokens.update({type: type,token: token},{$set:{userId: userId}}) - catch error - console.log error - - "getBCSSigniture": (filename,URI)-> - content = "MBO" + "\n"+"Method=PUT" + "\n"+"Bucket=travelers-km" + "\n"+"Object=/" + filename + "\n" - apiKey = '9Ud6jfxuTwkM0a7G6ZPjXbCe' - SecrectKey = 'zhoMHNUqtmQGgR4Il1GqmZiYoP0pX2AT' - hash = myCrypto.createHmac('sha1', SecrectKey).update(content).digest() - Signture = encodeURIComponent hash.toString('base64') - policy = { - signture: "MBO:"+apiKey+":"+Signture - orignalURI: URI - } - policy - "changeMyPassword": (userId, newPassword)-> - Accounts.setPassword userId, newPassword - "getAliyunWritePolicy": (filename, URI)-> - apiKey = 'Vh0snNA4Orv3emBj' - SecrectKey = 'd7p2eNO8GuMl1GtIZ0at4wPDyED4Nz' - date = new Date() - content = 'PUT\n\nimage/jpeg\n' + date.toGMTString() + '\n' + '/tiegushi/'+filename - hash = myCrypto.createHmac('sha1', SecrectKey).update(content).digest() - Signture = unescape(encodeURIComponent hash.toString('base64')) - #console.log 'Content is ' + content + ' Signture ' + Signture - authheader = "OSS " + apiKey + ":" + Signture - policy = { - orignalURI: URI - date: date.toGMTString() - auth: authheader - acceccURI: 'http://oss.tiegushi.com/'+filename - readURI: 'http://data.tiegushi.com/'+filename - } - policy - "getGeoFromConnection":()-> - clientIp = this.connection.clientAddress - #clientIp = '173.236.169.5' - json = GeoIP.lookup clientIp - #console.log('This connection is from ' + clientIp + ' Lookup result + ' + JSON.stringify(json)) - json - 'readMessage': (to)-> - switch to.type - when "user" - MsgSession.update({userId: this.userId, toUserId: to.id}, {$set: {isRead: true, readTime: new Date(), waitRead: 0}}) - Messages.update({userId: to.id, toUserId: this.userId},$set: {isRead: true, readTime: new Date()}) - when "group" - MsgSession.update({userId: this.userId, toGroupId: to.id}, {$set: {isRead: true, readTime: new Date(), waitRead: 0}}) - Messages.update({'toUsers.userId': this.userId, toGroupId: to.id},$set: {isRead: true, readTime: new Date()}) - when "session" - session = MsgSession.findOne(to.id) - if session.sesType is 'singleChat' - MsgSession.update({userId: this.userId, toUserId: session.toUserId}, {$set: {isRead: true, readTime: new Date(), waitRead: 0}}) - Messages.update({userId: session.toUserId, toUserId: this.userId},$set: {isRead: true, readTime: new Date()}) - else - MsgSession.update({userId: this.userId, toGroupId: session.toGroupId}, {$set: {isRead: true, readTime: new Date(), waitRead: 0}}) - Messages.update({'toUsers.userId': this.userId, toGroupId: session.toGroupId},$set: {isRead: true, readTime: new Date()}) - - 'addAssociatedUserNew': (userInfo)-> - this.unblock() - isEmail = /[a-z0-9-]{1,30}@[a-z0-9-]{1,65}.[a-z]{2,6}/ - # check params - if !this.userId or !userInfo or !userInfo.username or !userInfo.password - return {status: 'ERROR', message: 'Invalid Username'} - # find user - # console.log 'testEmail is ' + isEmail.test(userInfo.username) - if isEmail.test(userInfo.username) is true - userTarget = Accounts.findUserByEmail(userInfo.username) - # console.log 'this User Target is Email ' + userTarget - else - userTarget = Accounts.findUserByUsername(userInfo.username) - # console.log 'this User Target is Username ' + userTarget - if !userTarget - return {status: 'ERROR', message: 'Invalid Username'} - if userTarget._id is this.userId - return {status: 'ERROR', message: 'Can not add their own'} - - # console.log 'userTarget is ' + userTarget - # check passwod - isMatch = false - if userTarget isnt undefined and userTarget isnt null - if userInfo.type is undefined or userInfo.type is null - userInfo.type = '' - if userInfo.token is undefined or userInfo.token is null - userInfo.token = '' - passwordTarget = {digest: userInfo.password, algorithm: 'sha-256'}; - result = Accounts._checkPassword(userTarget, passwordTarget) - isMatch = (result.error is undefined) - unless isMatch - return {status: 'ERROR', message: 'Invalid Password'} - - # check relation - if UserRelation.find({userId: this.userId, toUserId: userTarget._id}).count() > 0 - return {status: 'ERROR', message: 'Exist Associate User'} - - # update token - if userInfo.type and userInfo.token - Meteor.users.update({_id: userTarget._id}, {$set: {type: userInfo.type, token: userInfo.token}}) - - # save relation - # 只记录单向关系 - me = Meteor.users.findOne({_id: this.userId}, {fields: {_id: 1, username: 1, 'profile.fullname': 1, 'profile.icon': 1}}) - UserRelation.insert { - userId: me._id - name: if me.profile and me.profile.fullname then me.profile.fullname else me.username - icon: if me.profile and me.profile.icon then me.profile.icon else '/userPicture.png' - toUserId: userTarget._id - toName: if userTarget.profile and userTarget.profile.fullname then userTarget.profile.fullname else userTarget.username - toIcon: if userTarget.profile and userTarget.profile.icon then userTarget.profile.icon else '/userPicture.png' - createAt: new Date() - } - if UserRelation.find({toUserId: this.userId, userId: userTarget._id}).count() <= 0 - UserRelation.insert { - userId: userTarget._id - name: if userTarget.profile and userTarget.profile.fullname then userTarget.profile.fullname else userTarget.username - icon: if userTarget.profile and userTarget.profile.icon then userTarget.profile.icon else '/userPicture.png' - toUserId: me._id - toName: if me.profile and me.profile.fullname then me.profile.fullname else me.username - toIcon: if me.profile and me.profile.icon then me.profile.icon else '/userPicture.png' - createAt: new Date() - } - - return {status: 'SUCCESS'} - - 'removeAssociatedUserNew': (userId)-> - this.unblock() - if !this.userId or !userId - return false - if UserRelation.find({userId: this.userId, toUserId: userId}).count() <= 0 - return false - - UserRelation.remove({userId: this.userId, toUserId: userId}) - UserRelation.remove({userId: userId, toUserId: this.userId}) - # UserRelation.remove({userId: userId}) - - return true - - 'addAssociatedUser': (userInfo)-> - this.unblock() - Meteor.defer ()-> - try - Meteor.call 'addAssociatedUserNew', userInfo - catch e - - if this.userId is undefined or this.userId is null or userInfo is undefined or userInfo is null - return false - - if userInfo.username is undefined or userInfo.username is null or userInfo.password is undefined or userInfo.password is null - return false - - #this.unblock() - - self = this - - #Meteor.defer ()-> - userTarget = Accounts.findUserByUsername(userInfo.username) - - if userTarget is undefined or userTarget is null - userTarget = Accounts.findUserByEmail(userInfo.username) - #userTarget = Meteor.users.findOne({username: userInfo.username}); - - if userTarget is undefined or userTarget is null - return {status: 'ERROR', message: 'Invalid Username'} - - if userTarget._id is this.userId - return {status: 'ERROR', message: 'Can not add their own'} - - if AssociatedUsers.find($or: [{userIdA: userTarget._id, userIdB: self.userId}, {userIdA: self.userId, userIdB: userTarget._id}]).count() > 0 - return {status: 'ERROR', message: 'Exist Associate User'} - - isMatch = false - if userTarget isnt undefined and userTarget isnt null - if userInfo.type is undefined or userInfo.type is null - userInfo.type = '' - if userInfo.token is undefined or userInfo.token is null - userInfo.token = '' - - passwordTarget = {digest: userInfo.password, algorithm: 'sha-256'}; - result = Accounts._checkPassword(userTarget, passwordTarget) - isMatch = (result.error is undefined) - isMatch && AssociatedUsers.insert({userIdA: self.userId, userIdB: userTarget._id, createdAt: Date.now()}) - if isMatch - Meteor.users.update({_id: userTarget._id}, {$set: {type: userInfo.type, token: userInfo.token}}) - # return - if isMatch - #throw new Meteor.Error 404, "value should be 1, bro" - return {status: 'SUCCESS'} - else - return {status: 'ERROR', message: 'Invalid Password'} - 'checkUserByEmail': (email)-> - return Accounts.findUserByEmail(email) - 'sendResetPasswordEmail':(userId, email)-> - return Accounts.sendResetPasswordEmail(userId, email) - 'removeAssociatedUser': (userId)-> - this.unblock() - Meteor.defer ()-> - try - Meteor.call 'removeAssociatedUserNew', userId - catch e - - if this.userId is undefined or this.userId is null or userId is undefined or userId is null - return false - self = this - Meteor.defer ()-> - AssociatedUsers.remove($or: [{userIdA: userId, userIdB: self.userId}, {userIdA: self.userId, userIdB: userId}]) - return - return - 'addBlackList': (blacker, blackBy)-> - BlackList.insert({blacker: [blacker],blackBy: blackBy}) - 'refreshAssociatedUserToken': (data)-> - return - 'updateGroupReportEmails':(groupId,emails)-> - SimpleChat.Groups.update({_id:groupId},{$set:{report_emails:emails}}) - 'updateGroupUserReportEmails':(groupId,userId,emails)-> - SimpleChat.GroupUsers.update({group_id:groupId,user_id:userId},{$set:{report_emails:emails}}) - 'updateGroupName':(groupId,name)-> - SimpleChat.Groups.update({_id:groupId},{$set:{name:name}}) - SimpleChat.GroupUsers.update({group_id:groupId},{$set:{group_name:name}},{multi: true}) - 'enableHomeAI':()-> - return withEnableHomeAI; diff --git a/server/set_no_login_expirations.coffee b/server/set_no_login_expirations.coffee deleted file mode 100644 index f5c3b5cb4..000000000 --- a/server/set_no_login_expirations.coffee +++ /dev/null @@ -1,3 +0,0 @@ -if Meteor.isServer - Meteor.startup -> - Accounts.config({loginExpirationInDays :null}) \ No newline at end of file diff --git a/server/sitemaps.coffee b/server/sitemaps.coffee deleted file mode 100644 index 3b94e55f3..000000000 --- a/server/sitemaps.coffee +++ /dev/null @@ -1,12 +0,0 @@ -sitemaps.add '/sitemap.xml', ()-> - maps = [] - posts = Posts.find({}, {limit: 50000}).fetch() - - _.each posts, (post)-> - maps.unshift { - page: 'http://www.tiegushi.com/posts/' + post._id - lastmod: post.createdAt - } - return - - return maps diff --git a/server/test_email_url.coffee b/server/test_email_url.coffee deleted file mode 100644 index 2974d8d95..000000000 --- a/server/test_email_url.coffee +++ /dev/null @@ -1,8 +0,0 @@ - -if Meteor.isServer - Meteor.startup ()-> - if (not process.env.MAIL_URL) or process.env.MAIL_URL is '' - console.log "----setup smtp server---" - process.env.MAIL_URL = 'smtp://postmaster%40tiegushi.com:a7e104e236965118d8f1bd3268f36d8c@smtp.mailgun.org:587' - #process.env.MAIL_URL = 'smtp://postmaster%40email.tiegushi.com:1b3e27a9f18007d6fedf46c9faed519a@smtp.mailgun.org:587' - #process.env.MAIL_URL = 'smtp://notify%40mail.tiegushi.com:Actiontec753951@smtpdm.aliyun.com:465' diff --git a/server/veworld_server.js b/server/veworld_server.js deleted file mode 100644 index 45bbad018..000000000 --- a/server/veworld_server.js +++ /dev/null @@ -1,21 +0,0 @@ -if(Meteor.isServer){ - Meteor.startup(function(){ - Meteor.methods({ - 'getOfficesOnlineInfo': function(ids,date){ - var lists = []; - ids.forEach(function(item){ - var group = SimpleChat.Groups.findOne({_id: item}); - if(group){ - var count = WorkStatus.find({date:date,group_id: item,status: 'in'}).count(); - lists.push({ - _id: group._id, - name: group.name, - value: count - }); - } - }); - return lists; - } - }) - }); -} \ No newline at end of file