Przeglądaj źródła

Merge branch 'master' of supplee.net:wekan

John R. Supplee 4 lat temu
rodzic
commit
734c56a6b6

+ 64 - 0
client/components/main/dueCards.jade

@@ -0,0 +1,64 @@
+template(name="dueCardsHeaderBar")
+  h1
+    //a.back-btn(href="{{pathFor 'home'}}")
+    //  i.fa.fa-chevron-left
+    | {{_ 'dueCards-title'}}
+
+  .board-header-btns.left
+    a.board-header-btn.js-due-cards-view-change(title="{{_ 'dueCardsViewChange-title'}}")
+      i.fa.fa-caret-down
+      if $eq dueCardsView 'me'
+        i.fa.fa-user
+        | {{_ 'dueCardsViewChange-choice-me'}}
+      if $eq dueCardsView 'all'
+        i.fa.fa-users
+        | {{_ 'dueCardsViewChange-choice-all'}}
+
+template(name="dueCardsModalTitle")
+  h2
+    i.fa.fa-keyboard-o
+    | {{_ 'dueCards-title'}}
+
+template(name="dueCards")
+  .wrapper
+    .due-cards-dueat-list-wrapper
+      each card in dueCardsList
+        .due-cards-card-wrapper
+          a.minicard-wrapper.card-title(href=card.absoluteUrl)
+            +minicard(card)
+          ul.due-cards-context-list
+            li.due-cards-context(title="{{_ 'board'}}")
+              +viewer
+                = card.getBoard.title
+            li.due-cards-context.due-cards-context-separator
+              = ' '
+              | {{_ 'context-separator'}}
+              = ' '
+            li.due-cards-context(title="{{_ 'swimlane'}}")
+              +viewer
+                = card.getSwimlane.title
+            li.due-cards-context
+              = ' '
+              | {{_ 'context-separator'}}
+              = ' '
+            li.due-cards-context(title="{{_ 'list'}}")
+              +viewer
+                = card.getList.title
+
+
+template(name="dueCardsViewChangePopup")
+  ul.pop-over-list
+    li
+      with "dueCardsViewChange-choice-me"
+        a.js-due-cards-view-me
+          i.fa.fa-user.colorful
+          | {{_ 'dueCardsViewChange-choice-me'}}
+          if $eq Utils.dueCardsView "me"
+            i.fa.fa-check
+    li
+      with "dueCardsViewChange-choice-all"
+        a.js-due-cards-view-all
+          i.fa.fa-users.colorful
+          | {{_ 'dueCardsViewChange-choice-all'}}
+          if $eq Utils.dueCardsView "all"
+            i.fa.fa-check

+ 140 - 0
client/components/main/dueCards.js

