Browse Source

Merge remote-tracking branch 'Angtrim/feature-duplicate' into edge

Lauri Ojansivu 6 years ago
parent
commit
56cccc6781

+ 70 - 0
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
+  }
+}

+ 1 - 1
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}}"

+ 15 - 0
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);

+ 13 - 0
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

+ 1 - 2
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

+ 5 - 1
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,

+ 2 - 1
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"
 }

+ 29 - 0
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

+ 1 - 0
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,

+ 10 - 17
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;
   }

+ 19 - 0
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);
+  },
+});
+
+
+

+ 25 - 0
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({

+ 24 - 0
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;
+}

+ 3 - 1
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'){

+ 3 - 3
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'],