UNPKG

@atlaskit/editor-core

Version:

A package contains Atlassian editor core functionality

559 lines • 19.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var utils_1 = require("../plugins/utils"); var utils_2 = require("./utils"); var schema_1 = require("../schema"); /* * It's important that this order follows the marks rank defined here: * https://product-fabric.atlassian.net/wiki/spaces/E/pages/11174043/Document+structure#Documentstructure-Rank */ exports.markOrder = [ 'link', 'em', 'strong', 'strike', 'subsup', 'underline', 'code', ]; exports.isSubSupType = function (type) { return type === 'sub' || type === 'sup'; }; /* * Sorts mark by the predefined order above */ exports.getMarksByOrder = function (marks) { return marks.slice().sort(function (a, b) { return exports.markOrder.indexOf(a.type.name) - exports.markOrder.indexOf(b.type.name); }); }; /* * Check if two marks are the same by comparing type and attrs */ exports.isSameMark = function (mark, otherMark) { if (!mark || !otherMark) { return false; } return mark.eq(otherMark); }; exports.getValidDocument = function (doc, schema) { if (schema === void 0) { schema = schema_1.defaultSchema; } var node = exports.getValidNode(doc, schema); if (node.type === 'doc') { return node; } return null; }; exports.getValidContent = function (content, schema) { if (schema === void 0) { schema = schema_1.defaultSchema; } return content.map(function (node) { return exports.getValidNode(node, schema); }); }; var TEXT_COLOR_PATTERN = /^#[0-9a-f]{6}$/i; var flattenUnknownBlockTree = function (node, schema) { if (schema === void 0) { schema = schema_1.defaultSchema; } var output = []; var isPrevLeafNode = false; for (var i = 0; i < node.content.length; i++) { var childNode = node.content[i]; var isLeafNode = !(childNode.content && childNode.content.length); if (i > 0) { if (isPrevLeafNode) { output.push({ type: 'text', text: ' ' }); } else { output.push({ type: 'hardBreak' }); } } if (isLeafNode) { output.push(exports.getValidNode(childNode, schema)); } else { output.push.apply(output, flattenUnknownBlockTree(childNode, schema)); } isPrevLeafNode = isLeafNode; } return output; }; // null is Object, also maybe check obj.constructor == Object if we want to skip Class var isValidObject = function (obj) { return obj !== null && typeof obj === 'object'; }; var isValidString = function (str) { return typeof str === 'string'; }; var keysLen = function (obj) { return Object.keys(obj).length; }; var isValidIcon = function (icon) { return isValidObject(icon) && keysLen(icon) === 2 && isValidString(icon.url) && isValidString(icon.label); }; var isValidUser = function (user) { var len = keysLen(user); return isValidObject(user) && len <= 2 && isValidIcon(user.icon) && (len === 1 || isValidString(user.id)); }; /** * Sanitize unknown node tree * * @see https://product-fabric.atlassian.net/wiki/spaces/E/pages/11174043/Document+structure#Documentstructure-ImplementationdetailsforHCNGwebrenderer */ exports.getValidUnknownNode = function (node) { var _a = node.attrs, attrs = _a === void 0 ? {} : _a, content = node.content, text = node.text, type = node.type; if (!content || !content.length) { var unknownInlineNode = { type: 'text', text: text || attrs.text || "[" + type + "]", }; if (attrs.textUrl) { unknownInlineNode.marks = [{ type: 'link', attrs: { href: attrs.textUrl, }, }]; } return unknownInlineNode; } /* * Find leaf nodes and join them. If leaf nodes' parent node is the same node * join with a blank space, otherwise they are children of different branches, i.e. * we need to join them with a hardBreak node */ return { type: 'unknownBlock', content: flattenUnknownBlockTree(node), }; }; /* * This method will validate a Node according to the spec defined here * https://product-fabric.atlassian.net/wiki/spaces/E/pages/11174043/Document+structure#Documentstructure-Nodes * * This is also the place to handle backwards compatibility. * * If a node is not recognized or is missing required attributes, we should return 'unknown' * */ exports.getValidNode = function (originalNode, schema) { if (schema === void 0) { schema = schema_1.defaultSchema; } var attrs = originalNode.attrs, marks = originalNode.marks, text = originalNode.text, type = originalNode.type; var content = originalNode.content; var node = { attrs: attrs, marks: marks, text: text, type: type }; if (content) { node.content = content = exports.getValidContent(content, schema); } // If node type doesn't exist in schema, make it an unknown node if (!schema.nodes[type]) { return exports.getValidUnknownNode(node); } if (type) { switch (type) { case 'applicationCard': { if (!attrs) { break; } var text_1 = attrs.text, link = attrs.link, background = attrs.background, preview = attrs.preview, title = attrs.title, description = attrs.description, details = attrs.details, context_1 = attrs.context; if (!isValidString(text_1) || !isValidObject(title) || !title.text) { break; } // title can contain at most two keys (text, user) var titleKeys = Object.keys(title); if (titleKeys.length > 2) { break; } if (titleKeys.length === 2 && !title.user) { break; } if (title.user && !isValidUser(title.user)) { break; } if ((link && !link.url) || (background && !background.url) || (preview && !preview.url) || (description && !description.text)) { break; } if (context_1 && !isValidString(context_1.text)) { break; } if (context_1 && !isValidIcon(context_1.icon)) { break; } if (details && !Array.isArray(details)) { break; } if (details && details.some(function (meta) { var badge = meta.badge, lozenge = meta.lozenge, users = meta.users; if (badge && !badge.value) { return true; } if (lozenge && !lozenge.text) { return true; } if (users && !Array.isArray(users)) { return true; } if (users && !users.every(isValidUser)) { return true; } })) { break; } return { type: type, text: text_1, attrs: attrs }; } case 'doc': { var version = originalNode.version; if (version && content && content.length) { return { type: type, content: content }; } break; } case 'codeBlock': { if (attrs && attrs.language !== undefined) { return { type: type, attrs: attrs, content: content }; } break; } case 'emoji': { if (attrs && attrs.shortName) { return { type: type, attrs: attrs }; } break; } case 'hardBreak': { return { type: type }; } case 'media': { var mediaId = ''; var mediaType = ''; var mediaCollection = []; if (attrs) { var id = attrs.id, collection = attrs.collection, type_1 = attrs.type; mediaId = id; mediaType = type_1; mediaCollection = collection; } if (mediaId && mediaType) { return { type: type, attrs: { type: mediaType, id: mediaId, collection: mediaCollection } }; } break; } case 'mediaGroup': { if (Array.isArray(content) && !content.some(function (e) { return e.type !== 'media'; })) { return { type: type, content: content }; } break; } case 'mention': { var mentionText = ''; var mentionId = void 0; var mentionAccess = void 0; if (attrs) { var text_2 = attrs.text, displayName = attrs.displayName, id = attrs.id, accessLevel = attrs.accessLevel; mentionText = text_2 || displayName; mentionId = id; mentionAccess = accessLevel; } if (!mentionText) { mentionText = text || '@unknown'; } if (mentionText && mentionId) { var mentionNode = { type: type, attrs: { id: mentionId, text: mentionText } }; if (mentionAccess) { mentionNode.attrs['accessLevel'] = mentionAccess; } return mentionNode; } break; } case 'paragraph': { if (content) { return { type: type, content: content }; } break; } case 'rule': { return { type: type, }; } case 'text': { var marks_1 = node.marks; if (text) { if (marks_1) { marks_1 = marks_1.reduce(function (acc, mark) { var validMark = exports.getValidMark(mark); if (validMark) { acc.push(validMark); } return acc; }, []); } return marks_1 ? { type: type, text: text, marks: marks_1 } : { type: type, text: text }; } break; } case 'heading': { if (attrs && content) { var level = attrs.level; var between = function (x, a, b) { return x >= a && x <= b; }; if (level && between(level, 1, 6)) { return { type: type, content: content, attrs: { level: level }, }; } } break; } case 'bulletList': { if (content) { return { type: type, content: content, }; } break; } case 'orderedList': { if (content) { return { type: type, content: content, attrs: { order: attrs && attrs.order }, }; } break; } case 'listItem': { if (content) { return { type: type, content: content, }; } break; } case 'blockquote': { if (content) { return { type: type, content: content, }; } break; } case 'panel': { var types = ['info', 'note', 'tip', 'warning']; if (attrs && content) { var panelType = attrs.panelType; if (types.indexOf(panelType) > -1) { return { type: type, attrs: { panelType: panelType }, content: content, }; } } break; } case 'decisionList': { return { type: type, content: content, attrs: { localId: attrs && attrs.localId || utils_1.uuid(), }, }; } case 'decisionItem': { return { type: type, content: content, attrs: { localId: attrs && attrs.localId || utils_1.uuid(), state: attrs && attrs.state || 'DECIDED' }, }; } case 'taskList': { return { type: type, content: content, attrs: { localId: attrs && attrs.localId || utils_1.uuid() }, }; } case 'taskItem': { return { type: type, content: content, attrs: { localId: attrs && attrs.localId || utils_1.uuid(), state: attrs && attrs.state || 'TODO' }, }; } case 'table': { if (Array.isArray(content) && content.length > 0 && !content.some(function (e) { return e.type !== 'tableRow'; })) { return { type: type, content: content }; } break; } case 'tableRow': { if (Array.isArray(content) && content.length > 0 && !content.some(function (e) { return e.type !== 'tableCell' && e.type !== 'tableHeader'; })) { return { type: type, content: content }; } break; } case 'tableCell': { if (content) { return { type: type, content: content }; } break; } case 'tableHeader': { if (content) { return { type: type, content: content }; } break; } } } return exports.getValidUnknownNode(node); }; /* * This method will validate a Mark according to the spec defined here * https://product-fabric.atlassian.net/wiki/spaces/E/pages/11174043/Document+structure#Documentstructure-Marks * * This is also the place to handle backwards compatibility. * * If a node is not recognized or is missing required attributes, we should return null * */ exports.getValidMark = function (mark) { var attrs = mark.attrs, type = mark.type; if (type) { switch (type) { case 'code': { return { type: type, }; } case 'em': { return { type: type, }; } case 'link': { if (attrs) { var href = attrs.href, url = attrs.url; var linkHref = href || url; if (linkHref.indexOf(':') === -1) { linkHref = "http://" + linkHref; } if (linkHref && utils_2.isSafeUrl(linkHref)) { return { type: type, attrs: { href: linkHref } }; } } break; } case 'strike': { return { type: type, }; } case 'strong': { return { type: type, }; } case 'subsup': { if (attrs && attrs['type']) { var subSupType = attrs['type']; if (exports.isSubSupType(subSupType)) { return { type: type, attrs: { type: subSupType } }; } } break; } case 'textColor': { if (attrs && TEXT_COLOR_PATTERN.test(attrs.color)) { return { type: type, attrs: attrs, }; } break; } case 'underline': { return { type: type, }; } } } return null; }; //# sourceMappingURL=validator.js.map