UNPKG

@atlaskit/adf-schema

Version:

Shared package that contains the ADF-schema (json) and ProseMirror node/mark specs

144 lines (139 loc) 4.35 kB
import { Fragment } from '@atlaskit/editor-prosemirror/model'; import { codeBlock as codeBlockFactory } from '../../next-schema/generated/nodeTypes'; /** * @name codeBlock_with_no_marks_node */ /** * @name codeBlock_node */ const getLanguageFromEditorStyle = dom => { return dom.getAttribute('data-language') || undefined; }; // example of BB style: // <div class="codehilite language-javascript"><pre><span>hello world</span><span>\n</span></pre></div> const getLanguageFromBitbucketStyle = dom => { if (dom && dom.classList.contains('codehilite')) { // code block html from Bitbucket always contains an extra new line return extractLanguageFromClass(dom.className); } return; }; // If there is a child code element, check that for data-language const getLanguageFromCode = dom => { const firstChild = dom.firstElementChild; if (firstChild && firstChild.nodeName === 'CODE') { return firstChild.getAttribute('data-language') || undefined; } }; const extractLanguageFromClass = className => { const languageRegex = /(?:^|\s)language-([^\s]+)/; const result = languageRegex.exec(className); if (result && result[1]) { return result[1]; } return; }; const removeLastNewLine = dom => { const parent = dom && dom.parentElement; if (parent && parent.classList.contains('codehilite')) { dom.textContent = dom.textContent.replace(/\n$/, ''); } return dom; }; function parseCodeFromHtml(node) { let code = ''; node.childNodes.forEach(child => { if (child.nodeType === Node.TEXT_NODE) { // append text code += child.nodeValue; } else if (child.nodeType === Node.ELEMENT_NODE && child instanceof Element) { const tagName = child.tagName.toLowerCase(); if (tagName === 'div' || tagName === 'p') { // add a newline before its content, unless it's the first child to avoid leading newlines if (child.previousElementSibling !== null) { code += '\n'; } } if (tagName === 'br') { code += '\n'; } else { code += parseCodeFromHtml(child); } } }); return code; } export const codeBlock = codeBlockFactory({ parseDOM: [{ tag: 'pre', preserveWhitespace: 'full', getAttrs: domNode => { let dom = domNode; const language = getLanguageFromBitbucketStyle(dom.parentElement) || getLanguageFromEditorStyle(dom.parentElement) || getLanguageFromCode(dom) || dom.getAttribute('data-language'); dom = removeLastNewLine(dom); return { language }; } }, // Handle VSCode, Android Studio paste // Checking `white-space: pre-wrap` is too aggressive @see ED-2627 { tag: 'div[style]', preserveWhitespace: 'full', getAttrs: domNode => { const dom = domNode; if (dom.style.whiteSpace === 'pre' || dom.style.fontFamily && dom.style.fontFamily.toLowerCase().indexOf('monospace') > -1) { return {}; } return false; }, getContent: (domNode, schema) => { const code = parseCodeFromHtml(domNode); return code ? Fragment.from(schema.text(code)) : Fragment.empty; } }, // Handle GitHub/Gist paste { tag: 'table[style]', preserveWhitespace: 'full', getAttrs: dom => { if (dom.querySelector('td[class*="blob-code"]')) { return {}; } return false; } }, { tag: 'div.code-block', preserveWhitespace: 'full', getAttrs: domNode => { const dom = domNode; // TODO: ED-5604 Fix it inside `react-syntax-highlighter` // Remove line numbers const lineNumber = dom.querySelectorAll('.react-syntax-highlighter-line-number'); if (lineNumber.length > 0) { // It's possible to copy without the line numbers too hence this // `react-syntax-highlighter-line-number` check, so that we don't remove real code lineNumber.forEach(line => line.remove()); } return {}; } }], toDOM(node) { return ['pre', ['code', { 'data-language': node.attrs.language }, 0]]; } }); export const toJSON = node => ({ attrs: Object.keys(node.attrs).reduce((memo, key) => { if (key === 'uniqueId') { return memo; } if (key === 'language' && node.attrs.language === null) { return memo; } memo[key] = node.attrs[key]; return memo; }, {}) });