Added support for secondary navigation (#11409)

no issue

- Secondary navigation means most nav concepts are supported, e.g. header & footer, or left & right
- The UI is added separately, this PR adds supporting concepts:
  - make sure the default value is an empty array
  - add support in the API (v3 only)
  - add handling in the navigation helper
This commit is contained in:
Hannah Wolfe 2019-12-04 04:12:02 +00:00 committed by Naz Gargol
parent 60c44d360b
commit 419e12d90a
8 changed files with 76 additions and 8 deletions

View File

@ -16,7 +16,11 @@ module.exports = function navigation(options) {
options.hash = options.hash || {};
options.data = options.data || {};
var navigationData = options.data.site.navigation,
const key = options.hash.type && options.hash.type === 'secondary' ? 'secondary_navigation' : 'navigation';
options.hash.isSecondary = options.hash.type && options.hash.type === 'secondary';
delete options.hash.type;
var navigationData = options.data.site[key],
currentUrl = options.data.root.relativeUrl,
self = this,
output;

View File

@ -2,7 +2,7 @@ const _ = require('lodash');
const utils = require('../../index');
const mapper = require('./utils/mapper');
const _private = {};
const deprecatedSettings = ['force_i18n', 'permalinks'];
const deprecatedSettings = ['force_i18n', 'permalinks', 'secondary_nav'];
/**
* ### Settings Filter

View File

@ -94,6 +94,9 @@
"navigation": {
"defaultValue": "[{\"label\":\"Home\", \"url\":\"/\"},{\"label\":\"Tag\", \"url\":\"/tag/getting-started/\"}, {\"label\":\"Author\", \"url\":\"/author/ghost/\"},{\"label\":\"Help\", \"url\":\"https://ghost.org/docs/\"}]"
},
"secondary_navigation": {
"defaultValue": "[]"
},
"slack": {
"defaultValue": "[{\"url\":\"\", \"username\":\"Ghost\"}]"
},

View File

@ -20,6 +20,7 @@ module.exports = {
ghost_head: 'ghost_head',
ghost_foot: 'ghost_foot',
navigation: 'navigation',
secondary_navigation: 'secondary_navigation',
meta_title: 'meta_title',
meta_description: 'meta_description',
og_image: 'og_image',

View File

@ -47,7 +47,7 @@ describe('Settings Content API', function () {
});
publicProperties.push('codeinjection_head', 'codeinjection_foot');
settings.should.have.properties(publicProperties);
Object.keys(settings).length.should.equal(21);
Object.keys(settings).length.should.equal(22);
// Verify that we are returning the defaults for each value
_.forEach(settings, (value, key) => {
@ -76,7 +76,7 @@ describe('Settings Content API', function () {
// Convert empty strings to null
defaultValue = defaultValue || null;
if (defaultKey === 'navigation') {
if (defaultKey === 'navigation' || defaultKey === 'secondary_navigation') {
defaultValue = JSON.parse(defaultValue);
}

View File

@ -31,7 +31,8 @@ describe('{{navigation}} helper', function () {
optionsData = {
data: {
site: {
navigation: []
navigation: [],
secondary_navigation: []
},
root: {
relativeUrl: ''
@ -177,6 +178,41 @@ describe('{{navigation}} helper', function () {
should.exist(rendered);
rendered.string.should.not.containEql('foo=space%2520bar');
});
describe('type="secondary"', function () {
it('can render one item', function () {
var singleItem = {label: 'Foo', url: '/foo'},
testUrl = 'href="' + configUtils.config.get('url') + '/foo"',
rendered;
optionsData.data.site.secondary_navigation = [singleItem];
optionsData.hash = {type: 'secondary'};
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.containEql('li');
rendered.string.should.containEql('nav-foo');
rendered.string.should.containEql(testUrl);
});
it('can render multiple items', function () {
var firstItem = {label: 'Foo', url: '/foo'},
secondItem = {label: 'Bar Baz Qux', url: '/qux'},
testUrl = 'href="' + configUtils.config.get('url') + '/foo"',
testUrl2 = 'href="' + configUtils.config.get('url') + '/qux"',
rendered;
optionsData.data.site.secondary_navigation = [firstItem, secondItem];
optionsData.hash = {type: 'secondary'};
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.containEql('nav-foo');
rendered.string.should.containEql('nav-bar-baz-qux');
rendered.string.should.containEql(testUrl);
rendered.string.should.containEql(testUrl2);
});
});
});
describe('{{navigation}} helper with custom template', function () {
@ -196,7 +232,8 @@ describe('{{navigation}} helper with custom template', function () {
optionsData = {
data: {
site: {
navigation: [{label: 'Foo', url: '/foo'}]
navigation: [{label: 'Foo', url: '/foo'}],
secondary_navigation: [{label: 'Fighters', url: '/foo'}]
},
root: {
relativeUrl: ''
@ -217,6 +254,7 @@ describe('{{navigation}} helper with custom template', function () {
should.exist(rendered);
rendered.string.should.containEql('Chaos is a ladder');
rendered.string.should.not.containEql('isHeader is set');
rendered.string.should.not.containEql('Jeremy Bearimy baby!');
rendered.string.should.containEql(testUrl);
rendered.string.should.containEql('Foo');
});
@ -233,7 +271,25 @@ describe('{{navigation}} helper with custom template', function () {
should.exist(rendered);
rendered.string.should.not.containEql('Chaos is a ladder');
rendered.string.should.containEql('isHeader is set');
rendered.string.should.not.containEql('Jeremy Bearimy baby!');
rendered.string.should.containEql(testUrl);
rendered.string.should.containEql('Foo');
});
it('sets isSecondary for type=secondary', function () {
var testUrl = 'href="' + configUtils.config.get('url') + '/foo"',
rendered;
// Simulate {{navigation type="secondary"}}
optionsData.hash = {type: 'secondary'};
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.not.containEql('Chaos is a ladder');
rendered.string.should.not.containEql('isHeader is set');
rendered.string.should.containEql('Jeremy Bearimy baby!');
rendered.string.should.containEql(testUrl);
rendered.string.should.containEql('Fighters');
});
});

View File

@ -2,6 +2,8 @@
{{#if isHeader}}isHeader is set{{/if}}
{{#if isSecondary}}Jeremy Bearimy baby!{{/if}}
{{#foreach navigation}}
<a href="{{url absolute="true"}}">{{label}}</a>
{{/foreach}}

View File

@ -113,7 +113,8 @@ describe('Unit: models/settings', function () {
return models.Settings.populateDefaults()
.then(() => {
eventSpy.callCount.should.equal(84);
// 2 events per item - settings.added and settings.[name].added
eventSpy.callCount.should.equal(86);
const eventsEmitted = eventSpy.args.map(args => args[0]);
const checkEventEmitted = event => should.ok(eventsEmitted.includes(event), `${event} event should be emitted`);
@ -135,7 +136,8 @@ describe('Unit: models/settings', function () {
return models.Settings.populateDefaults()
.then(() => {
eventSpy.callCount.should.equal(82);
// 2 events per item - settings.added and settings.[name].added
eventSpy.callCount.should.equal(84);
eventSpy.args[13][0].should.equal('settings.logo.added');
});