ソースを参照

Merge branch 'TNick-calendar' into devel

Lauri Ojansivu 7 年 前
コミット
523ebea4dd

+ 2 - 1
.meteor/packages

@@ -77,10 +77,11 @@ email@1.2.3
 horka:swipebox
 dynamic-import@0.2.0
 staringatlights:fast-render
-staringatlights:flow-router
 
 mixmax:smart-disconnect
 accounts-password@1.5.0
 cfs:gridfs
 browser-policy
 eluck:accounts-lockout
+rzymek:fullcalendar
+momentjs:moment@2.22.2

+ 2 - 1
.meteor/versions

@@ -103,6 +103,7 @@ mixmax:smart-disconnect@0.0.4
 mobile-status-bar@1.0.14
 modules@0.11.0
 modules-runtime@0.9.1
+momentjs:moment@2.22.2
 mongo@1.3.1
 mongo-dev-server@1.1.0
 mongo-id@1.0.6
@@ -139,6 +140,7 @@ reactive-var@1.0.11
 reload@1.1.11
 retry@1.0.9
 routepolicy@1.0.12
+rzymek:fullcalendar@3.8.0
 service-configuration@1.0.11
 session@1.1.7
 sha@1.0.9
@@ -155,7 +157,6 @@ srp@1.0.10
 standard-minifier-css@1.3.5
 standard-minifier-js@2.2.3
 staringatlights:fast-render@2.16.5
-staringatlights:flow-router@2.12.2
 staringatlights:inject-data@2.0.5
 stylus@2.513.13
 tap:i18n@1.8.2

+ 5 - 1
CHANGELOG.md

@@ -1,6 +1,10 @@
 # Upcoming Wekan release
 
