浏览代码

Create custom fields creation UI added to Board Menu, Model in progress

Pouyan Savoli 7 年之前
父节点
当前提交
ade3c02122

+ 1 - 0
.gitignore

@@ -13,3 +13,4 @@ package-lock.json
 **/stage
 **/prime
 **/*.snap
+.idea

+ 3 - 0
client/components/activities/activities.jade

@@ -50,6 +50,9 @@ template(name="boardActivities")
         if($eq activityType 'createCard')
           | {{{_ 'activity-added' cardLink boardLabel}}}.
 
+        if($eq activityType 'createCustomField')
+          | {{_ 'activity-customfield-created' customField}}.
+
         if($eq activityType 'createList')
           | {{_ 'activity-added' list.title boardLabel}}.
 

+ 5 - 0
client/components/activities/activities.js

@@ -91,6 +91,11 @@ BlazeComponent.extendComponent({
     }, attachment.name()));
   },
 
+  customField() {
+    const customField = this.currentData().customFieldId;
+    return customField;
+  },
+
   events() {
     return [{
       // XXX We should use Popup.afterConfirmation here

+ 1 - 0
client/components/boards/boardHeader.jade

@@ -103,6 +103,7 @@ template(name="boardHeaderBar")
 
 template(name="boardMenuPopup")
   ul.pop-over-list
+    li: a.js-custom-fields {{_ 'custom-fields'}}
     li: a.js-open-archives {{_ 'archived-items'}}
     if currentUser.isBoardAdmin
       li: a.js-change-board-color {{_ 'board-change-color'}}

+ 4 - 0
client/components/boards/boardHeader.js

@@ -1,5 +1,9 @@
 Template.boardMenuPopup.events({
   'click .js-rename-board': Popup.open('boardChangeTitle'),
+  'click .js-custom-fields'() {
+    Sidebar.setView('customFields');
+    Popup.close();
+  },
   'click .js-open-archives'() {
     Sidebar.setView('archives');
     Popup.close();

+ 3 - 0
client/components/main/popup.styl

@@ -33,6 +33,9 @@ $popupWidth = 300px
   textarea
     height: 72px
 
+  form a span
+    padding: 0 0.5rem
+
   .header
     height: 36px
     position: relative

+ 1 - 0
client/components/sidebar/sidebar.js

@@ -5,6 +5,7 @@ const defaultView = 'home';
 const viewTitles = {
   filter: 'filter-cards',
   multiselection: 'multi-selection',
+  customFields: 'custom-fields',
   archives: 'archives',
 };
 

+ 31 - 0
client/components/sidebar/sidebarCustomFields.jade

@@ -0,0 +1,31 @@
+template(name="customFieldsSidebar")
+    ul.sidebar-list
+        each customsFields
+            li
+                a.name
+                    span.sidebar-list-item-description
+                        {{_ 'some name'}}
+    if currentUser.isBoardMember
+        hr
+        a.sidebar-btn.js-open-create-custom-field
+            i.fa.fa-plus
+            span {{_ 'Create Custom Field'}}
+
+template(name="createCustomFieldPopup")
+    form
+        label
+            | {{_ 'name'}}
+            input.js-field-name(type="text" name="field-name" autofocus)
+        label
+            | {{_ 'type'}}
+            select.js-field-type(name="field-type")
+                option(value="string") String
+                option(value="number") Number
+                option(value="checkbox") Checkbox
+                option(value="date") Date
+                option(value="DropdownList") Dropdown List
+        a.flex.js-field-show-on-card
+            .materialCheckBox(class="{{#if showOnCard}}is-checked{{/if}}")
+
+            span {{_ 'show-field-on-card'}}
+        input.primary.wide(type="submit" value="{{_ 'save'}}")

+ 55 - 0
client/components/sidebar/sidebarCustomFields.js

@@ -0,0 +1,55 @@
+BlazeComponent.extendComponent({
+
+  customFields() {
+    return CustomFields.find({
+      boardId: Session.get('currentBoard'),
+    });
+  },
+
+  events() {
+    return [{
+      'click .js-open-create-custom-field': Popup.open('createCustomField'),
+      'click .js-edit-custom-field'() {
+        // todo
+      },
+      'click .js-delete-custom-field': Popup.afterConfirm('customFieldDelete', function() {
+        const customFieldId = this._id;
+        CustomFields.remove(customFieldId);
+        Popup.close();
+      }),
+    }];
+  },
+
+}).register('customFieldsSidebar');
+
+Template.createCustomFieldPopup.helpers({
+
+});
+
+Template.createCustomFieldPopup.events({
+  'click .js-field-show-on-card'(event) {
+    let $target = $(event.target);
+    if(!$target.hasClass('js-field-show-on-card')){
+      $target = $target.parent();
+    }
+    $target.find('.materialCheckBox').toggleClass('is-checked');
+    $target.toggleClass('is-checked');
+  },
+  'submit'(evt, tpl) {
+    evt.preventDefault();
+
+    const name = tpl.find('.js-field-name').value.trim();
+    const type = tpl.find('.js-field-type').value.trim();
+    const showOnCard = tpl.find('.js-field-show-on-card.is-checked') != null;
+    //console.log("Create",name,type,showOnCard);
+
+    CustomFields.insert({
+      boardId: Session.get('currentBoard'),
+      name: name,
+      type: type,
+      showOnCard: showOnCard
+    });
+
+    Popup.back();
+  },
+});

+ 8 - 0
i18n/en.i18n.json

@@ -7,6 +7,7 @@
     "act-addComment": "commented on __card__: __comment__",
     "act-createBoard": "created __board__",
     "act-createCard": "added __card__ to __list__",
+    "act-createCustomField": "created custom field __customField__",
     "act-createList": "added __list__ to __board__",
     "act-addBoardMember": "added __member__ to __board__",
     "act-archivedBoard": "archived __board__",
@@ -29,6 +30,7 @@
     "activity-archived": "archived %s",
     "activity-attached": "attached %s to %s",
     "activity-created": "created %s",
+    "activity-customfield-created": "created custom field %s",
     "activity-excluded": "excluded %s from %s",
     "activity-imported": "imported %s into %s from %s",
     "activity-imported-board": "imported %s from %s",
@@ -152,7 +154,11 @@
     "createBoardPopup-title": "Create Board",
     "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
+    "createCustomField": "Create Custom Field",
+    "createCustomFieldPopup-title": "Create Custom Field",
     "current": "current",
+    "custom-fields": "Custom Fields",
+    "customFieldDeletePopup-title": "Delete Card?",
     "date": "Date",
     "decline": "Decline",
     "default-avatar": "Default avatar",
@@ -330,6 +336,7 @@
     "title": "Title",
     "tracking": "Tracking",
     "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
+    "type": "Type",
     "unassign-member": "Unassign member",
     "unsaved-description": "You have an unsaved description.",
     "unwatch": "Unwatch",
@@ -387,6 +394,7 @@
     "hours": "hours",
     "minutes": "minutes",
     "seconds": "seconds",
+    "show-field-on-card": "Show this field on card",
     "yes": "Yes",
     "no": "No",
     "accounts": "Accounts",

+ 8 - 0
models/activities.js

@@ -41,6 +41,9 @@ Activities.helpers({
   checklistItem() {
     return Checklists.findOne(this.checklistId).getItem(this.checklistItemId);
   },
+  customField() {
+    return CustomFields.findOne(this.customFieldId);
+  },
 });
 
 Activities.before.insert((userId, doc) => {
@@ -57,6 +60,7 @@ if (Meteor.isServer) {
     Activities._collection._ensureIndex({ boardId: 1, createdAt: -1 });
     Activities._collection._ensureIndex({ commentId: 1 }, { partialFilterExpression: { commentId: { $exists: true } } });
     Activities._collection._ensureIndex({ attachmentId: 1 }, { partialFilterExpression: { attachmentId: { $exists: true } } });
+    Activities._collection._ensureIndex({ customFieldId: 1 }, { partialFilterExpression: { customFieldId: { $exists: true } } });
   });
 
   Activities.after.insert((userId, doc) => {
@@ -123,6 +127,10 @@ if (Meteor.isServer) {
       const checklistItem = activity.checklistItem();
       params.checklistItem = checklistItem.title;
     }
+    if (activity.customFieldId) {
+      const customField = activity.customField();
+      params.customField = customField.name;
+    }
     if (board) {
       const watchingUsers = _.pluck(_.where(board.watchers, {level: 'watching'}), 'userId');
       const trackingUsers = _.pluck(_.where(board.watchers, {level: 'tracking'}), 'userId');

+ 116 - 0
models/customFields.js

@@ -0,0 +1,116 @@
+CustomFields = new Mongo.Collection('customFields');
+
+CustomFields.attachSchema(new SimpleSchema({
+  boardId: {
+    type: String,
+  },
+  name: {
+    type: String,
+  },
+  type: {
+    type: String,
+  },
+  showOnCard: {
+    type: Boolean,
+  }
+}));
+
+CustomFields.allow({
+  insert(userId, doc) {
+    return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+  },
+  update(userId, doc) {
+    return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+  },
+  remove(userId, doc) {
+    return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+  },
+  fetch: ['boardId'],
+});
+
+// not sure if we need this?
+//CustomFields.hookOptions.after.update = { fetchPrevious: false };
+
+function customFieldCreation(userId, doc){
+  Activities.insert({
+    userId,
+    activityType: 'createCustomField',
+    boardId: doc.boardId,
+    customFieldId: doc._id,
+  });
+}
+
+if (Meteor.isServer) {
+  // Comments are often fetched within a card, so we create an index to make these
+  // queries more efficient.
+  Meteor.startup(() => {
+    CardComments._collection._ensureIndex({ cardId: 1, createdAt: -1 });
+  });
+
+  CustomFields.after.insert((userId, doc) => {
+    customFieldCreation(userId, doc);
+  });
+
+  CustomFields.after.remove((userId, doc) => {
+    const activity = Activities.findOne({ customFieldId: doc._id });
+    if (activity) {
+      Activities.remove(activity._id);
+    }
+  });
+}
+
+//CUSTOM FIELD REST API
+if (Meteor.isServer) {
+  JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', function (req, res, next) {
+    Authentication.checkUserId( req.userId);
+    const paramBoardId = req.params.boardId;
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: CustomFields.find({ boardId: paramBoardId })
+    });
+  });
+
+  JsonRoutes.add('GET', '/api/boards/:boardId/comments/:customFieldId', function (req, res, next) {
+    Authentication.checkUserId( req.userId);
+    const paramBoardId = req.params.boardId;
+    const paramCustomFieldId = req.params.customFieldId;
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: CustomFields.findOne({ _id: paramCustomFieldId, boardId: paramBoardId }),
+    });
+  });
+
+  JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', function (req, res, next) {
+    Authentication.checkUserId( req.userId);
+    const paramBoardId = req.params.boardId;
+    const id = CustomFields.direct.insert({
+      name: req.body.name,
+      type: req.body.type,
+      showOnCard: req.body.showOnCard,
+      boardId: paramBoardId,
+    });
+
+    const customField = CustomFields.findOne({_id: id, cardId:paramCardId, boardId: paramBoardId });
+    customFieldCreation(req.body.authorId, customField);
+
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: id,
+      },
+    });
+  });
+
+  JsonRoutes.add('DELETE', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res, next) {
+    Authentication.checkUserId( req.userId);
+    const paramBoardId = req.params.boardId;
+    const id = req.params.customFieldId;
+    CustomFields.remove({ _id: id, boardId: paramBoardId });
+    JsonRoutes.sendResult(res, {
+      code: 200,
+      data: {
+        _id: id,
+      },
+    });
+  });
+}

+ 3 - 0
server/publications/customFields.js

@@ -0,0 +1,3 @@
+Meteor.publish('customFields', function() {
+  return CustomFields.find();
+});