Merge branch 'feature/tables' into main
This commit is contained in:
7
docs/src/demos/Nodes/Table/index.spec.js
Normal file
7
docs/src/demos/Nodes/Table/index.spec.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
context('/api/nodes/table', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/api/nodes/table')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Write tests
|
||||||
|
})
|
||||||
208
docs/src/demos/Nodes/Table/index.vue
Normal file
208
docs/src/demos/Nodes/Table/index.vue
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()">
|
||||||
|
insertTable
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().addColumnBefore().run()">
|
||||||
|
addColumnBefore
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().addColumnAfter().run()">
|
||||||
|
addColumnAfter
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().deleteColumn().run()">
|
||||||
|
deleteColumn
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().addRowBefore().run()">
|
||||||
|
addRowBefore
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().addRowAfter().run()">
|
||||||
|
addRowAfter
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().deleteRow().run()">
|
||||||
|
deleteRow
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().deleteTable().run()">
|
||||||
|
deleteTable
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().mergeCells().run()">
|
||||||
|
mergeCells
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().splitCell().run()">
|
||||||
|
splitCell
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeaderColumn().run()">
|
||||||
|
toggleHeaderColumn
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeaderRow().run()">
|
||||||
|
toggleHeaderRow
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeaderCell().run()">
|
||||||
|
toggleHeaderCell
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().mergeOrSplit().run()">
|
||||||
|
mergeOrSplit
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().setCellAttributes({name: 'color', value: 'pink'}).run()">
|
||||||
|
setCellAttributes
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().fixTables().run()">
|
||||||
|
fixTables
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().goToNextCell().run()">
|
||||||
|
goToNextCell
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().goToPreviousCell().run()">
|
||||||
|
goToPreviousCell
|
||||||
|
</button>
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor } from '@tiptap/core'
|
||||||
|
import { EditorContent } from '@tiptap/vue'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import Table from '@tiptap/extension-table'
|
||||||
|
import TableRow from '@tiptap/extension-table-row'
|
||||||
|
import TableCell from '@tiptap/extension-table-cell'
|
||||||
|
import TableHeader from '@tiptap/extension-table-header'
|
||||||
|
import Gapcursor from '@tiptap/extension-gapcursor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
Gapcursor,
|
||||||
|
Table.configure({
|
||||||
|
resizable: true,
|
||||||
|
}),
|
||||||
|
TableRow,
|
||||||
|
TableHeader,
|
||||||
|
TableCell.extend({
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
// original attributes
|
||||||
|
colspan: {
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
rowspan: {
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
colwidth: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
// add a color attribute to the table cell
|
||||||
|
color: {
|
||||||
|
default: null,
|
||||||
|
renderHTML: attributes => {
|
||||||
|
return {
|
||||||
|
style: `color: ${attributes.color}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>
|
||||||
|
People
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th colspan="3">Description</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Cyndi Lauper</td>
|
||||||
|
<td>singer</td>
|
||||||
|
<td>songwriter</td>
|
||||||
|
<td>actress</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ProseMirror {
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
min-width: 1em;
|
||||||
|
border: 2px solid #ced4da;
|
||||||
|
padding: 3px 5px;
|
||||||
|
vertical-align: top;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
right: -2px;
|
||||||
|
top: 0;
|
||||||
|
bottom: -2px;
|
||||||
|
width: 4px;
|
||||||
|
background-color: #adf;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableWrapper {
|
||||||
|
padding: 1rem 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-cursor {
|
||||||
|
cursor: ew-resize;
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
docs/src/docPages/api/nodes/table-header.md
Normal file
8
docs/src/docPages/api/nodes/table-header.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# TableHeader
|
||||||
|
|
||||||
|
:::pro Fund the development 💖
|
||||||
|
We need your support to maintain, update, support and develop tiptap 2. If you’re waiting for this extension, [become a sponsor and fund open source](/sponsor).
|
||||||
|
:::
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
@@ -5,3 +5,10 @@ We need your support to maintain, update, support and develop tiptap 2. If you
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
|
⚠️ Preview
|
||||||
|
|
||||||
|
Tasks
|
||||||
|
- backspace: when all cells are selected, delete table
|
||||||
|
|
||||||
|
<demo name="Nodes/Table" />
|
||||||
|
|||||||
@@ -139,6 +139,9 @@
|
|||||||
- title: TableCell
|
- title: TableCell
|
||||||
link: /api/nodes/table-cell
|
link: /api/nodes/table-cell
|
||||||
type: draft
|
type: draft
|
||||||
|
- title: TableHeader
|
||||||
|
link: /api/nodes/table-header
|
||||||
|
type: draft
|
||||||
- title: TaskList
|
- title: TaskList
|
||||||
link: /api/nodes/task-list
|
link: /api/nodes/task-list
|
||||||
- title: TaskItem
|
- title: TaskItem
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"@types/prosemirror-model": "^1.11.2",
|
"@types/prosemirror-model": "^1.11.2",
|
||||||
"@types/prosemirror-schema-list": "^1.0.2",
|
"@types/prosemirror-schema-list": "^1.0.2",
|
||||||
"@types/prosemirror-state": "^1.2.6",
|
"@types/prosemirror-state": "^1.2.6",
|
||||||
|
"@types/prosemirror-tables": "^0.9.1",
|
||||||
"@types/prosemirror-transform": "^1.1.2",
|
"@types/prosemirror-transform": "^1.1.2",
|
||||||
"@types/prosemirror-view": "^1.17.1",
|
"@types/prosemirror-view": "^1.17.1",
|
||||||
"prosemirror-commands": "^1.1.3",
|
"prosemirror-commands": "^1.1.3",
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ export interface NodeConfig<Options = any, Commands = {}> extends Overwrite<Exte
|
|||||||
*/
|
*/
|
||||||
isolating?: NodeSpec['isolating'] | ((this: { options: Options }) => NodeSpec['isolating']),
|
isolating?: NodeSpec['isolating'] | ((this: { options: Options }) => NodeSpec['isolating']),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table Role
|
||||||
|
*/
|
||||||
|
tableRole?: NodeSpec['tableRole'] | ((this: { options: Options }) => NodeSpec['tableRole']),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse HTML
|
* Parse HTML
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { EditorState, TextSelection } from 'prosemirror-state'
|
import { EditorState, TextSelection } from 'prosemirror-state'
|
||||||
import { Command, FocusPosition } from '../types'
|
import { Command, FocusPosition } from '../types'
|
||||||
import minMax from '../utilities/minMax'
|
import minMax from '../utilities/minMax'
|
||||||
|
import isTextSelection from '../helpers/isTextSelection'
|
||||||
|
|
||||||
function resolveSelection(state: EditorState, position: FocusPosition = null) {
|
function resolveSelection(state: EditorState, position: FocusPosition = null) {
|
||||||
if (!position) {
|
if (!position) {
|
||||||
@@ -42,6 +43,12 @@ export const focus = (position: FocusPosition = null): Command => ({
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we don’t try to resolve a NodeSelection or CellSelection
|
||||||
|
if (dispatch && position === null && !isTextSelection(editor.state.selection)) {
|
||||||
|
view.focus()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const { from, to } = resolveSelection(editor.state, position) || editor.state.selection
|
const { from, to } = resolveSelection(editor.state, position) || editor.state.selection
|
||||||
const { doc } = tr
|
const { doc } = tr
|
||||||
const resolvedFrom = minMax(from, 0, doc.content.size)
|
const resolvedFrom = minMax(from, 0, doc.content.size)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const FocusEvents = Extension.create({
|
|||||||
|
|
||||||
view.dispatch(transaction)
|
view.dispatch(transaction)
|
||||||
|
|
||||||
return true
|
return false
|
||||||
},
|
},
|
||||||
blur: (view, event) => {
|
blur: (view, event) => {
|
||||||
editor.isFocused = false
|
editor.isFocused = false
|
||||||
@@ -35,7 +35,7 @@ export const FocusEvents = Extension.create({
|
|||||||
|
|
||||||
view.dispatch(transaction)
|
view.dispatch(transaction)
|
||||||
|
|
||||||
return true
|
return false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
23
packages/core/src/helpers/findParentNodeClosestToPos.ts
Normal file
23
packages/core/src/helpers/findParentNodeClosestToPos.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ResolvedPos, Node as ProsemirrorNode } from 'prosemirror-model'
|
||||||
|
|
||||||
|
export type Predicate = (node: ProsemirrorNode) => boolean
|
||||||
|
|
||||||
|
export default function findParentNodeClosestToPos($pos: ResolvedPos, predicate: Predicate): ({
|
||||||
|
pos: number,
|
||||||
|
start: number,
|
||||||
|
depth: number,
|
||||||
|
node: ProsemirrorNode,
|
||||||
|
} | undefined) {
|
||||||
|
for (let i = $pos.depth; i > 0; i -= 1) {
|
||||||
|
const node = $pos.node(i)
|
||||||
|
|
||||||
|
if (predicate(node)) {
|
||||||
|
return {
|
||||||
|
pos: i > 0 ? $pos.before(i) : 0,
|
||||||
|
start: $pos.start(i),
|
||||||
|
depth: i,
|
||||||
|
node,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ export default function getSchema(extensions: Extensions): Schema {
|
|||||||
code: callOrReturn(extension.config.code, context),
|
code: callOrReturn(extension.config.code, context),
|
||||||
defining: callOrReturn(extension.config.defining, context),
|
defining: callOrReturn(extension.config.defining, context),
|
||||||
isolating: callOrReturn(extension.config.isolating, context),
|
isolating: callOrReturn(extension.config.isolating, context),
|
||||||
|
tableRole: callOrReturn(extension.config.tableRole, context),
|
||||||
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
|
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
|
||||||
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
|
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
|
||||||
})),
|
})),
|
||||||
|
|||||||
6
packages/core/src/helpers/isCellSelection.ts
Normal file
6
packages/core/src/helpers/isCellSelection.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { CellSelection } from 'prosemirror-tables'
|
||||||
|
import isObject from '../utilities/isObject'
|
||||||
|
|
||||||
|
export default function isCellSelection(value: unknown): value is CellSelection {
|
||||||
|
return isObject(value) && value instanceof CellSelection
|
||||||
|
}
|
||||||
6
packages/core/src/helpers/isNodeSelection.ts
Normal file
6
packages/core/src/helpers/isNodeSelection.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { NodeSelection } from 'prosemirror-state'
|
||||||
|
import isObject from '../utilities/isObject'
|
||||||
|
|
||||||
|
export default function isNodeSelection(value: unknown): value is NodeSelection {
|
||||||
|
return isObject(value) && value instanceof NodeSelection
|
||||||
|
}
|
||||||
6
packages/core/src/helpers/isTextSelection.ts
Normal file
6
packages/core/src/helpers/isTextSelection.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { TextSelection } from 'prosemirror-state'
|
||||||
|
import isObject from '../utilities/isObject'
|
||||||
|
|
||||||
|
export default function isTextSelection(value: unknown): value is TextSelection {
|
||||||
|
return isObject(value) && value instanceof TextSelection
|
||||||
|
}
|
||||||
@@ -16,5 +16,9 @@ export { default as mergeAttributes } from './utilities/mergeAttributes'
|
|||||||
export { default as isActive } from './helpers/isActive'
|
export { default as isActive } from './helpers/isActive'
|
||||||
export { default as isMarkActive } from './helpers/isMarkActive'
|
export { default as isMarkActive } from './helpers/isMarkActive'
|
||||||
export { default as isNodeActive } from './helpers/isNodeActive'
|
export { default as isNodeActive } from './helpers/isNodeActive'
|
||||||
|
export { default as isNodeSelection } from './helpers/isNodeSelection'
|
||||||
|
export { default as isTextSelection } from './helpers/isTextSelection'
|
||||||
|
export { default as isCellSelection } from './helpers/isCellSelection'
|
||||||
|
export { default as findParentNodeClosestToPos } from './helpers/findParentNodeClosestToPos'
|
||||||
|
|
||||||
export interface AllExtensions {}
|
export interface AllExtensions {}
|
||||||
|
|||||||
14
packages/extension-table-cell/README.md
Normal file
14
packages/extension-table-cell/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# @tiptap/extension-table-cell
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/extension-table-cell)
|
||||||
|
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/extension-table-cell)
|
||||||
|
[](https://github.com/sponsors/ueberdosis)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
||||||
|
|
||||||
|
## Offical Documentation
|
||||||
|
Documentation can be found on the [tiptap website](https://tiptap.dev).
|
||||||
|
|
||||||
|
## License
|
||||||
|
tiptap is open-sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap-next/blob/main/LICENSE.md).
|
||||||
27
packages/extension-table-cell/package.json
Normal file
27
packages/extension-table-cell/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@tiptap/extension-table-cell",
|
||||||
|
"description": "table cell extension for tiptap",
|
||||||
|
"version": "2.0.0-alpha.5",
|
||||||
|
"homepage": "https://tiptap.dev",
|
||||||
|
"keywords": [
|
||||||
|
"tiptap",
|
||||||
|
"tiptap extension"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"main": "dist/tiptap-extension-table-cell.cjs.js",
|
||||||
|
"umd": "dist/tiptap-extension-table-cell.umd.js",
|
||||||
|
"module": "dist/tiptap-extension-table-cell.esm.js",
|
||||||
|
"unpkg": "dist/tiptap-extension-table-cell.bundle.umd.min.js",
|
||||||
|
"types": "dist/packages/extension-table-cell/src/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/core": "^2.0.0-alpha.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/extension-table-cell/src/index.ts
Normal file
5
packages/extension-table-cell/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { TableCell } from './table-cell'
|
||||||
|
|
||||||
|
export * from './table-cell'
|
||||||
|
|
||||||
|
export default TableCell
|
||||||
51
packages/extension-table-cell/src/table-cell.ts
Normal file
51
packages/extension-table-cell/src/table-cell.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Node, mergeAttributes } from '@tiptap/core'
|
||||||
|
|
||||||
|
export interface TableCellOptions {
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export const TableCell = Node.create({
|
||||||
|
name: 'tableCell',
|
||||||
|
|
||||||
|
defaultOptions: <TableCellOptions>{
|
||||||
|
HTMLAttributes: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
content: 'block+',
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
colspan: {
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
rowspan: {
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
colwidth: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tableRole: 'cell',
|
||||||
|
|
||||||
|
isolating: true,
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{ tag: 'td' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return ['td', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface AllExtensions {
|
||||||
|
TableCell: typeof TableCell,
|
||||||
|
}
|
||||||
|
}
|
||||||
14
packages/extension-table-header/README.md
Normal file
14
packages/extension-table-header/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# @tiptap/extension-table-header
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/extension-table-header)
|
||||||
|
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/extension-table-header)
|
||||||
|
[](https://github.com/sponsors/ueberdosis)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
||||||
|
|
||||||
|
## Offical Documentation
|
||||||
|
Documentation can be found on the [tiptap website](https://tiptap.dev).
|
||||||
|
|
||||||
|
## License
|
||||||
|
tiptap is open-sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap-next/blob/main/LICENSE.md).
|
||||||
27
packages/extension-table-header/package.json
Normal file
27
packages/extension-table-header/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@tiptap/extension-table-header",
|
||||||
|
"description": "table cell extension for tiptap",
|
||||||
|
"version": "2.0.0-alpha.5",
|
||||||
|
"homepage": "https://tiptap.dev",
|
||||||
|
"keywords": [
|
||||||
|
"tiptap",
|
||||||
|
"tiptap extension"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"main": "dist/tiptap-extension-table-header.cjs.js",
|
||||||
|
"umd": "dist/tiptap-extension-table-header.umd.js",
|
||||||
|
"module": "dist/tiptap-extension-table-header.esm.js",
|
||||||
|
"unpkg": "dist/tiptap-extension-table-header.bundle.umd.min.js",
|
||||||
|
"types": "dist/packages/extension-table-header/src/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/core": "^2.0.0-alpha.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/extension-table-header/src/index.ts
Normal file
5
packages/extension-table-header/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { TableHeader } from './table-header'
|
||||||
|
|
||||||
|
export * from './table-header'
|
||||||
|
|
||||||
|
export default TableHeader
|
||||||
51
packages/extension-table-header/src/table-header.ts
Normal file
51
packages/extension-table-header/src/table-header.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Node, mergeAttributes } from '@tiptap/core'
|
||||||
|
|
||||||
|
export interface TableHeaderOptions {
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export const TableHeader = Node.create({
|
||||||
|
name: 'tableHeader',
|
||||||
|
|
||||||
|
defaultOptions: <TableHeaderOptions>{
|
||||||
|
HTMLAttributes: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
content: 'block+',
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
colspan: {
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
rowspan: {
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
colwidth: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tableRole: 'header_cell',
|
||||||
|
|
||||||
|
isolating: true,
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{ tag: 'th' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return ['th', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface AllExtensions {
|
||||||
|
TableHeader: typeof TableHeader,
|
||||||
|
}
|
||||||
|
}
|
||||||
14
packages/extension-table-row/README.md
Normal file
14
packages/extension-table-row/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# @tiptap/extension-table-row
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/extension-table-row)
|
||||||
|
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/extension-table-row)
|
||||||
|
[](https://github.com/sponsors/ueberdosis)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
||||||
|
|
||||||
|
## Offical Documentation
|
||||||
|
Documentation can be found on the [tiptap website](https://tiptap.dev).
|
||||||
|
|
||||||
|
## License
|
||||||
|
tiptap is open-sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap-next/blob/main/LICENSE.md).
|
||||||
27
packages/extension-table-row/package.json
Normal file
27
packages/extension-table-row/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@tiptap/extension-table-row",
|
||||||
|
"description": "table row extension for tiptap",
|
||||||
|
"version": "2.0.0-alpha.5",
|
||||||
|
"homepage": "https://tiptap.dev",
|
||||||
|
"keywords": [
|
||||||
|
"tiptap",
|
||||||
|
"tiptap extension"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"main": "dist/tiptap-extension-table-row.cjs.js",
|
||||||
|
"umd": "dist/tiptap-extension-table-row.umd.js",
|
||||||
|
"module": "dist/tiptap-extension-table-row.esm.js",
|
||||||
|
"unpkg": "dist/tiptap-extension-table-row.bundle.umd.min.js",
|
||||||
|
"types": "dist/packages/extension-table-row/src/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/core": "^2.0.0-alpha.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/extension-table-row/src/index.ts
Normal file
5
packages/extension-table-row/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { TableRow } from './table-row'
|
||||||
|
|
||||||
|
export * from './table-row'
|
||||||
|
|
||||||
|
export default TableRow
|
||||||
35
packages/extension-table-row/src/table-row.ts
Normal file
35
packages/extension-table-row/src/table-row.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Node, mergeAttributes } from '@tiptap/core'
|
||||||
|
|
||||||
|
export interface TableRowOptions {
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TableRow = Node.create({
|
||||||
|
name: 'tableRow',
|
||||||
|
|
||||||
|
defaultOptions: <TableRowOptions>{
|
||||||
|
HTMLAttributes: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
content: '(tableCell | tableHeader)*',
|
||||||
|
|
||||||
|
tableRole: 'row',
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{ tag: 'tr' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return ['tr', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface AllExtensions {
|
||||||
|
TableRow: typeof TableRow,
|
||||||
|
}
|
||||||
|
}
|
||||||
14
packages/extension-table/README.md
Normal file
14
packages/extension-table/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# @tiptap/extension-table
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/extension-table)
|
||||||
|
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/extension-table)
|
||||||
|
[](https://github.com/sponsors/ueberdosis)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
||||||
|
|
||||||
|
## Offical Documentation
|
||||||
|
Documentation can be found on the [tiptap website](https://tiptap.dev).
|
||||||
|
|
||||||
|
## License
|
||||||
|
tiptap is open-sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap-next/blob/main/LICENSE.md).
|
||||||
31
packages/extension-table/package.json
Normal file
31
packages/extension-table/package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "@tiptap/extension-table",
|
||||||
|
"description": "table extension for tiptap",
|
||||||
|
"version": "2.0.0-alpha.5",
|
||||||
|
"homepage": "https://tiptap.dev",
|
||||||
|
"keywords": [
|
||||||
|
"tiptap",
|
||||||
|
"tiptap extension"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"main": "dist/tiptap-extension-table.cjs.js",
|
||||||
|
"umd": "dist/tiptap-extension-table.umd.js",
|
||||||
|
"module": "dist/tiptap-extension-table.esm.js",
|
||||||
|
"unpkg": "dist/tiptap-extension-table.bundle.umd.min.js",
|
||||||
|
"types": "dist/packages/extension-table/src/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/core": "^2.0.0-alpha.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-tables": "^1.1.1",
|
||||||
|
"prosemirror-view": "^1.16.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
89
packages/extension-table/src/TableView.ts
Normal file
89
packages/extension-table/src/TableView.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { NodeView } from 'prosemirror-view'
|
||||||
|
import { Node as ProseMirrorNode } from 'prosemirror-model'
|
||||||
|
|
||||||
|
export function updateColumns(node: ProseMirrorNode, colgroup: Element, table: Element, cellMinWidth: number, overrideCol?: number, overrideValue?: any) {
|
||||||
|
let totalWidth = 0
|
||||||
|
let fixedWidth = true
|
||||||
|
let nextDOM = colgroup.firstChild
|
||||||
|
const row = node.firstChild
|
||||||
|
|
||||||
|
for (let i = 0, col = 0; i < row.childCount; i += 1) {
|
||||||
|
const { colspan, colwidth } = row.child(i).attrs
|
||||||
|
|
||||||
|
for (let j = 0; j < colspan; j += 1, col += 1) {
|
||||||
|
const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j]
|
||||||
|
const cssWidth = hasWidth ? `${hasWidth}px` : ''
|
||||||
|
totalWidth += hasWidth || cellMinWidth
|
||||||
|
|
||||||
|
if (!hasWidth) {
|
||||||
|
fixedWidth = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextDOM) {
|
||||||
|
colgroup.appendChild(document.createElement('col')).style.width = cssWidth
|
||||||
|
} else {
|
||||||
|
if (nextDOM.style.width !== cssWidth) {
|
||||||
|
nextDOM.style.width = cssWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
nextDOM = nextDOM.nextSibling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (nextDOM) {
|
||||||
|
const after = nextDOM.nextSibling
|
||||||
|
nextDOM.parentNode.removeChild(nextDOM)
|
||||||
|
nextDOM = after
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fixedWidth) {
|
||||||
|
table.style.width = `${totalWidth}px`
|
||||||
|
table.style.minWidth = ''
|
||||||
|
} else {
|
||||||
|
table.style.width = ''
|
||||||
|
table.style.minWidth = `${totalWidth}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TableView implements NodeView {
|
||||||
|
|
||||||
|
node: ProseMirrorNode
|
||||||
|
|
||||||
|
cellMinWidth: number
|
||||||
|
|
||||||
|
dom: Element
|
||||||
|
|
||||||
|
table: Element
|
||||||
|
|
||||||
|
colgroup: Element
|
||||||
|
|
||||||
|
contentDOM: Element
|
||||||
|
|
||||||
|
constructor(node: ProseMirrorNode, cellMinWidth: number) {
|
||||||
|
this.node = node
|
||||||
|
this.cellMinWidth = cellMinWidth
|
||||||
|
this.dom = document.createElement('div')
|
||||||
|
this.dom.className = 'tableWrapper'
|
||||||
|
this.table = this.dom.appendChild(document.createElement('table'))
|
||||||
|
this.colgroup = this.table.appendChild(document.createElement('colgroup'))
|
||||||
|
updateColumns(node, this.colgroup, this.table, cellMinWidth)
|
||||||
|
this.contentDOM = this.table.appendChild(document.createElement('tbody'))
|
||||||
|
}
|
||||||
|
|
||||||
|
update(node: ProseMirrorNode) {
|
||||||
|
if (node.type !== this.node.type) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.node = node
|
||||||
|
updateColumns(node, this.colgroup, this.table, this.cellMinWidth)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
|
||||||
|
return mutation.type === 'attributes' && (mutation.target === this.table || this.colgroup.contains(mutation.target))
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/extension-table/src/index.ts
Normal file
5
packages/extension-table/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { Table } from './table'
|
||||||
|
|
||||||
|
export * from './table'
|
||||||
|
|
||||||
|
export default Table
|
||||||
230
packages/extension-table/src/table.ts
Normal file
230
packages/extension-table/src/table.ts
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import {
|
||||||
|
Command,
|
||||||
|
Node,
|
||||||
|
mergeAttributes,
|
||||||
|
isCellSelection,
|
||||||
|
findParentNodeClosestToPos,
|
||||||
|
} from '@tiptap/core'
|
||||||
|
import {
|
||||||
|
tableEditing,
|
||||||
|
columnResizing,
|
||||||
|
goToNextCell,
|
||||||
|
addColumnBefore,
|
||||||
|
addColumnAfter,
|
||||||
|
deleteColumn,
|
||||||
|
addRowBefore,
|
||||||
|
addRowAfter,
|
||||||
|
deleteRow,
|
||||||
|
deleteTable,
|
||||||
|
mergeCells,
|
||||||
|
splitCell,
|
||||||
|
toggleHeaderColumn,
|
||||||
|
toggleHeaderRow,
|
||||||
|
toggleHeaderCell,
|
||||||
|
setCellAttr,
|
||||||
|
fixTables,
|
||||||
|
} from 'prosemirror-tables'
|
||||||
|
import { NodeView } from 'prosemirror-view'
|
||||||
|
import { TextSelection } from 'prosemirror-state'
|
||||||
|
import { createTable } from './utilities/createTable'
|
||||||
|
import { TableView } from './TableView'
|
||||||
|
|
||||||
|
export interface TableOptions {
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
},
|
||||||
|
resizable: boolean,
|
||||||
|
handleWidth: number,
|
||||||
|
cellMinWidth: number,
|
||||||
|
View: NodeView,
|
||||||
|
lastColumnResizable: boolean,
|
||||||
|
allowTableNodeSelection: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Table = Node.create({
|
||||||
|
name: 'table',
|
||||||
|
|
||||||
|
defaultOptions: <TableOptions>{
|
||||||
|
HTMLAttributes: {},
|
||||||
|
resizable: false,
|
||||||
|
handleWidth: 5,
|
||||||
|
cellMinWidth: 25,
|
||||||
|
View: TableView,
|
||||||
|
lastColumnResizable: true,
|
||||||
|
allowTableNodeSelection: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
content: 'tableRow+',
|
||||||
|
|
||||||
|
tableRole: 'table',
|
||||||
|
|
||||||
|
isolating: true,
|
||||||
|
|
||||||
|
group: 'block',
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{ tag: 'table' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return ['table', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), ['tbody', 0]]
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
insertTable: ({ rows = 3, cols = 3, withHeaderRow = true }): Command => ({ tr, dispatch, editor }) => {
|
||||||
|
const offset = tr.selection.anchor + 1
|
||||||
|
const node = createTable(editor.schema, rows, cols, withHeaderRow)
|
||||||
|
|
||||||
|
if (dispatch) {
|
||||||
|
tr.replaceSelectionWith(node)
|
||||||
|
.scrollIntoView()
|
||||||
|
.setSelection(TextSelection.near(tr.doc.resolve(offset)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
addColumnBefore: (): Command => ({ state, dispatch }) => {
|
||||||
|
return addColumnBefore(state, dispatch)
|
||||||
|
},
|
||||||
|
addColumnAfter: (): Command => ({ state, dispatch }) => {
|
||||||
|
return addColumnAfter(state, dispatch)
|
||||||
|
},
|
||||||
|
deleteColumn: (): Command => ({ state, dispatch }) => {
|
||||||
|
return deleteColumn(state, dispatch)
|
||||||
|
},
|
||||||
|
addRowBefore: (): Command => ({ state, dispatch }) => {
|
||||||
|
return addRowBefore(state, dispatch)
|
||||||
|
},
|
||||||
|
addRowAfter: (): Command => ({ state, dispatch }) => {
|
||||||
|
return addRowAfter(state, dispatch)
|
||||||
|
},
|
||||||
|
deleteRow: (): Command => ({ state, dispatch }) => {
|
||||||
|
return deleteRow(state, dispatch)
|
||||||
|
},
|
||||||
|
deleteTable: (): Command => ({ state, dispatch }) => {
|
||||||
|
return deleteTable(state, dispatch)
|
||||||
|
},
|
||||||
|
mergeCells: (): Command => ({ state, dispatch }) => {
|
||||||
|
return mergeCells(state, dispatch)
|
||||||
|
},
|
||||||
|
splitCell: (): Command => ({ state, dispatch }) => {
|
||||||
|
return splitCell(state, dispatch)
|
||||||
|
},
|
||||||
|
toggleHeaderColumn: (): Command => ({ state, dispatch }) => {
|
||||||
|
return toggleHeaderColumn(state, dispatch)
|
||||||
|
},
|
||||||
|
toggleHeaderRow: (): Command => ({ state, dispatch }) => {
|
||||||
|
return toggleHeaderRow(state, dispatch)
|
||||||
|
},
|
||||||
|
toggleHeaderCell: (): Command => ({ state, dispatch }) => {
|
||||||
|
return toggleHeaderCell(state, dispatch)
|
||||||
|
},
|
||||||
|
mergeOrSplit: (): Command => ({ state, dispatch }) => {
|
||||||
|
if (mergeCells(state, dispatch)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return splitCell(state, dispatch)
|
||||||
|
},
|
||||||
|
setCellAttributes: ({ name, value }: { name: string, value: any }): Command => ({ state, dispatch }) => {
|
||||||
|
return setCellAttr(name, value)(state, dispatch)
|
||||||
|
},
|
||||||
|
goToNextCell: (): Command => ({ state, dispatch }) => {
|
||||||
|
return goToNextCell(1)(state, dispatch)
|
||||||
|
},
|
||||||
|
goToPreviousCell: (): Command => ({ state, dispatch }) => {
|
||||||
|
return goToNextCell(-1)(state, dispatch)
|
||||||
|
},
|
||||||
|
fixTables: (): Command => ({ state, dispatch }) => {
|
||||||
|
if (dispatch) {
|
||||||
|
fixTables(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addKeyboardShortcuts() {
|
||||||
|
const deleteTableWhenAllCellsSelected = () => {
|
||||||
|
const { selection } = this.editor.state
|
||||||
|
|
||||||
|
if (!isCellSelection(selection)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let cellCount = 0
|
||||||
|
const table = findParentNodeClosestToPos(selection.ranges[0].$from, node => {
|
||||||
|
return node.type.name === 'table'
|
||||||
|
})
|
||||||
|
|
||||||
|
table?.node.descendants(node => {
|
||||||
|
if (node.type.name === 'table') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['tableCell', 'tableHeader'].includes(node.type.name)) {
|
||||||
|
cellCount += 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const allCellsSelected = cellCount === selection.ranges.length
|
||||||
|
|
||||||
|
if (!allCellsSelected) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editor.commands.deleteTable()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
Tab: () => {
|
||||||
|
if (this.editor.commands.goToNextCell()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.editor.can().addRowAfter()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.editor
|
||||||
|
.chain()
|
||||||
|
.addRowAfter()
|
||||||
|
.goToNextCell()
|
||||||
|
.run()
|
||||||
|
},
|
||||||
|
'Shift-Tab': () => this.editor.commands.goToPreviousCell(),
|
||||||
|
Backspace: deleteTableWhenAllCellsSelected,
|
||||||
|
'Mod-Backspace': deleteTableWhenAllCellsSelected,
|
||||||
|
Delete: deleteTableWhenAllCellsSelected,
|
||||||
|
'Mod-Delete': deleteTableWhenAllCellsSelected,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [
|
||||||
|
...(this.options.resizable ? [columnResizing({
|
||||||
|
handleWidth: this.options.handleWidth,
|
||||||
|
cellMinWidth: this.options.cellMinWidth,
|
||||||
|
View: this.options.View,
|
||||||
|
// TODO: PR for @types/prosemirror-tables
|
||||||
|
// @ts-ignore (incorrect type)
|
||||||
|
lastColumnResizable: this.options.lastColumnResizable,
|
||||||
|
})] : []),
|
||||||
|
tableEditing({
|
||||||
|
allowTableNodeSelection: this.options.allowTableNodeSelection,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface AllExtensions {
|
||||||
|
Table: typeof Table,
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/extension-table/src/utilities/createCell.ts
Normal file
13
packages/extension-table/src/utilities/createCell.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import {
|
||||||
|
NodeType, Fragment,
|
||||||
|
Node as ProsemirrorNode,
|
||||||
|
Schema,
|
||||||
|
} from 'prosemirror-model'
|
||||||
|
|
||||||
|
export function createCell(cellType: NodeType, cellContent?: Fragment<Schema> | ProsemirrorNode<Schema> | Array<ProsemirrorNode<Schema>>): ProsemirrorNode | null | undefined {
|
||||||
|
if (cellContent) {
|
||||||
|
return cellType.createChecked(null, cellContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cellType.createAndFill()
|
||||||
|
}
|
||||||
33
packages/extension-table/src/utilities/createTable.ts
Normal file
33
packages/extension-table/src/utilities/createTable.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Schema, Fragment, Node as ProsemirrorNode } from 'prosemirror-model'
|
||||||
|
import { createCell } from './createCell'
|
||||||
|
import { getTableNodeTypes } from './getTableNodeTypes'
|
||||||
|
|
||||||
|
export function createTable(schema: Schema, rowsCount: number, colsCount: number, withHeaderRow: boolean, cellContent?: Fragment<Schema> | ProsemirrorNode<Schema> | Array<ProsemirrorNode<Schema>>): ProsemirrorNode {
|
||||||
|
const types = getTableNodeTypes(schema)
|
||||||
|
const headerCells = []
|
||||||
|
const cells = []
|
||||||
|
|
||||||
|
for (let index = 0; index < colsCount; index += 1) {
|
||||||
|
const cell = createCell(types.cell, cellContent)
|
||||||
|
|
||||||
|
if (cell) {
|
||||||
|
cells.push(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (withHeaderRow) {
|
||||||
|
const headerCell = createCell(types.header_cell, cellContent)
|
||||||
|
|
||||||
|
if (headerCell) {
|
||||||
|
headerCells.push(headerCell)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = []
|
||||||
|
|
||||||
|
for (let index = 0; index < rowsCount; index += 1) {
|
||||||
|
rows.push(types.row.createChecked(null, withHeaderRow && index === 0 ? headerCells : cells))
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.table.createChecked(null, rows)
|
||||||
|
}
|
||||||
21
packages/extension-table/src/utilities/getTableNodeTypes.ts
Normal file
21
packages/extension-table/src/utilities/getTableNodeTypes.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Schema, NodeType } from 'prosemirror-model'
|
||||||
|
|
||||||
|
export function getTableNodeTypes(schema: Schema): { [key: string]: NodeType } {
|
||||||
|
if (schema.cached.tableNodeTypes) {
|
||||||
|
return schema.cached.tableNodeTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles: { [key: string]: NodeType } = {}
|
||||||
|
|
||||||
|
Object.keys(schema.nodes).forEach(type => {
|
||||||
|
const nodeType = schema.nodes[type]
|
||||||
|
|
||||||
|
if (nodeType.spec.tableRole) {
|
||||||
|
roles[nodeType.spec.tableRole] = nodeType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.cached.tableNodeTypes = roles
|
||||||
|
|
||||||
|
return roles
|
||||||
|
}
|
||||||
@@ -2486,6 +2486,13 @@
|
|||||||
"@types/prosemirror-transform" "*"
|
"@types/prosemirror-transform" "*"
|
||||||
"@types/prosemirror-view" "*"
|
"@types/prosemirror-view" "*"
|
||||||
|
|
||||||
|
"@types/prosemirror-tables@^0.9.1":
|
||||||
|
version "0.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/prosemirror-tables/-/prosemirror-tables-0.9.1.tgz#d2203330f0fa1161c04152bf02c39e152082d408"
|
||||||
|
integrity sha512-zoY1qcAC6kG4UjnaQQXuoyYQdDJMQmY9uzRKdyUppP8rWRR5/kXBHOd84CD9ZvrYUBo3uDmS20qQnc3knr2j9A==
|
||||||
|
dependencies:
|
||||||
|
prosemirror-tables "*"
|
||||||
|
|
||||||
"@types/prosemirror-transform@*":
|
"@types/prosemirror-transform@*":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/prosemirror-transform/-/prosemirror-transform-1.1.1.tgz#5a0de16e8e0123b4c3d9559235e19f39cee85e5c"
|
resolved "https://registry.yarnpkg.com/@types/prosemirror-transform/-/prosemirror-transform-1.1.1.tgz#5a0de16e8e0123b4c3d9559235e19f39cee85e5c"
|
||||||
@@ -11842,7 +11849,7 @@ prosemirror-state@^1.3.4:
|
|||||||
prosemirror-model "^1.0.0"
|
prosemirror-model "^1.0.0"
|
||||||
prosemirror-transform "^1.0.0"
|
prosemirror-transform "^1.0.0"
|
||||||
|
|
||||||
prosemirror-tables@^1.1.1:
|
prosemirror-tables@*, prosemirror-tables@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.1.1.tgz#ad66300cc49500455cf1243bb129c9e7d883321e"
|
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.1.1.tgz#ad66300cc49500455cf1243bb129c9e7d883321e"
|
||||||
integrity sha512-LmCz4jrlqQZRsYRDzCRYf/pQ5CUcSOyqZlAj5kv67ZWBH1SVLP2U9WJEvQfimWgeRlIz0y0PQVqO1arRm1+woA==
|
integrity sha512-LmCz4jrlqQZRsYRDzCRYf/pQ5CUcSOyqZlAj5kv67ZWBH1SVLP2U9WJEvQfimWgeRlIz0y0PQVqO1arRm1+woA==
|
||||||
|
|||||||
Reference in New Issue
Block a user