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
57 changes: 21 additions & 36 deletions src/components/TableEditorDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,15 @@ export default defineComponent({
}

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

// If no content provided, create a minimal table with header and one body row
// This ensures the table editor recognizes it as a proper table with columns
if (!contentForEditor) {
contentForEditor = '| |\n| --- |\n| |\n'
}
// Use the dedicated createTable function for table-only editing
this.editor = await window.OCA.Text.createTable({
el: editorContainer,
Expand Down Expand Up @@ -97,47 +102,25 @@ export default defineComponent({
}

try {
// Extract HTML from the Text app's Tiptap editor to preserve all content (including pipe characters)
// The Text app's createTable() returns a wrapper object with a 'vm' property
// that contains the Vue instance rendering the editor component
let html = ''

// Access the Vue instance created by the Text app
const vm = this.editor.vm

// Navigate the component tree to find the Tiptap editor instance:
// vm.$children[0] is the Text app's table editor component
// which has an 'editor' property that is the actual Tiptap editor instance
if (vm && vm.$children && vm.$children.length > 0) {
const editorComponent = vm.$children[0]

if (editorComponent && editorComponent.editor) {
// Get raw HTML from Tiptap editor (this is the reliable source of content)
const fullHtml = editorComponent.editor.getHTML()
const parser = new DOMParser()
const doc = parser.parseFromString(fullHtml, 'text/html')
const table = doc.querySelector('table')

if (table) {
html = table.outerHTML
} else {
console.warn('No table found in HTML, using full HTML')
html = fullHtml
}
}
// Use Text app's getHTML() to extract clean HTML from Tiptap editor
const fullHtml = this.editor.getHTML()
if (!fullHtml) {
this.error = t('whiteboard', 'Failed to get editor content')
return
}

if (!html) {
console.error('Could not extract HTML from Tiptap editor')
}
// Parse the HTML and extract just the table element
const parser = new DOMParser()
const doc = parser.parseFromString(fullHtml, 'text/html')
const table = doc.querySelector('table')

if (!html || !html.trim()) {
this.error = t('whiteboard', 'Failed to extract table content')
if (!table) {
this.error = t('whiteboard', 'No table found in editor content')
return
}

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

this.show = false
Expand Down Expand Up @@ -228,7 +211,9 @@ export default defineComponent({
{{ t('whiteboard', 'Loading editor…') }}
</div>

<div ref="editorContainer" class="editor-container" />
<div class="editor-container">
<div ref="editorContainer" />
</div>

<div class="dialog-buttons">
<NcButton @click="onCancel">
Expand Down
40 changes: 30 additions & 10 deletions src/utils/tableToImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,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 TABLE_STYLE = 'border-collapse: collapse; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Arial, sans-serif; font-size: 14px; display: block;'
const TABLE_STYLE = 'border-collapse: collapse; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Arial, sans-serif; font-size: 14px;'

/**
* Convert HTML table to an image element for Excalidraw
Expand Down Expand Up @@ -85,6 +85,14 @@ function applyStylesToHtml(html: string): string {
const bodyCells = table.querySelectorAll('td')
bodyCells.forEach(cell => {
cell.setAttribute('style', CELL_BASE_STYLE)
// Ensure empty paragraphs don't collapse
const paragraphs = cell.querySelectorAll('p')
paragraphs.forEach(p => {
if (p instanceof HTMLElement) {
p.style.minHeight = '1.4em'
p.style.margin = '0'
}
})
})

return table.outerHTML
Expand All @@ -97,14 +105,16 @@ function applyStylesToHtml(html: string): string {
*/
async function htmlToDataUrl(html: string): Promise<string> {
return new Promise((resolve) => {
// Create a temporary container
// Create a temporary off-screen container for measurement
const container = document.createElement('div')
container.innerHTML = html
container.style.position = 'absolute'
container.style.backgroundColor = 'white'
container.style.left = '-9999px'
container.style.visibility = 'hidden'

document.body.appendChild(container)

// Wait for next frame to ensure rendering
// Wait for layout to complete
requestAnimationFrame(() => {
const svgDataUrl = createSvgDataUrl(container)
document.body.removeChild(container)
Expand All @@ -119,15 +129,25 @@ async function htmlToDataUrl(html: string): Promise<string> {
* @return SVG data URL
*/
function createSvgDataUrl(element: HTMLElement): string {
const bbox = element.getBoundingClientRect()
const width = Math.max(bbox.width)
const height = Math.max(bbox.height)
// Get the table element directly for accurate measurements
const table = element.querySelector('table') || element

// Get bounding box of the entire table to capture all content
const bbox = table.getBoundingClientRect()

// Add padding to prevent border/content cutoff
const padding = 4
const width = Math.ceil(bbox.width) + (padding * 2)
const height = Math.ceil(bbox.height) + (padding * 2)

// Get the table HTML with all our style overrides applied
const tableHtml = table.outerHTML

const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" style="background: white;">
${element.innerHTML}
<foreignObject x="0" y="0" width="${width}" height="${height}">
<div xmlns="http://www.w3.org/1999/xhtml" style="background: white; padding: ${padding}px;">
${tableHtml}
</div>
</foreignObject>
</svg>
Expand Down
Loading