diff options
-rw-r--r-- | client/components/boards/boardHeader.jade | 5 | ||||
-rw-r--r-- | client/components/import/import.jade | 5 | ||||
-rw-r--r-- | client/components/import/import.js | 28 | ||||
-rw-r--r-- | client/components/main/popup.styl | 6 | ||||
-rw-r--r-- | models/import.js | 60 |
5 files changed, 59 insertions, 45 deletions
diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index e460170b..cb86e9bb 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -107,8 +107,9 @@ template(name="createBoardPopup") | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. input.primary.wide(type="submit" value="{{_ 'create'}}") - | {{_ 'or'}} - a.js-import {{_ 'import-board'}} + span.quiet + | {{_ 'or'}} + a.js-import {{_ 'import-board'}} template(name="boardChangeTitlePopup") diff --git a/client/components/import/import.jade b/client/components/import/import.jade index 8059b65b..f63661af 100644 --- a/client/components/import/import.jade +++ b/client/components/import/import.jade @@ -2,7 +2,6 @@ template(name="importPopup") if error.get .warning {{_ error.get}} form - label - | {{_ getLabel}} - textarea.js-card-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus) + p: label(for='import-textarea') {{_ getLabel}} + textarea#import-textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus) input.primary.wide(type="submit" value="{{_ 'import'}}") diff --git a/client/components/import/import.js b/client/components/import/import.js index 00918aac..c6957fa9 100644 --- a/client/components/import/import.js +++ b/client/components/import/import.js @@ -1,20 +1,21 @@ -/** - * Abstract root for all import popup screens. - * Descendants must define: - * - getMethodName(): return the Meteor method to call for import, passing json data decoded as object - * and additional data (see below) - * - getAdditionalData(): return object containing additional data passed to Meteor method - * (like list ID and position for a card import) - * - getLabel(): i18n key for the text displayed in the popup, usually to explain how to get the data out of the - * source system. - */ +/// Abstract root for all import popup screens. +/// Descendants must define: +/// - getMethodName(): return the Meteor method to call for import, passing json +/// data decoded as object and additional data (see below); +/// - getAdditionalData(): return object containing additional data passed to +/// Meteor method (like list ID and position for a card import); +/// - getLabel(): i18n key for the text displayed in the popup, usually to +/// explain how to get the data out of the source system. const ImportPopup = BlazeComponent.extendComponent({ - template() {return 'importPopup';}, + template() { + return 'importPopup'; + }, + events() { return [{ 'submit': (evt) => { evt.preventDefault(); - const dataJson = $(evt.currentTarget).find('textarea').val(); + const dataJson = $(evt.currentTarget).find('.js-import-json').val(); let dataObject; try { dataObject = JSON.parse(dataJson); @@ -52,7 +53,8 @@ const ImportPopup = BlazeComponent.extendComponent({ ImportPopup.extendComponent({ getAdditionalData() { const listId = this.data()._id; - const firstCardDom = $(`#js-list-${this.currentData()._id} .js-minicard:first`).get(0); + const selector = `#js-list-${this.currentData()._id} .js-minicard:first`; + const firstCardDom = $(selector).get(0); const sortIndex = Utils.calculateIndex(null, firstCardDom).base; const result = {listId, sortIndex}; return result; diff --git a/client/components/main/popup.styl b/client/components/main/popup.styl index 3bef4f7d..8a685069 100644 --- a/client/components/main/popup.styl +++ b/client/components/main/popup.styl @@ -17,9 +17,11 @@ $popupWidth = 300px margin: 4px -10px width: $popupWidth + p, + textarea, input[type="text"], input[type="email"], - input[type="password"] + input[type="password"], input[type="file"] margin: 4px 0 12px width: 100% @@ -30,8 +32,6 @@ $popupWidth = 300px textarea height: 72px - margin: 4px 0 12px - width: 100% .header height: 36px diff --git a/models/import.js b/models/import.js index be699746..7b441df6 100644 --- a/models/import.js +++ b/models/import.js @@ -5,17 +5,18 @@ const DateString = Match.Where(function (dateAsString) { class TrelloCreator { constructor() { - // the object creation dates, indexed by Trello id (so we only parse actions once!) + // The object creation dates, indexed by Trello id (so we only parse actions + // once!) this.createdAt = { board: null, cards: {}, lists: {}, }; - // map of labels Trello ID => Wekan ID + // Map of labels Trello ID => Wekan ID this.labels = {}; - // map of lists Trello ID => Wekan ID + // Map of lists Trello ID => Wekan ID this.lists = {}; - // the comments, indexed by Trello card id (to map when importing cards) + // The comments, indexed by Trello card id (to map when importing cards) this.comments = {}; } @@ -33,9 +34,12 @@ class TrelloCreator { closed: Boolean, name: String, prefs: Match.ObjectIncluding({ - // XXX refine control by validating 'background' against a list of allowed values (is it worth the maintenance?) + // XXX refine control by validating 'background' against a list of + // allowed values (is it worth the maintenance?) background: String, - permissionLevel: Match.Where((value) => {return ['org', 'private', 'public'].indexOf(value)>= 0;}), + permissionLevel: Match.Where((value) => { + return ['org', 'private', 'public'].indexOf(value)>= 0; + }), }), })); } @@ -54,7 +58,8 @@ class TrelloCreator { checkLabels(trelloLabels) { check(trelloLabels, [Match.ObjectIncluding({ - // XXX refine control by validating 'color' against a list of allowed values (is it worth the maintenance?) + // XXX refine control by validating 'color' against a list of allowed + // values (is it worth the maintenance?) color: String, name: String, })]); @@ -67,9 +72,7 @@ class TrelloCreator { })]); } - /** - * must call parseActions before calling this one - */ + // You must call parseActions before calling this one. createBoardAndLabels(trelloBoard) { const createdAt = this.createdAt.board; const boardToCreate = { @@ -93,7 +96,8 @@ class TrelloCreator { color: label.color, name: label.name, }; - // we need to remember them by Trello ID, as this is the only ref we have when importing cards + // We need to remember them by Trello ID, as this is the only ref we have + // when importing cards. this.labels[label.id] = labelToCreate._id; boardToCreate.labels.push(labelToCreate); }); @@ -110,15 +114,14 @@ class TrelloCreator { system: 'Trello', url: trelloBoard.url, }, - // we attribute the import to current user, not the one from the original object + // We attribute the import to current user, not the one from the original + // object. userId: Meteor.userId(), }); return boardId; } - /** - * Create labels if they do not exist and load this.labels. - */ + // Create labels if they do not exist and load this.labels. createLabels(trelloLabels, board) { trelloLabels.forEach((label) => { const color = label.color; @@ -138,7 +141,11 @@ class TrelloCreator { const listToCreate = { archived: list.closed, boardId, - createdAt: this.createdAt.lists[list.id], + // 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 + // Trello boards (eg from 2013) that didn't log the 'createList' action + // we require. + createdAt: new Date(this.createdAt.lists[list.id] || Date.now()), title: list.name, userId: Meteor.userId(), }; @@ -156,7 +163,8 @@ class TrelloCreator { id: list.id, system: 'Trello', }, - // we attribute the import to current user, not the one from the original object + // We attribute the import to current user, not the one from the + // original object userId: Meteor.userId(), }); }); @@ -168,7 +176,7 @@ class TrelloCreator { const cardToCreate = { archived: card.closed, boardId, - createdAt: this.createdAt.cards[card.id], + createdAt: new Date(this.createdAt.cards[card.id] || Date.now()), dateLastActivity: new Date(), description: card.desc, listId: this.lists[card.idList], @@ -197,7 +205,8 @@ class TrelloCreator { system: 'Trello', url: card.url, }, - // we attribute the import to current user, not the one from the original card + // we attribute the import to current user, not the one from the + // original card userId: Meteor.userId(), }); // add comments @@ -212,7 +221,8 @@ class TrelloCreator { // XXX use the original comment user instead userId: Meteor.userId(), }; - // dateLastActivity will be set from activity insert, no need to update it ourselves + // dateLastActivity will be set from activity insert, no need to + // update it ourselves const commentId = CardComments.direct.insert(commentToCreate); Activities.direct.insert({ activityType: 'addComment', @@ -251,7 +261,8 @@ class TrelloCreator { if(trelloPermissionCode === 'public') { return 'public'; } - // Wekan does NOT have organization level, so we default both 'private' and 'org' to private. + // Wekan does NOT have organization level, so we default both 'private' and + // 'org' to private. return 'private'; } @@ -302,8 +313,8 @@ Meteor.methods({ throw new Meteor.Error('error-json-schema'); } - // 2. check parameters are ok from a business point of view (exist & authorized) - // nothing to check, everyone can import boards in their account + // 2. check parameters are ok from a business point of view (exist & + // authorized) nothing to check, everyone can import boards in their account // 3. create all elements trelloCreator.parseActions(trelloBoard.actions); @@ -330,7 +341,8 @@ Meteor.methods({ throw new Meteor.Error('error-json-schema'); } - // 2. check parameters are ok from a business point of view (exist & authorized) + // 2. check parameters are ok from a business point of view (exist & + // authorized) const list = Lists.findOne(data.listId); if(!list) { throw new Meteor.Error('error-list-doesNotExist'); |