Started on table editing/resizing
This commit is contained in:
parent
4b08eef12c
commit
9b4ea368dc
8 changed files with 196 additions and 48 deletions
4
TODO
4
TODO
|
@ -1,6 +1,8 @@
|
|||
### Next
|
||||
|
||||
//
|
||||
- Table cell height resize & cell width resize via width style
|
||||
- Column resize source: https://github.com/ProseMirror/prosemirror-tables/blob/master/src/columnresizing.js
|
||||
- Looks like all the required internals are exported so we can copy out & modify easily.
|
||||
|
||||
### In-Progress
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {EditorState} from "prosemirror-state";
|
||||
import {EditorView} from "prosemirror-view";
|
||||
import {exampleSetup} from "prosemirror-example-setup";
|
||||
import {tableEditing} from "prosemirror-tables";
|
||||
import {tableEditing, columnResizing} from "prosemirror-tables";
|
||||
|
||||
import {DOMParser} from "prosemirror-model";
|
||||
|
||||
|
@ -23,11 +23,16 @@ class ProseMirrorView {
|
|||
plugins: [
|
||||
...exampleSetup({schema, menuBar: false}),
|
||||
menu,
|
||||
columnResizing(),
|
||||
tableEditing(),
|
||||
]
|
||||
}),
|
||||
nodeViews,
|
||||
});
|
||||
|
||||
// Fix for native handles (Such as table size handling) in some browsers
|
||||
document.execCommand("enableObjectResizing", false, "false")
|
||||
document.execCommand("enableInlineTableEditing", false, "false")
|
||||
}
|
||||
|
||||
get content() {
|
||||
|
|
|
@ -61,9 +61,10 @@ export function insertBlockBefore(blockType) {
|
|||
/**
|
||||
* @param {Number} rows
|
||||
* @param {Number} columns
|
||||
* @param {Object} tableAttrs
|
||||
* @return {PmCommandHandler}
|
||||
*/
|
||||
export function insertTable(rows, columns) {
|
||||
export function insertTable(rows, columns, tableAttrs) {
|
||||
return function (state, dispatch) {
|
||||
if (!dispatch) return true;
|
||||
|
||||
|
@ -74,12 +75,13 @@ export function insertTable(rows, columns) {
|
|||
for (let y = 0; y < rows; y++) {
|
||||
const rowCells = [];
|
||||
for (let x = 0; x < columns; x++) {
|
||||
rowCells.push(nodes.table_cell.create(null));
|
||||
const cellText = nodes.paragraph.create(null);
|
||||
rowCells.push(nodes.table_cell.create(null, cellText));
|
||||
}
|
||||
rowNodes.push(nodes.table_row.create(null, rowCells));
|
||||
}
|
||||
|
||||
const table = nodes.table.create(null, rowNodes);
|
||||
const table = nodes.table.create(tableAttrs, rowNodes);
|
||||
tr.replaceSelectionWith(table);
|
||||
dispatch(tr);
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@ nodes.callout = function (state, node) {
|
|||
writeNodeAsHtml(state, node);
|
||||
};
|
||||
|
||||
nodes.table = function (state, node) {
|
||||
writeNodeAsHtml(state, node);
|
||||
};
|
||||
|
||||
function isPlainURL(link, parent, index, side) {
|
||||
if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) {
|
||||
return false
|
||||
|
|
|
@ -5,7 +5,6 @@ import {insertTable} from "../commands";
|
|||
class TableCreatorGrid {
|
||||
|
||||
constructor() {
|
||||
this.gridItems = [];
|
||||
this.size = 10;
|
||||
this.label = null;
|
||||
}
|
||||
|
@ -14,26 +13,31 @@ class TableCreatorGrid {
|
|||
// Renders the submenu.
|
||||
render(view) {
|
||||
|
||||
const gridItems = [];
|
||||
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));
|
||||
gridItems.push(elem);
|
||||
elem.addEventListener('mouseenter', event => {
|
||||
this.updateGridItemActiveStatus(elem, gridItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const gridWrap = crel("div", {
|
||||
class: prefix + "-table-creator-grid",
|
||||
style: `grid-template-columns: repeat(${this.size}, 14px);`,
|
||||
}, this.gridItems);
|
||||
}, gridItems);
|
||||
|
||||
gridWrap.addEventListener('mouseleave', event => {
|
||||
this.updateGridItemActiveStatus(null);
|
||||
this.updateGridItemActiveStatus(null, gridItems);
|
||||
});
|
||||
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 {x, y} = this.getPositionOfGridItem(event.target, gridItems);
|
||||
insertTable(y + 1, x + 1, {
|
||||
style: 'width: 100%;',
|
||||
})(view.state, view.dispatch);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -50,15 +54,16 @@ class TableCreatorGrid {
|
|||
|
||||
/**
|
||||
* @param {Element|null} newTarget
|
||||
* @param {Element[]} gridItems
|
||||
*/
|
||||
updateGridItemActiveStatus(newTarget) {
|
||||
const {x: xPos, y: yPos} = this.getPositionOfGridItem(newTarget);
|
||||
updateGridItemActiveStatus(newTarget, gridItems) {
|
||||
const {x: xPos, y: yPos} = this.getPositionOfGridItem(newTarget, gridItems);
|
||||
|
||||
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);
|
||||
gridItems[index].classList.toggle(prefix + "-table-creator-grid-item-active", active);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,10 +72,11 @@ class TableCreatorGrid {
|
|||
|
||||
/**
|
||||
* @param {Element} gridItem
|
||||
* @param {Element[]} gridItems
|
||||
* @return {{x: number, y: number}}
|
||||
*/
|
||||
getPositionOfGridItem(gridItem) {
|
||||
const index = this.gridItems.indexOf(gridItem);
|
||||
getPositionOfGridItem(gridItem, gridItems) {
|
||||
const index = gridItems.indexOf(gridItem);
|
||||
const y = Math.floor(index / this.size);
|
||||
const x = index % this.size;
|
||||
return {x, y};
|
||||
|
|
|
@ -17,17 +17,6 @@ function getAlignAttrFromDomNode(node) {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} className
|
||||
* @param {Object} attrs
|
||||
* @return {Object}
|
||||
*/
|
||||
function addClassToAttrs(className, attrs) {
|
||||
return Object.assign({}, attrs, {
|
||||
class: attrs.class ? attrs.class + ' ' + className : className,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param node
|
||||
* @param {Object} attrs
|
||||
|
@ -49,6 +38,45 @@ function getAttrsParserForAlignment(node) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} className
|
||||
* @param {Object} attrs
|
||||
* @return {Object}
|
||||
*/
|
||||
function addClassToAttrs(className, attrs) {
|
||||
return Object.assign({}, attrs, {
|
||||
class: attrs.class ? attrs.class + ' ' + className : className,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String[]} attrNames
|
||||
* @return {function(Element): {}}
|
||||
*/
|
||||
function domAttrsToAttrsParser(attrNames) {
|
||||
return function (node) {
|
||||
const attrs = {};
|
||||
for (const attr of attrNames) {
|
||||
attrs[attr] = node.hasAttribute(attr) ? node.getAttribute(attr) : null;
|
||||
}
|
||||
return attrs;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PmNode} node
|
||||
* @param {String[]} attrNames
|
||||
*/
|
||||
function extractAttrsForDom(node, attrNames) {
|
||||
const domAttrs = {};
|
||||
for (const attr of attrNames) {
|
||||
if (node.attrs[attr]) {
|
||||
domAttrs[attr] = node.attrs[attr];
|
||||
}
|
||||
}
|
||||
return domAttrs;
|
||||
}
|
||||
|
||||
const doc = {
|
||||
content: "block+",
|
||||
};
|
||||
|
@ -210,15 +238,29 @@ const bullet_list = Object.assign({}, bulletList, {content: "list_item+", group:
|
|||
const list_item = Object.assign({}, listItem, {content: 'paragraph block*'});
|
||||
|
||||
const {
|
||||
table,
|
||||
table_row,
|
||||
table_cell,
|
||||
table_header,
|
||||
} = tableNodes({
|
||||
tableGroup: "block",
|
||||
cellContent: "block*"
|
||||
cellContent: "block+"
|
||||
});
|
||||
|
||||
const table = {
|
||||
content: "table_row+",
|
||||
attrs: {
|
||||
style: {default: null},
|
||||
},
|
||||
tableRole: "table",
|
||||
isolating: true,
|
||||
group: "block",
|
||||
parseDOM: [{tag: "table", getAttrs: domAttrsToAttrsParser(['style'])}],
|
||||
toDOM(node) {
|
||||
console.log(extractAttrsForDom(node, ['style']));
|
||||
return ["table", extractAttrsForDom(node, ['style']), ["tbody", 0]]
|
||||
}
|
||||
};
|
||||
|
||||
const nodes = {
|
||||
doc,
|
||||
paragraph,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
|
||||
#editor.bs-editor {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
@ -46,9 +45,21 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.ProseMirror-hideselection *::selection { background: transparent; }
|
||||
.ProseMirror-hideselection *::-moz-selection { background: transparent; }
|
||||
.ProseMirror-hideselection { caret-color: transparent; }
|
||||
.ProseMirror table td, .ProseMirror table th {
|
||||
min-height: 1rem;
|
||||
}
|
||||
|
||||
.ProseMirror-hideselection *::selection {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ProseMirror-hideselection *::-moz-selection {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ProseMirror-hideselection {
|
||||
caret-color: transparent;
|
||||
}
|
||||
|
||||
.ProseMirror-selectednode {
|
||||
outline: 2px solid #8cf;
|
||||
|
@ -64,7 +75,9 @@ li.ProseMirror-selectednode:after {
|
|||
content: "";
|
||||
position: absolute;
|
||||
left: -32px;
|
||||
right: -2px; top: -2px; bottom: -2px;
|
||||
right: -2px;
|
||||
top: -2px;
|
||||
bottom: -2px;
|
||||
border: 2px solid #8cf;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -201,7 +214,9 @@ img.ProseMirror-separator {
|
|||
min-height: 1em;
|
||||
color: #666;
|
||||
padding: 1px 6px;
|
||||
top: 0; left: 0; right: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-bottom: 1px solid silver;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
|
@ -256,6 +271,7 @@ img.ProseMirror-separator {
|
|||
.ProseMirror-focused .ProseMirror-gapcursor {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Add space around the hr to make clicking it easier */
|
||||
|
||||
.ProseMirror-example-setup-style hr {
|
||||
|
@ -271,7 +287,8 @@ img.ProseMirror-separator {
|
|||
.ProseMirror blockquote {
|
||||
padding-left: 1em;
|
||||
border-left: 3px solid #eee;
|
||||
margin-left: 0; margin-right: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ProseMirror-example-setup-style img {
|
||||
|
@ -308,9 +325,12 @@ img.ProseMirror-separator {
|
|||
|
||||
.ProseMirror-prompt-close {
|
||||
position: absolute;
|
||||
left: 2px; top: 1px;
|
||||
left: 2px;
|
||||
top: 1px;
|
||||
color: #666;
|
||||
border: none; background: transparent; padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ProseMirror-prompt-close:after {
|
||||
|
@ -331,6 +351,7 @@ img.ProseMirror-separator {
|
|||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#editor, .editor {
|
||||
background: white;
|
||||
color: black;
|
||||
|
@ -341,13 +362,13 @@ img.ProseMirror-separator {
|
|||
margin-bottom: 23px;
|
||||
}
|
||||
|
||||
.ProseMirror p:first-child,
|
||||
.ProseMirror h1:first-child,
|
||||
.ProseMirror h2:first-child,
|
||||
.ProseMirror h3:first-child,
|
||||
.ProseMirror h4:first-child,
|
||||
.ProseMirror h5:first-child,
|
||||
.ProseMirror h6:first-child {
|
||||
.ProseMirror > p:first-child,
|
||||
.ProseMirror > h1:first-child,
|
||||
.ProseMirror > h2:first-child,
|
||||
.ProseMirror > h3:first-child,
|
||||
.ProseMirror > h4:first-child,
|
||||
.ProseMirror > h5:first-child,
|
||||
.ProseMirror > h6:first-child {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
@ -357,7 +378,9 @@ img.ProseMirror-separator {
|
|||
outline: none;
|
||||
}
|
||||
|
||||
.ProseMirror p { margin-bottom: 1em }
|
||||
.ProseMirror > p {
|
||||
margin-bottom: 1em
|
||||
}
|
||||
|
||||
.ProseMirror-menu-color-grid-container {
|
||||
display: grid;
|
||||
|
@ -454,6 +477,7 @@ img.ProseMirror-separator {
|
|||
color: #666;
|
||||
min-width: 80px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
@ -468,10 +492,12 @@ img.ProseMirror-separator {
|
|||
grid-template-columns: 1fr 2fr;
|
||||
align-items: center;
|
||||
padding: $-xs 0;
|
||||
|
||||
label {
|
||||
padding: 0 $-s;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 0 $-s;
|
||||
}
|
||||
|
@ -479,10 +505,12 @@ img.ProseMirror-separator {
|
|||
|
||||
.ProseMirror-menu-dialog-textarea-wrap {
|
||||
padding: $-xs $-s;
|
||||
|
||||
label {
|
||||
padding: 0 $-s;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
|
@ -495,6 +523,7 @@ img.ProseMirror-separator {
|
|||
font-size: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ProseMirror-imagewrap.ProseMirror-selectednode {
|
||||
outline: 0;
|
||||
}
|
||||
|
@ -502,6 +531,7 @@ img.ProseMirror-separator {
|
|||
.ProseMirror img[data-show-handles] {
|
||||
outline: 4px solid #000;
|
||||
}
|
||||
|
||||
.ProseMirror-dragdummy {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
@ -510,6 +540,7 @@ img.ProseMirror-separator {
|
|||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
.ProseMirror-grabhandle {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
@ -518,15 +549,55 @@ img.ProseMirror-separator {
|
|||
position: absolute;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.ProseMirror-grabhandle-left-top {
|
||||
cursor: nw-resize;
|
||||
}
|
||||
|
||||
.ProseMirror-grabhandle-right-top {
|
||||
cursor: ne-resize;
|
||||
}
|
||||
|
||||
.ProseMirror-grabhandle-right-bottom {
|
||||
cursor: se-resize;
|
||||
}
|
||||
|
||||
.ProseMirror-grabhandle-left-bottom {
|
||||
cursor: sw-resize;
|
||||
}
|
||||
}
|
||||
|
||||
.ProseMirror .tableWrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.ProseMirror table {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ProseMirror td, .ProseMirror th {
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
.ProseMirror .column-resize-handle {
|
||||
position: absolute;
|
||||
right: -2px; top: 0; bottom: 0;
|
||||
width: 4px;
|
||||
z-index: 20;
|
||||
background-color: #adf;
|
||||
pointer-events: none;
|
||||
}
|
||||
.ProseMirror.resize-cursor {
|
||||
cursor: ew-resize;
|
||||
cursor: col-resize;
|
||||
}
|
||||
/* Give selected cells a blue overlay */
|
||||
.ProseMirror .selectedCell:after {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
background: rgba(200, 200, 255, 0.4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,22 @@
|
|||
Some <span style="color: red;">Red Content</span> Lorem ipsum dolor sit amet. <br>
|
||||
Some <a href="https://cats.com" target="_blank" title="link A">Linked Content</a> Lorem ipsum dolor sit amet. <br>
|
||||
</p>
|
||||
|
||||
<table style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header A</th>
|
||||
<th>Header B</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Content 1</td>
|
||||
<td>Content 2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><img src="/user_avatar.png" alt="Logo"></p>
|
||||
<ul>
|
||||
<li>Item A</li>
|
||||
|
|
Loading…
Reference in a new issue