Added open graph tags for ghost head helper
issue #3900 - uses isPrivacyDisabled helper to see if useStructuredData has been disabled in config.js - adds an array of promises to deal with asynchronous data - resolves asynchronous data then adds open graph tags after canonical link - featured image and tags are only added if present - open graph tags only added on post and page - adds unit test to check correct data is returned - updates other unit tests to reflect changes
This commit is contained in:
parent
58ec6e0ac9
commit
487297ff81
@ -41,3 +41,10 @@ RPC pings only happen when Ghost is running in the `production` environment.
|
||||
### Sharing Buttons
|
||||
|
||||
The default theme which comes with Ghost contains three sharing buttons to [Twitter](http://twitter.com), [Facebook](http://facebook.com), and [Google Plus](http://plus.google.com). No resources are loaded from any services, however the buttons do allow visitors to your blog to share your content publicly on these respective networks.
|
||||
|
||||
|
||||
### Structured Data
|
||||
|
||||
Ghost outputs Meta data for your blog that allows published content to be more easily machine-readable. This allows content to be easily discoverable in search engines as well as popular social networks where blog posts are typically shared.
|
||||
|
||||
This includes output for post.hbs in {{ghost_head}} based on the Open Graph protocol specification.
|
@ -131,7 +131,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
|
||||
|
||||
if (placeholder.length > 156) {
|
||||
// Limit to 156 characters
|
||||
placeholder = placeholder.substring(0,156).trim();
|
||||
placeholder = placeholder.substring(0, 156).trim();
|
||||
placeholder = Ember.Handlebars.Utils.escapeExpression(placeholder);
|
||||
placeholder = new Ember.Handlebars.SafeString(placeholder + '…');
|
||||
}
|
||||
|
@ -491,20 +491,33 @@ coreHelpers.ghost_head = function (options) {
|
||||
/*jshint unused:false*/
|
||||
var self = this,
|
||||
blog = config.theme,
|
||||
useStructuredData = !config.isPrivacyDisabled('useStructuredData'),
|
||||
head = [],
|
||||
majorMinor = /^(\d+\.)?(\d+)/,
|
||||
trimmedVersion = this.version,
|
||||
trimmedUrlpattern = /.+(?=\/page\/\d*\/)/,
|
||||
trimmedUrl, next, prev;
|
||||
trimmedUrl, next, prev, tags,
|
||||
ops = [],
|
||||
structuredData;
|
||||
|
||||
trimmedVersion = trimmedVersion ? trimmedVersion.match(majorMinor)[0] : '?';
|
||||
|
||||
head.push('<meta name="generator" content="Ghost ' + trimmedVersion + '" />');
|
||||
// Push Async calls to an array of promises
|
||||
ops.push(coreHelpers.url.call(self, {hash: {absolute: true}}));
|
||||
ops.push(coreHelpers.meta_description.call(self));
|
||||
ops.push(coreHelpers.meta_title.call(self));
|
||||
|
||||
head.push('<link rel="alternate" type="application/rss+xml" title="' +
|
||||
_.escape(blog.title) + '" href="' + config.urlFor('rss') + '">');
|
||||
// Resolves promises then push pushes meta data into ghost_head
|
||||
return Promise.settle(ops).then(function (results) {
|
||||
var url = results[0].value(),
|
||||
metaDescription = results[1].value(),
|
||||
metaTitle = results[2].value(),
|
||||
publishedDate, modifiedDate;
|
||||
|
||||
if (!metaDescription) {
|
||||
metaDescription = coreHelpers.excerpt.call(self.post, {hash: {words: '40'}});
|
||||
}
|
||||
|
||||
return coreHelpers.url.call(self, {hash: {absolute: true}}).then(function (url) {
|
||||
head.push('<link rel="canonical" href="' + url + '" />');
|
||||
|
||||
if (self.pagination) {
|
||||
@ -521,9 +534,44 @@ coreHelpers.ghost_head = function (options) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test to see if we are on a post page and that Structured data has not been disabled in config.js
|
||||
if (self.post && useStructuredData) {
|
||||
publishedDate = moment(self.post.published_at).toISOString();
|
||||
modifiedDate = moment(self.post.updated_at).toISOString();
|
||||
|
||||
structuredData = {
|
||||
'og:site_name': _.escape(blog.title),
|
||||
'og:type': 'article',
|
||||
'og:title': metaTitle,
|
||||
'og:description': metaDescription + '...',
|
||||
'og:url': url,
|
||||
'article:published_time': publishedDate,
|
||||
'article:modified_time': modifiedDate
|
||||
};
|
||||
|
||||
if (self.post.image) {
|
||||
structuredData['og:image'] = _.escape(blog.url) + self.post.image;
|
||||
}
|
||||
|
||||
_.each(structuredData, function (content, property) {
|
||||
head.push('<meta property="' + property + '" content="' + content + '" />');
|
||||
});
|
||||
|
||||
// Calls tag helper and assigns an array of tag names for a post
|
||||
tags = coreHelpers.tags.call(self.post, {hash: {autolink: 'false'}}).string.split(',');
|
||||
|
||||
_.each(tags, function (tag) {
|
||||
if (tag !== '') {
|
||||
head.push('<meta property="article:tag" content="' + tag.trim() + '" />');
|
||||
}
|
||||
});
|
||||
}
|
||||
head.push('<meta name="generator" content="Ghost ' + trimmedVersion + '" />');
|
||||
head.push('<link rel="alternate" type="application/rss+xml" title="' +
|
||||
_.escape(blog.title) + '" href="' + config.urlFor('rss') + '" />');
|
||||
return filters.doFilter('ghost_head', head);
|
||||
}).then(function (head) {
|
||||
var headString = _.reduce(head, function (memo, item) { return memo + '\n' + item; }, '');
|
||||
var headString = _.reduce(head, function (memo, item) { return memo + '\n ' + item; }, '');
|
||||
return new hbs.handlebars.SafeString(headString.trim());
|
||||
});
|
||||
};
|
||||
@ -539,7 +587,7 @@ coreHelpers.ghost_foot = function (options) {
|
||||
}));
|
||||
|
||||
return filters.doFilter('ghost_foot', foot).then(function (foot) {
|
||||
var footString = _.reduce(foot, function (memo, item) { return memo + ' ' + item; }, '');
|
||||
var footString = _.reduce(foot, function (memo, item) { return memo + '\n' + item; }, '\n');
|
||||
return new hbs.handlebars.SafeString(footString.trim());
|
||||
});
|
||||
};
|
||||
|
@ -567,11 +567,11 @@ describe('Core Helpers', function () {
|
||||
|
||||
it('returns meta tag string', function (done) {
|
||||
config.set({url: 'http://testurl.com/'});
|
||||
helpers.ghost_head.call({version: '0.3.0'}).then(function (rendered) {
|
||||
helpers.ghost_head.call({version: '0.3.0', post: false}).then(function (rendered) {
|
||||
should.exist(rendered);
|
||||
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
|
||||
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
|
||||
'<link rel="canonical" href="http://testurl.com/" />');
|
||||
rendered.string.should.equal('<link rel="canonical" href="http://testurl.com/" />\n' +
|
||||
' <meta name="generator" content="Ghost 0.3" />\n' +
|
||||
' <link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/" />');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
@ -581,9 +581,41 @@ describe('Core Helpers', function () {
|
||||
config.set({url: 'http://testurl.com/'});
|
||||
helpers.ghost_head.call({version: '0.9'}).then(function (rendered) {
|
||||
should.exist(rendered);
|
||||
rendered.string.should.equal('<meta name="generator" content="Ghost 0.9" />\n' +
|
||||
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
|
||||
'<link rel="canonical" href="http://testurl.com/" />');
|
||||
rendered.string.should.equal('<link rel="canonical" href="http://testurl.com/" />\n' +
|
||||
' <meta name="generator" content="Ghost 0.9" />\n' +
|
||||
' <link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/" />');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('returns open graph data on post page', function (done) {
|
||||
config.set({url: 'http://testurl.com/'});
|
||||
var post = {
|
||||
meta_description: 'blog description',
|
||||
title: 'Welcome to Ghost',
|
||||
image: '/test-image.png',
|
||||
published_at: moment('2008-05-31T19:18:15').toISOString(),
|
||||
updated_at: moment('2014-10-06T15:23:54').toISOString(),
|
||||
tags: [{name: 'tag1'}, {name: 'tag2'}, {name: 'tag3'}]
|
||||
};
|
||||
|
||||
helpers.ghost_head.call({relativeUrl: '/post/', version: '0.3.0', post: post}).then(function (rendered) {
|
||||
should.exist(rendered);
|
||||
rendered.string.should.equal('<link rel="canonical" href="http://testurl.com/post/" />\n' +
|
||||
' <meta property="og:site_name" content="Ghost" />\n' +
|
||||
' <meta property="og:type" content="article" />\n' +
|
||||
' <meta property="og:title" content="Welcome to Ghost" />\n' +
|
||||
' <meta property="og:description" content="blog description..." />\n' +
|
||||
' <meta property="og:url" content="http://testurl.com/post/" />\n' +
|
||||
' <meta property="article:published_time" content="' + post.published_at + '" />\n' +
|
||||
' <meta property="article:modified_time" content="' + post.updated_at + '" />\n' +
|
||||
' <meta property="og:image" content="http://testurl.com/test-image.png" />\n' +
|
||||
' <meta property="article:tag" content="tag1" />\n' +
|
||||
' <meta property="article:tag" content="tag2" />\n' +
|
||||
' <meta property="article:tag" content="tag3" />\n' +
|
||||
' <meta name="generator" content="Ghost 0.3" />\n' +
|
||||
' <link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/" />');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
@ -593,9 +625,9 @@ describe('Core Helpers', function () {
|
||||
config.set({url: 'http://testurl.com/blog/'});
|
||||
helpers.ghost_head.call({version: '0.3.0'}).then(function (rendered) {
|
||||
should.exist(rendered);
|
||||
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
|
||||
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/blog/rss/">\n' +
|
||||
'<link rel="canonical" href="http://testurl.com/blog/" />');
|
||||
rendered.string.should.equal('<link rel="canonical" href="http://testurl.com/blog/" />\n' +
|
||||
' <meta name="generator" content="Ghost 0.3" />\n' +
|
||||
' <link rel="alternate" type="application/rss+xml" title="Ghost" href="/blog/rss/" />');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
@ -605,9 +637,9 @@ describe('Core Helpers', function () {
|
||||
config.set({url: 'http://testurl.com'});
|
||||
helpers.ghost_head.call({version: '0.3.0', relativeUrl: '/about/'}).then(function (rendered) {
|
||||
should.exist(rendered);
|
||||
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
|
||||
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
|
||||
'<link rel="canonical" href="http://testurl.com/about/" />');
|
||||
rendered.string.should.equal('<link rel="canonical" href="http://testurl.com/about/" />\n' +
|
||||
' <meta name="generator" content="Ghost 0.3" />\n' +
|
||||
' <link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/" />');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
@ -617,11 +649,11 @@ describe('Core Helpers', function () {
|
||||
config.set({url: 'http://testurl.com'});
|
||||
helpers.ghost_head.call({version: '0.3.0', relativeUrl: '/page/3/', pagination: {next: '4', prev: '2'}}).then(function (rendered) {
|
||||
should.exist(rendered);
|
||||
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
|
||||
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
|
||||
'<link rel="canonical" href="http://testurl.com/page/3/" />\n' +
|
||||
'<link rel="prev" href="http://testurl.com/page/2/" />\n' +
|
||||
'<link rel="next" href="http://testurl.com/page/4/" />');
|
||||
rendered.string.should.equal('<link rel="canonical" href="http://testurl.com/page/3/" />\n' +
|
||||
' <link rel="prev" href="http://testurl.com/page/2/" />\n' +
|
||||
' <link rel="next" href="http://testurl.com/page/4/" />\n' +
|
||||
' <meta name="generator" content="Ghost 0.3" />\n' +
|
||||
' <link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/" />');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
@ -630,11 +662,11 @@ describe('Core Helpers', function () {
|
||||
config.set({url: 'http://testurl.com'});
|
||||
helpers.ghost_head.call({version: '0.3.0', relativeUrl: '/page/2/', pagination: {next: '3', prev: '1'}}).then(function (rendered) {
|
||||
should.exist(rendered);
|
||||
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
|
||||
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
|
||||
'<link rel="canonical" href="http://testurl.com/page/2/" />\n' +
|
||||
'<link rel="prev" href="http://testurl.com/" />\n' +
|
||||
'<link rel="next" href="http://testurl.com/page/3/" />');
|
||||
rendered.string.should.equal('<link rel="canonical" href="http://testurl.com/page/2/" />\n' +
|
||||
' <link rel="prev" href="http://testurl.com/" />\n' +
|
||||
' <link rel="next" href="http://testurl.com/page/3/" />\n' +
|
||||
' <meta name="generator" content="Ghost 0.3" />\n' +
|
||||
' <link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/" />');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user