Browse Source

Merge branch 'master' of https://github.com/wekan/wekan into lib-change

Romulus Urakagi Tsai 5 years ago
parent
commit
4b196d5378
100 changed files with 11396 additions and 914 deletions
  1. 1 1
      .devcontainer/Dockerfile
  2. 1 0
      .eslintignore
  3. 1 0
      .eslintrc.json
  4. 2 0
      .github/ISSUE_TEMPLATE.md
  5. 17 0
      .github/workflows/dockerimage.yml
  6. 1 0
      .meteor/.finished-upgraders
  7. 6 5
      .meteor/packages
  8. 1 1
      .meteor/release
  9. 27 27
      .meteor/versions
  10. 1 0
      .prettierignore
  11. 20 0
      .snap-meteor-1.8/.meteor/.finished-upgraders
  12. 2 0
      .snap-meteor-1.8/.meteor/.gitignore
  13. 7 0
      .snap-meteor-1.8/.meteor/.id
  14. 0 0
      .snap-meteor-1.8/.meteor/cordova-plugins
  15. 99 0
      .snap-meteor-1.8/.meteor/packages
  16. 2 0
      .snap-meteor-1.8/.meteor/platforms
  17. 1 0
      .snap-meteor-1.8/.meteor/release
  18. 198 0
      .snap-meteor-1.8/.meteor/versions
  19. 914 0
      .snap-meteor-1.8/cfs_access-point.txt
  20. 238 0
      .snap-meteor-1.8/export.js
  21. 155 0
      .snap-meteor-1.8/future/snapcraft.yaml
  22. 584 0
      .snap-meteor-1.8/ldap.js
  23. 149 0
      .snap-meteor-1.8/oidc_server.js
  24. 5184 0
      .snap-meteor-1.8/package-lock.json
  25. 73 0
      .snap-meteor-1.8/package.json
  26. 47 65
      .snap-meteor-1.8/snapcraft.yaml
  27. 853 0
      .snap-meteor-1.8/wekanCreator.js
  28. 3 3
      .travis.yml
  29. 444 5
      CHANGELOG.md
  30. 5 5
      Dockerfile
  31. 1 1
      Stackerfile.yml
  32. 3 3
      client/components/activities/activities.jade
  33. 1 1
      client/components/activities/activities.styl
  34. 0 3
      client/components/activities/comments.js
  35. 20 0
      client/components/activities/comments.styl
  36. 52 13
      client/components/boards/boardBody.js
  37. 64 28
      client/components/boards/boardHeader.jade
  38. 32 21
      client/components/boards/boardHeader.js
  39. 16 13
      client/components/cards/attachments.jade
  40. 2 1
      client/components/cards/cardDate.js
  41. 301 174
      client/components/cards/cardDetails.jade
  42. 101 1
      client/components/cards/cardDetails.js
  43. 31 1
      client/components/cards/cardDetails.styl
  44. 3 1
      client/components/cards/checklists.jade
  45. 9 3
      client/components/cards/checklists.js
  46. 9 8
      client/components/cards/minicard.jade
  47. 23 3
      client/components/cards/minicard.js
  48. 3 1
      client/components/cards/subtasks.jade
  49. 6 3
      client/components/cards/subtasks.js
  50. 56 20
      client/components/lists/list.js
  51. 16 22
      client/components/lists/listBody.js
  52. 40 17
      client/components/lists/listHeader.jade
  53. 21 7
      client/components/lists/listHeader.js
  54. 3 0
      client/components/main/header.styl
  55. 25 12
      client/components/rules/actions/boardActions.jade
  56. 25 5
      client/components/rules/actions/boardActions.js
  57. 6 2
      client/components/settings/informationBody.jade
  58. 54 18
      client/components/settings/peopleBody.jade
  59. 1 1
      client/components/settings/peopleBody.styl
  60. 19 12
      client/components/settings/settingBody.jade
  61. 0 8
      client/components/settings/settingBody.js
  62. 4 1
      client/components/settings/settingBody.styl
  63. 188 26
      client/components/sidebar/sidebar.jade
  64. 399 4
      client/components/sidebar/sidebar.js
  65. 1 1
      client/components/sidebar/sidebar.styl
  66. 30 24
      client/components/sidebar/sidebarArchives.jade
  67. 9 0
      client/components/sidebar/sidebarArchives.js
  68. 8 7
      client/components/sidebar/sidebarFilters.jade
  69. 10 1
      client/components/swimlanes/swimlaneHeader.js
  70. 32 30
      client/components/swimlanes/swimlanes.jade
  71. 75 20
      client/components/swimlanes/swimlanes.js
  72. 49 13
      client/components/swimlanes/swimlanes.styl
  73. 1 0
      client/components/users/userAvatar.jade
  74. 47 18
      client/components/users/userHeader.jade
  75. 82 11
      client/components/users/userHeader.js
  76. 28 0
      client/lib/keyboard.js
  77. 8 3
      client/lib/textComplete.js
  78. 66 0
      client/lib/utils.js
  79. 7 3
      config/accounts.js
  80. 32 8
      docker-compose.yml
  81. 4 4
      fix-download-unicode/cfs_access-point.txt
  82. 1 1
      helm/wekan/requirements.yaml
  83. 1 1
      helm/wekan/templates/_helpers.tpl
  84. 1 1
      helm/wekan/templates/ingress.yaml
  85. 9 2
      i18n/ar.i18n.json
  86. 9 2
      i18n/bg.i18n.json
  87. 9 2
      i18n/br.i18n.json
  88. 10 3
      i18n/ca.i18n.json
  89. 15 8
      i18n/cs.i18n.json
  90. 9 2
      i18n/da.i18n.json
  91. 13 6
      i18n/de.i18n.json
  92. 9 2
      i18n/el.i18n.json
  93. 9 2
      i18n/en-GB.i18n.json
  94. 9 2
      i18n/en.i18n.json
  95. 37 30
      i18n/eo.i18n.json
  96. 9 2
      i18n/es-AR.i18n.json
  97. 10 3
      i18n/es.i18n.json
  98. 9 2
      i18n/eu.i18n.json
  99. 128 121
      i18n/fa.i18n.json
  100. 40 33
      i18n/fi.i18n.json

+ 1 - 1
.devcontainer/Dockerfile

@@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive
 
 
 ENV \
 ENV \
     DEBUG=false \
     DEBUG=false \
-    NODE_VERSION=8.16.1 \
+    NODE_VERSION=8.17.0 \
     METEOR_RELEASE=1.8.1 \
     METEOR_RELEASE=1.8.1 \
     USE_EDGE=false \
     USE_EDGE=false \
     METEOR_EDGE=1.5-beta.17 \
     METEOR_EDGE=1.5-beta.17 \

+ 1 - 0
.eslintignore

@@ -1 +1,2 @@
 packages/*
 packages/*
+.snap-meteor-1.8/*

+ 1 - 0
.eslintrc.json

@@ -145,6 +145,7 @@
     "allowIsBoardMemberByCard": true,
     "allowIsBoardMemberByCard": true,
     "allowIsBoardMemberCommentOnly": true,
     "allowIsBoardMemberCommentOnly": true,
     "allowIsBoardMemberNoComments": true,
     "allowIsBoardMemberNoComments": true,
+    "allowIsBoardMemberWorker": true,
     "Emoji": true,
     "Emoji": true,
     "Checklists": true,
     "Checklists": true,
     "Settings": true,
     "Settings": true,

+ 2 - 0
.github/ISSUE_TEMPLATE.md

@@ -2,6 +2,8 @@
 
 
 Add these issues to elsewhere:
 Add these issues to elsewhere:
 - Snap: https://github.com/wekan/wekan-snap/issues
 - Snap: https://github.com/wekan/wekan-snap/issues
+- LDAP: https://github.com/wekan/wekan-ldap/issues
+- UCS: https://github.com/wekan/univention/issues
 
 
 Other Wekan issues can be added here.
 Other Wekan issues can be added here.
 
 

+ 17 - 0
.github/workflows/dockerimage.yml

@@ -0,0 +1,17 @@
+name: Docker Image CI
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v1
+    - name: Build the Docker image
+      run: docker build . --file Dockerfile --tag wekan:$(date +%s)

+ 1 - 0
.meteor/.finished-upgraders

@@ -17,3 +17,4 @@ notices-for-facebook-graph-api-2
 1.4.3-split-account-service-packages
 1.4.3-split-account-service-packages
 1.5-add-dynamic-import-package
 1.5-add-dynamic-import-package
 1.7-split-underscore-from-meteor-base
 1.7-split-underscore-from-meteor-base
+1.8.3-split-jquery-from-blaze

+ 6 - 5
.meteor/packages

@@ -6,9 +6,9 @@
 meteor-base@1.4.0
 meteor-base@1.4.0
 
 
 # Build system
 # Build system
-ecmascript@0.12.4
-standard-minifier-css@1.5.3
-standard-minifier-js@2.4.1
+ecmascript@0.14.0
+standard-minifier-css@1.6.0
+standard-minifier-js@2.6.0
 mquandalle:jade
 mquandalle:jade
 
 
 # Polyfills
 # Polyfills
@@ -22,7 +22,7 @@ dburles:collection-helpers
 idmontie:migrations
 idmontie:migrations
 matb33:collection-hooks
 matb33:collection-hooks
 matteodem:easy-search
 matteodem:easy-search
-mongo@1.6.2
+mongo@1.8.0
 mquandalle:collection-mutations
 mquandalle:collection-mutations
 
 
 # Account system
 # Account system
@@ -75,7 +75,7 @@ horka:swipebox
 dynamic-import@0.5.1
 dynamic-import@0.5.1
 staringatlights:fast-render
 staringatlights:fast-render
 
 
-accounts-password@1.5.1
+accounts-password@1.5.2
 cfs:gridfs
 cfs:gridfs
 rzymek:fullcalendar
 rzymek:fullcalendar
 momentjs:moment@2.22.2
 momentjs:moment@2.22.2
@@ -97,3 +97,4 @@ percolate:synced-cron
 easylogic:summernote
 easylogic:summernote
 cfs:filesystem
 cfs:filesystem
 ostrio:files
 ostrio:files
+ostrio:cookies

+ 1 - 1
.meteor/release

@@ -1 +1 @@
-METEOR@1.8.1
+METEOR@1.9

+ 27 - 27
.meteor/versions

@@ -1,7 +1,7 @@
 3stack:presence@1.1.2
 3stack:presence@1.1.2
-accounts-base@1.4.4
+accounts-base@1.5.0
 accounts-oauth@1.1.16
 accounts-oauth@1.1.16
-accounts-password@1.5.1
+accounts-password@1.5.3
 aldeed:collection2@2.10.0
 aldeed:collection2@2.10.0
 aldeed:collection2-core@1.2.0
 aldeed:collection2-core@1.2.0
 aldeed:schema-deny@1.1.0
 aldeed:schema-deny@1.1.0
@@ -12,18 +12,18 @@ allow-deny@1.1.0
 arillo:flow-router-helpers@0.5.2
 arillo:flow-router-helpers@0.5.2
 audit-argument-checks@1.0.7
 audit-argument-checks@1.0.7
 autoupdate@1.6.0
 autoupdate@1.6.0
-babel-compiler@7.3.4
-babel-runtime@1.3.0
+babel-compiler@7.5.1
+babel-runtime@1.5.0
 base64@1.0.12
 base64@1.0.12
 binary-heap@1.0.11
 binary-heap@1.0.11
-blaze@2.3.3
+blaze@2.3.4
 blaze-tools@1.0.10
 blaze-tools@1.0.10
 boilerplate-generator@1.6.0
 boilerplate-generator@1.6.0
 browser-policy-common@1.0.11
 browser-policy-common@1.0.11
 browser-policy-framing@1.1.0
 browser-policy-framing@1.1.0
 caching-compiler@1.2.1
 caching-compiler@1.2.1
 caching-html-compiler@1.1.3
 caching-html-compiler@1.1.3
-callback-hook@1.1.0
+callback-hook@1.3.0
 cfs:access-point@0.1.49
 cfs:access-point@0.1.49
 cfs:base-package@0.0.30
 cfs:base-package@0.0.30
 cfs:collection@0.5.5
 cfs:collection@0.5.5
@@ -52,16 +52,16 @@ ddp@1.4.0
 ddp-client@2.3.3
 ddp-client@2.3.3
 ddp-common@1.4.0
 ddp-common@1.4.0
 ddp-rate-limiter@1.0.7
 ddp-rate-limiter@1.0.7
-ddp-server@2.3.0
+ddp-server@2.3.1
 deps@1.0.12
 deps@1.0.12
 diff-sequence@1.1.1
 diff-sequence@1.1.1
 dynamic-import@0.5.1
 dynamic-import@0.5.1
 easylogic:summernote@0.8.8
 easylogic:summernote@0.8.8
-ecmascript@0.12.7
+ecmascript@0.14.1
 ecmascript-runtime@0.7.0
 ecmascript-runtime@0.7.0
-ecmascript-runtime-client@0.8.0
-ecmascript-runtime-server@0.7.1
-ejson@1.1.0
+ecmascript-runtime-client@0.10.0
+ecmascript-runtime-server@0.9.0
+ejson@1.1.1
 email@1.2.3
 email@1.2.3
 es5-shim@4.8.0
 es5-shim@4.8.0
 fastclick@1.0.13
 fastclick@1.0.13
@@ -82,7 +82,7 @@ kadira:dochead@1.5.0
 kadira:flow-router@2.12.1
 kadira:flow-router@2.12.1
 kenton:accounts-sandstorm@0.7.0
 kenton:accounts-sandstorm@0.7.0
 konecty:mongo-counter@0.0.5_3
 konecty:mongo-counter@0.0.5_3
-lamhieu:meteorx@2.0.1
+lamhieu:meteorx@2.1.1
 lamhieu:unblock@1.0.0
 lamhieu:unblock@1.0.0
 launch-screen@1.1.1
 launch-screen@1.1.1
 livedata@1.0.18
 livedata@1.0.18
@@ -91,7 +91,7 @@ logging@1.1.20
 lucasantoniassi:accounts-lockout@1.0.0
 lucasantoniassi:accounts-lockout@1.0.0
 matb33:collection-hooks@0.9.1
 matb33:collection-hooks@0.9.1
 matteodem:easy-search@1.6.4
 matteodem:easy-search@1.6.4
-mdg:meteor-apm-agent@3.2.3
+mdg:meteor-apm-agent@3.2.5
 mdg:validation-error@0.5.1
 mdg:validation-error@0.5.1
 meteor@1.9.3
 meteor@1.9.3
 meteor-base@1.4.0
 meteor-base@1.4.0
@@ -101,16 +101,16 @@ meteorhacks:collection-utils@1.2.0
 meteorhacks:picker@1.0.3
 meteorhacks:picker@1.0.3
 meteorhacks:subs-manager@1.6.4
 meteorhacks:subs-manager@1.6.4
 meteorspark:util@0.2.0
 meteorspark:util@0.2.0
-minifier-css@1.4.2
-minifier-js@2.4.1
+minifier-css@1.5.0
+minifier-js@2.6.0
 minifiers@1.1.8-faster-rebuild.0
 minifiers@1.1.8-faster-rebuild.0
 minimongo@1.4.5
 minimongo@1.4.5
 mobile-status-bar@1.0.14
 mobile-status-bar@1.0.14
-modern-browsers@0.1.4
-modules@0.13.0
-modules-runtime@0.10.3
+modern-browsers@0.1.5
+modules@0.15.0
+modules-runtime@0.12.0
 momentjs:moment@2.24.0
 momentjs:moment@2.24.0
-mongo@1.6.3
+mongo@1.8.1
 mongo-decimal@0.1.1
 mongo-decimal@0.1.1
 mongo-dev-server@1.1.0
 mongo-dev-server@1.1.0
 mongo-id@1.0.7
 mongo-id@1.0.7
@@ -127,7 +127,7 @@ mquandalle:mousetrap-bindglobal@0.0.1
 mquandalle:perfect-scrollbar@0.6.5_2
 mquandalle:perfect-scrollbar@0.6.5_2
 msavin:usercache@1.8.0
 msavin:usercache@1.8.0
 npm-bcrypt@0.9.3
 npm-bcrypt@0.9.3
-npm-mongo@3.1.2
+npm-mongo@3.3.0
 oauth@1.2.8
 oauth@1.2.8
 oauth2@1.2.1
 oauth2@1.2.1
 observe-sequence@1.0.16
 observe-sequence@1.0.16
@@ -135,11 +135,11 @@ ongoworks:speakingurl@1.1.0
 ordered-dict@1.1.0
 ordered-dict@1.1.0
 ostrio:cookies@2.5.0
 ostrio:cookies@2.5.0
 ostrio:files@1.13.0
 ostrio:files@1.13.0
-peerlibrary:assert@0.2.5
+peerlibrary:assert@0.3.0
 peerlibrary:base-component@0.16.0
 peerlibrary:base-component@0.16.0
 peerlibrary:blaze-components@0.15.1
 peerlibrary:blaze-components@0.15.1
-peerlibrary:computed-field@0.9.0
-peerlibrary:reactive-field@0.5.0
+peerlibrary:computed-field@0.10.0
+peerlibrary:reactive-field@0.6.0
 percolate:synced-cron@1.3.2
 percolate:synced-cron@1.3.2
 promise@0.11.2
 promise@0.11.2
 raix:eventemitter@0.1.3
 raix:eventemitter@0.1.3
@@ -163,13 +163,13 @@ simple:json-routes@2.1.0
 simple:rest-accounts-password@1.1.2
 simple:rest-accounts-password@1.1.2
 simple:rest-bearer-token-parser@1.0.1
 simple:rest-bearer-token-parser@1.0.1
 simple:rest-json-error-handler@1.0.1
 simple:rest-json-error-handler@1.0.1
-socket-stream-client@0.2.2
+socket-stream-client@0.2.3
 softwarerero:accounts-t9n@1.3.11
 softwarerero:accounts-t9n@1.3.11
 spacebars@1.0.15
 spacebars@1.0.15
 spacebars-compiler@1.1.3
 spacebars-compiler@1.1.3
 srp@1.0.12
 srp@1.0.12
-standard-minifier-css@1.5.3
-standard-minifier-js@2.4.1
+standard-minifier-css@1.6.0
+standard-minifier-js@2.6.0
 staringatlights:fast-render@3.2.0
 staringatlights:fast-render@3.2.0
 staringatlights:inject-data@2.3.0
 staringatlights:inject-data@2.3.0
 tap:i18n@1.8.2
 tap:i18n@1.8.2
@@ -187,7 +187,7 @@ useraccounts:core@1.14.2
 useraccounts:flow-routing@1.14.2
 useraccounts:flow-routing@1.14.2
 useraccounts:unstyled@1.14.2
 useraccounts:unstyled@1.14.2
 verron:autosize@3.0.8
 verron:autosize@3.0.8
-webapp@1.7.4
+webapp@1.8.2
 webapp-hashing@1.0.9
 webapp-hashing@1.0.9
 wekan-accounts-cas@0.1.0
 wekan-accounts-cas@0.1.0
 wekan-accounts-oidc@1.0.10
 wekan-accounts-oidc@1.0.10

+ 1 - 0
.prettierignore

@@ -5,3 +5,4 @@ node_modules/
 .vscode/
 .vscode/
 .tx/
 .tx/
 .github/
 .github/
+.snap-meteor-1.8/

+ 20 - 0
.snap-meteor-1.8/.meteor/.finished-upgraders

@@ -0,0 +1,20 @@
+# This file contains information which helps Meteor properly upgrade your
+# app when you run 'meteor update'. You should check it into version control
+# with your project.
+
+notices-for-0.9.0
+notices-for-0.9.1
+0.9.4-platform-file
+notices-for-facebook-graph-api-2
+1.2.0-standard-minifiers-package
+1.2.0-meteor-platform-split
+1.2.0-cordova-changes
+1.2.0-breaking-changes
+1.3.0-split-minifiers-package
+1.3.5-remove-old-dev-bundle-link
+1.4.0-remove-old-dev-bundle-link
+1.4.1-add-shell-server-package
+1.4.3-split-account-service-packages
+1.5-add-dynamic-import-package
+1.7-split-underscore-from-meteor-base
+1.8.3-split-jquery-from-blaze

+ 2 - 0
.snap-meteor-1.8/.meteor/.gitignore

@@ -0,0 +1,2 @@
+dev_bundle
+local

+ 7 - 0
.snap-meteor-1.8/.meteor/.id

@@ -0,0 +1,7 @@
+# This file contains a token that is unique to your project.
+# Check it into your repository along with the rest of this directory.
+# It can be used for purposes such as:
+#   - ensuring you don't accidentally deploy one app on top of another
+#   - providing package authors with aggregated statistics
+
+dvyihgykyzec6y1dpg

+ 0 - 0
.snap-meteor-1.8/.meteor/cordova-plugins


+ 99 - 0
.snap-meteor-1.8/.meteor/packages

@@ -0,0 +1,99 @@
+# Meteor packages used by this project, one per line.
+#
+# 'meteor add' and 'meteor remove' will edit this file for you,
+# but you can also edit it by hand.
+
+meteor-base@1.4.0
+
+# Build system
+ecmascript@0.13.2
+standard-minifier-css@1.5.4
+standard-minifier-js@2.5.2
+mquandalle:jade
+
+# Polyfills
+es5-shim@4.8.0
+
+# Collections
+aldeed:collection2
+cfs:standard-packages
+cottz:publish-relations
+dburles:collection-helpers
+idmontie:migrations
+matb33:collection-hooks
+matteodem:easy-search
+mongo@1.7.0
+mquandalle:collection-mutations
+
+# Account system
+kenton:accounts-sandstorm
+service-configuration@1.0.11
+useraccounts:unstyled
+useraccounts:flow-routing
+wekan-ldap
+wekan-accounts-cas
+wekan-accounts-oidc
+
+# Utilities
+check@1.3.1
+jquery@1.11.10
+random@1.1.0
+reactive-dict@1.3.0
+session@1.2.0
+tracker@1.2.0
+underscore@1.0.10
+3stack:presence
+alethes:pages
+arillo:flow-router-helpers
+audit-argument-checks@1.0.7
+kadira:blaze-layout
+kadira:dochead
+mquandalle:autofocus
+ongoworks:speakingurl
+raix:handlebar-helpers
+tap:i18n
+http@1.4.2
+
+# UI components
+blaze
+reactive-var@1.0.11
+fortawesome:fontawesome
+mousetrap:mousetrap
+mquandalle:jquery-textcomplete
+mquandalle:jquery-ui-drag-drop-sort
+mquandalle:mousetrap-bindglobal
+peerlibrary:blaze-components@=0.15.1
+templates:tabs
+verron:autosize
+simple:json-routes
+rajit:bootstrap3-datepicker
+shell-server@0.4.0
+simple:rest-accounts-password
+useraccounts:core
+email@1.2.3
+horka:swipebox
+dynamic-import@0.5.1
+staringatlights:fast-render
+
+accounts-password@1.5.2
+cfs:gridfs
+rzymek:fullcalendar
+momentjs:moment@2.22.2
+browser-policy-framing@1.1.0
+mquandalle:moment
+msavin:usercache
+wekan-scrollbar
+mquandalle:perfect-scrollbar
+mdg:meteor-apm-agent@3.2.0-rc.0!
+coagmano:stylus
+lucasantoniassi:accounts-lockout
+meteorhacks:subs-manager
+meteorhacks:picker
+lamhieu:unblock
+meteorhacks:aggregate@1.3.0
+wekan-markdown
+konecty:mongo-counter
+percolate:synced-cron
+easylogic:summernote
+cfs:filesystem
+ostrio:cookies

+ 2 - 0
.snap-meteor-1.8/.meteor/platforms

@@ -0,0 +1,2 @@
+server
+browser

+ 1 - 0
.snap-meteor-1.8/.meteor/release

@@ -0,0 +1 @@
+METEOR@1.8.3

+ 198 - 0
.snap-meteor-1.8/.meteor/versions

@@ -0,0 +1,198 @@
+3stack:presence@1.1.2
+accounts-base@1.4.5
+accounts-oauth@1.1.16
+accounts-password@1.5.2
+aldeed:collection2@2.10.0
+aldeed:collection2-core@1.2.0
+aldeed:schema-deny@1.1.0
+aldeed:schema-index@1.1.1
+aldeed:simple-schema@1.5.4
+alethes:pages@1.8.6
+allow-deny@1.1.0
+arillo:flow-router-helpers@0.5.2
+audit-argument-checks@1.0.7
+autoupdate@1.6.0
+babel-compiler@7.4.2
+babel-runtime@1.4.0
+base64@1.0.12
+binary-heap@1.0.11
+blaze@2.3.4
+blaze-tools@1.0.10
+boilerplate-generator@1.6.0
+browser-policy-common@1.0.11
+browser-policy-framing@1.1.0
+caching-compiler@1.2.1
+caching-html-compiler@1.1.3
+callback-hook@1.2.0
+cfs:access-point@0.1.49
+cfs:base-package@0.0.30
+cfs:collection@0.5.5
+cfs:collection-filters@0.2.4
+cfs:data-man@0.0.6
+cfs:file@0.1.17
+cfs:filesystem@0.1.2
+cfs:gridfs@0.0.34
+cfs:http-methods@0.0.32
+cfs:http-publish@0.0.13
+cfs:power-queue@0.9.11
+cfs:reactive-list@0.0.9
+cfs:reactive-property@0.0.4
+cfs:standard-packages@0.5.10
+cfs:storage-adapter@0.2.4
+cfs:tempstore@0.1.6
+cfs:upload-http@0.0.20
+cfs:worker@0.1.5
+check@1.3.1
+chuangbo:cookie@1.1.0
+coagmano:stylus@2.0.0
+coffeescript@1.0.17
+cottz:publish-relations@2.0.8
+dburles:collection-helpers@1.1.0
+ddp@1.4.0
+ddp-client@2.3.3
+ddp-common@1.4.0
+ddp-rate-limiter@1.0.7
+ddp-server@2.3.0
+deps@1.0.12
+diff-sequence@1.1.1
+dynamic-import@0.5.1
+easylogic:summernote@0.8.8
+ecmascript@0.13.2
+ecmascript-runtime@0.7.0
+ecmascript-runtime-client@0.9.0
+ecmascript-runtime-server@0.8.0
+ejson@1.1.1
+email@1.2.3
+es5-shim@4.8.0
+fastclick@1.0.13
+fetch@0.1.1
+fortawesome:fontawesome@4.7.0
+geojson-utils@1.0.10
+horka:swipebox@1.0.2
+hot-code-push@1.0.4
+html-tools@1.0.11
+htmljs@1.0.11
+http@1.4.2
+id-map@1.1.0
+idmontie:migrations@1.0.3
+inter-process-messaging@0.1.0
+jquery@1.11.11
+kadira:blaze-layout@2.3.0
+kadira:dochead@1.5.0
+kadira:flow-router@2.12.1
+kenton:accounts-sandstorm@0.7.0
+konecty:mongo-counter@0.0.5_3
+lamhieu:meteorx@2.1.1
+lamhieu:unblock@1.0.0
+launch-screen@1.1.1
+livedata@1.0.18
+localstorage@1.2.0
+logging@1.1.20
+lucasantoniassi:accounts-lockout@1.0.0
+matb33:collection-hooks@0.9.1
+matteodem:easy-search@1.6.4
+mdg:meteor-apm-agent@3.2.5
+mdg:validation-error@0.5.1
+meteor@1.9.3
+meteor-base@1.4.0
+meteor-platform@1.2.6
+meteorhacks:aggregate@1.3.0
+meteorhacks:collection-utils@1.2.0
+meteorhacks:picker@1.0.3
+meteorhacks:subs-manager@1.6.4
+meteorspark:util@0.2.0
+minifier-css@1.4.3
+minifier-js@2.5.1
+minifiers@1.1.8-faster-rebuild.0
+minimongo@1.4.5
+mobile-status-bar@1.0.14
+modern-browsers@0.1.4
+modules@0.14.0
+modules-runtime@0.11.0
+momentjs:moment@2.24.0
+mongo@1.7.0
+mongo-decimal@0.1.1
+mongo-dev-server@1.1.0
+mongo-id@1.0.7
+mongo-livedata@1.0.12
+mousetrap:mousetrap@1.4.6_1
+mquandalle:autofocus@1.0.0
+mquandalle:collection-mutations@0.1.0
+mquandalle:jade@0.4.9
+mquandalle:jade-compiler@0.4.5
+mquandalle:jquery-textcomplete@0.8.0_1
+mquandalle:jquery-ui-drag-drop-sort@0.2.0
+mquandalle:moment@1.0.1
+mquandalle:mousetrap-bindglobal@0.0.1
+mquandalle:perfect-scrollbar@0.6.5_2
+msavin:usercache@1.8.0
+npm-bcrypt@0.9.3
+npm-mongo@3.2.0
+oauth@1.2.8
+oauth2@1.2.1
+observe-sequence@1.0.16
+ongoworks:speakingurl@1.1.0
+ordered-dict@1.1.0
+ostrio:cookies@2.5.0
+peerlibrary:assert@0.3.0
+peerlibrary:base-component@0.16.0
+peerlibrary:blaze-components@0.15.1
+peerlibrary:computed-field@0.10.0
+peerlibrary:reactive-field@0.6.0
+percolate:synced-cron@1.3.2
+promise@0.11.2
+raix:eventemitter@0.1.3
+raix:handlebar-helpers@0.2.5
+rajit:bootstrap3-datepicker@1.7.1_1
+random@1.1.0
+rate-limit@1.0.9
+reactive-dict@1.3.0
+reactive-var@1.0.11
+reload@1.3.0
+retry@1.1.0
+routepolicy@1.1.0
+rzymek:fullcalendar@3.8.0
+server-render@0.3.1
+service-configuration@1.0.11
+session@1.2.0
+sha@1.0.9
+shell-server@0.4.0
+simple:authenticate-user-by-token@1.0.1
+simple:json-routes@2.1.0
+simple:rest-accounts-password@1.1.2
+simple:rest-bearer-token-parser@1.0.1
+simple:rest-json-error-handler@1.0.1
+socket-stream-client@0.2.2
+softwarerero:accounts-t9n@1.3.11
+spacebars@1.0.15
+spacebars-compiler@1.1.3
+srp@1.0.12
+standard-minifier-css@1.5.4
+standard-minifier-js@2.5.2
+staringatlights:fast-render@3.2.0
+staringatlights:inject-data@2.3.0
+tap:i18n@1.8.2
+templates:tabs@2.3.0
+templating@1.3.2
+templating-compiler@1.3.3
+templating-runtime@1.3.2
+templating-tools@1.1.2
+tracker@1.2.0
+twbs:bootstrap@3.3.6
+ui@1.0.13
+underscore@1.0.10
+url@1.2.0
+useraccounts:core@1.14.2
+useraccounts:flow-routing@1.14.2
+useraccounts:unstyled@1.14.2
+verron:autosize@3.0.8
+webapp@1.7.5
+webapp-hashing@1.0.9
+wekan-accounts-cas@0.1.0
+wekan-accounts-oidc@1.0.10
+wekan-ldap@0.0.2
+wekan-markdown@1.0.7
+wekan-oidc@1.0.12
+wekan-scrollbar@3.1.3
+yasaricli:slugify@0.0.7
+zimme:active-route@2.3.2

+ 914 - 0
.snap-meteor-1.8/cfs_access-point.txt

@@ -0,0 +1,914 @@
+(function () {
+
+/* Imports */
+var Meteor = Package.meteor.Meteor;
+var global = Package.meteor.global;
+var meteorEnv = Package.meteor.meteorEnv;
+var FS = Package['cfs:base-package'].FS;
+var check = Package.check.check;
+var Match = Package.check.Match;
+var EJSON = Package.ejson.EJSON;
+var HTTP = Package['cfs:http-methods'].HTTP;
+
+/* Package-scope variables */
+var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection, _existingMountPoints, mountUrls;
+
+(function(){
+
+///////////////////////////////////////////////////////////////////////
+//                                                                   //
+// packages/cfs_access-point/packages/cfs_access-point.js            //
+//                                                                   //
+///////////////////////////////////////////////////////////////////////
+                                                                     //
+(function () {
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                                    //
+// packages/cfs:access-point/access-point-common.js                                                                   //
+//                                                                                                                    //
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+                                                                                                                      //
+rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || "";                                             // 1
+// Adjust the rootUrlPathPrefix if necessary                                                                          // 2
+if (rootUrlPathPrefix.length > 0) {                                                                                   // 3
+  if (rootUrlPathPrefix.slice(0, 1) !== '/') {                                                                        // 4
+    rootUrlPathPrefix = '/' + rootUrlPathPrefix;                                                                      // 5
+  }                                                                                                                   // 6
+  if (rootUrlPathPrefix.slice(-1) === '/') {                                                                          // 7
+    rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1);                                                               // 8
+  }                                                                                                                   // 9
+}                                                                                                                     // 10
+                                                                                                                      // 11
+// prepend ROOT_URL when isCordova                                                                                    // 12
+if (Meteor.isCordova) {                                                                                               // 13
+  rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, '');                  // 14
+}                                                                                                                     // 15
+                                                                                                                      // 16
+baseUrl = '/cfs';                                                                                                     // 17
+FS.HTTP = FS.HTTP || {};                                                                                              // 18
+                                                                                                                      // 19
+// Note the upload URL so that client uploader packages know what it is                                               // 20
+FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';                                                           // 21
+                                                                                                                      // 22
+/**                                                                                                                   // 23
+ * @method FS.HTTP.setBaseUrl                                                                                         // 24
+ * @public                                                                                                            // 25
+ * @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints.                            // 26
+ * @returns {undefined}                                                                                               // 27
+ */                                                                                                                   // 28
+FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) {                                                                // 29
+                                                                                                                      // 30
+  // Adjust the baseUrl if necessary                                                                                  // 31
+  if (newBaseUrl.slice(0, 1) !== '/') {                                                                               // 32
+    newBaseUrl = '/' + newBaseUrl;                                                                                    // 33
+  }                                                                                                                   // 34
+  if (newBaseUrl.slice(-1) === '/') {                                                                                 // 35
+    newBaseUrl = newBaseUrl.slice(0, -1);                                                                             // 36
+  }                                                                                                                   // 37
+                                                                                                                      // 38
+  // Update the base URL                                                                                              // 39
+  baseUrl = newBaseUrl;                                                                                               // 40
+                                                                                                                      // 41
+  // Change the upload URL so that client uploader packages know what it is                                           // 42
+  FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';                                                         // 43
+                                                                                                                      // 44
+  // Remount URLs with the new baseUrl, unmounting the old, on the server only.                                       // 45
+  // If existingMountPoints is empty, then we haven't run the server startup                                          // 46
+  // code yet, so this new URL will be used at that point for the initial mount.                                      // 47
+  if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) {                                                 // 48
+    mountUrls();                                                                                                      // 49
+  }                                                                                                                   // 50
+};                                                                                                                    // 51
+                                                                                                                      // 52
+/*                                                                                                                    // 53
+ * FS.File extensions                                                                                                 // 54
+ */                                                                                                                   // 55
+                                                                                                                      // 56
+/**                                                                                                                   // 57
+ * @method FS.File.prototype.url Construct the file url                                                               // 58
+ * @public                                                                                                            // 59
+ * @param {Object} [options]                                                                                          // 60
+ * @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
+ * @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
+ * @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
+ * @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
+ * @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
+ * @param {String} [options.uploading=null] A URL to return while the file is being uploaded.                         // 66
+ * @param {String} [options.storing=null] A URL to return while the file is being stored.                             // 67
+ * @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
+ *                                                                                                                    // 69
+ * Returns the HTTP URL for getting the file or its metadata.                                                         // 70
+ */                                                                                                                   // 71
+FS.File.prototype.url = function(options) {                                                                           // 72
+  var self = this;                                                                                                    // 73
+  options = options || {};                                                                                            // 74
+  options = FS.Utility.extend({                                                                                       // 75
+    store: null,                                                                                                      // 76
+    auth: null,                                                                                                       // 77
+    download: false,                                                                                                  // 78
+    metadata: false,                                                                                                  // 79
+    brokenIsFine: false,                                                                                              // 80
+    uploading: null, // return this URL while uploading                                                               // 81
+    storing: null, // return this URL while storing                                                                   // 82
+    filename: null // override the filename that is shown to the user                                                 // 83
+  }, options.hash || options); // check for "hash" prop if called as helper                                           // 84
+                                                                                                                      // 85
+  // Primarily useful for displaying a temporary image while uploading an image                                       // 86
+  if (options.uploading && !self.isUploaded()) {                                                                      // 87
+    return options.uploading;                                                                                         // 88
+  }                                                                                                                   // 89
+                                                                                                                      // 90
+  if (self.isMounted()) {                                                                                             // 91
+    // See if we've stored in the requested store yet                                                                 // 92
+    var storeName = options.store || self.collection.primaryStore.name;                                               // 93
+    if (!self.hasStored(storeName)) {                                                                                 // 94
+      if (options.storing) {                                                                                          // 95
+        return options.storing;                                                                                       // 96
+      } else if (!options.brokenIsFine) {                                                                             // 97
+        // We want to return null if we know the URL will be a broken                                                 // 98
+        // link because then we can avoid rendering broken links, broken                                              // 99
+        // images, etc.                                                                                               // 100
+        return null;                                                                                                  // 101
+      }                                                                                                               // 102
+    }                                                                                                                 // 103
+                                                                                                                      // 104
+    // Add filename to end of URL if we can determine one                                                             // 105
+    var filename = options.filename || self.name({store: storeName});                                                 // 106
+    if (typeof filename === "string" && filename.length) {                                                            // 107
+      filename = '/' + filename;                                                                                      // 108
+    } else {                                                                                                          // 109
+      filename = '';                                                                                                  // 110
+    }                                                                                                                 // 111
+                                                                                                                      // 112
+    // TODO: Could we somehow figure out if the collection requires login?                                            // 113
+    var authToken = '';                                                                                               // 114
+    if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") {     // 115
+      if (options.auth !== false) {                                                                                   // 116
+        // Add reactive deps on the user                                                                              // 117
+        Meteor.userId();                                                                                              // 118
+                                                                                                                      // 119
+        var authObject = {                                                                                            // 120
+          authToken: Accounts._storedLoginToken() || ''                                                               // 121
+        };                                                                                                            // 122
+                                                                                                                      // 123
+        // If it's a number, we use that as the expiration time (in seconds)                                          // 124
+        if (options.auth === +options.auth) {                                                                         // 125
+          authObject.expiration = FS.HTTP.now() + options.auth * 1000;                                                // 126
+        }                                                                                                             // 127
+                                                                                                                      // 128
+        // Set the authToken                                                                                          // 129
+        var authString = JSON.stringify(authObject);                                                                  // 130
+        authToken = FS.Utility.btoa(authString);                                                                      // 131
+      }                                                                                                               // 132
+    } else if (typeof options.auth === "string") {                                                                    // 133
+      // If the user supplies auth token the user will be responsible for                                             // 134
+      // updating                                                                                                     // 135
+      authToken = options.auth;                                                                                       // 136
+    }                                                                                                                 // 137
+                                                                                                                      // 138
+    // Construct query string                                                                                         // 139
+    var params = {};                                                                                                  // 140
+    if (authToken !== '') {                                                                                           // 141
+      params.token = authToken;                                                                                       // 142
+    }                                                                                                                 // 143
+    if (options.download) {                                                                                           // 144
+      params.download = true;                                                                                         // 145
+    }                                                                                                                 // 146
+    if (options.store) {                                                                                              // 147
+      // We use options.store here instead of storeName because we want to omit the queryString                       // 148
+      // whenever possible, allowing users to have "clean" URLs if they want. The server will                         // 149
+      // assume the first store defined on the server, which means that we are assuming that                          // 150
+      // the first on the client is also the first on the server. If that's not the case, the                         // 151
+      // store option should be supplied.                                                                             // 152
+      params.store = options.store;                                                                                   // 153
+    }                                                                                                                 // 154
+    var queryString = FS.Utility.encodeParams(params);                                                                // 155
+    if (queryString.length) {                                                                                         // 156
+      queryString = '?' + queryString;                                                                                // 157
+    }                                                                                                                 // 158
+                                                                                                                      // 159
+    // Determine which URL to use                                                                                     // 160
+    var area;                                                                                                         // 161
+    if (options.metadata) {                                                                                           // 162
+      area = '/record';                                                                                               // 163
+    } else {                                                                                                          // 164
+      area = '/files';                                                                                                // 165
+    }                                                                                                                 // 166
+                                                                                                                      // 167
+    // Construct and return the http method url                                                                       // 168
+    return rootUrlPathPrefix + baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString; // 169
+  }                                                                                                                   // 170
+                                                                                                                      // 171
+};                                                                                                                    // 172
+                                                                                                                      // 173
+                                                                                                                      // 174
+                                                                                                                      // 175
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}).call(this);
+
+
+
+
+
+
+(function () {
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                                    //
+// packages/cfs:access-point/access-point-handlers.js                                                                 //
+//                                                                                                                    //
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+                                                                                                                      //
+getHeaders = [];                                                                                                      // 1
+getHeadersByCollection = {};                                                                                          // 2
+                                                                                                                      // 3
+FS.HTTP.Handlers = {};                                                                                                // 4
+                                                                                                                      // 5
+/**                                                                                                                   // 6
+ * @method FS.HTTP.Handlers.Del                                                                                       // 7
+ * @public                                                                                                            // 8
+ * @returns {any} response                                                                                            // 9
+ *                                                                                                                    // 10
+ * HTTP DEL request handler                                                                                           // 11
+ */                                                                                                                   // 12
+FS.HTTP.Handlers.Del = function httpDelHandler(ref) {                                                                 // 13
+  var self = this;                                                                                                    // 14
+  var opts = FS.Utility.extend({}, self.query || {}, self.params || {});                                              // 15
+                                                                                                                      // 16
+  // If DELETE request, validate with 'remove' allow/deny, delete the file, and return                                // 17
+  FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId);                       // 18
+                                                                                                                      // 19
+  /*                                                                                                                  // 20
+   * From the DELETE spec:                                                                                            // 21
+   * A successful response SHOULD be 200 (OK) if the response includes an                                             // 22
+   * entity describing the status, 202 (Accepted) if the action has not                                               // 23
+   * yet been enacted, or 204 (No Content) if the action has been enacted                                             // 24
+   * but the response does not include an entity.                                                                     // 25
+   */                                                                                                                 // 26
+  self.setStatusCode(200);                                                                                            // 27
+                                                                                                                      // 28
+  return {                                                                                                            // 29
+    deleted: !!ref.file.remove()                                                                                      // 30
+  };                                                                                                                  // 31
+};                                                                                                                    // 32
+                                                                                                                      // 33
+/**                                                                                                                   // 34
+ * @method FS.HTTP.Handlers.GetList                                                                                   // 35
+ * @public                                                                                                            // 36
+ * @returns {Object} response                                                                                         // 37
+ *                                                                                                                    // 38
+ * HTTP GET file list request handler                                                                                 // 39
+ */                                                                                                                   // 40
+FS.HTTP.Handlers.GetList = function httpGetListHandler() {                                                            // 41
+  // Not Yet Implemented                                                                                              // 42
+  // Need to check publications and return file list based on                                                         // 43
+  // what user is allowed to see                                                                                      // 44
+};                                                                                                                    // 45
+                                                                                                                      // 46
+/*                                                                                                                    // 47
+  requestRange will parse the range set in request header - if not possible it                                        // 48
+  will throw fitting errors and autofill range for both partial and full ranges                                       // 49
+                                                                                                                      // 50
+  throws error or returns the object:                                                                                 // 51
+  {                                                                                                                   // 52
+    start                                                                                                             // 53
+    end                                                                                                               // 54
+    length                                                                                                            // 55
+    unit                                                                                                              // 56
+    partial                                                                                                           // 57
+  }                                                                                                                   // 58
+*/                                                                                                                    // 59
+var requestRange = function(req, fileSize) {                                                                          // 60
+  if (req) {                                                                                                          // 61
+    if (req.headers) {                                                                                                // 62
+      var rangeString = req.headers.range;                                                                            // 63
+                                                                                                                      // 64
+      // Make sure range is a string                                                                                  // 65
+      if (rangeString === ''+rangeString) {                                                                           // 66
+                                                                                                                      // 67
+        // range will be in the format "bytes=0-32767"                                                                // 68
+        var parts = rangeString.split('=');                                                                           // 69
+        var unit = parts[0];                                                                                          // 70
+                                                                                                                      // 71
+        // Make sure parts consists of two strings and range is of type "byte"                                        // 72
+        if (parts.length == 2 && unit == 'bytes') {                                                                   // 73
+          // Parse the range                                                                                          // 74
+          var range = parts[1].split('-');                                                                            // 75
+          var start = Number(range[0]);                                                                               // 76
+          var end = Number(range[1]);                                                                                 // 77
+                                                                                                                      // 78
+          // Fix invalid ranges?                                                                                      // 79
+          if (range[0] != start) start = 0;                                                                           // 80
+          if (range[1] != end || !end) end = fileSize - 1;                                                            // 81
+                                                                                                                      // 82
+          // Make sure range consists of a start and end point of numbers and start is less than end                  // 83
+          if (start < end) {                                                                                          // 84
+                                                                                                                      // 85
+            var partSize = 0 - start + end + 1;                                                                       // 86
+                                                                                                                      // 87
+            // Return the parsed range                                                                                // 88
+            return {                                                                                                  // 89
+              start: start,                                                                                           // 90
+              end: end,                                                                                               // 91
+              length: partSize,                                                                                       // 92
+              size: fileSize,                                                                                         // 93
+              unit: unit,                                                                                             // 94
+              partial: (partSize < fileSize)                                                                          // 95
+            };                                                                                                        // 96
+                                                                                                                      // 97
+          } else {                                                                                                    // 98
+            throw new Meteor.Error(416, "Requested Range Not Satisfiable");                                           // 99
+          }                                                                                                           // 100
+                                                                                                                      // 101
+        } else {                                                                                                      // 102
+          // The first part should be bytes                                                                           // 103
+          throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable");                                        // 104
+        }                                                                                                             // 105
+                                                                                                                      // 106
+      } else {                                                                                                        // 107
+        // No range found                                                                                             // 108
+      }                                                                                                               // 109
+                                                                                                                      // 110
+    } else {                                                                                                          // 111
+      // throw new Error('No request headers set for _parseRange function');                                          // 112
+    }                                                                                                                 // 113
+  } else {                                                                                                            // 114
+    throw new Error('No request object passed to _parseRange function');                                              // 115
+  }                                                                                                                   // 116
+                                                                                                                      // 117
+  return {                                                                                                            // 118
+    start: 0,                                                                                                         // 119
+    end: fileSize - 1,                                                                                                // 120
+    length: fileSize,                                                                                                 // 121
+    size: fileSize,                                                                                                   // 122
+    unit: 'bytes',                                                                                                    // 123
+    partial: false                                                                                                    // 124
+  };                                                                                                                  // 125
+};                                                                                                                    // 126
+                                                                                                                      // 127
+/**                                                                                                                   // 128
+ * @method FS.HTTP.Handlers.Get                                                                                       // 129
+ * @public                                                                                                            // 130
+ * @returns {any} response                                                                                            // 131
+ *                                                                                                                    // 132
+ * HTTP GET request handler                                                                                           // 133
+ */                                                                                                                   // 134
+FS.HTTP.Handlers.Get = function httpGetHandler(ref) {                                                                 // 135
+  var self = this;                                                                                                    // 136
+  // Once we have the file, we can test allow/deny validators                                                         // 137
+  // XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access?                                    // 138
+  FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/);  // 139
+                                                                                                                      // 140
+  var storeName = ref.storeName;                                                                                      // 141
+                                                                                                                      // 142
+  // If no storeName was specified, use the first defined storeName                                                   // 143
+  if (typeof storeName !== "string") {                                                                                // 144
+    // No store handed, we default to primary store                                                                   // 145
+    storeName = ref.collection.primaryStore.name;                                                                     // 146
+  }                                                                                                                   // 147
+                                                                                                                      // 148
+  // Get the storage reference                                                                                        // 149
+  var storage = ref.collection.storesLookup[storeName];                                                               // 150
+                                                                                                                      // 151
+  if (!storage) {                                                                                                     // 152
+    throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"');                                // 153
+  }                                                                                                                   // 154
+                                                                                                                      // 155
+  // Get the file                                                                                                     // 156
+  var copyInfo = ref.file.copies[storeName];                                                                          // 157
+                                                                                                                      // 158
+  if (!copyInfo) {                                                                                                    // 159
+    throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store');              // 160
+  }                                                                                                                   // 161
+                                                                                                                      // 162
+  // Set the content type for file                                                                                    // 163
+  if (typeof copyInfo.type === "string") {                                                                            // 164
+    self.setContentType(copyInfo.type);                                                                               // 165
+  } else {                                                                                                            // 166
+    self.setContentType('application/octet-stream');                                                                  // 167
+  }                                                                                                                   // 168
+                                                                                                                      // 169
+  // Add 'Content-Disposition' header if requested a download/attachment URL                                          // 170
+  if (typeof ref.download !== "undefined") {                                                                          // 171
+    var filename = ref.filename || copyInfo.name;                                                                     // 172
+    self.addHeader('Content-Disposition', 'attachment; filename="' + filename + '"');                                 // 173
+  } else {                                                                                                            // 174
+    self.addHeader('Content-Disposition', 'inline');                                                                  // 175
+  }                                                                                                                   // 176
+                                                                                                                      // 177
+  // Get the contents range from request                                                                              // 178
+  var range = requestRange(self.request, copyInfo.size);                                                              // 179
+                                                                                                                      // 180
+  // Some browsers cope better if the content-range header is                                                         // 181
+  // still included even for the full file being returned.                                                            // 182
+  self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);               // 183
+                                                                                                                      // 184
+  // If a chunk/range was requested instead of the whole file, serve that'                                            // 185
+  if (range.partial) {                                                                                                // 186
+    self.setStatusCode(206, 'Partial Content');                                                                       // 187
+  } else {                                                                                                            // 188
+    self.setStatusCode(200, 'OK');                                                                                    // 189
+  }                                                                                                                   // 190
+                                                                                                                      // 191
+  // Add any other global custom headers and collection-specific custom headers                                       // 192
+  FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) {            // 193
+    self.addHeader(header[0], header[1]);                                                                             // 194
+  });                                                                                                                 // 195
+                                                                                                                      // 196
+  // Inform clients about length (or chunk length in case of ranges)                                                  // 197
+  self.addHeader('Content-Length', range.length);                                                                     // 198
+                                                                                                                      // 199
+  // Last modified header (updatedAt from file info)                                                                  // 200
+  self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString());                                                  // 201
+                                                                                                                      // 202
+  // Inform clients that we accept ranges for resumable chunked downloads                                             // 203
+  self.addHeader('Accept-Ranges', range.unit);                                                                        // 204
+                                                                                                                      // 205
+  if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
+                                                                                                                      // 207
+  var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end});                  // 208
+                                                                                                                      // 209
+  readStream.on('error', function(err) {                                                                              // 210
+    // Send proper error message on get error                                                                         // 211
+    if (err.message && err.statusCode) {                                                                              // 212
+      self.Error(new Meteor.Error(err.statusCode, err.message));                                                      // 213
+    } else {                                                                                                          // 214
+      self.Error(new Meteor.Error(503, 'Service unavailable'));                                                       // 215
+    }                                                                                                                 // 216
+  });                                                                                                                 // 217
+                                                                                                                      // 218
+  readStream.pipe(self.createWriteStream());                                                                          // 219
+};                                                                                                                    // 220
+
+const originalHandler = FS.HTTP.Handlers.Get;
+FS.HTTP.Handlers.Get = function (ref) {
+//console.log(ref.filename);
+  try {
+     var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase();
+
+        if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('trident') >= 0 || userAgent.indexOf('chrome') >= 0) {
+            ref.filename =  encodeURIComponent(ref.filename);
+        } else if(userAgent.indexOf('firefox') >= 0) {
+            ref.filename = new Buffer(ref.filename).toString('binary');
+        } else {
+            /* safari*/
+            ref.filename = new Buffer(ref.filename).toString('binary');
+        }   
+   } catch (ex){
+        ref.filename = 'tempfix';
+   } 
+   return originalHandler.call(this, ref);
+};
+                                                                                                                      // 221
+/**                                                                                                                   // 222
+ * @method FS.HTTP.Handlers.PutInsert                                                                                 // 223
+ * @public                                                                                                            // 224
+ * @returns {Object} response object with _id property                                                                // 225
+ *                                                                                                                    // 226
+ * HTTP PUT file insert request handler                                                                               // 227
+ */                                                                                                                   // 228
+FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) {                                                     // 229
+  var self = this;                                                                                                    // 230
+  var opts = FS.Utility.extend({}, self.query || {}, self.params || {});                                              // 231
+                                                                                                                      // 232
+  FS.debug && console.log("HTTP PUT (insert) handler");                                                               // 233
+                                                                                                                      // 234
+  // Create the nice FS.File                                                                                          // 235
+  var fileObj = new FS.File();                                                                                        // 236
+                                                                                                                      // 237
+  // Set its name                                                                                                     // 238
+  fileObj.name(opts.filename || null);                                                                                // 239
+                                                                                                                      // 240
+  // Attach the readstream as the file's data                                                                         // 241
+  fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'});
+                                                                                                                      // 243
+  // Validate with insert allow/deny                                                                                  // 244
+  FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId);                        // 245
+                                                                                                                      // 246
+  // Insert file into collection, triggering readStream storage                                                       // 247
+  ref.collection.insert(fileObj);                                                                                     // 248
+                                                                                                                      // 249
+  // Send response                                                                                                    // 250
+  self.setStatusCode(200);                                                                                            // 251
+                                                                                                                      // 252
+  // Return the new file id                                                                                           // 253
+  return {_id: fileObj._id};                                                                                          // 254
+};                                                                                                                    // 255
+                                                                                                                      // 256
+/**                                                                                                                   // 257
+ * @method FS.HTTP.Handlers.PutUpdate                                                                                 // 258
+ * @public                                                                                                            // 259
+ * @returns {Object} response object with _id and chunk properties                                                    // 260
+ *                                                                                                                    // 261
+ * HTTP PUT file update chunk request handler                                                                         // 262
+ */                                                                                                                   // 263
+FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) {                                                     // 264
+  var self = this;                                                                                                    // 265
+  var opts = FS.Utility.extend({}, self.query || {}, self.params || {});                                              // 266
+                                                                                                                      // 267
+  var chunk = parseInt(opts.chunk, 10);                                                                               // 268
+  if (isNaN(chunk)) chunk = 0;                                                                                        // 269
+                                                                                                                      // 270
+  FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk);                                       // 271
+                                                                                                                      // 272
+  // Validate with insert allow/deny; also mounts and retrieves the file                                              // 273
+  FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId);                       // 274
+                                                                                                                      // 275
+  self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) );                                    // 276
+                                                                                                                      // 277
+  // Send response                                                                                                    // 278
+  self.setStatusCode(200);                                                                                            // 279
+                                                                                                                      // 280
+  return { _id: ref.file._id, chunk: chunk };                                                                         // 281
+};                                                                                                                    // 282
+                                                                                                                      // 283
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}).call(this);
+
+
+
+
+
+
+(function () {
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                                    //
+// packages/cfs:access-point/access-point-server.js                                                                   //
+//                                                                                                                    //
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+                                                                                                                      //
+var path = Npm.require("path");                                                                                       // 1
+                                                                                                                      // 2
+HTTP.publishFormats({                                                                                                 // 3
+  fileRecordFormat: function (input) {                                                                                // 4
+    // Set the method scope content type to json                                                                      // 5
+    this.setContentType('application/json');                                                                          // 6
+    if (FS.Utility.isArray(input)) {                                                                                  // 7
+      return EJSON.stringify(FS.Utility.map(input, function (obj) {                                                   // 8
+        return FS.Utility.cloneFileRecord(obj);                                                                       // 9
+      }));                                                                                                            // 10
+    } else {                                                                                                          // 11
+      return EJSON.stringify(FS.Utility.cloneFileRecord(input));                                                      // 12
+    }                                                                                                                 // 13
+  }                                                                                                                   // 14
+});                                                                                                                   // 15
+                                                                                                                      // 16
+/**                                                                                                                   // 17
+ * @method FS.HTTP.setHeadersForGet                                                                                   // 18
+ * @public                                                                                                            // 19
+ * @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
+ * @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections.
+ * @returns {undefined}                                                                                               // 22
+ */                                                                                                                   // 23
+FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) {                                          // 24
+  if (typeof collections === "string") {                                                                              // 25
+    collections = [collections];                                                                                      // 26
+  }                                                                                                                   // 27
+  if (collections) {                                                                                                  // 28
+    FS.Utility.each(collections, function(collectionName) {                                                           // 29
+      getHeadersByCollection[collectionName] = headers || [];                                                         // 30
+    });                                                                                                               // 31
+  } else {                                                                                                            // 32
+    getHeaders = headers || [];                                                                                       // 33
+  }                                                                                                                   // 34
+};                                                                                                                    // 35
+                                                                                                                      // 36
+/**                                                                                                                   // 37
+ * @method FS.HTTP.publish                                                                                            // 38
+ * @public                                                                                                            // 39
+ * @param {FS.Collection} collection                                                                                  // 40
+ * @param {Function} func - Publish function that returns a cursor.                                                   // 41
+ * @returns {undefined}                                                                                               // 42
+ *                                                                                                                    // 43
+ * Publishes all documents returned by the cursor at a GET URL                                                        // 44
+ * with the format baseUrl/record/collectionName. The publish                                                         // 45
+ * function `this` is similar to normal `Meteor.publish`.                                                             // 46
+ */                                                                                                                   // 47
+FS.HTTP.publish = function fsHttpPublish(collection, func) {                                                          // 48
+  var name = baseUrl + '/record/' + collection.name;                                                                  // 49
+  // Mount collection listing URL using http-publish package                                                          // 50
+  HTTP.publish({                                                                                                      // 51
+    name: name,                                                                                                       // 52
+    defaultFormat: 'fileRecordFormat',                                                                                // 53
+    collection: collection,                                                                                           // 54
+    collectionGet: true,                                                                                              // 55
+    collectionPost: false,                                                                                            // 56
+    documentGet: true,                                                                                                // 57
+    documentPut: false,                                                                                               // 58
+    documentDelete: false                                                                                             // 59
+  }, func);                                                                                                           // 60
+                                                                                                                      // 61
+  FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n');                    // 62
+};                                                                                                                    // 63
+                                                                                                                      // 64
+/**                                                                                                                   // 65
+ * @method FS.HTTP.unpublish                                                                                          // 66
+ * @public                                                                                                            // 67
+ * @param {FS.Collection} collection                                                                                  // 68
+ * @returns {undefined}                                                                                               // 69
+ *                                                                                                                    // 70
+ * Unpublishes a restpoint created by a call to `FS.HTTP.publish`                                                     // 71
+ */                                                                                                                   // 72
+FS.HTTP.unpublish = function fsHttpUnpublish(collection) {                                                            // 73
+  // Mount collection listing URL using http-publish package                                                          // 74
+  HTTP.unpublish(baseUrl + '/record/' + collection.name);                                                             // 75
+};                                                                                                                    // 76
+                                                                                                                      // 77
+_existingMountPoints = {};                                                                                            // 78
+                                                                                                                      // 79
+/**                                                                                                                   // 80
+ * @method defaultSelectorFunction                                                                                    // 81
+ * @private                                                                                                           // 82
+ * @returns { collection, file }                                                                                      // 83
+ *                                                                                                                    // 84
+ * This is the default selector function                                                                              // 85
+ */                                                                                                                   // 86
+var defaultSelectorFunction = function() {                                                                            // 87
+  var self = this;                                                                                                    // 88
+  // Selector function                                                                                                // 89
+  //                                                                                                                  // 90
+  // This function will have to return the collection and the                                                         // 91
+  // file. If file not found undefined is returned - if null is returned the                                          // 92
+  // search was not possible                                                                                          // 93
+  var opts = FS.Utility.extend({}, self.query || {}, self.params || {});                                              // 94
+                                                                                                                      // 95
+  // Get the collection name from the url                                                                             // 96
+  var collectionName = opts.collectionName;                                                                           // 97
+                                                                                                                      // 98
+  // Get the id from the url                                                                                          // 99
+  var id = opts.id;                                                                                                   // 100
+                                                                                                                      // 101
+  // Get the collection                                                                                               // 102
+  var collection = FS._collections[collectionName];                                                                   // 103
+                                                                                                                      // 104
+  // Get the file if possible else return null                                                                        // 105
+  var file = (id && collection)? collection.findOne({ _id: id }): null;                                               // 106
+                                                                                                                      // 107
+  // Return the collection and the file                                                                               // 108
+  return {                                                                                                            // 109
+    collection: collection,                                                                                           // 110
+    file: file,                                                                                                       // 111
+    storeName: opts.store,                                                                                            // 112
+    download: opts.download,                                                                                          // 113
+    filename: opts.filename                                                                                           // 114
+  };                                                                                                                  // 115
+};                                                                                                                    // 116
+                                                                                                                      // 117
+/*                                                                                                                    // 118
+ * @method FS.HTTP.mount                                                                                              // 119
+ * @public                                                                                                            // 120
+ * @param {array of string} mountPoints mount points to map rest functinality on                                      // 121
+ * @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with      // 122
+ *                                                                                                                    // 123
+*/                                                                                                                    // 124
+FS.HTTP.mount = function(mountPoints, selector_f) {                                                                   // 125
+  // We take mount points as an array and we get a selector function                                                  // 126
+  var selectorFunction = selector_f || defaultSelectorFunction;                                                       // 127
+                                                                                                                      // 128
+  var accessPoint = {                                                                                                 // 129
+    'stream': true,                                                                                                   // 130
+    'auth': expirationAuth,                                                                                           // 131
+    'post': function(data) {                                                                                          // 132
+      // Use the selector for finding the collection and file reference                                               // 133
+      var ref = selectorFunction.call(this);                                                                          // 134
+                                                                                                                      // 135
+      // We dont support post - this would be normal insert eg. of filerecord?                                        // 136
+      throw new Meteor.Error(501, "Not implemented", "Post is not supported");                                        // 137
+    },                                                                                                                // 138
+    'put': function(data) {                                                                                           // 139
+      // Use the selector for finding the collection and file reference                                               // 140
+      var ref = selectorFunction.call(this);                                                                          // 141
+                                                                                                                      // 142
+      // Make sure we have a collection reference                                                                     // 143
+      if (!ref.collection)                                                                                            // 144
+        throw new Meteor.Error(404, "Not Found", "No collection found");                                              // 145
+                                                                                                                      // 146
+      // Make sure we have a file reference                                                                           // 147
+      if (ref.file === null) {                                                                                        // 148
+        // No id supplied so we will create a new FS.File instance and                                                // 149
+        // insert the supplied data.                                                                                  // 150
+        return FS.HTTP.Handlers.PutInsert.apply(this, [ref]);                                                         // 151
+      } else {                                                                                                        // 152
+        if (ref.file) {                                                                                               // 153
+          return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]);                                                       // 154
+        } else {                                                                                                      // 155
+          throw new Meteor.Error(404, "Not Found", 'No file found');                                                  // 156
+        }                                                                                                             // 157
+      }                                                                                                               // 158
+    },                                                                                                                // 159
+    'get': function(data) {                                                                                           // 160
+      // Use the selector for finding the collection and file reference                                               // 161
+      var ref = selectorFunction.call(this);                                                                          // 162
+                                                                                                                      // 163
+      // Make sure we have a collection reference                                                                     // 164
+      if (!ref.collection)                                                                                            // 165
+        throw new Meteor.Error(404, "Not Found", "No collection found");                                              // 166
+                                                                                                                      // 167
+      // Make sure we have a file reference                                                                           // 168
+      if (ref.file === null) {                                                                                        // 169
+        // No id supplied so we will return the published list of files ala                                           // 170
+        // http.publish in json format                                                                                // 171
+        return FS.HTTP.Handlers.GetList.apply(this, [ref]);                                                           // 172
+      } else {                                                                                                        // 173
+        if (ref.file) {                                                                                               // 174
+          return FS.HTTP.Handlers.Get.apply(this, [ref]);                                                             // 175
+        } else {                                                                                                      // 176
+          throw new Meteor.Error(404, "Not Found", 'No file found');                                                  // 177
+        }                                                                                                             // 178
+      }                                                                                                               // 179
+    },                                                                                                                // 180
+    'delete': function(data) {                                                                                        // 181
+      // Use the selector for finding the collection and file reference                                               // 182
+      var ref = selectorFunction.call(this);                                                                          // 183
+                                                                                                                      // 184
+      // Make sure we have a collection reference                                                                     // 185
+      if (!ref.collection)                                                                                            // 186
+        throw new Meteor.Error(404, "Not Found", "No collection found");                                              // 187
+                                                                                                                      // 188
+      // Make sure we have a file reference                                                                           // 189
+      if (ref.file) {                                                                                                 // 190
+        return FS.HTTP.Handlers.Del.apply(this, [ref]);                                                               // 191
+      } else {                                                                                                        // 192
+        throw new Meteor.Error(404, "Not Found", 'No file found');                                                    // 193
+      }                                                                                                               // 194
+    }                                                                                                                 // 195
+  };                                                                                                                  // 196
+                                                                                                                      // 197
+  var accessPoints = {};                                                                                              // 198
+                                                                                                                      // 199
+  // Add debug message                                                                                                // 200
+  FS.debug && console.log('Registered HTTP method URLs:');                                                            // 201
+                                                                                                                      // 202
+  FS.Utility.each(mountPoints, function(mountPoint) {                                                                 // 203
+    // Couple mountpoint and accesspoint                                                                              // 204
+    accessPoints[mountPoint] = accessPoint;                                                                           // 205
+    // Remember our mountpoints                                                                                       // 206
+    _existingMountPoints[mountPoint] = mountPoint;                                                                    // 207
+    // Add debug message                                                                                              // 208
+    FS.debug && console.log(mountPoint);                                                                              // 209
+  });                                                                                                                 // 210
+                                                                                                                      // 211
+  // XXX: HTTP:methods should unmount existing mounts in case of overwriting?                                         // 212
+  HTTP.methods(accessPoints);                                                                                         // 213
+                                                                                                                      // 214
+};                                                                                                                    // 215
+                                                                                                                      // 216
+/**                                                                                                                   // 217
+ * @method FS.HTTP.unmount                                                                                            // 218
+ * @public                                                                                                            // 219
+ * @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted           // 220
+ *                                                                                                                    // 221
+ */                                                                                                                   // 222
+FS.HTTP.unmount = function(mountPoints) {                                                                             // 223
+  // The mountPoints is optional, can be string or array if undefined then                                            // 224
+  // _existingMountPoints will be used                                                                                // 225
+  var unmountList;                                                                                                    // 226
+  // Container for the mount points to unmount                                                                        // 227
+  var unmountPoints = {};                                                                                             // 228
+                                                                                                                      // 229
+  if (typeof mountPoints === 'undefined') {                                                                           // 230
+    // Use existing mount points - unmount all                                                                        // 231
+    unmountList = _existingMountPoints;                                                                               // 232
+  } else if (mountPoints === ''+mountPoints) {                                                                        // 233
+    // Got a string                                                                                                   // 234
+    unmountList = [mountPoints];                                                                                      // 235
+  } else if (mountPoints.length) {                                                                                    // 236
+    // Got an array                                                                                                   // 237
+    unmountList = mountPoints;                                                                                        // 238
+  }                                                                                                                   // 239
+                                                                                                                      // 240
+  // If we have a list to unmount                                                                                     // 241
+  if (unmountList) {                                                                                                  // 242
+    // Iterate over each item                                                                                         // 243
+    FS.Utility.each(unmountList, function(mountPoint) {                                                               // 244
+      // Check _existingMountPoints to make sure the mount point exists in our                                        // 245
+      // context / was created by the FS.HTTP.mount                                                                   // 246
+      if (_existingMountPoints[mountPoint]) {                                                                         // 247
+        // Mark as unmount                                                                                            // 248
+        unmountPoints[mountPoint] = false;                                                                            // 249
+        // Release                                                                                                    // 250
+        delete _existingMountPoints[mountPoint];                                                                      // 251
+      }                                                                                                               // 252
+    });                                                                                                               // 253
+    FS.debug && console.log('FS.HTTP.unmount:');                                                                      // 254
+    FS.debug && console.log(unmountPoints);                                                                           // 255
+    // Complete unmount                                                                                               // 256
+    HTTP.methods(unmountPoints);                                                                                      // 257
+  }                                                                                                                   // 258
+};                                                                                                                    // 259
+                                                                                                                      // 260
+// ### FS.Collection maps on HTTP pr. default on the following restpoints:                                            // 261
+// *                                                                                                                  // 262
+//    baseUrl + '/files/:collectionName/:id/:filename',                                                               // 263
+//    baseUrl + '/files/:collectionName/:id',                                                                         // 264
+//    baseUrl + '/files/:collectionName'                                                                              // 265
+//                                                                                                                    // 266
+// Change/ replace the existing mount point by:                                                                       // 267
+// ```js                                                                                                              // 268
+//   // unmount all existing                                                                                          // 269
+//   FS.HTTP.unmount();                                                                                               // 270
+//   // Create new mount point                                                                                        // 271
+//   FS.HTTP.mount([                                                                                                  // 272
+//    '/cfs/files/:collectionName/:id/:filename',                                                                     // 273
+//    '/cfs/files/:collectionName/:id',                                                                               // 274
+//    '/cfs/files/:collectionName'                                                                                    // 275
+//  ]);                                                                                                               // 276
+//  ```                                                                                                               // 277
+//                                                                                                                    // 278
+mountUrls = function mountUrls() {                                                                                    // 279
+  // We unmount first in case we are calling this a second time                                                       // 280
+  FS.HTTP.unmount();                                                                                                  // 281
+                                                                                                                      // 282
+  FS.HTTP.mount([                                                                                                     // 283
+    baseUrl + '/files/:collectionName/:id/:filename',                                                                 // 284
+    baseUrl + '/files/:collectionName/:id',                                                                           // 285
+    baseUrl + '/files/:collectionName'                                                                                // 286
+  ]);                                                                                                                 // 287
+};                                                                                                                    // 288
+                                                                                                                      // 289
+// Returns the userId from URL token                                                                                  // 290
+var expirationAuth = function expirationAuth() {                                                                      // 291
+  var self = this;                                                                                                    // 292
+                                                                                                                      // 293
+  // Read the token from '/hello?token=base64'                                                                        // 294
+  var encodedToken = self.query.token;                                                                                // 295
+                                                                                                                      // 296
+  FS.debug && console.log("token: "+encodedToken);                                                                    // 297
+                                                                                                                      // 298
+  if (!encodedToken || !Meteor.users) return false;                                                                   // 299
+                                                                                                                      // 300
+  // Check the userToken before adding it to the db query                                                             // 301
+  // Set the this.userId                                                                                              // 302
+  var tokenString = FS.Utility.atob(encodedToken);                                                                    // 303
+                                                                                                                      // 304
+  var tokenObject;                                                                                                    // 305
+  try {                                                                                                               // 306
+    tokenObject = JSON.parse(tokenString);                                                                            // 307
+  } catch(err) {                                                                                                      // 308
+    throw new Meteor.Error(400, 'Bad Request');                                                                       // 309
+  }                                                                                                                   // 310
+                                                                                                                      // 311
+  // XXX: Do some check here of the object                                                                            // 312
+  var userToken = tokenObject.authToken;                                                                              // 313
+  if (userToken !== ''+userToken) {                                                                                   // 314
+    throw new Meteor.Error(400, 'Bad Request');                                                                       // 315
+  }                                                                                                                   // 316
+                                                                                                                      // 317
+  // If we have an expiration token we should check that it's still valid                                             // 318
+  if (tokenObject.expiration != null) {                                                                               // 319
+    // check if its too old                                                                                           // 320
+    var now = Date.now();                                                                                             // 321
+    if (tokenObject.expiration < now) {                                                                               // 322
+      FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now);                   // 323
+      throw new Meteor.Error(500, 'Expired token');                                                                   // 324
+    }                                                                                                                 // 325
+  }                                                                                                                   // 326
+                                                                                                                      // 327
+  // We are not on a secure line - so we have to look up the user...                                                  // 328
+  var user = Meteor.users.findOne({                                                                                   // 329
+    $or: [                                                                                                            // 330
+      {'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)},                               // 331
+      {'services.resume.loginTokens.token': userToken}                                                                // 332
+    ]                                                                                                                 // 333
+  });                                                                                                                 // 334
+                                                                                                                      // 335
+  // Set the userId in the scope                                                                                      // 336
+  return user && user._id;                                                                                            // 337
+};                                                                                                                    // 338
+                                                                                                                      // 339
+HTTP.methods(                                                                                                         // 340
+  {'/cfs/servertime': {                                                                                               // 341
+    get: function(data) {                                                                                             // 342
+      return Date.now().toString();                                                                                   // 343
+    }                                                                                                                 // 344
+  }                                                                                                                   // 345
+});                                                                                                                   // 346
+                                                                                                                      // 347
+// Unify client / server api                                                                                          // 348
+FS.HTTP.now = function() {                                                                                            // 349
+  return Date.now();                                                                                                  // 350
+};                                                                                                                    // 351
+                                                                                                                      // 352
+// Start up the basic mount points                                                                                    // 353
+Meteor.startup(function () {                                                                                          // 354
+  mountUrls();                                                                                                        // 355
+});                                                                                                                   // 356
+                                                                                                                      // 357
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+}).call(this);
+
+///////////////////////////////////////////////////////////////////////
+
+}).call(this);
+
+
+/* Exports */
+if (typeof Package === 'undefined') Package = {};
+Package['cfs:access-point'] = {};
+
+})();

+ 238 - 0
.snap-meteor-1.8/export.js

@@ -0,0 +1,238 @@
+/* global JsonRoutes */
+if (Meteor.isServer) {
+  // todo XXX once we have a real API in place, move that route there
+  // todo XXX also  share the route definition between the client and the server
+  // so that we could use something like
+  // `ApiRoutes.path('boards/export', boardId)``
+  // on the client instead of copy/pasting the route path manually between the
+  // client and the server.
+  /**
+   * @operation export
+   * @tag Boards
+   *
+   * @summary This route is used to export the board.
+   *
+   * @description If user is already logged-in, pass loginToken as param
+   * "authToken": '/api/boards/:boardId/export?authToken=:token'
+   *
+   * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
+   * for detailed explanations
+   *
+   * @param {string} boardId the ID of the board we are exporting
+   * @param {string} authToken the loginToken
+   */
+  JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) {
+    const boardId = req.params.boardId;
+    let user = null;
+
+    const loginToken = req.query.authToken;
+    if (loginToken) {
+      const hashToken = Accounts._hashLoginToken(loginToken);
+      user = Meteor.users.findOne({
+        'services.resume.loginTokens.hashedToken': hashToken,
+      });
+    } else if (!Meteor.settings.public.sandstorm) {
+      Authentication.checkUserId(req.userId);
+      user = Users.findOne({ _id: req.userId, isAdmin: true });
+    }
+
+    const exporter = new Exporter(boardId);
+    if (exporter.canExport(user)) {
+      JsonRoutes.sendResult(res, {
+        code: 200,
+        data: exporter.build(),
+      });
+    } else {
+      // we could send an explicit error message, but on the other hand the only
+      // way to get there is by hacking the UI so let's keep it raw.
+      JsonRoutes.sendResult(res, 403);
+    }
+  });
+}
+
+// exporter maybe is broken since Gridfs introduced, add fs and path
+
+export class Exporter {
+  constructor(boardId) {
+    this._boardId = boardId;
+  }
+
+  build() {
+    const fs = Npm.require('fs');
+    const os = Npm.require('os');
+    const path = Npm.require('path');
+
+    const byBoard = { boardId: this._boardId };
+    const byBoardNoLinked = {
+      boardId: this._boardId,
+      linkedId: { $in: ['', null] },
+    };
+    // we do not want to retrieve boardId in related elements
+    const noBoardId = {
+      fields: {
+        boardId: 0,
+      },
+    };
+    const result = {
+      _format: 'wekan-board-1.0.0',
+    };
+    _.extend(
+      result,
+      Boards.findOne(this._boardId, {
+        fields: {
+          stars: 0,
+        },
+      }),
+    );
+    result.lists = Lists.find(byBoard, noBoardId).fetch();
+    result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch();
+    result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch();
+    result.customFields = CustomFields.find(
+      { boardIds: { $in: [this.boardId] } },
+      { fields: { boardId: 0 } },
+    ).fetch();
+    result.comments = CardComments.find(byBoard, noBoardId).fetch();
+    result.activities = Activities.find(byBoard, noBoardId).fetch();
+    result.rules = Rules.find(byBoard, noBoardId).fetch();
+    result.checklists = [];
+    result.checklistItems = [];
+    result.subtaskItems = [];
+    result.triggers = [];
+    result.actions = [];
+    result.cards.forEach(card => {
+      result.checklists.push(
+        ...Checklists.find({
+          cardId: card._id,
+        }).fetch(),
+      );
+      result.checklistItems.push(
+        ...ChecklistItems.find({
+          cardId: card._id,
+        }).fetch(),
+      );
+      result.subtaskItems.push(
+        ...Cards.find({
+          parentId: card._id,
+        }).fetch(),
+      );
+    });
+    result.rules.forEach(rule => {
+      result.triggers.push(
+        ...Triggers.find(
+          {
+            _id: rule.triggerId,
+          },
+          noBoardId,
+        ).fetch(),
+      );
+      result.actions.push(
+        ...Actions.find(
+          {
+            _id: rule.actionId,
+          },
+          noBoardId,
+        ).fetch(),
+      );
+    });
+
+    // [Old] for attachments we only export IDs and absolute url to original doc
+    // [New] Encode attachment to base64
+    const getBase64Data = function(doc, callback) {
+      let buffer = new Buffer(0);
+      // callback has the form function (err, res) {}
+      const tmpFile = path.join(
+        os.tmpdir(),
+        `tmpexport${process.pid}${Math.random()}`,
+      );
+      const tmpWriteable = fs.createWriteStream(tmpFile);
+      const readStream = doc.createReadStream();
+      readStream.on('data', function(chunk) {
+        buffer = Buffer.concat([buffer, chunk]);
+      });
+      readStream.on('error', function(err) {
+        callback(err, null);
+      });
+      readStream.on('end', function() {
+        // done
+        fs.unlink(tmpFile, () => {
+          //ignored
+        });
+        callback(null, buffer.toString('base64'));
+      });
+      readStream.pipe(tmpWriteable);
+    };
+    const getBase64DataSync = Meteor.wrapAsync(getBase64Data);
+    result.attachments = Attachments.find(byBoard)
+      .fetch()
+      .map(attachment => {
+        return {
+          _id: attachment._id,
+          cardId: attachment.cardId,
+          // url: FlowRouter.url(attachment.url()),
+          file: getBase64DataSync(attachment),
+          name: attachment.original.name,
+          type: attachment.original.type,
+        };
+      });
+
+    // we also have to export some user data - as the other elements only
+    // include id but we have to be careful:
+    // 1- only exports users that are linked somehow to that board
+    // 2- do not export any sensitive information
+    const users = {};
+    result.members.forEach(member => {
+      users[member.userId] = true;
+    });
+    result.lists.forEach(list => {
+      users[list.userId] = true;
+    });
+    result.cards.forEach(card => {
+      users[card.userId] = true;
+      if (card.members) {
+        card.members.forEach(memberId => {
+          users[memberId] = true;
+        });
+      }
+    });
+    result.comments.forEach(comment => {
+      users[comment.userId] = true;
+    });
+    result.activities.forEach(activity => {
+      users[activity.userId] = true;
+    });
+    result.checklists.forEach(checklist => {
+      users[checklist.userId] = true;
+    });
+    const byUserIds = {
+      _id: {
+        $in: Object.getOwnPropertyNames(users),
+      },
+    };
+    // we use whitelist to be sure we do not expose inadvertently
+    // some secret fields that gets added to User later.
+    const userFields = {
+      fields: {
+        _id: 1,
+        username: 1,
+        'profile.fullname': 1,
+        'profile.initials': 1,
+        'profile.avatarUrl': 1,
+      },
+    };
+    result.users = Users.find(byUserIds, userFields)
+      .fetch()
+      .map(user => {
+        // user avatar is stored as a relative url, we export absolute
+        if ((user.profile || {}).avatarUrl) {
+          user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
+        }
+        return user;
+      });
+    return result;
+  }
+
+  canExport(user) {
+    const board = Boards.findOne(this._boardId);
+    return board && board.isVisibleBy(user);
+  }
+}

+ 155 - 0
.snap-meteor-1.8/future/snapcraft.yaml

@@ -0,0 +1,155 @@
+name: wekan
+version: git
+summary: The open-source kanban
+description: |
+   Wekan is an open-source and collaborative kanban board application.
+
+   Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
+   Depending on target environment, some configuration settings might need to be adjusted.
+   For full list of configuration options call:
+   $ wekan.help
+
+confinement: strict
+grade: stable
+base: core18
+
+architectures:
+  - amd64
+
+plugs:
+  mongodb-plug:
+    interface: content
+    target: $SNAP_DATA/shared
+
+hooks:
+  configure:
+    plugs:
+      - network
+      - network-bind
+
+slots:
+  mongodb-slot:
+    interface: content
+    write:
+      - $SNAP_DATA/share
+
+apps:
+    wekan:
+        command: wekan-control
+        daemon: simple
+        plugs: [network, network-bind]
+
+    mongodb:
+        command: mongodb-control
+        daemon: simple
+        plugs: [network, network-bind]
+
+    caddy:
+        command: caddy-control
+        daemon: simple
+        plugs: [network, network-bind]
+
+    help:
+        command: wekan-help
+
+    database-backup:
+        command: mongodb-backup
+        plugs: [network, network-bind]
+
+    database-list-backups:
+        command: ls -al $SNAP_COMMON/db-backups/
+
+    database-restore:
+        command: mongodb-restore
+        plugs: [network, network-bind]
+
+parts:
+    mongodb:
+        source: https://repo.mongodb.org/apt/ubuntu/dists/xenial/mongodb-org/4.2/multiverse/binary-amd64/mongodb-org-server_4.2.2_amd64.deb
+        #https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.14.tgz
+        #https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.22.tgz
+        plugin: dump
+        stage-packages: [libssl1.0.0, libcurl3]
+        filesets:
+            mongo:
+                - usr
+                - bin
+                - lib
+        stage:
+            - $mongo
+        prime:
+            - $mongo
+
+    wekan:
+        source: .
+        plugin: nodejs
+        node-engine: 12.14.1
+        node-packages:
+            - node-gyp
+            - node-pre-gyp
+            - fibers
+        build-packages:
+            - ca-certificates
+            - apt-utils
+            - build-essential
+            - python
+            - python3
+            - g++
+            - capnproto
+            - curl
+            - libcurl3
+            - execstack
+            - nodejs
+            - npm
+        stage-packages:
+            - libfontconfig1
+        override-build: |
+            echo "Cleaning environment first"
+            rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
+            rm -rf .build
+            echo "Installing meteor"
+            curl https://install.meteor.com/ -o install_meteor.sh
+            chmod +x install_meteor.sh
+            sh install_meteor.sh
+            rm install_meteor.sh
+            rm -rf .build
+            meteor add standard-minifier-js --allow-superuser
+            meteor npm install --allow-superuser
+            meteor npm install --allow-superuser --save babel-runtime
+            meteor build .build --directory --allow-superuser
+            cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
+            cd .build/bundle/programs/server
+            npm install
+            npm install --allow-superuser --save babel-runtime
+            # Change back to Wekan source directory
+            cd ../../../..
+            cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
+            cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
+            rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
+            rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
+            rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp
+            rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
+            rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
+            rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
+
+        organize:
+            README: README.wekan
+        prime:
+            - -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
+
+    helpers:
+        source: snap-src
+        plugin: dump
+
+    caddy:
+        plugin: dump
+        source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
+        source-type: tar
+        organize:
+          caddy: bin/caddy
+          CHANGES.txt: CADDY_CHANGES.txt
+          EULA.txt: CADDY_EULA.txt
+          LICENSES.txt: CADDY_LICENSES.txt
+          README.txt: CADDY_README.txt
+        stage:
+          - -init

+ 584 - 0
.snap-meteor-1.8/ldap.js

@@ -0,0 +1,584 @@
+import ldapjs from 'ldapjs';
+import util from 'util';
+import Bunyan from 'bunyan';
+import {log_debug, log_info, log_warn, log_error} from './logger';
+
+
+export default class LDAP {
+  constructor() {
+    this.ldapjs = ldapjs;
+
+    this.connected = false;
+
+    this.options = {
+      host                               : this.constructor.settings_get('LDAP_HOST'),
+      port                               : this.constructor.settings_get('LDAP_PORT'),
+      Reconnect                          : this.constructor.settings_get('LDAP_RECONNECT'),
+      timeout                            : this.constructor.settings_get('LDAP_TIMEOUT'),
+      connect_timeout                    : this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
+      idle_timeout                       : this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
+      encryption                         : this.constructor.settings_get('LDAP_ENCRYPTION'),
+      ca_cert                            : this.constructor.settings_get('LDAP_CA_CERT'),
+      reject_unauthorized                : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
+      Authentication                     : this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
+      Authentication_UserDN              : this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
+      Authentication_Password            : this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
+      Authentication_Fallback            : this.constructor.settings_get('LDAP_LOGIN_FALLBACK'),
+      BaseDN                             : this.constructor.settings_get('LDAP_BASEDN'),
+      Internal_Log_Level                 : this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
+      User_Authentication                : this.constructor.settings_get('LDAP_USER_AUTHENTICATION'),
+      User_Authentication_Field          : this.constructor.settings_get('LDAP_USER_AUTHENTICATION_FIELD'),
+      User_Attributes                    : this.constructor.settings_get('LDAP_USER_ATTRIBUTES'),
+      User_Search_Filter                 : this.constructor.settings_get('LDAP_USER_SEARCH_FILTER'),
+      User_Search_Scope                  : this.constructor.settings_get('LDAP_USER_SEARCH_SCOPE'),
+      User_Search_Field                  : this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'),
+      Search_Page_Size                   : this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
+      Search_Size_Limit                  : this.constructor.settings_get('LDAP_SEARCH_SIZE_LIMIT'),
+      group_filter_enabled               : this.constructor.settings_get('LDAP_GROUP_FILTER_ENABLE'),
+      group_filter_object_class          : this.constructor.settings_get('LDAP_GROUP_FILTER_OBJECTCLASS'),
+      group_filter_group_id_attribute    : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE'),
+      group_filter_group_member_attribute: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE'),
+      group_filter_group_member_format   : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT'),
+      group_filter_group_name            : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_NAME'),
+    };
+  }
+
+  static settings_get(name, ...args) {
+    let value = process.env[name];
+    if (value !== undefined) {
+      if (value === 'true' || value === 'false') {
+        value = JSON.parse(value);
+      } else if (value !== '' && !isNaN(value)) {
+        value = Number(value);
+      }
+      return value;
+    } else {
+      log_warn(`Lookup for unset variable: ${name}`);
+    }
+  }
+
+  connectSync(...args) {
+     if (!this._connectSync) {
+      this._connectSync = Meteor.wrapAsync(this.connectAsync, this);
+    }
+    return this._connectSync(...args);
+  }
+
+  searchAllSync(...args) {
+
+    if (!this._searchAllSync) {
+      this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this);
+    }
+    return this._searchAllSync(...args);
+  }
+
+  connectAsync(callback) {
+    log_info('Init setup');
+
+    let replied = false;
+
+    const connectionOptions = {
+      url           : `${this.options.host}:${this.options.port}`,
+      timeout       : this.options.timeout,
+      connectTimeout: this.options.connect_timeout,
+      idleTimeout   : this.options.idle_timeout,
+      reconnect     : this.options.Reconnect,
+    };
+
+    if (this.options.Internal_Log_Level !== 'disabled') {
+      connectionOptions.log = new Bunyan({
+        name     : 'ldapjs',
+        component: 'client',
+        stream   : process.stderr,
+        level    : this.options.Internal_Log_Level,
+      });
+    }
+
+    const tlsOptions = {
+      rejectUnauthorized: this.options.reject_unauthorized,
+    };
+
+    if (this.options.ca_cert && this.options.ca_cert !== '') {
+      // Split CA cert into array of strings
+      const chainLines = this.constructor.settings_get('LDAP_CA_CERT').split('\n');
+      let cert         = [];
+      const ca         = [];
+      chainLines.forEach((line) => {
+        cert.push(line);
+        if (line.match(/-END CERTIFICATE-/)) {
+          ca.push(cert.join('\n'));
+          cert = [];
+        }
+      });
+      tlsOptions.ca = ca;
+    }
+
+    if (this.options.encryption === 'ssl') {
+      connectionOptions.url        = `ldaps://${connectionOptions.url}`;
+      connectionOptions.tlsOptions = tlsOptions;
+    } else {
+      connectionOptions.url = `ldap://${connectionOptions.url}`;
+    }
+
+    log_info('Connecting', connectionOptions.url);
+    log_debug(`connectionOptions${util.inspect(connectionOptions)}`);
+
+    this.client = ldapjs.createClient(connectionOptions);
+
+    this.bindSync = Meteor.wrapAsync(this.client.bind, this.client);
+
+    this.client.on('error', (error) => {
+      log_error('connection', error);
+      if (replied === false) {
+        replied = true;
+        callback(error, null);
+      }
+    });
+
+    this.client.on('idle', () => {
+      log_info('Idle');
+      this.disconnect();
+    });
+
+    this.client.on('close', () => {
+      log_info('Closed');
+    });
+
+    if (this.options.encryption === 'tls') {
+      // Set host parameter for tls.connect which is used by ldapjs starttls. This shouldn't be needed in newer nodejs versions (e.g v5.6.0).
+      // https://github.com/RocketChat/Rocket.Chat/issues/2035
+      // https://github.com/mcavage/node-ldapjs/issues/349
+      tlsOptions.host = this.options.host;
+
+      log_info('Starting TLS');
+      log_debug('tlsOptions', tlsOptions);
+
+      this.client.starttls(tlsOptions, null, (error, response) => {
+        if (error) {
+          log_error('TLS connection', error);
+          if (replied === false) {
+            replied = true;
+            callback(error, null);
+          }
+          return;
+        }
+
+        log_info('TLS connected');
+        this.connected = true;
+        if (replied === false) {
+          replied = true;
+          callback(null, response);
+        }
+      });
+    } else {
+      this.client.on('connect', (response) => {
+        log_info('LDAP connected');
+        this.connected = true;
+        if (replied === false) {
+          replied = true;
+          callback(null, response);
+        }
+      });
+    }
+
+    setTimeout(() => {
+      if (replied === false) {
+        log_error('connection time out', connectionOptions.connectTimeout);
+        replied = true;
+        callback(new Error('Timeout'));
+      }
+    }, connectionOptions.connectTimeout);
+  }
+
+  getUserFilter(username) {
+    const filter = [];
+
+    if (this.options.User_Search_Filter !== '') {
+      if (this.options.User_Search_Filter[0] === '(') {
+        filter.push(`${this.options.User_Search_Filter}`);
+      } else {
+        filter.push(`(${this.options.User_Search_Filter})`);
+      }
+    }
+
+    const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${item}=${username})`);
+
+    if (usernameFilter.length === 0) {
+      log_error('LDAP_LDAP_User_Search_Field not defined');
+    } else if (usernameFilter.length === 1) {
+      filter.push(`${usernameFilter[0]}`);
+    } else {
+      filter.push(`(|${usernameFilter.join('')})`);
+    }
+
+    return `(&${filter.join('')})`;
+  }
+
+  bindUserIfNecessary(username, password) {
+
+    if (this.domainBinded === true) {
+      return;
+    }
+
+    if (!this.options.User_Authentication) {
+      return;
+    }
+
+
+    if (!this.options.BaseDN) throw new Error('BaseDN is not provided');
+
+    const userDn = `${this.options.User_Authentication_Field}=${username},${this.options.BaseDN}`;
+
+    this.bindSync(userDn, password);
+    this.domainBinded = true;
+  }
+
+  bindIfNecessary() {
+    if (this.domainBinded === true) {
+      return;
+    }
+
+    if (this.options.Authentication !== true) {
+      return;
+    }
+
+    log_info('Binding UserDN', this.options.Authentication_UserDN);
+
+    this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password);
+    this.domainBinded = true;
+  }
+
+  searchUsersSync(username, page) {
+    this.bindIfNecessary();
+    const searchOptions = {
+      filter   : this.getUserFilter(username),
+      scope    : this.options.User_Search_Scope || 'sub',
+      sizeLimit: this.options.Search_Size_Limit,
+    };
+
+    if (!!this.options.User_Attributes) searchOptions.attributes = this.options.User_Attributes.split(',');
+
+    if (this.options.Search_Page_Size > 0) {
+      searchOptions.paged = {
+        pageSize : this.options.Search_Page_Size,
+        pagePause: !!page,
+      };
+    }
+
+    log_info('Searching user', username);
+    log_debug('searchOptions', searchOptions);
+    log_debug('BaseDN', this.options.BaseDN);
+
+    if (page) {
+      return this.searchAllPaged(this.options.BaseDN, searchOptions, page);
+    }
+
+    return this.searchAllSync(this.options.BaseDN, searchOptions);
+  }
+
+  getUserByIdSync(id, attribute) {
+    this.bindIfNecessary();
+
+    const Unique_Identifier_Field = this.constructor.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD').split(',');
+
+    let filter;
+
+    if (attribute) {
+      filter = new this.ldapjs.filters.EqualityFilter({
+        attribute,
+        value: new Buffer(id, 'hex'),
+      });
+    } else {
+      const filters = [];
+      Unique_Identifier_Field.forEach((item) => {
+        filters.push(new this.ldapjs.filters.EqualityFilter({
+          attribute: item,
+          value    : new Buffer(id, 'hex'),
+        }));
+      });
+
+      filter = new this.ldapjs.filters.OrFilter({ filters });
+    }
+
+    const searchOptions = {
+      filter,
+      scope: 'sub',
+    };
+
+    log_info('Searching by id', id);
+    log_debug('search filter', searchOptions.filter.toString());
+    log_debug('BaseDN', this.options.BaseDN);
+
+    const result = this.searchAllSync(this.options.BaseDN, searchOptions);
+
+    if (!Array.isArray(result) || result.length === 0) {
+      return;
+    }
+
+    if (result.length > 1) {
+      log_error('Search by id', id, 'returned', result.length, 'records');
+    }
+
+    return result[0];
+  }
+
+  getUserByUsernameSync(username) {
+    this.bindIfNecessary();
+
+    const searchOptions = {
+      filter: this.getUserFilter(username),
+      scope : this.options.User_Search_Scope || 'sub',
+    };
+
+    log_info('Searching user', username);
+    log_debug('searchOptions', searchOptions);
+    log_debug('BaseDN', this.options.BaseDN);
+
+    const result = this.searchAllSync(this.options.BaseDN, searchOptions);
+
+    if (!Array.isArray(result) || result.length === 0) {
+      return;
+    }
+
+    if (result.length > 1) {
+      log_error('Search by username', username, 'returned', result.length, 'records');
+    }
+
+    return result[0];
+  }
+
+  getUserGroups(username, ldapUser) {
+    if (!this.options.group_filter_enabled) {
+      return true;
+    }
+
+    const filter = ['(&'];
+
+    if (this.options.group_filter_object_class !== '') {
+      filter.push(`(objectclass=${this.options.group_filter_object_class})`);
+    }
+
+    if (this.options.group_filter_group_member_attribute !== '') {
+      const format_value = ldapUser[this.options.group_filter_group_member_format];
+      if (format_value) {
+        filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
+      }
+    }
+
+    filter.push(')');
+
+    const searchOptions = {
+      filter: filter.join('').replace(/#{username}/g, username),
+      scope : 'sub',
+    };
+
+    log_debug('Group list filter LDAP:', searchOptions.filter);
+
+    const result = this.searchAllSync(this.options.BaseDN, searchOptions);
+
+    if (!Array.isArray(result) || result.length === 0) {
+      return [];
+    }
+
+    const grp_identifier = this.options.group_filter_group_id_attribute || 'cn';
+    const groups         = [];
+    result.map((item) => {
+      groups.push(item[grp_identifier]);
+    });
+    log_debug(`Groups: ${groups.join(', ')}`);
+    return groups;
+
+  }
+
+  isUserInGroup(username, ldapUser) {
+    if (!this.options.group_filter_enabled) {
+      return true;
+    }
+
+    const grps = this.getUserGroups(username, ldapUser);
+
+    const filter = ['(&'];
+
+    if (this.options.group_filter_object_class !== '') {
+      filter.push(`(objectclass=${this.options.group_filter_object_class})`);
+    }
+
+    if (this.options.group_filter_group_member_attribute !== '') {
+      const format_value = ldapUser[this.options.group_filter_group_member_format];
+      if (format_value) {
+        filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
+      }
+    }
+
+    if (this.options.group_filter_group_id_attribute !== '') {
+      filter.push(`(${this.options.group_filter_group_id_attribute}=${this.options.group_filter_group_name})`);
+    }
+    filter.push(')');
+
+    const searchOptions = {
+      filter: filter.join('').replace(/#{username}/g, username),
+      scope : 'sub',
+    };
+
+    log_debug('Group filter LDAP:', searchOptions.filter);
+
+    const result = this.searchAllSync(this.options.BaseDN, searchOptions);
+
+    if (!Array.isArray(result) || result.length === 0) {
+      return false;
+    }
+    return true;
+  }
+
+  extractLdapEntryData(entry) {
+    const values = {
+      _raw: entry.raw,
+    };
+
+    Object.keys(values._raw).forEach((key) => {
+      const value = values._raw[key];
+
+      if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) {
+        if (value instanceof Buffer) {
+          values[key] = value.toString();
+        } else {
+          values[key] = value;
+        }
+      }
+    });
+
+    return values;
+  }
+
+  searchAllPaged(BaseDN, options, page) {
+    this.bindIfNecessary();
+
+    const processPage = ({ entries, title, end, next }) => {
+      log_info(title);
+      // Force LDAP idle to wait the record processing
+      this.client._updateIdle(true);
+      page(null, entries, {
+        end, next: () => {
+          // Reset idle timer
+          this.client._updateIdle();
+          next && next();
+        }
+      });
+    };
+
+    this.client.search(BaseDN, options, (error, res) => {
+      if (error) {
+        log_error(error);
+        page(error);
+        return;
+      }
+
+      res.on('error', (error) => {
+        log_error(error);
+        page(error);
+        return;
+      });
+
+      let entries = [];
+
+      const internalPageSize = options.paged && options.paged.pageSize > 0 ? options.paged.pageSize * 2 : 500;
+
+      res.on('searchEntry', (entry) => {
+        entries.push(this.extractLdapEntryData(entry));
+
+        if (entries.length >= internalPageSize) {
+          processPage({
+            entries,
+            title: 'Internal Page',
+            end  : false,
+          });
+          entries = [];
+        }
+      });
+
+      res.on('page', (result, next) => {
+        if (!next) {
+          this.client._updateIdle(true);
+          processPage({
+            entries,
+            title: 'Final Page',
+            end  : true,
+          });
+        } else if (entries.length) {
+          log_info('Page');
+          processPage({
+            entries,
+            title: 'Page',
+            end  : false,
+            next,
+          });
+          entries = [];
+        }
+      });
+
+      res.on('end', () => {
+        if (entries.length) {
+          processPage({
+            entries,
+            title: 'Final Page',
+            end  : true,
+          });
+          entries = [];
+        }
+      });
+    });
+  }
+
+  searchAllAsync(BaseDN, options, callback) {
+    this.bindIfNecessary();
+
+    this.client.search(BaseDN, options, (error, res) => {
+      if (error) {
+        log_error(error);
+        callback(error);
+        return;
+      }
+
+      res.on('error', (error) => {
+        log_error(error);
+        callback(error);
+        return;
+      });
+
+      const entries = [];
+
+      res.on('searchEntry', (entry) => {
+        entries.push(this.extractLdapEntryData(entry));
+      });
+
+      res.on('end', () => {
+        log_info('Search result count', entries.length);
+        callback(null, entries);
+      });
+    });
+  }
+
+  authSync(dn, password) {
+    log_info('Authenticating', dn);
+
+    try {
+      if (password === '') {
+        throw new Error('Password is not provided');
+      }
+      this.bindSync(dn, password);
+      log_info('Authenticated', dn);
+      return true;
+    } catch (error) {
+      log_info('Not authenticated', dn);
+      log_debug('error', error);
+      return false;
+    }
+  }
+
+  disconnect() {
+    this.connected    = false;
+    this.domainBinded = false;
+    log_info('Disconecting');
+    this.client.unbind();
+  }
+}

+ 149 - 0
.snap-meteor-1.8/oidc_server.js

@@ -0,0 +1,149 @@
+Oidc = {};
+
+OAuth.registerService('oidc', 2, null, function (query) {
+
+  var debug = process.env.DEBUG || false;
+  var token = getToken(query);
+  if (debug) console.log('XXX: register token:', token);
+
+  var accessToken = token.access_token || token.id_token;
+  var expiresAt = (+new Date) + (1000 * parseInt(token.expires_in, 10));
+
+  var userinfo = getUserInfo(accessToken);
+  if (debug) console.log('XXX: userinfo:', userinfo);
+
+  var serviceData = {};
+  serviceData.id = userinfo[process.env.OAUTH2_ID_MAP]; // || userinfo["id"];
+  serviceData.username = userinfo[process.env.OAUTH2_USERNAME_MAP]; // || userinfo["uid"];
+  serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"];
+  serviceData.accessToken = accessToken;
+  serviceData.expiresAt = expiresAt;
+  serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"];
+
+  if (accessToken) {
+    var tokenContent = getTokenContent(accessToken);
+    var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields);
+    _.extend(serviceData, fields);
+  }
+
+  if (token.refresh_token)
+    serviceData.refreshToken = token.refresh_token;
+  if (debug) console.log('XXX: serviceData:', serviceData);
+
+  var profile = {};
+  profile.name = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"];
+  profile.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"];
+  if (debug) console.log('XXX: profile:', profile);
+
+  return {
+    serviceData: serviceData,
+    options: { profile: profile }
+  };
+});
+
+var userAgent = "Meteor";
+if (Meteor.release) {
+  userAgent += "/" + Meteor.release;
+}
+
+var getToken = function (query) {
+  var debug = process.env.DEBUG || false;
+  var config = getConfiguration();
+  if(config.tokenEndpoint.includes('https://')){
+    var serverTokenEndpoint = config.tokenEndpoint;
+  }else{
+    var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint;
+  }
+  var requestPermissions = config.requestPermissions;
+  var response;
+
+  try {
+    response = HTTP.post(
+      serverTokenEndpoint,
+      {
+        headers: {
+          Accept: 'application/json',
+          "User-Agent": userAgent
+        },
+        params: {
+          code: query.code,
+          client_id: config.clientId,
+          client_secret: OAuth.openSecret(config.secret),
+          redirect_uri: OAuth._redirectUri('oidc', config),
+          grant_type: 'authorization_code',
+          scope: requestPermissions,
+          state: query.state
+        }
+      }
+    );
+  } catch (err) {
+    throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message),
+      { response: err.response });
+  }
+  if (response.data.error) {
+    // if the http response was a json object with an error attribute
+    throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error);
+  } else {
+    if (debug) console.log('XXX: getToken response: ', response.data);
+    return response.data;
+  }
+};
+
+var getUserInfo = function (accessToken) {
+  var debug = process.env.DEBUG || false;
+  var config = getConfiguration();
+  // Some userinfo endpoints use a different base URL than the authorization or token endpoints.
+  // This logic allows the end user to override the setting by providing the full URL to userinfo in their config.
+  if (config.userinfoEndpoint.includes("https://")) {
+    var serverUserinfoEndpoint = config.userinfoEndpoint;
+  } else {
+    var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint;
+  }
+  var response;
+  try {
+    response = HTTP.get(
+      serverUserinfoEndpoint,
+      {
+        headers: {
+          "User-Agent": userAgent,
+          "Authorization": "Bearer " + accessToken
+        }
+      }
+    );
+  } catch (err) {
+    throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message),
+                   {response: err.response});
+  }
+  if (debug) console.log('XXX: getUserInfo response: ', response.data);
+  return response.data;
+};
+
+var getConfiguration = function () {
+  var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' });
+  if (!config) {
+    throw new ServiceConfiguration.ConfigError('Service oidc not configured.');
+  }
+  return config;
+};
+
+var getTokenContent = function (token) {
+  var content = null;
+  if (token) {
+    try {
+      var parts = token.split('.');
+      var header = JSON.parse(new Buffer(parts[0], 'base64').toString());
+      content = JSON.parse(new Buffer(parts[1], 'base64').toString());
+      var signature = new Buffer(parts[2], 'base64');
+      var signed = parts[0] + '.' + parts[1];
+    } catch (err) {
+      this.content = {
+        exp: 0
+      };
+    }
+  }
+  return content;
+}
+
+Oidc.retrieveCredential = function (credentialToken, credentialSecret) {
+  return OAuth.retrieveCredential(credentialToken, credentialSecret);
+};

+ 5184 - 0
.snap-meteor-1.8/package-lock.json

@@ -0,0 +1,5184 @@
+{
+  "name": "wekan",
+  "version": "v3.78.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@babel/code-frame": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
+      "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
+      "dev": true,
+      "requires": {
+        "@babel/highlight": "^7.0.0"
+      }
+    },
+    "@babel/highlight": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
+      "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.0.0",
+        "esutils": "^2.0.2",
+        "js-tokens": "^4.0.0"
+      }
+    },
+    "@babel/runtime": {
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz",
+      "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==",
+      "requires": {
+        "regenerator-runtime": "^0.13.2"
+      }
+    },
+    "@samverschueren/stream-to-observable": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
+      "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==",
+      "dev": true,
+      "requires": {
+        "any-observable": "^0.3.0"
+      }
+    },
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+    },
+    "acorn": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
+      "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz",
+      "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==",
+      "dev": true
+    },
+    "ajv": {
+      "version": "5.5.2",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+      "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+      "requires": {
+        "co": "^4.6.0",
+        "fast-deep-equal": "^1.0.0",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.3.0"
+      }
+    },
+    "ajv-keywords": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz",
+      "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=",
+      "dev": true
+    },
+    "ansi-escapes": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz",
+      "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.5.2"
+      }
+    },
+    "ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "any-observable": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
+      "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==",
+      "dev": true
+    },
+    "aproba": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+    },
+    "are-we-there-yet": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+      "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+      "requires": {
+        "delegates": "^1.0.0",
+        "readable-stream": "^2.0.6"
+      }
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dev": true,
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "arr-diff": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+      "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+      "dev": true
+    },
+    "arr-flatten": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+      "dev": true
+    },
+    "arr-union": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+      "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+      "dev": true
+    },
+    "array-includes": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
+      "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.2",
+        "es-abstract": "^1.7.0"
+      }
+    },
+    "array-unique": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+      "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+      "dev": true
+    },
+    "asn1": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+      "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+    },
+    "assign-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+      "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+      "dev": true
+    },
+    "astral-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+      "dev": true
+    },
+    "atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "dev": true
+    },
+    "babel-code-frame": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+      "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+      "dev": true,
+      "requires": {
+        "chalk": "^1.1.3",
+        "esutils": "^2.0.2",
+        "js-tokens": "^3.0.2"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
+      }
+    },
+    "babel-runtime": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+      "requires": {
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
+      },
+      "dependencies": {
+        "regenerator-runtime": {
+          "version": "0.11.1",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+          "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+        }
+      }
+    },
+    "backoff": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
+      "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=",
+      "requires": {
+        "precond": "0.2"
+      }
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+    },
+    "base": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+      "dev": true,
+      "requires": {
+        "cache-base": "^1.0.1",
+        "class-utils": "^0.3.5",
+        "component-emitter": "^1.2.1",
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.1",
+        "mixin-deep": "^1.2.0",
+        "pascalcase": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "base64-js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
+    },
+    "bcrypt": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.6.tgz",
+      "integrity": "sha512-taA5bCTfXe7FUjKroKky9EXpdhkVvhE5owfxfLYodbrAR1Ul3juLmIQmIQBK4L9a5BuUcE6cqmwT+Da20lF9tg==",
+      "requires": {
+        "nan": "2.13.2",
+        "node-pre-gyp": "0.12.0"
+      }
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+      "dev": true,
+      "requires": {
+        "arr-flatten": "^1.1.0",
+        "array-unique": "^0.3.2",
+        "extend-shallow": "^2.0.1",
+        "fill-range": "^4.0.0",
+        "isobject": "^3.0.1",
+        "repeat-element": "^1.1.2",
+        "snapdragon": "^0.8.1",
+        "snapdragon-node": "^2.0.1",
+        "split-string": "^3.0.2",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "bson": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-4.0.2.tgz",
+      "integrity": "sha512-rBdCxMBCg2aR420e1oKUejjcuPZLTibA7zEhWAlliFWEwzuBCC9Dkp5r7VFFIQB2t1WVsvTbohry575mc7Xw5A==",
+      "requires": {
+        "buffer": "^5.1.0",
+        "long": "^4.0.0"
+      }
+    },
+    "buffer": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.3.0.tgz",
+      "integrity": "sha512-XykNc84nIOC32vZ9euOKbmGAP69JUkXDtBQfLq88c8/6J/gZi/t14A+l/p/9EM2TcT5xNC1MKPCrvO3LVUpVPw==",
+      "requires": {
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4"
+      }
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+    },
+    "bunyan": {
+      "version": "1.8.12",
+      "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
+      "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=",
+      "requires": {
+        "dtrace-provider": "~0.8",
+        "moment": "^2.10.6",
+        "mv": "~2",
+        "safe-json-stringify": "~1"
+      }
+    },
+    "cache-base": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+      "dev": true,
+      "requires": {
+        "collection-visit": "^1.0.0",
+        "component-emitter": "^1.2.1",
+        "get-value": "^2.0.6",
+        "has-value": "^1.0.0",
+        "isobject": "^3.0.1",
+        "set-value": "^2.0.0",
+        "to-object-path": "^0.3.0",
+        "union-value": "^1.0.0",
+        "unset-value": "^1.0.0"
+      }
+    },
+    "caller-callsite": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+      "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+      "dev": true,
+      "requires": {
+        "callsites": "^2.0.0"
+      },
+      "dependencies": {
+        "callsites": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+          "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+          "dev": true
+        }
+      }
+    },
+    "caller-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+      "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+      "dev": true,
+      "requires": {
+        "caller-callsite": "^2.0.0"
+      }
+    },
+    "callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      }
+    },
+    "chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
+    "chownr": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz",
+      "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A=="
+    },
+    "circular-json": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
+      "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==",
+      "dev": true
+    },
+    "class-utils": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "define-property": "^0.2.5",
+        "isobject": "^3.0.0",
+        "static-extend": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^3.1.0"
+      }
+    },
+    "cli-truncate": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
+      "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
+      "dev": true,
+      "requires": {
+        "slice-ansi": "0.0.4",
+        "string-width": "^1.0.1"
+      },
+      "dependencies": {
+        "slice-ansi": {
+          "version": "0.0.4",
+          "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+          "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+          "dev": true
+        }
+      }
+    },
+    "cli-width": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+      "dev": true
+    },
+    "co": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
+    },
+    "code-point-at": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+    },
+    "collection-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+      "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+      "dev": true,
+      "requires": {
+        "map-visit": "^1.0.0",
+        "object-visit": "^1.0.0"
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
+    "commander": {
+      "version": "2.20.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
+      "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ=="
+    },
+    "common-tags": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
+      "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==",
+      "dev": true
+    },
+    "component-emitter": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "console-control-strings": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+    },
+    "contains-path": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+      "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+      "dev": true
+    },
+    "copy-descriptor": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+      "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+      "dev": true
+    },
+    "core-js": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
+      "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A=="
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "cosmiconfig": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+      "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+      "dev": true,
+      "requires": {
+        "import-fresh": "^2.0.0",
+        "is-directory": "^0.3.1",
+        "js-yaml": "^3.13.1",
+        "parse-json": "^4.0.0"
+      },
+      "dependencies": {
+        "import-fresh": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+          "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+          "dev": true,
+          "requires": {
+            "caller-path": "^2.0.0",
+            "resolve-from": "^3.0.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "resolve-from": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+          "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+          "dev": true
+        }
+      }
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
+    "cssfilter": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
+      "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4="
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "date-fns": {
+      "version": "1.30.1",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
+      "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
+      "dev": true
+    },
+    "debug": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+      "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+      "requires": {
+        "ms": "^2.1.1"
+      }
+    },
+    "decode-uri-component": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+      "dev": true
+    },
+    "dedent": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
+      "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
+      "dev": true
+    },
+    "deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
+    },
+    "deep-is": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+      "dev": true
+    },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "dev": true,
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
+    "define-property": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+      "dev": true,
+      "requires": {
+        "is-descriptor": "^1.0.2",
+        "isobject": "^3.0.1"
+      },
+      "dependencies": {
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "delegates": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
+    },
+    "detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
+    },
+    "dlv": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+      "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+      "dev": true
+    },
+    "doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "requires": {
+        "esutils": "^2.0.2"
+      }
+    },
+    "dtrace-provider": {
+      "version": "0.8.7",
+      "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz",
+      "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=",
+      "optional": true,
+      "requires": {
+        "nan": "^2.10.0"
+      }
+    },
+    "elegant-spinner": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
+      "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=",
+      "dev": true
+    },
+    "emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "es-abstract": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
+      "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==",
+      "dev": true,
+      "requires": {
+        "es-to-primitive": "^1.2.0",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "is-callable": "^1.1.4",
+        "is-regex": "^1.0.4",
+        "object-keys": "^1.0.12"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+      "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+      "dev": true,
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "eslint": {
+      "version": "5.16.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz",
+      "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "ajv": "^6.9.1",
+        "chalk": "^2.1.0",
+        "cross-spawn": "^6.0.5",
+        "debug": "^4.0.1",
+        "doctrine": "^3.0.0",
+        "eslint-scope": "^4.0.3",
+        "eslint-utils": "^1.3.1",
+        "eslint-visitor-keys": "^1.0.0",
+        "espree": "^5.0.1",
+        "esquery": "^1.0.1",
+        "esutils": "^2.0.2",
+        "file-entry-cache": "^5.0.1",
+        "functional-red-black-tree": "^1.0.1",
+        "glob": "^7.1.2",
+        "globals": "^11.7.0",
+        "ignore": "^4.0.6",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "inquirer": "^6.2.2",
+        "js-yaml": "^3.13.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.3.0",
+        "lodash": "^4.17.11",
+        "minimatch": "^3.0.4",
+        "mkdirp": "^0.5.1",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.8.2",
+        "path-is-inside": "^1.0.2",
+        "progress": "^2.0.0",
+        "regexpp": "^2.0.1",
+        "semver": "^5.5.1",
+        "strip-ansi": "^4.0.0",
+        "strip-json-comments": "^2.0.1",
+        "table": "^5.2.3",
+        "text-table": "^0.2.0"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "fast-deep-equal": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+          "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+          "dev": true
+        },
+        "json-schema-traverse": {
+          "version": "0.4.1",
+          "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+          "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "eslint-config-meteor": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmjs.org/eslint-config-meteor/-/eslint-config-meteor-0.0.9.tgz",
+      "integrity": "sha1-a+IZQguko+oCPbMKhm5g70h2Uvo=",
+      "dev": true
+    },
+    "eslint-config-prettier": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-3.6.0.tgz",
+      "integrity": "sha512-ixJ4U3uTLXwJts4rmSVW/lMXjlGwCijhBJHk8iVqKKSifeI0qgFEfWl8L63isfc8Od7EiBALF6BX3jKLluf/jQ==",
+      "dev": true,
+      "requires": {
+        "get-stdin": "^6.0.0"
+      }
+    },
+    "eslint-import-resolver-meteor": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-meteor/-/eslint-import-resolver-meteor-0.4.0.tgz",
+      "integrity": "sha1-yGhjhAghIIz4EzxczlGQnCamFWk=",
+      "dev": true,
+      "requires": {
+        "object-assign": "^4.0.1",
+        "resolve": "^1.1.6"
+      }
+    },
+    "eslint-import-resolver-node": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
+      "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.6.9",
+        "resolve": "^1.5.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-module-utils": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz",
+      "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.6.8",
+        "pkg-dir": "^2.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-import": {
+      "version": "2.18.2",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz",
+      "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==",
+      "dev": true,
+      "requires": {
+        "array-includes": "^3.0.3",
+        "contains-path": "^0.1.0",
+        "debug": "^2.6.9",
+        "doctrine": "1.5.0",
+        "eslint-import-resolver-node": "^0.3.2",
+        "eslint-module-utils": "^2.4.0",
+        "has": "^1.0.3",
+        "minimatch": "^3.0.4",
+        "object.values": "^1.1.0",
+        "read-pkg-up": "^2.0.0",
+        "resolve": "^1.11.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "doctrine": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+          "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "isarray": "^1.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-meteor": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-meteor/-/eslint-plugin-meteor-5.2.0.tgz",
+      "integrity": "sha512-bHzs/0BwHdKcBbX7tYrSnBaMG+1i2f1wy8k6H/sBBsERD/yifmBUrNLiPyZkIvyVUeI8OaZw8U9fsMvLP5GhIg==",
+      "dev": true,
+      "requires": {
+        "invariant": "2.2.4"
+      }
+    },
+    "eslint-plugin-prettier": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.0.tgz",
+      "integrity": "sha512-XWX2yVuwVNLOUhQijAkXz+rMPPoCr7WFiAl8ig6I7Xn+pPVhDhzg4DxHpmbeb0iqjO9UronEA3Tb09ChnFVHHA==",
+      "dev": true,
+      "requires": {
+        "prettier-linter-helpers": "^1.0.0"
+      }
+    },
+    "eslint-scope": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+      "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.1.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "eslint-utils": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
+      "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
+      "dev": true,
+      "requires": {
+        "eslint-visitor-keys": "^1.0.0"
+      }
+    },
+    "eslint-visitor-keys": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
+      "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==",
+      "dev": true
+    },
+    "espree": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
+      "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==",
+      "dev": true,
+      "requires": {
+        "acorn": "^6.0.7",
+        "acorn-jsx": "^5.0.0",
+        "eslint-visitor-keys": "^1.0.0"
+      }
+    },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "dev": true
+    },
+    "esquery": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+      "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^4.0.0"
+      }
+    },
+    "esrecurse": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^4.1.0"
+      }
+    },
+    "estraverse": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+      "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "execa": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz",
+      "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==",
+      "dev": true,
+      "requires": {
+        "cross-spawn": "^5.0.1",
+        "get-stream": "^3.0.0",
+        "is-stream": "^1.1.0",
+        "npm-run-path": "^2.0.0",
+        "p-finally": "^1.0.0",
+        "signal-exit": "^3.0.0",
+        "strip-eof": "^1.0.0"
+      },
+      "dependencies": {
+        "cross-spawn": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^4.0.1",
+            "shebang-command": "^1.2.0",
+            "which": "^1.2.9"
+          }
+        }
+      }
+    },
+    "expand-brackets": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+      "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+      "dev": true,
+      "requires": {
+        "debug": "^2.3.3",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "posix-character-classes": "^0.1.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "extend-shallow": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+      "dev": true,
+      "requires": {
+        "assign-symbols": "^1.0.0",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "requires": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      }
+    },
+    "extglob": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+      "dev": true,
+      "requires": {
+        "array-unique": "^0.3.2",
+        "define-property": "^1.0.0",
+        "expand-brackets": "^2.1.4",
+        "extend-shallow": "^2.0.1",
+        "fragment-cache": "^0.2.1",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "extsprintf": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.2.0.tgz",
+      "integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk="
+    },
+    "fast-deep-equal": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
+      "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
+    },
+    "fast-diff": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+      "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+      "dev": true
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+      "dev": true
+    },
+    "figures": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz",
+      "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==",
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "^1.0.5"
+      }
+    },
+    "file-entry-cache": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+      "dev": true,
+      "requires": {
+        "flat-cache": "^2.0.1"
+      }
+    },
+    "fill-range": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+      "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1",
+        "to-regex-range": "^2.1.0"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "find-parent-dir": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz",
+      "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=",
+      "dev": true
+    },
+    "find-up": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+      "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+      "dev": true,
+      "requires": {
+        "locate-path": "^2.0.0"
+      }
+    },
+    "flat-cache": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+      "dev": true,
+      "requires": {
+        "flatted": "^2.0.0",
+        "rimraf": "2.6.3",
+        "write": "1.0.3"
+      }
+    },
+    "flatted": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+      "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
+      "dev": true
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+      "dev": true
+    },
+    "fragment-cache": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+      "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+      "dev": true,
+      "requires": {
+        "map-cache": "^0.2.2"
+      }
+    },
+    "fs-minipass": {
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz",
+      "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==",
+      "requires": {
+        "minipass": "^2.2.1"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+      "dev": true
+    },
+    "gauge": {
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+      "requires": {
+        "aproba": "^1.0.3",
+        "console-control-strings": "^1.0.0",
+        "has-unicode": "^2.0.0",
+        "object-assign": "^4.1.0",
+        "signal-exit": "^3.0.0",
+        "string-width": "^1.0.1",
+        "strip-ansi": "^3.0.1",
+        "wide-align": "^1.1.0"
+      }
+    },
+    "get-own-enumerable-property-symbols": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz",
+      "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==",
+      "dev": true
+    },
+    "get-stdin": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
+      "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
+      "dev": true
+    },
+    "get-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+      "dev": true
+    },
+    "get-value": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+      "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.1.4",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+      "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "dev": true
+    },
+    "graceful-fs": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz",
+      "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==",
+      "dev": true
+    },
+    "gridfs-stream": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/gridfs-stream/-/gridfs-stream-0.5.3.tgz",
+      "integrity": "sha1-wIlnKPo+qD9fo8nO1GGvt6A20Uk="
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "has-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+      "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+      "dev": true
+    },
+    "has-unicode": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
+    },
+    "has-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+      "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+      "dev": true,
+      "requires": {
+        "get-value": "^2.0.6",
+        "has-values": "^1.0.0",
+        "isobject": "^3.0.0"
+      }
+    },
+    "has-values": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+      "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "kind-of": "^4.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "hosted-git-info": {
+      "version": "2.8.4",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz",
+      "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==",
+      "dev": true
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "ieee754": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
+    },
+    "ignore": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+      "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+      "dev": true
+    },
+    "ignore-walk": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
+      "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
+      "requires": {
+        "minimatch": "^3.0.4"
+      }
+    },
+    "import-fresh": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
+      "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
+      "dev": true,
+      "requires": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "dependencies": {
+        "resolve-from": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+          "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+          "dev": true
+        }
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "indent-string": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+      "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+    },
+    "inquirer": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz",
+      "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^2.4.2",
+        "cli-cursor": "^3.1.0",
+        "cli-width": "^2.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.15",
+        "mute-stream": "0.0.8",
+        "run-async": "^2.2.0",
+        "rxjs": "^6.4.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^5.1.0",
+        "through": "^2.3.6"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz",
+          "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^5.2.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "invariant": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+      "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+      "dev": true,
+      "requires": {
+        "loose-envify": "^1.0.0"
+      }
+    },
+    "is-accessor-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+      "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+      "dev": true
+    },
+    "is-callable": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+      "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+      "dev": true
+    },
+    "is-data-descriptor": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+      "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-date-object": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+      "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+      "dev": true
+    },
+    "is-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+      "dev": true,
+      "requires": {
+        "is-accessor-descriptor": "^0.1.6",
+        "is-data-descriptor": "^0.1.4",
+        "kind-of": "^5.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+          "dev": true
+        }
+      }
+    },
+    "is-directory": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+      "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+      "dev": true
+    },
+    "is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+      "dev": true
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+      "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+      "requires": {
+        "number-is-nan": "^1.0.0"
+      }
+    },
+    "is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-number": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+      "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+      "dev": true
+    },
+    "is-observable": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz",
+      "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==",
+      "dev": true,
+      "requires": {
+        "symbol-observable": "^1.1.0"
+      }
+    },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+      "dev": true
+    },
+    "is-regex": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+      "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.1"
+      }
+    },
+    "is-regexp": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+      "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=",
+      "dev": true
+    },
+    "is-resolvable": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
+      "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
+      "dev": true
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
+    },
+    "is-symbol": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+      "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.0"
+      }
+    },
+    "is-windows": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+      "dev": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+      "dev": true
+    },
+    "jest-get-type": {
+      "version": "22.4.3",
+      "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz",
+      "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==",
+      "dev": true
+    },
+    "jest-validate": {
+      "version": "23.6.0",
+      "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz",
+      "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.0.1",
+        "jest-get-type": "^22.1.0",
+        "leven": "^2.1.0",
+        "pretty-format": "^23.6.0"
+      }
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+      "dev": true,
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "json-schema-traverse": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
+      "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
+    },
+    "json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+      "dev": true
+    },
+    "kind-of": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+      "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+      "dev": true
+    },
+    "ldap-filter": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.2.2.tgz",
+      "integrity": "sha1-8rhCvguG2jNSeYUFsx68rlkNd9A=",
+      "requires": {
+        "assert-plus": "0.1.5"
+      },
+      "dependencies": {
+        "assert-plus": {
+          "version": "0.1.5",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz",
+          "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA="
+        }
+      }
+    },
+    "ldapjs": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-1.0.2.tgz",
+      "integrity": "sha1-VE/3Ayt7g8aPBwEyjZKXqmlDQPk=",
+      "requires": {
+        "asn1": "0.2.3",
+        "assert-plus": "^1.0.0",
+        "backoff": "^2.5.0",
+        "bunyan": "^1.8.3",
+        "dashdash": "^1.14.0",
+        "dtrace-provider": "~0.8",
+        "ldap-filter": "0.2.2",
+        "once": "^1.4.0",
+        "vasync": "^1.6.4",
+        "verror": "^1.8.1"
+      }
+    },
+    "leven": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
+      "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
+      "dev": true
+    },
+    "levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2"
+      }
+    },
+    "lint-staged": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-7.3.0.tgz",
+      "integrity": "sha512-AXk40M9DAiPi7f4tdJggwuKIViUplYtVj1os1MVEteW7qOkU50EOehayCfO9TsoGK24o/EsWb41yrEgfJDDjCw==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.3.1",
+        "commander": "^2.14.1",
+        "cosmiconfig": "^5.0.2",
+        "debug": "^3.1.0",
+        "dedent": "^0.7.0",
+        "execa": "^0.9.0",
+        "find-parent-dir": "^0.3.0",
+        "is-glob": "^4.0.0",
+        "is-windows": "^1.0.2",
+        "jest-validate": "^23.5.0",
+        "listr": "^0.14.1",
+        "lodash": "^4.17.5",
+        "log-symbols": "^2.2.0",
+        "micromatch": "^3.1.8",
+        "npm-which": "^3.0.1",
+        "p-map": "^1.1.1",
+        "path-is-inside": "^1.0.2",
+        "pify": "^3.0.0",
+        "please-upgrade-node": "^3.0.2",
+        "staged-git-files": "1.1.1",
+        "string-argv": "^0.0.2",
+        "stringify-object": "^3.2.2"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        }
+      }
+    },
+    "listr": {
+      "version": "0.14.3",
+      "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
+      "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==",
+      "dev": true,
+      "requires": {
+        "@samverschueren/stream-to-observable": "^0.3.0",
+        "is-observable": "^1.1.0",
+        "is-promise": "^2.1.0",
+        "is-stream": "^1.1.0",
+        "listr-silent-renderer": "^1.1.1",
+        "listr-update-renderer": "^0.5.0",
+        "listr-verbose-renderer": "^0.5.0",
+        "p-map": "^2.0.0",
+        "rxjs": "^6.3.3"
+      },
+      "dependencies": {
+        "p-map": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+          "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+          "dev": true
+        }
+      }
+    },
+    "listr-silent-renderer": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
+      "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=",
+      "dev": true
+    },
+    "listr-update-renderer": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz",
+      "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==",
+      "dev": true,
+      "requires": {
+        "chalk": "^1.1.3",
+        "cli-truncate": "^0.2.1",
+        "elegant-spinner": "^1.0.1",
+        "figures": "^1.7.0",
+        "indent-string": "^3.0.0",
+        "log-symbols": "^1.0.2",
+        "log-update": "^2.3.0",
+        "strip-ansi": "^3.0.1"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "figures": {
+          "version": "1.7.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+          "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+          "dev": true,
+          "requires": {
+            "escape-string-regexp": "^1.0.5",
+            "object-assign": "^4.1.0"
+          }
+        },
+        "log-symbols": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
+          "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
+          "dev": true,
+          "requires": {
+            "chalk": "^1.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
+      }
+    },
+    "listr-verbose-renderer": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz",
+      "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.4.1",
+        "cli-cursor": "^2.1.0",
+        "date-fns": "^1.27.2",
+        "figures": "^2.0.0"
+      },
+      "dependencies": {
+        "cli-cursor": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+          "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+          "dev": true,
+          "requires": {
+            "restore-cursor": "^2.0.0"
+          }
+        },
+        "figures": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+          "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+          "dev": true,
+          "requires": {
+            "escape-string-regexp": "^1.0.5"
+          }
+        },
+        "mimic-fn": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+          "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+          "dev": true
+        },
+        "onetime": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+          "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+          "dev": true,
+          "requires": {
+            "mimic-fn": "^1.0.0"
+          }
+        },
+        "restore-cursor": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+          "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+          "dev": true,
+          "requires": {
+            "onetime": "^2.0.0",
+            "signal-exit": "^3.0.2"
+          }
+        }
+      }
+    },
+    "load-json-file": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+      "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "parse-json": "^2.2.0",
+        "pify": "^2.0.0",
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "locate-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+      "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+      "dev": true,
+      "requires": {
+        "p-locate": "^2.0.0",
+        "path-exists": "^3.0.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+      "dev": true
+    },
+    "lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "lodash.unescape": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
+      "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
+      "dev": true
+    },
+    "log-symbols": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+      "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.0.1"
+      }
+    },
+    "log-update": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz",
+      "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^3.0.0",
+        "cli-cursor": "^2.0.0",
+        "wrap-ansi": "^3.0.1"
+      },
+      "dependencies": {
+        "ansi-escapes": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+          "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+          "dev": true
+        },
+        "cli-cursor": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+          "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+          "dev": true,
+          "requires": {
+            "restore-cursor": "^2.0.0"
+          }
+        },
+        "mimic-fn": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+          "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+          "dev": true
+        },
+        "onetime": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+          "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+          "dev": true,
+          "requires": {
+            "mimic-fn": "^1.0.0"
+          }
+        },
+        "restore-cursor": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+          "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+          "dev": true,
+          "requires": {
+            "onetime": "^2.0.0",
+            "signal-exit": "^3.0.2"
+          }
+        }
+      }
+    },
+    "loglevel": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz",
+      "integrity": "sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==",
+      "dev": true
+    },
+    "loglevel-colored-level-prefix": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz",
+      "integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=",
+      "dev": true,
+      "requires": {
+        "chalk": "^1.1.3",
+        "loglevel": "^1.4.1"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
+      }
+    },
+    "long": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+    },
+    "loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "dev": true,
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
+    "lru-cache": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+      "dev": true,
+      "requires": {
+        "pseudomap": "^1.0.2",
+        "yallist": "^2.1.2"
+      },
+      "dependencies": {
+        "yallist": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "dev": true
+        }
+      }
+    },
+    "map-cache": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+      "dev": true
+    },
+    "map-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+      "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+      "dev": true,
+      "requires": {
+        "object-visit": "^1.0.0"
+      }
+    },
+    "memory-pager": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+      "optional": true
+    },
+    "meteor-node-stubs": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-0.4.1.tgz",
+      "integrity": "sha512-UO2OStvLOKoApmOdIP5eCqoLaa/ritMXRg4ffJVdkNLEsczzPvTjgC0Mxk4cM4R8MZkwll90FYgjDf5qUTJdMA==",
+      "requires": {
+        "assert": "^1.4.1",
+        "browserify-zlib": "^0.1.4",
+        "buffer": "^4.9.1",
+        "console-browserify": "^1.1.0",
+        "constants-browserify": "^1.0.0",
+        "crypto-browserify": "^3.11.0",
+        "domain-browser": "^1.1.7",
+        "events": "^1.1.1",
+        "https-browserify": "0.0.1",
+        "os-browserify": "^0.2.1",
+        "path-browserify": "0.0.0",
+        "process": "^0.11.9",
+        "punycode": "^1.4.1",
+        "querystring-es3": "^0.2.1",
+        "readable-stream": "^2.3.6",
+        "stream-browserify": "^2.0.1",
+        "stream-http": "^2.8.0",
+        "string_decoder": "^1.1.0",
+        "timers-browserify": "^1.4.2",
+        "tty-browserify": "0.0.0",
+        "url": "^0.11.0",
+        "util": "^0.10.3",
+        "vm-browserify": "0.0.4"
+      },
+      "dependencies": {
+        "asn1.js": {
+          "version": "4.10.1",
+          "bundled": true,
+          "requires": {
+            "bn.js": "^4.0.0",
+            "inherits": "^2.0.1",
+            "minimalistic-assert": "^1.0.0"
+          }
+        },
+        "assert": {
+          "version": "1.4.1",
+          "bundled": true,
+          "requires": {
+            "util": "0.10.3"
+          }
+        },
+        "base64-js": {
+          "version": "1.3.0",
+          "bundled": true
+        },
+        "bn.js": {
+          "version": "4.11.8",
+          "bundled": true
+        },
+        "brorand": {
+          "version": "1.1.0",
+          "bundled": true
+        },
+        "browserify-aes": {
+          "version": "1.2.0",
+          "bundled": true,
+          "requires": {
+            "buffer-xor": "^1.0.3",
+            "cipher-base": "^1.0.0",
+            "create-hash": "^1.1.0",
+            "evp_bytestokey": "^1.0.3",
+            "inherits": "^2.0.1",
+            "safe-buffer": "^5.0.1"
+          }
+        },
+        "browserify-cipher": {
+          "version": "1.0.1",
+          "bundled": true,
+          "requires": {
+            "browserify-aes": "^1.0.4",
+            "browserify-des": "^1.0.0",
+            "evp_bytestokey": "^1.0.0"
+          }
+        },
+        "browserify-des": {
+          "version": "1.0.1",
+          "bundled": true,
+          "requires": {
+            "cipher-base": "^1.0.1",
+            "des.js": "^1.0.0",
+            "inherits": "^2.0.1"
+          }
+        },
+        "browserify-rsa": {
+          "version": "4.0.1",
+          "bundled": true,
+          "requires": {
+            "bn.js": "^4.1.0",
+            "randombytes": "^2.0.1"
+          }
+        },
+        "browserify-sign": {
+          "version": "4.0.4",
+          "bundled": true,
+          "requires": {
+            "bn.js": "^4.1.1",
+            "browserify-rsa": "^4.0.0",
+            "create-hash": "^1.1.0",
+            "create-hmac": "^1.1.2",
+            "elliptic": "^6.0.0",
+            "inherits": "^2.0.1",
+            "parse-asn1": "^5.0.0"
+          }
+        },
+        "browserify-zlib": {
+          "version": "0.1.4",
+          "bundled": true,
+          "requires": {
+            "pako": "~0.2.0"
+          }
+        },
+        "buffer": {
+          "version": "4.9.1",
+          "bundled": true,
+          "requires": {
+            "base64-js": "^1.0.2",
+            "ieee754": "^1.1.4",
+            "isarray": "^1.0.0"
+          }
+        },
+        "buffer-xor": {
+          "version": "1.0.3",
+          "bundled": true
+        },
+        "builtin-status-codes": {
+          "version": "3.0.0",
+          "bundled": true
+        },
+        "cipher-base": {
+          "version": "1.0.4",
+          "bundled": true,
+          "requires": {
+            "inherits": "^2.0.1",
+            "safe-buffer": "^5.0.1"
+          }
+        },
+        "console-browserify": {
+          "version": "1.1.0",
+          "bundled": true,
+          "requires": {
+            "date-now": "^0.1.4"
+          }
+        },
+        "constants-browserify": {
+          "version": "1.0.0",
+          "bundled": true
+        },
+        "core-util-is": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "create-ecdh": {
+          "version": "4.0.3",
+          "bundled": true,
+          "requires": {
+            "bn.js": "^4.1.0",
+            "elliptic": "^6.0.0"
+          }
+        },
+        "create-hash": {
+          "version": "1.2.0",
+          "bundled": true,
+          "requires": {
+            "cipher-base": "^1.0.1",
+            "inherits": "^2.0.1",
+            "md5.js": "^1.3.4",
+            "ripemd160": "^2.0.1",
+            "sha.js": "^2.4.0"
+          }
+        },
+        "create-hmac": {
+          "version": "1.1.7",
+          "bundled": true,
+          "requires": {
+            "cipher-base": "^1.0.3",
+            "create-hash": "^1.1.0",
+            "inherits": "^2.0.1",
+            "ripemd160": "^2.0.0",
+            "safe-buffer": "^5.0.1",
+            "sha.js": "^2.4.8"
+          }
+        },
+        "crypto-browserify": {
+          "version": "3.12.0",
+          "bundled": true,
+          "requires": {
+            "browserify-cipher": "^1.0.0",
+            "browserify-sign": "^4.0.0",
+            "create-ecdh": "^4.0.0",
+            "create-hash": "^1.1.0",
+            "create-hmac": "^1.1.0",
+            "diffie-hellman": "^5.0.0",
+            "inherits": "^2.0.1",
+            "pbkdf2": "^3.0.3",
+            "public-encrypt": "^4.0.0",
+            "randombytes": "^2.0.0",
+            "randomfill": "^1.0.3"
+          }
+        },
+        "date-now": {
+          "version": "0.1.4",
+          "bundled": true
+        },
+        "des.js": {
+          "version": "1.0.0",
+          "bundled": true,
+          "requires": {
+            "inherits": "^2.0.1",
+            "minimalistic-assert": "^1.0.0"
+          }
+        },
+        "diffie-hellman": {
+          "version": "5.0.3",
+          "bundled": true,
+          "requires": {
+            "bn.js": "^4.1.0",
+            "miller-rabin": "^4.0.0",
+            "randombytes": "^2.0.0"
+          }
+        },
+        "domain-browser": {
+          "version": "1.2.0",
+          "bundled": true
+        },
+        "elliptic": {
+          "version": "6.4.0",
+          "bundled": true,
+          "requires": {
+            "bn.js": "^4.4.0",
+            "brorand": "^1.0.1",
+            "hash.js": "^1.0.0",
+            "hmac-drbg": "^1.0.0",
+            "inherits": "^2.0.1",
+            "minimalistic-assert": "^1.0.0",
+            "minimalistic-crypto-utils": "^1.0.0"
+          }
+        },
+        "events": {
+          "version": "1.1.1",
+          "bundled": true
+        },
+        "evp_bytestokey": {
+          "version": "1.0.3",
+          "bundled": true,
+          "requires": {
+            "md5.js": "^1.3.4",
+            "safe-buffer": "^5.1.1"
+          }
+        },
+        "hash-base": {
+          "version": "3.0.4",
+          "bundled": true,
+          "requires": {
+            "inherits": "^2.0.1",
+            "safe-buffer": "^5.0.1"
+          }
+        },
+        "hash.js": {
+          "version": "1.1.3",
+          "bundled": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "minimalistic-assert": "^1.0.0"
+          },
+          "dependencies": {
+            "inherits": {
+              "version": "2.0.3",
+              "bundled": true
+            }
+          }
+        },
+        "hmac-drbg": {
+          "version": "1.0.1",
+          "bundled": true,
+          "requires": {
+            "hash.js": "^1.0.3",
+            "minimalistic-assert": "^1.0.0",
+            "minimalistic-crypto-utils": "^1.0.1"
+          }
+        },
+        "https-browserify": {
+          "version": "0.0.1",
+          "bundled": true
+        },
+        "ieee754": {
+          "version": "1.1.11",
+          "bundled": true
+        },
+        "indexof": {
+          "version": "0.0.1",
+          "bundled": true
+        },
+        "inherits": {
+          "version": "2.0.1",
+          "bundled": true
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "bundled": true
+        },
+        "md5.js": {
+          "version": "1.3.4",
+          "bundled": true,
+          "requires": {
+            "hash-base": "^3.0.0",
+            "inherits": "^2.0.1"
+          }
+        },
+        "miller-rabin": {
+          "version": "4.0.1",
+          "bundled": true,
+          "requires": {
+            "bn.js": "^4.0.0",
+            "brorand": "^1.0.1"
+          }
+        },
+        "minimalistic-assert": {
+          "version": "1.0.1",
+          "bundled": true
+        },
+        "minimalistic-crypto-utils": {
+          "version": "1.0.1",
+          "bundled": true
+        },
+        "os-browserify": {
+          "version": "0.2.1",
+          "bundled": true
+        },
+        "pako": {
+          "version": "0.2.9",
+          "bundled": true
+        },
+        "parse-asn1": {
+          "version": "5.1.1",
+          "bundled": true,
+          "requires": {
+            "asn1.js": "^4.0.0",
+            "browserify-aes": "^1.0.0",
+            "create-hash": "^1.1.0",
+            "evp_bytestokey": "^1.0.0",
+            "pbkdf2": "^3.0.3"
+          }
+        },
+        "path-browserify": {
+          "version": "0.0.0",
+          "bundled": true
+        },
+        "pbkdf2": {
+          "version": "3.0.16",
+          "bundled": true,
+          "requires": {
+            "create-hash": "^1.1.2",
+            "create-hmac": "^1.1.4",
+            "ripemd160": "^2.0.1",
+            "safe-buffer": "^5.0.1",
+            "sha.js": "^2.4.8"
+          }
+        },
+        "process": {
+          "version": "0.11.10",
+          "bundled": true
+        },
+        "process-nextick-args": {
+          "version": "2.0.0",
+          "bundled": true
+        },
+        "public-encrypt": {
+          "version": "4.0.2",
+          "bundled": true,
+          "requires": {
+            "bn.js": "^4.1.0",
+            "browserify-rsa": "^4.0.0",
+            "create-hash": "^1.1.0",
+            "parse-asn1": "^5.0.0",
+            "randombytes": "^2.0.1"
+          }
+        },
+        "punycode": {
+          "version": "1.4.1",
+          "bundled": true
+        },
+        "querystring": {
+          "version": "0.2.0",
+          "bundled": true
+        },
+        "querystring-es3": {
+          "version": "0.2.1",
+          "bundled": true
+        },
+        "randombytes": {
+          "version": "2.0.6",
+          "bundled": true,
+          "requires": {
+            "safe-buffer": "^5.1.0"
+          }
+        },
+        "randomfill": {
+          "version": "1.0.4",
+          "bundled": true,
+          "requires": {
+            "randombytes": "^2.0.5",
+            "safe-buffer": "^5.1.0"
+          }
+        },
+        "readable-stream": {
+          "version": "2.3.6",
+          "bundled": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          },
+          "dependencies": {
+            "inherits": {
+              "version": "2.0.3",
+              "bundled": true
+            }
+          }
+        },
+        "ripemd160": {
+          "version": "2.0.2",
+          "bundled": true,
+          "requires": {
+            "hash-base": "^3.0.0",
+            "inherits": "^2.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "bundled": true
+        },
+        "sha.js": {
+          "version": "2.4.11",
+          "bundled": true,
+          "requires": {
+            "inherits": "^2.0.1",
+            "safe-buffer": "^5.0.1"
+          }
+        },
+        "stream-browserify": {
+          "version": "2.0.1",
+          "bundled": true,
+          "requires": {
+            "inherits": "~2.0.1",
+            "readable-stream": "^2.0.2"
+          }
+        },
+        "stream-http": {
+          "version": "2.8.1",
+          "bundled": true,
+          "requires": {
+            "builtin-status-codes": "^3.0.0",
+            "inherits": "^2.0.1",
+            "readable-stream": "^2.3.3",
+            "to-arraybuffer": "^1.0.0",
+            "xtend": "^4.0.0"
+          }
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "bundled": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        },
+        "timers-browserify": {
+          "version": "1.4.2",
+          "bundled": true,
+          "requires": {
+            "process": "~0.11.0"
+          }
+        },
+        "to-arraybuffer": {
+          "version": "1.0.1",
+          "bundled": true
+        },
+        "tty-browserify": {
+          "version": "0.0.0",
+          "bundled": true
+        },
+        "url": {
+          "version": "0.11.0",
+          "bundled": true,
+          "requires": {
+            "punycode": "1.3.2",
+            "querystring": "0.2.0"
+          },
+          "dependencies": {
+            "punycode": {
+              "version": "1.3.2",
+              "bundled": true
+            }
+          }
+        },
+        "util": {
+          "version": "0.10.3",
+          "bundled": true,
+          "requires": {
+            "inherits": "2.0.1"
+          }
+        },
+        "util-deprecate": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "vm-browserify": {
+          "version": "0.0.4",
+          "bundled": true,
+          "requires": {
+            "indexof": "0.0.1"
+          }
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "bundled": true
+        }
+      }
+    },
+    "micromatch": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "braces": "^2.3.1",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "extglob": "^2.0.4",
+        "fragment-cache": "^0.2.1",
+        "kind-of": "^6.0.2",
+        "nanomatch": "^1.2.9",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.2"
+      }
+    },
+    "mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+    },
+    "minipass": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
+      "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
+      "requires": {
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.0"
+      }
+    },
+    "minizlib": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
+      "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
+      "requires": {
+        "minipass": "^2.2.1"
+      }
+    },
+    "mixin-deep": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+      "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+      "dev": true,
+      "requires": {
+        "for-in": "^1.0.2",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "requires": {
+        "minimist": "0.0.8"
+      }
+    },
+    "moment": {
+      "version": "2.24.0",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+      "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
+      "optional": true
+    },
+    "mongodb": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.3.tgz",
+      "integrity": "sha512-MdRnoOjstmnrKJsK8PY0PjP6fyF/SBS4R8coxmhsfEU7tQ46/J6j+aSHF2n4c2/H8B+Hc/Klbfp8vggZfI0mmA==",
+      "requires": {
+        "bson": "^1.1.1",
+        "require_optional": "^1.0.1",
+        "safe-buffer": "^5.1.2",
+        "saslprep": "^1.0.0"
+      },
+      "dependencies": {
+        "bson": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz",
+          "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg=="
+        }
+      }
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+    },
+    "mute-stream": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
+      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+      "dev": true
+    },
+    "mv": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
+      "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
+      "optional": true,
+      "requires": {
+        "mkdirp": "~0.5.1",
+        "ncp": "~2.0.0",
+        "rimraf": "~2.4.0"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "6.0.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
+          "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
+          "optional": true,
+          "requires": {
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "2 || 3",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "rimraf": {
+          "version": "2.4.5",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
+          "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
+          "optional": true,
+          "requires": {
+            "glob": "^6.0.1"
+          }
+        }
+      }
+    },
+    "nan": {
+      "version": "2.13.2",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
+      "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
+    },
+    "nanomatch": {
+      "version": "1.2.13",
+      "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+      "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "fragment-cache": "^0.2.1",
+        "is-windows": "^1.0.2",
+        "kind-of": "^6.0.2",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      }
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+      "dev": true
+    },
+    "ncp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
+      "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
+      "optional": true
+    },
+    "needle": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
+      "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==",
+      "requires": {
+        "debug": "^3.2.6",
+        "iconv-lite": "^0.4.4",
+        "sax": "^1.2.4"
+      }
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "node-pre-gyp": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz",
+      "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==",
+      "requires": {
+        "detect-libc": "^1.0.2",
+        "mkdirp": "^0.5.1",
+        "needle": "^2.2.1",
+        "nopt": "^4.0.1",
+        "npm-packlist": "^1.1.6",
+        "npmlog": "^4.0.2",
+        "rc": "^1.2.7",
+        "rimraf": "^2.6.1",
+        "semver": "^5.3.0",
+        "tar": "^4"
+      }
+    },
+    "nopt": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+      "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+      "requires": {
+        "abbrev": "1",
+        "osenv": "^0.1.4"
+      }
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      }
+    },
+    "npm-bundled": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz",
+      "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g=="
+    },
+    "npm-packlist": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz",
+      "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==",
+      "requires": {
+        "ignore-walk": "^3.0.1",
+        "npm-bundled": "^1.0.1"
+      }
+    },
+    "npm-path": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz",
+      "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==",
+      "dev": true,
+      "requires": {
+        "which": "^1.2.10"
+      }
+    },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true,
+      "requires": {
+        "path-key": "^2.0.0"
+      }
+    },
+    "npm-which": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz",
+      "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=",
+      "dev": true,
+      "requires": {
+        "commander": "^2.9.0",
+        "npm-path": "^2.0.2",
+        "which": "^1.2.10"
+      }
+    },
+    "npmlog": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+      "requires": {
+        "are-we-there-yet": "~1.1.2",
+        "console-control-strings": "~1.1.0",
+        "gauge": "~2.7.3",
+        "set-blocking": "~2.0.0"
+      }
+    },
+    "number-is-nan": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "object-copy": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+      "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+      "dev": true,
+      "requires": {
+        "copy-descriptor": "^0.1.0",
+        "define-property": "^0.2.5",
+        "kind-of": "^3.0.3"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "dev": true
+    },
+    "object-visit": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+      "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.0"
+      }
+    },
+    "object.pick": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "object.values": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz",
+      "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.12.0",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "onetime": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
+      "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
+      "dev": true,
+      "requires": {
+        "mimic-fn": "^2.1.0"
+      }
+    },
+    "optionator": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+      "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+      "dev": true,
+      "requires": {
+        "deep-is": "~0.1.3",
+        "fast-levenshtein": "~2.0.4",
+        "levn": "~0.3.0",
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2",
+        "wordwrap": "~1.0.0"
+      }
+    },
+    "os": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz",
+      "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M="
+    },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
+    },
+    "os-shim": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz",
+      "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=",
+      "dev": true
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+    },
+    "osenv": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+      "requires": {
+        "os-homedir": "^1.0.0",
+        "os-tmpdir": "^1.0.0"
+      }
+    },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true
+    },
+    "p-limit": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+      "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+      "dev": true,
+      "requires": {
+        "p-try": "^1.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+      "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+      "dev": true,
+      "requires": {
+        "p-limit": "^1.1.0"
+      }
+    },
+    "p-map": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
+      "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
+      "dev": true
+    },
+    "p-try": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+      "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+      "dev": true
+    },
+    "page": {
+      "version": "1.11.4",
+      "resolved": "https://registry.npmjs.org/page/-/page-1.11.4.tgz",
+      "integrity": "sha512-8JMZzcE5W4qk+/DtmogN57cI+Yscy7xTYCpfSO7s3Tx6LjZuAfHFQY1+cKIAy60NaXdzVD6nOc3objaVbE0HJg==",
+      "requires": {
+        "path-to-regexp": "~1.2.1"
+      }
+    },
+    "parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "requires": {
+        "callsites": "^3.0.0"
+      }
+    },
+    "parse-json": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+      "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+      "dev": true,
+      "requires": {
+        "error-ex": "^1.2.0"
+      }
+    },
+    "pascalcase": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+      "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+      "dev": true
+    },
+    "path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
+    "path-to-regexp": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.2.1.tgz",
+      "integrity": "sha1-szcFwUAjTYc8hyHHuf2LVB7Tr/k=",
+      "requires": {
+        "isarray": "0.0.1"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        }
+      }
+    },
+    "path-type": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+      "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+      "dev": true,
+      "requires": {
+        "pify": "^2.0.0"
+      }
+    },
+    "pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+      "dev": true
+    },
+    "pkg-dir": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+      "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+      "dev": true,
+      "requires": {
+        "find-up": "^2.1.0"
+      }
+    },
+    "please-upgrade-node": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
+      "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
+      "dev": true,
+      "requires": {
+        "semver-compare": "^1.0.0"
+      }
+    },
+    "pluralize": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
+      "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
+      "dev": true
+    },
+    "posix-character-classes": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+      "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+      "dev": true
+    },
+    "pre-commit": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz",
+      "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=",
+      "dev": true,
+      "requires": {
+        "cross-spawn": "^5.0.1",
+        "spawn-sync": "^1.0.15",
+        "which": "1.2.x"
+      },
+      "dependencies": {
+        "cross-spawn": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^4.0.1",
+            "shebang-command": "^1.2.0",
+            "which": "^1.2.9"
+          }
+        },
+        "which": {
+          "version": "1.2.14",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
+          "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
+          "dev": true,
+          "requires": {
+            "isexe": "^2.0.0"
+          }
+        }
+      }
+    },
+    "precond": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
+      "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw="
+    },
+    "prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+      "dev": true
+    },
+    "prettier": {
+      "version": "1.18.2",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
+      "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
+      "dev": true
+    },
+    "prettier-eslint": {
+      "version": "8.8.2",
+      "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-8.8.2.tgz",
+      "integrity": "sha512-2UzApPuxi2yRoyMlXMazgR6UcH9DKJhNgCviIwY3ixZ9THWSSrUww5vkiZ3C48WvpFl1M1y/oU63deSy1puWEA==",
+      "dev": true,
+      "requires": {
+        "babel-runtime": "^6.26.0",
+        "common-tags": "^1.4.0",
+        "dlv": "^1.1.0",
+        "eslint": "^4.0.0",
+        "indent-string": "^3.2.0",
+        "lodash.merge": "^4.6.0",
+        "loglevel-colored-level-prefix": "^1.0.0",
+        "prettier": "^1.7.0",
+        "pretty-format": "^23.0.1",
+        "require-relative": "^0.8.7",
+        "typescript": "^2.5.1",
+        "typescript-eslint-parser": "^16.0.0",
+        "vue-eslint-parser": "^2.0.2"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "5.7.3",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
+          "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
+          "dev": true
+        },
+        "acorn-jsx": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
+          "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
+          "dev": true,
+          "requires": {
+            "acorn": "^3.0.4"
+          },
+          "dependencies": {
+            "acorn": {
+              "version": "3.3.0",
+              "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+              "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
+              "dev": true
+            }
+          }
+        },
+        "ansi-escapes": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+          "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+          "dev": true
+        },
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "chardet": {
+          "version": "0.4.2",
+          "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
+          "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
+          "dev": true
+        },
+        "cli-cursor": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+          "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+          "dev": true,
+          "requires": {
+            "restore-cursor": "^2.0.0"
+          }
+        },
+        "cross-spawn": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^4.0.1",
+            "shebang-command": "^1.2.0",
+            "which": "^1.2.9"
+          }
+        },
+        "doctrine": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+          "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2"
+          }
+        },
+        "eslint": {
+          "version": "4.19.1",
+          "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz",
+          "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==",
+          "dev": true,
+          "requires": {
+            "ajv": "^5.3.0",
+            "babel-code-frame": "^6.22.0",
+            "chalk": "^2.1.0",
+            "concat-stream": "^1.6.0",
+            "cross-spawn": "^5.1.0",
+            "debug": "^3.1.0",
+            "doctrine": "^2.1.0",
+            "eslint-scope": "^3.7.1",
+            "eslint-visitor-keys": "^1.0.0",
+            "espree": "^3.5.4",
+            "esquery": "^1.0.0",
+            "esutils": "^2.0.2",
+            "file-entry-cache": "^2.0.0",
+            "functional-red-black-tree": "^1.0.1",
+            "glob": "^7.1.2",
+            "globals": "^11.0.1",
+            "ignore": "^3.3.3",
+            "imurmurhash": "^0.1.4",
+            "inquirer": "^3.0.6",
+            "is-resolvable": "^1.0.0",
+            "js-yaml": "^3.9.1",
+            "json-stable-stringify-without-jsonify": "^1.0.1",
+            "levn": "^0.3.0",
+            "lodash": "^4.17.4",
+            "minimatch": "^3.0.2",
+            "mkdirp": "^0.5.1",
+            "natural-compare": "^1.4.0",
+            "optionator": "^0.8.2",
+            "path-is-inside": "^1.0.2",
+            "pluralize": "^7.0.0",
+            "progress": "^2.0.0",
+            "regexpp": "^1.0.1",
+            "require-uncached": "^1.0.3",
+            "semver": "^5.3.0",
+            "strip-ansi": "^4.0.0",
+            "strip-json-comments": "~2.0.1",
+            "table": "4.0.2",
+            "text-table": "~0.2.0"
+          }
+        },
+        "eslint-scope": {
+          "version": "3.7.3",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
+          "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==",
+          "dev": true,
+          "requires": {
+            "esrecurse": "^4.1.0",
+            "estraverse": "^4.1.1"
+          }
+        },
+        "espree": {
+          "version": "3.5.4",
+          "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
+          "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
+          "dev": true,
+          "requires": {
+            "acorn": "^5.5.0",
+            "acorn-jsx": "^3.0.0"
+          }
+        },
+        "external-editor": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+          "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
+          "dev": true,
+          "requires": {
+            "chardet": "^0.4.0",
+            "iconv-lite": "^0.4.17",
+            "tmp": "^0.0.33"
+          }
+        },
+        "figures": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+          "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+          "dev": true,
+          "requires": {
+            "escape-string-regexp": "^1.0.5"
+          }
+        },
+        "file-entry-cache": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
+          "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
+          "dev": true,
+          "requires": {
+            "flat-cache": "^1.2.1",
+            "object-assign": "^4.0.1"
+          }
+        },
+        "flat-cache": {
+          "version": "1.3.4",
+          "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz",
+          "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==",
+          "dev": true,
+          "requires": {
+            "circular-json": "^0.3.1",
+            "graceful-fs": "^4.1.2",
+            "rimraf": "~2.6.2",
+            "write": "^0.2.1"
+          }
+        },
+        "ignore": {
+          "version": "3.3.10",
+          "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
+          "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
+          "dev": true
+        },
+        "inquirer": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
+          "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
+          "dev": true,
+          "requires": {
+            "ansi-escapes": "^3.0.0",
+            "chalk": "^2.0.0",
+            "cli-cursor": "^2.1.0",
+            "cli-width": "^2.0.0",
+            "external-editor": "^2.0.4",
+            "figures": "^2.0.0",
+            "lodash": "^4.3.0",
+            "mute-stream": "0.0.7",
+            "run-async": "^2.2.0",
+            "rx-lite": "^4.0.8",
+            "rx-lite-aggregates": "^4.0.8",
+            "string-width": "^2.1.0",
+            "strip-ansi": "^4.0.0",
+            "through": "^2.3.6"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "mimic-fn": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+          "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+          "dev": true
+        },
+        "mute-stream": {
+          "version": "0.0.7",
+          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+          "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+          "dev": true
+        },
+        "onetime": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+          "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+          "dev": true,
+          "requires": {
+            "mimic-fn": "^1.0.0"
+          }
+        },
+        "regexpp": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
+          "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==",
+          "dev": true
+        },
+        "restore-cursor": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+          "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+          "dev": true,
+          "requires": {
+            "onetime": "^2.0.0",
+            "signal-exit": "^3.0.2"
+          }
+        },
+        "slice-ansi": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
+          "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0"
+          }
+        },
+        "string-width": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^4.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        },
+        "table": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
+          "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
+          "dev": true,
+          "requires": {
+            "ajv": "^5.2.3",
+            "ajv-keywords": "^2.1.0",
+            "chalk": "^2.1.0",
+            "lodash": "^4.17.4",
+            "slice-ansi": "1.0.0",
+            "string-width": "^2.1.1"
+          }
+        },
+        "write": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
+          "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
+          "dev": true,
+          "requires": {
+            "mkdirp": "^0.5.1"
+          }
+        }
+      }
+    },
+    "prettier-linter-helpers": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+      "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+      "dev": true,
+      "requires": {
+        "fast-diff": "^1.1.2"
+      }
+    },
+    "pretty-format": {
+      "version": "23.6.0",
+      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
+      "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^3.0.0",
+        "ansi-styles": "^3.2.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        }
+      }
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
+    "progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "dev": true
+    },
+    "pseudomap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "dev": true
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.8.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.8.0.tgz",
+      "integrity": "sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w=="
+    },
+    "rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "requires": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+        }
+      }
+    },
+    "read-pkg": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+      "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+      "dev": true,
+      "requires": {
+        "load-json-file": "^2.0.0",
+        "normalize-package-data": "^2.3.2",
+        "path-type": "^2.0.0"
+      }
+    },
+    "read-pkg-up": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+      "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+      "dev": true,
+      "requires": {
+        "find-up": "^2.0.0",
+        "read-pkg": "^2.0.0"
+      }
+    },
+    "readable-stream": {
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "regenerator-runtime": {
+      "version": "0.13.3",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+      "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
+    },
+    "regex-not": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "regexpp": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+      "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+      "dev": true
+    },
+    "repeat-element": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+      "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+      "dev": true
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+      "dev": true
+    },
+    "require-relative": {
+      "version": "0.8.7",
+      "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
+      "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
+      "dev": true
+    },
+    "require-uncached": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
+      "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
+      "dev": true,
+      "requires": {
+        "caller-path": "^0.1.0",
+        "resolve-from": "^1.0.0"
+      },
+      "dependencies": {
+        "caller-path": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
+          "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
+          "dev": true,
+          "requires": {
+            "callsites": "^0.2.0"
+          }
+        },
+        "callsites": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
+          "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
+          "dev": true
+        },
+        "resolve-from": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
+          "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
+          "dev": true
+        }
+      }
+    },
+    "require_optional": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+      "requires": {
+        "resolve-from": "^2.0.0",
+        "semver": "^5.1.0"
+      }
+    },
+    "resolve": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+      "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+      "dev": true,
+      "requires": {
+        "path-parse": "^1.0.6"
+      }
+    },
+    "resolve-from": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+    },
+    "resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+      "dev": true
+    },
+    "restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "requires": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "ret": {
+      "version": "0.1.15",
+      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "run-async": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+      "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+      "dev": true,
+      "requires": {
+        "is-promise": "^2.1.0"
+      }
+    },
+    "rx-lite": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
+      "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
+      "dev": true
+    },
+    "rx-lite-aggregates": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
+      "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=",
+      "dev": true,
+      "requires": {
+        "rx-lite": "*"
+      }
+    },
+    "rxjs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz",
+      "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safe-json-stringify": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
+      "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
+      "optional": true
+    },
+    "safe-regex": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+      "dev": true,
+      "requires": {
+        "ret": "~0.1.10"
+      }
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "saslprep": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+      "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+      "optional": true,
+      "requires": {
+        "sparse-bitfield": "^3.0.3"
+      }
+    },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+    },
+    "semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+    },
+    "semver-compare": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+      "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
+      "dev": true
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+    },
+    "set-value": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-extendable": "^0.1.1",
+        "is-plain-object": "^2.0.3",
+        "split-string": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+    },
+    "slice-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.0",
+        "astral-regex": "^1.0.0",
+        "is-fullwidth-code-point": "^2.0.0"
+      },
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        }
+      }
+    },
+    "snapdragon": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+      "dev": true,
+      "requires": {
+        "base": "^0.11.1",
+        "debug": "^2.2.0",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "map-cache": "^0.2.2",
+        "source-map": "^0.5.6",
+        "source-map-resolve": "^0.5.0",
+        "use": "^3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "snapdragon-node": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.0",
+        "snapdragon-util": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "snapdragon-util": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.2.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+    },
+    "source-map-resolve": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+      "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+      "dev": true,
+      "requires": {
+        "atob": "^2.1.1",
+        "decode-uri-component": "^0.2.0",
+        "resolve-url": "^0.2.1",
+        "source-map-url": "^0.4.0",
+        "urix": "^0.1.0"
+      }
+    },
+    "source-map-support": {
+      "version": "0.5.13",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+      "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
+    "source-map-url": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+      "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+      "dev": true
+    },
+    "sparse-bitfield": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+      "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+      "optional": true,
+      "requires": {
+        "memory-pager": "^1.0.2"
+      }
+    },
+    "spawn-sync": {
+      "version": "1.0.15",
+      "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz",
+      "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=",
+      "dev": true,
+      "requires": {
+        "concat-stream": "^1.4.7",
+        "os-shim": "^0.1.2"
+      }
+    },
+    "spdx-correct": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+      "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+      "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+      "dev": true
+    },
+    "split-string": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.0"
+      }
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+      "dev": true
+    },
+    "staged-git-files": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.1.1.tgz",
+      "integrity": "sha512-H89UNKr1rQJvI1c/PIR3kiAMBV23yvR7LItZiV74HWZwzt7f3YHuujJ9nJZlt58WlFox7XQsOahexwk7nTe69A==",
+      "dev": true
+    },
+    "static-extend": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+      "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+      "dev": true,
+      "requires": {
+        "define-property": "^0.2.5",
+        "object-copy": "^0.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "string-argv": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz",
+      "integrity": "sha1-2sMECGkMIfPDYwo/86BYd73L1zY=",
+      "dev": true
+    },
+    "string-width": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+      "requires": {
+        "code-point-at": "^1.0.0",
+        "is-fullwidth-code-point": "^1.0.0",
+        "strip-ansi": "^3.0.0"
+      }
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "stringify-object": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+      "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+      "dev": true,
+      "requires": {
+        "get-own-enumerable-property-symbols": "^3.0.0",
+        "is-obj": "^1.0.1",
+        "is-regexp": "^1.0.0"
+      }
+    },
+    "strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true
+    },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true
+    },
+    "strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+    },
+    "supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "symbol-observable": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+      "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
+      "dev": true
+    },
+    "table": {
+      "version": "5.4.5",
+      "resolved": "https://registry.npmjs.org/table/-/table-5.4.5.tgz",
+      "integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.10.2",
+        "lodash": "^4.17.14",
+        "slice-ansi": "^2.1.0",
+        "string-width": "^3.0.0"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "emoji-regex": {
+          "version": "7.0.3",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+          "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+          "dev": true
+        },
+        "fast-deep-equal": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+          "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "json-schema-traverse": {
+          "version": "0.4.1",
+          "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+          "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "tar": {
+      "version": "4.4.10",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz",
+      "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==",
+      "requires": {
+        "chownr": "^1.1.1",
+        "fs-minipass": "^1.2.5",
+        "minipass": "^2.3.5",
+        "minizlib": "^1.2.1",
+        "mkdirp": "^0.5.0",
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.3"
+      }
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+      "dev": true
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "requires": {
+        "os-tmpdir": "~1.0.2"
+      }
+    },
+    "to-object-path": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+      "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "to-regex": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "regex-not": "^1.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "to-regex-range": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+      "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1"
+      }
+    },
+    "tslib": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+      "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
+      "dev": true
+    },
+    "type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2"
+      }
+    },
+    "type-fest": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz",
+      "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==",
+      "dev": true
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "dev": true
+    },
+    "typescript": {
+      "version": "2.9.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
+      "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
+      "dev": true
+    },
+    "typescript-eslint-parser": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-16.0.1.tgz",
+      "integrity": "sha512-IKawLTu4A2xN3aN/cPLxvZ0bhxZHILGDKTZWvWNJ3sLNhJ3PjfMEDQmR2VMpdRPrmWOadgWXRwjLBzSA8AGsaQ==",
+      "dev": true,
+      "requires": {
+        "lodash.unescape": "4.0.1",
+        "semver": "5.5.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
+          "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
+          "dev": true
+        }
+      }
+    },
+    "union-value": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "get-value": "^2.0.6",
+        "is-extendable": "^0.1.1",
+        "set-value": "^2.0.1"
+      }
+    },
+    "unset-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+      "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+      "dev": true,
+      "requires": {
+        "has-value": "^0.3.1",
+        "isobject": "^3.0.0"
+      },
+      "dependencies": {
+        "has-value": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+          "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+          "dev": true,
+          "requires": {
+            "get-value": "^2.0.3",
+            "has-values": "^0.1.4",
+            "isobject": "^2.0.0"
+          },
+          "dependencies": {
+            "isobject": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+              "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+              "dev": true,
+              "requires": {
+                "isarray": "1.0.0"
+              }
+            }
+          }
+        },
+        "has-values": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+          "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+          "dev": true
+        }
+      }
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "urix": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+      "dev": true
+    },
+    "use": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+      "dev": true
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "vasync": {
+      "version": "1.6.4",
+      "resolved": "https://registry.npmjs.org/vasync/-/vasync-1.6.4.tgz",
+      "integrity": "sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8=",
+      "requires": {
+        "verror": "1.6.0"
+      },
+      "dependencies": {
+        "verror": {
+          "version": "1.6.0",
+          "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.0.tgz",
+          "integrity": "sha1-fROyex+swuLakEBetepuW90lLqU=",
+          "requires": {
+            "extsprintf": "1.2.0"
+          }
+        }
+      }
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "vue-eslint-parser": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz",
+      "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.1.0",
+        "eslint-scope": "^3.7.1",
+        "eslint-visitor-keys": "^1.0.0",
+        "espree": "^3.5.2",
+        "esquery": "^1.0.0",
+        "lodash": "^4.17.4"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "5.7.3",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
+          "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
+          "dev": true
+        },
+        "acorn-jsx": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
+          "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
+          "dev": true,
+          "requires": {
+            "acorn": "^3.0.4"
+          },
+          "dependencies": {
+            "acorn": {
+              "version": "3.3.0",
+              "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+              "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
+              "dev": true
+            }
+          }
+        },
+        "eslint-scope": {
+          "version": "3.7.3",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
+          "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==",
+          "dev": true,
+          "requires": {
+            "esrecurse": "^4.1.0",
+            "estraverse": "^4.1.1"
+          }
+        },
+        "espree": {
+          "version": "3.5.4",
+          "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
+          "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
+          "dev": true,
+          "requires": {
+            "acorn": "^5.5.0",
+            "acorn-jsx": "^3.0.0"
+          }
+        }
+      }
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "wide-align": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+      "requires": {
+        "string-width": "^1.0.2 || 2"
+      }
+    },
+    "wordwrap": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+      "dev": true
+    },
+    "wrap-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz",
+      "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=",
+      "dev": true,
+      "requires": {
+        "string-width": "^2.1.1",
+        "strip-ansi": "^4.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^4.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "write": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+      "dev": true,
+      "requires": {
+        "mkdirp": "^0.5.1"
+      }
+    },
+    "xss": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.6.tgz",
+      "integrity": "sha512-6Q9TPBeNyoTRxgZFk5Ggaepk/4vUOYdOsIUYvLehcsIZTFjaavbVnsuAkLA5lIFuug5hw8zxcB9tm01gsjph2A==",
+      "requires": {
+        "commander": "^2.9.0",
+        "cssfilter": "0.0.10"
+      }
+    },
+    "yallist": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+      "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
+    }
+  }
+}

+ 73 - 0
.snap-meteor-1.8/package.json

@@ -0,0 +1,73 @@
+{
+  "name": "wekan",
+  "version": "v3.78.0",
+  "description": "Open-Source kanban",
+  "private": true,
+  "scripts": {
+    "lint": "eslint --cache --ext .js --ignore-path .eslintignore .",
+    "lint:eslint:fix": "eslint --ext .js --ignore-path .eslintignore --fix .",
+    "lint:staged": "lint-staged",
+    "prettify": "prettier --write '**/*.js' '**/*.jsx'",
+    "test": "npm run lint"
+  },
+  "lint-staged": {
+    "*.js": [
+      "meteor npm run prettify",
+      "meteor npm run lint:eslint:fix",
+      "git add --force"
+    ],
+    "*.jsx": [
+      "meteor npm run prettify",
+      "meteor npm run lint:eslint:fix",
+      "git add --force"
+    ],
+    "*.json": [
+      "prettier --write",
+      "git add --force"
+    ]
+  },
+  "pre-commit": "lint:staged",
+  "eslintConfig": {
+    "extends": "@meteorjs/eslint-config-meteor"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/wekan/wekan.git"
+  },
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/wekan/wekan/issues"
+  },
+  "homepage": "https://wekan.github.io",
+  "devDependencies": {
+    "eslint": "^5.16.0",
+    "eslint-config-meteor": "0.0.9",
+    "eslint-config-prettier": "^3.6.0",
+    "eslint-import-resolver-meteor": "^0.4.0",
+    "eslint-plugin-import": "^2.18.0",
+    "eslint-plugin-meteor": "^5.1.0",
+    "eslint-plugin-prettier": "^3.1.0",
+    "lint-staged": "^7.3.0",
+    "pre-commit": "^1.2.2",
+    "prettier": "^1.18.2",
+    "prettier-eslint": "^8.8.2"
+  },
+  "dependencies": {
+    "@babel/runtime": "^7.6.2",
+    "ajv": "^5.0.0",
+    "babel-runtime": "^6.26.0",
+    "bcrypt": "^3.0.2",
+    "bson": "^4.0.0",
+    "bunyan": "^1.8.12",
+    "es6-promise": "^4.2.4",
+    "gridfs-stream": "^0.5.3",
+    "ldapjs": "^1.0.2",
+    "meteor-node-stubs": "^0.4.1",
+    "mongodb": "^3.3.3",
+    "os": "^0.1.1",
+    "page": "^1.8.6",
+    "qs": "^6.8.0",
+    "source-map-support": "^0.5.12",
+    "xss": "^1.0.6"
+  }
+}

+ 47 - 65
releases/not-working-meteor-1.8-snapcraft.yaml → .snap-meteor-1.8/snapcraft.yaml

@@ -1,6 +1,6 @@
 name: wekan
 name: wekan
-version: '0'
-version-script: git describe --dirty --tags | cut -c 2-
+version: 0
+version-script: git describe --tags |  cut -c 2-
 summary: The open-source kanban
 summary: The open-source kanban
 description: |
 description: |
    Wekan is an open-source and collaborative kanban board application.
    Wekan is an open-source and collaborative kanban board application.
@@ -65,75 +65,59 @@ apps:
 
 
 parts:
 parts:
     mongodb:
     mongodb:
-        source: https://repo.mongodb.org/apt/ubuntu/dists/xenial/mongodb-org/4.0/multiverse/binary-amd64/mongodb-org-server_4.0.11_amd64.deb
+        source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.22.tgz
         plugin: dump
         plugin: dump
-        stage-packages: [libssl-dev]
+        stage-packages: [libssl1.0.0]
         filesets:
         filesets:
             mongo:
             mongo:
                 - usr
                 - usr
                 - bin
                 - bin
                 - lib
                 - lib
-#        stage:
-#            - $mongo
-#        prime:
-#            - $mongo
+        stage:
+            - $mongo
+        prime:
+            - $mongo
+
     wekan:
     wekan:
         source: .
         source: .
         plugin: nodejs
         plugin: nodejs
-        node-engine: 8.16.0
-#        node-packages:
-#            - node-gyp
-#            - node-pre-gyp
-#            - fibers@4.0.1
+        node-engine: 8.17.0
+        node-packages:
+            - node-gyp
+            - node-pre-gyp
+            - fibers@2.0.0
         build-packages:
         build-packages:
             - ca-certificates
             - ca-certificates
             - apt-utils
             - apt-utils
-            - bsdtar
-            - gnupg
-            - bzip2
-        ##   - python3
+            - python
+#            - python3
             - g++
             - g++
-            - build-essential
-            - git
-        #    - capnproto
+            - capnproto
             - curl
             - curl
-            - libcurl3
-            - php-curl
-        #    - execstack
-        #    - nodejs
-        #    - npm
-        ##   - python3-pip
-        ##   - python3-venv
+            - execstack
+            - nodejs
+            - npm
         stage-packages:
         stage-packages:
             - libfontconfig1
             - libfontconfig1
         override-build: |
         override-build: |
-            set -o xtrace
             echo "Cleaning environment first"
             echo "Cleaning environment first"
             rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
             rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
-            # Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
-            # https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
-            cp $(which tar) $(which tar)~
-            ln -sf $(which bsdtar) $(which tar)
             # Create the OpenAPI specification
             # Create the OpenAPI specification
             rm -rf .build
             rm -rf .build
-            mkdir .build
-            ##mkdir -p .build/python
-            ##cd .build/python
-            ##python3 -m venv env
-            ##. ./env/bin/activate
-            ##pip3 install -U setuptools wheel
-            ##git clone --depth 1 -b master https://github.com/Kronuz/esprima-python
-            ##cd esprima-python
-            ##python3 setup.py install
-            ##cd ../../..
-            ##mkdir -p ./public/api
-            ##python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml
+            #mkdir -p .build/python
+            #cd .build/python
+            #git clone --depth 1 -b master https://github.com/Kronuz/esprima-python
+            #cd esprima-python
+            #python3 setup.py install
+            #cd ../../..
+            #mkdir -p ./public/api
+            #python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml
             # we temporary need api2html and mkdirp
             # we temporary need api2html and mkdirp
-            ##npm install -g --unsafe-perm api2html@0.3.0
-            ##npm install -g  --unsafe-perm mkdirp
-            ##api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml
-            ##npm uninstall -g --unsafe-perm mkdirp
-            ##npm uninstall -g --unsafe-perm api2html
+            #npm install -g api2html@0.3.0
+            #npm install -g mkdirp
+            #api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml
+            #npm uninstall -g mkdirp
+            #npm uninstall -g api2html
             # Node Fibers 100% CPU usage issue:
             # Node Fibers 100% CPU usage issue:
             # https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161
             # https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161
             # https://github.com/meteor/meteor/issues/9796#issuecomment-381676326
             # https://github.com/meteor/meteor/issues/9796#issuecomment-381676326
@@ -206,17 +190,12 @@ parts:
             #  git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
             #  git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
             #  cd ..
             #  cd ..
             #fi
             #fi
-            rm -rf package-lock.json .build
-            #meteor add standard-minifier-js --allow-superuser
-            meteor npm install --allow-superuser --unsafe-perm
+            rm -rf .build
+            meteor add standard-minifier-js --allow-superuser
+            meteor npm install --allow-superuser
+            meteor npm install --allow-superuser --save babel-runtime
             meteor build .build --directory --allow-superuser
             meteor build .build --directory --allow-superuser
             cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
             cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
-            # Disable code coverage on Snap, because it crashes Wekan starting.
-            # https://github.com/wekan/wekan/issues/2533#issuecomment-513506939
-            #sed -i 's|Profile(fileInfo.path, func).apply(global, args);|//Profile(fileInfo.path, func).apply(global, args);|g' .build/bundle/programs/server/boot.js
-            #   although it did not help, it said unable to find main function.
-            # Other option to fix would be to drop indexes at start of snap:
-            # https://stackoverflow.com/questions/24241742/drop-all-indexes-from-all-collections-in-a-mongodb-database-using-the-command-li
             #Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
             #Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
             #https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
             #https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
             #https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
             #https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
@@ -227,18 +206,21 @@ parts:
             #cd ../../../../
             #cd ../../../../
             # Change to directory .build/bundle/programs/server
             # Change to directory .build/bundle/programs/server
             cd .build/bundle/programs/server
             cd .build/bundle/programs/server
-            npm install --unsafe-perm
+            npm install
+            npm install --allow-superuser --save babel-runtime
             #meteor npm install --save bcrypt
             #meteor npm install --save bcrypt
             # Change back to Wekan source directory
             # Change back to Wekan source directory
             cd ../../../..
             cd ../../../..
             cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
             cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
             cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
             cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
-            # rm $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
-            #rm $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
-            # rm $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
-            # rm $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
-            # Put back the original tar
-            mv $(which tar)~ $(which tar)
+            rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
+            rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
+            rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp
+            rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
+            rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
+            # Meteor 1.8.x additional .swp remove
+            rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
+
         organize:
         organize:
             README: README.wekan
             README: README.wekan
         prime:
         prime:

+ 853 - 0
.snap-meteor-1.8/wekanCreator.js

@@ -0,0 +1,853 @@
+const DateString = Match.Where(function(dateAsString) {
+  check(dateAsString, String);
+  return moment(dateAsString, moment.ISO_8601).isValid();
+});
+
+export class WekanCreator {
+  constructor(data) {
+    // we log current date, to use the same timestamp for all our actions.
+    // this helps to retrieve all elements performed by the same import.
+    this._nowDate = new Date();
+    // The object creation dates, indexed by Wekan id
+    // (so we only parse actions once!)
+    this.createdAt = {
+      board: null,
+      cards: {},
+      lists: {},
+      swimlanes: {},
+    };
+    // The object creator Wekan Id, indexed by the object Wekan id
+    // (so we only parse actions once!)
+    this.createdBy = {
+      cards: {}, // only cards have a field for that
+    };
+
+    // Map of labels Wekan ID => Wekan ID
+    this.labels = {};
+    // Map of swimlanes Wekan ID => Wekan ID
+    this.swimlanes = {};
+    // Map of lists Wekan ID => Wekan ID
+    this.lists = {};
+    // Map of cards Wekan ID => Wekan ID
+    this.cards = {};
+    // Map of comments Wekan ID => Wekan ID
+    this.commentIds = {};
+    // Map of attachments Wekan ID => Wekan ID
+    this.attachmentIds = {};
+    // Map of checklists Wekan ID => Wekan ID
+    this.checklists = {};
+    // Map of checklistItems Wekan ID => Wekan ID
+    this.checklistItems = {};
+    // The comments, indexed by Wekan card id (to map when importing cards)
+    this.comments = {};
+    // Map of rules Wekan ID => Wekan ID
+    this.rules = {};
+    // the members, indexed by Wekan member id => Wekan user ID
+    this.members = data.membersMapping ? data.membersMapping : {};
+    // Map of triggers Wekan ID => Wekan ID
+    this.triggers = {};
+    // Map of actions Wekan ID => Wekan ID
+    this.actions = {};
+
+    // maps a wekanCardId to an array of wekanAttachments
+    this.attachments = {};
+  }
+
+  /**
+   * If dateString is provided,
+   * return the Date it represents.
+   * If not, will return the date when it was first called.
+   * This is useful for us, as we want all import operations to
+   * have the exact same date for easier later retrieval.
+   *
+   * @param {String} dateString a properly formatted Date
+   */
+  _now(dateString) {
+    if (dateString) {
+      return new Date(dateString);
+    }
+    if (!this._nowDate) {
+      this._nowDate = new Date();
+    }
+    return this._nowDate;
+  }
+
+  /**
+   * if wekanUserId is provided and we have a mapping,
+   * return it.
+   * Otherwise return current logged user.
+   * @param wekanUserId
+   * @private
+   */
+  _user(wekanUserId) {
+    if (wekanUserId && this.members[wekanUserId]) {
+      return this.members[wekanUserId];
+    }
+    return Meteor.userId();
+  }
+
+  checkActivities(wekanActivities) {
+    check(wekanActivities, [
+      Match.ObjectIncluding({
+        activityType: String,
+        createdAt: DateString,
+      }),
+    ]);
+    // XXX we could perform more thorough checks based on action type
+  }
+
+  checkBoard(wekanBoard) {
+    check(
+      wekanBoard,
+      Match.ObjectIncluding({
+        archived: Boolean,
+        title: String,
+        // XXX refine control by validating 'color' against a list of
+        // allowed values (is it worth the maintenance?)
+        color: String,
+        permission: Match.Where(value => {
+          return ['private', 'public'].indexOf(value) >= 0;
+        }),
+      }),
+    );
+  }
+
+  checkCards(wekanCards) {
+    check(wekanCards, [
+      Match.ObjectIncluding({
+        archived: Boolean,
+        dateLastActivity: DateString,
+        labelIds: [String],
+        title: String,
+        sort: Number,
+      }),
+    ]);
+  }
+
+  checkLabels(wekanLabels) {
+    check(wekanLabels, [
+      Match.ObjectIncluding({
+        // XXX refine control by validating 'color' against a list of allowed
+        // values (is it worth the maintenance?)
+        color: String,
+      }),
+    ]);
+  }
+
+  checkLists(wekanLists) {
+    check(wekanLists, [
+      Match.ObjectIncluding({
+        archived: Boolean,
+        title: String,
+      }),
+    ]);
+  }
+
+  checkSwimlanes(wekanSwimlanes) {
+    check(wekanSwimlanes, [
+      Match.ObjectIncluding({
+        archived: Boolean,
+        title: String,
+      }),
+    ]);
+  }
+
+  checkChecklists(wekanChecklists) {
+    check(wekanChecklists, [
+      Match.ObjectIncluding({
+        cardId: String,
+        title: String,
+      }),
+    ]);
+  }
+
+  checkChecklistItems(wekanChecklistItems) {
+    check(wekanChecklistItems, [
+      Match.ObjectIncluding({
+        cardId: String,
+        title: String,
+      }),
+    ]);
+  }
+
+  checkRules(wekanRules) {
+    check(wekanRules, [
+      Match.ObjectIncluding({
+        triggerId: String,
+        actionId: String,
+        title: String,
+      }),
+    ]);
+  }
+
+  checkTriggers(wekanTriggers) {
+    // XXX More check based on trigger type
+    check(wekanTriggers, [
+      Match.ObjectIncluding({
+        activityType: String,
+        desc: String,
+      }),
+    ]);
+  }
+
+  getMembersToMap(data) {
+    // we will work on the list itself (an ordered array of objects) when a
+    // mapping is done, we add a 'wekan' field to the object representing the
+    // imported member
+    const membersToMap = data.members;
+    const users = data.users;
+    // auto-map based on username
+    membersToMap.forEach(importedMember => {
+      importedMember.id = importedMember.userId;
+      delete importedMember.userId;
+      const user = users.filter(user => {
+        return user._id === importedMember.id;
+      })[0];
+      if (user.profile && user.profile.fullname) {
+        importedMember.fullName = user.profile.fullname;
+      }
+      importedMember.username = user.username;
+      const wekanUser = Users.findOne({ username: importedMember.username });
+      if (wekanUser) {
+        importedMember.wekanId = wekanUser._id;
+      }
+    });
+    return membersToMap;
+  }
+
+  checkActions(wekanActions) {
+    // XXX More check based on action type
+    check(wekanActions, [
+      Match.ObjectIncluding({
+        actionType: String,
+        desc: String,
+      }),
+    ]);
+  }
+
+  // You must call parseActions before calling this one.
+  createBoardAndLabels(boardToImport) {
+    const boardToCreate = {
+      archived: boardToImport.archived,
+      color: boardToImport.color,
+      // very old boards won't have a creation activity so no creation date
+      createdAt: this._now(boardToImport.createdAt),
+      labels: [],
+      members: [
+        {
+          userId: Meteor.userId(),
+          wekanId: Meteor.userId(),
+          isActive: true,
+          isAdmin: true,
+          isNoComments: false,
+          isCommentOnly: false,
+          swimlaneId: false,
+        },
+      ],
+      // Standalone Export has modifiedAt missing, adding modifiedAt to fix it
+      modifiedAt: this._now(boardToImport.modifiedAt),
+      permission: boardToImport.permission,
+      slug: getSlug(boardToImport.title) || 'board',
+      stars: 0,
+      title: boardToImport.title,
+    };
+    // now add other members
+    if (boardToImport.members) {
+      boardToImport.members.forEach(wekanMember => {
+        // do we already have it in our list?
+        if (
+          !boardToCreate.members.some(
+            member => member.wekanId === wekanMember.wekanId,
+          )
+        )
+          boardToCreate.members.push({
+            ...wekanMember,
+            userId: wekanMember.wekanId,
+          });
+      });
+    }
+    boardToImport.labels.forEach(label => {
+      const labelToCreate = {
+        _id: Random.id(6),
+        color: label.color,
+        name: label.name,
+      };
+      // We need to remember them by Wekan ID, as this is the only ref we have
+      // when importing cards.
+      this.labels[label._id] = labelToCreate._id;
+      boardToCreate.labels.push(labelToCreate);
+    });
+    const boardId = Boards.direct.insert(boardToCreate);
+    Boards.direct.update(boardId, {
+      $set: {
+        modifiedAt: this._now(),
+      },
+    });
+    // log activity
+    Activities.direct.insert({
+      activityType: 'importBoard',
+      boardId,
+      createdAt: this._now(),
+      source: {
+        id: boardToImport.id,
+        system: 'Wekan',
+      },
+      // We attribute the import to current user,
+      // not the author from the original object.
+      userId: this._user(),
+    });
+    return boardId;
+  }
+
+  /**
+   * Create the Wekan cards corresponding to the supplied Wekan cards,
+   * as well as all linked data: activities, comments, and attachments
+   * @param wekanCards
+   * @param boardId
+   * @returns {Array}
+   */
+  createCards(wekanCards, boardId) {
+    const result = [];
+    wekanCards.forEach(card => {
+      const cardToCreate = {
+        archived: card.archived,
+        boardId,
+        // very old boards won't have a creation activity so no creation date
+        createdAt: this._now(this.createdAt.cards[card._id]),
+        dateLastActivity: this._now(),
+        description: card.description,
+        listId: this.lists[card.listId],
+        swimlaneId: this.swimlanes[card.swimlaneId],
+        sort: card.sort,
+        title: card.title,
+        // we attribute the card to its creator if available
+        userId: this._user(this.createdBy.cards[card._id]),
+        isOvertime: card.isOvertime || false,
+        startAt: card.startAt ? this._now(card.startAt) : null,
+        dueAt: card.dueAt ? this._now(card.dueAt) : null,
+        spentTime: card.spentTime || null,
+      };
+      // add labels
+      if (card.labelIds) {
+        cardToCreate.labelIds = card.labelIds.map(wekanId => {
+          return this.labels[wekanId];
+        });
+      }
+      // add members {
+      if (card.members) {
+        const wekanMembers = [];
+        // we can't just map, as some members may not have been mapped
+        card.members.forEach(sourceMemberId => {
+          if (this.members[sourceMemberId]) {
+            const wekanId = this.members[sourceMemberId];
+            // we may map multiple Wekan members to the same wekan user
+            // in which case we risk adding the same user multiple times
+            if (!wekanMembers.find(wId => wId === wekanId)) {
+              wekanMembers.push(wekanId);
+            }
+          }
+          return true;
+        });
+        if (wekanMembers.length > 0) {
+          cardToCreate.members = wekanMembers;
+        }
+      }
+      // set color
+      if (card.color) {
+        cardToCreate.color = card.color;
+      }
+      // insert card
+      const cardId = Cards.direct.insert(cardToCreate);
+      // keep track of Wekan id => Wekan id
+      this.cards[card._id] = cardId;
+      // // log activity
+      // Activities.direct.insert({
+      //   activityType: 'importCard',
+      //   boardId,
+      //   cardId,
+      //   createdAt: this._now(),
+      //   listId: cardToCreate.listId,
+      //   source: {
+      //     id: card._id,
+      //     system: 'Wekan',
+      //   },
+      //   // we attribute the import to current user,
+      //   // not the author of the original card
+      //   userId: this._user(),
+      // });
+      // add comments
+      const comments = this.comments[card._id];
+      if (comments) {
+        comments.forEach(comment => {
+          const commentToCreate = {
+            boardId,
+            cardId,
+            createdAt: this._now(comment.createdAt),
+            text: comment.text,
+            // we attribute the comment to the original author, default to current user
+            userId: this._user(comment.userId),
+          };
+          // dateLastActivity will be set from activity insert, no need to
+          // update it ourselves
+          const commentId = CardComments.direct.insert(commentToCreate);
+          this.commentIds[comment._id] = commentId;
+          // Activities.direct.insert({
+          //   activityType: 'addComment',
+          //   boardId: commentToCreate.boardId,
+          //   cardId: commentToCreate.cardId,
+          //   commentId,
+          //   createdAt: this._now(commentToCreate.createdAt),
+          //   // we attribute the addComment (not the import)
+          //   // to the original author - it is needed by some UI elements.
+          //   userId: commentToCreate.userId,
+          // });
+        });
+      }
+      const attachments = this.attachments[card._id];
+      const wekanCoverId = card.coverId;
+      if (attachments) {
+        attachments.forEach(att => {
+          const file = new FS.File();
+          // Simulating file.attachData on the client generates multiple errors
+          // - HEAD returns null, which causes exception down the line
+          // - the template then tries to display the url to the attachment which causes other errors
+          // so we make it server only, and let UI catch up once it is done, forget about latency comp.
+          const self = this;
+          if (Meteor.isServer) {
+            if (att.url) {
+              file.attachData(att.url, function(error) {
+                file.boardId = boardId;
+                file.cardId = cardId;
+                file.userId = self._user(att.userId);
+                // The field source will only be used to prevent adding
+                // attachments' related activities automatically
+                file.source = 'import';
+                if (error) {
+                  throw error;
+                } else {
+                  const wekanAtt = Attachments.insert(file, () => {
+                    // we do nothing
+                  });
+                  self.attachmentIds[att._id] = wekanAtt._id;
+                  //
+                  if (wekanCoverId === att._id) {
+                    Cards.direct.update(cardId, {
+                      $set: {
+                        coverId: wekanAtt._id,
+                      },
+                    });
+                  }
+                }
+              });
+            } else if (att.file) {
+              file.attachData(
+                new Buffer(att.file, 'base64'),
+                {
+                  type: att.type,
+                },
+                error => {
+                  file.name(att.name);
+                  file.boardId = boardId;
+                  file.cardId = cardId;
+                  file.userId = self._user(att.userId);
+                  // The field source will only be used to prevent adding
+                  // attachments' related activities automatically
+                  file.source = 'import';
+                  if (error) {
+                    throw error;
+                  } else {
+                    const wekanAtt = Attachments.insert(file, () => {
+                      // we do nothing
+                    });
+                    this.attachmentIds[att._id] = wekanAtt._id;
+                    //
+                    if (wekanCoverId === att._id) {
+                      Cards.direct.update(cardId, {
+                        $set: {
+                          coverId: wekanAtt._id,
+                        },
+                      });
+                    }
+                  }
+                },
+              );
+            }
+          }
+          // todo XXX set cover - if need be
+        });
+      }
+      result.push(cardId);
+    });
+    return result;
+  }
+
+  // Create labels if they do not exist and load this.labels.
+  createLabels(wekanLabels, board) {
+    wekanLabels.forEach(label => {
+      const color = label.color;
+      const name = label.name;
+      const existingLabel = board.getLabel(name, color);
+      if (existingLabel) {
+        this.labels[label.id] = existingLabel._id;
+      } else {
+        const idLabelCreated = board.pushLabel(name, color);
+        this.labels[label.id] = idLabelCreated;
+      }
+    });
+  }
+
+  createLists(wekanLists, boardId) {
+    wekanLists.forEach((list, listIndex) => {
+      const listToCreate = {
+        archived: list.archived,
+        boardId,
+        // We are being defensing here by providing a default date (now) if the
+        // creation date wasn't found on the action log. This happen on old
+        // Wekan boards (eg from 2013) that didn't log the 'createList' action
+        // we require.
+        createdAt: this._now(this.createdAt.lists[list.id]),
+        title: list.title,
+        sort: list.sort ? list.sort : listIndex,
+      };
+      const listId = Lists.direct.insert(listToCreate);
+      Lists.direct.update(listId, {
+        $set: {
+          updatedAt: this._now(),
+        },
+      });
+      this.lists[list._id] = listId;
+      // // log activity
+      // Activities.direct.insert({
+      //   activityType: 'importList',
+      //   boardId,
+      //   createdAt: this._now(),
+      //   listId,
+      //   source: {
+      //     id: list._id,
+      //     system: 'Wekan',
+      //   },
+      //   // We attribute the import to current user,
+      //   // not the creator of the original object
+      //   userId: this._user(),
+      // });
+    });
+  }
+
+  createSwimlanes(wekanSwimlanes, boardId) {
+    wekanSwimlanes.forEach((swimlane, swimlaneIndex) => {
+      const swimlaneToCreate = {
+        archived: swimlane.archived,
+        boardId,
+        // We are being defensing here by providing a default date (now) if the
+        // creation date wasn't found on the action log. This happen on old
+        // Wekan boards (eg from 2013) that didn't log the 'createList' action
+        // we require.
+        createdAt: this._now(this.createdAt.swimlanes[swimlane._id]),
+        title: swimlane.title,
+        sort: swimlane.sort ? swimlane.sort : swimlaneIndex,
+      };
+      // set color
+      if (swimlane.color) {
+        swimlaneToCreate.color = swimlane.color;
+      }
+      const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate);
+      Swimlanes.direct.update(swimlaneId, {
+        $set: {
+          updatedAt: this._now(),
+        },
+      });
+      this.swimlanes[swimlane._id] = swimlaneId;
+    });
+  }
+
+  createChecklists(wekanChecklists) {
+    const result = [];
+    wekanChecklists.forEach((checklist, checklistIndex) => {
+      // Create the checklist
+      const checklistToCreate = {
+        cardId: this.cards[checklist.cardId],
+        title: checklist.title,
+        createdAt: checklist.createdAt,
+        sort: checklist.sort ? checklist.sort : checklistIndex,
+      };
+      const checklistId = Checklists.direct.insert(checklistToCreate);
+      this.checklists[checklist._id] = checklistId;
+      result.push(checklistId);
+    });
+    return result;
+  }
+
+  createTriggers(wekanTriggers, boardId) {
+    wekanTriggers.forEach(trigger => {
+      if (trigger.hasOwnProperty('labelId')) {
+        trigger.labelId = this.labels[trigger.labelId];
+      }
+      if (trigger.hasOwnProperty('memberId')) {
+        trigger.memberId = this.members[trigger.memberId];
+      }
+      trigger.boardId = boardId;
+      const oldId = trigger._id;
+      delete trigger._id;
+      this.triggers[oldId] = Triggers.direct.insert(trigger);
+    });
+  }
+
+  createActions(wekanActions, boardId) {
+    wekanActions.forEach(action => {
+      if (action.hasOwnProperty('labelId')) {
+        action.labelId = this.labels[action.labelId];
+      }
+      if (action.hasOwnProperty('memberId')) {
+        action.memberId = this.members[action.memberId];
+      }
+      action.boardId = boardId;
+      const oldId = action._id;
+      delete action._id;
+      this.actions[oldId] = Actions.direct.insert(action);
+    });
+  }
+
+  createRules(wekanRules, boardId) {
+    wekanRules.forEach(rule => {
+      // Create the rule
+      rule.boardId = boardId;
+      rule.triggerId = this.triggers[rule.triggerId];
+      rule.actionId = this.actions[rule.actionId];
+      delete rule._id;
+      Rules.direct.insert(rule);
+    });
+  }
+
+  createChecklistItems(wekanChecklistItems) {
+    wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => {
+      // Create the checklistItem
+      const checklistItemTocreate = {
+        title: checklistitem.title,
+        checklistId: this.checklists[checklistitem.checklistId],
+        cardId: this.cards[checklistitem.cardId],
+        sort: checklistitem.sort ? checklistitem.sort : checklistitemIndex,
+        isFinished: checklistitem.isFinished,
+      };
+      const checklistItemId = ChecklistItems.direct.insert(
+        checklistItemTocreate,
+      );
+      this.checklistItems[checklistitem._id] = checklistItemId;
+    });
+  }
+
+  parseActivities(wekanBoard) {
+    wekanBoard.activities.forEach(activity => {
+      switch (activity.activityType) {
+        case 'addAttachment': {
+          // We have to be cautious, because the attachment could have been removed later.
+          // In that case Wekan still reports its addition, but removes its 'url' field.
+          // So we test for that
+          const wekanAttachment = wekanBoard.attachments.filter(attachment => {
+            return attachment._id === activity.attachmentId;
+          })[0];
+
+          if (typeof wekanAttachment !== 'undefined' && wekanAttachment) {
+            if (wekanAttachment.url || wekanAttachment.file) {
+              // we cannot actually create the Wekan attachment, because we don't yet
+              // have the cards to attach it to, so we store it in the instance variable.
+              const wekanCardId = activity.cardId;
+              if (!this.attachments[wekanCardId]) {
+                this.attachments[wekanCardId] = [];
+              }
+              this.attachments[wekanCardId].push(wekanAttachment);
+            }
+          }
+          break;
+        }
+        case 'addComment': {
+          const wekanComment = wekanBoard.comments.filter(comment => {
+            return comment._id === activity.commentId;
+          })[0];
+          const id = activity.cardId;
+          if (!this.comments[id]) {
+            this.comments[id] = [];
+          }
+          this.comments[id].push(wekanComment);
+          break;
+        }
+        case 'createBoard': {
+          this.createdAt.board = activity.createdAt;
+          break;
+        }
+        case 'createCard': {
+          const cardId = activity.cardId;
+          this.createdAt.cards[cardId] = activity.createdAt;
+          this.createdBy.cards[cardId] = activity.userId;
+          break;
+        }
+        case 'createList': {
+          const listId = activity.listId;
+          this.createdAt.lists[listId] = activity.createdAt;
+          break;
+        }
+        case 'createSwimlane': {
+          const swimlaneId = activity.swimlaneId;
+          this.createdAt.swimlanes[swimlaneId] = activity.createdAt;
+          break;
+        }
+      }
+    });
+  }
+
+  importActivities(activities, boardId) {
+    activities.forEach(activity => {
+      switch (activity.activityType) {
+        // Board related activities
+        // TODO: addBoardMember, removeBoardMember
+        case 'createBoard': {
+          Activities.direct.insert({
+            userId: this._user(activity.userId),
+            type: 'board',
+            activityTypeId: boardId,
+            activityType: activity.activityType,
+            boardId,
+            createdAt: this._now(activity.createdAt),
+          });
+          break;
+        }
+        // List related activities
+        // TODO: removeList, archivedList
+        case 'createList': {
+          Activities.direct.insert({
+            userId: this._user(activity.userId),
+            type: 'list',
+            activityType: activity.activityType,
+            listId: this.lists[activity.listId],
+            boardId,
+            createdAt: this._now(activity.createdAt),
+          });
+          break;
+        }
+        // Card related activities
+        // TODO: archivedCard, restoredCard, joinMember, unjoinMember
+        case 'createCard': {
+          Activities.direct.insert({
+            userId: this._user(activity.userId),
+            activityType: activity.activityType,
+            listId: this.lists[activity.listId],
+            cardId: this.cards[activity.cardId],
+            boardId,
+            createdAt: this._now(activity.createdAt),
+          });
+          break;
+        }
+        case 'moveCard': {
+          Activities.direct.insert({
+            userId: this._user(activity.userId),
+            oldListId: this.lists[activity.oldListId],
+            activityType: activity.activityType,
+            listId: this.lists[activity.listId],
+            cardId: this.cards[activity.cardId],
+            boardId,
+            createdAt: this._now(activity.createdAt),
+          });
+          break;
+        }
+        // Comment related activities
+        case 'addComment': {
+          Activities.direct.insert({
+            userId: this._user(activity.userId),
+            activityType: activity.activityType,
+            cardId: this.cards[activity.cardId],
+            commentId: this.commentIds[activity.commentId],
+            boardId,
+            createdAt: this._now(activity.createdAt),
+          });
+          break;
+        }
+        // Attachment related activities
+        case 'addAttachment': {
+          Activities.direct.insert({
+            userId: this._user(activity.userId),
+            type: 'card',
+            activityType: activity.activityType,
+            attachmentId: this.attachmentIds[activity.attachmentId],
+            cardId: this.cards[activity.cardId],
+            boardId,
+            createdAt: this._now(activity.createdAt),
+          });
+          break;
+        }
+        // Checklist related activities
+        case 'addChecklist': {
+          Activities.direct.insert({
+            userId: this._user(activity.userId),
+            activityType: activity.activityType,
+            cardId: this.cards[activity.cardId],
+            checklistId: this.checklists[activity.checklistId],
+            boardId,
+            createdAt: this._now(activity.createdAt),
+          });
+          break;
+        }
+        case 'addChecklistItem': {
+          Activities.direct.insert({
+            userId: this._user(activity.userId),
+            activityType: activity.activityType,
+            cardId: this.cards[activity.cardId],
+            checklistId: this.checklists[activity.checklistId],
+            checklistItemId: activity.checklistItemId.replace(
+              activity.checklistId,
+              this.checklists[activity.checklistId],
+            ),
+            boardId,
+            createdAt: this._now(activity.createdAt),
+          });
+          break;
+        }
+      }
+    });
+  }
+
+  //check(board) {
+  check() {
+    //try {
+    // check(data, {
+    //   membersMapping: Match.Optional(Object),
+    // });
+    // this.checkActivities(board.activities);
+    // this.checkBoard(board);
+    // this.checkLabels(board.labels);
+    // this.checkLists(board.lists);
+    // this.checkSwimlanes(board.swimlanes);
+    // this.checkCards(board.cards);
+    //this.checkChecklists(board.checklists);
+    // this.checkRules(board.rules);
+    // this.checkActions(board.actions);
+    //this.checkTriggers(board.triggers);
+    //this.checkChecklistItems(board.checklistItems);
+    //} catch (e) {
+    //  throw new Meteor.Error('error-json-schema');
+    // }
+  }
+
+  create(board, currentBoardId) {
+    // TODO : Make isSandstorm variable global
+    const isSandstorm =
+      Meteor.settings &&
+      Meteor.settings.public &&
+      Meteor.settings.public.sandstorm;
+    if (isSandstorm && currentBoardId) {
+      const currentBoard = Boards.findOne(currentBoardId);
+      currentBoard.archive();
+    }
+    this.parseActivities(board);
+    const boardId = this.createBoardAndLabels(board);
+    this.createLists(board.lists, boardId);
+    this.createSwimlanes(board.swimlanes, boardId);
+    this.createCards(board.cards, boardId);
+    this.createChecklists(board.checklists);
+    this.createChecklistItems(board.checklistItems);
+    this.importActivities(board.activities, boardId);
+    this.createTriggers(board.triggers, boardId);
+    this.createActions(board.actions, boardId);
+    this.createRules(board.rules, boardId);
+    // XXX add members
+    return boardId;
+  }
+}

+ 3 - 3
.travis.yml

@@ -1,10 +1,10 @@
-dist: disco
+dist: eoan
 sudo: required
 sudo: required
 
 
 env:
 env:
   TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0
   TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0
-  TRAVIS_NODE_VERSION: 8.16.1
-  TRAVIS_NPM_VERSION: 6.4.1
+  TRAVIS_NODE_VERSION: 12.15.0
+  TRAVIS_NPM_VERSION: latest
 
 
 before_install:
 before_install:
   - sudo apt-get update -y
   - sudo apt-get update -y

+ 444 - 5
CHANGELOG.md

@@ -1,4 +1,416 @@
-# Upcoming Wekan release
+# v3.78 2020-02-12 Wekan release
+
+This release adds the following features:
+
+- [Card Settings / Show on Card: Description Title and Description Text](https://github.com/wekan/wekan/commit/e89965f6422fd95b4ad2112ae407b1dde4853510).
+  Thanks to e-stoniauk, 2020product and xet7.
+
+and fixes the following bugs:
+
+- [Remove card element grouping to create compact card layout](https://github.com/wekan/wekan/commit/e89965f6422fd95b4ad2112ae407b1dde4853510).
+  Thanks to e-stoniauk, 2020product and xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.77 2020-02-10 Wekan release
+
+This release removes the following features:
+
+- [Remove hiding comments and activities](https://github.com/wekan/wekan/commit/2a54218f3f68547032bd53a04a968b233be21e15).
+  Thanks to xet7.
+
+and fixes the following bugs:
+
+- Fix Copy Card Link to Clipboard button at card title did not
+  work [Part 1](https://github.com/wekan/wekan/commit/9a21b0a1c933e7f778e4e57a8258e150ccea1620)
+  and [Part2](https://github.com/wekan/wekan/commit/4467a68b97a3fbf0fbae7f05177d978f2aa80287).
+  Thanks to 2020product and xet7.
+    
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.76 2020-02-07 Wekan release
+
+This release adds the following updates:
+
+- [Use Meteor 1.9 and Node.js 12.15.0 on Snap and Docker](https://github.com/wekan/wekan/commit/8384d68a060ef8f2c202ce2fa6064c5c823d28dc).
+  This also fixes bug that exporting some boards was not possible, downloading export file failed.
+  Thanks to xet7.
+
+and fixes the following bugs:
+
+- [Fix Bug enable/disable Comments in Card Settings](https://github.com/wekan/wekan/issues/2923).
+  Thanks to warnt, mdurokov and xet7.
+- [Try to disable dragging Swimlanes/Lists/Cards/Checklists/Subtasks on small mobile smartphones webbrowsers,
+  and hide drag handles on mobile web](https://github.com/wekan/wekan/commit/bf78b093bad7d463ee325ad96e8b485264d4a3be).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.75 2020-02-05 Wekan release
+
+This release adds the following new features:
+
+- [Fix](https://github.com/wekan/wekan/commit/f22785dbcde42e425c9ead209ec224aef6e11c16)
+  [adding comments](https://github.com/wekan/wekan/issues/2918).
+  Thanks to xet7.
+
+and fixes the following bugs:
+
+- [Added some working layout changes like activities using less space from https://github.com/wekan/wekan/pull/2920](https://github.com/wekan/wekan/commit/f22785dbcde42e425c9ead209ec224aef6e11c16).
+  Thanks to 2020product.
+- [Fixed Card Settings not working at Sandstorm](https://github.com/wekan/wekan/commit/f22785dbcde42e425c9ead209ec224aef6e11c16).
+  Thanks to xet7.
+- Add [Card Description title](https://github.com/wekan/wekan/issues/2918#issuecomment-582346577)
+  [back](https://github.com/wekan/wekan/commit/f22785dbcde42e425c9ead209ec224aef6e11c16).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.74 2020-02-05 Wekan release
+
+This release adds the following new features:
+
+- [For BoardAdmin, add way to hide parts of a card, at Board Settings/Card Settings/Show on Card: Received, Start, ... etc.
+  Add to card title bar Copy card to Clipboard button](https://github.com/wekan/wekan/pull/2915).
+  Thanks to 2020product and xet7.
+- [Set default to RICHER_CARD_COMMENT_EDITOR=false](https://github.com/wekan/wekan/commit/65fa2f626f503b8089e0d982901cffb3990426cb).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.73 2020-01-29 Wekan release
+
+This release adds the following new features:
+
+- [Login to Wekan with Nextcloud](https://github.com/wekan/wekan/pull/2897).
+  Thanks to bogie.
+- [Add rule action to move cards to other boards](https://github.com/wekan/wekan/pull/2899).
+  Thanks to peterverraedt.
+
+and fixes the following bugs:
+
+- [Show System Wide Announcement in one line](https://github.com/wekan/wekan/pull/2891).
+  Thanks to tsia.
+- [Fixed board export with attachment in Wekan Meteor 1.9.x version](https://github.com/wekan/wekan/pull/2898).
+  Thanks to izadpoor.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.72 2020-01-19 Sandstorm-only Wekan release
+
+This release fixes the following bugs:
+
+- Try to fix Wekan at Sandstorm.
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.71 2020-01-18 Sandstorm-only Wekan release
+
+This release fixes the following bugs:
+
+- [Try to fix Wekan at Sandstorm by using Meteor 1.8.x and Node 8.17.0 at Sandstorm](https://github.com/wekan/wekan/commit/5e5ab95410c715a4379631456fc5547c497898b0).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.70 2020-01-18 Wekan release
+
+This release fixes the following bugs:
+
+- [Add missing LD_LIBRARY_PATH to use libssl and libcurl](https://github.com/wekan/wekan/10f142a1a05acb98a175ccb0326fb0c1d3e3713f).
+  Thanks to xet7.
+- [Use Meteor 1.8.x](https://github.com/wekan/wekan/commit/55a2aa90cbbf44200e9b0b9f4bd08b6177f1bb95)
+  [on Snap](https://github.com/wekan/wekan/commit/6a01170d8696322462c4065ce0cf4a637a058975), because
+  Snap builds do not work yet for Meteor 1.9, Node 12.14.1 and MongoDB 4.2.2.
+  Docker version works with Meteor 1.9.
+  Thanks to xet7.
+- [Try to fix Node 12 Buffer() deprecation errors](https://github.com/wekan/wekan/commit/9b905c2833d54cf34d1875148075b2bf756d943a).
+  Thanks to xet7.
+- [Add Snap Meteor 1.8.x files to lint ignore files](https://github.com/wekan/wekan/commit/48f8050c25e40f737dfdd3a98923cb87cd4e77e2).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.69 2020-01-10 Wekan release
+
+This release fixes the following bugs:
+
+- [Fix docker-compose.yml to not use --smallfiles that is not supported in
+  MongoDB 4.x](https://github.com/wekan/wekan/commit/ecb76842fcbd81701afcab8db0ed106e6be0fdec).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.68 2020-01-10 Wekan release
+
+This release tries to fix the following bugs:
+
+- [Try to fix Snap by removing MongoDB option --smallfiles that is not supported
+  in MongoDB 4.x](https://github.com/wekan/wekan/commit/031df54a2e0a03dcb7a2586667e60e5bd4eef706)
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.67 2020-01-10 Wekan release
+
+This release tries to fix the following bugs:
+
+- [Try to fix Snap](https://github.com/wekan/wekan/commit/2b382b940be9af575fab4c2e955702d8cde55ae9).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.66 2020-01-10 Wekan release
+
+This release tries to fix the following bugs:
+
+- [Try to fix Snap](https://github.com/wekan/wekan/commit/39bf1e375e2962f824e6f8cfa425ea51aa4efa24).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.65 2020-01-10 Wekan release
+
+This release adds the following features:
+
+- [More keyboard shortcuts: c for archive card](https://github.com/wekan/wekan/commit/d16a601c04aeb1d3550c5c541be02a67276a34cf).
+  Thanks to xet7.
+
+and adds the following updates:
+
+- [Upgrade to Meteor 1.9, Node 12.14.1 and MongoDB 4.2.2](https://github.com/wekan/wekan/commit/785f3cf88b61f687ef5ad4a529768221d1a54c86).
+  Thanks to xet7.
+- [Add more issue repo links to GitHub issue template](https://github.com/wekan/wekan/commit/5724674e73246f4e52843a6d6906c0ecdd85cccc).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.64 2020-01-06 Wekan release
+
+This release adds the following warning for CentOS 7 users:
+
+- [WARNING: DO NOT USE SNAP ON CENTOS 7, THERE IS UPDATE BUG](https://github.com/wekan/wekan-snap/wiki/CentOS-7).
+  Thanks to andy-twosticks and xet7.
+
+and adds the following features:
+
+- [Wider sidebar](https://github.com/wekan/wekan/commit/5058233509e44916296e38fb8a6c5dd591c46d8b).
+  Thanks to vjrj.
+
+and removes the following features:
+
+- [Removed Custom HTML feature that does not work](https://github.com/wekan/wekan/commit/ddce0ada094e6450be260b4cda21fdfa09ae0133).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.63 2020-01-06 Wekan release
+
+This release fixes the following bugs:
+
+- [Fix: Unable to find Archive Card/List/Swimlane in board
+  settings](https://github.com/wekan/wekan/commit/8ce993921718f3e10c2daa5fabb145b939d789dd).
+  Thanks to neobradley and xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.62 2020-01-05 Wekan release
+
+This release adds the following features:
+
+- [Add Worker role](https://github.com/wekan/wekan/issues/2788).
+  This was originally added at Wekan v3.58, reverted at Wekan v3.60 because of bugs,
+  and now after fixes added back.
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.61 2020-01-03 Wekan release
+
+This release adds the following features:
+
+- [Add more Font Awesome icons. This was originally added
+  at Wekan v3.58, removed at Wekan v3.60, and now
+  added back at Wekan v3.61](https://github.com/wekan/wekan/commit/cd253522a305523e3e36bb73313e8c4db500a717).
+  Thanks to xet7.
+
+and fixes the following bugs:
+
+- [Fix browser javascript console errors when editing profile. This was originally added
+  at Wekan v3.58, removed at Wekan v3.60, and now added back at
+  Wekan v3.61](https://github.com/wekan/wekan/commit/cd253522a305523e3e36bb73313e8c4db500a717).
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.60 2020-01-03 Wekan release
+
+This release fixes the following bugs:
+
+- [Revert to Wekan v3.57 version of client and models directories,
+  removing Worker role temporarily, because Worker role changes
+  broke saving card](https://github.com/wekan/wekan/commit/27943796ade78ca3c503637a1340918bf06a1267).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.59 2020-01-03 Wekan release
+
+This release fixes the following bugs:
+
+- [Fix not being able to edit received date](https://github.com/wekan/wekan/commit/5376bc7b7905c0dd99fae1aeae3f63b4583a3e3f).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.58 2020-01-03 Wekan release
+
+This release adds the following features:
+
+- [Add Worker role](https://github.com/wekan/wekan/issues/2788). Thanks to xet7.
+- [Add more Font Awesome icons](https://github.com/wekan/wekan/commit/2bf004120d5a43cd3c3c060fc7c0c30d1b01f220).
+  Thanks to xet7.
+
+and fixes the following bugs:
+
+- [Fix: k8s templates update for helm](https://github.com/wekan/wekan/pull/2867).
+  1. Upgrade mongo replica version.
+  2. Access mongo via service url.
+  3. Change the expose servicePort to numeric.
+  Thanks to jiangytcn.
+- [Fix browser console errors when editing user profile name](https://github.com/wekan/wekan/commit/2bf004120d5a43cd3c3c060fc7c0c30d1b01f220).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.57 2019-12-22 Wekan release
+
+This release adds the following features:
+
+- [Allow card and checklist API creation for authorized board members](https://github.com/wekan/wekan/pull/2854).
+  Thanks to Robert-Lebedeu.
+- [Visual difference for inactive user in Administration: strikethrough](https://github.com/wekan/wekan/commit/1f1aea87a421ca5e7931d220d10c838574208e2c).
+  Thanks to hever and xet7.
+
+and adds the following updates:
+
+- [Upgrade to Meteor 1.8.3 and Node 8.17.0. Update release scripts. Fix ldap background sync documentation part 2](https://github.com/wekan/wekan/commit/782d0b620988628f40f50f9cd824f6652cfb0dd9).
+  Thanks to xet7.
+
+and fixes the following bugs:
+
+- [Fix: Don't add a blank space for empty custom fields on minicards](https://github.com/wekan/wekan/commit/e2a374f0aad8489a84d6de9966c281a812b5eca3).
+  Thanks to roobre and xet7.
+- [Fix: Allow to set empty card title, AssignedBy and RequestedBy](https://github.com/wekan/wekan/commit/25561946edf37351f67cf7500902dde7d9114d2f).
+  Thanks to justinr1234 and xet7.
+- [Fix comment text disappearing when clicking outside of comment text area.
+  Fix lint error](https://github.com/wekan/wekan/commit/3b3950369ce07aa9e6fc4ab1bef9fb8a4993e398).
+  Thanks to xet7.
+- [Fix ldap background sync documentation](https://github.com/wekan/wekan/pull/2855).
+  Thanks to koelle25.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.56 2019-11-21 Wekan release
+
+This release adds the following updates:
+
+- [Update to Meteor 1.8.2. Update dependencies](https://github.com/wekan/wekan/commit/38dfe0b9a71a083adc2de1a81170fea0e4a8e53f).
+  Thanks to xet7.
+- [Fix lint errors and update travis NPM version](https://github.com/wekan/wekan/commit/b0f345ba21830b033c9edcc8ee5252b280111ae7).
+  Thanks to xet7.
+- [Change base image to rolling, that is currently Ubuntu eoan
+  version](https://github.com/wekan/wekan/commit/c66cc3d4dadb15b669256530cfda89359cdb9340).
+  Thanks to xet7.
+- [It seems Ubuntu eoan package bsdtar has been renamed to
+  libarchive-tools](https://github.com/wekan/wekan/commit/c60967e935bdc0e7e9aea0a1c23178aee8a73c29).
+  Thanks to xet7.
+
+and fixes the following bugs:
+
+- [Fix slow scroll on card detail by setting scrollInertia to 0](https://github.com/wekan/wekan/commit/599ace1db7918df41d9708d14b0351acb0f8688e).
+  Thanks to cafeoh.
+- [Fix lint errors](https://github.com/wekan/wekan/commit/788dd0a81a06efee165007a92780f9e8c2c754ac).
+  Thanks to xet7.
+- [Remove eslint option that does not work](https://github.com/wekan/wekan/commit/a06daff92e5f7cca55d1698252e3aa6526877c8b).
+  Thanks to xet7.
+- [Try to fix lint errors](https://github.com/wekan/wekan/commit/58e505f79a0617011576bdded9427b0d448d6107).
+  Thanks to xet7.
+- [Add to Snap MongoDB logging option --quiet](https://github.com/wekan/wekan/commit/c7ded515022fff2c1167ce8938405a846185a710).
+  Thanks to fmeehan and xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.55 2019-11-19 Wekan release
+
+This release fixes the following bugs:
+
+- [When logged in, use database for setting, so that changes are immediate. Only on public board use cookies.
+  Comment out Collapse CSS that is not in use](https://github.com/wekan/wekan/commit/351d4767d7e93c90ac798769d6071da8730d834f).
+  Thanks to xet7.
+- [Use database when logged in. Part 2](https://github.com/wekan/wekan/commit/4786b0c18ddeb8f48525216eabebdced7159467d).
+  Thanks to xet7.
+- [Use database when logged in. Part 3](https://github.com/wekan/wekan/commit/115d23f9293cad8a93f18f75a47a8a65756f71ce).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.54 2019-11-18 Wekan release
+
+This release adds the following new features:
+
+- [New feature: Now there is popup selection of Lists/Swimlanes/Calendar/Roles](https://github.com/wekan/wekan/commit/96abe3c6914ce37d9fb44da8fda375e40ad65c9e).
+  Thanks to xet7.
+- [New feature, not set visible yet, because switching to it does not
+  work properly yet: Collapsible Swimlanes](https://github.com/wekan/wekan/issues/2804).
+  Thanks to xet7.
+
+and fixes the following bugs:
+
+- [Fix: Public board now loads correctly. When you select one of Lists/Swimlanes/Calendar view and
+  reload webbrowser page, it can change view](https://github.com/wekan/wekan/issues/2311).
+  Thanks to xet7.
+- [Fix: List sorting commented out](https://github.com/wekan/wekan/issues/2800).
+  Thanks to xet7.
+- [Fix: Errors hasHiddenMinicardText, hasShowDragHandles, showSort, hasSortBy, profile,
+  FirefoxAndroid/IE11/Vivaldi/Chromium browsers not working by using cookies instead of
+  database](https://github.com/wekan/wekan/issues/2643#issuecomment-554907955).
+  Note: Cookie changes are not always immediate, if there is no effect, you may need to
+  reload webbrowser page. This could be improved later.
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.53 2019-11-14 Wekan release
+
+This release fixes the following bugs:
+
+- [Revert list sorting change of Wekan v3.51 because it reversed alphabetical sorting of
+  lists](https://github.com/wekan/wekan/commit/ab2a721a1443b903cdbbbe275f41ffd3269012c6).
+  Thanks to Dalisay and xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.52 2019-11-14 Wekan release
+
+This release fixes the following bugs:
+
+- [Add database migration for assignee](https://github.com/wekan/wekan/commit/5b41d72e8de93833e1788962427422cff62c09a2).
+  Thanks to ocdtrekkie and xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.51 2019-11-14 Wekan release
+
+This release fixes the following bugs:
+
+- [Change sorting lists to work on desktop drag handle page instead,
+  where it seems to work better](https://github.com/wekan/wekan/commit/bbc3ab3f994c5a61a4414bc64b05f5a03d259e46).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
+# v3.50 2019-11-13 Wekan release
 
 
 This release adds the following new features:
 This release adds the following new features:
 
 
@@ -6,7 +418,7 @@ This release adds the following new features:
   mode](https://github.com/wekan/wekan/commits/77f8b76d4e13c35ea3451622176bbb69a4d39a32).
   mode](https://github.com/wekan/wekan/commits/77f8b76d4e13c35ea3451622176bbb69a4d39a32).
   Thanks to whowillcare.
   Thanks to whowillcare.
 - Allow user to sort Lists in Board by his own preference boardadmin can star
 - Allow user to sort Lists in Board by his own preference boardadmin can star
-  list [1](https://github.com/wekan/wekan/commit/bc2a20f04e32607f8488a9cecd815647fb43e40e), 
+  list [1](https://github.com/wekan/wekan/commit/bc2a20f04e32607f8488a9cecd815647fb43e40e),
   [2](https://github.com/wekan/wekan/commit/bc2a20f04e32607f8488a9cecd815647fb43e40e).
   [2](https://github.com/wekan/wekan/commit/bc2a20f04e32607f8488a9cecd815647fb43e40e).
   Thanks to whowillcare.
   Thanks to whowillcare.
 - [Allowing user to filter list in Filter function not just cards
 - [Allowing user to filter list in Filter function not just cards
@@ -17,6 +429,31 @@ This release adds the following new features:
 - Enhancement: [Set card times more sensible using the 'Today' button in
 - Enhancement: [Set card times more sensible using the 'Today' button in
   datepicker](https://github.com/wekan/wekan/pull/2747).
   datepicker](https://github.com/wekan/wekan/pull/2747).
   Thanks to liske.
   Thanks to liske.
+- [At card, added Assignee field like Jira, and REST API for it](https://github.com/wekan/wekan/issues/2452).
+  Parts:
+  [Add assignee](https://github.com/wekan/wekan/commit/9e1aaf163f3bd0b3c2d2aee8225d111f83b3d421),
+  [Remove Assignee. Avatar icon is at card and assignee details](https://github.com/wekan/wekan/commit/3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a),
+  [When selecting new assignee (+) icon, list shows names who to add](https://github.com/wekan/wekan/commit/32ce2b51d8bff5e8851732394a8bae3c56f8b0b6),
+  [More progress](https://github.com/wekan/wekan/commit/ea823ab68fd5243c8485177e44a074be836836b8),
+  [In add assignee popup, avatars are now visible](https://github.com/wekan/wekan/commit/56efb5c41075151eeb259d99990a7e86695b2b69),
+  [Add assignee popup title](https://github.com/wekan/wekan/commit/31dbdc835d5a092b8360a4dbe93e9fbcce068855),
+  [Prevent more than one assignee](https://github.com/wekan/wekan/commit/1728298659521ee8e6fc94fedad3160030b9a2c3),
+  [When there is one selected assignee on card, don't show + button for adding more assignees, because there can only be one
+  assignee](https://github.com/wekan/wekan/commit/3cf09efb13438d66db6cf739591c679ea538d812),
+  [Now assignee is visible also at minicard](https://github.com/wekan/wekan/commit/9fd14f7ecb593d3debf5adff8f6c61adb0c3feca),
+  [Update REST API docs, there can only be one assignee in array](https://github.com/wekan/wekan/commit/de7509dc60257667192054e320b381f9dd0f0a31).
+  Thanks to xet7.
+- [More mobile drag handles, and optional desktop drag handles](https://github.com/wekan/wekan/issues/2081): In Progress.
+  Parts:
+  [Some drag handle fixes](https://github.com/wekan/wekan/commit/6a8960547729148bd3085cb469f9e93d510ed66c),
+  [Fix desktop swimlane drag handle position](https://github.com/wekan/wekan/commit/2ec15602d284122fce1a45bed352d0d4050162e2),
+  [Fix card, list and swimlane move. Allow moving cards in multiselect mode](https://github.com/wekan/wekan/commit/537a48bede250155b30ec264904ba320625bab73).
+  Thanks to xet7.
+
+and adds the following updates:
+
+- [Update Node.js to v8.16.2](https://github.com/wekan/wekan/commit/1eb3d25b40797fdab41d7dd59405cfcea81dcc61).
+  Thanks to xet7.
 
 
 and fixes the following bugs:
 and fixes the following bugs:
 
 
@@ -45,6 +482,8 @@ and fixes the following bugs:
   Thanks to jymcheong.
   Thanks to jymcheong.
 - [Fixed OpenAPI docs generation](https://github.com/wekan/wekan/pull/2783).
 - [Fixed OpenAPI docs generation](https://github.com/wekan/wekan/pull/2783).
   Thanks to bentiss.
   Thanks to bentiss.
+- [Fixed close card button not visible on mobile web](https://github.com/wekan/wekan/36b5965dd07e3f0fd90069353310739c394c220f).
+  Thanks to xet7.
 
 
 Thanks to above GitHub users for their contributions and translators for their translations.
 Thanks to above GitHub users for their contributions and translators for their translations.
 
 
@@ -96,7 +535,7 @@ This release adds the following new features:
   NOTIFY_DUE_DAYS_BEFORE_AND_AFTER = 2,0 it means notification will be sent on both due day and two days before.
   NOTIFY_DUE_DAYS_BEFORE_AND_AFTER = 2,0 it means notification will be sent on both due day and two days before.
   Thanks to whowillcare.
   Thanks to whowillcare.
 - [Added modifications the help files, related to NOTIFY_DUE_DAYS_BEFORE_AND_AFTER](https://github.com/wekan/wekan/pull/2740).
 - [Added modifications the help files, related to NOTIFY_DUE_DAYS_BEFORE_AND_AFTER](https://github.com/wekan/wekan/pull/2740).
-  Thanks to whowillcare. 
+  Thanks to whowillcare.
 
 
 and fixes the following bugs:
 and fixes the following bugs:
 
 
@@ -147,7 +586,7 @@ This release adds the following new features:
 
 
 - [More Mobile and Desktop drag handles for Swimlanes/Lists/Cards. Part 1](https://github.com/wekan/wekan/commit/ff550e91103115e7b731dd80c4588b93b2d4c64f).
 - [More Mobile and Desktop drag handles for Swimlanes/Lists/Cards. Part 1](https://github.com/wekan/wekan/commit/ff550e91103115e7b731dd80c4588b93b2d4c64f).
   Thanks to xet7.
   Thanks to xet7.
-    
+
 Thanks to above GitHub users for their contributions and translators for their translations.
 Thanks to above GitHub users for their contributions and translators for their translations.
 
 
 # v3.40 2019-09-11 Wekan release
 # v3.40 2019-09-11 Wekan release
@@ -556,7 +995,7 @@ This release fixes the following bugs:
 
 
 - [Add missing dependencies back and revert deleting phantomjs](https://github.com/wekan/wekan/commit/32e9aa0ddaf1b015825b8c62ad17ed74b449e4b1).
 - [Add missing dependencies back and revert deleting phantomjs](https://github.com/wekan/wekan/commit/32e9aa0ddaf1b015825b8c62ad17ed74b449e4b1).
   Thanks to whowillcare and xet7.
   Thanks to whowillcare and xet7.
-    
+
 Thanks to above GitHub users for their contributions and translators for their translations.
 Thanks to above GitHub users for their contributions and translators for their translations.
 
 
 # v3.09 2019-08-07 Wekan release
 # v3.09 2019-08-07 Wekan release

+ 5 - 5
Dockerfile

@@ -1,13 +1,13 @@
-FROM ubuntu:disco
+FROM ubuntu:rolling
 LABEL maintainer="wekan"
 LABEL maintainer="wekan"
 
 
 # Set the environment variables (defaults where required)
 # Set the environment variables (defaults where required)
 # DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
 # DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
 # ENV BUILD_DEPS="paxctl"
 # ENV BUILD_DEPS="paxctl"
-ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates" \
+ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
     DEBUG=false \
     DEBUG=false \
-    NODE_VERSION=v8.16.1 \
-    METEOR_RELEASE=1.8.1 \
+    NODE_VERSION=v12.15.0 \
+    METEOR_RELEASE=1.9.0 \
     USE_EDGE=false \
     USE_EDGE=false \
     METEOR_EDGE=1.5-beta.17 \
     METEOR_EDGE=1.5-beta.17 \
     NPM_VERSION=latest \
     NPM_VERSION=latest \
@@ -21,7 +21,7 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 g++ build-essential
     ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \
     ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \
     ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \
     ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \
     ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \
     ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \
-    RICHER_CARD_COMMENT_EDITOR=true \
+    RICHER_CARD_COMMENT_EDITOR=false \
     CARD_OPENED_WEBHOOK_ENABLED=false \
     CARD_OPENED_WEBHOOK_ENABLED=false \
     ATTACHMENTS_STORE_PATH="" \
     ATTACHMENTS_STORE_PATH="" \
     MAX_IMAGE_PIXEL="" \
     MAX_IMAGE_PIXEL="" \

+ 1 - 1
Stackerfile.yml

@@ -1,5 +1,5 @@
 appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
 appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
-appVersion: "v3.49.0"
+appVersion: "v3.78.0"
 files:
 files:
   userUploads:
   userUploads:
     - README.md
     - README.md

+ 3 - 3
client/components/activities/activities.jade

@@ -201,20 +201,20 @@ template(name="cardActivities")
           .activity-checklist(href="{{ card.absoluteUrl }}")
           .activity-checklist(href="{{ card.absoluteUrl }}")
             +viewer
             +viewer
               = checklistItem.title
               = checklistItem.title
-        
+
         if(currentData.timeKey)
         if(currentData.timeKey)
           | {{{_ activityType }}}
           | {{{_ activityType }}}
           = ' '
           = ' '
           i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
           i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
           if (currentData.timeOldValue)
           if (currentData.timeOldValue)
-              = ' '                                                                                    
+              = ' '
               | {{{_ "previous_as" }}}
               | {{{_ "previous_as" }}}
               = ' '
               = ' '
               i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
               i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
           = ' @'
           = ' @'
         else if(currentData.timeValue)
         else if(currentData.timeValue)
           | {{{_ activityType currentData.timeValue}}}
           | {{{_ activityType currentData.timeValue}}}
-        
+
 
 
         if($eq activityType 'deleteComment')
         if($eq activityType 'deleteComment')
           | {{{_ 'activity-deleteComment' currentData.commentId}}}.
           | {{{_ 'activity-deleteComment' currentData.commentId}}}.

+ 1 - 1
client/components/activities/activities.styl

@@ -9,7 +9,7 @@
   clear: both
   clear: both
 
 
   .activity
   .activity
-    margin: 10px 0
+    margin: 0.5px 0
     display: flex
     display: flex
 
 
     .member
     .member

+ 0 - 3
client/components/activities/comments.js

@@ -16,9 +16,6 @@ BlazeComponent.extendComponent({
   events() {
   events() {
     return [
     return [
       {
       {
-        'click .js-new-comment:not(.focus)'() {
-          commentFormIsOpen.set(true);
-        },
         'submit .js-new-comment-form'(evt) {
         'submit .js-new-comment-form'(evt) {
           const input = this.getInput();
           const input = this.getInput();
           const text = input.val().trim();
           const text = input.val().trim();

+ 20 - 0
client/components/activities/comments.styl

@@ -46,3 +46,23 @@
 
 
     &:is-open
     &:is-open
       cursor: auto
       cursor: auto
+
+.comment-item
+  background-color: #fff
+  border: 0
+  box-shadow: 0 1px 2px rgba(0, 0, 0, .23)
+  color: #8c8c8c
+  height: 36px
+  margin: 4px 4px 6px 0
+  width: 92%
+
+  &:hover
+    background: darken(white, 12%)
+
+  &.add-comment
+    display: flex
+    margin: 5px
+
+    a
+      display: block
+      margin: auto

+ 52 - 13
client/components/boards/boardBody.js

@@ -1,3 +1,5 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
 const subManager = new SubsManager();
 const subManager = new SubsManager();
 const { calculateIndex, enableClickOnTouch } = Utils;
 const { calculateIndex, enableClickOnTouch } = Utils;
 const swimlaneWhileSortingHeight = 150;
 const swimlaneWhileSortingHeight = 150;
@@ -89,7 +91,6 @@ BlazeComponent.extendComponent({
         helper.append(list.clone());
         helper.append(list.clone());
         return helper;
         return helper;
       },
       },
-      handle: '.js-swimlane-header-handle',
       items: '.swimlane:not(.placeholder)',
       items: '.swimlane:not(.placeholder)',
       placeholder: 'swimlane placeholder',
       placeholder: 'swimlane placeholder',
       distance: 7,
       distance: 7,
@@ -193,6 +194,32 @@ BlazeComponent.extendComponent({
     // ugly touch event hotfix
     // ugly touch event hotfix
     enableClickOnTouch('.js-swimlane:not(.placeholder)');
     enableClickOnTouch('.js-swimlane:not(.placeholder)');
 
 
+    this.autorun(() => {
+      let showDesktopDragHandles = false;
+      currentUser = Meteor.user();
+      if (currentUser) {
+        showDesktopDragHandles = (currentUser.profile || {})
+          .showDesktopDragHandles;
+      } else if (cookies.has('showDesktopDragHandles')) {
+        showDesktopDragHandles = true;
+      } else {
+        showDesktopDragHandles = false;
+      }
+      if (!Utils.isMiniScreen() && showDesktopDragHandles) {
+        $swimlanesDom.sortable({
+          handle: '.js-swimlane-header-handle',
+        });
+      } else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
+        $swimlanesDom.sortable({
+          handle: '.swimlane-header',
+        });
+      }
+
+      // Disable drag-dropping if the current user is not a board member or is miniscreen
+      $swimlanesDom.sortable('option', 'disabled', !userIsMember());
+      $swimlanesDom.sortable('option', 'disabled', Utils.isMiniScreen());
+    });
+
     function userIsMember() {
     function userIsMember() {
       return (
       return (
         Meteor.user() &&
         Meteor.user() &&
@@ -210,21 +237,30 @@ BlazeComponent.extendComponent({
   },
   },
 
 
   isViewSwimlanes() {
   isViewSwimlanes() {
-    const currentUser = Meteor.user();
-    if (!currentUser) return false;
-    return (currentUser.profile || {}).boardView === 'board-view-swimlanes';
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).boardView === 'board-view-swimlanes';
+    } else {
+      return cookies.get('boardView') === 'board-view-swimlanes';
+    }
   },
   },
 
 
   isViewLists() {
   isViewLists() {
-    const currentUser = Meteor.user();
-    if (!currentUser) return true;
-    return (currentUser.profile || {}).boardView === 'board-view-lists';
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).boardView === 'board-view-lists';
+    } else {
+      return cookies.get('boardView') === 'board-view-lists';
+    }
   },
   },
 
 
   isViewCalendar() {
   isViewCalendar() {
-    const currentUser = Meteor.user();
-    if (!currentUser) return false;
-    return (currentUser.profile || {}).boardView === 'board-view-cal';
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).boardView === 'board-view-cal';
+    } else {
+      return cookies.get('boardView') === 'board-view-cal';
+    }
   },
   },
 
 
   openNewListForm() {
   openNewListForm() {
@@ -381,8 +417,11 @@ BlazeComponent.extendComponent({
     };
     };
   },
   },
   isViewCalendar() {
   isViewCalendar() {
-    const currentUser = Meteor.user();
-    if (!currentUser) return false;
-    return (currentUser.profile || {}).boardView === 'board-view-cal';
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).boardView === 'board-view-cal';
+    } else {
+      return cookies.get('boardView') === 'board-view-cal';
+    }
   },
   },
 }).register('calendarView');
 }).register('calendarView');

+ 64 - 28
client/components/boards/boardHeader.jade

@@ -77,10 +77,11 @@ template(name="boardHeaderBar")
             i.fa.fa-archive
             i.fa.fa-archive
             span {{_ 'archives'}}
             span {{_ 'archives'}}
 
 
-      if showSort
-       a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
-        i.fa(class="{{directionClass}}")
-        span {{_ 'sort'}}{{_ listSortShortDesc}}
+      //if showSort
+      //  a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
+      //    i.fa(class="{{directionClass}}")
+      //    span {{_ 'sort'}}{{_ listSortShortDesc}}
+
       a.board-header-btn.js-open-filter-view(
       a.board-header-btn.js-open-filter-view(
           title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
           title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
           class="{{#if Filter.isActive}}emphasis{{/if}}")
           class="{{#if Filter.isActive}}emphasis{{/if}}")
@@ -89,15 +90,6 @@ template(name="boardHeaderBar")
         if Filter.isActive
         if Filter.isActive
           a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
           a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
             i.fa.fa-times-thin
             i.fa.fa-times-thin
-            
-      if currentUser.isAdmin
-        a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}")
-          i.fa.fa-magic
-          span {{_ 'rules'}}
-      else if currentUser.isBoardAdmin
-        a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}")
-          i.fa.fa-magic
-          span {{_ 'rules'}}
 
 
       a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
       a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
         i.fa.fa-search
         i.fa.fa-search
@@ -106,8 +98,14 @@ template(name="boardHeaderBar")
       unless currentBoard.isTemplatesBoard
       unless currentBoard.isTemplatesBoard
         a.board-header-btn.js-toggle-board-view(
         a.board-header-btn.js-toggle-board-view(
           title="{{_ 'board-view'}}")
           title="{{_ 'board-view'}}")
-          i.fa.fa-th-large
-          span {{#if currentUser.profile.boardView}}{{_ currentUser.profile.boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}
+          i.fa.fa-caret-down
+          if $eq boardView 'board-view-lists'
+            i.fa.fa-trello
+          if $eq boardView 'board-view-swimlanes'
+            i.fa.fa-th-large
+          if $eq boardView 'board-view-cal'
+            i.fa.fa-calendar
+          span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}
 
 
       if canModifyBoard
       if canModifyBoard
         a.board-header-btn.js-multiselection-activate(
         a.board-header-btn.js-multiselection-activate(
@@ -172,6 +170,44 @@ template(name="boardChangeWatchPopup")
             i.fa.fa-check
             i.fa.fa-check
           span.sub-name {{_ 'muted-info'}}
           span.sub-name {{_ 'muted-info'}}
 
 
+template(name="boardChangeViewPopup")
+  ul.pop-over-list
+    li
+      with "board-view-lists"
+        a.js-open-lists-view
+          i.fa.fa-trello.colorful
+          | {{_ 'board-view-lists'}}
+          if $eq Utils.boardView "board-view-lists"
+            i.fa.fa-check
+    li
+      with "board-view-swimlanes"
+        a.js-open-swimlanes-view
+          i.fa.fa-th-large.colorful
+          | {{_ 'board-view-swimlanes'}}
+          if $eq Utils.boardView "board-view-swimlanes"
+            i.fa.fa-check
+    li
+      with "board-view-cal"
+        a.js-open-cal-view
+          i.fa.fa-calendar.colorful
+          | {{_ 'board-view-cal'}}
+          if $eq Utils.boardView "board-view-cal"
+            i.fa.fa-check
+    if currentUser.isAdmin
+      hr
+      li
+        with "board-view-rules"
+          a.js-open-rules-view(title="{{_ 'rules'}}")
+            i.fa.fa-magic
+            | {{_ 'rules'}}
+    else if currentUser.isBoardAdmin
+      hr
+      li
+        with "board-view-rules"
+          a.js-open-rules-view(title="{{_ 'rules'}}")
+            i.fa.fa-magic
+            | {{_ 'rules'}}
+
 template(name="createBoard")
 template(name="createBoard")
   form
   form
     label
     label
@@ -198,19 +234,19 @@ template(name="createBoard")
       | /
       | /
       a.js-board-template {{_ 'template'}}
       a.js-board-template {{_ 'template'}}
 
 
-template(name="listsortPopup")
-  h2
-   | {{_ 'list-sort-by'}}
-  hr
-  ul.pop-over-list
-    each value in allowedSortValues
-     li
-      a.js-sort-by(name="{{value.name}}")
-        if $eq sortby value.name
-           i(class="fa {{Direction}}") 
-        | {{_ value.label }}{{_ value.shortLabel}}
-        if $eq sortby value.name
-           i(class="fa fa-check")
+//template(name="listsortPopup")
+//  h2
+//   | {{_ 'list-sort-by'}}
+//  hr
+//  ul.pop-over-list
+//    each value in allowedSortValues
+//     li
+//      a.js-sort-by(name="{{value.name}}")
+//        if $eq sortby value.name
+//           i(class="fa {{Direction}}")
+//        | {{_ value.label }}{{_ value.shortLabel}}
+//        if $eq sortby value.name
+//           i(class="fa fa-check")
 
 
 template(name="boardChangeTitlePopup")
 template(name="boardChangeTitlePopup")
   form
   form

+ 32 - 21
client/components/boards/boardHeader.js

@@ -1,5 +1,7 @@
+/*
 const DOWNCLS = 'fa-sort-down';
 const DOWNCLS = 'fa-sort-down';
 const UPCLS = 'fa-sort-up';
 const UPCLS = 'fa-sort-up';
+*/
 Template.boardMenuPopup.events({
 Template.boardMenuPopup.events({
   'click .js-rename-board': Popup.open('boardChangeTitle'),
   'click .js-rename-board': Popup.open('boardChangeTitle'),
   'click .js-custom-fields'() {
   'click .js-custom-fields'() {
@@ -28,6 +30,7 @@ Template.boardMenuPopup.events({
   'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
   'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
   'click .js-import-board': Popup.open('chooseBoardSource'),
   'click .js-import-board': Popup.open('chooseBoardSource'),
   'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
   'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
+  'click .js-card-settings': Popup.open('boardCardSettings'),
 });
 });
 
 
 Template.boardMenuPopup.helpers({
 Template.boardMenuPopup.helpers({
@@ -82,6 +85,7 @@ BlazeComponent.extendComponent({
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
     return currentBoard && currentBoard.stars >= 2;
     return currentBoard && currentBoard.stars >= 2;
   },
   },
+  /*
   showSort() {
   showSort() {
     return Meteor.user().hasSortBy();
     return Meteor.user().hasSortBy();
   },
   },
@@ -101,6 +105,7 @@ BlazeComponent.extendComponent({
   listSortShortDesc() {
   listSortShortDesc() {
     return `list-label-short-${this.currentListSortBy()}`;
     return `list-label-short-${this.currentListSortBy()}`;
   },
   },
+  */
   events() {
   events() {
     return [
     return [
       {
       {
@@ -114,30 +119,14 @@ BlazeComponent.extendComponent({
         'click .js-open-archived-board'() {
         'click .js-open-archived-board'() {
           Modal.open('archivedBoards');
           Modal.open('archivedBoards');
         },
         },
-        'click .js-toggle-board-view'() {
-          const currentUser = Meteor.user();
-          if (
-            (currentUser.profile || {}).boardView === 'board-view-swimlanes'
-          ) {
-            currentUser.setBoardView('board-view-cal');
-          } else if (
-            (currentUser.profile || {}).boardView === 'board-view-lists'
-          ) {
-            currentUser.setBoardView('board-view-swimlanes');
-          } else if (
-            (currentUser.profile || {}).boardView === 'board-view-cal'
-          ) {
-            currentUser.setBoardView('board-view-lists');
-          } else {
-            currentUser.setBoardView('board-view-swimlanes');
-          }
-        },
+        'click .js-toggle-board-view': Popup.open('boardChangeView'),
         'click .js-toggle-sidebar'() {
         'click .js-toggle-sidebar'() {
           Sidebar.toggle();
           Sidebar.toggle();
         },
         },
         'click .js-open-filter-view'() {
         'click .js-open-filter-view'() {
           Sidebar.setView('filter');
           Sidebar.setView('filter');
         },
         },
+        /*
         'click .js-open-sort-view'(evt) {
         'click .js-open-sort-view'(evt) {
           const target = evt.target;
           const target = evt.target;
           if (target.tagName === 'I') {
           if (target.tagName === 'I') {
@@ -148,6 +137,7 @@ BlazeComponent.extendComponent({
             Popup.open('listsort')(evt);
             Popup.open('listsort')(evt);
           }
           }
         },
         },
+        */
         'click .js-filter-reset'(event) {
         'click .js-filter-reset'(event) {
           event.stopPropagation();
           event.stopPropagation();
           Sidebar.setView();
           Sidebar.setView();
@@ -156,9 +146,6 @@ BlazeComponent.extendComponent({
         'click .js-open-search-view'() {
         'click .js-open-search-view'() {
           Sidebar.setView('search');
           Sidebar.setView('search');
         },
         },
-        'click .js-open-rules-view'() {
-          Modal.openWide('rulesMain');
-        },
         'click .js-multiselection-activate'() {
         'click .js-multiselection-activate'() {
           const currentCard = Session.get('currentCard');
           const currentCard = Session.get('currentCard');
           MultiSelection.activate();
           MultiSelection.activate();
@@ -186,6 +173,28 @@ Template.boardHeaderBar.helpers({
       !Meteor.user().isCommentOnly()
       !Meteor.user().isCommentOnly()
     );
     );
   },
   },
+  boardView() {
+    return Utils.boardView();
+  },
+});
+
+Template.boardChangeViewPopup.events({
+  'click .js-open-lists-view'() {
+    Utils.setBoardView('board-view-lists');
+    Popup.close();
+  },
+  'click .js-open-swimlanes-view'() {
+    Utils.setBoardView('board-view-swimlanes');
+    Popup.close();
+  },
+  'click .js-open-cal-view'() {
+    Utils.setBoardView('board-view-cal');
+    Popup.close();
+  },
+  'click .js-open-rules-view'() {
+    Modal.openWide('rulesMain');
+    Popup.close();
+  },
 });
 });
 
 
 const CreateBoard = BlazeComponent.extendComponent({
 const CreateBoard = BlazeComponent.extendComponent({
@@ -308,6 +317,7 @@ BlazeComponent.extendComponent({
   },
   },
 }).register('boardChangeWatchPopup');
 }).register('boardChangeWatchPopup');
 
 
+/*
 BlazeComponent.extendComponent({
 BlazeComponent.extendComponent({
   onCreated() {
   onCreated() {
     //this.sortBy = new ReactiveVar();
     //this.sortBy = new ReactiveVar();
@@ -377,3 +387,4 @@ BlazeComponent.extendComponent({
     ];
     ];
   },
   },
 }).register('listsortPopup');
 }).register('listsortPopup');
+*/

+ 16 - 13
client/components/cards/attachments.jade

@@ -45,19 +45,22 @@ template(name="attachmentsGalery")
               | {{_ 'download'}}
               | {{_ 'download'}}
             if currentUser.isBoardMember
             if currentUser.isBoardMember
               unless currentUser.isCommentOnly
               unless currentUser.isCommentOnly
-                if isImage
-                  a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
-                    i.fa.fa-thumb-tack
-                    if($eq ../coverId _id)
-                      | {{_ 'remove-cover'}}
-                    else
-                      | {{_ 'add-cover'}}
-                a.js-confirm-delete
-                  i.fa.fa-close
-                  | {{_ 'delete'}}
+                unless currentUser.isWorker
+                  if isImage
+                    a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
+                      i.fa.fa-thumb-tack
+                      if($eq ../coverId _id)
+                        | {{_ 'remove-cover'}}
+                      else
+                        | {{_ 'add-cover'}}
+                  a.js-confirm-delete
+                    i.fa.fa-close
+                    | {{_ 'delete'}}
 
 
     if currentUser.isBoardMember
     if currentUser.isBoardMember
       unless currentUser.isCommentOnly
       unless currentUser.isCommentOnly
-        li.attachment-item.add-attachment
-          a.js-add-attachment {{_ 'add-attachment' }}
-
+        unless currentUser.isWorker
+          //li.attachment-item.add-attachment
+          a.js-add-attachment
+            i.fa.fa-paperclip
+            | {{_ 'add-attachment' }}

+ 2 - 1
client/components/cards/cardDate.js

@@ -97,7 +97,8 @@ Template.dateBadge.helpers({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 });
 });

+ 301 - 174
client/components/cards/cardDetails.jade

@@ -4,15 +4,27 @@ template(name="cardDetails")
       +inlinedForm(classNames="js-card-details-title")
       +inlinedForm(classNames="js-card-details-title")
         +editCardTitleForm
         +editCardTitleForm
       else
       else
-        a.fa.fa-times-thin.close-card-details.js-close-card-details
-        if currentUser.isBoardMember
-          a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
+        unless isMiniScreen
+          a.fa.fa-times-thin.close-card-details.js-close-card-details
+          if currentUser.isBoardMember
+            a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
+            input.inline-input(type="text" id="cardURL_copy" value="{{ absoluteUrl }}")
+            a.fa.fa-link.card-copy-button.js-copy-link(
+              class="fa-link"
+              title="{{_ 'copy-card-link-to-clipboard'}}"
+              value="{{ absoluteUrl }}"
+            )
+        if isMiniScreen
+          a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details
+          if currentUser.isBoardMember
+            a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu
+            a.fa.fa-link.card-copy-mobile-button
         h2.card-details-title.js-card-title(
         h2.card-details-title.js-card-title(
           class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
           class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
             +viewer
             +viewer
               = getTitle
               = getTitle
-              if isWatching
-                i.fa.fa-eye.card-details-watch
+            if isWatching
+              i.card-details-watch.fa.fa-eye
         .card-details-path
         .card-details-path
           each parentList
           each parentList
             | &nbsp; &gt; &nbsp;
             | &nbsp; &gt; &nbsp;
@@ -31,70 +43,105 @@ template(name="cardDetails")
         p.warning {{_ 'card-archived'}}
         p.warning {{_ 'card-archived'}}
 
 
     .card-details-items
     .card-details-items
-      .card-details-item.card-details-item-received
-        h3.card-details-item-title {{_ 'card-received'}}
-        if getReceived
-          +cardReceivedDate
-        else
-          if canModifyCard
-            a.js-received-date {{_ 'add'}}
-
-      .card-details-item.card-details-item-start
-        h3.card-details-item-title {{_ 'card-start'}}
-        if getStart
-          +cardStartDate
-        else
-          if canModifyCard
-            a.js-start-date {{_ 'add'}}
-
-      .card-details-item.card-details-item-due
-        h3.card-details-item-title {{_ 'card-due'}}
-        if getDue
-          +cardDueDate
-        else
+      if currentBoard.allowsReceivedDate
+        .card-details-item.card-details-item-received
+          h3
+            i.fa.fa-sign-out
+            card-details-item-title {{_ 'card-received'}}
+          if getReceived
+            +cardReceivedDate
+          else
+            if canModifyCard
+              unless currentUser.isWorker
+                a.card-label.add-label.js-received-date
+                  i.fa.fa-plus
+
+      if currentBoard.allowsStartDate
+        .card-details-item.card-details-item-start
+          h3
+            i.fa.fa-hourglass-start
+            card-details-item-title {{_ 'card-start'}}
+          if getStart
+            +cardStartDate
+          else
+            if canModifyCard
+              unless currentUser.isWorker
+                a.card-label.add-label.js-start-date
+                  i.fa.fa-plus
+
+      if currentBoard.allowsDueDate
+        .card-details-item.card-details-item-due
+          h3
+            i.fa.fa-sign-in
+            card-details-item-title {{_ 'card-due'}}
+          if getDue
+            +cardDueDate
+          else
+            if canModifyCard
+              unless currentUser.isWorker
+                a.card-label.add-label.js-due-date
+                  i.fa.fa-plus
+
+      if currentBoard.allowsEndDate
+        .card-details-item.card-details-item-end
+          h3
+            i.fa.fa-hourglass-end
+            card-details-item-title {{_ 'card-end'}}
+          if getEnd
+            +cardEndDate
+          else
+            if canModifyCard
+              unless currentUser.isWorker
+                a.card-label.add-label.js-end-date
+                  i.fa.fa-plus
+
+      //.card-details-items
+      if currentBoard.allowsMembers
+        .card-details-item.card-details-item-members
+          h3
+            i.fa.fa-users
+            card-details-item-title {{_ 'members'}}
+          each getMembers
+            +userAvatar(userId=this cardId=../_id)
+            | {{! XXX Hack to hide syntaxic coloration /// }}
           if canModifyCard
           if canModifyCard
-            a.js-due-date {{_ 'add'}}
-
-      .card-details-item.card-details-item-end
-        h3.card-details-item-title {{_ 'card-end'}}
-        if getEnd
-          +cardEndDate
-        else
+            unless currentUser.isWorker
+              a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
+                i.fa.fa-plus
+
+      //if assigneeSelected
+      if currentBoard.allowsAssignee
+        .card-details-item.card-details-item-assignees
+          h3
+            i.fa.fa-user
+            card-details-item-title {{_ 'assignee'}}
+          each getAssignees
+            +userAvatarAssignee(userId=this cardId=../_id)
+            | {{! XXX Hack to hide syntaxic coloration /// }}
           if canModifyCard
           if canModifyCard
-            a.js-end-date {{_ 'add'}}
-
-    .card-details-items
-      .card-details-item.card-details-item-members
-        h3.card-details-item-title {{_ 'members'}}
-        each getMembers
-          +userAvatar(userId=this cardId=../_id)
-          | {{! XXX Hack to hide syntaxic coloration /// }}
-        if canModifyCard
-          a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
-            i.fa.fa-plus
-
-      .card-details-item.card-details-item-assignees
-        h3.card-details-item-title {{_ 'assignee'}}
-        each getAssignees
-          +userAvatarAssignee(userId=this cardId=../_id)
-          | {{! XXX Hack to hide syntaxic coloration /// }}
-        if canModifyCard
-          unless assigneeSelected
             a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
             a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
               i.fa.fa-plus
               i.fa.fa-plus
+          if currentUser.isWorker
+            unless assigneeSelected
+              a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
+                i.fa.fa-plus
+
+      if currentBoard.allowsLabels
+        .card-details-item.card-details-item-labels
+          h3
+            i.fa.fa-tags
+            card-details-item-title {{_ 'labels'}}
+          a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
+            each labels
+              span.card-label(class="card-label-{{color}}" title=name)
+                +viewer
+                  = name
+          if canModifyCard
+            unless currentUser.isWorker
+              a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
+                i.fa.fa-plus
 
 
-      .card-details-item.card-details-item-labels
-        h3.card-details-item-title {{_ 'labels'}}
-        a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
-          each labels
-            span.card-label(class="card-label-{{color}}" title=name)
-              +viewer
-                = name
-        if canModifyCard
-          a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
-            i.fa.fa-plus
-
-    .card-details-items
+      //.card-details-items
       each customFieldsWD
       each customFieldsWD
         .card-details-item.card-details-item-customfield
         .card-details-item.card-details-item-customfield
           h3.card-details-item-title
           h3.card-details-item-title
@@ -102,7 +149,7 @@ template(name="cardDetails")
               = definition.name
               = definition.name
           +cardCustomField
           +cardCustomField
 
 
-    .card-details-items
+      //.card-details-items
       if getSpentTime
       if getSpentTime
         .card-details-item.card-details-item-spent
         .card-details-item.card-details-item-spent
           if getIsOvertime
           if getIsOvertime
@@ -111,84 +158,103 @@ template(name="cardDetails")
             h3.card-details-item-title {{_ 'spent-time-hours'}}
             h3.card-details-item-title {{_ 'spent-time-hours'}}
           +cardSpentTime
           +cardSpentTime
 
 
-    //- XXX We should use "editable" to avoid repetiting ourselves
-    if canModifyCard
-      h3.card-details-item-title {{_ 'description'}}
-      +inlinedCardDescription(classNames="card-description js-card-description")
-        +editor(autofocus=true)
-          | {{getUnsavedValue 'cardDescription' _id getDescription}}
-        .edit-controls.clearfix
-          button.primary(type="submit") {{_ 'save'}}
-          a.fa.fa-times-thin.js-close-inlined-form
-      else
-        a.js-open-inlined-form
-          if getDescription
+      //.card-details-items
+      if currentBoard.allowsRequestedBy
+        .card-details-item.card-details-item-name
+          h3
+            i.fa.fa-shopping-cart
+            card-details-item-title {{_ 'requested-by'}}
+          if canModifyCard
+            unless currentUser.isWorker
+              +inlinedForm(classNames="js-card-details-requester")
+                +editCardRequesterForm
+              else
+                a.js-open-inlined-form
+                  if getRequestedBy
+                    +viewer
+                      = getRequestedBy
+                  else
+                    | {{_ 'add'}}
+          else if getRequestedBy
             +viewer
             +viewer
-              = getDescription
-          else
-            | {{_ 'edit'}}
-        if (hasUnsavedValue 'cardDescription' _id)
-          p.quiet
-            | {{_ 'unsaved-description'}}
-            a.js-open-inlined-form {{_ 'view-it'}}
-            = ' - '
-            a.js-close-inlined-form {{_ 'discard'}}
-    else if getDescription
-      h3.card-details-item-title {{_ 'description'}}
-      +viewer
-        = getDescription
+              = getRequestedBy
 
 
-    .card-details-items
-      .card-details-item.card-details-item-name
-        h3.card-details-item-title {{_ 'requested-by'}}
-        if canModifyCard
-          +inlinedForm(classNames="js-card-details-requester")
-            +editCardRequesterForm
-          else
-            a.js-open-inlined-form
-              if getRequestedBy
-                +viewer
-                  = getRequestedBy
-              else
-                | {{_ 'add'}}
-        else if getRequestedBy
-          +viewer
-            = getRequestedBy
-
-      .card-details-item.card-details-item-name
-        h3.card-details-item-title {{_ 'assigned-by'}}
-        if canModifyCard
-          +inlinedForm(classNames="js-card-details-assigner")
-            +editCardAssignerForm
-          else
-            a.js-open-inlined-form
-              if getAssignedBy
-                +viewer
-                  = getAssignedBy
+      if currentBoard.allowsAssignedBy
+        .card-details-item.card-details-item-name
+          h3
+            i.fa.fa-user-plus
+            card-details-item-title {{_ 'assigned-by'}}
+          if canModifyCard
+            unless currentUser.isWorker
+              +inlinedForm(classNames="js-card-details-assigner")
+                +editCardAssignerForm
               else
               else
-                | {{_ 'add'}}
-        else if getRequestedBy
-          +viewer
-            = getAssignedBy
-
-    hr
-    +checklists(cardId = _id)
-
-    if currentBoard.allowsSubtasks
-      hr
-      +subtasks(cardId = _id)
-
-    hr
-    h3
-      i.fa.fa-paperclip
-      | {{_ 'attachments'}}
+                a.js-open-inlined-form
+                  if getAssignedBy
+                    +viewer
+                      = getAssignedBy
+                  else
+                    | {{_ 'add'}}
+          else if getRequestedBy
+            +viewer
+              = getAssignedBy
 
 
-    +attachmentsGalery
+    //- XXX We should use "editable" to avoid repetiting ourselves
+    if canModifyCard
+      unless currentUser.isWorker
+        if currentBoard.allowsDescriptionTitle
+          h3
+            i.fa.fa-align-left
+            card-details-item-title {{_ 'description'}}
+        if currentBoard.allowsDescriptionText
+          +inlinedCardDescription(classNames="card-description js-card-description")
+            +editor(autofocus=true)
+              | {{getUnsavedValue 'cardDescription' _id getDescription}}
+            .edit-controls.clearfix
+              button.primary(type="submit") {{_ 'save'}}
+              a.fa.fa-times-thin.js-close-inlined-form
+          else
+            if currentBoard.allowsDescriptionText
+              a.js-open-inlined-form
+                if getDescription
+                  +viewer
+                    = getDescription
+                else
+                  | {{_ 'edit'}}
+              if (hasUnsavedValue 'cardDescription' _id)
+                p.quiet
+                  | {{_ 'unsaved-description'}}
+                  a.js-open-inlined-form {{_ 'view-it'}}
+                  = ' - '
+                  a.js-close-inlined-form {{_ 'discard'}}
+    else if getDescription
+      if currentBoard.allowsDescriptionTitle
+        h3.card-details-item-title {{_ 'description'}}
+      if currentBoard.allowsDescriptionText
+        +viewer
+          = getDescription
+
+    .card-checklist-attachmentGalerys
+      .card-checklist-attachmentGalery.card-checklists
+        if currentBoard.allowsChecklists
+          +checklists(cardId = _id)
+        if currentBoard.allowsSubtasks
+          hr
+          +subtasks(cardId = _id)
+      if currentBoard.allowsAttachments
+        //- hr
+        //- h3
+        //- i.fa.fa-paperclip
+        //- | {{_ 'attachments'}}
+        .card-checklist-attachmentGalery.card-attachmentGalery
+          +attachmentsGalery
 
 
     hr
     hr
     unless currentUser.isNoComments
     unless currentUser.isNoComments
       .activity-title
       .activity-title
-        h3 {{ _ 'activity'}}
+        h3
+          i.fa.fa-history
+          | {{ _ 'activity'}}
         if currentUser.isBoardMember
         if currentUser.isBoardMember
           .material-toggle-switch
           .material-toggle-switch
             span.toggle-switch-title {{_ 'hide-system-messages'}}
             span.toggle-switch-title {{_ 'hide-system-messages'}}
@@ -197,9 +263,10 @@ template(name="cardDetails")
             else
             else
               input.toggle-switch(type="checkbox" id="toggleButton")
               input.toggle-switch(type="checkbox" id="toggleButton")
             label.toggle-label(for="toggleButton")
             label.toggle-label(for="toggleButton")
-    if currentUser.isBoardMember
-      unless currentUser.isNoComments
-        +commentForm
+    if currentBoard.allowsComments
+      if currentUser.isBoardMember
+        unless currentUser.isNoComments
+          +commentForm
     unless currentUser.isNoComments
     unless currentUser.isNoComments
       if isLoaded.get
       if isLoaded.get
         if isLinkedCard
         if isLinkedCard
@@ -230,32 +297,79 @@ template(name="editCardAssignerForm")
 
 
 template(name="cardDetailsActionsPopup")
 template(name="cardDetailsActionsPopup")
   ul.pop-over-list
   ul.pop-over-list
-    li: a.js-toggle-watch-card {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
+    li
+      a.js-toggle-watch-card
+        if isWatching
+          i.fa.fa-eye
+          |  {{_ 'unwatch'}}
+        else
+          i.fa.fa-eye-slash
+          |  {{_ 'watch'}}
   if canModifyCard
   if canModifyCard
-    hr
-    ul.pop-over-list
-      //li: a.js-members {{_ 'card-edit-members'}}
-      //li: a.js-labels {{_ 'card-edit-labels'}}
-      //li: a.js-attachments {{_ 'card-edit-attachments'}}
-      li: a.js-custom-fields {{_ 'card-edit-custom-fields'}}
-      //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
-      //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
-      //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
-      //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
-      li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}}
-      li: a.js-set-card-color {{_ 'setCardColorPopup-title'}}
-    hr
-    ul.pop-over-list
-      li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}}
-      li: a.js-move-card-to-bottom {{_ 'moveCardToBottom-title'}}
-    hr
+    unless currentUser.isWorker
+      hr
+      ul.pop-over-list
+        //li: a.js-members {{_ 'card-edit-members'}}
+        //li: a.js-labels {{_ 'card-edit-labels'}}
+        //li: a.js-attachments {{_ 'card-edit-attachments'}}
+        li
+          a.js-custom-fields
+            i.fa.fa-list-alt
+            | {{_ 'card-edit-custom-fields'}}
+        //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
+        //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
+        //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
+        //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
+        li
+          a.js-spent-time
+            i.fa.fa-clock-o
+            | {{_ 'editCardSpentTimePopup-title'}}
+        li
+          a.js-set-card-color
+            i.fa.fa-paint-brush
+            | {{_ 'setCardColorPopup-title'}}
+      hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a.js-move-card {{_ 'moveCardPopup-title'}}
-      li: a.js-copy-card {{_ 'copyCardPopup-title'}}
-      li: a.js-copy-checklist-cards {{_ 'copyChecklistToManyCardsPopup-title'}}
+      li
+        a.js-move-card-to-top
+          i.fa.fa-arrow-up
+          | {{_ 'moveCardToTop-title'}}
+      li
+        a.js-move-card-to-bottom
+          i.fa.fa-arrow-down
+          | {{_ 'moveCardToBottom-title'}}
+    unless currentUser.isWorker
+      hr
+      ul.pop-over-list
+        li
+          a.js-move-card
+            i.fa.fa-arrow-right
+            | {{_ 'moveCardPopup-title'}}
+        li
+          a.js-copy-card
+            i.fa.fa-copy
+            | {{_ 'copyCardPopup-title'}}
+      hr
+      ul.pop-over-list
+        li
+          a.js-copy-checklist-cards
+            i.fa.fa-list
+            i.fa.fa-copy
+            | {{_ 'copyChecklistToManyCardsPopup-title'}}
       unless archived
       unless archived
-        li: a.js-archive {{_ 'archive-card'}}
-      li: a.js-more {{_ 'cardMorePopup-title'}}
+        hr
+        ul.pop-over-list
+          li
+            a.js-archive
+              i.fa.fa-arrow-right
+              i.fa.fa-archive
+              | {{_ 'archive-card'}}
+      hr
+      ul.pop-over-list
+        li
+          a.js-more
+            i.fa.fa-link
+            | {{_ 'cardMorePopup-title'}}
 
 
 template(name="moveCardPopup")
 template(name="moveCardPopup")
   +boardsAndLists
   +boardsAndLists
@@ -307,16 +421,27 @@ template(name="cardMembersPopup")
             i.fa.fa-check
             i.fa.fa-check
 
 
 template(name="cardAssigneesPopup")
 template(name="cardAssigneesPopup")
-  ul.pop-over-list.js-card-assignee-list
-    each board.activeMembers
-      li.item(class="{{#if isCardAssignee}}active{{/if}}")
-        a.name.js-select-assignee(href="#")
-          +userAvatar(userId=user._id)
-          span.full-name
-            = user.profile.fullname
-            | (<span class="username">{{ user.username }}</span>)
-          if isCardAssignee
-            i.fa.fa-check
+  unless currentUser.isWorker
+    ul.pop-over-list.js-card-assignee-list
+      each board.activeMembers
+        li.item(class="{{#if isCardAssignee}}active{{/if}}")
+          a.name.js-select-assignee(href="#")
+            +userAvatar(userId=user._id)
+            span.full-name
+              = user.profile.fullname
+              | (<span class="username">{{ user.username }}</span>)
+            if isCardAssignee
+              i.fa.fa-check
+  if currentUser.isWorker
+    ul.pop-over-list.js-card-assignee-list
+        li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
+          a.name.js-select-assignee(href="#")
+            +userAvatar(userId=currentUser._id)
+            span.full-name
+              = currentUser.profile.fullname
+              | (<span class="username">{{ currentUser.username }}</span>)
+            if currentUser.isCardAssignee
+              i.fa.fa-check
 
 
 template(name="userAvatarAssignee")
 template(name="userAvatarAssignee")
   a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
   a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
@@ -344,11 +469,13 @@ template(name="cardAssigneePopup")
         p.quiet @{{ user.username }}
         p.quiet @{{ user.username }}
     ul.pop-over-list
     ul.pop-over-list
       if currentUser.isNotCommentOnly
       if currentUser.isNotCommentOnly
+        unless currentUser.isWorker
           li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
           li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
 
 
-      if $eq currentUser._id user._id
-        with currentUser
-          li: a.js-edit-profile {{_ 'edit-profile'}}
+      unless currentUser.isWorker
+        if $eq currentUser._id user._id
+          with currentUser
+            li: a.js-edit-profile {{_ 'edit-profile'}}
 
 
 template(name="userAvatarAssigneeInitials")
 template(name="userAvatarAssigneeInitials")
   svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")
   svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")

+ 101 - 1
client/components/cards/cardDetails.js

@@ -51,7 +51,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 
@@ -252,6 +253,12 @@ BlazeComponent.extendComponent({
       if ($subtasksDom.data('sortable')) {
       if ($subtasksDom.data('sortable')) {
         $subtasksDom.sortable('option', 'disabled', !userIsMember());
         $subtasksDom.sortable('option', 'disabled', !userIsMember());
       }
       }
+      if ($checklistsDom.data('sortable')) {
+        $checklistsDom.sortable('option', 'disabled', Utils.isMiniScreen());
+      }
+      if ($subtasksDom.data('sortable')) {
+        $subtasksDom.sortable('option', 'disabled', Utils.isMiniScreen());
+      }
     });
     });
   },
   },
 
 
@@ -278,6 +285,29 @@ BlazeComponent.extendComponent({
         'click .js-close-card-details'() {
         'click .js-close-card-details'() {
           Utils.goBoardId(this.data().boardId);
           Utils.goBoardId(this.data().boardId);
         },
         },
+        'click .js-copy-link'() {
+          StringToCopyElement = document.getElementById('cardURL_copy');
+          StringToCopyElement.select();
+          if (document.execCommand('copy')) {
+            StringToCopyElement.blur();
+          } else {
+            document.getElementById('cardURL_copy').selectionStart = 0;
+            document.getElementById('cardURL_copy').selectionEnd = 999;
+            document.execCommand('copy');
+            if (window.getSelection) {
+              if (window.getSelection().empty) {
+                // Chrome
+                window.getSelection().empty();
+              } else if (window.getSelection().removeAllRanges) {
+                // Firefox
+                window.getSelection().removeAllRanges();
+              }
+            } else if (document.selection) {
+              // IE?
+              document.selection.empty();
+            }
+          }
+        },
         'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
         'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
         'submit .js-card-description'(event) {
         'submit .js-card-description'(event) {
           event.preventDefault();
           event.preventDefault();
@@ -291,6 +321,8 @@ BlazeComponent.extendComponent({
             .trim();
             .trim();
           if (title) {
           if (title) {
             this.data().setTitle(title);
             this.data().setTitle(title);
+          } else {
+            this.data().setTitle('');
           }
           }
         },
         },
         'submit .js-card-details-assigner'(event) {
         'submit .js-card-details-assigner'(event) {
@@ -300,6 +332,8 @@ BlazeComponent.extendComponent({
             .trim();
             .trim();
           if (assigner) {
           if (assigner) {
             this.data().setAssignedBy(assigner);
             this.data().setAssignedBy(assigner);
+          } else {
+            this.data().setAssignedBy('');
           }
           }
         },
         },
         'submit .js-card-details-requester'(event) {
         'submit .js-card-details-requester'(event) {
@@ -309,6 +343,8 @@ BlazeComponent.extendComponent({
             .trim();
             .trim();
           if (requester) {
           if (requester) {
             this.data().setRequestedBy(requester);
             this.data().setRequestedBy(requester);
+          } else {
+            this.data().setRequestedBy('');
           }
           }
         },
         },
         'click .js-member': Popup.open('cardMember'),
         'click .js-member': Popup.open('cardMember'),
@@ -364,6 +400,54 @@ Template.cardDetails.helpers({
     });
     });
   },
   },
 
 
+  receivedSelected() {
+    if (this.getReceived().length === 0) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
+  startSelected() {
+    if (this.getStart().length === 0) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
+  endSelected() {
+    if (this.getEnd().length === 0) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
+  dueSelected() {
+    if (this.getDue().length === 0) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
+  memberSelected() {
+    if (this.getMembers().length === 0) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
+  labelSelected() {
+    if (this.getLabels().length === 0) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
   assigneeSelected() {
   assigneeSelected() {
     if (this.getAssignees().length === 0) {
     if (this.getAssignees().length === 0) {
       return false;
       return false;
@@ -372,6 +456,22 @@ Template.cardDetails.helpers({
     }
     }
   },
   },
 
 
+  requestBySelected() {
+    if (this.getRequestBy().length === 0) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
+  assigneeBySelected() {
+    if (this.getAssigneeBy().length === 0) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
   memberType() {
   memberType() {
     const user = Users.findOne(this.userId);
     const user = Users.findOne(this.userId);
     return user && user.isBoardAdmin() ? 'admin' : 'normal';
     return user && user.isBoardAdmin() ? 'admin' : 'normal';

+ 31 - 1
client/components/cards/cardDetails.styl

@@ -4,6 +4,12 @@
 
 
 avatar-radius = 50%
 avatar-radius = 50%
 
 
+#cardURL_copy
+  // Have clipboard text not visible by moving it to far left
+  position: absolute
+  left: -2000px
+  top: 0px
+
 .assignee
 .assignee
   border-radius: 3px
   border-radius: 3px
   display: block
   display: block
@@ -107,7 +113,11 @@ avatar-radius = 50%
     border-bottom: 1px solid darken(white, 14%)
     border-bottom: 1px solid darken(white, 14%)
 
 
     .close-card-details,
     .close-card-details,
-    .card-details-menu
+    .card-details-menu,
+    .card-copy-button,
+    .card-copy-mobile-button,
+    .close-card-details-mobile-web,
+    .card-details-menu-mobile-web
       float: right
       float: right
 
 
     .close-card-details
     .close-card-details
@@ -115,10 +125,30 @@ avatar-radius = 50%
       padding: 5px
       padding: 5px
       margin-right: -8px
       margin-right: -8px
 
 
+    .close-card-details-mobile-web
+      font-size: 24px
+      padding: 5px
+      margin-right: 40px
+
+    .card-copy-button
+      font-size: 17px
+      padding: 10px
+      margin-right: 10px
+
+    .card-copy-mobile-button
+      font-size: 17px
+      padding: 10px
+      margin-right: 10px
+
     .card-details-menu
     .card-details-menu
       font-size: 17px
       font-size: 17px
       padding: 10px
       padding: 10px
 
 
+    .card-details-menu-mobile-web
+      font-size: 17px
+      padding: 10px
+      margin-right: 30px
+
     .card-details-watch
     .card-details-watch
       font-size: 17px
       font-size: 17px
       padding-left: 7px
       padding-left: 7px

+ 3 - 1
client/components/cards/checklists.jade

@@ -1,5 +1,7 @@
 template(name="checklists")
 template(name="checklists")
-  h3 {{_ 'checklists'}}
+  h3
+    i.fa.fa-check
+    | {{_ 'checklists'}}
   if toggleDeleteDialog.get
   if toggleDeleteDialog.get
     .board-overlay#card-details-overlay
     .board-overlay#card-details-overlay
     +checklistDeleteDialog(checklist = checklistToDelete)
     +checklistDeleteDialog(checklist = checklistToDelete)

+ 9 - 3
client/components/cards/checklists.js

@@ -60,6 +60,9 @@ BlazeComponent.extendComponent({
       if ($itemsDom.data('sortable')) {
       if ($itemsDom.data('sortable')) {
         $(self.itemsDom).sortable('option', 'disabled', !userIsMember());
         $(self.itemsDom).sortable('option', 'disabled', !userIsMember());
       }
       }
+      if ($itemsDom.data('sortable')) {
+        $(self.itemsDom).sortable('option', 'disabled', Utils.isMiniScreen());
+      }
     });
     });
   },
   },
 
 
@@ -67,7 +70,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 }).register('checklistDetail');
 }).register('checklistDetail');
@@ -120,7 +124,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 
@@ -228,7 +233,8 @@ Template.checklistItemDetail.helpers({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 });
 });

+ 9 - 8
client/components/cards/minicard.jade

@@ -4,8 +4,8 @@ template(name="minicard")
     class="{{#if isLinkedBoard}}linked-board{{/if}}"
     class="{{#if isLinkedBoard}}linked-board{{/if}}"
     class="minicard-{{colorClass}}")
     class="minicard-{{colorClass}}")
     if isMiniScreen
     if isMiniScreen
-      .handle
-        .fa.fa-arrows
+      //.handle
+      //  .fa.fa-arrows
     unless isMiniScreen
     unless isMiniScreen
       if showDesktopDragHandles
       if showDesktopDragHandles
         .handle
         .handle
@@ -67,14 +67,15 @@ template(name="minicard")
     .minicard-custom-fields
     .minicard-custom-fields
       each customFieldsWD
       each customFieldsWD
         if definition.showOnCard
         if definition.showOnCard
-          .minicard-custom-field
-            if definition.showLabelOnMiniCard
+          if trueValue
+            .minicard-custom-field
+              if definition.showLabelOnMiniCard
+                .minicard-custom-field-item
+                  +viewer
+                    = definition.name
               .minicard-custom-field-item
               .minicard-custom-field-item
                 +viewer
                 +viewer
-                  = definition.name
-            .minicard-custom-field-item
-              +viewer
-                = trueValue
+                  = trueValue
 
 
     if getAssignees
     if getAssignees
       .minicard-assignees.js-minicard-assignees
       .minicard-assignees.js-minicard-assignees

+ 23 - 3
client/components/cards/minicard.js

@@ -1,3 +1,5 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
 // Template.cards.events({
 // Template.cards.events({
 //   'click .member': Popup.open('cardMember')
 //   'click .member': Popup.open('cardMember')
 // });
 // });
@@ -18,7 +20,11 @@ BlazeComponent.extendComponent({
       },
       },
       {
       {
         'click .js-toggle-minicard-label-text'() {
         'click .js-toggle-minicard-label-text'() {
-          Meteor.call('toggleMinicardLabelText');
+          if (cookies.has('hiddenMinicardLabelText')) {
+            cookies.remove('hiddenMinicardLabelText'); //true
+          } else {
+            cookies.set('hiddenMinicardLabelText', 'true'); //true
+          }
         },
         },
       },
       },
     ];
     ];
@@ -27,10 +33,24 @@ BlazeComponent.extendComponent({
 
 
 Template.minicard.helpers({
 Template.minicard.helpers({
   showDesktopDragHandles() {
   showDesktopDragHandles() {
-    return Meteor.user().hasShowDesktopDragHandles();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
   hiddenMinicardLabelText() {
   hiddenMinicardLabelText() {
-    return Meteor.user().hasHiddenMinicardLabelText();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).hiddenMinicardLabelText;
+    } else if (cookies.has('hiddenMinicardLabelText')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
   coverUrl() {
   coverUrl() {
     return Attachments.findOne(this.coverId).link('original', '/');
     return Attachments.findOne(this.coverId).link('original', '/');

+ 3 - 1
client/components/cards/subtasks.jade

@@ -1,5 +1,7 @@
 template(name="subtasks")
 template(name="subtasks")
-  h3 {{_ 'subtasks'}}
+  h3
+    i.fa.fa-sitemap
+    | {{_ 'subtasks'}}
   if toggleDeleteDialog.get
   if toggleDeleteDialog.get
     .board-overlay#card-details-overlay
     .board-overlay#card-details-overlay
     +subtaskDeleteDialog(subtask = subtaskToDelete)
     +subtaskDeleteDialog(subtask = subtaskToDelete)

+ 6 - 3
client/components/cards/subtasks.js

@@ -3,7 +3,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 }).register('subtaskDetail');
 }).register('subtaskDetail');
@@ -55,7 +56,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 
@@ -154,7 +156,8 @@ Template.subtaskItemDetail.helpers({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 });
 });

+ 56 - 20
client/components/lists/list.js

@@ -1,3 +1,5 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
 const { calculateIndex, enableClickOnTouch } = Utils;
 const { calculateIndex, enableClickOnTouch } = Utils;
 
 
 BlazeComponent.extendComponent({
 BlazeComponent.extendComponent({
@@ -31,18 +33,6 @@ BlazeComponent.extendComponent({
     const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
     const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
     const $cards = this.$('.js-minicards');
     const $cards = this.$('.js-minicards');
 
 
-    if (Utils.isMiniScreen) {
-      $('.js-minicards').sortable({
-        handle: '.handle',
-      });
-    }
-
-    if (!Utils.isMiniScreen && showDesktopDragHandles) {
-      $('.js-minicards').sortable({
-        handle: '.handle',
-      });
-    }
-
     $cards.sortable({
     $cards.sortable({
       connectWith: '.js-minicards:not(.js-list-full)',
       connectWith: '.js-minicards:not(.js-list-full)',
       tolerance: 'pointer',
       tolerance: 'pointer',
@@ -85,16 +75,15 @@ BlazeComponent.extendComponent({
         const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
         const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
         const currentBoard = Boards.findOne(Session.get('currentBoard'));
         const currentBoard = Boards.findOne(Session.get('currentBoard'));
         let swimlaneId = '';
         let swimlaneId = '';
-        const boardView = (Meteor.user().profile || {}).boardView;
         if (
         if (
-          boardView === 'board-view-swimlanes' ||
+          Utils.boardView() === 'board-view-swimlanes' ||
           currentBoard.isTemplatesBoard()
           currentBoard.isTemplatesBoard()
         )
         )
           swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
           swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
         else if (
         else if (
-          boardView === 'board-view-lists' ||
-          boardView === 'board-view-cal' ||
-          !boardView
+          Utils.boardView() === 'board-view-lists' ||
+          Utils.boardView() === 'board-view-cal' ||
+          !Utils.boardView
         )
         )
           swimlaneId = currentBoard.getDefaultSwimline()._id;
           swimlaneId = currentBoard.getDefaultSwimline()._id;
 
 
@@ -128,9 +117,49 @@ BlazeComponent.extendComponent({
     // ugly touch event hotfix
     // ugly touch event hotfix
     enableClickOnTouch(itemsSelector);
     enableClickOnTouch(itemsSelector);
 
 
-    // Disable drag-dropping if the current user is not a board member or is comment only
     this.autorun(() => {
     this.autorun(() => {
-      $cards.sortable('option', 'disabled', !userIsMember());
+      let showDesktopDragHandles = false;
+      currentUser = Meteor.user();
+      if (currentUser) {
+        showDesktopDragHandles = (currentUser.profile || {})
+          .showDesktopDragHandles;
+      } else if (cookies.has('showDesktopDragHandles')) {
+        showDesktopDragHandles = true;
+      } else {
+        showDesktopDragHandles = false;
+      }
+
+      if (!Utils.isMiniScreen() && showDesktopDragHandles) {
+        $cards.sortable({
+          handle: '.handle',
+        });
+      } else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
+        $cards.sortable({
+          handle: '.minicard',
+        });
+      }
+
+      if ($cards.data('sortable')) {
+        $cards.sortable(
+          'option',
+          'disabled',
+          // Disable drag-dropping when user is not member/is miniscreen
+          !userIsMember(),
+          // Not disable drag-dropping while in multi-selection mode
+          // MultiSelection.isActive() || !userIsMember(),
+        );
+      }
+
+      if ($cards.data('sortable')) {
+        $cards.sortable(
+          'option',
+          'disabled',
+          // Disable drag-dropping when user is not member/is miniscreen
+          Utils.isMiniScreen(),
+          // Not disable drag-dropping while in multi-selection mode
+          // MultiSelection.isActive() || !userIsMember(),
+        );
+      }
     });
     });
 
 
     // We want to re-run this function any time a card is added.
     // We want to re-run this function any time a card is added.
@@ -163,7 +192,14 @@ BlazeComponent.extendComponent({
 
 
 Template.list.helpers({
 Template.list.helpers({
   showDesktopDragHandles() {
   showDesktopDragHandles() {
-    return Meteor.user().hasShowDesktopDragHandles();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
 });
 });
 
 

+ 16 - 22
client/components/lists/listBody.js

@@ -48,7 +48,6 @@ BlazeComponent.extendComponent({
     const board = this.data().board();
     const board = this.data().board();
     let linkedId = '';
     let linkedId = '';
     let swimlaneId = '';
     let swimlaneId = '';
-    const boardView = (Meteor.user().profile || {}).boardView;
     let cardType = 'cardType-card';
     let cardType = 'cardType-card';
     if (title) {
     if (title) {
       if (board.isTemplatesBoard()) {
       if (board.isTemplatesBoard()) {
@@ -71,14 +70,14 @@ BlazeComponent.extendComponent({
           });
           });
           cardType = 'cardType-linkedBoard';
           cardType = 'cardType-linkedBoard';
         }
         }
-      } else if (boardView === 'board-view-swimlanes')
+      } else if (Utils.boardView() === 'board-view-swimlanes')
         swimlaneId = this.parentComponent()
         swimlaneId = this.parentComponent()
           .parentComponent()
           .parentComponent()
           .data()._id;
           .data()._id;
       else if (
       else if (
-        boardView === 'board-view-lists' ||
-        boardView === 'board-view-cal' ||
-        !boardView
+        Utils.boardView() === 'board-view-lists' ||
+        Utils.boardView() === 'board-view-cal' ||
+        !Utils.boardView
       )
       )
         swimlaneId = board.getDefaultSwimline()._id;
         swimlaneId = board.getDefaultSwimline()._id;
 
 
@@ -157,9 +156,8 @@ BlazeComponent.extendComponent({
   },
   },
 
 
   idOrNull(swimlaneId) {
   idOrNull(swimlaneId) {
-    const currentUser = Meteor.user();
     if (
     if (
-      (currentUser.profile || {}).boardView === 'board-view-swimlanes' ||
+      Utils.boardView() === 'board-view-swimlanes' ||
       this.data()
       this.data()
         .board()
         .board()
         .isTemplatesBoard()
         .isTemplatesBoard()
@@ -191,7 +189,8 @@ BlazeComponent.extendComponent({
       !this.reachedWipLimit() &&
       !this.reachedWipLimit() &&
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 
@@ -397,10 +396,9 @@ BlazeComponent.extendComponent({
       '.js-swimlane',
       '.js-swimlane',
     );
     );
     this.swimlaneId = '';
     this.swimlaneId = '';
-    const boardView = (Meteor.user().profile || {}).boardView;
-    if (boardView === 'board-view-swimlanes')
+    if (Utils.boardView() === 'board-view-swimlanes')
       this.swimlaneId = Blaze.getData(swimlane[0])._id;
       this.swimlaneId = Blaze.getData(swimlane[0])._id;
-    else if (boardView === 'board-view-lists' || !boardView)
+    else if (Utils.boardView() === 'board-view-lists' || !Utils.boardView)
       this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
       this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
   },
   },
 
 
@@ -580,7 +578,7 @@ BlazeComponent.extendComponent({
       const swimlane = $(Popup._getTopStack().openerElement).parents(
       const swimlane = $(Popup._getTopStack().openerElement).parents(
         '.js-swimlane',
         '.js-swimlane',
       );
       );
-      if ((Meteor.user().profile || {}).boardView === 'board-view-swimlanes')
+      if (Utils.boardView() === 'board-view-swimlanes')
         this.swimlaneId = Blaze.getData(swimlane[0])._id;
         this.swimlaneId = Blaze.getData(swimlane[0])._id;
       else this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
       else this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
       // List where to insert card
       // List where to insert card
@@ -709,22 +707,18 @@ BlazeComponent.extendComponent({
     if (isSandstorm) {
     if (isSandstorm) {
       const user = Meteor.user();
       const user = Meteor.user();
       if (user) {
       if (user) {
-        const boardView = (Meteor.user().profile || {}).boardView;
-        if (boardView === 'board-view-swimlanes') {
+        if (Utils.boardView() === 'board-view-swimlanes') {
           this.swimlaneId = this.parentComponent()
           this.swimlaneId = this.parentComponent()
             .parentComponent()
             .parentComponent()
             .parentComponent()
             .parentComponent()
             .data()._id;
             .data()._id;
         }
         }
       }
       }
-    } else {
-      const boardView = (Meteor.user().profile || {}).boardView;
-      if (boardView === 'board-view-swimlanes') {
-        this.swimlaneId = this.parentComponent()
-          .parentComponent()
-          .parentComponent()
-          .data()._id;
-      }
+    } else if (Utils.boardView() === 'board-view-swimlanes') {
+      this.swimlaneId = this.parentComponent()
+        .parentComponent()
+        .parentComponent()
+        .data()._id;
     }
     }
   },
   },
 
 

+ 40 - 17
client/components/lists/listHeader.jade

@@ -10,7 +10,7 @@ template(name="listHeader")
           a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
           a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
       h2.list-header-name(
       h2.list-header-name(
         title="{{ moment modifiedAt 'LLL' }}"
         title="{{ moment modifiedAt 'LLL' }}"
-        class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}js-open-inlined-form is-editable{{/unless}}{{/if}}")
+        class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
         +viewer
         +viewer
           = title
           = title
         if wipLimit.enabled
         if wipLimit.enabled
@@ -30,17 +30,17 @@ template(name="listHeader")
               if canSeeAddCard
               if canSeeAddCard
                 a.js-add-card.fa.fa-plus.list-header-plus-icon
                 a.js-add-card.fa.fa-plus.list-header-plus-icon
               a.fa.fa-navicon.js-open-list-menu
               a.fa.fa-navicon.js-open-list-menu
-          a.list-header-handle.handle.fa.fa-arrows.js-list-handle
+          //a.list-header-handle.handle.fa.fa-arrows.js-list-handle
         else
         else
           a.list-header-menu-icon.fa.fa-angle-right.js-select-list
           a.list-header-menu-icon.fa.fa-angle-right.js-select-list
-          a.list-header-handle.handle.fa.fa-arrows.js-list-handle
+          //a.list-header-handle.handle.fa.fa-arrows.js-list-handle
       else if currentUser.isBoardMember
       else if currentUser.isBoardMember
         if isWatching
         if isWatching
           i.list-header-watch-icon.fa.fa-eye
           i.list-header-watch-icon.fa.fa-eye
         div.list-header-menu
         div.list-header-menu
           unless currentUser.isCommentOnly
           unless currentUser.isCommentOnly
-            if isBoardAdmin
-              a.fa.js-list-star.list-header-plus-icon(class="fa-star{{#unless starred}}-o{{/unless}}")
+            //if isBoardAdmin
+            //  a.fa.js-list-star.list-header-plus-icon(class="fa-star{{#unless starred}}-o{{/unless}}")
             if canSeeAddCard
             if canSeeAddCard
               a.js-add-card.fa.fa-plus.list-header-plus-icon
               a.js-add-card.fa.fa-plus.list-header-plus-icon
             a.fa.fa-navicon.js-open-list-menu
             a.fa.fa-navicon.js-open-list-menu
@@ -56,25 +56,47 @@ template(name="editListTitleForm")
 
 
 template(name="listActionPopup")
 template(name="listActionPopup")
   ul.pop-over-list
   ul.pop-over-list
-    li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
+    li
+      a.js-toggle-watch-list
+        if isWatching
+          i.fa.fa-eye
+          |  {{_ 'unwatch'}}
+        else
+          i.fa.fa-eye-slash
+          |  {{_ 'watch'}}
   unless currentUser.isCommentOnly
   unless currentUser.isCommentOnly
-    hr
-    ul.pop-over-list
-      li: a.js-set-color-list {{_ 'set-color-list'}}
-    hr
+    unless currentUser.isWorker
+      ul.pop-over-list
+        li
+          a.js-set-color-list
+            i.fa.fa-paint-brush
+            | {{_ 'set-color-list'}}
     ul.pop-over-list
     ul.pop-over-list
       if cards.count
       if cards.count
-        li: a.js-select-cards {{_ 'list-select-cards'}}
-        hr
+        li
+          a.js-select-cards
+            i.fa.fa-check-square
+            | {{_ 'list-select-cards'}}
     if currentUser.isBoardAdmin
     if currentUser.isBoardAdmin
       ul.pop-over-list
       ul.pop-over-list
-        li: a.js-set-wip-limit {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
+        li
+          a.js-set-wip-limit
+            i.fa.fa-ban
+            | {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
+    unless currentUser.isWorker
       hr
       hr
-    ul.pop-over-list
-      li: a.js-close-list {{_ 'archive-list'}}
+      ul.pop-over-list
+        li
+          a.js-close-list
+            i.fa.fa-arrow-right
+            i.fa.fa-archive
+            | {{_ 'archive-list'}}
     hr
     hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a.js-more {{_ 'listMorePopup-title'}}
+      li
+        a.js-more
+          i.fa.fa-link
+          | {{_ 'listMorePopup-title'}}
 
 
 template(name="boardLists")
 template(name="boardLists")
   ul.pop-over-list
   ul.pop-over-list
@@ -94,7 +116,8 @@ template(name="listMorePopup")
       input.inline-input(type="text" readonly value="{{ rootUrl }}")
       input.inline-input(type="text" readonly value="{{ rootUrl }}")
     | {{_ 'added'}}
     | {{_ 'added'}}
     span.date(title=list.createdAt) {{ moment createdAt 'LLL' }}
     span.date(title=list.createdAt) {{ moment createdAt 'LLL' }}
-    a.js-delete {{_ 'delete'}}
+    unless currentUser.isWorker
+      a.js-delete {{_ 'delete'}}
 
 
 template(name="listDeletePopup")
 template(name="listDeletePopup")
   p {{_ "list-delete-pop"}}
   p {{_ "list-delete-pop"}}

+ 21 - 7
client/components/lists/listHeader.js

@@ -1,3 +1,5 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
 let listsColors;
 let listsColors;
 Meteor.startup(() => {
 Meteor.startup(() => {
   listsColors = Lists.simpleSchema()._schema.color.allowedValues;
   listsColors = Lists.simpleSchema()._schema.color.allowedValues;
@@ -7,9 +9,10 @@ BlazeComponent.extendComponent({
   canSeeAddCard() {
   canSeeAddCard() {
     const list = Template.currentData();
     const list = Template.currentData();
     return (
     return (
-      !list.getWipLimit('enabled') ||
-      list.getWipLimit('soft') ||
-      !this.reachedWipLimit()
+      (!list.getWipLimit('enabled') ||
+        list.getWipLimit('soft') ||
+        !this.reachedWipLimit()) &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 
@@ -44,14 +47,18 @@ BlazeComponent.extendComponent({
   },
   },
 
 
   limitToShowCardsCount() {
   limitToShowCardsCount() {
-    return Meteor.user().getLimitToShowCardsCount();
+    const currentUser = Meteor.user();
+    if (currentUser) {
+      return Meteor.user().getLimitToShowCardsCount();
+    } else {
+      return false;
+    }
   },
   },
 
 
   cardsCount() {
   cardsCount() {
     const list = Template.currentData();
     const list = Template.currentData();
     let swimlaneId = '';
     let swimlaneId = '';
-    const boardView = (Meteor.user().profile || {}).boardView;
-    if (boardView === 'board-view-swimlanes')
+    if (Utils.boardView() === 'board-view-swimlanes')
       swimlaneId = this.parentComponent()
       swimlaneId = this.parentComponent()
         .parentComponent()
         .parentComponent()
         .data()._id;
         .data()._id;
@@ -100,7 +107,14 @@ BlazeComponent.extendComponent({
 
 
 Template.listHeader.helpers({
 Template.listHeader.helpers({
   showDesktopDragHandles() {
   showDesktopDragHandles() {
-    return Meteor.user().hasShowDesktopDragHandles();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
 });
 });
 
 

+ 3 - 0
client/components/main/header.styl

@@ -218,6 +218,9 @@
       padding: 10px
       padding: 10px
       margin: -10px 0 -10px -10px
       margin: -10px 0 -10px -10px
 
 
+.announcement .viewer
+  display: inline-block
+
 .announcement,
 .announcement,
 .offline-warning
 .offline-warning
   width: 100%
   width: 100%

+ 25 - 12
client/components/rules/actions/boardActions.jade

@@ -1,29 +1,42 @@
 template(name="boardActions")
 template(name="boardActions")
   div.trigger-item
   div.trigger-item
     div.trigger-content
     div.trigger-content
-      div.trigger-text 
+      div.trigger-text
         | {{_'r-move-card-to'}}
         | {{_'r-move-card-to'}}
       div.trigger-dropdown
       div.trigger-dropdown
         select(id="move-gen-action")
         select(id="move-gen-action")
           option(value="top") {{_'r-top-of'}}
           option(value="top") {{_'r-top-of'}}
           option(value="bottom") {{_'r-bottom-of'}}
           option(value="bottom") {{_'r-bottom-of'}}
-      div.trigger-text 
+      div.trigger-text
         | {{_'r-its-list'}}
         | {{_'r-its-list'}}
     div.trigger-button.js-add-gen-move-action.js-goto-rules
     div.trigger-button.js-add-gen-move-action.js-goto-rules
       i.fa.fa-plus
       i.fa.fa-plus
 
 
   div.trigger-item
   div.trigger-item
     div.trigger-content
     div.trigger-content
-      div.trigger-text 
+      div.trigger-text
         | {{_'r-move-card-to'}}
         | {{_'r-move-card-to'}}
       div.trigger-dropdown
       div.trigger-dropdown
         select(id="move-spec-action")
         select(id="move-spec-action")
           option(value="top") {{_'r-top-of'}}
           option(value="top") {{_'r-top-of'}}
           option(value="bottom") {{_'r-bottom-of'}}
           option(value="bottom") {{_'r-bottom-of'}}
-      div.trigger-text 
-        | {{_'r-list'}}
+      div.trigger-text
+        | {{_'r-the-board'}}
+      div.trigger-dropdown
+        select(id="board-id")
+          each boards
+            if $eq _id currentBoard._id
+              option(value="{{_id}}" selected) {{_ 'current'}}
+            else
+              option(value="{{_id}}") {{title}}
+      div.trigger-text
+        | {{_'r-in-list'}}
       div.trigger-dropdown
       div.trigger-dropdown
         input(id="listName",type=text,placeholder="{{_'r-name'}}")
         input(id="listName",type=text,placeholder="{{_'r-name'}}")
+      div.trigger-text
+        | {{_'r-in-swimlane'}}
+      div.trigger-dropdown
+        input(id="swimlaneName",type=text,placeholder="{{_'r-name'}}")
     div.trigger-button.js-add-spec-move-action.js-goto-rules
     div.trigger-button.js-add-spec-move-action.js-goto-rules
       i.fa.fa-plus
       i.fa.fa-plus
 
 
@@ -33,14 +46,14 @@ template(name="boardActions")
         select(id="arch-action")
         select(id="arch-action")
           option(value="archive") {{_'r-archive'}}
           option(value="archive") {{_'r-archive'}}
           option(value="unarchive") {{_'r-unarchive'}}
           option(value="unarchive") {{_'r-unarchive'}}
-      div.trigger-text 
+      div.trigger-text
         | {{_'r-card'}}
         | {{_'r-card'}}
     div.trigger-button.js-add-arch-action.js-goto-rules
     div.trigger-button.js-add-arch-action.js-goto-rules
       i.fa.fa-plus
       i.fa.fa-plus
 
 
   div.trigger-item
   div.trigger-item
     div.trigger-content
     div.trigger-content
-      div.trigger-text 
+      div.trigger-text
         | {{_'r-add-swimlane'}}
         | {{_'r-add-swimlane'}}
       div.trigger-dropdown
       div.trigger-dropdown
         input(id="swimlane-name",type=text,placeholder="{{_'r-name'}}")
         input(id="swimlane-name",type=text,placeholder="{{_'r-name'}}")
@@ -49,15 +62,15 @@ template(name="boardActions")
 
 
   div.trigger-item
   div.trigger-item
     div.trigger-content
     div.trigger-content
-      div.trigger-text 
+      div.trigger-text
         | {{_'r-create-card'}}
         | {{_'r-create-card'}}
       div.trigger-dropdown
       div.trigger-dropdown
         input(id="card-name",type=text,placeholder="{{_'r-name'}}")
         input(id="card-name",type=text,placeholder="{{_'r-name'}}")
-      div.trigger-text 
+      div.trigger-text
         | {{_'r-in-list'}}
         | {{_'r-in-list'}}
       div.trigger-dropdown
       div.trigger-dropdown
         input(id="list-name",type=text,placeholder="{{_'r-name'}}")
         input(id="list-name",type=text,placeholder="{{_'r-name'}}")
-      div.trigger-text 
+      div.trigger-text
         | {{_'r-in-swimlane'}}
         | {{_'r-in-swimlane'}}
       div.trigger-dropdown
       div.trigger-dropdown
         input(id="swimlane-name2",type=text,placeholder="{{_'r-name'}}")
         input(id="swimlane-name2",type=text,placeholder="{{_'r-name'}}")
@@ -65,8 +78,8 @@ template(name="boardActions")
       i.fa.fa-plus
       i.fa.fa-plus
 
 
 
 
-   
-  
+
+
 
 
 
 
 
 

+ 25 - 5
client/components/rules/actions/boardActions.js

@@ -1,6 +1,22 @@
 BlazeComponent.extendComponent({
 BlazeComponent.extendComponent({
   onCreated() {},
   onCreated() {},
 
 
+  boards() {
+    const boards = Boards.find(
+      {
+        archived: false,
+        'members.userId': Meteor.userId(),
+        _id: {
+          $ne: Meteor.user().getTemplatesBoardId(),
+        },
+      },
+      {
+        sort: ['title'],
+      },
+    );
+    return boards;
+  },
+
   events() {
   events() {
     return [
     return [
       {
       {
@@ -52,15 +68,18 @@ BlazeComponent.extendComponent({
           const ruleName = this.data().ruleName.get();
           const ruleName = this.data().ruleName.get();
           const trigger = this.data().triggerVar.get();
           const trigger = this.data().triggerVar.get();
           const actionSelected = this.find('#move-spec-action').value;
           const actionSelected = this.find('#move-spec-action').value;
-          const listTitle = this.find('#listName').value;
+          const swimlaneName = this.find('#swimlaneName').value;
+          const listName = this.find('#listName').value;
           const boardId = Session.get('currentBoard');
           const boardId = Session.get('currentBoard');
+          const destBoardId = this.find('#board-id').value;
           const desc = Utils.getTriggerActionDesc(event, this);
           const desc = Utils.getTriggerActionDesc(event, this);
           if (actionSelected === 'top') {
           if (actionSelected === 'top') {
             const triggerId = Triggers.insert(trigger);
             const triggerId = Triggers.insert(trigger);
             const actionId = Actions.insert({
             const actionId = Actions.insert({
               actionType: 'moveCardToTop',
               actionType: 'moveCardToTop',
-              listTitle,
-              boardId,
+              listName,
+              swimlaneName,
+              boardId: destBoardId,
               desc,
               desc,
             });
             });
             Rules.insert({
             Rules.insert({
@@ -74,8 +93,9 @@ BlazeComponent.extendComponent({
             const triggerId = Triggers.insert(trigger);
             const triggerId = Triggers.insert(trigger);
             const actionId = Actions.insert({
             const actionId = Actions.insert({
               actionType: 'moveCardToBottom',
               actionType: 'moveCardToBottom',
-              listTitle,
-              boardId,
+              listName,
+              swimlaneName,
+              boardId: destBoardId,
               desc,
               desc,
             });
             });
             Rules.insert({
             Rules.insert({

+ 6 - 2
client/components/settings/informationBody.jade

@@ -4,12 +4,16 @@ template(name='information')
       | {{_ 'error-notAuthorized'}}
       | {{_ 'error-notAuthorized'}}
     else
     else
       .content-title
       .content-title
-        span {{_ 'info'}}
+        span
+          i.fa.fa-info-circle
+          | {{_ 'info'}}
       .content-body
       .content-body
         .side-menu
         .side-menu
           ul
           ul
             li.active
             li.active
-              a.js-setting-menu(data-id="information-display") {{_ 'info'}}
+              a.js-setting-menu(data-id="information-display")
+                i.fa.fa-info-circle
+                | {{_ 'info'}}
         .main-body
         .main-body
           +statistics
           +statistics
 
 

+ 54 - 18
client/components/settings/peopleBody.jade

@@ -5,16 +5,22 @@ template(name="people")
     else
     else
       .content-title.ext-box
       .content-title.ext-box
         .ext-box-left
         .ext-box-left
-          span {{_ 'people'}}
+          span
+            i.fa.fa-users
+            | {{_ 'people'}}
           input#searchInput(placeholder="{{_ 'search'}}")
           input#searchInput(placeholder="{{_ 'search'}}")
-          button#searchButton {{_ 'search'}}
+          button#searchButton
+            i.fa.fa-search
+            | {{_ 'search'}}
         .ext-box-right
         .ext-box-right
           span {{_ 'people-number'}} #{peopleNumber}
           span {{_ 'people-number'}} #{peopleNumber}
       .content-body
       .content-body
         .side-menu
         .side-menu
           ul
           ul
             li.active
             li.active
-              a.js-setting-menu(data-id="people-setting") {{_ 'people'}}
+              a.js-setting-menu(data-id="people-setting")
+                i.fa.fa-users
+                | {{_ 'people'}}
         .main-body
         .main-body
           if loading.get
           if loading.get
             +spinner
             +spinner
@@ -39,28 +45,58 @@ template(name="peopleGeneral")
 
 
 template(name="peopleRow")
 template(name="peopleRow")
   tr
   tr
-    td.username {{ userData.username }}
-    td {{ userData.profile.fullname }}
-    td
-      if userData.isAdmin
-        | {{_ 'yes'}}
-      else
-        | {{_ 'no'}}
-    td {{ userData.emails.[0].address }}
-    td
-      if userData.emails.[0].verified
-        | {{_ 'yes'}}
-      else
-        | {{_ 'no'}}
-    td {{ moment userData.createdAt 'LLL' }}
+    if userData.loginDisabled
+      td.username <s>{{ userData.username }}</s>
+    else
+      td.username {{ userData.username }}
+    if userData.loginDisabled
+      td <s>{{ userData.profile.fullname }}</s>
+    else
+      td {{ userData.profile.fullname }}
+    if userData.loginDisabled
+      td
+        if userData.isAdmin
+          | <s>{{_ 'yes'}}</s>
+        else
+          | <s>{{_ 'no'}}</s>
+    else
+      td
+        if userData.isAdmin
+          | {{_ 'yes'}}
+        else
+          | {{_ 'no'}}
+    if userData.loginDisabled
+      td <s>{{ userData.emails.[0].address }}</s>
+    else
+      td {{ userData.emails.[0].address }}
+    if userData.loginDisabled
+      td
+        if userData.emails.[0].verified
+          | <s>{{_ 'yes'}}</s>
+        else
+          | <s>{{_ 'no'}}</s>
+    else
+      td
+        if userData.emails.[0].verified
+          | {{_ 'yes'}}
+        else
+          | {{_ 'no'}}
+    if userData.loginDisabled
+      td <s>{{ moment userData.createdAt 'LLL' }}</s>
+    else
+      td {{ moment userData.createdAt 'LLL' }}
     td
     td
       if userData.loginDisabled
       if userData.loginDisabled
         | {{_ 'no'}}
         | {{_ 'no'}}
       else
       else
         | {{_ 'yes'}}
         | {{_ 'yes'}}
-    td {{_ userData.authenticationMethod }}
+    if userData.loginDisabled
+      td <s>{{_ userData.authenticationMethod }}</s>
+    else
+      td {{_ userData.authenticationMethod }}
     td
     td
       a.edit-user
       a.edit-user
+        i.fa.fa-edit
         | {{_ 'edit'}}
         | {{_ 'edit'}}
 
 
 template(name="editUserPopup")
 template(name="editUserPopup")

+ 1 - 1
client/components/settings/peopleBody.styl

@@ -33,7 +33,7 @@ table
     padding: 0;
     padding: 0;
 
 
   button
   button
-    min-width: 60px;
+    min-width: 90px;
 
 
 .content-wrapper
 .content-wrapper
   margin-top: 10px
   margin-top: 10px

+ 19 - 12
client/components/settings/settingBody.jade

@@ -4,22 +4,35 @@ template(name="setting")
       | {{_ 'error-notAuthorized'}}
       | {{_ 'error-notAuthorized'}}
     else
     else
       .content-title
       .content-title
+        i.fa.fa-cog
         span {{_ 'settings'}}
         span {{_ 'settings'}}
       .content-body
       .content-body
         .side-menu
         .side-menu
           ul
           ul
             li.active
             li.active
-              a.js-setting-menu(data-id="registration-setting") {{_ 'registration'}}
+              a.js-setting-menu(data-id="registration-setting")
+                i.fa.fa-sign-in
+                | {{_ 'registration'}}
             li
             li
-              a.js-setting-menu(data-id="email-setting") {{_ 'email'}}
+              a.js-setting-menu(data-id="email-setting")
+                i.fa.fa-envelope
+                | {{_ 'email'}}
             li
             li
-              a.js-setting-menu(data-id="account-setting") {{_ 'accounts'}}
+              a.js-setting-menu(data-id="account-setting")
+                i.fa.fa-users
+                | {{_ 'accounts'}}
             li
             li
-              a.js-setting-menu(data-id="announcement-setting") {{_ 'admin-announcement'}}
+              a.js-setting-menu(data-id="announcement-setting")
+                i.fa.fa-bullhorn
+                | {{_ 'admin-announcement'}}
             li
             li
-              a.js-setting-menu(data-id="layout-setting") {{_ 'layout'}}
+              a.js-setting-menu(data-id="layout-setting")
+                i.fa.fa-object-group
+                | {{_ 'layout'}}
             li
             li
-              a.js-setting-menu(data-id="webhook-setting") {{_ 'global-webhook'}}
+              a.js-setting-menu(data-id="webhook-setting")
+                i.fa.fa-globe
+                | {{_ 'global-webhook'}}
         .main-body
         .main-body
           if loading.get
           if loading.get
             +spinner
             +spinner
@@ -171,12 +184,6 @@ template(name='layoutSettings')
       .title {{_ 'custom-product-name'}}
       .title {{_ 'custom-product-name'}}
       .form-group
       .form-group
         input.wekan-form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}")
         input.wekan-form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}")
-    li.layout-form
-      .title {{_ 'add-custom-html-after-body-start'}}
-      textarea#customHTMLafterBodyStart.wekan-form-control= currentSetting.customHTMLafterBodyStart
-    li.layout-form
-      .title {{_ 'add-custom-html-before-body-end'}}
-      textarea#customHTMLbeforeBodyEnd.wekan-form-control= currentSetting.customHTMLbeforeBodyEnd
     li
     li
       button.js-save-layout.primary {{_ 'save'}}
       button.js-save-layout.primary {{_ 'save'}}
 
 

+ 0 - 8
client/components/settings/settingBody.js

@@ -171,20 +171,12 @@ BlazeComponent.extendComponent({
     const displayAuthenticationMethod =
     const displayAuthenticationMethod =
       $('input[name=displayAuthenticationMethod]:checked').val() === 'true';
       $('input[name=displayAuthenticationMethod]:checked').val() === 'true';
     const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
     const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
-    const customHTMLafterBodyStart = $('#customHTMLafterBodyStart')
-      .val()
-      .trim();
-    const customHTMLbeforeBodyEnd = $('#customHTMLbeforeBodyEnd')
-      .val()
-      .trim();
 
 
     try {
     try {
       Settings.update(Settings.findOne()._id, {
       Settings.update(Settings.findOne()._id, {
         $set: {
         $set: {
           productName,
           productName,
           hideLogo: hideLogoChange,
           hideLogo: hideLogoChange,
-          customHTMLafterBodyStart,
-          customHTMLbeforeBodyEnd,
           displayAuthenticationMethod,
           displayAuthenticationMethod,
           defaultAuthenticationMethod,
           defaultAuthenticationMethod,
         },
         },

+ 4 - 1
client/components/settings/settingBody.styl

@@ -41,15 +41,18 @@
           &:hover
           &:hover
             background #fff
             background #fff
             box-shadow 0 1px 2px rgba(0,0,0,0.15);
             box-shadow 0 1px 2px rgba(0,0,0,0.15);
+
           a
           a
             @extends .flex
             @extends .flex
             padding: 1rem 0 1rem 1rem
             padding: 1rem 0 1rem 1rem
             width: 100% - 5rem
             width: 100% - 5rem
 
 
-
             span
             span
               font-size: 13px
               font-size: 13px
 
 
+            i
+              margin-right: 20px
+
     .main-body
     .main-body
       padding: 0.1em 1em
       padding: 0.1em 1em
       -webkit-user-select: text // Safari 3.1+
       -webkit-user-select: text // Safari 3.1+

+ 188 - 26
client/components/sidebar/sidebar.jade

@@ -37,11 +37,12 @@ template(name='homeSidebar')
 template(name="membersWidget")
 template(name="membersWidget")
   .board-widget.board-widget-members
   .board-widget.board-widget-members
     h3
     h3
-      i.fa.fa-user
+      i.fa.fa-users
       | {{_ 'members'}}
       | {{_ 'members'}}
       unless currentUser.isCommentOnly
       unless currentUser.isCommentOnly
-        a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
-          i.board-header-btn-icon.fa.fa-cog
+        unless currentUser.isWorker
+          a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
+            i.board-header-btn-icon.fa.fa-cog
 
 
     .board-widget-content
     .board-widget-content
       each currentBoard.activeMembers
       each currentBoard.activeMembers
@@ -71,6 +72,108 @@ template(name="boardChangeColorPopup")
           if isSelected
           if isSelected
             i.fa.fa-check
             i.fa.fa-check
 
 
+template(name="boardCardSettingsPopup")
+  form.board-card-settings
+    h3 {{_ 'show-on-card'}}
+    div.check-div
+      a.flex.js-field-has-receiveddate(class="{{#if allowsReceivedDate}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsReceivedDate}}is-checked{{/if}}")
+        span
+          i.fa.fa-sign-out
+          | {{_ 'card-received'}}
+    div.check-div
+      a.flex.js-field-has-startdate(class="{{#if allowsStartDate}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsStartDate}}is-checked{{/if}}")
+        span
+          i.fa.fa-hourglass-start
+          | {{_ 'card-start'}}
+    div.check-div
+      a.flex.js-field-has-duedate(class="{{#if allowsDueDate}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsDueDate}}is-checked{{/if}}")
+        span
+          i.fa.fa-sign-in
+          | {{_ 'card-due'}}
+    div.check-div
+      a.flex.js-field-has-enddate(class="{{#if allowsEndDate}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsEndDate}}is-checked{{/if}}")
+        span
+          i.fa.fa-hourglass-end
+          | {{_ 'card-end'}}
+    div.check-div
+      a.flex.js-field-has-members(class="{{#if allowsMembers}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsMembers}}is-checked{{/if}}")
+        span
+          i.fa.fa-users
+          | {{_ 'members'}}
+    div.check-div
+      a.flex.js-field-has-assignee(class="{{#if allowsAssignee}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsAssignee}}is-checked{{/if}}")
+        span
+          i.fa.fa-user
+          | {{_ 'assignee'}}
+    div.check-div
+      a.flex.js-field-has-assigned-by(class="{{#if allowsAssignedBy}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsAssignedBy}}is-checked{{/if}}")
+        span
+          i.fa.fa-shopping-cart
+          | {{_ 'assigned-by'}}
+    div.check-div
+      a.flex.js-field-has-requested-by(class="{{#if allowsRequestedBy}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsRequestedBy}}is-checked{{/if}}")
+        span
+          i.fa.fa-user-plus
+          | {{_ 'requested-by'}}
+    div.check-div
+      a.flex.js-field-has-labels(class="{{#if allowsLabels}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsLabels}}is-checked{{/if}}")
+        span
+          i.fa.fa-tags
+          | {{_ 'labels'}}
+    div.check-div
+      a.flex.js-field-has-description-title(class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
+        span
+          i.fa.fa-align-left
+          | {{_ 'description'}}
+          | {{_ 'title'}}
+    div.check-div
+      a.flex.js-field-has-description-text(class="{{#if allowsDescriptionText}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsDescriptionText}}is-checked{{/if}}")
+        span
+          i.fa.fa-align-left
+          | {{_ 'description'}}
+          | {{_ 'custom-field-text'}}
+    div.check-div
+      a.flex.js-field-has-checklists(class="{{#if allowsChecklists}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsChecklists}}is-checked{{/if}}")
+        span
+          i.fa.fa-check
+          | {{_ 'checklists'}}
+    div.check-div
+      a.flex.js-field-has-subtasks(class="{{#if allowsSubtasks}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsSubtasks}}is-checked{{/if}}")
+        span
+          i.fa.fa-sitemap
+          | {{_ 'subtasks'}}
+    div.check-div
+      a.flex.js-field-has-attachments(class="{{#if allowsAttachments}}is-checked{{/if}}")
+        .materialCheckBox(class="{{#if allowsAttachments}}is-checked{{/if}}")
+        span
+          i.fa.fa-paperclip
+          | {{_ 'attachments'}}
+    //div.check-div
+    //  a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}")
+    //    .materialCheckBox(class="{{#if allowsComments}}is-checked{{/if}}")
+    //    span
+    //      i.fa.fa-comment-o
+    //      | {{_ 'comment'}}
+    //div.check-div
+    //  a.flex.js-field-has-activities(class="{{#if allowsActivities}}is-checked{{/if}}")
+    //    .materialCheckBox(class="{{#if allowsActivities}}is-checked{{/if}}")
+    //    span
+    //      i.fa.fa-history
+    //      | {{_ 'activities'}}
+
 template(name="boardSubtaskSettingsPopup")
 template(name="boardSubtaskSettingsPopup")
   form.board-subtask-settings
   form.board-subtask-settings
     h3 {{_ 'show-parent-in-minicard'}}
     h3 {{_ 'show-parent-in-minicard'}}
@@ -130,7 +233,9 @@ template(name="chooseBoardSource")
 
 
 template(name="archiveBoardPopup")
 template(name="archiveBoardPopup")
   p {{_ 'close-board-pop'}}
   p {{_ 'close-board-pop'}}
-  button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
+  button.js-confirm.negate.full(type="submit")
+    i.fa.fa-archive
+    | {{_ 'archive'}}
 
 
 template(name="outgoingWebhooksPopup")
 template(name="outgoingWebhooksPopup")
   each integrations
   each integrations
@@ -163,37 +268,88 @@ template(name="outgoingWebhooksPopup")
 template(name="boardMenuPopup")
 template(name="boardMenuPopup")
   ul.pop-over-list
   ul.pop-over-list
     li: a.js-custom-fields {{_ 'custom-fields'}}
     li: a.js-custom-fields {{_ 'custom-fields'}}
-    li: a.js-open-archives {{_ 'archived-items'}}
+    li
+      a.js-open-archives
+        i.fa.fa-archive
+        | {{_ 'archived-items'}}
     if currentUser.isBoardAdmin
     if currentUser.isBoardAdmin
-      li: a.js-change-board-color {{_ 'board-change-color'}}
+      li
+        a.js-change-board-color
+          i.fa.fa-paint-brush
+          | {{_ 'board-change-color'}}
+
     //-
     //-
       XXX Language should be handled by sandstorm, but for now display a
       XXX Language should be handled by sandstorm, but for now display a
       language selection link in the board menu. This link is normally present
       language selection link in the board menu. This link is normally present
       in the header bar that is not displayed on sandstorm.
       in the header bar that is not displayed on sandstorm.
     if isSandstorm
     if isSandstorm
-      li: a.js-change-language {{_ 'language'}}
+      li
+        a.js-change-language
+          i.fa.fa-flag
+          | {{_ 'language'}}
   unless isSandstorm
   unless isSandstorm
     if currentUser.isBoardAdmin
     if currentUser.isBoardAdmin
       hr
       hr
       ul.pop-over-list
       ul.pop-over-list
-        li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
-        unless currentBoard.isTemplatesBoard
-          li: a.js-archive-board {{_ 'archive-board'}}
-        li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
-      hr
-      ul.pop-over-list
-        li: a.js-subtask-settings {{_ 'subtask-settings'}}
+        li
+          a(href="{{exportUrl}}", download="{{exportFilename}}")
+            i.fa.fa-share-alt
+            | {{_ 'export-board'}}
+        li
+          a.js-outgoing-webhooks
+            i.fa.fa-globe
+            | {{_ 'outgoing-webhooks'}}
+        li
+          a.js-card-settings
+            i.fa.fa-id-card-o
+            | {{_ 'card-settings'}}
+        li
+          a.js-subtask-settings
+            i.fa.fa-sitemap
+            | {{_ 'subtask-settings'}}
+      unless currentBoard.isTemplatesBoard
+        hr
+        ul.pop-over-list
+          li
+            a.js-archive-board
+              i.fa.fa-arrow-right
+              i.fa.fa-archive
+              | {{_ 'archive-board'}}
 
 
   if isSandstorm
   if isSandstorm
     hr
     hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
-      li: a.js-import-board {{_ 'import-board-c'}}
-      li: a.js-archive-board {{_ 'archive-board'}}
-      li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
+      li
+        a(href="{{exportUrl}}", download="{{exportFilename}}")
+          i.fa.fa-share-alt
+          i.fa.fa-sign-out
+          | {{_ 'export-board'}}
+      li
+        a.js-import-board
+          i.fa.fa-share-alt
+          i.fa.fa-sign-in
+          | {{_ 'import-board-c'}}
+      li
+        a.js-archive-board
+          i.fa.fa-arrow-right
+          i.fa.fa-archive
+          | {{_ 'archive-board'}}
+      li
+        a.js-outgoing-webhooks
+          i.fa.fa-globe
+          | {{_ 'outgoing-webhooks'}}
+    hr
+    ul.pop-over-list
+      li
+        a.js-card-settings
+          i.fa.fa-id-card-o
+          | {{_ 'card-settings'}}
     hr
     hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a.js-subtask-settings {{_ 'subtask-settings'}}
+      li
+        a.js-subtask-settings
+          i.fa.fa-sitemap
+          | {{_ 'subtask-settings'}}
 
 
 template(name="labelsWidget")
 template(name="labelsWidget")
   .board-widget.board-widget-labels
   .board-widget.board-widget-labels
@@ -203,7 +359,7 @@ template(name="labelsWidget")
     .board-widget-content
     .board-widget-content
       each currentBoard.labels
       each currentBoard.labels
           a.card-label(class="card-label-{{color}}"
           a.card-label(class="card-label-{{color}}"
-            class="{{#if currentUser.isNotCommentOnly}}js-label{{/if}}")
+            class="{{#if currentUser.isNotCommentOnly}}{{#if currentUser.isNotWorker}}js-label{{/if}}{{/if}}")
             span.card-label-name
             span.card-label-name
               +viewer
               +viewer
                 = name
                 = name
@@ -232,12 +388,12 @@ template(name="memberPopup")
           a.js-change-role
           a.js-change-role
             | {{_ 'change-permissions'}}
             | {{_ 'change-permissions'}}
             span.quiet (#{memberType})
             span.quiet (#{memberType})
-      li
-        if $eq currentUser._id userId
-          a.js-leave-member {{_ 'leave-board'}}
-        else if currentUser.isBoardAdmin
-          a.js-remove-member {{_ 'remove-from-board'}}
-
+      unless currentUser.isWorker
+        li
+          if $eq currentUser._id userId
+            a.js-leave-member {{_ 'leave-board'}}
+          else if currentUser.isBoardAdmin
+            a.js-remove-member {{_ 'remove-from-board'}}
 
 
 template(name="removeMemberPopup")
 template(name="removeMemberPopup")
   p {{_ 'remove-member-pop' name=user.profile.fullname username=user.username boardTitle=board.title}}
   p {{_ 'remove-member-pop' name=user.profile.fullname username=user.username boardTitle=board.title}}
@@ -301,6 +457,12 @@ template(name="changePermissionsPopup")
         if isCommentOnly
         if isCommentOnly
           i.fa.fa-check
           i.fa.fa-check
         span.sub-name {{_ 'comment-only-desc'}}
         span.sub-name {{_ 'comment-only-desc'}}
+    li
+      a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
+        | {{_ 'worker'}}
+        if isWorker
+          i.fa.fa-check
+        span.sub-name {{_ 'worker-desc'}}
   if isLastAdmin
   if isLastAdmin
     hr
     hr
     p.quiet.bottom {{_ 'last-admin-desc'}}
     p.quiet.bottom {{_ 'last-admin-desc'}}

+ 399 - 4
client/components/sidebar/sidebar.js

@@ -1,3 +1,5 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
 Sidebar = null;
 Sidebar = null;
 
 
 const defaultView = 'home';
 const defaultView = 'home';
@@ -107,7 +109,14 @@ BlazeComponent.extendComponent({
         'click .js-toggle-sidebar': this.toggle,
         'click .js-toggle-sidebar': this.toggle,
         'click .js-back-home': this.setView,
         'click .js-back-home': this.setView,
         'click .js-toggle-minicard-label-text'() {
         'click .js-toggle-minicard-label-text'() {
-          Meteor.call('toggleMinicardLabelText');
+          currentUser = Meteor.user();
+          if (currentUser) {
+            Meteor.call('toggleMinicardLabelText');
+          } else if (cookies.has('hiddenMinicardLabelText')) {
+            cookies.remove('hiddenMinicardLabelText');
+          } else {
+            cookies.set('hiddenMinicardLabelText', 'true');
+          }
         },
         },
         'click .js-shortcuts'() {
         'click .js-shortcuts'() {
           FlowRouter.go('shortcuts');
           FlowRouter.go('shortcuts');
@@ -121,7 +130,14 @@ Blaze.registerHelper('Sidebar', () => Sidebar);
 
 
 Template.homeSidebar.helpers({
 Template.homeSidebar.helpers({
   hiddenMinicardLabelText() {
   hiddenMinicardLabelText() {
-    return Meteor.user().hasHiddenMinicardLabelText();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).hiddenMinicardLabelText;
+    } else if (cookies.has('hiddenMinicardLabelText')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
 });
 });
 
 
@@ -145,10 +161,13 @@ Template.memberPopup.helpers({
       const currentBoard = Boards.findOne(Session.get('currentBoard'));
       const currentBoard = Boards.findOne(Session.get('currentBoard'));
       const commentOnly = currentBoard.hasCommentOnly(this.userId);
       const commentOnly = currentBoard.hasCommentOnly(this.userId);
       const noComments = currentBoard.hasNoComments(this.userId);
       const noComments = currentBoard.hasNoComments(this.userId);
+      const worker = currentBoard.hasWorker(this.userId);
       if (commentOnly) {
       if (commentOnly) {
         return TAPi18n.__('comment-only').toLowerCase();
         return TAPi18n.__('comment-only').toLowerCase();
       } else if (noComments) {
       } else if (noComments) {
         return TAPi18n.__('no-comments').toLowerCase();
         return TAPi18n.__('no-comments').toLowerCase();
+      } else if (worker) {
+        return TAPi18n.__('worker').toLowerCase();
       } else {
       } else {
         return TAPi18n.__(type).toLowerCase();
         return TAPi18n.__(type).toLowerCase();
       }
       }
@@ -189,6 +208,7 @@ Template.boardMenuPopup.events({
   'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
   'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
   'click .js-import-board': Popup.open('chooseBoardSource'),
   'click .js-import-board': Popup.open('chooseBoardSource'),
   'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
   'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
+  'click .js-card-settings': Popup.open('boardCardSettings'),
 });
 });
 
 
 Template.boardMenuPopup.helpers({
 Template.boardMenuPopup.helpers({
@@ -251,6 +271,14 @@ Template.membersWidget.helpers({
     const user = Meteor.user();
     const user = Meteor.user();
     return user && user.isInvitedTo(Session.get('currentBoard'));
     return user && user.isInvitedTo(Session.get('currentBoard'));
   },
   },
+  isWorker() {
+    const user = Meteor.user();
+    if (user) {
+      return Meteor.call(Boards.hasWorker(user.memberId));
+    } else {
+      return false;
+    }
+  },
 });
 });
 
 
 Template.membersWidget.events({
 Template.membersWidget.events({
@@ -445,6 +473,10 @@ BlazeComponent.extendComponent({
     return this.currentBoard.allowsSubtasks;
     return this.currentBoard.allowsSubtasks;
   },
   },
 
 
+  allowsReceivedDate() {
+    return this.currentBoard.allowsReceivedDate;
+  },
+
   isBoardSelected() {
   isBoardSelected() {
     return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
     return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
   },
   },
@@ -558,6 +590,359 @@ BlazeComponent.extendComponent({
   },
   },
 }).register('boardSubtaskSettingsPopup');
 }).register('boardSubtaskSettingsPopup');
 
 
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.currentBoard = Boards.findOne(Session.get('currentBoard'));
+  },
+
+  allowsReceivedDate() {
+    return this.currentBoard.allowsReceivedDate;
+  },
+
+  allowsStartDate() {
+    return this.currentBoard.allowsStartDate;
+  },
+
+  allowsDueDate() {
+    return this.currentBoard.allowsDueDate;
+  },
+
+  allowsEndDate() {
+    return this.currentBoard.allowsEndDate;
+  },
+
+  allowsSubtasks() {
+    return this.currentBoard.allowsSubtasks;
+  },
+
+  allowsMembers() {
+    return this.currentBoard.allowsMembers;
+  },
+
+  allowsAssignee() {
+    return this.currentBoard.allowsAssignee;
+  },
+
+  allowsAssignedBy() {
+    return this.currentBoard.allowsAssignedBy;
+  },
+
+  allowsRequestedBy() {
+    return this.currentBoard.allowsRequestedBy;
+  },
+
+  allowsLabels() {
+    return this.currentBoard.allowsLabels;
+  },
+
+  allowsChecklists() {
+    return this.currentBoard.allowsChecklists;
+  },
+
+  allowsAttachments() {
+    return this.currentBoard.allowsAttachments;
+  },
+
+  allowsComments() {
+    return this.currentBoard.allowsComments;
+  },
+
+  allowsDescriptionTitle() {
+    return this.currentBoard.allowsDescriptionTitle;
+  },
+
+  allowsDescriptionText() {
+    return this.currentBoard.allowsDescriptionText;
+  },
+
+  isBoardSelected() {
+    return this.currentBoard.dateSettingsDefaultBoardID;
+  },
+
+  isNullBoardSelected() {
+    return (
+      this.currentBoard.dateSettingsDefaultBoardId === null ||
+      this.currentBoard.dateSettingsDefaultBoardId === undefined
+    );
+  },
+
+  boards() {
+    return Boards.find(
+      {
+        archived: false,
+        'members.userId': Meteor.userId(),
+      },
+      {
+        sort: ['title'],
+      },
+    );
+  },
+
+  lists() {
+    return Lists.find(
+      {
+        boardId: this.currentBoard._id,
+        archived: false,
+      },
+      {
+        sort: ['title'],
+      },
+    );
+  },
+
+  hasLists() {
+    return this.lists().count() > 0;
+  },
+
+  isListSelected() {
+    return (
+      this.currentBoard.dateSettingsDefaultBoardId === this.currentData()._id
+    );
+  },
+
+  events() {
+    return [
+      {
+        'click .js-field-has-receiveddate'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsReceivedDate = !this.currentBoard
+            .allowsReceivedDate;
+          this.currentBoard.setAllowsReceivedDate(
+            this.currentBoard.allowsReceivedDate,
+          );
+          $(`.js-field-has-receiveddate ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsReceivedDate,
+          );
+          $('.js-field-has-receiveddate').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsReceivedDate,
+          );
+        },
+        'click .js-field-has-startdate'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsStartDate = !this.currentBoard
+            .allowsStartDate;
+          this.currentBoard.setAllowsStartDate(
+            this.currentBoard.allowsStartDate,
+          );
+          $(`.js-field-has-startdate ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsStartDate,
+          );
+          $('.js-field-has-startdate').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsStartDate,
+          );
+        },
+        'click .js-field-has-enddate'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsEndDate = !this.currentBoard.allowsEndDate;
+          this.currentBoard.setAllowsEndDate(this.currentBoard.allowsEndDate);
+          $(`.js-field-has-enddate ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsEndDate,
+          );
+          $('.js-field-has-enddate').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsEndDate,
+          );
+        },
+        'click .js-field-has-duedate'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsDueDate = !this.currentBoard.allowsDueDate;
+          this.currentBoard.setAllowsDueDate(this.currentBoard.allowsDueDate);
+          $(`.js-field-has-duedate ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsDueDate,
+          );
+          $('.js-field-has-duedate').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsDueDate,
+          );
+        },
+        'click .js-field-has-subtasks'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
+          this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
+          $(`.js-field-has-subtasks ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsSubtasks,
+          );
+          $('.js-field-has-subtasks').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsSubtasks,
+          );
+        },
+        'click .js-field-has-members'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsMembers = !this.currentBoard.allowsMembers;
+          this.currentBoard.setAllowsMembers(this.currentBoard.allowsMembers);
+          $(`.js-field-has-members ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsMembers,
+          );
+          $('.js-field-has-members').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsMembers,
+          );
+        },
+        'click .js-field-has-assignee'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsAssignee = !this.currentBoard.allowsAssignee;
+          this.currentBoard.setAllowsAssignee(this.currentBoard.allowsAssignee);
+          $(`.js-field-has-assignee ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsAssignee,
+          );
+          $('.js-field-has-assignee').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsAssignee,
+          );
+        },
+        'click .js-field-has-assigned-by'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsAssignedBy = !this.currentBoard
+            .allowsAssignedBy;
+          this.currentBoard.setAllowsAssignedBy(
+            this.currentBoard.allowsAssignedBy,
+          );
+          $(`.js-field-has-assigned-by ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsAssignedBy,
+          );
+          $('.js-field-has-assigned-by').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsAssignedBy,
+          );
+        },
+        'click .js-field-has-requested-by'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsRequestedBy = !this.currentBoard
+            .allowsRequestedBy;
+          this.currentBoard.setAllowsRequestedBy(
+            this.currentBoard.allowsRequestedBy,
+          );
+          $(`.js-field-has-requested-by ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsRequestedBy,
+          );
+          $('.js-field-has-requested-by').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsRequestedBy,
+          );
+        },
+        'click .js-field-has-labels'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsLabels = !this.currentBoard.allowsLabels;
+          this.currentBoard.setAllowsLabels(this.currentBoard.allowsLabels);
+          $(`.js-field-has-labels ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsAssignee,
+          );
+          $('.js-field-has-labels').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsLabels,
+          );
+        },
+        'click .js-field-has-description-title'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsDescriptionTitle = !this.currentBoard
+            .allowsDescriptionTitle;
+          this.currentBoard.setAllowsDescriptionTitle(
+            this.currentBoard.allowsDescriptionTitle,
+          );
+          $(`.js-field-has-description-title ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsDescriptionTitle,
+          );
+          $('.js-field-has-description-title').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsDescriptionTitle,
+          );
+        },
+        'click .js-field-has-description-text'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsDescriptionText = !this.currentBoard
+            .allowsDescriptionText;
+          this.currentBoard.setAllowsDescriptionText(
+            this.currentBoard.allowsDescriptionText,
+          );
+          $(`.js-field-has-description-text ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsDescriptionText,
+          );
+          $('.js-field-has-description-text').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsDescriptionText,
+          );
+        },
+        'click .js-field-has-checklists'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsChecklists = !this.currentBoard
+            .allowsChecklists;
+          this.currentBoard.setAllowsChecklists(
+            this.currentBoard.allowsChecklists,
+          );
+          $(`.js-field-has-checklists ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsChecklists,
+          );
+          $('.js-field-has-checklists').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsChecklists,
+          );
+        },
+        'click .js-field-has-attachments'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsAttachments = !this.currentBoard
+            .allowsAttachments;
+          this.currentBoard.setAllowsAttachments(
+            this.currentBoard.allowsAttachments,
+          );
+          $(`.js-field-has-attachments ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsAttachments,
+          );
+          $('.js-field-has-attachments').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsAttachments,
+          );
+        },
+        'click .js-field-has-comments'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsComments = !this.currentBoard.allowsComments;
+          this.currentBoard.setAllowsComments(this.currentBoard.allowsComments);
+          $(`.js-field-has-comments ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsComments,
+          );
+          $('.js-field-has-comments').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsComments,
+          );
+        },
+        'click .js-field-has-activities'(evt) {
+          evt.preventDefault();
+          this.currentBoard.allowsActivities = !this.currentBoard
+            .allowsActivities;
+          this.currentBoard.setAllowsActivities(
+            this.currentBoard.allowsActivities,
+          );
+          $(`.js-field-has-activities ${MCB}`).toggleClass(
+            CKCLS,
+            this.currentBoard.allowsActivities,
+          );
+          $('.js-field-has-activities').toggleClass(
+            CKCLS,
+            this.currentBoard.allowsActivities,
+          );
+        },
+      },
+    ];
+  },
+}).register('boardCardSettingsPopup');
+
 BlazeComponent.extendComponent({
 BlazeComponent.extendComponent({
   onCreated() {
   onCreated() {
     this.error = new ReactiveVar('');
     this.error = new ReactiveVar('');
@@ -628,7 +1013,7 @@ BlazeComponent.extendComponent({
 }).register('addMemberPopup');
 }).register('addMemberPopup');
 
 
 Template.changePermissionsPopup.events({
 Template.changePermissionsPopup.events({
-  'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only'(
+  'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
     event,
     event,
   ) {
   ) {
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
@@ -638,11 +1023,13 @@ Template.changePermissionsPopup.events({
       'js-set-comment-only',
       'js-set-comment-only',
     );
     );
     const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
     const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
+    const isWorker = $(event.currentTarget).hasClass('js-set-worker');
     currentBoard.setMemberPermission(
     currentBoard.setMemberPermission(
       memberId,
       memberId,
       isAdmin,
       isAdmin,
       isNoComments,
       isNoComments,
       isCommentOnly,
       isCommentOnly,
+      isWorker,
     );
     );
     Popup.back(1);
     Popup.back(1);
   },
   },
@@ -659,7 +1046,8 @@ Template.changePermissionsPopup.helpers({
     return (
     return (
       !currentBoard.hasAdmin(this.userId) &&
       !currentBoard.hasAdmin(this.userId) &&
       !currentBoard.hasNoComments(this.userId) &&
       !currentBoard.hasNoComments(this.userId) &&
-      !currentBoard.hasCommentOnly(this.userId)
+      !currentBoard.hasCommentOnly(this.userId) &&
+      !currentBoard.hasWorker(this.userId)
     );
     );
   },
   },
 
 
@@ -679,6 +1067,13 @@ Template.changePermissionsPopup.helpers({
     );
     );
   },
   },
 
 
+  isWorker() {
+    const currentBoard = Boards.findOne(Session.get('currentBoard'));
+    return (
+      !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
+    );
+  },
+
   isLastAdmin() {
   isLastAdmin() {
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
     return (
     return (

+ 1 - 1
client/components/sidebar/sidebar.styl

@@ -109,7 +109,7 @@
     color: darken(white, 40%)
     color: darken(white, 40%)
 
 
 .board-sidebar
 .board-sidebar
-  width: 248px
+  width: 548px
   right: -@width
   right: -@width
   transition: top .1s, right .1s, width .1s
   transition: top .1s, right .1s, width .1s
 
 

+ 30 - 24
client/components/sidebar/sidebarArchives.jade

@@ -2,54 +2,60 @@ template(name="archivesSidebar")
   if isArchiveReady.get
   if isArchiveReady.get
     +basicTabs(tabs=tabs)
     +basicTabs(tabs=tabs)
       +tabContent(slug="cards")
       +tabContent(slug="cards")
-        p.quiet
-          a.js-restore-all-cards {{_ 'restore-all'}}
-          | -
-          a.js-delete-all-cards {{_ 'delete-all'}}
+        unless isWorker
+          p.quiet
+            a.js-restore-all-cards {{_ 'restore-all'}}
+            | -
+            a.js-delete-all-cards {{_ 'delete-all'}}
         each archivedCards
         each archivedCards
           .minicard-wrapper.js-minicard
           .minicard-wrapper.js-minicard
             +minicard(this)
             +minicard(this)
           if currentUser.isBoardMember
           if currentUser.isBoardMember
-            p.quiet
-              a.js-restore-card {{_ 'restore'}}
-              | -
-              a.js-delete-card {{_ 'delete'}}
+            unless isWorker
+              p.quiet
+                a.js-restore-card {{_ 'restore'}}
+                | -
+                a.js-delete-card {{_ 'delete'}}
             if cardIsInArchivedList
             if cardIsInArchivedList
               p.quiet.small ({{_ 'warn-list-archived'}})
               p.quiet.small ({{_ 'warn-list-archived'}})
         else
         else
           p.no-items-message {{_ 'no-archived-cards'}}
           p.no-items-message {{_ 'no-archived-cards'}}
 
 
       +tabContent(slug="lists")
       +tabContent(slug="lists")
-        p.quiet
-          a.js-restore-all-lists {{_ 'restore-all'}}
-          | -
-          a.js-delete-all-lists {{_ 'delete-all'}}
+        unless isWorker
+          p.quiet
+            a.js-restore-all-lists {{_ 'restore-all'}}
+            | -
+            a.js-delete-all-lists {{_ 'delete-all'}}
         ul.archived-lists
         ul.archived-lists
           each archivedLists
           each archivedLists
             li.archived-lists-item
             li.archived-lists-item
               = title
               = title
               if currentUser.isBoardMember
               if currentUser.isBoardMember
-                p.quiet
-                  a.js-restore-list {{_ 'restore'}}
-                  | -
-                  a.js-delete-list {{_ 'delete'}}
+                unless isWorker
+                  p.quiet
+                    a.js-restore-list {{_ 'restore'}}
+                    | -
+                    a.js-delete-list {{_ 'delete'}}
           else
           else
             li.no-items-message {{_ 'no-archived-lists'}}
             li.no-items-message {{_ 'no-archived-lists'}}
 
 
       +tabContent(slug="swimlanes")
       +tabContent(slug="swimlanes")
-        p.quiet
-          a.js-restore-all-swimlanes {{_ 'restore-all'}}
-          | -
-          a.js-delete-all-swimlanes {{_ 'delete-all'}}
+        unless isWorker
+          p.quiet
+            a.js-restore-all-swimlanes {{_ 'restore-all'}}
+            | -
+            a.js-delete-all-swimlanes {{_ 'delete-all'}}
         ul.archived-lists
         ul.archived-lists
           each archivedSwimlanes
           each archivedSwimlanes
             li.archived-lists-item
             li.archived-lists-item
               = title
               = title
               if currentUser.isBoardMember
               if currentUser.isBoardMember
-                p.quiet
-                  a.js-restore-swimlane {{_ 'restore'}}
-                  | -
-                  a.js-delete-swimlane {{_ 'delete'}}
+                unless isWorker
+                  p.quiet
+                    a.js-restore-swimlane {{_ 'restore'}}
+                    | -
+                    a.js-delete-swimlane {{_ 'delete'}}
           else
           else
             li.no-items-message {{_ 'no-archived-swimlanes'}}
             li.no-items-message {{_ 'no-archived-swimlanes'}}
   else
   else

+ 9 - 0
client/components/sidebar/sidebarArchives.js

@@ -139,3 +139,12 @@ BlazeComponent.extendComponent({
     ];
     ];
   },
   },
 }).register('archivesSidebar');
 }).register('archivesSidebar');
+
+Template.archivesSidebar.helpers({
+  isWorker() {
+    const currentBoard = Boards.findOne(Session.get('currentBoard'));
+    return (
+      !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
+    );
+  },
+});

+ 8 - 7
client/components/sidebar/sidebarFilters.jade

@@ -117,13 +117,14 @@ template(name="multiselectionSidebar")
               i.fa.fa-check
               i.fa.fa-check
             else if someSelectedElementHave 'member' _id
             else if someSelectedElementHave 'member' _id
               i.fa.fa-ellipsis-h
               i.fa.fa-ellipsis-h
-  hr
-  a.sidebar-btn.js-move-selection
-    i.fa.fa-share
-    span {{_ 'move-selection'}}
-  a.sidebar-btn.js-archive-selection
-    i.fa.fa-archive
-    span {{_ 'archive-selection'}}
+  unless currentUser.isWorker
+    hr
+    a.sidebar-btn.js-move-selection
+      i.fa.fa-share
+      span {{_ 'move-selection'}}
+    a.sidebar-btn.js-archive-selection
+      i.fa.fa-archive
+      span {{_ 'archive-selection'}}
 
 
 template(name="disambiguateMultiLabelPopup")
 template(name="disambiguateMultiLabelPopup")
   p {{_ 'what-to-do'}}
   p {{_ 'what-to-do'}}

+ 10 - 1
client/components/swimlanes/swimlaneHeader.js

@@ -1,3 +1,5 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
 const { calculateIndexData } = Utils;
 const { calculateIndexData } = Utils;
 
 
 let swimlaneColors;
 let swimlaneColors;
@@ -30,7 +32,14 @@ BlazeComponent.extendComponent({
 
 
 Template.swimlaneHeader.helpers({
 Template.swimlaneHeader.helpers({
   showDesktopDragHandles() {
   showDesktopDragHandles() {
-    return Meteor.user().hasShowDesktopDragHandles();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
 });
 });
 
 

+ 32 - 30
client/components/swimlanes/swimlanes.jade

@@ -1,24 +1,25 @@
 template(name="swimlane")
 template(name="swimlane")
   .swimlane
   .swimlane
     +swimlaneHeader
     +swimlaneHeader
-  .swimlane.js-lists.js-swimlane
-    if isMiniScreen
-      if currentListIsInThisSwimlane _id
-        +list(currentList)
-      unless currentList
+  unless collapseSwimlane
+    .swimlane.js-lists.js-swimlane
+      if isMiniScreen
+        if currentListIsInThisSwimlane _id
+          +list(currentList)
+        unless currentList
+          each lists
+            +miniList(this)
+          if currentUser.isBoardMember
+            unless currentUser.isCommentOnly
+              +addListForm
+      else
         each lists
         each lists
-          +miniList(this)
+          +list(this)
+          if currentCardIsInThisList _id ../_id
+            +cardDetails(currentCard)
         if currentUser.isBoardMember
         if currentUser.isBoardMember
           unless currentUser.isCommentOnly
           unless currentUser.isCommentOnly
             +addListForm
             +addListForm
-    else
-      each lists
-        +list(this)
-        if currentCardIsInThisList _id ../_id
-          +cardDetails(currentCard)
-      if currentUser.isBoardMember
-        unless currentUser.isCommentOnly
-          +addListForm
 
 
 template(name="listsGroup")
 template(name="listsGroup")
   .swimlane.list-group.js-lists
   .swimlane.list-group.js-lists
@@ -42,19 +43,20 @@ template(name="listsGroup")
           +addListForm
           +addListForm
 
 
 template(name="addListForm")
 template(name="addListForm")
-  .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
-    .list-header-add
-      +inlinedForm(autoclose=false)
-        input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
-          autocomplete="off" autofocus)
-        .edit-controls.clearfix
-          button.primary.confirm(type="submit") {{_ 'save'}}
-          unless currentBoard.isTemplatesBoard
-            unless currentBoard.isTemplateBoard
-              span.quiet
-                | {{_ 'or'}}
-                a.js-list-template {{_ 'template'}}
-      else
-        a.open-list-composer.js-open-inlined-form
-          i.fa.fa-plus
-          | {{_ 'add-list'}}
+  unless currentUser.isWorker
+    .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
+      .list-header-add
+        +inlinedForm(autoclose=false)
+          input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
+            autocomplete="off" autofocus)
+          .edit-controls.clearfix
+            button.primary.confirm(type="submit") {{_ 'save'}}
+            unless currentBoard.isTemplatesBoard
+              unless currentBoard.isTemplateBoard
+                span.quiet
+                  | {{_ 'or'}}
+                  a.js-list-template {{_ 'template'}}
+        else
+          a.open-list-composer.js-open-inlined-form
+            i.fa.fa-plus
+            | {{_ 'add-list'}}

+ 75 - 20
client/components/swimlanes/swimlanes.js

@@ -1,3 +1,5 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
 const { calculateIndex, enableClickOnTouch } = Utils;
 const { calculateIndex, enableClickOnTouch } = Utils;
 
 
 function currentListIsInThisSwimlane(swimlaneId) {
 function currentListIsInThisSwimlane(swimlaneId) {
@@ -14,7 +16,7 @@ function currentCardIsInThisList(listId, swimlaneId) {
   if (
   if (
     currentUser &&
     currentUser &&
     currentUser.profile &&
     currentUser.profile &&
-    currentUser.profile.boardView === 'board-view-swimlanes'
+    Utils.boardView() === 'board-view-swimlanes'
   )
   )
     return (
     return (
       currentCard &&
       currentCard &&
@@ -53,18 +55,6 @@ function initSortable(boardComponent, $listsDom) {
     },
     },
   };
   };
 
 
-  if (Utils.isMiniScreen) {
-    $listsDom.sortable({
-      handle: '.js-list-handle',
-    });
-  }
-
-  if (!Utils.isMiniScreen && showDesktopDragHandles) {
-    $listsDom.sortable({
-      handle: '.js-list-header',
-    });
-  }
-
   $listsDom.sortable({
   $listsDom.sortable({
     tolerance: 'pointer',
     tolerance: 'pointer',
     helper: 'clone',
     helper: 'clone',
@@ -104,19 +94,64 @@ function initSortable(boardComponent, $listsDom) {
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   }
   }
 
 
-  // Disable drag-dropping while in multi-selection mode, or if the current user
-  // is not a board member
   boardComponent.autorun(() => {
   boardComponent.autorun(() => {
+    let showDesktopDragHandles = false;
+    currentUser = Meteor.user();
+    if (currentUser) {
+      showDesktopDragHandles = (currentUser.profile || {})
+        .showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      showDesktopDragHandles = true;
+    } else {
+      showDesktopDragHandles = false;
+    }
+
+    if (!Utils.isMiniScreen() && showDesktopDragHandles) {
+      $listsDom.sortable({
+        handle: '.js-list-handle',
+      });
+    } else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
+      $listsDom.sortable({
+        handle: '.js-list-header',
+      });
+    }
+
     const $listDom = $listsDom;
     const $listDom = $listsDom;
     if ($listDom.data('sortable')) {
     if ($listDom.data('sortable')) {
       $listsDom.sortable(
       $listsDom.sortable(
         'option',
         'option',
         'disabled',
         'disabled',
-        MultiSelection.isActive() || !userIsMember(),
+        // Disable drag-dropping when user is not member/is worker/is miniscreen
+        !userIsMember(),
+        // Not disable drag-dropping while in multi-selection mode
+        // MultiSelection.isActive() || !userIsMember(),
+      );
+    }
+
+    if ($listDom.data('sortable')) {
+      $listsDom.sortable(
+        'option',
+        'disabled',
+        // Disable drag-dropping when user is not member/is worker/is miniscreen
+        Meteor.user().isWorker(),
+        // Not disable drag-dropping while in multi-selection mode
+        // MultiSelection.isActive() || !userIsMember(),
+      );
+    }
+
+    if ($listDom.data('sortable')) {
+      $listsDom.sortable(
+        'option',
+        'disabled',
+        // Disable drag-dropping when user is not member/is worker/is miniscreen
+        Utils.isMiniScreen(),
+        // Not disable drag-dropping while in multi-selection mode
+        // MultiSelection.isActive() || !userIsMember(),
       );
       );
     }
     }
   });
   });
@@ -163,8 +198,20 @@ BlazeComponent.extendComponent({
           // the user will legitimately expect to be able to select some text with
           // the user will legitimately expect to be able to select some text with
           // his mouse.
           // his mouse.
 
 
+          let showDesktopDragHandles = false;
+          currentUser = Meteor.user();
+          if (currentUser) {
+            showDesktopDragHandles = (currentUser.profile || {})
+              .showDesktopDragHandles;
+          } else if (cookies.has('showDesktopDragHandles')) {
+            showDesktopDragHandles = true;
+          } else {
+            showDesktopDragHandles = false;
+          }
+
           const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
           const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
-            Util.isMiniScreen || (!Util.isMiniScreen && showDesktopDragHandles)
+            Utils.isMiniScreen() ||
+              (!Utils.isMiniScreen() && showDesktopDragHandles)
               ? ['.js-list-handle', '.js-swimlane-header-handle']
               ? ['.js-list-handle', '.js-swimlane-header-handle']
               : ['.js-list-header'],
               : ['.js-list-header'],
           );
           );
@@ -245,13 +292,21 @@ BlazeComponent.extendComponent({
 
 
 Template.swimlane.helpers({
 Template.swimlane.helpers({
   showDesktopDragHandles() {
   showDesktopDragHandles() {
-    return Meteor.user().hasShowDesktopDragHandles();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
   canSeeAddList() {
   canSeeAddList() {
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 });
 });

+ 49 - 13
client/components/swimlanes/swimlanes.styl

@@ -1,5 +1,41 @@
 @import 'nib'
 @import 'nib'
 
 
+/*
+// Minimize swimlanes start https://www.w3schools.com/howto/howto_js_accordion.asp
+
+.accordion
+  cursor: pointer
+  width: 30px
+  height: 20px
+  border: none
+  outline: none
+  font-size: 18px
+  transition: 0.4s
+  padding-top: 0px
+  margin-top: 0px
+
+.accordion:after
+  // Unicode triagle right:
+  content: '\25B6'
+  color: #777
+  font-weight: bold
+  float: left
+
+.active:after
+  // Unicode triangle down:
+  content: '\25BC'
+
+.panel
+  width: 100%
+  max-height: 0
+  overflow: hidden
+  transition: max-height 0.2s ease-out
+  margin: 0px
+  padding: 0px
+
+// Minimize swimlanes end https://www.w3schools.com/howto/howto_js_accordion.asp
+*/
+
 .swimlane
 .swimlane
   // Even if this background color is the same as the body we can't leave it
   // Even if this background color is the same as the body we can't leave it
   // transparent, because that won't work during a swimlane drag.
   // transparent, because that won't work during a swimlane drag.
@@ -25,22 +61,22 @@
       cursor: grabbing
       cursor: grabbing
 
 
   .swimlane-header-wrap
   .swimlane-header-wrap
-    display: flex;
-    flex-direction: row;
-    flex: 1 0 100%;
-    background-color: #ccc;
+    display: flex
+    flex-direction: row
+    flex: 1 0 100%
+    background-color: #ccc
 
 
     .swimlane-header
     .swimlane-header
-      font-size: 14px;
+      font-size: 14px
       padding: 5px 5px
       padding: 5px 5px
-      font-weight: bold;
-      min-height: 9px;
-      width: 100%;
-      overflow: hidden;
-      -o-text-overflow: ellipsis;
-      text-overflow: ellipsis;
-      word-wrap: break-word;
-      text-align: center;
+      font-weight: bold
+      min-height: 9px
+      width: 100%
+      overflow: hidden
+      -o-text-overflow: ellipsis
+      text-overflow: ellipsis
+      word-wrap: break-word
+      text-align: center
 
 
     .swimlane-header-menu
     .swimlane-header-menu
       position: absolute
       position: absolute

+ 1 - 0
client/components/users/userAvatar.jade

@@ -73,6 +73,7 @@ template(name="cardMemberPopup")
         p.quiet @{{ user.username }}
         p.quiet @{{ user.username }}
     ul.pop-over-list
     ul.pop-over-list
       if currentUser.isNotCommentOnly
       if currentUser.isNotCommentOnly
+        if currentUser.isNotWorker
           li: a.js-remove-member {{_ 'remove-member-from-card'}}
           li: a.js-remove-member {{_ 'remove-member-from-card'}}
 
 
       if $eq currentUser._id user._id
       if $eq currentUser._id user._id

+ 47 - 18
client/components/users/userHeader.jade

@@ -13,21 +13,46 @@ template(name="headerUserBar")
 template(name="memberMenuPopup")
 template(name="memberMenuPopup")
   ul.pop-over-list
   ul.pop-over-list
     with currentUser
     with currentUser
-      li: a.js-edit-profile {{_ 'edit-profile'}}
-      li: a.js-change-settings {{_ 'change-settings'}}
-      li: a.js-change-avatar {{_ 'edit-avatar'}}
+      li
+        a.js-edit-profile
+          i.fa.fa-user
+          | {{_ 'edit-profile'}}
+      li
+        a.js-change-settings
+          i.fa.fa-cog
+          | {{_ 'change-settings'}}
+      li
+        a.js-change-avatar
+          i.fa.fa-picture-o
+          | {{_ 'edit-avatar'}}
       unless isSandstorm
       unless isSandstorm
-        li: a.js-change-password {{_ 'changePasswordPopup-title'}}
-        li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
+        li
+          a.js-change-password
+            i.fa.fa-key
+            | {{_ 'changePasswordPopup-title'}}
+        li
+          a.js-change-language
+            i.fa.fa-flag
+            | {{_ 'changeLanguagePopup-title'}}
     if currentUser.isAdmin
     if currentUser.isAdmin
-      li: a.js-go-setting(href="{{pathFor 'setting'}}") {{_ 'admin-panel'}}
-  hr
-  ul.pop-over-list
-    li: a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") {{_ 'templates'}}
+      li
+        a.js-go-setting(href="{{pathFor 'setting'}}")
+          i.fa.fa-lock
+          | {{_ 'admin-panel'}}
+  unless currentUser.isWorker
+    hr
+    ul.pop-over-list
+      li
+        a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
+          i.fa.fa-clone
+          | {{_ 'templates'}}
   unless isSandstorm
   unless isSandstorm
     hr
     hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a.js-logout {{_ 'log-out'}}
+      li
+        a.js-logout
+          i.fa.fa-sign-out
+          | {{_ 'log-out'}}
 
 
 template(name="editProfilePopup")
 template(name="editProfilePopup")
   form
   form
@@ -75,21 +100,25 @@ template(name="changeSettingsPopup")
   ul.pop-over-list
   ul.pop-over-list
     li
     li
       a.js-toggle-system-messages
       a.js-toggle-system-messages
+        i.fa.fa-comments-o
         | {{_ 'hide-system-messages'}}
         | {{_ 'hide-system-messages'}}
         if hiddenSystemMessages
         if hiddenSystemMessages
           i.fa.fa-check
           i.fa.fa-check
     li
     li
       a.js-toggle-desktop-drag-handles
       a.js-toggle-desktop-drag-handles
+        i.fa.fa-arrows
         | {{_ 'show-desktop-drag-handles'}}
         | {{_ 'show-desktop-drag-handles'}}
         if showDesktopDragHandles
         if showDesktopDragHandles
           i.fa.fa-check
           i.fa.fa-check
-    li
-      label.bold
-        | {{_ 'show-cards-minimum-count'}}
-      input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
-      input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}")
-
+    unless currentUser.isWorker
+      li
+        label.bold
+          i.fa.fa-sort-numeric-asc
+          | {{_ 'show-cards-minimum-count'}}
+        input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
+        input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}")
 
 
 template(name="userDeletePopup")
 template(name="userDeletePopup")
-  p {{_ 'delete-user-confirm-popup'}}
-  button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
+  unless currentUser.isWorker
+    p {{_ 'delete-user-confirm-popup'}}
+    button.js-confirm.negate.full(type="submit") {{_ 'delete'}}

+ 82 - 11
client/components/users/userHeader.js

@@ -1,3 +1,6 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
+
 Template.headerUserBar.events({
 Template.headerUserBar.events({
   'click .js-open-header-member-menu': Popup.open('memberMenu'),
   'click .js-open-header-member-menu': Popup.open('memberMenu'),
   'click .js-change-avatar': Popup.open('changeAvatar'),
   'click .js-change-avatar': Popup.open('changeAvatar'),
@@ -5,10 +8,22 @@ Template.headerUserBar.events({
 
 
 Template.memberMenuPopup.helpers({
 Template.memberMenuPopup.helpers({
   templatesBoardId() {
   templatesBoardId() {
-    return Meteor.user().getTemplatesBoardId();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return Meteor.user().getTemplatesBoardId();
+    } else {
+      // No need to getTemplatesBoardId on public board
+      return false;
+    }
   },
   },
   templatesBoardSlug() {
   templatesBoardSlug() {
-    return Meteor.user().getTemplatesBoardSlug();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return Meteor.user().getTemplatesBoardSlug();
+    } else {
+      // No need to getTemplatesBoardSlug() on public board
+      return false;
+    }
   },
   },
 });
 });
 
 
@@ -30,13 +45,31 @@ Template.memberMenuPopup.events({
 
 
 Template.editProfilePopup.helpers({
 Template.editProfilePopup.helpers({
   allowEmailChange() {
   allowEmailChange() {
-    return AccountSettings.findOne('accounts-allowEmailChange').booleanValue;
+    Meteor.call('AccountSettings.allowEmailChange', (_, result) => {
+      if (result) {
+        return true;
+      } else {
+        return false;
+      }
+    });
   },
   },
   allowUserNameChange() {
   allowUserNameChange() {
-    return AccountSettings.findOne('accounts-allowUserNameChange').booleanValue;
+    Meteor.call('AccountSettings.allowUserNameChange', (_, result) => {
+      if (result) {
+        return true;
+      } else {
+        return false;
+      }
+    });
   },
   },
   allowUserDelete() {
   allowUserDelete() {
-    return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
+    Meteor.call('AccountSettings.allowUserDelete', (_, result) => {
+      if (result) {
+        return true;
+      } else {
+        return false;
+      }
+    });
   },
   },
 });
 });
 
 
@@ -162,22 +195,55 @@ Template.changeLanguagePopup.events({
 
 
 Template.changeSettingsPopup.helpers({
 Template.changeSettingsPopup.helpers({
   showDesktopDragHandles() {
   showDesktopDragHandles() {
-    return Meteor.user().hasShowDesktopDragHandles();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
   hiddenSystemMessages() {
   hiddenSystemMessages() {
-    return Meteor.user().hasHiddenSystemMessages();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).hasHiddenSystemMessages;
+    } else if (cookies.has('hasHiddenSystemMessages')) {
+      return true;
+    } else {
+      return false;
+    }
   },
   },
   showCardsCountAt() {
   showCardsCountAt() {
-    return Meteor.user().getLimitToShowCardsCount();
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return Meteor.user().getLimitToShowCardsCount();
+    } else {
+      return cookies.get('limitToShowCardsCount');
+    }
   },
   },
 });
 });
 
 
 Template.changeSettingsPopup.events({
 Template.changeSettingsPopup.events({
   'click .js-toggle-desktop-drag-handles'() {
   'click .js-toggle-desktop-drag-handles'() {
-    Meteor.call('toggleDesktopDragHandles');
+    currentUser = Meteor.user();
+    if (currentUser) {
+      Meteor.call('toggleDesktopDragHandles');
+    } else if (cookies.has('showDesktopDragHandles')) {
+      cookies.remove('showDesktopDragHandles');
+    } else {
+      cookies.set('showDesktopDragHandles', 'true');
+    }
   },
   },
   'click .js-toggle-system-messages'() {
   'click .js-toggle-system-messages'() {
-    Meteor.call('toggleSystemMessages');
+    currentUser = Meteor.user();
+    if (currentUser) {
+      Meteor.call('toggleSystemMessages');
+    } else if (cookies.has('hasHiddenSystemMessages')) {
+      cookies.remove('hasHiddenSystemMessages');
+    } else {
+      cookies.set('hasHiddenSystemMessages', 'true');
+    }
   },
   },
   'click .js-apply-show-cards-at'(event, templateInstance) {
   'click .js-apply-show-cards-at'(event, templateInstance) {
     event.preventDefault();
     event.preventDefault();
@@ -186,7 +252,12 @@ Template.changeSettingsPopup.events({
       10,
       10,
     );
     );
     if (!isNaN(minLimit)) {
     if (!isNaN(minLimit)) {
-      Meteor.call('changeLimitToShowCardsCount', minLimit);
+      currentUser = Meteor.user();
+      if (currentUser) {
+        Meteor.call('changeLimitToShowCardsCount', minLimit);
+      } else {
+        cookies.set('limitToShowCardsCount', minLimit);
+      }
       Popup.back();
       Popup.back();
     }
     }
   },
   },

+ 28 - 0
client/lib/keyboard.js

@@ -70,6 +70,30 @@ Mousetrap.bind('space', evt => {
   }
   }
 });
 });
 
 
+// XXX This shortcut should also work when hovering over a card in board view
+Mousetrap.bind('c', evt => {
+  if (!Session.get('currentCard')) {
+    return;
+  }
+
+  const currentUserId = Meteor.userId();
+  if (currentUserId === null) {
+    return;
+  }
+
+  if (
+    Meteor.user().isBoardMember() &&
+    !Meteor.user().isCommentOnly() &&
+    !Meteor.user().isWorker()
+  ) {
+    const card = Cards.findOne(Session.get('currentCard'));
+    card.archive();
+    // We should prevent scrolling in card when spacebar is clicked
+    // This should do it according to Mousetrap docs, but it doesn't
+    evt.preventDefault();
+  }
+});
+
 Template.keyboardShortcuts.helpers({
 Template.keyboardShortcuts.helpers({
   mapping: [
   mapping: [
     {
     {
@@ -104,5 +128,9 @@ Template.keyboardShortcuts.helpers({
       keys: ['SPACE'],
       keys: ['SPACE'],
       action: 'shortcut-assign-self',
       action: 'shortcut-assign-self',
     },
     },
+    {
+      keys: ['C'],
+      action: 'archive-card',
+    },
   ],
   ],
 });
 });

+ 8 - 3
client/lib/textComplete.js

@@ -48,6 +48,11 @@ $.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
   return this;
   return this;
 };
 };
 
 
-EscapeActions.register('textcomplete', () => {}, () => dropdownMenuIsOpened, {
-  noClickEscapeOn: '.textcomplete-dropdown',
-});
+EscapeActions.register(
+  'textcomplete',
+  () => {},
+  () => dropdownMenuIsOpened,
+  {
+    noClickEscapeOn: '.textcomplete-dropdown',
+  },
+);

+ 66 - 0
client/lib/utils.js

@@ -1,4 +1,40 @@
+import { Cookies } from 'meteor/ostrio:cookies';
+const cookies = new Cookies();
+
 Utils = {
 Utils = {
+  setBoardView(view) {
+    currentUser = Meteor.user();
+    if (currentUser) {
+      Meteor.user().setBoardView(view);
+    } else if (view === 'board-view-lists') {
+      cookies.set('boardView', 'board-view-lists'); //true
+    } else if (view === 'board-view-swimlanes') {
+      cookies.set('boardView', 'board-view-swimlanes'); //true
+    } else if (view === 'board-view-cal') {
+      cookies.set('boardView', 'board-view-cal'); //true
+    }
+  },
+
+  unsetBoardView() {
+    cookies.remove('boardView');
+    cookies.remove('collapseSwimlane');
+  },
+
+  boardView() {
+    currentUser = Meteor.user();
+    if (currentUser) {
+      return (currentUser.profile || {}).boardView;
+    } else if (cookies.get('boardView') === 'board-view-lists') {
+      return 'board-view-lists';
+    } else if (cookies.get('boardView') === 'board-view-swimlanes') {
+      return 'board-view-swimlanes';
+    } else if (cookies.get('boardView') === 'board-view-cal') {
+      return 'board-view-cal';
+    } else {
+      return false;
+    }
+  },
+
   // XXX We should remove these two methods
   // XXX We should remove these two methods
   goBoardId(_id) {
   goBoardId(_id) {
     const board = Boards.findOne(_id);
     const board = Boards.findOne(_id);
@@ -117,8 +153,38 @@ Utils = {
   // in a small window (even on desktop), Wekan run in compact mode.
   // in a small window (even on desktop), Wekan run in compact mode.
   // we can easily debug with a small window of desktop browser. :-)
   // we can easily debug with a small window of desktop browser. :-)
   isMiniScreen() {
   isMiniScreen() {
+    // OLD WINDOW WIDTH DETECTION:
     this.windowResizeDep.depend();
     this.windowResizeDep.depend();
     return $(window).width() <= 800;
     return $(window).width() <= 800;
+
+    // NEW TOUCH DEVICE DETECTION:
+    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
+
+    /*
+    var hasTouchScreen = false;
+    if ("maxTouchPoints" in navigator) {
+        hasTouchScreen = navigator.maxTouchPoints > 0;
+    } else if ("msMaxTouchPoints" in navigator) {
+        hasTouchScreen = navigator.msMaxTouchPoints > 0;
+    } else {
+        var mQ = window.matchMedia && matchMedia("(pointer:coarse)");
+        if (mQ && mQ.media === "(pointer:coarse)") {
+            hasTouchScreen = !!mQ.matches;
+        } else if ('orientation' in window) {
+            hasTouchScreen = true; // deprecated, but good fallback
+        } else {
+            // Only as a last resort, fall back to user agent sniffing
+            var UA = navigator.userAgent;
+            hasTouchScreen = (
+                /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
+                /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
+            );
+        }
+    }
+    */
+    //if (hasTouchScreen)
+    //    document.getElementById("exampleButton").style.padding="1em";
+    //return false;
   },
   },
 
 
   calculateIndexData(prevData, nextData, nItems = 1) {
   calculateIndexData(prevData, nextData, nItems = 1) {

+ 7 - 3
config/accounts.js

@@ -38,9 +38,13 @@ AccountsTemplates.configure({
   },
   },
 });
 });
 
 
-['signIn', 'signUp', 'resetPwd', 'forgotPwd', 'enrollAccount'].forEach(
-  routeName => AccountsTemplates.configureRoute(routeName),
-);
+[
+  'signIn',
+  'signUp',
+  'resetPwd',
+  'forgotPwd',
+  'enrollAccount',
+].forEach(routeName => AccountsTemplates.configureRoute(routeName));
 
 
 // We display the form to change the password in a popup window that already
 // We display the form to change the password in a popup window that already
 // have a title, so we unset the title automatically displayed by useraccounts.
 // have a title, so we unset the title automatically displayed by useraccounts.

+ 32 - 8
docker-compose.yml

@@ -93,14 +93,14 @@ services:
     #-------------------------------------------------------------------------------------
     #-------------------------------------------------------------------------------------
     # ==== MONGODB AND METEOR VERSION ====
     # ==== MONGODB AND METEOR VERSION ====
     # a) For Wekan Meteor 1.8.x version at master branch, use mongo 4.x
     # a) For Wekan Meteor 1.8.x version at master branch, use mongo 4.x
-    image: mongo:4.0.12
+    image: mongo:4.2.2
     # b) For Wekan Meteor 1.6.x version at devel branch.
     # b) For Wekan Meteor 1.6.x version at devel branch.
     # Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x
     # Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x
     #image: mongo:3.2.21
     #image: mongo:3.2.21
     #-------------------------------------------------------------------------------------
     #-------------------------------------------------------------------------------------
     container_name: wekan-db
     container_name: wekan-db
     restart: always
     restart: always
-    command: mongod --smallfiles --oplogSize 128
+    command: mongod --oplogSize 128
     networks:
     networks:
       - wekan-tier
       - wekan-tier
     expose:
     expose:
@@ -238,7 +238,7 @@ services:
       #---------------------------------------------------------------
       #---------------------------------------------------------------
       # ==== RICH TEXT EDITOR IN CARD COMMENTS ====
       # ==== RICH TEXT EDITOR IN CARD COMMENTS ====
       # https://github.com/wekan/wekan/pull/2560
       # https://github.com/wekan/wekan/pull/2560
-      - RICHER_CARD_COMMENT_EDITOR=true
+      - RICHER_CARD_COMMENT_EDITOR=false
       #---------------------------------------------------------------
       #---------------------------------------------------------------
       # ==== CARD OPENED, SEND WEBHOOK MESSAGE ====
       # ==== CARD OPENED, SEND WEBHOOK MESSAGE ====
       # https://github.com/wekan/wekan/issues/2518
       # https://github.com/wekan/wekan/issues/2518
@@ -342,6 +342,31 @@ services:
       # Tthe claim name you want to map to the email field:
       # Tthe claim name you want to map to the email field:
       #- OAUTH2_EMAIL_MAP=email
       #- OAUTH2_EMAIL_MAP=email
       #-----------------------------------------------------------------
       #-----------------------------------------------------------------
+      # ==== OAUTH2 Nextcloud ====
+      # 1) Register the application with Nextcloud: https://your.nextcloud/settings/admin/security
+      #    Make sure you capture the application ID as well as generate a secret key.
+      # 2) Configure the environment variables. This differs slightly
+      #     by installation type, but make sure you have the following:
+      #- OAUTH2_ENABLED=true
+      # OAuth2 login style: popup or redirect.
+      #- OAUTH2_LOGIN_STYLE=redirect
+      # Application GUID captured during app registration:
+      #- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
+      # Secret key generated during app registration:
+      #- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+      #- OAUTH2_SERVER_URL=https://your-nextcloud.tld
+      #- OAUTH2_AUTH_ENDPOINT=/index.php/apps/oauth2/authorize
+      #- OAUTH2_USERINFO_ENDPOINT=/ocs/v2.php/cloud/user?format=json
+      #- OAUTH2_TOKEN_ENDPOINT=/index.php/apps/oauth2/api/v1/token
+      # The claim name you want to map to the unique ID field:
+      #- OAUTH2_ID_MAP=id
+      # The claim name you want to map to the username field:
+      #- OAUTH2_USERNAME_MAP=id
+      # The claim name you want to map to the full name field:
+      #- OAUTH2_FULLNAME_MAP=display-name
+      # Tthe claim name you want to map to the email field:
+      #- OAUTH2_EMAIL_MAP=email
+      #-----------------------------------------------------------------
       # ==== OAUTH2 KEYCLOAK ====
       # ==== OAUTH2 KEYCLOAK ====
       # https://github.com/wekan/wekan/wiki/Keycloak  <== MAPPING INFO, REQUIRED
       # https://github.com/wekan/wekan/wiki/Keycloak  <== MAPPING INFO, REQUIRED
       #- OAUTH2_ENABLED=true
       #- OAUTH2_ENABLED=true
@@ -440,10 +465,10 @@ services:
       # If the sync of the users should be done in the background
       # If the sync of the users should be done in the background
       #- LDAP_BACKGROUND_SYNC=false
       #- LDAP_BACKGROUND_SYNC=false
       #
       #
-      # At which interval does the background task sync in milliseconds.
-      # Leave this unset, so it uses default, and does not crash.
-      # https://github.com/wekan/wekan/issues/2354#issuecomment-515305722
-      - LDAP_BACKGROUND_SYNC_INTERVAL=''
+      # At which interval does the background task sync.
+      # The format must be as specified in:
+      # https://bunkat.github.io/later/parsers.html#text
+      #- LDAP_BACKGROUND_SYNC_INTERVAL='every 1 hour'
       #
       #
       #- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
       #- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
       #
       #
@@ -559,7 +584,6 @@ services:
       # example : LOGOUT_ON_MINUTES=55
       # example : LOGOUT_ON_MINUTES=55
       #- LOGOUT_ON_MINUTES=
       #- LOGOUT_ON_MINUTES=
       #-------------------------------------------------------------------
       #-------------------------------------------------------------------
-
     depends_on:
     depends_on:
       - wekandb
       - wekandb
 
 

+ 4 - 4
fix-download-unicode/cfs_access-point.txt

@@ -451,14 +451,14 @@ FS.HTTP.Handlers.Get = function (ref) {
         if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('trident') >= 0 || userAgent.indexOf('chrome') >= 0) {
         if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('trident') >= 0 || userAgent.indexOf('chrome') >= 0) {
             ref.filename =  encodeURIComponent(ref.filename);
             ref.filename =  encodeURIComponent(ref.filename);
         } else if(userAgent.indexOf('firefox') >= 0) {
         } else if(userAgent.indexOf('firefox') >= 0) {
-            ref.filename = new Buffer(ref.filename).toString('binary');
+            ref.filename = Buffer.from(ref.filename).toString('binary');
         } else {
         } else {
             /* safari*/
             /* safari*/
-            ref.filename = new Buffer(ref.filename).toString('binary');
-        }   
+            ref.filename = Buffer.from(ref.filename).toString('binary');
+        }
    } catch (ex){
    } catch (ex){
         ref.filename = 'tempfix';
         ref.filename = 'tempfix';
-   } 
+   }
    return originalHandler.call(this, ref);
    return originalHandler.call(this, ref);
 };
 };
                                                                                                                       // 221
                                                                                                                       // 221

+ 1 - 1
helm/wekan/requirements.yaml

@@ -1,5 +1,5 @@
 dependencies:
 dependencies:
 - name: mongodb-replicaset
 - name: mongodb-replicaset
-  version: 3.6.x
+  version: 3.11.x
   repository: "https://kubernetes-charts.storage.googleapis.com/"
   repository: "https://kubernetes-charts.storage.googleapis.com/"
   condition: mongodb-replicaset.enabled
   condition: mongodb-replicaset.enabled

+ 1 - 1
helm/wekan/templates/_helpers.tpl

@@ -75,7 +75,7 @@ else use user-provided URL.
 {{- if (index .Values "mongodb-replicaset" "enabled") -}}
 {{- if (index .Values "mongodb-replicaset" "enabled") -}}
 {{- $count := (int (index .Values "mongodb-replicaset" "replicas")) -}}
 {{- $count := (int (index .Values "mongodb-replicaset" "replicas")) -}}
 {{- $release := .Release.Name -}}
 {{- $release := .Release.Name -}}
-mongodb://{{- range $v := until $count }}{{ $release }}-mongodb-replicaset-{{ $v }}.{{ $release }}-mongodb-replicaset:27017{{ if ne $v (sub $count 1) }},{{- end -}}{{- end -}}?replicaSet={{ index .Values "mongodb-replicaset" "replicaSetName" }}
+mongodb://{{ $release }}-mongodb-replicaset:27017/admin?replicaSet={{ index .Values "mongodb-replicaset" "replicaSetName" }}
 {{- else -}}
 {{- else -}}
 {{- index .Values "mongodb-replicaset" "url" -}}
 {{- index .Values "mongodb-replicaset" "url" -}}
 {{- end -}}
 {{- end -}}

+ 1 - 1
helm/wekan/templates/ingress.yaml

@@ -35,6 +35,6 @@ spec:
           - path: {{ $ingressPath }}
           - path: {{ $ingressPath }}
             backend:
             backend:
               serviceName: {{ $fullName }}
               serviceName: {{ $fullName }}
-              servicePort: http
+              servicePort: 80
   {{- end }}
   {{- end }}
 {{- end }}
 {{- end }}

+ 9 - 2
i18n/ar.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "تعديل وضوح الرؤية",
   "boardChangeVisibilityPopup-title": "تعديل وضوح الرؤية",
   "boardChangeWatchPopup-title": "تغيير المتابعة",
   "boardChangeWatchPopup-title": "تغيير المتابعة",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "عرض اللوحات",
   "boards": "لوحات",
   "boards": "لوحات",
   "board-view": "عرض اللوحات",
   "board-view": "عرض اللوحات",
   "board-view-cal": "التقويم",
   "board-view-cal": "التقويم",
   "board-view-swimlanes": "خطوط السباحة",
   "board-view-swimlanes": "خطوط السباحة",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "القائمات",
   "board-view-lists": "القائمات",
   "bucket-example": "مثل « todo list » على سبيل المثال",
   "bucket-example": "مثل « todo list » على سبيل المثال",
   "cancel": "إلغاء",
   "cancel": "إلغاء",
@@ -221,6 +223,8 @@
   "comment-only-desc": "يمكن التعليق على بطاقات فقط.",
   "comment-only-desc": "يمكن التعليق على بطاقات فقط.",
   "no-comments": "No comments",
   "no-comments": "No comments",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "حاسوب",
   "computer": "حاسوب",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 9 - 2
i18n/bg.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeWatchPopup-title": "Промени наблюдаването",
   "boardChangeWatchPopup-title": "Промени наблюдаването",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "Board View",
   "boards": "Табла",
   "boards": "Табла",
   "board-view": "Board View",
   "board-view": "Board View",
   "board-view-cal": "Календар",
   "board-view-cal": "Календар",
   "board-view-swimlanes": "Коридори",
   "board-view-swimlanes": "Коридори",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Списъци",
   "board-view-lists": "Списъци",
   "bucket-example": "Like “Bucket List” for example",
   "bucket-example": "Like “Bucket List” for example",
   "cancel": "Cancel",
   "cancel": "Cancel",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Може да коментира само в карти.",
   "comment-only-desc": "Може да коментира само в карти.",
   "no-comments": "Няма коментари",
   "no-comments": "Няма коментари",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Компютър",
   "computer": "Компютър",
   "confirm-subtask-delete-dialog": "Сигурен ли сте, че искате да изтриете подзадачата?",
   "confirm-subtask-delete-dialog": "Сигурен ли сте, че искате да изтриете подзадачата?",
   "confirm-checklist-delete-dialog": "Сигурни ли сте, че искате да изтриете този чеклист?",
   "confirm-checklist-delete-dialog": "Сигурни ли сте, че искате да изтриете този чеклист?",
@@ -579,8 +583,9 @@
   "default": "по подразбиране",
   "default": "по подразбиране",
   "queue": "Опашка",
   "queue": "Опашка",
   "subtask-settings": "Настройки на Подзадачите",
   "subtask-settings": "Настройки на Подзадачите",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Настройки за Подзадачите за това Табло",
   "boardSubtaskSettingsPopup-title": "Настройки за Подзадачите за това Табло",
-  "show-subtasks-field": "Картата може да има подзадачи",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 9 - 2
i18n/br.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "Board View",
   "boards": "Boards",
   "boards": "Boards",
   "board-view": "Board View",
   "board-view": "Board View",
   "board-view-cal": "Calendar",
   "board-view-cal": "Calendar",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Lists",
   "board-view-lists": "Lists",
   "bucket-example": "Like “Bucket List” for example",
   "bucket-example": "Like “Bucket List” for example",
   "cancel": "Cancel",
   "cancel": "Cancel",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Can comment on cards only.",
   "comment-only-desc": "Can comment on cards only.",
   "no-comments": "No comments",
   "no-comments": "No comments",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Computer",
   "computer": "Computer",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 10 - 3
i18n/ca.i18n.json

@@ -78,7 +78,7 @@
   "add-attachment": "Afegeix adjunt",
   "add-attachment": "Afegeix adjunt",
   "add-board": "Afegeix Tauler",
   "add-board": "Afegeix Tauler",
   "add-card": "Afegeix Fitxa",
   "add-card": "Afegeix Fitxa",
-  "add-swimlane": "Afegix Carril de Natació",
+  "add-swimlane": "Afegeix carril de natació",
   "add-subtask": "Afegir Subtasca",
   "add-subtask": "Afegir Subtasca",
   "add-checklist": "Afegeix checklist",
   "add-checklist": "Afegeix checklist",
   "add-checklist-item": "Afegeix un ítem al checklist",
   "add-checklist-item": "Afegeix un ítem al checklist",
@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Canvia visibilitat",
   "boardChangeVisibilityPopup-title": "Canvia visibilitat",
   "boardChangeWatchPopup-title": "Canvia seguiment",
   "boardChangeWatchPopup-title": "Canvia seguiment",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "Visió del tauler",
   "boards": "Taulers",
   "boards": "Taulers",
   "board-view": "Visió del tauler",
   "board-view": "Visió del tauler",
   "board-view-cal": "Calendari",
   "board-view-cal": "Calendari",
   "board-view-swimlanes": "Carrils de Natació",
   "board-view-swimlanes": "Carrils de Natació",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Llistes",
   "board-view-lists": "Llistes",
   "bucket-example": "Igual que “Bucket List”,  per exemple",
   "bucket-example": "Igual que “Bucket List”,  per exemple",
   "cancel": "Cancel·la",
   "cancel": "Cancel·la",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Només pots fer comentaris a les fitxes",
   "comment-only-desc": "Només pots fer comentaris a les fitxes",
   "no-comments": "Sense comentaris",
   "no-comments": "Sense comentaris",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Ordinador",
   "computer": "Ordinador",
   "confirm-subtask-delete-dialog": "Esteu segur que voleu eliminar la subtasca?",
   "confirm-subtask-delete-dialog": "Esteu segur que voleu eliminar la subtasca?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 15 - 8
i18n/cs.i18n.json

@@ -74,7 +74,7 @@
   "activity-checklist-completed-card": "dokončil(a) zaškrtávací seznam __checklist__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__",
   "activity-checklist-completed-card": "dokončil(a) zaškrtávací seznam __checklist__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__",
   "activity-checklist-uncompleted-card": "nedokončený seznam %s",
   "activity-checklist-uncompleted-card": "nedokončený seznam %s",
   "activity-editComment": "edited comment %s",
   "activity-editComment": "edited comment %s",
-  "activity-deleteComment": "deleted comment %s",
+  "activity-deleteComment": "smazat komentář %s",
   "add-attachment": "Přidat přílohu",
   "add-attachment": "Přidat přílohu",
   "add-board": "Přidat tablo",
   "add-board": "Přidat tablo",
   "add-card": "Přidat kartu",
   "add-card": "Přidat kartu",
@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Upravit viditelnost",
   "boardChangeVisibilityPopup-title": "Upravit viditelnost",
   "boardChangeWatchPopup-title": "Změnit sledování",
   "boardChangeWatchPopup-title": "Změnit sledování",
   "boardMenuPopup-title": "Nastavení Tabla",
   "boardMenuPopup-title": "Nastavení Tabla",
+  "boardChangeViewPopup-title": "Náhled tabla",
   "boards": "Tabla",
   "boards": "Tabla",
   "board-view": "Náhled tabla",
   "board-view": "Náhled tabla",
   "board-view-cal": "Kalendář",
   "board-view-cal": "Kalendář",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Sloupce",
   "board-view-lists": "Sloupce",
   "bucket-example": "Například \"O čem sním\"",
   "bucket-example": "Například \"O čem sním\"",
   "cancel": "Zrušit",
   "cancel": "Zrušit",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Může přidávat komentáře pouze do karet.",
   "comment-only-desc": "Může přidávat komentáře pouze do karet.",
   "no-comments": "Žádné komentáře",
   "no-comments": "Žádné komentáře",
   "no-comments-desc": "Nemůže vidět komentáře a aktivity",
   "no-comments-desc": "Nemůže vidět komentáře a aktivity",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Počítač",
   "computer": "Počítač",
   "confirm-subtask-delete-dialog": "Opravdu chcete smazat tento podúkol?",
   "confirm-subtask-delete-dialog": "Opravdu chcete smazat tento podúkol?",
   "confirm-checklist-delete-dialog": "Opravdu chcete smazat tento checklist?",
   "confirm-checklist-delete-dialog": "Opravdu chcete smazat tento checklist?",
@@ -300,9 +304,9 @@
   "error-username-taken": "Toto uživatelské jméno již existuje",
   "error-username-taken": "Toto uživatelské jméno již existuje",
   "error-email-taken": "Tento email byl již použit",
   "error-email-taken": "Tento email byl již použit",
   "export-board": "Exportovat tablo",
   "export-board": "Exportovat tablo",
-  "sort": "Sort",
+  "sort": "řadit",
   "sort-desc": "Click to Sort List",
   "sort-desc": "Click to Sort List",
-  "list-sort-by": "Sort the List By:",
+  "list-sort-by": "řadit seznam podle",
   "list-label-modifiedAt": "Last Access Time",
   "list-label-modifiedAt": "Last Access Time",
   "list-label-title": "Name of the List",
   "list-label-title": "Name of the List",
   "list-label-sort": "Your Manual Order",
   "list-label-sort": "Your Manual Order",
@@ -532,7 +536,7 @@
   "no-name": "(Neznámé)",
   "no-name": "(Neznámé)",
   "Node_version": "Node verze",
   "Node_version": "Node verze",
   "Meteor_version": "Meteor version",
   "Meteor_version": "Meteor version",
-  "MongoDB_version": "MongoDB version",
+  "MongoDB_version": "MongoDB verze",
   "MongoDB_storage_engine": "MongoDB storage engine",
   "MongoDB_storage_engine": "MongoDB storage engine",
   "MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
   "MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
   "OS_Arch": "OS Architektura",
   "OS_Arch": "OS Architektura",
@@ -579,8 +583,9 @@
   "default": "Výchozí",
   "default": "Výchozí",
   "queue": "Fronta",
   "queue": "Fronta",
   "subtask-settings": "Nastavení podúkolů",
   "subtask-settings": "Nastavení podúkolů",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Nastavení podúkolů tabla",
   "boardSubtaskSettingsPopup-title": "Nastavení podúkolů tabla",
-  "show-subtasks-field": "Karty mohou mít podúkoly",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Vložit podúkoly do tohoto tabla",
   "deposit-subtasks-board": "Vložit podúkoly do tohoto tabla",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Ukázat předka na minikartě",
   "show-parent-in-minicard": "Ukázat předka na minikartě",
@@ -699,9 +704,9 @@
   "r-set": "Set",
   "r-set": "Set",
   "r-update": "Update",
   "r-update": "Update",
   "r-datefield": "date field",
   "r-datefield": "date field",
-  "r-df-start-at": "start",
+  "r-df-start-at": "začátek",
   "r-df-due-at": "due",
   "r-df-due-at": "due",
-  "r-df-end-at": "end",
+  "r-df-end-at": "konec",
   "r-df-received-at": "received",
   "r-df-received-at": "received",
   "r-to-current-datetime": "to current date/time",
   "r-to-current-datetime": "to current date/time",
   "r-remove-value-from": "Remove value from",
   "r-remove-value-from": "Remove value from",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 9 - 2
i18n/da.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "Board View",
   "boards": "Boards",
   "boards": "Boards",
   "board-view": "Board View",
   "board-view": "Board View",
   "board-view-cal": "Calendar",
   "board-view-cal": "Calendar",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Lists",
   "board-view-lists": "Lists",
   "bucket-example": "Like “Bucket List” for example",
   "bucket-example": "Like “Bucket List” for example",
   "cancel": "Cancel",
   "cancel": "Cancel",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Can comment on cards only.",
   "comment-only-desc": "Can comment on cards only.",
   "no-comments": "No comments",
   "no-comments": "No comments",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Computer",
   "computer": "Computer",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 13 - 6
i18n/de.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Sichtbarkeit ändern",
   "boardChangeVisibilityPopup-title": "Sichtbarkeit ändern",
   "boardChangeWatchPopup-title": "Beobachtung ändern",
   "boardChangeWatchPopup-title": "Beobachtung ändern",
   "boardMenuPopup-title": "Boardeinstellungen",
   "boardMenuPopup-title": "Boardeinstellungen",
+  "boardChangeViewPopup-title": "Boardansicht",
   "boards": "Boards",
   "boards": "Boards",
   "board-view": "Boardansicht",
   "board-view": "Boardansicht",
   "board-view-cal": "Kalender",
   "board-view-cal": "Kalender",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Einklappen",
   "board-view-lists": "Listen",
   "board-view-lists": "Listen",
   "bucket-example": "z.B. \"Löffelliste\"",
   "bucket-example": "z.B. \"Löffelliste\"",
   "cancel": "Abbrechen",
   "cancel": "Abbrechen",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Kann Karten nur kommentieren.",
   "comment-only-desc": "Kann Karten nur kommentieren.",
   "no-comments": "Keine Kommentare",
   "no-comments": "Keine Kommentare",
   "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.",
   "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.",
+  "worker": "Arbeiter",
+  "worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.",
   "computer": "Computer",
   "computer": "Computer",
   "confirm-subtask-delete-dialog": "Wollen Sie die Teilaufgabe wirklich löschen?",
   "confirm-subtask-delete-dialog": "Wollen Sie die Teilaufgabe wirklich löschen?",
   "confirm-checklist-delete-dialog": "Wollen Sie die Checkliste wirklich löschen?",
   "confirm-checklist-delete-dialog": "Wollen Sie die Checkliste wirklich löschen?",
@@ -301,11 +305,11 @@
   "error-email-taken": "E-Mail wird schon verwendet",
   "error-email-taken": "E-Mail wird schon verwendet",
   "export-board": "Board exportieren",
   "export-board": "Board exportieren",
   "sort": "Sortieren",
   "sort": "Sortieren",
-  "sort-desc": "Zum sortieren der Liste, klicken",
+  "sort-desc": "Zum Sortieren der Liste klicken",
   "list-sort-by": "Sortieren der Liste nach:",
   "list-sort-by": "Sortieren der Liste nach:",
   "list-label-modifiedAt": "Letzte Zugriffszeit",
   "list-label-modifiedAt": "Letzte Zugriffszeit",
   "list-label-title": "Name der Liste",
   "list-label-title": "Name der Liste",
-  "list-label-sort": "Deine manuelle Sortierung",
+  "list-label-sort": "Ihre manuelle Sortierung",
   "list-label-short-modifiedAt": "(Z)",
   "list-label-short-modifiedAt": "(Z)",
   "list-label-short-title": "(N)",
   "list-label-short-title": "(N)",
   "list-label-short-sort": "(M)",
   "list-label-short-sort": "(M)",
@@ -579,8 +583,9 @@
   "default": "Standard",
   "default": "Standard",
   "queue": "Warteschlange",
   "queue": "Warteschlange",
   "subtask-settings": "Einstellungen für Teilaufgaben",
   "subtask-settings": "Einstellungen für Teilaufgaben",
+  "card-settings": "Karten-Einstellungen",
   "boardSubtaskSettingsPopup-title": "Boardeinstellungen für Teilaufgaben",
   "boardSubtaskSettingsPopup-title": "Boardeinstellungen für Teilaufgaben",
-  "show-subtasks-field": "Karten können Teilaufgaben haben",
+  "boardCardSettingsPopup-title": "Karten-Einstellungen",
   "deposit-subtasks-board": "Teilaufgaben in diesem Board ablegen:",
   "deposit-subtasks-board": "Teilaufgaben in diesem Board ablegen:",
   "deposit-subtasks-list": "Zielliste für hier abgelegte Teilaufgaben:",
   "deposit-subtasks-list": "Zielliste für hier abgelegte Teilaufgaben:",
   "show-parent-in-minicard": "Übergeordnetes Element auf Minikarte anzeigen:",
   "show-parent-in-minicard": "Übergeordnetes Element auf Minikarte anzeigen:",
@@ -738,16 +743,18 @@
   "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend",
   "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend",
   "pastdue": "aktuelles Fälligkeitsdatum %s überschritten",
   "pastdue": "aktuelles Fälligkeitsdatum %s überschritten",
   "duenow": "aktuelles Fälligkeitsdatum %s heute",
   "duenow": "aktuelles Fälligkeitsdatum %s heute",
-  "act-newDue": "__list__/__card__ hat seine 1. fällig Erinnerung [__board__]",
+  "act-newDue": "__list__/__card__ hat seine 1. fällige Erinnerung [__board__]",
   "act-withDue": "Erinnerung an Fällikgeit von __card__ [__board__]",
   "act-withDue": "Erinnerung an Fällikgeit von __card__ [__board__]",
   "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend",
   "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend",
   "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei",
   "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei",
   "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt",
   "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt",
-  "act-atUserComment": "Du wurdest in [__board__] __list__/__card__ erwähnt",
+  "act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt",
   "delete-user-confirm-popup": "Sind Sie sicher, dass Sie diesen Account löschen wollen? Die Aktion kann nicht rückgängig gemacht werden.",
   "delete-user-confirm-popup": "Sind Sie sicher, dass Sie diesen Account löschen wollen? Die Aktion kann nicht rückgängig gemacht werden.",
   "accounts-allowUserDelete": "Erlaube Benutzern ihren eigenen Account zu löschen",
   "accounts-allowUserDelete": "Erlaube Benutzern ihren eigenen Account zu löschen",
   "hide-minicard-label-text": "Labeltext auf Minikarte ausblenden",
   "hide-minicard-label-text": "Labeltext auf Minikarte ausblenden",
   "show-desktop-drag-handles": "Desktop-Ziehpunkte anzeigen",
   "show-desktop-drag-handles": "Desktop-Ziehpunkte anzeigen",
   "assignee": "Zugewiesen",
   "assignee": "Zugewiesen",
-  "cardAssigneesPopup-title": "Zugewiesen"
+  "cardAssigneesPopup-title": "Zugewiesen",
+  "addmore-detail": "Eine detailliertere Beschreibung hinzufügen",
+  "show-on-card": "Zeige auf Karte"
 }
 }

+ 9 - 2
i18n/el.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Αλλαγή Ορατότητας",
   "boardChangeVisibilityPopup-title": "Αλλαγή Ορατότητας",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardMenuPopup-title": "Ρυθμίσεις Πίνακα",
   "boardMenuPopup-title": "Ρυθμίσεις Πίνακα",
+  "boardChangeViewPopup-title": "Board View",
   "boards": "Πίνακες",
   "boards": "Πίνακες",
   "board-view": "Board View",
   "board-view": "Board View",
   "board-view-cal": "Ημερολόγιο",
   "board-view-cal": "Ημερολόγιο",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Λίστες",
   "board-view-lists": "Λίστες",
   "bucket-example": "Like “Bucket List” for example",
   "bucket-example": "Like “Bucket List” for example",
   "cancel": "Ακύρωση",
   "cancel": "Ακύρωση",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Can comment on cards only.",
   "comment-only-desc": "Can comment on cards only.",
   "no-comments": "Χωρίς σχόλια",
   "no-comments": "Χωρίς σχόλια",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Υπολογιστής",
   "computer": "Υπολογιστής",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 9 - 2
i18n/en-GB.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "Board View",
   "boards": "Boards",
   "boards": "Boards",
   "board-view": "Board View",
   "board-view": "Board View",
   "board-view-cal": "Calendar",
   "board-view-cal": "Calendar",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Lists",
   "board-view-lists": "Lists",
   "bucket-example": "Like “Bucket List” for example",
   "bucket-example": "Like “Bucket List” for example",
   "cancel": "Cancel",
   "cancel": "Cancel",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Can comment on cards only.",
   "comment-only-desc": "Can comment on cards only.",
   "no-comments": "No comments",
   "no-comments": "No comments",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Computer",
   "computer": "Computer",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 9 - 2
i18n/en.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "Board View",
   "boards": "Boards",
   "boards": "Boards",
   "board-view": "Board View",
   "board-view": "Board View",
   "board-view-cal": "Calendar",
   "board-view-cal": "Calendar",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Lists",
   "board-view-lists": "Lists",
   "bucket-example": "Like “Bucket List” for example",
   "bucket-example": "Like “Bucket List” for example",
   "cancel": "Cancel",
   "cancel": "Cancel",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Can comment on cards only.",
   "comment-only-desc": "Can comment on cards only.",
   "no-comments": "No comments",
   "no-comments": "No comments",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Computer",
   "computer": "Computer",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -752,5 +757,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 37 - 30
i18n/eo.i18n.json

@@ -128,14 +128,16 @@
   "board-private-info": "This board will be <strong>private</strong>.",
   "board-private-info": "This board will be <strong>private</strong>.",
   "board-public-info": "This board will be <strong>public</strong>.",
   "board-public-info": "This board will be <strong>public</strong>.",
   "boardChangeColorPopup-title": "Change Board Background",
   "boardChangeColorPopup-title": "Change Board Background",
-  "boardChangeTitlePopup-title": "Rename Board",
+  "boardChangeTitlePopup-title": "Renomi tavolon",
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeVisibilityPopup-title": "Change Visibility",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardChangeWatchPopup-title": "Change Watch",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "Board View",
   "boards": "Boards",
   "boards": "Boards",
   "board-view": "Board View",
   "board-view": "Board View",
   "board-view-cal": "Calendar",
   "board-view-cal": "Calendar",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Listoj",
   "board-view-lists": "Listoj",
   "bucket-example": "Like “Bucket List” for example",
   "bucket-example": "Like “Bucket List” for example",
   "cancel": "Cancel",
   "cancel": "Cancel",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Can comment on cards only.",
   "comment-only-desc": "Can comment on cards only.",
   "no-comments": "No comments",
   "no-comments": "No comments",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Komputilo",
   "computer": "Komputilo",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -246,8 +250,8 @@
   "custom-field-dropdown-options": "List Options",
   "custom-field-dropdown-options": "List Options",
   "custom-field-dropdown-options-placeholder": "Press enter to add more options",
   "custom-field-dropdown-options-placeholder": "Press enter to add more options",
   "custom-field-dropdown-unknown": "(unknown)",
   "custom-field-dropdown-unknown": "(unknown)",
-  "custom-field-number": "Number",
-  "custom-field-text": "Text",
+  "custom-field-number": "Nombro",
+  "custom-field-text": "Teksto",
   "custom-fields": "Custom Fields",
   "custom-fields": "Custom Fields",
   "date": "Dato",
   "date": "Dato",
   "decline": "Decline",
   "decline": "Decline",
@@ -323,13 +327,13 @@
   "filter-to-selection": "Filter to selection",
   "filter-to-selection": "Filter to selection",
   "advanced-filter-label": "Advanced Filter",
   "advanced-filter-label": "Advanced Filter",
   "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i",
   "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i",
-  "fullname": "Full Name",
+  "fullname": "Plena nomo",
   "header-logo-title": "Go back to your boards page.",
   "header-logo-title": "Go back to your boards page.",
   "hide-system-messages": "Hide system messages",
   "hide-system-messages": "Hide system messages",
   "headerBarCreateBoardPopup-title": "Krei tavolon",
   "headerBarCreateBoardPopup-title": "Krei tavolon",
   "home": "Hejmo",
   "home": "Hejmo",
   "import": "Importi",
   "import": "Importi",
-  "link": "Link",
+  "link": "Ligilo",
   "import-board": "import board",
   "import-board": "import board",
   "import-board-c": "Import board",
   "import-board-c": "Import board",
   "import-board-title-trello": "Import board from Trello",
   "import-board-title-trello": "Import board from Trello",
@@ -431,11 +435,11 @@
   "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.",
   "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.",
   "removeMemberPopup-title": "Remove Member?",
   "removeMemberPopup-title": "Remove Member?",
   "rename": "Renomi",
   "rename": "Renomi",
-  "rename-board": "Rename Board",
+  "rename-board": "Renomi tavolon",
   "restore": "Forigi",
   "restore": "Forigi",
   "save": "Savi",
   "save": "Savi",
   "search": "Serĉi",
   "search": "Serĉi",
-  "rules": "Rules",
+  "rules": "Reguloj",
   "search-cards": "Search from card/list titles and descriptions on this board",
   "search-cards": "Search from card/list titles and descriptions on this board",
   "search-example": "Text to search for?",
   "search-example": "Text to search for?",
   "select-color": "Select Color",
   "select-color": "Select Color",
@@ -470,7 +474,7 @@
   "title": "Titolo",
   "title": "Titolo",
   "tracking": "Tracking",
   "tracking": "Tracking",
   "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
   "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
-  "type": "Type",
+  "type": "Tipo",
   "unassign-member": "Unassign member",
   "unassign-member": "Unassign member",
   "unsaved-description": "You have an unsaved description.",
   "unsaved-description": "You have an unsaved description.",
   "unwatch": "Unwatch",
   "unwatch": "Unwatch",
@@ -551,8 +555,8 @@
   "show-field-on-card": "Show this field on card",
   "show-field-on-card": "Show this field on card",
   "automatically-field-on-card": "Auto create field to all cards",
   "automatically-field-on-card": "Auto create field to all cards",
   "showLabel-field-on-card": "Show field label on minicard",
   "showLabel-field-on-card": "Show field label on minicard",
-  "yes": "Yes",
-  "no": "No",
+  "yes": "Jes",
+  "no": "Ne",
   "accounts": "Accounts",
   "accounts": "Accounts",
   "accounts-allowEmailChange": "Allow Email Change",
   "accounts-allowEmailChange": "Allow Email Change",
   "accounts-allowUserNameChange": "Allow Username Change",
   "accounts-allowUserNameChange": "Allow Username Change",
@@ -576,11 +580,12 @@
   "boardDeletePopup-title": "Delete Board?",
   "boardDeletePopup-title": "Delete Board?",
   "delete-board": "Delete Board",
   "delete-board": "Delete Board",
   "default-subtasks-board": "Subtasks for __board__ board",
   "default-subtasks-board": "Subtasks for __board__ board",
-  "default": "Default",
+  "default": "Defaŭlto",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -600,13 +605,13 @@
   "activity-delete-attach-card": "deleted an attachment",
   "activity-delete-attach-card": "deleted an attachment",
   "activity-set-customfield": "set custom field '%s' to '%s' in %s",
   "activity-set-customfield": "set custom field '%s' to '%s' in %s",
   "activity-unset-customfield": "unset custom field '%s' in %s",
   "activity-unset-customfield": "unset custom field '%s' in %s",
-  "r-rule": "Rule",
+  "r-rule": "Regulo",
   "r-add-trigger": "Add trigger",
   "r-add-trigger": "Add trigger",
   "r-add-action": "Add action",
   "r-add-action": "Add action",
   "r-board-rules": "Board rules",
   "r-board-rules": "Board rules",
-  "r-add-rule": "Add rule",
+  "r-add-rule": "Aldoni regulon",
   "r-view-rule": "View rule",
   "r-view-rule": "View rule",
-  "r-delete-rule": "Delete rule",
+  "r-delete-rule": "Forigi regulon",
   "r-new-rule-name": "New rule title",
   "r-new-rule-name": "New rule title",
   "r-no-rules": "No rules",
   "r-no-rules": "No rules",
   "r-when-a-card": "When a card",
   "r-when-a-card": "When a card",
@@ -615,7 +620,7 @@
   "r-added-to": "added to",
   "r-added-to": "added to",
   "r-removed-from": "Removed from",
   "r-removed-from": "Removed from",
   "r-the-board": "the board",
   "r-the-board": "the board",
-  "r-list": "list",
+  "r-list": "listo",
   "set-filter": "Set Filter",
   "set-filter": "Set Filter",
   "r-moved-to": "Moved to",
   "r-moved-to": "Moved to",
   "r-moved-from": "Moved from",
   "r-moved-from": "Moved from",
@@ -627,7 +632,7 @@
   "r-list-name": "list name",
   "r-list-name": "list name",
   "r-when-a-member": "When a member is",
   "r-when-a-member": "When a member is",
   "r-when-the-member": "When the member",
   "r-when-the-member": "When the member",
-  "r-name": "name",
+  "r-name": "nomo",
   "r-when-a-attach": "When an attachment",
   "r-when-a-attach": "When an attachment",
   "r-when-a-checklist": "When a checklist is",
   "r-when-a-checklist": "When a checklist is",
   "r-when-the-checklist": "When the checklist",
   "r-when-the-checklist": "When the checklist",
@@ -645,12 +650,12 @@
   "r-unarchive": "Restore from Archive",
   "r-unarchive": "Restore from Archive",
   "r-card": "card",
   "r-card": "card",
   "r-add": "Aldoni",
   "r-add": "Aldoni",
-  "r-remove": "Remove",
+  "r-remove": "Forigi",
   "r-label": "label",
   "r-label": "label",
-  "r-member": "member",
+  "r-member": "membro",
   "r-remove-all": "Remove all members from the card",
   "r-remove-all": "Remove all members from the card",
   "r-set-color": "Set color to",
   "r-set-color": "Set color to",
-  "r-checklist": "checklist",
+  "r-checklist": "kontrololisto",
   "r-check-all": "Check all",
   "r-check-all": "Check all",
   "r-uncheck-all": "Uncheck all",
   "r-uncheck-all": "Uncheck all",
   "r-items-check": "items of checklist",
   "r-items-check": "items of checklist",
@@ -660,7 +665,7 @@
   "r-of-checklist": "of checklist",
   "r-of-checklist": "of checklist",
   "r-send-email": "Send an email",
   "r-send-email": "Send an email",
   "r-to": "to",
   "r-to": "to",
-  "r-subject": "subject",
+  "r-subject": "temo",
   "r-rule-details": "Rule details",
   "r-rule-details": "Rule details",
   "r-d-move-to-top-gen": "Move card to top of its list",
   "r-d-move-to-top-gen": "Move card to top of its list",
   "r-d-move-to-top-spec": "Move card to top of list",
   "r-d-move-to-top-spec": "Move card to top of list",
@@ -668,8 +673,8 @@
   "r-d-move-to-bottom-spec": "Move card to bottom of list",
   "r-d-move-to-bottom-spec": "Move card to bottom of list",
   "r-d-send-email": "Send email",
   "r-d-send-email": "Send email",
   "r-d-send-email-to": "to",
   "r-d-send-email-to": "to",
-  "r-d-send-email-subject": "subject",
-  "r-d-send-email-message": "message",
+  "r-d-send-email-subject": "temo",
+  "r-d-send-email-message": "mesaĝo",
   "r-d-archive": "Move card to Archive",
   "r-d-archive": "Move card to Archive",
   "r-d-unarchive": "Restore card from Archive",
   "r-d-unarchive": "Restore card from Archive",
   "r-d-add-label": "Add label",
   "r-d-add-label": "Add label",
@@ -677,18 +682,18 @@
   "r-create-card": "Create new card",
   "r-create-card": "Create new card",
   "r-in-list": "in list",
   "r-in-list": "in list",
   "r-in-swimlane": "in swimlane",
   "r-in-swimlane": "in swimlane",
-  "r-d-add-member": "Add member",
-  "r-d-remove-member": "Remove member",
-  "r-d-remove-all-member": "Remove all member",
+  "r-d-add-member": "Aldoni membron",
+  "r-d-remove-member": "Forigi membron",
+  "r-d-remove-all-member": "Forigi ĉiujn membrojn",
   "r-d-check-all": "Check all items of a list",
   "r-d-check-all": "Check all items of a list",
   "r-d-uncheck-all": "Uncheck all items of a list",
   "r-d-uncheck-all": "Uncheck all items of a list",
   "r-d-check-one": "Check item",
   "r-d-check-one": "Check item",
   "r-d-uncheck-one": "Uncheck item",
   "r-d-uncheck-one": "Uncheck item",
   "r-d-check-of-list": "of checklist",
   "r-d-check-of-list": "of checklist",
-  "r-d-add-checklist": "Add checklist",
-  "r-d-remove-checklist": "Remove checklist",
+  "r-d-add-checklist": "Aldoni kontrololiston",
+  "r-d-remove-checklist": "Forigi kontrololiston",
   "r-by": "by",
   "r-by": "by",
-  "r-add-checklist": "Add checklist",
+  "r-add-checklist": "Aldoni kontrololiston",
   "r-with-items": "with items",
   "r-with-items": "with items",
   "r-items-list": "item1,item2,item3",
   "r-items-list": "item1,item2,item3",
   "r-add-swimlane": "Add swimlane",
   "r-add-swimlane": "Add swimlane",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 9 - 2
i18n/es-AR.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Cambiar Visibilidad",
   "boardChangeVisibilityPopup-title": "Cambiar Visibilidad",
   "boardChangeWatchPopup-title": "Alternar Seguimiento",
   "boardChangeWatchPopup-title": "Alternar Seguimiento",
   "boardMenuPopup-title": "Opciones del Tablero",
   "boardMenuPopup-title": "Opciones del Tablero",
+  "boardChangeViewPopup-title": "Vista de Tablero",
   "boards": "Tableros",
   "boards": "Tableros",
   "board-view": "Vista de Tablero",
   "board-view": "Vista de Tablero",
   "board-view-cal": "Calendario",
   "board-view-cal": "Calendario",
   "board-view-swimlanes": "Calles",
   "board-view-swimlanes": "Calles",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Listas",
   "board-view-lists": "Listas",
   "bucket-example": "Como \"Lista de Contenedores\" por ejemplo",
   "bucket-example": "Como \"Lista de Contenedores\" por ejemplo",
   "cancel": "Cancelar",
   "cancel": "Cancelar",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Puede comentar en tarjetas solamente.",
   "comment-only-desc": "Puede comentar en tarjetas solamente.",
   "no-comments": "Sin comentarios",
   "no-comments": "Sin comentarios",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Computadora",
   "computer": "Computadora",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 10 - 3
i18n/es.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Cambiar visibilidad",
   "boardChangeVisibilityPopup-title": "Cambiar visibilidad",
   "boardChangeWatchPopup-title": "Cambiar vigilancia",
   "boardChangeWatchPopup-title": "Cambiar vigilancia",
   "boardMenuPopup-title": "Preferencias del tablero",
   "boardMenuPopup-title": "Preferencias del tablero",
+  "boardChangeViewPopup-title": "Vista del tablero",
   "boards": "Tableros",
   "boards": "Tableros",
   "board-view": "Vista del tablero",
   "board-view": "Vista del tablero",
   "board-view-cal": "Calendario",
   "board-view-cal": "Calendario",
   "board-view-swimlanes": "Carriles",
   "board-view-swimlanes": "Carriles",
+  "board-view-collapse": "Contraer",
   "board-view-lists": "Listas",
   "board-view-lists": "Listas",
   "bucket-example": "Como “Cosas por hacer” por ejemplo",
   "bucket-example": "Como “Cosas por hacer” por ejemplo",
   "cancel": "Cancelar",
   "cancel": "Cancelar",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Solo puedes comentar en las tarjetas.",
   "comment-only-desc": "Solo puedes comentar en las tarjetas.",
   "no-comments": "No hay comentarios",
   "no-comments": "No hay comentarios",
   "no-comments-desc": "No se pueden mostrar comentarios ni actividades.",
   "no-comments-desc": "No se pueden mostrar comentarios ni actividades.",
+  "worker": "Trabajador",
+  "worker-desc": "Solo puede mover tarjetas, asignarse a la tarjeta y comentar.",
   "computer": "el ordenador",
   "computer": "el ordenador",
   "confirm-subtask-delete-dialog": "¿Seguro que quieres eliminar la subtarea?",
   "confirm-subtask-delete-dialog": "¿Seguro que quieres eliminar la subtarea?",
   "confirm-checklist-delete-dialog": "¿Seguro que quieres eliminar la lista de verificación?",
   "confirm-checklist-delete-dialog": "¿Seguro que quieres eliminar la lista de verificación?",
@@ -579,8 +583,9 @@
   "default": "Por defecto",
   "default": "Por defecto",
   "queue": "Cola",
   "queue": "Cola",
   "subtask-settings": "Preferencias de las subtareas",
   "subtask-settings": "Preferencias de las subtareas",
+  "card-settings": "Preferencias de la tarjeta",
   "boardSubtaskSettingsPopup-title": "Preferencias de las subtareas del tablero",
   "boardSubtaskSettingsPopup-title": "Preferencias de las subtareas del tablero",
-  "show-subtasks-field": "Las tarjetas pueden tener subtareas",
+  "boardCardSettingsPopup-title": "Preferencias de la tarjeta",
   "deposit-subtasks-board": "Depositar subtareas en este tablero:",
   "deposit-subtasks-board": "Depositar subtareas en este tablero:",
   "deposit-subtasks-list": "Lista de destino para subtareas depositadas aquí:",
   "deposit-subtasks-list": "Lista de destino para subtareas depositadas aquí:",
   "show-parent-in-minicard": "Mostrar el padre en una minitarjeta:",
   "show-parent-in-minicard": "Mostrar el padre en una minitarjeta:",
@@ -748,6 +753,8 @@
   "accounts-allowUserDelete": "Permitir a los usuarios eliminar su cuenta",
   "accounts-allowUserDelete": "Permitir a los usuarios eliminar su cuenta",
   "hide-minicard-label-text": "Ocultar el texto de la etiqueta de la minitarjeta",
   "hide-minicard-label-text": "Ocultar el texto de la etiqueta de la minitarjeta",
   "show-desktop-drag-handles": "Mostrar los controles de arrastre del escritorio",
   "show-desktop-drag-handles": "Mostrar los controles de arrastre del escritorio",
-  "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "assignee": "Asignado",
+  "cardAssigneesPopup-title": "Asignado",
+  "addmore-detail": "Añadir una descripción detallada",
+  "show-on-card": "Mostrar en la tarjeta"
 }
 }

+ 9 - 2
i18n/eu.i18n.json

@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Aldatu ikusgaitasuna",
   "boardChangeVisibilityPopup-title": "Aldatu ikusgaitasuna",
   "boardChangeWatchPopup-title": "Aldatu ikuskatzea",
   "boardChangeWatchPopup-title": "Aldatu ikuskatzea",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
+  "boardChangeViewPopup-title": "Board View",
   "boards": "Arbelak",
   "boards": "Arbelak",
   "board-view": "Board View",
   "board-view": "Board View",
   "board-view-cal": "Calendar",
   "board-view-cal": "Calendar",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "Collapse",
   "board-view-lists": "Zerrendak",
   "board-view-lists": "Zerrendak",
   "bucket-example": "Esaterako \"Pertz zerrenda\"",
   "bucket-example": "Esaterako \"Pertz zerrenda\"",
   "cancel": "Utzi",
   "cancel": "Utzi",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Iruzkinak txarteletan soilik egin ditzake",
   "comment-only-desc": "Iruzkinak txarteletan soilik egin ditzake",
   "no-comments": "No comments",
   "no-comments": "No comments",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
+  "worker": "Worker",
+  "worker-desc": "Can only move cards, assign itself to card and comment.",
   "computer": "Ordenagailua",
   "computer": "Ordenagailua",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
@@ -579,8 +583,9 @@
   "default": "Default",
   "default": "Default",
   "queue": "Queue",
   "queue": "Queue",
   "subtask-settings": "Subtasks Settings",
   "subtask-settings": "Subtasks Settings",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
   "boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
-  "show-subtasks-field": "Cards can have subtasks",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-board": "Deposit subtasks to this board:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "deposit-subtasks-list": "Landing list for subtasks deposited here:",
   "show-parent-in-minicard": "Show parent in minicard:",
   "show-parent-in-minicard": "Show parent in minicard:",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 128 - 121
i18n/fa.i18n.json

@@ -1,45 +1,45 @@
 {
 {
   "accept": "پذیرش",
   "accept": "پذیرش",
   "act-activity-notify": "اعلان فعالیت",
   "act-activity-notify": "اعلان فعالیت",
-  "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-createBoard": "created board __board__",
-  "act-createSwimlane": "created swimlane __swimlane__ to board __board__",
-  "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
-  "act-createCustomField": "created custom field __customField__ at board __board__",
-  "act-deleteCustomField": "deleted custom field __customField__ at board __board__",
-  "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-createList": "added list __list__ to board __board__",
-  "act-addBoardMember": "added member __member__ to board __board__",
-  "act-archivedBoard": "Board __board__ moved to Archive",
-  "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive",
-  "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive",
-  "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive",
-  "act-importBoard": "imported board __board__",
-  "act-importCard": "imported card  __card__ to list __list__ at swimlane __swimlane__ at board __board__",
-  "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__",
-  "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__",
-  "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__",
-  "act-removeBoardMember": "removed member __member__ from board __board__",
-  "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
-  "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
+  "act-addAttachment": "ضمیمه __attachment__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
+  "act-deleteAttachment": "ضمیمه __attachment__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
+  "act-addSubtask": "زیر وظیفه __subtask__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
+  "act-addLabel": "برچسب __label__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
+  "act-addedLabel": "برچسب __label__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
+  "act-removeLabel": "برچسب __label__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
+  "act-removedLabel": "برچسب __label__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
+  "act-addChecklist": "سیاهه __checklist__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
+  "act-addChecklistItem": "چک لیست __checklistItem__ را به سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
+  "act-removeChecklist": "سیاهه __checklist__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
+  "act-removeChecklistItem": "چک لیست __checklistItem__ را از سیاهه __checkList__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
+  "act-checkedItem": "چک لیست __checklistItem__ را از سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ تیک زد",
+  "act-uncheckedItem": "چک لیست __checklistItem__ را از سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بدونِ تیک کرد",
+  "act-completeChecklist": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ کامل کرد",
+  "act-uncompleteChecklist": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ ناقص کرد",
+  "act-addComment": "روی کارت __card__ نظر داد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__",
+  "act-editComment": "نظر روی کارت __card__ را ویرایش کرد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__",
+  "act-deleteComment": "نظر روی کارت __card__ را حذف کرد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__",
+  "act-createBoard": "برد __board__ را ایجاد کرد",
+  "act-createSwimlane": "مسیر شناور __swimlane__ را در برد __board__ ایجاد کرد",
+  "act-createCard": "کارت __card__ را در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ ایجاد کرد",
+  "act-createCustomField": "فیلد شخصی __customField__ را در برد __board__ ایجاد کرد",
+  "act-deleteCustomField": "فیلد شخصی __customField__ را در برد __board__ حذف کرد",
+  "act-setCustomField": "فیلد شخصی __customField__ را ویرایش کرد: __customFieldValue__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__",
+  "act-createList": "لیست __list__ را به برد __board__ اضافه کرد",
+  "act-addBoardMember": "عضو __member__ را به برد __board__ اضافه کرد",
+  "act-archivedBoard": "برد __board__ را بایگانی کرد",
+  "act-archivedCard": "کارت __card__ را در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بایگانی کرد",
+  "act-archivedList": "لیست __list__ را در مسیر شناور __swimlane__ در برد __board__ بایگانی کرد",
+  "act-archivedSwimlane": "مسیر شناور __swimlane__ را در برد __board__ بایگانی کرد",
+  "act-importBoard": "برد __board__ را وارد کرد",
+  "act-importCard": "کارت __card__ را به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ وارد کرد",
+  "act-importList": "لیست __list__ را به مسیر شناور __swimlane__ در برد __board__ وارد کرد",
+  "act-joinMember": "عضو __member__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
+  "act-moveCard": "کارت __card__ را در برد __board__ از لیست __oldList__ در مسیر شناور __oldSwimlane__ به لیست __list__ در مسیر شناور __swimlane__ منتقل کرد",
+  "act-moveCardToOtherBoard": "کارت __card__ را از لیست __oldList__ در مسیر شناور __oldSwimlane__ در برد __oldBoard__ به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ منتقل کرد",
+  "act-removeBoardMember": "عضو __member__ را از برد __board__ حذف کرد",
+  "act-restoredCard": "کارت __card__ را به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بازگرداند",
+  "act-unjoinMember": "عضو __member__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
   "act-withBoardTitle": "__board__",
   "act-withBoardTitle": "__board__",
   "act-withCardTitle": "[__board__] __card__",
   "act-withCardTitle": "[__board__] __card__",
   "actions": "اعمال",
   "actions": "اعمال",
@@ -64,21 +64,21 @@
   "activity-unchecked-item": "چک نشده  %s در چک لیست %s از %s",
   "activity-unchecked-item": "چک نشده  %s در چک لیست %s از %s",
   "activity-checklist-added": "سیاهه به %s اضافه شد",
   "activity-checklist-added": "سیاهه به %s اضافه شد",
   "activity-checklist-removed": "از چک لیست حذف گردید",
   "activity-checklist-removed": "از چک لیست حذف گردید",
-  "activity-checklist-completed": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
+  "activity-checklist-completed": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ کامل کرد",
   "activity-checklist-uncompleted": "تمام نشده ها در چک لیست %s از %s",
   "activity-checklist-uncompleted": "تمام نشده ها در چک لیست %s از %s",
   "activity-checklist-item-added": "added checklist item to '%s' in %s",
   "activity-checklist-item-added": "added checklist item to '%s' in %s",
   "activity-checklist-item-removed": "حذف شده از چک لیست  '%s' در %s",
   "activity-checklist-item-removed": "حذف شده از چک لیست  '%s' در %s",
   "add": "افزودن",
   "add": "افزودن",
   "activity-checked-item-card": "چک شده  %s در چک لیست %s",
   "activity-checked-item-card": "چک شده  %s در چک لیست %s",
   "activity-unchecked-item-card": "چک نشده  %s در چک لیست %s",
   "activity-unchecked-item-card": "چک نشده  %s در چک لیست %s",
-  "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
+  "activity-checklist-completed-card": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ کامل کرد",
   "activity-checklist-uncompleted-card": "چک لیست تمام نشده %s",
   "activity-checklist-uncompleted-card": "چک لیست تمام نشده %s",
-  "activity-editComment": "edited comment %s",
-  "activity-deleteComment": "deleted comment %s",
+  "activity-editComment": "نظر ویرایش شد %s",
+  "activity-deleteComment": "نظر حذف شد %s",
   "add-attachment": "افزودن ضمیمه",
   "add-attachment": "افزودن ضمیمه",
   "add-board": "افزودن برد",
   "add-board": "افزودن برد",
   "add-card": "افزودن کارت",
   "add-card": "افزودن کارت",
-  "add-swimlane": "Add Swimlane",
+  "add-swimlane": "اضافه کردن مسیر شناور",
   "add-subtask": "افزودن زیر وظیفه",
   "add-subtask": "افزودن زیر وظیفه",
   "add-checklist": "افزودن چک لیست",
   "add-checklist": "افزودن چک لیست",
   "add-checklist-item": "افزودن مورد به سیاهه",
   "add-checklist-item": "افزودن مورد به سیاهه",
@@ -89,11 +89,11 @@
   "added": "اضافه گردید",
   "added": "اضافه گردید",
   "addMemberPopup-title": "اعضا",
   "addMemberPopup-title": "اعضا",
   "admin": "مدیر",
   "admin": "مدیر",
-  "admin-desc": "امکان دیدن و ویرایش کارتها،پاک کردن کاربران و تغییر تنظیمات برای تخته",
+  "admin-desc": "امکان دیدن و ویرایش کارتها، پاک کردن کاربران و تغییر تنظیمات برای برد.",
   "admin-announcement": "اعلان",
   "admin-announcement": "اعلان",
   "admin-announcement-active": "اعلان سراسری فعال",
   "admin-announcement-active": "اعلان سراسری فعال",
   "admin-announcement-title": "اعلان از سوی مدیر",
   "admin-announcement-title": "اعلان از سوی مدیر",
-  "all-boards": "تمام تخته‌ها",
+  "all-boards": "تمام بردها",
   "and-n-other-card": "و __count__ کارت دیگر",
   "and-n-other-card": "و __count__ کارت دیگر",
   "and-n-other-card_plural": "و __count__ کارت دیگر",
   "and-n-other-card_plural": "و __count__ کارت دیگر",
   "apply": "اعمال",
   "apply": "اعمال",
@@ -108,7 +108,7 @@
   "archiveBoardPopup-title": "انتقال برد به آرشیو؟",
   "archiveBoardPopup-title": "انتقال برد به آرشیو؟",
   "archived-items": "بایگانی",
   "archived-items": "بایگانی",
   "archived-boards": "برد های داخل آرشیو",
   "archived-boards": "برد های داخل آرشیو",
-  "restore-board": "بازیابی تخته",
+  "restore-board": "بازیابی برد",
   "no-archived-boards": "هیچ بردی داخل آرشیو نیست",
   "no-archived-boards": "هیچ بردی داخل آرشیو نیست",
   "archives": "بایگانی",
   "archives": "بایگانی",
   "template": "Template",
   "template": "Template",
@@ -119,23 +119,25 @@
   "attachment-delete-pop": "حذف پیوست دایمی و بی بازگشت خواهد بود.",
   "attachment-delete-pop": "حذف پیوست دایمی و بی بازگشت خواهد بود.",
   "attachmentDeletePopup-title": "آیا می خواهید ضمیمه را حذف کنید؟",
   "attachmentDeletePopup-title": "آیا می خواهید ضمیمه را حذف کنید؟",
   "attachments": "ضمائم",
   "attachments": "ضمائم",
-  "auto-watch": "اضافه شدن خودکار دیده بانی تخته زمانی که ایجاد می شوند",
+  "auto-watch": "اضافه شدن خودکار دیده‌بانی بردها زمانی که ایجاد می‌شوند",
   "avatar-too-big": "تصویر کاربر بسیار بزرگ است ـ حداکثر۷۰ کیلوبایت ـ",
   "avatar-too-big": "تصویر کاربر بسیار بزرگ است ـ حداکثر۷۰ کیلوبایت ـ",
   "back": "بازگشت",
   "back": "بازگشت",
   "board-change-color": "تغییر رنگ",
   "board-change-color": "تغییر رنگ",
   "board-nb-stars": "%s ستاره",
   "board-nb-stars": "%s ستاره",
-  "board-not-found": "تخته مورد نظر پیدا نشد",
-  "board-private-info": "این تخته <strong>خصوصی</strong> خواهد بود.",
-  "board-public-info": "این تخته <strong>عمومی</strong> خواهد بود.",
-  "boardChangeColorPopup-title": "تغییر پس زمینه تخته",
-  "boardChangeTitlePopup-title": "تغییر نام تخته",
+  "board-not-found": "برد مورد نظر پیدا نشد",
+  "board-private-info": "این برد <strong>خصوصی</strong> خواهد بود.",
+  "board-public-info": "این برد <strong>عمومی</strong> خواهد بود.",
+  "boardChangeColorPopup-title": "تغییر پس زمینه برد",
+  "boardChangeTitlePopup-title": "تغییر نام برد",
   "boardChangeVisibilityPopup-title": "تغییر وضعیت نمایش",
   "boardChangeVisibilityPopup-title": "تغییر وضعیت نمایش",
   "boardChangeWatchPopup-title": "تغییر دیده بانی",
   "boardChangeWatchPopup-title": "تغییر دیده بانی",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
-  "boards": "تخته‌ها",
-  "board-view": "نمایش تخته",
+  "boardChangeViewPopup-title": "نمایش برد",
+  "boards": "بردها",
+  "board-view": "نمایش برد",
   "board-view-cal": "تقویم",
   "board-view-cal": "تقویم",
   "board-view-swimlanes": "Swimlanes",
   "board-view-swimlanes": "Swimlanes",
+  "board-view-collapse": "بستن",
   "board-view-lists": "فهرست‌ها",
   "board-view-lists": "فهرست‌ها",
   "bucket-example": "برای مثال چیزی شبیه \"لیست سبدها\"",
   "bucket-example": "برای مثال چیزی شبیه \"لیست سبدها\"",
   "cancel": "انصراف",
   "cancel": "انصراف",
@@ -170,7 +172,7 @@
   "casSignIn": "ورود با استفاده از CAS",
   "casSignIn": "ورود با استفاده از CAS",
   "cardType-card": "کارت",
   "cardType-card": "کارت",
   "cardType-linkedCard": "کارت‌های مرتبط",
   "cardType-linkedCard": "کارت‌های مرتبط",
-  "cardType-linkedBoard": "تخته‌های مرتبط",
+  "cardType-linkedBoard": "برد مرتبط",
   "change": "تغییر",
   "change": "تغییر",
   "change-avatar": "تغییر تصویر",
   "change-avatar": "تغییر تصویر",
   "change-password": "تغییر کلمه عبور",
   "change-password": "تغییر کلمه عبور",
@@ -221,6 +223,8 @@
   "comment-only-desc": "فقط می‌تواند روی کارت‌ها نظر دهد.",
   "comment-only-desc": "فقط می‌تواند روی کارت‌ها نظر دهد.",
   "no-comments": "هیچ کامنتی موجود نیست",
   "no-comments": "هیچ کامنتی موجود نیست",
   "no-comments-desc": "نظرات و فعالیت ها را نمی توان دید.",
   "no-comments-desc": "نظرات و فعالیت ها را نمی توان دید.",
+  "worker": "کارگر",
+  "worker-desc": "تنها میتوانید کارت ها را جابجا کنید، این را به یک کارت اضافه کنید.",
   "computer": "رایانه",
   "computer": "رایانه",
   "confirm-subtask-delete-dialog": "از حذف این زیر وظیفه اطمینان دارید؟",
   "confirm-subtask-delete-dialog": "از حذف این زیر وظیفه اطمینان دارید؟",
   "confirm-checklist-delete-dialog": "مطمئنا چک لیست پاک شود؟",
   "confirm-checklist-delete-dialog": "مطمئنا چک لیست پاک شود؟",
@@ -232,8 +236,8 @@
   "copyChecklistToManyCardsPopup-instructions": "عنوان و توضیحات کارت مقصد در این قالب JSON",
   "copyChecklistToManyCardsPopup-instructions": "عنوان و توضیحات کارت مقصد در این قالب JSON",
   "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]",
   "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]",
   "create": "ایجاد",
   "create": "ایجاد",
-  "createBoardPopup-title": "ایجاد تخته",
-  "chooseBoardSourcePopup-title": "بارگذاری تخته",
+  "createBoardPopup-title": "ایجاد برد",
+  "chooseBoardSourcePopup-title": "بارگذاری برد",
   "createLabelPopup-title": "ایجاد برچسب",
   "createLabelPopup-title": "ایجاد برچسب",
   "createCustomField": "ایجاد فیلد",
   "createCustomField": "ایجاد فیلد",
   "createCustomFieldPopup-title": "ایجاد فیلد",
   "createCustomFieldPopup-title": "ایجاد فیلد",
@@ -281,16 +285,16 @@
   "email-invalid": "رایانامه نادرست",
   "email-invalid": "رایانامه نادرست",
   "email-invite": "دعوت از طریق رایانامه",
   "email-invite": "دعوت از طریق رایانامه",
   "email-invite-subject": "__inviter__ برای شما دعوت نامه ارسال کرده است",
   "email-invite-subject": "__inviter__ برای شما دعوت نامه ارسال کرده است",
-  "email-invite-text": "__User__ عزیز\n __inviter__ شما را به عضویت تخته  \"__board__\" برای همکاری دعوت کرده است.\nلطفا لینک زیر را دنبال کنید، باتشکر:\n__url__",
+  "email-invite-text": "__User__ عزیز\n __inviter__ شما را به عضویت برد \"__board__\" برای همکاری دعوت کرده است.\nلطفا لینک زیر را دنبال کنید، باتشکر:\n__url__",
   "email-resetPassword-subject": "تنظیم مجدد کلمه عبور در __siteName__",
   "email-resetPassword-subject": "تنظیم مجدد کلمه عبور در __siteName__",
   "email-resetPassword-text": "سلام __user__\nجهت تنظیم مجدد کلمه عبور آدرس زیر را دنبال نمایید، باتشکر:\n__url__",
   "email-resetPassword-text": "سلام __user__\nجهت تنظیم مجدد کلمه عبور آدرس زیر را دنبال نمایید، باتشکر:\n__url__",
   "email-sent": "نامه الکترونیکی فرستاده شد",
   "email-sent": "نامه الکترونیکی فرستاده شد",
   "email-verifyEmail-subject": "تایید آدرس الکترونیکی شما در __siteName__",
   "email-verifyEmail-subject": "تایید آدرس الکترونیکی شما در __siteName__",
   "email-verifyEmail-text": "سلام __user__\nبه منظور تایید آدرس الکترونیکی حساب خود، آدرس زیر را دنبال نمایید، باتشکر:\n__url__.",
   "email-verifyEmail-text": "سلام __user__\nبه منظور تایید آدرس الکترونیکی حساب خود، آدرس زیر را دنبال نمایید، باتشکر:\n__url__.",
   "enable-wip-limit": "Enable WIP Limit",
   "enable-wip-limit": "Enable WIP Limit",
-  "error-board-doesNotExist": "تخته مورد نظر وجود ندارد",
-  "error-board-notAdmin": "شما جهت انجام آن باید مدیر تخته باشید",
-  "error-board-notAMember": "شما انجام آن ،اید عضو این تخته باشید.",
+  "error-board-doesNotExist": "برد مورد نظر وجود ندارد",
+  "error-board-notAdmin": "شما جهت انجام آن باید مدیر برد باشید",
+  "error-board-notAMember": "شما برای انجام آن، باید عضو این برد باشید",
   "error-json-malformed": "متن درغالب صحیح Json نمی باشد.",
   "error-json-malformed": "متن درغالب صحیح Json نمی باشد.",
   "error-json-schema": "داده های Json شما، شامل اطلاعات صحیح در غالب درستی نمی باشد.",
   "error-json-schema": "داده های Json شما، شامل اطلاعات صحیح در غالب درستی نمی باشد.",
   "error-list-doesNotExist": "این لیست موجود نیست",
   "error-list-doesNotExist": "این لیست موجود نیست",
@@ -299,40 +303,40 @@
   "error-user-notCreated": "این کاربر ایجاد نشده است",
   "error-user-notCreated": "این کاربر ایجاد نشده است",
   "error-username-taken": "این نام کاربری استفاده شده است",
   "error-username-taken": "این نام کاربری استفاده شده است",
   "error-email-taken": "رایانامه توسط گیرنده دریافت شده است",
   "error-email-taken": "رایانامه توسط گیرنده دریافت شده است",
-  "export-board": "انتقال به بیرون تخته",
-  "sort": "Sort",
-  "sort-desc": "Click to Sort List",
-  "list-sort-by": "Sort the List By:",
-  "list-label-modifiedAt": "Last Access Time",
-  "list-label-title": "Name of the List",
-  "list-label-sort": "Your Manual Order",
+  "export-board": "انتقال به بیرون برد",
+  "sort": "مرتب سازی",
+  "sort-desc": "برای مرتب سازی لیست کلیک کنید",
+  "list-sort-by": "مرتب سازی لیست بر اساس:",
+  "list-label-modifiedAt": "زمان دسترسی قبلی",
+  "list-label-title": "نام لیست",
+  "list-label-sort": "دلخواه شما",
   "list-label-short-modifiedAt": "(L)",
   "list-label-short-modifiedAt": "(L)",
   "list-label-short-title": "(N)",
   "list-label-short-title": "(N)",
   "list-label-short-sort": "(M)",
   "list-label-short-sort": "(M)",
-  "filter": "صافی ـFilterـ",
-  "filter-cards": "Filter Cards or Lists",
-  "list-filter-label": "Filter List by Title",
-  "filter-clear": "حذف صافی ـFilterـ",
+  "filter": "صافی ـ فیلتر ـ",
+  "filter-cards": "فیلتر کارت‌ها یا لیست‌ها",
+  "list-filter-label": "فیلتر لیست بر اساس عنوان",
+  "filter-clear": "حذف صافی ـ فیلتر ـ",
   "filter-no-label": "بدون برچسب",
   "filter-no-label": "بدون برچسب",
   "filter-no-member": "بدون عضو",
   "filter-no-member": "بدون عضو",
   "filter-no-custom-fields": "هیچ فیلدشخصی ای وجود ندارد",
   "filter-no-custom-fields": "هیچ فیلدشخصی ای وجود ندارد",
-  "filter-show-archive": "Show archived lists",
-  "filter-hide-empty": "Hide empty lists",
-  "filter-on": "صافی ـFilterـ فعال است",
-  "filter-on-desc": "شما صافی ـFilterـ برای کارتهای تخته را روشن کرده اید. جهت ویرایش کلیک نمایید.",
-  "filter-to-selection": "صافی ـFilterـ برای موارد انتخابی",
-  "advanced-filter-label": "صافی پیشرفته",
+  "filter-show-archive": "نمایش لیست‌های آرشیو شده",
+  "filter-hide-empty": "مخفی کردن لیست‌های خالی",
+  "filter-on": "صافی ـ فیلتر ـ فعال است",
+  "filter-on-desc": "شما درحال صافی ـ فیلتر ـ کارت‌های این برد هستید. برای ویرایش فیلتر کلیک نمایید.",
+  "filter-to-selection": "صافی ـ فیلتر ـ برای موارد انتخابی",
+  "advanced-filter-label": "صافی ـ فیلتر ـ پیشرفته",
   "advanced-filter-description": "فیلتر پیشرفته اجازه می دهد تا برای نوشتن رشته حاوی اپراتورهای زیر: ==! = <=> = && || () یک فضای به عنوان یک جداساز بین اپراتورها استفاده می شود. با تایپ کردن نام ها و مقادیر آنها می توانید برای تمام زمینه های سفارشی فیلتر کنید. به عنوان مثال: Field1 == Value1. نکته: اگر فیلدها یا مقادیر حاوی فضاها باشند، شما باید آنها را به یک نقل قول کپسول کنید. برای مثال: 'فیلد 1' == 'مقدار 1'. برای تک تک کاراکترهای کنترل (\\\\) که می توانید از آنها استفاده کنید، می توانید از \\ استفاده کنید. به عنوان مثال: Field1 == I \\ 'm. همچنین شما می توانید شرایط مختلف را ترکیب کنید. برای مثال: F1 == V1 || F1 == V2. به طور معمول همه اپراتورها از چپ به راست تفسیر می شوند. شما می توانید سفارش را با قرار دادن براکت تغییر دهید. برای مثال: F1 == V1 && (F2 == V2 || F2 == V3). همچنین می توانید فیلدهای متنی را با استفاده از regex جستجو کنید: F1 == /Tes.*/i",
   "advanced-filter-description": "فیلتر پیشرفته اجازه می دهد تا برای نوشتن رشته حاوی اپراتورهای زیر: ==! = <=> = && || () یک فضای به عنوان یک جداساز بین اپراتورها استفاده می شود. با تایپ کردن نام ها و مقادیر آنها می توانید برای تمام زمینه های سفارشی فیلتر کنید. به عنوان مثال: Field1 == Value1. نکته: اگر فیلدها یا مقادیر حاوی فضاها باشند، شما باید آنها را به یک نقل قول کپسول کنید. برای مثال: 'فیلد 1' == 'مقدار 1'. برای تک تک کاراکترهای کنترل (\\\\) که می توانید از آنها استفاده کنید، می توانید از \\ استفاده کنید. به عنوان مثال: Field1 == I \\ 'm. همچنین شما می توانید شرایط مختلف را ترکیب کنید. برای مثال: F1 == V1 || F1 == V2. به طور معمول همه اپراتورها از چپ به راست تفسیر می شوند. شما می توانید سفارش را با قرار دادن براکت تغییر دهید. برای مثال: F1 == V1 && (F2 == V2 || F2 == V3). همچنین می توانید فیلدهای متنی را با استفاده از regex جستجو کنید: F1 == /Tes.*/i",
   "fullname": "نام و نام خانوادگی",
   "fullname": "نام و نام خانوادگی",
-  "header-logo-title": "بازگشت به صفحه تخته.",
+  "header-logo-title": "بازگشت به صفحه بردها.",
   "hide-system-messages": "عدم نمایش پیامهای سیستمی",
   "hide-system-messages": "عدم نمایش پیامهای سیستمی",
-  "headerBarCreateBoardPopup-title": "ایجاد تخته",
+  "headerBarCreateBoardPopup-title": "ایجاد برد",
   "home": "خانه",
   "home": "خانه",
   "import": "وارد کردن",
   "import": "وارد کردن",
   "link": "ارتباط",
   "link": "ارتباط",
-  "import-board": "وارد کردن تخته",
-  "import-board-c": "وارد کردن تخته",
-  "import-board-title-trello": "وارد کردن تخته از Trello",
+  "import-board": "وارد کردن برد",
+  "import-board-c": "وارد کردن برد",
+  "import-board-title-trello": "وارد کردن برد از Trello",
   "import-board-title-wekan": "بارگذاری برد ها از آخرین خروجی",
   "import-board-title-wekan": "بارگذاری برد ها از آخرین خروجی",
   "import-sandstorm-backup-warning": "قبل از بررسی این داده ها را از صفحه اصلی صادر شده یا Trello وارد نمیکنید این دانه دوباره باز می شود و یا دوباره باز می شود، یا برد را پیدا نمی کنید، این بدان معنی است که از دست دادن اطلاعات.",
   "import-sandstorm-backup-warning": "قبل از بررسی این داده ها را از صفحه اصلی صادر شده یا Trello وارد نمیکنید این دانه دوباره باز می شود و یا دوباره باز می شود، یا برد را پیدا نمی کنید، این بدان معنی است که از دست دادن اطلاعات.",
   "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
   "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
@@ -353,7 +357,7 @@
   "invalid-time": "زمان نامعتبر",
   "invalid-time": "زمان نامعتبر",
   "invalid-user": "کاربر نامعتیر",
   "invalid-user": "کاربر نامعتیر",
   "joined": "متصل",
   "joined": "متصل",
-  "just-invited": "هم اکنون، شما به این تخته دعوت شده اید.",
+  "just-invited": "هم اکنون، شما به این برد دعوت شده‌اید.",
   "keyboard-shortcuts": "میانبر کلیدها",
   "keyboard-shortcuts": "میانبر کلیدها",
   "label-create": "ایجاد برچسب",
   "label-create": "ایجاد برچسب",
   "label-default": "%s برچسب(پیش فرض)",
   "label-default": "%s برچسب(پیش فرض)",
@@ -361,7 +365,7 @@
   "labels": "برچسب ها",
   "labels": "برچسب ها",
   "language": "زبان",
   "language": "زبان",
   "last-admin-desc": "شما نمی توانید نقش ـroleـ را تغییر دهید چراکه باید حداقل یک مدیری وجود داشته باشد.",
   "last-admin-desc": "شما نمی توانید نقش ـroleـ را تغییر دهید چراکه باید حداقل یک مدیری وجود داشته باشد.",
-  "leave-board": "خروج از تخته",
+  "leave-board": "خروج از برد",
   "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.",
   "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.",
   "leaveBoardPopup-title": "Leave Board ?",
   "leaveBoardPopup-title": "Leave Board ?",
   "link-card": "ارجاع به این کارت",
   "link-card": "ارجاع به این کارت",
@@ -394,8 +398,8 @@
   "multi-selection": "امکان چند انتخابی",
   "multi-selection": "امکان چند انتخابی",
   "multi-selection-on": "حالت چند انتخابی روشن است",
   "multi-selection-on": "حالت چند انتخابی روشن است",
   "muted": "بی صدا",
   "muted": "بی صدا",
-  "muted-info": "شما هیچگاه از تغییرات این تخته مطلع نخواهید شد",
-  "my-boards": "تخته‌های من",
+  "muted-info": "شما هیچگاه از تغییرات این برد مطلع نخواهید شد",
+  "my-boards": "بردهای من",
   "name": "نام",
   "name": "نام",
   "no-archived-cards": "هیچ کارتی در آرشیو موجود نمی باشد",
   "no-archived-cards": "هیچ کارتی در آرشیو موجود نمی باشد",
   "no-archived-lists": "هیچ لیستی در آرشیو موجود نمی باشد",
   "no-archived-lists": "هیچ لیستی در آرشیو موجود نمی باشد",
@@ -405,7 +409,7 @@
   "normal-desc": "امکان نمایش و تنظیم کارت بدون امکان تغییر تنظیمات",
   "normal-desc": "امکان نمایش و تنظیم کارت بدون امکان تغییر تنظیمات",
   "not-accepted-yet": "دعوت نامه هنوز پذیرفته نشده است",
   "not-accepted-yet": "دعوت نامه هنوز پذیرفته نشده است",
   "notify-participate": "اطلاع رسانی از هرگونه تغییر در کارتهایی که ایجاد کرده اید ویا عضو آن هستید",
   "notify-participate": "اطلاع رسانی از هرگونه تغییر در کارتهایی که ایجاد کرده اید ویا عضو آن هستید",
-  "notify-watch": "اطلاع رسانی از هرگونه تغییر در تخته، لیست یا کارتهایی که از آنها دیده بانی میکنید",
+  "notify-watch": "اطلاع رسانی از هرگونه تغییر در بردها، لیست‌ها یا کارت‌هایی که از آنها دیده‌بانی می‌کنید",
   "optional": "انتخابی",
   "optional": "انتخابی",
   "or": "یا",
   "or": "یا",
   "page-maybe-private": "این صفحه ممکن است خصوصی باشد. شما با<a href='%s'>ورود</a> می‌توانید آن را ببینید.",
   "page-maybe-private": "این صفحه ممکن است خصوصی باشد. شما با<a href='%s'>ورود</a> می‌توانید آن را ببینید.",
@@ -417,26 +421,26 @@
   "previewAttachedImagePopup-title": "پیش‌نمایش",
   "previewAttachedImagePopup-title": "پیش‌نمایش",
   "previewClipboardImagePopup-title": "پیش‌نمایش",
   "previewClipboardImagePopup-title": "پیش‌نمایش",
   "private": "خصوصی",
   "private": "خصوصی",
-  "private-desc": "این تخته خصوصی است. فقط تنها افراد اضافه شده به آن  می توانند مشاهده و ویرایش کنند.",
+  "private-desc": "این برد خصوصی است. فقط افراد اضافه شده به برد می‌توانند مشاهده و ویرایش کنند.",
   "profile": "حساب کاربری",
   "profile": "حساب کاربری",
   "public": "عمومی",
   "public": "عمومی",
-  "public-desc": "این تخته عمومی است. برای هر کسی با آدرس ویا جستجو درموتورها مانند گوگل قابل مشاهده است . فقط افرادی که به آن اضافه شده اند  امکان ویرایش دارند.",
-  "quick-access-description": "جهت افزودن یک تخته به اینجا،آنرا ستاره دار نمایید.",
+  "public-desc": "این برد عمومی است. برای هر کسی با آدرس و یا جستجو در موتورها مانند گوگل قابل مشاهده است. فقط افرادی که به برد اضافه شده‌اند امکان ویرایش دارند.",
+  "quick-access-description": "جهت افزودن یک برد به اینجا، آن را ستاره دار نمایید.",
   "remove-cover": "حذف کاور",
   "remove-cover": "حذف کاور",
-  "remove-from-board": "حذف از تخته",
+  "remove-from-board": "حذف از برد",
   "remove-label": "حذف برچسب",
   "remove-label": "حذف برچسب",
   "listDeletePopup-title": "حذف فهرست؟",
   "listDeletePopup-title": "حذف فهرست؟",
   "remove-member": "حذف عضو",
   "remove-member": "حذف عضو",
   "remove-member-from-card": "حذف از کارت",
   "remove-member-from-card": "حذف از کارت",
-  "remove-member-pop": "آیا __name__ (__username__) را از __boardTitle__ حذف می کنید? کاربر از تمام کارت ها در این تخته حذف خواهد شد و آنها ازین اقدام مطلع خواهند شد.",
+  "remove-member-pop": "آیا __name__ (__username__) را از __boardTitle__ حذف می‌کنید؟ کاربر از تمام کارت‌ها در این برد حذف خواهد شد. آنها از این اقدام مطلع خواهند شد.",
   "removeMemberPopup-title": "آیا می خواهید کاربر را حذف کنید؟",
   "removeMemberPopup-title": "آیا می خواهید کاربر را حذف کنید؟",
   "rename": "تغیر نام",
   "rename": "تغیر نام",
-  "rename-board": "تغییر نام تخته",
+  "rename-board": "تغییر نام برد",
   "restore": "بازیابی",
   "restore": "بازیابی",
   "save": "ذخیره",
   "save": "ذخیره",
   "search": "جستجو",
   "search": "جستجو",
   "rules": "قوانین",
   "rules": "قوانین",
-  "search-cards": "Search from card/list titles and descriptions on this board",
+  "search-cards": "جتستجو از عنوان لیست/کارت ها و توضیحات در این برد",
   "search-example": "متن مورد جستجو؟",
   "search-example": "متن مورد جستجو؟",
   "select-color": "انتخاب رنگ",
   "select-color": "انتخاب رنگ",
   "set-wip-limit-value": "تعیین بیشینه تعداد وظایف در این فهرست",
   "set-wip-limit-value": "تعیین بیشینه تعداد وظایف در این فهرست",
@@ -444,22 +448,22 @@
   "shortcut-assign-self": "اختصاص خود به کارت فعلی",
   "shortcut-assign-self": "اختصاص خود به کارت فعلی",
   "shortcut-autocomplete-emoji": "تکمیل خودکار شکلکها",
   "shortcut-autocomplete-emoji": "تکمیل خودکار شکلکها",
   "shortcut-autocomplete-members": "تکمیل خودکار کاربرها",
   "shortcut-autocomplete-members": "تکمیل خودکار کاربرها",
-  "shortcut-clear-filters": "حذف تمامی صافی ـfilterـ",
+  "shortcut-clear-filters": "حذف تمامی صافی‌ها ـ فیلترها ـ",
   "shortcut-close-dialog": "بستن محاوره",
   "shortcut-close-dialog": "بستن محاوره",
   "shortcut-filter-my-cards": "کارت های من",
   "shortcut-filter-my-cards": "کارت های من",
   "shortcut-show-shortcuts": "بالا آوردن میانبر این لیست",
   "shortcut-show-shortcuts": "بالا آوردن میانبر این لیست",
-  "shortcut-toggle-filterbar": "ضامن نوار جداکننده صافی ـfilterـ",
-  "shortcut-toggle-sidebar": "ضامن نوار جداکننده تخته",
+  "shortcut-toggle-filterbar": "ضامن نوار جداکننده صافی ـ فیلتر ـ",
+  "shortcut-toggle-sidebar": "ضامن نوار جداکننده برد",
   "show-cards-minimum-count": "نمایش تعداد کارتها اگر لیست شامل بیشتراز",
   "show-cards-minimum-count": "نمایش تعداد کارتها اگر لیست شامل بیشتراز",
   "sidebar-open": "بازکردن جداکننده",
   "sidebar-open": "بازکردن جداکننده",
   "sidebar-close": "بستن جداکننده",
   "sidebar-close": "بستن جداکننده",
   "signupPopup-title": "ایجاد یک کاربر",
   "signupPopup-title": "ایجاد یک کاربر",
-  "star-board-title": "برای ستاره دادن، کلیک کنید.این در بالای لیست تخته های شما نمایش داده خواهد شد.",
-  "starred-boards": "تخته های ستاره دار",
-  "starred-boards-description": "تخته های ستاره دار در بالای لیست تخته ها نمایش داده می شود.",
+  "star-board-title": "برای ستاره دار کردن این برد کلیک کنید. این در بالای لیست بردهای شما نمایش داده خواهد شد.",
+  "starred-boards": "بردهای ستاره دار",
+  "starred-boards-description": "بردهای ستاره دار در بالای لیست بردها نمایش داده می‌شود.",
   "subscribe": "عضوشدن",
   "subscribe": "عضوشدن",
   "team": "تیم",
   "team": "تیم",
-  "this-board": "این تخته",
+  "this-board": "این برد",
   "this-card": "این کارت",
   "this-card": "این کارت",
   "spent-time-hours": "زمان صرف شده (ساعت)",
   "spent-time-hours": "زمان صرف شده (ساعت)",
   "overtime-hours": "Overtime (hours)",
   "overtime-hours": "Overtime (hours)",
@@ -482,8 +486,8 @@
   "warn-list-archived": "اخطار:این کارت در یک لیست در آرشیو موجود می باشد",
   "warn-list-archived": "اخطار:این کارت در یک لیست در آرشیو موجود می باشد",
   "watch": "دیده بانی",
   "watch": "دیده بانی",
   "watching": "درحال دیده بانی",
   "watching": "درحال دیده بانی",
-  "watching-info": "شما از هر تغییری دراین تخته آگاه خواهید شد",
-  "welcome-board": "به این تخته خوش آمدید",
+  "watching-info": "شما از هر تغییری در این برد آگاه خواهید شد",
+  "welcome-board": "به این برد خوش آمدید",
   "welcome-swimlane": "Milestone 1",
   "welcome-swimlane": "Milestone 1",
   "welcome-list1": "پایه ای ها",
   "welcome-list1": "پایه ای ها",
   "welcome-list2": "پیشرفته",
   "welcome-list2": "پیشرفته",
@@ -501,7 +505,7 @@
   "disable-self-registration": "‌غیرفعال‌سازی خودثبت‌نامی",
   "disable-self-registration": "‌غیرفعال‌سازی خودثبت‌نامی",
   "invite": "دعوت",
   "invite": "دعوت",
   "invite-people": "دعوت از افراد",
   "invite-people": "دعوت از افراد",
-  "to-boards": "به تخته(ها)",
+  "to-boards": "به برد(ها)",
   "email-addresses": "نشانی رایانامه",
   "email-addresses": "نشانی رایانامه",
   "smtp-host-description": "آدرس سرور SMTP ای که پست الکترونیکی شما برروی آن است",
   "smtp-host-description": "آدرس سرور SMTP ای که پست الکترونیکی شما برروی آن است",
   "smtp-port-description": "شماره درگاه ـPortـ ای که سرور SMTP شما جهت ارسال از آن استفاده می کند",
   "smtp-port-description": "شماره درگاه ـPortـ ای که سرور SMTP شما جهت ارسال از آن استفاده می کند",
@@ -520,21 +524,21 @@
   "email-smtp-test-text": "با موفقیت، یک رایانامه را فرستادید",
   "email-smtp-test-text": "با موفقیت، یک رایانامه را فرستادید",
   "error-invitation-code-not-exist": "چنین کد دعوتی یافت نشد",
   "error-invitation-code-not-exist": "چنین کد دعوتی یافت نشد",
   "error-notAuthorized": "شما مجاز به دیدن این صفحه نیستید.",
   "error-notAuthorized": "شما مجاز به دیدن این صفحه نیستید.",
-  "webhook-title": "Webhook Name",
-  "webhook-token": "Token (Optional for Authentication)",
+  "webhook-title": "نام وب‌هوک",
+  "webhook-token": "توکن",
   "outgoing-webhooks": "Outgoing Webhooks",
   "outgoing-webhooks": "Outgoing Webhooks",
-  "bidirectional-webhooks": "Two-Way Webhooks",
+  "bidirectional-webhooks": "وب‌هوک two-way",
   "outgoingWebhooksPopup-title": "Outgoing Webhooks",
   "outgoingWebhooksPopup-title": "Outgoing Webhooks",
   "boardCardTitlePopup-title": "فیلتر موضوع کارت",
   "boardCardTitlePopup-title": "فیلتر موضوع کارت",
-  "disable-webhook": "Disable This Webhook",
-  "global-webhook": "Global Webhooks",
+  "disable-webhook": "حذف این وب‌هوک",
+  "global-webhook": "وب‌هوک‌های سراسری",
   "new-outgoing-webhook": "New Outgoing Webhook",
   "new-outgoing-webhook": "New Outgoing Webhook",
   "no-name": "(ناشناخته)",
   "no-name": "(ناشناخته)",
   "Node_version": "نسخه Node",
   "Node_version": "نسخه Node",
   "Meteor_version": "Meteor version",
   "Meteor_version": "Meteor version",
-  "MongoDB_version": "MongoDB version",
-  "MongoDB_storage_engine": "MongoDB storage engine",
-  "MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
+  "MongoDB_version": "ورژن MongoDB",
+  "MongoDB_storage_engine": "موتور ذخیره سازی MongoDB",
+  "MongoDB_Oplog_enabled": "MongoDB Oplog فعال",
   "OS_Arch": "OS Arch",
   "OS_Arch": "OS Arch",
   "OS_Cpus": "OS CPU Count",
   "OS_Cpus": "OS CPU Count",
   "OS_Freemem": "OS Free Memory",
   "OS_Freemem": "OS Free Memory",
@@ -573,14 +577,15 @@
   "requested-by": "تقاضا شده توسط",
   "requested-by": "تقاضا شده توسط",
   "board-delete-notice": "حذف دائمی است شما تمام لیست ها، کارت ها و اقدامات مرتبط با این برد را از دست خواهید داد.",
   "board-delete-notice": "حذف دائمی است شما تمام لیست ها، کارت ها و اقدامات مرتبط با این برد را از دست خواهید داد.",
   "delete-board-confirm-popup": "تمام لیست ها، کارت ها، برچسب ها و فعالیت ها حذف خواهند شد و شما نمی توانید محتوای برد را بازیابی کنید. هیچ واکنشی وجود ندارد",
   "delete-board-confirm-popup": "تمام لیست ها، کارت ها، برچسب ها و فعالیت ها حذف خواهند شد و شما نمی توانید محتوای برد را بازیابی کنید. هیچ واکنشی وجود ندارد",
-  "boardDeletePopup-title": "حذف تخته؟",
-  "delete-board": "حذف تخته",
+  "boardDeletePopup-title": "حذف برد؟",
+  "delete-board": "حذف برد",
   "default-subtasks-board": "ریزکار برای __board__ برد",
   "default-subtasks-board": "ریزکار برای __board__ برد",
   "default": "پیش‌فرض",
   "default": "پیش‌فرض",
   "queue": "صف",
   "queue": "صف",
   "subtask-settings": "تنظیمات ریزکارها",
   "subtask-settings": "تنظیمات ریزکارها",
+  "card-settings": "Card Settings",
   "boardSubtaskSettingsPopup-title": "تنظیمات ریزکار های برد",
   "boardSubtaskSettingsPopup-title": "تنظیمات ریزکار های برد",
-  "show-subtasks-field": "کارت می تواند ریزکار داشته باشد",
+  "boardCardSettingsPopup-title": "Card Settings",
   "deposit-subtasks-board": "افزودن ریزکار به برد:",
   "deposit-subtasks-board": "افزودن ریزکار به برد:",
   "deposit-subtasks-list": "لیست برای ریزکار های افزوده شده",
   "deposit-subtasks-list": "لیست برای ریزکار های افزوده شده",
   "show-parent-in-minicard": "نمایش خانواده در ریز کارت",
   "show-parent-in-minicard": "نمایش خانواده در ریز کارت",
@@ -695,10 +700,10 @@
   "r-swimlane-name": "نام مسیر شناور",
   "r-swimlane-name": "نام مسیر شناور",
   "r-board-note": "نکته: برای نمایش موارد ممکن کادر را خالی بگذارید.",
   "r-board-note": "نکته: برای نمایش موارد ممکن کادر را خالی بگذارید.",
   "r-checklist-note": "نکته: چک‌لیست‌ها باید توسط کاما از یک‌دیگر جدا شوند.",
   "r-checklist-note": "نکته: چک‌لیست‌ها باید توسط کاما از یک‌دیگر جدا شوند.",
-  "r-when-a-card-is-moved": "دمانی که یک کارت به لیست دیگری منتقل شد",
+  "r-when-a-card-is-moved": "زمانی که یک کارت به لیست دیگری منتقل شد",
   "r-set": "Set",
   "r-set": "Set",
-  "r-update": "Update",
-  "r-datefield": "date field",
+  "r-update": "به روز رسانی",
+  "r-datefield": "تاریخ",
   "r-df-start-at": "شروع",
   "r-df-start-at": "شروع",
   "r-df-due-at": "ناشی از",
   "r-df-due-at": "ناشی از",
   "r-df-end-at": "پایان",
   "r-df-end-at": "پایان",
@@ -749,5 +754,7 @@
   "hide-minicard-label-text": "Hide minicard label text",
   "hide-minicard-label-text": "Hide minicard label text",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "show-desktop-drag-handles": "Show desktop drag handles",
   "assignee": "Assignee",
   "assignee": "Assignee",
-  "cardAssigneesPopup-title": "Assignee"
+  "cardAssigneesPopup-title": "Assignee",
+  "addmore-detail": "Add a more detailed description",
+  "show-on-card": "Show on Card"
 }
 }

+ 40 - 33
i18n/fi.i18n.json

@@ -4,10 +4,10 @@
   "act-addAttachment": "lisätty liite __attachment__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-addAttachment": "lisätty liite __attachment__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-deleteAttachment": "poistettu liite __attachment__ kortilla __card__ listalla __list__ swimlanella  __swimlane__ taululla __board__",
   "act-deleteAttachment": "poistettu liite __attachment__ kortilla __card__ listalla __list__ swimlanella  __swimlane__ taululla __board__",
   "act-addSubtask": "lisätty alitehtävä __subtask__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-addSubtask": "lisätty alitehtävä __subtask__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
-  "act-addLabel": "Lisätty tunniste __label__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
-  "act-addedLabel": "Lisätty tunniste __label__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
-  "act-removeLabel": "Poistettu tunniste __label__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
-  "act-removedLabel": "Poistettu tunniste __label__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
+  "act-addLabel": "Lisätty nimilappu __label__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
+  "act-addedLabel": "Lisätty nimilappu __label__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
+  "act-removeLabel": "Poistettu nimilappu __label__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
+  "act-removedLabel": "Poistettu nimilappu __label__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-addChecklist": "lisätty tarkistuslista __checklist__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-addChecklist": "lisätty tarkistuslista __checklist__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-addChecklistItem": "lisätty tarkistuslistan kohta __checklistItem__ tarkistuslistalle __checklist__ kortilla __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-addChecklistItem": "lisätty tarkistuslistan kohta __checklistItem__ tarkistuslistalle __checklist__ kortilla __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-removeChecklist": "poistettu tarkistuslista __checklist__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
   "act-removeChecklist": "poistettu tarkistuslista __checklist__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
@@ -83,7 +83,7 @@
   "add-checklist": "Lisää tarkistuslista",
   "add-checklist": "Lisää tarkistuslista",
   "add-checklist-item": "Lisää kohta tarkistuslistaan",
   "add-checklist-item": "Lisää kohta tarkistuslistaan",
   "add-cover": "Lisää kansi",
   "add-cover": "Lisää kansi",
-  "add-label": "Lisää tunniste",
+  "add-label": "Lisää nimilappu",
   "add-list": "Lisää lista",
   "add-list": "Lisää lista",
   "add-members": "Lisää jäseniä",
   "add-members": "Lisää jäseniä",
   "added": "Lisätty",
   "added": "Lisätty",
@@ -132,10 +132,12 @@
   "boardChangeVisibilityPopup-title": "Muokkaa näkyvyyttä",
   "boardChangeVisibilityPopup-title": "Muokkaa näkyvyyttä",
   "boardChangeWatchPopup-title": "Muokkaa seuraamista",
   "boardChangeWatchPopup-title": "Muokkaa seuraamista",
   "boardMenuPopup-title": "Tauluasetukset",
   "boardMenuPopup-title": "Tauluasetukset",
+  "boardChangeViewPopup-title": "Taulunäkymä",
   "boards": "Taulut",
   "boards": "Taulut",
   "board-view": "Taulunäkymä",
   "board-view": "Taulunäkymä",
   "board-view-cal": "Kalenteri",
   "board-view-cal": "Kalenteri",
   "board-view-swimlanes": "Swimlanet",
   "board-view-swimlanes": "Swimlanet",
+  "board-view-collapse": "Pienennä",
   "board-view-lists": "Listat",
   "board-view-lists": "Listat",
   "bucket-example": "Kuten “Laatikko lista” esimerkiksi",
   "bucket-example": "Kuten “Laatikko lista” esimerkiksi",
   "cancel": "Peruuta",
   "cancel": "Peruuta",
@@ -150,9 +152,9 @@
   "card-spent": "Käytetty aika",
   "card-spent": "Käytetty aika",
   "card-edit-attachments": "Muokkaa liitetiedostoja",
   "card-edit-attachments": "Muokkaa liitetiedostoja",
   "card-edit-custom-fields": "Muokkaa mukautettuja kenttiä",
   "card-edit-custom-fields": "Muokkaa mukautettuja kenttiä",
-  "card-edit-labels": "Muokkaa tunnisteita",
+  "card-edit-labels": "Muokkaa nimilappuja",
   "card-edit-members": "Muokkaa jäseniä",
   "card-edit-members": "Muokkaa jäseniä",
-  "card-labels-title": "Muokkaa kortin tunnisteita.",
+  "card-labels-title": "Muokkaa kortin nimilappuja.",
   "card-members-title": "Lisää tai poista taulun jäseniä tältä kortilta.",
   "card-members-title": "Lisää tai poista taulun jäseniä tältä kortilta.",
   "card-start": "Alkaa",
   "card-start": "Alkaa",
   "card-start-on": "Alkaa",
   "card-start-on": "Alkaa",
@@ -161,7 +163,7 @@
   "cardCustomFieldsPopup-title": "Muokkaa mukautettuja kenttiä",
   "cardCustomFieldsPopup-title": "Muokkaa mukautettuja kenttiä",
   "cardDeletePopup-title": "Poista kortti?",
   "cardDeletePopup-title": "Poista kortti?",
   "cardDetailsActionsPopup-title": "Korttitoimet",
   "cardDetailsActionsPopup-title": "Korttitoimet",
-  "cardLabelsPopup-title": "Tunnisteet",
+  "cardLabelsPopup-title": "Nimilaput",
   "cardMembersPopup-title": "Jäsenet",
   "cardMembersPopup-title": "Jäsenet",
   "cardMorePopup-title": "Lisää",
   "cardMorePopup-title": "Lisää",
   "cardTemplatePopup-title": "Luo malli",
   "cardTemplatePopup-title": "Luo malli",
@@ -221,6 +223,8 @@
   "comment-only-desc": "Voi vain kommentoida kortteja",
   "comment-only-desc": "Voi vain kommentoida kortteja",
   "no-comments": "Ei kommentteja",
   "no-comments": "Ei kommentteja",
   "no-comments-desc": "Ei voi nähdä kommentteja ja toimintaa.",
   "no-comments-desc": "Ei voi nähdä kommentteja ja toimintaa.",
+  "worker": "Työntekijä",
+  "worker-desc": "Voi vain siirtää kortteja, ilmoittautua kortin käsittelijäksi ja kommentoida.",
   "computer": "Tietokone",
   "computer": "Tietokone",
   "confirm-subtask-delete-dialog": "Haluatko varmasti poistaa alitehtävän?",
   "confirm-subtask-delete-dialog": "Haluatko varmasti poistaa alitehtävän?",
   "confirm-checklist-delete-dialog": "Haluatko varmasti poistaa tarkistuslistan?",
   "confirm-checklist-delete-dialog": "Haluatko varmasti poistaa tarkistuslistan?",
@@ -234,7 +238,7 @@
   "create": "Luo",
   "create": "Luo",
   "createBoardPopup-title": "Luo taulu",
   "createBoardPopup-title": "Luo taulu",
   "chooseBoardSourcePopup-title": "Tuo taulu",
   "chooseBoardSourcePopup-title": "Tuo taulu",
-  "createLabelPopup-title": "Luo tunniste",
+  "createLabelPopup-title": "Luo nimilappu",
   "createCustomField": "Luo kenttä",
   "createCustomField": "Luo kenttä",
   "createCustomFieldPopup-title": "Luo kenttä",
   "createCustomFieldPopup-title": "Luo kenttä",
   "current": "nykyinen",
   "current": "nykyinen",
@@ -254,9 +258,9 @@
   "default-avatar": "Oletusprofiilikuva",
   "default-avatar": "Oletusprofiilikuva",
   "delete": "Poista",
   "delete": "Poista",
   "deleteCustomFieldPopup-title": "Poista mukautettu kenttä?",
   "deleteCustomFieldPopup-title": "Poista mukautettu kenttä?",
-  "deleteLabelPopup-title": "Poista tunniste?",
+  "deleteLabelPopup-title": "Poista nimilappu?",
   "description": "Kuvaus",
   "description": "Kuvaus",
-  "disambiguateMultiLabelPopup-title": "Yksikäsitteistä tunnistetoiminta",
+  "disambiguateMultiLabelPopup-title": "Yksikäsitteistä nimilapputoiminta",
   "disambiguateMultiMemberPopup-title": "Yksikäsitteistä jäsentoiminta",
   "disambiguateMultiMemberPopup-title": "Yksikäsitteistä jäsentoiminta",
   "discard": "Hylkää",
   "discard": "Hylkää",
   "done": "Valmis",
   "done": "Valmis",
@@ -270,7 +274,7 @@
   "editCardDueDatePopup-title": "Muokkaa eräpäivää",
   "editCardDueDatePopup-title": "Muokkaa eräpäivää",
   "editCustomFieldPopup-title": "Muokkaa kenttää",
   "editCustomFieldPopup-title": "Muokkaa kenttää",
   "editCardSpentTimePopup-title": "Muuta käytettyä aikaa",
   "editCardSpentTimePopup-title": "Muuta käytettyä aikaa",
-  "editLabelPopup-title": "Muokkaa tunnistetta",
+  "editLabelPopup-title": "Muokkaa nimilappua",
   "editNotificationPopup-title": "Muokkaa ilmoituksia",
   "editNotificationPopup-title": "Muokkaa ilmoituksia",
   "editProfilePopup-title": "Muokkaa profiilia",
   "editProfilePopup-title": "Muokkaa profiilia",
   "email": "Sähköposti",
   "email": "Sähköposti",
@@ -313,7 +317,7 @@
   "filter-cards": "Suodata kortit tai listat",
   "filter-cards": "Suodata kortit tai listat",
   "list-filter-label": "Suodata listat otsikon mukaan",
   "list-filter-label": "Suodata listat otsikon mukaan",
   "filter-clear": "Poista suodatin",
   "filter-clear": "Poista suodatin",
-  "filter-no-label": "Ei tunnistetta",
+  "filter-no-label": "Ei nimilappua",
   "filter-no-member": "Ei jäseniä",
   "filter-no-member": "Ei jäseniä",
   "filter-no-custom-fields": "Ei mukautettuja kenttiä",
   "filter-no-custom-fields": "Ei mukautettuja kenttiä",
   "filter-show-archive": "Näytä arkistoidut listat",
   "filter-show-archive": "Näytä arkistoidut listat",
@@ -355,10 +359,10 @@
   "joined": "liittyi",
   "joined": "liittyi",
   "just-invited": "Sinut on juuri kutsuttu tälle taululle",
   "just-invited": "Sinut on juuri kutsuttu tälle taululle",
   "keyboard-shortcuts": "Pikanäppäimet",
   "keyboard-shortcuts": "Pikanäppäimet",
-  "label-create": "Luo tunniste",
-  "label-default": "%s tunniste (oletus)",
-  "label-delete-pop": "Tätä ei voi peruuttaa. Tämä poistaa tämän tunnisteen kaikista korteista ja tuhoaa sen historian.",
-  "labels": "Tunnisteet",
+  "label-create": "Luo nimilappu",
+  "label-default": "%s nimilappu (oletus)",
+  "label-delete-pop": "Tätä ei voi peruuttaa. Tämä poistaa tämän nimilapun kaikista korteista ja tuhoaa sen historian.",
+  "labels": "Nimilaput",
   "language": "Kieli",
   "language": "Kieli",
   "last-admin-desc": "Et voi vaihtaa rooleja koska täytyy olla olemassa ainakin yksi ylläpitäjä.",
   "last-admin-desc": "Et voi vaihtaa rooleja koska täytyy olla olemassa ainakin yksi ylläpitäjä.",
   "leave-board": "Jää pois taululta",
   "leave-board": "Jää pois taululta",
@@ -424,7 +428,7 @@
   "quick-access-description": "Merkkaa taulu tähdellä lisätäksesi pikavalinta tähän palkkiin.",
   "quick-access-description": "Merkkaa taulu tähdellä lisätäksesi pikavalinta tähän palkkiin.",
   "remove-cover": "Poista kansi",
   "remove-cover": "Poista kansi",
   "remove-from-board": "Poista taululta",
   "remove-from-board": "Poista taululta",
-  "remove-label": "Poista tunniste",
+  "remove-label": "Poista nimilappu",
   "listDeletePopup-title": "Poista lista?",
   "listDeletePopup-title": "Poista lista?",
   "remove-member": "Poista jäsen",
   "remove-member": "Poista jäsen",
   "remove-member-from-card": "Poista kortilta",
   "remove-member-from-card": "Poista kortilta",
@@ -550,7 +554,7 @@
   "seconds": "sekuntia",
   "seconds": "sekuntia",
   "show-field-on-card": "Näytä tämä kenttä kortilla",
   "show-field-on-card": "Näytä tämä kenttä kortilla",
   "automatically-field-on-card": "Luo kenttä automaattisesti kaikille korteille",
   "automatically-field-on-card": "Luo kenttä automaattisesti kaikille korteille",
-  "showLabel-field-on-card": "Näytä kentän tunniste minikortilla",
+  "showLabel-field-on-card": "Näytä kentän nimilappu minikortilla",
   "yes": "Kyllä",
   "yes": "Kyllä",
   "no": "Ei",
   "no": "Ei",
   "accounts": "Tilit",
   "accounts": "Tilit",
@@ -572,15 +576,16 @@
   "assigned-by": "Tehtävänantaja",
   "assigned-by": "Tehtävänantaja",
   "requested-by": "Pyytäjä",
   "requested-by": "Pyytäjä",
   "board-delete-notice": "Poistaminen on lopullista. Menetät kaikki listat, kortit ja toimet tällä taululla.",
   "board-delete-notice": "Poistaminen on lopullista. Menetät kaikki listat, kortit ja toimet tällä taululla.",
-  "delete-board-confirm-popup": "Kaikki listat, kortit, tunnisteet ja toimet poistetaan ja et pysty palauttamaan taulun sisältöä. Tätä ei voi peruuttaa.",
+  "delete-board-confirm-popup": "Kaikki listat, kortit, nimilaput ja toimet poistetaan ja et pysty palauttamaan taulun sisältöä. Tätä ei voi peruuttaa.",
   "boardDeletePopup-title": "Poista taulu?",
   "boardDeletePopup-title": "Poista taulu?",
   "delete-board": "Poista taulu",
   "delete-board": "Poista taulu",
   "default-subtasks-board": "Alitehtävät taululle __board__",
   "default-subtasks-board": "Alitehtävät taululle __board__",
   "default": "Oletus",
   "default": "Oletus",
   "queue": "Jono",
   "queue": "Jono",
   "subtask-settings": "Alitehtävä-asetukset",
   "subtask-settings": "Alitehtävä-asetukset",
+  "card-settings": "Kortin asetukset",
   "boardSubtaskSettingsPopup-title": "Taulualitehtävien asetukset",
   "boardSubtaskSettingsPopup-title": "Taulualitehtävien asetukset",
-  "show-subtasks-field": "Korteilla voi olla alitehtäviä",
+  "boardCardSettingsPopup-title": "Kortin asetukset",
   "deposit-subtasks-board": "Talleta alitehtävät tälle taululle:",
   "deposit-subtasks-board": "Talleta alitehtävät tälle taululle:",
   "deposit-subtasks-list": "Laskeutumislista alatehtäville tallennettu tänne:",
   "deposit-subtasks-list": "Laskeutumislista alatehtäville tallennettu tänne:",
   "show-parent-in-minicard": "Näytä ylätehtävä minikortilla:",
   "show-parent-in-minicard": "Näytä ylätehtävä minikortilla:",
@@ -592,11 +597,11 @@
   "parent-card": "Ylätehtäväkortti",
   "parent-card": "Ylätehtäväkortti",
   "source-board": "Lähdetaulu",
   "source-board": "Lähdetaulu",
   "no-parent": "Älä näytä ylätehtävää",
   "no-parent": "Älä näytä ylätehtävää",
-  "activity-added-label": "lisätty tunniste '%s' kohteeseen %s",
-  "activity-removed-label": "poistettu tunniste '%s' kohteesta %s",
+  "activity-added-label": "lisätty nimilappu '%s' kohteeseen %s",
+  "activity-removed-label": "poistettu nimilappu '%s' kohteesta %s",
   "activity-delete-attach": "poistettu liitetiedosto kohteesta %s",
   "activity-delete-attach": "poistettu liitetiedosto kohteesta %s",
-  "activity-added-label-card": "lisätty tunniste '%s'",
-  "activity-removed-label-card": "poistettu tunniste '%s'",
+  "activity-added-label-card": "lisätty nimilappu '%s'",
+  "activity-removed-label-card": "poistettu nimilappu '%s'",
   "activity-delete-attach-card": "poistettu liitetiedosto",
   "activity-delete-attach-card": "poistettu liitetiedosto",
   "activity-set-customfield": "asetettu mukautettu kentän '%s' sisällöksi '%s' kortilla %s",
   "activity-set-customfield": "asetettu mukautettu kentän '%s' sisällöksi '%s' kortilla %s",
   "activity-unset-customfield": "poistettu mukautettu kenttä '%s' kortilla %s",
   "activity-unset-customfield": "poistettu mukautettu kenttä '%s' kortilla %s",
@@ -622,8 +627,8 @@
   "r-archived": "Siirretty Arkistoon",
   "r-archived": "Siirretty Arkistoon",
   "r-unarchived": "Palautettu Arkistosta",
   "r-unarchived": "Palautettu Arkistosta",
   "r-a-card": "kortti",
   "r-a-card": "kortti",
-  "r-when-a-label-is": "Kun tunniste on",
-  "r-when-the-label": "Kun tunniste on",
+  "r-when-a-label-is": "Kun nimilappu on",
+  "r-when-the-label": "Kun nimilappu on",
   "r-list-name": "listan nimi",
   "r-list-name": "listan nimi",
   "r-when-a-member": "Kun jäsen on",
   "r-when-a-member": "Kun jäsen on",
   "r-when-the-member": "Kun käyttäjä",
   "r-when-the-member": "Kun käyttäjä",
@@ -646,7 +651,7 @@
   "r-card": "kortti",
   "r-card": "kortti",
   "r-add": "Lisää",
   "r-add": "Lisää",
   "r-remove": "Poista",
   "r-remove": "Poista",
-  "r-label": "tunniste",
+  "r-label": "nimilappu",
   "r-member": "jäsen",
   "r-member": "jäsen",
   "r-remove-all": "Poista kaikki jäsenet kortilta",
   "r-remove-all": "Poista kaikki jäsenet kortilta",
   "r-set-color": "Aseta väriksi",
   "r-set-color": "Aseta väriksi",
@@ -672,8 +677,8 @@
   "r-d-send-email-message": "viesti",
   "r-d-send-email-message": "viesti",
   "r-d-archive": "Siirrä kortti Arkistoon",
   "r-d-archive": "Siirrä kortti Arkistoon",
   "r-d-unarchive": "Palauta kortti Arkistosta",
   "r-d-unarchive": "Palauta kortti Arkistosta",
-  "r-d-add-label": "Lisää tunniste",
-  "r-d-remove-label": "Poista tunniste",
+  "r-d-add-label": "Lisää nimilappu",
+  "r-d-remove-label": "Poista nimilappu",
   "r-create-card": "Luo uusi kortti",
   "r-create-card": "Luo uusi kortti",
   "r-in-list": "listassa",
   "r-in-list": "listassa",
   "r-in-swimlane": "swimlanessa",
   "r-in-swimlane": "swimlanessa",
@@ -746,8 +751,10 @@
   "act-atUserComment": "Sinut mainittiin [__board__] __list__/__card__",
   "act-atUserComment": "Sinut mainittiin [__board__] __list__/__card__",
   "delete-user-confirm-popup": "Haluatko varmasti poistaa tämän käyttäjätilin? Tätä ei voi peruuttaa.",
   "delete-user-confirm-popup": "Haluatko varmasti poistaa tämän käyttäjätilin? Tätä ei voi peruuttaa.",
   "accounts-allowUserDelete": "Salli käyttäjien poistaa tilinsä itse",
   "accounts-allowUserDelete": "Salli käyttäjien poistaa tilinsä itse",
-  "hide-minicard-label-text": "Piilota minikortin tunniste teksti",
+  "hide-minicard-label-text": "Piilota minikortin nimilappu teksti",
   "show-desktop-drag-handles": "Näytä työpöydän vedon kahvat",
   "show-desktop-drag-handles": "Näytä työpöydän vedon kahvat",
-  "assignee": "Valtuutettu",
-  "cardAssigneesPopup-title": "Valtuutettu"
+  "assignee": "Käsittelijä",
+  "cardAssigneesPopup-title": "Käsittelijä",
+  "addmore-detail": "Lisää tarkempi kuvaus",
+  "show-on-card": "Näytä kortilla"
 }
 }

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