Ghost/core/server/data/meta/schema.js
Katharina Irrgang 7845617607 Custom Post Excerpt Feature (#8792)
closes #8793

- 1.3 post excerpt migration
  - add 1.3 migration to add `excerpt` to post schema

NOTE:
    - knex-migrator relies on the package.json safe version
    - so right now Ghost is on 1.2
    - the migration script is for 1.3
    - if you pull down the PR (or if we merge this PR into master), you have to run `knex-migrator migrate --v 1.3 --force`
    - knex-migrator will tell you what you have todo

- Bump dependencies
  - knex-migrator@2.1.3
- Soft limit for custom_excerpt
- Extended {{excerpt}} to use custom excerpt
   - when a `custom_excerpt` field exists, the `{{excerpt}}` helper will output this and fall back to autogenerated excerpt if not.
- Refactored behaviour of (meta) description
   - html tag `<meta name="description" />` for posts, tags and author doesn't get rendered if not provided.
        - fallback for `author.bio` removed
        - fallback for `tag.description` removed
   - structured data and schema.org for `post` context takes the following order to render description fields:
        1. custom excerpt
        2. meta description
        3. automated excerpt (50 words)
    - updated and added tests to reflect the changes
2017-08-01 12:39:34 +04:00

190 lines
5.8 KiB
JavaScript

var config = require('../../config'),
escapeExpression = require('../../themes/engine').escapeExpression,
socialUrls = require('../../utils/social-urls'),
_ = require('lodash');
function schemaImageObject(metaDataVal) {
var imageObject;
if (!metaDataVal) {
return null;
}
if (!metaDataVal.dimensions) {
return metaDataVal.url;
}
imageObject = {
'@type': 'ImageObject',
url: metaDataVal.url,
width: metaDataVal.dimensions.width,
height: metaDataVal.dimensions.height
};
return imageObject;
}
// Creates the final schema object with values that are not null
function trimSchema(schema) {
var schemaObject = {};
_.each(schema, function (value, key) {
if (value !== null && typeof value !== 'undefined') {
schemaObject[key] = value;
}
});
return schemaObject;
}
function trimSameAs(data, context) {
var sameAs = [];
if (context === 'post') {
if (data.post.author.website) {
sameAs.push(escapeExpression(data.post.author.website));
}
if (data.post.author.facebook) {
sameAs.push(socialUrls.facebookUrl(data.post.author.facebook));
}
if (data.post.author.twitter) {
sameAs.push(socialUrls.twitterUrl(data.post.author.twitter));
}
} else if (context === 'author') {
if (data.author.website) {
sameAs.push(escapeExpression(data.author.website));
}
if (data.author.facebook) {
sameAs.push(socialUrls.facebookUrl(data.author.facebook));
}
if (data.author.twitter) {
sameAs.push(socialUrls.twitterUrl(data.author.twitter));
}
}
return sameAs;
}
function getPostSchema(metaData, data) {
// CASE: metaData.excerpt for post context is populated by either the custom excerpt, the meta description,
// or the automated excerpt of 50 words. It is empty for any other context.
var description = metaData.excerpt ? escapeExpression(metaData.excerpt) : null,
schema;
schema = {
'@context': 'https://schema.org',
'@type': 'Article',
publisher: {
'@type': 'Organization',
name: escapeExpression(metaData.blog.title),
logo: schemaImageObject(metaData.blog.logo) || null
},
author: {
'@type': 'Person',
name: escapeExpression(data.post.author.name),
image: schemaImageObject(metaData.authorImage),
url: metaData.authorUrl,
sameAs: trimSameAs(data, 'post'),
description: data.post.author.metaDescription ?
escapeExpression(data.post.author.metaDescription) :
null
},
headline: escapeExpression(metaData.metaTitle),
url: metaData.url,
datePublished: metaData.publishedDate,
dateModified: metaData.modifiedDate,
image: schemaImageObject(metaData.coverImage),
keywords: metaData.keywords && metaData.keywords.length > 0 ?
metaData.keywords.join(', ') : null,
description: description,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': metaData.blog.url || null
}
};
schema.author = trimSchema(schema.author);
return trimSchema(schema);
}
function getHomeSchema(metaData) {
var schema = {
'@context': 'https://schema.org',
'@type': 'Website',
publisher: {
'@type': 'Organization',
name: escapeExpression(metaData.blog.title),
logo: schemaImageObject(metaData.blog.logo) || null
},
url: metaData.url,
image: schemaImageObject(metaData.coverImage),
mainEntityOfPage: {
'@type': 'WebPage',
'@id': metaData.blog.url || null
},
description: metaData.metaDescription ?
escapeExpression(metaData.metaDescription) :
null
};
return trimSchema(schema);
}
function getTagSchema(metaData, data) {
var schema = {
'@context': 'https://schema.org',
'@type': 'Series',
publisher: {
'@type': 'Organization',
name: escapeExpression(metaData.blog.title),
logo: schemaImageObject(metaData.blog.logo) || null
},
url: metaData.url,
image: schemaImageObject(metaData.coverImage),
name: data.tag.name,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': metaData.blog.url || null
},
description: metaData.metaDescription ?
escapeExpression(metaData.metaDescription) :
null
};
return trimSchema(schema);
}
function getAuthorSchema(metaData, data) {
var schema = {
'@context': 'https://schema.org',
'@type': 'Person',
sameAs: trimSameAs(data, 'author'),
name: escapeExpression(data.author.name),
url: metaData.authorUrl,
image: schemaImageObject(metaData.coverImage),
mainEntityOfPage: {
'@type': 'WebPage',
'@id': metaData.blog.url || null
},
description: metaData.metaDescription ?
escapeExpression(metaData.metaDescription) :
null
};
return trimSchema(schema);
}
function getSchema(metaData, data) {
if (!config.isPrivacyDisabled('useStructuredData')) {
var context = data.context ? data.context : null;
if (_.includes(context, 'post') || _.includes(context, 'page') || _.includes(context, 'amp')) {
return getPostSchema(metaData, data);
} else if (_.includes(context, 'home')) {
return getHomeSchema(metaData);
} else if (_.includes(context, 'tag')) {
return getTagSchema(metaData, data);
} else if (_.includes(context, 'author')) {
return getAuthorSchema(metaData, data);
}
}
return null;
}
module.exports = getSchema;