mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
resolving merge conflicts
This commit is contained in:
commit
9518a5c11e
30 changed files with 880 additions and 58 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,3 +15,4 @@ package-lock.json
|
||||||
**/prime
|
**/prime
|
||||||
**/*.snap
|
**/*.snap
|
||||||
snap/.snapcraft/
|
snap/.snapcraft/
|
||||||
|
.idea
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@ template(name="boardActivities")
|
||||||
if($eq activityType 'createCard')
|
if($eq activityType 'createCard')
|
||||||
| {{{_ 'activity-added' cardLink boardLabel}}}.
|
| {{{_ 'activity-added' cardLink boardLabel}}}.
|
||||||
|
|
||||||
|
if($eq activityType 'createCustomField')
|
||||||
|
| {{_ 'activity-customfield-created' customField}}.
|
||||||
|
|
||||||
if($eq activityType 'createList')
|
if($eq activityType 'createList')
|
||||||
| {{_ 'activity-added' list.title boardLabel}}.
|
| {{_ 'activity-added' list.title boardLabel}}.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,11 @@ BlazeComponent.extendComponent({
|
||||||
}, attachment.name()));
|
}, attachment.name()));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
customField() {
|
||||||
|
const customField = this.currentData().customField();
|
||||||
|
return customField.name;
|
||||||
|
},
|
||||||
|
|
||||||
events() {
|
events() {
|
||||||
return [{
|
return [{
|
||||||
// XXX We should use Popup.afterConfirmation here
|
// XXX We should use Popup.afterConfirmation here
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ template(name="boardHeaderBar")
|
||||||
|
|
||||||
template(name="boardMenuPopup")
|
template(name="boardMenuPopup")
|
||||||
ul.pop-over-list
|
ul.pop-over-list
|
||||||
|
li: a.js-custom-fields {{_ 'custom-fields'}}
|
||||||
li: a.js-open-archives {{_ 'archived-items'}}
|
li: a.js-open-archives {{_ 'archived-items'}}
|
||||||
if currentUser.isBoardAdmin
|
if currentUser.isBoardAdmin
|
||||||
li: a.js-change-board-color {{_ 'board-change-color'}}
|
li: a.js-change-board-color {{_ 'board-change-color'}}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
Template.boardMenuPopup.events({
|
Template.boardMenuPopup.events({
|
||||||
'click .js-rename-board': Popup.open('boardChangeTitle'),
|
'click .js-rename-board': Popup.open('boardChangeTitle'),
|
||||||
|
'click .js-custom-fields'() {
|
||||||
|
Sidebar.setView('customFields');
|
||||||
|
Popup.close();
|
||||||
|
},
|
||||||
'click .js-open-archives'() {
|
'click .js-open-archives'() {
|
||||||
Sidebar.setView('archives');
|
Sidebar.setView('archives');
|
||||||
Popup.close();
|
Popup.close();
|
||||||
|
|
|
||||||
76
client/components/cards/cardCustomFields.jade
Normal file
76
client/components/cards/cardCustomFields.jade
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
template(name="cardCustomFieldsPopup")
|
||||||
|
ul.pop-over-list
|
||||||
|
each board.customFields
|
||||||
|
li.item(class="")
|
||||||
|
a.name.js-select-field(href="#")
|
||||||
|
span.full-name
|
||||||
|
= name
|
||||||
|
if hasCustomField
|
||||||
|
i.fa.fa-check
|
||||||
|
hr
|
||||||
|
a.quiet-button.full.js-settings
|
||||||
|
i.fa.fa-cog
|
||||||
|
span {{_ 'settings'}}
|
||||||
|
|
||||||
|
template(name="cardCustomField")
|
||||||
|
+Template.dynamic(template=getTemplate)
|
||||||
|
|
||||||
|
template(name="cardCustomField-text")
|
||||||
|
if canModifyCard
|
||||||
|
+inlinedForm(classNames="js-card-customfield-text")
|
||||||
|
+editor(autofocus=true)
|
||||||
|
= 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
|
||||||
|
+viewer
|
||||||
|
= value
|
||||||
|
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")
|
||||||
|
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'}}
|
||||||
179
client/components/cards/cardCustomFields.js
Normal file
179
client/components/cards/cardCustomFields.js
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
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'));
|
||||||
|
const customFieldId = this._id;
|
||||||
|
card.toggleCustomField(customFieldId);
|
||||||
|
evt.preventDefault();
|
||||||
|
},
|
||||||
|
'click .js-settings'(evt) {
|
||||||
|
EscapeActions.executeUpTo('detailsPane');
|
||||||
|
Sidebar.setView('customFields');
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// cardCustomField
|
||||||
|
const CardCustomField = BlazeComponent.extendComponent({
|
||||||
|
|
||||||
|
getTemplate() {
|
||||||
|
return 'cardCustomField-' + this.data().definition.type;
|
||||||
|
},
|
||||||
|
|
||||||
|
onCreated() {
|
||||||
|
const self = this;
|
||||||
|
self.card = Cards.findOne(Session.get('currentCard'));
|
||||||
|
self.customFieldId = this.data()._id;
|
||||||
|
},
|
||||||
|
|
||||||
|
canModifyCard() {
|
||||||
|
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
CardCustomField.register('cardCustomField');
|
||||||
|
|
||||||
|
// cardCustomField-text
|
||||||
|
(class extends CardCustomField {
|
||||||
|
|
||||||
|
onCreated() {
|
||||||
|
super.onCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
events() {
|
||||||
|
return [{
|
||||||
|
'submit .js-card-customfield-text'(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
const value = this.currentComponent().getValue();
|
||||||
|
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({
|
||||||
|
_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 value = this.find('select').value;
|
||||||
|
this.card.setCustomField(this.customFieldId, value);
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
}).register('cardCustomField-dropdown');
|
||||||
|
|
@ -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")
|
template(name="dateBadge")
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}")
|
a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}")
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ Template.dateBadge.helpers({
|
||||||
|
|
||||||
|
|
||||||
// editCardStartDatePopup
|
// editCardStartDatePopup
|
||||||
(class extends EditCardDate {
|
(class extends DatePicker {
|
||||||
onCreated() {
|
onCreated() {
|
||||||
super.onCreated();
|
super.onCreated();
|
||||||
this.data().startAt && this.date.set(moment(this.data().startAt));
|
this.data().startAt && this.date.set(moment(this.data().startAt));
|
||||||
|
|
@ -133,7 +133,7 @@ Template.dateBadge.helpers({
|
||||||
}).register('editCardStartDatePopup');
|
}).register('editCardStartDatePopup');
|
||||||
|
|
||||||
// editCardDueDatePopup
|
// editCardDueDatePopup
|
||||||
(class extends EditCardDate {
|
(class extends DatePicker {
|
||||||
onCreated() {
|
onCreated() {
|
||||||
super.onCreated();
|
super.onCreated();
|
||||||
this.data().dueAt && this.date.set(moment(this.data().dueAt));
|
this.data().dueAt && this.date.set(moment(this.data().dueAt));
|
||||||
|
|
|
||||||
|
|
@ -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
|
.card-date
|
||||||
display: block
|
display: block
|
||||||
border-radius: 4px
|
border-radius: 4px
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,22 @@ template(name="cardDetails")
|
||||||
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
|
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
|
||||||
i.fa.fa-plus
|
i.fa.fa-plus
|
||||||
|
|
||||||
|
if startAt
|
||||||
|
.card-details-item.card-details-item-start
|
||||||
|
h3.card-details-item-title {{_ 'card-start'}}
|
||||||
|
+cardStartDate
|
||||||
|
|
||||||
|
if dueAt
|
||||||
|
.card-details-item.card-details-item-due
|
||||||
|
h3.card-details-item-title {{_ 'card-due'}}
|
||||||
|
+cardDueDate
|
||||||
|
|
||||||
|
each customFieldsWD
|
||||||
|
.card-details-item.card-details-item-customfield
|
||||||
|
h3.card-details-item-title
|
||||||
|
= definition.name
|
||||||
|
+cardCustomField
|
||||||
|
|
||||||
.card-details-items
|
.card-details-items
|
||||||
if spentTime
|
if spentTime
|
||||||
.card-details-item.card-details-item-spent
|
.card-details-item.card-details-item-spent
|
||||||
|
|
@ -144,6 +160,7 @@ template(name="cardDetailsActionsPopup")
|
||||||
li: a.js-labels {{_ 'card-edit-labels'}}
|
li: a.js-labels {{_ 'card-edit-labels'}}
|
||||||
li: a.js-attachments {{_ 'card-edit-attachments'}}
|
li: a.js-attachments {{_ 'card-edit-attachments'}}
|
||||||
li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
|
li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
|
||||||
|
li: a.js-custom-fields {{_ 'card-edit-custom-fields'}}
|
||||||
li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
|
li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
|
||||||
li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
|
li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
|
||||||
li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
|
li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@ Template.cardDetailsActionsPopup.events({
|
||||||
'click .js-labels': Popup.open('cardLabels'),
|
'click .js-labels': Popup.open('cardLabels'),
|
||||||
'click .js-attachments': Popup.open('cardAttachments'),
|
'click .js-attachments': Popup.open('cardAttachments'),
|
||||||
'click .js-received-date': Popup.open('editCardReceivedDate'),
|
'click .js-received-date': Popup.open('editCardReceivedDate'),
|
||||||
|
'click .js-custom-fields': Popup.open('cardCustomFields'),
|
||||||
'click .js-start-date': Popup.open('editCardStartDate'),
|
'click .js-start-date': Popup.open('editCardStartDate'),
|
||||||
'click .js-due-date': Popup.open('editCardDueDate'),
|
'click .js-due-date': Popup.open('editCardDueDate'),
|
||||||
'click .js-end-date': Popup.open('editCardEndDate'),
|
'click .js-end-date': Popup.open('editCardEndDate'),
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,11 @@
|
||||||
|
|
||||||
.card-details-items
|
.card-details-items
|
||||||
display: flex
|
display: flex
|
||||||
margin: 15px 0
|
flex-wrap: wrap
|
||||||
|
margin: 0 0 15px
|
||||||
|
|
||||||
.card-details-item
|
.card-details-item
|
||||||
margin-right: 0.5em
|
margin: 15px 0.5em 0 0
|
||||||
&:last-child
|
&:last-child
|
||||||
margin-right: 0
|
margin-right: 0
|
||||||
&.card-details-item-labels,
|
&.card-details-item-labels,
|
||||||
|
|
@ -83,6 +84,9 @@
|
||||||
&.card-details-item-end
|
&.card-details-item-end
|
||||||
width: 50%
|
width: 50%
|
||||||
flex-shrink: 1
|
flex-shrink: 1
|
||||||
|
&.card-details-item-customfield
|
||||||
|
max-width: 50%
|
||||||
|
flex-grow: 1
|
||||||
|
|
||||||
.card-details-item-title
|
.card-details-item-title
|
||||||
font-size: 16px
|
font-size: 16px
|
||||||
|
|
|
||||||
15
client/components/forms/datepicker.jade
Normal file
15
client/components/forms/datepicker.jade
Normal file
|
|
@ -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'}}
|
||||||
17
client/components/forms/datepicker.styl
Normal file
17
client/components/forms/datepicker.styl
Normal file
|
|
@ -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
|
||||||
|
|
@ -85,6 +85,9 @@ select
|
||||||
width: 256px
|
width: 256px
|
||||||
margin-bottom: 8px
|
margin-bottom: 8px
|
||||||
|
|
||||||
|
&.inline
|
||||||
|
width: 100%
|
||||||
|
|
||||||
option[disabled]
|
option[disabled]
|
||||||
color: #8c8c8c
|
color: #8c8c8c
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
const members = formComponent.members.get();
|
const members = formComponent.members.get();
|
||||||
const labelIds = formComponent.labels.get();
|
const labelIds = formComponent.labels.get();
|
||||||
|
const customFields = formComponent.customFields.get();
|
||||||
|
console.log("members", members);
|
||||||
|
console.log("labelIds", labelIds);
|
||||||
|
console.log("customFields", customFields);
|
||||||
|
|
||||||
const boardId = this.data().board()._id;
|
const boardId = this.data().board()._id;
|
||||||
let swimlaneId = '';
|
let swimlaneId = '';
|
||||||
|
|
@ -49,6 +53,7 @@ BlazeComponent.extendComponent({
|
||||||
title,
|
title,
|
||||||
members,
|
members,
|
||||||
labelIds,
|
labelIds,
|
||||||
|
customFields,
|
||||||
listId: this.data()._id,
|
listId: this.data()._id,
|
||||||
boardId: this.data().board()._id,
|
boardId: this.data().board()._id,
|
||||||
sort: sortIndex,
|
sort: sortIndex,
|
||||||
|
|
@ -146,11 +151,13 @@ BlazeComponent.extendComponent({
|
||||||
onCreated() {
|
onCreated() {
|
||||||
this.labels = new ReactiveVar([]);
|
this.labels = new ReactiveVar([]);
|
||||||
this.members = new ReactiveVar([]);
|
this.members = new ReactiveVar([]);
|
||||||
|
this.customFields = new ReactiveVar([]);
|
||||||
},
|
},
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.labels.set([]);
|
this.labels.set([]);
|
||||||
this.members.set([]);
|
this.members.set([]);
|
||||||
|
this.customFields.set([]);
|
||||||
},
|
},
|
||||||
|
|
||||||
getLabels() {
|
getLabels() {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ $popupWidth = 300px
|
||||||
textarea
|
textarea
|
||||||
height: 72px
|
height: 72px
|
||||||
|
|
||||||
|
form a span
|
||||||
|
padding: 0 0.5rem
|
||||||
|
|
||||||
.header
|
.header
|
||||||
height: 36px
|
height: 36px
|
||||||
position: relative
|
position: relative
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const viewTitles = {
|
||||||
filter: 'filter-cards',
|
filter: 'filter-cards',
|
||||||
search: 'search-cards',
|
search: 'search-cards',
|
||||||
multiselection: 'multi-selection',
|
multiselection: 'multi-selection',
|
||||||
|
customFields: 'custom-fields',
|
||||||
archives: 'archives',
|
archives: 'archives',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,28 +45,45 @@
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
|
|
||||||
li > a
|
li
|
||||||
display: flex
|
& > a
|
||||||
height: 30px
|
display: flex
|
||||||
margin: 0
|
height: 30px
|
||||||
padding: 4px
|
margin: 0
|
||||||
border-radius: 3px
|
padding: 4px
|
||||||
align-items: center
|
border-radius: 3px
|
||||||
|
align-items: center
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
&, i, .quiet
|
&, i, .quiet
|
||||||
color white
|
color white
|
||||||
|
|
||||||
.member, .card-label
|
.member, .card-label
|
||||||
margin-right: 7px
|
margin-right: 7px
|
||||||
margin-top: 5px
|
margin-top: 5px
|
||||||
|
|
||||||
.sidebar-list-item-description
|
.minicard-edit-button
|
||||||
flex: 1
|
float: right
|
||||||
overflow: ellipsis
|
padding: 8px
|
||||||
|
border-radius: 3px
|
||||||
|
|
||||||
.fa.fa-check
|
.sidebar-list-item-description
|
||||||
margin: 0 4px
|
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
|
.sidebar-btn
|
||||||
display: block
|
display: block
|
||||||
|
|
|
||||||
52
client/components/sidebar/sidebarCustomFields.jade
Normal file
52
client/components/sidebar/sidebarCustomFields.jade
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
template(name="customFieldsSidebar")
|
||||||
|
ul.sidebar-list
|
||||||
|
each customFields
|
||||||
|
li
|
||||||
|
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 {{_ 'createCustomField'}}
|
||||||
|
|
||||||
|
template(name="createCustomFieldPopup")
|
||||||
|
form
|
||||||
|
label
|
||||||
|
| {{_ 'name'}}
|
||||||
|
unless _id
|
||||||
|
input.js-field-name(type="text" autofocus)
|
||||||
|
else
|
||||||
|
input.js-field-name(type="text" value=name)
|
||||||
|
|
||||||
|
label
|
||||||
|
| {{_ 'type'}}
|
||||||
|
select.js-field-type(disabled="{{#if _id}}disabled{{/if}}")
|
||||||
|
each types
|
||||||
|
if selected
|
||||||
|
option(value=value selected="selected") {{name}}
|
||||||
|
else
|
||||||
|
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="button")
|
||||||
|
| {{_ 'save'}}
|
||||||
|
if _id
|
||||||
|
button.negate.wide.right.js-delete-custom-field(type="button")
|
||||||
|
| {{_ 'delete'}}
|
||||||
|
|
||||||
|
template(name="deleteCustomFieldPopup")
|
||||||
|
p {{_ "custom-field-delete-pop"}}
|
||||||
|
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
||||||
130
client/components/sidebar/sidebarCustomFields.js
Normal file
130
client/components/sidebar/sidebarCustomFields.js
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
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': Popup.open('editCustomField'),
|
||||||
|
}];
|
||||||
|
},
|
||||||
|
|
||||||
|
}).register('customFieldsSidebar');
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
getSettings() {
|
||||||
|
let settings = {};
|
||||||
|
switch (this.type.get()) {
|
||||||
|
case 'dropdown':
|
||||||
|
let dropdownItems = this.getDropdownItems().filter(item => !!item.name.trim());
|
||||||
|
settings.dropdownItems = dropdownItems;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return settings;
|
||||||
|
},
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert or update
|
||||||
|
if (!this.data()._id) {
|
||||||
|
CustomFields.insert(data);
|
||||||
|
} else {
|
||||||
|
CustomFields.update(this.data()._id, {$set: data});
|
||||||
|
}
|
||||||
|
|
||||||
|
Popup.back();
|
||||||
|
},
|
||||||
|
'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) {
|
||||||
|
const customFieldId = this._id;
|
||||||
|
CustomFields.remove(customFieldId);
|
||||||
|
Popup.close();
|
||||||
|
}
|
||||||
|
});*/
|
||||||
86
client/lib/datepicker.js
Normal file
86
client/lib/datepicker.js
Normal file
|
|
@ -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();
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
"act-addComment": "commented on __card__: __comment__",
|
"act-addComment": "commented on __card__: __comment__",
|
||||||
"act-createBoard": "created __board__",
|
"act-createBoard": "created __board__",
|
||||||
"act-createCard": "added __card__ to __list__",
|
"act-createCard": "added __card__ to __list__",
|
||||||
|
"act-createCustomField": "created custom field __customField__",
|
||||||
"act-createList": "added __list__ to __board__",
|
"act-createList": "added __list__ to __board__",
|
||||||
"act-addBoardMember": "added __member__ to __board__",
|
"act-addBoardMember": "added __member__ to __board__",
|
||||||
"act-archivedBoard": "__board__ moved to Recycle Bin",
|
"act-archivedBoard": "__board__ moved to Recycle Bin",
|
||||||
|
|
@ -30,6 +31,7 @@
|
||||||
"activity-archived": "%s moved to Recycle Bin",
|
"activity-archived": "%s moved to Recycle Bin",
|
||||||
"activity-attached": "attached %s to %s",
|
"activity-attached": "attached %s to %s",
|
||||||
"activity-created": "created %s",
|
"activity-created": "created %s",
|
||||||
|
"activity-customfield-created": "created custom field %s",
|
||||||
"activity-excluded": "excluded %s from %s",
|
"activity-excluded": "excluded %s from %s",
|
||||||
"activity-imported": "imported %s into %s from %s",
|
"activity-imported": "imported %s into %s from %s",
|
||||||
"activity-imported-board": "imported %s from %s",
|
"activity-imported-board": "imported %s from %s",
|
||||||
|
|
@ -111,6 +113,7 @@
|
||||||
"card-due-on": "Due on",
|
"card-due-on": "Due on",
|
||||||
"card-spent": "Spent Time",
|
"card-spent": "Spent Time",
|
||||||
"card-edit-attachments": "Edit attachments",
|
"card-edit-attachments": "Edit attachments",
|
||||||
|
"card-edit-custom-fields": "Edit custom fields",
|
||||||
"card-edit-labels": "Edit labels",
|
"card-edit-labels": "Edit labels",
|
||||||
"card-edit-members": "Edit members",
|
"card-edit-members": "Edit members",
|
||||||
"card-labels-title": "Change the labels for the card.",
|
"card-labels-title": "Change the labels for the card.",
|
||||||
|
|
@ -118,6 +121,8 @@
|
||||||
"card-start": "Start",
|
"card-start": "Start",
|
||||||
"card-start-on": "Starts on",
|
"card-start-on": "Starts on",
|
||||||
"cardAttachmentsPopup-title": "Attach From",
|
"cardAttachmentsPopup-title": "Attach From",
|
||||||
|
"cardCustomField-datePopup-title": "Change date",
|
||||||
|
"cardCustomFieldsPopup-title": "Edit custom fields",
|
||||||
"cardDeletePopup-title": "Delete Card?",
|
"cardDeletePopup-title": "Delete Card?",
|
||||||
"cardDetailsActionsPopup-title": "Card Actions",
|
"cardDetailsActionsPopup-title": "Card Actions",
|
||||||
"cardLabelsPopup-title": "Labels",
|
"cardLabelsPopup-title": "Labels",
|
||||||
|
|
@ -167,11 +172,25 @@
|
||||||
"createBoardPopup-title": "Create Board",
|
"createBoardPopup-title": "Create Board",
|
||||||
"chooseBoardSourcePopup-title": "Import board",
|
"chooseBoardSourcePopup-title": "Import board",
|
||||||
"createLabelPopup-title": "Create Label",
|
"createLabelPopup-title": "Create Label",
|
||||||
|
"createCustomField": "Create Field",
|
||||||
|
"createCustomFieldPopup-title": "Create Field",
|
||||||
"current": "current",
|
"current": "current",
|
||||||
|
"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-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",
|
"date": "Date",
|
||||||
"decline": "Decline",
|
"decline": "Decline",
|
||||||
"default-avatar": "Default avatar",
|
"default-avatar": "Default avatar",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
"deleteCustomFieldPopup-title": "Delete Custom Field?",
|
||||||
"deleteLabelPopup-title": "Delete Label?",
|
"deleteLabelPopup-title": "Delete Label?",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"disambiguateMultiLabelPopup-title": "Disambiguate Label Action",
|
"disambiguateMultiLabelPopup-title": "Disambiguate Label Action",
|
||||||
|
|
@ -186,7 +205,7 @@
|
||||||
"soft-wip-limit": "Soft WIP Limit",
|
"soft-wip-limit": "Soft WIP Limit",
|
||||||
"editCardStartDatePopup-title": "Change start date",
|
"editCardStartDatePopup-title": "Change start date",
|
||||||
"editCardDueDatePopup-title": "Change due date",
|
"editCardDueDatePopup-title": "Change due date",
|
||||||
"editCardSpentTimePopup-title": "Change spent time",
|
"editCustomFieldPopup-title": "Edit Field",
|
||||||
"editLabelPopup-title": "Change Label",
|
"editLabelPopup-title": "Change Label",
|
||||||
"editNotificationPopup-title": "Edit Notification",
|
"editNotificationPopup-title": "Edit Notification",
|
||||||
"editProfilePopup-title": "Edit Profile",
|
"editProfilePopup-title": "Edit Profile",
|
||||||
|
|
@ -366,6 +385,7 @@
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"tracking": "Tracking",
|
"tracking": "Tracking",
|
||||||
"tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
|
"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",
|
"unassign-member": "Unassign member",
|
||||||
"unsaved-description": "You have an unsaved description.",
|
"unsaved-description": "You have an unsaved description.",
|
||||||
"unwatch": "Unwatch",
|
"unwatch": "Unwatch",
|
||||||
|
|
@ -430,6 +450,7 @@
|
||||||
"hours": "hours",
|
"hours": "hours",
|
||||||
"minutes": "minutes",
|
"minutes": "minutes",
|
||||||
"seconds": "seconds",
|
"seconds": "seconds",
|
||||||
|
"show-field-on-card": "Show this field on card",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"accounts": "Accounts",
|
"accounts": "Accounts",
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ Activities.helpers({
|
||||||
checklistItem() {
|
checklistItem() {
|
||||||
return ChecklistItems.findOne(this.checklistItemId);
|
return ChecklistItems.findOne(this.checklistItemId);
|
||||||
},
|
},
|
||||||
|
customField() {
|
||||||
|
return CustomFields.findOne(this.customFieldId);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Activities.before.insert((userId, doc) => {
|
Activities.before.insert((userId, doc) => {
|
||||||
|
|
@ -60,6 +63,7 @@ if (Meteor.isServer) {
|
||||||
Activities._collection._ensureIndex({ boardId: 1, createdAt: -1 });
|
Activities._collection._ensureIndex({ boardId: 1, createdAt: -1 });
|
||||||
Activities._collection._ensureIndex({ commentId: 1 }, { partialFilterExpression: { commentId: { $exists: true } } });
|
Activities._collection._ensureIndex({ commentId: 1 }, { partialFilterExpression: { commentId: { $exists: true } } });
|
||||||
Activities._collection._ensureIndex({ attachmentId: 1 }, { partialFilterExpression: { attachmentId: { $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) => {
|
Activities.after.insert((userId, doc) => {
|
||||||
|
|
@ -127,6 +131,10 @@ if (Meteor.isServer) {
|
||||||
const checklistItem = activity.checklistItem();
|
const checklistItem = activity.checklistItem();
|
||||||
params.checklistItem = checklistItem.title;
|
params.checklistItem = checklistItem.title;
|
||||||
}
|
}
|
||||||
|
if (activity.customFieldId) {
|
||||||
|
const customField = activity.customField();
|
||||||
|
params.customField = customField.name;
|
||||||
|
}
|
||||||
if (board) {
|
if (board) {
|
||||||
const watchingUsers = _.pluck(_.where(board.watchers, {level: 'watching'}), 'userId');
|
const watchingUsers = _.pluck(_.where(board.watchers, {level: 'watching'}), 'userId');
|
||||||
const trackingUsers = _.pluck(_.where(board.watchers, {level: 'tracking'}), 'userId');
|
const trackingUsers = _.pluck(_.where(board.watchers, {level: 'tracking'}), 'userId');
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,10 @@ Boards.helpers({
|
||||||
return `board-color-${this.color}`;
|
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 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...
|
// XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove...
|
||||||
pushLabel(name, color) {
|
pushLabel(name, color) {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,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: {
|
dateLastActivity: {
|
||||||
type: Date,
|
type: Date,
|
||||||
autoValue() {
|
autoValue() {
|
||||||
|
|
@ -192,6 +207,31 @@ Cards.helpers({
|
||||||
return this.checklistItemCount() !== 0;
|
return this.checklistItemCount() !== 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
customFieldIndex(customFieldId) {
|
||||||
|
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() {
|
absoluteUrl() {
|
||||||
const board = this.board();
|
const board = this.board();
|
||||||
return FlowRouter.url('card', {
|
return FlowRouter.url('card', {
|
||||||
|
|
@ -271,6 +311,32 @@ Cards.mutations({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
assignCustomField(customFieldId) {
|
||||||
|
return {$addToSet: {customFields: {_id: customFieldId, value: null}}};
|
||||||
|
},
|
||||||
|
|
||||||
|
unassignCustomField(customFieldId) {
|
||||||
|
return {$pull: {customFields: {_id: customFieldId}}};
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleCustomField(customFieldId) {
|
||||||
|
if (this.customFields && this.customFieldIndex(customFieldId) > -1) {
|
||||||
|
return this.unassignCustomField(customFieldId);
|
||||||
|
} else {
|
||||||
|
return this.assignCustomField(customFieldId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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) {
|
setCover(coverId) {
|
||||||
return {$set: {coverId}};
|
return {$set: {coverId}};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
132
models/customFields.js
Normal file
132
models/customFields.js
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
CustomFields = new Mongo.Collection('customFields');
|
||||||
|
|
||||||
|
CustomFields.attachSchema(new SimpleSchema({
|
||||||
|
boardId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
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: ['userId', '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) {
|
||||||
|
/*Meteor.startup(() => {
|
||||||
|
CustomFields._collection._ensureIndex({ boardId: 1});
|
||||||
|
});*/
|
||||||
|
|
||||||
|
CustomFields.after.insert((userId, doc) => {
|
||||||
|
customFieldCreation(userId, doc);
|
||||||
|
});
|
||||||
|
|
||||||
|
CustomFields.after.remove((userId, doc) => {
|
||||||
|
Activities.remove({
|
||||||
|
customFieldId: doc._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/custom-fields/: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,
|
||||||
|
settings: req.body.settings,
|
||||||
|
showOnCard: req.body.showOnCard,
|
||||||
|
boardId: paramBoardId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const customField = CustomFields.findOne({_id: id, 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -75,6 +75,7 @@ Meteor.publishRelations('board', function(boardId) {
|
||||||
this.cursor(Lists.find({ boardId }));
|
this.cursor(Lists.find({ boardId }));
|
||||||
this.cursor(Swimlanes.find({ boardId }));
|
this.cursor(Swimlanes.find({ boardId }));
|
||||||
this.cursor(Integrations.find({ boardId }));
|
this.cursor(Integrations.find({ boardId }));
|
||||||
|
this.cursor(CustomFields.find({ boardId }, { sort: { name: 1 } }));
|
||||||
|
|
||||||
// Cards and cards comments
|
// Cards and cards comments
|
||||||
// XXX Originally we were publishing the card documents as a child of the
|
// XXX Originally we were publishing the card documents as a child of the
|
||||||
|
|
|
||||||
3
server/publications/customFields.js
Normal file
3
server/publications/customFields.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
Meteor.publish('customFields', function() {
|
||||||
|
return CustomFields.find();
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue