@@ -79,6 +79,7 @@ type Style = {
7979 replaceNext ?: string
8080 scanFor ?: string
8181 orderedList ?: boolean
82+ unorderedList ?: boolean
8283 prefixSpace ?: boolean
8384}
8485
@@ -205,7 +206,7 @@ if (!window.customElements.get('md-image')) {
205206class MarkdownUnorderedListButtonElement extends MarkdownButtonElement {
206207 constructor ( ) {
207208 super ( )
208- styles . set ( this , { prefix : '- ' , multiline : true , surroundWithNewlines : true } )
209+ styles . set ( this , { prefix : '- ' , multiline : true , unorderedList : true } )
209210 }
210211}
211212
@@ -421,8 +422,8 @@ function styleSelectedText(textarea: HTMLTextAreaElement, styleArgs: StyleArgs)
421422 const text = textarea . value . slice ( textarea . selectionStart , textarea . selectionEnd )
422423
423424 let result
424- if ( styleArgs . orderedList ) {
425- result = orderedList ( textarea )
425+ if ( styleArgs . orderedList || styleArgs . unorderedList ) {
426+ result = listStyle ( textarea , styleArgs )
426427 } else if ( styleArgs . multiline && isMultipleLines ( text ) ) {
427428 result = multilineStyle ( textarea , styleArgs )
428429 } else {
@@ -432,6 +433,21 @@ function styleSelectedText(textarea: HTMLTextAreaElement, styleArgs: StyleArgs)
432433 insertText ( textarea , result )
433434}
434435
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+
435451function expandSelectedText (
436452 textarea : HTMLTextAreaElement ,
437453 prefixToUse : string ,
@@ -587,41 +603,115 @@ function multilineStyle(textarea: HTMLTextAreaElement, arg: StyleArgs) {
587603 return { text, selectionStart, selectionEnd}
588604}
589605
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' )
591612 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 {
592665 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
616697 }
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
617707 } 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+ }
625715 }
626716
627717 return { text, selectionStart, selectionEnd}
@@ -638,21 +728,10 @@ interface StyleArgs {
638728 scanFor : string
639729 surroundWithNewlines : boolean
640730 orderedList : boolean
731+ unorderedList : boolean
641732 trimFirst : boolean
642733}
643734
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-
656735function applyStyle ( button : Element , stylesToApply : Style ) {
657736 const toolbar = button . closest ( 'markdown-toolbar' )
658737 if ( ! ( toolbar instanceof MarkdownToolbarElement ) ) return
@@ -668,6 +747,7 @@ function applyStyle(button: Element, stylesToApply: Style) {
668747 scanFor : '' ,
669748 surroundWithNewlines : false ,
670749 orderedList : false ,
750+ unorderedList : false ,
671751 trimFirst : false
672752 }
673753
0 commit comments