3
0

Add TipTap table extensions for improved rich text editing

This commit is contained in:
Emmy Leke 2024-09-20 12:16:57 +01:00 committed by Jože Fortun
parent a99d90b105
commit 69494a9834
9 changed files with 208 additions and 7 deletions

View File

@ -65,6 +65,7 @@ import {
faEllipsisV,
faLocationArrow,
faTools,
faTable,
} from '@fortawesome/free-solid-svg-icons'
import {
@ -178,4 +179,5 @@ library.add(
faLocationArrow,
faTools,
faExclamationCircle,
faTable,
)

View File

@ -43,3 +43,70 @@
padding-bottom: 0px !important;
}
}
/* Basic editor styles */
.editor__content :first-child {
margin-top: 0;
}
/* Table-specific styling */
.editor__content table {
border-collapse: collapse;
margin: 0;
overflow: hidden;
table-layout: fixed;
width: 100%;
}
.editor__content td,
.editor__content th {
border: 1px solid var(--gray);
box-sizing: border-box;
min-width: 1em;
padding: 6px 8px;
position: relative;
vertical-align: top;
}
.editor__content td > *,
.editor__content th > * {
margin-bottom: 0;
}
.editor__content th {
background-color: var(--gray-dark);
font-weight: bold;
text-align: left;
}
.editor__content .selectedCell::after {
background: var(--gray-dark);
content: "";
left: 0;
right: 0;
top: 0;
bottom: 0;
pointer-events: none;
position: absolute;
z-index: 2;
}
.editor__content .column-resize-handle {
background-color: var(--purple);
bottom: -2px;
pointer-events: none;
position: absolute;
right: -2px;
top: 0;
width: 4px;
}
.editor__content .tableWrapper {
margin: 1.5rem 0;
overflow-x: auto;
}
.editor__content .resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}

View File

@ -75,6 +75,7 @@ export default {
}
},
)
return rtr
},

View File

@ -1,8 +1,8 @@
<template>
<b-dropdown
menu-class="text-center"
variant="link">
variant="link"
>
<template slot="button-content">
<span class="text-dark font-weight-bold">
<span :class="rootActiveClasses()">
@ -28,7 +28,8 @@
<span :class="activeClasses(v.attrs)">
<font-awesome-icon
v-if="format.icon"
:icon="v.icon" />
:icon="v.icon"
/>
<span v-else>
{{ v.label }}
</span>

View File

@ -0,0 +1,94 @@
<template>
<b-dropdown
menu-class="text-center"
variant="link">
<template slot="button-content">
<span class="text-dark font-weight-bold">
<span :class="rootActiveClasses()">
<font-awesome-icon
v-if="format.icon"
:icon="format.icon" />
<span v-else>
{{ format.label }}
</span>
</span>
</span>
</template>
<b-dropdown-item
v-for="v of format.variants"
:key="v.variant"
@click="emitClick(v)"
>
{{ v.label }}
</b-dropdown-item>
</b-dropdown>
</template>
<script>
import base from '../TNode/base.vue'
import { nodeTypes } from '../../lib/formats'
/**
* Component is used to display node alignment formatting
*/
export default {
name: 't-nattr-table',
extends: base,
props: {
isActive: {
type: Object,
required: false,
default: () => ({}),
},
},
methods: {
activeClasses (attrs) {
const an = this.activeNode(nodeTypes, attrs)
if (!an || !an.node) {
return undefined
}
const ac = (type, attrs) => {
const b = (this.isActive[type])
return b && (b(attrs))
}
if (ac(an.node.type.name, { ...an.node.attrs, ...attrs })) {
return ['text-success']
}
return undefined
},
/**
* dispatches node attr update for all affected nodes
* use a single transaction, so ctrl + z works as intended
*/
dispatchTransaction (v) {
const ann = this.activeNodes(nodeTypes)
const tr = this.$attrs.editor.state.tr
for (const an of ann) {
tr.setNodeMarkup(an.position, an.node.type, { ...an.node.attrs, ...v.attrs })
}
this.$attrs.editor.dispatchTransaction(tr)
},
emitClick (v) {
this.$emit('click', { type: v.type, attrs: { ...v.attrs } })
},
/**
* Helper method to determine if the root formater should be shown as active
* @returns {Array|undefined}
*/
rootActiveClasses (v) {
if (this.format.variants.find(({ type, attrs }) => this.activeClasses(attrs))) {
return ['text-success']
}
},
},
}
</script>

