mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-18 02:21:47 -05:00
Refactored staff user modals
refs https://github.com/TryGhost/Team/issues/1734 refs https://github.com/TryGhost/Team/issues/559 refs https://github.com/TryGhost/Ghost/issues/14101 - switches to newer modal patterns ready for later Ember upgrades - cleaned up the `upload-image` modal which had multiple areas of code that were no longer being used - disabled `no-duplicate-landmark-elements` template lint rule as it's buggy and mostly gives false positives
This commit is contained in:
parent
74d66ca9be
commit
85cce39af7
36 changed files with 566 additions and 662 deletions
ghost/admin
.lint-todo.template-lintrc.js
app
components
gh-image-uploader.jsmodal-delete-user.hbsmodal-delete-user.jsmodal-import-members.hbsmodal-regenerate-token.hbsmodal-regenerate-token.jsmodal-select-user-role.hbsmodal-select-user-role.jsmodal-suspend-user.hbsmodal-suspend-user.jsmodal-transfer-owner.hbsmodal-transfer-owner.jsmodal-unsuspend-user.hbsmodal-unsuspend-user.jsmodal-upgrade-unsuspend-user-host-limit.hbsmodal-upgrade-unsuspend-user-host-limit.jsmodal-upload-image.hbsmodal-upload-image.js
settings/staff/modals
controllers/settings/staff
templates/settings/staff
|
@ -858,3 +858,32 @@ remove|ember-template-lint|no-unused-block-params|1|0|1|0|6640f2d788e2ebf173ee6a
|
|||
add|ember-template-lint|no-duplicate-attributes|9|4|9|4|1bd74ed221db1070a4ef257ccb712285f6e4ebc6|1663977600000|1674349200000|1679533200000|app/components/gh-members-segment-select.hbs
|
||||
add|ember-template-lint|table-groups|29|12|29|12|fd6c7d9c26f38dac21ab2603a31d20617717ab33|1663977600000|1674349200000|1679533200000|app/templates/offers.hbs
|
||||
add|ember-template-lint|no-unused-block-params|1|0|1|0|1709beda164cdda6c0196211f71d73a81b9251dd|1663977600000|1674349200000|1679533200000|lib/koenig-editor/addon/components/koenig-card-email-cta.hbs
|
||||
remove|ember-template-lint|no-action|47|93|47|93|a927cb3d62f088a149f64288c634921673563e7d|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|83|29|83|29|c81a8b61bc2a6a24c5103fd71843b71c96a7e413|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|84|27|84|27|a927cb3d62f088a149f64288c634921673563e7d|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|53|99|53|99|7628e64f5e59b4de2f529e029596f5570a4827ae|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|91|29|91|29|509c2ee1c3b1c57ee10b62649404561d5817de7f|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|92|27|92|27|7628e64f5e59b4de2f529e029596f5570a4827ae|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|60|103|60|103|a98190ea4757d865b31ad00184c6cc12a89c1009|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|102|31|102|31|a98190ea4757d865b31ad00184c6cc12a89c1009|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|107|33|107|33|34da22516e03547d6f322fe271a3251add8f3ec9|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|108|31|108|31|a98190ea4757d865b31ad00184c6cc12a89c1009|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|34|54|34|54|5af61080eb82340db68e59c287a1e9273f805b1e|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|39|49|39|49|7f6812a5fb3d34d95b4e27ce4b9691b3065f7b8c|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|40|47|40|47|5af61080eb82340db68e59c287a1e9273f805b1e|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|473|62|473|62|4150a56e60c88b9d8203a48c28069896522205ed|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|493|49|493|49|60dfff263d99797e3bcdf455c9c6ccf8ae539f1e|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|494|47|494|47|a1a5fab148882be2de9da5aca1d7d7e39e65a326|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-duplicate-landmark-elements|459|16|459|16|c7e339eb9f5d83115a7fda2636d1487ba11b133d|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-duplicate-landmark-elements|19|4|19|4|75d318fd9d711332a80f28848acc874ec14b0f4f|1662681600000|1673053200000|1678237200000|app/components/modal-members-label-form.hbs
|
||||
remove|ember-template-lint|no-duplicate-landmark-elements|16|16|16|16|1661d2edb187b634c8187e5ecb0db15a4c7262fc|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs
|
||||
remove|ember-template-lint|no-duplicate-landmark-elements|46|16|46|16|f2740bc03b393e8708035d4952a2ab630472bd22|1662681600000|1673053200000|1678237200000|app/templates/settings/navigation.hbs
|
||||
remove|ember-template-lint|no-duplicate-landmark-elements|398|16|398|16|f8a398a428b26623555526f6e625aeca79d8dc72|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|210|75|210|75|e0b0429cdf24580adc31da56392cc9814a42b2bb|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|218|45|218|45|4a66eafd7ad8fea066c11d298665e40bb02e3fc1|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|219|43|219|43|923e950a7705bc29217afe2e0a81dbf6012777f1|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-invalid-interactive|210|62|210|62|19a5403007099acc29f4f7bbbb8fd008405002eb|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|125|88|125|88|ea7c6e414cc202b6e4049eae4f60eb0829bd3ff0|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|129|35|129|35|ea7c6e414cc202b6e4049eae4f60eb0829bd3ff0|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|136|42|136|42|7a2a7a1b2159d811b310a9b89817108ddff9df88|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
remove|ember-template-lint|no-action|140|35|140|35|7a2a7a1b2159d811b310a9b89817108ddff9df88|1662681600000|1673053200000|1678237200000|app/templates/settings/staff/user.hbs
|
||||
|
|
|
@ -4,6 +4,7 @@ module.exports = {
|
|||
rules: {
|
||||
'no-forbidden-elements': ['meta', 'html', 'script'],
|
||||
'no-implicit-this': {allow: ['noop', 'now', 'site-icon-style', 'accent-color-background']},
|
||||
'no-inline-styles': false
|
||||
'no-inline-styles': false,
|
||||
'no-duplicate-landmark-elements': false
|
||||
}
|
||||
};
|
||||
|
|
|
@ -248,10 +248,11 @@ export default Component.extend({
|
|||
message = `The image type you uploaded is not supported. Please use ${validExtensions}`;
|
||||
} else if (isRequestEntityTooLargeError(error)) {
|
||||
message = 'The image you uploaded was larger than the maximum file size your server allows.';
|
||||
} else if (error.payload.errors && !isBlank(error.payload.errors[0].message)) {
|
||||
} else if (!isBlank(error.payload?.errors[0]?.message)) {
|
||||
message = error.payload.errors[0].message;
|
||||
} else {
|
||||
message = 'Something went wrong :(';
|
||||
console.error(error); // eslint-disable-line
|
||||
}
|
||||
|
||||
this.set('failureMessage', message);
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<header class="modal-header" data-test-modal="delete-user">
|
||||
<h1>Are you sure you want to delete this user?</h1>
|
||||
</header>
|
||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||
|
||||
<div class="modal-body">
|
||||
{{#if this.user.count.posts}}
|
||||
<p>
|
||||
<strong>{{this.user.name}}</strong> will be permanently deleted and their <strong data-test-text="user-post-count">{{gh-pluralize this.user.count.posts 'post'}}</strong> will be automatically assigned to <strong>{{this.ownerUser.name}}</strong>.
|
||||
</p>
|
||||
|
||||
<p class="gh-transfer-tag">
|
||||
To make these easy to find in the future, each post will be given an internal tag of <strong>#{{this.user.slug}}</strong>
|
||||
</p>
|
||||
{{else}}
|
||||
<p>
|
||||
<strong>{{this.user.name}}</strong> will be permanently deleted.
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" data-test-button="cancel-delete-user" type="button" {{action "closeModal"}}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<GhTaskButton @buttonText="Delete user"
|
||||
@successText="Deleted"
|
||||
@task={{this.deleteUser}}
|
||||
@class="gh-btn gh-btn-red gh-btn-icon"
|
||||
data-test-button="confirm-delete-user" />
|
||||
</div>
|
|
@ -1,31 +0,0 @@
|
|||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import {alias} from '@ember/object/computed';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
store: service(),
|
||||
|
||||
// Allowed actions
|
||||
confirm: () => {},
|
||||
|
||||
user: alias('model'),
|
||||
|
||||
actions: {
|
||||
confirm() {
|
||||
this.deleteUser.perform();
|
||||
}
|
||||
},
|
||||
|
||||
get ownerUser() {
|
||||
return this.store.peekAll('user').findBy('isOwnerOnly', true);
|
||||
},
|
||||
|
||||
deleteUser: task(function* () {
|
||||
try {
|
||||
yield this.confirm();
|
||||
} finally {
|
||||
this.send('closeModal');
|
||||
}
|
||||
}).drop()
|
||||
});
|
|
@ -1,5 +1,4 @@
|
|||
<div class="gh-member-import-wrapper {{if (or (eq this.state 'MAPPING') (eq this.state 'UPLOADING')) "wide"}}">
|
||||
{{!-- template-lint-disable no-duplicate-landmark-elements --}}
|
||||
{{#if (eq this.state 'INIT')}}
|
||||
<header class="modal-header" data-test-modal="import-members">
|
||||
<h1>Import members</h1>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<header class="modal-header">
|
||||
<h1>Regenerate your Staff Access Token</h1>
|
||||
</header>
|
||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
You can regenerate your Staff Access Token any time, but any scripts or applications using it will need to be updated.
|
||||
</p>
|
||||
{{#if this.errorMessage}}
|
||||
<p class='red'> {{this.errorMessage}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" autofocus type="button" {{action "closeModal"}}><span>Cancel</span></button>
|
||||
<button class="gh-btn gh-btn-icon gh-btn-red" type="button" {{action "confirm"}}>
|
||||
<span>Regenerate your Staff Access Token</span>
|
||||
</button>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
actions: {
|
||||
confirm() {
|
||||
this.confirm();
|
||||
this.send('closeModal');
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,16 +0,0 @@
|
|||
<header class="modal-header">
|
||||
<h1>Change user role</h1>
|
||||
</header>
|
||||
<a class="close" href="" role="button" title="Close" {{on "click" this.close}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||
|
||||
<div class="modal-body" {{did-insert this.setRoleFromModel}}>
|
||||
<GhRoleSelection
|
||||
@selected={{this.role}}
|
||||
@setRole={{fn (mut this.role)}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{on "click" this.close}}><span>Cancel</span></button>
|
||||
<button class="gh-btn gh-btn-black" type="button" {{on "click" this.confirmAction}}><span>Change role</span></button>
|
||||
</div>
|
|
@ -1,39 +0,0 @@
|
|||
import ModalBase from 'ghost-admin/components/modal-base';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {action} from '@ember/object';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
// TODO: update modals to work fully with Glimmer components
|
||||
@classic
|
||||
export default class ModalPostPreviewComponent extends ModalBase {
|
||||
@tracked role;
|
||||
|
||||
// TODO: rename to confirm() when modals have full Glimmer support
|
||||
@action
|
||||
confirmAction() {
|
||||
this.confirm(this.role);
|
||||
this.close();
|
||||
}
|
||||
|
||||
@action
|
||||
close(event) {
|
||||
event?.preventDefault?.();
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
@action
|
||||
setRoleFromModel() {
|
||||
this.role = this.model;
|
||||
}
|
||||
|
||||
actions = {
|
||||
confirm() {
|
||||
this.confirmAction(...arguments);
|
||||
},
|
||||
|
||||
// needed because ModalBase uses .send() for keyboard events
|
||||
closeModal() {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<header class="modal-header">
|
||||
<h1>Are you sure you want to suspend this user?</h1>
|
||||
</header>
|
||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||
|
||||
<div class="modal-body">
|
||||
<strong>WARNING:</strong> This user will no longer be able to log in but their posts will be kept.
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{action "closeModal"}}><span>Cancel</span></button>
|
||||
<GhTaskButton @buttonText="Suspend" @successText="Suspended" @task={{this.suspendUser}} @class="gh-btn gh-btn-red gh-btn-icon" data-test-modal-confirm="true" />
|
||||
</div>
|
|
@ -1,24 +0,0 @@
|
|||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import {alias} from '@ember/object/computed';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
// Allowed actions
|
||||
confirm: () => {},
|
||||
|
||||
user: alias('model'),
|
||||
|
||||
actions: {
|
||||
confirm() {
|
||||
return this.suspendUser.perform();
|
||||
}
|
||||
},
|
||||
|
||||
suspendUser: task(function* () {
|
||||
try {
|
||||
yield this.confirm();
|
||||
} finally {
|
||||
this.send('closeModal');
|
||||
}
|
||||
}).drop()
|
||||
});
|
|
@ -1,16 +0,0 @@
|
|||
<header class="modal-header">
|
||||
<h1>Transfer Ownership</h1>
|
||||
</header>
|
||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Are you sure you want to transfer the ownership of this blog?
|
||||
You will not be able to undo this action.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{action "closeModal"}}><span>Cancel</span></button>
|
||||
<GhTaskButton @buttonText="Yep - I'm sure" @task={{this.transferOwnership}} @class="gh-btn gh-btn-red gh-btn-icon" />
|
||||
</div>
|
|
@ -1,23 +0,0 @@
|
|||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
user: null,
|
||||
|
||||
// Allowed actions
|
||||
confirm: () => {},
|
||||
|
||||
actions: {
|
||||
confirm() {
|
||||
this.transferOwnership.perform();
|
||||
}
|
||||
},
|
||||
|
||||
transferOwnership: task(function* () {
|
||||
try {
|
||||
yield this.confirm();
|
||||
} finally {
|
||||
this.send('closeModal');
|
||||
}
|
||||
}).drop()
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
<header class="modal-header">
|
||||
<h1>Are you sure you want to un-suspend this user?</h1>
|
||||
</header>
|
||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||
|
||||
<div class="modal-body">
|
||||
<strong>WARNING:</strong> This user will be able to log in again and will have the same permissions they had previously.
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{action "closeModal"}}><span>Cancel</span></button>
|
||||
<GhTaskButton @buttonText="Un-suspend" @successText="Suspended" @task={{this.unsuspendUser}} @class="gh-btn gh-btn-red gh-btn-icon" data-test-modal-confirm="true" />
|
||||
</div>
|
|
@ -1,24 +0,0 @@
|
|||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import {alias} from '@ember/object/computed';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
// Allowed actions
|
||||
confirm: () => {},
|
||||
|
||||
user: alias('model'),
|
||||
|
||||
actions: {
|
||||
confirm() {
|
||||
return this.unsuspendUser.perform();
|
||||
}
|
||||
},
|
||||
|
||||
unsuspendUser: task(function* () {
|
||||
try {
|
||||
yield this.confirm();
|
||||
} finally {
|
||||
this.send('closeModal');
|
||||
}
|
||||
}).drop()
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
<header class="modal-header" data-test-modal="upgrade-unsuspended-user-host-limit">
|
||||
<h1>Upgrade to un-suspend this user</h1>
|
||||
</header>
|
||||
<button class="close" title="Close" type="button" {{on "click" this.closeModal}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
{{html-safe this.model.message}} To give this user access to Ghost, upgrade to a different plan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" data-test-button="cancel-upgrade" type="button" {{on "click" this.closeModal}}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
|
||||
<button class="gh-btn gh-btn-green" data-test-button="upgrade-plan" type="button" {{on "click" (action "upgrade")}}>
|
||||
<span>Upgrade my plan</span>
|
||||
</button>
|
||||
</div>
|
|
@ -1,16 +0,0 @@
|
|||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
router: service(),
|
||||
|
||||
actions: {
|
||||
upgrade() {
|
||||
this.router.transitionTo('pro');
|
||||
},
|
||||
|
||||
confirm() {
|
||||
this.send('upgrade');
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
<div class="modal-body">
|
||||
{{#if this.url}}
|
||||
<div class="gh-image-uploader -with-image">
|
||||
<div><img src={{this.url}}></div>
|
||||
<a class="image-delete" title="Delete" {{action 'removeImage'}}>
|
||||
{{svg-jar "trash"}}
|
||||
<span class="hidden">Delete</span>
|
||||
</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<GhImageUploader
|
||||
@image={{this.newUrl}}
|
||||
@saveButton={{false}}
|
||||
@update={{action "fileUploaded"}}
|
||||
@uploadStarted={{action "isUploading"}}
|
||||
@uploadFinished={{action "isUploading"}}
|
||||
@accept={{this.model.accept}}
|
||||
@extensions={{this.model.extensions}}
|
||||
@uploadUrl={{this.model.uploadUrl}}
|
||||
@paramsHash={{this.model.paramsHas}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{action "closeModal"}}><span>Cancel</span></button>
|
||||
{{#if this._isUploading}}
|
||||
<button class="gh-btn gh-btn-black right gh-btn-icon disabled" type="button"><span>Save</span></button>
|
||||
{{else}}
|
||||
<GhTaskButton @task={{this.uploadImage}} @class="gh-btn gh-btn-black right gh-btn-icon" data-test-modal-accept-button={{true}} />
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,109 +0,0 @@
|
|||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import cajaSanitizers from 'ghost-admin/utils/caja-sanitizers';
|
||||
import {computed} from '@ember/object';
|
||||
import {isEmpty} from '@ember/utils';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
config: service(),
|
||||
notifications: service(),
|
||||
|
||||
model: null,
|
||||
|
||||
url: '',
|
||||
newUrl: '',
|
||||
_isUploading: false,
|
||||
|
||||
image: computed('model.{model,imageProperty}', {
|
||||
get() {
|
||||
let imageProperty = this.get('model.imageProperty');
|
||||
|
||||
return this.get(`model.model.${imageProperty}`);
|
||||
},
|
||||
|
||||
set(key, value) {
|
||||
let model = this.get('model.model');
|
||||
let imageProperty = this.get('model.imageProperty');
|
||||
|
||||
return model.set(imageProperty, value);
|
||||
}
|
||||
}),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
let image = this.image;
|
||||
this.set('url', image);
|
||||
this.set('newUrl', image);
|
||||
},
|
||||
|
||||
actions: {
|
||||
fileUploaded(url) {
|
||||
this.set('url', url);
|
||||
this.set('newUrl', url);
|
||||
},
|
||||
|
||||
removeImage() {
|
||||
this.set('url', '');
|
||||
this.set('newUrl', '');
|
||||
},
|
||||
|
||||
confirm() {
|
||||
this.uploadImage.perform();
|
||||
},
|
||||
|
||||
isUploading() {
|
||||
this.toggleProperty('_isUploading');
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: should validation be handled in the gh-image-uploader component?
|
||||
// pro - consistency everywhere, simplification here
|
||||
// con - difficult if the "save" is happening externally as it does here
|
||||
//
|
||||
// maybe it should be handled at the model level?
|
||||
// - automatically present everywhere
|
||||
// - file uploads should always result in valid urls so it should only
|
||||
// affect the url input form
|
||||
keyDown() {
|
||||
this._setErrorState(false);
|
||||
},
|
||||
|
||||
_setErrorState(state) {
|
||||
if (state) {
|
||||
this.element.querySelector('.url').classList.add('error');
|
||||
} else {
|
||||
this.element.querySelector('.url').classList.remove('error');
|
||||
}
|
||||
},
|
||||
|
||||
_validateUrl(url) {
|
||||
if (!isEmpty(url) && !cajaSanitizers.url(url)) {
|
||||
this._setErrorState(true);
|
||||
return {message: 'Image URI is not valid'};
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
// end validation
|
||||
|
||||
uploadImage: task(function* () {
|
||||
let model = this.get('model.model');
|
||||
let newUrl = this.newUrl;
|
||||
let result = this._validateUrl(newUrl);
|
||||
let notifications = this.notifications;
|
||||
|
||||
if (result === true) {
|
||||
this.set('image', newUrl);
|
||||
|
||||
try {
|
||||
yield model.save();
|
||||
} catch (e) {
|
||||
notifications.showAPIError(e, {key: 'image.upload'});
|
||||
} finally {
|
||||
this.send('closeModal');
|
||||
}
|
||||
}
|
||||
}).drop()
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
<div class="modal-content" data-test-modal="delete-user">
|
||||
<header class="modal-header">
|
||||
<h1>Are you sure you want to delete this user?</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
{{#if @data.user.count.posts}}
|
||||
<p>
|
||||
<strong>{{@data.user.name}}</strong> will be permanently deleted and their <strong data-test-text="user-post-count">{{gh-pluralize @data.user.count.posts 'post'}}</strong> will be automatically assigned to <strong>{{this.ownerUser.name}}</strong>.
|
||||
</p>
|
||||
|
||||
<p class="gh-transfer-tag">
|
||||
To make these easy to find in the future, each post will be given an internal tag of <strong>#{{@data.user.slug}}</strong>
|
||||
</p>
|
||||
{{else}}
|
||||
<p>
|
||||
<strong>{{@data.user.name}}</strong> will be permanently deleted.
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" data-test-button="cancel-delete-user" type="button" {{on "click" @close}}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<GhTaskButton
|
||||
@buttonText="Delete user"
|
||||
@successText="Deleted"
|
||||
@task={{this.deleteUserTask}}
|
||||
@class="gh-btn gh-btn-red gh-btn-icon"
|
||||
data-test-button="confirm-delete-user" />
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,31 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default class DeleteUserModal extends Component {
|
||||
@service notifications;
|
||||
@service router;
|
||||
@service store;
|
||||
|
||||
get ownerUser() {
|
||||
return this.store.peekAll('user').findBy('isOwnerOnly', true);
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*deleteUserTask() {
|
||||
try {
|
||||
const {user} = this.args.data;
|
||||
|
||||
yield user.destroyRecord();
|
||||
|
||||
this.notifications.closeAlerts('user.delete');
|
||||
this.store.unloadAll('post');
|
||||
this.router.transitionTo('settings.staff');
|
||||
} catch (error) {
|
||||
this.notifications.showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'});
|
||||
throw error;
|
||||
} finally {
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<div class="modal-content" data-test-modal="regenerate-staff-token">
|
||||
<header class="modal-header">
|
||||
<h1>Regenerate your Staff Access Token</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
You can regenerate your Staff Access Token any time, but any scripts or applications using it will need to be updated.
|
||||
</p>
|
||||
{{#if this.errorMessage}}
|
||||
<p class='red'> {{this.errorMessage}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{on "click" @close}}><span>Cancel</span></button>
|
||||
<button class="gh-btn gh-btn-icon gh-btn-red" type="button" {{on "click" this.regenerateStaffToken}}>
|
||||
<span>Regenerate your Staff Access Token</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class RegenerateStaffTokenModal extends Component {
|
||||
@service ajax;
|
||||
@service ghostPaths;
|
||||
@service notifications;
|
||||
|
||||
@action
|
||||
async regenerateStaffToken() {
|
||||
const url = this.ghostPaths.url.api('users', 'me', 'token');
|
||||
|
||||
try {
|
||||
const {apiKey} = await this.ajax.put(url, {data: {}});
|
||||
|
||||
this.args.close(`${apiKey.id}:${apiKey.secret}`);
|
||||
} catch (error) {
|
||||
this.notifications.showAPIError(error, {key: 'token.regenerate'});
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<div class="modal-content" data-test-modal="select-role">
|
||||
<header class="modal-header">
|
||||
<h1>Change user role</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<GhRoleSelection
|
||||
@selected={{this.role}}
|
||||
@setRole={{fn (mut this.role)}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{on "click" @close}}><span>Cancel</span></button>
|
||||
<button class="gh-btn gh-btn-black" type="button" {{on "click" (fn @close this.role)}}><span>Change role</span></button>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,11 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class SelectRoleModal extends Component {
|
||||
@tracked role;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.role = this.args.data.currentRole;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<div class="modal-content">
|
||||
<header class="modal-header">
|
||||
<h1>Are you sure you want to suspend this user?</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<strong>WARNING:</strong> This user will no longer be able to log in but their posts will be kept.
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{on "click" @close}}><span>Cancel</span></button>
|
||||
<GhTaskButton
|
||||
@buttonText="Suspend"
|
||||
@successText="Suspended"
|
||||
@task={{this.suspendUserTask}}
|
||||
@class="gh-btn gh-btn-red gh-btn-icon"
|
||||
data-test-modal-confirm="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,24 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default class SuspendUserModal extends Component {
|
||||
@service notifications;
|
||||
|
||||
@task({drop: true})
|
||||
*suspendUserTask() {
|
||||
try {
|
||||
const {user, saveTask} = this.args.data;
|
||||
|
||||
user.status = 'inactive';
|
||||
yield saveTask.perform();
|
||||
|
||||
this.notifications.closeAlerts('user.suspend');
|
||||
} catch (error) {
|
||||
this.notifications.showAlert('The user could not be suspended. Please try again.', {type: 'error', key: 'user.suspend.failed'});
|
||||
throw error;
|
||||
} finally {
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<div class="modal-content" data-test-modal="transfer-owner">
|
||||
<header class="modal-header">
|
||||
<h1>Transfer Ownership</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Are you sure you want to transfer the ownership of this blog?
|
||||
You will not be able to undo this action.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{on "click" @close}}><span>Cancel</span></button>
|
||||
<GhTaskButton
|
||||
@buttonText="Yep - I'm sure"
|
||||
@task={{this.transferOwnershipTask}}
|
||||
@class="gh-btn gh-btn-red gh-btn-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,47 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {isArray} from '@ember/array';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default class TransferOwnershipModal extends Component {
|
||||
@service ajax;
|
||||
@service dropdown;
|
||||
@service ghostPaths;
|
||||
@service notifications;
|
||||
@service store;
|
||||
|
||||
@task({drop: true})
|
||||
*transferOwnershipTask() {
|
||||
try {
|
||||
const {user} = this.args.data;
|
||||
|
||||
this.dropdown.closeDropdowns();
|
||||
|
||||
const response = yield this.ajax.put(url, {
|
||||
data: {
|
||||
owner: [{
|
||||
id: user.id
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
// manually update the roles for the users that just changed roles
|
||||
// because store.pushPayload is not working with embedded relations
|
||||
if (isArray(response?.users)) {
|
||||
response.users.forEach((userJSON) => {
|
||||
const updatedUser = this.store.peekRecord('user', userJSON.id);
|
||||
const role = this.store.peekRecord('role', userJSON.roles[0].id);
|
||||
|
||||
updatedUser.role = role;
|
||||
});
|
||||
}
|
||||
|
||||
this.notifications.showAlert(`Ownership successfully transferred to ${user.get('name')}`, {type: 'success', key: 'owner.transfer.success'});
|
||||
} catch (error) {
|
||||
this.notifications.showAPIError(error, {key: 'owner.transfer'});
|
||||
throw error;
|
||||
} finally {
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
{{#if this.hostLimitError}}
|
||||
<div class="modal-content" data-test-modal="upgrade-unsuspend-user-host-limit">
|
||||
<header class="modal-header">
|
||||
<h1>Upgrade to un-suspend this user</h1>
|
||||
</header>
|
||||
<button class="close" title="Close" type="button" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
{{html-safe this.hostLimitError}} To give this user access to Ghost, upgrade to a different plan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" data-test-button="cancel-upgrade" type="button" {{on "click" @close}}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
|
||||
<button class="gh-btn gh-btn-green" data-test-button="upgrade-plan" type="button" {{on "click" this.upgrade}}>
|
||||
<span>Upgrade my plan</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="modal-content" data-test-modal="unsuspend-user">
|
||||
<header class="modal-header">
|
||||
<h1>Are you sure you want to un-suspend this user?</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<strong>WARNING:</strong> This user will be able to log in again and will have the same permissions they had previously.
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{on "click" @close}}><span>Cancel</span></button>
|
||||
<GhTaskButton
|
||||
@buttonText="Un-suspend"
|
||||
@successText="Suspended"
|
||||
@task={{this.unsuspendUserTask}}
|
||||
@class="gh-btn gh-btn-red gh-btn-icon"
|
||||
data-test-modal-confirm="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -0,0 +1,57 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class UnsuspendUserModal extends Component {
|
||||
@service limit;
|
||||
@service notifications;
|
||||
@service router;
|
||||
|
||||
@tracked hostLimitError = null;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.checkHostLimitsTask.perform();
|
||||
}
|
||||
|
||||
@action
|
||||
upgrade() {
|
||||
this.router.transitionTo('pro');
|
||||
this.args.close();
|
||||
}
|
||||
|
||||
@task
|
||||
*checkHostLimitsTask() {
|
||||
if (this.args.data.user.role.name !== 'Contributor' && this.limit.limiter.isLimited('staff')) {
|
||||
try {
|
||||
yield this.limit.limiter.errorIfWouldGoOverLimit('staff');
|
||||
} catch (error) {
|
||||
if (error.errorType === 'HostLimitError') {
|
||||
this.hostLimitError = error.message;
|
||||
} else {
|
||||
this.notifications.showAPIError(error, {key: 'staff.limit'});
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*unsuspendUserTask() {
|
||||
try {
|
||||
const {user, saveTask} = this.args.data;
|
||||
|
||||
user.status = 'active';
|
||||
yield saveTask.perform();
|
||||
|
||||
this.notifications.closeAlerts('user.unsuspend');
|
||||
} catch (error) {
|
||||
this.notifications.showAlert('The user could not be unsuspended. Please try again.', {type: 'error', key: 'user.unsuspend.failed'});
|
||||
throw error;
|
||||
} finally {
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<div class="modal-content" data-test-modal="upload-image">
|
||||
<div class="modal-body">
|
||||
{{#if this.url}}
|
||||
<div class="gh-image-uploader -with-image">
|
||||
<div><img src={{this.url}} alt="" role="presentation"></div>
|
||||
<button type="button" class="image-delete" title="Delete" {{on "click" this.removeImage}}>
|
||||
{{svg-jar "trash"}}
|
||||
<span class="hidden">Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<GhImageUploader
|
||||
@image={{this.url}}
|
||||
@saveButton={{false}}
|
||||
@update={{this.fileUploaded}}
|
||||
@uploadStarted={{fn (mut this.isUploading) true}}
|
||||
@uploadFinished={{fn (mut this.isUploading) false}}
|
||||
@accept={{@data.accept}}
|
||||
@extensions={{@data.extensions}}
|
||||
@uploadUrl={{@data.uploadUrl}}
|
||||
@paramsHash={{@data.paramsHas}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{on "click" @close}}><span>Cancel</span></button>
|
||||
{{#if this.isUploading}}
|
||||
<button class="gh-btn gh-btn-black right gh-btn-icon disabled" type="button"><span>Save</span></button>
|
||||
{{else}}
|
||||
<GhTaskButton
|
||||
@task={{this.uploadImageTask}}
|
||||
@class="gh-btn gh-btn-black right gh-btn-icon"
|
||||
data-test-modal-accept-button
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,52 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class UploadImageModal extends Component {
|
||||
@service notifications;
|
||||
|
||||
@tracked errorMessage;
|
||||
@tracked isUploading = false;
|
||||
@tracked url = '';
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.url = this._getModelProperty();
|
||||
}
|
||||
|
||||
@action
|
||||
fileUploaded(url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@action
|
||||
removeImage() {
|
||||
this.url = '';
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*uploadImageTask() {
|
||||
this._setModelProperty(this.url);
|
||||
|
||||
try {
|
||||
yield this.args.data.model.save();
|
||||
} catch (e) {
|
||||
this.notifications.showAPIError(e, {key: 'image.upload'});
|
||||
} finally {
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
|
||||
_getModelProperty() {
|
||||
const {model, modelProperty} = this.args.data;
|
||||
return model[modelProperty];
|
||||
}
|
||||
|
||||
_setModelProperty(url) {
|
||||
const {model, modelProperty} = this.args.data;
|
||||
model[modelProperty] = url;
|
||||
return url;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,11 @@
|
|||
import Controller from '@ember/controller';
|
||||
import DeleteUserModal from '../../../components/settings/staff/modals/delete-user';
|
||||
import RegenerateStaffTokenModal from '../../../components/settings/staff/modals/regenerate-staff-token';
|
||||
import SelectRoleModal from '../../../components/settings/staff/modals/select-role';
|
||||
import SuspendUserModal from '../../../components/settings/staff/modals/suspend-user';
|
||||
import TransferOwnershipModal from '../../../components/settings/staff/modals/transfer-ownership';
|
||||
import UnsuspendUserModal from '../../../components/settings/staff/modals/unsuspend-user';
|
||||
import UploadImageModal from '../../../components/settings/staff/modals/upload-image';
|
||||
import boundOneWay from 'ghost-admin/utils/bound-one-way';
|
||||
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
|
||||
import isNumber from 'ghost-admin/utils/isNumber';
|
||||
|
@ -6,7 +13,6 @@ import validator from 'validator';
|
|||
import windowProxy from 'ghost-admin/utils/window-proxy';
|
||||
import {action, computed} from '@ember/object';
|
||||
import {alias, and, not, or, readOnly} from '@ember/object/computed';
|
||||
import {isArray as isEmberArray} from '@ember/array';
|
||||
import {run} from '@ember/runloop';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task, taskGroup, timeout} from 'ember-concurrency';
|
||||
|
@ -14,26 +20,17 @@ import {task, taskGroup, timeout} from 'ember-concurrency';
|
|||
export default Controller.extend({
|
||||
ajax: service(),
|
||||
config: service(),
|
||||
dropdown: service(),
|
||||
ghostPaths: service(),
|
||||
limit: service(),
|
||||
membersUtils: service(),
|
||||
modals: service(),
|
||||
notifications: service(),
|
||||
session: service(),
|
||||
slugGenerator: service(),
|
||||
utils: service(),
|
||||
membersUtils: service(),
|
||||
|
||||
personalToken: null,
|
||||
limitErrorMessage: null,
|
||||
personalTokenRegenerated: false,
|
||||
dirtyAttributes: false,
|
||||
showDeleteUserModal: false,
|
||||
showSuspendUserModal: false,
|
||||
showTransferOwnerModal: false,
|
||||
showUploadCoverModal: false,
|
||||
showUploadImageModal: false,
|
||||
showRegenerateTokenModal: false,
|
||||
showRoleSelectionModal: false,
|
||||
_scratchFacebook: null,
|
||||
_scratchTwitter: null,
|
||||
|
||||
|
@ -88,62 +85,6 @@ export default Controller.extend({
|
|||
}),
|
||||
|
||||
actions: {
|
||||
toggleRoleSelectionModal(event) {
|
||||
event?.preventDefault?.();
|
||||
this.toggleProperty('showRoleSelectionModal');
|
||||
},
|
||||
|
||||
changeRole(newRole) {
|
||||
this.user.set('role', newRole);
|
||||
this.set('dirtyAttributes', true);
|
||||
},
|
||||
|
||||
toggleDeleteUserModal() {
|
||||
if (this.deleteUserActionIsVisible) {
|
||||
this.toggleProperty('showDeleteUserModal');
|
||||
}
|
||||
},
|
||||
|
||||
suspendUser() {
|
||||
this.user.set('status', 'inactive');
|
||||
return this.save.perform();
|
||||
},
|
||||
|
||||
toggleSuspendUserModal() {
|
||||
if (this.deleteUserActionIsVisible) {
|
||||
this.toggleProperty('showSuspendUserModal');
|
||||
}
|
||||
},
|
||||
|
||||
unsuspendUser() {
|
||||
this.user.set('status', 'active');
|
||||
return this.save.perform();
|
||||
},
|
||||
|
||||
toggleUnsuspendUserModal() {
|
||||
if (this.deleteUserActionIsVisible) {
|
||||
if (this.user.role.name !== 'Contributor'
|
||||
&& this.limit.limiter
|
||||
&& this.limit.limiter.isLimited('staff')
|
||||
) {
|
||||
this.limit.limiter.errorIfWouldGoOverLimit('staff')
|
||||
.then(() => {
|
||||
this.toggleProperty('showUnsuspendUserModal');
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.errorType === 'HostLimitError') {
|
||||
this.limitErrorMessage = error.message;
|
||||
this.toggleProperty('showUnsuspendUserModal');
|
||||
} else {
|
||||
this.notifications.showAPIError(error, {key: 'staff.limit'});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.toggleProperty('showUnsuspendUserModal');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
validateFacebookUrl() {
|
||||
let newUrl = this._scratchFacebook;
|
||||
let oldUrl = this.get('user.facebook');
|
||||
|
@ -255,50 +196,6 @@ export default Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
transferOwnership() {
|
||||
let user = this.user;
|
||||
let url = this.get('ghostPaths.url').api('users', 'owner');
|
||||
|
||||
this.dropdown.closeDropdowns();
|
||||
|
||||
return this.ajax.put(url, {
|
||||
data: {
|
||||
owner: [{
|
||||
id: user.get('id')
|
||||
}]
|
||||
}
|
||||
}).then((response) => {
|
||||
// manually update the roles for the users that just changed roles
|
||||
// because store.pushPayload is not working with embedded relations
|
||||
if (response && isEmberArray(response.users)) {
|
||||
response.users.forEach((userJSON) => {
|
||||
let updatedUser = this.store.peekRecord('user', userJSON.id);
|
||||
let role = this.store.peekRecord('role', userJSON.roles[0].id);
|
||||
|
||||
updatedUser.set('role', role);
|
||||
});
|
||||
}
|
||||
|
||||
this.notifications.showAlert(`Ownership successfully transferred to ${user.get('name')}`, {type: 'success', key: 'owner.transfer.success'});
|
||||
}).catch((error) => {
|
||||
this.notifications.showAPIError(error, {key: 'owner.transfer'});
|
||||
});
|
||||
},
|
||||
|
||||
toggleTransferOwnerModal() {
|
||||
if (this.canMakeOwner) {
|
||||
this.toggleProperty('showTransferOwnerModal');
|
||||
}
|
||||
},
|
||||
|
||||
toggleUploadCoverModal() {
|
||||
this.toggleProperty('showUploadCoverModal');
|
||||
},
|
||||
|
||||
toggleUploadImageModal() {
|
||||
this.toggleProperty('showUploadImageModal');
|
||||
},
|
||||
|
||||
// TODO: remove those mutation actions once we have better
|
||||
// inline validations that auto-clear errors on input
|
||||
updatePassword(password) {
|
||||
|
@ -317,28 +214,77 @@ export default Controller.extend({
|
|||
this.set('user.ne2Password', password);
|
||||
this.get('user.hasValidated').removeObject('ne2Password');
|
||||
this.get('user.errors').remove('ne2Password');
|
||||
},
|
||||
|
||||
confirmRegenerateTokenModal() {
|
||||
this.set('showRegenerateTokenModal', true);
|
||||
},
|
||||
|
||||
cancelRegenerateTokenModal() {
|
||||
this.set('showRegenerateTokenModal', false);
|
||||
},
|
||||
|
||||
regenerateToken() {
|
||||
let url = this.get('ghostPaths.url').api('users', 'me', 'token');
|
||||
|
||||
return this.ajax.put(url, {data: {}}).then(({apiKey}) => {
|
||||
this.set('personalToken', apiKey.id + ':' + apiKey.secret);
|
||||
this.set('personalTokenRegenerated', true);
|
||||
}).catch((error) => {
|
||||
this.notifications.showAPIError(error, {key: 'token.regenerate'});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
deleteUser: action(async function () {
|
||||
if (this.deleteUserActionIsVisible) {
|
||||
await this.modals.open(DeleteUserModal, {
|
||||
user: this.model
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
suspendUser: action(async function () {
|
||||
if (this.deleteUserActionIsVisible) {
|
||||
await this.modals.open(SuspendUserModal, {
|
||||
user: this.model,
|
||||
saveTask: this.save
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
unsuspendUser: action(async function () {
|
||||
if (this.deleteUserActionIsVisible) {
|
||||
await this.modals.open(UnsuspendUserModal, {
|
||||
user: this.model,
|
||||
saveTask: this.save
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
transferOwnership: action(async function () {
|
||||
if (this.canMakeOwner) {
|
||||
await this.modals.open(TransferOwnershipModal, {
|
||||
user: this.model
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
regenerateStaffToken: action(async function () {
|
||||
const apiToken = await this.modals.open(RegenerateStaffTokenModal);
|
||||
|
||||
if (apiToken) {
|
||||
this.set('personalToken', apiToken);
|
||||
this.set('personalTokenRegenerated', true);
|
||||
}
|
||||
}),
|
||||
|
||||
selectRole: action(async function () {
|
||||
const newRole = await this.modals.open(SelectRoleModal, {
|
||||
currentRole: this.model.role
|
||||
});
|
||||
|
||||
if (newRole) {
|
||||
this.user.role = newRole;
|
||||
this.set('dirtyAttributes', true);
|
||||
}
|
||||
}),
|
||||
|
||||
changeCoverImage: action(async function () {
|
||||
await this.modals.open(UploadImageModal, {
|
||||
model: this.model,
|
||||
modelProperty: 'coverImage'
|
||||
});
|
||||
}),
|
||||
|
||||
changeProfileImage: action(async function () {
|
||||
await this.modals.open(UploadImageModal, {
|
||||
model: this.model,
|
||||
modelProperty: 'profileImage'
|
||||
});
|
||||
}),
|
||||
|
||||
reset: action(function () {
|
||||
this.user.rollbackAttributes();
|
||||
this.user.password = '';
|
||||
|
@ -362,19 +308,6 @@ export default Controller.extend({
|
|||
}
|
||||
}),
|
||||
|
||||
deleteUser: task(function *() {
|
||||
try {
|
||||
yield this.user.destroyRecord();
|
||||
|
||||
this.notifications.closeAlerts('user.delete');
|
||||
this.store.unloadAll('post');
|
||||
this.transitionToRoute('settings.staff');
|
||||
} catch (error) {
|
||||
this.notifications.showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'});
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
updateSlug: task(function* (newSlug) {
|
||||
let slug = this.get('user.slug');
|
||||
|
||||
|
|
|
@ -31,33 +31,27 @@
|
|||
<GhDropdown @name="user-actions-menu" @tagName="ul" @classNames="user-actions-menu dropdown-menu dropdown-align-right">
|
||||
{{#if this.canMakeOwner}}
|
||||
<li>
|
||||
<button type="button" {{action "toggleTransferOwnerModal"}}>
|
||||
<button type="button" {{on "click" this.transferOwnership}}>
|
||||
Make owner
|
||||
</button>
|
||||
{{#if this.showTransferOwnerModal}}
|
||||
<GhFullscreenModal @modal="transfer-owner"
|
||||
@confirm={{action "transferOwnership"}}
|
||||
@close={{action "toggleTransferOwnerModal"}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if this.deleteUserActionIsVisible}}
|
||||
<li>
|
||||
<button class="delete" data-test-delete-button type="button" {{action "toggleDeleteUserModal"}}>
|
||||
<button class="delete" data-test-delete-button type="button" {{on "click" this.deleteUser}}>
|
||||
Delete user
|
||||
</button>
|
||||
</li>
|
||||
{{#if this.user.isActive}}
|
||||
<li>
|
||||
<button class="suspend" data-test-suspend-button type="button" {{action "toggleSuspendUserModal"}}>
|
||||
<button class="suspend" data-test-suspend-button type="button" {{on "click" this.suspendUser}}>
|
||||
Suspend user
|
||||
</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if this.user.isSuspended}}
|
||||
<li>
|
||||
<button class="unsuspend" data-test-unsuspend-button type="button" {{action "toggleUnsuspendUserModal"}}>
|
||||
<button class="unsuspend" data-test-unsuspend-button type="button" {{on "click" this.unsuspendUser}}>
|
||||
Un-suspend user
|
||||
</button>
|
||||
</li>
|
||||
|
@ -74,39 +68,6 @@
|
|||
{{/if}}
|
||||
|
||||
<GhTaskButton @class="gh-btn gh-btn-primary gh-btn-icon" @task={{this.save}} data-test-save-button={{true}} />
|
||||
|
||||
{{#if this.showDeleteUserModal}}
|
||||
<GhFullscreenModal @modal="delete-user"
|
||||
@model={{this.user}}
|
||||
@confirm={{action (perform this.deleteUser)}}
|
||||
@close={{action "toggleDeleteUserModal"}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showSuspendUserModal}}
|
||||
<GhFullscreenModal @modal="suspend-user"
|
||||
@model={{this.user}}
|
||||
@confirm={{action "suspendUser"}}
|
||||
@close={{action "toggleSuspendUserModal"}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showUnsuspendUserModal}}
|
||||
{{#if this.limitErrorMessage}}
|
||||
<GhFullscreenModal @modal="upgrade-unsuspend-user-host-limit"
|
||||
@model={{hash
|
||||
message=this.limitErrorMessage
|
||||
}}
|
||||
@close={{action "toggleUnsuspendUserModal"}}
|
||||
@modifier="action wide" />
|
||||
{{else}}
|
||||
<GhFullscreenModal @modal="unsuspend-user"
|
||||
@model={{this.user}}
|
||||
@confirm={{action "unsuspendUser"}}
|
||||
@close={{action "toggleUnsuspendUserModal"}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
@ -120,24 +81,12 @@
|
|||
<form class="user-profile" novalidate="novalidate" autocomplete="off" {{action (perform this.save) on="submit"}}>
|
||||
|
||||
<figure class="user-cover" style={{background-image-style this.user.coverImageUrl}}>
|
||||
<button type="button" class="gh-btn gh-btn-default user-cover-edit" {{action "toggleUploadCoverModal"}}><span>Change cover</span></button>
|
||||
{{#if this.showUploadCoverModal}}
|
||||
<GhFullscreenModal @modal="upload-image"
|
||||
@model={{hash model=this.user imageProperty="coverImage"}}
|
||||
@close={{action "toggleUploadCoverModal"}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
<button type="button" class="gh-btn gh-btn-default user-cover-edit" {{on "click" this.changeCoverImage}}><span>Change cover</span></button>
|
||||
</figure>
|
||||
|
||||
<figure class="user-image bg-whitegrey">
|
||||
<div id="user-image" class="img" style={{background-image-style this.user.profileImageUrl}}><span class="hidden">{{this.user.name}}"s picture</span></div>
|
||||
<button type="button" {{action "toggleUploadImageModal"}} class="edit-user-image">Edit picture</button>
|
||||
{{#if this.showUploadImageModal}}
|
||||
<GhFullscreenModal @modal="upload-image"
|
||||
@model={{hash model=this.user imageProperty="profileImage" paramsHash=(hash purpose="profile_image")}}
|
||||
@close={{action "toggleUploadImageModal"}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
<button type="button" class="edit-user-image" {{on "click" this.changeProfileImage}}>Edit picture</button>
|
||||
</figure>
|
||||
|
||||
<div class="pa5">
|
||||
|
@ -205,19 +154,9 @@
|
|||
{{#if this.rolesDropdownIsVisible}}
|
||||
<div class="form-group">
|
||||
<label for="user-role">Role</label>
|
||||
<div class="gh-input pointer" {{on "click" (action "toggleRoleSelectionModal")}}>{{this.user.role.name}}{{svg-jar "arrow-down-small"}}</div>
|
||||
<button type="button" class="gh-input tl" {{on "click" this.selectRole}}>{{this.user.role.name}}{{svg-jar "arrow-down-small"}}</button>
|
||||
<p>What permissions should this user have?</p>
|
||||
</div>
|
||||
|
||||
{{#if this.showRoleSelectionModal}}
|
||||
<GhFullscreenModal
|
||||
@modal="select-user-role"
|
||||
@model={{readonly this.user.role}}
|
||||
@confirm={{action "changeRole"}}
|
||||
@close={{action "toggleRoleSelectionModal"}}
|
||||
@modifier="change-role"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<GhFormGroup @errors={{this.user.errors}} @hasValidated={{this.user.hasValidated}} @property="location">
|
||||
|
@ -468,14 +407,14 @@
|
|||
onclick="this.select()"
|
||||
/>
|
||||
<div class="app-api-personal-token-buttons child">
|
||||
<button type="button" {{action "confirmRegenerateTokenModal"}} class="app-button-regenerate" data-tooltip="Regenerate">
|
||||
<button type="button" class="app-button-regenerate" {{on "click" this.regenerateStaffToken}} data-tooltip="Regenerate">
|
||||
{{svg-jar "reload" class="w4 h4 stroke-midgrey"}}
|
||||
</button>
|
||||
<button type="button" {{action (perform this.copyContentKey)}} class="app-button-copy">
|
||||
{{#if this.copyContentKey.isRunning}}
|
||||
{{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied
|
||||
{{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied
|
||||
{{else}}
|
||||
Copy
|
||||
Copy
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -486,13 +425,6 @@
|
|||
{{#if this.personalTokenRegenerated}}
|
||||
<p class="green">Staff access token was successfully regenerated </p>
|
||||
{{/if}}
|
||||
{{#if this.showRegenerateTokenModal}}
|
||||
<GhFullscreenModal @modal="regenerate-token"
|
||||
@confirm={{action "regenerateToken"}}
|
||||
@close={{action "cancelRegenerateTokenModal"}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
|
||||
</GhFormGroup>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue