EmailEditor.js
import React, { Component } from 'react';
import {
Editor,
EditorUtils,
EditorTools,
ProseMirror
} from '@progress/kendo-react-editor';
import { extendTableNodes } from './schemaNodes';
const {
Bold,
Italic,
Underline,
AlignLeft,
AlignRight,
AlignCenter,
Indent,
Outdent,
OrderedList,
UnorderedList,
Undo,
Redo,
Link,
Unlink,
InsertImage,
InsertTable,
AddRowBefore,
AddRowAfter,
AddColumnBefore,
AddColumnAfter,
DeleteRow,
DeleteColumn,
DeleteTable,
FormatBlock,
FontName,
FontSize,
ViewHtml
} = EditorTools;
const { Schema, EditorView, EditorState } = ProseMirror;
class EmailEditor extends Component {
constructor(props) {
super(props);
this.editorRef = null;
}
onExecute = ({ transaction, state }) => {
const { doc, selection } = transaction;
if (doc.eq(state.doc)) return;
if (this.props.onChange) {
const nextState = EditorState.create({
doc,
selection
});
const editorValue = EditorUtils.getHtml(nextState);
this.props.onChange.call(undefined, editorValue);
}
};
onMount = (event) => {
const { viewProps } = event;
const schema = viewProps.state.schema;
const plugins = viewProps.state.plugins.filter(
(p) => p.key.indexOf('selectingCells') !== 0
);
const tableNodes = extendTableNodes();
const marks = schema.spec.marks;
// update built-in schema nodes
let nodes = schema.spec.nodes;
for (const nodeName in tableNodes) {
if (nodeName) {
nodes = nodes.update(nodeName, tableNodes[nodeName]);
}
}
const mySchema = new Schema({ nodes, marks });
// Create an empty document to load the schema.
const doc = EditorUtils.createDocument(mySchema, '');
// Return the custom EditorView object that will be used by Editor.
return new EditorView(
{ mount: event.dom },
{
...event.viewProps,
state: EditorState.create({ doc, plugins })
}
);
};
setHtml = (content) => {
if (!this.editorRef.view) return;
const view = this.editorRef.view;
EditorUtils.setHtml(view, content);
};
getHtml = () => {
if (!this.editorRef.view) return '';
const view = this.editorRef.view;
const content = EditorUtils.getHtml(view.state);
return content;
};
render() {
return (
<Editor
ref={(editor) => (this.editorRef = editor)}
onExecute={this.onExecute}
contentStyle={{ height: 600 }}
tools={[
[Bold, Italic, Underline],
[Undo, Redo],
[Link, Unlink],
[AlignLeft, AlignCenter, AlignRight],
[OrderedList, UnorderedList, Indent, Outdent],
[Link, Unlink, InsertImage],
[InsertTable],
[AddRowBefore, AddRowAfter, AddColumnBefore, AddColumnAfter],
[DeleteRow, DeleteColumn, DeleteTable],
[ViewHtml],
FontSize,
FontName,
FormatBlock
]}
defaultEditMode="div"
onMount={this.onMount}
/>
);
}
}
export { EmailEditor };
schemaNodes.js
const getAttributes = (dom, isBlock) => {
if (isBlock && dom.children.length === 0) return false;
if (!isBlock && dom.children.length > 0) return false;
const result = {};
const attributes = dom.attributes;
let attr;
for (let i = 0; i < attributes.length; i++) {
attr = attributes[i];
result[attr.name] = attr.value;
}
if (!isBlock && dom.innerHTML.trim() === ' ') {
result['width'] = 0;
}
return result;
};
const hole = 0;
const tableAttrs = {
align: { default: null },
border: { default: null },
cellpadding: { default: null },
cellspacing: { default: null },
style: { default: null },
width: { default: null },
height: { default: null },
bgcolor: { default: null }
};
const styleAttrs = {
style: { default: null }
};
const cellAttrs = {
colspan: { default: null },
colwidth: { default: null },
rowspan: { default: null },
bgcolor: { default: null },
style: { default: null },
align: { default: null },
width: { default: null },
height: { default: null }
};
export const extendTableNodes = () => {
return {
table: {
content: '(table_colgroup | table_tbody)+',
tableRole: 'table',
isolating: true,
group: 'block',
attrs: { ...tableAttrs },
parseDOM: [{ tag: 'table', getAttrs: dom => getAttributes(dom, true) }],
toDOM: node => ['table', node.attrs, hole]
},
table_tbody: {
content: 'table_row+',
tableRole: 'tbody',
group: 'block',
parseDOM: [{ tag: 'tbody' }],
toDOM: function toDOM() {
return ['tbody', 0];
}
},
table_colgroup: {
content: 'table_col+',
tableRole: 'colgroup',
parseDOM: [{ tag: 'colgroup' }],
toDOM: function toDOM() {
return ['colgroup', 0];
}
},
table_col: {
tableRole: 'col',
attrs: { ...styleAttrs },
parseDOM: [{ tag: 'col', getAttrs: dom => getAttributes(dom, true) }],
toDOM: node => ['col', node.attrs]
},
table_row: {
content:
'(table_cell | table_cell_block | table_header | table_header_block)*',
tableRole: 'row',
attrs: { ...styleAttrs },
parseDOM: [{ tag: 'tr', getAttrs: dom => getAttributes(dom, true) }],
toDOM: node => ['tr', node.attrs, hole]
},
table_header: {
content: 'text*',
tableRole: 'cell',
group: 'block',
isolating: true,
marks: '',
attrs: { ...cellAttrs },
parseDOM: [{ tag: 'th', getAttrs: dom => getAttributes(dom, false) }],
toDOM: node => ['th', node.attrs, hole]
},
table_cell: {
content: 'text*',
tableRole: 'cell',
group: 'block',
isolating: true,
marks: '',
attrs: { ...cellAttrs },
parseDOM: [{ tag: 'td', getAttrs: dom => getAttributes(dom, false) }],
toDOM: node => ['td', node.attrs, hole]
},
// need duplicate definitions with for the original block versions
table_header_block: {
content: 'block+',
tableRole: 'cell',
group: 'block',
isolating: true,
attrs: { ...cellAttrs },
parseDOM: [{ tag: 'th', getAttrs: dom => getAttributes(dom, true) }],
toDOM: node => ['th', node.attrs, hole]
},
table_cell_block: {
content: 'block+',
tableRole: 'cell',
group: 'block',
isolating: true,
attrs: { ...cellAttrs },
parseDOM: [{ tag: 'td', getAttrs: dom => getAttributes(dom, true) }],
toDOM: node => ['td', node.attrs, hole]
}
};
};
@canvaspixels hope this helps