@@ -0,0 +1,140 @@
+BlazeComponent.extendComponent({
+  dueCardsView() {
+    // eslint-disable-next-line no-console
+    // console.log('sort:', Utils.dueCardsView());
+    return Utils.dueCardsView();
+  },
+
+  events() {
+    return [
+      {
+        'click .js-due-cards-view-change': Popup.open('dueCardsViewChange'),
+      },
+    ];
+  },
+}).register('dueCardsHeaderBar');
+
+Template.dueCards.helpers({
+  userId() {
+    return Meteor.userId();
+  },
+});
+
+BlazeComponent.extendComponent({
+  events() {
+    return [
+      {
+        'click .js-due-cards-view-me'() {
+          Utils.setDueCardsView('me');
+          Popup.close();
+        },
+
+        'click .js-due-cards-view-all'() {
+          Utils.setDueCardsView('all');
+          Popup.close();
+        },
+      },
+    ];
+  },
+}).register('dueCardsViewChangePopup');
+
+BlazeComponent.extendComponent({
+  onCreated() {
+    Meteor.subscribe('setting');
+    Meteor.subscribe('dueCards', Utils.dueCardsView() === 'all');
+  },
+
+  dueCardsView() {
+    // eslint-disable-next-line no-console
+    console.log('sort:', Utils.dueCardsView());
+    return Utils.dueCardsView();
+  },
+
+  sortByBoard() {
+    return this.dueCardsView() === 'board';
+  },
+
+  dueCardsList() {
+    const allUsers = Utils.dueCardsView() === 'all';
+
+    const user = Meteor.user();
+
+    const archivedBoards = [];
+    Boards.find({ archived: true }).forEach(board => {
+      archivedBoards.push(board._id);
+    });
+
+    const permiitedBoards = [];
+    let selector = {
+      archived: false,
+    };
+    // if user is not an admin allow her to see cards only from public boards
+    // or those where she is a member
+    if (!user.isAdmin) {
+      selector.$or = [
+        { permission: 'public' },
+        { members: { $elemMatch: { userId: user._id, isActive: true } } },
+      ];
+    }
+    Boards.find(selector).forEach(board => {
+      permiitedBoards.push(board._id);
+    });
+
+    const archivedSwimlanes = [];
+    Swimlanes.find({ archived: true }).forEach(swimlane => {
+      archivedSwimlanes.push(swimlane._id);
+    });
+
+    const archivedLists = [];
+    Lists.find({ archived: true }).forEach(list => {
+      archivedLists.push(list._id);
+    });
+
+    selector = {
+      archived: false,
+      boardId: {
+        $nin: archivedBoards,
+        $in: permiitedBoards,
+      },
+      swimlaneId: { $nin: archivedSwimlanes },
+      listId: { $nin: archivedLists },
+      dueAt: { $ne: null },
+      endAt: null,
+    };
+
+    if (!allUsers) {
+      selector.$or = [{ members: user._id }, { assignees: user._id }];
+    }
+
+    const cards = [];
+
+    // eslint-disable-next-line no-console
+    // console.log('cards selector:', selector);
+    Cards.find(selector).forEach(card => {
+      cards.push(card);
+      // eslint-disable-next-line no-console
+      // console.log(
+      //   'board:',
+      //   card.board(),
+      //   'swimlane:',
+      //   card.swimlane(),
+      //   'list:',
+      //   card.list(),
+      // );
+    });
+
+    cards.sort((a, b) => {
+      const x = a.dueAt === null ? Date('2100-12-31') : a.dueAt;
+      const y = b.dueAt === null ? Date('2100-12-31') : b.dueAt;
+
+      if (x > y) return 1;
+      else if (x < y) return -1;
+
+      return 0;
+    });
+
+    // eslint-disable-next-line no-console
+    // console.log('cards:', cards);
+    return cards;
+  },
+}).register('dueCards');

+ 69 - 0
client/components/main/dueCards.styl

@@ -0,0 +1,69 @@
+.due-cards-board-wrapper
+  border-radius: 8px
+  //padding: 0.5rem
+  min-width: 400px
+  border-width: 8px
+  border-color: grey
+  border-style: solid
+  margin-bottom: 2rem
+  margin-right: auto
+  margin-left: auto
+
+.due-cards-board-title
+  font-size: 1.4rem
+  font-weight: bold
+  padding: 0.5rem
+  background-color: grey
+  color: white
+
+.due-cards-swimlane-title
+  font-size: 1.1rem
+  font-weight: bold
+  padding: 0.5rem
+  padding-bottom: 0.4rem
+  margin-top: 0
+  margin-bottom: 0.5rem
+  //border-top: black 1px solid
+  //border-bottom: black 1px solid
+  text-align: center
+
+.swimlane-default-color
+  background-color: lightgrey
+
+.due-cards-list-title
+  font-weight: bold
+  font-size: 1.1rem
+  //padding-bottom: 0
+  //margin-bottom: 0
+  text-align: center
+  margin-bottom: 0.7rem
+
+.due-cards-list-wrapper
+  margin: 1rem
+  border-radius: 5px
+  padding: 1.5rem
+  padding-top: 0.75rem
+  display: inline-block
+  min-width: 250px
+  max-width: 350px
+
+.due-cards-card-wrapper
+  margin-top: 0
+  margin-bottom: 10px
+
+.due-cards-dueat-list-wrapper
+  max-width: 500px
+  margin-right: auto
+  margin-left: auto
+
+.due-cards-field-name
+  font-weight: bold
+
+.due-cards-context
+  display: inline-block
+
+.due-cards-context-separator
+  font-weight: bold
+
+.due-cards-context-list
+  margin-bottom: 0.7rem

