Skip to content

Commit

Permalink
feat: Table
Browse files Browse the repository at this point in the history
  • Loading branch information
iamsivin committed Aug 30, 2024
1 parent 48057c1 commit 60ec1e4
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 21 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
},
"homepage": "https://github.com/chatwoot/prosemirror-schema#readme",
"dependencies": {
"markdown-it": "^12.0.0",
"markdown-it-multimd-table": "^4.2.3",
"markdown-it-sup": "^1.0.0",
"markdown-it-table": "^4.1.1",
"prosemirror-commands": "^1.1.4",
"prosemirror-dropcursor": "^1.3.2",
"prosemirror-gapcursor": "^1.1.5",
Expand All @@ -31,7 +34,7 @@
"prosemirror-model": "^1.1.0",
"prosemirror-schema-list": "^1.1.4",
"prosemirror-state": "^1.3.3",
"prosemirror-tables": "^1.3.0",
"prosemirror-tables": "^1.5.0",
"prosemirror-utils": "^0.9.6",
"prosemirror-view": "^1.17.2"
},
Expand Down
18 changes: 18 additions & 0 deletions src/icons.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { Plugin } from "prosemirror-state";
import { dropCursor } from "prosemirror-dropcursor";
import { gapCursor } from "prosemirror-gapcursor";
import { menuBar } from "prosemirror-menu";

import { keymap } from "prosemirror-keymap";
import { tableEditing, goToNextCell } from "prosemirror-tables";
// import { tableInputRule } from "./rules/tables";
import Placeholder from "./Placeholder";
import {
listInputRules,
Expand Down Expand Up @@ -52,9 +54,19 @@ export const buildEditor = ({
onImageUpload,
}),
}),
// Add table-related plugins only if enabledMenuOptions includes 'table'
...(enabledMenuOptions.includes('table') ? [
// tableInputRule(schema),
tableEditing(),
keymap({
Tab: goToNextCell(1),
'Shift-Tab': goToNextCell(-1),
}),
] : []),
new Plugin({
props: {
attributes: { class: "ProseMirror-woot-style" },
},
}),

];
89 changes: 83 additions & 6 deletions src/menu/menuOptions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { wrapInList } from "prosemirror-schema-list";
import { TextSelection } from 'prosemirror-state';
import { Fragment } from 'prosemirror-model';
import { toggleMark } from "prosemirror-commands";
import { MenuItem } from "prosemirror-menu";
import { Dropdown, DropdownSubmenu, MenuItem } from "prosemirror-menu";
import { undo, redo } from "prosemirror-history";
import {
addColumnAfter,
addColumnBefore,
deleteColumn,
addRowAfter,
addRowBefore,
deleteRow,
mergeCells,
splitCell,
deleteTable,
} from 'prosemirror-tables';

import { openPrompt } from "../prompt";
import { TextField } from "../TextField";
import {
Expand Down Expand Up @@ -54,6 +68,56 @@ const headerItem = (nodeType, options) => {
});
};

const createTable = (state, dispatch) => {
const offset = state.tr.selection.anchor + 1;
const transaction = state.tr;
const createCell = () => state.schema.nodes.table_cell.createAndFill(null, state.schema.nodes.paragraph.create());
const node = state.schema.nodes.table.create(
null,
Fragment.fromArray([
state.schema.nodes.table_row.create(
null,
Fragment.fromArray([createCell(), createCell(), createCell()])
),
state.schema.nodes.table_row.create(
null,
Fragment.fromArray([createCell(), createCell(), createCell()])
)
])
);

if (dispatch) {
dispatch(
transaction
.replaceSelectionWith(node)
.setSelection(
TextSelection.near(
transaction.doc.resolve(offset)
)
)
);
}

return true;
}

const tableMenu = [
{
label: 'Insert table',
run: createTable,
icon: icons.insertTable // Assuming you have this icon
},
{ label: 'Delete table', run: deleteTable, select: deleteTable, icon: icons.deleteTable },
{ label: 'Insert column before', run: addColumnBefore, select: addColumnBefore, icon: icons.insertColumnBefore },
{ label: 'Insert column after', run: addColumnAfter, select: addColumnAfter, icon: icons.insertColumnAfter },
{ label: 'Delete column', run: deleteColumn, select: deleteColumn, icon: icons.deleteColumn },
{ label: 'Insert row before', run: addRowBefore, select: addRowBefore, icon: icons.insertRowBefore },
{ label: 'Insert row after', run: addRowAfter, select: addRowAfter, icon: icons.insertRowAfter },
{ label: 'Delete row', run: deleteRow, select: deleteRow, icon: icons.deleteRow },
{ label: 'Merge cells', run: mergeCells, select: mergeCells, icon: icons.mergeCells },
{ label: 'Split cell', run: splitCell, select: splitCell, icon: icons.splitCell },
];

const linkItem = (markType) =>
new MenuItem({
title: "Add or remove link",
Expand Down Expand Up @@ -99,6 +163,7 @@ const buildMenuOptions = (
"redo",
"bulletList",
"orderedList",
"table",
],
onImageUpload = () => {},
}
Expand Down Expand Up @@ -153,13 +218,25 @@ const buildMenuOptions = (
icon: icons.h3,
}),
imageUpload: imageUploadItem(schema.nodes.image, onImageUpload),
table: new Dropdown(tableMenu.map(item => new MenuItem({
title: item.label,
label: item.label,
run: item.run,
select: item.select,
enable: item.enable,
})), {
label: "Table",
title: "Table operations",
icon: icons.table,
class: "prosemirror-menu-table-dropdown",
}),
};

