view/misc/open_graph.jsx

/**
 * A JSX component that renders Open Graph tags.
 * @module view/misc/open_graph
 *
 * @see https://hexo.io/docs/helpers#open-graph
 * @see https://github.com/hexojs/hexo/blob/4.2.0/lib/plugins/helper/open_graph.js
 */
const urlFn = require('url');
const moment = require('moment');
const { Component } = require('inferno');
const { encodeURL, stripHTML, escapeHTML } = require('hexo-util');
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 localeRegex = new RegExp(Object.keys(localeMap).join('|'), 'i');

/**
 * A JSX component that renders Open Graph tags.
 *
 * @name OpenGraph
 * @see https://hexo.io/docs/helpers#open-graph
 * @see https://github.com/hexojs/hexo/blob/4.2.0/lib/plugins/helper/open_graph.js
 * @example
 * <OpenGraph
 *     type="blog"
 *     title="Page title"
 *     language="Page language"
 *     description="Page description"
 *     date="Page publish date"
 *     updated="Page update date"
 *     author="Page author"
 *     keywords="keyword1,keyword2,..."
 *     images={[ '/path/to/image.png' ]}
 *     url="/path/to/page"
 *     siteName="Site name"
 *     twitterId="Twitter ID"
 *     twitterCard="summary"
 *     twitterSite="Twitter Site"
 *     googlePlus="/path/to/google/plus"
 *     facebookAdmins="Facebook admin ID"
 *     facebookAppId="Facebook APP ID" />
 */
module.exports = class extends Component {
  render() {
    const {
      type,
      title,
      date,
      updated,
      author,
      url,
      siteName,
      twitterCard,
      twitterSite,
      googlePlus,
      facebookAdmins,
      facebookAppId,
    } = this.props;
    let { description, language, images, keywords, twitterId } = this.props;

    const htmlTags = [];

    if (description) {
      description = escapeHTML(stripHTML(description).substring(0, 200).trim()).replace(/\n/g, ' ');
      htmlTags.push(<meta name="description" content={description} />);
    }

    htmlTags.push(<meta property="og:type" content={type || 'website'} />);
    htmlTags.push(<meta property="og:title" content={title} />);
    htmlTags.push(<meta property="og:url" content={encodeURL(url)} />);
    htmlTags.push(<meta property="og:site_name" content={siteName} />);

    if (description) {
      htmlTags.push(<meta property="og:description" content={description} />);
    }

    if (language) {
      if (language.length === 2) {
        language = language.replace(localeRegex, (str) => localeMap[str]);
        htmlTags.push(<meta property="og:locale" content={language} />);
      } else if (language.length === 5) {
        const territory = language.slice(-2);
        const territoryRegex = new RegExp(territory.concat('$'));
        language = language.replace('-', '_').replace(territoryRegex, territory.toUpperCase());
        htmlTags.push(<meta property="og:locale" content={language} />);
      }
    }

    if (!Array.isArray(images)) {
      images = [images];
    }
    images
      .map((path) => {
        if (!urlFn.parse(path).host) {
          // resolve `path`'s absolute path relative to current page's url
          // `path` can be both absolute (starts with `/`) or relative.
          return urlFn.resolve(url, path);
        }
        return path;
      })
      .forEach((path) => {
        htmlTags.push(<meta property="og:image" content={path} />);
      });

    if (date && (moment.isMoment(date) || moment.isDate(date)) && !isNaN(date.valueOf())) {
      htmlTags.push(<meta property="article:published_time" content={date.toISOString()} />);
    }

    if (
      updated &&
      (moment.isMoment(updated) || moment.isDate(updated)) &&
      !isNaN(updated.valueOf())
    ) {
      htmlTags.push(<meta property="article:modified_time" content={updated.toISOString()} />);
    }

    if (author) {
      htmlTags.push(<meta property="article:author" content={author} />);
    }

    if (keywords) {
      if (typeof keywords === 'string') {
        keywords = [keywords];
      }

      keywords
        .map((tag) => {
          return tag.name ? tag.name : tag;
        })
        .filter(Boolean)
        .forEach((keyword) => {
          htmlTags.push(<meta property="article:tag" content={keyword} />);
        });
    }

    htmlTags.push(<meta property="twitter:card" content={twitterCard || 'summary'} />);

    if (images.length) {
      let image = images[0];
      if (!urlFn.parse(image).host) {
        // resolve `path`'s absolute path relative to current page's url
        // `path` can be both absolute (starts with `/`) or relative.
        image = urlFn.resolve(url, image);
      }
      htmlTags.push(<meta property="twitter:image:src" content={image} />);
    }

    if (twitterId) {
      if (twitterId[0] !== '@') {
        twitterId = `@${twitterId}`;
      }
      htmlTags.push(<meta property="twitter:creator" content={twitterId} />);
    }

    if (twitterSite) {
      htmlTags.push(<meta property="twitter:site" content={twitterSite} />);
    }

    if (googlePlus) {
      htmlTags.push(<link rel="publisher" href={googlePlus} />);
    }

    if (facebookAdmins) {
      htmlTags.push(<meta property="fb:admins" content={facebookAdmins} />);
    }

    if (facebookAppId) {
      htmlTags.push(<meta property="fb:app_id" content={facebookAppId} />);
    }

    return <>{htmlTags}</>;
  }
};