Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 56 additions & 11 deletions src/components/TextEditor/TextEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@
/>
</slot>
<slot name="bottom" :editor />
<TableBorderMenu
:show="showTableBorderMenu"
:axis="tableBorderAxis"
:position="tableBorderMenuPos"
:cell-info="tableCellInfo"
:can-merge-cells="canMergeCells"
@add-row-before="addRowBefore"
@add-row-after="addRowAfter"
@delete-row="deleteRow"
@add-column-before="addColumnBefore"
@add-column-after="addColumnAfter"
@delete-column="deleteColumn"
@merge-cells="mergeCells"
@toggle-header="toggleHeader"
@set-background-color="setBackgroundColor"
@set-border-color="setBorderColor"
@set-border-width="setBorderWidth"
/>
</div>
</template>

Expand All @@ -47,12 +65,12 @@ import Typography from '@tiptap/extension-typography'
import { TextStyleKit } from '@tiptap/extension-text-style'
import { TaskList, TaskItem } from '@tiptap/extension-list'
import TextAlign from '@tiptap/extension-text-align'
import {
Table,
TableRow,
TableCell,
TableHeader,
} from '@tiptap/extension-table'
// import {
// Table,
// TableRow,
// TableCell,
// TableHeader,
// } from '@tiptap/extension-table'

import { ImageExtension } from './extensions/image'
import { VideoExtension } from './extensions/video-extension'
Expand All @@ -70,6 +88,13 @@ import { ContentPasteExtension } from './extensions/content-paste-extension'
import { Heading } from './extensions/heading/heading'
import { ImageGroup } from './extensions/image-group/image-group-extension'
import { ExtendedCode, ExtendedCodeBlock } from './extensions/code-block'
import TableExtension from './extensions/tables/table-extension'
import TableCellExtension from './extensions/tables/table-cell-extension'
import TableHeaderExtension from './extensions/tables/table-header-extension'
import TableRowExtension from './extensions/tables/table-row-extension'
import TableBorderMenu from './extensions/tables/TableBorderMenu.vue'
import { useTableMenu } from './extensions/tables/use-table-menu'
import { TableCommandsExtension } from './extensions/tables/table-selection-extension'

