feat: revert table for basic user experience

This commit is contained in:
fantasticit 2022-04-24 20:57:24 +08:00
parent 3f5d5be67a
commit ab1dd6f05d
5 changed files with 270 additions and 380 deletions

View File

@ -1,8 +1,8 @@
import { mergeAttributes } from '@tiptap/core';
import { TableCell as BuiltInTableCell } from '@tiptap/extension-table-cell';
import ReactDOM from 'react-dom';
import { Button } from '@douyinfe/semi-ui';
import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
import { mergeAttributes } from '@tiptap/core';
import { TableCell as BuiltInTableCell } from '@tiptap/extension-table-cell';
import { Tooltip } from 'components/tooltip';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
@ -67,146 +67,146 @@ export const TableCell = BuiltInTableCell.extend({
return ['td', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addProseMirrorPlugins() {
const extensionThis = this;
let selectedRowIndex = -1;
// addProseMirrorPlugins() {
// const extensionThis = this;
// let selectedRowIndex = -1;
return [
new Plugin({
key: new PluginKey(`${this.name}FloatMenu`),
view: () =>
new FloatMenuView({
editor: this.editor,
tippyOptions: {
zIndex: 100,
offset: [-28, 0],
},
shouldShow: ({ editor }, floatMenuView) => {
if (!editor.isEditable) {
return false;
}
if (isTableSelected(editor.state.selection)) {
return false;
}
const cells = getCellsInColumn(0)(editor.state.selection);
if (selectedRowIndex > -1) {
// 获取当前行的第一个单元格的位置
const rowCells = getCellsInRow(selectedRowIndex)(editor.state.selection);
if (rowCells && rowCells[0]) {
const node = editor.view.nodeDOM(rowCells[0].pos) as HTMLElement;
if (node) {
const el = node.querySelector('a.grip-row') as HTMLElement;
if (el) {
floatMenuView.parentNode = el;
}
}
}
}
return !!cells?.some((cell, index) => isRowSelected(index)(editor.state.selection));
},
init: (dom, editor) => {
dom.classList.add('bubble-memu-table-cell');
dom.classList.add('row');
ReactDOM.render(
<>
<Tooltip content="向前插入一行" position="left">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus />}
onClick={() => {
editor.chain().addRowBefore().run();
}}
/>
</Tooltip>
<Tooltip content="删除当前行" position="left">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconDelete />}
onClick={() => {
editor.chain().deleteRow().run();
}}
/>
</Tooltip>
<Tooltip content="向后插入一行" position="left" hideOnClick>
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus />}
onClick={() => {
editor.chain().addRowAfter().run();
}}
/>
</Tooltip>
</>,
dom
);
},
}),
props: {
decorations: (state) => {
if (!extensionThis.editor.isEditable) {
return;
}
// return [
// new Plugin({
// key: new PluginKey(`${this.name}FloatMenu`),
// view: () =>
// new FloatMenuView({
// editor: this.editor,
// tippyOptions: {
// zIndex: 100,
// offset: [-28, 0],
// },
// shouldShow: ({ editor }, floatMenuView) => {
// if (!editor.isEditable) {
// return false;
// }
// if (isTableSelected(editor.state.selection)) {
// return false;
// }
// const cells = getCellsInColumn(0)(editor.state.selection);
// if (selectedRowIndex > -1) {
// // 获取当前行的第一个单元格的位置
// const rowCells = getCellsInRow(selectedRowIndex)(editor.state.selection);
// if (rowCells && rowCells[0]) {
// const node = editor.view.nodeDOM(rowCells[0].pos) as HTMLElement;
// if (node) {
// const el = node.querySelector('a.grip-row') as HTMLElement;
// if (el) {
// floatMenuView.parentNode = el;
// }
// }
// }
// }
// return !!cells?.some((cell, index) => isRowSelected(index)(editor.state.selection));
// },
// init: (dom, editor) => {
// dom.classList.add('bubble-memu-table-cell');
// dom.classList.add('row');
// ReactDOM.render(
// <>
// <Tooltip content="向前插入一行" position="left">
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconPlus />}
// onClick={() => {
// editor.chain().addRowBefore().run();
// }}
// />
// </Tooltip>
// <Tooltip content="删除当前行" position="left">
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconDelete />}
// onClick={() => {
// editor.chain().deleteRow().run();
// }}
// />
// </Tooltip>
// <Tooltip content="向后插入一行" position="left" hideOnClick>
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconPlus />}
// onClick={() => {
// editor.chain().addRowAfter().run();
// }}
// />
// </Tooltip>
// </>,
// dom
// );
// },
// }),
// props: {
// decorations: (state) => {
// if (!extensionThis.editor.isEditable) {
// return;
// }
const { doc, selection } = state;
const decorations: Decoration[] = [];
const cells = getCellsInColumn(0)(selection);
// const { doc, selection } = state;
// const decorations: Decoration[] = [];
// const cells = getCellsInColumn(0)(selection);
if (cells) {
cells.forEach(({ pos }, index) => {
if (index === 0) {
decorations.push(
Decoration.widget(pos + 1, () => {
const grip = document.createElement('a');
grip.classList.add('grip-table');
if (isTableSelected(selection)) {
grip.classList.add('selected');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
selectedRowIndex = -1;
this.editor.view.dispatch(selectTable(this.editor.state.tr));
});
return grip;
})
);
}
decorations.push(
Decoration.widget(pos + 1, () => {
const rowSelected = isRowSelected(index)(selection);
const grip = document.createElement('a');
grip.classList.add('grip-row');
if (rowSelected) {
grip.classList.add('selected');
}
if (index === 0) {
grip.classList.add('first');
}
if (index === cells.length - 1) {
grip.classList.add('last');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
selectedRowIndex = index;
this.editor.view.dispatch(selectRow(index)(this.editor.state.tr));
});
return grip;
})
);
});
}
// if (cells) {
// cells.forEach(({ pos }, index) => {
// if (index === 0) {
// decorations.push(
// Decoration.widget(pos + 1, () => {
// const grip = document.createElement('a');
// grip.classList.add('grip-table');
// if (isTableSelected(selection)) {
// grip.classList.add('selected');
// }
// grip.addEventListener('mousedown', (event) => {
// event.preventDefault();
// event.stopImmediatePropagation();
// selectedRowIndex = -1;
// this.editor.view.dispatch(selectTable(this.editor.state.tr));
// });
// return grip;
// })
// );
// }
// decorations.push(
// Decoration.widget(pos + 1, () => {
// const rowSelected = isRowSelected(index)(selection);
// const grip = document.createElement('a');
// grip.classList.add('grip-row');
// if (rowSelected) {
// grip.classList.add('selected');
// }
// if (index === 0) {
// grip.classList.add('first');
// }
// if (index === cells.length - 1) {
// grip.classList.add('last');
// }
// grip.addEventListener('mousedown', (event) => {
// event.preventDefault();
// event.stopImmediatePropagation();
// selectedRowIndex = index;
// this.editor.view.dispatch(selectRow(index)(this.editor.state.tr));
// });
// return grip;
// })
// );
// });
// }
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
// return DecorationSet.create(doc, decorations);
// },
// },
// }),
// ];
// },
});

