@@ -2,32 +2,115 @@ import '@logseq/libs'
2
2
import { BlockEntity } from '@logseq/libs/dist/LSPlugin'
3
3
4
4
5
- type MarkUp = [ string , string ] [ ] & { wrappedWith ?: string }
5
+ class MarkUp implements Iterable < [ string , string ] > {
6
+ public wrap : [ string , string ]
7
+ public alternativeWrap : [ string , string ]
8
+ public unwrap : [ string , string ] [ ]
9
+
10
+ public wrappedWith ?: string
11
+ public alternativeWrapWhenMatch ?: RegExp
12
+
13
+ constructor ( { wrap, unwrap, alternativeWrapWhenMatch, alternativeWrapIndex } : {
14
+ wrap : [ string , string ]
15
+ unwrap : ( [ string , string ] | null ) [ ]
16
+ alternativeWrapWhenMatch ?: RegExp
17
+ alternativeWrapIndex ?: number
18
+ } ) {
19
+ this . wrap = wrap
20
+ this . unwrap = unwrap . map ( ( item ) => item === null ? wrap : item )
21
+ if ( ! this . unwrap . includes ( wrap ) )
22
+ this . unwrap . splice ( 0 , 0 , wrap )
23
+ this . wrappedWith = undefined
24
+ this . alternativeWrapWhenMatch = alternativeWrapWhenMatch
25
+ this . alternativeWrap = this . unwrap [ alternativeWrapIndex ?? 1 ]
26
+ }
27
+
28
+ [ Symbol . iterator ] ( ) {
29
+ let index = 0
30
+ return {
31
+ next : ( ) => {
32
+ return {
33
+ done : index >= this . unwrap . length ,
34
+ value : this . unwrap [ index ++ ] ,
35
+ }
36
+ }
37
+ }
38
+ }
39
+ getWrapFor ( selection : string ) {
40
+ if ( this . alternativeWrapWhenMatch && this . alternativeWrapWhenMatch . test ( selection ) )
41
+ return this . alternativeWrap
42
+ return this . wrap
43
+ }
44
+ getUnwrapFor ( line : string , selectPosition : [ number , number ] ) : [ string , string ] | null {
45
+ // Assertion: selectPosition should be already trimmed, so markup is always OUTside
46
+
47
+ for ( const markup of this . unwrap )
48
+ if ( MarkUp . isMarkedUpWith ( line , selectPosition , markup ) )
49
+ return markup
50
+ return null
51
+ }
52
+ static isMarkedUpWith ( line : string , selectPosition : [ number , number ] , markup : [ string , string ] ) : boolean {
53
+ const [ start , end ] = selectPosition
54
+ const [ frontMarkup , backMarkup ] = markup
55
+
56
+ if ( start < frontMarkup . length )
57
+ return false
58
+ if ( end + backMarkup . length > line . length )
59
+ return false
60
+
61
+ const charsBefore = line . slice ( start - frontMarkup . length , start )
62
+ const charsAfter = line . slice ( end , end + backMarkup . length )
63
+ return charsBefore === frontMarkup && charsAfter === backMarkup
64
+ }
65
+ }
66
+
6
67
const MARKUP : { [ type : string ] : MarkUp } = {
7
- // first pair is for wrapping, others — for unwrapping
8
- bold : [ [ '**' , '**' ] , [ '<b>' , '</b>' ] ] ,
9
- italics : [ [ '_' , '_' ] , [ '*' , '*' ] , [ '<i>' , '</i>' ] ] ,
10
- strikethrough : [ [ '~~' , '~~' ] , [ '<s>' , '</s>' ] ] ,
11
- highlight : [ [ '==' , '==' ] , [ '<mark>' , '</mark>' ] ] ,
12
- underline : [ [ '<ins>' , '</ins>' ] , [ '<u>' , '</u>' ] ] ,
13
- code : [ [ '`' , '`' ] , [ '<code>' , '</code>' ] ] ,
68
+ bold : new MarkUp ( {
69
+ wrap : [ '**' , '**' ] ,
70
+ unwrap : [ [ '<b>' , '</b>' ] ] } ) ,
71
+ italics : new MarkUp ( {
72
+ wrap : [ '_' , '_' ] ,
73
+ unwrap : [ [ '*' , '*' ] , [ '<i>' , '</i>' ] ] } ) ,
74
+ strikethrough : new MarkUp ( {
75
+ wrap : [ '~~' , '~~' ] ,
76
+ unwrap : [ [ '<s>' , '</s>' ] ] } ) ,
77
+ highlight : new MarkUp ( {
78
+ wrap : [ '==' , '==' ] ,
79
+ unwrap : [ [ '<mark>' , '</mark>' ] ] } ) ,
80
+ underline : new MarkUp ( {
81
+ wrap : [ '<ins>' , '</ins>' ] ,
82
+ unwrap : [ [ '<u>' , '</u>' ] ] } ) ,
83
+ code : new MarkUp ( {
84
+ wrap : [ '`' , '`' ] ,
85
+ unwrap : [ [ '<code>' , '</code>' ] ] } ) ,
86
+ ref : new MarkUp ( {
87
+ wrap : [ '[[' , ']]' ] ,
88
+ unwrap : [ [ '#[[' , ']]' ] , null , [ '#' , '' ] ] } ) ,
89
+ tag : new MarkUp ( {
90
+ wrap : [ '#' , '' ] ,
91
+ unwrap : [ [ '#[[' , ']]' ] , [ '[[' , ']]' ] ] ,
92
+ alternativeWrapWhenMatch : / \s + / ,
93
+ alternativeWrapIndex : 1 , } ) ,
14
94
}
95
+ type MARKUP_NAME = keyof typeof MARKUP
15
96
16
97
const TRIM_BEGIN = [
17
- / ^ \s + / ,
98
+ / ^ \s + / , // spaces at the beginning
18
99
19
- / ^ # { 1 , 6 } / ,
100
+ / ^ # { 1 , 6 } / , // headings
20
101
21
- / ^ \s * [ - + * ] / ,
22
- / ^ \s * [ - + * ] \[ .\] / ,
23
- / ^ > / ,
102
+ / ^ \s * [ - + * ] / , // list item
103
+ / ^ \s * [ - + * ] \[ .\] / , // list item with markdown task
104
+ / ^ > / , // quote
24
105
106
+ // logseq tasks
25
107
/ ^ ( L A T E R | T O D O ) ( \[ # ( A | B | C ) \] ) ? / ,
26
108
/ ^ ( N O W | D O I N G ) ( \[ # ( A | B | C ) \] ) ? / ,
27
109
/ ^ D O N E ( \[ # ( A | B | C ) \] ) ? / ,
28
110
/ ^ ( W A I T | W A I T I N G ) ( \[ # ( A | B | C ) \] ) ? / ,
29
111
/ ^ ( C A N C E L E D | C A N C E L L E D ) ( \[ # ( A | B | C ) \] ) ? / ,
30
- / ^ \[ # ( A | B | C ) \] / ,
112
+
113
+ / ^ \[ # ( A | B | C ) \] / , // non-task blocks with priorities
31
114
32
115
/ ^ \s * [ ^ \s : ; , ^ @ # ~ " ` / | \\ ( ) { } [ \] ] + : : / u, // property without value
33
116
/ ^ \s * D E A D L I N E : < [ ^ > ] + > $ / ,
@@ -36,20 +119,18 @@ const TRIM_BEGIN = [
36
119
const TRIM_BEGIN_SELECTION_ADDITION = [
37
120
/ ^ \s * [ ^ \s : ; , ^ @ # ~ " ` / | \\ ( ) { } [ \] ] + : : .* $ / u, // property with value
38
121
]
39
-
40
122
const TRIM_BEFORE : ( string | RegExp ) [ ] = [
41
123
/ ^ \( \( [ a - f 0 - 9 ] { 8 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 12 } \) \) / , // block ref
42
124
/ ^ { { \w + .* ?} } / , // macro call
43
125
44
126
/ ^ \s / ,
45
127
]
46
-
47
128
const TRIM_AFTER : ( string | RegExp ) [ ] = [
48
129
/ \! \[ .* ?\] \( .+ ?\) { .* ?} $ / , // link to image
49
130
/ \! \[ .* ?\] \( .+ ?\) $ / , // link to image
50
131
51
- // /\]\(.+?\)$/, // link to page
52
- / \] \( \[ \[ .+ ?\] \] \) $ / , // link to page
132
+ // /\]\(.+?\)$/, // link to page
133
+ / \] \( \[ \[ .+ ?\] \] \) $ / , // link to page
53
134
54
135
/ \] \( \( \( [ a - f 0 - 9 ] { 8 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 12 } \) \) \) $ / , // link to block
55
136
/ \( \( [ a - f 0 - 9 ] { 8 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 4 } - [ a - f 0 - 9 ] { 12 } \) \) $ / , // block ref
@@ -60,7 +141,6 @@ const TRIM_AFTER: (string | RegExp)[] = [
60
141
61
142
/ \] \( $ / , // to not break markdown links
62
143
]
63
-
64
144
const EXPAND_WHEN_OUTSIDE = [
65
145
// this is OR groups
66
146
// order is expansion direction
@@ -116,6 +196,9 @@ function trim(line: string, markup: MarkUp, selectPosition: [number, number], is
116
196
while ( true ) {
117
197
let wasTrimmed = false
118
198
trimBefore . forEach ( strOrRE => {
199
+ if ( ! strOrRE )
200
+ return
201
+
119
202
if ( typeof strOrRE === 'string' ) {
120
203
if ( selection . startsWith ( strOrRE ) ) {
121
204
selection = selection . slice ( strOrRE . length )
@@ -139,6 +222,9 @@ function trim(line: string, markup: MarkUp, selectPosition: [number, number], is
139
222
while ( true ) {
140
223
let wasTrimmed = false
141
224
trimAfter . forEach ( ( strOrRE ) => {
225
+ if ( ! strOrRE )
226
+ return
227
+
142
228
if ( typeof strOrRE === 'string' ) {
143
229
if ( selection . endsWith ( strOrRE ) ) {
144
230
selection = selection . slice ( 0 , - strOrRE . length )
@@ -162,29 +248,6 @@ function trim(line: string, markup: MarkUp, selectPosition: [number, number], is
162
248
selectPosition [ 1 ] = end
163
249
}
164
250
165
- function isMarkedUp ( line : string , markup : MarkUp , selectPosition : [ number , number ] ) {
166
- // note: may change markup
167
- // assertion:: selectPosition should be already trimmed, so markup is always OUTside
168
-
169
- const [ start , end ] = selectPosition
170
- for ( const [ i , [ frontMarkup , backMarkup ] ] of Object . entries ( markup ) ) {
171
- if ( start < frontMarkup . length )
172
- continue
173
- if ( end + backMarkup . length > line . length )
174
- continue
175
-
176
- const charsBefore = line . slice ( start - frontMarkup . length , start )
177
- const charsAfter = line . slice ( end , end + backMarkup . length )
178
- if ( charsBefore === frontMarkup && charsAfter === backMarkup ) {
179
- markup . wrappedWith = i
180
- Object . defineProperty ( markup , 'wrappedWith' , { enumerable : false } )
181
- return true
182
- }
183
- }
184
-
185
- return false
186
- }
187
-
188
251
function wordAtPosition ( line : string , position : number ) {
189
252
const wordLeftRegexp = / (? ! _ ) [ \p{ Letter} \p{ Number} ' _ - ] * $ / u
190
253
const wordRightRegexp = / ^ [ \p{ Letter} \p{ Number} ' _ - ] * (?< ! _ ) / u
@@ -199,7 +262,7 @@ function wordAtPosition(line: string, position: number) {
199
262
function expand ( line : string , markup : MarkUp , selectPosition : [ number , number ] ) {
200
263
let [ start , end ] = selectPosition
201
264
202
- let wordEdge
265
+ let wordEdge : number
203
266
[ start , wordEdge ] = wordAtPosition ( line , start )
204
267
if ( wordEdge < end )
205
268
[ wordEdge , end ] = wordAtPosition ( line , end )
@@ -229,7 +292,7 @@ function expand(line: string, markup: MarkUp, selectPosition: [number, number])
229
292
230
293
const trimLastSpace = Boolean ( pair [ 2 ] )
231
294
232
- if ( isMarkedUp ( line , [ [ L , R ] ] as MarkUp , [ start , end ] ) ) {
295
+ if ( MarkUp . isMarkedUpWith ( line , [ start , end ] , [ L , R ] ) ) {
233
296
start -= L . length
234
297
end += R . length
235
298
setSelection ( )
@@ -239,10 +302,11 @@ function expand(line: string, markup: MarkUp, selectPosition: [number, number])
239
302
} )
240
303
}
241
304
242
- function applyMarkup ( line : string , markup : MarkUp , selectPosition ?: [ number , number ] ) : string {
305
+ function applyMarkup ( line : string , markupName : MARKUP_NAME , selectPosition ?: [ number , number ] ) : string {
243
306
if ( ! line && ! selectPosition )
244
307
return ''
245
308
309
+ const markup = MARKUP [ markupName ]
246
310
247
311
const isSelectionMode = Boolean ( selectPosition )
248
312
if ( ! selectPosition )
@@ -262,23 +326,23 @@ function applyMarkup(line: string, markup: MarkUp, selectPosition?: [number, num
262
326
}
263
327
264
328
let selection = line . slice ( start , end )
265
-
266
- if ( isMarkedUp ( line , markup , selectPosition ) ) {
267
- const [ frontMarkup , backMarkup ] = markup [ markup . wrappedWith ! ]
329
+ const wrappedWith = markup . getUnwrapFor ( line , selectPosition )
330
+ if ( wrappedWith ) {
331
+ const [ frontMarkup , backMarkup ] = wrappedWith
268
332
selectPosition [ 0 ] -= frontMarkup . length
269
333
selectPosition [ 1 ] -= frontMarkup . length
270
334
return line . slice ( 0 , start - frontMarkup . length ) + selection + line . slice ( end + backMarkup . length )
271
335
} else {
272
- const [ frontMarkup , backMarkup ] = markup [ 0 ]
336
+ const [ frontMarkup , backMarkup ] = markup . getWrapFor ( selection )
273
337
selectPosition [ 0 ] += frontMarkup . length
274
338
selectPosition [ 1 ] += frontMarkup . length
275
339
return line . slice ( 0 , start ) + frontMarkup + selection + backMarkup + line . slice ( end )
276
340
}
277
341
}
278
342
279
- function wrap ( block : BlockEntity , content : string , markup : MarkUp ) {
343
+ function wrap ( block : BlockEntity , content : string , markupName : MARKUP_NAME ) {
280
344
if ( ! block . _selectPosition )
281
- return content . split ( '\n' ) . map ( ( line ) => applyMarkup ( line , markup ) ) . join ( '\n' )
345
+ return content . split ( '\n' ) . map ( ( line ) => applyMarkup ( line , markupName ) ) . join ( '\n' )
282
346
283
347
let lineStartPosition = 0
284
348
let newLineStartPosition = 0
@@ -316,7 +380,7 @@ function wrap(block: BlockEntity, content: string, markup: MarkUp) {
316
380
Math . max ( selectStart - lineStartPosition , 0 ) ,
317
381
Math . min ( selectEnd - lineStartPosition , line . length ) ,
318
382
]
319
- const newLine = applyMarkup ( line , markup , wholeLine ? undefined : selectPositionLine )
383
+ const newLine = applyMarkup ( line , markupName , wholeLine ? undefined : selectPositionLine )
320
384
321
385
if ( ! wholeLine ) {
322
386
// first line of selection
@@ -338,25 +402,33 @@ function wrap(block: BlockEntity, content: string, markup: MarkUp) {
338
402
}
339
403
340
404
export function magicBold ( content , level , block , parent ) {
341
- return wrap ( block , content , MARKUP . bold )
405
+ return wrap ( block , content , ' bold me' )
342
406
}
343
407
344
408
export function magicItalics ( content , level , block , parent ) {
345
- return wrap ( block , content , MARKUP . italics )
409
+ return wrap ( block , content , ' italics' )
346
410
}
347
411
348
412
export function magicStrikethrough ( content , level , block , parent ) {
349
- return wrap ( block , content , MARKUP . strikethrough )
413
+ return wrap ( block , content , ' strikethrough' )
350
414
}
351
415
352
416
export function magicHighlight ( content , level , block , parent ) {
353
- return wrap ( block , content , MARKUP . highlight )
417
+ return wrap ( block , content , ' highlight' )
354
418
}
355
419
356
420
export function magicUnderline ( content , level , block , parent ) {
357
- return wrap ( block , content , MARKUP . underline )
421
+ return wrap ( block , content , ' underline' )
358
422
}
359
423
360
424
export function magicCode ( content , level , block , parent ) {
361
- return wrap ( block , content , MARKUP . code )
425
+ return wrap ( block , content , 'code' )
426
+ }
427
+
428
+ export function magicRef ( content , level , block , parent ) {
429
+ return wrap ( block , content , 'ref' )
430
+ }
431
+
432
+ export function magicTag ( content , level , block , parent ) {
433
+ return wrap ( block , content , 'tag' )
362
434
}
0 commit comments