@atlaskit/editor-jira-transformer
Version:
Editor JIRA transformer's
540 lines (527 loc) • 22.9 kB
JavaScript
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
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 { Fragment, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
import parseHtml from './parse-html';
import fixDoc from './fix-doc';
import { bfsOrder, convert, ensureBlocks } from './utils';
import { isSchemaWithLists, isSchemaWithMentions, isSchemaWithEmojis, isSchemaWithCodeBlock, isSchemaWithBlockQuotes, isSchemaWithMedia, isSchemaWithTables } from '@atlaskit/adf-schema/schema-jira';
export var JIRATransformer = /*#__PURE__*/function () {
function JIRATransformer(schema, customEncoders, mediaContextInfo) {
_classCallCheck(this, JIRATransformer);
this.schema = schema;
this.customEncoders = customEncoders || {};
this.mediaContextInfo = mediaContextInfo;
}
return _createClass(JIRATransformer, [{
key: "encode",
value: function encode(node) {
this.doc = this.makeDocument();
this.doc.body.appendChild(this.encodeFragment(node.content));
var html = this.doc.body.innerHTML;
// JIRA encodes empty documents as an empty string
if (html === '<p></p>') {
return '';
}
// Normalise to XHTML style self closing tags.
return html
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
.replace(/<br><\/br>/g, '<br />')
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
.replace(/<br>/g, '<br />')
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
.replace(/<hr><\/hr>/g, '<hr />')
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
.replace(/<hr>/g, '<hr />')
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
.replace(/&/g, '&');
}
}, {
key: "parse",
value: function parse(html) {
var convertedNodes = new WeakMap();
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var dom = fixDoc(parseHtml(html)).querySelector('body');
var nodes = bfsOrder(dom);
// JIRA encodes empty content as a single nbsp
if (nodes.length === 1 && nodes[0].textContent === '\xa0') {
var schemaNodes = this.schema.nodes;
return schemaNodes.doc.createChecked({}, schemaNodes.paragraph.createChecked());
}
// Process through nodes in reverse (so deepest child elements are first).
for (var i = nodes.length - 1; i >= 0; i--) {
var node = nodes[i];
// for tables we take tbody content, because tbody is not in schema so the whole bfs thing wouldn't work
var targetNode =
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
node.tagName && node.tagName.toUpperCase() === 'TABLE' ? node.firstChild : node;
var _content = this.getContent(targetNode, convertedNodes);
var candidate = convert(_content, node, this.schema);
if (typeof candidate !== 'undefined') {
convertedNodes.set(node, candidate);
}
}
var content = this.getContent(dom, convertedNodes);
// Dangling inline nodes can't be directly inserted into a document, so
// we attempt to wrap in a paragraph.
var compatibleContent = this.schema.nodes.doc.validContent(content) ? content : ensureBlocks(content, this.schema);
return this.schema.nodes.doc.createChecked({}, compatibleContent);
}
/*
* Contructs a struct string of replacement blocks and marks for a given node
*/
}, {
key: "getContent",
value: function getContent(node, convertedNodes) {
var fragment = Fragment.fromArray([]);
var childIndex;
for (childIndex = 0; childIndex < node.childNodes.length; childIndex++) {
var child = node.childNodes[childIndex];
var thing = convertedNodes.get(child);
if (thing instanceof Fragment || thing instanceof PMNode) {
fragment = fragment.append(Fragment.from(thing));
}
}
return fragment;
}
}, {
key: "encodeNode",
value: function encodeNode(node) {
var _this$schema$nodes = this.schema.nodes,
blockquote = _this$schema$nodes.blockquote,
bulletList = _this$schema$nodes.bulletList,
codeBlock = _this$schema$nodes.codeBlock,
hardBreak = _this$schema$nodes.hardBreak,
heading = _this$schema$nodes.heading,
listItem = _this$schema$nodes.listItem,
mention = _this$schema$nodes.mention,
emoji = _this$schema$nodes.emoji,
orderedList = _this$schema$nodes.orderedList,
paragraph = _this$schema$nodes.paragraph,
rule = _this$schema$nodes.rule,
mediaInline = _this$schema$nodes.mediaInline,
mediaGroup = _this$schema$nodes.mediaGroup,
mediaSingle = _this$schema$nodes.mediaSingle,
media = _this$schema$nodes.media,
table = _this$schema$nodes.table;
// We compare node type names in this method rather than
// node types directly as the node comparision fails.
// This suggests the schema which is coming from
// the doc has been recreated after being passed
// to this transformer on setup which is somewhat
// unexpected.
// This package is internally (within editor) considered
// "obsolete", and we are aiming to either hand over
// or deprecate.
// This "fix" is being done to unblock an existing
// consumer so they can apply an unrelated security
// upgrade.
if (node.isText) {
return this.encodeText(node);
} else if (node.type.name === heading.name) {
return this.encodeHeading(node);
} else if (node.type.name === rule.name) {
return this.encodeHorizontalRule();
} else if (node.type.name === paragraph.name) {
return this.encodeParagraph(node);
} else if (node.type.name === hardBreak.name) {
return this.encodeHardBreak();
}
if (isSchemaWithLists(this.schema)) {
if (node.type.name === bulletList.name) {
return this.encodeBulletList(node);
} else if (node.type.name === orderedList.name) {
return this.encodeOrderedList(node);
} else if (node.type.name === listItem.name) {
return this.encodeListItem(node);
}
}
if (isSchemaWithMentions(this.schema) && node.type.name === mention.name) {
return this.encodeMention(node, this.customEncoders.mention);
}
if (isSchemaWithEmojis(this.schema) && node.type.name === emoji.name) {
return this.encodeEmoji(node);
}
if (isSchemaWithCodeBlock(this.schema) && node.type.name === codeBlock.name) {
return this.encodeCodeBlock(node);
}
if (isSchemaWithBlockQuotes(this.schema) && node.type.name === blockquote.name) {
return this.encodeBlockQuote(node);
}
if (isSchemaWithMedia(this.schema)) {
if (node.type.name === mediaGroup.name) {
return this.encodeMediaGroup(node);
} else if (node.type.name === mediaSingle.name) {
return this.encodeMediaSingle(node);
} else if (node.type.name === media.name || node.type.name === mediaInline.name) {
return this.encodeMedia(node);
}
}
if (isSchemaWithTables(this.schema) && node.type.name === table.name) {
return this.encodeTable(node);
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
throw new Error("Unexpected node '".concat(node.type.name, "' for HTML encoding"));
}
}, {
key: "makeDocument",
value: function makeDocument() {
var doc = document.implementation.createHTMLDocument('');
doc.body = doc.createElement('body');
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
doc.documentElement.appendChild(doc.body);
return doc;
}
}, {
key: "encodeFragment",
value: function encodeFragment(fragment) {
var _this = this;
var documentFragment = this.doc.createDocumentFragment();
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
fragment.forEach(function (node) {
return documentFragment.appendChild(_this.encodeNode(node));
});
return documentFragment;
}
}, {
key: "encodeHeading",
value: function encodeHeading(node) {
function anchorNameEncode(name) {
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var noSpaces = name.replace(/ /g, '');
var uriEncoded = encodeURIComponent(noSpaces);
var specialsEncoded = uriEncoded.replace(
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
/[!'()*]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16);
});
return specialsEncoded;
}
var level = node.attrs.level;
// @see ED-4708
var elem = this.doc.createElement("h".concat(level === 6 ? 5 : level));
var anchor = this.doc.createElement('a');
anchor.setAttribute('name', anchorNameEncode(node.textContent));
elem.appendChild(anchor);
elem.appendChild(this.encodeFragment(node.content));
return elem;
}
}, {
key: "encodeParagraph",
value: function encodeParagraph(node) {
var elem = this.doc.createElement('p');
elem.appendChild(this.encodeFragment(node.content));
return elem;
}
}, {
key: "encodeText",
value: function encodeText(node) {
if (node.text) {
var root = this.doc.createDocumentFragment();
var elem = root;
var _this$schema$marks = this.schema.marks,
code = _this$schema$marks.code,
em = _this$schema$marks.em,
link = _this$schema$marks.link,
typeAheadQuery = _this$schema$marks.typeAheadQuery,
strike = _this$schema$marks.strike,
strong = _this$schema$marks.strong,
subsup = _this$schema$marks.subsup,
underline = _this$schema$marks.underline,
textColor = _this$schema$marks.textColor;
var _iterator = _createForOfIteratorHelper(node.marks),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var mark = _step.value;
switch (mark.type) {
case strong:
elem = elem.appendChild(this.doc.createElement('b'));
break;
case em:
elem = elem.appendChild(this.doc.createElement('em'));
break;
case code:
elem = elem.appendChild(this.doc.createElement('tt'));
break;
case strike:
elem = elem.appendChild(this.doc.createElement('del'));
break;
case underline:
elem = elem.appendChild(this.doc.createElement('ins'));
break;
case subsup:
elem = elem.appendChild(this.doc.createElement(mark.attrs['type']));
break;
case link:
var linkElem = this.doc.createElement('a');
var href = mark.attrs['href'];
/** JIRA always expects external-link attribute set on links created via editor unless its #fragment */
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
if (!href.match(/^#/)) {
linkElem.setAttribute('class', 'external-link');
linkElem.setAttribute('rel', 'nofollow');
}
linkElem.setAttribute('href', href);
if (mark.attrs['title']) {
linkElem.setAttribute('title', mark.attrs['title']);
}
elem = elem.appendChild(linkElem);
break;
case textColor:
var fontElem = this.doc.createElement('font');
fontElem.setAttribute('color', mark.attrs['color']);
elem = elem.appendChild(fontElem);
break;
case typeAheadQuery:
break;
default:
throw new Error("Unable to encode mark '".concat(mark.type.name, "'"));
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
elem.textContent = node.text;
return root;
} else {
return this.doc.createTextNode('');
}
}
}, {
key: "encodeHardBreak",
value: function encodeHardBreak() {
return this.doc.createElement('br');
}
}, {
key: "encodeHorizontalRule",
value: function encodeHorizontalRule() {
return this.doc.createElement('hr');
}
}, {
key: "encodeBulletList",
value: function encodeBulletList(node) {
var elem = this.doc.createElement('ul');
elem.setAttribute('class', 'alternate');
elem.setAttribute('type', 'disc');
elem.appendChild(this.encodeFragment(node.content));
for (var index = 0; index < elem.childElementCount; index++) {
elem.children[index].setAttribute('data-parent', 'ul');
}
return elem;
}
}, {
key: "encodeOrderedList",
value: function encodeOrderedList(node) {
var elem = this.doc.createElement('ol');
elem.appendChild(this.encodeFragment(node.content));
for (var index = 0; index < elem.childElementCount; index++) {
elem.children[index].setAttribute('data-parent', 'ol');
}
return elem;
}
}, {
key: "encodeListItem",
value: function encodeListItem(node) {
var _this2 = this;
var elem = this.doc.createElement('li');
if (node.content.childCount) {
var hasBlocks = false;
node.content.forEach(function (childNode) {
if (childNode.type === _this2.schema.nodes.bulletList || childNode.type === _this2.schema.nodes.orderedList) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var list = _this2.encodeNode(childNode);
/**
* Changing type for nested list:
*
* Second level -> circle
* Third and deeper -> square
*/
if (list instanceof HTMLElement && list.tagName === 'UL') {
list.setAttribute('type', 'circle');
[].forEach.call(list.querySelectorAll('ul'), function (ul) {
ul.setAttribute('type', 'square');
});
}
elem.appendChild(list);
} else if (childNode.type.name === 'paragraph' && !hasBlocks) {
// Strip the paragraph node from the list item.
elem.appendChild(_this2.encodeFragment(childNode.content));
} else {
if (childNode.isBlock) {
hasBlocks = true;
}
elem.appendChild(_this2.encodeNode(childNode));
}
});
}
return elem;
}
}, {
key: "encodeMention",
value: function encodeMention(node, encoder) {
var elem = this.doc.createElement('a');
elem.setAttribute('class', 'user-hover');
elem.setAttribute('href', encoder ? encoder(node.attrs.id) : node.attrs.id);
elem.setAttribute('rel', node.attrs.id);
elem.appendChild(this.doc.createTextNode(node.attrs.text));
return elem;
}
}, {
key: "encodeEmoji",
value: function encodeEmoji(node) {
return this.doc.createTextNode(node.attrs && node.attrs.text);
}
}, {
key: "encodeCodeBlock",
value: function encodeCodeBlock(node) {
var elem = this.doc.createElement('div');
elem.setAttribute('class', 'code panel');
var content = this.doc.createElement('div');
content.setAttribute('class', 'codeContent panelContent');
var pre = this.doc.createElement('pre');
// java is default language for JIRA
pre.setAttribute('class', "code-".concat((node.attrs.language || 'plain').toLocaleLowerCase()));
pre.appendChild(this.encodeFragment(node.content));
content.appendChild(pre);
elem.appendChild(content);
return elem;
}
}, {
key: "encodeBlockQuote",
value: function encodeBlockQuote(node) {
var elem = this.doc.createElement('blockquote');
elem.appendChild(this.encodeFragment(node.content));
return elem;
}
}, {
key: "encodeMediaGroup",
value: function encodeMediaGroup(node) {
var elem = this.doc.createElement('p');
elem.setAttribute('class', 'mediaGroup');
elem.appendChild(this.encodeFragment(node.content));
return elem;
}
}, {
key: "encodeMediaSingle",
value: function encodeMediaSingle(node) {
var elem = this.doc.createElement('p');
elem.setAttribute('class', 'mediaSingle');
elem.appendChild(this.encodeFragment(node.content));
return elem;
}
}, {
key: "addDataToNode",
value: function addDataToNode(domNode, mediaNode) {
var defaultDisplayType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'thumbnail';
var _mediaNode$attrs = mediaNode.attrs,
id = _mediaNode$attrs.id,
type = _mediaNode$attrs.type,
collection = _mediaNode$attrs.collection,
__fileName = _mediaNode$attrs.__fileName,
__displayType = _mediaNode$attrs.__displayType,
width = _mediaNode$attrs.width,
height = _mediaNode$attrs.height;
// Order of dataset matters in IE Edge, please keep the current order
domNode.setAttribute('data-attachment-type', __displayType || defaultDisplayType);
if (__fileName) {
domNode.setAttribute('data-attachment-name', __fileName);
}
if (width) {
domNode.setAttribute('data-width', width);
}
if (height) {
domNode.setAttribute('data-height', height);
}
domNode.setAttribute('data-media-services-type', type);
domNode.setAttribute('data-media-services-id', id);
if (collection) {
domNode.setAttribute('data-media-services-collection', collection);
}
}
}, {
key: "buildURLWithContextInfo",
value: function buildURLWithContextInfo(fileId, contextInfo) {
var clientId = contextInfo.clientId,
baseUrl = contextInfo.baseUrl,
token = contextInfo.token,
collection = contextInfo.collection;
return "".concat(baseUrl, "/file/").concat(fileId, "/image?token=").concat(token, "&client=").concat(clientId, "&collection=").concat(collection, "&width=200&height=200&mode=fit");
}
}, {
key: "isImageMimeType",
value: function isImageMimeType(mimeType) {
return mimeType && mimeType.indexOf('image/') > -1;
}
}, {
key: "encodeMedia",
value: function encodeMedia(node) {
// span.image-wrap > a > jira-attachment-thumbnail > img[data-media-*] > content
// span.no-br > a[data-media] > content
var elem = this.doc.createElement('span');
var a = this.doc.createElement('a');
if (node.attrs.__displayType === 'file' || !(node.attrs.__displayType || this.isImageMimeType(node.attrs.__fileMimeType))) {
elem.setAttribute('class', 'nobr');
this.addDataToNode(a, node, 'file');
a.textContent = node.attrs.__fileName || '';
} else {
elem.setAttribute('class', 'image-wrap');
var img = this.doc.createElement('img');
img.setAttribute('alt', node.attrs.__fileName);
// Newly uploaded items have collection
if (node.attrs.collection && this.mediaContextInfo && this.mediaContextInfo.uploadContext) {
img.setAttribute('src', this.buildURLWithContextInfo(node.attrs.id, this.mediaContextInfo.uploadContext));
} else if (this.mediaContextInfo && this.mediaContextInfo.viewContext) {
img.setAttribute('src', this.buildURLWithContextInfo(node.attrs.id, this.mediaContextInfo.viewContext));
}
this.addDataToNode(img, node);
var jiraThumb = this.doc.createElement('jira-attachment-thumbnail');
jiraThumb.appendChild(img);
a.appendChild(jiraThumb);
}
elem.appendChild(a);
return elem;
}
}, {
key: "encodeTable",
value: function encodeTable(node) {
var _this3 = this;
var elem = this.doc.createElement('table');
var tbody = this.doc.createElement('tbody');
node.descendants(function (rowNode) {
var rowElement = _this3.doc.createElement('tr');
rowNode.descendants(function (colNode) {
var cellType = colNode.type === _this3.schema.nodes.tableCell ? 'd' : 'h';
var cellElement = _this3.doc.createElement("t".concat(cellType));
cellElement.setAttribute('class', "confluenceT".concat(cellType));
cellElement.appendChild(_this3.encodeFragment(colNode.content));
rowElement.appendChild(cellElement);
return false;
});
tbody.appendChild(rowElement);
return false;
});
elem.appendChild(tbody);
elem.setAttribute('class', 'confluenceTable');
return elem;
}
}]);
}();