Ghost/core/server/helpers/ghost_head.js
Markus Siemens c5fe9aa99f Rewrite meta_description and meta_title to depend upon the current context
closes #4850

- fixed `meta_description` and `meta_title` when used within a `{{#foreach}}`
- `meta_description` and `meta_title` now depend upon the current context
  to get the right string (author bio, tag description, ...).
  Note: `ghost_head.js` and `ghost_head_spec.js` have been touched to add
  the required context information when calling the helpers.
2015-03-24 22:42:45 +01:00

178 lines
7.4 KiB
JavaScript

// # Ghost Head Helper
// Usage: `{{ghost_head}}`
//
// Outputs scripts and other assets at the top of a Ghost theme
//
// We use the name ghost_head to match the helper for consistency:
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
var hbs = require('express-hbs'),
moment = require('moment'),
_ = require('lodash'),
Promise = require('bluebird'),
config = require('../config'),
filters = require('../filters'),
api = require('../api'),
urlHelper = require('./url'),
meta_description = require('./meta_description'),
meta_title = require('./meta_title'),
excerpt = require('./excerpt'),
tagsHelper = require('./tags'),
imageHelper = require('./image'),
ghost_head;
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*\/)/,
tagOrAuthorPattern = /\/(tag)|(author)\//,
trimmedUrl, next, prev, tags,
ops = [],
structuredData,
coverImage, authorImage, keywords,
schema,
title = hbs.handlebars.Utils.escapeExpression(blog.title);
trimmedVersion = trimmedVersion ? trimmedVersion.match(majorMinor)[0] : '?';
// Push Async calls to an array of promises
ops.push(urlHelper.call(self, {hash: {absolute: true}}));
ops.push(meta_description.call(self, options));
ops.push(meta_title.call(self, options));
if (self.post) {
ops.push(imageHelper.call(self.post, {hash: {absolute:true}}));
if (self.post.author) {
ops.push(imageHelper.call(self.post.author, {hash: {absolute:true}}));
}
}
// 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(),
coverImage = results.length > 3 ? results[3].value() : null,
authorImage = results.length > 4 ? results[4].value() : null,
publishedDate, modifiedDate,
tags = tagsHelper.call(self.post, {hash: {autolink: 'false'}}).string.split(','),
card = 'summary',
type, authorUrl;
if (!metaDescription) {
metaDescription = excerpt.call(self.post, {hash: {words: '40'}}).string;
}
if (tags[0] !== '') {
keywords = hbs.handlebars.Utils.escapeExpression(tagsHelper.call(self.post, {hash: {autolink: 'false', separator: ', '}}).string);
}
head.push('<link rel="canonical" href="' + url + '" />');
if (self.pagination) {
trimmedUrl = self.relativeUrl.match(trimmedUrlpattern);
if (self.pagination.prev) {
prev = (self.pagination.prev > 1 ? prev = '/page/' + self.pagination.prev + '/' : prev = '/');
prev = (trimmedUrl) ? trimmedUrl + prev : prev;
head.push('<link rel="prev" href="' + config.urlFor({relativeUrl: prev, secure: self.secure}, true) + '" />');
}
if (self.pagination.next) {
next = '/page/' + self.pagination.next + '/';
if (trimmedUrl) {
next = trimmedUrl + next;
} else if (tagOrAuthorPattern.test(self.relativeUrl)) {
next = self.relativeUrl.slice(0, -1) + next;
}
head.push('<link rel="next" href="' + config.urlFor({relativeUrl: next, secure: self.secure}, true) + '" />');
}
}
// 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();
if (coverImage) {
card = 'summary_large_image';
}
// escaped data
metaTitle = hbs.handlebars.Utils.escapeExpression(metaTitle);
metaDescription = hbs.handlebars.Utils.escapeExpression(metaDescription + '...');
authorUrl = hbs.handlebars.Utils.escapeExpression(blog.url + '/author/' + self.post.author.slug);
schema = {
'@context': 'http://schema.org',
'@type': 'Article',
publisher: title,
author: {
'@type': 'Person',
name: self.post.author.name,
image: authorImage,
url: authorUrl,
sameAs: self.post.author.website
},
headline: metaTitle,
url: url,
datePublished: publishedDate,
dateModified: modifiedDate,
image: coverImage,
keywords: keywords,
description: metaDescription
};
structuredData = {
'og:site_name': title,
'og:type': 'article',
'og:title': metaTitle,
'og:description': metaDescription,
'og:url': url,
'og:image': coverImage,
'article:published_time': publishedDate,
'article:modified_time': modifiedDate,
'article:tag': tags,
'twitter:card': card,
'twitter:title': metaTitle,
'twitter:description': metaDescription,
'twitter:url': url,
'twitter:image:src': coverImage
};
head.push('');
_.each(structuredData, function (content, property) {
if (property === 'article:tag') {
_.each(tags, function (tag) {
if (tag !== '') {
tag = hbs.handlebars.Utils.escapeExpression(tag.trim());
head.push('<meta property="' + property + '" content="' + tag + '" />');
}
});
head.push('');
} else if (content !== null && content !== undefined) {
type = property.substring(0, 7) === 'twitter' ? 'name' : 'property';
head.push('<meta ' + type + '="' + property + '" content="' + content + '" />');
}
});
head.push('');
head.push('<script type="application/ld+json">\n' + JSON.stringify(schema, null, ' ') + '\n </script>\n');
}
head.push('<meta name="generator" content="Ghost ' + trimmedVersion + '" />');
head.push('<link rel="alternate" type="application/rss+xml" title="' +
title + '" href="' + config.urlFor('rss', null, true) + '" />');
}).then(function () {
return api.settings.read({key: 'ghost_head'});
}).then(function (response) {
head.push(response.settings[0].value);
return filters.doFilter('ghost_head', head);
}).then(function (head) {
var headString = _.reduce(head, function (memo, item) { return memo + '\n ' + item; }, '');
return new hbs.handlebars.SafeString(headString.trim());
});
};
module.exports = ghost_head;