"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.handleHtml = exports.getBase64 = exports.extraCSSRulesOpts = exports.TEXT_RULE = exports.INLINE_TAGS = void 0;

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

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

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

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

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

var _isEmpty = _interopRequireDefault(require("lodash/isEmpty"));

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 _blockTags = require("./block-tags");

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }

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

var MARK_TAGS = {
  b: 'bold',
  em: 'italic',
  u: 'underline',
  s: 'strikethrough',
  code: 'code',
  strong: 'bold',
  blockquote: 'blockquote',
  h3: 'h3',
  sup: 'sup',
  sub: 'sub'
};

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', 'align'];
/**
 * Serializer rules.
 *
 * @type {Array}
 */

var blocks = {
  deserialize: function deserialize(el, next) {
    log('[blocks:deserialize] block: ', el);

    var block = _blockTags.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 _blockTags.BLOCK_TAGS) {
      if (_blockTags.BLOCK_TAGS[key] === object.type) {
        var Tag = key;
        return /*#__PURE__*/_react["default"].createElement(Tag, jsonData.attributes, children);
      }
    }
  }
};
var INLINE_TAGS = {
  span: 'span'
};
exports.INLINE_TAGS = INLINE_TAGS;
var inlines = {
  deserialize: function deserialize(el, next) {
    log('[inlines:deserialize] inline: ', el);
    var inlineTag = INLINE_TAGS[el.tagName.toLowerCase()];
    if (!inlineTag) return;
    log('[inlines:deserialize] inline: ', inlineTag);

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

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

    return {
      object: 'inline',
      type: inlineTag,

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

    for (key in INLINE_TAGS) {
      if (INLINE_TAGS[key] === object.type) {
        var Tag = key;
        return /*#__PURE__*/_react["default"].createElement(Tag, jsonData.attributes, children);
      }
    }
  }
};
var extraCSSRulesOpts = {};
exports.extraCSSRulesOpts = extraCSSRulesOpts;
var STYLES_MAP = {
  h3: {
    fontSize: 'inherit',
    fontWeight: 'inherit'
  },
  blockquote: {
    background: '#f9f9f9',
    borderLeft: '5px solid #ccc',
    margin: '1.5em 10px',
    padding: '.5em 10px'
  }
};
var reactToHTMLAttributesMap = {
  "class": 'className'
};
var marks = {
  deserialize: function deserialize(el, next) {
    var mark = MARK_TAGS[el.tagName.toLowerCase()];
    var elClasses = el.getAttribute('class') || '';
    var hasCSSRule = ((extraCSSRulesOpts === null || extraCSSRulesOpts === void 0 ? void 0 : extraCSSRulesOpts.names) || []).find(function (name) {
      return elClasses === null || elClasses === void 0 ? void 0 : elClasses.includes(name);
    });

    if (!mark && !hasCSSRule) {
      return;
    }

    log('[deserialize] mark: ', mark);
    var attrs = attributes.reduce(attributesToMap(el), {});
    var data = (0, _isEmpty["default"])(attrs) ? undefined : {
      attributes: attrs
    };
    return {
      object: 'mark',
      type: hasCSSRule ? 'css' : mark,

      /**
       * Here for rendering styles for all elements
       */
      data: data,
      nodes: next(el.childNodes)
    };
  },
  serialize: function serialize(object, children) {
    var _object$data, _jsonData$attributes;

    var jsonData = ((_object$data = object.data) === null || _object$data === void 0 ? void 0 : _object$data.toJSON()) || {};
    var elClasses = ((_jsonData$attributes = jsonData.attributes) === null || _jsonData$attributes === void 0 ? void 0 : _jsonData$attributes["class"]) || '';
    var hasCSSRule = ((extraCSSRulesOpts === null || extraCSSRulesOpts === void 0 ? void 0 : extraCSSRulesOpts.names) || []).find(function (name) {
      return elClasses === null || elClasses === void 0 ? void 0 : elClasses.includes(name);
    });

    if (hasCSSRule) {
      var htmlAttrs = Object.keys(jsonData.attributes).reduce(function (obj, key) {
        obj[reactToHTMLAttributesMap[key] || key] = jsonData.attributes[key];
        return obj;
      }, {});
      return /*#__PURE__*/_react["default"].createElement("span", htmlAttrs, children);
    }

    if (_slate.Mark.isMark(object)) {
      for (var key in MARK_TAGS) {
        if (MARK_TAGS[key] === object.type) {
          var Tag = key;
          var additionalStyles = STYLES_MAP[Tag];

          if (additionalStyles) {
            if (!jsonData.attributes) {
              jsonData.attributes = {};
            }

            jsonData.attributes.style = _objectSpread(_objectSpread({}, jsonData.attributes.style), additionalStyles);
          }

          return /*#__PURE__*/_react["default"].createElement(Tag, jsonData.attributes, 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, inlines, 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) {
    var _n$nextSibling, _n$nextSibling$tagNam, _n$previousSibling, _n$previousSibling$ta;

    var isWhiteSpace = allWhitespace(n);
    var isNotNearMarkup = !MARK_TAGS[(_n$nextSibling = n.nextSibling) === null || _n$nextSibling === void 0 ? void 0 : (_n$nextSibling$tagNam = _n$nextSibling.tagName) === null || _n$nextSibling$tagNam === void 0 ? void 0 : _n$nextSibling$tagNam.toLowerCase()] && !MARK_TAGS[(_n$previousSibling = n.previousSibling) === null || _n$previousSibling === void 0 ? void 0 : (_n$previousSibling$ta = _n$previousSibling.tagName) === null || _n$previousSibling$ta === void 0 ? void 0 : _n$previousSibling$ta.toLowerCase()];

    if (isWhiteSpace && isNotNearMarkup || 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;
};
/**
 * Makes sure that the html provided respects the schema
 * rules for the slate editor.
 * @param markup
 * @returns {string}
 */


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

  var fixParagraphs = function fixParagraphs(child) {
    var p = document.createElement('p');
    p.innerHTML = child.innerHTML;
    Array.from(child.attributes).forEach(function (attr) {
      p.setAttribute(attr.name, attr.value);
    });
    child.replaceWith(p);
  };
  /**
   * @summary Makes sure that tables are placed in the root document.
   * @description This function removes the tables from the nodes that are
   * placed inside the root element and places them exactly near
   * the parent element.
   * @param tableArray
   */


  var fixTables = function fixTables(tableArray) {
    tableArray.forEach(function (el) {
      var index = el.index,
          child = el.child,
          parent = el.parent;
      var nodesBefore = [];
      var nodesAfter = [];
      var allNodes = Array.from(parent.childNodes);
      var i;

      for (i = 0; i < allNodes.length; i++) {
        var node = allNodes[i];

        if (i < index) {
          nodesBefore.push(node);
        } else if (i > index) {
          nodesAfter.push(node);
        }
      } // creating the element that is going to be placed instead of the parent


      var beforeEl = document.createElement(parent.nodeName);
      beforeEl.append.apply(beforeEl, nodesBefore); // replacing parent with the beforeElement

      parent.replaceWith(beforeEl); // adding the table right after the before element

      beforeEl.after(child); // creating the element that is going to be placed after the table

      var afterEl = document.createElement(parent.nodeName);
      afterEl.append.apply(afterEl, nodesAfter); // adding the after element near the table

      child.after(afterEl);
    });
  };

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

      if (hasParagraphs && child.nodeName === 'DIV') {
        fixParagraphs(child);
      }

      if (wrapperEl !== el && child.nodeName === 'TABLE') {
        // we don't need to fix tables in the root element
        tables.push({
          index: index,
          child: child,
          parent: el
        });
      }

      parseNode(child);
    });

    if (tables.length) {
      fixTables(tables);
    }
  };

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

var handleHtml = function handleHtml(html) {
  return fixHtmlCode(reduceRedundantNewLineCharacters(reduceMultipleBrs(html)));
};

exports.handleHtml = handleHtml;

var htmlToValue = function htmlToValue(html) {
  try {
    return serializer.deserialize(handleHtml(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