Skip to content
Merged
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
6 changes: 3 additions & 3 deletions packages/web-pkg/src/editor/components/TextEditorContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}"
>
<DragHandle
v-if="!isSourceMode"
v-show="!isSourceMode"
:editor="textEditor.editor.value"
@node-change="onDragHandleNodeChange"
>
Expand All @@ -31,8 +31,8 @@
</oc-button>
</div>
</DragHandle>
<TextEditorTableBubbleMenu v-if="!isSourceMode" />
<TextEditorLinkBubbleMenu v-if="!isSourceMode" />
<TextEditorTableBubbleMenu v-show="!isSourceMode" />
<TextEditorLinkBubbleMenu v-show="!isSourceMode" />
<EditorContent v-show="!isSourceMode" :editor="textEditor.editor.value" class="h-full" />
<div v-if="isSourceMode" class="flex size-full justify-center">
<textarea
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
appearance="raw"
class="text-editor-bubble-menu-btn inline-flex items-center justify-center p-2"
:aria-label="action.title"
:disabled="!isItemEnabled(action)"
@mousedown.prevent
@click.stop="onActionClick(action)"
>
Expand Down Expand Up @@ -96,7 +95,7 @@ const getReferencedVirtualElement: BubbleMenuPluginProps['getReferencedVirtualEl
const groupDefinitions = [
{
id: 'rows',
actions: ['add-row-before', 'add-row-after', 'delete-row']
actions: ['toggle-header-row', 'add-row-before', 'add-row-after', 'delete-row']
},
{
id: 'columns',
Expand All @@ -113,6 +112,11 @@ const tableActionGroups = computed(() => {
return []
}

const editor = unref(textEditor.editor)
if (!editor) {
return []
}

const allActions = textEditor.actionGroups().flatMap((group) => group.actions)
const actionMap = new Map(allActions.map((action) => [action.id, action]))

Expand All @@ -122,6 +126,7 @@ const tableActionGroups = computed(() => {
actions: group.actions
.map((actionId) => actionMap.get(actionId))
.filter((a): a is EditorAction => a !== undefined)
.filter((action) => !action.isEnabled || action.isEnabled(editor))
}))
.filter((group) => group.actions.length > 0)
})
Expand All @@ -134,13 +139,4 @@ const onActionClick = (action: EditorAction) => {

action.toolbarAction?.(editor)
}

const isItemEnabled = (item: EditorAction) => {
const editor = unref(textEditor?.editor)
if (!editor) {
return false
}

return item.isEnabled ? item.isEnabled(editor) : true
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<template>
<div class="table-size-selector min-w-max">
<div
ref="gridRef"
class="grid"
:style="{
gridTemplateColumns: `repeat(${maxCols}, 16px)`,
gap: '4px'
}"
@mouseleave="resetHover"
>
<div
v-for="(cell, index) in totalCells"
:key="index"
class="w-4 h-4 cursor-pointer rounded border transition-colors"
:class="{
'border-role-primary bg-role-primary': isHighlighted(index),
'border-role-outline-variant bg-transparent': !isHighlighted(index)
}"
@mouseenter="updateHover(index)"
@click="selectSize"
/>
</div>
<div class="mt-3 text-center text-sm text-role-on-surface-variant min-h-[1.25rem]">
{{ gridLabel }}
</div>
</div>
</template>

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

const { editor, closeMenu } = defineProps<{
editor: Editor
closeMenu: () => void
}>()

const maxRows = 9
const maxCols = 9
const totalCells = maxRows * maxCols

const hoveredIndex = ref(-1)
const hoveredRow = computed(() => Math.floor(hoveredIndex.value / maxCols) + 1)
const hoveredCol = computed(() => (hoveredIndex.value % maxCols) + 1)

const gridLabel = computed(() => {
if (hoveredIndex.value === -1) {
return ' '
}
return `${hoveredRow.value} × ${hoveredCol.value}`
})

function isHighlighted(index: number): boolean {
if (hoveredIndex.value === -1) {
return false
}
const row = Math.floor(index / maxCols)
const col = index % maxCols
return row < hoveredRow.value && col < hoveredCol.value
}

function updateHover(index: number) {
hoveredIndex.value = index
}

function resetHover() {
hoveredIndex.value = -1
}

