UNPKG

@atlaskit/editor-core

Version:

A package contains Atlassian editor core functionality

110 lines (94 loc) 3.65 kB
/** * A replacement for `Array.from` until it becomes widely implemented. */ function arrayFrom(obj: any): any[] { return Array.prototype.slice.call(obj); } /** * A replacement for `String.repeat` until it becomes widely available. */ export function stringRepeat(text: string, length: number): string { let result = ''; for (let x = 0; x < length; x++) { result += text; } return result; } /** * This function escapes all plain-text sequences that might get converted into markdown * formatting by Bitbucket server (via python-markdown). * @see MarkdownSerializerState.esc() */ export function escapeMarkdown(str: string, startOfLine?: boolean): string { let strToEscape = str || ''; strToEscape = strToEscape.replace(/[`*\\+_|()\[\]{}]/g, '\\$&'); if (startOfLine) { strToEscape = strToEscape.replace(/^[#-*]/, '\\$&').replace(/^(\d+)\./, '$1\\.'); } return strToEscape; } /** * This function gets markup rendered by Bitbucket server and transforms it into markup that * can be consumed by Prosemirror HTML parser, conforming to our schema. */ export function transformHtml(html: string): HTMLElement { const el = document.createElement('div'); el.innerHTML = html; // Remove zero-width-non-joiner arrayFrom(el.querySelectorAll('p')).forEach((p: HTMLParagraphElement) => { const zwnj = /\u200c/g; if (p.textContent && zwnj.test(p.textContent)) { p.textContent = p.textContent.replace(zwnj, ''); } }); // Convert mention containers, i.e.: // <a href="/abodera/" rel="nofollow" title="@abodera" class="mention mention-me">Artur Bodera</a> arrayFrom(el.querySelectorAll('a.mention')).forEach((a: HTMLLinkElement) => { const span = document.createElement('span'); span.setAttribute('class', 'editor-entity-mention'); span.setAttribute('contenteditable', 'false'); const title = a.getAttribute('title') || ''; if (title) { const usernameMatch = title.match(/^@(.*?)$/); if (usernameMatch) { const username = usernameMatch[1]; span.setAttribute('data-mention-id', username); } } const text = a.textContent || ''; if (text.indexOf('@') === 0) { span.textContent = a.textContent; } else { span.textContent = `@${a.textContent}`; } a.parentNode!.insertBefore(span, a); a.parentNode!.removeChild(a); }); // Parse emojis i.e. // <img src="https://d301sr5gafysq2.cloudfront.net/207268dc597d/emoji/img/diamond_shape_with_a_dot_inside.svg" alt="diamond shape with a dot inside" title="diamond shape with a dot inside" class="emoji"> arrayFrom(el.querySelectorAll('img.emoji')).forEach((img: HTMLImageElement) => { const span = document.createElement('span'); let shortName = img.getAttribute('data-emoji-short-name') || ''; if (!shortName) { // Fallback to parsing Bitbucket's src attributes to find the // short name const src = img.getAttribute('src'); const idMatch = !src ? false : src.match(/([^\/]+)\.[^\/]+$/); if (idMatch) { shortName = `:${decodeURIComponent(idMatch[1])}:`; } } if (shortName) { span.setAttribute('data-emoji-short-name', shortName); } img.parentNode!.insertBefore(span, img); img.parentNode!.removeChild(img); }); // Convert all automatic links to plain text, because they will be re-created on render by the server arrayFrom(el.querySelectorAll('a[rel="nofollow"]')).forEach((a: HTMLLinkElement) => { const text = document.createTextNode(a.innerText); a.parentNode!.insertBefore(text, a); a.parentNode!.removeChild(a); }); return el; }