-This release fixes the following bugs:
+This release adds the following new features:
+
+* [Calendar](https://github.com/wekan/wekan/pull/1728).
+
+and fixes the following bugs:
 
 * To fix ["title is required"](https://github.com/wekan/wekan/issues/1576) fix only
   add-checklist-items and revert all other migration changes](https://github.com/wekan/wekan/issues/1734);

+ 2 - 0
client/components/boards/boardBody.jade

@@ -25,3 +25,5 @@ template(name="boardBody")
           +swimlane(this)
       if isViewLists
         +listsGroup
+      if isViewCalendar
+        +fullcalendar(calendarOptions)

+ 62 - 0
client/components/boards/boardBody.js

@@ -98,6 +98,12 @@ BlazeComponent.extendComponent({
     return (currentUser.profile.boardView === 'board-view-lists');
   },
 
+  isViewCalendar() {
+    const currentUser = Meteor.user();
+    if (!currentUser) return true;
+    return (currentUser.profile.boardView === 'board-view-cal');
+  },
+
   openNewListForm() {
     if (this.isViewSwimlanes()) {
       this.childComponents('swimlane')[0]
@@ -108,6 +114,62 @@ BlazeComponent.extendComponent({
     }
   },
 
+  calendarOptions() {
+    return {
+      id: 'calendar-view',
+      defaultView: 'basicWeek',
+      header: {
+        left: 'title',
+        center: 'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,timelineMonth timelineYear',
+        right: 'today prev,next',
+      },
+      views: {
+        basic: {
+          // options apply to basicWeek and basicDay views
+        },
+        agenda: {
+          // options apply to agendaWeek and agendaDay views
+        },
+        week: {
+          // options apply to basicWeek and agendaWeek views
+        },
+        day: {
+          // options apply to basicDay and agendaDay views
+        },
+      },
+      themeSystem: 'jquery-ui',
+      height: 'parent',
+      /* TODO: lists as resources: https://fullcalendar.io/docs/vertical-resource-view */
+      navLinks: true,
+      nowIndicator: true,
+      businessHours: {
+        // days of week. an array of zero-based day of week integers (0=Sunday)
+        dow: [ 1, 2, 3, 4, 5 ], // Monday - Thursday
+        start: '8:00',
+        end: '18:00',
+      },
+      locale: TAPi18n.getLanguage(),
+      events(start, end, timezone, callback) {
+        const currentBoard = Boards.findOne(Session.get('currentBoard'));
+        const events = [];
+        currentBoard.cardsInInterval(start.toDate(), end.toDate()).forEach(function(card){
+          events.push({
+            id: card.id,
+            title: card.title,
+            start: card.startAt,
+            end: card.endAt,
+            url: FlowRouter.url('card', {
+              boardId: currentBoard._id,
+              slug: currentBoard.slug,
+              cardId: card._id,
+            }),
+          });
+        });
+        callback(events);
+      },
+    };
+  },
+
   events() {
     return [{
       // XXX The board-overlay div should probably be moved to the parent

+ 3 - 1
client/components/boards/boardHeader.js

@@ -89,9 +89,11 @@ BlazeComponent.extendComponent({
       'click .js-toggle-board-view'() {
         const currentUser = Meteor.user();
         if (currentUser.profile.boardView === 'board-view-swimlanes') {
-          currentUser.setBoardView('board-view-lists');
+          currentUser.setBoardView('board-view-cal');
         } else if (currentUser.profile.boardView === 'board-view-lists') {
           currentUser.setBoardView('board-view-swimlanes');
+        } else if (currentUser.profile.boardView === 'board-view-cal') {
+          currentUser.setBoardView('board-view-lists');
         }
       },
       'click .js-open-filter-view'() {

+ 36 - 21
client/components/cards/cardDate.js

@@ -218,10 +218,13 @@ class CardReceivedDate extends CardDate {
   }
 
   classes() {
-    let classes = 'received-date' + ' ';
-    if (this.date.get().isBefore(this.now.get(), 'minute') &&
-        this.now.get().isBefore(this.data().dueAt)) {
-      classes += 'current';
+    let classes = 'received-date ';
+    const dueAt = this.data().dueAt;
+    if (dueAt) {
+      if (this.date.get().isBefore(this.now.get(), 'minute') &&
+          this.now.get().isBefore(dueAt)) {
+        classes += 'current';
+      }
     }
     return classes;
   }
@@ -249,9 +252,12 @@ class CardStartDate extends CardDate {
 
   classes() {
     let classes = 'start-date' + ' ';
-    if (this.date.get().isBefore(this.now.get(), 'minute') &&
-        this.now.get().isBefore(this.data().dueAt)) {
-      classes += 'current';
+    const dueAt = this.data().dueAt;
+    if (dueAt) {
+      if (this.date.get().isBefore(this.now.get(), 'minute') &&
+          this.now.get().isBefore(dueAt)) {
+        classes += 'current';
+      }
     }
     return classes;
   }
@@ -279,18 +285,23 @@ class CardDueDate extends CardDate {
 
   classes() {
     let classes = 'due-date' + ' ';
+
     // if endAt exists & is < dueAt, dueAt doesn't need to be flagged
-    if ((this.data().endAt !== 0) &&
-       (this.data().endAt !== null) &&
-       (this.data().endAt !== '') &&
-       (this.data().endAt !== undefined) &&
-       (this.date.get().isBefore(this.data().endAt)))
+    const endAt = this.data().endAt;
+    const theDate = this.date.get();
+    const now = this.now.get();
+
+    if ((endAt !== 0) &&
+       (endAt !== null) &&
+       (endAt !== '') &&
+       (endAt !== undefined) &&
+       (theDate.isBefore(endAt)))
       classes += 'current';
-    else if (this.now.get().diff(this.date.get(), 'days') >= 2)
+    else if (now.diff(theDate, 'days') >= 2)
       classes += 'long-overdue';
-    else if (this.now.get().diff(this.date.get(), 'minute') >= 0)
+    else if (now.diff(theDate, 'minute') >= 0)
       classes += 'due';
-    else if (this.now.get().diff(this.date.get(), 'days') >= -1)
+    else if (now.diff(theDate, 'days') >= -1)
       classes += 'almost-due';
     return classes;
   }
@@ -318,12 +329,16 @@ class CardEndDate extends CardDate {
 
   classes() {
     let classes = 'end-date' + ' ';
-    if (this.data.dueAt.diff(this.date.get(), 'days') >= 2)
-      classes += 'long-overdue';
-    else if (this.data.dueAt.diff(this.date.get(), 'days') > 0)
-      classes += 'due';
-    else if (this.data.dueAt.diff(this.date.get(), 'days') <= 0)
-      classes += 'current';
+    const dueAt = this.data.dueAt;
+    if (dueAt) {
+      const diff = dueAt.diff(this.date.get(), 'days');
+      if (diff >= 2)
+        classes += 'long-overdue';
+      else if (diff > 0)
+        classes += 'due';
+      else if (diff <= 0)
+        classes += 'current';
+    }
     return classes;
   }
 

+ 1 - 1
client/components/lists/listBody.js

@@ -45,7 +45,7 @@ BlazeComponent.extendComponent({
     const boardView = Meteor.user().profile.boardView;
     if (boardView === 'board-view-swimlanes')
       swimlaneId = this.parentComponent().parentComponent().data()._id;
-    else if (boardView === 'board-view-lists')
+    else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal'))
       swimlaneId = Swimlanes.findOne({boardId})._id;
 
     if (title) {

+ 2 - 0
client/components/swimlanes/swimlanes.js

@@ -7,6 +7,8 @@ function currentCardIsInThisList(listId, swimlaneId) {
     return currentCard && currentCard.listId === listId;
   else if (currentUser.profile.boardView === 'board-view-swimlanes')
     return currentCard && currentCard.listId === listId && currentCard.swimlaneId === swimlaneId;
+  else if (currentUser.profile.boardView === 'board-view-cal')
+    return currentCard;
   else
     return false;
 }

+ 1 - 0
i18n/en.i18n.json

@@ -100,6 +100,7 @@
     "boardMenuPopup-title": "Board Menu",
     "boards": "Boards",
     "board-view": "Board View",
+    "board-view-cal": "Calendar",
     "board-view-swimlanes": "Swimlanes",
     "board-view-lists": "Lists",
     "bucket-example": "Like “Bucket List” for example",

+ 27 - 0
models/boards.js

@@ -284,6 +284,33 @@ Boards.helpers({
 
     return Cards.find(query, projection);
   },
+
+  cardsInInterval(start, end) {
+    return Cards.find({
+      $or: [
+        {
+          startAt: {
+            $lte: start,
+          }, endAt: {
+            $gte: start,
+          },
+        }, {
+          startAt: {
+            $lte: end,
+          }, endAt: {
+            $gte: end,
+          },
+        }, {
+          startAt: {
+            $gte: start,
+          }, endAt: {
+            $lte: end,
+          },
+        },
+      ],
+    });
+  },
+
 });
 
 Boards.mutations({

+ 5 - 0
models/users.js

@@ -100,6 +100,11 @@ Users.attachSchema(new SimpleSchema({
   'profile.boardView': {
     type: String,
     optional: true,
+    allowedValues: [
+      'board-view-lists',
+      'board-view-swimlanes',
+      'board-view-cal',
+    ],
   },
   services: {
     type: Object,