Skip to content

Commit 12f589c

Browse files
committed
Add context-aware Format Table button
- Shows "Format Table" button in toolbar when cursor is inside a table - Automatically aligns columns with proper padding - Preserves alignment markers (:--- :---: ---:) in separator row
1 parent 7233929 commit 12f589c

File tree

1 file changed

+126
-1
lines changed

1 file changed

+126
-1
lines changed

assets/blocks/djot/index.js

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@
7676
table: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
7777
wp.element.createElement( 'path', { d: 'M3 3v18h18V3H3zm8 16H5v-6h6v6zm0-8H5V5h6v6zm8 8h-6v-6h6v6zm0-8h-6V5h6v6z' } )
7878
),
79+
formatTable: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
80+
wp.element.createElement( 'path', { d: 'M3 3v18h18V3H3zm8 16H5v-6h6v6zm0-8H5V5h6v6zm8 8h-6v-6h6v6zm0-8h-6V5h6v6z' } ),
81+
wp.element.createElement( 'path', { d: 'M17 17l4 4m0-4l-4 4', stroke: 'currentColor', strokeWidth: 2, fill: 'none' } )
82+
),
7983
};
8084

8185
registerBlockType( 'wp-djot/djot', {
@@ -94,6 +98,7 @@
9498
const [ imageAlt, setImageAlt ] = useState( '' );
9599
const [ tableCols, setTableCols ] = useState( 3 );
96100
const [ tableRows, setTableRows ] = useState( 2 );
101+
const [ cursorInTable, setCursorInTable ] = useState( false );
97102
const textareaRef = useRef( null );
98103
const [ selectionStart, setSelectionStart ] = useState( 0 );
99104
const [ selectionEnd, setSelectionEnd ] = useState( 0 );
@@ -102,13 +107,27 @@
102107
className: 'wp-djot-block',
103108
} );
104109

105-
// Track selection in textarea
110+
// Track selection in textarea and check if in table
106111
function updateSelection() {
107112
if ( textareaRef.current ) {
108113
const textarea = textareaRef.current.querySelector( 'textarea' );
109114
if ( textarea ) {
110115
setSelectionStart( textarea.selectionStart );
111116
setSelectionEnd( textarea.selectionEnd );
117+
118+
// Check if cursor is in a table
119+
var text = content || '';
120+
var cursorPos = textarea.selectionStart;
121+
var lineStart = cursorPos;
122+
while ( lineStart > 0 && text[ lineStart - 1 ] !== '\n' ) {
123+
lineStart--;
124+
}
125+
var lineEnd = cursorPos;
126+
while ( lineEnd < text.length && text[ lineEnd ] !== '\n' ) {
127+
lineEnd++;
128+
}
129+
var currentLine = text.substring( lineStart, lineEnd ).trim();
130+
setCursorInTable( currentLine.startsWith( '|' ) && currentLine.endsWith( '|' ) );
112131
}
113132
}
114133
}
@@ -428,6 +447,107 @@
428447
setShowTableModal( false );
429448
}
430449

