### In-Progress
-//
+- Tables
### Features
-- Tables
- Images
- Drawings
- LTR/RTL control
"prosemirror-model": "^1.15.0",
"prosemirror-schema-list": "^1.1.6",
"prosemirror-state": "^1.3.4",
+ "prosemirror-tables": "^1.1.1",
"prosemirror-view": "^1.23.2",
"sortablejs": "^1.14.0"
},
"prosemirror-transform": "^1.0.0"
}
},
+ "node_modules/prosemirror-tables": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.1.1.tgz",
+ "integrity": "sha512-LmCz4jrlqQZRsYRDzCRYf/pQ5CUcSOyqZlAj5kv67ZWBH1SVLP2U9WJEvQfimWgeRlIz0y0PQVqO1arRm1+woA==",
+ "dependencies": {
+ "prosemirror-keymap": "^1.1.2",
+ "prosemirror-model": "^1.8.1",
+ "prosemirror-state": "^1.3.1",
+ "prosemirror-transform": "^1.2.1",
+ "prosemirror-view": "^1.13.3"
+ }
+ },
"node_modules/prosemirror-transform": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.3.3.tgz",
"prosemirror-transform": "^1.0.0"
}
},
+ "prosemirror-tables": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.1.1.tgz",
+ "integrity": "sha512-LmCz4jrlqQZRsYRDzCRYf/pQ5CUcSOyqZlAj5kv67ZWBH1SVLP2U9WJEvQfimWgeRlIz0y0PQVqO1arRm1+woA==",
+ "requires": {
+ "prosemirror-keymap": "^1.1.2",
+ "prosemirror-model": "^1.8.1",
+ "prosemirror-state": "^1.3.1",
+ "prosemirror-transform": "^1.2.1",
+ "prosemirror-view": "^1.13.3"
+ }
+ },
"prosemirror-transform": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.3.3.tgz",
"prosemirror-model": "^1.15.0",
"prosemirror-schema-list": "^1.1.6",
"prosemirror-state": "^1.3.4",
+ "prosemirror-tables": "^1.1.1",
"prosemirror-view": "^1.23.2",
"sortablejs": "^1.14.0"
}
import {EditorState} from "prosemirror-state";
import {EditorView} from "prosemirror-view";
import {exampleSetup} from "prosemirror-example-setup";
+import {tableEditing} from "prosemirror-tables";
import {DOMParser} from "prosemirror-model";
plugins: [
...exampleSetup({schema, menuBar: false}),
menu,
+ tableEditing(),
]
}),
nodeViews,
}
}
+/**
+ * @param {Number} rows
+ * @param {Number} columns
+ * @return {PmCommandHandler}
+ */
+export function insertTable(rows, columns) {
+ return function (state, dispatch) {
+ if (!dispatch) return true;
+
+ const tr = state.tr;
+ const nodes = state.schema.nodes;
+
+ const rowNodes = [];
+ for (let y = 0; y < rows; y++) {
+ const rowCells = [];
+ for (let x = 0; x < columns; x++) {
+ rowCells.push(nodes.table_cell.create(null));
+ }
+ rowNodes.push(nodes.table_row.create(null, rowCells));
+ }
+
+ const table = nodes.table.create(null, rowNodes);
+ tr.replaceSelectionWith(table);
+ dispatch(tr);
+
+ return true;
+ }
+}
+
/**
* @return {PmCommandHandler}
*/
--- /dev/null
+import crel from "crelt"
+import {prefix} from "./menu-utils";
+import {insertTable} from "../commands";
+
+class TableCreatorGrid {
+
+ constructor() {
+ this.gridItems = [];
+ this.size = 10;
+ this.label = null;
+ }
+
+ // :: (EditorView) → {dom: dom.Node, update: (EditorState) → bool}
+ // Renders the submenu.
+ render(view) {
+
+ for (let y = 0; y < this.size; y++) {
+ for (let x = 0; x < this.size; x++) {
+ const elem = crel("div", {class: prefix + "-table-creator-grid-item"});
+ this.gridItems.push(elem);
+ elem.addEventListener('mouseenter', event => this.updateGridItemActiveStatus(elem));
+ }
+ }
+
+ const gridWrap = crel("div", {
+ class: prefix + "-table-creator-grid",
+ style: `grid-template-columns: repeat(${this.size}, 14px);`,
+ }, this.gridItems);
+
+ gridWrap.addEventListener('mouseleave', event => {
+ this.updateGridItemActiveStatus(null);
+ });
+ gridWrap.addEventListener('click', event => {
+ if (event.target.classList.contains(prefix + "-table-creator-grid-item")) {
+ const {x, y} = this.getPositionOfGridItem(event.target);
+ insertTable(y + 1, x + 1)(view.state, view.dispatch);
+ }
+ });
+
+ const gridLabel = crel("div", {class: prefix + "-table-creator-grid-label"});
+ this.label = gridLabel;
+ const wrap = crel("div", {class: prefix + "-table-creator-grid-container"}, [gridWrap, gridLabel]);
+
+ function update(state) {
+ return true;
+ }
+
+ return {dom: wrap, update}
+ }
+
+ /**
+ * @param {Element|null} newTarget
+ */
+ updateGridItemActiveStatus(newTarget) {
+ const {x: xPos, y: yPos} = this.getPositionOfGridItem(newTarget);
+
+ for (let y = 0; y < this.size; y++) {
+ for (let x = 0; x < this.size; x++) {
+ const active = x <= xPos && y <= yPos;
+ const index = (y * this.size) + x;
+ this.gridItems[index].classList.toggle(prefix + "-table-creator-grid-item-active", active);
+ }
+ }
+
+ this.label.textContent = (xPos + yPos < 0) ? '' : `${xPos + 1} x ${yPos + 1}`;
+ }
+
+ /**
+ * @param {Element} gridItem
+ * @return {{x: number, y: number}}
+ */
+ getPositionOfGridItem(gridItem) {
+ const index = this.gridItems.indexOf(gridItem);
+ const y = Math.floor(index / this.size);
+ const x = index % this.size;
+ return {x, y};
+ }
+}
+
+export default TableCreatorGrid;
\ No newline at end of file
source_code: {
width: 24, height: 24,
path: "M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z",
+ },
+ table: {
+ width: 24, height: 24,
+ path: "M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 20H4v-4h4v4zm0-6H4v-4h4v4zm0-6H4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4z",
}
};
} from "./menu"
import {icons} from "./icons";
import ColorPickerGrid from "./ColorPickerGrid";
-import DialogBox from "./DialogBox";
+import TableCreatorGrid from "./TableCreatorGrid";
import {toggleMark} from "prosemirror-commands";
import {menuBar} from "./menubar"
import schema from "../schema";
import {removeMarks} from "../commands";
-import DialogForm from "./DialogForm";
-import DialogInput from "./DialogInput";
import itemAnchorButtonItem from "./item-anchor-button";
import itemHtmlSourceButton from "./item-html-source-button";
title: "Horizontal Rule",
icon: icons.horizontal_rule,
}),
+ new DropdownSubmenu([
+ new TableCreatorGrid()
+ ], {icon: icons.table}),
itemHtmlSourceButton(),
];
import {orderedList, bulletList, listItem} from "prosemirror-schema-list";
+import {tableNodes} from "prosemirror-tables";
/**
* @param {HTMLElement} node
],
toDOM(node) {
const type = node.attrs.type || 'info';
- return ['p', addAlignmentAttr(node, {class: 'callout ' + type}) , 0];
+ return ['p', addAlignmentAttr(node, {class: 'callout ' + type}), 0];
}
};
const bullet_list = Object.assign({}, bulletList, {content: "list_item+", group: "block"});
const list_item = Object.assign({}, listItem, {content: 'paragraph block*'});
+const {
+ table,
+ table_row,
+ table_cell,
+ table_header,
+} = tableNodes({
+ tableGroup: "block",
+ cellContent: "block*"
+});
+
const nodes = {
doc,
paragraph,
ordered_list,
bullet_list,
list_item,
+ table,
+ table_row,
+ table_cell,
+ table_header,
};
export default nodes;
\ No newline at end of file
display: block;
}
+.ProseMirror-menu-table-creator-grid {
+ display: grid;
+ gap: 2px;
+}
+
+.ProseMirror-menu-table-creator-grid-item {
+ width: 14px;
+ height: 14px;
+ border: 2px solid #BBB;
+ display: block;
+ cursor: pointer;
+}
+
+.ProseMirror-menu-table-creator-grid-item-active {
+ border: 2px solid #555;
+ background-color: #DDD;
+}
+
+.ProseMirror-menu-table-creator-grid-label {
+ padding: $-xs;
+ text-align: center;
+}
+
.ProseMirror-menu-dialog-wrap {
position: fixed;
top: 0;