View File

@ -1,18 +1,14 @@
import { mergeAttributes } from '@tiptap/core';
import { TableHeader as BuiltInTableHeader } from '@tiptap/extension-table-header';
import ReactDOM from 'react-dom';
import { Button, Space } from '@douyinfe/semi-ui';
import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
import { TableHeader as BuiltInTableHeader } from '@tiptap/extension-table-header';
import { Tooltip } from 'components/tooltip';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';
import { getCellsInRow, isColumnSelected, isTableSelected, selectColumn } from '../utils/table';
import { FloatMenuView } from '../views/float-menu';
// @flow
/* eslint-disable no-unused-vars */
import { mergeAttributes } from '@tiptap/core';
// import TableHeader from "@tiptap/extension-table-header";
export const TableHeader = BuiltInTableHeader.extend({
addAttributes() {
return {
@ -64,117 +60,117 @@ export const TableHeader = BuiltInTableHeader.extend({
return ['th', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addProseMirrorPlugins() {
const extensionThis = this;
// addProseMirrorPlugins() {
// const extensionThis = this;
return [
new Plugin({
key: new PluginKey(`${this.name}FloatMenu`),
view: () =>
new FloatMenuView({
editor: this.editor,
tippyOptions: {
zIndex: 100,
},
shouldShow: ({ editor }, floatMenuView) => {
if (!editor.isEditable) {
return false;
}
const selection = editor.state.selection;
if (isTableSelected(selection)) {
return false;
}
const cells = getCellsInRow(0)(selection);
// return [
// new Plugin({
// key: new PluginKey(`${this.name}FloatMenu`),
// view: () =>
// new FloatMenuView({
// editor: this.editor,
// tippyOptions: {
// zIndex: 100,
// },
// shouldShow: ({ editor }, floatMenuView) => {
// if (!editor.isEditable) {
// return false;
// }
// const selection = editor.state.selection;
// if (isTableSelected(selection)) {
// return false;
// }
// const cells = getCellsInRow(0)(selection);
if (cells && cells[0]) {
const node = editor.view.nodeDOM(cells[0].pos) as HTMLElement;
floatMenuView.setConatiner(node.parentElement.parentElement.parentElement.parentElement);
}
// if (cells && cells[0]) {
// const node = editor.view.nodeDOM(cells[0].pos) as HTMLElement;
// floatMenuView.setConatiner(node.parentElement.parentElement.parentElement.parentElement);
// }
return !!cells?.some((cell, index) => isColumnSelected(index)(selection));
},
init: (dom, editor) => {
dom.classList.add('bubble-memu-table-cell');
ReactDOM.render(
<Space>
<Tooltip content="向前插入一列">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus />}
onClick={() => {
editor.chain().addColumnBefore().run();
}}
/>
</Tooltip>
<Tooltip content="删除当前列">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconDelete />}
onClick={() => {
editor.chain().deleteColumn().run();
}}
/>
</Tooltip>
<Tooltip content="向后插入一列" hideOnClick>
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconPlus />}
onClick={() => {
editor.chain().addColumnAfter().run();
}}
/>
</Tooltip>
</Space>,
dom
);
},
}),
props: {
decorations: (state) => {
if (!extensionThis.editor.isEditable) {
return;
}
// return !!cells?.some((cell, index) => isColumnSelected(index)(selection));
// },
// init: (dom, editor) => {
// dom.classList.add('bubble-memu-table-cell');
// ReactDOM.render(
// <Space>
// <Tooltip content="向前插入一列">
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconPlus />}
// onClick={() => {
// editor.chain().addColumnBefore().run();
// }}
// />
// </Tooltip>
// <Tooltip content="删除当前列">
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconDelete />}
// onClick={() => {
// editor.chain().deleteColumn().run();
// }}
// />
// </Tooltip>
// <Tooltip content="向后插入一列" hideOnClick>
// <Button
// size="small"
// theme="borderless"
// type="tertiary"
// icon={<IconPlus />}
// onClick={() => {
// editor.chain().addColumnAfter().run();
// }}
// />
// </Tooltip>
// </Space>,
// dom
// );
// },
// }),
// props: {
// decorations: (state) => {
// if (!extensionThis.editor.isEditable) {
// return;
// }
const { doc, selection } = state;
const decorations: Decoration[] = [];
const cells = getCellsInRow(0)(selection);
// const { doc, selection } = state;
// const decorations: Decoration[] = [];
// const cells = getCellsInRow(0)(selection);
if (cells) {
cells.forEach(({ pos }, index) => {
decorations.push(
Decoration.widget(pos + 1, () => {
const colSelected = isColumnSelected(index)(selection);
const grip = document.createElement('a');
grip.classList.add('grip-column');
if (colSelected) {
grip.classList.add('selected');
}
if (index === 0) {
grip.classList.add('first');
} else if (index === cells.length - 1) {
grip.classList.add('last');
}
grip.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.editor.view.dispatch(selectColumn(index)(this.editor.state.tr));
});
return grip;
})
);
});
}
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
// if (cells) {
// cells.forEach(({ pos }, index) => {
// decorations.push(
// Decoration.widget(pos + 1, () => {
// const colSelected = isColumnSelected(index)(selection);
// const grip = document.createElement('a');
// grip.classList.add('grip-column');
// if (colSelected) {
// grip.classList.add('selected');
// }
// if (index === 0) {
// grip.classList.add('first');
// } else if (index === cells.length - 1) {
// grip.classList.add('last');
// }
// grip.addEventListener('mousedown', (event) => {
// event.preventDefault();
// event.stopImmediatePropagation();
// this.editor.view.dispatch(selectColumn(index)(this.editor.state.tr));
// });
// return grip;
// })
// );
// });
// }
// return DecorationSet.create(doc, decorations);
// },
// },
// }),
// ];
// },
});

View File

@ -1,57 +1,3 @@
import { mergeAttributes } from '@tiptap/core';
import { Table as BuiltInTable } from '@tiptap/extension-table';
import BuiltInTable from '@tiptap/extension-table';
export const Table = BuiltInTable.extend({
addAttributes() {
return {
style: {
default: null,
},
};
},
renderHTML({ node, HTMLAttributes }) {
let totalWidth = 0;
let fixedWidth = true;
try {
// use first row to determine width of table;
// @ts-ignore
const tr = node.content.content[0];
tr.content.content.forEach((td) => {
if (td.attrs.colwidth) {
td.attrs.colwidth.forEach((col) => {
if (!col) {
fixedWidth = false;
totalWidth += this.options.cellMinWidth;
} else {
totalWidth += col;
}
});
} else {
fixedWidth = false;
const colspan = td.attrs.colspan ? td.attrs.colspan : 1;
totalWidth += this.options.cellMinWidth * colspan;
}
});
} catch (error) {
fixedWidth = false;
}
if (fixedWidth && totalWidth > 0) {
HTMLAttributes.style = `width: ${totalWidth}px;`;
} else if (totalWidth && totalWidth > 0) {
HTMLAttributes.style = `min-width: ${totalWidth}px`;
} else {
HTMLAttributes.style = null;
}
return [
'div',
{ class: 'tableWrapper' },
['table', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), ['tbody', 0]],
];
},
}).configure({
resizable: true,
cellMinWidth: 50,
});
export const Table = BuiltInTable.configure({ resizable: true });

View File

@ -29,9 +29,7 @@ export const TableBubbleMenu = ({ editor }) => {
maxWidth: 486,
placement: 'bottom',
}}
matchRenderContainer={(node: HTMLElement) =>
node && node.classList && node.classList.contains('tableWrapper') && node.tagName === 'DIV'
}
matchRenderContainer={(node: HTMLElement) => node && node.tagName === 'TABLE'}
>
<Space>
<Tooltip content="向前插入一列">

View File

@ -1,20 +1,10 @@
.ProseMirror {
.tableWrapper {
max-width: 100%;
margin-top: 0.75em;
overflow: auto;
&.has-focus {
padding: 1em 0 0 1em;
}
}
table {
table-layout: fixed;
border-collapse: collapse;
border-width: 1px;
border-style: solid;
border-color: var(--semi-color-fill-2);
overflow: hidden;
table-layout: fixed;
width: 100%;
margin: 0.75em 0 0;
td,
th {
@ -44,60 +34,6 @@
background: var(--semi-color-info-light-hover);
}
.grip-column {
position: absolute;
top: -1em;
left: 0;
z-index: 10;
display: block;
width: 100%;
height: 0.7em;
margin-bottom: 3px;
cursor: pointer;
background: #ced4da;
&:hover,
&.selected {
background: var(--semi-color-info);
}
}
.grip-row {
position: absolute;
top: 0;
left: -1em;
z-index: 10;
display: block;
width: 0.7em;
height: 100%;
margin-right: 3px;
cursor: pointer;
background: #ced4da;
&:hover,
&.selected {
background: var(--semi-color-info);
}
}
.grip-table {
position: absolute;
top: -1em;
left: -1em;
z-index: 10;
display: block;
width: 0.8em;
height: 0.8em;
cursor: pointer;
background: #ced4da;
border-radius: 50%;
&:hover,
&.selected {
background: var(--semi-color-info);
}
}
.column-resize-handle {
position: absolute;
top: 0;
@ -107,5 +43,19 @@
pointer-events: none;
background-color: #adf;
}
p {
margin: 0;
}
}
}
.tableWrapper {
padding: 1rem 0;
overflow-x: auto;
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}