@bagelink/vue 1.15.515 → 1.15.521

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -358,10 +358,31 @@ const tableForm = ref({
358
358
  alternateRowBgColor: '#f9f9f9',
359
359
  alternateRowTextColor: '#333333',
360
360
  fixedLayout: true, // תאים ברוחב קבוע
361
+ columnWidths: [33.34, 33.33, 33.33] as number[],
361
362
  alignment: 'left', // left, center, right
362
363
  direction: 'ltr' // ltr, rtl
363
364
  })
364
365
 
366
+ const columnWidthsTotal = computed(() => tableForm.value.columnWidths
367
+ .slice(0, tableForm.value.cols)
368
+ .reduce((total, width) => total + (Number(width) || 0), 0))
369
+
370
+ function equalColumnWidths(cols: number): number[] {
371
+ if (cols <= 0) { return [] }
372
+ const base = Number((100 / cols).toFixed(2))
373
+ const widths = Array.from({ length: cols }, () => base)
374
+ widths[cols - 1] = Number((100 - base * (cols - 1)).toFixed(2))
375
+ return widths
376
+ }
377
+
378
+ function syncColumnWidths(cols: number) {
379
+ const current = tableForm.value.columnWidths
380
+ if (current.length === cols) { return }
381
+ tableForm.value.columnWidths = equalColumnWidths(cols)
382
+ }
383
+
384
+ watch(() => tableForm.value.cols, syncColumnWidths)
385
+
365
386
  // Table context menu state
366
387
  const showTableContextMenu = ref(false)
367
388
  const contextMenuPosition = ref({ x: 0, y: 0 })
