diff options
author | Lauri Ojansivu <x@xet7.org> | 2017-07-12 01:29:57 +0300 |
---|---|---|
committer | Lauri Ojansivu <x@xet7.org> | 2017-07-12 01:29:57 +0300 |
commit | 1aea465a23ccbc70301ba7bd2d544a3c1d9e0d29 (patch) | |
tree | 18043c74084e5e925583f49161bef1c2a78524fd | |
parent | 61b2c91ffed53a616eeab0cee4e8b0c4eb7f764e (diff) | |
parent | b25436e1bf0c9085bd2f21ef1bc8b12c4f6b179e (diff) | |
download | wekan-1aea465a23ccbc70301ba7bd2d544a3c1d9e0d29.tar.gz wekan-1aea465a23ccbc70301ba7bd2d544a3c1d9e0d29.tar.bz2 wekan-1aea465a23ccbc70301ba7bd2d544a3c1d9e0d29.zip |
Merge branch 'GhassenRjab-devel' into devel
Import Wekan board. Thanks to GhassenRjab !
44 files changed, 1404 insertions, 676 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f812c50..967c8aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,14 @@ This release adds the following new features: [related fix](https://github.com/wekan/wekan/pull/1097); * [When finished input of checklist item, open new checklist item](https://github.com/wekan/wekan/pull/1099); -* [Improve UI design of checklist items](https://github.com/wekan/wekan/pull/1108). +* [Improve UI design of checklist items](https://github.com/wekan/wekan/pull/1108); +* [Import Wekan board](https://github.com/wekan/wekan/pull/1117). and fixes the following bugs: * [Possible to add empty item to checklist](https://github.com/wekan/wekan/pull/1107). -Thanks to GitHub users nztqa and zarnifoulette for their contributions! +Thanks to GitHub users GhassenRjab, nztqa and zarnifoulette for their contributions. # v0.27 2017-06-28 Wekan release diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index e61aea35..a5b7face 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -191,8 +191,14 @@ template(name="createBoard") input.primary.wide(type="submit" value="{{_ 'create'}}") span.quiet | {{_ 'or'}} - a(href="{{pathFor 'import'}}") {{_ 'import-board'}} + a.js-import-board {{_ 'import-board'}} +template(name="chooseBoardSource") + ul.pop-over-list + li + a(href="{{pathFor 'import/trello'}}") {{_ 'from-trello'}} + li + a(href="{{pathFor 'import/wekan'}}") {{_ 'from-wekan'}} template(name="boardChangeTitlePopup") form diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 06defbfa..c8b44824 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -174,10 +174,17 @@ const CreateBoard = BlazeComponent.extendComponent({ 'click .js-change-visibility': this.toggleVisibilityMenu, 'click .js-import': Popup.open('boardImportBoard'), submit: this.onSubmit, + 'click .js-import-board': Popup.open('chooseBoardSource'), }]; }, }).register('createBoardPopup'); +BlazeComponent.extendComponent({ + template() { + return 'chooseBoardSource'; + }, +}).register('chooseBoardSourcePopup'); + (class HeaderBarCreateBoard extends CreateBoard { onSubmit(evt) { super.onSubmit(evt); diff --git a/client/components/import/import.jade b/client/components/import/import.jade index d4def7d8..d1b3489a 100644 --- a/client/components/import/import.jade +++ b/client/components/import/import.jade @@ -2,7 +2,7 @@ template(name="importHeaderBar") h1 a.back-btn(href="{{pathFor 'home'}}") i.fa.fa-chevron-left - | {{_ 'import-board-title'}} + | {{_ title}} template(name="import") .wrapper @@ -12,7 +12,7 @@ template(name="import") template(name="importTextarea") form - p: label(for='import-textarea') {{_ 'import-board-trello-instruction'}} + p: label(for='import-textarea') {{_ instruction}} textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus) | {{jsonText}} input.primary.wide(type="submit" value="{{_ 'import'}}") diff --git a/client/components/import/import.js b/client/components/import/import.js index 11a5308a..d02637d6 100644 --- a/client/components/import/import.js +++ b/client/components/import/import.js @@ -1,3 +1,12 @@ +import trelloMembersMapper from './trelloMembersMapper'; +import wekanMembersMapper from './wekanMembersMapper'; + +BlazeComponent.extendComponent({ + title() { + return `import-board-title-${Session.get('importSource')}!`; + }, +}).register('importHeaderBar'); + BlazeComponent.extendComponent({ onCreated() { this.error = new ReactiveVar(''); @@ -5,6 +14,7 @@ BlazeComponent.extendComponent({ this._currentStepIndex = new ReactiveVar(0); this.importedData = new ReactiveVar(); this.membersToMap = new ReactiveVar([]); + this.importSource = Session.get('importSource'); }, currentTemplate() { @@ -27,7 +37,10 @@ BlazeComponent.extendComponent({ const dataObject = JSON.parse(dataJson); this.setError(''); this.importedData.set(dataObject); - this._prepareAdditionalData(dataObject); + const membersToMap = this._prepareAdditionalData(dataObject); + // store members data and mapping in Session + // (we go deep and 2-way, so storing in data context is not a viable option) + this.membersToMap.set(membersToMap); this.nextStep(); } catch (e) { this.setError('error-json-malformed'); @@ -51,7 +64,10 @@ BlazeComponent.extendComponent({ additionalData.membersMapping = mappingById; } this.membersToMap.set([]); - Meteor.call('importTrelloBoard', this.importedData.get(), additionalData, + Meteor.call('importBoard', + this.importedData.get(), + additionalData, + this.importSource, (err, res) => { if (err) { this.setError(err.error); @@ -63,20 +79,16 @@ BlazeComponent.extendComponent({ }, _prepareAdditionalData(dataObject) { - // 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 = dataObject.members; - // auto-map based on username - membersToMap.forEach((importedMember) => { - const wekanUser = Users.findOne({ username: importedMember.username }); - if (wekanUser) { - importedMember.wekanId = wekanUser._id; - } - }); - // store members data and mapping in Session - // (we go deep and 2-way, so storing in data context is not a viable option) - this.membersToMap.set(membersToMap); + const importSource = Session.get('importSource'); + let membersToMap; + switch (importSource) { + case 'trello': + membersToMap = trelloMembersMapper.getMembersToMap(dataObject); + break; + case 'wekan': + membersToMap = wekanMembersMapper.getMembersToMap(dataObject); + break; + } return membersToMap; }, @@ -90,6 +102,10 @@ BlazeComponent.extendComponent({ return 'importTextarea'; }, + instruction() { + return `import-board-instruction-${Session.get('importSource')}!`; + }, + events() { return [{ submit(evt) { diff --git a/client/components/import/trelloMembersMapper.js b/client/components/import/trelloMembersMapper.js new file mode 100644 index 00000000..0f353bf1 --- /dev/null +++ b/client/components/import/trelloMembersMapper.js @@ -0,0 +1,14 @@ +export function 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; + // auto-map based on username + membersToMap.forEach((importedMember) => { + const wekanUser = Users.findOne({ username: importedMember.username }); + if (wekanUser) { + importedMember.wekanId = wekanUser._id; + } + }); + return membersToMap; +} diff --git a/client/components/import/wekanMembersMapper.js b/client/components/import/wekanMembersMapper.js new file mode 100644 index 00000000..f4c110f7 --- /dev/null +++ b/client/components/import/wekanMembersMapper.js @@ -0,0 +1,24 @@ +export function 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; +} diff --git a/config/router.js b/config/router.js index 75f6ab58..d4d13be5 100644 --- a/config/router.js +++ b/config/router.js @@ -80,19 +80,16 @@ FlowRouter.route('/shortcuts', { }, }); -FlowRouter.route('/import', { +FlowRouter.route('/import/:source', { name: 'import', - triggersEnter: [ - AccountsTemplates.ensureSignedIn, - () => { - Session.set('currentBoard', null); - Session.set('currentCard', null); + triggersEnter: [AccountsTemplates.ensureSignedIn], + action(params) { + Session.set('currentBoard', null); + Session.set('currentCard', null); + Session.set('importSource', params.source); - Filter.reset(); - EscapeActions.executeAll(); - }, - ], - action() { + Filter.reset(); + EscapeActions.executeAll(); BlazeLayout.render('defaultLayout', { headerBar: 'importHeaderBar', content: 'import', @@ -132,6 +129,7 @@ const redirections = { '/boards': '/', '/boards/:id/:slug': '/b/:id/:slug', '/boards/:id/:slug/:cardId': '/b/:id/:slug/:cardId', + '/import': '/import/trello', }; _.each(redirections, (newPath, oldPath) => { diff --git a/i18n/ar.i18n.json b/i18n/ar.i18n.json index cd565776..c4fefcb6 100644 --- a/i18n/ar.i18n.json +++ b/i18n/ar.i18n.json @@ -145,6 +145,7 @@ "computer": "حاسوب", "create": "إنشاء", "createBoardPopup-title": "إنشاء لوحة", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "إنشاء علامة", "current": "الحالي", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "إنشاء لوحة", "home": "الرئيسية", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/br.i18n.json b/i18n/br.i18n.json index 497bc003..ecc2f0b2 100644 --- a/i18n/br.i18n.json +++ b/i18n/br.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Krouiñ", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/ca.i18n.json b/i18n/ca.i18n.json index df3d5053..4fb0937d 100644 --- a/i18n/ca.i18n.json +++ b/i18n/ca.i18n.json @@ -145,6 +145,7 @@ "computer": "Ordinador", "create": "Crea", "createBoardPopup-title": "Crea tauler", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Crea etiqueta", "current": "Actual", "date": "Data", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Crea tauler", "home": "Inici", "import": "importa", - "import-board": "Importa des de Trello", - "import-board-title": "Importa tauler des de Trello", - "import-board-trello-instruction": "En el teu tauler Trello, ves a 'Menú', 'Més'.' Imprimir i Exportar', 'Exportar JSON', i copia el text resultant.", + "import-board": "import board", + "import-board-title-trello": "Importa tauler des de Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "En el teu tauler Trello, ves a 'Menú', 'Més'.' Imprimir i Exportar', 'Exportar JSON', i copia el text resultant.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Aferra codi JSON vàlid aquí", "import-map-members": "Mapeja el membres", "import-members-map": "El tauler importat conté membres. Assigna els membres que vulguis importar a usuaris Wekan", diff --git a/i18n/cs.i18n.json b/i18n/cs.i18n.json index 36d46ec0..83cf8522 100644 --- a/i18n/cs.i18n.json +++ b/i18n/cs.i18n.json @@ -145,6 +145,7 @@ "computer": "Počítač", "create": "Vytvořit", "createBoardPopup-title": "Vytvořit tablo", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Vytvořit štítek", "current": "Aktuální", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Vytvořit tablo", "home": "Domů", "import": "Import", - "import-board": "Importovat ze služby Trello", - "import-board-title": "Import board from Trello", - "import-board-trello-instruction": "Na svém Trello tablu, otevři 'Menu', pak 'More', 'Print and Export', 'Export JSON', a zkopíruj výsledný text", + "import-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "Na svém Trello tablu, otevři 'Menu', pak 'More', 'Print and Export', 'Export JSON', a zkopíruj výsledný text", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Sem vlož validní JSON data", "import-map-members": "Map members", "import-members-map": "Toto importované tablo obsahuje několik členů. Namapuj členy z importu na uživatelské účty Wekan.", diff --git a/i18n/de.i18n.json b/i18n/de.i18n.json index 912b9597..dec0d416 100644 --- a/i18n/de.i18n.json +++ b/i18n/de.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Erstellen", "createBoardPopup-title": "Board erstellen", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Label erstellen", "current": "aktuell", "date": "Datum", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Board erstellen", "home": "Home", "import": "Importieren", - "import-board": "von Trello importieren", - "import-board-title": "Board von Trello importieren", - "import-board-trello-instruction": "Gehen Sie in ihrem Trello-Board auf 'Menü', dann 'Mehr', 'Drucken und Exportieren', 'JSON-Export' und kopieren Sie den dort angezeigten Text", + "import-board": "import board", + "import-board-title-trello": "Board von Trello importieren", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "Gehen Sie in ihrem Trello-Board auf 'Menü', dann 'Mehr', 'Drucken und Exportieren', 'JSON-Export' und kopieren Sie den dort angezeigten Text", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Fügen Sie die korrekten JSON-Daten hier ein", "import-map-members": "Mitglieder zuordnen", "import-members-map": "Das importierte Board hat einige Mitglieder. Bitte ordnen Sie die Mitglieder, die importiert werden sollen, Wekan-Nutzern zu", diff --git a/i18n/en-GB.i18n.json b/i18n/en-GB.i18n.json index 718b7bed..92800e5e 100644 --- a/i18n/en-GB.i18n.json +++ b/i18n/en-GB.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Create", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 122d0b8d..ed80fad2 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Create", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/eo.i18n.json b/i18n/eo.i18n.json index 17c758c2..00245a93 100644 --- a/i18n/eo.i18n.json +++ b/i18n/eo.i18n.json @@ -145,6 +145,7 @@ "computer": "Komputilo", "create": "Krei", "createBoardPopup-title": "Krei ", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Dato", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Krei ", "home": "Hejmo", "import": "Importi", - "import-board": "Importi de Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/es.i18n.json b/i18n/es.i18n.json index 4076f79e..a1ba9744 100644 --- a/i18n/es.i18n.json +++ b/i18n/es.i18n.json @@ -145,6 +145,7 @@ "computer": "Ordenador", "create": "Crear", "createBoardPopup-title": "Crear tablero", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Crear etiqueta", "current": "actual", "date": "Fecha", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Crear tablero", "home": "Inicio", "import": "Importar", - "import-board": "importar desde Trello", - "import-board-title": "Importar tablero desde 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-board": "import board", + "import-board-title-trello": "Importar tablero desde Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Mapa de miembros", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/eu.i18n.json b/i18n/eu.i18n.json index 2ac1c6da..576e94fb 100644 --- a/i18n/eu.i18n.json +++ b/i18n/eu.i18n.json @@ -145,6 +145,7 @@ "computer": "Ordenagailua", "create": "Sortu", "createBoardPopup-title": "Sortu arbela", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Sortu etiketa", "current": "unekoa", "date": "Data", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Sortu arbela", "home": "Hasiera", "import": "Inportatu", - "import-board": "Inportatu Trellotik", - "import-board-title": "Inportatu arbela Trellotik", - "import-board-trello-instruction": "Zure Trello arbelean, aukeratu 'Menu\", 'More', 'Print and Export', 'Export JSON', eta kopiatu jasotako testua hemen.", + "import-board": "import board", + "import-board-title-trello": "Inportatu arbela Trellotik", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "Zure Trello arbelean, aukeratu 'Menu\", 'More', 'Print and Export', 'Export JSON', eta kopiatu jasotako testua hemen.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Isatsi baliozko JSON datuak hemen", "import-map-members": "Kideen mapa", "import-members-map": "Inportatu duzun arbela kide batzuk ditu, mesedez lotu inportatu nahi dituzun kideak Wekan erabiltzaileekin", diff --git a/i18n/fa.i18n.json b/i18n/fa.i18n.json index 9044b79f..8a1ab7e9 100644 --- a/i18n/fa.i18n.json +++ b/i18n/fa.i18n.json @@ -145,6 +145,7 @@ "computer": "رایانه", "create": "ایجاد", "createBoardPopup-title": "ایجاد تخته", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "ایجاد برچسب", "current": "جاری", "date": "تاریخ", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "ایجاد تخته", "home": "خانه", "import": "وارد کردن", - "import-board": "وارد کردن از Trello", - "import-board-title": "وارد کردن تخته ها از Trello", - "import-board-trello-instruction": "در Trello-ی خود به 'Menu'، 'More'، 'Print'، 'Export to JSON رفته و متن نهایی را دراینجا وارد نمایید.", + "import-board": "import board", + "import-board-title-trello": "وارد کردن تخته ها از Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "در Trello-ی خود به 'Menu'، 'More'، 'Print'، 'Export to JSON رفته و متن نهایی را دراینجا وارد نمایید.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "اطلاعات Json معتبر خود را اینجا وارد کنید.", "import-map-members": "نگاشت اعضا", "import-members-map": "تعدادی عضو در تخته وارد شده می باشد. لطفا کاربرانی که باید وارد نرم افزار بشوند را مشخص کنید.", diff --git a/i18n/fi.i18n.json b/i18n/fi.i18n.json index dd1f51ab..724fca47 100644 --- a/i18n/fi.i18n.json +++ b/i18n/fi.i18n.json @@ -145,6 +145,7 @@ "computer": "Tietokone", "create": "Luo", "createBoardPopup-title": "Luo taulu", + "chooseBoardSourcePopup-title": "Tuo taulu", "createLabelPopup-title": "Luo tunniste", "current": "nykyinen", "date": "Päivämäärä", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Luo taulu", "home": "Koti", "import": "Tuo", - "import-board": "tuo Trellosta", - "import-board-title": "Tuo taulu Trellosta", - "import-board-trello-instruction": "Trello taulullasi, mene 'Menu', sitten 'More', 'Print and Export', 'Export JSON', ja kopioi tuloksena saamasi teksti", + "import-board": "tuo taulu", + "import-board-title-trello": "Tuo taulu Trellosta", + "import-board-title-wekan": "Tuo taulu Wekanista", + "from-trello": "Trellosta", + "from-wekan": "Wekanista", + "import-board-instruction-trello": "Trello taulullasi, mene 'Menu', sitten 'More', 'Print and Export', 'Export JSON', ja kopioi tuloksena saamasi teksti", + "import-board-instruction-wekan": "Wekan taulullasi, mene 'Valikko', sitten 'Vie taulu', ja kopioi teksti ladatusta tiedostosta.", "import-json-placeholder": "Liitä kelvollinen JSON tietosi tähän", "import-map-members": "Vastaavat jäsenet", "import-members-map": "Tuomallasi taululla on muutamia jäseniä. Ole hyvä ja valitse tuomiasi jäseniä vastaavat Wekan käyttäjät", diff --git a/i18n/fr.i18n.json b/i18n/fr.i18n.json index d27aa1d7..64d2cb97 100644 --- a/i18n/fr.i18n.json +++ b/i18n/fr.i18n.json @@ -145,6 +145,7 @@ "computer": "Ordinateur", "create": "Créer", "createBoardPopup-title": "Créer un tableau", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Créer une étiquette", "current": "actuel", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Créer un tableau", "home": "Accueil", "import": "Importer", - "import-board": "Importer depuis Trello", - "import-board-title": "Importer le tableau depuis Trello", - "import-board-trello-instruction": "Dans votre tableau Trello, allez sur 'Menu', puis sur 'Plus', 'Imprimer et exporter', 'Exporter en JSON' et copiez le texte du résultat", + "import-board": "import board", + "import-board-title-trello": "Importer le tableau depuis Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "Dans votre tableau Trello, allez sur 'Menu', puis sur 'Plus', 'Imprimer et exporter', 'Exporter en JSON' et copiez le texte du résultat", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Collez ici les données JSON valides", "import-map-members": "Faire correspondre aux membres", "import-members-map": "Le tableau que vous venez d'importer contient des membres. Veuillez associer les membres que vous souhaitez importer à des utilisateurs de Wekan.", diff --git a/i18n/he.i18n.json b/i18n/he.i18n.json index 736b29c4..82672dd2 100644 --- a/i18n/he.i18n.json +++ b/i18n/he.i18n.json @@ -145,6 +145,7 @@ "computer": "מחשב", "create": "יצירה", "createBoardPopup-title": "יצירת לוח", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "יצירת תווית", "current": "נוכחי", "date": "תאריך", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "יצירת לוח", "home": "בית", "import": "ייבוא", - "import-board": "ייבוא מטרלו", - "import-board-title": "ייבוא לוח מטרלו", - "import-board-trello-instruction": "בלוח הטרלו שלך, עליך ללחוץ על 'Menu', ואז על 'More', 'Print and Export', 'Export JSON' ולהעתיק את הטקסט שנוצר.", + "import-board": "import board", + "import-board-title-trello": "ייבוא לוח מטרלו", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "בלוח הטרלו שלך, עליך ללחוץ על 'Menu', ואז על 'More', 'Print and Export', 'Export JSON' ולהעתיק את הטקסט שנוצר.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "יש להדביק את נתוני ה־JSON התקינים לכאן", "import-map-members": "מיפוי חברים", "import-members-map": "הלוחות המיובאים שלך מכילים חברים. נא למפות את החברים שברצונך לייבא למשתמשי Wekan", diff --git a/i18n/hu.i18n.json b/i18n/hu.i18n.json index b9cf5585..66f7bcbf 100644 --- a/i18n/hu.i18n.json +++ b/i18n/hu.i18n.json @@ -145,6 +145,7 @@ "computer": "Számítógép", "create": "Létrehoz", "createBoardPopup-title": "Új tábla", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Új cimke", "current": "aktuális", "date": "Dátum", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Új tábla", "home": "Kezdőlap", "import": "Importálás", - "import-board": "importálás a Trello-ról", - "import-board-title": "Tábla importálása a Trello-ról", - "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board": "import board", + "import-board-title-trello": "Tábla importálása a Trello-ról", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Tagok megjelenítése", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/id.i18n.json b/i18n/id.i18n.json index 45c6187e..fd36f33a 100644 --- a/i18n/id.i18n.json +++ b/i18n/id.i18n.json @@ -145,6 +145,7 @@ "computer": "Komputer", "create": "Buat", "createBoardPopup-title": "Buat Panel", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Buat Label", "current": "sekarang", "date": "Tanggal", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Buat Panel", "home": "Beranda", "import": "Impor", - "import-board": "Impor dari Trello", - "import-board-title": "Impor panel dari Trello", - "import-board-trello-instruction": "Di panel Trello anda, ke 'Menu', terus 'More', 'Print and Export','Export JSON', dan salin hasilnya", + "import-board": "import board", + "import-board-title-trello": "Impor panel dari Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "Di panel Trello anda, ke 'Menu', terus 'More', 'Print and Export','Export JSON', dan salin hasilnya", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Tempelkan data JSON yang sah disini", "import-map-members": "Petakan partisipan", "import-members-map": "Panel yang anda impor punya partisipan. Silahkan petakan anggota yang anda ingin impor ke user [Wekan]", diff --git a/i18n/it.i18n.json b/i18n/it.i18n.json index 1214dd82..22cf5b38 100644 --- a/i18n/it.i18n.json +++ b/i18n/it.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Crea", "createBoardPopup-title": "Crea bacheca", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Crea etichetta", "current": "corrente", "date": "Data", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Crea bacheca", "home": "Home", "import": "Importa", - "import-board": "importa da Trello", - "import-board-title": "Importa una bacheca da Trello", - "import-board-trello-instruction": "Nella tua bacheca Trello vai a 'Menu', poi 'Altro', 'Stampa ed esporta', 'Esporta JSON', e copia il testo che compare.", + "import-board": "import board", + "import-board-title-trello": "Importa una bacheca da Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "Nella tua bacheca Trello vai a 'Menu', poi 'Altro', 'Stampa ed esporta', 'Esporta JSON', e copia il testo che compare.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Incolla un JSON valido qui", "import-map-members": "Mappatura dei membri", "import-members-map": "La bacheca che hai importato ha alcuni membri. Per favore scegli i membri che vuoi vengano importati negli utenti di Wekan", diff --git a/i18n/ja.i18n.json b/i18n/ja.i18n.json index c7b5cbd7..c8c4e4d6 100644 --- a/i18n/ja.i18n.json +++ b/i18n/ja.i18n.json @@ -145,6 +145,7 @@ "computer": "コンピューター", "create": "作成", "createBoardPopup-title": "ボードの作成", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "ラベルの作成", "current": "現在", "date": "日付", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "ボードの作成", "home": "ホーム", "import": "インポート", - "import-board": "Trelloからインポート", - "import-board-title": "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-board": "import board", + "import-board-title-trello": "Trelloからボードをインポート", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "メンバーを紐付け", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/ko.i18n.json b/i18n/ko.i18n.json index c3abf6d1..39237cb0 100644 --- a/i18n/ko.i18n.json +++ b/i18n/ko.i18n.json @@ -38,14 +38,14 @@ "activity-unjoined": "%s에서 멤버 해제", "activity-checklist-added": "%s에 체크리스트를 추가함", "add": "추가", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-card": "Add Card", - "add-checklist": "Add Checklist", + "add-attachment": "첨부파일 추가", + "add-board": "보드 추가", + "add-card": "카드 추가", + "add-checklist": "체크리스트 추가", "add-checklist-item": "체크리스트에 항목 추가", "add-cover": "커버 추가", - "add-label": "Add Label", - "add-list": "Add List", + "add-label": "라벨 추가", + "add-list": "리스트 추가", "add-members": "멤버 추가", "added": "추가됨", "addMemberPopup-title": "멤버", @@ -60,13 +60,13 @@ "archive-all": "모두 보관", "archive-board": "보드 저장소 보관", "archive-card": "카드 저장소 보관", - "archive-list": "Archive List", + "archive-list": "리스트 저장소 보관", "archive-selection": "저장소 선택", "archiveBoardPopup-title": "보드를 저장소에 보관하시겠습니까?", "archived-items": "보관된 아이템", - "archived-boards": "Archived Boards", - "restore-board": "Restore Board", - "no-archived-boards": "No Archived Boards.", + "archived-boards": "저장소 보관된 보드들", + "restore-board": "보드 복구", + "no-archived-boards": "보관된 보드가 없습니다.", "archives": "저장소", "assign-member": "멤버 지정", "attached": "첨부됨", @@ -74,8 +74,8 @@ "attachment-delete-pop": "영구 첨부파일을 삭제합니다. 되돌릴 수 없습니다.", "attachmentDeletePopup-title": "첨부 파일을 삭제합니까?", "attachments": "첨부 파일", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "auto-watch": "생성한 보드를 자동으로 감시합니다.", + "avatar-too-big": "아바타 파일이 너무 큽니다. (최대 70KB)", "back": "뒤로", "board-change-color": "보드 색 변경", "board-nb-stars": "%s개의 별", @@ -139,12 +139,13 @@ "color-sky": "스카이", "color-yellow": "옐로우", "comment": "댓글", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", + "comment-placeholder": "댓글 입력", + "comment-only": "댓글만 입력 가능", "comment-only-desc": "카드에 댓글만 달수 있습니다.", "computer": "내 컴퓨터", "create": "생성", "createBoardPopup-title": "보드 생성", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "라벨 생성", "current": "경향", "date": "날짜", @@ -186,7 +187,7 @@ "error-json-schema": "JSON 데이터에 정보가 올바른 형식으로 포함되어 있지 않습니다.", "error-list-doesNotExist": "목록이 없습니다.", "error-user-doesNotExist": "멤버의 정보가 없습니다.", - "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notAllowSelf": "자기 자신을 초대할 수 없습니다.", "error-user-notCreated": "유저가 생성되지 않았습니다.", "error-username-taken": "중복된 아이디 입니다.", "export-board": "보드 내보내기", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "보드 생성", "home": "홈", "import": "가져오기", - "import-board": "Trello에서 가져오기", - "import-board-title": "Trello에서 보드 가져오기", - "import-board-trello-instruction": "Trello 게시판에서 'Menu' -> 'More' -> 'Print and Export', 'Export JSON' 선택하여 텍스트 결과값 복사", + "import-board": "import board", + "import-board-title-trello": "Trello에서 보드 가져오기", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "Trello 게시판에서 'Menu' -> 'More' -> 'Print and Export', 'Export JSON' 선택하여 텍스트 결과값 복사", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "유효한 JSON 데이터를 여기에 붙여 넣으십시오.", "import-map-members": "보드 멤버들", "import-members-map": "가져온 보드에는 멤버가 있습니다. 원하는 멤버를 Wekan 멤버로 매핑하세요.", @@ -234,9 +239,9 @@ "listActionPopup-title": "동작 목록", "listImportCardPopup-title": "Trello 카드 가져 오기", "listMorePopup-title": "더보기", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can archive a list to remove it from the board and preserve the activity.", + "link-list": "이 리스트에 링크", + "list-delete-pop": "모든 작업이 활동내역에서 제거되며 리스트를 복구 할 수 없습니다. 실행 취소는 불가능 합니다.", + "list-delete-suggest-archive": "리스트를 보관하여 보드에서 삭제하고 활동내역을 보존 할 수 있습니다.", "lists": "목록들", "log-out": "로그아웃", "log-in": "로그인", @@ -281,8 +286,8 @@ "quick-access-description": "여기에 바로 가기를 추가하려면 보드에 별 표시를 체크하세요.", "remove-cover": "커버 제거", "remove-from-board": "보드에서 제거", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", + "remove-label": "라벨 제거", + "listDeletePopup-title": "리스트를 삭제합니까?", "remove-member": "멤버 제거", "remove-member-from-card": "카드에서 제거", "remove-member-pop": "__boardTitle__에서 __name__(__username__) 을 제거합니까? 이 보드의 모든 카드에서 제거됩니다. 해당 내용을 __name__(__username__) 은(는) 알림으로 받게됩니다.", @@ -292,7 +297,7 @@ "restore": "복구", "save": "저장", "search": "검색", - "select-color": "Select Color", + "select-color": "색 선택", "shortcut-assign-self": "현재 카드에 자신을 지정하세요.", "shortcut-autocomplete-emoji": "이모티콘 자동완성", "shortcut-autocomplete-members": "멤버 자동완성", @@ -344,16 +349,16 @@ "email-addresses": "이메일 주소", "smtp-host-description": "이메일을 처리하는 SMTP 서버의 주소입니다.", "smtp-port-description": "SMTP 서버가 보내는 전자 메일에 사용하는 포트입니다.", - "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-tls-description": "SMTP 서버에 TLS 지원 사용", "smtp-host": "SMTP 호스트", "smtp-port": "SMTP 포트", "smtp-username": "사용자 이름", "smtp-password": "암호", - "smtp-tls": "TLS support", + "smtp-tls": "TLS 지원", "send-from": "보낸 사람", "invitation-code": "초대 코드", "email-invite-register-subject": "\"__inviter__ 님이 당신에게 초대장을 보냈습니다.", "email-invite-register-text": "\"__user__ 님, \n\n__inviter__ 님이 Wekan 보드에 협업을 위하여 초대합니다.\n\n아래 링크를 클릭해주세요 : \n__url__\n\n그리고 초대 코드는 __icode__ 입니다.\n\n감사합니다.", "error-invitation-code-not-exist": "초대 코드가 존재하지 않습니다.", - "error-notAuthorized": "You are not authorized to view this page." + "error-notAuthorized": "이 페이지를 볼 수있는 권한이 없습니다." }
\ No newline at end of file diff --git a/i18n/nb.i18n.json b/i18n/nb.i18n.json index 02547da3..c092676f 100644 --- a/i18n/nb.i18n.json +++ b/i18n/nb.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Create", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/pl.i18n.json b/i18n/pl.i18n.json index 4e3a33c8..91a6cbbf 100644 --- a/i18n/pl.i18n.json +++ b/i18n/pl.i18n.json @@ -145,6 +145,7 @@ "computer": "Komputer", "create": "Utwórz", "createBoardPopup-title": "Utwórz tablicę", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Utwórz etykietę", "current": "obecny", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Utwórz tablicę", "home": "Strona główna", "import": "Importu", - "import-board": "zaimportuj z Trello", - "import-board-title": "Import board from Trello", - "import-board-trello-instruction": "W twojej tablicy na Trello, idź do 'Menu', następnie 'More', 'Print and Export', 'Export JSON' i skopiuj wynik", + "import-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "W twojej tablicy na Trello, idź do 'Menu', następnie 'More', 'Print and Export', 'Export JSON' i skopiuj wynik", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Wklej twój JSON tutaj", "import-map-members": "Map members", "import-members-map": "Twoje zaimportowane tablice mają kilku członków. Proszę wybierz członków których chcesz zaimportować do Wekan", diff --git a/i18n/pt-BR.i18n.json b/i18n/pt-BR.i18n.json index 3ef482ff..dc7764c8 100644 --- a/i18n/pt-BR.i18n.json +++ b/i18n/pt-BR.i18n.json @@ -145,6 +145,7 @@ "computer": "Computador", "create": "Criar", "createBoardPopup-title": "Criar Quadro", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Criar Etiqueta", "current": "atual", "date": "Data", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Criar Quadro", "home": "Início", "import": "Importar", - "import-board": "Importar do Trello", - "import-board-title": "Importar board do Trello", - "import-board-trello-instruction": "No seu quadro do Trello, vá em 'Menu', depois em 'Mais', 'Imprimir e Exportar', 'Exportar JSON', então copie o texto emitido", + "import-board": "import board", + "import-board-title-trello": "Importar board do Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "No seu quadro do Trello, vá em 'Menu', depois em 'Mais', 'Imprimir e Exportar', 'Exportar JSON', então copie o texto emitido", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Cole seus dados JSON válidos aqui", "import-map-members": "Mapear membros", "import-members-map": "O seu quadro importado tem alguns membros. Por favor determine os membros que você deseja importar para os usuários Wekan", diff --git a/i18n/ro.i18n.json b/i18n/ro.i18n.json index 51ddf2fb..b3d6424d 100644 --- a/i18n/ro.i18n.json +++ b/i18n/ro.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Create", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/ru.i18n.json b/i18n/ru.i18n.json index ab71df62..e4c1789f 100644 --- a/i18n/ru.i18n.json +++ b/i18n/ru.i18n.json @@ -145,6 +145,7 @@ "computer": "Загрузить с компьютера", "create": "Создать", "createBoardPopup-title": "Создать доску", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Создать метку", "current": "Текущий", "date": "Дата", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Создать доску", "home": "Главная", "import": "Импорт", - "import-board": "Импорт с Trello", - "import-board-title": "Импортировать доску из Trello", - "import-board-trello-instruction": "На вашей Trello доске нажмите “Menu” - “More” - “Print and export - “Export JSON” и скопируйте полученный текст", + "import-board": "import board", + "import-board-title-trello": "Импортировать доску из Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "На вашей Trello доске нажмите “Menu” - “More” - “Print and export - “Export JSON” и скопируйте полученный текст", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Вставьте JSON сюда", "import-map-members": "Карта пользователей", "import-members-map": "Вы ипортировали доску с пользователями. Пожалуйста, составьте карту пользователей, которых вы хотите импортировать в Wekan пользователей", diff --git a/i18n/sr.i18n.json b/i18n/sr.i18n.json index a5f0bb52..717edcee 100644 --- a/i18n/sr.i18n.json +++ b/i18n/sr.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Create", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Datum", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Uvezi tablu iz Trella", - "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board": "import board", + "import-board-title-trello": "Uvezi tablu iz Trella", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Mapiraj članove", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/sv.i18n.json b/i18n/sv.i18n.json index e92227d1..347b6f94 100644 --- a/i18n/sv.i18n.json +++ b/i18n/sv.i18n.json @@ -145,6 +145,7 @@ "computer": "Dator", "create": "Skapa", "createBoardPopup-title": "Skapa anslagstavla", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Skapa etikett", "current": "aktuell", "date": "Datum", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Skapa anslagstavla", "home": "Hem", "import": "Importera", - "import-board": "Importera från Trello", - "import-board-title": "Importera anslagstavla från Trello", - "import-board-trello-instruction": "I din Trello-anslagstavla, gå till 'Meny', sedan 'Mera', 'Skriv ut och exportera', 'Exportera JSON' och kopiera den resulterande text.", + "import-board": "import board", + "import-board-title-trello": "Importera anslagstavla från Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "I din Trello-anslagstavla, gå till 'Meny', sedan 'Mera', 'Skriv ut och exportera', 'Exportera JSON' och kopiera den resulterande text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Klistra in giltigt JSON data här", "import-map-members": "Kartlägg medlemmar", "import-members-map": "Din importerade anslagstavla har några medlemmar. Kartlägg medlemmarna som du vill importera till Wekan-användare", diff --git a/i18n/ta.i18n.json b/i18n/ta.i18n.json index f242b8d4..4e857180 100644 --- a/i18n/ta.i18n.json +++ b/i18n/ta.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Create", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/th.i18n.json b/i18n/th.i18n.json index c20ff2a0..4fe75484 100644 --- a/i18n/th.i18n.json +++ b/i18n/th.i18n.json @@ -145,6 +145,7 @@ "computer": "คอมพิวเตอร์", "create": "สร้าง", "createBoardPopup-title": "สร้างบอร์ด", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "สร้างป้ายกำกับ", "current": "ปัจจุบัน", "date": "วันที่", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "สร้างบอร์ด", "home": "หน้าหลัก", "import": "นำเข้า", - "import-board": "นำเข้าจาก Trello", - "import-board-title": "นำเข้าบอร์ดจาก Trello", - "import-board-trello-instruction": "ใน Trello ของคุณให้ไปที่ 'Menu' และไปที่ More -> Print and Export -> Export JSON และคัดลอกข้อความจากนั้น", + "import-board": "import board", + "import-board-title-trello": "นำเข้าบอร์ดจาก Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "ใน Trello ของคุณให้ไปที่ 'Menu' และไปที่ More -> Print and Export -> Export JSON และคัดลอกข้อความจากนั้น", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "วางข้อมูล JSON ที่ถูกต้องของคุณที่นี่", "import-map-members": "แผนที่สมาชิก", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/tr.i18n.json b/i18n/tr.i18n.json index 09ab7fb7..07252e01 100644 --- a/i18n/tr.i18n.json +++ b/i18n/tr.i18n.json @@ -1,6 +1,6 @@ { "accept": "Kabul Et", - "act-activity-notify": "[Wekan] Aktivite Bildirimi", + "act-activity-notify": "[Wekan] Etkinlik Bildirimi", "act-addAttachment": "__card__ kartına __attachment__ dosyasını ekledi", "act-addComment": "__card__ kartına bir yorum bırakıt: __comment__", "act-createBoard": "__board__ panosunu oluşturdu", @@ -38,14 +38,14 @@ "activity-unjoined": "%s içinden ayrıldı", "activity-checklist-added": "%s içine liste ekledi", "add": "Ekle", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-card": "Add Card", - "add-checklist": "Add Checklist", - "add-checklist-item": "Listeye yeni bir eleman ekle", + "add-attachment": "Ek Ekle", + "add-board": "Pano Ekle", + "add-card": "Kart Ekle", + "add-checklist": "Yapılacak Listesi Ekle", + "add-checklist-item": "Yapılacak listes yeni bir eleman ekle", "add-cover": "Kapak resmi ekle", - "add-label": "Add Label", - "add-list": "Add List", + "add-label": "Etiket Ekle", + "add-list": "Liste Ekle", "add-members": "Üye ekle", "added": "Eklendi", "addMemberPopup-title": "Üyeler", @@ -59,23 +59,23 @@ "archive": "Arşivle", "archive-all": "Tümünü Arşivle", "archive-board": "Panoyu Arşivle", - "archive-card": "Kartı arşivle", - "archive-list": "Archive List", + "archive-card": "Kartı Arşivle", + "archive-list": "Listeyi Arşivle", "archive-selection": "Seçimi arşivle", "archiveBoardPopup-title": "Pano arşivlensin mi?", "archived-items": "Arşivlenmiş Öğeler", - "archived-boards": "Archived Boards", - "restore-board": "Restore Board", - "no-archived-boards": "No Archived Boards.", + "archived-boards": "Arşivlenmiş Panolar", + "restore-board": "Panoyu Tekrar Yükle", + "no-archived-boards": "Arşivlenmiş Pano Yok.", "archives": "Arşiv", "assign-member": "Üye ata", - "attached": "dosya eklendi", + "attached": "dosya(sı) eklendi", "attachment": "Ek Dosya", - "attachment-delete-pop": "Ek dosya silme işlemi kalıcıdır. Geri dönüşü yok", - "attachmentDeletePopup-title": "Ek Dosya Silinsin Mi?", - "attachments": "Ek Dosyalar", + "attachment-delete-pop": "Ek silme işlemi kalıcıdır ve geri alınamaz.", + "attachmentDeletePopup-title": "Ek Silinsin Mi?", + "attachments": "Ekler", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "Avatar boyutu çok büyük (En fazla 70KB olabilir)", "back": "Geri", "board-change-color": "Renk değiştir", "board-nb-stars": "%s yıldız", @@ -124,27 +124,28 @@ "checklists": "Doğrulama Listeleri", "click-to-star": "Bu panoyu yıldızlamak için tıkla.", "click-to-unstar": "Bu panunun yıldızını kaldırmak için tıkla.", - "clipboard": "Panodan (cliboard) veya sürükle ve bırak ile", + "clipboard": "Panodan (clipboard) veya sürükle ve bırak ile", "close": "Kapat", "close-board": "Panoyu kapat", "close-board-pop": "You will be able to restore the board by clicking the “Archives” button from the home header.", "color-black": "siyah", "color-blue": "mavi", "color-green": "yeşil", - "color-lime": "çim", + "color-lime": "misket limonu", "color-orange": "turuncu", "color-pink": "pembe", "color-purple": "mor", "color-red": "kırmızı", "color-sky": "açık mavi", "color-yellow": "sarı", - "comment": "Yorum Gönder", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", + "comment": "Yorum", + "comment-placeholder": "Yorum Yaz", + "comment-only": "Sadece yorum", "comment-only-desc": "Sadece kartlara yorum yazabilir.", "computer": "Bilgisayar", "create": "Oluştur", "createBoardPopup-title": "Pano Oluşturma", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Etiket Oluşturma", "current": "mevcut", "date": "Tarih", @@ -186,7 +187,7 @@ "error-json-schema": "Girdiğin JSON metni tüm bilgileri doğru biçimde barındırmıyor", "error-list-doesNotExist": "Liste bulunamadı", "error-user-doesNotExist": "Kullanıcı bulunamadı", - "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notAllowSelf": "Kendi kendini davet edemezsin", "error-user-notCreated": "Bu üye oluşturulmadı", "error-username-taken": "Kullanıcı adı zaten alınmış", "export-board": "Panoyu dışarı aktar", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Pano Oluşturma", "home": "Ana Sayfa", "import": "İçeri aktar", - "import-board": "Trello'dan içeri aktar", - "import-board-title": "Trello'dan panoları içeri aktarır", - "import-board-trello-instruction": "Trello panonuzda, 'Menü'ye gidip, ardıdan, 'daha fazlası' na tıklayın, 'Yazdır ve Çıktı al' sayfasından 'JSON biçiminde çıktı al' diyip, çıkan metni buraya kopyalayın.", + "import-board": "import board", + "import-board-title-trello": "Trello'dan panoyu içeri aktar", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "Trello panonuzda, 'Menü'ye gidip, ardıdan, 'daha fazlası' na tıklayın, 'Yazdır ve Çıktı al' sayfasından 'JSON biçiminde çıktı al' diyip, çıkan metni buraya kopyalayın.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Geçerli JSON verisini buraya yapıştırın", "import-map-members": "Üyeleri eşleştirme", "import-members-map": "İçe aktardığın panoda bazı kullanıcılar var. Lütfen bu kullanıcıları Wekan panosundaki kullanıcılarla eşleştirin.", @@ -216,7 +221,7 @@ "info": "Bilgiler", "initials": "İlk Harfleri", "invalid-date": "Geçersiz tarih", - "joined": "katılma", + "joined": "katıldı", "just-invited": "Bu panoya şimdi davet edildin.", "keyboard-shortcuts": "Klavye kısayolları", "label-create": "Etiket Oluşturma", diff --git a/i18n/uk.i18n.json b/i18n/uk.i18n.json index aa6b1da3..d072eef4 100644 --- a/i18n/uk.i18n.json +++ b/i18n/uk.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Create", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/vi.i18n.json b/i18n/vi.i18n.json index 839a712b..6dedad46 100644 --- a/i18n/vi.i18n.json +++ b/i18n/vi.i18n.json @@ -145,6 +145,7 @@ "computer": "Computer", "create": "Create", "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "current": "current", "date": "Date", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", - "import-board": "import from Trello", - "import-board-title": "Import board 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-board": "import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "Paste your valid JSON data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users", diff --git a/i18n/zh-CN.i18n.json b/i18n/zh-CN.i18n.json index c04d84f4..22f43f19 100644 --- a/i18n/zh-CN.i18n.json +++ b/i18n/zh-CN.i18n.json @@ -145,6 +145,7 @@ "computer": "从本机上传", "create": "创建", "createBoardPopup-title": "创建看板", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "创建标签", "current": "当前", "date": "日期", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "创建看板", "home": "首页", "import": "导入", - "import-board": "从 Trello 导入", - "import-board-title": "从Trello导入看板", - "import-board-trello-instruction": "在你的Trello看板中,点击“菜单”,然后选择“更多”,“打印与导出”,“导出为 JSON” 并拷贝结果文本", + "import-board": "import board", + "import-board-title-trello": "从Trello导入看板", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "在你的Trello看板中,点击“菜单”,然后选择“更多”,“打印与导出”,“导出为 JSON” 并拷贝结果文本", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "粘贴您有效的 JSON 数据至此", "import-map-members": "映射成员", "import-members-map": "您导入的看板有一些成员。请将您想导入的成员映射到 Wekan 用户。", diff --git a/i18n/zh-TW.i18n.json b/i18n/zh-TW.i18n.json index df1cdb55..2fef492d 100644 --- a/i18n/zh-TW.i18n.json +++ b/i18n/zh-TW.i18n.json @@ -145,6 +145,7 @@ "computer": "從本機上傳", "create": "建立", "createBoardPopup-title": "建立看板", + "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "建立標籤", "current": "目前", "date": "日期", @@ -204,9 +205,13 @@ "headerBarCreateBoardPopup-title": "建立看板", "home": "首頁", "import": "匯入", - "import-board": "匯入 Trello 資料", - "import-board-title": "匯入在 Trello 的看板", - "import-board-trello-instruction": "在你的Trello看板中,點選“功能表”,然後選擇“更多”,“列印與匯出”,“匯出為 JSON” 並拷貝結果文本", + "import-board": "import board", + "import-board-title-trello": "匯入在 Trello 的看板", + "import-board-title-wekan": "Import board from Wekan", + "from-trello": "From Trello", + "from-wekan": "From Wekan", + "import-board-instruction-trello": "在你的Trello看板中,點選“功能表”,然後選擇“更多”,“列印與匯出”,“匯出為 JSON” 並拷貝結果文本", + "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-json-placeholder": "貼上您有效的 JSON 資料至此", "import-map-members": "複製成員", "import-members-map": "您匯入的看板有一些成員。請將您想匯入的成員映射到 Wekan 使用者。", diff --git a/models/import.js b/models/import.js index 39a8835e..2e58c90f 100644 --- a/models/import.js +++ b/models/import.js @@ -1,507 +1,28 @@ -const DateString = Match.Where(function (dateAsString) { - check(dateAsString, String); - return moment(dateAsString, moment.ISO_8601).isValid(); -}); - -class TrelloCreator { - 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 Trello id - // (so we only parse actions once!) - this.createdAt = { - board: null, - cards: {}, - lists: {}, - }; - // The object creator Trello Id, indexed by the object Trello id - // (so we only parse actions once!) - this.createdBy = { - cards: {}, // only cards have a field for that - }; - - // Map of labels Trello ID => Wekan ID - this.labels = {}; - // Map of lists Trello ID => Wekan ID - this.lists = {}; - // Map of cards Trello ID => Wekan ID - this.cards = {}; - // The comments, indexed by Trello card id (to map when importing cards) - this.comments = {}; - // the members, indexed by Trello member id => Wekan user ID - this.members = data.membersMapping ? data.membersMapping : {}; - - // maps a trelloCardId to an array of trelloAttachments - 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 trelloUserId is provided and we have a mapping, - * return it. - * Otherwise return current logged user. - * @param trelloUserId - * @private - */ - _user(trelloUserId) { - if(trelloUserId && this.members[trelloUserId]) { - return this.members[trelloUserId]; - } - return Meteor.userId(); - } - - 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, - })]); - } - - checkChecklists(trelloChecklists) { - check(trelloChecklists, [Match.ObjectIncluding({ - idBoard: String, - idCard: String, - name: String, - checkItems: [Match.ObjectIncluding({ - state: String, - name: String, - })], - })]); - } - - // You must call parseActions before calling this one. - createBoardAndLabels(trelloBoard) { - const boardToCreate = { - archived: trelloBoard.closed, - color: this.getColor(trelloBoard.prefs.background), - // very old boards won't have a creation activity so no creation date - createdAt: this._now(this.createdAt.board), - labels: [], - members: [{ - userId: Meteor.userId(), - isAdmin: true, - isActive: true, - isCommentOnly: false, - }], - permission: this.getPermission(trelloBoard.prefs.permissionLevel), - slug: getSlug(trelloBoard.name) || 'board', - stars: 0, - title: trelloBoard.name, - }; - // now add other members - if(trelloBoard.memberships) { - trelloBoard.memberships.forEach((trelloMembership) => { - const trelloId = trelloMembership.idMember; - // do we have a mapping? - if(this.members[trelloId]) { - const wekanId = this.members[trelloId]; - // do we already have it in our list? - const wekanMember = boardToCreate.members.find((wekanMember) => wekanMember.userId === wekanId); - if(wekanMember) { - // we're already mapped, but maybe with lower rights - if(!wekanMember.isAdmin) { - wekanMember.isAdmin = this.getAdmin(trelloMembership.memberType); - } - } else { - boardToCreate.members.push({ - userId: wekanId, - isAdmin: this.getAdmin(trelloMembership.memberType), - isActive: true, - isCommentOnly: false, - }); - } - } - }); - } - 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 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: trelloBoard.id, - system: 'Trello', - url: trelloBoard.url, - }, - // 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 Trello cards, - * as well as all linked data: activities, comments, and attachments - * @param trelloCards - * @param boardId - * @returns {Array} - */ - createCards(trelloCards, boardId) { - const result = []; - trelloCards.forEach((card) => { - const cardToCreate = { - archived: card.closed, - 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.desc, - listId: this.lists[card.idList], - sort: card.pos, - title: card.name, - // we attribute the card to its creator if available - userId: this._user(this.createdBy.cards[card.id]), - dueAt: card.due ? this._now(card.due) : null, - }; - // add labels - if (card.idLabels) { - cardToCreate.labelIds = card.idLabels.map((trelloId) => { - return this.labels[trelloId]; - }); - } - // add members { - if(card.idMembers) { - const wekanMembers = []; - // we can't just map, as some members may not have been mapped - card.idMembers.forEach((trelloId) => { - if(this.members[trelloId]) { - const wekanId = this.members[trelloId]; - // we may map multiple Trello 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; - } - } - // insert card - const cardId = Cards.direct.insert(cardToCreate); - // keep track of Trello 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: 'Trello', - url: card.url, - }, - // 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.date), - text: comment.data.text, - // we attribute the comment to the original author, default to current user - userId: this._user(comment.idMemberCreator), - }; - // 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: 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 trelloCoverId = card.idAttachmentCover; - 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. - if(Meteor.isServer) { - file.attachData(att.url, function (error) { - file.boardId = boardId; - file.cardId = cardId; - if (error) { - throw(error); - } else { - const wekanAtt = Attachments.insert(file, () => { - // we do nothing - }); - // - if(trelloCoverId === 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(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: this._now(this.createdAt.lists[list.id]), - title: list.name, - }; - 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: 'Trello', - }, - // We attribute the import to current user, - // not the creator of the original object - userId: this._user(), - }); - }); - } - - createChecklists(trelloChecklists) { - trelloChecklists.forEach((checklist) => { - // Create the checklist - const checklistToCreate = { - cardId: this.cards[checklist.idCard], - title: checklist.name, - createdAt: this._now(), - }; - const checklistId = Checklists.direct.insert(checklistToCreate); - // Now add the items to the checklist - const itemsToCreate = []; - checklist.checkItems.forEach((item) => { - itemsToCreate.push({ - _id: checklistId + itemsToCreate.length, - title: item.name, - isFinished: item.state === 'complete', - }); - }); - Checklists.direct.update(checklistId, {$set: {items: itemsToCreate}}); - }); - } - - getAdmin(trelloMemberType) { - return trelloMemberType === 'admin'; - } - - 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) => { - if (action.type === 'addAttachmentToCard') { - // We have to be cautious, because the attachment could have been removed later. - // In that case Trello still reports its addition, but removes its 'url' field. - // So we test for that - const trelloAttachment = action.data.attachment; - if(trelloAttachment.url) { - // 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 trelloCardId = action.data.card.id; - if(!this.attachments[trelloCardId]) { - this.attachments[trelloCardId] = []; - } - this.attachments[trelloCardId].push(trelloAttachment); - } - } else if (action.type === 'commentCard') { - const id = action.data.card.id; - if (this.comments[id]) { - this.comments[id].push(action); - } else { - this.comments[id] = [action]; - } - } else if (action.type === 'createBoard') { - this.createdAt.board = action.date; - } else if (action.type === 'createCard') { - const cardId = action.data.card.id; - this.createdAt.cards[cardId] = action.date; - this.createdBy.cards[cardId] = action.idMemberCreator; - } else if (action.type === 'createList') { - const listId = action.data.list.id; - this.createdAt.lists[listId] = action.date; - } - }); - } -} +import { TrelloCreator } from './trelloCreator'; +import { WekanCreator } from './wekanCreator'; Meteor.methods({ - importTrelloBoard(trelloBoard, data) { - const trelloCreator = new TrelloCreator(data); + importBoard(board, data, importSource) { + check(board, Object); + check(data, Object); + check(importSource, String); + let creator; + switch (importSource) { + case 'trello': + creator = new TrelloCreator(data); + break; + case 'wekan': + creator = new WekanCreator(data); + break; + } // 1. check all parameters are ok from a syntax point of view - try { - check(data, { - membersMapping: Match.Optional(Object), - }); - trelloCreator.checkActions(trelloBoard.actions); - trelloCreator.checkBoard(trelloBoard); - trelloCreator.checkLabels(trelloBoard.labels); - trelloCreator.checkLists(trelloBoard.lists); - trelloCreator.checkCards(trelloBoard.cards); - trelloCreator.checkChecklists(trelloBoard.checklists); - } catch (e) { - throw new Meteor.Error('error-json-schema'); - } + creator.check(board); // 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.createCards(trelloBoard.cards, boardId); - trelloCreator.createChecklists(trelloBoard.checklists); - // XXX add members - return boardId; + return creator.create(board); }, }); diff --git a/models/trelloCreator.js b/models/trelloCreator.js new file mode 100644 index 00000000..fbc4a878 --- /dev/null +++ b/models/trelloCreator.js @@ -0,0 +1,500 @@ +const DateString = Match.Where(function (dateAsString) { + check(dateAsString, String); + return moment(dateAsString, moment.ISO_8601).isValid(); +}); + +export class TrelloCreator { + 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 Trello id + // (so we only parse actions once!) + this.createdAt = { + board: null, + cards: {}, + lists: {}, + }; + // The object creator Trello Id, indexed by the object Trello id + // (so we only parse actions once!) + this.createdBy = { + cards: {}, // only cards have a field for that + }; + + // Map of labels Trello ID => Wekan ID + this.labels = {}; + // Map of lists Trello ID => Wekan ID + this.lists = {}; + // Map of cards Trello ID => Wekan ID + this.cards = {}; + // The comments, indexed by Trello card id (to map when importing cards) + this.comments = {}; + // the members, indexed by Trello member id => Wekan user ID + this.members = data.membersMapping ? data.membersMapping : {}; + + // maps a trelloCardId to an array of trelloAttachments + 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 trelloUserId is provided and we have a mapping, + * return it. + * Otherwise return current logged user. + * @param trelloUserId + * @private + */ + _user(trelloUserId) { + if(trelloUserId && this.members[trelloUserId]) { + return this.members[trelloUserId]; + } + return Meteor.userId(); + } + + 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, + })]); + } + + checkChecklists(trelloChecklists) { + check(trelloChecklists, [Match.ObjectIncluding({ + idBoard: String, + idCard: String, + name: String, + checkItems: [Match.ObjectIncluding({ + state: String, + name: String, + })], + })]); + } + + // You must call parseActions before calling this one. + createBoardAndLabels(trelloBoard) { + const boardToCreate = { + archived: trelloBoard.closed, + color: this.getColor(trelloBoard.prefs.background), + // very old boards won't have a creation activity so no creation date + createdAt: this._now(this.createdAt.board), + labels: [], + members: [{ + userId: Meteor.userId(), + isAdmin: true, + isActive: true, + isCommentOnly: false, + }], + permission: this.getPermission(trelloBoard.prefs.permissionLevel), + slug: getSlug(trelloBoard.name) || 'board', + stars: 0, + title: trelloBoard.name, + }; + // now add other members + if(trelloBoard.memberships) { + trelloBoard.memberships.forEach((trelloMembership) => { + const trelloId = trelloMembership.idMember; + // do we have a mapping? + if(this.members[trelloId]) { + const wekanId = this.members[trelloId]; + // do we already have it in our list? + const wekanMember = boardToCreate.members.find((wekanMember) => wekanMember.userId === wekanId); + if(wekanMember) { + // we're already mapped, but maybe with lower rights + if(!wekanMember.isAdmin) { + wekanMember.isAdmin = this.getAdmin(trelloMembership.memberType); + } + } else { + boardToCreate.members.push({ + userId: wekanId, + isAdmin: this.getAdmin(trelloMembership.memberType), + isActive: true, + isCommentOnly: false, + }); + } + } + }); + } + 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 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: trelloBoard.id, + system: 'Trello', + url: trelloBoard.url, + }, + // 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 Trello cards, + * as well as all linked data: activities, comments, and attachments + * @param trelloCards + * @param boardId + * @returns {Array} + */ + createCards(trelloCards, boardId) { + const result = []; + trelloCards.forEach((card) => { + const cardToCreate = { + archived: card.closed, + 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.desc, + listId: this.lists[card.idList], + sort: card.pos, + title: card.name, + // we attribute the card to its creator if available + userId: this._user(this.createdBy.cards[card.id]), + dueAt: card.due ? this._now(card.due) : null, + }; + // add labels + if (card.idLabels) { + cardToCreate.labelIds = card.idLabels.map((trelloId) => { + return this.labels[trelloId]; + }); + } + // add members { + if(card.idMembers) { + const wekanMembers = []; + // we can't just map, as some members may not have been mapped + card.idMembers.forEach((trelloId) => { + if(this.members[trelloId]) { + const wekanId = this.members[trelloId]; + // we may map multiple Trello 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; + } + } + // insert card + const cardId = Cards.direct.insert(cardToCreate); + // keep track of Trello 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: 'Trello', + url: card.url, + }, + // 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.date), + text: comment.data.text, + // we attribute the comment to the original author, default to current user + userId: this._user(comment.idMemberCreator), + }; + // 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: 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 trelloCoverId = card.idAttachmentCover; + 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. + if(Meteor.isServer) { + file.attachData(att.url, function (error) { + file.boardId = boardId; + file.cardId = cardId; + if (error) { + throw(error); + } else { + const wekanAtt = Attachments.insert(file, () => { + // we do nothing + }); + // + if(trelloCoverId === 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(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: this._now(this.createdAt.lists[list.id]), + title: list.name, + }; + 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: 'Trello', + }, + // We attribute the import to current user, + // not the creator of the original object + userId: this._user(), + }); + }); + } + + createChecklists(trelloChecklists) { + trelloChecklists.forEach((checklist) => { + // Create the checklist + const checklistToCreate = { + cardId: this.cards[checklist.idCard], + title: checklist.name, + createdAt: this._now(), + }; + const checklistId = Checklists.direct.insert(checklistToCreate); + // Now add the items to the checklist + const itemsToCreate = []; + checklist.checkItems.forEach((item) => { + itemsToCreate.push({ + _id: checklistId + itemsToCreate.length, + title: item.name, + isFinished: item.state === 'complete', + }); + }); + Checklists.direct.update(checklistId, {$set: {items: itemsToCreate}}); + }); + } + + getAdmin(trelloMemberType) { + return trelloMemberType === 'admin'; + } + + 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) => { + if (action.type === 'addAttachmentToCard') { + // We have to be cautious, because the attachment could have been removed later. + // In that case Trello still reports its addition, but removes its 'url' field. + // So we test for that + const trelloAttachment = action.data.attachment; + if(trelloAttachment.url) { + // 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 trelloCardId = action.data.card.id; + if(!this.attachments[trelloCardId]) { + this.attachments[trelloCardId] = []; + } + this.attachments[trelloCardId].push(trelloAttachment); + } + } else if (action.type === 'commentCard') { + const id = action.data.card.id; + if (this.comments[id]) { + this.comments[id].push(action); + } else { + this.comments[id] = [action]; + } + } else if (action.type === 'createBoard') { + this.createdAt.board = action.date; + } else if (action.type === 'createCard') { + const cardId = action.data.card.id; + this.createdAt.cards[cardId] = action.date; + this.createdBy.cards[cardId] = action.idMemberCreator; + } else if (action.type === 'createList') { + const listId = action.data.list.id; + this.createdAt.lists[listId] = action.date; + } + }); + } + + check(board) { + try { + // check(data, { + // membersMapping: Match.Optional(Object), + // }); + this.checkActions(board.actions); + this.checkBoard(board); + this.checkLabels(board.labels); + this.checkLists(board.lists); + this.checkCards(board.cards); + this.checkChecklists(board.checklists); + } catch (e) { + throw new Meteor.Error('error-json-schema'); + } + } + + create(board) { + this.parseActions(board.actions); + const boardId = this.createBoardAndLabels(board); + this.createLists(board.lists, boardId); + this.createCards(board.cards, boardId); + this.createChecklists(board.checklists); + // XXX add members + return boardId; + } +} diff --git a/models/wekanCreator.js b/models/wekanCreator.js new file mode 100644 index 00000000..b91b875a --- /dev/null +++ b/models/wekanCreator.js @@ -0,0 +1,476 @@ +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: {}, + }; + // 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 lists Wekan ID => Wekan ID + this.lists = {}; + // Map of cards Wekan ID => Wekan ID + this.cards = {}; + // The comments, indexed by Wekan card id (to map when importing cards) + this.comments = {}; + // the members, indexed by Wekan member id => Wekan user ID + this.members = data.membersMapping ? data.membersMapping : {}; + + // 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({ + userId: String, + 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], + members: [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, + name: String, + })]); + } + + checkLists(wekanLists) { + check(wekanLists, [Match.ObjectIncluding({ + archived: Boolean, + title: String, + })]); + } + + // checkChecklists(wekanChecklists) { + // check(wekanChecklists, [Match.ObjectIncluding({ + // idBoard: String, + // idCard: String, + // name: String, + // checkItems: [Match.ObjectIncluding({ + // state: String, + // name: String, + // })], + // })]); + // } + + // You must call parseActions before calling this one. + createBoardAndLabels(wekanBoard) { + const boardToCreate = { + archived: wekanBoard.archived, + color: wekanBoard.color, + // very old boards won't have a creation activity so no creation date + createdAt: this._now(wekanBoard.createdAt), + labels: [], + members: [{ + userId: Meteor.userId(), + isAdmin: true, + isActive: true, + isCommentOnly: false, + }], + permission: wekanBoard.permission, + slug: getSlug(wekanBoard.title) || 'board', + stars: 0, + title: wekanBoard.title, + }; + // now add other members + if(wekanBoard.members) { + wekanBoard.members.forEach((wekanMember) => { + const wekanId = wekanMember.userId; + // do we have a mapping? + if(this.members[wekanId]) { + const wekanId = this.members[wekanId]; + // do we already have it in our list? + const wekanMember = boardToCreate.members.find((wekanMember) => wekanMember.userId === wekanId); + if(!wekanMember) { + boardToCreate.members.push({ + userId: wekanId, + isAdmin: wekanMember.isAdmin, + isActive: true, + isCommentOnly: false, + }); + } + } + }); + } + wekanBoard.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: wekanBoard.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], + sort: card.sort, + title: card.title, + // we attribute the card to its creator if available + userId: this._user(this.createdBy.cards[card._id]), + dueAt: card.dueAt ? this._now(card.dueAt) : 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; + } + } + // 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.date), + 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); + 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. + if(Meteor.isServer) { + file.attachData(att.url, function (error) { + file.boardId = boardId; + file.cardId = cardId; + if (error) { + throw(error); + } else { + const wekanAtt = Attachments.insert(file, () => { + // we do nothing + }); + // + 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) => { + 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, + }; + 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(), + }); + }); + } + + // createChecklists(wekanChecklists) { + // wekanChecklists.forEach((checklist) => { + // // Create the checklist + // const checklistToCreate = { + // cardId: this.cards[checklist.cardId], + // title: checklist.title, + // createdAt: this._now(), + // }; + // const checklistId = Checklists.direct.insert(checklistToCreate); + // // Now add the items to the checklist + // const itemsToCreate = []; + // checklist.checkItems.forEach((item) => { + // itemsToCreate.push({ + // _id: checklistId + itemsToCreate.length, + // title: item.title, + // isFinished: item.isFinished, + // }); + // }); + // Checklists.direct.update(checklistId, {$set: {items: itemsToCreate}}); + // }); + // } + + 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(wekanAttachment.url) { + // 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; + }} + }); + } + + check(board) { + try { + // check(data, { + // membersMapping: Match.Optional(Object), + // }); + this.checkActivities(board.activities); + this.checkBoard(board); + this.checkLabels(board.labels); + this.checkLists(board.lists); + this.checkCards(board.cards); + // Checklists are not exported yet + // this.checkChecklists(board.checklists); + } catch (e) { + throw new Meteor.Error('error-json-schema'); + } + } + + create(board) { + this.parseActivities(board); + const boardId = this.createBoardAndLabels(board); + this.createLists(board.lists, boardId); + this.createCards(board.cards, boardId); + // Checklists are not exported yet + // this.createChecklists(board.checklists); + // XXX add members + return boardId; + } +} |