Skip to content

Commit 28b75f9

Browse files
committed
Add Tab/Shift+Tab for indent/outdent in editor
- Tab indents current or selected lines by 4 spaces - Shift+Tab outdents (removes up to 4 spaces or 1 tab) - Selection is preserved for multi-level adjustments - Added to keyboard shortcuts panel
1 parent 7fb6d47 commit 28b75f9

File tree

1 file changed

+111
-0
lines changed

1 file changed

+111
-0
lines changed

assets/blocks/djot/index.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,8 +698,117 @@
698698
}
699699
}
700700

701+
// Indent/outdent handlers
702+
function indentLines() {
703+
var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
704+
if ( ! textarea ) return;
705+
706+
var text = content || '';
707+
var start = textarea.selectionStart;
708+
var end = textarea.selectionEnd;
709+
710+
// Find line boundaries
711+
var lineStart = start;
712+
while ( lineStart > 0 && text[ lineStart - 1 ] !== '\n' ) {
713+
lineStart--;
714+
}
715+
var lineEnd = end;
716+
while ( lineEnd < text.length && text[ lineEnd ] !== '\n' ) {
717+
lineEnd++;
718+
}
719+
720+
// Get selected lines and indent each
721+
var selectedText = text.substring( lineStart, lineEnd );
722+
var indentedText = selectedText.split( '\n' ).map( function( line ) {
723+
return ' ' + line;
724+
} ).join( '\n' );
725+
726+
var newText = text.substring( 0, lineStart ) + indentedText + text.substring( lineEnd );
727+
var lineCount = selectedText.split( '\n' ).length;
728+
var newStart = start + 4;
729+
var newEnd = end + ( lineCount * 4 );
730+
731+
setAttributes( { content: newText } );
732+
requestAnimationFrame( function() {
733+
textarea.focus( { preventScroll: true } );
734+
textarea.setSelectionRange( newStart, newEnd );
735+
} );
736+
}
737+
738+
function outdentLines() {
739+
var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
740+
if ( ! textarea ) return;
741+
742+
var text = content || '';
743+
var start = textarea.selectionStart;
744+
var end = textarea.selectionEnd;
745+
746+
// Find line boundaries
747+
var lineStart = start;
748+
while ( lineStart > 0 && text[ lineStart - 1 ] !== '\n' ) {
749+
lineStart--;
750+
}
751+
var lineEnd = end;
752+
while ( lineEnd < text.length && text[ lineEnd ] !== '\n' ) {
753+
lineEnd++;
754+
}
755+
756+
// Get selected lines and outdent each
757+
var selectedText = text.substring( lineStart, lineEnd );
758+
var removedTotal = 0;
759+
var removedFirst = 0;
760+
var isFirst = true;
761+
var outdentedText = selectedText.split( '\n' ).map( function( line ) {
762+
var removed = 0;
763+
// Remove up to 4 spaces or 1 tab
764+
if ( line.startsWith( ' ' ) ) {
765+
line = line.substring( 4 );
766+
removed = 4;
767+
} else if ( line.startsWith( '\t' ) ) {
768+
line = line.substring( 1 );
769+
removed = 1;
770+
} else if ( line.startsWith( ' ' ) ) {
771+
line = line.substring( 3 );
772+
removed = 3;
773+
} else if ( line.startsWith( ' ' ) ) {
774+
line = line.substring( 2 );
775+
removed = 2;
776+
} else if ( line.startsWith( ' ' ) ) {
777+
line = line.substring( 1 );
778+
removed = 1;
779+
}
780+
if ( isFirst ) {
781+
removedFirst = removed;
782+
isFirst = false;
783+
}
784+
removedTotal += removed;
785+
return line;
786+
} ).join( '\n' );
787+
788+
var newText = text.substring( 0, lineStart ) + outdentedText + text.substring( lineEnd );
789+
var newStart = Math.max( lineStart, start - removedFirst );
790+
var newEnd = Math.max( newStart, end - removedTotal );
791+
792+
setAttributes( { content: newText } );
793+
requestAnimationFrame( function() {
794+
textarea.focus( { preventScroll: true } );
795+
textarea.setSelectionRange( newStart, newEnd );
796+
} );
797+
}
798+
701799
// Keyboard shortcut handler for textarea
702800
function handleTextareaKeyDown( e ) {
801+
// Handle Tab/Shift+Tab for indent/outdent
802+
if ( e.key === 'Tab' ) {
803+
e.preventDefault();
804+
if ( e.shiftKey ) {
805+
outdentLines();
806+
} else {
807+
indentLines();
808+
}
809+
return;
810+
}
811+
703812
const isMod = e.ctrlKey || e.metaKey;
704813
if ( ! isMod ) return;
705814

@@ -1030,6 +1139,8 @@
10301139
wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+,' ), ' Subscript' ),
10311140
wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+Shift+H' ), ' Highlight' ),
10321141
wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+Shift+X' ), ' Strikethrough' ),
1142+
wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Tab' ), ' Indent' ),
1143+
wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Shift+Tab' ), ' Outdent' ),
10331144
wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'ESC' ), ' Exit preview' )
10341145
)
10351146
),

0 commit comments

Comments
 (0)