@atlaskit/editor-jira-transformer
Version:
Editor JIRA transformer's
412 lines (400 loc) • 17.2 kB
JavaScript
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; }
import { normalizeHexColor } from '@atlaskit/adf-schema';
import { isSchemaWithLists, isSchemaWithMentions, isSchemaWithLinks, isSchemaWithAdvancedTextFormattingMarks, isSchemaWithCodeBlock, isSchemaWithBlockQuotes, isSchemaWithMedia, isSchemaWithSubSupMark, isSchemaWithTextColor, isSchemaWithTables } from '@atlaskit/adf-schema/schema-jira';
import { Fragment } from '@atlaskit/editor-prosemirror/model';
import { mapImageToEmoji } from './emojiHelper';
/**
* Ensure that each node in the fragment is a block, wrapping
* in a block node if necessary.
*/
export function ensureBlocks(fragment, schema, nodeType) {
// If all the nodes are inline, we want to wrap in a single paragraph.
if (schema.nodes.paragraph.validContent(fragment)) {
return Fragment.fromArray([schema.nodes.paragraph.createChecked({}, fragment)]);
}
// Either all the nodes are blocks, or a mix of inline and blocks.
// We convert each (if any) inline nodes to blocks.
var blockNodes = [];
// Following if condition has been added as fix for #ED-3431.
// First child of list-item should be paragraph,
// if that is not the case paragraph requires to be added.
if (nodeType && nodeType === schema.nodes.listItem && fragment.firstChild && (fragment.firstChild.type === schema.nodes.bulletList || fragment.firstChild.type === schema.nodes.orderedList)) {
blockNodes.push(schema.nodes.paragraph.createAndFill());
}
fragment.forEach(function (child) {
if (child.isBlock) {
blockNodes.push(child);
} else {
blockNodes.push(schema.nodes.paragraph.createChecked({}, child));
}
});
return Fragment.fromArray(blockNodes);
}
/**
* This function will convert all content to inline nodes
*/
export var ensureInline = function ensureInline(schema, content, supportedMarks) {
var result = [];
content.forEach(function (node) {
if (node.isInline) {
var filteredMarks = node.marks.filter(function (mark) {
return mark.isInSet(supportedMarks);
});
result.push(node.mark(filteredMarks));
return;
}
// We replace an non-inline node with UnsupportedInline node
result.push(schema.text(node.textContent));
});
return Fragment.fromArray(result);
};
export function convert(content, node, schema) {
// text
if (node.nodeType === Node.TEXT_NODE) {
var text = node.textContent;
return text ? schema.text(text) : null;
}
// marks and nodes
if (node instanceof HTMLElement) {
var tag = node.tagName.toUpperCase();
switch (tag) {
// Marks
case 'DEL':
if (!isSchemaWithAdvancedTextFormattingMarks(schema)) {
return null;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return addMarks(content, [schema.marks.strike.create()]);
case 'B':
return addMarks(content, [schema.marks.strong.create()]);
case 'EM':
return addMarks(content, [schema.marks.em.create()]);
case 'TT':
if (!isSchemaWithAdvancedTextFormattingMarks(schema)) {
return null;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return addMarks(content, [schema.marks.code.create()]);
case 'SUB':
case 'SUP':
if (!isSchemaWithSubSupMark(schema)) {
return null;
}
var type = tag === 'SUB' ? 'sub' : 'sup';
return addMarks(content, [schema.marks.subsup.create({
type: type
})]);
case 'INS':
return addMarks(content, [schema.marks.underline.create()]);
case 'FONT':
if (!isSchemaWithTextColor(schema)) {
return null;
}
var color = normalizeHexColor(node.getAttribute('color'), '#333333');
return color ? addMarks(content, [schema.marks.textColor.create({
color: color
})]) : content;
// Nodes
case 'A':
if (node.className === 'user-hover' && isSchemaWithMentions(schema)) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return schema.nodes.mention.createChecked({
id: node.getAttribute('rel'),
text: node.textContent
});
}
if (node.className.match('jira-issue-macro-key') || !content || !isSchemaWithLinks(schema)) {
return null;
}
var href = node.getAttribute('href');
var title = node.getAttribute('title');
return href ? addMarks(content, [schema.marks.link.create({
href: href,
title: title
})]) : content;
case 'SPAN':
/**
* JIRA ISSUE MACROS
* `````````````````
* <span class="jira-issue-macro" data-jira-key="ED-1">
* <a href="https://product-fabric.atlassian.net/browse/ED-1" class="jira-issue-macro-key issue-link">
* <img class="icon" src="./epic.svg" />
* ED-1
* </a>
* <span class="aui-lozenge aui-lozenge-subtle aui-lozenge-current jira-macro-single-issue-export-pdf">
* In Progress
* </span>
* </span>
*/
if (node.className.split(' ').indexOf('jira-issue-macro') > -1) {
var jiraKey = node.getAttribute('data-jira-key');
var link = node.getElementsByTagName('a')[0];
if (jiraKey && link) {
return addMarks(Fragment.from(schema.text(jiraKey)), [
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
schema.marks.link.create({
href: link.getAttribute('href'),
title: link.getAttribute('title')
})]);
}
return null;
} else if (node.className.match('jira-macro-single-issue-export-pdf')) {
return null;
} else if (node.className.match('code-')) {
// Removing spans with syntax highlighting from JIRA
return null;
} else if (isMedia(node) && isSchemaWithMedia(schema)) {
var dataNode = node.querySelector('[data-media-services-id]');
if (dataNode && dataNode instanceof HTMLElement) {
var id = dataNode.getAttribute('data-media-services-id');
var _type = dataNode.getAttribute('data-media-services-type');
var collection = dataNode.getAttribute('data-media-services-collection') || '';
var attachmentName = dataNode.getAttribute('data-attachment-name');
var attachmentType = dataNode.getAttribute('data-attachment-type');
var fileName = dataNode.getAttribute('data-file-name');
var displayType = dataNode.getAttribute('data-display-type');
var width = parseInt(dataNode.getAttribute('data-width') || '', 10);
var height = parseInt(dataNode.getAttribute('data-height') || '', 10);
return schema.nodes.media.createChecked({
id: id,
type: _type,
collection: collection,
width: width || null,
height: height || null,
__fileName: attachmentName || fileName,
__displayType: attachmentType || displayType || 'thumbnail'
});
}
}
break;
case 'IMG':
if (node.parentElement && node.parentElement.className.match('jira-issue-macro-key')) {
return null;
} else if (node.className === 'emoticon') {
var emojiResult = mapImageToEmoji(node);
if (emojiResult) {
return schema.text(emojiResult);
}
}
break;
case 'H1':
case 'H2':
case 'H3':
case 'H4':
case 'H5':
case 'H6':
var level = Number(tag.charAt(1));
var supportedMarks = [schema.marks.link].filter(function (mark) {
return !!mark;
});
return schema.nodes.heading.createChecked(
// @see ED-4708
{
level: level === 6 ? 5 : level
}, schema.nodes.heading.validContent(content) ? content :
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ensureInline(schema, content, supportedMarks));
case 'BR':
return schema.nodes.hardBreak.createChecked();
case 'HR':
return schema.nodes.rule.createChecked();
case 'P':
if (node.firstElementChild && isMedia(node.firstElementChild)) {
// Filter out whitespace text nodes
var _schema$nodes = schema.nodes,
mediaSingle = _schema$nodes.mediaSingle,
mediaGroup = _schema$nodes.mediaGroup,
paragraph = _schema$nodes.paragraph;
var mediaArray = [];
var contentArray = [];
var fragmentArray = [];
var hasNonMediaChildren = false;
content.forEach(function (child) {
if (child.type === schema.nodes.media) {
mediaArray.push(child);
return;
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
} else if (!(child.isText && /^\s*$/.test(child.text || ''))) {
hasNonMediaChildren = true;
}
contentArray.push(child);
});
if (hasNonMediaChildren && contentArray.length) {
fragmentArray.push(paragraph.createChecked({}, contentArray));
}
if (isSchemaWithMedia(schema) && mediaArray.length) {
var mediaNodeType = isMediaSingle(node.firstElementChild) ? mediaSingle : mediaGroup;
fragmentArray.push(mediaNodeType.createChecked({}, mediaArray));
}
if (fragmentArray.length) {
return Fragment.fromArray(fragmentArray);
}
return null;
}
return schema.nodes.paragraph.createChecked({}, content);
}
// lists
if (isSchemaWithLists(schema)) {
switch (tag) {
case 'UL':
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return schema.nodes.bulletList.createChecked({}, content);
case 'OL':
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return schema.nodes.orderedList.createChecked({}, content);
case 'LI':
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var compatibleContent = schema.nodes.listItem.validContent(content) ? content : ensureBlocks(content, schema, schema.nodes.listItem);
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return schema.nodes.listItem.createChecked({}, compatibleContent);
}
}
// code block
if (isSchemaWithCodeBlock(schema)) {
switch (tag) {
case 'DIV':
if (node.className === 'codeContent panelContent' || node.className.match('preformattedContent')) {
return null;
} else if (node.className === 'code panel' || node.className === 'preformatted panel') {
var pre = node.querySelector('pre');
if (!pre) {
return null;
}
var language = node.className === 'preformatted panel' ? 'plain' : pre.className.split('-')[1];
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var textContent = (pre.textContent || '').replace(/\r\n/g, '\n');
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return schema.nodes.codeBlock.createChecked({
language: language
}, textContent ? schema.text(textContent) : undefined);
}
break;
case 'PRE':
return null;
}
}
if (isSchemaWithBlockQuotes(schema) && tag === 'BLOCKQUOTE') {
var blockquoteContent =
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content && content.content.length ? content : schema.nodes.paragraph.createChecked();
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return schema.nodes.blockquote.createChecked({}, blockquoteContent);
}
// table
if (isSchemaWithTables(schema)) {
switch (tag) {
case 'TABLE':
return schema.nodes.table.createChecked({}, content);
case 'TR':
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return schema.nodes.tableRow.createChecked({}, content);
case 'TD':
var tdContent = schema.nodes.tableCell.validContent(content) ? content : ensureBlocks(content, schema);
return schema.nodes.tableCell.createChecked({}, tdContent);
case 'TH':
var thContent = schema.nodes.tableHeader.validContent(content) ? content : ensureBlocks(content, schema);
return schema.nodes.tableHeader.createChecked({}, thContent);
}
}
}
return;
}
/*
* Flattens DOM tree into single array
*/
export function bfsOrder(root) {
var inqueue = [root];
var outqueue = [];
var elem;
// Ignored via go/ees005
// eslint-disable-next-line no-cond-assign
while (elem = inqueue.shift()) {
outqueue.push(elem);
var childIndex = void 0;
for (childIndex = 0; childIndex < elem.childNodes.length; childIndex++) {
var child = elem.childNodes[childIndex];
switch (child.nodeType) {
case Node.ELEMENT_NODE:
case Node.TEXT_NODE:
inqueue.push(child);
break;
default:
// eslint-disable-next-line no-console
console.error("Not pushing: ".concat(child.nodeType, " ").concat(child.nodeName));
}
}
}
outqueue.shift();
return outqueue;
}
/**
* Create a fragment by adding a set of marks to each node.
*/
function addMarks(fragment, marks) {
var result = fragment;
for (var i = 0; i < fragment.childCount; i++) {
var child = result.child(i);
var newChild = child;
var _iterator = _createForOfIteratorHelper(marks),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var mark = _step.value;
newChild = newChild.mark(mark.addToSet(newChild.marks));
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
result = result.replaceChild(i, newChild);
}
return result;
}
function getNodeName(node) {
return node.nodeName.toUpperCase();
}
function isMedia(node) {
if (node && node instanceof HTMLElement) {
if (node.parentNode && getNodeName(node.parentNode) === 'P') {
if (getNodeName(node) === 'SPAN') {
return !!node.querySelector('a > jira-attachment-thumbnail > img[data-attachment-type="thumbnail"], ' + 'a[data-attachment-type="file"]');
}
}
}
return false;
}
function isMediaSingle(node) {
if (isMedia(node)) {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
var dataNode = node.querySelector('[data-media-services-id]');
if (dataNode instanceof HTMLElement) {
var width = parseInt(dataNode.getAttribute('data-width') || '', 10);
var height = parseInt(dataNode.getAttribute('data-height') || '', 10);
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
if (node.parentNode.classList.contains('mediaSingle') && width && height) {
return true;
}
}
}
return false;
}