@@ -735,6 +735,67 @@ function deleteTableColumn(table, colIdx) {
735735 } ) ;
736736}
737737
738+ // Module-level clipboard for table row/column copy-paste.
739+ // Stores the inner HTML of each cell so they can be re-inserted into other rows/cols.
740+ let _tableRowClipboard = null ; // { cells: [html, ...] }
741+ let _tableColClipboard = null ; // { cells: [html, ...] } one per row in source table
742+
743+ function copyTableRow ( tr ) {
744+ _tableRowClipboard = {
745+ cells : Array . from ( tr . children ) . map ( td => td . innerHTML )
746+ } ;
747+ }
748+
749+ function copyTableColumn ( table , colIdx ) {
750+ const rows = table . querySelectorAll ( "tr" ) ;
751+ _tableColClipboard = {
752+ cells : Array . from ( rows ) . map ( row => {
753+ const cell = row . children [ colIdx ] ;
754+ return cell ? cell . innerHTML : "" ;
755+ } )
756+ } ;
757+ }
758+
759+ function pasteTableRow ( table , afterRow ) {
760+ if ( ! _tableRowClipboard ) return ;
761+ const tbody = table . querySelector ( "tbody" ) || table ;
762+ const refRow = afterRow || tbody . lastElementChild ;
763+ const colCount = refRow ? refRow . children . length : _tableRowClipboard . cells . length ;
764+ const newRow = document . createElement ( "tr" ) ;
765+ for ( let i = 0 ; i < colCount ; i ++ ) {
766+ const td = document . createElement ( "td" ) ;
767+ td . innerHTML = _tableRowClipboard . cells [ i ] != null ? _tableRowClipboard . cells [ i ] : " " ;
768+ newRow . appendChild ( td ) ;
769+ }
770+ if ( afterRow && afterRow . nextSibling ) {
771+ afterRow . parentNode . insertBefore ( newRow , afterRow . nextSibling ) ;
772+ } else if ( afterRow ) {
773+ afterRow . parentNode . appendChild ( newRow ) ;
774+ } else {
775+ tbody . appendChild ( newRow ) ;
776+ }
777+ focusCell ( newRow . firstElementChild ) ;
778+ return newRow ;
779+ }
780+
781+ function pasteTableColumn ( table , afterColIdx ) {
782+ if ( ! _tableColClipboard ) return ;
783+ const rows = table . querySelectorAll ( "tr" ) ;
784+ const insertIdx = afterColIdx != null ? afterColIdx + 1 : ( rows [ 0 ] ?. children . length || 0 ) ;
785+ rows . forEach ( ( row , rowIdx ) => {
786+ const isHeader = row . parentElement . tagName === "THEAD" ;
787+ const cell = document . createElement ( isHeader ? "th" : "td" ) ;
788+ const html = _tableColClipboard . cells [ rowIdx ] ;
789+ cell . innerHTML = ( html != null && html !== "" ) ? html : ( isHeader ? t ( "table.header" ) : " " ) ;
790+ const refCell = row . children [ insertIdx ] ;
791+ if ( refCell ) {
792+ row . insertBefore ( cell , refCell ) ;
793+ } else {
794+ row . appendChild ( cell ) ;
795+ }
796+ } ) ;
797+ }
798+
738799function deleteTable ( table ) {
739800 const wrapper = table . closest ( ".table-wrapper" ) ;
740801 const target = wrapper || table ;
@@ -858,6 +919,10 @@ function showHandleMenu(anchor, type, ctx, contentEl, wrapper, clickX) {
858919 { label : t ( "table.insert_row_above" ) , action : ( ) => { flushSnapshot ( contentEl ) ; addTableRow ( ctx . table , null , ctx . tr ) ; dispatchInputEvent ( contentEl ) ; } } ,
859920 { label : t ( "table.insert_row_below" ) , action : ( ) => { flushSnapshot ( contentEl ) ; addTableRow ( ctx . table , ctx . tr ) ; dispatchInputEvent ( contentEl ) ; } } ,
860921 { divider : true } ,
922+ { label : t ( "table.copy_row" ) , action : ( ) => { copyTableRow ( ctx . tr ) ; } } ,
923+ { label : t ( "table.cut_row" ) , disabled : ctx . isHeader , action : ( ) => { flushSnapshot ( contentEl ) ; copyTableRow ( ctx . tr ) ; deleteTableRow ( ctx . table , ctx . tr ) ; dispatchInputEvent ( contentEl ) ; } } ,
924+ ...( _tableRowClipboard ? [ { label : t ( "table.paste_row" ) , action : ( ) => { flushSnapshot ( contentEl ) ; pasteTableRow ( ctx . table , ctx . tr ) ; dispatchInputEvent ( contentEl ) ; } } ] : [ ] ) ,
925+ { divider : true } ,
861926 { label : t ( "table.delete_row" ) , destructive : true , disabled : ctx . isHeader , action : ( ) => { flushSnapshot ( contentEl ) ; deleteTableRow ( ctx . table , ctx . tr ) ; dispatchInputEvent ( contentEl ) ; } } ,
862927 { divider : true } ,
863928 { label : t ( "table.delete_table" ) , destructive : true , action : ( ) => { flushSnapshot ( contentEl ) ; deleteTable ( ctx . table ) ; dispatchInputEvent ( contentEl ) ; } }
@@ -867,6 +932,10 @@ function showHandleMenu(anchor, type, ctx, contentEl, wrapper, clickX) {
867932 { label : t ( "table.insert_col_left" ) , action : ( ) => { flushSnapshot ( contentEl ) ; addTableColumn ( ctx . table , ctx . colIdx - 1 ) ; dispatchInputEvent ( contentEl ) ; } } ,
868933 { label : t ( "table.insert_col_right" ) , action : ( ) => { flushSnapshot ( contentEl ) ; addTableColumn ( ctx . table , ctx . colIdx ) ; dispatchInputEvent ( contentEl ) ; } } ,
869934 { divider : true } ,
935+ { label : t ( "table.copy_col" ) , action : ( ) => { copyTableColumn ( ctx . table , ctx . colIdx ) ; } } ,
936+ { label : t ( "table.cut_col" ) , action : ( ) => { flushSnapshot ( contentEl ) ; copyTableColumn ( ctx . table , ctx . colIdx ) ; deleteTableColumn ( ctx . table , ctx . colIdx ) ; dispatchInputEvent ( contentEl ) ; } } ,
937+ ...( _tableColClipboard ? [ { label : t ( "table.paste_col" ) , action : ( ) => { flushSnapshot ( contentEl ) ; pasteTableColumn ( ctx . table , ctx . colIdx ) ; dispatchInputEvent ( contentEl ) ; } } ] : [ ] ) ,
938+ { divider : true } ,
870939 { label : t ( "table.delete_col" ) , destructive : true , action : ( ) => { flushSnapshot ( contentEl ) ; deleteTableColumn ( ctx . table , ctx . colIdx ) ; dispatchInputEvent ( contentEl ) ; } } ,
871940 { divider : true } ,
872941 { label : t ( "table.delete_table" ) , destructive : true , action : ( ) => { flushSnapshot ( contentEl ) ; deleteTable ( ctx . table ) ; dispatchInputEvent ( contentEl ) ; } }
@@ -1091,6 +1160,14 @@ function showTableContextMenu(x, y, ctx, contentEl) {
10911160 { label : t ( "table.add_col_left" ) , action : ( ) => { flushSnapshot ( contentEl ) ; addTableColumn ( ctx . table , ctx . colIdx - 1 ) ; dispatchInputEvent ( contentEl ) ; } } ,
10921161 { label : t ( "table.add_col_right" ) , action : ( ) => { flushSnapshot ( contentEl ) ; addTableColumn ( ctx . table , ctx . colIdx ) ; dispatchInputEvent ( contentEl ) ; } } ,
10931162 { divider : true } ,
1163+ { label : t ( "table.copy_row" ) , action : ( ) => { copyTableRow ( ctx . tr ) ; } } ,
1164+ { label : t ( "table.cut_row" ) , action : ( ) => { flushSnapshot ( contentEl ) ; copyTableRow ( ctx . tr ) ; deleteTableRow ( ctx . table , ctx . tr ) ; dispatchInputEvent ( contentEl ) ; } } ,
1165+ ...( _tableRowClipboard ? [ { label : t ( "table.paste_row" ) , action : ( ) => { flushSnapshot ( contentEl ) ; pasteTableRow ( ctx . table , ctx . tr ) ; dispatchInputEvent ( contentEl ) ; } } ] : [ ] ) ,
1166+ { divider : true } ,
1167+ { label : t ( "table.copy_col" ) , action : ( ) => { copyTableColumn ( ctx . table , ctx . colIdx ) ; } } ,
1168+ { label : t ( "table.cut_col" ) , action : ( ) => { flushSnapshot ( contentEl ) ; copyTableColumn ( ctx . table , ctx . colIdx ) ; deleteTableColumn ( ctx . table , ctx . colIdx ) ; dispatchInputEvent ( contentEl ) ; } } ,
1169+ ...( _tableColClipboard ? [ { label : t ( "table.paste_col" ) , action : ( ) => { flushSnapshot ( contentEl ) ; pasteTableColumn ( ctx . table , ctx . colIdx ) ; dispatchInputEvent ( contentEl ) ; } } ] : [ ] ) ,
1170+ { divider : true } ,
10941171 { label : t ( "table.delete_row" ) , destructive : true , action : ( ) => { flushSnapshot ( contentEl ) ; deleteTableRow ( ctx . table , ctx . tr ) ; dispatchInputEvent ( contentEl ) ; } } ,
10951172 { label : t ( "table.delete_col" ) , destructive : true , action : ( ) => { flushSnapshot ( contentEl ) ; deleteTableColumn ( ctx . table , ctx . colIdx ) ; dispatchInputEvent ( contentEl ) ; } } ,
10961173 { divider : true } ,
0 commit comments