You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

198 lines
5.3 KiB

'use strict';
const { parse, resolve } = require('url');
const { isMoment, isDate } = require('moment');
const { encodeURL, prettyUrls, htmlTag, stripHTML, escapeHTML } = require('hexo-util');
const { default: moize } = require('moize');
const localeMap = {
'en': 'en_US',
'de': 'de_DE',
'es': 'es_ES',
'fr': 'fr_FR',
'hu': 'hu_HU',
'id': 'id_ID',
'it': 'it_IT',
'ja': 'ja_JP',
'ko': 'ko_KR',
'nl': 'nl_NL',
'ru': 'ru_RU',
'th': 'th_TH',
'tr': 'tr_TR',
'vi': 'vi_VN'
};
const localeToTerritory = moize.shallow(str => {
if (str.length === 2 && localeMap[str]) return localeMap[str];
if (str.length === 5) {
let territory = [];
if (str.includes('-')) {
territory = str.split('-');
} else {
territory = str.split('_');
}
if (territory.length === 2) return territory[0].toLowerCase() + '_' + territory[1].toUpperCase();
}
});
const meta = (name, content, escape) => {
if (escape !== false && typeof content === 'string') {
content = escapeHTML(content);
}
if (content) return `<meta name="${name}" content="${content}">\n`;
return `<meta name="${name}">\n`;
};
const og = (name, content, escape) => {
if (escape !== false && typeof content === 'string') {
content = escapeHTML(content);
}
if (content) return `<meta property="${name}" content="${content}">\n`;
return `<meta property="${name}">\n`;
};
function openGraphHelper(options = {}) {
const { config, page } = this;
const { content } = page;
let images = options.image || options.images || page.photos || [];
let description = options.description || page.description || page.excerpt || content || config.description;
let keywords = (page.tags && page.tags.length ? page.tags : undefined) || config.keywords || false;
const title = options.title || page.title || config.title;
const type = options.type || (this.is_post() ? 'article' : 'website');
const url = prettyUrls(options.url || this.url, config.pretty_urls);
const siteName = options.site_name || config.title;
const twitterCard = options.twitter_card || 'summary';
const date = options.date !== false ? options.date || page.date : false;
const updated = options.updated !== false ? options.updated || page.updated : false;
const language = options.language || page.lang || page.language || config.language;
const author = options.author || config.author;
if (!Array.isArray(images)) images = [images];
if (description) {
description = escapeHTML(stripHTML(description).substring(0, 200)
.trim() // Remove prefixing/trailing spaces
).replace(/\n/g, ' '); // Replace new lines by spaces
}
if (!images.length && content) {
images = images.slice();
if (content.includes('<img')) {
let img;
const imgPattern = /<img [^>]*src=['"]([^'"]+)([^>]*>)/gi;
while ((img = imgPattern.exec(content)) !== null) {
images.push(img[1]);
}
}
}
let result = '';
if (description) {
result += meta('description', description);
}
result += og('og:type', type);
result += og('og:title', title);
if (url) {
result += og('og:url', encodeURL(url), false);
} else {
result += og('og:url');
}
result += og('og:site_name', siteName);
if (description) {
result += og('og:description', description, false);
}
if (language) {
result += og('og:locale', localeToTerritory(language), false);
}
images = images.map(path => {
if (!parse(path).host) {
// resolve `path`'s absolute path relative to current page's url
// `path` can be both absolute (starts with `/`) or relative.
return resolve(url || config.url, path);
}
return path;
});
images.forEach(path => {
result += og('og:image', path, false);
});
if (date) {
if ((isMoment(date) || isDate(date)) && !isNaN(date.valueOf())) {
result += og('article:published_time', date.toISOString());
}
}
if (updated) {
if ((isMoment(updated) || isDate(updated)) && !isNaN(updated.valueOf())) {
result += og('article:modified_time', updated.toISOString());
}
}
if (author) {
result += og('article:author', author);
}
if (keywords) {
if (typeof keywords === 'string') keywords = [keywords];
keywords.map(tag => {
return tag.name ? tag.name : tag;
}).filter(Boolean).forEach(keyword => {
result += og('article:tag', keyword);
});
}
result += meta('twitter:card', twitterCard);
if (options.twitter_image) {
let twitter_image = options.twitter_image;
if (!parse(twitter_image).host) {
twitter_image = resolve(url || config.url, twitter_image);
}
result += meta('twitter:image', twitter_image, false);
} else if (images.length) {
result += meta('twitter:image', images[0], false);
}
if (options.twitter_id) {
let twitterId = options.twitter_id;
if (!twitterId.startsWith('@')) twitterId = `@${twitterId}`;
result += meta('twitter:creator', twitterId);
}
if (options.twitter_site) {
result += meta('twitter:site', options.twitter_site, false);
}
if (options.google_plus) {
result += `${htmlTag('link', { rel: 'publisher', href: options.google_plus })}\n`;
}
if (options.fb_admins) {
result += og('fb:admins', options.fb_admins);
}
if (options.fb_app_id) {
result += og('fb:app_id', options.fb_app_id);
}
return result.trim();
}
module.exports = openGraphHelper;