@atlaskit/editor-wikimarkup-transformer
Version:
Wiki markup transformer for JIRA and Confluence
259 lines (254 loc) • 8.77 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import { getType as getListType, ListBuilder } from '../builder/list-builder';
import { parseString } from '../text';
import { normalizePMNodes } from '../utils/normalize';
import { parseMacroKeyword } from './keyword';
import { TokenType, parseToken } from './';
import { parseNewlineOnly } from './whitespace';
import { hasAnyOfMarks } from '../utils/text';
export var MAX_LIST_DEPTH = 20;
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var LIST_ITEM_REGEXP = new RegExp("^ *([*\\-#]{1,".concat(MAX_LIST_DEPTH, "}) "));
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var EMPTY_LINE_REGEXP = /^[ \t]*\r?\n/;
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var RULER_SYMBOL_REGEXP = /^-{4,5}/;
var processState = {
NEW_LINE: 0,
BUFFER: 1,
END: 2,
MACRO: 3
};
export var list = function list(_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 listItem
*/
var ignoreTokenTypes = [TokenType.QUADRUPLE_DASH_SYMBOL, TokenType.LIST, TokenType.TABLE];
var index = position;
var state = processState.NEW_LINE;
var buffer = [];
var lastListSymbols = null;
var builder = null;
var contentBuffer = [];
var output = [];
while (index < input.length) {
var char = input.charAt(index);
switch (state) {
case processState.NEW_LINE:
{
var substring = input.substring(index);
var listMatch = substring.match(LIST_ITEM_REGEXP);
if (listMatch) {
var _listMatch = _slicedToArray(listMatch, 2),
symbols = _listMatch[1];
// Handle ruler in list
var rulerMatch = symbols.match(RULER_SYMBOL_REGEXP);
if (rulerMatch) {
var remainingAfterSymbol = input.substring(index + rulerMatch[0].length);
var _emptyLineMatch = remainingAfterSymbol.match(EMPTY_LINE_REGEXP);
// If this is an empty line skip to the buffering step rather than match as a list element
if (_emptyLineMatch) {
state = processState.BUFFER;
continue;
}
}
if (!builder) {
// It happens because this is the first item of the list
builder = new ListBuilder(schema, symbols);
lastListSymbols = symbols;
} else {
/**
* There is a builder, so we are in the middle of building a list
* and now there is a new list item
*/
if (buffer.length > 0) {
var _contentBuffer;
// Wrap up previous list item and clear buffer
var content = parseString({
ignoreTokenTypes: ignoreTokenTypes,
schema: schema,
context: context,
input: buffer.join(''),
includeLeadingSpace: true
});
(_contentBuffer = contentBuffer).push.apply(_contentBuffer, _toConsumableArray(content));
builder.add([{
style: lastListSymbols,
content: sanitize(normalizePMNodes(contentBuffer, schema), schema)
}]);
buffer = [];
contentBuffer = [];
}
// We finished last list item here, going to the new one
lastListSymbols = symbols;
var type = getListType(symbols);
// If it's top level and doesn't match, create a new list
if (type !== builder.type && symbols.length === 1) {
output.push.apply(output, _toConsumableArray(builder.buildPMNode()));
builder = new ListBuilder(schema, symbols);
}
}
index += listMatch[0].length;
}
// If we encounter an empty line, we should end the list
var emptyLineMatch = substring.match(EMPTY_LINE_REGEXP);
if (emptyLineMatch) {
state = processState.END;
continue;
}
state = processState.BUFFER;
continue;
}
case processState.BUFFER:
{
var length = parseNewlineOnly(input.substring(index));
if (length) {
buffer.push(input.substr(index, length));
state = processState.NEW_LINE;
index += length;
continue;
}
if (char === '{') {
state = processState.MACRO;
continue;
} else {
buffer.push(char);
}
break;
}
case processState.MACRO:
{
var match = parseMacroKeyword(input.substring(index));
if (!match) {
buffer.push(char);
state = processState.BUFFER;
break;
}
var token = parseToken(input, match.type, index, schema, context);
buffer.push(input.substr(index, token.length));
index += token.length;
state = processState.BUFFER;
continue;
}
case processState.END:
{
if (!builder) {
// Something is really wrong here
return fallback(input, position);
}
if (buffer.length > 0) {
var _contentBuffer2;
// Wrap up previous list item and clear buffer
var _content = parseString({
ignoreTokenTypes: ignoreTokenTypes,
schema: schema,
context: context,
input: buffer.join(''),
includeLeadingSpace: true
});
(_contentBuffer2 = contentBuffer).push.apply(_contentBuffer2, _toConsumableArray(_content));
}
builder.add([{
style: lastListSymbols,
content: sanitize(normalizePMNodes(contentBuffer, schema), schema)
}]);
output.push.apply(output, _toConsumableArray(builder.buildPMNode()));
return {
type: 'pmnode',
nodes: output,
length: index - position
};
}
}
index++;
}
if (buffer.length > 0) {
var _contentBuffer3;
// Wrap up what's left in the buffer
var _content2 = parseString({
ignoreTokenTypes: ignoreTokenTypes,
schema: schema,
context: context,
input: buffer.join(''),
includeLeadingSpace: true
});
(_contentBuffer3 = contentBuffer).push.apply(_contentBuffer3, _toConsumableArray(_content2));
}
if (builder) {
builder.add([{
style: lastListSymbols,
content: sanitize(normalizePMNodes(contentBuffer, schema), schema)
}]);
output.push.apply(output, _toConsumableArray(builder.buildPMNode()));
}
return {
type: 'pmnode',
nodes: output,
length: index - position
};
};
function sanitize(nodes, schema) {
return nodes.reduce(function (result, curr) {
switch (curr.type.name) {
case 'blockquote':
{
/**
* If a blockquote is inside a list item
* - Convert it to paragraph
*/
curr.content.forEach(function (n) {
result.push(n);
});
break;
}
case 'heading':
{
/**
* If a heading is inside a list item
* - Convert the heading to paragraph
* - Convert text to upper case
* - Mark text with strong.
*/
var contentBuffer = [];
curr.content.forEach(function (n) {
var mark = schema.marks.strong.create();
if (n.type.name === 'text') {
if (n.text) {
// @ts-ignore - [unblock prosemirror bump] allow assign to readonly
n.text = n.text.toUpperCase();
}
if (n.type.name === 'text' && !hasAnyOfMarks(n, ['strong', 'code'])) {
contentBuffer.push(n.mark([].concat(_toConsumableArray(n.marks), [mark])));
} else {
contentBuffer.push(n);
}
} else {
contentBuffer.push(n);
}
});
var p = schema.nodes.paragraph.createChecked({}, contentBuffer);
result.push(p);
break;
}
default:
result.push(curr);
}
return result;
}, []);
}
function fallback(input, position) {
return {
type: 'text',
text: input.substr(position, 1),
length: 1
};
}