Commit ada1729d authored by Enrique Alcantara's avatar Enrique Alcantara

Separate serializers and tiptap extensions

Move node and mark serializers out of the
tiptap extension files. Define the serializer
config in the Markdown Serializer factory
module
parent bb480536
import { Blockquote } from '@tiptap/extension-blockquote';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = Blockquote;
export const serializer = defaultMarkdownSerializer.nodes.blockquote;
export { Blockquote as default } from '@tiptap/extension-blockquote';
import { Bold } from '@tiptap/extension-bold';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = Bold;
export const serializer = defaultMarkdownSerializer.marks.strong;
export { Bold as default } from '@tiptap/extension-bold';
import { BulletList } from '@tiptap/extension-bullet-list';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = BulletList;
export const serializer = defaultMarkdownSerializer.nodes.bullet_list;
export { BulletList as default } from '@tiptap/extension-bullet-list';
import { Code } from '@tiptap/extension-code';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = Code;
export const serializer = defaultMarkdownSerializer.marks.code;
export { Code as default } from '@tiptap/extension-code';
import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight';
import * as lowlight from 'lowlight';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
const extractLanguage = (element) => element.getAttribute('lang');
const ExtendedCodeBlockLowlight = CodeBlockLowlight.extend({
export default CodeBlockLowlight.extend({
addAttributes() {
return {
language: {
......@@ -38,6 +37,3 @@ const ExtendedCodeBlockLowlight = CodeBlockLowlight.extend({
}).configure({
lowlight,
});
export const tiptapExtension = ExtendedCodeBlockLowlight;
export const serializer = defaultMarkdownSerializer.nodes.code_block;
import Document from '@tiptap/extension-document';
export const tiptapExtension = Document;
export { Document as default } from '@tiptap/extension-document';
import Dropcursor from '@tiptap/extension-dropcursor';
export const tiptapExtension = Dropcursor;
export { Dropcursor as default } from '@tiptap/extension-dropcursor';
import Gapcursor from '@tiptap/extension-gapcursor';
export const tiptapExtension = Gapcursor;
export { Gapcursor as default } from '@tiptap/extension-gapcursor';
import { HardBreak } from '@tiptap/extension-hard-break';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
const ExtendedHardBreak = HardBreak.extend({
export default HardBreak.extend({
addKeyboardShortcuts() {
return {
'Shift-Enter': () => this.editor.commands.setHardBreak(),
};
},
});
export const tiptapExtension = ExtendedHardBreak;
export const serializer = defaultMarkdownSerializer.nodes.hard_break;
import { Heading } from '@tiptap/extension-heading';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = Heading;
export const serializer = defaultMarkdownSerializer.nodes.heading;
export { Heading as default } from '@tiptap/extension-heading';
import History from '@tiptap/extension-history';
export const tiptapExtension = History;
export { History as default } from '@tiptap/extension-history';
import { nodeInputRule } from '@tiptap/core';
import { HorizontalRule } from '@tiptap/extension-horizontal-rule';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const hrInputRuleRegExp = /^---$/;
export const tiptapExtension = HorizontalRule.extend({
export default HorizontalRule.extend({
addInputRules() {
return [nodeInputRule(hrInputRuleRegExp, this.type)];
},
});
export const serializer = defaultMarkdownSerializer.nodes.horizontal_rule;
......@@ -48,11 +48,12 @@ const handleFileEvent = ({ editor, file, uploadsPath, renderMarkdown }) => {
return false;
};
const ExtendedImage = Image.extend({
export default Image.extend({
defaultOptions: {
...Image.options,
uploadsPath: null,
renderMarkdown: null,
inline: true,
},
addAttributes() {
return {
......@@ -152,17 +153,3 @@ const ExtendedImage = Image.extend({
return VueNodeViewRenderer(ImageWrapper);
},
});
const serializer = (state, node) => {
const { alt, canonicalSrc, src, title } = node.attrs;
const quotedTitle = title ? ` ${state.quote(title)}` : '';
state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`);
};
export const configure = ({ renderMarkdown, uploadsPath }) => {
return {
tiptapExtension: ExtendedImage.configure({ inline: true, renderMarkdown, uploadsPath }),
serializer,
};
};
import { Italic } from '@tiptap/extension-italic';
export const tiptapExtension = Italic;
export const serializer = { open: '_', close: '_', mixable: true, expelEnclosingWhitespace: true };
export { Italic as default } from '@tiptap/extension-italic';
......@@ -20,7 +20,7 @@ export const extractHrefFromMarkdownLink = (match) => {
return extractHrefFromMatch(match);
};
export const tiptapExtension = Link.extend({
export default Link.extend({
addInputRules() {
return [
markInputRule(markdownLinkSyntaxInputRuleRegExp, this.type, extractHrefFromMarkdownLink),
......@@ -51,13 +51,3 @@ export const tiptapExtension = Link.extend({
}).configure({
openOnClick: false,
});
export const serializer = {
open() {
return '[';
},
close(state, mark) {
const href = mark.attrs.canonicalSrc || mark.attrs.href;
return `](${state.esc(href)}${mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ''})`;
},
};
import { ListItem } from '@tiptap/extension-list-item';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = ListItem;
export const serializer = defaultMarkdownSerializer.nodes.list_item;
export { ListItem as default } from '@tiptap/extension-list-item';
import { OrderedList } from '@tiptap/extension-ordered-list';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = OrderedList;
export const serializer = defaultMarkdownSerializer.nodes.ordered_list;
export { OrderedList as default } from '@tiptap/extension-ordered-list';
import { Paragraph } from '@tiptap/extension-paragraph';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = Paragraph;
export const serializer = defaultMarkdownSerializer.nodes.paragraph;
export { Paragraph as default } from '@tiptap/extension-paragraph';
import { Strike } from '@tiptap/extension-strike';
export const tiptapExtension = Strike;
export const serializer = {
open: '~~',
close: '~~',
mixable: true,
expelEnclosingWhitespace: true,
};
export { Strike as default } from '@tiptap/extension-strike';
import { Table } from '@tiptap/extension-table';
export const tiptapExtension = Table;
export function serializer(state, node) {
state.renderContent(node);
}
export { Table as default } from '@tiptap/extension-table';
import { TableCell } from '@tiptap/extension-table-cell';
export const tiptapExtension = TableCell.extend({
export default TableCell.extend({
content: 'inline*',
});
export function serializer(state, node) {
state.renderInline(node);
}
import { TableHeader } from '@tiptap/extension-table-header';
export const tiptapExtension = TableHeader.extend({
export default TableHeader.extend({
content: 'inline*',
});
export function serializer(state, node) {
state.renderInline(node);
}
import { TableRow } from '@tiptap/extension-table-row';
export const tiptapExtension = TableRow.extend({
export default TableRow.extend({
allowGapCursor: false,
});
export function serializer(state, node) {
const isHeaderRow = node.child(0).type.name === 'tableHeader';
const renderRow = () => {
const cellWidths = [];
state.flushClose(1);
state.write('| ');
node.forEach((cell, _, i) => {
if (i) state.write(' | ');
const { length } = state.out;
state.render(cell, node, i);
cellWidths.push(state.out.length - length);
});
state.write(' |');
state.closeBlock(node);
return cellWidths;
};
const renderHeaderRow = (cellWidths) => {
state.flushClose(1);
state.write('|');
node.forEach((cell, _, i) => {
if (i) state.write('|');
state.write(cell.attrs.align === 'center' ? ':' : '-');
state.write(state.repeat('-', cellWidths[i]));
state.write(cell.attrs.align === 'center' || cell.attrs.align === 'right' ? ':' : '-');
});
state.write('|');
state.closeBlock(node);
};
if (isHeaderRow) {
renderHeaderRow(renderRow());
} else {
renderRow();
}
}
import { Text } from '@tiptap/extension-text';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const tiptapExtension = Text;
export const serializer = defaultMarkdownSerializer.nodes.text;
export { Text as default } from '@tiptap/extension-text';
import { MarkdownSerializer as ProseMirrorMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
import {
MarkdownSerializer as ProseMirrorMarkdownSerializer,
defaultMarkdownSerializer,
} from 'prosemirror-markdown/src/to_markdown';
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
import Blockquote from '../extensions/blockquote';
import Bold from '../extensions/bold';
import BulletList from '../extensions/bullet_list';
import Code from '../extensions/code';
import CodeBlockHighlight from '../extensions/code_block_highlight';
import HardBreak from '../extensions/hard_break';
import Heading from '../extensions/heading';
import HorizontalRule from '../extensions/horizontal_rule';
import Image from '../extensions/image';
import Italic from '../extensions/italic';
import Link from '../extensions/link';
import ListItem from '../extensions/list_item';
import OrderedList from '../extensions/ordered_list';
import Paragraph from '../extensions/paragraph';
import Strike from '../extensions/strike';
import Table from '../extensions/table';
import TableCell from '../extensions/table_cell';
import TableHeader from '../extensions/table_header';
import TableRow from '../extensions/table_row';
import Text from '../extensions/text';
const defaultSerializerConfig = {
marks: {
[Bold.name]: defaultMarkdownSerializer.marks.strong,
[Code.name]: defaultMarkdownSerializer.marks.code,
[Italic.name]: { open: '_', close: '_', mixable: true, expelEnclosingWhitespace: true },
[Link.name]: {
open() {
return '[';
},
close(state, mark) {
const href = mark.attrs.canonicalSrc || mark.attrs.href;
return `](${state.esc(href)}${
mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ''
})`;
},
},
[Strike.name]: {
open: '~~',
close: '~~',
mixable: true,
expelEnclosingWhitespace: true,
},
},
nodes: {
[Blockquote.name]: defaultMarkdownSerializer.nodes.blockquote,
[BulletList.name]: defaultMarkdownSerializer.nodes.bullet_list,
[CodeBlockHighlight.name]: defaultMarkdownSerializer.nodes.code_block,
[HardBreak.name]: defaultMarkdownSerializer.nodes.hard_break,
[Heading.name]: defaultMarkdownSerializer.nodes.heading,
[HorizontalRule.name]: defaultMarkdownSerializer.nodes.horizontal_rule,
[Image.name]: (state, node) => {
const { alt, canonicalSrc, src, title } = node.attrs;
const quotedTitle = title ? ` ${state.quote(title)}` : '';
state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`);
},
[ListItem.name]: defaultMarkdownSerializer.nodes.list_item,
[OrderedList.name]: defaultMarkdownSerializer.nodes.ordered_list,
[Paragraph.name]: defaultMarkdownSerializer.nodes.paragraph,
[Table.name]: (state, node) => {
state.renderContent(node);
},
[TableCell.name]: (state, node) => {
state.renderInline(node);
},
[TableHeader.name]: (state, node) => {
state.renderInline(node);
},
[TableRow.name]: (state, node) => {
const isHeaderRow = node.child(0).type.name === 'tableHeader';
const renderRow = () => {
const cellWidths = [];
state.flushClose(1);
state.write('| ');
node.forEach((cell, _, i) => {
if (i) state.write(' | ');
const { length } = state.out;
state.render(cell, node, i);
cellWidths.push(state.out.length - length);
});
state.write(' |');
state.closeBlock(node);
return cellWidths;
};
const renderHeaderRow = (cellWidths) => {
state.flushClose(1);
state.write('|');
node.forEach((cell, _, i) => {
if (i) state.write('|');
state.write(cell.attrs.align === 'center' ? ':' : '-');
state.write(state.repeat('-', cellWidths[i]));
state.write(cell.attrs.align === 'center' || cell.attrs.align === 'right' ? ':' : '-');
});
state.write('|');
state.closeBlock(node);
};
if (isHeaderRow) {
renderHeaderRow(renderRow());
} else {
renderRow();
}
},
[Text.name]: defaultMarkdownSerializer.nodes.text,
},
};
const wrapHtmlPayload = (payload) => `<div>${payload}</div>`;
......@@ -50,8 +170,16 @@ export default ({ render = () => null, serializerConfig }) => ({
*/
serialize: ({ schema, content }) => {
const proseMirrorDocument = schema.nodeFromJSON(content);
const { nodes, marks } = serializerConfig;
const serializer = new ProseMirrorMarkdownSerializer(nodes, marks);
const serializer = new ProseMirrorMarkdownSerializer(
{
...defaultSerializerConfig.nodes,
...serializerConfig.nodes,
},
{
...defaultSerializerConfig.marks,
...serializerConfig.marks,
},
);
return serializer.serialize(proseMirrorDocument, {
tightLists: true,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment