"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.valueToHtml = exports.reduceMultipleBrs = exports.reactAttributes = exports.parseStyleString = exports.htmlToValue = exports.getBase64 = exports.TEXT_RULE = exports.BLOCK_TAGS = void 0;

var _slateHtmlSerializer = _interopRequireDefault(require("slate-html-serializer"));

var _react = _interopRequireDefault(require("react"));

var _debug = _interopRequireDefault(require("debug"));

var _toStyle = require("to-style");

var _image = require("./plugins/image");

var _math = require("./plugins/math");

var _media = require("./plugins/media");

var _list = require("./plugins/list");

var _table = require("./plugins/table");

var _respArea = require("./plugins/respArea");

var _slate = require("slate");

var log = (0, _debug["default"])('@pie-lib:editable-html:serialization');
/**
 * Tags to blocks.
 *
 * @type {Object}
 */

var BLOCK_TAGS = {
  div: 'div',
  span: 'span',
  p: 'paragraph',
  blockquote: 'quote',
  pre: 'code',
  h1: 'heading-one',
  h2: 'heading-two',
  h3: 'heading-three',
  h4: 'heading-four',
  h5: 'heading-five',
  h6: 'heading-six'
};
/**
 * Tags to marks.
 *
 * @type {Object}
 */

exports.BLOCK_TAGS = BLOCK_TAGS;
var MARK_TAGS = {
  b: 'bold',
  em: 'italic',
  u: 'underline',
  s: 'strikethrough',
  code: 'code',
  strong: 'bold'
};

var parseStyleString = function parseStyleString(s) {
  var regex = /([\w-]*)\s*:\s*([^;]*)/g;
  var match;
  var result = {};

  while (match = regex.exec(s)) {
    result[match[1]] = match[2].trim();
  }

  return result;
};

exports.parseStyleString = parseStyleString;

var getBase64 = function getBase64(file) {
  return new Promise(function (resolve, reject) {
    var reader = new FileReader();
    reader.readAsDataURL(file);

    reader.onload = function () {
      return resolve(reader.result);
    };

    reader.onerror = function (error) {
      return reject(error);
    };
  });
};

exports.getBase64 = getBase64;

var reactAttributes = function reactAttributes(o) {
  return (0, _toStyle.object)(o, {
    camelize: true,
    addUnits: false
  });
};

exports.reactAttributes = reactAttributes;

var attributesToMap = function attributesToMap(el) {
  return function (acc, attribute) {
    var value = el.getAttribute(attribute);

    if (value) {
      if (attribute === 'style') {
        var styleString = el.getAttribute(attribute);
        var reactStyleObject = reactAttributes(parseStyleString(styleString));
        acc['style'] = reactStyleObject;
      } else {
        acc[attribute] = el.getAttribute(attribute);
      }
    }

    return acc;
  };
};

var attributes = ['border', 'cellpadding', 'cellspacing', 'class', 'style'];
/**
 * Serializer rules.
 *
 * @type {Array}
 */

var blocks = {
  deserialize: function deserialize(el, next) {
    log('[blocks:deserialize] block: ', el);
    var block = BLOCK_TAGS[el.tagName.toLowerCase()];
    if (!block) return;
    log('[blocks:deserialize] block: ', block);

    if (el.childNodes.length === 1) {
      var cn = el.childNodes[0];

      if (cn && cn.tagName && cn.tagName.toLowerCase() === block) {
        log('[we have a child node of the same]...');
        return;
      }
    }

    return {
      object: 'block',
      type: block,

      /**
       * Here for rendering styles for all block elements
       */
      data: {
        attributes: attributes.reduce(attributesToMap(el), {})
      },
      nodes: next(el.childNodes)
    };
  },
  serialize: function serialize(object, children) {
    if (object.object !== 'block') return;
    var jsonData = object.data.toJSON();
    log('[blocks:serialize] object: ', object, children);
    var key;

    for (key in BLOCK_TAGS) {
      if (BLOCK_TAGS[key] === object.type) {
        var Tag = key;
        return /*#__PURE__*/_react["default"].createElement(Tag, jsonData.attributes, children);
      }
    }
  }
};
var marks = {
  deserialize: function deserialize(el, next) {
    var mark = MARK_TAGS[el.tagName.toLowerCase()];
    if (!mark) return;
    log('[deserialize] mark: ', mark);
    return {
      object: 'mark',
      type: mark,
      nodes: next(el.childNodes)
    };
  },
  serialize: function serialize(object, children) {
    if (_slate.Mark.isMark(object)) {
      for (var key in MARK_TAGS) {
        if (MARK_TAGS[key] === object.type) {
          var Tag = key;
          return /*#__PURE__*/_react["default"].createElement(Tag, null, children);
        }
      }
    }
  }
}; // eslint-disable-next-line no-unused-vars

