From ade3c02122d262c72bd7c4fd1cbcab8e136184ba Mon Sep 17 00:00:00 2001 From: Pouyan Savoli Date: Fri, 25 Aug 2017 02:59:20 +0200 Subject: Create custom fields creation UI added to Board Menu, Model in progress --- .gitignore | 1 + client/components/activities/activities.jade | 3 + client/components/activities/activities.js | 5 + client/components/boards/boardHeader.jade | 1 + client/components/boards/boardHeader.js | 4 + client/components/main/popup.styl | 3 + client/components/sidebar/sidebar.js | 1 + client/components/sidebar/sidebarCustomFields.jade | 31 ++++++ client/components/sidebar/sidebarCustomFields.js | 55 ++++++++++ i18n/en.i18n.json | 8 ++ models/activities.js | 8 ++ models/customFields.js | 116 +++++++++++++++++++++ server/publications/customFields.js | 3 + 13 files changed, 239 insertions(+) create mode 100644 client/components/sidebar/sidebarCustomFields.jade create mode 100644 client/components/sidebar/sidebarCustomFields.js create mode 100644 models/customFields.js create mode 100644 server/publications/customFields.js 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(); +}); -- cgit v1.2.3-1-g7c22 From afd87e3caa1fedbe8fe5dbaefa485fee1ed85c71 Mon Sep 17 00:00:00 2001 From: Pouyan Savoli Date: Sun, 27 Aug 2017 22:31:24 +0200 Subject: many custom fields model and UI enhancements --- client/components/activities/activities.js | 4 +- client/components/boards/boardHeader.jade | 2 +- client/components/boards/boardHeader.js | 2 +- client/components/cards/cardDetails.jade | 15 ++++++ client/components/cards/cardDetails.js | 15 ++++++ client/components/sidebar/sidebar.js | 2 +- client/components/sidebar/sidebar.styl | 61 ++++++++++++++-------- client/components/sidebar/sidebarCustomFields.jade | 44 +++++++++++----- client/components/sidebar/sidebarCustomFields.js | 36 +++++++++---- i18n/en.i18n.json | 17 ++++-- models/boards.js | 4 ++ models/customFields.js | 21 ++++---- server/publications/boards.js | 1 + 13 files changed, 157 insertions(+), 67 deletions(-) diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index 81645a51..95699961 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -92,8 +92,8 @@ BlazeComponent.extendComponent({ }, customField() { - const customField = this.currentData().customFieldId; - return customField; + const customField = this.currentData().customField(); + return customField.name; }, events() { diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 67acdc9e..9d16b10a 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -103,7 +103,7 @@ template(name="boardHeaderBar") template(name="boardMenuPopup") ul.pop-over-list - li: a.js-custom-fields {{_ 'custom-fields'}} + li: a.js-configure-custom-fields {{_ 'configure-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 8983c722..61cdb149 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -1,6 +1,6 @@ Template.boardMenuPopup.events({ 'click .js-rename-board': Popup.open('boardChangeTitle'), - 'click .js-custom-fields'() { + 'click .js-configure-custom-fields'() { Sidebar.setView('customFields'); Popup.close(); }, diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 09e0532c..ccb3ae97 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -107,6 +107,7 @@ template(name="cardDetailsActionsPopup") li: a.js-members {{_ 'card-edit-members'}} li: a.js-labels {{_ 'card-edit-labels'}} li: a.js-attachments {{_ 'card-edit-attachments'}} + li: a.js-custom-fields {{_ 'card-edit-custom-fields'}} li: a.js-start-date {{_ 'editCardStartDatePopup-title'}} li: a.js-due-date {{_ 'editCardDueDatePopup-title'}} hr @@ -143,6 +144,20 @@ template(name="cardMembersPopup") if isCardMember i.fa.fa-check +template(name="cardCustomFieldsPopup") + ul.pop-over-list + each board.customFields + li.item(class="") + a.name.js-select-field(href="#") + span.full-name + = name + if isCardMember + i.fa.fa-check + hr + a.quiet-button.full.js-configure-custom-fields + i.fa.fa-cog + span {{_ 'configure-custom-fields'}} + template(name="cardMorePopup") p.quiet span.clearfix diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 7c6c3ce7..f972424f 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -154,6 +154,7 @@ Template.cardDetailsActionsPopup.events({ 'click .js-members': Popup.open('cardMembers'), 'click .js-labels': Popup.open('cardLabels'), 'click .js-attachments': Popup.open('cardAttachments'), + 'click .js-custom-fields': Popup.open('cardCustomFields'), 'click .js-start-date': Popup.open('editCardStartDate'), 'click .js-due-date': Popup.open('editCardDueDate'), 'click .js-move-card': Popup.open('moveCard'), @@ -196,6 +197,20 @@ Template.editCardTitleForm.events({ }, }); +Template.cardCustomFieldsPopup.events({ + 'click .js-select-field'(evt) { + const card = Cards.findOne(Session.get('currentCard')); + const customFieldId = this.customFieldId; + card.toggleCustomField(customFieldId); + evt.preventDefault(); + }, + 'click .js-configure-custom-fields'(evt) { + EscapeActions.executeUpTo('detailsPane'); + Sidebar.setView('customFields'); + evt.preventDefault(); + } +}); + Template.moveCardPopup.events({ 'click .js-select-list' () { // XXX We should *not* get the currentCard from the global state, but diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 59a2b42c..0ff73215 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -5,7 +5,7 @@ const defaultView = 'home'; const viewTitles = { filter: 'filter-cards', multiselection: 'multi-selection', - customFields: 'custom-fields', + customFields: 'configure-custom-fields', archives: 'archives', }; diff --git a/client/components/sidebar/sidebar.styl b/client/components/sidebar/sidebar.styl index 8f2f493e..740186b5 100644 --- a/client/components/sidebar/sidebar.styl +++ b/client/components/sidebar/sidebar.styl @@ -45,28 +45,45 @@ display: flex flex-direction: column - li > a - display: flex - height: 30px - margin: 0 - padding: 4px - border-radius: 3px - align-items: center - - &:hover - &, i, .quiet - color white - - .member, .card-label - margin-right: 7px - margin-top: 5px - - .sidebar-list-item-description - flex: 1 - overflow: ellipsis - - .fa.fa-check - margin: 0 4px + li + & > a + display: flex + height: 30px + margin: 0 + padding: 4px + border-radius: 3px + align-items: center + + &:hover + &, i, .quiet + color white + + .member, .card-label + margin-right: 7px + margin-top: 5px + + .minicard-edit-button + float: right + padding: 8px + border-radius: 3px + + .sidebar-list-item-description + flex: 1 + overflow: ellipsis + + .fa.fa-check + margin: 0 4px + + .minicard + padding: 6px 8px 4px + + .minicard-edit-button + float: right + padding: 4px + border-radius: 3px + + &:hover + background: #dbdbdb .sidebar-btn display: block diff --git a/client/components/sidebar/sidebarCustomFields.jade b/client/components/sidebar/sidebarCustomFields.jade index 33688441..e17bb75d 100644 --- a/client/components/sidebar/sidebarCustomFields.jade +++ b/client/components/sidebar/sidebarCustomFields.jade @@ -1,31 +1,49 @@ template(name="customFieldsSidebar") ul.sidebar-list - each customsFields + each customFields li - a.name - span.sidebar-list-item-description - {{_ 'some name'}} + div.minicard-wrapper.js-minicard + div.minicard + a.fa.fa-pencil.js-edit-custom-field.minicard-edit-button + div.minicard-title + | {{ name }} ({{ type }}) + if currentUser.isBoardMember hr a.sidebar-btn.js-open-create-custom-field i.fa.fa-plus - span {{_ 'Create Custom Field'}} + span {{_ 'createCustomField'}} template(name="createCustomFieldPopup") form label | {{_ 'name'}} - input.js-field-name(type="text" name="field-name" autofocus) + unless _id + input.js-field-name(type="text" name="field-name" autofocus) + else + input.js-field-name(type="text" name="field-name" value=name) + 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 + select.js-field-type(name="field-type" disabled="{{#if _id}}disabled{{/if}}") + each types + if selected + option(value=type selected="selected") {{name}} + else + option(value=type) {{name}} 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 + button.primary.wide.left(type="submit") + | {{_ 'save'}} + if _id + button.negate.wide.right.js-delete-custom-field + | {{_ 'delete'}} + +template(name="editCustomFieldPopup") + | {{> createCustomFieldPopup}} + +template(name="deleteCustomFieldPopup") + p {{_ "custom-field-delete-pop"}} + button.js-confirm.negate.full(type="submit") {{_ 'delete'}} \ No newline at end of file diff --git a/client/components/sidebar/sidebarCustomFields.js b/client/components/sidebar/sidebarCustomFields.js index da03f484..6ddd466e 100644 --- a/client/components/sidebar/sidebarCustomFields.js +++ b/client/components/sidebar/sidebarCustomFields.js @@ -9,21 +9,22 @@ BlazeComponent.extendComponent({ 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(); - }), + 'click .js-edit-custom-field': Popup.open('editCustomField'), }]; }, }).register('customFieldsSidebar'); Template.createCustomFieldPopup.helpers({ - + types() { + var currentType = this.type; + return ['text', 'number', 'checkbox', 'date', 'dropdown']. + map(type => {return { + type: type, + name: TAPi18n.__('custom-field-' + type), + selected: type == currentType, + }}); + }, }); Template.createCustomFieldPopup.events({ @@ -41,7 +42,7 @@ Template.createCustomFieldPopup.events({ 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); + //console.log('Create',name,type,showOnCard); CustomFields.insert({ boardId: Session.get('currentBoard'), @@ -52,4 +53,17 @@ Template.createCustomFieldPopup.events({ Popup.back(); }, -}); \ No newline at end of file + 'click .js-delete-custom-field': Popup.afterConfirm('deleteCustomField', function() { + const customFieldId = this._id; + CustomFields.remove(customFieldId); + Popup.close(); + }), +}); + +/*Template.deleteCustomFieldPopup.events({ + 'submit'(evt) { + const customFieldId = this._id; + CustomFields.remove(customFieldId); + Popup.close(); + } +});*/ \ No newline at end of file diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index d7b9a48b..1039d3c6 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -103,6 +103,7 @@ "card-due": "Due", "card-due-on": "Due on", "card-edit-attachments": "Edit attachments", + "card-edit-custom-fields": "Edit custom fields", "card-edit-labels": "Edit labels", "card-edit-members": "Edit members", "card-labels-title": "Change the labels for the card.", @@ -110,6 +111,7 @@ "card-start": "Start", "card-start-on": "Starts on", "cardAttachmentsPopup-title": "Attach From", + "cardCustomFieldsPopup-title": "Edit custom fields", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -153,16 +155,22 @@ "create": "Create", "createBoardPopup-title": "Create Board", "chooseBoardSourcePopup-title": "Import board", + "configure-custom-fields": "Configure Custom Fields", "createLabelPopup-title": "Create Label", - "createCustomField": "Create Custom Field", - "createCustomFieldPopup-title": "Create Custom Field", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", "current": "current", - "custom-fields": "Custom Fields", - "customFieldDeletePopup-title": "Delete Card?", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-date": "Date", + "custom-field-dropdown": "Dropdown List", + "custom-field-number": "Number", + "custom-field-text": "Text", "date": "Date", "decline": "Decline", "default-avatar": "Default avatar", "delete": "Delete", + "deleteCustomFieldPopup-title": "Delete Custom Field?", "deleteLabelPopup-title": "Delete Label?", "description": "Description", "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", @@ -175,6 +183,7 @@ "edit-profile": "Edit Profile", "editCardStartDatePopup-title": "Change start date", "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", "editLabelPopup-title": "Change Label", "editNotificationPopup-title": "Edit Notification", "editProfilePopup-title": "Edit Profile", diff --git a/models/boards.js b/models/boards.js index 8a7844e2..98c0e46d 100644 --- a/models/boards.js +++ b/models/boards.js @@ -235,6 +235,10 @@ Boards.helpers({ return `board-color-${this.color}`; }, + customFields() { + return CustomFields.find({ boardId: this._id }, { sort: { name: 1 } }); + }, + // XXX currently mutations return no value so we have an issue when using addLabel in import // XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove... pushLabel(name, color) { diff --git a/models/customFields.js b/models/customFields.js index 75ee55e8..5e76db35 100644 --- a/models/customFields.js +++ b/models/customFields.js @@ -25,7 +25,7 @@ CustomFields.allow({ remove(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - fetch: ['boardId'], + fetch: ['userId', 'boardId'], }); // not sure if we need this? @@ -41,21 +41,18 @@ function customFieldCreation(userId, doc){ } 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 }); - }); + /*Meteor.startup(() => { + CustomFields._collection._ensureIndex({ boardId: 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); - } + Activities.remove({ + customFieldId: doc._id, + }); }); } @@ -70,7 +67,7 @@ if (Meteor.isServer) { }); }); - JsonRoutes.add('GET', '/api/boards/:boardId/comments/:customFieldId', function (req, res, next) { + JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res, next) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; const paramCustomFieldId = req.params.customFieldId; @@ -90,7 +87,7 @@ if (Meteor.isServer) { boardId: paramBoardId, }); - const customField = CustomFields.findOne({_id: id, cardId:paramCardId, boardId: paramBoardId }); + const customField = CustomFields.findOne({_id: id, boardId: paramBoardId }); customFieldCreation(req.body.authorId, customField); JsonRoutes.sendResult(res, { diff --git a/server/publications/boards.js b/server/publications/boards.js index f482f619..7cadf0c6 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -74,6 +74,7 @@ Meteor.publishRelations('board', function(boardId) { }, { limit: 1 }), function(boardId, board) { this.cursor(Lists.find({ boardId })); this.cursor(Integrations.find({ boardId })); + this.cursor(CustomFields.find({ boardId }, { sort: { name: 1 } })); // Cards and cards comments // XXX Originally we were publishing the card documents as a child of the -- cgit v1.2.3-1-g7c22 From d87191f17ee1cd49def9ca5a4d3d1568b041e6a2 Mon Sep 17 00:00:00 2001 From: Pouyan Savoli Date: Wed, 30 Aug 2017 02:54:54 +0200 Subject: card model and card ui preparation for custom fields --- client/components/cards/cardDetails.jade | 3 +++ client/components/cards/cardDetails.js | 2 +- client/components/cards/cardDetails.styl | 5 +++-- client/components/lists/listBody.js | 7 +++++++ models/cards.js | 33 ++++++++++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index ccb3ae97..7cb4f8d8 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -45,6 +45,9 @@ template(name="cardDetails") h3.card-details-item-title {{_ 'card-due'}} +cardDueDate + each customFields + .card-details-item.card-details-item-customfield + h3.card-details-item-title {{_ 'some-title' }} //- XXX We should use "editable" to avoid repetiting ourselves if canModifyCard diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index f972424f..1cc39492 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -200,7 +200,7 @@ Template.editCardTitleForm.events({ Template.cardCustomFieldsPopup.events({ 'click .js-select-field'(evt) { const card = Cards.findOne(Session.get('currentCard')); - const customFieldId = this.customFieldId; + const customFieldId = this._id; card.toggleCustomField(customFieldId); evt.preventDefault(); }, diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index f209862c..5ad0c9f5 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -70,6 +70,7 @@ .card-details-items display: flex + flex-wrap: wrap margin: 15px 0 .card-details-item @@ -80,8 +81,8 @@ &.card-details-item-members, &.card-details-item-start, &.card-details-item-due - width: 50% - flex-shrink: 1 + max-width: 50% + flex-grow: 1 .card-details-item-title font-size: 14px diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 724e805b..edac5b03 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -35,12 +35,17 @@ BlazeComponent.extendComponent({ const members = formComponent.members.get(); const labelIds = formComponent.labels.get(); + const customFields = formComponent.customFields.get(); + console.log("members", members); + console.log("labelIds", labelIds); + console.log("customFields", customFields); if (title) { const _id = Cards.insert({ title, members, labelIds, + customFields, listId: this.data()._id, boardId: this.data().board()._id, sort: sortIndex, @@ -121,11 +126,13 @@ BlazeComponent.extendComponent({ onCreated() { this.labels = new ReactiveVar([]); this.members = new ReactiveVar([]); + this.customFields = new ReactiveVar([]); }, reset() { this.labels.set([]); this.members.set([]); + this.customFields.set([]); }, getLabels() { diff --git a/models/cards.js b/models/cards.js index 0a440697..6896970c 100644 --- a/models/cards.js +++ b/models/cards.js @@ -38,6 +38,21 @@ Cards.attachSchema(new SimpleSchema({ } }, }, + customFields: { + type: [Object], + optional: true, + }, + 'customFields.$': { + type: new SimpleSchema({ + _id: { + type: String, + }, + value: { + type: Match.OneOf(String,Number,Boolean,Date), + optional: true, + }, + }) + }, dateLastActivity: { type: Date, autoValue() { @@ -238,6 +253,24 @@ Cards.mutations({ } }, + assignCustomField(customFieldId) { + console.log("assignCustomField", customFieldId); + return {$push: {customFields: {_id: customFieldId, value: null}}}; + }, + + unassignCustomField(customFieldId) { + console.log("unassignCustomField", customFieldId); + return {$pull: {customFields: {_id: customFieldId}}}; + }, + + toggleCustomField(customFieldId) { + if (this.customFields && this.customFields[customFieldId]) { + return this.unassignCustomField(customFieldId); + } else { + return this.assignCustomField(customFieldId); + } + }, + setCover(coverId) { return {$set: {coverId}}; }, -- cgit v1.2.3-1-g7c22 From 733b14dcd8f4b94047ffd444e7ab7ded49c245c0 Mon Sep 17 00:00:00 2001 From: Pouyan Savoli Date: Wed, 30 Aug 2017 03:23:57 +0200 Subject: card model and card ui preparation for custom fields #2 --- client/components/cards/cardDetails.styl | 7 ++++--- models/cards.js | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index 5ad0c9f5..c981e2a2 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -71,16 +71,17 @@ .card-details-items display: flex flex-wrap: wrap - margin: 15px 0 + margin: 0 0 15px .card-details-item - margin-right: 0.5em + margin: 15px 0.5em 0 0 &:last-child margin-right: 0 &.card-details-item-labels, &.card-details-item-members, &.card-details-item-start, - &.card-details-item-due + &.card-details-item-due, + &.card-details-item-customfield max-width: 50% flex-grow: 1 diff --git a/models/cards.js b/models/cards.js index 6896970c..670a47cb 100644 --- a/models/cards.js +++ b/models/cards.js @@ -186,6 +186,10 @@ Cards.helpers({ return this.checklistItemCount() !== 0; }, + customFieldIndex(customFieldId) { + return _.pluck(this.customFields, '_id').indexOf(customFieldId); + }, + absoluteUrl() { const board = this.board(); return FlowRouter.url('card', { @@ -255,7 +259,7 @@ Cards.mutations({ assignCustomField(customFieldId) { console.log("assignCustomField", customFieldId); - return {$push: {customFields: {_id: customFieldId, value: null}}}; + return {$addToSet: {customFields: {_id: customFieldId, value: null}}}; }, unassignCustomField(customFieldId) { @@ -264,7 +268,7 @@ Cards.mutations({ }, toggleCustomField(customFieldId) { - if (this.customFields && this.customFields[customFieldId]) { + if (this.customFields && this.customFieldIndex(customFieldId) > -1) { return this.unassignCustomField(customFieldId); } else { return this.assignCustomField(customFieldId); -- cgit v1.2.3-1-g7c22 From 6ff89b43b61ace7ea26d50f091ea6b714fa79c84 Mon Sep 17 00:00:00 2001 From: Pouyan Savoli Date: Tue, 5 Sep 2017 02:34:18 +0200 Subject: show custom fields on cards but still with dummy value --- client/components/cards/cardCustomFields.jade | 23 +++++++++++++++ client/components/cards/cardCustomFields.js | 41 +++++++++++++++++++++++++++ client/components/cards/cardDetails.jade | 20 +++---------- client/components/cards/cardDetails.js | 14 --------- models/cards.js | 21 ++++++++++++++ 5 files changed, 89 insertions(+), 30 deletions(-) create mode 100644 client/components/cards/cardCustomFields.jade create mode 100644 client/components/cards/cardCustomFields.js diff --git a/client/components/cards/cardCustomFields.jade b/client/components/cards/cardCustomFields.jade new file mode 100644 index 00000000..3c885b93 --- /dev/null +++ b/client/components/cards/cardCustomFields.jade @@ -0,0 +1,23 @@ +template(name="cardCustomFieldsPopup") + ul.pop-over-list + each board.customFields + li.item(class="") + a.name.js-select-field(href="#") + span.full-name + = name + if isCardMember + i.fa.fa-check + hr + a.quiet-button.full.js-configure-custom-fields + i.fa.fa-cog + span {{_ 'configure-custom-fields'}} + +template(name="cardCustomFieldText") + if canModifyCard + .item-title.js-open-inlined-form.is-editable + if value + = value + else + | {{_ 'edit'}} + else + .item-title {{value}} \ No newline at end of file diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js new file mode 100644 index 00000000..73dda02e --- /dev/null +++ b/client/components/cards/cardCustomFields.js @@ -0,0 +1,41 @@ +Template.cardCustomFieldsPopup.events({ + 'click .js-select-field'(evt) { + const card = Cards.findOne(Session.get('currentCard')); + const customFieldId = this._id; + card.toggleCustomField(customFieldId); + evt.preventDefault(); + }, + 'click .js-configure-custom-fields'(evt) { + EscapeActions.executeUpTo('detailsPane'); + Sidebar.setView('customFields'); + evt.preventDefault(); + } +}); + +const CardCustomField = BlazeComponent.extendComponent({ + template() { + return 'cardCustomFieldText'; + }, + + onCreated() { + const self = this; + self.date = ReactiveVar(); + self.now = ReactiveVar(moment()); + }, + + value() { + return "this is the value"; + }, + + showISODate() { + return this.date.get().toISOString(); + }, + + events() { + return [{ + 'click .js-edit-date': Popup.open('editCardStartDate'), + }]; + }, +}); + +CardCustomField.register('cardCustomField'); \ No newline at end of file diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 7cb4f8d8..f72abe6d 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -45,9 +45,11 @@ template(name="cardDetails") h3.card-details-item-title {{_ 'card-due'}} +cardDueDate - each customFields + each customFieldsWD .card-details-item.card-details-item-customfield - h3.card-details-item-title {{_ 'some-title' }} + h3.card-details-item-title + = definition.name + +cardCustomField //- XXX We should use "editable" to avoid repetiting ourselves if canModifyCard @@ -147,20 +149,6 @@ template(name="cardMembersPopup") if isCardMember i.fa.fa-check -template(name="cardCustomFieldsPopup") - ul.pop-over-list - each board.customFields - li.item(class="") - a.name.js-select-field(href="#") - span.full-name - = name - if isCardMember - i.fa.fa-check - hr - a.quiet-button.full.js-configure-custom-fields - i.fa.fa-cog - span {{_ 'configure-custom-fields'}} - template(name="cardMorePopup") p.quiet span.clearfix diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 1cc39492..8d5c478d 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -197,20 +197,6 @@ Template.editCardTitleForm.events({ }, }); -Template.cardCustomFieldsPopup.events({ - 'click .js-select-field'(evt) { - const card = Cards.findOne(Session.get('currentCard')); - const customFieldId = this._id; - card.toggleCustomField(customFieldId); - evt.preventDefault(); - }, - 'click .js-configure-custom-fields'(evt) { - EscapeActions.executeUpTo('detailsPane'); - Sidebar.setView('customFields'); - evt.preventDefault(); - } -}); - Template.moveCardPopup.events({ 'click .js-select-list' () { // XXX We should *not* get the currentCard from the global state, but diff --git a/models/cards.js b/models/cards.js index 670a47cb..fe86e642 100644 --- a/models/cards.js +++ b/models/cards.js @@ -190,6 +190,27 @@ Cards.helpers({ return _.pluck(this.customFields, '_id').indexOf(customFieldId); }, + // customFields with definitions + customFieldsWD() { + + // get all definitions + const definitions = CustomFields.find({ + boardId: this.boardId, + }).fetch(); + + // match right definition to each field + return this.customFields.map((customField) => { + return { + _id: customField._id, + value: customField.value, + definition: definitions.find((definition) => { + return definition._id == customField._id; + }) + } + }); + + }, + absoluteUrl() { const board = this.board(); return FlowRouter.url('card', { -- cgit v1.2.3-1-g7c22 From caad952bc1b29bb925c1347a14daa5d1ec8ada81 Mon Sep 17 00:00:00 2001 From: Pouyan Savoli Date: Thu, 14 Sep 2017 00:50:05 +0200 Subject: text custom fields are now editable using inlinedForm --- client/components/cards/cardCustomFields.jade | 20 +++++++++++++------- client/components/cards/cardCustomFields.js | 21 ++++++++++++++++++++- models/cards.js | 12 ++++++++++-- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/client/components/cards/cardCustomFields.jade b/client/components/cards/cardCustomFields.jade index 3c885b93..6515aa79 100644 --- a/client/components/cards/cardCustomFields.jade +++ b/client/components/cards/cardCustomFields.jade @@ -5,7 +5,7 @@ template(name="cardCustomFieldsPopup") a.name.js-select-field(href="#") span.full-name = name - if isCardMember + if hasCustomField i.fa.fa-check hr a.quiet-button.full.js-configure-custom-fields @@ -14,10 +14,16 @@ template(name="cardCustomFieldsPopup") template(name="cardCustomFieldText") if canModifyCard - .item-title.js-open-inlined-form.is-editable - if value + +inlinedForm(classNames="js-card-customfield-text") + +editor(autofocus=true) = value - else - | {{_ 'edit'}} - else - .item-title {{value}} \ No newline at end of file + .edit-controls.clearfix + button.primary(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + else + a.js-open-inlined-form + if value + +viewer + = value + else + | {{_ 'edit'}} \ No newline at end of file diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js index 73dda02e..7009dede 100644 --- a/client/components/cards/cardCustomFields.js +++ b/client/components/cards/cardCustomFields.js @@ -1,3 +1,11 @@ +Template.cardCustomFieldsPopup.helpers({ + hasCustomField() { + const card = Cards.findOne(Session.get('currentCard')); + const customFieldId = this._id; + return card.customFieldIndex(customFieldId) > -1; + }, +}); + Template.cardCustomFieldsPopup.events({ 'click .js-select-field'(evt) { const card = Cards.findOne(Session.get('currentCard')); @@ -24,7 +32,7 @@ const CardCustomField = BlazeComponent.extendComponent({ }, value() { - return "this is the value"; + return this.data().value; }, showISODate() { @@ -33,9 +41,20 @@ const CardCustomField = BlazeComponent.extendComponent({ events() { return [{ + 'submit .js-card-customfield-text'(evt) { + evt.preventDefault(); + const card = Cards.findOne(Session.get('currentCard')); + const customFieldId = this.data()._id; + const value = this.currentComponent().getValue(); + card.setCustomField(customFieldId,value); + }, 'click .js-edit-date': Popup.open('editCardStartDate'), }]; }, + + canModifyCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, }); CardCustomField.register('cardCustomField'); \ No newline at end of file diff --git a/models/cards.js b/models/cards.js index fe86e642..17abf430 100644 --- a/models/cards.js +++ b/models/cards.js @@ -279,12 +279,10 @@ Cards.mutations({ }, assignCustomField(customFieldId) { - console.log("assignCustomField", customFieldId); return {$addToSet: {customFields: {_id: customFieldId, value: null}}}; }, unassignCustomField(customFieldId) { - console.log("unassignCustomField", customFieldId); return {$pull: {customFields: {_id: customFieldId}}}; }, @@ -296,6 +294,16 @@ Cards.mutations({ } }, + setCustomField(customFieldId, value) { + // todo + const index = this.customFieldIndex(customFieldId); + if (index > -1) { + var update = {$set: {}}; + update.$set["customFields." + index + ".value"] = value; + return update; + } + }, + setCover(coverId) { return {$set: {coverId}}; }, -- cgit v1.2.3-1-g7c22 From 3753337d60e17e5e72ec071aa4a1b28e36297d15 Mon Sep 17 00:00:00 2001 From: Pouyan Savoli Date: Mon, 18 Sep 2017 00:46:17 +0200 Subject: dropdown items --- client/components/boards/boardHeader.jade | 2 +- client/components/boards/boardHeader.js | 2 +- client/components/cards/cardCustomFields.jade | 29 ++++- client/components/cards/cardCustomFields.js | 62 +++++++--- client/components/forms/forms.styl | 3 + client/components/sidebar/sidebar.js | 2 +- client/components/sidebar/sidebarCustomFields.jade | 23 ++-- client/components/sidebar/sidebarCustomFields.js | 129 +++++++++++++++------ i18n/en.i18n.json | 6 +- models/customFields.js | 19 +++ 10 files changed, 209 insertions(+), 68 deletions(-) diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 9d16b10a..67acdc9e 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -103,7 +103,7 @@ template(name="boardHeaderBar") template(name="boardMenuPopup") ul.pop-over-list - li: a.js-configure-custom-fields {{_ 'configure-custom-fields'}} + 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 61cdb149..8983c722 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -1,6 +1,6 @@ Template.boardMenuPopup.events({ 'click .js-rename-board': Popup.open('boardChangeTitle'), - 'click .js-configure-custom-fields'() { + 'click .js-custom-fields'() { Sidebar.setView('customFields'); Popup.close(); }, diff --git a/client/components/cards/cardCustomFields.jade b/client/components/cards/cardCustomFields.jade index 6515aa79..9e9f7d84 100644 --- a/client/components/cards/cardCustomFields.jade +++ b/client/components/cards/cardCustomFields.jade @@ -8,11 +8,14 @@ template(name="cardCustomFieldsPopup") if hasCustomField i.fa.fa-check hr - a.quiet-button.full.js-configure-custom-fields + a.quiet-button.full.js-settings i.fa.fa-cog - span {{_ 'configure-custom-fields'}} + span {{_ 'settings'}} -template(name="cardCustomFieldText") +template(name="cardCustomField") + +Template.dynamic(template=getTemplate) + +template(name="cardCustomField-text") if canModifyCard +inlinedForm(classNames="js-card-customfield-text") +editor(autofocus=true) @@ -25,5 +28,25 @@ template(name="cardCustomFieldText") if value +viewer = value + else + | {{_ 'edit'}} + +template(name="cardCustomField-dropdown") + if canModifyCard + +inlinedForm(classNames="js-card-customfield-dropdown") + select.inline + each items + if($eq data.value this._id) + option(value=_id selected="selected") {{name}} + else + option(value=_id) {{name}} + .edit-controls.clearfix + button.primary(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + else + a.js-open-inlined-form + if value + +viewer + = selectedItem else | {{_ 'edit'}} \ No newline at end of file diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js index 7009dede..f9fa760c 100644 --- a/client/components/cards/cardCustomFields.js +++ b/client/components/cards/cardCustomFields.js @@ -13,7 +13,7 @@ Template.cardCustomFieldsPopup.events({ card.toggleCustomField(customFieldId); evt.preventDefault(); }, - 'click .js-configure-custom-fields'(evt) { + 'click .js-settings'(evt) { EscapeActions.executeUpTo('detailsPane'); Sidebar.setView('customFields'); evt.preventDefault(); @@ -21,23 +21,25 @@ Template.cardCustomFieldsPopup.events({ }); const CardCustomField = BlazeComponent.extendComponent({ - template() { - return 'cardCustomFieldText'; + + getTemplate() { + return 'cardCustomField-' + this.data().definition.type; }, onCreated() { const self = this; - self.date = ReactiveVar(); - self.now = ReactiveVar(moment()); }, - value() { - return this.data().value; + canModifyCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); }, +}); +CardCustomField.register('cardCustomField'); - showISODate() { - return this.date.get().toISOString(); - }, +(class extends CardCustomField { + + onCreated() { + } events() { return [{ @@ -48,13 +50,39 @@ const CardCustomField = BlazeComponent.extendComponent({ const value = this.currentComponent().getValue(); card.setCustomField(customFieldId,value); }, - 'click .js-edit-date': Popup.open('editCardStartDate'), }]; - }, + } - canModifyCard() { - return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); - }, -}); +}).register('cardCustomField-text'); + +(class extends CardCustomField { + + onCreated() { + this._items = this.data().definition.settings.dropdownItems; + this.items = this._items.slice(0); + this.items.unshift({ + _id: "", + name: TAPi18n.__('custom-field-dropdown-none') + }); + } + + selectedItem() { + const selected = this._items.find((item) => { + return item._id == this.data().value; + }); + return (selected) ? selected.name : TAPi18n.__('custom-field-dropdown-unknown'); + } + + events() { + return [{ + 'submit .js-card-customfield-dropdown'(evt) { + evt.preventDefault(); + const card = Cards.findOne(Session.get('currentCard')); + const customFieldId = this.data()._id; + const value = this.find('select').value; + card.setCustomField(customFieldId,value); + }, + }]; + } -CardCustomField.register('cardCustomField'); \ No newline at end of file +}).register('cardCustomField-dropdown'); \ No newline at end of file diff --git a/client/components/forms/forms.styl b/client/components/forms/forms.styl index 646da657..81780a6f 100644 --- a/client/components/forms/forms.styl +++ b/client/components/forms/forms.styl @@ -85,6 +85,9 @@ select width: 256px margin-bottom: 8px + &.inline + width: 100% + option[disabled] color: #8c8c8c diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 0ff73215..59a2b42c 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -5,7 +5,7 @@ const defaultView = 'home'; const viewTitles = { filter: 'filter-cards', multiselection: 'multi-selection', - customFields: 'configure-custom-fields', + customFields: 'custom-fields', archives: 'archives', }; diff --git a/client/components/sidebar/sidebarCustomFields.jade b/client/components/sidebar/sidebarCustomFields.jade index e17bb75d..def083e9 100644 --- a/client/components/sidebar/sidebarCustomFields.jade +++ b/client/components/sidebar/sidebarCustomFields.jade @@ -19,31 +19,34 @@ template(name="createCustomFieldPopup") label | {{_ 'name'}} unless _id - input.js-field-name(type="text" name="field-name" autofocus) + input.js-field-name(type="text" autofocus) else - input.js-field-name(type="text" name="field-name" value=name) + input.js-field-name(type="text" value=name) label | {{_ 'type'}} - select.js-field-type(name="field-type" disabled="{{#if _id}}disabled{{/if}}") + select.js-field-type(disabled="{{#if _id}}disabled{{/if}}") each types if selected - option(value=type selected="selected") {{name}} + option(value=value selected="selected") {{name}} else - option(value=type) {{name}} + option(value=value) {{name}} + div.js-field-settings.js-field-settings-dropdown(class="{{#if isTypeNotSelected 'dropdown'}}hide{{/if}}") + label + | {{_ 'custom-field-dropdown-options'}} + each dropdownItems.get + input.js-dropdown-item(type="text" value=name placeholder="") + input.js-dropdown-item.last(type="text" value="" placeholder="{{_ 'custom-field-dropdown-options-placeholder'}}") a.flex.js-field-show-on-card .materialCheckBox(class="{{#if showOnCard}}is-checked{{/if}}") span {{_ 'show-field-on-card'}} - button.primary.wide.left(type="submit") + button.primary.wide.left(type="button") | {{_ 'save'}} if _id - button.negate.wide.right.js-delete-custom-field + button.negate.wide.right.js-delete-custom-field(type="button") | {{_ 'delete'}} -template(name="editCustomFieldPopup") - | {{> createCustomFieldPopup}} - template(name="deleteCustomFieldPopup") p {{_ "custom-field-delete-pop"}} button.js-confirm.negate.full(type="submit") {{_ 'delete'}} \ No newline at end of file diff --git a/client/components/sidebar/sidebarCustomFields.js b/client/components/sidebar/sidebarCustomFields.js index 6ddd466e..139b8a42 100644 --- a/client/components/sidebar/sidebarCustomFields.js +++ b/client/components/sidebar/sidebarCustomFields.js @@ -15,50 +15,111 @@ BlazeComponent.extendComponent({ }).register('customFieldsSidebar'); -Template.createCustomFieldPopup.helpers({ +const CreateCustomFieldPopup = BlazeComponent.extendComponent({ + + _types: ['text', 'number', 'checkbox', 'date', 'dropdown'], + + onCreated() { + this.type = new ReactiveVar((this.data().type) ? this.data().type : this._types[0]); + this.dropdownItems = new ReactiveVar((this.data().settings && this.data().settings.dropdownItems) ? this.data().settings.dropdownItems : []); + }, + types() { - var currentType = this.type; - return ['text', 'number', 'checkbox', 'date', 'dropdown']. - map(type => {return { - type: type, - name: TAPi18n.__('custom-field-' + type), - selected: type == currentType, - }}); + const currentType = this.data().type; + return this._types. + map(type => {return { + value: type, + name: TAPi18n.__('custom-field-' + type), + selected: type == currentType, + }}); + }, + + isTypeNotSelected(type) { + return this.type.get() !== type; + }, + + getDropdownItems() { + var items = this.dropdownItems.get(); + Array.from(this.findAll('.js-field-settings-dropdown input')).forEach((el, index) => { + //console.log('each item!', index, el.value); + if (!items[index]) items[index] = { + _id: Random.id(6), + }; + items[index].name = el.value.trim(); + }); + return items; }, -}); -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(); + getSettings() { + let settings = {}; + switch (this.type.get()) { + case 'dropdown': + let dropdownItems = this.getDropdownItems().filter(item => !!item.name.trim()); + settings.dropdownItems = dropdownItems; + break; } - $target.find('.materialCheckBox').toggleClass('is-checked'); - $target.toggleClass('is-checked'); + return settings; }, - '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); + events() { + return [{ + 'change .js-field-type'(evt) { + const value = evt.target.value; + this.type.set(value); + }, + 'keydown .js-dropdown-item.last'(evt) { + if (evt.target.value.trim() && evt.keyCode === 13) { + let items = this.getDropdownItems(); + this.dropdownItems.set(items); + evt.target.value = ''; + } + }, + 'click .js-field-show-on-card'(evt) { + let $target = $(evt.target); + if(!$target.hasClass('js-field-show-on-card')){ + $target = $target.parent(); + } + $target.find('.materialCheckBox').toggleClass('is-checked'); + $target.toggleClass('is-checked'); + }, + 'click .primary'(evt) { + evt.preventDefault(); + + const data = { + boardId: Session.get('currentBoard'), + name: this.find('.js-field-name').value.trim(), + type: this.type.get(), + settings: this.getSettings(), + showOnCard: this.find('.js-field-show-on-card.is-checked') != null + } - CustomFields.insert({ - boardId: Session.get('currentBoard'), - name: name, - type: type, - showOnCard: showOnCard - }); + // insert or update + if (!this.data()._id) { + CustomFields.insert(data); + } else { + CustomFields.update(this.data()._id, {$set: data}); + } - Popup.back(); + Popup.back(); + }, + 'click .js-delete-custom-field': Popup.afterConfirm('deleteCustomField', function() { + const customFieldId = this._id; + CustomFields.remove(customFieldId); + Popup.close(); + }), + }]; }, - 'click .js-delete-custom-field': Popup.afterConfirm('deleteCustomField', function() { - const customFieldId = this._id; - CustomFields.remove(customFieldId); - Popup.close(); - }), + }); +CreateCustomFieldPopup.register('createCustomFieldPopup'); + +(class extends CreateCustomFieldPopup { + + template() { + return 'createCustomFieldPopup'; + } + +}).register('editCustomFieldPopup'); /*Template.deleteCustomFieldPopup.events({ 'submit'(evt) { diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 1039d3c6..a6893117 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -155,7 +155,6 @@ "create": "Create", "createBoardPopup-title": "Create Board", "chooseBoardSourcePopup-title": "Import board", - "configure-custom-fields": "Configure Custom Fields", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", "createCustomFieldPopup-title": "Create Field", @@ -164,8 +163,13 @@ "custom-field-checkbox": "Checkbox", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", "custom-field-number": "Number", "custom-field-text": "Text", + "custom-fields": "Custom Fields", "date": "Date", "decline": "Decline", "default-avatar": "Default avatar", diff --git a/models/customFields.js b/models/customFields.js index 5e76db35..8b0abef4 100644 --- a/models/customFields.js +++ b/models/customFields.js @@ -9,6 +9,24 @@ CustomFields.attachSchema(new SimpleSchema({ }, type: { type: String, + allowedValues: ['text', 'number', 'checkbox', 'date', 'dropdown'] + }, + settings: { + type: Object, + }, + 'settings.dropdownItems': { + type: [Object], + optional: true + }, + 'settings.dropdownItems.$': { + type: new SimpleSchema({ + _id: { + type: String, + }, + name: { + type: String, + }, + }) }, showOnCard: { type: Boolean, @@ -83,6 +101,7 @@ if (Meteor.isServer) { const id = CustomFields.direct.insert({ name: req.body.name, type: req.body.type, + settings: req.body.settings, showOnCard: req.body.showOnCard, boardId: paramBoardId, }); -- cgit v1.2.3-1-g7c22 From 8b16955cc27b29ae507be084adccc4fad61b05ef Mon Sep 17 00:00:00 2001 From: Pouyan Savoli Date: Sat, 14 Oct 2017 01:38:25 +0200 Subject: number + date fields --- client/components/cards/cardCustomFields.jade | 24 ++++++ client/components/cards/cardCustomFields.js | 103 ++++++++++++++++++++++++-- client/components/cards/cardDate.jade | 16 ---- client/components/cards/cardDate.js | 91 +---------------------- client/components/cards/cardDate.styl | 19 ----- client/components/forms/datepicker.jade | 15 ++++ client/components/forms/datepicker.styl | 17 +++++ client/lib/datepicker.js | 86 +++++++++++++++++++++ i18n/en.i18n.json | 1 + 9 files changed, 242 insertions(+), 130 deletions(-) create mode 100644 client/components/forms/datepicker.jade create mode 100644 client/components/forms/datepicker.styl create mode 100644 client/lib/datepicker.js diff --git a/client/components/cards/cardCustomFields.jade b/client/components/cards/cardCustomFields.jade index 9e9f7d84..65081e3b 100644 --- a/client/components/cards/cardCustomFields.jade +++ b/client/components/cards/cardCustomFields.jade @@ -31,6 +31,30 @@ template(name="cardCustomField-text") else | {{_ 'edit'}} +template(name="cardCustomField-number") + if canModifyCard + +inlinedForm(classNames="js-card-customfield-number") + input(type="number" value=data.value) + .edit-controls.clearfix + button.primary(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + else + a.js-open-inlined-form + if value + = value + else + | {{_ 'edit'}} + +template(name="cardCustomField-date") + if canModifyCard + a.js-edit-date(title="{{showTitle}}" class="{{classes}}") + if value + div.card-date + time(datetime="{{showISODate}}") + | {{showDate}} + else + | {{_ 'edit'}} + template(name="cardCustomField-dropdown") if canModifyCard +inlinedForm(classNames="js-card-customfield-dropdown") diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js index f9fa760c..e014de4a 100644 --- a/client/components/cards/cardCustomFields.js +++ b/client/components/cards/cardCustomFields.js @@ -20,6 +20,7 @@ Template.cardCustomFieldsPopup.events({ } }); +// cardCustomField const CardCustomField = BlazeComponent.extendComponent({ getTemplate() { @@ -28,6 +29,8 @@ const CardCustomField = BlazeComponent.extendComponent({ onCreated() { const self = this; + self.card = Cards.findOne(Session.get('currentCard')); + self.customFieldId = this.data()._id; }, canModifyCard() { @@ -36,28 +39,118 @@ const CardCustomField = BlazeComponent.extendComponent({ }); CardCustomField.register('cardCustomField'); +// cardCustomField-text (class extends CardCustomField { onCreated() { + super.onCreated(); } events() { return [{ 'submit .js-card-customfield-text'(evt) { evt.preventDefault(); - const card = Cards.findOne(Session.get('currentCard')); - const customFieldId = this.data()._id; const value = this.currentComponent().getValue(); - card.setCustomField(customFieldId,value); + this.card.setCustomField(this.customFieldId, value); }, }]; } }).register('cardCustomField-text'); +// cardCustomField-number (class extends CardCustomField { onCreated() { + super.onCreated(); + } + + events() { + return [{ + 'submit .js-card-customfield-number'(evt) { + evt.preventDefault(); + const value = parseInt(this.find('input').value); + this.card.setCustomField(this.customFieldId, value); + }, + }]; + } + +}).register('cardCustomField-number'); + +// cardCustomField-date +(class extends CardCustomField { + + onCreated() { + super.onCreated(); + const self = this; + self.date = ReactiveVar(); + self.now = ReactiveVar(moment()); + window.setInterval(() => { + self.now.set(moment()); + }, 60000); + + self.autorun(() => { + self.date.set(moment(self.data().value)); + }); + } + + showDate() { + // this will start working once mquandalle:moment + // is updated to at least moment.js 2.10.5 + // until then, the date is displayed in the "L" format + return this.date.get().calendar(null, { + sameElse: 'llll', + }); + } + + showISODate() { + return this.date.get().toISOString(); + } + + classes() { + if (this.date.get().isBefore(this.now.get(), 'minute') && + this.now.get().isBefore(this.data().value)) { + return 'current'; + } + return ''; + } + + showTitle() { + return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`; + } + + events() { + return [{ + 'click .js-edit-date': Popup.open('cardCustomField-date'), + }]; + } + +}).register('cardCustomField-date'); + +// cardCustomField-datePopup +(class extends DatePicker { + onCreated() { + super.onCreated(); + const self = this; + self.card = Cards.findOne(Session.get('currentCard')); + self.customFieldId = this.data()._id; + this.data().value && this.date.set(moment(this.data().value)); + } + + _storeDate(date) { + this.card.setCustomField(this.customFieldId, date); + } + + _deleteDate() { + this.card.setCustomField(this.customFieldId, ''); + } +}).register('cardCustomField-datePopup'); + +// cardCustomField-dropdown +(class extends CardCustomField { + + onCreated() { + super.onCreated(); this._items = this.data().definition.settings.dropdownItems; this.items = this._items.slice(0); this.items.unshift({ @@ -77,10 +170,8 @@ CardCustomField.register('cardCustomField'); return [{ 'submit .js-card-customfield-dropdown'(evt) { evt.preventDefault(); - const card = Cards.findOne(Session.get('currentCard')); - const customFieldId = this.data()._id; const value = this.find('select').value; - card.setCustomField(customFieldId,value); + this.card.setCustomField(this.customFieldId, value); }, }]; } diff --git a/client/components/cards/cardDate.jade b/client/components/cards/cardDate.jade index 525f27ed..2e447506 100644 --- a/client/components/cards/cardDate.jade +++ b/client/components/cards/cardDate.jade @@ -1,19 +1,3 @@ -template(name="editCardDate") - .edit-card-date - form.edit-date - .fields - .left - label(for="date") {{_ 'date'}} - input.js-date-field#date(type="text" name="date" value=showDate placeholder=dateFormat autofocus) - .right - label(for="time") {{_ 'time'}} - input.js-time-field#time(type="text" name="time" value=showTime placeholder=timeFormat) - .js-datepicker - if error.get - .warning {{_ error.get}} - button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} - button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} - template(name="dateBadge") if canModifyCard a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}") diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 3f69f384..09a6761b 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -1,91 +1,4 @@ // Edit start & due dates -const EditCardDate = BlazeComponent.extendComponent({ - template() { - return 'editCardDate'; - }, - - onCreated() { - this.error = new ReactiveVar(''); - this.card = this.data(); - this.date = new ReactiveVar(moment.invalid()); - }, - - onRendered() { - const $picker = this.$('.js-datepicker').datepicker({ - todayHighlight: true, - todayBtn: 'linked', - language: TAPi18n.getLanguage(), - }).on('changeDate', function(evt) { - this.find('#date').value = moment(evt.date).format('L'); - this.error.set(''); - this.find('#time').focus(); - }.bind(this)); - - if (this.date.get().isValid()) { - $picker.datepicker('update', this.date.get().toDate()); - } - }, - - showDate() { - if (this.date.get().isValid()) - return this.date.get().format('L'); - return ''; - }, - showTime() { - if (this.date.get().isValid()) - return this.date.get().format('LT'); - return ''; - }, - dateFormat() { - return moment.localeData().longDateFormat('L'); - }, - timeFormat() { - return moment.localeData().longDateFormat('LT'); - }, - - events() { - return [{ - 'keyup .js-date-field'() { - // parse for localized date format in strict mode - const dateMoment = moment(this.find('#date').value, 'L', true); - if (dateMoment.isValid()) { - this.error.set(''); - this.$('.js-datepicker').datepicker('update', dateMoment.toDate()); - } - }, - 'keyup .js-time-field'() { - // parse for localized time format in strict mode - const dateMoment = moment(this.find('#time').value, 'LT', true); - if (dateMoment.isValid()) { - this.error.set(''); - } - }, - 'submit .edit-date'(evt) { - evt.preventDefault(); - - // if no time was given, init with 12:00 - const time = evt.target.time.value || moment(new Date().setHours(12, 0, 0)).format('LT'); - - const dateString = `${evt.target.date.value} ${time}`; - const newDate = moment(dateString, 'L LT', true); - if (newDate.isValid()) { - this._storeDate(newDate.toDate()); - Popup.close(); - } - else { - this.error.set('invalid-date'); - evt.target.date.focus(); - } - }, - 'click .js-delete-date'(evt) { - evt.preventDefault(); - this._deleteDate(); - Popup.close(); - }, - }]; - }, -}); - Template.dateBadge.helpers({ canModifyCard() { return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); @@ -93,7 +6,7 @@ Template.dateBadge.helpers({ }); // editCardStartDatePopup -(class extends EditCardDate { +(class extends DatePicker { onCreated() { super.onCreated(); this.data().startAt && this.date.set(moment(this.data().startAt)); @@ -109,7 +22,7 @@ Template.dateBadge.helpers({ }).register('editCardStartDatePopup'); // editCardDueDatePopup -(class extends EditCardDate { +(class extends DatePicker { onCreated() { super.onCreated(); this.data().dueAt && this.date.set(moment(this.data().dueAt)); diff --git a/client/components/cards/cardDate.styl b/client/components/cards/cardDate.styl index 1631baa5..87a3ed25 100644 --- a/client/components/cards/cardDate.styl +++ b/client/components/cards/cardDate.styl @@ -1,22 +1,3 @@ -.edit-card-date - .fields - .left - width: 56% - .right - width: 38% - .datepicker - width: 100% - table - width: 100% - border: none - border-spacing: 0 - border-collapse: collapse - thead - background: none - td, th - box-sizing: border-box - - .card-date display: block border-radius: 4px diff --git a/client/components/forms/datepicker.jade b/client/components/forms/datepicker.jade new file mode 100644 index 00000000..96f63bc4 --- /dev/null +++ b/client/components/forms/datepicker.jade @@ -0,0 +1,15 @@ +template(name="datepicker") + .datepicker-container + form.edit-date + .fields + .left + label(for="date") {{_ 'date'}} + input.js-date-field#date(type="text" name="date" value=showDate placeholder=dateFormat autofocus) + .right + label(for="time") {{_ 'time'}} + input.js-time-field#time(type="text" name="time" value=showTime placeholder=timeFormat) + .js-datepicker + if error.get + .warning {{_ error.get}} + button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} + button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} \ No newline at end of file diff --git a/client/components/forms/datepicker.styl b/client/components/forms/datepicker.styl new file mode 100644 index 00000000..a2558094 --- /dev/null +++ b/client/components/forms/datepicker.styl @@ -0,0 +1,17 @@ +.datepicker-container + .fields + .left + width: 56% + .right + width: 38% + .datepicker + width: 100% + table + width: 100% + border: none + border-spacing: 0 + border-collapse: collapse + thead + background: none + td, th + box-sizing: border-box \ No newline at end of file diff --git a/client/lib/datepicker.js b/client/lib/datepicker.js new file mode 100644 index 00000000..aac061cf --- /dev/null +++ b/client/lib/datepicker.js @@ -0,0 +1,86 @@ +DatePicker = BlazeComponent.extendComponent({ + template() { + return 'datepicker'; + }, + + onCreated() { + this.error = new ReactiveVar(''); + this.card = this.data(); + this.date = new ReactiveVar(moment.invalid()); + }, + + onRendered() { + const $picker = this.$('.js-datepicker').datepicker({ + todayHighlight: true, + todayBtn: 'linked', + language: TAPi18n.getLanguage(), + }).on('changeDate', function(evt) { + this.find('#date').value = moment(evt.date).format('L'); + this.error.set(''); + this.find('#time').focus(); + }.bind(this)); + + if (this.date.get().isValid()) { + $picker.datepicker('update', this.date.get().toDate()); + } + }, + + showDate() { + if (this.date.get().isValid()) + return this.date.get().format('L'); + return ''; + }, + showTime() { + if (this.date.get().isValid()) + return this.date.get().format('LT'); + return ''; + }, + dateFormat() { + return moment.localeData().longDateFormat('L'); + }, + timeFormat() { + return moment.localeData().longDateFormat('LT'); + }, + + events() { + return [{ + 'keyup .js-date-field'() { + // parse for localized date format in strict mode + const dateMoment = moment(this.find('#date').value, 'L', true); + if (dateMoment.isValid()) { + this.error.set(''); + this.$('.js-datepicker').datepicker('update', dateMoment.toDate()); + } + }, + 'keyup .js-time-field'() { + // parse for localized time format in strict mode + const dateMoment = moment(this.find('#time').value, 'LT', true); + if (dateMoment.isValid()) { + this.error.set(''); + } + }, + 'submit .edit-date'(evt) { + evt.preventDefault(); + + // if no time was given, init with 12:00 + const time = evt.target.time.value || moment(new Date().setHours(12, 0, 0)).format('LT'); + + const dateString = `${evt.target.date.value} ${time}`; + const newDate = moment(dateString, 'L LT', true); + if (newDate.isValid()) { + this._storeDate(newDate.toDate()); + Popup.close(); + } + else { + this.error.set('invalid-date'); + evt.target.date.focus(); + } + }, + 'click .js-delete-date'(evt) { + evt.preventDefault(); + this._deleteDate(); + Popup.close(); + }, + }]; + }, +}); \ No newline at end of file diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index a6893117..e954e6eb 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -111,6 +111,7 @@ "card-start": "Start", "card-start-on": "Starts on", "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", -- cgit v1.2.3-1-g7c22