diff options
-rw-r--r-- | client/components/activities/activities.jade | 56 | ||||
-rw-r--r-- | client/components/activities/activities.js | 17 | ||||
-rw-r--r-- | client/components/boards/boardHeader.jade | 3 | ||||
-rw-r--r-- | client/components/boards/boardHeader.js | 1 | ||||
-rw-r--r-- | client/components/boards/boardHeader.styl | 2 | ||||
-rw-r--r-- | client/components/import/import.jade | 7 | ||||
-rw-r--r-- | client/components/import/import.js | 90 | ||||
-rw-r--r-- | client/components/lists/listHeader.jade | 9 | ||||
-rw-r--r-- | client/components/lists/listHeader.js | 39 | ||||
-rw-r--r-- | client/components/main/popup.styl | 6 | ||||
-rw-r--r-- | i18n/en.i18n.json | 9 | ||||
-rw-r--r-- | models/boards.js | 8 | ||||
-rw-r--r-- | models/import.js | 448 |
13 files changed, 511 insertions, 184 deletions
diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade index c611ad75..28a9f9c9 100644 --- a/client/components/activities/activities.jade +++ b/client/components/activities/activities.jade @@ -14,41 +14,56 @@ template(name="boardActivities") p.activity-desc +memberName(user=user) - if($eq activityType 'createBoard') - | {{_ 'activity-created' boardLabel}}. + if($eq activityType 'addAttachment') + | {{{_ 'activity-attached' attachmentLink cardLink}}}. - if($eq activityType 'createList') - | {{_ 'activity-added' list.title boardLabel}}. + if($eq activityType 'addBoardMember') + | {{{_ 'activity-added' memberLink boardLabel}}}. + + if($eq activityType 'addComment') + | {{{_ 'activity-on' cardLink}}} + a.activity-comment(href="{{ card.absoluteUrl }}") + +viewer + = comment.text + + if($eq activityType 'archivedCard') + | {{{_ 'activity-archived' cardLink}}}. if($eq activityType 'archivedList') | {{_ 'activity-archived' list.title}}. + if($eq activityType 'createBoard') + | {{_ 'activity-created' boardLabel}}. + if($eq activityType 'createCard') | {{{_ 'activity-added' cardLink boardLabel}}}. + if($eq activityType 'createList') + | {{_ 'activity-added' list.title boardLabel}}. + + if($eq activityType 'importBoard') + | {{{_ 'activity-imported-board' boardLabel sourceLink}}}. + if($eq activityType 'importCard') | {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}. - if($eq activityType 'archivedCard') - | {{{_ 'activity-archived' cardLink}}}. + if($eq activityType 'importList') + | {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}. - if($eq activityType 'restoredCard') - | {{{_ 'activity-sent' cardLink boardLabel}}}. + if($eq activityType 'joinMember') + if($eq currentUser._id member._id) + | {{{_ 'activity-joined' cardLink}}}. + else + | {{{_ 'activity-added' memberLink cardLink}}}. if($eq activityType 'moveCard') | {{{_ 'activity-moved' cardLink oldList.title list.title}}}. - if($eq activityType 'addBoardMember') - | {{{_ 'activity-added' memberLink boardLabel}}}. - if($eq activityType 'removeBoardMember') | {{{_ 'activity-excluded' memberLink boardLabel}}}. - if($eq activityType 'joinMember') - if($eq currentUser._id member._id) - | {{{_ 'activity-joined' cardLink}}}. - else - | {{{_ 'activity-added' memberLink cardLink}}}. + if($eq activityType 'restoredCard') + | {{{_ 'activity-sent' cardLink boardLabel}}}. if($eq activityType 'unjoinMember') if($eq currentUser._id member._id) @@ -56,15 +71,6 @@ template(name="boardActivities") else | {{{_ 'activity-removed' memberLink cardLink}}}. - if($eq activityType 'addComment') - | {{{_ 'activity-on' cardLink}}} - a.activity-comment(href="{{ card.absoluteUrl }}") - +viewer - = comment.text - - if($eq activityType 'addAttachment') - | {{{_ 'activity-attached' attachmentLink cardLink}}}. - span.activity-meta {{ moment createdAt }} template(name="cardActivities") diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index b80493f7..b25c0ca8 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -60,11 +60,22 @@ BlazeComponent.extendComponent({ }, card.title)); }, + listLabel() { + return this.currentData().list().title; + }, + sourceLink() { const source = this.currentData().source; - return source && Blaze.toHTML(HTML.A({ - href: source.url, - }, source.system)); + if(source) { + if(source.url) { + return Blaze.toHTML(HTML.A({ + href: source.url, + }, source.system)); + } else { + return source.system; + } + } + return null; }, memberLink() { diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index ffc79143..cb86e9bb 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -107,6 +107,9 @@ template(name="createBoardPopup") | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. input.primary.wide(type="submit" value="{{_ 'create'}}") + span.quiet + | {{_ 'or'}} + a.js-import {{_ 'import-board'}} template(name="boardChangeTitlePopup") diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index dbd76895..92d5f6d4 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -145,6 +145,7 @@ BlazeComponent.extendComponent({ this.setVisibility(this.currentData()); }, 'click .js-change-visibility': this.toggleVisibilityMenu, + 'click .js-import': Popup.open('boardImportBoard'), submit: this.onSubmit, }]; }, diff --git a/client/components/boards/boardHeader.styl b/client/components/boards/boardHeader.styl new file mode 100644 index 00000000..adfe4b19 --- /dev/null +++ b/client/components/boards/boardHeader.styl @@ -0,0 +1,2 @@ +a.js-import + text-decoration underline diff --git a/client/components/import/import.jade b/client/components/import/import.jade new file mode 100644 index 00000000..f63661af --- /dev/null +++ b/client/components/import/import.jade @@ -0,0 +1,7 @@ +template(name="importPopup") + if error.get + .warning {{_ error.get}} + form + 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 new file mode 100644 index 00000000..c6957fa9 --- /dev/null +++ b/client/components/import/import.js @@ -0,0 +1,90 @@ +/// 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'; + }, + + events() { + return [{ + 'submit': (evt) => { + evt.preventDefault(); + const dataJson = $(evt.currentTarget).find('.js-import-json').val(); + let dataObject; + try { + dataObject = JSON.parse(dataJson); + } catch (e) { + this.setError('error-json-malformed'); + return; + } + Meteor.call(this.getMethodName(), dataObject, this.getAdditionalData(), + (error, response) => { + if (error) { + this.setError(error.error); + } else { + Filter.addException(response); + this.onFinish(response); + } + } + ); + }, + }]; + }, + + onCreated() { + this.error = new ReactiveVar(''); + }, + + setError(error) { + this.error.set(error); + }, + + onFinish() { + Popup.close(); + }, +}); + +ImportPopup.extendComponent({ + getAdditionalData() { + const listId = this.data()._id; + 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; + }, + + getMethodName() { + return 'importTrelloCard'; + }, + + getLabel() { + return 'import-card-trello-instruction'; + }, +}).register('listImportCardPopup'); + +ImportPopup.extendComponent({ + getAdditionalData() { + const result = {}; + return result; + }, + + getMethodName() { + return 'importTrelloBoard'; + }, + + getLabel() { + return 'import-board-trello-instruction'; + }, + + onFinish(response) { + Utils.goBoardId(response); + }, +}).register('boardImportBoardPopup'); + diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index e7b16912..72cd0fe9 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -31,15 +31,6 @@ template(name="listActionPopup") template(name="listMoveCardsPopup") +boardLists -template(name="listImportCardPopup") - if error.get - .warning {{_ error.get}} - form - label - | {{_ 'card-json'}} - textarea.js-card-json(placeholder="{{_ 'card-json-placeholder'}}" autofocus) - input.primary.wide(type="submit" value="{{_ 'import'}}") - template(name="boardLists") ul.pop-over-list each currentBoard.lists diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index e34d23fd..4f5fc3a0 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -49,45 +49,6 @@ Template.listActionPopup.events({ }, }); - -BlazeComponent.extendComponent({ - events() { - return [{ - 'submit': (evt) => { - evt.preventDefault(); - const jsonData = $(evt.currentTarget).find('textarea').val(); - const firstCardDom = $(`#js-list-${this.currentData()._id} .js-minicard:first`).get(0); - const sortIndex = Utils.calculateIndex(null, firstCardDom).base; - let trelloCard; - try { - trelloCard = JSON.parse(jsonData); - } catch (e) { - this.setError('error-json-malformed'); - return; - } - Meteor.call('importTrelloCard', trelloCard, this.currentData()._id, sortIndex, - (error, response) => { - if (error) { - this.setError(error.error); - } else { - Filter.addException(response); - Popup.close(); - } - } - ); - }, - }]; - }, - - onCreated() { - this.error = new ReactiveVar(''); - }, - - setError(error) { - this.error.set(error); - }, -}).register('listImportCardPopup'); - Template.listMoveCardsPopup.events({ 'click .js-select-list'() { const fromList = Template.parentData(2).data; 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/i18n/en.i18n.json b/i18n/en.i18n.json index efc6128d..0823ba08 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -8,6 +8,7 @@ "activity-created": "created %s", "activity-excluded": "excluded %s from %s", "activity-imported": "imported %s into %s from %s", + "activity-imported-board": "imported %s from %s", "activity-joined": "joined %s", "activity-moved": "moved %s from %s to %s", "activity-on": "on %s", @@ -54,6 +55,7 @@ "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", + "boardImportBoardPopup-title": "Import board from Trello", "boardMenuPopup-title": "Board Menu", "boards": "Boards", "bucket-example": "Like “Bucket List” for example", @@ -66,8 +68,6 @@ "card-edit-attachments": "Edit attachments", "card-edit-labels": "Edit labels", "card-edit-members": "Edit members", - "card-json": "Go to a Trello card, select 'Share and more...' then 'Export JSON' and copy the resulting text", - "card-json-placeholder": "Paste your valid JSON data here", "card-labels-title": "Change the labels for the card.", "card-members-title": "Add or remove members of the board from the card.", "cardAttachmentsPopup-title": "Attach From", @@ -136,7 +136,11 @@ "header-logo-title": "Go back to your boards page.", "home": "Home", "import": "Import", + "import-board": "import from Trello", + "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", "import-card": "Import a Trello card", + "import-card-trello-instruction": "Go to a Trello card, select 'Share and more...' then 'Export JSON' and copy the resulting text", + "import-json-placeholder": "Paste your valid JSON data here", "info": "Infos", "initials": "Initials", "joined": "joined", @@ -175,6 +179,7 @@ "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", "optional": "optional", + "or": "or", "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", "page-not-found": "Page not found.", "password": "Password", diff --git a/models/boards.js b/models/boards.js index 8a9d21e8..4d9fd7c0 100644 --- a/models/boards.js +++ b/models/boards.js @@ -111,6 +111,14 @@ Boards.helpers({ colorClass() { return `board-color-${this.color}`; }, + + // XXX currently mutations return no value so we have an issue when using addLabel in import + // XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove... + pushLabel(name, color) { + const _id = Random.id(6); + Boards.direct.update(this._id, { $push: {labels: { _id, name, color }}}); + return _id; + }, }); Boards.mutations({ diff --git a/models/import.js b/models/import.js index e1f77efc..a6e9f3d5 100644 --- a/models/import.js +++ b/models/import.js @@ -1,36 +1,349 @@ -Meteor.methods({ - importTrelloCard(trelloCard, listId, sortIndex) { - // 1. check parameters are ok from a syntax point of view - const DateString = Match.Where(function (dateAsString) { - check(dateAsString, String); - return moment(dateAsString, moment.ISO_8601).isValid(); +const DateString = Match.Where(function (dateAsString) { + check(dateAsString, String); + return moment(dateAsString, moment.ISO_8601).isValid(); +}); + +class TrelloCreator { + constructor() { + // 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 + this.labels = {}; + // Map of lists Trello ID => Wekan ID + this.lists = {}; + // The comments, indexed by Trello card id (to map when importing cards) + this.comments = {}; + } + + checkActions(trelloActions) { + check(trelloActions, [Match.ObjectIncluding({ + data: Object, + date: DateString, + type: String, + })]); + // XXX we could perform more thorough checks based on action type + } + + checkBoard(trelloBoard) { + check(trelloBoard, Match.ObjectIncluding({ + closed: Boolean, + name: String, + prefs: Match.ObjectIncluding({ + // 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; + }), + }), + })); + } + + checkCards(trelloCards) { + check(trelloCards, [Match.ObjectIncluding({ + closed: Boolean, + dateLastActivity: DateString, + desc: String, + idLabels: [String], + idMembers: [String], + name: String, + pos: Number, + })]); + } + + checkLabels(trelloLabels) { + check(trelloLabels, [Match.ObjectIncluding({ + // XXX refine control by validating 'color' against a list of allowed + // values (is it worth the maintenance?) + color: String, + name: String, + })]); + } + + checkLists(trelloLists) { + check(trelloLists, [Match.ObjectIncluding({ + closed: Boolean, + name: String, + })]); + } + + // You must call parseActions before calling this one. + createBoardAndLabels(trelloBoard) { + const createdAt = this.createdAt.board; + const boardToCreate = { + archived: trelloBoard.closed, + color: this.getColor(trelloBoard.prefs.background), + createdAt, + labels: [], + members: [{ + userId: Meteor.userId(), + isAdmin: true, + isActive: true, + }], + permission: this.getPermission(trelloBoard.prefs.permissionLevel), + slug: getSlug(trelloBoard.name) || 'board', + stars: 0, + title: trelloBoard.name, + }; + trelloBoard.labels.forEach((label) => { + const labelToCreate = { + _id: Random.id(6), + 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. + this.labels[label.id] = labelToCreate._id; + boardToCreate.labels.push(labelToCreate); + }); + const now = new Date(); + const boardId = Boards.direct.insert(boardToCreate); + Boards.direct.update(boardId, {$set: {modifiedAt: now}}); + // log activity + Activities.direct.insert({ + activityType: 'importBoard', + boardId, + createdAt: now, + source: { + id: trelloBoard.id, + system: 'Trello', + url: trelloBoard.url, + }, + // 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. + createLabels(trelloLabels, board) { + trelloLabels.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(trelloLists, boardId) { + trelloLists.forEach((list) => { + const listToCreate = { + archived: list.closed, + 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 + // 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(), + }; + const listId = Lists.direct.insert(listToCreate); + const now = new Date(); + Lists.direct.update(listId, {$set: {'updatedAt': now}}); + this.lists[list.id] = listId; + // log activity + Activities.direct.insert({ + activityType: 'importList', + boardId, + createdAt: now, + listId, + source: { + id: list.id, + system: 'Trello', + }, + // We attribute the import to current user, not the one from the + // original object + userId: Meteor.userId(), + }); + }); + } + + createCardsAndComments(trelloCards, boardId) { + const result = []; + trelloCards.forEach((card) => { + const cardToCreate = { + archived: card.closed, + boardId, + createdAt: new Date(this.createdAt.cards[card.id] || Date.now()), + dateLastActivity: new Date(), + description: card.desc, + listId: this.lists[card.idList], + sort: card.pos, + title: card.name, + // XXX use the original user? + userId: Meteor.userId(), + }; + // add labels + if (card.idLabels) { + cardToCreate.labelIds = card.idLabels.map((trelloId) => { + return this.labels[trelloId]; + }); + } + // insert card + const cardId = Cards.direct.insert(cardToCreate); + // log activity + Activities.direct.insert({ + activityType: 'importCard', + boardId, + cardId, + createdAt: new Date(), + listId: cardToCreate.listId, + source: { + id: card.id, + system: 'Trello', + url: card.url, + }, + // we attribute the import to current user, not the one from the + // original card + userId: Meteor.userId(), + }); + // add comments + const comments = this.comments[card.id]; + if (comments) { + comments.forEach((comment) => { + const commentToCreate = { + boardId, + cardId, + createdAt: comment.date, + text: comment.data.text, + // XXX use the original comment user instead + userId: Meteor.userId(), + }; + // dateLastActivity will be set from activity insert, no need to + // update it ourselves + const commentId = CardComments.direct.insert(commentToCreate); + Activities.direct.insert({ + activityType: 'addComment', + boardId: commentToCreate.boardId, + cardId: commentToCreate.cardId, + commentId, + createdAt: commentToCreate.createdAt, + userId: commentToCreate.userId, + }); + }); + } + // XXX add attachments + result.push(cardId); + }); + return result; + } + + getColor(trelloColorCode) { + // trello color name => wekan color + const mapColors = { + 'blue': 'belize', + 'orange': 'pumpkin', + 'green': 'nephritis', + 'red': 'pomegranate', + 'purple': 'wisteria', + 'pink': 'pomegranate', + 'lime': 'nephritis', + 'sky': 'belize', + 'grey': 'midnight', + }; + const wekanColor = mapColors[trelloColorCode]; + return wekanColor || Boards.simpleSchema()._schema.color.allowedValues[0]; + } + + getPermission(trelloPermissionCode) { + if (trelloPermissionCode === 'public') { + return 'public'; + } + // Wekan does NOT have organization level, so we default both 'private' and + // 'org' to private. + return 'private'; + } + + parseActions(trelloActions) { + trelloActions.forEach((action) => { + switch (action.type) { + case 'createBoard': + this.createdAt.board = action.date; + break; + case 'createCard': + const cardId = action.data.card.id; + this.createdAt.cards[cardId] = action.date; + break; + case 'createList': + const listId = action.data.list.id; + this.createdAt.lists[listId] = action.date; + break; + case 'commentCard': + const id = action.data.card.id; + if (this.comments[id]) { + this.comments[id].push(action); + } else { + this.comments[id] = [action]; + } + break; + default: + // do nothing + break; + } }); + } +} + +Meteor.methods({ + importTrelloBoard(trelloBoard, data) { + const trelloCreator = new TrelloCreator(); + + // 1. check all parameters are ok from a syntax point of view try { - check(trelloCard, Match.ObjectIncluding({ - name: String, - desc: String, - closed: Boolean, - dateLastActivity: DateString, - labels: [Match.ObjectIncluding({ - name: String, - color: String, - })], - actions: [Match.ObjectIncluding({ - type: String, - date: DateString, - data: Object, - })], - members: [Object], - })); - check(listId, String); - check(sortIndex, Number); + // we don't use additional data - this should be an empty object + check(data, {}); + trelloCreator.checkActions(trelloBoard.actions); + trelloCreator.checkBoard(trelloBoard); + trelloCreator.checkLabels(trelloBoard.labels); + trelloCreator.checkLists(trelloBoard.lists); + trelloCreator.checkCards(trelloBoard.cards); } catch (e) { 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 + + // 3. create all elements + trelloCreator.parseActions(trelloBoard.actions); + const boardId = trelloCreator.createBoardAndLabels(trelloBoard); + trelloCreator.createLists(trelloBoard.lists, boardId); + trelloCreator.createCardsAndComments(trelloBoard.cards, boardId); + // XXX add members + return boardId; + }, + + importTrelloCard(trelloCard, data) { + const trelloCreator = new TrelloCreator(); + + // 1. check parameters are ok from a syntax point of view + try { + check(data, { + listId: String, + sortIndex: Number, + }); + trelloCreator.checkCards([trelloCard]); + trelloCreator.checkLabels(trelloCard.labels); + trelloCreator.checkActions(trelloCard.actions); + } catch(e) { + throw new Meteor.Error('error-json-schema'); + } + + // 2. check parameters are ok from a business point of view (exist & // authorized) - const list = Lists.findOne(listId); + const list = Lists.findOne(data.listId); if (!list) { throw new Meteor.Error('error-list-doesNotExist'); } @@ -40,83 +353,12 @@ Meteor.methods({ } } - // 3. map all fields for the card to create - const dateOfImport = new Date(); - const cardToCreate = { - archived: trelloCard.closed, - boardId: list.boardId, - // this is a default date, we'll fetch the actual one from the actions array - createdAt: dateOfImport, - dateLastActivity: dateOfImport, - description: trelloCard.desc, - labelIds: [], - listId: list._id, - sort: sortIndex, - title: trelloCard.name, - // XXX use the original user? - userId: Meteor.userId(), - }; - - // 4. find actual creation date - const creationAction = trelloCard.actions.find((action) => { - return action.type === 'createCard'; - }); - if (creationAction) { - cardToCreate.createdAt = creationAction.date; - } - - // 5. map labels - create missing ones - trelloCard.labels.forEach((currentLabel) => { - const { name, color } = currentLabel; - // `addLabel` won't create dublicate labels (ie labels with the same name - // and color) so here it is used more in a "enforceLabelExistence" way. - list.board().addLabel(name, color); - const { _id: labelId } = list.board().getLabel(name, color); - if (labelId) { - cardToCreate.labelIds.push(labelId); - } - }); - - // 6. insert new card into list - const cardId = Cards.direct.insert(cardToCreate); - Activities.direct.insert({ - activityType: 'importCard', - boardId: cardToCreate.boardId, - cardId, - createdAt: dateOfImport, - listId: cardToCreate.listId, - source: { - id: trelloCard.id, - system: 'Trello', - url: trelloCard.url, - }, - // we attribute the import to current user, not the one from the original - // card - userId: Meteor.userId(), - }); - - // 7. parse actions and add comments - trelloCard.actions.forEach((currentAction) => { - if (currentAction.type === 'commentCard') { - const commentToCreate = { - boardId: list.boardId, - cardId, - createdAt: currentAction.date, - text: currentAction.data.text, - // XXX use the original comment user instead - userId: Meteor.userId(), - }; - const commentId = CardComments.direct.insert(commentToCreate); - Activities.direct.insert({ - activityType: 'addComment', - boardId: commentToCreate.boardId, - cardId: commentToCreate.cardId, - commentId, - createdAt: commentToCreate.createdAt, - userId: commentToCreate.userId, - }); - } - }); - return cardId; + // 3. create all elements + trelloCreator.lists[trelloCard.idList] = data.listId; + trelloCreator.parseActions(trelloCard.actions); + const board = list.board(); + trelloCreator.createLabels(trelloCard.labels, board); + const cardIds = trelloCreator.createCardsAndComments([trelloCard], board._id); + return cardIds[0]; }, }); |