UNPKG

@atlaskit/editor-jira-transformer

Version:
540 lines (527 loc) 22.9 kB
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(/&amp;/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; } }]); }();