+ 监控组
+ -
+ {{#each officialImages}}
+
- + + + {{/each}} + {{#if mainImage}} +
- + + + {{/if}} +
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/.travis.yml b/.travis.yml
index 1ad395d75..8c3367f18 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,6 +6,8 @@ android:
- tools
- build-tools-22.0.1
- android-22
+ - build-tools-23.0.1
+ - android-23
addons:
apt:
sources:
@@ -17,24 +19,33 @@ env:
- secure: P2clWXCVh5m3rDBLKVO+t1A3E6lpMhIZuEQ3EfYwr5YNLR3rHCsGQuuHhBVcdogR1c2+q2BgGmidQP9A25rY+W/peSC4qwWlpyKQ4cvkyhUGwHYfArB/lCehQr4DdrWUjrrvw/glSvdLpEqEOqVMnvpibwTiHtleowjx5SCUNz0LlvbGYqP78EhYB6NQHUm1osEYrEQ4cEPiYPVOW/erWlRqxeU2AXfgHvq+Bbmbk2iZDRhX2vo3vCDirmANgb1VXOidP2VlPCfyO9euFUvKBMKaHH+3Tvdq9YCv3Oael/2YzJOR4P2zpmmM8JBnQlPcGISiE8/B4JJGimaQ7D8R0tDgHCbYDK6PC36Ux/1idCa+TnQUTyG3kkOb/MNG5e+rhNXU0e82cmcDOJJBBWAMj5HmOzSyRb+BRQu4j8Fi5/9cezBQEMuMU6F4N5b6FvUrPeUmMNOsKL+djWlcwuLF7XlyDn0vggyt5tCNkGqQz/rGiLQKYEZ0Xem0uzxS5EdvUc24KRtyZ5VJbPmuiJnrJIC2WWoj4RpAh1hd5h6te8ydzpUCTTKrzC4Na+ffDXNFVvK8Q2/nd9FN6kwnbkI5ogTlywqbSYLzHc3n3ISHu/teHeqGJN1dYagzM3A3p8MjgK4SK3+5r7jj+E6b4VBlpdwqNri97zK2ikn+q/K5qCc=
- secure: lDfLSzTHNQ7AYPcj5TCk+1PgVdBJW3NXvWaVgkPkunWG8wvQz8EajsQunKtspEvU9Rgwl1E7nKJlx2XpLsgoWp18wqlPBCywWl71Pto10BggP5eXw4m+t6pWZ/B8nYQYqydwsmDlwQ+uQV4SzSEmDYTP67EyFCNJ88Yebowd0RdqJu2SRQ+HmEmgUPCT24NJqLb5sbjujGjnEsTm0/+CYwV03/3vX7zbzVck2DY5icOX9QjwneUcKmZL1cHK+hfHME7fpG+fjE25fZ6VH+24GtcUr7VwG1kABeciBKdpse5Juc4G91a3Emzu3IVFZlwOI4YWwtgRIU8/kJlaAx7C1NAIeujOmnqXRJPbcMKlX8EM/1ox+xo4OcWN2oPL7moojFzbEhiG2HcZYWHa7mRVpEo4Ohj7DXXlNghAYH2lQYqY3g/xRePIqmMM3UNwaZQ3MLj9KxneD0V/wKTMh12FBiag2FYllRbkvt7FkweO89MMe3lnklswZTKTB8ybElsouZlhBLdZii4fafvVzFqM4XNQnrBz+YDzkaDvom403lpH6wDQyPX3SNoyGSsjXk5CsqriMs9kod60qGhRIm+Yy+pmwKBvnHXj2453QwZD+3U2y5r+i7bn/KRkrYPZd0AhAgqJ4raEqWc00pNWAYC3Opq929zOzhbCsgMEAmtieX4=
before_script:
-- curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
-- sudo apt-get install -y nodejs
-- node --version
-- sudo npm install -g cordova
-- curl https://install.meteor.com?release=1.4.1.2 | /bin/sh
-- export PATH=$HOME/.meteor:$PATH
-- meteor npm install
-script: ls -alh $HOME/
+ #- curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
+ #- sudo apt-get install -y nodejs
+ #- node --version
+ #- sudo npm install -g cordova
+ #- curl https://install.meteor.com | /bin/sh
+ #- export PATH=$HOME/.meteor:$PATH
+ #- meteor npm install
+ #- cd $HOME
+ #- mkdir .gradle/wrapper/dists && cd .gradle/wrapper/dists
+ #- wget https://downloads.gradle.org/distributions/gradle-2.2.1-all.zip
+script:
+ - ls -alh $HOME/
+ - docker run --rm -it -e APP_SERVER=http://10.168.1.221:3000/ -v $TRAVIS_BUILD_DIR:/app -v $(pwd):/build shareai/meteor-android-build
+ - ls ./ -alh
+ - ls ./android -alh
+ #- meteor build $HOME --server=$API_SERVER_ADDRESS
+ #- cat $TRAVIS_BUILD_DIR/.meteor/local/cordova-build/platforms/android/build.gradle
+ #- ls $HOME/ -alh
+ #- ls $HOME/android -alh
services:
-- mongodb
+ - docker
+ - mongodb
before_install:
- openssl aes-256-cbc -K $encrypted_66e97601dc46_key -iv $encrypted_66e97601dc46_iv
-in .keystore.enc -out .keystore -d
before_deploy:
-- meteor build $HOME --server=$API_SERVER_ADDRESS
- cp $TRAVIS_BUILD_DIR/.keystore $HOME
-- find $HOME/build/
-- ls $HOME/build/outputs/apk/ -alh
- jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 $HOME/android/release-unsigned.apk
sharpai-app -keystore $HOME/.keystore -storepass $storepass -keypass $keypass
- "${ANDROID_HOME}/build-tools/22.0.1/zipalign 4 $HOME/android/release-unsigned.apk
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/client/lib/DateExtend.js b/client/lib/DateExtend.js new file mode 100644 index 000000000..70f483705 --- /dev/null +++ b/client/lib/DateExtend.js @@ -0,0 +1,130 @@ +Date.prototype.parseDate = function (pattern) { + /** + * 分隔符只能是 - / 单个空格 : . + * 年 YYYY || YY + * 月 MM || M + * 日 DD || DD + * 时 hh || h + * 分 mm || m + * 秒 ss || s + * 毫秒 ms || msss + * + * example: + * d = new Date() + * d.parseDate('YYYY-MM-DD') // "2017-13-06" + */ + var self = this; + var parse = function (date) { + var YYYY = date.getFullYear(), + YY = ('' + YYYY).substr(2), + M = date.getMonth() + 1, + MM = ('0' + M).slice(-2), + D = date.getDate(), + DD = ('0' + D).slice(-2), + h = (date.getHours() > 12)?(date.getHours()-12):date.getHours() , + // hh = ('0' + h).slice(-2), + hh = (date.getHours() > 9)?date.getHours():'0' + date.getHours(), + m = date.getMinutes(), + mm = ('0' + m).slice(-2), + s = date.getSeconds(), + ss = ('0' + s).slice(-2), + ms = date.getMilliseconds(), + msss = ('00' + ms).slice(-3); + return { + YYYY: YYYY, + YY: YY, + M: M, + MM: MM, + D: D, + DD: DD, + h: h, + hh: hh, + m: m, + mm: mm, + s: s, + ss: ss, + ms: ms, + msss: msss + } + }; + var format = function (date, pattern) { + var result = ''; + var dateObj = parse(date); + var splitArr = pattern.match(/(\w+((?=[\-\/ \:\.])|$))|([\-\/ \:\.]+)/g); + var acceptReg = /YYYY|YY|M|MM|D|DD|h|hh|m|mm|s|ss|ms|msss/i; + for (var i = 0, len = splitArr.length, item = ''; i < len; i++) { + item = splitArr[i]; + if (acceptReg.test(item)) { + item = dateObj[item]; + } + result += item; + } + return result; + }; + return format(self, pattern); +} + +Date.prototype.shortTime = function (time_offset, only_H_S) { + /** + * 0:00—6:00凌晨,6:00—11:00上午,11:00—13:00中午,13:00—16:00下午,16:00—18:00傍晚,18:00—24:00晚上 + * exmaple: + * d = new Date() + * d.shortTime() // "今天 中午 12:27" + */ + + + function DateTimezone(d, time_offset) { + if (time_offset == undefined){ + if (d.getTimezoneOffset() == 420){ + time_offset = -7 + }else { + time_offset = 8 + } + } + // 取得 UTC time + var utc = d.getTime() + (d.getTimezoneOffset() * 60000); + var local_now = new Date(utc + (3600000*time_offset)) + var today_now = new Date(local_now.getFullYear(), local_now.getMonth(), local_now.getDate(), + local_now.getHours(), local_now.getMinutes()); + + return today_now; + } + + var self = this; + var now = DateTimezone(new Date(), time_offset); + var result = ''; + var self = DateTimezone(this, time_offset); + + var DayDiff = now.getDate() - self.getDate(); + var 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'); + if(only_H_S){ + return self.parseDate('hh:mm'); + } + return result; +} \ No newline at end of file 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/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/mobile-config.js b/mobile-config.js index 14fe7ae22..440b9aaeb 100644 --- a/mobile-config.js +++ b/mobile-config.js @@ -1,9 +1,88 @@ App.info({ id: 'org.sharpai.everywhere', version: '2.0.2', - name: 'sharpai-web', + name: 'sharpai', description: 'Share everything with everyone', author: 'hotShare Design Team', email: '', website: '' }); + +App.setPreference('SplashMaintainAspectRatio', true); +App.setPreference('KeyboardDisplayRequiresUserAction', false); +App.setPreference('StatusBarOverlaysWebView', false); +App.setPreference('orientation', 'portrait'); +// App.setPreference('StatusBarBackgroundColor', '#000000'); +App.setPreference('StatusBarBackgroundColor','#37a7fe'); +// App.setPreference('StatusBarStyle','blacktranslucent'); +App.setPreference('StatusBarStyle','lightcontent'); +App.setPreference('AutoHideSplashScreen', false); +App.setPreference('AndroidPersistentFileLocation','Internal'); +App.setPreference('iosPersistentFileLocation','Library'); +App.accessRule('*'); +App.accessRule('http://*'); +App.accessRule('https://*'); +App.accessRule('*', { type: 'navigation' } ); +App.accessRule('data:*', { type: 'navigation' }); + +App.icons({ + //'iphone': 'resource/icon_57.png', + 'iphone_2x': 'resource/icon_120.png', + 'iphone_3x': 'resource/icon_180.png', + 'ipad': 'resource/icon_76.png', + 'ipad_2x': 'resource/icon_152.png', + 'ios_settings': 'resource/icon_29.png', + 'ios_settings_2x': 'resource/icon_58.png', + 'ios_settings_3x': 'resource/icon_87.png', + //'android_ldpi': 'resource/icon_36.png', + 'android_mdpi': 'resource/icon_48.png', + 'android_hdpi': 'resource/icon_72.png', + 'android_xhdpi': 'resource/icon_96.png', + 'android_xxhdpi': 'resource/icon.png', + 'android_xxxhdpi': 'resource/icon_192.png' +}); + +App.launchScreens({ + //'iphone': 'resource/splash_768_1024.png', + 'iphone_2x': 'resource/splash_theme_640_960.png', + 'iphone5': 'resource/splash_theme_640_1136.png', + 'iphone6': 'resource/splash_theme_750_1334.png', + 'iphone6p_portrait': 'resource/splash_theme_1242_2208.png', + 'ipad_portrait': 'resource/splash_theme_768_1024.png', + 'ipad_portrait_2x': 'resource/splash_theme_1536_2048.png', + //'android_ldpi_portrait': 'resource/splash.png', + 'android_mdpi_portrait': 'resource/splash_theme_320_470.png', + 'android_hdpi_portrait': 'resource/splash_theme_480_640.png', + 'android_xhdpi_portrait': 'resource/splash_theme_720_960.png', + 'android_xxhdpi_portrait': 'resource/splash_theme_1080_1440.png' +}); + +App.configurePlugin('cordova-plugin-x-socialsharing', { + APP_ID: 'YOUR_API_ID' +}); + +App.configurePlugin('com.leon.cordova.wechat', { + APP_ID: 'YOUR_API_ID', + QQ_APP_ID: 'YOUR_APP_ID' +}); +App.configurePlugin('com.share.wechatShare', { + APP_ID: 'YOUR_API_ID', +}); + +App.configurePlugin('org.zy.yuancheng.qq', { + QQ_APP_ID: 'YOUR_APP_ID' +}); + +App.configurePlugin('org.hotshare.baidutts', { + API_KEY: 'YOUR_API_KEY', + API_SECRET: 'YOUR_API_SECRET', + APP_ID: 'YOUR_APP_ID' +}); + +App.configurePlugin('jpush-phonegap-plugin', { + APP_KEY: 'YOUR_API_KEY', + CHANNEL: 'developer-default' +}); +App.configurePlugin('phonegap-plugin-push', { + SENDER_ID: 'NOTUSEDFORNOW' +}); 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+++++
++ ++ ++ + +++ +{{_ "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}} ++ + +
+