Skip to content

Commit

Permalink
feat: apply toastmark parser to merged table plugin (#1011)
Browse files Browse the repository at this point in the history
* feat: add renderer, parser option for plugin

* chore: add plugin object option test case

* feat: add custom parser option

* refactor: add startIdx, endIdx to table node

* chore: add custom parser option test case(toastmark)

* feat: apply toastmark parser, renderer

* chore: fix eslint complexity rule

* chore: remove unnecessary files

* fix: calculate wrong rowspanMap count

* feat: add customParser option to createMarkdownToHTML API

* chore: add toastmark local dependency to merged table plugin for testing

* chore: add merged table parser test case and remove unnecessary test case

* fix: broken the table with last tail rowspan cell

* chore: change complexity rulw

* refactor: unify the condition expression and apply eslint rule

* chore: !entering => entering

* chore: apply code review

* fix: parse the table row which has non children

* fix: remove tomark-pass attribute
  • Loading branch information
js87zz authored Jun 9, 2020
1 parent ec405e2 commit cc54ec2
Show file tree
Hide file tree
Showing 30 changed files with 1,023 additions and 915 deletions.
11 changes: 9 additions & 2 deletions apps/editor/src/js/convertor.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ const HTML_TAG_RX = new RegExp(openingTag, 'g');
*/
class Convertor {
constructor(em, options = {}) {
const { linkAttribute, customHTMLRenderer, extendedAutolinks, referenceDefinition } = options;
const {
linkAttribute,
customHTMLRenderer,
extendedAutolinks,
referenceDefinition,
customParser
} = options;

this.mdReader = new Parser({
extendedAutolinks,
disallowedHtmlBlockTags: ['br'],
referenceDefinition,
disallowDeepHeading: true
disallowDeepHeading: true,
customParser
});

this.renderHTML = createRenderHTML({
Expand Down
24 changes: 16 additions & 8 deletions apps/editor/src/js/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import WwTableManager from './wwTableManager';
import WwTableSelectionManager from './wwTableSelectionManager';
import codeBlockManager from './codeBlockManager';
import toMarkRenderer from './toMarkRenderer';
import { invokePlugins } from './pluginHelper';
import { invokePlugins, getPluginInfo } from './pluginHelper';
import htmlSanitizer from './htmlSanitizer';

// markdown commands
Expand Down Expand Up @@ -182,13 +182,20 @@ class ToastUIEditor {
});

const linkAttribute = sanitizeLinkAttribute(this.options.linkAttribute);
// eslint-disable-next-line prettier/prettier
const { customHTMLRenderer, customHTMLSanitizer, extendedAutolinks, referenceDefinition, useDefaultHTMLSanitizer } = this.options;
const { renderer, parser, plugins } = getPluginInfo(this.options.plugins);
const {
customHTMLRenderer,
customHTMLSanitizer,
extendedAutolinks,
referenceDefinition,
useDefaultHTMLSanitizer
} = this.options;
const rendererOptions = {
linkAttribute,
customHTMLRenderer,
customHTMLRenderer: { ...renderer, ...customHTMLRenderer },
extendedAutolinks,
referenceDefinition
referenceDefinition,
customParser: parser
};

if (this.options.customConvertor) {
Expand Down Expand Up @@ -223,7 +230,8 @@ class ToastUIEditor {
disallowedHtmlBlockTags: ['br'],
extendedAutolinks,
referenceDefinition,
disallowDeepHeading: true
disallowDeepHeading: true,
customParser: parser
});

this.mdEditor = MarkdownEditor.factory(
Expand Down Expand Up @@ -254,8 +262,8 @@ class ToastUIEditor {
renderer: toMarkRenderer
};

if (this.options.plugins) {
invokePlugins(this.options.plugins, this);
if (plugins) {
invokePlugins(plugins, this);
}

this.changePreviewStyle(this.options.previewStyle);
Expand Down
5 changes: 3 additions & 2 deletions apps/editor/src/js/markdownToHTML.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { Parser, createRenderHTML } from '@toast-ui/toastmark';
import { getHTMLRenderConvertors } from './htmlRenderConvertors';

export function createMarkdownToHTML(options) {
const { extendedAutolinks, customHTMLRenderer, referenceDefinition } = options;
const { extendedAutolinks, customHTMLRenderer, referenceDefinition, customParser } = options;

const parser = new Parser({
disallowedHtmlBlockTags: ['br'],
extendedAutolinks,
referenceDefinition,
disallowDeepHeading: true
disallowDeepHeading: true,
customParser
});

const renderHTML = createRenderHTML({
Expand Down
35 changes: 35 additions & 0 deletions apps/editor/src/js/pluginHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,38 @@ export function invokePlugins(plugins, editor) {
}
});
}

/**
* Get plugin info
* @param {Array.<Function|Array>} plugins - list of plugin function only or
* plugin function with options
* @returns {Object} - plugin info
*/
export function getPluginInfo(plugins) {
if (!plugins) {
return {};
}

return plugins.reduce(
(acc, plugin) => {
const pluginInfo = isArray(plugin) ? plugin[0] : plugin;

if (!isFunction(pluginInfo)) {
const { renderer, parser, pluginFn } = pluginInfo;

plugin = isArray(plugin) ? [pluginFn, plugin[1]] : pluginFn;

if (renderer) {
acc.renderer = { ...acc.renderer, ...renderer };
}
if (parser) {
acc.parser = { ...acc.parser, ...parser };
}
}
acc.plugins.push(plugin);

return acc;
},
{ plugins: [], renderer: {}, parser: {} }
);
}
20 changes: 13 additions & 7 deletions apps/editor/src/js/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import CommandManager from './commandManager';
import Convertor from './convertor';
import domUtils from './utils/dom';
import codeBlockManager from './codeBlockManager';
import { invokePlugins } from './pluginHelper';
import { invokePlugins, getPluginInfo } from './pluginHelper';
import { sanitizeLinkAttribute } from './utils/common';
import htmlSanitizer from './htmlSanitizer';

Expand Down Expand Up @@ -63,13 +63,19 @@ class ToastUIEditorViewer {
this.commandManager = new CommandManager(this);

const linkAttribute = sanitizeLinkAttribute(this.options.linkAttribute);
// eslint-disable-next-line prettier/prettier
const { customHTMLRenderer, customHTMLSanitizer, extendedAutolinks, referenceDefinition } = this.options;
const rendererOptions = {
linkAttribute,
const { renderer, parser, plugins } = getPluginInfo(this.options.plugins);
const {
customHTMLRenderer,
customHTMLSanitizer,
extendedAutolinks,
referenceDefinition
} = this.options;
const rendererOptions = {
linkAttribute,
customHTMLRenderer: { ...renderer, ...customHTMLRenderer },
extendedAutolinks,
referenceDefinition,
customParser: parser
};

if (this.options.customConvertor) {
Expand Down Expand Up @@ -110,8 +116,8 @@ class ToastUIEditorViewer {

on(this.preview.el, 'mousedown', this._toggleTask.bind(this));

if (this.options.plugins) {
invokePlugins(this.options.plugins, this);
if (plugins) {
invokePlugins(plugins, this);
}

if (initialValue) {
Expand Down
13 changes: 13 additions & 0 deletions apps/editor/test/unit/editor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,19 @@ describe('Editor', () => {

expect(plugin).toHaveBeenCalledWith(editor, options);
});

it('should extract plugin function with options of plugin object', () => {
const plugin = jasmine.createSpy('plugin');
const options = {};
const pluginInfo = { pluginFn: plugin };

editor = new Editor({
el: container,
plugins: [[pluginInfo, options]]
});

expect(plugin).toHaveBeenCalledWith(editor, options);
});
});

describe('usageStatistics', () => {
Expand Down
37 changes: 36 additions & 1 deletion libs/toastmark/src/commonmark/__test__/options.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Parser } from '../blocks';
import { Parser, CustomParserMap } from '../blocks';
import { pos } from './helper.spec';
import { Node } from '../node';

it('tags in disallowedHtmlBlockTags should not be parsed as a HTML block', () => {
const reader = new Parser({ disallowedHtmlBlockTags: ['br', 'span'] });
Expand Down Expand Up @@ -160,3 +161,37 @@ describe('disallowDeepHeading: true', () => {
});
});
});

it('should apply the custom parser', () => {
let inEmph = false;
const customParser: CustomParserMap = {
emph(node: Node, { entering }) {
inEmph = entering;

while (node.firstChild) {
node.insertBefore(node.firstChild);
}
node.unlink();
},
text(node: Node) {
if (inEmph) {
node.literal = node.literal!.toUpperCase();
}
}
};
const reader = new Parser({ customParser });
const root = reader.parse('*test*');

expect(root).toMatchObject({
type: 'document',
firstChild: {
type: 'paragraph',
sourcepos: pos(1, 1, 1, 6),
firstChild: {
type: 'text',
sourcepos: pos(1, 2, 1, 5),
literal: 'TEST'
}
}
});
});
22 changes: 17 additions & 5 deletions libs/toastmark/src/commonmark/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
isCodeBlock,
isHtmlBlock,
createNode,
TableCellNode
TableCellNode,
NodeType
} from './node';
import { InlineParser, C_NEWLINE } from './inlines';
import { blockHandlers, Process } from './blockHandlers';
Expand Down Expand Up @@ -41,16 +42,21 @@ const defaultOptions = {
extendedAutolinks: false,
disallowedHtmlBlockTags: [],
referenceDefinition: false,
disallowDeepHeading: false
disallowDeepHeading: false,
customParser: null
};

export type CustomParser = (node: Node, context: { entering: boolean }) => void;
export type CustomParserMap = Partial<Record<NodeType, CustomParser>>;

export interface Options {
smart: boolean;
tagFilter: boolean;
extendedAutolinks: boolean | AutolinkParser;
disallowedHtmlBlockTags: string[];
referenceDefinition: boolean;
disallowDeepHeading: boolean;
customParser: CustomParserMap | null;
}

export class Parser {
Expand Down Expand Up @@ -225,21 +231,27 @@ export class Parser {
// into inline content where appropriate.
processInlines(block: BlockNode) {
let event;
const { customParser } = this.options;
const walker = block.walker();
this.inlineParser.refMap = this.refMap;
this.inlineParser.refLinkCandidateMap = this.refLinkCandidateMap;
this.inlineParser.refDefCandidateMap = this.refDefCandidateMap;
this.inlineParser.options = this.options;
while ((event = walker.next())) {
const node = event.node as BlockNode;
const { node, entering } = event;
const t = node.type;

if (customParser && customParser[t]) {
customParser[t]!(node, { entering });
}

if (
!event.entering &&
!entering &&
(t === 'paragraph' ||
t === 'heading' ||
(t === 'tableCell' && !(node as TableCellNode).ignored))
) {
this.inlineParser.parse(node);
this.inlineParser.parse(node as BlockNode);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion libs/toastmark/src/commonmark/gfm/tableBlockStart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ function generateTableCells(
]) as TableCellNode;

tableCell.stringContent = trimmed.replace(/\\\|/g, '|'); // replace esacped pipe(\|)
tableCell.columnIdx = cells.length;
tableCell.startIdx = cells.length;
tableCell.endIdx = cells.length;
tableCell.lineOffsets = [chPosStart - 1];
tableCell.paddingLeft = paddingLeft;
tableCell.paddingRight = paddingRight;
Expand Down
13 changes: 12 additions & 1 deletion libs/toastmark/src/commonmark/inlines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1105,9 +1105,20 @@ export class InlineParser {
this.processEmphasis(null);
this.mergeTextNodes(block.walker());

const { extendedAutolinks } = this.options;
const { extendedAutolinks, customParser } = this.options;
if (extendedAutolinks) {
convertExtAutoLinks(block.walker(), extendedAutolinks);
}
if (customParser && block.firstChild) {
let event;
const walker = block.firstChild.walker();

while ((event = walker.next())) {
const { node, entering } = event;
if (customParser[node.type]) {
customParser[node.type]!(node, { entering });
}
}
}
}
}
3 changes: 2 additions & 1 deletion libs/toastmark/src/commonmark/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ export class TableNode extends BlockNode {
}

export class TableCellNode extends BlockNode {
public columnIdx = 0;
public startIdx = 0;
public endIdx = 0;
public paddingLeft = 0;
public paddingRight = 0;
public ignored = false;
Expand Down
4 changes: 2 additions & 2 deletions libs/toastmark/src/html/gfmConvertors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const gfmConvertors: HTMLConvertorMap = {
const result: HTMLToken[] = [];
if (node.lastChild) {
const columnLen = (node.parent!.parent as TableNode).columns.length;
const lastColIdx = (node.lastChild as TableCellNode).columnIdx;
const lastColIdx = (node.lastChild as TableCellNode).endIdx;
for (let i = lastColIdx + 1; i < columnLen; i += 1) {
result.push(
{
Expand Down Expand Up @@ -121,7 +121,7 @@ export const gfmConvertors: HTMLConvertorMap = {
const tablePart = node.parent!.parent!;
const tagName = tablePart.type === 'tableHead' ? 'th' : 'td';
const table = tablePart.parent as TableNode;
const columnInfo = table.columns[(node as TableCellNode).columnIdx];
const columnInfo = table.columns[(node as TableCellNode).startIdx];
const align = columnInfo && columnInfo.align !== 'left' ? columnInfo.align : null;
const attributes = align ? { align } : null;

Expand Down
5 changes: 4 additions & 1 deletion plugins/table-merged-cell/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ module.exports = {
}
],
'arrow-body-style': ['error', 'as-needed', { requireReturnForObjectLiteral: true }],
'object-property-newline': ['error', { allowMultiplePropertiesPerLine: true }]
'object-property-newline': ['error', { allowMultiplePropertiesPerLine: true }],
'complexity': ["error", 8],
'max-depth': ["error", 4],
'max-nested-callbacks': ["error", 4]
}
};
Loading

0 comments on commit cc54ec2

Please sign in to comment.