@atlaskit/adf-utils
Version:
Set of utilities to traverse, modify and create ADF documents.
1,040 lines (1,023 loc) • 44.2 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof3 = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createSpec = createSpec;
exports.validateAttrs = validateAttrs;
exports.validator = validator;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
var specs = _interopRequireWildcard(require("./specs"));
var _utils = require("./utils");
var _extractAllowedContent = require("./extractAllowedContent");
var _rules = require("./rules");
var _excluded = ["items"]; // Ignored via go/ees005
// eslint-disable-next-line import/no-namespace
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof3(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function mapMarksItems(spec) {
var fn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (x) {
return x;
};
if (spec.props && spec.props.marks) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var _ref = spec.props.marks,
items = _ref.items,
rest = (0, _objectWithoutProperties2.default)(_ref, _excluded);
return _objectSpread(_objectSpread({}, spec), {}, {
props: _objectSpread(_objectSpread({}, spec.props), {}, {
marks: _objectSpread(_objectSpread({}, rest), {}, {
/**
* `Text & MarksObject<Mark-1>` produces `items: ['mark-1']`
* `Text & MarksObject<Mark-1 | Mark-2>` produces `items: [['mark-1', 'mark-2']]`
*/
items: items.length ? Array.isArray(items[0]) ? items.map(fn) : [fn(items)] : [[]]
})
})
});
} else {
return spec;
}
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var partitionObject = function partitionObject(obj, predicate) {
return Object.keys(obj).reduce(function (acc, key) {
var result = predicate(key, obj[key], obj);
acc[result ? 0 : 1].push(key);
return acc;
}, [[], []]);
};
/**
* Checks if a spec is a variant spec.
* A variant spec is an array where the first element is a string (base spec name)
* and the second element is a ValidatorSpec object { props: { ... } }
*
* @param spec - The spec to check
* @returns true if the spec is a variant spec, false otherwise
*/
var isVariant = function isVariant(spec) {
return (0, _typeof2.default)(spec) === 'object' && !!spec && 0 in spec && 1 in spec && typeof spec[0] === 'string' && (0, _typeof2.default)(spec[1]) === 'object';
};
/**
* Normalizes the structure of files imported from './specs'.
* We denormalised the spec to save bundle size.
*/
function createSpec(nodes, marks) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Object.keys(specs).reduce(function (newSpecs, k) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var spec = _objectSpread({}, specs[k]);
if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_schema', 'isEnabled', true) && isVariant(spec) &&
// Only apply to variants which are explicitly marked for override in `variantSpecOverrides`
Object.values(variantSpecOverrides).includes(k)) {
// This allows the variant spec to also have the content normalization applied to it
// When the spec is a variant it will be in the form of ['base_spec_name', { props: { ... } }]
// So the actual validator spec of the variant will be the second item in the array
// We also need to shallow clone this to ensure we don't mutate the original spec
spec = _objectSpread({}, spec[1]);
}
if (spec.props) {
spec.props = _objectSpread({}, spec.props);
if (spec.props.content) {
// 'tableCell_content' => { type: 'array', items: [ ... ] }
if ((0, _utils.isString)(spec.props.content)) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
spec.props.content = specs[spec.props.content];
}
// ['inline', 'emoji']
if (Array.isArray(spec.props.content)) {
/**
* Flatten
*
* Input:
* [ { type: 'array', items: [ 'tableHeader' ] }, { type: 'array', items: [ 'tableCell' ] } ]
*
* Output:
* { type: 'array', items: [ [ 'tableHeader' ], [ 'tableCell' ] ] }
*/
spec.props.content = {
type: 'array',
items: (spec.props.content || []).map(function (arr) {
return arr.items;
})
};
} else {
spec.props.content = _objectSpread({}, spec.props.content);
}
spec.props.content.items = spec.props.content.items
// ['inline'] => [['emoji', 'hr', ...]]
// ['media'] => [['media']]
.map(function (item) {
return (0, _utils.isString)(item) ?
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Array.isArray(specs[item]) ?
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
specs[item] : [item] : item;
})
// [['emoji', 'hr', 'inline_code']] => [['emoji', 'hr', ['text', { marks: {} }]]]
.map(function (item) {
return item.map(function (subItem) {
return (
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Array.isArray(specs[subItem]) ?
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
specs[subItem] : (0, _utils.isString)(subItem) ? subItem :
// Now `NoMark` produces `items: []`, should be fixed in generator
['text', subItem]
);
})
// Remove unsupported nodes & marks
// Filter nodes
.filter(function (subItem) {
if (nodes) {
// Node with overrides
// ['mediaSingle', { props: { content: { items: [ 'media', 'caption' ] } }}]
if (Array.isArray(subItem)) {
var _subItem$;
var isMainNodeSupported = nodes.indexOf(subItem[0]) > -1;
if (isMainNodeSupported && (_subItem$ = subItem[1]) !== null && _subItem$ !== void 0 && (_subItem$ = _subItem$.props) !== null && _subItem$ !== void 0 && (_subItem$ = _subItem$.content) !== null && _subItem$ !== void 0 && _subItem$.items) {
return subItem[1].props.content.items.every(function (item) {
return nodes.indexOf(item) > -1;
});
}
return isMainNodeSupported;
}
return nodes.indexOf(subItem) > -1;
}
return true;
})
// Filter marks
.map(function (subItem) {
return Array.isArray(subItem) && marks ?
/**
* TODO: Probably try something like immer, but it's 3.3kb gzipped.
* Not worth it just for this.
*/
[subItem[0], mapMarksItems(subItem[1])] : subItem;
});
});
}
}
newSpecs[k] = spec;
return newSpecs;
}, {});
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getOptionsForType(type, list) {
if (!list) {
return {};
}
for (var i = 0, len = list.length; i < len; i++) {
var spec = list[i];
var name = spec;
var options = {};
if (Array.isArray(spec)) {
var _spec = (0, _slicedToArray2.default)(spec, 2);
name = _spec[0];
options = _spec[1];
}
if (name === type) {
return options;
}
}
return false;
}
var isValidatorSpecAttrs = function isValidatorSpecAttrs(spec) {
return !!spec.props;
};
function validateAttrs(spec, value) {
if (!(0, _utils.isDefined)(value)) {
return !!spec.optional;
}
if (isValidatorSpecAttrs(spec)) {
// If spec has ".props" it is ValidatorSpecAttrs and need to pipe back in recursively
var _partitionObject = partitionObject(spec.props, function (key, subSpec) {
return (
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
validateAttrs(subSpec, value[key])
);
}),
_partitionObject2 = (0, _slicedToArray2.default)(_partitionObject, 2),
_ = _partitionObject2[0],
invalidKeys = _partitionObject2[1];
return invalidKeys.length === 0;
}
// extension_node parameters has no type
if (!(0, _utils.isDefined)(spec.type)) {
return !!spec.optional;
}
switch (spec.type) {
case 'boolean':
return (0, _utils.isBoolean)(value);
case 'number':
return (0, _utils.isNumber)(value) && ((0, _utils.isDefined)(spec.minimum) ? spec.minimum <= value : true) && ((0, _utils.isDefined)(spec.maximum) ? spec.maximum >= value : true);
case 'integer':
return (0, _utils.isInteger)(value) && ((0, _utils.isDefined)(spec.minimum) ? spec.minimum <= value : true) && ((0, _utils.isDefined)(spec.maximum) ? spec.maximum >= value : true);
case 'string':
var validatorFnPassed = function validatorFnPassed(rule) {
return typeof value === 'string' && (0, _utils.isDefined)(_rules.validatorFnMap[rule]) && _rules.validatorFnMap[rule](value);
};
return (0, _utils.isString)(value) && (
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(0, _utils.isDefined)(spec.minLength) ? spec.minLength <= value.length : true) && ((0, _utils.isDefined)(spec.validatorFn) ? validatorFnPassed(spec.validatorFn) : true) && (
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
spec.pattern ? new RegExp(spec.pattern).test(value) : true);
case 'object':
return (0, _utils.isPlainObject)(value);
case 'array':
if (Array.isArray(value)) {
var isTuple = !!spec.isTupleLike;
var minItems = spec.minItems,
maxItems = spec.maxItems;
if (minItems !== undefined && value.length < minItems || maxItems !== undefined && value.length > maxItems) {
return false;
}
if (isTuple) {
// If value has fewer items than tuple has specs - we are fine with that.
var numberOfItemsToCheck = Math.min(spec.items.length, value.length);
return Array(numberOfItemsToCheck).fill(null).every(function (_, i) {
return validateAttrs(spec.items[i], value[i]);
});
} else {
return value.every(function (valueItem) {
return (
// We check that at least one of the specs in the list (spec.items) matches each value from
spec.items.some(function (itemSpec) {
return validateAttrs(itemSpec, valueItem);
})
);
});
}
}
return false;
case 'enum':
return (0, _utils.isString)(value) && spec.values.indexOf(value) > -1;
}
}
var errorMessageFor = function errorMessageFor(type, message) {
return "".concat(type, ": ").concat(message, ".");
};
var getUnsupportedOptions = function getUnsupportedOptions(spec) {
if (spec && spec.props && spec.props.content) {
var _spec$props$content = spec.props.content,
allowUnsupportedBlock = _spec$props$content.allowUnsupportedBlock,
allowUnsupportedInline = _spec$props$content.allowUnsupportedInline;
return {
allowUnsupportedBlock: allowUnsupportedBlock,
allowUnsupportedInline: allowUnsupportedInline
};
}
return {};
};
var invalidChildContent = function invalidChildContent(child, errorCallback, parentSpec) {
var message = errorMessageFor(child.type, 'invalid content');
if (!errorCallback) {
throw new Error(message);
} else {
var _parentSpec$props;
return errorCallback(_objectSpread({}, child), {
code: 'INVALID_CONTENT',
message: message,
meta: {
parentType: parentSpec === null || parentSpec === void 0 || (_parentSpec$props = parentSpec.props) === null || _parentSpec$props === void 0 || (_parentSpec$props = _parentSpec$props.type) === null || _parentSpec$props === void 0 ? void 0 : _parentSpec$props.values[0]
}
}, getUnsupportedOptions(parentSpec));
}
};
var unsupportedMarkContent = function unsupportedMarkContent(errorCode, mark, errorCallback, errorMessage) {
var message = errorMessage || errorMessageFor(mark.type, 'unsupported mark');
if (!errorCallback) {
throw new Error(message);
} else {
return errorCallback(_objectSpread({}, mark), {
code: errorCode,
message: message,
meta: mark
}, {
allowUnsupportedBlock: false,
allowUnsupportedInline: false,
isMark: true
});
}
};
var unsupportedNodeAttributesContent = function unsupportedNodeAttributesContent(entity, errorCode, invalidAttributes, message, errorCallback) {
if (!errorCallback) {
throw new Error(message);
} else {
return errorCallback({
type: entity.type
}, {
code: errorCode,
message: message,
meta: invalidAttributes
}, {
allowUnsupportedBlock: false,
allowUnsupportedInline: false,
isMark: false,
isNodeAttribute: true
});
}
};
/**
* Map of base spec names to a preferred variant spec that should be used in their place during validation.
*
* WARNING: The variant spec must be a strict superset of the base spec, i.e. any content valid
* under the base spec must also be valid under the variant
*/
var variantSpecOverrides = {
listItem: 'listItem_with_flexible_first_child',
taskList: 'taskList_with_flexible_first_child'
};
/**
* Replaces base validator specs with their designated variant overrides
*/
var applyVariantSpecOverrides = function applyVariantSpecOverrides(validatorSpecs) {
Object.entries(variantSpecOverrides).forEach(function (_ref2) {
var _ref3 = (0, _slicedToArray2.default)(_ref2, 2),
base = _ref3[0],
variant = _ref3[1];
var baseSpec = validatorSpecs[base];
var variantOverride = validatorSpecs[variant];
if (baseSpec !== null && baseSpec !== void 0 && baseSpec.props && variantOverride !== null && variantOverride !== void 0 && variantOverride.props && (0, _typeof2.default)(baseSpec.props) === 'object' && (0, _typeof2.default)(variantOverride.props) === 'object') {
// Merge variant overrides INTO the base spec
baseSpec.props = _objectSpread(_objectSpread({}, baseSpec.props), variantOverride.props);
}
});
};
function validator(nodes, marks, options) {
var validatorSpecs = createSpec(nodes, marks);
if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_schema', 'isEnabled', true)) {
applyVariantSpecOverrides(validatorSpecs);
}
var _ref4 = options || {},
_ref4$mode = _ref4.mode,
mode = _ref4$mode === void 0 ? 'strict' : _ref4$mode,
_ref4$allowPrivateAtt = _ref4.allowPrivateAttributes,
allowPrivateAttributes = _ref4$allowPrivateAtt === void 0 ? false : _ref4$allowPrivateAtt;
var validate = function validate(entity, errorCallback, allowed, parentSpec) {
if (!allowed) {
var _iterator = _createForOfIteratorHelper((0, _extractAllowedContent.extractAllowedContent)(validatorSpecs, entity)),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _allowed = _step.value;
var _validationResult = validateNode(entity, errorCallback, _allowed, parentSpec);
if (_validationResult.valid) {
return {
entity: _validationResult.entity,
valid: _validationResult.valid
};
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
// If `allowed` was provided or we haven't passed yet, return the initial result
var validationResult = validateNode(entity, errorCallback, allowed, parentSpec);
return {
entity: validationResult.entity,
valid: validationResult.valid
};
};
var validateNode = function validateNode(entity, errorCallback, allowed, parentSpec) {
var isMark = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
var type = entity.type;
var newEntity = _objectSpread({}, entity);
var err = function err(code, msg, meta) {
var message = errorMessageFor(type, msg);
if (errorCallback) {
return {
valid: false,
entity: errorCallback(newEntity, {
code: code,
message: message,
meta: meta
}, getUnsupportedOptions(parentSpec))
};
} else {
throw new Error(message);
}
};
if (type) {
var typeOptions = getOptionsForType(type, allowed);
if (typeOptions === false) {
return isMark ? {
valid: false
} : err('INVALID_TYPE', 'type not allowed here');
}
var spec = validatorSpecs[type];
if (!spec) {
return err('INVALID_TYPE', "".concat(type, ": No validation spec found for type!"));
}
var specBasedValidationResult = specBasedValidationFor(spec, typeOptions, entity, err, newEntity, type, errorCallback, isMark);
if (specBasedValidationResult.hasValidated && specBasedValidationResult.result) {
return specBasedValidationResult.result;
}
} else {
return err('INVALID_TYPE', 'ProseMirror Node/Mark should contain a `type`');
}
return {
valid: true,
entity: newEntity
};
};
return validate;
function marksValidationFor(validator, entity, errorCallback, newEntity, err) {
var validationResult;
if (validator.props && validator.props.marks) {
var marksSet = allowedMarksFor(validator);
var marksValidationResult = marksAfterValidation(entity, errorCallback, marksSet, validator);
validationResult = {
valid: true,
entity: newEntity,
marksValidationOutput: marksValidationResult
};
} else {
validationResult = marksForEntitySpecNotSupportingMarks(entity, newEntity, errorCallback, err);
}
return validationResult;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function validatorFor(spec, typeOptions) {
return _objectSpread(_objectSpread(_objectSpread({}, spec), typeOptions), spec.props ? {
props: _objectSpread(_objectSpread({}, spec.props), typeOptions['props'] || {})
} : {});
}
function marksAfterValidation(entity, errorCallback, marksSet, validator) {
return entity.marks ? entity.marks.map(function (mark) {
var isAKnownMark = marks ? marks.indexOf(mark.type) > -1 : true;
if (mode === 'strict' && isAKnownMark) {
var finalResult = validateNode(mark, errorCallback, marksSet, validator, true);
var finalMark = finalResult.entity;
if (finalMark) {
return {
valid: true,
originalMark: mark,
newMark: finalMark
};
}
// this checks for mark level attribute errors
// and propagates error code and message
else if (finalResult.marksValidationOutput && finalResult.marksValidationOutput.length) {
return {
valid: false,
originalMark: mark,
errorCode: finalResult.marksValidationOutput[0].errorCode,
message: finalResult.marksValidationOutput[0].message
};
} else {
return {
valid: false,
originalMark: mark,
errorCode: 'INVALID_TYPE'
};
}
} else {
return {
valid: false,
originalMark: mark,
errorCode: 'INVALID_CONTENT'
};
}
}) : [];
}
function allowedMarksFor(validator) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var _ref5 = validator.props.marks,
items = _ref5.items;
var marksSet = items.length ? Array.isArray(items[0]) ? items[0] : items : [];
return marksSet;
}
function marksForEntitySpecNotSupportingMarks(prevEntity, newEntity, errorCallback, err) {
var errorCode = 'REDUNDANT_MARKS';
var currentMarks = prevEntity.marks || [];
var newMarks = currentMarks.map(function (mark) {
var isUnsupportedNodeAttributeMark = mark.type === 'unsupportedNodeAttribute';
if (isUnsupportedNodeAttributeMark) {
return mark;
}
return unsupportedMarkContent(errorCode, mark, errorCallback);
});
if (newMarks.length) {
newEntity.marks = newMarks;
return {
valid: true,
entity: newEntity
};
} else {
return err('REDUNDANT_MARKS', 'redundant marks', {
marks: Object.keys(currentMarks)
});
}
}
function requiredPropertyValidationFor(validatorSpec, prevEntity, err) {
var result = {
valid: true,
entity: prevEntity
};
if (validatorSpec.required) {
if (!validatorSpec.required.every(function (prop) {
return (0, _utils.isDefined)(prevEntity[prop]);
})) {
result = err('MISSING_PROPERTIES', 'required prop missing');
}
}
return result;
}
function textPropertyValidationFor(validatorSpec, prevEntity, err) {
var result = {
valid: true,
entity: prevEntity
};
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (validatorSpec.props.text) {
if ((0, _utils.isDefined)(prevEntity.text) &&
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
!validateAttrs(validatorSpec.props.text, prevEntity.text)) {
result = err('INVALID_TEXT', "'text' validation failed");
}
}
return result;
}
function contentLengthValidationFor(validatorSpec, prevEntity, err) {
var result = {
valid: true,
entity: prevEntity
};
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (validatorSpec.props.content && prevEntity.content) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var _content = validatorSpec.props.content,
minItems = _content.minItems,
maxItems = _content.maxItems;
var length = prevEntity.content.length;
if ((0, _utils.isDefined)(minItems) && minItems > length) {
result = err('INVALID_CONTENT_LENGTH', "'content' should have more than ".concat(minItems, " child"), {
length: length,
requiredLength: minItems,
type: 'minimum'
});
} else if ((0, _utils.isDefined)(maxItems) && maxItems < length) {
result = err('INVALID_CONTENT_LENGTH', "'content' should have less than ".concat(maxItems, " child"), {
length: length,
requiredLength: maxItems,
type: 'maximum'
});
}
}
return result;
}
function invalidAttributesFor(validatorSpec, prevEntity) {
var invalidAttrs = [];
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var validatorAttrs = {};
if (validatorSpec.props && validatorSpec.props.attrs) {
var attrOptions = (0, _utils.makeArray)(validatorSpec.props.attrs);
/**
* Attrs can be union type so try each path
* attrs: [{ props: { url: { type: 'string' } } }, { props: { data: {} } }],
* Gotcha: It will always report the last failure.
*/
for (var i = 0, length = attrOptions.length; i < length; ++i) {
var attrOption = attrOptions[i];
if (attrOption && attrOption.props) {
var _partitionObject3 = partitionObject(attrOption.props, function (key, spec) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var valueToValidate = prevEntity.attrs[key];
return validateAttrs(spec, valueToValidate);
});
var _partitionObject4 = (0, _slicedToArray2.default)(_partitionObject3, 2);
invalidAttrs = _partitionObject4[1];
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
validatorAttrs = attrOption;
if (!invalidAttrs.length) {
break;
}
}
}
return {
invalidAttrs: invalidAttrs,
validatorAttrs: validatorAttrs
};
}
function attributesValidationFor(validatorSpec, prevEntity, newEntity, isMark, errorCallback) {
var validatorSpecAllowsAttributes = validatorSpec.props && validatorSpec.props.attrs;
if (prevEntity.attrs) {
if (!validatorSpecAllowsAttributes) {
if (isMark) {
return handleNoAttibutesAllowedInSpecForMark(prevEntity, prevEntity.attrs);
}
var attrs = Object.keys(prevEntity.attrs);
return handleUnsupportedNodeAttributes(prevEntity, newEntity, [], attrs, errorCallback);
}
var _validateAttributes = validateAttributes(validatorSpec, prevEntity, prevEntity.attrs),
hasUnsupportedAttrs = _validateAttributes.hasUnsupportedAttrs,
redundantAttrs = _validateAttributes.redundantAttrs,
invalidAttrs = _validateAttributes.invalidAttrs;
if (hasUnsupportedAttrs) {
if (isMark) {
return handleUnsupportedMarkAttributes(prevEntity, invalidAttrs, redundantAttrs);
}
return handleUnsupportedNodeAttributes(prevEntity, newEntity, invalidAttrs, redundantAttrs, errorCallback);
}
}
return {
valid: true,
entity: prevEntity
};
}
function validateAttributes(validatorSpec, prevEntity,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
attributes) {
var invalidAttributesResult = invalidAttributesFor(validatorSpec, prevEntity);
var invalidAttrs = invalidAttributesResult.invalidAttrs;
var validatorAttrs = invalidAttributesResult.validatorAttrs;
var attrs = Object.keys(attributes).filter(function (k) {
return !(allowPrivateAttributes && k.startsWith('__'));
});
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var redundantAttrs = attrs.filter(function (a) {
return !validatorAttrs.props[a];
});
var hasRedundantAttrs = redundantAttrs.length > 0;
var hasUnsupportedAttrs = invalidAttrs.length || hasRedundantAttrs;
return {
hasUnsupportedAttrs: hasUnsupportedAttrs,
invalidAttrs: invalidAttrs,
redundantAttrs: redundantAttrs
};
}
function handleUnsupportedNodeAttributes(prevEntity, newEntity, invalidAttrs, redundantAttrs, errorCallback) {
var attr = invalidAttrs.concat(redundantAttrs);
var result = {
valid: true,
entity: prevEntity
};
var message = errorMessageFor(prevEntity.type, "'attrs' validation failed");
var errorCode = 'UNSUPPORTED_ATTRIBUTES';
newEntity.marks = wrapUnSupportedNodeAttributes(prevEntity, newEntity, attr, errorCode, message, errorCallback);
result = {
valid: true,
entity: newEntity
};
return result;
}
function handleUnsupportedMarkAttributes(prevEntity, invalidAttrs, redundantAttrs) {
var errorCode = 'INVALID_ATTRIBUTES';
var message = errorMessageFor(prevEntity.type, "'attrs' validation failed");
var hasRedundantAttrs = redundantAttrs.length;
var hasBothInvalidAndRedundantAttrs = hasRedundantAttrs && invalidAttrs.length;
if (!hasBothInvalidAndRedundantAttrs && hasRedundantAttrs) {
errorCode = 'REDUNDANT_ATTRIBUTES';
message = errorMessageFor('redundant attributes found', redundantAttrs.join(', '));
}
var markValidationResult = {
valid: true,
originalMark: prevEntity,
errorCode: errorCode,
message: message
};
return {
valid: false,
marksValidationOutput: [markValidationResult]
};
}
function handleNoAttibutesAllowedInSpecForMark(prevEntity,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
attributes) {
var message = errorMessageFor('redundant attributes found', Object.keys(attributes).join(', '));
var errorCode = 'REDUNDANT_ATTRIBUTES';
var markValidationResult = {
valid: true,
originalMark: prevEntity,
errorCode: errorCode,
message: message
};
return {
valid: false,
marksValidationOutput: [markValidationResult]
};
}
function wrapUnSupportedNodeAttributes(prevEntity, newEntity, invalidAttrs, errorCode, message, errorCallback) {
var invalidValues = {};
// eslint-disable-next-line guard-for-in
for (var invalidAttr in invalidAttrs) {
invalidValues[invalidAttrs[invalidAttr]] = prevEntity.attrs && prevEntity.attrs[invalidAttrs[invalidAttr]];
if (newEntity.attrs) {
delete newEntity.attrs[invalidAttrs[invalidAttr]];
}
}
var unsupportedNodeAttributeValues = unsupportedNodeAttributesContent(prevEntity, errorCode, invalidValues, message, errorCallback);
var finalEntity = _objectSpread({}, newEntity);
if (finalEntity.marks) {
if (!unsupportedNodeAttributeValues) {
return finalEntity.marks;
}
// If there is an existing unsupported node attribute mark, overwrite it to avoid duplicate marks
var existingMark = finalEntity.marks.find(function (mark) {
return mark.type === unsupportedNodeAttributeValues.type;
});
if (existingMark) {
existingMark.attrs = unsupportedNodeAttributeValues.attrs;
} else {
finalEntity.marks.push(unsupportedNodeAttributeValues);
}
return finalEntity.marks;
} else {
return [unsupportedNodeAttributeValues];
}
}
function extraPropsValidationFor(validatorSpec, prevEntity, err, newEntity, type) {
var result = {
valid: true,
entity: prevEntity
};
var _partitionObject5 = partitionObject(prevEntity, function (k) {
return (
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(0, _utils.isDefined)(validatorSpec.props[k])
);
}),
_partitionObject6 = (0, _slicedToArray2.default)(_partitionObject5, 2),
requiredProps = _partitionObject6[0],
redundantProps = _partitionObject6[1];
if (redundantProps.length) {
if (mode === 'loose') {
newEntity = {
type: type
};
requiredProps.reduce(function (acc, p) {
return (0, _utils.copy)(prevEntity, acc, p);
}, newEntity);
} else {
if (!((redundantProps.indexOf('marks') > -1 || redundantProps.indexOf('attrs') > -1) && redundantProps.length === 1)) {
return err('REDUNDANT_PROPERTIES', "redundant props found: ".concat(redundantProps.join(', ')), {
props: redundantProps
});
}
}
}
return result;
}
function specBasedValidationFor(spec,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
typeOptions, prevEntity, err, newEntity, type, errorCallback, isMark) {
var specBasedValidationResult = {
hasValidated: false
};
var validatorSpec = validatorFor(spec, typeOptions);
if (!validatorSpec) {
return specBasedValidationResult;
}
// Required Props
// For array format where `required` is an array
var requiredPropertyValidatonResult = requiredPropertyValidationFor(validatorSpec, prevEntity, err);
if (!requiredPropertyValidatonResult.valid) {
return {
hasValidated: true,
result: requiredPropertyValidatonResult
};
}
if (!validatorSpec.props) {
var props = Object.keys(prevEntity);
// If there's no validator.props then there shouldn't be any key except `type`
if (props.length > 1) {
return {
hasValidated: true,
result: err('REDUNDANT_PROPERTIES', "redundant props found: ".concat(Object.keys(prevEntity).join(', ')), {
props: props
})
};
}
return specBasedValidationResult;
}
// Check text
var textPropertyValidationResult = textPropertyValidationFor(validatorSpec, prevEntity, err);
if (!textPropertyValidationResult.valid) {
return {
hasValidated: true,
result: textPropertyValidationResult
};
}
// Content Length
var contentLengthValidationResult = contentLengthValidationFor(validatorSpec, prevEntity, err);
if (!contentLengthValidationResult.valid) {
return {
hasValidated: true,
result: contentLengthValidationResult
};
}
// Required Props
// For object format based on `optional` property
var _partitionObject7 = partitionObject(validatorSpec.props, function (k, v) {
var _validatorSpec$requir;
// if the validator is an array, then check
// if the `required` field contains the key.
var isOptional = Array.isArray(v) ? !((_validatorSpec$requir = validatorSpec.required) !== null && _validatorSpec$requir !== void 0 && _validatorSpec$requir.includes(k)) : (0, _typeof2.default)(v) === 'object' && v !== null && 'optional' in v ? v.optional : false;
return isOptional || (0, _utils.isDefined)(prevEntity[k]);
}),
_partitionObject8 = (0, _slicedToArray2.default)(_partitionObject7, 2),
missingProps = _partitionObject8[1];
if (missingProps.length) {
return {
hasValidated: true,
result: err('MISSING_PROPERTIES', 'required prop missing', {
props: missingProps
})
};
}
var attributesValidationResult = attributesValidationFor(validatorSpec, prevEntity, newEntity, isMark, errorCallback);
if (!attributesValidationResult.valid) {
return {
hasValidated: true,
result: attributesValidationResult
};
}
if (isMark && attributesValidationResult.valid) {
return {
hasValidated: true,
result: attributesValidationResult
};
}
var extraPropsValidationResult = extraPropsValidationFor(validatorSpec, prevEntity, err, newEntity, type);
if (!extraPropsValidationResult.valid) {
return {
hasValidated: true,
result: extraPropsValidationResult
};
}
// Children
if (validatorSpec.props.content) {
var contentValidatorSpec = validatorSpec.props.content;
if (prevEntity.content) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var validateChildNode = function validateChildNode(child, index) {
if (child === undefined) {
return child;
}
var validateChildMarks = function validateChildMarks(childEntity, marksValidationOutput, errorCallback, isLastValidationSpec) {
var isParentTupleLike = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
var marksAreValid = true;
if (childEntity && childEntity.marks && marksValidationOutput) {
var validMarks = marksValidationOutput.filter(function (mark) {
return mark.valid;
});
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var finalMarks = marksValidationOutput.map(function (mr) {
if (mr.valid) {
return mr.newMark;
} else {
if (validMarks.length || isLastValidationSpec || isParentTupleLike || mr.errorCode === 'INVALID_TYPE' || mr.errorCode === 'INVALID_CONTENT' || mr.errorCode === 'REDUNDANT_ATTRIBUTES' || mr.errorCode === 'INVALID_ATTRIBUTES') {
return unsupportedMarkContent(
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mr.errorCode, mr.originalMark, errorCallback, mr.message);
}
return;
}
}).filter(Boolean);
if (finalMarks.length) {
childEntity.marks = finalMarks;
} else {
delete childEntity.marks;
marksAreValid = false;
}
}
return {
valid: marksAreValid,
entity: childEntity
};
};
var hasMultipleCombinationOfContentAllowed = !!contentValidatorSpec.isTupleLike;
if (hasMultipleCombinationOfContentAllowed) {
var _validateNode = validateNode(child, errorCallback, (0, _utils.makeArray)(contentValidatorSpec.items[index] || contentValidatorSpec.items[contentValidatorSpec.items.length - 1]), validatorSpec),
newChildEntity = _validateNode.entity,
marksValidationOutput = _validateNode.marksValidationOutput;
var _validateChildMarks = validateChildMarks(newChildEntity, marksValidationOutput, errorCallback, false, true),
entity = _validateChildMarks.entity;
return entity;
}
// Only go inside valid branch
var allowedSpecsForEntity = contentValidatorSpec.items.filter(function (item) {
return Array.isArray(item) ? item.some(
// [p, hr, ...] or [p, [text, {}], ...]
function (spec) {
return (Array.isArray(spec) ? spec[0] : spec) === child.type;
}) : true;
});
if (allowedSpecsForEntity.length) {
if (allowedSpecsForEntity.length > 1) {
throw new Error('Consider using Tuple instead!');
}
var maybeArray = (0, _utils.makeArray)(allowedSpecsForEntity[0]);
var allowedSpecsForChild = maybeArray.filter(function (item) {
return (Array.isArray(item) ? item[0] : item) === child.type;
});
if (allowedSpecsForChild.length === 0) {
return invalidChildContent(child, errorCallback, validatorSpec);
}
/**
* When there's multiple possible branches try all of them.
* If all of them fails, throw the first one.
* e.g.- [['text', { marks: ['a'] }], ['text', { marks: ['b'] }]]
*/
var firstError;
var firstChild;
for (var i = 0, len = allowedSpecsForChild.length; i < len; i++) {
try {
var allowedValueForCurrentSpec = [allowedSpecsForChild[i]];
var _validateNode2 = validateNode(child, errorCallback, allowedValueForCurrentSpec, validatorSpec),
valid = _validateNode2.valid,
_newChildEntity = _validateNode2.entity,
_marksValidationOutput = _validateNode2.marksValidationOutput;
if (valid) {
var isLastValidationSpec = i === allowedSpecsForChild.length - 1;
var _validateChildMarks2 = validateChildMarks(_newChildEntity, _marksValidationOutput, errorCallback, isLastValidationSpec),
marksAreValid = _validateChildMarks2.valid,
_entity = _validateChildMarks2.entity;
var unsupportedMarks = _entity && _entity.marks && _entity.marks.filter(function (mark) {
return mark.type === 'unsupportedMark';
}) || [];
if (marksAreValid && !unsupportedMarks.length) {
return _entity;
} else {
firstChild = firstChild || _newChildEntity;
}
} else {
firstChild = firstChild || _newChildEntity;
}
} catch (error) {
firstError = firstError || error;
}
}
if (!errorCallback) {
throw firstError;
} else {
return firstChild;
}
} else {
return invalidChildContent(child, errorCallback, validatorSpec);
}
};
newEntity.content = prevEntity.content.map(validateChildNode).filter(Boolean);
} else if (!contentValidatorSpec.optional) {
return {
hasValidated: true,
result: err('MISSING_PROPERTIES', 'missing `content` prop')
};
}
}
// Marks
if (prevEntity.marks) {
return {
hasValidated: true,
result: marksValidationFor(validatorSpec, prevEntity, errorCallback, newEntity, err)
};
}
return specBasedValidationResult;
}
}