import TextEditorFixedMenu from './components/TextEditorFixedMenu.vue'
import TextEditorBubbleMenu from './components/TextEditorBubbleMenu.vue'
Expand Down Expand Up @@ -175,12 +200,13 @@ onMounted(() => {
? props.starterkitOptions.heading
: {}),
}),
Table.configure({
resizable: true,
TableExtension.configure({
resizable: false,
}),
TableRow,
TableHeader,
TableCell,
TableCellExtension,
TableHeaderExtension,
TableRowExtension,
TableCommandsExtension,
TaskList,
TaskItem.configure({
nested: true,
Expand Down Expand Up @@ -269,6 +295,25 @@ defineExpose({
editor,
rootRef,
})

const {
showTableBorderMenu,
tableBorderAxis,
tableBorderMenuPos,
tableCellInfo,
canMergeCells,
addRowBefore,
addRowAfter,
deleteRow,
addColumnBefore,
addColumnAfter,
deleteColumn,
mergeCells,
toggleHeader,
setBackgroundColor,
setBorderColor,
setBorderWidth,
} = useTableMenu(editor)
</script>

<style>
Expand Down
10 changes: 4 additions & 6 deletions src/components/TextEditor/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,10 @@ export default {
InsertTable: {
label: 'Insert Table',
icon: Table,
action: (editor) =>
editor
.chain()
.focus()
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run(),
component: defineAsyncComponent(
() => import('./components/TableSizeSelector.vue'),
),
isTableSizeSelector: true,
isActive: (editor) => editor.can().deleteTable(),
},
AddColumnBefore: {
Expand Down
29 changes: 22 additions & 7 deletions src/components/TextEditor/components/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,22 @@
<ul
class="p-1.5 mt-2 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<!-- fix: isDisabled hides here, and disables elsewhere -->
<li
v-for="option in button"
v-show="option.isDisabled ? !option.isDisabled(editor) : true"
>
<component
v-if="option.component"
v-if="option.component && option.isTableSizeSelector"
:is="option.component"
v-bind="{ editor, close }"
/>
<component
v-else-if="
option.component &&
!button.some((b) => b.isTableSizeSelector)
"
:is="option.component || 'div'"
v-bind="{ editor }"
v-bind="{ editor, close }"
>
<template v-slot="componentSlotProps">
<button
Expand Down Expand Up @@ -74,7 +81,7 @@
</template>
</component>
<button
v-else
v-else-if="!button.some((b) => b.isTableSizeSelector)"
class="w-full h-7 rounded px-2 text-base flex items-center gap-2 hover:bg-surface-gray-3"
@click="
() => {
Expand Down Expand Up @@ -168,10 +175,16 @@
class="flex rounded p-1 text-ink-gray-8 transition-colors"
:class="[
button.isDisabled?.(editor) && 'opacity-50 pointer-events-none',
'hover:bg-surface-gray-2',
button.isActive?.(editor) || componentSlotProps?.isActive
? 'bg-surface-gray-3'
: 'hover:bg-surface-gray-2',
button.class,
]"
@click="onButtonClick(button)"
@click="
componentSlotProps?.onClick
? componentSlotProps.onClick(button)
: onButtonClick(button)
"
:title="button.label"
>
<component v-if="button.icon" :is="button.icon" class="h-4 w-4" />
Expand All @@ -198,7 +211,9 @@ const props = defineProps({
const editor = inject('editor')

const onButtonClick = (button) => {
button.action(editor.value)
if (button.action && typeof button.action === 'function') {
button.action(editor.value)
}
}
const getActiveButton = (group) => {
return group.find((b) => b.isActive?.(editor.value))
Expand Down
70 changes: 70 additions & 0 deletions src/components/TextEditor/components/TableSizeSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<div class="p-2" @click.stop>
<div class="grid grid-cols-6 gap-0.5 w-[120px] mx-auto">
<div
v-for="row in maxRows"
:key="`row-${row}`"
class="contents"
>
<button
v-for="col in maxCols"
:key="`cell-${row}-${col}`"
class="h-4 w-4 border border-outline-gray-2 cursor-pointer transition-all duration-150 ease-in-out"
:class="{
'bg-surface-gray-3 border-outline-gray-3': row <= hoveredRows && col <= hoveredCols,
'hover:bg-ink-gray-9 hover:border-ink-gray-9': !(row <= hoveredRows && col <= hoveredCols)
}"
@mouseenter="hoveredRows = row; hoveredCols = col"
@mouseleave="hoveredRows = 0; hoveredCols = 0"
@click="selectSize(row, col)"
/>
</div>
</div>
<div class="text-center text-xs text-ink-gray-7 font-medium py-1 min-h-5">
{{ hoveredRows || selectedRows }} × {{ hoveredCols || selectedCols }}
</div>
</div>
</template>

<script setup lang="ts">
import { ref, inject } from 'vue'
import type { Editor } from '@tiptap/vue-3'

const props = defineProps<{
editor?: Editor
onClick?: (option: any) => void
close?: () => void
}>()

const injectedEditor = inject<{ value: Editor | null }>('editor', { value: null })
const editor = props.editor || injectedEditor.value

const maxRows = 6
const maxCols = 6
const selectedRows = ref(3)
const selectedCols = ref(3)
const hoveredRows = ref(0)
const hoveredCols = ref(0)

const selectSize = (rows: number, cols: number) => {
selectedRows.value = rows
selectedCols.value = cols

if (props.onClick) {
props.onClick({ rows, cols })
} else if (editor) {
editor
.chain()
.focus()
.insertTable({ rows, cols, withHeaderRow: true })
.run()
}

if (props.close) {
props.close()
}
}

</script>


32 changes: 14 additions & 18 deletions src/components/TextEditor/components/TextEditorBubbleMenu.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
<template>
<BubbleMenu v-if="bubbleMenuButtons" class="bubble-menu rounded-md z-[100]"
:class="bubbleMenuButtons.length > 1 && 'shadow-sm'" :editor="editor" v-bind="options">
<Menu class="rounded" :class="bubbleMenuButtons.length > 1 && 'shadow-lg'" :buttons="bubbleMenuButtons" />
<BubbleMenu
v-if="bubbleMenuButtons"
class="bubble-menu rounded-md z-[100]"
:class="bubbleMenuButtons.length > 1 && 'shadow-sm'"
:editor="editor"
:should-show="shouldShow"
v-bind="options"
>
<Menu
class="rounded"
:class="bubbleMenuButtons.length > 1 && 'shadow-lg'"
:buttons="bubbleMenuButtons"
/>
</BubbleMenu>
</template>
<script>
Expand Down Expand Up @@ -45,21 +55,7 @@ export default {
'Video',
'Blockquote',
'Code',
[
'InsertTable',
'AddColumnBefore',
'AddColumnAfter',
'DeleteColumn',
'AddRowBefore',
'AddRowAfter',
'DeleteRow',
'MergeCells',
'SplitCell',
'ToggleHeaderColumn',
'ToggleHeaderRow',
'ToggleHeaderCell',
'DeleteTable',
],
['InsertTable'],
]
}
return buttons.map(createEditorButton)
Expand Down
Loading