Home Manual Reference Source Test Repository

src/Publisher/Builder/util.js

import marked from 'marked';
import escape from 'escape-html';

/**
 * shorten description.
 * e.g. ``this is JavaScript. this is Java.`` => ``this is JavaScript.``.
 *
 * @param {DocObject} doc - target doc object.
 * @param {boolean} [asMarkdown=false] - is true, test as markdown and convert to html.
 * @returns {string} shorten description.
 * @todo shorten before process markdown.
 */
export function shorten(doc, asMarkdown = false) {
  if (!doc) return '';

  if (doc.summary) return doc.summary;

  let desc = doc.descriptionRaw;
  if (!desc) return '';

  let len = desc.length;
  let inSQuote = false;
  let inWQuote = false;
  let inCode = false;
  for (let i = 0; i < desc.length; i++) {
    let char1 = desc.charAt(i);
    let char2 = desc.charAt(i + 1);
    let char4 = desc.substr(i, 6);
    let char5 = desc.substr(i, 7);
    if (char1 === "'") inSQuote = !inSQuote;
    else if (char1 === '"') inWQuote = !inWQuote;
    else if (char4 === '<code>') inCode = true;
    else if (char5 === '</code>') inCode = false;

    if (inSQuote || inCode || inWQuote) continue;

    if (char1 === '.') {
      if (char2 === ' ' || char2 === '\n' || char2 === '<') {
        len = i + 1;
        break;
      }
    } else if (char1 === '\n' && char2 === '\n') {
      len = i + 1;
      break;
    }
  }

  let result = desc.substr(0, len);
  if (asMarkdown) {
    result = markdown(result);
  }

  return result;
}

/**
 * convert markdown text to html.
 * @param {string} text - markdown text.
 * @param {boolean} [breaks=false] if true, break line. FYI gfm is not breaks.
 * @return {string} html.
 */
export function markdown(text, breaks = false) {
  const availableTags = ['span', 'a', 'p', 'div', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'br', 'hr', 'li', 'ul', 'ol', 'code', 'pre'];
  const availableAttributes = ['src', 'href', 'title', 'class', 'id', 'name', 'width', 'height'];

  let compiled = marked(text, {
    gfm: true,
    tables: true,
    breaks: breaks,
    sanitize: true,
    sanitizer: (tag) =>{
      if (tag.match(/<!--.*-->/)) {
        return tag;
      }
      const tagName = tag.match(/^<\/?(\w+)/)[1];
      if (!availableTags.includes(tagName)) {
        return escape(tag);
      }

      const sanitizedTag = tag.replace(/([\w\-]+)=(["'].*?["'])/g, (_, attr, val)=>{
        if (!availableAttributes.includes(attr)) return '';
        if (val.indexOf('javascript:') !== -1) return '';
        return `${attr}=${val}`;
      });

      return sanitizedTag;
    },
    highlight: function (code) {
      //return `<pre class="source-code"><code class="prettyprint">${escape(code)}</code></pre>`;
      return `<code class="source-code prettyprint">${escape(code)}</code>`;
    }
  });

  return compiled;
}

/**
 * get UTC date string.
 * @param {Date} date - target date object.
 * @returns {string} UTC date string(yyyy-mm-dd hh:mm:ss)
 */
export function dateForUTC(date) {
  function pad(num, len) {
    let count = Math.max(0, len - `${num}`.length);
    return '0'.repeat(count) + num;
  }

  let year = date.getUTCFullYear();
  let month = pad(date.getUTCMonth() + 1, 2);
  let day = pad(date.getUTCDay() + 1, 2);
  let hours = pad(date.getUTCHours(), 2);
  let minutes = pad(date.getUTCMinutes(), 2);
  let seconds = pad(date.getUTCSeconds(), 2);

  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} (UTC)`;
}

/**
 * parse ``@example`` value.
 * ``@example`` value can have ``<caption>`` tag.
 *
 * @param {string} example - target example value.
 * @returns {{body: string, caption: string}} parsed example value.
 */
export function parseExample(example) {
  let body = example;
  let caption = '';

  let regexp = new RegExp("^<caption>(.*?)</caption>\n");
  let matched = example.match(regexp);
  if (matched) {
    body = example.replace(regexp, '');
    caption = matched[1].trim();
  }

  return {body, caption};
}