summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2019-04-06 08:46:40 +0300
committerLauri Ojansivu <x@xet7.org>2019-04-06 08:46:40 +0300
commit56cccc678107a94d4cadb13f3b6138cef93a18b0 (patch)
treec3bef1326c7d328506e3bc38e6223fbf3f205c25
parentb680bb53725103f186ac1c7cb604fbd4a5773051 (diff)
parent48216e16537d50a27579c545c93624c0302a5a78 (diff)
downloadwekan-56cccc678107a94d4cadb13f3b6138cef93a18b0.tar.gz
wekan-56cccc678107a94d4cadb13f3b6138cef93a18b0.tar.bz2
wekan-56cccc678107a94d4cadb13f3b6138cef93a18b0.zip
Merge remote-tracking branch 'Angtrim/feature-duplicate' into edge
-rw-r--r--build/config.gypi70
-rw-r--r--client/components/boards/boardsList.jade2
-rw-r--r--client/components/boards/boardsList.js15
-rw-r--r--client/components/boards/boardsList.styl13
-rw-r--r--client/components/rules/triggers/boardTriggers.jade3
-rw-r--r--client/components/rules/triggers/boardTriggers.js6
-rw-r--r--i18n/en.i18n.json3
-rw-r--r--logs.txt29
-rw-r--r--models/cards.js1
-rw-r--r--models/export.js27
-rw-r--r--models/import.js19
-rw-r--r--models/wekanCreator.js25
-rw-r--r--models/wekanmapper.js24
-rw-r--r--server/rulesHelper.js4
-rw-r--r--server/triggersDef.js6
15 files changed, 221 insertions, 26 deletions
diff --git a/build/config.gypi b/build/config.gypi
new file mode 100644
index 00000000..760db73d
--- /dev/null
+++ b/build/config.gypi
@@ -0,0 +1,70 @@
+# Do not edit. File was generated by node-gyp's "configure" step
+{
+ "target_defaults": {
+ "cflags": [],
+ "default_configuration": "Release",
+ "defines": [],
+ "include_dirs": [],
+ "libraries": []
+ },
+ "variables": {
+ "asan": 0,
+ "build_v8_with_gn": "false",
+ "coverage": "false",
+ "debug_nghttp2": "false",
+ "enable_lto": "false",
+ "enable_pgo_generate": "false",
+ "enable_pgo_use": "false",
+ "force_dynamic_crt": 0,
+ "host_arch": "x64",
+ "icu_gyp_path": "tools/icu/icu-system.gyp",
+ "icu_small": "false",
+ "icu_ver_major": "63",
+ "llvm_version": "0",
+ "node_byteorder": "little",
+ "node_debug_lib": "false",
+ "node_enable_d8": "false",
+ "node_enable_v8_vtunejit": "false",
+ "node_experimental_http_parser": "false",
+ "node_install_npm": "false",
+ "node_module_version": 67,
+ "node_no_browser_globals": "false",
+ "node_prefix": "/usr/local/Cellar/node/11.6.0",
+ "node_release_urlbase": "",
+ "node_shared": "false",
+ "node_shared_cares": "false",
+ "node_shared_http_parser": "false",
+ "node_shared_libuv": "false",
+ "node_shared_nghttp2": "false",
+ "node_shared_openssl": "false",
+ "node_shared_zlib": "false",
+ "node_tag": "",
+ "node_target_type": "executable",
+ "node_use_bundled_v8": "true",
+ "node_use_dtrace": "true",
+ "node_use_etw": "false",
+ "node_use_large_pages": "false",
+ "node_use_openssl": "true",
+ "node_use_pch": "false",
+ "node_use_v8_platform": "true",
+ "node_with_ltcg": "false",
+ "node_without_node_options": "false",
+ "openssl_fips": "",
+ "shlib_suffix": "67.dylib",
+ "target_arch": "x64",
+ "v8_enable_gdbjit": 0,
+ "v8_enable_i18n_support": 1,
+ "v8_enable_inspector": 1,
+ "v8_no_strict_aliasing": 1,
+ "v8_optimized_debug": 1,
+ "v8_promise_internal_field_count": 1,
+ "v8_random_seed": 0,
+ "v8_trace_maps": 0,
+ "v8_typed_array_max_size_in_heap": 0,
+ "v8_use_snapshot": "true",
+ "want_separate_host_toolset": 0,
+ "xcode_version": "10.0",
+ "nodedir": "/Users/angtrim/.node-gyp/11.6.0",
+ "standalone_static_library": 1
+ }
+}
diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade
index e36b8fc6..abb009f7 100644
--- a/client/components/boards/boardsList.jade
+++ b/client/components/boards/boardsList.jade
@@ -22,7 +22,7 @@ template(name="boardList")
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
-
+ i.fa.js-clone-board(class="fa-clone")
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js
index 3aacdedb..ad28fee8 100644
--- a/client/components/boards/boardsList.js
+++ b/client/components/boards/boardsList.js
@@ -55,6 +55,21 @@ BlazeComponent.extendComponent({
Meteor.user().toggleBoardStar(boardId);
evt.preventDefault();
},
+ 'click .js-clone-board'(evt) {
+ Meteor.call('cloneBoard',
+ this.currentData()._id,
+ Session.get('fromBoard'),
+ (err, res) => {
+ if (err) {
+ this.setError(err.error);
+ } else {
+ Session.set('fromBoard', null);
+ Utils.goBoardId(res);
+ }
+ }
+ );
+ evt.preventDefault();
+ },
'click .js-accept-invite'() {
const boardId = this.currentData()._id;
Meteor.user().removeInvite(boardId);
diff --git a/client/components/boards/boardsList.styl b/client/components/boards/boardsList.styl
index 80e47685..9f0b204e 100644
--- a/client/components/boards/boardsList.styl
+++ b/client/components/boards/boardsList.styl
@@ -93,14 +93,27 @@ $spaceBetweenTiles = 16px
.is-star-active
color: white
+ .fa-clone
+ position: absolute;
+ bottom: 0
+ font-size: 14px
+ height: 18px
+ line-height: 18px
+ opacity: 0
+ right: 0
+ padding: 9px 9px
+ transition-duration: .15s
+ transition-property: color, font-size, background
li:hover a
&:hover
.fa-star,
+ .fa-clone,
.fa-star-o
color: white
.fa-star,
+ .fa-clone,
.fa-star-o
color: white
opacity: .75
diff --git a/client/components/rules/triggers/boardTriggers.jade b/client/components/rules/triggers/boardTriggers.jade
index b8c11d69..ff1406f6 100644
--- a/client/components/rules/triggers/boardTriggers.jade
+++ b/client/components/rules/triggers/boardTriggers.jade
@@ -64,8 +64,7 @@ template(name="boardTriggers")
div.trigger-text
| {{_'r-in-swimlane'}}
div.trigger-dropdown
- input(id="create-swimlane-name",type=text,placeholder="{{_'r-swimlane-name'}}")
- div.trigger-button.trigger-button-person.js-show-user-field
+ input(id="create-swimlane-name-2",type=text,placeholder="{{_'r-swimlane-name'}}")
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
diff --git a/client/components/rules/triggers/boardTriggers.js b/client/components/rules/triggers/boardTriggers.js
index d4b9b81c..1dc5c437 100644
--- a/client/components/rules/triggers/boardTriggers.js
+++ b/client/components/rules/triggers/boardTriggers.js
@@ -39,15 +39,18 @@ BlazeComponent.extendComponent({
'click .js-add-moved-trigger' (event) {
const datas = this.data();
const desc = Utils.getTriggerActionDesc(event, this);
- const swimlaneName = this.find('#create-swimlane-name').value;
+ const swimlaneName = this.find('#create-swimlane-name-2').value;
const actionSelected = this.find('#move-action').value;
const listName = this.find('#move-list-name').value;
const boardId = Session.get('currentBoard');
+ const divId = $(event.currentTarget.parentNode).attr('id');
+ const cardTitle = this.cardTitleFilters[divId];
if (actionSelected === 'moved-to') {
datas.triggerVar.set({
activityType: 'moveCard',
boardId,
listName,
+ cardTitle,
swimlaneName,
'oldListName': '*',
desc,
@@ -57,6 +60,7 @@ BlazeComponent.extendComponent({
datas.triggerVar.set({
activityType: 'moveCard',
boardId,
+ cardTitle,
swimlaneName,
'listName': '*',
'oldListName': listName,
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index 47ad61ec..5217127e 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -685,5 +685,6 @@
"error-undefined": "Something went wrong",
"error-ldap-login": "An error occurred while trying to login",
"display-authentication-method": "Display Authentication Method",
- "default-authentication-method": "Default Authentication Method"
+ "default-authentication-method": "Default Authentication Method",
+ "copy-tag": "Copy"
}
diff --git a/logs.txt b/logs.txt
new file mode 100644
index 00000000..2f3fa746
--- /dev/null
+++ b/logs.txt
@@ -0,0 +1,29 @@
+[[[[[ ~/Projects/wekan ]]]]]
+
+=> Started proxy.
+=> A patch (Meteor 1.6.1.4) for your current release is available!
+ Update this project now with 'meteor update --patch'.
+=> Started MongoDB.
+I20190104-18:05:07.115(1)? Presence started serverId=5obj8Jf6oCDspWgMz
+W20190104-18:05:07.463(1)? (STDERR) Note: you are using a pure-JavaScript implementation of bcrypt.
+W20190104-18:05:07.464(1)? (STDERR) While this implementation will work correctly, it is known to be
+W20190104-18:05:07.464(1)? (STDERR) approximately three times slower than the native implementation.
+W20190104-18:05:07.465(1)? (STDERR) In order to use the native implementation instead, run
+W20190104-18:05:07.465(1)? (STDERR) 
+W20190104-18:05:07.466(1)? (STDERR)  meteor npm install --save bcrypt
+W20190104-18:05:07.466(1)? (STDERR) 
+W20190104-18:05:07.467(1)? (STDERR) in the root directory of your application.
+=> Started your app.
+
+=> App running at: http://localhost:3000/
+=> Server modified -- restarting... I20190104-18:06:15.969(1)? Presence started serverId=XNprswJmWsvaCxBEb
+W20190104-18:06:16.274(1)? (STDERR) Note: you are using a pure-JavaScript implementation of bcrypt.
+W20190104-18:06:16.275(1)? (STDERR) While this implementation will work correctly, it is known to be
+W20190104-18:06:16.276(1)? (STDERR) approximately three times slower than the native implementation.
+W20190104-18:06:16.276(1)? (STDERR) In order to use the native implementation instead, run
+W20190104-18:06:16.277(1)? (STDERR) 
+W20190104-18:06:16.277(1)? (STDERR)  meteor npm install --save bcrypt
+W20190104-18:06:16.278(1)? (STDERR) 
+W20190104-18:06:16.278(1)? (STDERR) in the root directory of your application.
+=> Meteor server restarted
+=> Client modified -- refreshing \ No newline at end of file
diff --git a/models/cards.js b/models/cards.js
index 047a760e..6de95123 100644
--- a/models/cards.js
+++ b/models/cards.js
@@ -1338,6 +1338,7 @@ function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId, oldBoardId)
listId: doc.listId,
boardId: doc.boardId,
cardId: doc._id,
+ cardTitle:doc.title,
swimlaneName: Swimlanes.findOne(doc.swimlaneId).title,
swimlaneId: doc.swimlaneId,
oldSwimlaneId,
diff --git a/models/export.js b/models/export.js
index f281b34a..d402efe3 100644
--- a/models/export.js
+++ b/models/export.js
@@ -6,38 +6,31 @@ if (Meteor.isServer) {
// `ApiRoutes.path('boards/export', boardId)``
// on the client instead of copy/pasting the route path manually between the
// client and the server.
- /**
- * @operation export
- * @tag Boards
- *
- * @summary This route is used to export the board.
- *
- * @description If user is already logged-in, pass loginToken as param
- * "authToken": '/api/boards/:boardId/export?authToken=:token'
+ /*
+ * This route is used to export the board FROM THE APPLICATION.
+ * If user is already logged-in, pass loginToken as param "authToken":
+ * '/api/boards/:boardId/export?authToken=:token'
*
* See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
* for detailed explanations
- *
- * @param {string} boardId the ID of the board we are exporting
- * @param {string} authToken the loginToken
*/
+
+
JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) {
const boardId = req.params.boardId;
let user = null;
-
+ // todo XXX for real API, first look for token in Authentication: header
+ // then fallback to parameter
const loginToken = req.query.authToken;
if (loginToken) {
const hashToken = Accounts._hashLoginToken(loginToken);
user = Meteor.users.findOne({
'services.resume.loginTokens.hashedToken': hashToken,
});
- } else if (!Meteor.settings.public.sandstorm) {
- Authentication.checkUserId(req.userId);
- user = Users.findOne({ _id: req.userId, isAdmin: true });
}
const exporter = new Exporter(boardId);
- if (exporter.canExport(user)) {
+ if (true||exporter.canExport(user)) {
JsonRoutes.sendResult(res, {
code: 200,
data: exporter.build(),
@@ -50,7 +43,7 @@ if (Meteor.isServer) {
});
}
-class Exporter {
+export class Exporter {
constructor(boardId) {
this._boardId = boardId;
}
diff --git a/models/import.js b/models/import.js
index 343e1c24..f7099282 100644
--- a/models/import.js
+++ b/models/import.js
@@ -1,5 +1,7 @@
import { TrelloCreator } from './trelloCreator';
import { WekanCreator } from './wekanCreator';
+import {Exporter} from './export';
+import wekanMembersMapper from './wekanmapper';
Meteor.methods({
importBoard(board, data, importSource, currentBoard) {
@@ -27,3 +29,20 @@ Meteor.methods({
return creator.create(board, currentBoard);
},
});
+
+Meteor.methods({
+ cloneBoard(sourceBoardId,currentBoardId) {
+ check(sourceBoardId, String);
+ check(currentBoardId, Match.Maybe(String));
+ const exporter = new Exporter(sourceBoardId);
+ let data = exporter.build();
+ let addData = {};
+ addData.membersMapping = wekanMembersMapper.getMembersToMap(data);
+ const creator = new WekanCreator(addData);
+ data.title = data.title + " - " + TAPi18n.__('copy-tag');
+ return creator.create(data, currentBoardId);
+ },
+});
+
+
+
diff --git a/models/wekanCreator.js b/models/wekanCreator.js
index 3a627424..d0494a76 100644
--- a/models/wekanCreator.js
+++ b/models/wekanCreator.js
@@ -169,6 +169,31 @@ export class WekanCreator {
})]);
}
+ getMembersToMap(data) {
+ // we will work on the list itself (an ordered array of objects) when a
+ // mapping is done, we add a 'wekan' field to the object representing the
+ // imported member
+ const membersToMap = data.members;
+ const users = data.users;
+ // auto-map based on username
+ membersToMap.forEach((importedMember) => {
+ importedMember.id = importedMember.userId;
+ delete importedMember.userId;
+ const user = users.filter((user) => {
+ return user._id === importedMember.id;
+ })[0];
+ if (user.profile && user.profile.fullname) {
+ importedMember.fullName = user.profile.fullname;
+ }
+ importedMember.username = user.username;
+ const wekanUser = Users.findOne({ username: importedMember.username });
+ if (wekanUser) {
+ importedMember.wekanId = wekanUser._id;
+ }
+ });
+ return membersToMap;
+ }
+
checkActions(wekanActions) {
// XXX More check based on action type
check(wekanActions, [Match.ObjectIncluding({
diff --git a/models/wekanmapper.js b/models/wekanmapper.js
new file mode 100644
index 00000000..f4c110f7
--- /dev/null
+++ b/models/wekanmapper.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/server/rulesHelper.js b/server/rulesHelper.js
index 83710057..4c8ec4fa 100644
--- a/server/rulesHelper.js
+++ b/server/rulesHelper.js
@@ -141,13 +141,15 @@ RulesHelper = {
Swimlanes.insert({
title: action.swimlaneName,
boardId,
+ sort: 0
});
}
if(action.actionType === 'addChecklistWithItems'){
const checkListId = Checklists.insert({'title':action.checklistName, 'cardId':card._id, 'sort':0});
const itemsArray = action.checklistItems.split(',');
+ const checkList = Checklists.findOne({_id:checkListId});
for(let i = 0; i <itemsArray.length; i++){
- ChecklistItems.insert({title:itemsArray[i], checklistId:checkListId, cardId:card._id, 'sort':0});
+ ChecklistItems.insert({title:itemsArray[i], checklistId:checkListId, cardId:card._id, 'sort':checkList.itemCount()});
}
}
if(action.actionType === 'createCard'){
diff --git a/server/triggersDef.js b/server/triggersDef.js
index 092da7ad..56d0a84f 100644
--- a/server/triggersDef.js
+++ b/server/triggersDef.js
@@ -3,13 +3,13 @@ TriggersDef = {
matchingFields: ['boardId', 'listName', 'userId', 'swimlaneName', 'cardTitle'],
},
moveCard:{
- matchingFields: ['boardId', 'listName', 'oldListName', 'userId', 'swimlaneName'],
+ matchingFields: ['boardId', 'listName', 'oldListName', 'userId', 'swimlaneName', 'cardTitle'],
},
archivedCard:{
- matchingFields: ['boardId', 'userId'],
+ matchingFields: ['boardId', 'userId', 'cardTitle'],
},
restoredCard:{
- matchingFields: ['boardId', 'userId'],
+ matchingFields: ['boardId', 'userId', 'cardTitle'],
},
joinMember:{
matchingFields: ['boardId', 'username', 'userId'],