Adding Table Support

This commit is contained in:
Chrissi2812
2018-12-06 18:07:08 +01:00
parent 19202f25f0
commit c830768b3b
18 changed files with 473 additions and 0 deletions

View File

@@ -79,6 +79,53 @@
border-radius: 3px; border-radius: 3px;
} }
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 0;
overflow: hidden;
td, th {
min-width: 1em;
border: 1px solid #ddd;
padding: 3px 5px;
vertical-align: top;
box-sizing: border-box;
position: relative;
p {
margin: 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: 0;
width: 4px;
z-index: 20;
background-color: #adf;
pointer-events: none;
}
}
.tableWrapper {
margin: 1em 0;
overflow-x: auto;
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
} }
} }

View File

@@ -0,0 +1,268 @@
<template>
<div class="editor">
<editor-menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<div class="toolbar">
<button
class="menubar__button"
:class="{ 'is-active': isActive.bold() }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.italic() }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.strike() }"
@click="commands.strike"
>
<icon name="strike" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.underline() }"
@click="commands.underline"
>
<icon name="underline" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.code() }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.paragraph() }"
@click="commands.paragraph"
>
<icon name="paragraph" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.heading({ level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.heading({ level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.heading({ level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.bullet_list() }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.ordered_list() }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.blockquote() }"
@click="commands.blockquote"
>
<icon name="quote" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive.code_block() }"
@click="commands.code_block"
>
<icon name="code" />
</button>
<button
class="menubar__button"
@click="commands.redo"
>
<icon name="redo" />
</button>
<button
class="menubar__button"
@click="commands.undo"
>
<icon name="undo" />
</button>
<button
class="menubar__button"
@click="commands.createTable({rowsCount: 3, colsCount: 3, withHeaderRow: false })"
>
<icon name="table" />
</button>
<span v-if="isActive.table()">
<button
class="menubar__button"
@click="commands.addColumnBefore"
>
<icon name="add_col_before" />
</button>
<button
class="menubar__button"
@click="commands.addColumnAfter"
>
<icon name="add_col_after" />
</button>
<button
class="menubar__button"
@click="commands.deleteColumn"
>
<icon name="delete_col" />
</button>
<button
class="menubar__button"
@click="commands.addRowBefore"
>
<icon name="add_row_before" />
</button>
<button
class="menubar__button"
@click="commands.addRowAfter"
>
<icon name="add_row_after" />
</button>
<button
class="menubar__button"
@click="commands.deleteRow"
>
<icon name="delete_row" />
</button>
<button
class="menubar__button"
@click="commands.mergeCells"
>
<icon name="combine_cells" />
</button>
</span>
</div>
</div>
</editor-menu-bar>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, EditorMenuBar } from 'tiptap'
import {
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Table,
TableHeader,
TableCell,
TableRow,
Strike,
Underline,
History,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
EditorMenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
new Table(),
new TableHeader(),
new TableCell(),
new TableRow(),
],
content: `
<h2>
Example content
</h2>
<p>The table:</p>
<table>
<tr>
<th colspan=3 data-colwidth="100,0,0">Wide header</th>
</tr>
<tr>
<td>One</td>
<td>Two</td>
<td>Three</td>
</tr>
<tr>
<td>Four</td>
<td>Five</td>
<td>Six</td>
</tr>
</table>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View File

@@ -21,6 +21,9 @@
<router-link class="subnavigation__link" to="/todo-list"> <router-link class="subnavigation__link" to="/todo-list">
Todo List Todo List
</router-link> </router-link>
<router-link class="subnavigation__link" to="/table">
Table
</router-link>
<router-link class="subnavigation__link" to="/suggestions"> <router-link class="subnavigation__link" to="/suggestions">
Suggestions Suggestions
</router-link> </router-link>

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 25 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path fill="none" d="M.534 0h24v24h-24z"/><clipPath id="a"><path d="M.534 0h24v24h-24z"/></clipPath><g clip-path="url(#a)"><path d="M2.534 2h20v3.714h-20z"/><path d="M22.534 22h-20V2h20v20zm-19.131-.869h18.262V2.869H3.403v18.262z"/><path d="M16.485 22H8.522V3.314h7.963V22zM9.391 4.183v16.948h6.225V4.183H9.391z"/><path d="M22.534 16.427h-20v-6.005h20v6.005zM3.403 11.291v4.267h18.262v-4.267H3.403z"/><path d="M16.485 0v24h5.248V0h-5.248z" fill="#4caf50"/></g></svg>

After

Width:  |  Height:  |  Size: 616 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 25 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path fill="none" d="M.653 0h24v24h-24z"/><path d="M2.653 2h20v3.714h-20z"/><path d="M22.653 22h-20V2h20v20zm-19.13-.869h18.261V2.869H3.523v18.262z"/><path d="M16.605 22H8.641V3.314h7.964V22zM9.51 4.183v16.948h6.226V4.183H9.51z"/><path d="M22.653 16.427h-20v-6.005h20v6.005zm-19.13-5.136v4.267h18.261v-4.267H3.523z"/><path d="M3.483 0v24h5.158V0H3.483z" fill="#4caf50"/></svg>

After

Width:  |  Height:  |  Size: 526 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 25 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path fill="none" d="M.77 0h24v24h-24z"/><clipPath id="a"><path d="M.77 0h24v24h-24z"/></clipPath><g clip-path="url(#a)"><path d="M2.77 2h20v3.714h-20z"/><path d="M22.77 22h-20V2h20v20zM3.64 2.869v18.262h18.261V2.869H3.64z"/><path d="M16.722 22H8.758V3.314h7.964V22zM9.627 4.183v16.948h6.226V4.183H9.627z"/><path d="M22.77 16.427h-20v-6.005h20v6.005zM3.64 11.291v4.267h18.261v-4.267H3.64z"/><path fill="#4caf50" d="M.874 16.427h24v4.783h-24z"/></g></svg>

After

Width:  |  Height:  |  Size: 604 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 25 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path fill="none" d="M.77 0h24v24h-24z"/><path d="M2.77 2h20v3.714h-20z"/><path d="M22.77 22h-20V2h20v20zm-19.13-.869h18.261V2.869H3.64v18.262z"/><path d="M16.722 22H8.758V3.314h7.964V22zM9.627 4.183v16.948h6.226V4.183H9.627z"/><path d="M22.77 16.427h-20v-6.005h20v6.005zM3.64 11.291v4.267h18.261v-4.267H3.64z"/><path fill="#4caf50" d="M.77 5.639h24v4.783h-24z"/></svg>

After

Width:  |  Height:  |  Size: 519 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 24 25" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path fill="none" d="M0 .108h24v24H0z"/><path d="M2 2.108h20v3.714H2z"/><path d="M22 22.108H2v-20h20v20zM2.869 2.977v18.262h18.262V2.977H2.869z"/><path d="M15.951 22.108H7.988V3.421h7.963v18.687zM8.857 4.29v16.949h6.225V4.29H8.857z"/><path d="M22 16.534H2V10.53h20v6.004zM2.869 11.399v4.266h18.262v-4.266H2.869z"/><path fill="#0277bd" d="M2.826 11.323h18.272v4.386H2.826z"/></svg>

After

Width:  |  Height:  |  Size: 530 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 25 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path fill="none" d="M.066 0h24v24h-24z"/><path d="M2.066 2h20v3.714h-20z"/><path d="M22.066 22h-20V2h20v20zm-19.131-.869h18.262V2.869H2.935v18.262z"/><path d="M16.017 22H8.054V3.314h7.963V22zM8.923 4.183v16.948h6.225V4.183H8.923z"/><path d="M22.066 16.427h-20v-6.005h20v6.005zM2.935 11.291v4.267h18.262v-4.267H2.935z"/><path d="M8.954 0v24h6.224V0H8.954z" fill="#c62828"/></svg>

After

Width:  |  Height:  |  Size: 529 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 25 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path fill="none" d="M.789 0h24v24h-24z"/><path d="M2.789 2h20v3.714h-20z"/><path d="M22.789 22h-20V2h20v20zm-19.131-.869H21.92V2.869H3.658v18.262z"/><path d="M16.74 22H8.777V3.314h7.963V22zM9.646 4.183v16.948h6.225V4.183H9.646z"/><path d="M22.789 16.427h-20v-6.005h20v6.005zM3.658 11.291v4.267H21.92v-4.267H3.658z"/><path fill="#c62828" d="M.789 11.114h24v4.547h-24z"/></svg>

After

Width:  |  Height:  |  Size: 526 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path fill="none" d="M0 0h24v24H0z"/><path d="M2 2h20v3.714H2z"/><path d="M22 22H2V2h20v20zm-19.131-.869h18.262V2.869H2.869v18.262z"/><path d="M15.951 22H7.988V3.314h7.963V22zM8.857 4.183v16.948h6.225V4.183H8.857z"/><path d="M22 16.427H2v-6.005h20v6.005zM2.869 11.291v4.267h18.262v-4.267H2.869z"/></svg>

After

Width:  |  Height:  |  Size: 453 B

View File

@@ -54,6 +54,13 @@ const routes = [
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/HidingMenuBar', githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/HidingMenuBar',
}, },
}, },
{
path: '/table',
component: () => import('Components/Routes/Table'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Table',
},
},
{ {
path: '/todo-list', path: '/todo-list',
component: () => import('Components/Routes/TodoList'), component: () => import('Components/Routes/TodoList'),

View File

@@ -8,6 +8,10 @@ export { default as Image } from './nodes/Image'
export { default as ListItem } from './nodes/ListItem' export { default as ListItem } from './nodes/ListItem'
export { default as Mention } from './nodes/Mention' export { default as Mention } from './nodes/Mention'
export { default as OrderedList } from './nodes/OrderedList' export { default as OrderedList } from './nodes/OrderedList'
export { default as Table } from './nodes/Table'
export { default as TableHeader } from './nodes/TableHeader'
export { default as TableCell } from './nodes/TableCell'
export { default as TableRow } from './nodes/TableRow'
export { default as TodoItem } from './nodes/TodoItem' export { default as TodoItem } from './nodes/TodoItem'
export { default as TodoList } from './nodes/TodoList' export { default as TodoList } from './nodes/TodoList'

View File

@@ -0,0 +1,74 @@
import { Node } from 'tiptap'
import {
tableEditing,
columnResizing,
goToNextCell,
addColumnBefore,
addColumnAfter,
deleteColumn,
addRowBefore,
addRowAfter,
deleteRow,
deleteTable,
mergeCells,
splitCell,
toggleHeaderColumn,
toggleHeaderRow,
toggleHeaderCell,
setCellAttr,
fixTables,
} from 'prosemirror-tables'
import { createTable } from 'prosemirror-utils'
import TableNodes from './TableNodes'
export default class Table extends Node {
get name() {
return 'table'
}
get schema() {
return TableNodes.table
}
commands({ schema }) {
return {
createTable: ({ rowsCount, colsCount, withHeaderRow }) => (
(state, dispatch) => {
const nodes = createTable(schema, rowsCount, colsCount, withHeaderRow)
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView()
dispatch(tr)
}
),
addColumnBefore: () => addColumnBefore,
addColumnAfter: () => addColumnAfter,
deleteColumn: () => deleteColumn,
addRowBefore: () => addRowBefore,
addRowAfter: () => addRowAfter,
deleteRow: () => deleteRow,
deleteTable: () => deleteTable,
mergeCells: () => mergeCells,
splitCell: () => splitCell,
toggleHeaderColumn: () => toggleHeaderColumn,
toggleHeaderRow: () => toggleHeaderRow,
toggleHeaderCell: () => toggleHeaderCell,
setCellAttr: () => setCellAttr,
fixTables: () => fixTables,
}
}
keys() {
return {
Tab: goToNextCell(1),
'Shift-Tab': goToNextCell(-1),
}
}
get plugins() {
return [
columnResizing(),
tableEditing(),
]
}
}

View File

@@ -0,0 +1,14 @@
import { Node } from 'tiptap'
import TableNodes from './TableNodes'
export default class TableCell extends Node {
get name() {
return 'table_cell'
}
get schema() {
return TableNodes.table_cell
}
}

View File

@@ -0,0 +1,14 @@
import { Node } from 'tiptap'
import TableNodes from './TableNodes'
export default class TableHeader extends Node {
get name() {
return 'table_header'
}
get schema() {
return TableNodes.table_header
}
}

View File

@@ -0,0 +1,20 @@
import { tableNodes } from 'prosemirror-tables'
export default tableNodes({
tableGroup: 'block',
cellContent: 'block+',
cellAttributes: {
background: {
default: null,
getFromDOM(dom) {
return dom.style.backgroundColor || null
},
setDOMAttr(value, attrs) {
if (value) {
const style = { style: `${(attrs.style || '')}background-color: ${value};` }
Object.assign(attrs, style)
}
},
},
},
})

View File

@@ -0,0 +1,14 @@
import { Node } from 'tiptap'
import TableNodes from './TableNodes'
export default class TableRow extends Node {
get name() {
return 'table_row'
}
get schema() {
return TableNodes.table_row
}
}