UNPKG

@atlaskit/editor-wikimarkup-transformer

Version:

Wiki markup transformer for JIRA and Confluence

185 lines (184 loc) 6.99 kB
import { createTextNode } from './nodes/text'; import { parseOtherKeyword, parseLeadingKeyword, parseMacroKeyword, parseIssueKeyword } from './tokenize/keyword'; import { parseToken, TokenType } from './tokenize'; import { parseWhitespaceOnly } from './tokenize/whitespace'; import { escapeHandler } from './utils/escape'; import { normalizePMNodes } from './utils/normalize'; const processState = { NEWLINE: 0, BUFFER: 1, TOKEN: 2, ESCAPE: 3 }; export function parseString({ input, schema, ignoreTokenTypes = [], context, includeLeadingSpace = false }) { let index = 0; let state = processState.NEWLINE; let buffer = []; let tokenType = TokenType.STRING; let newLines = []; const output = []; let inlineNodes = []; while (index < input.length) { const char = input.charAt(index); switch (state) { case processState.NEWLINE: { /** * During this state, the parser will trim leading * spaces and looking for any leading keywords. */ const substring = input.substring(index); const length = parseWhitespaceOnly(substring); if (length) { index += length; if (includeLeadingSpace) { buffer.push(char); } continue; } const match = parseLeadingKeyword(substring) || parseMacroKeyword(substring) || parseOtherKeyword(substring) || parseIssueKeyword(substring, context.issueKeyRegex); if (match && ignoreTokenTypes.indexOf(match.type) === -1) { tokenType = match.type; state = processState.TOKEN; continue; } else { state = processState.BUFFER; continue; } } case processState.BUFFER: { /** * During this state, the parser will start * saving plaintext into the buffer until it hits * a keyword */ const substring = input.substring(index); /** * If the previous char is not a alphanumeric, we will parse * format keywords. * If the previous char is '{', we need to skip parse macro * keyword */ let match = null; if (buffer.length > 0 && buffer[buffer.length - 1].endsWith('{')) { match = parseOtherKeyword(substring); } else { match = parseMacroKeyword(substring) || parseOtherKeyword(substring) || parseIssueKeyword(substring, context.issueKeyRegex); } if (match && ignoreTokenTypes.indexOf(match.type) === -1) { tokenType = match.type; state = processState.TOKEN; continue; } if (char === '\\') { state = processState.ESCAPE; continue; } buffer.push(char); break; } case processState.TOKEN: { const token = parseToken(input, tokenType, index, schema, context); if (token.type === 'text') { buffer.push(token.text); } else if (token.type === 'pmnode') { /*ESS-2539 We are keeping track of consecutive newLines in the newLines array Whenever more than two consecutive newLines are encountered, we start a new paragraph */ if (newLines.length >= 2 && (tokenType !== TokenType.HARD_BREAK || buffer.length > 0)) { output.push(...normalizePMNodes(inlineNodes, schema)); // push newLines to the buffer as a separator between media nodes inlineNodes = isConsecutiveMediaGroups(inlineNodes, token.nodes) ? [...newLines] : []; newLines = []; } if (inlineNodes.length === 0) { newLines = []; } if (newLines.length > 0 && isNewLineRequiredBetweenNodes(inlineNodes, buffer, token.nodes)) { inlineNodes.push(...newLines); newLines = []; } inlineNodes.push(...createTextNode(buffer.join(''), schema)); if (tokenType === TokenType.HARD_BREAK) { newLines.push(...token.nodes); } else { inlineNodes.push(...token.nodes); if (token.nodes.length > 0) { newLines = []; } } buffer = []; // clear the buffer } index += token.length; if (tokenType === TokenType.HARD_BREAK) { state = processState.NEWLINE; } else { state = processState.BUFFER; } continue; } case processState.ESCAPE: { const token = escapeHandler(input, index); buffer.push(token.text); index += token.length; state = processState.BUFFER; continue; } default: } index++; } const bufferedStr = buffer.join(''); if (bufferedStr.length > 0) { // Wrapping the rest of the buffer into a text node if (newLines.length >= 2) { // normalize the nodes already parsed if more than two consecutive newLines are encountered output.push(...normalizePMNodes(inlineNodes, schema)); inlineNodes = []; newLines = []; } if (newLines.length > 0 && inlineNodes.length > 0 && !inlineNodes[inlineNodes.length - 1].isBlock) { inlineNodes.push(...newLines); } inlineNodes.push(...createTextNode(bufferedStr, schema)); } return [...output, ...inlineNodes]; } /* checks whether a newLine is required between two consecutive nodes Returns true for inline nodes, false for block nodes */ function isNewLineRequiredBetweenNodes(currentNodes, buffer, nextNodes) { var _currentNodes; if (currentNodes.length === 0) { return false; } if (buffer.length > 0 && (_currentNodes = currentNodes[currentNodes.length - 1]) !== null && _currentNodes !== void 0 && _currentNodes.isBlock) { return false; } if (buffer.length === 0) { var _nextNodes$, _nextNodes$2, _currentNodes2; if (nextNodes.length === 0) { return false; } if (((_nextNodes$ = nextNodes[0]) === null || _nextNodes$ === void 0 ? void 0 : _nextNodes$.type.name) === 'hardBreak') { return false; } if ((_nextNodes$2 = nextNodes[0]) !== null && _nextNodes$2 !== void 0 && _nextNodes$2.isBlock || (_currentNodes2 = currentNodes[currentNodes.length - 1]) !== null && _currentNodes2 !== void 0 && _currentNodes2.isBlock) { return false; } } return true; } function isConsecutiveMediaGroups(currentNodes, nextNodes) { var _currentNodes3, _nextNodes$3; return currentNodes.length > 0 && ((_currentNodes3 = currentNodes[currentNodes.length - 1]) === null || _currentNodes3 === void 0 ? void 0 : _currentNodes3.type.name) === 'mediaGroup' && ((_nextNodes$3 = nextNodes[0]) === null || _nextNodes$3 === void 0 ? void 0 : _nextNodes$3.type.name) === 'mediaGroup'; }