View File

@ -1,5 +1,6 @@
import Alignment from './Alignment.vue'
import Table from './Table.vue'
export default {
Alignment,
Table,
}

View File

@ -2,13 +2,13 @@
<div class="d-flex flex-wrap">
<component
v-for="(f, i) of formats"
:key="`${f.name}${i}}`"
:key="`${f.name}${i}`"
:is="getItem(f)"
:format="f"
v-bind="$props"
:labels="labels"
:current-value="currentValue"
@click="(commands[$event.type])($event.attrs)" />
@click="triggerCommand" />
<!-- Extra button to remove formatting -->
<b-button
@ -103,6 +103,10 @@ export default {
removeMarks () {
removeMark(null)(this.editor.view.state, this.editor.view.dispatch)
},
triggerCommand (v) {
this.commands[v.type](v.attrs)
},
},
}
</script>

View File

@ -51,6 +51,7 @@ export default {
required: false,
default: null,
},
labels: {
type: Object,
default: () => ({})
@ -62,7 +63,7 @@ export default {
return {
formats,
toolbar: getToolbar(),
// Helper to determine if current content differes from prop's content
// Helper to determine if current content differs from prop's content
emittedContent: false,
editor: undefined,
currentValue: '',

View File

@ -23,6 +23,10 @@ import {
ListItem,
TodoItem,
History,
Table,
TableRow,
TableHeader,
TableCell,
} from 'tiptap-extensions'
// Defines a set of formats that our document supports
@ -46,6 +50,12 @@ export const getFormats = () => [
new History(),
new TextBackground(),
new TextColor(),
new Table({
resizable: true,
}),
new TableHeader(),
new TableCell(),
new TableRow(),
]
// Defines the structure of our editor toolbar
@ -80,6 +90,26 @@ export const getToolbar = () => [
],
},
{
type: 'table',
icon: 'table',
nodeAttr: true,
component: 'Table',
variants: [
{ variant: 'insert', icon: '', label: 'Insert Table', type: 'createTable', attrs: {rowsCount: 3, colsCount: 3, withHeaderRow: true } },
{ variant: 'splitCell', icon: '', label: 'Split Cell', type: 'splitCell', },
{ variant: 'mergeCells', icon: '', label: 'Merge Cells', type: 'mergeCells', },
{ variant: 'deleteColumn', icon: '', label: 'Delete Column', type: 'deleteColumn', },
{ variant: 'insertRowBefore', icon: '', label: 'Add Row Before', type: 'addRowBefore', },
{ variant: 'insertColumnAfter', icon: '', label: 'Insert Column After', type: 'addColumnAfter', },
{ variant: 'insertHeaderRow', icon: '', label: 'Insert Header Row', type: 'toggleHeaderRow', },
{ variant: 'insertHeaderCell', icon: '', label: 'Insert Header Cell', type: 'toggleHeaderCell', },
{ variant: 'insertColumnBefore', icon: '', label: 'Insert Column Before', type: 'addColumnBefore', },
{ variant: 'insertHeaderColumn', icon: '', label: 'Insert Header Column', type: 'toggleHeaderColumn', },
{ variant: 'deleteTable', icon: '', label: 'Delete Table', type: 'deleteTable', },
]
},
{ type: 'link', mark: true, component: 'Link', icon: 'link', attrs: { href: null } },
// @note There is no free FA icon for this