@@ -79,6 +79,7 @@ type Style = {
79
79
replaceNext ?: string
80
80
scanFor ?: string
81
81
orderedList ?: boolean
82
+ unorderedList ?: boolean
82
83
prefixSpace ?: boolean
83
84
}
84
85
@@ -205,7 +206,7 @@ if (!window.customElements.get('md-image')) {
205
206
class MarkdownUnorderedListButtonElement extends MarkdownButtonElement {
206
207
constructor ( ) {
207
208
super ( )
208
- styles . set ( this , { prefix : '- ' , multiline : true , surroundWithNewlines : true } )
209
+ styles . set ( this , { prefix : '- ' , multiline : true , unorderedList : true } )
209
210
}
210
211
}
211
212
@@ -421,8 +422,8 @@ function styleSelectedText(textarea: HTMLTextAreaElement, styleArgs: StyleArgs)
421
422
const text = textarea . value . slice ( textarea . selectionStart , textarea . selectionEnd )
422
423
423
424
let result
424
- if ( styleArgs . orderedList ) {
425
- result = orderedList ( textarea )
425
+ if ( styleArgs . orderedList || styleArgs . unorderedList ) {
426
+ result = listStyle ( textarea , styleArgs )
426
427
} else if ( styleArgs . multiline && isMultipleLines ( text ) ) {
427
428
result = multilineStyle ( textarea , styleArgs )
428
429
} else {
@@ -432,6 +433,21 @@ function styleSelectedText(textarea: HTMLTextAreaElement, styleArgs: StyleArgs)
432
433
insertText ( textarea , result )
433
434
}
434
435
436
+ function expandSelectionToLine ( textarea : HTMLTextAreaElement ) {
437
+ const lines = textarea . value . split ( '\n' )
438
+ let counter = 0
439
+ for ( let index = 0 ; index < lines . length ; index ++ ) {
440
+ const lineLength = lines [ index ] . length + 1
441
+ if ( textarea . selectionStart >= counter && textarea . selectionStart < counter + lineLength ) {
442
+ textarea . selectionStart = counter
443
+ }
444
+ if ( textarea . selectionEnd >= counter && textarea . selectionEnd < counter + lineLength ) {
445
+ textarea . selectionEnd = counter + lineLength - 1
446
+ }
447
+ counter += lineLength
448
+ }
449
+ }
450
+
435
451
function expandSelectedText (
436
452
textarea : HTMLTextAreaElement ,
437
453
prefixToUse : string ,
@@ -587,41 +603,115 @@ function multilineStyle(textarea: HTMLTextAreaElement, arg: StyleArgs) {
587
603
return { text, selectionStart, selectionEnd}
588
604
}
589
605
590
- function orderedList ( textarea : HTMLTextAreaElement ) : SelectionRange {
606
+ interface UndoResult {
607
+ text : string
608
+ processed : boolean
609
+ }
610
+ function undoOrderedListStyle ( text : string ) : UndoResult {
611
+ const lines = text . split ( '\n' )
591
612
const orderedListRegex = / ^ \d + \. \s + /
613
+ const shouldUndoOrderedList = lines . every ( line => orderedListRegex . test ( line ) )
614
+ let result = lines
615
+ if ( shouldUndoOrderedList ) {
616
+ result = lines . map ( line => line . replace ( orderedListRegex , '' ) )
617
+ }
618
+
619
+ return {
620
+ text : result . join ( '\n' ) ,
621
+ processed : shouldUndoOrderedList
622
+ }
623
+ }
624
+
625
+ function undoUnorderedListStyle ( text : string ) : UndoResult {
626
+ const lines = text . split ( '\n' )
627
+ const unorderedListPrefix = '- '
628
+ const shouldUndoUnorderedList = lines . every ( line => line . startsWith ( unorderedListPrefix ) )
629
+ let result = lines
630
+ if ( shouldUndoUnorderedList ) {
631
+ result = lines . map ( line => line . slice ( unorderedListPrefix . length , line . length ) )
632
+ }
633
+
634
+ return {
635
+ text : result . join ( '\n' ) ,
636
+ processed : shouldUndoUnorderedList
637
+ }
638
+ }
639
+
640
+ function makePrefix ( index : number , unorderedList : boolean ) : string {
641
+ if ( unorderedList ) {
642
+ return '- '
643
+ } else {
644
+ return `${ index + 1 } . `
645
+ }
646
+ }
647
+
648
+ function clearExistingListStyle ( style : StyleArgs , selectedText : string ) : [ UndoResult , UndoResult , string ] {
649
+ let undoResultOpositeList : UndoResult
650
+ let undoResult : UndoResult
651
+ let pristineText
652
+ if ( style . orderedList ) {
653
+ undoResult = undoOrderedListStyle ( selectedText )
654
+ undoResultOpositeList = undoUnorderedListStyle ( undoResult . text )
655
+ pristineText = undoResultOpositeList . text
656
+ } else {
657
+ undoResult = undoUnorderedListStyle ( selectedText )
658
+ undoResultOpositeList = undoOrderedListStyle ( undoResult . text )
659
+ pristineText = undoResultOpositeList . text
660
+ }
661
+ return [ undoResult , undoResultOpositeList , pristineText ]
662
+ }
663
+
664
+ function listStyle ( textarea : HTMLTextAreaElement , style : StyleArgs ) : SelectionRange {
592
665
const noInitialSelection = textarea . selectionStart === textarea . selectionEnd
593
- let selectionEnd
594
- let selectionStart
595
- let text = textarea . value . slice ( textarea . selectionStart , textarea . selectionEnd )
596
- let textToUnstyle = text
597
- let lines = text . split ( '\n' )
598
- let startOfLine , endOfLine
599
- if ( noInitialSelection ) {
600
- const linesBefore = textarea . value . slice ( 0 , textarea . selectionStart ) . split ( / \n / )
601
- startOfLine = textarea . selectionStart - linesBefore [ linesBefore . length - 1 ] . length
602
- endOfLine = wordSelectionEnd ( textarea . value , textarea . selectionStart , true )
603
- textToUnstyle = textarea . value . slice ( startOfLine , endOfLine )
604
- }
605
- const linesToUnstyle = textToUnstyle . split ( '\n' )
606
- const undoStyling = linesToUnstyle . every ( line => orderedListRegex . test ( line ) )
607
-
608
- if ( undoStyling ) {
609
- lines = linesToUnstyle . map ( line => line . replace ( orderedListRegex , '' ) )
610
- text = lines . join ( '\n' )
611
- if ( noInitialSelection && startOfLine && endOfLine ) {
612
- const lengthDiff = linesToUnstyle [ 0 ] . length - lines [ 0 ] . length
613
- selectionStart = selectionEnd = textarea . selectionStart - lengthDiff
614
- textarea . selectionStart = startOfLine
615
- textarea . selectionEnd = endOfLine
666
+ let selectionStart = textarea . selectionStart
667
+ let selectionEnd = textarea . selectionEnd
668
+
669
+ // Select whole line
670
+ expandSelectionToLine ( textarea )
671
+
672
+ const selectedText = textarea . value . slice ( textarea . selectionStart , textarea . selectionEnd )
673
+
674
+ // If the user intent was to do an undo, we will stop after this.
675
+ // Otherwise, we will still undo to other list type to prevent list stacking
676
+ const [ undoResult , undoResultOpositeList , pristineText ] = clearExistingListStyle ( style , selectedText )
677
+
678
+ const prefixedLines = pristineText . split ( '\n' ) . map ( ( value , index ) => {
679
+ return `${ makePrefix ( index , style . unorderedList ) } ${ value } `
680
+ } )
681
+
682
+ const totalPrefixLength = prefixedLines . reduce ( ( previousValue , _currentValue , currentIndex ) => {
683
+ return previousValue + makePrefix ( currentIndex , style . unorderedList ) . length
684
+ } , 0 )
685
+
686
+ const totalPrefixLengthOpositeList = prefixedLines . reduce ( ( previousValue , _currentValue , currentIndex ) => {
687
+ return previousValue + makePrefix ( currentIndex , ! style . unorderedList ) . length
688
+ } , 0 )
689
+
690
+ if ( undoResult . processed ) {
691
+ if ( noInitialSelection ) {
692
+ selectionStart = Math . max ( selectionStart - makePrefix ( 0 , style . unorderedList ) . length , 0 )
693
+ selectionEnd = selectionStart
694
+ } else {
695
+ selectionStart = textarea . selectionStart
696
+ selectionEnd = textarea . selectionEnd - totalPrefixLength
616
697
}
698
+ return { text : pristineText , selectionStart, selectionEnd}
699
+ }
700
+
701
+ const { newlinesToAppend, newlinesToPrepend} = newlinesToSurroundSelectedText ( textarea )
702
+ const text = newlinesToAppend + prefixedLines . join ( '\n' ) + newlinesToPrepend
703
+
704
+ if ( noInitialSelection ) {
705
+ selectionStart = Math . max ( selectionStart + makePrefix ( 0 , style . unorderedList ) . length + newlinesToAppend . length , 0 )
706
+ selectionEnd = selectionStart
617
707
} else {
618
- lines = numberedLines ( lines )
619
- text = lines . join ( '\n' )
620
- const { newlinesToAppend , newlinesToPrepend } = newlinesToSurroundSelectedText ( textarea )
621
- selectionStart = textarea . selectionStart + newlinesToAppend . length
622
- selectionEnd = selectionStart + text . length
623
- if ( noInitialSelection ) selectionStart = selectionEnd
624
- text = newlinesToAppend + text + newlinesToPrepend
708
+ if ( undoResultOpositeList . processed ) {
709
+ selectionStart = Math . max ( textarea . selectionStart + newlinesToAppend . length , 0 )
710
+ selectionEnd = textarea . selectionEnd + newlinesToAppend . length + totalPrefixLength - totalPrefixLengthOpositeList
711
+ } else {
712
+ selectionStart = Math . max ( textarea . selectionStart + newlinesToAppend . length , 0 )
713
+ selectionEnd = textarea . selectionEnd + newlinesToAppend . length + totalPrefixLength
714
+ }
625
715
}
626
716
627
717
return { text, selectionStart, selectionEnd}
@@ -638,21 +728,10 @@ interface StyleArgs {
638
728
scanFor : string
639
729
surroundWithNewlines : boolean
640
730
orderedList : boolean
731
+ unorderedList : boolean
641
732
trimFirst : boolean
642
733
}
643
734
644
- function numberedLines ( lines : string [ ] ) {
645
- let i
646
- let len
647
- let index
648
- const results = [ ]
649
- for ( index = i = 0 , len = lines . length ; i < len ; index = ++ i ) {
650
- const line = lines [ index ]
651
- results . push ( `${ index + 1 } . ${ line } ` )
652
- }
653
- return results
654
- }
655
-
656
735
function applyStyle ( button : Element , stylesToApply : Style ) {
657
736
const toolbar = button . closest ( 'markdown-toolbar' )
658
737
if ( ! ( toolbar instanceof MarkdownToolbarElement ) ) return
@@ -668,6 +747,7 @@ function applyStyle(button: Element, stylesToApply: Style) {
668
747
scanFor : '' ,
669
748
surroundWithNewlines : false ,
670
749
orderedList : false ,
750
+ unorderedList : false ,
671
751
trimFirst : false
672
752
}
673
753
0 commit comments