diff options
26 files changed, 387 insertions, 224 deletions
diff --git a/.eslintrc.json b/.eslintrc.json index 939f7b46..87c2e2cf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -74,6 +74,7 @@ "Avatars": true, "BlazeComponent": false, "BlazeLayout": false, + "CollectionHooks": false, "DocHead": false, "ESSearchResults": false, "FastRender": false, diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders index 61ee3132..dacc2c0d 100644 --- a/.meteor/.finished-upgraders +++ b/.meteor/.finished-upgraders @@ -10,3 +10,4 @@ notices-for-facebook-graph-api-2 1.2.0-meteor-platform-split 1.2.0-cordova-changes 1.2.0-breaking-changes +1.3.0-split-minifiers-package diff --git a/.meteor/.gitignore b/.meteor/.gitignore index 40830374..501f92e4 100644 --- a/.meteor/.gitignore +++ b/.meteor/.gitignore @@ -1 +1,2 @@ +dev_bundle local diff --git a/.meteor/packages b/.meteor/packages index f6e11fcf..e57bdb19 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -8,7 +8,8 @@ meteor-base # Build system ecmascript stylus -standard-minifiers +standard-minifier-css +standard-minifier-js mquandalle:jade # Polyfills diff --git a/.meteor/release b/.meteor/release index aa93b69d..e5b4dc18 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.2.2-faster-rebuilds.0 +METEOR@1.3.4.4 diff --git a/.meteor/versions b/.meteor/versions index 6c8051a7..843cd138 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,26 +1,26 @@ 3stack:presence@1.0.5 -accounts-base@1.2.2 -accounts-password@1.1.4 -aldeed:collection2@2.8.0 -aldeed:collection2-core@1.0.0 +accounts-base@1.2.8 +accounts-password@1.1.12 +aldeed:collection2@2.9.1 +aldeed:collection2-core@1.1.1 aldeed:schema-deny@1.0.1 aldeed:schema-index@1.0.1 aldeed:simple-schema@1.5.3 -alethes:pages@1.8.4 -arillo:flow-router-helpers@0.4.7 -audit-argument-checks@1.0.4 -autoupdate@1.2.4 -babel-compiler@5.8.25-faster-rebuild.0 -babel-runtime@0.1.4 -base64@1.0.4 -binary-heap@1.0.4 -blaze@2.1.3 -blaze-html-templates@1.0.1 -blaze-tools@1.0.4 -boilerplate-generator@1.0.4 -caching-compiler@1.0.1-faster-rebuild.0 -caching-html-compiler@1.0.2 -callback-hook@1.0.4 +alethes:pages@1.8.6 +allow-deny@1.0.5 +arillo:flow-router-helpers@0.5.2 +audit-argument-checks@1.0.7 +autoupdate@1.2.11 +babel-compiler@6.8.4 +babel-runtime@0.1.9_1 +base64@1.0.9 +binary-heap@1.0.9 +blaze@2.1.8 +blaze-tools@1.0.9 +boilerplate-generator@1.0.9 +caching-compiler@1.0.6 +caching-html-compiler@1.0.6 +callback-hook@1.0.9 cfs:access-point@0.1.49 cfs:base-package@0.0.30 cfs:collection@0.5.5 @@ -28,7 +28,7 @@ cfs:collection-filters@0.2.4 cfs:data-man@0.0.6 cfs:file@0.1.17 cfs:gridfs@0.0.33 -cfs:http-methods@0.0.30 +cfs:http-methods@0.0.32 cfs:http-publish@0.0.13 cfs:power-queue@0.9.11 cfs:reactive-list@0.0.9 @@ -38,115 +38,120 @@ cfs:storage-adapter@0.2.3 cfs:tempstore@0.1.5 cfs:upload-http@0.0.20 cfs:worker@0.1.4 -check@1.1.0 +check@1.2.3 chuangbo:cookie@1.1.0 -coffeescript@1.0.12-faster-rebuild.0 -cosmos:browserify@0.9.3 -cottz:publish-relations@2.0.0 +coffeescript@1.1.3 +cottz:publish-relations@2.0.6 dburles:collection-helpers@1.0.4 -ddp@1.2.2 -ddp-client@1.2.2-faster-rebuild.0 -ddp-common@1.2.2 -ddp-rate-limiter@1.0.0 -ddp-server@1.2.3-faster-rebuild.0 -deps@1.0.9 -diff-sequence@1.0.1 -ecmascript@0.1.7-faster-rebuild.0 -ecmascript-runtime@0.2.6 -ejson@1.0.7 -email@1.0.9-faster-rebuild.0 -es5-shim@4.1.14 -fastclick@1.0.7 +ddp@1.2.5 +ddp-client@1.2.9 +ddp-common@1.2.6 +ddp-rate-limiter@1.0.5 +ddp-server@1.2.9 +deps@1.0.12 +diff-sequence@1.0.6 +ecmascript@0.4.7 +ecmascript-runtime@0.2.12 +ejson@1.0.12 +email@1.0.15 +es5-shim@4.5.13 +fastclick@1.0.12 fortawesome:fontawesome@4.5.0 -geojson-utils@1.0.4 -hot-code-push@1.0.0 -html-tools@1.0.5 -htmljs@1.0.5 -http@1.1.2-faster-rebuild.0 -id-map@1.0.4 +geojson-utils@1.0.9 +hot-code-push@1.0.4 +html-tools@1.0.10 +htmljs@1.0.10 +http@1.1.8 +id-map@1.0.8 idmontie:migrations@1.0.1 -jquery@1.11.4 +jquery@1.11.9 kadira:blaze-layout@2.3.0 -kadira:dochead@1.4.0 -kadira:flow-router@2.10.1 -kenton:accounts-sandstorm@0.1.8 -launch-screen@1.0.4 -livedata@1.0.15 -localstorage@1.0.5 -logging@1.0.9-faster-rebuild.0 +kadira:dochead@1.5.0 +kadira:flow-router@2.12.1 +kenton:accounts-sandstorm@0.5.1 +launch-screen@1.0.12 +livedata@1.0.18 +localstorage@1.0.11 +logging@1.0.14 matb33:collection-hooks@0.8.1 matteodem:easy-search@1.6.4 mdg:validation-error@0.2.0 -meteor@1.1.11-faster-rebuild.0 -meteor-base@1.0.1 -meteor-platform@1.2.3 +meteor@1.1.16 +meteor-base@1.0.4 +meteor-platform@1.2.6 meteorhacks:aggregate@1.3.0 meteorhacks:collection-utils@1.2.0 -meteorhacks:fast-render@2.11.0 -meteorhacks:inject-data@1.4.1 +meteorhacks:fast-render@2.14.0 +meteorhacks:inject-data@2.0.0 +meteorhacks:meteorx@1.4.1 meteorhacks:picker@1.0.3 -meteorhacks:subs-manager@1.6.3 +meteorhacks:subs-manager@1.6.4 meteorspark:util@0.2.0 +minifier-css@1.1.13 +minifier-js@1.1.13 minifiers@1.1.8-faster-rebuild.0 -minimongo@1.0.10 -mobile-status-bar@1.0.6 -mongo@1.1.4-faster-rebuild.0 -mongo-id@1.0.1 -mongo-livedata@1.0.9 +minimongo@1.0.17 +mobile-status-bar@1.0.12 +modules@0.6.5 +modules-runtime@0.6.5 +mongo@1.1.9_1 +mongo-id@1.0.5 +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.8 +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 -npm-bcrypt@0.7.8_2 -npm-mongo@1.4.40-faster-rebuild.0 -observe-sequence@1.0.7 +npm-bcrypt@0.8.6_2 +npm-mongo@1.4.45 +observe-sequence@1.0.12 ongoworks:speakingurl@1.1.0 -ordered-dict@1.0.4 +ordered-dict@1.0.8 peerlibrary:assert@0.2.5 peerlibrary:base-component@0.14.0 peerlibrary:blaze-components@0.15.1 peerlibrary:computed-field@0.3.1 peerlibrary:reactive-field@0.1.0 perak:markdown@1.0.5 -promise@0.5.2-faster-rebuild.0 +promise@0.7.3 raix:eventemitter@0.1.3 raix:handlebar-helpers@0.2.5 -random@1.0.5 -rate-limit@1.0.0 -reactive-dict@1.1.3 -reactive-var@1.0.6 -reload@1.1.4 -retry@1.0.4 -routepolicy@1.0.6 +random@1.0.10 +rate-limit@1.0.5 +reactive-dict@1.1.8 +reactive-var@1.0.10 +reload@1.1.10 +retry@1.0.8 +routepolicy@1.0.11 seriousm:emoji-continued@1.4.0 -service-configuration@1.0.5 -session@1.1.1 -sha@1.0.4 +service-configuration@1.0.10 +session@1.1.6 +sha@1.0.8 simple:json-routes@1.0.4 -softwarerero:accounts-t9n@1.1.7 -spacebars@1.0.7 -spacebars-compiler@1.0.7 -srp@1.0.4 -standard-minifiers@1.0.3-faster-rebuild.0 -stylus@2.511.2-faster-rebuild.0 -tap:i18n@1.7.0 -templates:tabs@2.2.0 -templating@1.1.6-faster-rebuild.0 -templating-tools@1.0.0 -tracker@1.0.9 -ui@1.0.8 -underscore@1.0.4 -url@1.0.5 -useraccounts:core@1.13.1 -useraccounts:flow-routing@1.13.1 -useraccounts:unstyled@1.13.1 +softwarerero:accounts-t9n@1.3.4 +spacebars@1.0.12 +spacebars-compiler@1.0.12 +srp@1.0.9 +standard-minifier-css@1.0.8 +standard-minifier-js@1.0.8 +stylus@2.512.4 +tap:i18n@1.8.2 +templates:tabs@2.2.2 +templating@1.1.13 +templating-tools@1.0.4 +tracker@1.0.14 +ui@1.0.11 +underscore@1.0.9 +url@1.0.10 +useraccounts:core@1.14.2 +useraccounts:flow-routing@1.14.2 +useraccounts:unstyled@1.14.2 verron:autosize@3.0.8 -webapp@1.2.4-faster-rebuild.0 -webapp-hashing@1.0.5 +webapp@1.2.10 +webapp-hashing@1.0.9 zimme:active-route@2.3.2 diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index e61831a1..03768b36 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -89,7 +89,7 @@ template(name="boardHeaderBar") span {{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}} if MultiSelection.isActive a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}") - i.fa.fa-times-thin + i.fa.fa-times-thin .separator a.board-header-btn.js-open-board-menu diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 299bbf34..5dd0b762 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -147,12 +147,12 @@ Template.cardDetailsActionsPopup.events({ 'click .js-move-card-to-top'(evt) { evt.preventDefault(); const minOrder = _.min(this.list().cards().map((c) => c.sort)); - this.move(this.listId, minOrder / 2); + this.move(this.listId, minOrder - 1); }, 'click .js-move-card-to-bottom'(evt) { evt.preventDefault(); const maxOrder = _.max(this.list().cards().map((c) => c.sort)); - this.move(this.listId, Math.floor(maxOrder) + 1); + this.move(this.listId, maxOrder + 1); }, 'click .js-archive'(evt) { evt.preventDefault(); diff --git a/client/components/cards/labels.js b/client/components/cards/labels.js index 20d95bc6..cdd5a700 100644 --- a/client/components/cards/labels.js +++ b/client/components/cards/labels.js @@ -9,9 +9,7 @@ BlazeComponent.extendComponent({ }, labels() { - return labelColors.map((color) => { - return { color, name: '' }; - }); + return labelColors.map((color) => ({ color, name: '' })); }, isSelected(color) { diff --git a/client/components/import/import.jade b/client/components/import/import.jade index e10072b4..d4def7d8 100644 --- a/client/components/import/import.jade +++ b/client/components/import/import.jade @@ -55,10 +55,10 @@ template(name="importMapMembersAddPopup") ul.pop-over-list +esEach(index="users") li.item.js-member-item - a.name.js-select-import(title="{{profile.name}} ({{username}})" data-id="{{_id}}") + a.name.js-select-import(title="{{profile.fullname}} ({{username}})" data-id="{{_id}}") +userAvatar(userId=_id esSearch=true) span.full-name - = profile.name + = profile.fullname | (<span class="username">{{username}}</span>) +ifEsIsSearching(index='users') +spinner diff --git a/client/components/main/editor.js b/client/components/main/editor.js index da66bb74..17429067 100755 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -90,15 +90,10 @@ Blaze.Template.registerHelper('mentions', new Template('mentions', function() { })); Template.viewer.events({ - 'click .js-open-member'(evt, tpl) { - const userId = evt.currentTarget.dataset.userid; - Popup.open('member').call({ userId }, evt, tpl); - }, - // Viewer sometimes have click-able wrapper around them (for instance to edit // the corresponding text). Clicking a link shouldn't fire these actions, stop // we stop these event at the viewer component level. - 'click a'(evt) { + 'click a'(evt, tpl) { evt.stopPropagation(); // XXX We hijack the build-in browser action because we currently don't have @@ -106,9 +101,16 @@ Template.viewer.events({ // handled by a third party package that we can't configure easily. Fix that // by using directly `_blank` attribute in the rendered HTML. evt.preventDefault(); - const href = evt.currentTarget.href; - if (href) { - window.open(href, '_blank'); + + const userId = evt.currentTarget.dataset.userid; + if (userId) { + Popup.open('member').call({ userId }, evt, tpl); + } + else { + const href = evt.currentTarget.href; + if (href) { + window.open(href, '_blank'); + } } }, }); diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index 893b5164..4f5586cb 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -102,7 +102,7 @@ template(name="addMemberPopup") ul.pop-over-list +esEach(index="users") li.item.js-member-item(class="{{#if isBoardMember}}disabled{{/if}}") - a.name.js-select-member(title="{{profile.name}} ({{username}})") + a.name.js-select-member(title="{{profile.fullname}} ({{username}})") +userAvatar(userId=_id esSearch=true) span.full-name = profile.fullname diff --git a/client/config/gecko-fix.js b/client/config/gecko-fix.js new file mode 100644 index 00000000..00e28970 --- /dev/null +++ b/client/config/gecko-fix.js @@ -0,0 +1,4 @@ +if (Object.prototype.hasOwnProperty('watch')) { + Object.prototype.watch = undefined; + Object.prototype.unwatch = undefined; +} diff --git a/client/lib/popup.js b/client/lib/popup.js index 3166ca30..3658d883 100644 --- a/client/lib/popup.js +++ b/client/lib/popup.js @@ -75,7 +75,7 @@ window.Popup = new class { // If there are no popup currently opened we use the Blaze API to render // one into the DOM. We use a reactive function as the data parameter that - // return the the complete along with its top element and depends on our + // return the complete along with its top element and depends on our // internal dependency that is being invalidated every time the top // element of the stack has changed and we want to update the popup. // diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index b171bdf9..2047d8e1 100755 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -294,5 +294,8 @@ "watch": "Watch", "watching": "Watching", "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", "what-to-do": "What do you want to do?" } diff --git a/meta/icons/wekan-150.png b/meta/icons/wekan-150.png Binary files differindex baca8d9d..e8e89c62 100644 --- a/meta/icons/wekan-150.png +++ b/meta/icons/wekan-150.png diff --git a/models/activities.js b/models/activities.js index ad920149..aa2ea3ec 100644 --- a/models/activities.js +++ b/models/activities.js @@ -41,12 +41,14 @@ Activities.before.insert((userId, doc) => { doc.createdAt = new Date(); }); -// For efficiency create an index on the date of creation. if (Meteor.isServer) { + // For efficiency create indexes on the date of creation, and on the date of + // creation in conjunction with the card or board id, as corresponding views + // are largely used in the App. See #524. Meteor.startup(() => { - Activities._collection._ensureIndex({ - createdAt: -1, - }); + Activities._collection._ensureIndex({ createdAt: -1 }); + Activities._collection._ensureIndex({ cardId: 1, createdAt: -1 }); + Activities._collection._ensureIndex({ boardId: 1, createdAt: -1 }); }); Activities.after.insert((userId, doc) => { diff --git a/models/boards.js b/models/boards.js index 52272cce..715bb838 100644 --- a/models/boards.js +++ b/models/boards.js @@ -6,25 +6,77 @@ Boards.attachSchema(new SimpleSchema({ }, slug: { type: String, + autoValue() { // eslint-disable-line consistent-return + // XXX We need to improve slug management. Only the id should be necessary + // to identify a board in the code. + // XXX If the board title is updated, the slug should also be updated. + // In some cases (Chinese and Japanese for instance) the `getSlug` function + // return an empty string. This is causes bugs in our application so we set + // a default slug in this case. + if (this.isInsert && !this.isSet) { + let slug = 'board'; + const title = this.field('title'); + if (title.isSet) { + slug = getSlug(title.value) || slug; + } + return slug; + } + }, }, archived: { type: Boolean, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return false; + } + }, }, createdAt: { type: Date, - denyUpdate: true, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert) { + return new Date(); + } else { + this.unset(); + } + }, }, // XXX Inconsistent field naming modifiedAt: { type: Date, - denyInsert: true, optional: true, + autoValue() { // eslint-disable-line consistent-return + if (this.isUpdate) { + return new Date(); + } else { + this.unset(); + } + }, }, // De-normalized number of users that have starred this board stars: { type: Number, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert) { + return 0; + } + }, }, // De-normalized label system + 'labels': { + type: [Object], + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + const colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; + const defaultLabelsColors = _.clone(colors).splice(0, 6); + return defaultLabelsColors.map((color) => ({ + color, + _id: Random.id(6), + name: '', + })); + } + }, + }, 'labels.$._id': { // We don't specify that this field must be unique in the board because that // will cause performance penalties and is not necessary since this field is @@ -47,6 +99,19 @@ Boards.attachSchema(new SimpleSchema({ // XXX We might want to maintain more informations under the member sub- // documents like de-normalized meta-data (the date the member joined the // board, the number of contributions, etc.). + 'members': { + type: [Object], + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return [{ + userId: this.userId, + isAdmin: true, + isActive: true, + isInvited: false, + }]; + } + }, + }, 'members.$.userId': { type: String, }, @@ -70,6 +135,11 @@ Boards.attachSchema(new SimpleSchema({ 'wisteria', 'midnight', ], + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return Boards.simpleSchema()._schema.color.allowedValues[0]; + } + }, }, description: { type: String, @@ -338,41 +408,6 @@ if (Meteor.isServer) { }); } -Boards.before.insert((userId, doc) => { - // XXX We need to improve slug management. Only the id should be necessary - // to identify a board in the code. - // XXX If the board title is updated, the slug should also be updated. - // In some cases (Chinese and Japanese for instance) the `getSlug` function - // return an empty string. This is causes bugs in our application so we set - // a default slug in this case. - doc.slug = doc.slug || getSlug(doc.title) || 'board'; - doc.createdAt = new Date(); - doc.archived = false; - doc.members = doc.members || [{ - userId, - isAdmin: true, - isActive: true, - }]; - doc.stars = 0; - doc.color = Boards.simpleSchema()._schema.color.allowedValues[0]; - - // Handle labels - const colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; - const defaultLabelsColors = _.clone(colors).splice(0, 6); - doc.labels = defaultLabelsColors.map((color) => { - return { - color, - _id: Random.id(6), - name: '', - }; - }); -}); - -Boards.before.update((userId, doc, fieldNames, modifier) => { - modifier.$set = modifier.$set || {}; - modifier.$set.modifiedAt = new Date(); -}); - if (Meteor.isServer) { // Let MongoDB ensure that a member is not included twice in the same board Meteor.startup(() => { diff --git a/models/cardComments.js b/models/cardComments.js index 224deb03..ce6edf3c 100644 --- a/models/cardComments.js +++ b/models/cardComments.js @@ -16,10 +16,22 @@ CardComments.attachSchema(new SimpleSchema({ createdAt: { type: Date, denyUpdate: false, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert) { + return new Date(); + } else { + this.unset(); + } + }, }, // XXX Should probably be called `authorId` userId: { type: String, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return this.userId; + } + }, }, })); @@ -44,11 +56,6 @@ CardComments.helpers({ CardComments.hookOptions.after.update = { fetchPrevious: false }; -CardComments.before.insert((userId, doc) => { - doc.createdAt = new Date(); - doc.userId = userId; -}); - if (Meteor.isServer) { CardComments.after.insert((userId, doc) => { Activities.insert({ diff --git a/models/cards.js b/models/cards.js index 09c86191..84fbb6c2 100644 --- a/models/cards.js +++ b/models/cards.js @@ -9,6 +9,11 @@ Cards.attachSchema(new SimpleSchema({ }, archived: { type: Boolean, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return false; + } + }, }, listId: { type: String, @@ -25,10 +30,19 @@ Cards.attachSchema(new SimpleSchema({ }, createdAt: { type: Date, - denyUpdate: true, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert) { + return new Date(); + } else { + this.unset(); + } + }, }, dateLastActivity: { type: Date, + autoValue() { + return new Date(); + }, }, description: { type: String, @@ -46,6 +60,11 @@ Cards.attachSchema(new SimpleSchema({ // the `members` field? userId: { type: String, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return this.userId; + } + }, }, sort: { type: Number, @@ -190,18 +209,13 @@ Cards.mutations({ }, }); -Cards.before.insert((userId, doc) => { - doc.createdAt = new Date(); - doc.dateLastActivity = new Date(); - if(!doc.hasOwnProperty('archived')){ - doc.archived = false; - } - if (!doc.userId) { - doc.userId = userId; - } -}); - if (Meteor.isServer) { + // Cards are often fetched within a board, so we create an index to make these + // queries more efficient. + Meteor.startup(() => { + Cards._collection._ensureIndex({ boardId: 1 }); + }); + Cards.after.insert((userId, doc) => { Activities.insert({ userId, diff --git a/models/lists.js b/models/lists.js index 4e4a1134..9ae2e4f7 100644 --- a/models/lists.js +++ b/models/lists.js @@ -6,13 +6,24 @@ Lists.attachSchema(new SimpleSchema({ }, archived: { type: Boolean, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return false; + } + }, }, boardId: { type: String, }, createdAt: { type: Date, - denyUpdate: true, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert) { + return new Date(); + } else { + this.unset(); + } + }, }, sort: { type: Number, @@ -22,8 +33,14 @@ Lists.attachSchema(new SimpleSchema({ }, updatedAt: { type: Date, - denyInsert: true, optional: true, + autoValue() { // eslint-disable-line consistent-return + if (this.isUpdate) { + return new Date(); + } else { + this.unset(); + } + }, }, })); @@ -73,19 +90,11 @@ Lists.mutations({ Lists.hookOptions.after.update = { fetchPrevious: false }; -Lists.before.insert((userId, doc) => { - doc.createdAt = new Date(); - doc.archived = false; - if (!doc.userId) - doc.userId = userId; -}); - -Lists.before.update((userId, doc, fieldNames, modifier) => { - modifier.$set = modifier.$set || {}; - modifier.$set.modifiedAt = new Date(); -}); - if (Meteor.isServer) { + Meteor.startup(() => { + Lists._collection._ensureIndex({ boardId: 1 }); + }); + Lists.after.insert((userId, doc) => { Activities.insert({ userId, diff --git a/models/unsavedEdits.js b/models/unsavedEdits.js index 87a70e22..25952fb5 100644 --- a/models/unsavedEdits.js +++ b/models/unsavedEdits.js @@ -14,6 +14,11 @@ UnsavedEditCollection.attachSchema(new SimpleSchema({ }, userId: { type: String, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return this.userId; + } + }, }, })); @@ -28,7 +33,3 @@ if (Meteor.isServer) { fetch: ['userId'], }); } - -UnsavedEditCollection.before.insert((userId, doc) => { - doc.userId = userId; -}); diff --git a/models/users.js b/models/users.js index 4e4a0fac..790ee0a1 100644 --- a/models/users.js +++ b/models/users.js @@ -1,5 +1,95 @@ Users = Meteor.users; +Users.attachSchema(new SimpleSchema({ + username: { + type: String, + optional: true, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + const name = this.field('profile.fullname'); + if (name.isSet) { + return name.value.toLowerCase().replace(/\s/g, ''); + } + } + }, + }, + emails: { + type: [Object], + optional: true, + }, + 'emails.$.address': { + type: String, + regEx: SimpleSchema.RegEx.Email, + }, + 'emails.$.verified': { + type: Boolean, + }, + createdAt: { + type: Date, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert) { + return new Date(); + } else { + this.unset(); + } + }, + }, + profile: { + type: Object, + optional: true, + autoValue() { // eslint-disable-line consistent-return + if (this.isInsert && !this.isSet) { + return {}; + } + }, + }, + 'profile.avatarUrl': { + type: String, + optional: true, + }, + 'profile.emailBuffer': { + type: [String], + optional: true, + }, + 'profile.fullname': { + type: String, + optional: true, + }, + 'profile.initials': { + type: String, + optional: true, + }, + 'profile.invitedBoards': { + type: [String], + optional: true, + }, + 'profile.language': { + type: String, + optional: true, + }, + 'profile.notifications': { + type: [String], + optional: true, + }, + 'profile.starredBoards': { + type: [String], + optional: true, + }, + 'profile.tags': { + type: [String], + optional: true, + }, + services: { + type: Object, + optional: true, + blackbox: true, + }, + heartbeat: { + type: Date, + optional: true, + }, +})); + // Search a user in the complete server database by its name or username. This // is used for instance to add a new user to a board. const searchInFields = ['username', 'profile.fullname']; @@ -259,14 +349,6 @@ if (Meteor.isServer) { }); } -Users.before.insert((userId, doc) => { - doc.profile = doc.profile || {}; - - if (!doc.username && doc.profile.name) { - doc.username = doc.profile.name.toLowerCase().replace(/\s/g, ''); - } -}); - if (Meteor.isServer) { // Let mongoDB ensure username unicity Meteor.startup(() => { @@ -306,32 +388,29 @@ if (Meteor.isServer) { incrementBoards(_.difference(newIds, oldIds), +1); }); - // XXX i18n + const fakeUserId = new Meteor.EnvironmentVariable(); + const getUserId = CollectionHooks.getUserId; + CollectionHooks.getUserId = () => { + return fakeUserId.get() || getUserId(); + }; + Users.after.insert((userId, doc) => { - const ExampleBoard = { - title: 'Welcome Board', - userId: doc._id, - permission: 'private', + const fakeUser = { + extendAutoValueContext: { + userId: doc._id, + }, }; - // Insert the Welcome Board - Boards.insert(ExampleBoard, (err, boardId) => { - - ['Basics', 'Advanced'].forEach((title) => { - const list = { - title, - boardId, - userId: ExampleBoard.userId, - - // XXX Not certain this is a bug, but we except these fields get - // inserted by the Lists.before.insert collection-hook. Since this - // hook is not called in this case, we have to dublicate the logic and - // set them here. - archived: false, - createdAt: new Date(), - }; + fakeUserId.withValue(doc._id, () => { + // Insert the Welcome Board + Boards.insert({ + title: TAPi18n.__('welcome-board'), + permission: 'private', + }, fakeUser, (err, boardId) => { - Lists.insert(list); + ['welcome-list1', 'welcome-list2'].forEach((title) => { + Lists.insert({ title: TAPi18n.__(title), boardId }, fakeUser); + }); }); }); }); diff --git a/public/wekan-favicon.png b/public/wekan-favicon.png Binary files differindex bd66f554..8beb85f4 100644 --- a/public/wekan-favicon.png +++ b/public/wekan-favicon.png diff --git a/public/wekan-logo-header.png b/public/wekan-logo-header.png Binary files differindex 441b6007..16ffa102 100644 --- a/public/wekan-logo-header.png +++ b/public/wekan-logo-header.png diff --git a/public/wekan-logo.png b/public/wekan-logo.png Binary files differindex 1d98f176..6a2740f2 100644 --- a/public/wekan-logo.png +++ b/public/wekan-logo.png |