book-source 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +168 -0
- package/Makefile +112 -0
- package/README.md +17 -0
- package/brainstorming +213 -0
- package/css/code.css +120 -0
- package/css/core.css +317 -0
- package/css/standalone.css +30 -0
- package/css/unimplemented.css +65 -0
- package/documentation.md +17 -0
- package/extlib/chromajs.custom.js +865 -0
- package/lib/Color.js +197 -0
- package/lib/HtmlRenderer.js +618 -0
- package/lib/Palette.js +313 -0
- package/lib/StructuredDocument.js +1973 -0
- package/lib/Style.js +102 -0
- package/lib/Theme.js +127 -0
- package/lib/book-source.js +68 -0
- package/package.json +38 -0
|
@@ -0,0 +1,1973 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Book Source
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2023 Cédric Ronvel
|
|
5
|
+
|
|
6
|
+
The MIT License (MIT)
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
"use strict" ;
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
const HtmlRenderer = require( './HtmlRenderer.js' ) ;
|
|
32
|
+
const Style = require( './Style.js' ) ;
|
|
33
|
+
|
|
34
|
+
const emoji = require( 'string-kit/lib/emoji.js' ) ;
|
|
35
|
+
const inPlaceFilter = require( 'array-kit/lib/inPlaceFilter.js' ) ;
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
function StructuredDocument() {
|
|
40
|
+
this.title = 'Document' ;
|
|
41
|
+
this.metadata = null ;
|
|
42
|
+
this.theme = null ;
|
|
43
|
+
this.parts = [] ;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = StructuredDocument ;
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
StructuredDocument.prototype.toHtml = function( theme , params ) {
|
|
51
|
+
return this.render( new HtmlRenderer( theme , params ) ) ;
|
|
52
|
+
} ;
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
StructuredDocument.prototype.render = function( renderer ) {
|
|
57
|
+
var meta = {
|
|
58
|
+
title: this.title
|
|
59
|
+
} ;
|
|
60
|
+
|
|
61
|
+
var str = this.renderParts( renderer , this.parts , [] ) ;
|
|
62
|
+
|
|
63
|
+
if ( renderer.document ) {
|
|
64
|
+
str = renderer.document( meta , str ) ;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return str ;
|
|
68
|
+
} ;
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
StructuredDocument.prototype.renderParts = function( renderer , parts , partStack ) {
|
|
73
|
+
var str = '' , index = 0 ;
|
|
74
|
+
|
|
75
|
+
for ( let part of parts ) {
|
|
76
|
+
let childrenStr = '' ;
|
|
77
|
+
|
|
78
|
+
if ( part.children ) {
|
|
79
|
+
if ( renderer.group?.[ part.type ] ) {
|
|
80
|
+
// Some renderer have to group children an apply things to each group,
|
|
81
|
+
// E.g. HTML groups 'tableHeader' to produce <thead> and 'tableRow' to produce <tbody>
|
|
82
|
+
let groupList = this.groupByType( part.children , renderer.group[ part.type ] ) ;
|
|
83
|
+
//console.error( "groupList:" , groupList ) ;
|
|
84
|
+
|
|
85
|
+
for ( let group of groupList ) {
|
|
86
|
+
if ( renderer.group[ part.type ][ group.type ] ) {
|
|
87
|
+
partStack.push( part ) ;
|
|
88
|
+
let groupStr = this.renderParts( renderer , group.children , partStack ) ;
|
|
89
|
+
partStack.pop() ;
|
|
90
|
+
childrenStr += renderer.group[ part.type ][ group.type ]( part , groupStr , partStack ) ;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
partStack.push( part ) ;
|
|
96
|
+
childrenStr = this.renderParts( renderer , part.children , [ ... partStack , part ] ) ;
|
|
97
|
+
partStack.pop() ;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if ( renderer[ part.type ] ) {
|
|
102
|
+
str += renderer[ part.type ]( part , childrenStr , partStack , index ) ;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
index ++ ;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return str ;
|
|
109
|
+
} ;
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
StructuredDocument.prototype.groupByType = function( parts , rendererGroup ) {
|
|
114
|
+
var group , groupMap = {} , groupList = [] ;
|
|
115
|
+
|
|
116
|
+
for ( let part of parts ) {
|
|
117
|
+
group = groupMap[ part.type ] ;
|
|
118
|
+
if ( ! group ) {
|
|
119
|
+
group = groupMap[ part.type ] = {
|
|
120
|
+
type: part.type ,
|
|
121
|
+
children: [] ,
|
|
122
|
+
order: + rendererGroup[ part.type ]?.order || 0
|
|
123
|
+
} ;
|
|
124
|
+
groupList.push( group ) ;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
group.children.push( part ) ;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
groupList.sort( ( a , b ) => a.order - b.order ) ;
|
|
131
|
+
|
|
132
|
+
return groupList ;
|
|
133
|
+
} ;
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
StructuredDocument.prototype.autoTitle = function() {
|
|
138
|
+
var header , str ;
|
|
139
|
+
|
|
140
|
+
for ( let part of this.parts ) {
|
|
141
|
+
if ( part.type === 'header' ) { header = part ; break ; }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if ( ! header ) { return ; }
|
|
145
|
+
|
|
146
|
+
str = this.getText( header.children ) ;
|
|
147
|
+
if ( str ) { this.title = str ; }
|
|
148
|
+
} ;
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
StructuredDocument.prototype.getText = function( parts ) { //= this.parts ) {
|
|
153
|
+
var str = '' ;
|
|
154
|
+
|
|
155
|
+
for ( let part of parts ) {
|
|
156
|
+
if ( part.text ) { str += part.text ; }
|
|
157
|
+
else if ( part.children?.length ) { str += this.getText( part.children ) ; }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return str ;
|
|
161
|
+
} ;
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
// Parser
|
|
166
|
+
|
|
167
|
+
StructuredDocument.parse = function( str , options ) {
|
|
168
|
+
if ( ! options || typeof options !== 'object' ) { options = {} ; }
|
|
169
|
+
|
|
170
|
+
if ( typeof str !== 'string' ) {
|
|
171
|
+
if ( str && typeof str === 'object' ) { str = str.toString() ; }
|
|
172
|
+
else { throw new TypeError( "Argument #0 should be a string or an object with a .toString() method" ) ; }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
var ctx = {
|
|
176
|
+
i: 0 ,
|
|
177
|
+
iStartOfInlineChunk: 0 ,
|
|
178
|
+
forceInlineChunkSpace: false ,
|
|
179
|
+
iEndOfBlock: str.length , // For nested things, the parent end here
|
|
180
|
+
lastLineWasEmpty: false ,
|
|
181
|
+
lastBlock: null ,
|
|
182
|
+
rowSpanTables: new Set() , // Set of table that needs post-processing to apply row span
|
|
183
|
+
structuredDocument: new StructuredDocument() ,
|
|
184
|
+
stack: [] ,
|
|
185
|
+
parts: null ,
|
|
186
|
+
parent: null ,
|
|
187
|
+
rawMetadata: null
|
|
188
|
+
} ;
|
|
189
|
+
|
|
190
|
+
ctx.parts = ctx.structuredDocument.parts ;
|
|
191
|
+
|
|
192
|
+
parseBlocks( str , ctx ) ;
|
|
193
|
+
|
|
194
|
+
for ( let table of ctx.rowSpanTables ) { postProcessTableRowSpan( table ) ; }
|
|
195
|
+
ctx.structuredDocument.autoTitle() ;
|
|
196
|
+
|
|
197
|
+
if ( ctx.rawMetadata ) {
|
|
198
|
+
let metadataParser = options.metadataParser || JSON.parse ;
|
|
199
|
+
for ( let type in ctx.rawMetadata ) {
|
|
200
|
+
try {
|
|
201
|
+
let parsed = metadataParser( ctx.rawMetadata[ type ] ) ;
|
|
202
|
+
if ( type === 'metadata' ) {
|
|
203
|
+
ctx.structuredDocument.metadata = parsed ;
|
|
204
|
+
}
|
|
205
|
+
else if ( type === 'theme' && parsed && typeof parsed === 'object' ) {
|
|
206
|
+
ctx.structuredDocument.theme = parsed ;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch ( error ) {}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Call depthManagement() one last time, because some instanceOf may still be hanging...
|
|
214
|
+
//ctx.depth = -1 ;
|
|
215
|
+
//depthManagement( ctx ) ;
|
|
216
|
+
|
|
217
|
+
return ctx.structuredDocument ;
|
|
218
|
+
} ;
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
function parseBlocks( str , ctx ) {
|
|
223
|
+
while ( ctx.i < str.length ) {
|
|
224
|
+
parseBlock( str , ctx ) ;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
function parseBlock( str , ctx ) {
|
|
231
|
+
var { isEmptyLine , endOfEmptyLine , indentCharCount , indentSpaces , indentType } = detectIndent( str , ctx.i , ctx.parent?.indent ) ;
|
|
232
|
+
|
|
233
|
+
if ( isEmptyLine ) {
|
|
234
|
+
ctx.i = endOfEmptyLine + 1 ;
|
|
235
|
+
ctx.lastLineWasEmpty = true ;
|
|
236
|
+
ctx.lastBlock = null ;
|
|
237
|
+
return ;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if ( indentType === QUOTE_INDENT ) {
|
|
241
|
+
ctx.parts.push( { type: 'quote' , indent: indentSpaces } ) ;
|
|
242
|
+
stack( ctx ) ;
|
|
243
|
+
}
|
|
244
|
+
else if ( indentType <= 0 ) {
|
|
245
|
+
unstackToIndent( ctx , indentSpaces ) ;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
ctx.i += indentCharCount ;
|
|
249
|
+
var blockType = detectBlockType( str , ctx.i ) ;
|
|
250
|
+
//console.error( "=== parseBlock() ===" , { blockType , isEmptyLine , endOfEmptyLine , indentCharCount , indentSpaces , indentType } ) ;
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
switch ( blockType ) {
|
|
254
|
+
case BLOCK_PARAGRAPH :
|
|
255
|
+
parseParagraph( str , ctx ) ;
|
|
256
|
+
break ;
|
|
257
|
+
case BLOCK_HEADER :
|
|
258
|
+
parseHeader( str , ctx ) ;
|
|
259
|
+
break ;
|
|
260
|
+
case BLOCK_CITE :
|
|
261
|
+
parseCite( str , ctx ) ;
|
|
262
|
+
break ;
|
|
263
|
+
case BLOCK_LIST_ITEM :
|
|
264
|
+
parseListItem( str , ctx , indentSpaces ) ;
|
|
265
|
+
break ;
|
|
266
|
+
case BLOCK_ORDERED_LIST_ITEM :
|
|
267
|
+
parseOrderedListItem( str , ctx , indentSpaces ) ;
|
|
268
|
+
break ;
|
|
269
|
+
case BLOCK_MEDIA :
|
|
270
|
+
parseMedia( str , ctx ) ;
|
|
271
|
+
break ;
|
|
272
|
+
case BLOCK_FLOAT_LEFT_MEDIA :
|
|
273
|
+
parseMedia( str , ctx , 'left' ) ;
|
|
274
|
+
break ;
|
|
275
|
+
case BLOCK_FLOAT_RIGHT_MEDIA :
|
|
276
|
+
parseMedia( str , ctx , 'right' ) ;
|
|
277
|
+
break ;
|
|
278
|
+
case BLOCK_HORIZONTAL_RULE :
|
|
279
|
+
parseHorizontalRule( str , ctx ) ;
|
|
280
|
+
break ;
|
|
281
|
+
case BLOCK_CLEAR_FLOAT :
|
|
282
|
+
parseClearFloat( str , ctx ) ;
|
|
283
|
+
break ;
|
|
284
|
+
case BLOCK_CODE :
|
|
285
|
+
parseCodeBlock( str , ctx ) ;
|
|
286
|
+
break ;
|
|
287
|
+
case BLOCK_TABLE_CAPTION :
|
|
288
|
+
parseTableCaption( str , ctx ) ;
|
|
289
|
+
break ;
|
|
290
|
+
case BLOCK_TABLE_ROW :
|
|
291
|
+
parseTableRow( str , ctx ) ;
|
|
292
|
+
break ;
|
|
293
|
+
case BLOCK_TABLE_ROW_SEPARATOR :
|
|
294
|
+
parseTableRowSeparator( str , ctx ) ;
|
|
295
|
+
break ;
|
|
296
|
+
case BLOCK_TABLE_ROW_THICK_SEPARATOR :
|
|
297
|
+
parseTableRowSeparator( str , ctx , true ) ;
|
|
298
|
+
break ;
|
|
299
|
+
case BLOCK_ANCHOR :
|
|
300
|
+
parseAnchor( str , ctx ) ;
|
|
301
|
+
break ;
|
|
302
|
+
case BLOCK_METADATA :
|
|
303
|
+
parseMetadata( str , ctx ) ;
|
|
304
|
+
break ;
|
|
305
|
+
default :
|
|
306
|
+
throw new Error( "Bad block detection: " + blockType ) ;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
ctx.lastLineWasEmpty = false ;
|
|
310
|
+
ctx.lastBlock = blockType ;
|
|
311
|
+
if ( str[ ctx.i ] === '\n' ) { ctx.i ++ ; }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
const UNQUOTE_INDENT = - 3 ;
|
|
317
|
+
const UNLIST_INDENT = - 2 ;
|
|
318
|
+
const DISCONTINUE_INDENT = - 1 ;
|
|
319
|
+
const NO_INDENT = 0 ;
|
|
320
|
+
const CONTINUE_INDENT = 1 ; // 2 spaces, continue the previous block
|
|
321
|
+
const LIST_INDENT = 2 ; // 4 spaces (sub-list)
|
|
322
|
+
const QUOTE_INDENT = 3 ; // 8 spaces (quote)
|
|
323
|
+
|
|
324
|
+
const DETECT_INDENT = {
|
|
325
|
+
isEmptyLine: false ,
|
|
326
|
+
endOfEmptyLine: - 1 ,
|
|
327
|
+
indentCharCount: 0 ,
|
|
328
|
+
indentSpaces: 0 ,
|
|
329
|
+
indentDelta: 0 ,
|
|
330
|
+
indentType: NO_INDENT
|
|
331
|
+
} ;
|
|
332
|
+
|
|
333
|
+
function detectIndent( str , i , parentIndent ) {
|
|
334
|
+
parentIndent = parentIndent || 0 ;
|
|
335
|
+
|
|
336
|
+
DETECT_INDENT.isEmptyLine = false ;
|
|
337
|
+
DETECT_INDENT.endOfEmptyLine = - 1 ;
|
|
338
|
+
DETECT_INDENT.indentCharCount = DETECT_INDENT.indentSpaces = DETECT_INDENT.indentDelta = 0 ;
|
|
339
|
+
DETECT_INDENT.indentType = NO_INDENT ;
|
|
340
|
+
|
|
341
|
+
if ( str[ i ] === '\n' ) {
|
|
342
|
+
DETECT_INDENT.isEmptyLine = true ;
|
|
343
|
+
DETECT_INDENT.endOfEmptyLine = i ;
|
|
344
|
+
return DETECT_INDENT ;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
var iSearch = i ;
|
|
349
|
+
|
|
350
|
+
for ( ; iSearch < str.length ; iSearch ++ ) {
|
|
351
|
+
if ( str[ iSearch ] === '\n' ) {
|
|
352
|
+
DETECT_INDENT.isEmptyLine = true ;
|
|
353
|
+
DETECT_INDENT.endOfEmptyLine = iSearch ;
|
|
354
|
+
return DETECT_INDENT ;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if ( str[ iSearch ] === '\t' ) {
|
|
358
|
+
DETECT_INDENT.indentCharCount ++ ;
|
|
359
|
+
DETECT_INDENT.indentSpaces += 4 ;
|
|
360
|
+
}
|
|
361
|
+
else if ( str[ iSearch ] === ' ' ) {
|
|
362
|
+
DETECT_INDENT.indentCharCount ++ ;
|
|
363
|
+
DETECT_INDENT.indentSpaces ++ ;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
break ;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
DETECT_INDENT.indentDelta = DETECT_INDENT.indentSpaces - parentIndent ;
|
|
371
|
+
|
|
372
|
+
if ( DETECT_INDENT.indentDelta >= 8 ) {
|
|
373
|
+
DETECT_INDENT.indentType = QUOTE_INDENT ;
|
|
374
|
+
}
|
|
375
|
+
else if ( DETECT_INDENT.indentDelta >= 4 ) {
|
|
376
|
+
DETECT_INDENT.indentType = LIST_INDENT ;
|
|
377
|
+
}
|
|
378
|
+
else if ( DETECT_INDENT.indentDelta >= 2 ) {
|
|
379
|
+
DETECT_INDENT.indentType = CONTINUE_INDENT ;
|
|
380
|
+
}
|
|
381
|
+
else if ( DETECT_INDENT.indentDelta <= - 8 ) {
|
|
382
|
+
DETECT_INDENT.indentType = UNQUOTE_INDENT ;
|
|
383
|
+
}
|
|
384
|
+
else if ( DETECT_INDENT.indentDelta <= - 4 ) {
|
|
385
|
+
DETECT_INDENT.indentType = UNLIST_INDENT ;
|
|
386
|
+
}
|
|
387
|
+
else if ( DETECT_INDENT.indentDelta <= - 2 ) {
|
|
388
|
+
DETECT_INDENT.indentType = DISCONTINUE_INDENT ;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return DETECT_INDENT ;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
const BLOCK_PARAGRAPH = 1 ;
|
|
397
|
+
const BLOCK_HEADER = 2 ;
|
|
398
|
+
const BLOCK_LIST_ITEM = 3 ;
|
|
399
|
+
const BLOCK_ORDERED_LIST_ITEM = 4 ;
|
|
400
|
+
const BLOCK_CITE = 5 ;
|
|
401
|
+
const BLOCK_MEDIA = 10 ;
|
|
402
|
+
const BLOCK_FLOAT_LEFT_MEDIA = 11 ;
|
|
403
|
+
const BLOCK_FLOAT_RIGHT_MEDIA = 12 ;
|
|
404
|
+
const BLOCK_HORIZONTAL_RULE = 20 ;
|
|
405
|
+
const BLOCK_CLEAR_FLOAT = 21 ;
|
|
406
|
+
const BLOCK_CODE = 30 ;
|
|
407
|
+
const BLOCK_TABLE_ROW = 40 ;
|
|
408
|
+
const BLOCK_TABLE_ROW_SEPARATOR = 41 ;
|
|
409
|
+
const BLOCK_TABLE_ROW_THICK_SEPARATOR = 42 ;
|
|
410
|
+
const BLOCK_TABLE_CAPTION = 43 ;
|
|
411
|
+
const BLOCK_ANCHOR = 50 ;
|
|
412
|
+
const BLOCK_METADATA = 60 ;
|
|
413
|
+
|
|
414
|
+
function detectBlockType( str , i ) {
|
|
415
|
+
if ( str[ i ] === '\\' ) {
|
|
416
|
+
return BLOCK_PARAGRAPH ;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if ( str[ i ] === '#' ) {
|
|
420
|
+
if ( str[ i + 1 ] === '(' ) { return BLOCK_ANCHOR ; }
|
|
421
|
+
return BLOCK_HEADER ;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if ( str[ i ] === '!' && str[ i + 2 ] === '[' ) {
|
|
425
|
+
if ( str[ i + 1 ] === '=' ) { return BLOCK_MEDIA ; }
|
|
426
|
+
else if ( str[ i + 1 ] === '<' ) { return BLOCK_FLOAT_LEFT_MEDIA ; }
|
|
427
|
+
else if ( str[ i + 1 ] === '>' ) { return BLOCK_FLOAT_RIGHT_MEDIA ; }
|
|
428
|
+
return BLOCK_PARAGRAPH ;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if ( ( str[ i ] === '*' || str[ i ] === '-' ) && str[ i + 1 ] === ' ' ) {
|
|
432
|
+
return BLOCK_LIST_ITEM ;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if ( str[ i ] === '-' && str[ i + 1 ] === '-' ) {
|
|
436
|
+
if ( str[ i + 2 ] === '-' ) {
|
|
437
|
+
if ( str[ i + 3 ] === '[' ) {
|
|
438
|
+
return BLOCK_METADATA ;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return BLOCK_HORIZONTAL_RULE ;
|
|
442
|
+
|
|
443
|
+
}
|
|
444
|
+
if ( str[ i + 2 ] === ' ' && searchEndOfEmptyLine( str , i + 3 ) === - 1 ) {
|
|
445
|
+
return BLOCK_CITE ;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if ( str[ i ] === '<' && str[ i + 1 ] === '-' && str[ i + 2 ] === '-' && str[ i + 3 ] === '-' ) {
|
|
450
|
+
return BLOCK_CLEAR_FLOAT ;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if ( str[ i ] === '`' && str[ i + 1 ] === '`' && str[ i + 2 ] === '`' ) {
|
|
454
|
+
return BLOCK_CODE ;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if ( str[ i ] === '|' ) {
|
|
458
|
+
if ( str[ i + 1 ] === '[' ) { return BLOCK_TABLE_CAPTION ; }
|
|
459
|
+
|
|
460
|
+
if (
|
|
461
|
+
str[ i + 1 ] === '-'
|
|
462
|
+
|| ( ( str[ i + 1 ] === '<' || str[ i + 1 ] === '>' ) && str[ i + 2 ] === '-' )
|
|
463
|
+
) {
|
|
464
|
+
return BLOCK_TABLE_ROW_SEPARATOR ;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (
|
|
468
|
+
str[ i + 1 ] === '='
|
|
469
|
+
|| ( ( str[ i + 1 ] === '<' || str[ i + 1 ] === '>' ) && str[ i + 2 ] === '=' )
|
|
470
|
+
) {
|
|
471
|
+
return BLOCK_TABLE_ROW_THICK_SEPARATOR ;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return BLOCK_TABLE_ROW ;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if ( str[ i ] >= '0' && str[ i ] <= '9' ) {
|
|
478
|
+
let iAfterNumber = i + 1 ;
|
|
479
|
+
while ( str[ iAfterNumber ] >= '0' && str[ iAfterNumber ] <= '9' ) { iAfterNumber ++ ; }
|
|
480
|
+
|
|
481
|
+
if ( str[ iAfterNumber ] === '.' && ( str[ iAfterNumber + 1 ] === ' ' || str[ iAfterNumber + 1 ] === '\t' ) ) {
|
|
482
|
+
return BLOCK_ORDERED_LIST_ITEM ;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return BLOCK_PARAGRAPH ;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
const DEFAULT_BLOCK_END_PARAMS = {
|
|
492
|
+
acceptEmptyLine: false ,
|
|
493
|
+
acceptBlockType: - 1 ,
|
|
494
|
+
acceptContinueIndent: true ,
|
|
495
|
+
acceptIndent: false
|
|
496
|
+
} ;
|
|
497
|
+
|
|
498
|
+
/*
|
|
499
|
+
Params:
|
|
500
|
+
acceptEmptyLine: the block is not interrupted by empty line
|
|
501
|
+
acceptBlockType: this block is accepted as a continuation
|
|
502
|
+
acceptContinueIndent: continue if the block is indented 2 spaces (“continue indent”)
|
|
503
|
+
acceptIndent: continue if the block is indented to the next level (4 or more)
|
|
504
|
+
*/
|
|
505
|
+
function detectBlockEnd( str , nextScanStart , parentIndent = 0 , params = DEFAULT_BLOCK_END_PARAMS ) {
|
|
506
|
+
var detectedBlockType , isEmptyLine , endOfEmptyLine , indentType ,
|
|
507
|
+
blockEnd = nextScanStart ;
|
|
508
|
+
|
|
509
|
+
//console.error( "=== detectBlockEnd() ===" , nextScanStart , parentIndent , params ) ;
|
|
510
|
+
while ( nextScanStart < str.length ) {
|
|
511
|
+
// First, move at the start of the next line...
|
|
512
|
+
let endOfLine = searchEndOfLine( str , nextScanStart ) ;
|
|
513
|
+
//console.error( "-> endOfLine:" , endOfLine ) ;
|
|
514
|
+
blockEnd = endOfLine ;
|
|
515
|
+
nextScanStart = endOfLine + 1 ;
|
|
516
|
+
|
|
517
|
+
if ( nextScanStart > str.length ) { break ; }
|
|
518
|
+
|
|
519
|
+
( { isEmptyLine , endOfEmptyLine , indentType } = detectIndent( str , nextScanStart , parentIndent ) ) ;
|
|
520
|
+
//console.error( "-> detectIndent():" , { isEmptyLine , endOfEmptyLine , indentCharCount , indentSpaces , indentType } ) ;
|
|
521
|
+
|
|
522
|
+
if ( isEmptyLine ) {
|
|
523
|
+
if ( ! params.acceptEmptyLine ) { return blockEnd ; }
|
|
524
|
+
nextScanStart = endOfEmptyLine + 1 ;
|
|
525
|
+
continue ;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if ( indentType < 0 ) { return blockEnd ; }
|
|
529
|
+
if ( indentType === CONTINUE_INDENT && params.acceptContinueIndent ) { continue ; }
|
|
530
|
+
if ( indentType > 0 && params.acceptIndent ) { continue ; }
|
|
531
|
+
|
|
532
|
+
detectedBlockType = detectBlockType( str , nextScanStart ) ;
|
|
533
|
+
//console.error( "-> detectBlockType():" , detectedBlockType ) ;
|
|
534
|
+
if ( params.acceptBlockType !== detectedBlockType ) { return blockEnd ; }
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return blockEnd ;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
const PARAGRAPH_END_PARAMS = {
|
|
543
|
+
acceptEmptyLine: false ,
|
|
544
|
+
acceptBlockType: BLOCK_PARAGRAPH ,
|
|
545
|
+
acceptContinueIndent: false ,
|
|
546
|
+
acceptIndent: false
|
|
547
|
+
} ;
|
|
548
|
+
|
|
549
|
+
function parseParagraph( str , ctx ) {
|
|
550
|
+
//console.error( "parseParagraph in:" , ctx.i , str.slice( ctx.i ) ) ;
|
|
551
|
+
ctx.parts.push( { type: 'paragraph' } ) ;
|
|
552
|
+
|
|
553
|
+
var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , PARAGRAPH_END_PARAMS ) ;
|
|
554
|
+
//console.error( "blockEnd:" , blockEnd ) ;
|
|
555
|
+
parseInlineChildren( str , ctx , blockEnd ) ;
|
|
556
|
+
|
|
557
|
+
//console.error( "children:" , children ) ;
|
|
558
|
+
//console.error( "parseParagraph out:" , ctx.i , str.slice( ctx.i ) ) ;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
const HEADER_END_PARAMS = {
|
|
564
|
+
acceptEmptyLine: false ,
|
|
565
|
+
//acceptBlockType: BLOCK_HEADER ,
|
|
566
|
+
acceptContinueIndent: true ,
|
|
567
|
+
acceptIndent: false
|
|
568
|
+
} ;
|
|
569
|
+
|
|
570
|
+
function parseHeader( str , ctx ) {
|
|
571
|
+
var streak = countStreak( str , ctx.i , '#' ) ;
|
|
572
|
+
//if ( str[ ctx.i + streak ] !== ' ' ) { return parseParagraph( str , ctx ) ; }
|
|
573
|
+
|
|
574
|
+
ctx.i += streak ;
|
|
575
|
+
if ( str[ ctx.i ] === ' ' ) { ctx.i ++ ; }
|
|
576
|
+
ctx.parts.push( { type: 'header' , level: streak } ) ;
|
|
577
|
+
|
|
578
|
+
var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , HEADER_END_PARAMS ) ;
|
|
579
|
+
parseInlineChildren( str , ctx , blockEnd ) ;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
const CITE_END_PARAMS = {
|
|
585
|
+
acceptEmptyLine: false ,
|
|
586
|
+
//acceptBlockType: BLOCK_CITE ,
|
|
587
|
+
acceptContinueIndent: true ,
|
|
588
|
+
acceptIndent: false
|
|
589
|
+
} ;
|
|
590
|
+
|
|
591
|
+
function parseCite( str , ctx ) {
|
|
592
|
+
ctx.i += 3 ;
|
|
593
|
+
ctx.parts.push( { type: 'cite' } ) ;
|
|
594
|
+
|
|
595
|
+
var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , CITE_END_PARAMS ) ;
|
|
596
|
+
parseInlineChildren( str , ctx , blockEnd ) ;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
// Lists themselve are auto-aggregating, accepting empty-lines, item needs continue-indent
|
|
602
|
+
const LIST_ITEM_END_PARAMS = {
|
|
603
|
+
acceptEmptyLine: false ,
|
|
604
|
+
//acceptBlockType: BLOCK_LIST_ITEM ,
|
|
605
|
+
acceptContinueIndent: true ,
|
|
606
|
+
acceptIndent: false
|
|
607
|
+
} ;
|
|
608
|
+
|
|
609
|
+
function parseListItem( str , ctx , indent ) {
|
|
610
|
+
ctx.i += 2 ;
|
|
611
|
+
|
|
612
|
+
var lastPart = ctx.parts[ ctx.parts.length - 1 ] ;
|
|
613
|
+
|
|
614
|
+
if ( ! lastPart || lastPart.type !== 'list' ) {
|
|
615
|
+
ctx.parts.push( { type: 'list' , indent } ) ;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
stack( ctx ) ;
|
|
619
|
+
|
|
620
|
+
ctx.parts.push( { type: 'listItem' , indent } ) ;
|
|
621
|
+
|
|
622
|
+
var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , LIST_ITEM_END_PARAMS ) ;
|
|
623
|
+
parseInlineChildren( str , ctx , blockEnd ) ;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
function parseOrderedListItem( str , ctx , indent ) {
|
|
629
|
+
var endOfNumber = ctx.i ;
|
|
630
|
+
while ( str[ endOfNumber ] >= '0' && str[ endOfNumber ] <= '9' ) { endOfNumber ++ ; }
|
|
631
|
+
|
|
632
|
+
var order = parseInt( str.slice( ctx.i , endOfNumber ) , 10 ) ;
|
|
633
|
+
ctx.i = endOfNumber + 2 ;
|
|
634
|
+
|
|
635
|
+
var lastPart = ctx.parts[ ctx.parts.length - 1 ] ;
|
|
636
|
+
|
|
637
|
+
if ( ! lastPart || lastPart.type !== 'orderedList' ) {
|
|
638
|
+
ctx.parts.push( { type: 'orderedList' , indent } ) ;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
stack( ctx ) ;
|
|
642
|
+
|
|
643
|
+
ctx.parts.push( { type: 'orderedListItem' , indent , order } ) ;
|
|
644
|
+
|
|
645
|
+
var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , LIST_ITEM_END_PARAMS ) ;
|
|
646
|
+
parseInlineChildren( str , ctx , blockEnd ) ;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
const MEDIA_DATA_MARK = {
|
|
652
|
+
text: true , href: true , style: false , extra: false
|
|
653
|
+
} ;
|
|
654
|
+
|
|
655
|
+
function parseMedia( str , ctx , float = null ) {
|
|
656
|
+
var end = searchCloser( str , ctx.i + 3 , '[' , ']' ) ;
|
|
657
|
+
if ( end < 0 ) { return ; }
|
|
658
|
+
|
|
659
|
+
var text = str.slice( ctx.i + 3 , end ) ;
|
|
660
|
+
|
|
661
|
+
ctx.i = end ;
|
|
662
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
663
|
+
var data = parseDataMark( str , ctx , MEDIA_DATA_MARK ) ;
|
|
664
|
+
if ( ! data ) { return ; }
|
|
665
|
+
|
|
666
|
+
if ( ! data.href?.[ 0 ] ) { return ; }
|
|
667
|
+
|
|
668
|
+
var type = 'imageBlock' ;
|
|
669
|
+
|
|
670
|
+
if ( data.href[ 1 ] ) {
|
|
671
|
+
switch ( data.href[ 1 ] ) {
|
|
672
|
+
case 'image' :
|
|
673
|
+
type = 'imageBlock' ;
|
|
674
|
+
break ;
|
|
675
|
+
case 'audio' :
|
|
676
|
+
type = 'audioBlock' ;
|
|
677
|
+
break ;
|
|
678
|
+
case 'video' :
|
|
679
|
+
type = 'videoBlock' ;
|
|
680
|
+
break ;
|
|
681
|
+
default :
|
|
682
|
+
return ;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
var params = { type , altText: text , href: data.href[ 0 ] } ;
|
|
687
|
+
|
|
688
|
+
if ( float ) { params.float = float ; }
|
|
689
|
+
|
|
690
|
+
if ( data.text?.length ) {
|
|
691
|
+
params.caption = data.text[ 0 ] ;
|
|
692
|
+
if ( data.text[ 1 ] ) { params.title = data.text[ 1 ] ; }
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
//if ( data.extra?.length ) { params.title = data.extra[ 0 ] ; }
|
|
696
|
+
|
|
697
|
+
ctx.parts.push( params ) ;
|
|
698
|
+
ctx.i ++ ;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
function parseHorizontalRule( str , ctx ) {
|
|
704
|
+
var params = { type: 'horizontalRule' } ,
|
|
705
|
+
streak = countStreak( str , ctx.i , '-' ) ;
|
|
706
|
+
|
|
707
|
+
if (
|
|
708
|
+
str[ ctx.i + streak ] === '<'
|
|
709
|
+
&& str[ ctx.i + streak + 1 ] === '>'
|
|
710
|
+
&& str[ ctx.i + streak + 2 ] === '-'
|
|
711
|
+
&& str[ ctx.i + streak + 3 ] === '-'
|
|
712
|
+
&& str[ ctx.i + streak + 4 ] === '-'
|
|
713
|
+
) {
|
|
714
|
+
params.clearFloat = true ;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
ctx.parts.push( params ) ;
|
|
718
|
+
ctx.i = searchEndOfLine( str , ctx.i ) + 1 ;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
function parseClearFloat( str , ctx ) {
|
|
724
|
+
var streak = countStreak( str , ctx.i + 1 , '-' ) ;
|
|
725
|
+
|
|
726
|
+
if ( str[ ctx.i + 1 + streak ] === '>' ) {
|
|
727
|
+
ctx.parts.push( { type: 'clearFloat' } ) ;
|
|
728
|
+
ctx.i = searchEndOfLine( str , ctx.i ) + 1 ;
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
parseParagraph( str , ctx ) ;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
function parseCodeBlock( str , ctx ) {
|
|
738
|
+
var streak = countStreak( str , ctx.i , '`' ) ,
|
|
739
|
+
endOfLine = searchEndOfLine( str , ctx.i + streak ) ,
|
|
740
|
+
lang = str.slice( ctx.i + streak , endOfLine ).trim() || null ,
|
|
741
|
+
contentStart = endOfLine + 1 ;
|
|
742
|
+
|
|
743
|
+
var ends = searchBlockSwitchCloser( str , contentStart , '`' , 3 ) ;
|
|
744
|
+
|
|
745
|
+
if ( ! ends ) {
|
|
746
|
+
return parseParagraph( str , ctx ) ;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
var [ contentEnd , blockEnd ] = ends ;
|
|
750
|
+
|
|
751
|
+
var params = { type: 'codeBlock' } ;
|
|
752
|
+
if ( lang ) { params.lang = lang ; }
|
|
753
|
+
params.text = str.slice( contentStart , contentEnd - 1 ) ; // We strip the last newline
|
|
754
|
+
ctx.parts.push( params ) ;
|
|
755
|
+
ctx.i = blockEnd ;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
function parseMetadata( str , ctx ) {
|
|
761
|
+
var endOfLine = searchEndOfLine( str , ctx.i + 4 ) ,
|
|
762
|
+
nextBracket = searchNext( str , ctx.i + 4 , endOfLine , '[' ) ;
|
|
763
|
+
|
|
764
|
+
if ( nextBracket < 0 ) { return parseParagraph( str , ctx ) ; }
|
|
765
|
+
|
|
766
|
+
var type = str.slice( ctx.i + 4 , nextBracket ).trim() || 'metadata' ,
|
|
767
|
+
contentStart = endOfLine + 1 ,
|
|
768
|
+
ends = searchFixedBlockSwitchCloser( str , contentStart , ']]---' ) ;
|
|
769
|
+
|
|
770
|
+
if ( ! ends ) { return parseParagraph( str , ctx ) ; }
|
|
771
|
+
|
|
772
|
+
var [ contentEnd , blockEnd ] = ends ;
|
|
773
|
+
|
|
774
|
+
if ( ! ctx.rawMetadata ) { ctx.rawMetadata = {} ; }
|
|
775
|
+
if ( ! ctx.rawMetadata[ type ] ) { ctx.rawMetadata[ type ] = '' ; }
|
|
776
|
+
ctx.rawMetadata[ type ] += str.slice( contentStart , contentEnd ) ; // We DON'T strip the last newline
|
|
777
|
+
ctx.i = blockEnd ;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
function parseAnchor( str , ctx ) {
|
|
783
|
+
//console.log( "parseAnchor()" ) ;
|
|
784
|
+
var end = searchCloser( str , ctx.i + 2 , '(' , ')' , true ) ;
|
|
785
|
+
if ( end < 0 ) { return parseParagraph( str , ctx ) ; }
|
|
786
|
+
|
|
787
|
+
ctx.parts.push( {
|
|
788
|
+
type: 'anchor' ,
|
|
789
|
+
href: str.slice( ctx.i + 2 , end )
|
|
790
|
+
} ) ;
|
|
791
|
+
|
|
792
|
+
ctx.i = end + 1 ;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
function parseTableCaption( str , ctx ) {
|
|
798
|
+
//console.log( "parseTableCaption()" ) ;
|
|
799
|
+
var lastCharOfLine = searchLastCharOfLine( str , ctx.i + 1 ) ;
|
|
800
|
+
|
|
801
|
+
var end = searchCloser( str , ctx.i + 2 , '[' , ']' , true , lastCharOfLine ) ;
|
|
802
|
+
|
|
803
|
+
// Check that the syntax is correct: |[ must be followed by a space and ]| must be preceded by a space
|
|
804
|
+
if ( str[ ctx.i + 2 ] !== ' ' || str[ end - 1 ] !== ' ' || str[ end + 1 ] !== '|' ) {
|
|
805
|
+
return parseParagraph( str , ctx ) ;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
var table = ctx.parts[ ctx.parts.length - 1 ] ;
|
|
809
|
+
|
|
810
|
+
if ( ! table || table.type !== 'table' || ctx.lastLineWasEmpty ) {
|
|
811
|
+
table = { type: 'table' , columns: [] } ;
|
|
812
|
+
ctx.parts.push( table ) ;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
ctx.i += 3 ;
|
|
816
|
+
stack( ctx ) ;
|
|
817
|
+
|
|
818
|
+
var lastRow = ctx.parts[ ctx.parts.length - 1 ] || null ,
|
|
819
|
+
tableCaption = lastRow ;
|
|
820
|
+
|
|
821
|
+
if ( ! lastRow || lastRow.type !== 'tableCaption' ) {
|
|
822
|
+
tableCaption = { type: 'tableCaption' } ;
|
|
823
|
+
ctx.parts.push( tableCaption ) ;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
parseInlineChildren( str , ctx , end - 1 , true ) ;
|
|
827
|
+
|
|
828
|
+
if ( str[ end + 2 ] === '<' ) {
|
|
829
|
+
ctx.i = end + 1 ;
|
|
830
|
+
let data = parseDataMark( str , ctx , CELL_DATA_MARK , lastCharOfLine + 1 , false ) ;
|
|
831
|
+
if ( data?.style?.[ 0 ] ) { tableCaption.style = data.style[ 0 ] ; }
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
ctx.i = lastCharOfLine + 1 ;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
const CELL_DATA_MARK = {
|
|
840
|
+
text: false , href: false , style: true , extra: false
|
|
841
|
+
} ;
|
|
842
|
+
|
|
843
|
+
function parseTableRow( str , ctx ) {
|
|
844
|
+
//console.log( "parseTableRow()" ) ;
|
|
845
|
+
var lastCharOfLine = searchLastCharOfLine( str , ctx.i + 1 ) ,
|
|
846
|
+
table = ctx.parts[ ctx.parts.length - 1 ] ,
|
|
847
|
+
tableRow ,
|
|
848
|
+
mergeMode = false ;
|
|
849
|
+
|
|
850
|
+
if ( ! table || table.type !== 'table' || ctx.lastLineWasEmpty ) {
|
|
851
|
+
table = { type: 'table' , columns: [] , children: [] } ;
|
|
852
|
+
ctx.parts.push( table ) ;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
stack( ctx ) ;
|
|
856
|
+
|
|
857
|
+
if ( table.multilineRowMode && ctx.lastBlock === BLOCK_TABLE_ROW ) {
|
|
858
|
+
//console.error( "???" , ctx.lastBlock , BLOCK_TABLE_ROW ) ;
|
|
859
|
+
let lastRow = ctx.parts[ ctx.parts.length - 1 ] || null ;
|
|
860
|
+
tableRow = lastRow ;
|
|
861
|
+
|
|
862
|
+
if ( ! lastRow || lastRow.type !== 'tableRow' ) {
|
|
863
|
+
tableRow = { type: 'tableRow' } ;
|
|
864
|
+
ctx.parts.push( tableRow ) ;
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
mergeMode = true ;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
tableRow = { type: 'tableRow' } ;
|
|
872
|
+
ctx.parts.push( tableRow ) ;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
stack( ctx ) ;
|
|
876
|
+
|
|
877
|
+
if ( mergeMode ) { return parseTableMultilineRow( str , ctx , lastCharOfLine , table , tableRow ) ; }
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
//var leftAlign , rightAlign , leftCenter , rightCenter , headColumn ;
|
|
881
|
+
var columnSeparator , style ,
|
|
882
|
+
nextBar , firstSpace , lastSpace ,
|
|
883
|
+
firstBar = ctx.i ,
|
|
884
|
+
currentBar = ctx.i ;
|
|
885
|
+
|
|
886
|
+
while ( ( nextBar = searchNext( str , currentBar + 1 , lastCharOfLine + 1 , '|' ) ) !== - 1 ) {
|
|
887
|
+
//leftAlign = rightAlign = leftCenter = rightCenter = headColumn = false ;
|
|
888
|
+
columnSeparator = false ;
|
|
889
|
+
style = null ;
|
|
890
|
+
|
|
891
|
+
lastSpace = searchPrevious( str , nextBar - 1 , currentBar , ' ' ) ;
|
|
892
|
+
|
|
893
|
+
if ( str[ nextBar + 1 ] === '|' ) { columnSeparator = true ; }
|
|
894
|
+
|
|
895
|
+
if ( str[ currentBar + 1 ] === '<' ) {
|
|
896
|
+
ctx.i = currentBar ;
|
|
897
|
+
let data = parseDataMark( str , ctx , CELL_DATA_MARK , nextBar , false ) ;
|
|
898
|
+
if ( data?.style?.[ 0 ] ) { style = data.style[ 0 ] ; }
|
|
899
|
+
firstSpace = searchNext( str , ctx.i , nextBar , ' ' ) ;
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
firstSpace = searchNext( str , currentBar + 1 , nextBar , ' ' ) ;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
let tableCell = { type: 'tableCell' } ;
|
|
906
|
+
|
|
907
|
+
// The '|' bar position helps for column span calculation
|
|
908
|
+
// sx = Start X, the x position of the left bar
|
|
909
|
+
tableCell.sx = currentBar - firstBar ;
|
|
910
|
+
// ex = End X, the x position of the right bar
|
|
911
|
+
tableCell.ex = nextBar - firstBar ;
|
|
912
|
+
|
|
913
|
+
if ( style ) { tableCell.style = style ; }
|
|
914
|
+
if ( columnSeparator ) { tableCell.columnSeparator = true ; }
|
|
915
|
+
|
|
916
|
+
ctx.parts.push( tableCell ) ;
|
|
917
|
+
|
|
918
|
+
ctx.i = firstSpace + 1 ;
|
|
919
|
+
parseInlineChildren( str , ctx , lastSpace , true ) ;
|
|
920
|
+
|
|
921
|
+
currentBar = columnSeparator ? nextBar + 1 : nextBar ;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Compute cells indexes, columnSpan, rowSpan, column template
|
|
925
|
+
computeIndexColumnSpan( ctx , table , tableRow ) ;
|
|
926
|
+
|
|
927
|
+
if ( str[ currentBar + 1 ] === '<' ) {
|
|
928
|
+
ctx.i = currentBar ;
|
|
929
|
+
let data = parseDataMark( str , ctx , CELL_DATA_MARK , lastCharOfLine + 1 , false ) ;
|
|
930
|
+
if ( data?.style?.[ 0 ] ) { tableRow.style = data.style[ 0 ] ; }
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
ctx.i = lastCharOfLine + 1 ;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
// Merge current row with the previous
|
|
939
|
+
// Should only be called by parseTableRow() which put in the correct scope
|
|
940
|
+
function parseTableMultilineRow( str , ctx , lastCharOfLine , table , tableRow ) {
|
|
941
|
+
var tableCell , columnSeparator , nextBar , firstSpace , lastSpace ,
|
|
942
|
+
//firstBar = ctx.i ,
|
|
943
|
+
currentBar = ctx.i ,
|
|
944
|
+
columnIndex = 0 ;
|
|
945
|
+
|
|
946
|
+
while ( ( nextBar = searchNext( str , currentBar + 1 , lastCharOfLine + 1 , '|' ) ) !== - 1 ) {
|
|
947
|
+
columnSeparator = false ;
|
|
948
|
+
|
|
949
|
+
firstSpace = searchNext( str , currentBar + 1 , nextBar , ' ' ) ;
|
|
950
|
+
lastSpace = searchPrevious( str , nextBar - 1 , currentBar , ' ' ) ;
|
|
951
|
+
|
|
952
|
+
if ( str[ nextBar + 1 ] === '|' ) { columnSeparator = true ; }
|
|
953
|
+
|
|
954
|
+
tableCell = tableRow.children[ columnIndex ] ;
|
|
955
|
+
|
|
956
|
+
if ( tableCell ) {
|
|
957
|
+
ctx.i = firstSpace + 1 ;
|
|
958
|
+
ctx.forceInlineChunkSpace = true ;
|
|
959
|
+
parseInlineChildrenOfParent( str , ctx , tableCell , lastSpace , true ) ;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
currentBar = columnSeparator ? nextBar + 1 : nextBar ;
|
|
963
|
+
columnIndex ++ ;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
ctx.i = lastCharOfLine + 1 ;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
function parseTableRowSeparator( str , ctx , thick = false ) {
|
|
972
|
+
//console.log( "parseTableRowSeparator()" ) ;
|
|
973
|
+
var tableRow , columnIndex ,
|
|
974
|
+
lastCharOfLine = searchLastCharOfLine( str , ctx.i + 1 ) ,
|
|
975
|
+
table = ctx.parts[ ctx.parts.length - 1 ] ;
|
|
976
|
+
|
|
977
|
+
if ( ! table || table.type !== 'table' || ctx.lastLineWasEmpty ) {
|
|
978
|
+
table = { type: 'table' , columns: [] , children: [] } ;
|
|
979
|
+
ctx.parts.push( table ) ;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Fix previous table row as table head row
|
|
983
|
+
if ( ! table.hasHeadSeparator ) { return parseTableHeadRowSeparator( str , ctx , thick , lastCharOfLine ) ; }
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
// So this is a true row separator, not a head/body separator
|
|
987
|
+
|
|
988
|
+
// If this is the first row separator, we have to merge all existing rows into one
|
|
989
|
+
if ( ! table.hasRowSeparator ) {
|
|
990
|
+
table.multilineRowMode = true ;
|
|
991
|
+
|
|
992
|
+
for ( let index = 0 ; index < table.children.length ; index ++ ) {
|
|
993
|
+
let child = table.children[ index ] ;
|
|
994
|
+
|
|
995
|
+
if ( child.type === 'tableRow' ) {
|
|
996
|
+
if ( ! tableRow ) {
|
|
997
|
+
tableRow = child ;
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
// All subsequent tableRows, are merged into the first tableRow
|
|
1001
|
+
|
|
1002
|
+
if ( child.children ) {
|
|
1003
|
+
for ( columnIndex = 0 ; columnIndex < child.children.length ; columnIndex ++ ) {
|
|
1004
|
+
let child2 = child.children[ columnIndex ] ;
|
|
1005
|
+
if ( child2.type === 'tableCell' || child2.type === 'tableHeadCell' ) {
|
|
1006
|
+
if ( tableRow.children[ columnIndex ] ) {
|
|
1007
|
+
// Merge the cells
|
|
1008
|
+
mergeInlineParts( tableRow.children[ columnIndex ].children , child2.children ) ;
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
tableRow.children[ columnIndex ] = child2 ;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
table.children.splice( index , 1 ) ;
|
|
1018
|
+
index -- ;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
table.hasRowSeparator = true ;
|
|
1025
|
+
|
|
1026
|
+
if ( ! tableRow ) {
|
|
1027
|
+
tableRow = searchLastChildOfType( table , 'tableRow' ) ;
|
|
1028
|
+
|
|
1029
|
+
if ( ! tableRow ) {
|
|
1030
|
+
ctx.i = lastCharOfLine + 1 ;
|
|
1031
|
+
return ;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if ( thick ) { tableRow.rowSeparator = true ; }
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
// Store row span
|
|
1039
|
+
var columnSeparator , nextBar ,
|
|
1040
|
+
firstBar = ctx.i ,
|
|
1041
|
+
currentBar = ctx.i ,
|
|
1042
|
+
columns = table.columns ,
|
|
1043
|
+
separatorCellIndex = 0 ;
|
|
1044
|
+
|
|
1045
|
+
while ( ( nextBar = searchNext( str , currentBar + 1 , lastCharOfLine + 1 , '|' ) ) !== - 1 ) {
|
|
1046
|
+
columnSeparator = false ;
|
|
1047
|
+
|
|
1048
|
+
if ( str[ nextBar + 1 ] === '|' ) { columnSeparator = true ; }
|
|
1049
|
+
|
|
1050
|
+
if ( str[ currentBar + 1 ] === '-' && str[ currentBar + 2 ] === ' ' && str[ nextBar - 1 ] === '-' && str[ nextBar - 2 ] === ' ' ) {
|
|
1051
|
+
// This is a rowSpan
|
|
1052
|
+
ctx.rowSpanTables.add( table ) ;
|
|
1053
|
+
if ( ! tableRow.continueRowSpan ) { tableRow.continueRowSpan = [] ; }
|
|
1054
|
+
|
|
1055
|
+
let sx = currentBar - firstBar ;
|
|
1056
|
+
let closestDelta = Infinity ;
|
|
1057
|
+
let closestColumnIndex = separatorCellIndex ;
|
|
1058
|
+
|
|
1059
|
+
for ( columnIndex = separatorCellIndex ; columnIndex < columns.length ; columnIndex ++ ) {
|
|
1060
|
+
let column = columns[ columnIndex ] ;
|
|
1061
|
+
let delta = Math.abs( sx - column.sx ) ;
|
|
1062
|
+
if ( delta < closestDelta ) {
|
|
1063
|
+
closestDelta = delta ;
|
|
1064
|
+
closestColumnIndex = columnIndex ;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
tableRow.continueRowSpan.push( closestColumnIndex ) ;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
currentBar = columnSeparator ? nextBar + 1 : nextBar ;
|
|
1072
|
+
separatorCellIndex ++ ;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
ctx.i = lastCharOfLine + 1 ;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
function parseTableHeadRowSeparator( str , ctx , thick , lastCharOfLine ) {
|
|
1081
|
+
var columnIndex , tableHeadRow ,
|
|
1082
|
+
table = ctx.parts[ ctx.parts.length - 1 ] ,
|
|
1083
|
+
columns = table.columns ;
|
|
1084
|
+
|
|
1085
|
+
table.hasHeadSeparator = true ;
|
|
1086
|
+
var leftAlign , rightAlign , leftCenter , rightCenter , headColumn , columnSeparator , style ,
|
|
1087
|
+
nextBar , firstHbar , lastHbar , hbarStreak ,
|
|
1088
|
+
hbarChar = thick ? '=' : '-' ,
|
|
1089
|
+
firstBar = ctx.i ,
|
|
1090
|
+
currentBar = ctx.i ;
|
|
1091
|
+
|
|
1092
|
+
columnIndex = 0 ;
|
|
1093
|
+
|
|
1094
|
+
while ( ( nextBar = searchNext( str , currentBar + 1 , lastCharOfLine + 1 , '|' ) ) !== - 1 ) {
|
|
1095
|
+
leftAlign = rightAlign = leftCenter = rightCenter = headColumn = columnSeparator = false ;
|
|
1096
|
+
style = null ;
|
|
1097
|
+
|
|
1098
|
+
firstHbar = searchNext( str , currentBar + 1 , nextBar , hbarChar ) ;
|
|
1099
|
+
lastHbar = searchPrevious( str , nextBar - 1 , currentBar , hbarChar ) ;
|
|
1100
|
+
|
|
1101
|
+
if ( str[ nextBar + 1 ] === '|' ) { columnSeparator = true ; }
|
|
1102
|
+
|
|
1103
|
+
if ( firstHbar !== - 1 ) {
|
|
1104
|
+
if ( firstHbar - currentBar >= 2 ) {
|
|
1105
|
+
for ( let i = currentBar + 1 ; i < firstHbar ; i ++ ) {
|
|
1106
|
+
if ( str[ i ] === '<' ) { leftAlign = true ; }
|
|
1107
|
+
else if ( str[ i ] === '>' ) { leftCenter = true ; }
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if ( nextBar - lastHbar >= 2 ) {
|
|
1112
|
+
for ( let i = lastHbar + 1 ; i < nextBar ; i ++ ) {
|
|
1113
|
+
if ( str[ i ] === '<' ) { rightCenter = true ; }
|
|
1114
|
+
else if ( str[ i ] === '>' ) { rightAlign = true ; }
|
|
1115
|
+
else if ( str[ i ] === ':' ) { headColumn = true ; }
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
hbarStreak = countStreak( str , firstHbar , hbarChar ) ;
|
|
1120
|
+
if ( firstHbar + hbarStreak - 1 !== lastHbar ) {
|
|
1121
|
+
// Check for style mark
|
|
1122
|
+
if ( str[ firstHbar + hbarStreak ] === '<' ) {
|
|
1123
|
+
ctx.i = firstHbar + hbarStreak - 1 ;
|
|
1124
|
+
let data = parseDataMark( str , ctx , CELL_DATA_MARK , lastHbar , false ) ;
|
|
1125
|
+
if ( data?.style?.[ 0 ] ) { style = data.style[ 0 ] ; }
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
let columnTemplate = columns[ columnIndex ] ;
|
|
1131
|
+
if ( ! columnTemplate ) { columnTemplate = columns[ columnIndex ] = {} ; }
|
|
1132
|
+
|
|
1133
|
+
// The '|' bar position helps for column span calculation
|
|
1134
|
+
// sx = Start X, the x position of the left bar
|
|
1135
|
+
columnTemplate.sx = currentBar - firstBar ;
|
|
1136
|
+
// ex = End X, the x position of the right bar
|
|
1137
|
+
columnTemplate.ex = nextBar - firstBar ;
|
|
1138
|
+
|
|
1139
|
+
if ( headColumn ) { columnTemplate.headColumn = true ; }
|
|
1140
|
+
if ( style ) { columnTemplate.style = style ; }
|
|
1141
|
+
if ( columnSeparator ) { columnTemplate.columnSeparator = true ; }
|
|
1142
|
+
if ( leftAlign || rightAlign || leftCenter || rightCenter ) {
|
|
1143
|
+
columnTemplate.align =
|
|
1144
|
+
leftCenter && rightCenter ? 'center' :
|
|
1145
|
+
leftAlign && rightAlign ? 'justify' :
|
|
1146
|
+
leftAlign ? 'left' :
|
|
1147
|
+
rightAlign ? 'right' :
|
|
1148
|
+
'default' ;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
currentBar = columnSeparator ? nextBar + 1 : nextBar ;
|
|
1152
|
+
columnIndex ++ ;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
// Should come AFTER, because it needs column info
|
|
1157
|
+
// Fix previous table row as table head row: turn all existing tableRow into tableHeadRow
|
|
1158
|
+
|
|
1159
|
+
for ( let index = 0 ; index < table.children.length ; index ++ ) {
|
|
1160
|
+
let child = table.children[ index ] ;
|
|
1161
|
+
|
|
1162
|
+
if ( child.type === 'tableRow' ) {
|
|
1163
|
+
if ( ! tableHeadRow ) {
|
|
1164
|
+
// This is the first tableRow, turn it into a a tableHeadRow
|
|
1165
|
+
child.type = 'tableHeadRow' ;
|
|
1166
|
+
|
|
1167
|
+
if ( child.children ) {
|
|
1168
|
+
for ( columnIndex = 0 ; columnIndex < child.children.length ; columnIndex ++ ) {
|
|
1169
|
+
let child2 = child.children[ columnIndex ] ;
|
|
1170
|
+
if ( child2.type === 'tableCell' || child2.type === 'tableHeadCell' ) {
|
|
1171
|
+
child2.type = 'tableHeadCell' ;
|
|
1172
|
+
child2.isColumnHead = true ;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
tableHeadRow = child ;
|
|
1178
|
+
if ( thick ) { tableHeadRow.rowSeparator = true ; }
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
// All subsequent tableRows, are merged into the first tableHeadRow created
|
|
1182
|
+
|
|
1183
|
+
if ( child.children ) {
|
|
1184
|
+
for ( columnIndex = 0 ; columnIndex < child.children.length ; columnIndex ++ ) {
|
|
1185
|
+
let child2 = child.children[ columnIndex ] ;
|
|
1186
|
+
if ( child2.type === 'tableCell' || child2.type === 'tableHeadCell' ) {
|
|
1187
|
+
if ( tableHeadRow.children[ columnIndex ] ) {
|
|
1188
|
+
// Merge the cells
|
|
1189
|
+
mergeInlineParts( tableHeadRow.children[ columnIndex ].children , child2.children ) ;
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
child2.type = 'tableHeadCell' ;
|
|
1193
|
+
child2.isColumnHead = true ;
|
|
1194
|
+
tableHeadRow.children[ columnIndex ] = child2 ;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
table.children.splice( index , 1 ) ;
|
|
1201
|
+
index -- ;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
if ( tableHeadRow ) {
|
|
1207
|
+
// Compute cells indexes, columnSpan, rowSpan, column template
|
|
1208
|
+
computeIndexColumnSpan( ctx , table , tableHeadRow ) ;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
ctx.i = lastCharOfLine + 1 ;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
function computeIndexColumnSpan( ctx , table , tableRow ) {
|
|
1217
|
+
var tableCell , cellIndex , column , columnIndex , columnSpan ,
|
|
1218
|
+
columns = table.columns ,
|
|
1219
|
+
extraSpan = columns ? columns.length - tableRow.children.length : 0 ;
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
for ( cellIndex = columnIndex = 0 ; cellIndex < tableRow.children.length ; cellIndex ++ , columnIndex ++ ) {
|
|
1223
|
+
tableCell = tableRow.children[ cellIndex ] ;
|
|
1224
|
+
tableCell.column = columnIndex ;
|
|
1225
|
+
columnSpan = 1 ;
|
|
1226
|
+
|
|
1227
|
+
if ( columns ) {
|
|
1228
|
+
column = columns[ columnIndex ] ;
|
|
1229
|
+
|
|
1230
|
+
if ( column ) {
|
|
1231
|
+
if ( column.headColumn ) {
|
|
1232
|
+
tableCell.type = 'tableHeadCell' ;
|
|
1233
|
+
tableCell.isRowHead = true ;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
while ( extraSpan > 0 && Math.abs( tableCell.ex - columns[ columnIndex ].ex ) > Math.abs( tableCell.ex - columns[ columnIndex + 1 ].ex ) ) {
|
|
1238
|
+
columnIndex ++ ;
|
|
1239
|
+
extraSpan -- ;
|
|
1240
|
+
columnSpan ++ ;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if ( columnSpan >= 2 ) {
|
|
1244
|
+
tableCell.columnSpan = columnSpan ;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
function parseInlineChildren( str , ctx , blockEnd , trim = false ) {
|
|
1253
|
+
stack( ctx ) ;
|
|
1254
|
+
parseInline( str , ctx , blockEnd , trim ) ;
|
|
1255
|
+
unstack( ctx ) ;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
function parseInlineChildrenOfParent( str , ctx , parent , blockEnd , trim = false ) {
|
|
1261
|
+
stack( ctx , parent ) ;
|
|
1262
|
+
parseInline( str , ctx , blockEnd , trim ) ;
|
|
1263
|
+
unstack( ctx ) ;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
// Try to parse non-block content
|
|
1269
|
+
function parseInline( str , ctx , blockEnd , trim = false ) {
|
|
1270
|
+
//console.log( "parseInline() -- remaining:" , ctx.i , str.slice( ctx.i ) ) ;
|
|
1271
|
+
var isSpace , scanEnd ,
|
|
1272
|
+
lastWasSpace = WHITE_SPACES.has( str[ ctx.i - 1 ] ) ;
|
|
1273
|
+
|
|
1274
|
+
scanEnd = blockEnd = blockEnd ?? searchEndOfLine( str , ctx.i ) ;
|
|
1275
|
+
|
|
1276
|
+
if ( trim ) {
|
|
1277
|
+
let first = searchNextNotInSet( str , ctx.i , blockEnd , WHITE_SPACES ) ;
|
|
1278
|
+
|
|
1279
|
+
if ( first === - 1 ) {
|
|
1280
|
+
ctx.i = blockEnd ;
|
|
1281
|
+
if ( str[ ctx.i ] === '\n' ) { ctx.i ++ ; }
|
|
1282
|
+
return ;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
let last = searchPreviousNotInSet( str , blockEnd - 1 , first - 1 , WHITE_SPACES ) ;
|
|
1286
|
+
// The scan can't fail, 'last' can't be -1, because the forward search succeeded
|
|
1287
|
+
ctx.i = first ;
|
|
1288
|
+
scanEnd = last + 1 ;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
ctx.iStartOfInlineChunk = ctx.i ;
|
|
1292
|
+
|
|
1293
|
+
for ( ; ctx.i < scanEnd ; ctx.i ++ ) {
|
|
1294
|
+
let char = str[ ctx.i ] ;
|
|
1295
|
+
|
|
1296
|
+
//if ( lastWasSpace ) {}
|
|
1297
|
+
//console.error( "Checking: " , string.inspect( char ) ) ;
|
|
1298
|
+
|
|
1299
|
+
isSpace = WHITE_SPACES.has( char ) ;
|
|
1300
|
+
|
|
1301
|
+
if ( isSpace ) {
|
|
1302
|
+
addInlineTextChunk( str , ctx ) ;
|
|
1303
|
+
parseWhiteSpace( str , ctx ) ;
|
|
1304
|
+
}
|
|
1305
|
+
else if ( char === '\\' ) {
|
|
1306
|
+
addInlineTextChunk( str , ctx ) ;
|
|
1307
|
+
parseEscape( str , ctx ) ;
|
|
1308
|
+
}
|
|
1309
|
+
else if ( char === '*' && ! WHITE_SPACES.has( str[ ctx.i + 1 ] ) ) {
|
|
1310
|
+
addInlineTextChunk( str , ctx ) ;
|
|
1311
|
+
parseEmphasis( str , ctx , scanEnd ) ;
|
|
1312
|
+
}
|
|
1313
|
+
else if ( char === '_' && ! WHITE_SPACES.has( str[ ctx.i + 1 ] ) ) {
|
|
1314
|
+
addInlineTextChunk( str , ctx ) ;
|
|
1315
|
+
parseDecoration( str , ctx , scanEnd ) ;
|
|
1316
|
+
}
|
|
1317
|
+
else if ( char === '`' ) {
|
|
1318
|
+
addInlineTextChunk( str , ctx ) ;
|
|
1319
|
+
parseCode( str , ctx , scanEnd ) ;
|
|
1320
|
+
}
|
|
1321
|
+
else if ( char === '[' ) {
|
|
1322
|
+
addInlineTextChunk( str , ctx ) ;
|
|
1323
|
+
parseStyledText( str , ctx , scanEnd ) ;
|
|
1324
|
+
}
|
|
1325
|
+
else if ( char === '!' && str[ ctx.i + 1 ] === '[' && lastWasSpace ) {
|
|
1326
|
+
addInlineTextChunk( str , ctx ) ;
|
|
1327
|
+
parseImage( str , ctx , scanEnd ) ;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
lastWasSpace = isSpace ;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
addInlineTextChunk( str , ctx ) ;
|
|
1334
|
+
|
|
1335
|
+
ctx.i = blockEnd ;
|
|
1336
|
+
if ( str[ ctx.i ] === '\n' ) { ctx.i ++ ; }
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
function addInlineTextChunk( str , ctx , forcedChunk = null ) {
|
|
1342
|
+
var chunk = forcedChunk ?? str.slice( ctx.iStartOfInlineChunk , ctx.i ) ;
|
|
1343
|
+
|
|
1344
|
+
if ( ctx.forceInlineChunkSpace ) {
|
|
1345
|
+
chunk = ' ' + chunk ;
|
|
1346
|
+
ctx.forceInlineChunkSpace = false ;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
if ( chunk ) {
|
|
1350
|
+
let lastPart = ctx.parts[ ctx.parts.length - 1 ] ;
|
|
1351
|
+
|
|
1352
|
+
if ( lastPart && lastPart.type === 'text' ) {
|
|
1353
|
+
lastPart.text += chunk ;
|
|
1354
|
+
}
|
|
1355
|
+
else {
|
|
1356
|
+
ctx.parts.push( {
|
|
1357
|
+
type: 'text' ,
|
|
1358
|
+
text: chunk
|
|
1359
|
+
} ) ;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if ( ! forcedChunk ) {
|
|
1364
|
+
ctx.iStartOfInlineChunk = ctx.i ;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
const WHITE_SPACES = new Set( [ ' ' , '\t' , '\n' , '\r' ] ) ;
|
|
1371
|
+
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
function parseWhiteSpace( str , ctx ) {
|
|
1375
|
+
var end = ctx.i + 1 ;
|
|
1376
|
+
while ( WHITE_SPACES.has( str[ end ] ) ) { end ++ ; }
|
|
1377
|
+
|
|
1378
|
+
ctx.i = end - 1 ;
|
|
1379
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1380
|
+
|
|
1381
|
+
addInlineTextChunk( str , ctx , ' ' ) ;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
function parseEscape( str , ctx ) {
|
|
1387
|
+
if ( ctx.i + 1 >= str.length ) {
|
|
1388
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1389
|
+
return ;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
if ( str[ ctx.i + 1 ] === ' ' ) {
|
|
1393
|
+
if ( str[ ctx.i - 1 ] === '\n' ) {
|
|
1394
|
+
addInlineTextChunk( str , ctx , '\n' ) ;
|
|
1395
|
+
}
|
|
1396
|
+
else if ( searchEndOfEmptyLine( str , ctx.i + 2 ) !== - 1 ) {
|
|
1397
|
+
// Since we are not at the begining of the line, it actually search for trailing white chars
|
|
1398
|
+
addInlineTextChunk( str , ctx , '\n' ) ;
|
|
1399
|
+
}
|
|
1400
|
+
else {
|
|
1401
|
+
addInlineTextChunk( str , ctx , ' ' ) ;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
else if ( str[ ctx.i + 1 ] === '\n' ) {
|
|
1405
|
+
addInlineTextChunk( str , ctx , '\n' ) ;
|
|
1406
|
+
}
|
|
1407
|
+
else {
|
|
1408
|
+
addInlineTextChunk( str , ctx , str[ ctx.i + 1 ] ) ;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
ctx.i ++ ;
|
|
1412
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
|
|
1416
|
+
|
|
1417
|
+
function parseEmphasis( str , ctx , blockEnd ) {
|
|
1418
|
+
//console.error( "parseStyledText()" ) ;
|
|
1419
|
+
var streak = countStreak( str , ctx.i , '*' ) ;
|
|
1420
|
+
if ( streak > 3 ) { return ; }
|
|
1421
|
+
var end = searchSwitchCloser( str , ctx.i + streak , '*' , streak , true , false , blockEnd ) ;
|
|
1422
|
+
if ( end < 0 ) { return ; }
|
|
1423
|
+
|
|
1424
|
+
var text = str.slice( ctx.i + streak , end + 1 - streak ) ;
|
|
1425
|
+
|
|
1426
|
+
ctx.parts.push( { type: 'emphasis' , level: streak , text } ) ;
|
|
1427
|
+
ctx.i = end ;
|
|
1428
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
function parseDecoration( str , ctx , blockEnd ) {
|
|
1434
|
+
//console.error( "parseStyledText()" ) ;
|
|
1435
|
+
var streak = countStreak( str , ctx.i , '_' ) ;
|
|
1436
|
+
if ( streak > 2 ) { return ; }
|
|
1437
|
+
var end = searchSwitchCloser( str , ctx.i + streak , '_' , streak , true , false , blockEnd ) ;
|
|
1438
|
+
if ( end < 0 ) { return ; }
|
|
1439
|
+
|
|
1440
|
+
var text = str.slice( ctx.i + streak , end + 1 - streak ) ;
|
|
1441
|
+
|
|
1442
|
+
ctx.parts.push( {
|
|
1443
|
+
type: 'decoration' , underline: true , level: streak , text
|
|
1444
|
+
} ) ;
|
|
1445
|
+
ctx.i = end ;
|
|
1446
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
|
|
1451
|
+
function parseCode( str , ctx , blockEnd ) {
|
|
1452
|
+
//console.error( "parseStyledText()" ) ;
|
|
1453
|
+
var streak = countStreak( str , ctx.i , '`' ) ;
|
|
1454
|
+
// Markdown supports inline code inside two pairs of backquote, to allow backquote in code, hence streak can be 2.
|
|
1455
|
+
if ( streak > 2 ) { return ; }
|
|
1456
|
+
var end = searchSwitchCloser( str , ctx.i + streak , '`' , streak , false , false , blockEnd ) ;
|
|
1457
|
+
if ( end < 0 ) { return ; }
|
|
1458
|
+
|
|
1459
|
+
var sliceStart = ctx.i + streak ,
|
|
1460
|
+
sliceEnd = end + 1 - streak ;
|
|
1461
|
+
|
|
1462
|
+
if ( str[ sliceStart ] === ' ' && str[ sliceStart + 1 ] === '`' ) { sliceStart ++ ; }
|
|
1463
|
+
if ( str[ sliceEnd - 1 ] === ' ' && str[ sliceEnd - 2 ] === '`' ) { sliceEnd -- ; }
|
|
1464
|
+
|
|
1465
|
+
var text = str.slice( sliceStart , sliceEnd ) ;
|
|
1466
|
+
|
|
1467
|
+
ctx.parts.push( { type: 'code' , text } ) ;
|
|
1468
|
+
ctx.i = end ;
|
|
1469
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
|
|
1473
|
+
|
|
1474
|
+
const STYLE_DATA_MARK = {
|
|
1475
|
+
text: true , href: true , style: true , extra: false
|
|
1476
|
+
} ;
|
|
1477
|
+
|
|
1478
|
+
function parseStyledText( str , ctx , blockEnd ) {
|
|
1479
|
+
//console.error( "parseStyledText()" ) ;
|
|
1480
|
+
var end = searchCloser( str , ctx.i + 1 , '[' , ']' , false , blockEnd ) ;
|
|
1481
|
+
if ( end < 0 ) { return ; }
|
|
1482
|
+
|
|
1483
|
+
var text = str.slice( ctx.i + 1 , end ) ;
|
|
1484
|
+
|
|
1485
|
+
ctx.i = end ;
|
|
1486
|
+
var data = parseDataMark( str , ctx , STYLE_DATA_MARK , blockEnd ) ;
|
|
1487
|
+
if ( ! data ) { return ; }
|
|
1488
|
+
|
|
1489
|
+
var params = { type: '' , text } ;
|
|
1490
|
+
if ( data.href?.[ 0 ] ) { params.href = data.href[ 0 ] ; }
|
|
1491
|
+
if ( data.style?.[ 0 ] ) { params.style = data.style[ 0 ] ; }
|
|
1492
|
+
if ( data.text?.[ 0 ] ) { params.title = data.text[ 0 ] ; }
|
|
1493
|
+
|
|
1494
|
+
if ( params.href ) {
|
|
1495
|
+
params.type = 'link' ;
|
|
1496
|
+
ctx.parts.push( params ) ;
|
|
1497
|
+
}
|
|
1498
|
+
else if ( params.style || params.title ) {
|
|
1499
|
+
params.type = 'styledText' ;
|
|
1500
|
+
ctx.parts.push( params ) ;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
const IMAGE_DATA_MARK = {
|
|
1507
|
+
text: true , href: true , style: false , extra: false
|
|
1508
|
+
} ;
|
|
1509
|
+
|
|
1510
|
+
function parseImage( str , ctx , blockEnd ) {
|
|
1511
|
+
//console.error( "parseStyledText()" ) ;
|
|
1512
|
+
var end = searchCloser( str , ctx.i + 2 , '[' , ']' , false , blockEnd ) ;
|
|
1513
|
+
if ( end < 0 ) { return ; }
|
|
1514
|
+
|
|
1515
|
+
var text = str.slice( ctx.i + 2 , end ) ;
|
|
1516
|
+
|
|
1517
|
+
ctx.i = end ;
|
|
1518
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1519
|
+
var data = parseDataMark( str , ctx , IMAGE_DATA_MARK , blockEnd ) ;
|
|
1520
|
+
if ( ! data ) { return ; }
|
|
1521
|
+
|
|
1522
|
+
var params = { type: '' , altText: text } ;
|
|
1523
|
+
if ( data.href?.[ 0 ] ) { params.href = data.href[ 0 ] ; }
|
|
1524
|
+
|
|
1525
|
+
if ( params.href ) {
|
|
1526
|
+
params.type = 'image' ;
|
|
1527
|
+
params.altText = text ;
|
|
1528
|
+
if ( data.text?.[ 0 ] ) { params.title = data.text[ 0 ] ; }
|
|
1529
|
+
ctx.parts.push( params ) ;
|
|
1530
|
+
}
|
|
1531
|
+
else {
|
|
1532
|
+
params.type = 'pictogram' ;
|
|
1533
|
+
params.code = text ;
|
|
1534
|
+
let emojiChar = emoji.get( text ) ;
|
|
1535
|
+
if ( emojiChar ) { params.emoji = emojiChar ; }
|
|
1536
|
+
|
|
1537
|
+
if ( data.text?.[ 0 ] ) { params.altText = data.text[ 0 ] ; }
|
|
1538
|
+
if ( data.text?.[ 1 ] ) { params.title = data.text[ 1 ] ; }
|
|
1539
|
+
|
|
1540
|
+
if ( emojiChar && ! params.altText ) {
|
|
1541
|
+
params.altText = emoji.getCanonicalName( emojiChar ) ;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
ctx.parts.push( params ) ;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
|
|
1550
|
+
function parseDataMark( str , ctx , allow , blockEnd , forTextElement = true ) {
|
|
1551
|
+
var end ,
|
|
1552
|
+
data = {} ;
|
|
1553
|
+
|
|
1554
|
+
for ( ;; ) {
|
|
1555
|
+
if ( str[ ctx.i + 1 ] === '[' && allow.text ) {
|
|
1556
|
+
end = searchCloser( str , ctx.i + 2 , '[' , ']' , false , blockEnd ) ;
|
|
1557
|
+
if ( end < 0 ) { return ; }
|
|
1558
|
+
if ( ! data.text ) { data.text = [] ; }
|
|
1559
|
+
data.text.push( str.slice( ctx.i + 2 , end ) ) ;
|
|
1560
|
+
ctx.i = end ;
|
|
1561
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1562
|
+
}
|
|
1563
|
+
else if ( str[ ctx.i + 1 ] === '(' && allow.href ) {
|
|
1564
|
+
end = searchCloser( str , ctx.i + 2 , '(' , ')' , true , blockEnd ) ;
|
|
1565
|
+
if ( end < 0 ) { return ; }
|
|
1566
|
+
if ( ! data.href ) { data.href = [] ; }
|
|
1567
|
+
data.href.push( str.slice( ctx.i + 2 , end ) ) ;
|
|
1568
|
+
ctx.i = end ;
|
|
1569
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1570
|
+
}
|
|
1571
|
+
else if ( str[ ctx.i + 1 ] === '<' && allow.style ) {
|
|
1572
|
+
end = searchCloser( str , ctx.i + 2 , '<' , '>' , true , blockEnd ) ;
|
|
1573
|
+
if ( end < 0 ) { return ; }
|
|
1574
|
+
if ( ! data.style ) { data.style = [] ; }
|
|
1575
|
+
//data.style.push( str.slice( ctx.i + 2 , end ) ) ;
|
|
1576
|
+
let style = Style.parse( str.slice( ctx.i + 2 , end ) , forTextElement ) ;
|
|
1577
|
+
data.style.push( style ) ;
|
|
1578
|
+
//console.error( "Parsed style:" , style ) ;
|
|
1579
|
+
ctx.i = end ;
|
|
1580
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1581
|
+
}
|
|
1582
|
+
/*
|
|
1583
|
+
else if ( str[ ctx.i + 1 ] === '{' && allow.extra ) {
|
|
1584
|
+
end = searchCloser( str , ctx.i + 2 , '{' , '}' , false , blockEnd ) ;
|
|
1585
|
+
if ( end < 0 ) { return ; }
|
|
1586
|
+
if ( ! data.extra ) { data.extra = [] ; }
|
|
1587
|
+
data.extra.push( str.slice( ctx.i + 2 , end ) ) ;
|
|
1588
|
+
ctx.i = end ;
|
|
1589
|
+
ctx.iStartOfInlineChunk = ctx.i + 1 ;
|
|
1590
|
+
}
|
|
1591
|
+
*/
|
|
1592
|
+
else {
|
|
1593
|
+
break ;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
return data ;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
|
|
1601
|
+
|
|
1602
|
+
function postProcessTableRowSpan( table ) {
|
|
1603
|
+
var tableRow , tableCell , masterCell , lastRow , lastContinueRowSpan ;
|
|
1604
|
+
|
|
1605
|
+
// First pass: merge cells
|
|
1606
|
+
for ( tableRow of table.children ) {
|
|
1607
|
+
if ( tableRow.type !== 'tableRow' ) { continue ; }
|
|
1608
|
+
|
|
1609
|
+
if ( lastContinueRowSpan ) {
|
|
1610
|
+
for ( let columnIndex of lastContinueRowSpan ) {
|
|
1611
|
+
tableCell = searchColumn( tableRow , columnIndex ) ;
|
|
1612
|
+
masterCell = searchColumn( lastRow , columnIndex ) ;
|
|
1613
|
+
if ( tableCell && masterCell ) {
|
|
1614
|
+
if ( masterCell.masterCell ) { masterCell = masterCell.masterCell ; }
|
|
1615
|
+
mergeInlineParts( masterCell.children , tableCell.children ) ;
|
|
1616
|
+
masterCell.rowSpan = ( masterCell.rowSpan || 1 ) + 1 ;
|
|
1617
|
+
tableCell.masterCell = masterCell ;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
lastRow = tableRow ;
|
|
1623
|
+
lastContinueRowSpan = tableRow.continueRowSpan ;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// Second pass: remove dead cells
|
|
1627
|
+
for ( tableRow of table.children ) {
|
|
1628
|
+
if ( tableRow.type !== 'tableRow' ) { continue ; }
|
|
1629
|
+
inPlaceFilter( tableRow.children , tableCell_ => ! tableCell_.masterCell ) ;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
|
|
1634
|
+
|
|
1635
|
+
// Merge two inline parts blocks, adding an extra space joint in between if necessary
|
|
1636
|
+
function mergeInlineParts( parts , extraParts ) {
|
|
1637
|
+
if ( ! extraParts.length ) { return ; }
|
|
1638
|
+
|
|
1639
|
+
if ( ! parts.length ) {
|
|
1640
|
+
parts.push( ... extraParts ) ;
|
|
1641
|
+
return ;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
var lastPart = parts[ parts.length - 1 ] ,
|
|
1645
|
+
firstExtraPart = extraParts[ 0 ] ;
|
|
1646
|
+
|
|
1647
|
+
var needExtraSpace = ! WHITE_SPACES.has( lastPart.text[ lastPart.text.length - 1 ] ) && ! WHITE_SPACES.has( firstExtraPart.text[ 0 ] ) ;
|
|
1648
|
+
|
|
1649
|
+
if ( lastPart.type === 'text' && firstExtraPart.type === 'text' ) {
|
|
1650
|
+
// Combine the last existing with the first additional part
|
|
1651
|
+
if ( needExtraSpace ) { lastPart.text += ' ' + firstExtraPart.text ; }
|
|
1652
|
+
else { lastPart.text += firstExtraPart.text ; }
|
|
1653
|
+
for ( let i = 1 ; i < extraParts.length ; i ++ ) { parts.push( extraParts[ i ] ) ; }
|
|
1654
|
+
|
|
1655
|
+
return ;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
if ( needExtraSpace ) {
|
|
1659
|
+
if ( lastPart.type === 'text' ) {
|
|
1660
|
+
lastPart.text += ' ' ;
|
|
1661
|
+
}
|
|
1662
|
+
if ( firstExtraPart.type === 'text' ) {
|
|
1663
|
+
firstExtraPart.text = ' ' + firstExtraPart.text ;
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
// Add an additional joint part
|
|
1667
|
+
parts.push( { type: 'text' , text: ' ' } ) ;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
parts.push( ... extraParts ) ;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
|
|
1675
|
+
|
|
1676
|
+
function searchEndOfLine( str , i ) {
|
|
1677
|
+
var length = str.length ;
|
|
1678
|
+
|
|
1679
|
+
for ( ; i < length ; i ++ ) {
|
|
1680
|
+
if ( str[ i ] === '\n' ) { return i ; }
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
return str.length ;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
|
|
1687
|
+
|
|
1688
|
+
// Like searchEndOfLine() but return -1 if it's not an empty line (a line containing characters that are not space or tabs)
|
|
1689
|
+
function searchEndOfEmptyLine( str , i ) {
|
|
1690
|
+
var length = str.length ;
|
|
1691
|
+
|
|
1692
|
+
for ( ; i < length ; i ++ ) {
|
|
1693
|
+
if ( str[ i ] === '\n' ) { return i ; }
|
|
1694
|
+
if ( str[ i ] !== '\t' && str[ i ] !== ' ' ) { return - 1 ; }
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
return str.length ;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
|
|
1701
|
+
|
|
1702
|
+
function searchLastCharOfLine( str , i ) {
|
|
1703
|
+
var length = str.length ,
|
|
1704
|
+
lastCharIndex = - 1 ;
|
|
1705
|
+
|
|
1706
|
+
for ( ; i < length ; i ++ ) {
|
|
1707
|
+
if ( str[ i ] === '\n' ) { return lastCharIndex ; }
|
|
1708
|
+
if ( ! WHITE_SPACES.has( str[ i ] ) ) { lastCharIndex = i ; }
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
return lastCharIndex ;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
|
|
1715
|
+
|
|
1716
|
+
function searchNext( str , start , end , nextChar ) {
|
|
1717
|
+
for ( let i = start ; i < end ; i ++ ) {
|
|
1718
|
+
if ( str[ i ] === '\n' ) { return - 1 ; }
|
|
1719
|
+
if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
|
|
1720
|
+
if ( str[ i ] === nextChar ) { return i ; }
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
return - 1 ;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
|
|
1727
|
+
|
|
1728
|
+
function searchPrevious( str , start , end , previousChar ) {
|
|
1729
|
+
for ( let i = start ; i > end ; i -- ) {
|
|
1730
|
+
if ( str[ i ] === '\n' ) { return - 1 ; }
|
|
1731
|
+
if ( str[ i - 1 ] === '\\' ) { i -- ; continue ; }
|
|
1732
|
+
if ( str[ i ] === previousChar ) { return i ; }
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
return - 1 ;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
|
|
1739
|
+
|
|
1740
|
+
// notInSet is a Set
|
|
1741
|
+
function searchNextNotInSet( str , start , end , notInSet ) {
|
|
1742
|
+
for ( let i = start ; i < end ; i ++ ) {
|
|
1743
|
+
if ( str[ i ] === '\n' ) { return - 1 ; }
|
|
1744
|
+
if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
|
|
1745
|
+
if ( ! notInSet.has( str[ i ] ) ) { return i ; }
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
return - 1 ;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
// notInSet is a Set
|
|
1754
|
+
function searchPreviousNotInSet( str , start , end , notInSet ) {
|
|
1755
|
+
for ( let i = start ; i > end ; i -- ) {
|
|
1756
|
+
if ( str[ i ] === '\n' ) { return - 1 ; }
|
|
1757
|
+
if ( str[ i - 1 ] === '\\' ) { i -- ; continue ; }
|
|
1758
|
+
if ( ! notInSet.has( str[ i ] ) ) { return i ; }
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
return - 1 ;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
|
|
1765
|
+
|
|
1766
|
+
function searchCloser( str , i , opener , closer , inline = false , end = str.length ) {
|
|
1767
|
+
var opened = 1 ;
|
|
1768
|
+
|
|
1769
|
+
for ( ; i < end ; i ++ ) {
|
|
1770
|
+
if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
|
|
1771
|
+
if ( inline && str[ i ] === '\n' ) { break ; }
|
|
1772
|
+
|
|
1773
|
+
if ( str[ i ] === opener ) {
|
|
1774
|
+
opened ++ ;
|
|
1775
|
+
}
|
|
1776
|
+
else if ( str[ i ] === closer ) {
|
|
1777
|
+
opened -- ;
|
|
1778
|
+
if ( ! opened ) { return i ; }
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
return - 1 ;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
|
|
1786
|
+
|
|
1787
|
+
/*
|
|
1788
|
+
Same that searchCloser() but for things like '*' that starts and stop with the same amount of '*'.
|
|
1789
|
+
|
|
1790
|
+
closerStreak: number of times the closer char should repeat
|
|
1791
|
+
noSpaceBefore: no space should be present right before the closer
|
|
1792
|
+
inline: true if it doesn't span over multiple lines
|
|
1793
|
+
end: the position in the string where to stop scanning
|
|
1794
|
+
*/
|
|
1795
|
+
function searchSwitchCloser( str , i , closer , closerStreak = 1 , noSpaceBefore = false , inline = false , end = str.length ) {
|
|
1796
|
+
var streak = 0 ;
|
|
1797
|
+
|
|
1798
|
+
for ( ; i < end ; i ++ ) {
|
|
1799
|
+
if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
|
|
1800
|
+
if ( inline && str[ i ] === '\n' ) { break ; }
|
|
1801
|
+
|
|
1802
|
+
if ( str[ i ] === closer && ( ! noSpaceBefore || ! WHITE_SPACES.has( str[ i - 1 ] ) ) ) {
|
|
1803
|
+
streak ++ ;
|
|
1804
|
+
if ( streak === closerStreak && str[ i + 1 ] !== closer ) { return i ; }
|
|
1805
|
+
}
|
|
1806
|
+
else {
|
|
1807
|
+
streak = 0 ;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
return - 1 ;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
|
|
1815
|
+
|
|
1816
|
+
/*
|
|
1817
|
+
Same that searchSwitchCloser() but for things like block switcher like '```'.
|
|
1818
|
+
So it search for new lines, and search the switcher at the begining of that line.
|
|
1819
|
+
It returns an array with the block end index before and after the ending markup, or null if nothing was found.
|
|
1820
|
+
|
|
1821
|
+
closerMinStreak: number of times the closer char should repeat
|
|
1822
|
+
end: the position in the string where to stop scanning
|
|
1823
|
+
*/
|
|
1824
|
+
function searchBlockSwitchCloser( str , i , closer , closerMinStreak = 1 , end = str.length ) {
|
|
1825
|
+
var streak = 0 ;
|
|
1826
|
+
|
|
1827
|
+
while ( i < end ) {
|
|
1828
|
+
// Search next line
|
|
1829
|
+
while ( str[ i ] !== '\n' && i < end ) { i ++ ; }
|
|
1830
|
+
|
|
1831
|
+
i ++ ;
|
|
1832
|
+
|
|
1833
|
+
if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
|
|
1834
|
+
|
|
1835
|
+
if ( str[ i ] === closer ) {
|
|
1836
|
+
streak = countStreak( str , i , closer ) ;
|
|
1837
|
+
if ( streak >= closerMinStreak ) {
|
|
1838
|
+
end = searchEndOfEmptyLine( str , i + streak ) ;
|
|
1839
|
+
if ( end >= 0 ) {
|
|
1840
|
+
return [ i , end ] ;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
return null ;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
|
|
1850
|
+
|
|
1851
|
+
/*
|
|
1852
|
+
Same that searchBlockSwitchCloser() but with a callback function.
|
|
1853
|
+
*/
|
|
1854
|
+
function searchFixedBlockSwitchCloser( str , i , fixed , end = str.length ) {
|
|
1855
|
+
var test , j , failed ;
|
|
1856
|
+
|
|
1857
|
+
while ( i < end ) {
|
|
1858
|
+
// Search next line
|
|
1859
|
+
while ( str[ i ] !== '\n' && i < end ) { i ++ ; }
|
|
1860
|
+
|
|
1861
|
+
i ++ ;
|
|
1862
|
+
|
|
1863
|
+
if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
|
|
1864
|
+
|
|
1865
|
+
if ( str[ i ] === fixed[ 0 ] ) {
|
|
1866
|
+
failed = false ;
|
|
1867
|
+
for ( j = 1 ; j < fixed.length ; j ++ ) {
|
|
1868
|
+
if ( str[ i + j ] !== fixed[ j ] ) { failed = true ; break ; }
|
|
1869
|
+
}
|
|
1870
|
+
if ( ! failed ) {
|
|
1871
|
+
end = searchEndOfEmptyLine( str , i + fixed.length ) ;
|
|
1872
|
+
if ( end >= 0 ) {
|
|
1873
|
+
return [ i , end ] ;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
return null ;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
// Count successive char
|
|
1885
|
+
function countStreak( str , i , streaker ) {
|
|
1886
|
+
var length = str.length ,
|
|
1887
|
+
count = 0 ;
|
|
1888
|
+
|
|
1889
|
+
while ( i < length && str[ i ] === streaker ) {
|
|
1890
|
+
i ++ ;
|
|
1891
|
+
count ++ ;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
return count ;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
function searchChildOfType( parent , type ) {
|
|
1900
|
+
var children = parent.children ;
|
|
1901
|
+
if ( ! children ) { return null ; }
|
|
1902
|
+
|
|
1903
|
+
for ( let i = 0 ; i < children.length ; i ++ ) {
|
|
1904
|
+
let child = children[ i ] ;
|
|
1905
|
+
if ( child.type === type ) { return child ; }
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
return null ;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
function searchLastChildOfType( parent , type ) {
|
|
1914
|
+
var children = parent.children ;
|
|
1915
|
+
if ( ! children ) { return null ; }
|
|
1916
|
+
|
|
1917
|
+
for ( let i = children.length - 1 ; i >= 0 ; i -- ) {
|
|
1918
|
+
let child = children[ i ] ;
|
|
1919
|
+
if ( child.type === type ) { return child ; }
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
return null ;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
|
|
1926
|
+
|
|
1927
|
+
function searchColumn( tableRow , column ) {
|
|
1928
|
+
for ( let tableCell of tableRow.children ) {
|
|
1929
|
+
if ( tableCell.column === column ) { return tableCell ; }
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
return null ;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
|
|
1936
|
+
|
|
1937
|
+
function stack( ctx , parent = ctx.parts[ ctx.parts.length - 1 ] ) {
|
|
1938
|
+
ctx.stack.push( {
|
|
1939
|
+
parts: ctx.parts ,
|
|
1940
|
+
parent: ctx.parent ,
|
|
1941
|
+
iEndOfBlock: ctx.iEndOfBlock
|
|
1942
|
+
} ) ;
|
|
1943
|
+
|
|
1944
|
+
ctx.parent = parent ;
|
|
1945
|
+
ctx.parts = parent.children = parent.children || [] ;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
|
|
1949
|
+
|
|
1950
|
+
function unstack( ctx ) {
|
|
1951
|
+
if ( ! ctx.stack.length ) { return ; }
|
|
1952
|
+
var old = ctx.stack.pop() ;
|
|
1953
|
+
ctx.parts = old.parts ;
|
|
1954
|
+
ctx.parent = old.parent ;
|
|
1955
|
+
ctx.iEndOfBlock = old.iEndOfBlock ;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
|
|
1959
|
+
|
|
1960
|
+
function unstackToIndent( ctx , toIndent = 0 ) {
|
|
1961
|
+
var parentIndent = ctx.parent?.indent || 0 ,
|
|
1962
|
+
parentType = ctx.parent?.type ;
|
|
1963
|
+
|
|
1964
|
+
while ( ctx.parent && ( toIndent < parentIndent || ( toIndent === parentIndent && parentType !== 'quote' ) ) ) {
|
|
1965
|
+
let old = ctx.stack.pop() ;
|
|
1966
|
+
ctx.parts = old.parts ;
|
|
1967
|
+
ctx.parent = old.parent ;
|
|
1968
|
+
ctx.iEndOfBlock = old.iEndOfBlock ;
|
|
1969
|
+
parentIndent = ctx.parent?.indent || 0 ;
|
|
1970
|
+
parentType = ctx.parent?.type ;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|