1- import { type ChangeSpec , EditorSelection } from "@codemirror/state" ;
1+ import { type ChangeSpec , EditorSelection , Line } from "@codemirror/state" ;
22import { EditorView } from "codemirror" ;
33
44import { ValidIconName } from "../../../../components/Icon/canonicalIconNames" ;
@@ -27,9 +27,11 @@ type formatConfig = { start: number; startDelimiter: string; stop?: number; endD
2727type headerLevels = 1 | 2 | 3 | 4 | 5 | 6 ;
2828type ListType = "ul" | "ol" | "todo" ;
2929
30+ //contains all utilities for markdown toolbar
3031export default class MarkdownCommand {
3132 private view : EditorView | null = null ;
3233
34+ //list of supported commands as well as the valid icon names.
3335 public static commands = {
3436 paragraphs : [
3537 Commands . header1 ,
@@ -62,81 +64,71 @@ export default class MarkdownCommand {
6264 this . view = view ;
6365 }
6466
67+ /**
68+ * Supported list types are ol, ul, todo.
69+ * utility helps to determine which at the start of the line
70+ */
6571 private getListTypeOfLine = ( text : string ) : [ ListType , number ?] | undefined => {
66- text = text && text . trimStart ( ) ;
67-
68- if ( ! text ) {
69- return undefined ;
70- }
72+ if ( ! text ) return ;
73+ text = text ?. trimStart ( ) ;
7174
7275 if ( text . startsWith ( "- " ) ) {
73- if ( text . startsWith ( "- [ ] " ) || text . startsWith ( "- [x] " ) ) {
74- return [ "todo" ] ;
75- }
76-
76+ if ( text . startsWith ( "- [ ] " ) || text . startsWith ( "- [x] " ) ) return [ "todo" ] ;
7777 return [ "ul" ] ;
7878 }
7979
8080 const v = text . match ( / ^ ( \d + ) \. / ) ;
8181
82- if ( v ) {
83- return [ "ol" , Number . parseInt ( v [ 1 ] , 10 ) ] ;
84- }
85-
86- return undefined ;
82+ return v ? [ "ol" , Number . parseInt ( v [ 1 ] , 10 ) ] : undefined ;
8783 } ;
8884
85+ //inserts the list delimiters of "-", "- [ ]" and "{number}."
86+ private createListDelimiter ( text : string , type : string , orderedList : { currentIndex : number } ) {
87+ return text . replace ( / ^ ( ( * ) ( - ( \[ [ x ] ] ) ? | \d + \. ) ) ? / , ( ...args ) => {
88+ const { space = "" } = args [ args . length - 1 ] ;
89+
90+ let newFlag = "- " ;
91+
92+ if ( type === "ol" ) {
93+ newFlag = `${ orderedList . currentIndex } . ` ;
94+ orderedList . currentIndex ++ ;
95+ } else if ( type === "todo" ) {
96+ newFlag = "- [ ] " ;
97+ }
98+
99+ return space + newFlag ;
100+ } ) ;
101+ }
102+
103+ //factory for different list types.
89104 private createList = ( type : ListType ) => {
90105 if ( ! this . view ) return ;
91106 const view = this . view ;
92- const { state } = view ;
93-
94- const { doc } = state ;
107+ const doc = view . state . doc ;
95108
96- let olIndex = 1 ;
109+ const orderedList = { currentIndex : 1 } ;
97110
98111 view . dispatch (
99112 view . state . changeByRange ( ( range ) => {
100- const startLine = doc . lineAt ( range . from ) ;
101-
102113 const text = doc . slice ( range . from , range . to ) ;
103-
104- const lineCount = text . lines ;
105-
106114 const changes : ChangeSpec [ ] = [ ] ;
107115
108116 let selectionStart : number = range . from ;
109117 let selectionLength : number = range . to - range . from ;
110118
111- new Array ( lineCount ) . fill ( 0 ) . forEach ( ( _ , index ) => {
112- const line = doc . line ( startLine . number + index ) ;
119+ Array . from ( { length : text . lines } ) . forEach ( ( _ , index ) => {
120+ const line = doc . line ( doc . lineAt ( range . from ) . number + index ) ;
113121
114122 const currentListType = this . getListTypeOfLine ( line . text ) ;
115123
116124 if ( currentListType && currentListType [ 0 ] === type ) {
117125 if ( currentListType [ 0 ] === "ol" && currentListType [ 1 ] ) {
118- olIndex = currentListType [ 1 ] ;
126+ orderedList . currentIndex = currentListType [ 1 ] ;
119127 }
120128
121129 return ;
122130 }
123-
124- const content = line . text . replace ( / ^ ( ( * ) ( - ( \[ [ x ] ] ) ? | \d + \. ) ) ? / , ( ...args ) => {
125- const params = args [ args . length - 1 ] ;
126-
127- const { space = "" } = params ;
128-
129- let newFlag = "- " ;
130-
131- if ( type === "ol" ) {
132- newFlag = `${ olIndex } . ` ;
133- olIndex ++ ;
134- } else if ( type === "todo" ) {
135- newFlag = "- [ ] " ;
136- }
137-
138- return space + newFlag ;
139- } ) ;
131+ const content = this . createListDelimiter ( line . text , type , orderedList ) ;
140132
141133 const diffLength = content . length - line . length ;
142134
@@ -163,16 +155,29 @@ export default class MarkdownCommand {
163155 view . focus ( ) ;
164156 } ;
165157
158+ private enforceCursorFocus = ( cursorPosition : number ) => {
159+ if ( ! this . view ) return ;
160+ const view = this . view ;
161+ setTimeout ( ( ) => {
162+ view . dispatch ( {
163+ selection : EditorSelection . cursor ( cursorPosition ) ,
164+ } ) ;
165+ view . focus ( ) ;
166+ } , 50 ) ;
167+ } ;
168+
169+ //supported headers from h1-h6, h6 being the smallest
166170 private createHeading = ( level : headerLevels ) => {
167171 if ( ! this . view ) return ;
168172 const view = this . view ;
169173 const state = view . state ;
170174
171175 const flags = "#" . repeat ( level ) + " " ;
176+ let line = new Line ( ) ;
172177
173178 view . dispatch (
174179 state . changeByRange ( ( range ) => {
175- const line = state . doc . lineAt ( range . from ) ;
180+ line = state . doc . lineAt ( range . from ) ;
176181
177182 const content = line . text . replace ( / ^ ( ( # + ) ) ? / , flags ) ;
178183
@@ -188,8 +193,7 @@ export default class MarkdownCommand {
188193 } ;
189194 } )
190195 ) ;
191-
192- view . focus ( ) ;
196+ this . enforceCursorFocus ( line . length + level + 1 ) ; //captures the length of the text plus the added # tags
193197 } ;
194198
195199 private applyFormatting = ( {
@@ -253,6 +257,8 @@ export default class MarkdownCommand {
253257 const { state } = view ;
254258 const { doc } = state ;
255259
260+ let lastCursorPosition = 0 ;
261+
256262 view . dispatch (
257263 view . state . changeByRange ( ( range ) => {
258264 const startLine = doc . lineAt ( range . from ) ;
@@ -284,16 +290,15 @@ export default class MarkdownCommand {
284290 }
285291 } ) ;
286292
293+ lastCursorPosition = selectionStart + selectionLength ;
294+
287295 return {
288296 changes,
289297 range : EditorSelection . range ( selectionStart , selectionStart + selectionLength ) ,
290298 } ;
291299 } )
292300 ) ;
293-
294- view . focus ( ) ;
295-
296- return true ;
301+ this . enforceCursorFocus ( lastCursorPosition ) ;
297302 } ;
298303
299304 executeCommand = ( command : Commands ) : true | void => {
0 commit comments