"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.htmlToSlate = htmlToSlate;
exports.markdownToHtml = markdownToHtml;
exports.markdownToRemark = markdownToRemark;
exports.markdownToSlate = markdownToSlate;
exports.remarkToMarkdown = remarkToMarkdown;
exports.slateToMarkdown = slateToMarkdown;
var _trimEnd2 = _interopRequireDefault(require("lodash/trimEnd"));
var _unified = _interopRequireDefault(require("unified"));
var _unistBuilder = _interopRequireDefault(require("unist-builder"));
var _remarkParse = _interopRequireDefault(require("remark-parse"));
var _remarkStringify = _interopRequireDefault(require("remark-stringify"));
var _remarkRehype = _interopRequireDefault(require("remark-rehype"));
var _rehypeStringify = _interopRequireDefault(require("rehype-stringify"));
var _rehypeParse = _interopRequireDefault(require("rehype-parse"));
var _rehypeRemark = _interopRequireDefault(require("rehype-remark"));
var _remarkRehypeShortcodes = _interopRequireDefault(require("./remarkRehypeShortcodes"));
var _rehypePaperEmoji = _interopRequireDefault(require("./rehypePaperEmoji"));
var _remarkAssertParents = _interopRequireDefault(require("./remarkAssertParents"));
var _remarkPaddedLinks = _interopRequireDefault(require("./remarkPaddedLinks"));
var _remarkWrapHtml = _interopRequireDefault(require("./remarkWrapHtml"));
var _remarkSlate = _interopRequireDefault(require("./remarkSlate"));
var _remarkSquashReferences = _interopRequireDefault(require("./remarkSquashReferences"));
var _remarkShortcodes = require("./remarkShortcodes");
var _remarkEscapeMarkdownEntities = _interopRequireDefault(require("./remarkEscapeMarkdownEntities"));
var _remarkStripTrailingBreaks = _interopRequireDefault(require("./remarkStripTrailingBreaks"));
var _remarkAllowHtmlEntities = _interopRequireDefault(require("./remarkAllowHtmlEntities"));
var _slateRemark = _interopRequireDefault(require("./slateRemark"));
var _MarkdownControl = require("../MarkdownControl");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
 * This module contains all serializers for the Markdown widget.
 *
 * The value of a Markdown widget is transformed to various formats during
 * editing, and these formats are referenced throughout serializer source
 * documentation. Below is brief glossary of the formats used.
 *
 * - Markdown {string}
 *   The stringified Markdown value. The value of the field is persisted
 *   (stored) in this format, and the stringified value is also used when the
 *   editor is in "raw" Markdown mode.
 *
 * - MDAST {object}
 *   Also loosely referred to as "Remark". MDAST stands for MarkDown AST
 *   (Abstract Syntax Tree), and is an object representation of a Markdown
 *   document. Underneath, it's a Unist tree with a Markdown-specific schema.
 *   MDAST syntax is a part of the Unified ecosystem, and powers the Remark
 *   processor, so Remark plugins may be used.
 *
 * - HAST {object}
 *   Also loosely referred to as "Rehype". HAST, similar to MDAST, is an object
 *   representation of an HTML document.  The field value takes this format
 *   temporarily before the document is stringified to HTML.
 *
 * - HTML {string}
 *   The field value is stringified to HTML for preview purposes - the HTML value
 *   is never parsed, it is output only.
 *
 * - Slate Raw AST {object}
 *   Slate's Raw AST is a very simple and unopinionated object representation of
 *   a document in a Slate editor. We define our own Markdown-specific schema
 *   for serialization to/from Slate's Raw AST and MDAST.
 */

/**
 * Deserialize a Markdown string to an MDAST.
 */
function markdownToRemark(markdown, remarkPlugins) {
  const processor = (0, _unified.default)().use(_remarkParse.default, {
    fences: true,
    commonmark: true
  }).use(markdownToRemarkRemoveTokenizers, {
    inlineTokenizers: ['url']
  }).use(_remarkShortcodes.remarkParseShortcodes, {
    plugins: (0, _MarkdownControl.getEditorComponents)()
  }).use(_remarkAllowHtmlEntities.default).use(_remarkSquashReferences.default).use(remarkPlugins);

  /**
   * Parse the Markdown string input to an MDAST.
   */
  const parsed = processor.parse(markdown);

  /**
   * Further transform the MDAST with plugins.
   */
  const result = processor.runSync(parsed);
  return result;
}

/**
 * Remove named tokenizers from the parser, effectively deactivating them.
 */