+ 9 - 21
client/components/main/myCards.jade

@@ -5,15 +5,14 @@ template(name="myCardsHeaderBar")
     | {{_ 'my-cards'}}
 
   .board-header-btns.left
-    a.board-header-btn.js-toggle-my-cards-choose-sort(title="{{_ 'my-cards-sort'}}")
-      //i.fa.fa-caret-down
-      i.fa.fa-sort
+    a.board-header-btn.js-toggle-my-cards-choose-sort(title="{{_ 'myCardsSortChange-title'}}")
+      i.fa.fa-caret-down
       if $eq myCardsSort 'board'
         i.fa.fa-th-large
-        | {{_ 'my-cards-sort-board'}}
+        | {{_ 'myCardsSortChange-choice-board'}}
       if $eq myCardsSort 'dueAt'
         i.fa.fa-calendar
-        | {{_ 'my-cards-sort-dueat'}}
+        | {{_ 'myCardsSortChange-choice-dueat'}}
 
 template(name="myCardsModalTitle")
   h2
@@ -50,21 +49,21 @@ template(name="myCards")
             ul.my-cards-context-list
               li.my-cards-context(title="{{_ 'board'}}")
                 +viewer
-                  = card.board.title
+                  = card.getBoard.title
               li.my-cards-context.my-cards-context-separator
                 = ' '
                 | {{_ 'context-separator'}}
                 = ' '
               li.my-cards-context(title="{{_ 'swimlane'}}")
                 +viewer
-                  = card.swimlane.title
+                  = card.getSwimlane.title
               li.my-cards-context
                 = ' '
                 | {{_ 'context-separator'}}
                 = ' '
               li.my-cards-context(title="{{_ 'list'}}")
                 +viewer
-                  = card.list.title
+                  = card.getList.title
 
 
 template(name="myCardsSortChangePopup")
@@ -73,24 +72,13 @@ template(name="myCardsSortChangePopup")
       with "my-cards-sort-board"
         a.js-my-cards-sort-board
           i.fa.fa-th-large.colorful
-          | {{_ 'my-cards-sort-board'}}
+          | {{_ 'myCardsSortChange-choice-board'}}
           if $eq Utils.myCardsSort "board"
             i.fa.fa-check
     li
       with "my-cards-sort-dueat"
         a.js-my-cards-sort-dueat
           i.fa.fa-calendar.colorful
-          | {{_ 'my-cards-sort-dueat'}}
+          | {{_ 'myCardsSortChange-choice-dueat'}}
           if $eq Utils.myCardsSort "dueAt"
             i.fa.fa-check
-
-//template(name="myCardsSortChangePopup")
-//  ul.pop-over-list
-//    li
-//      a.js-my-cards-sort-board
-//        i.fa.fa-th-large.colorful
-//        | {{_ 'my-cards-sort-board'}}
-//    li
-//      a.js-my-cards-sort-dueat
-//        i.fa.fa-calendar.colorful
-//        | {{_ 'my-cards-sort-dueat'}}

+ 13 - 10
client/components/main/myCards.js