var findPreviousText = function findPreviousText(el) {
  if (el.nodeName === '#text') {
    return el;
  }

  if (el.previousSibling) {
    return findPreviousText(el.previousSibling);
  }

  return null;
};

var TEXT_RULE = {
  deserialize: function deserialize(el) {
    /**
     * This needs to be called on the dom element in order to merge the adjacent text nodes together
     * */
    el.normalize();

    if (el.tagName && el.tagName.toLowerCase() === 'br') {
      return {
        object: 'text',
        leaves: [{
          object: 'leaf',
          text: '\n'
        }]
      };
    }

    if (el.nodeName === '#text') {
      if (el.nodeValue && el.nodeValue.match(/<!--.*?-->/)) return;
      log('[text:deserialize] return text object..');
      return {
        object: 'text',
        leaves: [{
          object: 'leaf',
          text: el.nodeValue
        }]
      };
    }
  },
  serialize: function serialize(obj, children) {
    if (obj.object === 'string') {
      return children.split('\n').reduce(function (array, text, i) {
        if (i !== 0) array.push( /*#__PURE__*/_react["default"].createElement("br", null));
        array.push(text);
        return array;
      }, []);
    }
  }
};
exports.TEXT_RULE = TEXT_RULE;
var RULES = [_list.serialization, _math.serialization, _media.serialization, _image.serialization, _table.serialization, _respArea.serialization, TEXT_RULE, blocks, marks];

function allWhitespace(node) {
  // Use ECMA-262 Edition 3 String and RegExp features
  return !/[^\t\n\r ]/.test(node.textContent);
}

function defaultParseHtml(html) {
  if (typeof DOMParser === 'undefined') {
    throw new Error('The native `DOMParser` global which the `Html` serializer uses by default is not present in this environment. You must supply the `options.parseHtml` function instead.');
  }

  var parsed = new DOMParser().parseFromString(html, 'text/html');
  var body = parsed.body;
  var textNodes = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, null);
  var n = textNodes.nextNode();

  while (n) {
    if (allWhitespace(n) || n.nodeValue === "\u200B") {
      n.parentNode.removeChild(n);
    }

    n = textNodes.nextNode();
  }

  return body;
}
/** If this lib is used on the server side, we need to bypass using the DOMParser - just put in a stub. */


var parseHtml = typeof window === 'undefined' ? function () {
  return {
    childNodes: []
  };
} : defaultParseHtml;
var serializer = new _slateHtmlSerializer["default"]({
  defaultBlock: 'div',
  rules: RULES,
  parseHtml: parseHtml
});

var _extends = Object.assign || function (target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];

    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
  }

  return target;
};
/**
 * This is needed in order to override the function that eventually leads
 * to the max iteration of 12, which in most cases it's not enough. The newer versions
 * have a different way to calculate this, but updating to a newer version of slate
 * requires a lot of work fixing other issues. So we just increase the rules by 1000,
 * which means a lot of iterations.
 * Below is the code that calculates the max iterations.
 * var max = schema.stack.plugins.length + schema.rules.length + 1;
 */