@@ -388,6 +409,7 @@ const tablePreviewHtml = computed(() => {
388
409
  // For cell text alignment, we'll apply it to individual cells, not the table
389
410
 
390
411
  let html = `<table style="${tableStyle}" dir="${form.direction}">`
412
+ html += `<colgroup>${form.columnWidths.slice(0, form.cols).map(width => `<col style="width: ${width}%;">`).join('')}</colgroup>`
391
413
 
392
414
  // Add header row if enabled
393
415
  if (form.showHeaders) {
@@ -399,7 +421,7 @@ const tablePreviewHtml = computed(() => {
399
421
  background-color: ${form.headerBgColor};
400
422
  color: ${form.headerTextColor};
401
423
  text-align: ${form.alignment};
402
- ${form.fixedLayout ? `width: ${100 / form.cols}%;` : ''}
424
+ ${form.fixedLayout ? `width: ${form.columnWidths[j] ?? (100 / form.cols)}%;` : ''}
403
425
  `
404
426
  html += `<th style="${thStyle}">Header ${j + 1}</th>`
405
427
  }
@@ -415,7 +437,7 @@ const tablePreviewHtml = computed(() => {
415
437
  padding: ${form.cellPadding}px;
416
438
  border: ${form.borderWidth}px solid ${form.borderColor};
417
439
  text-align: ${form.alignment};
418
- ${form.fixedLayout ? `width: ${100 / form.cols}%;` : ''}
440
+ ${form.fixedLayout ? `width: ${form.columnWidths[j] ?? (100 / form.cols)}%;` : ''}
419
441
  ${form.alternateRows && i % 2 === 1
420
442
  ? `background-color: ${form.alternateRowBgColor}; color: ${form.alternateRowTextColor};`
421
443
  : `background-color: ${form.cellBgColor}; color: ${form.cellTextColor};`}
@@ -434,38 +456,57 @@ editor.state.content = props.modelValue
434
456
 
435
457
  // Matches Hebrew, Arabic, Syriac, Thaana, Arabic Supplement, Arabic Presentation Forms
436
458
  const RTL_REGEX = /[\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\uFB1D-\uFDFD\uFE70-\uFEFC]/
459
+ const LTR_REGEX = /[A-Za-z\u00C0-\u02AF\u0370-\u058F]/
460
+
461
+ function getTextDirection(text: string, fallback: 'ltr' | 'rtl' = 'ltr'): 'ltr' | 'rtl' {
462
+ for (const char of text) {
463
+ if (RTL_REGEX.test(char)) { return 'rtl' }
464
+ if (LTR_REGEX.test(char)) { return 'ltr' }
465
+ }
466
+ return fallback
467
+ }
468
+
469
+ function getBlockDirection(element: Element, fallback: 'ltr' | 'rtl', preserveExplicit = true): 'ltr' | 'rtl' {
470
+ const explicitDirection = element.getAttribute('dir')
471
+ if (preserveExplicit && (explicitDirection === 'rtl' || explicitDirection === 'ltr')) { return explicitDirection }
472
+ return getTextDirection(element.textContent || '', fallback)
473
+ }
437
474
 
438
475
  function detectAndSetDirection() {
439
476
  const { doc } = editor.state
440
477
  if (!doc?.body) { return }
441
478
 
442
- const allText = doc.body.textContent || ''
443
- const firstChars = allText.trim().substring(0, 20)
444
- const currentDir = doc.body.dir || 'ltr'
445
- const newDirection = RTL_REGEX.test(firstChars) ? 'rtl' : 'ltr'
446
-
447
- if (newDirection !== currentDir) {
448
- doc.body.dir = newDirection
449
- doc.body.style.direction = newDirection
450
- doc.querySelectorAll('p').forEach((p) => {
451
- if (!p.classList.contains('placeholder')) {
452
- p.setAttribute('dir', newDirection)
453
- }
454
- })
455
- }
479
+ const fallback = getTextDirection(doc.body.textContent || '', (doc.body.dir || 'ltr') as 'ltr' | 'rtl')
480
+ doc.body.dir = fallback
481
+ doc.body.style.direction = fallback
482
+
483
+ doc.querySelectorAll('p,h1,h2,h3,h4,h5,h6,blockquote,li').forEach((block) => {
484
+ if (!block.classList.contains('placeholder')) {
485
+ block.setAttribute('dir', getBlockDirection(block, fallback, false))
486
+ }
487
+ })
456
488
  }
457
489
 
458
490
  function getCurrentDirection() {
459
491
  const { doc } = editor.state
460
492
  if (!doc?.body) { return 'ltr' }
461
- const firstChars = (doc.body.textContent || '').trim().substring(0, 20)
462
- return RTL_REGEX.test(firstChars) ? 'rtl' : 'ltr'
493
+ const selection = doc.getSelection()
494
+ const selectedNode = selection?.rangeCount ? selection.getRangeAt(0).startContainer : null
495
+ const selectedElement = selectedNode?.nodeType === Node.ELEMENT_NODE
496
+ ? selectedNode as Element
497
+ : selectedNode?.parentElement
498
+ const block = selectedElement?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,li')
499
+ const fallback = getTextDirection(doc.body.textContent || '', (doc.body.dir || 'ltr') as 'ltr' | 'rtl')
500
+ return block ? getBlockDirection(block, fallback) : fallback
463
501
  }
464
502
 
465
503
  // Helper function to update content with history tracking
466
504
  function updateContentWithHistory(doc: Document, skipHistory = false) {
467
505
  if (!doc) { return }
468
506
 
507
+ if (doc === editor.state.doc) {
508
+ detectAndSetDirection()
509
+ }
469
510
  const newContent = doc.body.innerHTML
470
511
  if (newContent !== editor.state.content) {
471
512
  if (!skipHistory) {
@@ -479,11 +520,6 @@ function updateContentWithHistory(doc: Document, skipHistory = false) {
479
520
  }
480
521
  }
481
522
  editor.state.content = newContent
482
-
483
- // Check direction after content update
484
- if (doc === editor.state.doc) {
485
- detectAndSetDirection()
486
- }
487
523
  }
488
524
  }
489
525
 
@@ -1372,8 +1408,15 @@ function detectTableAlignment(table: HTMLTableElement): 'left' | 'center' | 'rig
1372
1408
  }
1373
1409
 
1374
1410
  function openTableEditor(existingTable: HTMLTableElement | null = null) {
1411
+ console.log('[RichText table editor] open requested', {
1412
+ hasTable: Boolean(existingTable),
1413
+ isFullscreen: editor.state.isFullscreen,
1414
+ })
1375
1415
  const { doc } = editor.state
1376
- if (!doc) { return }
1416
+ if (!doc) {
1417
+ console.warn('[RichText table editor] aborted: editor document is unavailable')
1418
+ return
1419
+ }
1377
1420
 
1378
1421
  // Get current selection for new tables
1379
1422
  if (!existingTable) {
@@ -1394,6 +1437,8 @@ function openTableEditor(existingTable: HTMLTableElement | null = null) {
1394
1437
  // Populate form with existing table data
1395
1438
  const tbody = existingTable.querySelector('tbody')
1396
1439
  const thead = existingTable.querySelector('thead')
1440
+ const editorWindow = existingTable.ownerDocument.defaultView
1441
+ const getEditorStyle = (element: Element) => editorWindow?.getComputedStyle(element) ?? null
1397
1442
 
1398
1443
  // Detect alternating rows by comparing background colors
1399
1444
  let hasAlternatingRows = false
@@ -1408,14 +1453,14 @@ function openTableEditor(existingTable: HTMLTableElement | null = null) {
1408
1453
  const firstCell = firstRow.cells[0] as HTMLElement
1409
1454
  const secondCell = secondRow.cells[0] as HTMLElement
1410
1455
 
1411
- const firstBgColor = getComputedStyle(firstCell).backgroundColor
1412
- const secondBgColor = getComputedStyle(secondCell).backgroundColor
1456
+ const firstBgColor = getEditorStyle(firstCell)?.backgroundColor || ''
1457
+ const secondBgColor = getEditorStyle(secondCell)?.backgroundColor || ''
1413
1458
 
1414
1459
  // Check if colors are different (indicating alternating rows)
1415
1460
  if (firstBgColor !== secondBgColor) {
1416
1461
  hasAlternatingRows = true
1417
1462
  alternateRowBg = secondBgColor
1418
- alternateRowTextColor = getComputedStyle(secondCell).color
1463
+ alternateRowTextColor = getEditorStyle(secondCell)?.color || '#333333'
1419
1464
  }
1420
1465
  }
1421
1466
  }
@@ -1428,14 +1473,27 @@ function openTableEditor(existingTable: HTMLTableElement | null = null) {
1428
1473
  borderColor: existingTable.style.borderColor || '#dddddd',
1429
1474
  cellPadding: 8, // default, hard to extract
1430
1475
  showHeaders: !!thead,
1431
- headerBgColor: thead ? getComputedStyle(thead.querySelector('th') || thead).backgroundColor || '#f4f4f4' : '#f4f4f4',
1432
- headerTextColor: thead ? getComputedStyle(thead.querySelector('th') || thead).color || '#333333' : '#333333',
1433
- cellBgColor: tbody ? getComputedStyle(tbody.querySelector('td') || tbody).backgroundColor || '#ffffff' : '#ffffff',
1434
- cellTextColor: tbody ? getComputedStyle(tbody.querySelector('td') || tbody).color || '#333333' : '#333333',
1476
+ headerBgColor: thead ? getEditorStyle(thead.querySelector('th') || thead)?.backgroundColor || '#f4f4f4' : '#f4f4f4',
1477
+ headerTextColor: thead ? getEditorStyle(thead.querySelector('th') || thead)?.color || '#333333' : '#333333',
1478
+ cellBgColor: tbody ? getEditorStyle(tbody.querySelector('td') || tbody)?.backgroundColor || '#ffffff' : '#ffffff',
1479
+ cellTextColor: tbody ? getEditorStyle(tbody.querySelector('td') || tbody)?.color || '#333333' : '#333333',
1435
1480
  alternateRows: hasAlternatingRows,
1436
1481
  alternateRowBgColor: alternateRowBg,
1437
1482
  alternateRowTextColor,
1438
1483
  fixedLayout: existingTable.style.tableLayout === 'fixed' || true, // ברירת מחדל true
1484
+ columnWidths: (() => {
1485
+ const cols = tbody && tbody.rows.length > 0 ? tbody.rows[0].cells.length : 3
1486
+ const columnElements = Array.from(existingTable.querySelectorAll(':scope > colgroup > col')) as HTMLElement[]
1487
+ if (columnElements.length === cols) {
1488
+ return columnElements.map((column, index) =>
1489
+ Number.parseFloat(column.style.width)
1490
+ || Number.parseFloat((existingTable.rows[0]?.cells[index] as HTMLElement | undefined)?.style.width || '')
1491
+ || (100 / cols))
1492
+ }
1493
+ return Array.from({ length: cols }, (_, index) =>
1494
+ Number.parseFloat((existingTable.rows[0]?.cells[index] as HTMLElement | undefined)?.style.width || '')
1495
+ || (100 / cols))
1496
+ })(),
1439
1497
  alignment: detectTableAlignment(existingTable),
1440
1498
  direction: existingTable.dir || 'ltr'
1441
1499
  }
@@ -1457,10 +1515,24 @@ function openTableEditor(existingTable: HTMLTableElement | null = null) {
1457
1515
  alternateRowBgColor: '#f9f9f9',
1458
1516
  alternateRowTextColor: '#333333',
1459
1517
  fixedLayout: true,
1518
+ columnWidths: equalColumnWidths(3),
1460
1519
  alignment: 'left',
1461
1520
  direction: 'ltr'
1462
1521
  }
1463
- } showTableEditor.value = true
1522
+ }
1523
+ showTableEditor.value = true
1524
+ console.log('[RichText table editor] dialog state opened', {
1525
+ open: showTableEditor.value,
1526
+ hasPendingTable: Boolean(pendingTableData?.existingTable),
1527
+ })
1528
+ }
1529
+
1530
+ function openExistingTableAdvancedSettings() {
1531
+ const table = contextMenuCell.value?.closest('table') as HTMLTableElement | null
1532
+ if (!table) { return }
1533
+
1534
+ openTableEditor(table)
1535
+ showTableContextMenu.value = false
1464
1536
  }
1465
1537
 
1466
1538
  // Function to submit table changes
@@ -1487,6 +1559,17 @@ function submitTable() {
1487
1559
  table.style.tableLayout = 'auto'
1488
1560
  }
1489
1561
 
1562
+ let colgroup = table.querySelector(':scope > colgroup')
1563
+ if (!colgroup) {
1564
+ colgroup = doc.createElement('colgroup')
1565
+ table.insertBefore(colgroup, table.firstChild)
1566
+ }
1567
+ colgroup.replaceChildren(...tableForm.value.columnWidths.slice(0, tableForm.value.cols).map((width) => {
1568
+ const column = doc.createElement('col')
1569
+ column.style.width = `${width}%`
1570
+ return column
1571
+ }))
1572
+
1490
1573
  // Don't force text alignment for all cells - let them inherit from table direction
1491
1574
  // console.log('Setting cell text alignment:', tableForm.value.alignment)
1492
1575
  // const allCells = table.querySelectorAll('td, th')
@@ -1507,8 +1590,8 @@ function submitTable() {
1507
1590
 
1508
1591
  // Set fixed width for fixed layout
1509
1592
  if (tableForm.value.fixedLayout) {
1510
- const colCount = table.querySelector('tr')?.querySelectorAll('td, th').length || tableForm.value.cols
1511
- cellEl.style.width = `${100 / colCount}%`
1593
+ const cellIndex = (cellEl as HTMLTableCellElement).cellIndex
1594
+ cellEl.style.width = `${tableForm.value.columnWidths[cellIndex] ?? (100 / tableForm.value.cols)}%`
1512
1595
  }
1513
1596
  })
1514
1597
 
@@ -1587,6 +1670,14 @@ function createNewTable(doc: Document) {
1587
1670
  // Set table direction
1588
1671
  table.dir = tableForm.value.direction
1589
1672
 
1673
+ const colgroup = doc.createElement('colgroup')
1674
+ tableForm.value.columnWidths.slice(0, tableForm.value.cols).forEach((width) => {
1675
+ const column = doc.createElement('col')
1676
+ column.style.width = `${width}%`
1677
+ colgroup.appendChild(column)
1678
+ })
1679
+ table.appendChild(colgroup)
1680
+
1590
1681
  // Add header if enabled
1591
1682
  if (tableForm.value.showHeaders) {
1592
1683
  const thead = doc.createElement('thead')
@@ -1603,7 +1694,7 @@ function createNewTable(doc: Document) {
1603
1694
 
1604
1695
  // Set fixed width for fixed layout
1605
1696
  if (tableForm.value.fixedLayout) {
1606
- th.style.width = `${100 / tableForm.value.cols}%`
1697
+ th.style.width = `${tableForm.value.columnWidths[j] ?? (100 / tableForm.value.cols)}%`
1607
1698
  }
1608
1699
 
1609
1700
  headerRow.appendChild(th)
@@ -1625,7 +1716,7 @@ function createNewTable(doc: Document) {
1625
1716
 
1626
1717
  // Set fixed width for fixed layout
1627
1718
  if (tableForm.value.fixedLayout) {
1628
- cell.style.width = `${100 / tableForm.value.cols}%`
1719
+ cell.style.width = `${tableForm.value.columnWidths[j] ?? (100 / tableForm.value.cols)}%`
1629
1720
  }
1630
1721
 
1631
1722
  // Apply background and text colors
@@ -2220,7 +2311,10 @@ function handleTableContextMenu(event: MouseEvent) {
2220
2311
 
2221
2312
  // Expose debug methods if debug mode is enabled
2222
2313
  const debugMethods = computed(() => editor.state.debug)
2223
- const hasRTL = computed(() => RTL_REGEX.test(props.modelValue))
2314
+ const hasRTL = computed(() => getTextDirection(
2315
+ props.modelValue.replace(/<[^>]*>/g, ''),
2316
+ 'ltr'
2317
+ ) === 'rtl')
2224
2318
 
2225
2319
  // Computed property to handle height prop
2226
2320
  const editorHeight = computed(() => {
@@ -2243,16 +2337,49 @@ onUnmounted(() => {
2243
2337
  })
2244
2338
 
2245
2339
  function setupTableEditButtons(doc: Document) {
2340
+ const previousEditHandler = (doc as any).__tableEditPointerHandler as ((event: PointerEvent) => void) | undefined
2341
+ if (previousEditHandler) {
2342
+ doc.removeEventListener('pointerdown', previousEditHandler, true)
2343
+ }
2344
+
2345
+ const tableEditPointerHandler = (event: PointerEvent) => {
2346
+ const target = event.target as Element | null
2347
+ const editButton = target?.closest('.table-edit-btn')
2348
+ if (!editButton) { return }
2349
+
2350
+ const table = editButton.closest('.table-wrapper')?.querySelector('table') as HTMLTableElement | null
2351
+ console.log('[RichText table editor] delegated pointerdown received', {
2352
+ pointerType: event.pointerType,
2353
+ hasTable: Boolean(table),
2354
+ })
2355
+ if (!table) { return }
2356
+
2357
+ event.preventDefault()
2358
+ event.stopPropagation()
2359
+ event.stopImmediatePropagation()
2360
+ globalThis.setTimeout(() => {
2361
+ try {
2362
+ openTableEditor(table)
2363
+ } catch (error) {
2364
+ console.error('[RichText table editor] delegated open failed', error)
2365
+ }
2366
+ }, 0)
2367
+ }
2368
+
2369
+ ;(doc as any).__tableEditPointerHandler = tableEditPointerHandler
2370
+ doc.addEventListener('pointerdown', tableEditPointerHandler, true)
2371
+
2246
2372
  // Simple function to add edit buttons to all tables
2247
2373
  function addEditButtonsToTables() {
2248
2374
  const tables = doc.querySelectorAll<HTMLTableElement>('table:not([data-edit-button-added])')
2249
2375
 
2250
- tables.forEach((table, index) => {
2376
+ tables.forEach((table) => {
2251
2377
  // Create edit button as a span element instead of button
2252
2378
  const editBtn = doc.createElement('span')
2253
2379
  editBtn.className = 'table-edit-btn'
2254
- editBtn.textContent = '✎ Edit'
2380
+ editBtn.textContent = '✎'
2255
2381
  editBtn.title = 'Edit Table'
2382
+ editBtn.setAttribute('aria-label', 'Edit Table')
2256
2383
 
2257
2384
  // Prevent any content insertion into button
2258
2385
  editBtn.setAttribute('contenteditable', 'false')
@@ -2261,10 +2388,10 @@ function setupTableEditButtons(doc: Document) {
2261
2388
  editBtn.style.userSelect = 'none'
2262
2389
 
2263
2390
  editBtn.addEventListener('click', (e) => {
2391
+ console.log('[RichText table editor] edit icon click received')
2264
2392
  e.preventDefault()
2265
2393
  e.stopPropagation()
2266
2394
  e.stopImmediatePropagation()
2267
- openTableEditor(table)
2268
2395
  })
2269
2396
 
2270
2397
  // Prevent focus and content insertion
@@ -2274,9 +2401,14 @@ function setupTableEditButtons(doc: Document) {
2274
2401
  editBtn.blur()
2275
2402
  })
2276
2403
 
2277
- editBtn.addEventListener('mousedown', (e) => {
2404
+ editBtn.addEventListener('pointerdown', (e) => {
2405
+ console.log('[RichText table editor] edit icon pointerdown received', {
2406
+ pointerType: e.pointerType,
2407
+ tableConnected: table.isConnected,
2408
+ })
2278
2409
  e.preventDefault()
2279
2410
  e.stopPropagation()
2411
+ e.stopImmediatePropagation()
2280
2412
  })
2281
2413
 
2282
2414
  // Create a wrapper div to contain both table and button
@@ -2549,7 +2681,10 @@ function setupAutoWrapping(doc: Document) {
2549
2681
  }
2550
2682
 
2551
2683
  // For RTL text, be more careful about splitting
2552
- const isRTLContent = RTL_REGEX.test(paragraph.textContent || '')
2684
+ const paragraphDirection = getTextDirection(
2685
+ paragraph.textContent || '',
2686
+ (doc.body.dir || 'ltr') as 'ltr' | 'rtl'
2687
+ )
2553
2688
 
2554
2689
  // Prevent default and handle manually to avoid browser inconsistencies
2555
2690
  e.preventDefault()
@@ -2565,7 +2700,7 @@ function setupAutoWrapping(doc: Document) {
2565
2700
 
2566
2701
  // Create new paragraph with proper direction
2567
2702
  const newParagraph = doc.createElement('p')
2568
- newParagraph.dir = isRTLContent ? 'rtl' : doc.body.dir || 'ltr'
2703
+ newParagraph.dir = paragraphDirection
2569
2704
 
2570
2705
  // If we're splitting text, handle it carefully
2571
2706
  if (range.startContainer.nodeType === Node.TEXT_NODE && textAfterCursor.trim()) {
@@ -2857,10 +2992,15 @@ async function initEditor() {
2857
2992
  background: transparent;
2858
2993
  max-width: 860px;
2859
2994
  margin: 0 auto;
2995
+ unicode-bidi: plaintext;
2996
+ }
2997
+
2998
+ p, h1, h2, h3, h4, h5, h6, blockquote, li {
2999
+ unicode-bidi: plaintext;
2860
3000
  }
2861
3001
 
2862
3002
  /* Ensure all text elements inherit the theme color */
2863
- p, h1, h2, h3, h4, h5, h6, div, span, li, td, th {
3003
+ p, h1, h2, h3, h4, h5, h6, div, span, li {
2864
3004
  color: inherit !important;
2865
3005
  }
2866
3006
  table {
@@ -2885,37 +3025,52 @@ async function initEditor() {
2885
3025
  position: absolute;
2886
3026
  top: -8px;
2887
3027
  right: -8px;
2888
- background: #007bff;
2889
- color: white;
2890
- border: none;
3028
+ background: #000;
3029
+ color: #fff !important;
3030
+ border: 1px solid #fff;
2891
3031
  border-radius: 4px;
2892
3032
  padding: 4px 8px;
2893
3033
  font-size: 12px;
2894
3034
  cursor: pointer;
2895
- opacity: 1;
3035
+ opacity: 0;
3036
+ visibility: hidden;
2896
3037
  z-index: 99;
2897
3038
  font-family: sans-serif;
2898
3039
  pointer-events: auto;
2899
3040
  user-select: none;
2900
3041
  display: block;
2901
3042
  line-height: 1;
3043
+ width: 28px;
3044
+ height: 28px;
3045
+ padding: 0;
3046
+ text-align: center;
3047
+ align-content: center;
2902
3048
  /* Prevent content insertion */
2903
3049
  contenteditable: false !important;
2904
3050
  outline: none !important;
3051
+ transition: opacity 0.15s ease, visibility 0.15s ease, background 0.15s ease;
3052
+ }
3053
+
3054
+ .table-wrapper:hover > .table-edit-btn,
3055
+ .table-wrapper:focus-within > .table-edit-btn,
3056
+ .table-edit-btn:hover,
3057
+ .table-edit-btn:focus {
3058
+ opacity: 1;
3059
+ visibility: visible;
2905
3060
  }
2906
3061
 
2907
3062
  .table-edit-btn:hover {
2908
- background: #0056b3;
3063
+ background: #1f1f1f;
2909
3064
  }
2910
3065
 
2911
3066
  /* Prevent any focus or content insertion */
2912
3067
  .table-edit-btn:focus {
2913
3068
  outline: none !important;
2914
- background: #007bff;
3069
+ background: #000;
2915
3070
  }
2916
3071
 
2917
3072
  .table-edit-btn:active {
2918
- background: #0056b3;
3073
+ background: #1f1f1f;
2919
3074
  }
2920
3075
  .richtext-editor-content blockquote {
2921
3076
  padding: 8px !important;
@@ -3086,6 +3241,16 @@ async function initEditor() {
3086
3241
 
3087
3242
  // Set default direction based on content
3088
3243
  doc.body.dir = hasRTL.value ? 'rtl' : 'ltr'
3244
+ doc.body.style.direction = doc.body.dir
3245
+
3246
+ // Resolve bidirectional text independently for each block. Using the
3247
+ // first strong character keeps English fragments stable inside Hebrew
3248
+ // text (and vice versa) without forcing one direction on the whole document.
3249
+ doc.body.querySelectorAll('p,h1,h2,h3,h4,h5,h6,blockquote,li').forEach((block) => {
3250
+ if (!block.classList.contains('placeholder')) {
3251
+ block.setAttribute('dir', getBlockDirection(block, doc.body.dir as 'ltr' | 'rtl'))
3252
+ }
3253
+ })
3089
3254
 
3090
3255
  // Apply direction to existing blockquotes and lists
3091
3256
  const blockElements = doc.body.querySelectorAll('blockquote, ul, ol')
@@ -3303,7 +3468,7 @@ function updateIframeColors() {
3303
3468
  }
3304
3469
 
3305
3470
  /* Ensure all text elements inherit the color and font size */
3306
- p, h1, h2, h3, h4, h5, h6, div, span, li, td, th {
3471
+ p, h1, h2, h3, h4, h5, h6, div, span, li {
3307
3472
  color: inherit !important;
3308
3473
  font-size: inherit !important;
3309
3474
  }
@@ -3348,9 +3513,6 @@ watch(() => props.modelValue, (newValue, oldValue) => {
3348
3513
 
3349
3514
  // Always emit clean, vanilla HTML — never the editor's internal DOM format
3350
3515
  watch(() => editor.state.content, () => {
3351
- if (editor.state.doc?.body) {
3352
- editor.state.doc.body.dir = hasRTL.value ? 'rtl' : 'ltr'
3353
- }
3354
3516
  emit('update:modelValue', getCleanHTML(editor.state.doc ?? null))
3355
3517
  })
3356
3518
 
@@ -3536,6 +3698,12 @@ const imgTransform = computed({
3536
3698
  </button>
3537
3699
  </div>
3538
3700
  <div class="px-025">
3701
+ <Btn
3702
+ full-width align-txt="start" thin icon="settings"
3703
+ class="context-menu-btn context-menu-btn--advanced" value="Advanced Settings"
3704
+ @mousedown.stop @click.stop="openExistingTableAdvancedSettings"
3705
+ />
3706
+ <div class="context-menu-separator" />
3539
3707
  <Btn
3540
3708
  v-if="canMergeRight" full-width align-txt="start" thin flat icon="start"
3541
3709
  class="context-menu-btn" @click="mergeCellRight(); showTableContextMenu = false"
@@ -3790,11 +3958,12 @@ const imgTransform = computed({
3790
3958
  </template>
3791
3959
  </Dialog>
3792
3960
 
3793
- <!-- Table Editor Modal -->
3794
- <Dialog
3795
- :open="showTableEditor" :title="pendingTableData?.existingTable ? 'Edit Table' : 'Insert Table'"
3796
- width="l" @update:open="showTableEditor = $event"
3797
- >
3961
+ <!-- Keep the table editor in the document top layer, including while the rich-text editor is fullscreen. -->
3962
+ <Teleport to="body">
3963
+ <Dialog
3964
+ :open="showTableEditor" :title="pendingTableData?.existingTable ? 'Edit Table' : 'Insert Table'"
3965
+ width="l" @update:open="showTableEditor = $event"
3966
+ >
3798
3967
  <template #default>
3799
3968
  <div class="grid grid-wrap-4 m_grid-wrap-2 gap-col-1 table-editor testMe1">
3800
3969
  <!-- Structure Section -->
@@ -3807,6 +3976,22 @@ const imgTransform = computed({
3807
3976
  </div>
3808
3977
  <NumberInput v-model="tableForm.rows" :min="1" :max="20" label="Rows" />
3809
3978
  <NumberInput v-model="tableForm.cols" :min="1" :max="10" label="Columns" />
3979
+ <div class="grid-span-4 m_grid-span-2">
3980
+ <label class="label">Column Widths (%)</label>
3981
+ <div class="grid grid-wrap-4 m_grid-wrap-2 gap-col-1 mt-025">
3982
+ <NumberInput
3983
+ v-for="(_, index) in tableForm.columnWidths.slice(0, tableForm.cols)"
3984
+ :key="index"
3985
+ v-model="tableForm.columnWidths[index]"
3986
+ :min="1"
3987
+ :max="100"
3988
+ :label="`Column ${index + 1}`"
3989
+ />
3990
+ </div>
3991
+ <p class="txt12 mt-025 mb-0" :class="{ 'color-red': Math.abs(columnWidthsTotal - 100) > 0.01 }">
3992
+ Total: {{ Number(columnWidthsTotal.toFixed(2)) }}%
3993
+ </p>
3994
+ </div>
3810
3995
  <!-- Cell Text Alignment -->
3811
3996
  <div class="grid-span-1">
3812
3997
  <label class="label">Cell Text Alignment</label>
@@ -3939,7 +4124,8 @@ const imgTransform = computed({
3939
4124
  @click="submitTable"
3940
4125
  />
3941
4126
  </template>
3942
- </Dialog>
4127
+ </Dialog>
4128
+ </Teleport>
3943
4129
  </template>
3944
4130
 
3945
4131
  <style>
@@ -4250,6 +4436,19 @@ margin: 4px 0;
4250
4436
  background-color: var(--hover-bg, #f5f5f5) !important;
4251
4437
  }
4252
4438
 
4439
+ .context-menu-btn--advanced,
4440
+ .context-menu-btn--advanced:hover,
4441
+ .context-menu-btn--advanced:focus-visible {
4442
+ background: #000 !important;
4443
+ border: 1px solid #fff !important;
4444
+ color: #fff !important;
4445
+ }
4446
+
4447
+ .context-menu-btn--advanced:hover,
4448
+ .context-menu-btn--advanced:focus-visible {
4449
+ background: #1f1f1f !important;
4450
+ }
4451
+
4253
4452
  /* Table Cell Selection */
4254
4453
  .table-cell-selected {
4255
4454
  background-color: rgba(0, 123, 255, 0.1) !important;
@@ -4283,10 +4482,14 @@ top: 5px !important;
4283
4482
  right: 5px !important;
4284
4483
  background: rgba(0, 0, 0, 0.7) !important;
4285
4484
  color: white !important;
4286
- padding: 4px 8px !important;
4485
+ -webkit-text-fill-color: white !important;
4486
+ padding: 0 !important;
4487
+ border: 1px solid white !important;
4287
4488
  border-radius: 4px !important;
4288
4489
  font-size: 12px !important;
4289
4490
  cursor: pointer !important;
4491
+ opacity: 0 !important;
4492
+ visibility: hidden !important;
4290
4493
  z-index: 100 !important;
4291
4494
  pointer-events: auto !important;
4292
4495
  user-select: none !important;
@@ -4294,16 +4497,15 @@ user-select: none !important;
4294
4497
  -moz-user-select: none !important;
4295
4498
  -ms-user-select: none !important;
4296
4499
  outline: none !important;
4297
- border: none !important;
4298
4500
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important;
4299
4501
  white-space: nowrap !important;
4300
4502
  overflow: hidden !important;
4301
4503
  text-overflow: clip !important;
4302
4504
  min-width: unset !important;
4303
4505
  max-width: unset !important;
4304
- width: auto !important;
4305
- height: auto !important;
4306
- line-height: normal !important;
4506
+ width: 28px !important;
4507
+ height: 28px !important;
4508
+ line-height: 26px !important;
4307
4509
  font-family: inherit !important;
4308
4510
  font-weight: normal !important;
4309
4511
  text-align: center !important;
@@ -4316,6 +4518,15 @@ text-indent: 0 !important;
4316
4518
  text-shadow: none !important;
4317
4519
  direction: ltr !important;
4318
4520
  unicode-bidi: normal !important;
4521
+ transition: opacity 0.15s ease, visibility 0.15s ease, background 0.15s ease !important;
4522
+ }
4523
+
4524
+ .table-wrapper:hover > .table-edit-btn,
4525
+ .table-wrapper:focus-within > .table-edit-btn,
4526
+ .table-edit-btn:hover,
4527
+ .table-edit-btn:focus {
4528
+ opacity: 1 !important;
4529
+ visibility: visible !important;
4319
4530
  }
4320
4531
 
4321
4532
  .table-edit-btn:hover {