open_graph.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. 'use strict';
  2. const { parse, resolve } = require('url');
  3. const { isMoment, isDate } = require('moment');
  4. const { encodeURL, prettyUrls, htmlTag, stripHTML, escapeHTML, Cache } = require('hexo-util');
  5. const localeMap = {
  6. 'en': 'en_US',
  7. 'de': 'de_DE',
  8. 'es': 'es_ES',
  9. 'fr': 'fr_FR',
  10. 'hu': 'hu_HU',
  11. 'id': 'id_ID',
  12. 'it': 'it_IT',
  13. 'ja': 'ja_JP',
  14. 'ko': 'ko_KR',
  15. 'nl': 'nl_NL',
  16. 'ru': 'ru_RU',
  17. 'th': 'th_TH',
  18. 'tr': 'tr_TR',
  19. 'vi': 'vi_VN'
  20. };
  21. const localeCache = new Cache();
  22. const localeToTerritory = str => localeCache.apply(str, () => {
  23. if (str.length === 2 && localeMap[str]) return localeMap[str];
  24. if (str.length === 5) {
  25. let territory = [];
  26. if (str.includes('-')) {
  27. territory = str.split('-');
  28. } else {
  29. territory = str.split('_');
  30. }
  31. if (territory.length === 2) return territory[0].toLowerCase() + '_' + territory[1].toUpperCase();
  32. }
  33. });
  34. const meta = (name, content, escape) => {
  35. if (escape !== false && typeof content === 'string') {
  36. content = escapeHTML(content);
  37. }
  38. if (content) return `<meta name="${name}" content="${content}">\n`;
  39. return `<meta name="${name}">\n`;
  40. };
  41. const og = (name, content, escape) => {
  42. if (escape !== false && typeof content === 'string') {
  43. content = escapeHTML(content);
  44. }
  45. if (content) return `<meta property="${name}" content="${content}">\n`;
  46. return `<meta property="${name}">\n`;
  47. };
  48. function openGraphHelper(options = {}) {
  49. const { config, page } = this;
  50. const { content } = page;
  51. let images = options.image || options.images || page.photos || [];
  52. let description = options.description || page.description || page.excerpt || content || config.description;
  53. let keywords = (page.tags && page.tags.length ? page.tags : undefined) || config.keywords || false;
  54. const title = options.title || page.title || config.title;
  55. const type = options.type || (this.is_post() ? 'article' : 'website');
  56. const url = prettyUrls(options.url || this.url, config.pretty_urls);
  57. const siteName = options.site_name || config.title;
  58. const twitterCard = options.twitter_card || 'summary';
  59. const date = options.date !== false ? options.date || page.date : false;
  60. const updated = options.updated !== false ? options.updated || page.updated : false;
  61. const language = options.language || page.lang || page.language || config.language;
  62. const author = options.author || config.author;
  63. if (!Array.isArray(images)) images = [images];
  64. if (description) {
  65. description = escapeHTML(stripHTML(description).substring(0, 200)
  66. .trim() // Remove prefixing/trailing spaces
  67. ).replace(/\n/g, ' '); // Replace new lines by spaces
  68. }
  69. if (!images.length && content) {
  70. images = images.slice();
  71. if (content.includes('<img')) {
  72. let img;
  73. const imgPattern = /<img [^>]*src=['"]([^'"]+)([^>]*>)/gi;
  74. while ((img = imgPattern.exec(content)) !== null) {
  75. images.push(img[1]);
  76. }
  77. }
  78. }
  79. let result = '';
  80. if (description) {
  81. result += meta('description', description);
  82. }
  83. result += og('og:type', type);
  84. result += og('og:title', title);
  85. if (url) {
  86. result += og('og:url', encodeURL(url), false);
  87. } else {
  88. result += og('og:url');
  89. }
  90. result += og('og:site_name', siteName);
  91. if (description) {
  92. result += og('og:description', description, false);
  93. }
  94. if (language) {
  95. result += og('og:locale', localeToTerritory(language), false);
  96. }
  97. images = images.map(path => {
  98. if (!parse(path).host) {
  99. // resolve `path`'s absolute path relative to current page's url
  100. // `path` can be both absolute (starts with `/`) or relative.
  101. return resolve(url || config.url, path);
  102. }
  103. return path;
  104. });
  105. images.forEach(path => {
  106. result += og('og:image', path, false);
  107. });
  108. if (date) {
  109. if ((isMoment(date) || isDate(date)) && !isNaN(date.valueOf())) {
  110. result += og('article:published_time', date.toISOString());
  111. }
  112. }
  113. if (updated) {
  114. if ((isMoment(updated) || isDate(updated)) && !isNaN(updated.valueOf())) {
  115. result += og('article:modified_time', updated.toISOString());
  116. }
  117. }
  118. if (author) {
  119. result += og('article:author', author);
  120. }
  121. if (keywords) {
  122. if (typeof keywords === 'string') keywords = [keywords];
  123. keywords.map(tag => {
  124. return tag.name ? tag.name : tag;
  125. }).filter(Boolean).forEach(keyword => {
  126. result += og('article:tag', keyword);
  127. });
  128. }
  129. result += meta('twitter:card', twitterCard);
  130. if (images.length) {
  131. result += meta('twitter:image', images[0], false);
  132. }
  133. if (options.twitter_id) {
  134. let twitterId = options.twitter_id;
  135. if (!twitterId.startsWith('@')) twitterId = `@${twitterId}`;
  136. result += meta('twitter:creator', twitterId);
  137. }
  138. if (options.twitter_site) {
  139. result += meta('twitter:site', options.twitter_site, false);
  140. }
  141. if (options.google_plus) {
  142. result += `${htmlTag('link', { rel: 'publisher', href: options.google_plus })}\n`;
  143. }
  144. if (options.fb_admins) {
  145. result += og('fb:admins', options.fb_admins);
  146. }
  147. if (options.fb_app_id) {
  148. result += og('fb:app_id', options.fb_app_id);
  149. }
  150. return result.trim();
  151. }
  152. module.exports = openGraphHelper;