Ghost/ghost/admin/app/components/gh-member-label-input.js
Rishabh Garg d115204d9f Added labels for Members (#1477)
no issue

refs TryGhost/Ghost#11538

* Added members label handling v1

* Added members label dropdown in list screen

* Updated selected labels and member list UI refactor

* Added v1 label add/delete modal

* Added add label modal v1

* Fixed disabled check for new label save

* First stab at member filters layout

* Updated member detail screen design

* Member detail refinements

* Added basic editing for member labels

* Fixed label deletion from members list

* Updated filtered list header

* Refinements for dropdown and empty filter state

* Refined label modal

* Updated member labels fetch logic

* Added custom dropdown component for labels

* Refined style for dropdown

* Refined dropdown button

* Restructure and refine members dropdown styles

* Added selected indication to dropdown

* Fixed dropdown footer style

* Removed member label placeholder

* Removed add label from member list actions

* New label refinements

* Added confirmation modal for label delete

* Added duplicate validation check for labels

* Updated validation check with slug

* Updated copy

* Updated actions dropdown copy

* Fixed visual glitch of dropdown on FF

* Hide scrollbar for labels dropdown

Co-authored-by: Peter Zimon <peter.zimon@gmail.com>
2020-02-14 15:04:01 +05:30

96 lines
3.0 KiB
JavaScript

import Component from '@ember/component';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';
import {sort} from '@ember/object/computed';
export default Component.extend({
store: service(),
// public attrs
member: null,
labelName: '',
// internal attrs
_availableLabels: null,
selectedLabels: computed.reads('member.labels'),
availableLabels: sort('_availableLabels.[]', function (labelA, labelB) {
// ignorePunctuation means the # in label names is ignored
return labelA.name.localeCompare(labelB.name, undefined, {ignorePunctuation: true});
}),
availableLabelNames: computed('availableLabels.@each.name', function () {
return this.availableLabels.map(label => label.name.toLowerCase());
}),
init() {
this._super(...arguments);
// perform a background query to fetch all users and set `availableLabels`
// to a live-query that will be immediately populated with what's in the
// store and be updated when the above query returns
this.store.query('label', {limit: 'all'});
this.set('_availableLabels', this.store.peekAll('label'));
},
actions: {
matchLabels(labelName, term) {
return labelName.toLowerCase() === term.trim().toLowerCase();
},
hideCreateOptionOnMatchingLabel(term) {
return !this.availableLabelNames.includes(term.toLowerCase());
},
updateLabels(newLabels) {
let currentLabels = this.get('member.labels');
// destroy new+unsaved labels that are no longer selected
currentLabels.forEach(function (label) {
if (!newLabels.includes(label) && label.get('isNew')) {
label.destroyRecord();
}
});
// update labels
return this.set('member.labels', newLabels);
},
createLabel(labelName) {
let currentLabels = this.get('member.labels');
let currentLabelNames = currentLabels.map(label => label.get('name').toLowerCase());
let labelToAdd;
labelName = labelName.trim();
// abort if label is already selected
if (currentLabelNames.includes(labelName.toLowerCase())) {
return;
}
// find existing label if there is one
labelToAdd = this._findLabelByName(labelName);
// create new label if no match
if (!labelToAdd) {
labelToAdd = this.store.createRecord('label', {
name: labelName
});
}
// push label onto member relationship
return currentLabels.pushObject(labelToAdd);
}
},
// methods
_findLabelByName(name) {
let withMatchingName = function (label) {
return label.name.toLowerCase() === name.toLowerCase();
};
return this.availableLabels.find(withMatchingName);
}
});