mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 22:43:30 +03:00
✨ Added Secondary Navigation (#1410)
refs: https://github.com/TryGhost/Ghost/pull/11409 - Added a new UI for a second set of navigation links - This should support most concepts of nav, e.g. left and right, or header and footer - This PR mostly updates the design and nav components to cope with a second set of nav
This commit is contained in:
parent
a50cc62617
commit
00ce91ce3a
@ -23,8 +23,8 @@ export default Component.extend(ValidationState, {
|
||||
}),
|
||||
|
||||
actions: {
|
||||
addItem() {
|
||||
this.addItem();
|
||||
addItem(item) {
|
||||
this.addItem(item);
|
||||
},
|
||||
|
||||
deleteItem(item) {
|
||||
@ -53,7 +53,7 @@ export default Component.extend(ValidationState, {
|
||||
if (event.keyCode === 13 && this.get('navItem.isNew')) {
|
||||
event.preventDefault();
|
||||
run.scheduleOnce('actions', this, function () {
|
||||
this.send('addItem');
|
||||
this.send('addItem', this.get('navItem'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,14 @@ export default Controller.extend({
|
||||
|
||||
dirtyAttributes: false,
|
||||
newNavItem: null,
|
||||
newSecondaryNavItem: null,
|
||||
themes: null,
|
||||
themeToDelete: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set('newNavItem', NavigationItem.create({isNew: true}));
|
||||
this.set('newSecondaryNavItem', NavigationItem.create({isNew: true, isSecondary: true}));
|
||||
},
|
||||
|
||||
showDeleteThemeModal: notEmpty('themeToDelete'),
|
||||
@ -40,16 +42,14 @@ export default Controller.extend({
|
||||
this.save.perform();
|
||||
},
|
||||
|
||||
addNavItem() {
|
||||
let newNavItem = this.newNavItem;
|
||||
|
||||
addNavItem(item) {
|
||||
// If the url sent through is blank (user never edited the url)
|
||||
if (newNavItem.get('url') === '') {
|
||||
newNavItem.set('url', '/');
|
||||
if (item.get('url') === '') {
|
||||
item.set('url', '/');
|
||||
}
|
||||
|
||||
return newNavItem.validate().then(() => {
|
||||
this.addNewNavItem();
|
||||
return item.validate().then(() => {
|
||||
this.addNewNavItem(item);
|
||||
});
|
||||
},
|
||||
|
||||
@ -58,7 +58,7 @@ export default Controller.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
let navItems = this.get('settings.navigation');
|
||||
let navItems = item.isSecondary ? this.get('settings.secondaryNavigation') : this.get('settings.navigation');
|
||||
|
||||
navItems.removeObject(item);
|
||||
this.set('dirtyAttributes', true);
|
||||
@ -196,23 +196,33 @@ export default Controller.extend({
|
||||
|
||||
reset() {
|
||||
this.set('newNavItem', NavigationItem.create({isNew: true}));
|
||||
this.set('newSecondaryNavItem', NavigationItem.create({isNew: true, isSecondary: true}));
|
||||
}
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
let navItems = this.get('settings.navigation');
|
||||
let newNavItem = this.newNavItem;
|
||||
let secondaryNavItems = this.get('settings.secondaryNavigation');
|
||||
|
||||
let notifications = this.notifications;
|
||||
let validationPromises = [];
|
||||
|
||||
if (!newNavItem.get('isBlank')) {
|
||||
validationPromises.pushObject(this.send('addNavItem'));
|
||||
if (!this.newNavItem.get('isBlank')) {
|
||||
validationPromises.pushObject(this.send('addNavItem', this.newNavItem));
|
||||
}
|
||||
|
||||
if (!this.newSecondaryNavItem.get('isBlank')) {
|
||||
validationPromises.pushObject(this.send('addNavItem', this.newSecondaryNavItem));
|
||||
}
|
||||
|
||||
navItems.map((item) => {
|
||||
validationPromises.pushObject(item.validate());
|
||||
});
|
||||
|
||||
secondaryNavItems.map((item) => {
|
||||
validationPromises.pushObject(item.validate());
|
||||
});
|
||||
|
||||
try {
|
||||
yield RSVP.all(validationPromises);
|
||||
this.set('dirtyAttributes', false);
|
||||
@ -225,15 +235,20 @@ export default Controller.extend({
|
||||
}
|
||||
}),
|
||||
|
||||
addNewNavItem() {
|
||||
let navItems = this.get('settings.navigation');
|
||||
let newNavItem = this.newNavItem;
|
||||
addNewNavItem(item) {
|
||||
let navItems = item.isSecondary ? this.get('settings.secondaryNavigation') : this.get('settings.navigation');
|
||||
|
||||
newNavItem.set('isNew', false);
|
||||
navItems.pushObject(newNavItem);
|
||||
item.set('isNew', false);
|
||||
navItems.pushObject(item);
|
||||
this.set('dirtyAttributes', true);
|
||||
this.set('newNavItem', NavigationItem.create({isNew: true}));
|
||||
$('.gh-blognav-line:last input:first').focus();
|
||||
|
||||
if (item.isSecondary) {
|
||||
this.set('newSecondaryNavItem', NavigationItem.create({isNew: true, isSecondary: true}));
|
||||
$('.gh-blognav-container:last .gh-blognav-line:last input:first').focus();
|
||||
} else {
|
||||
this.set('newNavItem', NavigationItem.create({isNew: true}));
|
||||
$('.gh-blognav-container:first .gh-blognav-line:last input:first').focus();
|
||||
}
|
||||
},
|
||||
|
||||
_deleteTheme() {
|
||||
|
@ -7,6 +7,7 @@ export default EmberObject.extend(ValidationEngine, {
|
||||
label: '',
|
||||
url: '',
|
||||
isNew: false,
|
||||
isSecondary: false,
|
||||
|
||||
validationType: 'navItem',
|
||||
|
||||
|
@ -19,6 +19,7 @@ export default Model.extend(ValidationEngine, {
|
||||
twitter: attr('twitter-url-user'),
|
||||
labs: attr('string'),
|
||||
navigation: attr('navigation-settings'),
|
||||
secondaryNavigation: attr('navigation-settings', {isSecondary: true}),
|
||||
isPrivate: attr('boolean'),
|
||||
publicHash: attr('string'),
|
||||
password: attr('string'),
|
||||
|
@ -32,7 +32,7 @@
|
||||
</div>
|
||||
|
||||
{{#if navItem.isNew}}
|
||||
<button type="button" class="gh-blognav-add" {{action "addItem"}}>
|
||||
<button type="button" class="gh-blognav-add" {{action "addItem" navItem}}>
|
||||
{{svg-jar "add"}}<span class="sr-only">Add</span>
|
||||
</button>
|
||||
{{else}}
|
||||
|
@ -16,7 +16,6 @@
|
||||
{{/if}}
|
||||
|
||||
<section class="view-container">
|
||||
|
||||
<div class="gh-setting-header gh-first-header">Navigation</div>
|
||||
<div class="gh-blognav-container pa5 pt6 bg-grouped-table shadow-1 br3">
|
||||
<form id="settings-navigation" class="gh-blognav" novalidate="novalidate">
|
||||
@ -43,6 +42,32 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="gh-setting-header">Secondary Navigation</div>
|
||||
<div class="gh-blognav-container pa5 pt6 bg-grouped-table shadow-1 br3">
|
||||
<form id="secondary-navigation" class="gh-blognav" novalidate="novalidate">
|
||||
{{#sortable-objects sortableObjectList=settings.secondaryNavigation useSwap=false}}
|
||||
{{#each settings.secondaryNavigation as |navItem index|}}
|
||||
{{#draggable-object content=navItem dragHandle=".gh-blognav-grab" isSortable=true}}
|
||||
{{gh-navitem
|
||||
navItem=navItem
|
||||
baseUrl=blogUrl
|
||||
addItem=(action "addNavItem")
|
||||
deleteItem=(action "deleteNavItem")
|
||||
updateUrl=(action "updateUrl")
|
||||
updateLabel=(action "updateLabel")
|
||||
data-test-navitem=index}}
|
||||
{{/draggable-object}}
|
||||
{{/each}}
|
||||
{{/sortable-objects}}
|
||||
{{gh-navitem
|
||||
navItem=newSecondaryNavItem
|
||||
baseUrl=blogUrl
|
||||
addItem=(action "addNavItem")
|
||||
updateUrl=(action "updateUrl")
|
||||
data-test-navitem="new"}}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="gh-setting-header">Theme Directory</div>
|
||||
<div class="gh-theme-directory-container">
|
||||
<div class="theme-directory">
|
||||
|
@ -3,7 +3,7 @@ import Transform from 'ember-data/transform';
|
||||
import {A as emberA, isArray as isEmberArray} from '@ember/array';
|
||||
|
||||
export default Transform.extend({
|
||||
deserialize(serialized) {
|
||||
deserialize(serialized, options) {
|
||||
let navItems, settingsArray;
|
||||
|
||||
try {
|
||||
@ -12,7 +12,10 @@ export default Transform.extend({
|
||||
settingsArray = [];
|
||||
}
|
||||
|
||||
navItems = settingsArray.map(itemDetails => NavigationItem.create(itemDetails));
|
||||
navItems = settingsArray.map((itemDetails) => {
|
||||
itemDetails.isSecondary = options && options.isSecondary || false;
|
||||
return NavigationItem.create(itemDetails);
|
||||
});
|
||||
|
||||
return emberA(navItems);
|
||||
},
|
||||
|
@ -192,5 +192,15 @@ export default [
|
||||
created_by: 1,
|
||||
updated_at: '2019-10-09T09:49:00.000Z',
|
||||
updated_by: 1
|
||||
},
|
||||
{
|
||||
id: 25,
|
||||
key: 'secondary_navigation',
|
||||
type: 'blog',
|
||||
created_at: '2019-11-20T09:44:30.810Z',
|
||||
created_by: 1,
|
||||
updated_at: '2019-11-20T13:32:49.868Z',
|
||||
updated_by: 1,
|
||||
value: JSON.stringify([])
|
||||
}
|
||||
];
|
||||
|
@ -60,18 +60,18 @@ describe('Acceptance: Settings - Design', function () {
|
||||
expect(currentRouteName()).to.equal('settings.design.index');
|
||||
expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save');
|
||||
|
||||
// fixtures contain two nav items, check for three rows as we
|
||||
// should have one extra that's blank
|
||||
// fixtures contain two nav items, check for four rows as we
|
||||
// should have one extra that's blank for each navigation section
|
||||
expect(
|
||||
findAll('[data-test-navitem]').length,
|
||||
'navigation items count'
|
||||
).to.equal(3);
|
||||
).to.equal(4);
|
||||
});
|
||||
|
||||
it('saves navigation settings', async function () {
|
||||
await visit('/settings/design');
|
||||
await fillIn('[data-test-navitem="0"] [data-test-input="label"]', 'Test');
|
||||
await typeIn('[data-test-navitem="0"] [data-test-input="url"]', '/test');
|
||||
await fillIn('#settings-navigation [data-test-navitem="0"] [data-test-input="label"]', 'Test');
|
||||
await typeIn('#settings-navigation [data-test-navitem="0"] [data-test-input="url"]', '/test');
|
||||
await click('[data-test-save-button]');
|
||||
|
||||
let [navSetting] = this.server.db.settings.where({key: 'navigation'});
|
||||
@ -91,23 +91,23 @@ describe('Acceptance: Settings - Design', function () {
|
||||
await click('[data-test-save-button]');
|
||||
|
||||
expect(
|
||||
findAll('[data-test-navitem]').length,
|
||||
findAll('#settings-navigation [data-test-navitem]').length,
|
||||
'number of nav items after saving with blank new item'
|
||||
).to.equal(3);
|
||||
|
||||
await fillIn('[data-test-navitem="new"] [data-test-input="label"]', 'Test');
|
||||
await fillIn('[data-test-navitem="new"] [data-test-input="url"]', '');
|
||||
await typeIn('[data-test-navitem="new"] [data-test-input="url"]', 'http://invalid domain/');
|
||||
await fillIn('#settings-navigation [data-test-navitem="new"] [data-test-input="label"]', 'Test');
|
||||
await fillIn('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]', '');
|
||||
await typeIn('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]', 'http://invalid domain/');
|
||||
|
||||
await click('[data-test-save-button]');
|
||||
|
||||
expect(
|
||||
findAll('[data-test-navitem]').length,
|
||||
findAll('#settings-navigation [data-test-navitem]').length,
|
||||
'number of nav items after saving with invalid new item'
|
||||
).to.equal(3);
|
||||
|
||||
expect(
|
||||
withText(findAll('[data-test-navitem="new"] [data-test-error]')).length,
|
||||
withText(findAll('#settings-navigation [data-test-navitem="new"] [data-test-error]')).length,
|
||||
'number of invalid fields in new item'
|
||||
).to.equal(1);
|
||||
});
|
||||
@ -135,7 +135,7 @@ describe('Acceptance: Settings - Design', function () {
|
||||
|
||||
it('can add and remove items', async function () {
|
||||
await visit('/settings/design');
|
||||
await click('.gh-blognav-add');
|
||||
await click('#settings-navigation .gh-blognav-add');
|
||||
|
||||
expect(
|
||||
find('[data-test-navitem="new"] [data-test-error="label"]').textContent.trim(),
|
||||
@ -166,17 +166,17 @@ describe('Acceptance: Settings - Design', function () {
|
||||
await click('.gh-blognav-add');
|
||||
|
||||
expect(
|
||||
findAll('[data-test-navitem]').length,
|
||||
findAll('#settings-navigation [data-test-navitem]').length,
|
||||
'number of nav items after successful add'
|
||||
).to.equal(4);
|
||||
|
||||
expect(
|
||||
find('[data-test-navitem="new"] [data-test-input="label"]').value,
|
||||
find('#settings-navigation [data-test-navitem="new"] [data-test-input="label"]').value,
|
||||
'new item label value after successful add'
|
||||
).to.be.empty;
|
||||
|
||||
expect(
|
||||
find('[data-test-navitem="new"] [data-test-input="url"]').value,
|
||||
find('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]').value,
|
||||
'new item url value after successful add'
|
||||
).to.equal(`${window.location.origin}/`);
|
||||
|
||||
@ -185,10 +185,10 @@ describe('Acceptance: Settings - Design', function () {
|
||||
'number or validation errors shown after successful add'
|
||||
).to.equal(0);
|
||||
|
||||
await click('[data-test-navitem="0"] .gh-blognav-delete');
|
||||
await click('#settings-navigation [data-test-navitem="0"] .gh-blognav-delete');
|
||||
|
||||
expect(
|
||||
findAll('[data-test-navitem]').length,
|
||||
findAll('#settings-navigation [data-test-navitem]').length,
|
||||
'number of nav items after successful remove'
|
||||
).to.equal(3);
|
||||
|
||||
@ -204,6 +204,81 @@ describe('Acceptance: Settings - Design', function () {
|
||||
expect(navSetting.value).to.equal('[{"label":"About","url":"/about"},{"label":"New","url":"/new/"}]');
|
||||
});
|
||||
|
||||
it('can also add and remove items from seconday nav', async function () {
|
||||
await visit('/settings/design');
|
||||
await click('#secondary-navigation .gh-blognav-add');
|
||||
|
||||
expect(
|
||||
find('#secondary-navigation [data-test-navitem="new"] [data-test-error="label"]').textContent.trim(),
|
||||
'blank label has validation error'
|
||||
).to.not.be.empty;
|
||||
|
||||
await fillIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]', '');
|
||||
await typeIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]', 'Foo');
|
||||
|
||||
expect(
|
||||
find('#secondary-navigation [data-test-navitem="new"] [data-test-error="label"]').textContent.trim(),
|
||||
'label validation is visible after typing'
|
||||
).to.be.empty;
|
||||
|
||||
await fillIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]', '');
|
||||
await typeIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]', '/bar');
|
||||
await blur('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]');
|
||||
|
||||
expect(
|
||||
find('#secondary-navigation [data-test-navitem="new"] [data-test-error="url"]').textContent.trim(),
|
||||
'url validation is visible after typing'
|
||||
).to.be.empty;
|
||||
|
||||
expect(
|
||||
find('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]').value
|
||||
).to.equal(`${window.location.origin}/bar/`);
|
||||
|
||||
await click('[data-test-save-button]');
|
||||
|
||||
expect(
|
||||
findAll('#secondary-navigation [data-test-navitem]').length,
|
||||
'number of nav items after successful add'
|
||||
).to.equal(2);
|
||||
|
||||
expect(
|
||||
find('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]').value,
|
||||
'new item label value after successful add'
|
||||
).to.be.empty;
|
||||
|
||||
expect(
|
||||
find('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]').value,
|
||||
'new item url value after successful add'
|
||||
).to.equal(`${window.location.origin}/`);
|
||||
|
||||
expect(
|
||||
withText(findAll('#secondary-navigation [data-test-navitem] [data-test-error]')).length,
|
||||
'number or validation errors shown after successful add'
|
||||
).to.equal(0);
|
||||
|
||||
let [navSetting] = this.server.db.settings.where({key: 'secondary_navigation'});
|
||||
|
||||
expect(navSetting.value).to.equal('[{"label":"Foo","url":"/bar/"}]');
|
||||
|
||||
await click('#secondary-navigation [data-test-navitem="0"] .gh-blognav-delete');
|
||||
|
||||
expect(
|
||||
findAll('#secondary-navigation [data-test-navitem]').length,
|
||||
'number of nav items after successful remove'
|
||||
).to.equal(1);
|
||||
|
||||
// CMD-S shortcut works
|
||||
await triggerEvent('.gh-app', 'keydown', {
|
||||
keyCode: 83, // s
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
});
|
||||
|
||||
[navSetting] = this.server.db.settings.where({key: 'secondary_navigation'});
|
||||
|
||||
expect(navSetting.value).to.equal('[]');
|
||||
});
|
||||
|
||||
it('allows management of themes', async function () {
|
||||
// lists available themes + active theme is highlighted
|
||||
|
||||
|
@ -102,7 +102,7 @@ describe('Unit: Controller: settings/design', function () {
|
||||
ctrl.set('newNavItem.url', '/new');
|
||||
|
||||
run(() => {
|
||||
ctrl.send('addNavItem');
|
||||
ctrl.send('addNavItem', ctrl.get('newNavItem'));
|
||||
});
|
||||
|
||||
expect(ctrl.get('settings.navigation.length')).to.equal(2);
|
||||
@ -122,7 +122,7 @@ describe('Unit: Controller: settings/design', function () {
|
||||
NavItem.create({label: '', url: '', last: true})
|
||||
]}));
|
||||
expect(ctrl.get('settings.navigation.length')).to.equal(1);
|
||||
ctrl.send('addNavItem');
|
||||
ctrl.send('addNavItem', ctrl.get('settings.navigation.lastObject'));
|
||||
expect(ctrl.get('settings.navigation.length')).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user