UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

129 lines (127 loc) 4.18 kB
import { validator } from '@atlaskit/adf-utils/validator'; import { ACTION_SUBJECT_ID } from '../analytics'; export const UNSUPPORTED_NODE_ATTRIBUTE = 'unsupportedNodeAttribute'; import { fireUnsupportedEvent } from './track-unsupported-content'; const errorCallbackFor = (marks, validate, dispatchAnalyticsEvent) => { return (entity, error, options) => { return validationErrorHandler(entity, error, options, marks, validate, dispatchAnalyticsEvent); }; }; export const validationErrorHandler = (entity, error, options, marks, validate, dispatchAnalyticsEvent) => { if (entity && entity.type === UNSUPPORTED_NODE_ATTRIBUTE) { return entity; } if (options.isMark) { return wrapWithUnsupported(error.meta, 'mark'); } if (options.isNodeAttribute) { const entityType = entity && entity.type ? entity.type : undefined; return { type: UNSUPPORTED_NODE_ATTRIBUTE, attrs: { type: { nodeType: entityType }, unsupported: error.meta } }; } if (entity && marks.indexOf(entity.type) > -1) { return; } /** * There's a inconsistency between ProseMirror and ADF. * `content` is actually optional in ProseMirror. * And, also empty `text` node is not valid. */ if (error.code === 'MISSING_PROPERTIES' && entity.type === 'paragraph') { return { type: 'paragraph', content: [] }; } // TODO: We can repair missing content like `panel` without a `paragraph`. if (error.code === 'INVALID_CONTENT_LENGTH') { if (error.meta && options.allowUnsupportedBlock && entity.content) { return getEntityForInvalidContentLength(error, entity, validate, marks, dispatchAnalyticsEvent); } else { // Can't fix it by wrapping if (dispatchAnalyticsEvent) { trackValidationError(dispatchAnalyticsEvent, error, entity); } } } if (options.allowUnsupportedBlock) { return wrapWithUnsupported(entity); } if (options.allowUnsupportedInline) { return wrapWithUnsupported(entity, 'inline'); } if (dispatchAnalyticsEvent) { trackValidationError(dispatchAnalyticsEvent, error, entity); } return entity; }; function getEntityForInvalidContentLength(error, entity, validate, marks, dispatchAnalyticsEvent) { const meta = error.meta; if (meta.type === 'maximum') { entity.content = entity.content.filter(x => !!x).map((child, index) => { return index >= meta.requiredLength && child.type !== 'unsupportedBlock' ? wrapWithUnsupported(child) : validate(child, errorCallbackFor(marks, validate, dispatchAnalyticsEvent)).entity; }); } if (meta.type === 'minimum') { if (entity.content.length === 0) { return wrapWithUnsupported(entity); } entity.content = entity.content.filter(x => !!x).map(child => { return child.type !== 'unsupportedBlock' ? wrapWithUnsupported(child) : child; }); } return entity; } function trackValidationError(dispatchAnalyticsEvent, error, entity) { if (!dispatchAnalyticsEvent) { return; } fireUnsupportedEvent(dispatchAnalyticsEvent, ACTION_SUBJECT_ID.UNSUPPORTED_ERROR, { type: entity.type || '', ancestry: entity.ancestorHierarchy || '', parentType: entity.parentType || '', marks: entity.marks || [], attrs: entity.attrs || {} }, error.code); } export const validateADFEntity = (schema, node, dispatchAnalyticsEvent) => { const nodes = Object.keys(schema.nodes); const marks = Object.keys(schema.marks); const validate = validator(nodes, marks, { allowPrivateAttributes: true }); const emptyDoc = { type: 'doc', content: [] }; const { entity = emptyDoc } = validate(node, errorCallbackFor(marks, validate, dispatchAnalyticsEvent)); return entity; }; export function wrapWithUnsupported(originalValue, type = 'block') { let unsupportedNodeType; switch (type) { case 'inline': unsupportedNodeType = 'unsupportedInline'; break; case 'mark': unsupportedNodeType = 'unsupportedMark'; break; default: unsupportedNodeType = 'unsupportedBlock'; } return { type: unsupportedNodeType, attrs: { originalValue } }; }