function selectSize() {
if (hoveredIndex.value === -1) {
return
}
editor
.chain()
.focus()
.insertTable({ rows: hoveredRow.value, cols: hoveredCol.value, withHeaderRow: true })
.run()
closeMenu()
}
</script>
38 changes: 38 additions & 0 deletions packages/web-pkg/src/editor/components/TextEditorToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,28 @@
class="oc-rounded oc-menu-item-hover"
>
<oc-button
v-if="child.menuComponent"
:id="`toolbar-dropdown-trigger-${child.id}`"
appearance="raw-inverse"
color-role="surface"
justify-content="space-between"
class="p-1"
:disabled="!isItemEnabled(child)"
@mousedown.prevent
@click.stop
>
<span class="inline-flex items-center gap-2">
<oc-icon
:name="child.icon"
:fill-type="child.iconFillType || 'none'"
size-class="size-4"
/>
<span>{{ child.title }}</span>
</span>
<oc-icon name="arrow-right-s" fill-type="line" size-class="size-4" />
</oc-button>
<oc-button
v-else
:appearance="isItemActive(child) ? 'filled' : 'raw-inverse'"
:color-role="isItemActive(child) ? 'secondaryContainer' : 'surface'"
:no-hover="isItemActive(child)"
Expand Down Expand Up @@ -88,6 +110,22 @@
size-class="size-4"
/>
</oc-button>
<oc-drop
v-if="child.menuComponent"
:ref="
(el) =>
setDropRef(child.id, el as ComponentPublicInstance<typeof OcDrop> | null)
"
:drop-id="`toolbar-dropdown-${child.id}`"
:toggle="`#toolbar-dropdown-trigger-${child.id}`"
mode="hover"
class="text-editor-toolbar-dropdown-nested w-fit"
:close-on-click="child.menuCloseOnClick ?? true"
position="right-start"
teleport="body"
>
<component :is="child.menuComponent" v-bind="getMenuComponentAttrs(child)" />
</oc-drop>
</li>
</ul>
</oc-drop>
Expand Down
2 changes: 2 additions & 0 deletions packages/web-pkg/src/editor/composables/strategies/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export const useStrategyHtml = (editorState: TextEditorState): ContentTypeStrate
addColumnBefore,
addColumnAfter,
deleteColumn,
toggleHeaderRow,
deleteTable,
menuSearchAndReplace
} = useEditorActions(editorState)
Expand Down Expand Up @@ -172,6 +173,7 @@ export const useStrategyHtml = (editorState: TextEditorState): ContentTypeStrate
imageUrl(),
imageUpload(),
createTable(),
toggleHeaderRow(),
addRowBefore(),
addRowAfter(),
deleteRow(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const useStrategyMarkdown = (editorState: TextEditorState): ContentTypeSt
addColumnBefore,
addColumnAfter,
deleteColumn,
toggleHeaderRow,
deleteTable
} = useEditorActions(editorState)
const editorActionGroups = (): EditorActionGroup[] => {
Expand Down Expand Up @@ -157,6 +158,7 @@ export const useStrategyMarkdown = (editorState: TextEditorState): ContentTypeSt
imageUrl(),
imageUpload(),
createTable(),
toggleHeaderRow(),
addRowBefore(),
addRowAfter(),
deleteRow(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const useStrategyTiptapJson = (editorState: TextEditorState): ContentType
addColumnBefore,
addColumnAfter,
deleteColumn,
toggleHeaderRow,
deleteTable
} = useEditorActions(editorState)
const editorActionGroups = (): EditorActionGroup[] => {
Expand Down Expand Up @@ -151,6 +152,7 @@ export const useStrategyTiptapJson = (editorState: TextEditorState): ContentType
imageUrl(),
imageUpload(),
createTable(),
toggleHeaderRow(),
addRowBefore(),
addRowAfter(),
deleteRow(),
Expand Down
46 changes: 43 additions & 3 deletions packages/web-pkg/src/editor/composables/useEditorActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { arrayBufferToDataUrl } from '../../helpers'
import { TextEditorState } from '../types'
import { requestLinkPanel } from '../helpers/link'
import TextEditorSearchAndReplacePanel from '../components/TextEditorSearchAndReplacePanel.vue'
import TextEditorTableSizeSelector from '../components/TextEditorTableSizeSelector.vue'

export interface EditorAction {
// Core identification
Expand Down Expand Up @@ -739,11 +740,34 @@ export function useEditorActions(state: TextEditorState) {
const createTable = (): EditorAction => ({
id: 'table',
title: $gettext('Create a table'),
description: $gettext('3×3 table with header row'),
description: $gettext('Insert a table'),
icon: 'table-line',
keywords: ['grid'],
toolbarAction: (editor) =>
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
childActions: [
{
id: 'table-default',
title: $gettext('Small table (3×3)'),
description: $gettext('3×3 table with header row'),
icon: 'table-line',
toolbarAction: (editor) =>
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
isActive: () => false
},
{
id: 'table-custom',
title: $gettext('Choose rows & columns'),
description: $gettext('Select custom table size'),
icon: 'grid',
iconFillType: 'line',
menuComponent: markRaw(TextEditorTableSizeSelector),
menuCloseOnClick: false,
menuComponentAttrs: (editor, closeMenu) => ({
editor,
closeMenu
}),
isActive: () => false
}
],
slashCommandAction: ({ editor, range }) => {
editor
.chain()
Expand Down Expand Up @@ -861,6 +885,21 @@ export function useEditorActions(state: TextEditorState) {
isEnabled: (editor) => editor.isActive('table')
})

const toggleHeaderRow = (): EditorAction => ({
id: 'toggle-header-row',
title: $gettext('Toggle header row'),
description: $gettext('Toggle header row'),
icon: 'layout-row-fill',
keywords: ['table', 'header', 'row'],
showInToolbar: false,
toolbarAction: (editor) => editor.chain().focus().toggleHeaderRow().run(),
slashCommandAction: ({ editor, range }) => {
editor.chain().focus().deleteRange(range).toggleHeaderRow().run()
},
isActive: () => false,
isEnabled: (editor) => editor.isActive('table')
})

const deleteTable = (): EditorAction => ({
id: 'delete-table',
title: $gettext('Delete table'),
Expand Down Expand Up @@ -929,6 +968,7 @@ export function useEditorActions(state: TextEditorState) {
addColumnBefore,
addColumnAfter,
deleteColumn,
toggleHeaderRow,
deleteTable
}
}
Loading