@@ -8,12 +8,9 @@ BlazeComponent.extendComponent({
   events() {
     return [
       {
-        'click .js-toggle-my-cards-choose-sort'() {
-          // eslint-disable-next-line no-console
-          // console.log('open sort');
-          // Popup.open('myCardsSortChange');
-          Utils.myCardsSortToggle();
-        },
+        'click .js-toggle-my-cards-choose-sort': Popup.open(
+          'myCardsSortChange',
+        ),
       },
     ];
   },
@@ -93,7 +90,7 @@ BlazeComponent.extendComponent({
       if (list === null || card.listId !== list._id) {
         // eslint-disable-next-line no-console
         // console.log('new list');
-        list = card.list();
+        list = card.getList();
         if (list.archived) {
           list = null;
           return;
@@ -104,7 +101,7 @@ BlazeComponent.extendComponent({
       if (swimlane === null || card.swimlaneId !== swimlane._id) {
         // eslint-disable-next-line no-console
         // console.log('new swimlane');
-        swimlane = card.swimlane();
+        swimlane = card.getSwimlane();
         if (swimlane.archived) {
           swimlane = null;
           return;
@@ -115,7 +112,7 @@ BlazeComponent.extendComponent({
       if (board === null || card.boardId !== board._id) {
         // eslint-disable-next-line no-console
         // console.log('new board');
-        board = card.board();
+        board = card.getBoard();
         if (board.archived) {
           board = null;
           return;
@@ -200,7 +197,13 @@ BlazeComponent.extendComponent({
 
     const cards = [];
     cursor.forEach(card => {
-      cards.push(card);
+      if (
+        !card.getBoard().archived &&
+        !card.getSwimlane().archived &&
+        !card.getList().archived
+      ) {
+        cards.push(card);
+      }
     });
 
     cards.sort((a, b) => {

+ 25 - 0
client/lib/utils.js

@@ -69,6 +69,31 @@ Utils = {
     location.reload();
   },
 
+  archivedBoardIds() {
+    const archivedBoards = [];
+    Boards.find({ archived: false }).forEach(board => {
+      archivedBoards.push(board._id);
+    });
+    return archivedBoards;
+  },
+
+  dueCardsView() {
+    let view = window.localStorage.getItem('dueCardsView');
+
+    if (!view || !['me', 'all'].includes(view)) {
+      window.localStorage.setItem('dueCardsView', 'me');
+      location.reload();
+      view = 'me';
+    }
+
+    return view;
+  },
+
+  setDueCardsView(view) {
+    window.localStorage.setItem('dueCardsView', view);
+    location.reload();
+  },
+
   // XXX We should remove these two methods
   goBoardId(_id) {
     const board = Boards.findOne(_id);

+ 26 - 0
config/router.js

@@ -139,6 +139,32 @@ FlowRouter.route('/my-cards', {
   },
 });
 
+FlowRouter.route('/due-cards', {
+  name: 'due-cards',
+  action() {
+    const dueCardsTemplate = 'dueCards';
+
+    Filter.reset();
+    // EscapeActions.executeAll();
+    EscapeActions.executeUpTo('popup-close');
+
+    Utils.manageCustomUI();
+    Utils.manageMatomo();
+
+    // if (previousPath) {
+    //   Modal.open(dueCardsTemplate, {
+    //     header: 'dueCardsModalTitle',
+    //     onCloseGoTo: previousPath,
+    //   });
+    // } else {
+    BlazeLayout.render('defaultLayout', {
+      headerBar: 'dueCardsHeaderBar',
+      content: dueCardsTemplate,
+    });
+    // }
+  },
+});
+
 FlowRouter.route('/import/:source', {
   name: 'import',
   triggersEnter: [AccountsTemplates.ensureSignedIn],

+ 7 - 3
i18n/en.i18n.json

@@ -854,7 +854,11 @@
   "list": "List",
   "board": "Board",
   "context-separator": "/",
-  "my-cards-sort": "My Cards Sort",
-  "my-cards-sort-board": "By Board",
-  "my-cards-sort-dueat": "By Due Date"
+  "myCardsSortChange-title": "My Cards Sort",
+  "myCardsSortChange-choice-board": "By Board",
+  "myCardsSortChange-choice-dueat": "By Due Date",
+  "dueCards-title": "Due Cards",
+  "dueCardsViewChange-title": "Due Cards View",
+  "dueCardsViewChange-choice-me": "Me",
+  "dueCardsViewChange-choice-all": "All Users"
 }

+ 39 - 0
models/cards.js

@@ -469,6 +469,45 @@ Cards.helpers({
     return Boards.findOne(this.boardId);
   },
 
+  getList() {
+    const list = this.list();
+    if (!list) {
+      return {
+        _id: this.listId,
+        title: 'Undefined List',
+        archived: false,
+        colorClass: '',
+      };
+    }
+    return list;
+  },
+
+  getSwimlane() {
+    const swimlane = this.swimlane();
+    if (!swimlane) {
+      return {
+        _id: this.swimlaneId,
+        title: 'Undefined Swimlane',
+        archived: false,
+        colorClass: '',
+      };
+    }
+    return swimlane;
+  },
+
+  getBoard() {
+    const board = this.board();
+    if (!board) {
+      return {
+        _id: this.boardId,
+        title: 'Undefined Board',
+        archived: false,
+        colorClass: '',
+      };
+    }
+    return board;
+  },
+
   labels() {
     const boardLabels = this.board().labels;
     const cardLabels = _.filter(boardLabels, label => {

+ 87 - 0
server/publications/cards.js

@@ -32,3 +32,90 @@ Meteor.publish('myCards', function() {
     },
   );
 });
+
+Meteor.publish('dueCards', function(allUsers = false) {
+  check(allUsers, Boolean);
+
+  // eslint-disable-next-line no-console
+  // console.log('all users:', allUsers);
+
+  const user = Users.findOne(this.userId);
+
+  const archivedBoards = [];
+  Boards.find({ archived: true }).forEach(board => {
+    archivedBoards.push(board._id);
+  });
+
+  const permiitedBoards = [];
+  let selector = {
+    archived: false,
+  };
+  // if user is not an admin allow her to see cards only from boards where
+  // she is a member
+  if (!user.isAdmin) {
+    selector.$or = [
+      { permission: 'public' },
+      { members: { $elemMatch: { userId: user._id, isActive: true } } },
+    ];
+  }
+  Boards.find(selector).forEach(board => {
+    permiitedBoards.push(board._id);
+  });
+
+  const archivedSwimlanes = [];
+  Swimlanes.find({ archived: true }).forEach(swimlane => {
+    archivedSwimlanes.push(swimlane._id);
+  });
+
+  const archivedLists = [];
+  Lists.find({ archived: true }).forEach(list => {
+    archivedLists.push(list._id);
+  });
+
+  selector = {
+    archived: false,
+    boardId: { $nin: archivedBoards, $in: permiitedBoards },
+    swimlaneId: { $nin: archivedSwimlanes },
+    listId: { $nin: archivedLists },
+    dueAt: { $ne: null },
+    endAt: null,
+  };
+
+  if (!allUsers) {
+    selector.$or = [{ members: user._id }, { assignees: user._id }];
+  }
+
+  const cards = Cards.find(selector, {
+    fields: {
+      _id: 1,
+      archived: 1,
+      boardId: 1,
+      swimlaneId: 1,
+      listId: 1,
+      title: 1,
+      type: 1,
+      sort: 1,
+      members: 1,
+      assignees: 1,
+      colors: 1,
+      dueAt: 1,
+    },
+  });
+
+  const boards = [];
+  const swimlanes = [];
+  const lists = [];
+
+  cards.forEach(card => {
+    if (card.boardId) boards.push(card.boardId);
+    if (card.swimlaneId) swimlanes.push(card.swimlaneId);
+    if (card.listId) lists.push(card.listId);
+  });
+
+  return [
+    cards,
+    Boards.find({ _id: { $in: boards } }),
+    Swimlanes.find({ _id: { $in: swimlanes } }),
+    Lists.find({ _id: { $in: lists } }),
+  ];
+});