450+
// Format table at cursor position
451+
function onFormatTable() {
452+
var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
453+
if ( ! textarea ) return;
454+
455+
var text = content || '';
456+
var cursorPos = textarea.selectionStart;
457+
458+
// Find table boundaries
459+
var lines = text.split( '\n' );
460+
var lineIndex = 0;
461+
var charCount = 0;
462+
for ( var i = 0; i < lines.length; i++ ) {
463+
if ( charCount + lines[ i ].length >= cursorPos ) {
464+
lineIndex = i;
465+
break;
466+
}
467+
charCount += lines[ i ].length + 1; // +1 for newline
468+
}
469+
470+
// Find table start (go up until non-table line)
471+
var tableStart = lineIndex;
472+
while ( tableStart > 0 && lines[ tableStart - 1 ].trim().startsWith( '|' ) ) {
473+
tableStart--;
474+
}
475+
476+
// Find table end (go down until non-table line)
477+
var tableEnd = lineIndex;
478+
while ( tableEnd < lines.length - 1 && lines[ tableEnd + 1 ].trim().startsWith( '|' ) ) {
479+
tableEnd++;
480+
}
481+
482+
// Extract table lines
483+
var tableLines = lines.slice( tableStart, tableEnd + 1 );
484+
if ( tableLines.length < 2 ) return; // Need at least header + separator
485+
486+
// Parse cells
487+
var parsedRows = tableLines.map( function( line ) {
488+
// Remove leading/trailing pipes and split
489+
var trimmed = line.trim();
490+
if ( trimmed.startsWith( '|' ) ) trimmed = trimmed.substring( 1 );
491+
if ( trimmed.endsWith( '|' ) ) trimmed = trimmed.substring( 0, trimmed.length - 1 );
492+
return trimmed.split( '|' ).map( function( cell ) {
493+
return cell.trim();
494+
} );
495+
} );
496+
497+
// Find max width per column
498+
var colWidths = [];
499+
parsedRows.forEach( function( row, rowIdx ) {
500+
row.forEach( function( cell, colIdx ) {
501+
// Skip separator row for width calculation (use dashes count)
502+
var width = cell.length;
503+
if ( rowIdx === 1 && /^[-:]+$/.test( cell ) ) {
504+
width = 3; // minimum for separator
505+
}
506+
if ( ! colWidths[ colIdx ] || width > colWidths[ colIdx ] ) {
507+
colWidths[ colIdx ] = Math.max( width, 3 );
508+
}
509+
} );
510+
} );
511+
512+
// Rebuild table with padding
513+
var formattedLines = parsedRows.map( function( row, rowIdx ) {
514+
var cells = row.map( function( cell, colIdx ) {
515+
var width = colWidths[ colIdx ] || 3;
516+
if ( rowIdx === 1 && /^[-:]+$/.test( cell ) ) {
517+
// Separator row - preserve alignment markers
518+
var leftAlign = cell.startsWith( ':' );
519+
var rightAlign = cell.endsWith( ':' );
520+
var dashes = '-'.repeat( width );
521+
if ( leftAlign && rightAlign ) {
522+
return ':' + '-'.repeat( width - 2 ) + ':';
523+
} else if ( leftAlign ) {
524+
return ':' + '-'.repeat( width - 1 );
525+
} else if ( rightAlign ) {
526+
return '-'.repeat( width - 1 ) + ':';
527+
}
528+
return dashes;
529+
}
530+
// Pad cell with spaces
531+
return cell + ' '.repeat( width - cell.length );
532+
} );
533+
return '| ' + cells.join( ' | ' ) + ' |';
534+
} );
535+
536+
// Replace table in content
537+
var newLines = lines.slice( 0, tableStart ).concat( formattedLines ).concat( lines.slice( tableEnd + 1 ) );
538+
var newText = newLines.join( '\n' );
539+
540+
// Calculate new cursor position (keep it roughly in same place)
541+
var newCursorPos = 0;
542+
for ( var i = 0; i < tableStart; i++ ) {
543+
newCursorPos += newLines[ i ].length + 1;
544+
}
545+
newCursorPos += formattedLines[ 0 ].length; // Put cursor at end of first table line
546+
547+
setAttributes( { content: newText } );
548+
restoreFocus( textarea, newCursorPos );
549+
}
550+
431551
// Keyboard shortcut handler for textarea
432552
function handleTextareaKeyDown( e ) {
433553
const isMod = e.ctrlKey || e.metaKey;
@@ -677,6 +797,11 @@
677797
label: __( 'Table', 'wp-djot' ),
678798
onClick: onTable,
679799
} ),
800+
cursorInTable && wp.element.createElement( ToolbarButton, {
801+
icon: icons.formatTable,
802+
label: __( 'Format Table', 'wp-djot' ),
803+
onClick: onFormatTable,
804+
} ),
680805
wp.element.createElement( ToolbarButton, {
681806
icon: icons.div,
682807
label: __( 'Div Block (::: class)', 'wp-djot' ),

0 commit comments

Comments
 (0)