return [
enabledMenuOptions
.filter((menuOptionKey) => !!availableMenuOptions[menuOptionKey])
.map((menuOptionKey) => availableMenuOptions[menuOptionKey]),
];
const menuItems = enabledMenuOptions
.filter((menuOptionKey) => !!availableMenuOptions[menuOptionKey])
.map((menuOptionKey) => availableMenuOptions[menuOptionKey]);

return [menuItems];
};

export default buildMenuOptions;
1 change: 1 addition & 0 deletions src/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { listInputRules } from './lists';
export { linksInputRules } from './links';
export { blocksInputRule } from './blocks';
export { hrInputRules } from './hr';
export { tableInputRule } from './tables';
export { baseKeyMaps } from '../keymap';

export { textFormattingInputRules } from './marks';
50 changes: 50 additions & 0 deletions src/rules/tables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { InputRule } from "prosemirror-inputrules";
import { NodeType, Schema } from "prosemirror-model";
import { EditorState, TextSelection, Transaction } from "prosemirror-state";

function createTable(schema, rowsCount, colsCount) {
const {
table,
table_row: tableRow,
table_cell: tableCell,
table_header: tableHeader,
paragraph,
} = schema.nodes;

const cells = [];
for (let i = 0; i < colsCount; i++) {
cells.push(
tableHeader.createAndFill() || tableCell.createAndFill()
);
}

const rows = [];
for (let i = 0; i < rowsCount; i++) {
rows.push(tableRow.createChecked(null, cells));
}

return table.createChecked(null, rows);
}

export function tableInputRule(schema) {
return new InputRule(
/^\|\s+([\s\S]*)\s+\|\s*$/,
(state, match, start, end) => {
const [okay, columns] = match;
if (okay) {
const parts = columns.split("|").map((s) => s.trim());
const table = createTable(schema, 1, parts.length);

const tr = state.tr.replaceRangeWith(start, end, table);
const selection = TextSelection.create(
tr.doc,
start + 1
);
return tr.setSelection(selection);
}
return null;
}
);
}

export default tableInputRule;
17 changes: 17 additions & 0 deletions src/schema/article.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { orderedList, bulletList, listItem } from 'prosemirror-schema-list';
import { Schema } from 'prosemirror-model';
import { schema } from 'prosemirror-markdown';
import { tableNodes } from 'prosemirror-tables';

export const fullSchema = new Schema({
nodes: {
Expand All @@ -22,6 +23,22 @@ export const fullSchema = new Schema({
group: 'block',
}),
list_item: Object.assign(listItem, { content: 'paragraph block*' }),
...tableNodes({
tableGroup: 'block',
cellContent: 'paragraph+',
cellAttributes: {
background: {
default: null,
getFromDOM: (dom) => dom.style.backgroundColor || null,
setDOMAttr: (value, attrs) => {
if (value) attrs.style = (attrs.style || '') + `background-color: ${value};`;
},
},
colspan: { default: 1 },
rowspan: { default: 1 },
alignment: { default: null },
},
}),
},
marks: {
link: schema.spec.marks.get('link'),
Expand Down
38 changes: 36 additions & 2 deletions src/schema/markdown/articleParser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import MarkdownIt from 'markdown-it';
import MarkdownItSup from 'markdown-it-sup';
import { MarkdownParser } from 'prosemirror-markdown';
import MarkdownItMultimdTable from 'markdown-it-multimd-table';

import {
baseSchemaToMdMapping,
baseNodesMdToPmMapping,
Expand All @@ -14,6 +16,7 @@ export const articleSchemaToMdMapping = {
rule: 'hr',
heading: ['heading'],
image: 'image',
// table: 'table',
},
marks: { ...baseSchemaToMdMapping.marks },
};
Expand All @@ -33,20 +36,50 @@ export const articleMdToPmMapping = {
return { userId, userFullName };
},
},
table: {
node: 'table',
getAttrs: tok => ({ alignment: tok.info })
},
tr: {
node: 'table_row',
},
td: {
node: 'table_cell',
getAttrs: tok => ({
colspan: +(tok.attrGet("colspan") || 1),
rowspan: +(tok.attrGet("rowspan") || 1),
alignment: tok.info,
}),
},
th: {
node: 'table_header',
getAttrs: tok => ({
colspan: +(tok.attrGet("colspan") || 1),
rowspan: +(tok.attrGet("rowspan") || 1),
alignment: tok.info,
}),
},
};

const md = MarkdownIt('commonmark', {
html: false,
linkify: true,
breaks: true,
}).use(MarkdownItSup);
}).use(MarkdownItSup).use(MarkdownItMultimdTable, {
multiline: false,
rowspan: false,
headerless: false,
multibody: false,
autolabel: false,
});

md.enable([
// Process html entity - &#123;, &#xAF;, &quot;, ...
'entity',
// Process escaped chars and hardbreaks
'escape',
'hr',
// 'table',
]);

export class ArticleMarkdownTransformer {
Expand All @@ -66,11 +99,12 @@ export class ArticleMarkdownTransformer {
filterMdToPmSchemaMapping(schema, articleMdToPmMapping)
);
}

encode(_node) {
throw new Error('This is not implemented yet');
}

parse(content) {
return this.markdownParser.parse(content);
}
}
}
6 changes: 6 additions & 0 deletions src/schema/markdown/articleSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import {
strong,
link,
code,
table,
table_row,
table_cell,
} from './serializer';

export const ArticleMarkdownSerializer = new MarkdownSerializerBase(
Expand All @@ -33,6 +36,9 @@ export const ArticleMarkdownSerializer = new MarkdownSerializerBase(
image,
hard_break,
text,
table,
table_row,
table_cell,
},
{
em,
Expand Down
Loading

0 comments on commit 60ec1e4

Please sign in to comment.