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
21 changes: 15 additions & 6 deletions src/components/TableEditorDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export default defineComponent({
editor: null,
isLoading: true,
error: null,
currentHtml: this.initialHtml || '',
hasEnsuredTextStyles: false,
}
},
Expand Down Expand Up @@ -62,8 +61,8 @@ export default defineComponent({
}

// Convert HTML to markdown for the Text editor input
let contentForEditor = this.currentHtml && this.currentHtml.trim()
? this.generateMarkdownFromHtml(this.currentHtml)
let contentForEditor = this.initialHtml && this.initialHtml.trim()
? this.generateMarkdownFromHtml(this.initialHtml)
: ''

// If no content provided, create a minimal table with header and one body row
Expand Down Expand Up @@ -164,8 +163,10 @@ export default defineComponent({
return
}

const tableHtml = table.outerHTML.trim()

this.$emit('submit', {
html: table.outerHTML.trim(),
html: tableHtml,
})

this.show = false
Expand Down Expand Up @@ -213,8 +214,15 @@ export default defineComponent({
const headers = headerCells.map(cell => getCellContent(cell))
markdown += '| ' + headers.join(' | ') + ' |\n'

// Add separator
markdown += '| ' + headers.map(() => '---').join(' | ') + ' |\n'
// Add separator with alignment markers
// Read text-align from header cells (style) to preserve alignment
const separators = headerCells.map(cell => {
const align = cell.style.textAlign
if (align === 'center') return ':---:'
if (align === 'right') return '---:'
return '---' // left or default
})
markdown += '| ' + separators.join(' | ') + ' |\n'

// Process remaining rows as body
for (let i = 1; i < rows.length; i++) {
Expand Down Expand Up @@ -242,6 +250,7 @@ export default defineComponent({
},
},
})

</script>

<template>
Expand Down
10 changes: 4 additions & 6 deletions src/hooks/useTableInsertion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { mdiTable } from '@mdi/js'
import { useExcalidrawStore } from '../stores/useExcalidrawStore'
import { useWhiteboardConfigStore } from '../stores/useWhiteboardConfigStore'
import { useShallow } from 'zustand/react/shallow'
// @ts-expect-error - Vue component import
import TableEditorDialog from '../components/TableEditorDialog.vue'
import { renderToolbarButton } from '../components/ToolbarButton'
import { convertHtmlTableToImage } from '../utils/tableToImage'
Expand Down Expand Up @@ -113,16 +114,13 @@ export function useTableInsertion() {
x: tableElement.x,
y: tableElement.y,
angle: tableElement.angle,

// Increment version numbers to ensure this update wins during collaborative reconciliation
// Excalidraw uses these to resolve conflicts when multiple users edit simultaneously
version: (currentElement.version || 0) + 1,
versionNonce: (currentElement.versionNonce || 0) + 1,
customData: {
// Include the new markdown and isTable flag from the newly generated element
...(newImageElement.customData || {}),
// Explicitly clear the lock so other users can edit
tableLock: undefined,
},
// Include updated table data (tableHtml, isTable flag, and cleared lock)
customData: newImageElement.customData,
}

elements[elementIndex] = updatedElement
Expand Down
21 changes: 21 additions & 0 deletions src/types/nextcloud.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,24 @@ declare module '@nextcloud/router' {
declare module '@nextcloud/vue/dist/Components/NcRichText.js' {
export function getLinkWithPicker(initialValue?: string | null, isLink?: boolean): Promise<string>
}

// Extend window.OCA type to include Text app API
interface Window {
OCA?: {
Viewer?: {
compareFileInfo?: unknown
}
Text?: {
createTable: (options: {
el: HTMLElement
content: string
readOnly?: boolean
onUpdate?: (data: { markdown: string }) => void
}) => Promise<{
destroy: () => void
getHTML: () => string
focus?: () => void
}>
}
}
}
16 changes: 11 additions & 5 deletions src/utils/tableToImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { convertToExcalidrawElements } from '@nextcloud/excalidraw'

// Style constants - hardcoded values for static image rendering (CSS variables won't work in exported images)
const CELL_BASE_STYLE = 'border: 1px solid #ddd; padding: 12px 16px; line-height: 1.4;'
const HEADER_CELL_STYLE = `${CELL_BASE_STYLE} background-color: #f5f5f5; font-weight: 600; text-align: left;`
const HEADER_CELL_STYLE = `${CELL_BASE_STYLE} background-color: #f5f5f5; font-weight: 600;`
const TABLE_STYLE = 'border-collapse: collapse; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Arial, sans-serif; font-size: 14px;'

/**
Expand Down Expand Up @@ -79,12 +79,18 @@ function applyStylesToHtml(html: string): string {

table.setAttribute('style', TABLE_STYLE)
const headerCells = table.querySelectorAll('th')
headerCells.forEach(cell => {
cell.setAttribute('style', HEADER_CELL_STYLE)
headerCells.forEach((cell) => {
// Preserve text-align from style, otherwise use left
const align = (cell as HTMLElement).style.textAlign || 'left'
cell.setAttribute('style', HEADER_CELL_STYLE);
(cell as HTMLElement).style.textAlign = align
})
const bodyCells = table.querySelectorAll('td')
bodyCells.forEach(cell => {
cell.setAttribute('style', CELL_BASE_STYLE)
bodyCells.forEach((cell) => {
// Preserve text-align from style, otherwise use left
const align = (cell as HTMLElement).style.textAlign || 'left'
cell.setAttribute('style', CELL_BASE_STYLE);
(cell as HTMLElement).style.textAlign = align
// Ensure empty paragraphs don't collapse
const paragraphs = cell.querySelectorAll('p')
paragraphs.forEach(p => {
Expand Down
Loading