@atlaskit/editor-wikimarkup-transformer
Version:
Wiki markup transformer for JIRA and Confluence
326 lines (321 loc) • 10.3 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import { TableBuilder } from '../builder/table-builder';
import { parseString } from '../text';
import { normalizePMNodes } from '../utils/normalize';
import { linkFormat } from './links/link-format';
import { media } from './media';
import { emoji } from './emoji';
import { TokenType, parseToken } from './';
import { parseNewlineOnly } from './whitespace';
import { parseMacroKeyword } from './keyword';
import { hasAnyOfMarks } from '../utils/text';
/*
The following are currently NOT supported
1. Macros
2. Escape |
3. Table of table
*/
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var CELL_REGEXP = /^([ \t]*)([|]+)([ \t]*)/;
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var EMPTY_LINE_REGEXP = /^[ \t]*\r?\n/;
var processState = {
END_TABLE: 2,
BUFFER: 4,
CLOSE_ROW: 5,
NEW_ROW: 6,
LINE_BREAK: 7,
LINK: 8,
MEDIA: 9,
MACRO: 10,
EMOJI: 11
};
export var table = function table(_ref) {
var input = _ref.input,
position = _ref.position,
schema = _ref.schema,
context = _ref.context;
/**
* The following token types will be ignored in parsing
* the content of a table cell
*/
var ignoreTokenTypes = [TokenType.DOUBLE_DASH_SYMBOL, TokenType.TRIPLE_DASH_SYMBOL, TokenType.QUADRUPLE_DASH_SYMBOL, TokenType.TABLE, TokenType.RULER];
var output = [];
var index = position;
var currentState = processState.NEW_ROW;
var buffer = [];
var cellsBuffer = [];
var cellStyle = '';
var builder = null;
while (index < input.length) {
var char = input.charAt(index);
var substring = input.substring(index);
switch (currentState) {
case processState.NEW_ROW:
{
var tableMatch = substring.match(CELL_REGEXP);
if (tableMatch) {
if (!builder) {
builder = new TableBuilder(schema);
}
// Capture empty spaces
index += tableMatch[1].length;
cellStyle = tableMatch[2];
index += tableMatch[2].length;
currentState = processState.BUFFER;
continue;
}
currentState = processState.END_TABLE;
continue;
}
case processState.LINE_BREAK:
{
var emptyLineMatch = substring.match(EMPTY_LINE_REGEXP);
if (emptyLineMatch) {
// If we encounter an empty line, we should end the table
bufferToCells(cellStyle, buffer.join(''), cellsBuffer, schema, ignoreTokenTypes, context);
currentState = processState.END_TABLE;
continue;
}
// If we enconter a new row
var cellMatch = substring.match(CELL_REGEXP);
if (cellMatch) {
currentState = processState.CLOSE_ROW;
} else {
currentState = processState.BUFFER;
}
continue;
}
case processState.BUFFER:
{
var length = parseNewlineOnly(substring);
if (length) {
// Calculate the index of the end of the current cell,
// upto and including the new line
var endIndex = index;
// Calculate the index of the start of the current cell
var startIndex = input.lastIndexOf('|', endIndex) + 1;
var charsBefore = input.substring(startIndex, endIndex);
if (charsBefore === '' || charsBefore.match(EMPTY_LINE_REGEXP)) {
currentState = processState.CLOSE_ROW;
} else {
currentState = processState.LINE_BREAK;
buffer.push(input.substr(index, length));
}
index += length;
continue;
}
switch (char) {
case '|':
{
// This is now end of a cell, we should wrap the buffer into a cell
bufferToCells(cellStyle, buffer.join(''), cellsBuffer, schema, ignoreTokenTypes, context);
buffer = [];
// Update cells tyle
var _cellMatch = substring.match(CELL_REGEXP);
// The below if statement should aways be true, we leave it here to prevent any future code changes fall into infinite loop
if (_cellMatch) {
cellStyle = _cellMatch[2];
// Move into the cell content
index += _cellMatch[2].length;
continue;
}
break;
}
case ':':
case ';':
case '(':
{
currentState = processState.EMOJI;
continue;
}
case '[':
{
currentState = processState.LINK;
continue;
}
case '!':
{
currentState = processState.MEDIA;
continue;
}
case '{':
{
currentState = processState.MACRO;
continue;
}
default:
{
buffer.push(char);
index++;
continue;
}
}
break;
}
case processState.CLOSE_ROW:
{
var _bufferOutput = buffer.join('');
if (_bufferOutput.trim().length > 0) {
bufferToCells(cellStyle, _bufferOutput, cellsBuffer, schema, ignoreTokenTypes, context);
buffer = [];
}
if (builder) {
builder.add(cellsBuffer);
cellsBuffer = [];
}
currentState = processState.NEW_ROW;
continue;
}
case processState.END_TABLE:
{
if (builder) {
if (cellsBuffer.length) {
builder.add(cellsBuffer);
}
output.push(builder.buildPMNode());
}
return {
type: 'pmnode',
nodes: output,
length: index - position
};
}
case processState.MEDIA:
{
var token = media({
input: input,
schema: schema,
context: context,
position: index
});
buffer.push(input.substr(index, token.length));
index += token.length;
currentState = processState.BUFFER;
continue;
}
case processState.EMOJI:
{
var _token = emoji({
input: input,
schema: schema,
context: context,
position: index
});
buffer.push(input.substr(index, _token.length));
index += _token.length;
currentState = processState.BUFFER;
continue;
}
case processState.LINK:
{
/**
* We should "fly over" the link format and we dont want
* -awesome [link|https://www.atlass-ian.com] nice
* to be a strike through because of the '-' in link
*/
var _token2 = linkFormat({
input: input,
schema: schema,
context: context,
position: index
});
if (_token2.type === 'text') {
buffer.push(_token2.text);
index += _token2.length;
currentState = processState.BUFFER;
continue;
} else if (_token2.type === 'pmnode') {
buffer.push(input.substr(index, _token2.length));
index += _token2.length;
currentState = processState.BUFFER;
continue;
}
break;
}
case processState.MACRO:
{
var match = parseMacroKeyword(input.substring(index));
if (!match) {
buffer.push(char);
currentState = processState.BUFFER;
break;
}
var _token3 = parseToken(input, match.type, index, schema, context);
buffer.push(input.substr(index, _token3.length));
index += _token3.length;
currentState = processState.BUFFER;
continue;
}
}
index++;
}
/**
* If there are left over content which didn't have a closing |
* For example
* |cell1|cell2|cell3
* we still want to create a new cell for the last cell3 if it's
* not empty.
*/
var bufferOutput = buffer.join('');
if (bufferOutput.trim().length > 0) {
bufferToCells(cellStyle, bufferOutput, cellsBuffer, schema, ignoreTokenTypes, context);
}
if (builder) {
if (cellsBuffer.length) {
builder.add(cellsBuffer);
}
output.push(builder.buildPMNode());
}
return {
type: 'pmnode',
nodes: output,
length: index - position
};
};
function bufferToCells(style, buffer, cellsBuffer, schema, ignoreTokenTypes, context) {
if (buffer.length) {
var contentNode = parseString({
schema: schema,
context: context,
ignoreTokenTypes: ignoreTokenTypes,
input: buffer
});
if (style === '||') {
contentNode = contentNode.map(function (e) {
return createTableHeader(e, schema);
});
}
cellsBuffer.push({
style: style,
content: normalizePMNodes(contentNode, schema)
});
}
}
function createTableHeader(node, schema) {
var mark = schema.marks.strong.create();
return traverseNodeAndAddMarks(node, mark, schema);
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function traverseContent(node, mark, schema) {
if (node.content.childCount === 0 || !node.content.child(0) || !node.content.firstChild || node.type.name === 'codeBlock') {
return node;
}
for (var i = 0; i < node.content.childCount; i++) {
var child = node.content.child(i);
var markedChild = traverseNodeAndAddMarks(child, mark, schema);
var updatedContent = node.content.replaceChild(i, markedChild);
node = node.copy(updatedContent);
}
return node;
}
function traverseNodeAndAddMarks(node, mark, schema) {
if (node.type.name === 'text' && !hasAnyOfMarks(node, ['strong', 'code'])) {
var newNode = node.mark([].concat(_toConsumableArray(node.marks), [mark]));
return newNode;
}
return traverseContent(node, mark, schema);
}