summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--client/components/activities/activities.jade3
-rw-r--r--client/components/activities/activities.js5
-rw-r--r--client/components/boards/boardHeader.jade1
-rw-r--r--client/components/boards/boardHeader.js4
-rw-r--r--client/components/main/popup.styl3
-rw-r--r--client/components/sidebar/sidebar.js1
-rw-r--r--client/components/sidebar/sidebarCustomFields.jade31
-rw-r--r--client/components/sidebar/sidebarCustomFields.js55
-rw-r--r--i18n/en.i18n.json8
-rw-r--r--models/activities.js8
-rw-r--r--models/customFields.js116
-rw-r--r--server/publications/customFields.js3
13 files changed, 239 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 7642f23d..a5abba70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ package-lock.json
**/stage
**/prime
**/*.snap
+.idea \ No newline at end of file
diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade
index be12a728..b52a2981 100644
--- a/client/components/activities/activities.jade
+++ b/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}}.
diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js
index ccb064f3..81645a51 100644
--- a/client/components/activities/activities.js
+++ b/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
diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade
index ffb8eb27..67acdc9e 100644
--- a/client/components/boards/boardHeader.jade
+++ b/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'}}
diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js
index b7807ca9..8983c722 100644
--- a/client/components/boards/boardHeader.js
+++ b/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();
diff --git a/client/components/main/popup.styl b/client/components/main/popup.styl
index b7c9e264..ff00eef3 100644
--- a/client/components/main/popup.styl
+++ b/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
diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js
index 1290fd13..59a2b42c 100644
--- a/client/components/sidebar/sidebar.js
+++ b/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',
};
diff --git a/client/components/sidebar/sidebarCustomFields.jade b/client/components/sidebar/sidebarCustomFields.jade
new file mode 100644
index 00000000..33688441
--- /dev/null
+++ b/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'}}") \ No newline at end of file
diff --git a/client/components/sidebar/sidebarCustomFields.js b/client/components/sidebar/sidebarCustomFields.js
new file mode 100644
index 00000000..da03f484
--- /dev/null
+++ b/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();
+ },
+}); \ No newline at end of file
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index 64a720db..d7b9a48b 100644
--- a/i18n/en.i18n.json
+++ b/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",
diff --git a/models/activities.js b/models/activities.js
index 4ddcfa72..237283f8 100644
--- a/models/activities.js
+++ b/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');
diff --git a/models/customFields.js b/models/customFields.js
new file mode 100644
index 00000000..75ee55e8
--- /dev/null
+++ b/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,
+ },
+ });
+ });
+}
diff --git a/server/publications/customFields.js b/server/publications/customFields.js
new file mode 100644
index 00000000..25dada59
--- /dev/null
+++ b/server/publications/customFields.js
@@ -0,0 +1,3 @@
+Meteor.publish('customFields', function() {
+ return CustomFields.find();
+});