UNPKG

@atlaskit/editor-core

Version:

A package contains Atlassian editor core functionality

338 lines (290 loc) • 11.1 kB
import { Fragment, Node as PMNode, MediaAttributes, Schema } from '../../'; import parseCxhtml from './parse-cxhtml'; import { AC_XMLNS, FAB_XMLNS, default as encodeCxhtml } from './encode-cxhtml'; import { mapCodeLanguage } from './languageMap'; export default function encode(node: PMNode, schema: Schema<any, any>) { const docType = document.implementation.createDocumentType('html', '-//W3C//DTD XHTML 1.0 Strict//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); const doc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', docType); return encodeCxhtml(encodeFragment(node.content)); function encodeNode(node: PMNode) { if (node.isText) { return encodeText(node); } else if (node.type === schema.nodes.blockquote) { return encodeBlockquote(node); } else if (node.type === schema.nodes.bulletList) { return encodeBulletList(node); } else if (node.type === schema.nodes.heading) { return encodeHeading(node); } else if (node.type === schema.nodes.confluenceJiraIssue) { return encodeJiraIssue(node); } else if (node.type === schema.nodes.rule) { return encodeHorizontalRule(); } else if (node.type === schema.nodes.listItem) { return encodeListItem(node); } else if (node.type === schema.nodes.orderedList) { return encodeOrderedList(node); } else if (node.type === schema.nodes.paragraph) { return encodeParagraph(node); } else if (node.type === schema.nodes.hardBreak) { return encodeHardBreak(); } else if (node.type === schema.nodes.codeBlock) { return encodeCodeBlock(node); } else if (node.type === schema.nodes.panel) { return encodePanel(node); } else if (node.type === schema.nodes.mention) { return encodeMention(node); } else if (node.type === schema.nodes.confluenceUnsupportedBlock || node.type === schema.nodes.confluenceUnsupportedInline) { return encodeUnsupported(node); } else if (node.type === schema.nodes.mediaGroup) { return encodeMediaGroup(node); } else if (node.type === schema.nodes.media) { return encodeMedia(node); } else if (node.type === schema.nodes.table) { return encodeTable(node); }else { throw new Error(`Unexpected node '${(node as PMNode).type.name}' for CXHTML encoding`); } } function encodeBlockquote(node: PMNode) { const elem = doc.createElement('blockquote'); elem.appendChild(encodeFragment(node.content)); return elem; } function encodeFragment(fragment: Fragment) { const documentFragment = doc.createDocumentFragment(); fragment.forEach(node => { const domNode = encodeNode(node); if (domNode) { documentFragment.appendChild(domNode); } }); return documentFragment; } function encodeHeading(node: PMNode) { const elem = doc.createElement(`h${node.attrs.level}`); elem.appendChild(encodeFragment(node.content)); return elem; } function encodeParagraph(node: PMNode) { const elem = doc.createElement('p'); elem.appendChild(encodeFragment(node.content)); return elem; } function encodeMediaGroup(node: PMNode) { const elem = doc.createElement('p'); elem.appendChild(encodeFragment(node.content)); return elem; } function encodeMedia(node: PMNode): Element { const elem = doc.createElementNS(FAB_XMLNS, 'fab:media'); const attrs = node.attrs as MediaAttributes; elem.setAttribute('media-id', attrs.id); elem.setAttribute('media-type', attrs.type); elem.setAttribute('media-collection', attrs.collection); if (attrs.__fileName) { elem.setAttribute('file-name', attrs.__fileName); } if (attrs.__fileSize) { elem.setAttribute('file-size', `${attrs.__fileSize}`); } if (attrs.__fileMimeType) { elem.setAttribute('file-mime-type', attrs.__fileMimeType); } return elem; } function encodeTable(node: PMNode): Element { const elem = doc.createElement('table'); const tbody = doc.createElement('tbody'); node.descendants(rowNode => { const rowElement = doc.createElement('tr'); rowNode.descendants(colNode => { const cellElement = ( colNode.type === schema.nodes.tableCell ? doc.createElement('td') : doc.createElement('th') ); cellElement.appendChild(encodeFragment(colNode.content)); rowElement.appendChild(cellElement); return false; }); tbody.appendChild(rowElement); return false; }); elem.appendChild(tbody); elem.setAttribute('class', 'confluenceTable'); return elem; } function encodeText(node: PMNode) { if (node.text) { const root = doc.createDocumentFragment(); let elem = root as Node; for (const mark of node.marks) { switch (mark.type) { case schema.marks.strong: elem = elem.appendChild(doc.createElement('strong')); break; case schema.marks.em: elem = elem.appendChild(doc.createElement('em')); break; case schema.marks.strike: elem = elem.appendChild(doc.createElement('s')); break; case schema.marks.underline: elem = elem.appendChild(doc.createElement('u')); break; case schema.marks.subsup: elem = elem.appendChild(doc.createElement(mark.attrs['type'])); break; case schema.marks.code: elem = elem.appendChild(doc.createElement('code')); break; case schema.marks.mentionQuery: break; case schema.marks.link: elem = elem.appendChild(encodeLink(node)); break; default: throw new Error(`Unable to encode mark '${mark.type.name}'`); } } elem.textContent = node.text; return root; } else { return doc.createTextNode(''); } } function encodeHardBreak() { return doc.createElement('br'); } function encodeHorizontalRule() { return doc.createElement('hr'); } function encodeBulletList(node: PMNode) { const elem = doc.createElement('ul'); elem.appendChild(encodeFragment(node.content)); return elem; } function encodeOrderedList(node: PMNode) { const elem = doc.createElement('ol'); elem.appendChild(encodeFragment(node.content)); return elem; } function encodeListItem(node: PMNode) { const elem = doc.createElement('li'); elem.appendChild(encodeFragment(node.content)); return elem; } function encodeLink(node: PMNode) { const link: HTMLAnchorElement = doc.createElement('a'); link.innerHTML = node.text || ''; let href = ''; if (node.marks) { node.marks.forEach(mark => { if (mark.type.name === 'link') { href = mark.attrs.href; } }); } link.href = href; return link; } function encodeCodeBlock(node: PMNode) { const elem = createMacroElement('code'); if (node.attrs.language) { const langParam = doc.createElementNS(AC_XMLNS, 'ac:parameter'); langParam.setAttributeNS(AC_XMLNS, 'ac:name', 'language'); langParam.textContent = mapCodeLanguage(node.attrs.language); elem.appendChild(langParam); } const plainTextBody = doc.createElementNS(AC_XMLNS, 'ac:plain-text-body'); const fragment = doc.createDocumentFragment(); (node.textContent || '').split(/]]>/g).map((value, index, array) => { const isFirst = index === 0; const isLast = index === array.length - 1; const prefix = isFirst ? '' : '>'; const suffix = isLast ? '' : ']]'; return doc.createCDATASection(prefix + value + suffix); }).forEach(cdata => fragment.appendChild(cdata)); plainTextBody.appendChild(fragment); elem.appendChild(plainTextBody); return elem; } function encodePanel (node: PMNode) { const elem = createMacroElement(node.attrs.panelType); const body = doc.createElementNS(AC_XMLNS, 'ac:rich-text-body'); const fragment = doc.createDocumentFragment(); node.descendants(function (node, pos) { // there is at least one top-level paragraph node in the panel body // all text nodes will be handled by "encodeNode" if (node.isBlock) { // panel title if (node.type.name === 'heading' && pos === 0) { const title = doc.createElementNS(AC_XMLNS, 'ac:parameter'); title.setAttributeNS(AC_XMLNS, 'ac:name', 'title'); title.textContent = node.firstChild!.textContent; elem.appendChild(title); } // panel content else { const domNode = encodeNode(node); if (domNode) { fragment.appendChild(domNode); } } } return false; }); body.appendChild(fragment); elem.appendChild(body); return elem; } function encodeMention(node: PMNode) { const link = doc.createElementNS(FAB_XMLNS, 'fab:link'); const mention = doc.createElementNS(FAB_XMLNS, 'fab:mention'); mention.setAttribute('atlassian-id', node.attrs['id']); // NOTE: (ED-1736) We're stripping @ from beginning of mention text, due to Confluence compatibility issues. const cdata = doc.createCDATASection(node.attrs['text'].replace(/^@/, '')); mention.appendChild(cdata); link.appendChild(mention); return link; } function encodeUnsupported(node: PMNode) { const domNode = parseCxhtml(node.attrs.cxhtml || '').querySelector('body')!.firstChild; if (domNode) { return doc.importNode(domNode, true); } } function encodeJiraIssue(node: PMNode) { // if this is an issue list, parse it as unsupported node // @see https://product-fabric.atlassian.net/browse/ED-1193?focusedCommentId=26672&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-26672 if (!node.attrs.issueKey) { return encodeUnsupported(node); } const elem = createMacroElement('jira'); elem.setAttributeNS(AC_XMLNS, 'ac:macro-id', node.attrs.macroId); const serverParam = doc.createElementNS(AC_XMLNS, 'ac:parameter'); serverParam.setAttributeNS(AC_XMLNS, 'ac:name', 'server'); serverParam.textContent = node.attrs.server; elem.appendChild(serverParam); const serverIdParam = doc.createElementNS(AC_XMLNS, 'ac:parameter'); serverIdParam.setAttributeNS(AC_XMLNS, 'ac:name', 'serverId'); serverIdParam.textContent = node.attrs.serverId; elem.appendChild(serverIdParam); const keyParam = doc.createElementNS(AC_XMLNS, 'ac:parameter'); keyParam.setAttributeNS(AC_XMLNS, 'ac:name', 'key'); keyParam.textContent = node.attrs.issueKey; elem.appendChild(keyParam); return elem; } function createMacroElement (name) { const elem = doc.createElementNS(AC_XMLNS, 'ac:structured-macro'); elem.setAttributeNS(AC_XMLNS, 'ac:name', name); elem.setAttributeNS(AC_XMLNS, 'ac:schema-version', '1'); return elem; } }