serializer.deserialize = function deserialize(html) {
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  var _options$toJSON = options.toJSON;
  var toJSON = _options$toJSON === undefined ? false : _options$toJSON;
  var defaultBlock = this.defaultBlock;
  var parseHtml = this.parseHtml;
  var fragment = parseHtml(html);
  var children = Array.from(fragment.childNodes);
  var nodes = this.deserializeElements(children); // COMPAT: ensure that all top-level inline nodes are wrapped into a block.

  nodes = nodes.reduce(function (memo, node, i, original) {
    if (node.object === 'block') {
      memo.push(node);
      return memo;
    }

    if (i > 0 && original[i - 1].object !== 'block') {
      var _block = memo[memo.length - 1];

      _block.nodes.push(node);

      return memo;
    }

    var block = _extends({
      object: 'block',
      data: {},
      isVoid: false
    }, defaultBlock, {
      nodes: [node]
    });

    memo.push(block);
    return memo;
  }, []);

  if (nodes.length === 0) {
    nodes = [_extends({
      object: 'block',
      data: {},
      isVoid: false
    }, defaultBlock, {
      nodes: [{
        object: 'text',
        leaves: [{
          object: 'leaf',
          text: '',
          marks: []
        }]
      }]
    })];
  }

  var json = {
    object: 'value',
    document: {
      object: 'document',
      data: {},
      nodes: nodes
    },
    schema: {
      rules: []
    }
  };
  var i;

  for (i = 0; i < 3000; i++) {
    json.schema.rules.push({
      match: {
        object: 'document'
      },
      nodes: [{
        match: {
          object: 'block'
        }
      }]
    });
  }

  var ret = toJSON ? json : _slate.Value.fromJSON(json);

  if (ret) {
    return ret;
  }

  return null;
};

var reduceMultipleBrs = function reduceMultipleBrs(markup) {
  try {
    return markup.replace(/(<br\s*\/?>){3,}/gi, '<br>');
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log("Couldn't remove <br/> tags: ", e);
  }

  return markup;
};

exports.reduceMultipleBrs = reduceMultipleBrs;

var reduceRedundantNewLineCharacters = function reduceRedundantNewLineCharacters(markup) {
  try {
    return markup.replace(/\n/gi, '');
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log("Couldn't remove <br/> tags: ", e);
  }

  return markup;
};

var wrapHtmlProperly = function wrapHtmlProperly(markup) {
  var el = document.createElement('div');
  el.innerHTML = markup;
  /**
   * DIV elements that are at the same level as paragraphs
   * are replaced with P elements for normalizing purposes
   * @param el
   */

  var parseNode = function parseNode(el) {
    var childArray = Array.from(el.children);
    var hasParagraphs = childArray.find(function (child) {
      return child.nodeName === 'P';
    });
    childArray.forEach(function (child) {
      // removing empty blocks
      if ((child.nodeName === 'DIV' || child.nodeName === 'P') && child.childNodes.length === 0) {
        child.remove();
        return;
      }

      if (hasParagraphs && child.nodeName === 'DIV') {
        var p = document.createElement('p');
        p.innerHTML = child.innerHTML;
        child.replaceWith(p);
      }

      parseNode(child);
    });
  };

  parseNode(el);
  return el.innerHTML;
};

var htmlToValue = function htmlToValue(html) {
  try {
    return serializer.deserialize(wrapHtmlProperly(reduceRedundantNewLineCharacters(reduceMultipleBrs(html))));
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log("Couldn't parse html: ", e);
    return {};
  }
};

exports.htmlToValue = htmlToValue;

var valueToHtml = function valueToHtml(value) {
  return serializer.serialize(value);
};
/**
 *
 * <div><div>a</div></div> -> <div>a</div>
 *
 * <div><div>a</div><div>b</div></div> -> <div>a</div><div>b</div>
 * <div><div>a</div>4444<div>b</div></div> -> <div>a</div>4444<div>b</div>
 */


exports.valueToHtml = valueToHtml;
//# sourceMappingURL=serialization.js.map