Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Blocks.js
001 var setextLines = {};
002
003 function parse()
004 {
005 matchSetextLines();
006
007 var blocks = [],
008 blocksCnt = 0,
009 codeFence,
010 codeIndent = 4,
011 codeTag,
012 lineIsEmpty = true,
013 lists = [],
014 listsCnt = 0,
015 newContext = false,
016 textBoundary = 0,
017 breakParagraph,
018 continuation,
019 ignoreLen,
020 indentStr,
021 indentLen,
022 lfPos,
023 listIndex,
024 maxIndent,
025 minIndent,
026 blockDepth,
027 tagPos,
028 tagLen;
029
030 // Capture all the lines at once so that we can overwrite newlines safely, without preventing
031 // further matches
032 var matches = [],
033 m,
034 regexp = /^(?:(?=[-*+\d \t>`~#_])((?: {0,3}>(?:(?!!)|!(?![^\n>]*?!<)) ?)+)?([ \t]+)?(\* *\* *\*[* ]*$|- *- *-[- ]*$|_ *_ *_[_ ]*$)?((?:[-*+]|\d+\.)[ \t]+(?=\S))?[ \t]*(#{1,6}[ \t]+|```+[^`\n]*$|~~~+[^~\n]*$)?)?/gm;
035 while (m = regexp.exec(text))
036 {
037 matches.push(m);
038
039 // Move regexp.lastIndex if the current match is empty
040 if (m.index === regexp.lastIndex)
041 {
042 ++regexp.lastIndex;
043 }
044 }
045
046 matches.forEach(function(m)
047 {
048 var blockMarks = [],
049 matchPos = m.index,
050 matchLen = m[0].length,
051 startPos,
052 startLen,
053 endPos,
054 endLen;
055
056 ignoreLen = 0;
057 blockDepth = 0;
058
059 // If the last line was empty then this is not a continuation, and vice-versa
060 continuation = !lineIsEmpty;
061
062 // Capture the position of the end of the line and determine whether the line is empty
063 lfPos = text.indexOf("\n", matchPos);
064 lineIsEmpty = (lfPos === matchPos + matchLen && !m[3] && !m[4] && !m[5]);
065
066 // If the match is empty we need to move the cursor manually
067 if (!matchLen)
068 {
069 ++regexp.lastIndex;
070 }
071
072 // If the line is empty and it's the first empty line then we break current paragraph.
073 breakParagraph = (lineIsEmpty && continuation);
074
075 // Count block marks
076 if (m[1])
077 {
078 blockMarks = getBlockMarks(m[1]);
079 blockDepth = blockMarks.length;
080 ignoreLen = m[1].length;
081 if (codeTag && codeTag.hasAttribute('blockDepth'))
082 {
083 blockDepth = Math.min(blockDepth, codeTag.getAttribute('blockDepth'));
084 ignoreLen = computeBlockIgnoreLen(m[1], blockDepth);
085 }
086
087 // Overwrite block markup
088 overwrite(matchPos, ignoreLen);
089 }
090
091 // Close supernumerary blocks
092 if (blockDepth < blocksCnt && !continuation)
093 {
094 newContext = true;
095 do
096 {
097 var startTag = blocks.pop();
098 addEndTag(startTag.getName(), textBoundary, 0).pairWith(startTag);
099 }
100 while (blockDepth < --blocksCnt);
101 }
102
103 // Open new blocks
104 if (blockDepth > blocksCnt && !lineIsEmpty)
105 {
106 newContext = true;
107 do
108 {
109 var tagName = (blockMarks[blocksCnt] === '>!') ? 'SPOILER' : 'QUOTE';
110 blocks.push(addStartTag(tagName, matchPos, 0, -999));
111 }
112 while (blockDepth > ++blocksCnt);
113 }
114
115 // Compute the width of the indentation
116 var indentWidth = 0,
117 indentPos = 0;
118 if (m[2] && !codeFence)
119 {
120 indentStr = m[2];
121 indentLen = indentStr.length;
122
123 do
124 {
125 if (indentStr[indentPos] === ' ')
126 {
127 ++indentWidth;
128 }
129 else
130 {
131 indentWidth = (indentWidth + 4) & ~3;
132 }
133 }
134 while (++indentPos < indentLen && indentWidth < codeIndent);
135 }
136
137 // Test whether we're out of a code block
138 if (codeTag && !codeFence && indentWidth < codeIndent && !lineIsEmpty)
139 {
140 newContext = true;
141 }
142
143 if (newContext)
144 {
145 newContext = false;
146
147 // Close the code block if applicable
148 if (codeTag)
149 {
150 if (textBoundary > codeTag.getPos())
151 {
152 // Overwrite the whole block
153 overwrite(codeTag.getPos(), textBoundary - codeTag.getPos());
154 codeTag.pairWith(addEndTag('CODE', textBoundary, 0, -1));
155 }
156 else
157 {
158 // The code block is empty
159 codeTag.invalidate();
160 }
161 codeTag = null;
162 codeFence = null;
163 }
164
165 // Close all the lists
166 lists.forEach(function(list)
167 {
168 closeList(list, textBoundary);
169 });
170 lists = [];
171 listsCnt = 0;
172
173 // Mark the block boundary
174 if (matchPos)
175 {
176 markBoundary(matchPos - 1);
177 }
178 }
179
180 if (indentWidth >= codeIndent)
181 {
182 if (codeTag || !continuation)
183 {
184 // Adjust the amount of text being ignored
185 ignoreLen = (m[1] || '').length + indentPos;
186
187 if (!codeTag)
188 {
189 // Create code block
190 codeTag = addStartTag('CODE', matchPos + ignoreLen, 0, -999);
191 }
192
193 // Clear the captures to prevent any further processing
194 m = {};
195 }
196 }
197 else if (!codeTag)
198 {
199 var hasListItem = !!m[4];
200
201 if (!indentWidth && !continuation && !hasListItem)
202 {
203 // Start of a new context
204 listIndex = -1;
205 }
206 else if (continuation && !hasListItem)
207 {
208 // Continuation of current list item or paragraph
209 listIndex = listsCnt - 1;
210 }
211 else if (!listsCnt)
212 {
213 // We're not inside of a list already, we can start one if there's a list item
214 listIndex = (hasListItem) ? 0 : -1;
215 }
216 else
217 {
218 // We're inside of a list but we need to compute the depth
219 listIndex = 0;
220 while (listIndex < listsCnt && indentWidth > lists[listIndex].maxIndent)
221 {
222 ++listIndex;
223 }
224 }
225
226 // Close deeper lists
227 while (listIndex < listsCnt - 1)
228 {
229 closeList(lists.pop(), textBoundary);
230 --listsCnt;
231 }
232
233 // If there's no list item at current index, we'll need to either create one or
234 // drop down to previous index, in which case we have to adjust maxIndent
235 if (listIndex === listsCnt && !hasListItem)
236 {
237 --listIndex;
238 }
239
240 if (hasListItem && listIndex >= 0)
241 {
242 breakParagraph = true;
243
244 // Compute the position and amount of text consumed by the item tag
245 tagPos = matchPos + ignoreLen + indentPos;
246 tagLen = m[4].length;
247
248 // Create a LI tag that consumes its markup
249 var itemTag = addStartTag('LI', tagPos, tagLen);
250
251 // Overwrite the markup
252 overwrite(tagPos, tagLen);
253
254 // If the list index is within current lists count it means this is not a new
255 // list and we have to close the last item. Otherwise, it's a new list that we
256 // have to create
257 if (listIndex < listsCnt)
258 {
259 addEndTag('LI', textBoundary, 0).pairWith(lists[listIndex].itemTag);
260
261 // Record the item in the list
262 lists[listIndex].itemTag = itemTag;
263 lists[listIndex].itemTags.push(itemTag);
264 }
265 else
266 {
267 ++listsCnt;
268
269 if (listIndex)
270 {
271 minIndent = lists[listIndex - 1].maxIndent + 1;
272 maxIndent = Math.max(minIndent, listIndex * 4);
273 }
274 else
275 {
276 minIndent = 0;
277 maxIndent = indentWidth;
278 }
279
280 // Create a 0-width LIST tag right before the item tag LI
281 var listTag = addStartTag('LIST', tagPos, 0);
282
283 // Test whether the list item ends with a dot, as in "1."
284 if (m[4].indexOf('.') > -1)
285 {
286 listTag.setAttribute('type', 'decimal');
287
288 var start = +m[4];
289 if (start !== 1)
290 {
291 listTag.setAttribute('start', start);
292 }
293 }
294
295 // Record the new list depth
296 lists.push({
297 listTag : listTag,
298 itemTag : itemTag,
299 itemTags : [itemTag],
300 minIndent : minIndent,
301 maxIndent : maxIndent,
302 tight : true
303 });
304 }
305 }
306
307 // If we're in a list, on a non-empty line preceded with a blank line...
308 if (listsCnt && !continuation && !lineIsEmpty)
309 {
310 // ...and this is not the first item of the list...
311 if (lists[0].itemTags.length > 1 || !hasListItem)
312 {
313 // ...every list that is currently open becomes loose
314 lists.forEach(function(list)
315 {
316 list.tight = false;
317 });
318 }
319 }
320
321 codeIndent = (listsCnt + 1) * 4;
322 }
323
324 if (m[5])
325 {
326 // Headers
327 if (m[5][0] === '#')
328 {
329 startLen = m[5].length;
330 startPos = matchPos + matchLen - startLen;
331 endLen = getAtxHeaderEndTagLen(matchPos + matchLen, lfPos);
332 endPos = lfPos - endLen;
333
334 addTagPair('H' + /#{1,6}/.exec(m[5])[0].length, startPos, startLen, endPos, endLen);
335
336 // Mark the start and the end of the header as boundaries
337 markBoundary(startPos);
338 markBoundary(lfPos);
339
340 if (continuation)
341 {
342 breakParagraph = true;
343 }
344 }
345 // Code fence
346 else if (m[5][0] === '`' || m[5][0] === '~')
347 {
348 tagPos = matchPos + ignoreLen;
349 tagLen = lfPos - tagPos;
350
351 if (codeTag && m[5] === codeFence)
352 {
353 codeTag.pairWith(addEndTag('CODE', tagPos, tagLen, -1));
354 addIgnoreTag(textBoundary, tagPos - textBoundary);
355
356 // Overwrite the whole block
357 overwrite(codeTag.getPos(), tagPos + tagLen - codeTag.getPos());
358 codeTag = null;
359 codeFence = null;
360 }
361 else if (!codeTag)
362 {
363 // Create code block
364 codeTag = addStartTag('CODE', tagPos, tagLen);
365 codeFence = m[5].replace(/[^`~]+/, '');
366 codeTag.setAttribute('blockDepth', blockDepth);
367
368 // Ignore the next character, which should be a newline
369 addIgnoreTag(tagPos + tagLen, 1);
370
371 // Add the language if present, e.g. ```php
372 var lang = m[5].replace(/^[`~\s]*/, '').replace(/\s+$/, '');
373 if (lang !== '')
374 {
375 codeTag.setAttribute('lang', lang);
376 }
377 }
378 }
379 }
380 else if (m[3] && !listsCnt && text[matchPos + matchLen] !== "\x17")
381 {
382 // Horizontal rule
383 addSelfClosingTag('HR', matchPos + ignoreLen, matchLen - ignoreLen);
384 breakParagraph = true;
385
386 // Mark the end of the line as a boundary
387 markBoundary(lfPos);
388 }
389 else if (setextLines[lfPos] && setextLines[lfPos].blockDepth === blockDepth && !lineIsEmpty && !listsCnt && !codeTag)
390 {
391 // Setext-style header
392 addTagPair(
393 setextLines[lfPos].tagName,
394 matchPos + ignoreLen,
395 0,
396 setextLines[lfPos].endPos,
397 setextLines[lfPos].endLen
398 );
399
400 // Mark the end of the Setext line
401 markBoundary(setextLines[lfPos].endPos + setextLines[lfPos].endLen);
402 }
403
404 if (breakParagraph)
405 {
406 addParagraphBreak(textBoundary);
407 markBoundary(textBoundary);
408 }
409
410 if (!lineIsEmpty)
411 {
412 textBoundary = lfPos;
413 }
414
415 if (ignoreLen)
416 {
417 addIgnoreTag(matchPos, ignoreLen, 1000);
418 }
419 });
420 }
421
422 /**
423 * Close a list at given offset
424 *
425 * @param {!Object} list
426 * @param {number} textBoundary
427 */
428 function closeList(list, textBoundary)
429 {
430 addEndTag('LIST', textBoundary, 0).pairWith(list.listTag);
431 addEndTag('LI', textBoundary, 0).pairWith(list.itemTag);
432
433 if (list.tight)
434 {
435 list.itemTags.forEach(function(itemTag)
436 {
437 itemTag.removeFlags(RULE_CREATE_PARAGRAPHS);
438 });
439 }
440 }
441
442 /**
443 * Compute the amount of text to ignore at the start of a block line
444 *
445 * @param {string} str Original block markup
446 * @param {number} maxBlockDepth Maximum block depth
447 * @return {number} Number of characters to ignore
448 */
449 function computeBlockIgnoreLen(str, maxBlockDepth)
450 {
451 var remaining = str;
452 while (--maxBlockDepth >= 0)
453 {
454 remaining = remaining.replace(/^ *>!? ?/, '');
455 }
456
457 return str.length - remaining.length;
458 }
459
460 /**
461 * Return the length of the markup at the end of an ATX header
462 *
463 * @param {number} startPos Start of the header's text
464 * @param {number} endPos End of the header's text
465 * @return {number}
466 */
467 function getAtxHeaderEndTagLen(startPos, endPos)
468 {
469 var content = text.substring(startPos, endPos),
470 m = /[ \t]*#*[ \t]*$/.exec(content);
471
472 return m[0].length;
473 }
474
475 /**
476 * Capture and return block marks from given string
477 *
478 * @param {string} str Block markup, composed of ">", "!" and whitespace
479 * @return {!Array<string>}
480 */
481 function getBlockMarks(str)
482 {
483 var blockMarks = [],
484 regexp = />!?/g,
485 m;
486 while (m = regexp.exec(str))
487 {
488 blockMarks.push(m[0]);
489 }
490
491 return blockMarks;
492 }
493
494 /**
495 * Capture and store lines that contain a Setext-tyle header
496 */
497 function matchSetextLines()
498 {
499 // Capture the underlines used for Setext-style headers
500 if (text.indexOf('-') === -1 && text.indexOf('=') === -1)
501 {
502 return;
503 }
504
505 // Capture the any series of - or = alone on a line, optionally preceded with the
506 // angle brackets notation used in block markup
507 var m, regexp = /^(?=[-=>])(?:>!? ?)*(?=[-=])(?:-+|=+) *$/gm;
508
509 while (m = regexp.exec(text))
510 {
511 var match = m[0],
512 matchPos = m.index;
513
514 // Compute the position of the end tag. We start on the LF character before the
515 // match and keep rewinding until we find a non-space character
516 var endPos = matchPos - 1;
517 while (endPos > 0 && text[endPos - 1] === ' ')
518 {
519 --endPos;
520 }
521
522 // Store at the offset of the LF character
523 setextLines[matchPos - 1] = {
524 endLen : matchPos + match.length - endPos,
525 endPos : endPos,
526 blockDepth : match.length - match.replace(/>/g, '').length,
527 tagName : (match[0] === '=') ? 'H1' : 'H2'
528 };
529 }
530 }