function markdownToRemarkRemoveTokenizers({
  inlineTokenizers
}) {
  inlineTokenizers && inlineTokenizers.forEach(tokenizer => {
    delete this.Parser.prototype.inlineTokenizers[tokenizer];
  });
}

/**
 * Serialize an MDAST to a Markdown string.
 */
function remarkToMarkdown(obj, remarkPlugins) {
  /**
   * Rewrite the remark-stringify text visitor to simply return the text value,
   * without encoding or escaping any characters. This means we're completely
   * trusting the markdown that we receive.
   */
  function remarkAllowAllText() {
    const Compiler = this.Compiler;
    const visitors = Compiler.prototype.visitors;
    visitors.text = node => node.value;
  }

  /**
   * Provide an empty MDAST if no value is provided.
   */
  const mdast = obj || (0, _unistBuilder.default)('root', [(0, _unistBuilder.default)('paragraph', [(0, _unistBuilder.default)('text', '')])]);
  const remarkToMarkdownPluginOpts = {
    commonmark: true,
    fences: true,
    listItemIndent: '1',
    /**
     * Use asterisk for everything, it's the most versatile. Eventually using
     * other characters should be an option.
     */
    bullet: '*',
    emphasis: '*',
    strong: '*',
    rule: '-'
  };
  const processor = (0, _unified.default)().use({
    settings: remarkToMarkdownPluginOpts
  }).use(_remarkEscapeMarkdownEntities.default).use(_remarkStripTrailingBreaks.default).use(_remarkStringify.default).use(remarkAllowAllText).use((0, _remarkShortcodes.createRemarkShortcodeStringifier)({
    plugins: (0, _MarkdownControl.getEditorComponents)()
  })).use(remarkPlugins);

  /**
   * Transform the MDAST with plugins.
   */
  const processedMdast = processor.runSync(mdast);

  /**
   * Serialize the MDAST to markdown.
   */
  const markdown = processor.stringify(processedMdast).replace(/\r?/g, '');

  /**
   * Return markdown with trailing whitespace removed.
   */
  return (0, _trimEnd2.default)(markdown);
}

/**
 * Convert Markdown to HTML.
 */
function markdownToHtml(markdown, {
  getAsset,
  resolveWidget,
  remarkPlugins = []
} = {}) {
  const mdast = markdownToRemark(markdown, remarkPlugins);
  const hast = (0, _unified.default)().use(_remarkRehypeShortcodes.default, {
    plugins: (0, _MarkdownControl.getEditorComponents)(),
    getAsset,
    resolveWidget
  }).use(_remarkRehype.default, {
    allowDangerousHTML: true
  }).runSync(mdast);
  const html = (0, _unified.default)().use(_rehypeStringify.default, {
    allowDangerousHtml: true,
    allowDangerousCharacters: true,
    closeSelfClosing: true,
    entities: {
      useNamedReferences: true
    }
  }).stringify(hast);
  return html;
}

/**
 * Deserialize an HTML string to Slate's Raw AST. Currently used for HTML
 * pastes.
 */
function htmlToSlate(html) {
  const hast = (0, _unified.default)().use(_rehypeParse.default, {
    fragment: true
  }).parse(html);
  const mdast = (0, _unified.default)().use(_rehypePaperEmoji.default).use(_rehypeRemark.default, {
    minify: false
  }).runSync(hast);
  const slateRaw = (0, _unified.default)().use(_remarkAssertParents.default).use(_remarkPaddedLinks.default).use(_remarkWrapHtml.default).use(_remarkSlate.default).runSync(mdast);
  return slateRaw;
}

/**
 * Convert Markdown to Slate's Raw AST.
 */
function markdownToSlate(markdown, {
  voidCodeBlock,
  remarkPlugins = []
} = {}) {
  const mdast = markdownToRemark(markdown, remarkPlugins);
  const slateRaw = (0, _unified.default)().use(_remarkWrapHtml.default).use(_remarkSlate.default, {
    voidCodeBlock
  }).runSync(mdast);
  return slateRaw.children;
}

/**
 * Convert a Slate Raw AST to Markdown.
 *
 * Requires shortcode plugins to parse shortcode nodes back to text.
 *
 * Note that Unified is not utilized for the conversion from Slate's Raw AST to
 * MDAST. The conversion is manual because Unified can only operate on Unist
 * trees.
 */
function slateToMarkdown(raw, {
  voidCodeBlock,
  remarkPlugins = []
} = {}) {
  const mdast = (0, _slateRemark.default)(raw, {
    voidCodeBlock
  });
  const markdown = remarkToMarkdown(mdast, remarkPlugins);
  return markdown;
}