@atlaskit/editor-core
Version:
A package contains Atlassian editor core functionality
338 lines (290 loc) • 11.1 kB
text/typescript
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;
}
}