| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Copyright (c) Microsoft Corporation. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  | // @ts-check
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {{ | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |  *    type: string, | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |  *    text?: string, | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |  *    children?: MarkdownNode[], | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |  *    codeLang?: string, | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |  *  }} MarkdownBaseNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {MarkdownBaseNode & { | 
					
						
							|  |  |  |  *    type: 'text', | 
					
						
							|  |  |  |  *    text: string, | 
					
						
							|  |  |  |  *  }} MarkdownTextNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {MarkdownBaseNode & { | 
					
						
							|  |  |  |  *    type: 'h0' | 'h1' | 'h2' | 'h3' | 'h4', | 
					
						
							|  |  |  |  *    text: string, | 
					
						
							|  |  |  |  *    children: MarkdownNode[] | 
					
						
							|  |  |  |  *  }} MarkdownHeaderNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {MarkdownBaseNode & { | 
					
						
							|  |  |  |  *    type: 'li', | 
					
						
							|  |  |  |  *    text: string, | 
					
						
							|  |  |  |  *    liType: 'default' | 'bullet' | 'ordinal', | 
					
						
							|  |  |  |  *    children: MarkdownNode[] | 
					
						
							|  |  |  |  *  }} MarkdownLiNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {MarkdownBaseNode & { | 
					
						
							|  |  |  |  *    type: 'code', | 
					
						
							|  |  |  |  *    lines: string[], | 
					
						
							|  |  |  |  *    codeLang: string, | 
					
						
							| 
									
										
										
										
											2023-03-29 23:15:39 +02:00
										 |  |  |  *    title?: string, | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |  *  }} MarkdownCodeNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {MarkdownBaseNode & { | 
					
						
							|  |  |  |  *    type: 'note', | 
					
						
							| 
									
										
										
										
											2022-11-14 13:05:05 -08:00
										 |  |  |  *    text: string, | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |  *    noteType: string, | 
					
						
							|  |  |  |  *  }} MarkdownNoteNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {MarkdownBaseNode & { | 
					
						
							|  |  |  |  *    type: 'null', | 
					
						
							|  |  |  |  *  }} MarkdownNullNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {MarkdownBaseNode & { | 
					
						
							|  |  |  |  *    type: 'properties', | 
					
						
							|  |  |  |  *    lines: string[], | 
					
						
							|  |  |  |  *  }} MarkdownPropsNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  | /** @typedef {{ | 
					
						
							|  |  |  |  * maxColumns?: number, | 
					
						
							|  |  |  |  * omitLastCR?: boolean, | 
					
						
							|  |  |  |  * flattenText?: boolean | 
					
						
							| 
									
										
										
										
											2023-05-10 18:38:12 +02:00
										 |  |  |  * renderCodeBlockTitlesInHeader?: boolean | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |  * }} RenderOptions | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  | /** @typedef {MarkdownTextNode | MarkdownLiNode | MarkdownCodeNode | MarkdownNoteNode | MarkdownHeaderNode | MarkdownNullNode | MarkdownPropsNode } MarkdownNode */ | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  | function flattenWrappedLines(content) { | 
					
						
							| 
									
										
										
										
											2021-01-08 15:00:14 -08:00
										 |  |  |   const inLines = content.replace(/\r\n/g, '\n').split('\n'); | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |   let inCodeBlock = false; | 
					
						
							|  |  |  |   const outLines = []; | 
					
						
							|  |  |  |   let outLineTokens = []; | 
					
						
							|  |  |  |   for (const line of inLines) { | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     const trimmedLine = line.trim(); | 
					
						
							| 
									
										
										
										
											2021-09-13 18:48:16 -07:00
										 |  |  |     const singleLineExpression = line.startsWith('#'); | 
					
						
							|  |  |  |     const codeBlockBoundary = trimmedLine.startsWith('```') || trimmedLine.startsWith('---') || trimmedLine.startsWith(':::'); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     let flushLastParagraph = !trimmedLine | 
					
						
							|  |  |  |       || trimmedLine.startsWith('1.') | 
					
						
							|  |  |  |       || trimmedLine.startsWith('<') | 
					
						
							|  |  |  |       || trimmedLine.startsWith('>') | 
					
						
							|  |  |  |       || trimmedLine.startsWith('|') | 
					
						
							|  |  |  |       || trimmedLine.startsWith('-') | 
					
						
							|  |  |  |       || trimmedLine.startsWith('*') | 
					
						
							|  |  |  |       || line.match(/\[[^\]]+\]:.*/) | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |       || singleLineExpression; | 
					
						
							| 
									
										
										
										
											2021-09-13 18:48:16 -07:00
										 |  |  |     if (codeBlockBoundary) { | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |       inCodeBlock = !inCodeBlock; | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       flushLastParagraph = true; | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     if (flushLastParagraph && outLineTokens.length) { | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |       outLines.push(outLineTokens.join('↵')); | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |       outLineTokens = []; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-13 18:48:16 -07:00
										 |  |  |     if (inCodeBlock || singleLineExpression || codeBlockBoundary) | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |       outLines.push(line); | 
					
						
							| 
									
										
										
										
											2020-12-03 16:02:34 -08:00
										 |  |  |     else if (trimmedLine) | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       outLineTokens.push(outLineTokens.length ? line.trim() : line); | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |   } | 
					
						
							|  |  |  |   if (outLineTokens.length) | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |     outLines.push(outLineTokens.join('↵')); | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |   return outLines; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {string[]} lines | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  | function buildTree(lines) { | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |   /** @type {MarkdownNode} */ | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |   const root = { | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |     type: 'h0', | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |     text: '<root>', | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  |     children: [] | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |   /** @type {MarkdownNode[]} */ | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |   const headerStack = [root]; | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |   /** @type {{ indent: string, node: MarkdownNode }[]} */ | 
					
						
							|  |  |  |   let sectionStack = []; | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * @param {string} indent | 
					
						
							|  |  |  |    * @param {MarkdownNode} node | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   const appendNode = (indent, node) => { | 
					
						
							|  |  |  |     while (sectionStack.length && sectionStack[0].indent.length >= indent.length) | 
					
						
							|  |  |  |       sectionStack.shift(); | 
					
						
							|  |  |  |     const parentNode = sectionStack.length ? sectionStack[0].node :headerStack[0]; | 
					
						
							|  |  |  |     if (!parentNode.children) | 
					
						
							|  |  |  |       parentNode.children = []; | 
					
						
							|  |  |  |     parentNode.children.push(node); | 
					
						
							|  |  |  |     if (node.type === 'li') | 
					
						
							|  |  |  |       sectionStack.unshift({ indent, node }); | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |   for (let i = 0; i < lines.length; ++i) { | 
					
						
							|  |  |  |     let line = lines[i]; | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     // Headers form hierarchy.
 | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |     const header = line.match(/^(#+)/); | 
					
						
							|  |  |  |     if (header) { | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  |       const h = header[1].length; | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |       const node = /** @type {MarkdownNode} */({ type: 'h' + h, text: line.substring(h + 1), children: [] }); | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       while (true) { | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |         const lastH = +headerStack[0].type.substring(1); | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  |         if (h <= lastH) | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |           headerStack.shift(); | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  |         else | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |       /** @type {MarkdownNode[]}*/(headerStack[0].children).push(node); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       headerStack.unshift(node); | 
					
						
							|  |  |  |       continue; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Remaining items respect indent-based nesting.
 | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |     const [, indent, content] = /** @type {string[]} */ (line.match('^([ ]*)(.*)')); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     if (content.startsWith('```')) { | 
					
						
							| 
									
										
										
										
											2023-03-29 23:15:39 +02:00
										 |  |  |       const [codeLang, title] = parseCodeBlockMetadata(content); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       /** @type {MarkdownNode} */ | 
					
						
							|  |  |  |       const node = { | 
					
						
							|  |  |  |         type: 'code', | 
					
						
							|  |  |  |         lines: [], | 
					
						
							| 
									
										
										
										
											2023-03-29 23:15:39 +02:00
										 |  |  |         codeLang, | 
					
						
							|  |  |  |         title, | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       }; | 
					
						
							|  |  |  |       line = lines[++i]; | 
					
						
							|  |  |  |       while (!line.trim().startsWith('```')) { | 
					
						
							| 
									
										
										
										
											2021-02-21 18:36:39 -08:00
										 |  |  |         if (line && !line.startsWith(indent)) { | 
					
						
							| 
									
										
										
										
											2021-01-08 15:00:14 -08:00
										 |  |  |           const from = Math.max(0, i - 5) | 
					
						
							|  |  |  |           const to = Math.min(lines.length, from + 10); | 
					
						
							|  |  |  |           const snippet = lines.slice(from, to); | 
					
						
							|  |  |  |           throw new Error(`Bad code block: ${snippet.join('\n')}`); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-02-21 18:36:39 -08:00
										 |  |  |         if (line) | 
					
						
							|  |  |  |           line = line.substring(indent.length); | 
					
						
							|  |  |  |         node.lines.push(line); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |         line = lines[++i]; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       appendNode(indent, node); | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |       continue; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-12 12:14:27 -08:00
										 |  |  |     if (content.startsWith(':::')) { | 
					
						
							|  |  |  |       /** @type {MarkdownNode} */ | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |       const node = /** @type {MarkdownNoteNode} */ ({ | 
					
						
							| 
									
										
										
										
											2021-01-12 12:14:27 -08:00
										 |  |  |         type: 'note', | 
					
						
							| 
									
										
										
										
											2021-03-18 13:50:17 -07:00
										 |  |  |         noteType: content.substring(3) | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-01-12 12:14:27 -08:00
										 |  |  |       line = lines[++i]; | 
					
						
							|  |  |  |       const tokens = []; | 
					
						
							|  |  |  |       while (!line.trim().startsWith(':::')) { | 
					
						
							|  |  |  |         if (!line.startsWith(indent)) { | 
					
						
							|  |  |  |           const from = Math.max(0, i - 5) | 
					
						
							|  |  |  |           const to = Math.min(lines.length, from + 10); | 
					
						
							|  |  |  |           const snippet = lines.slice(from, to); | 
					
						
							|  |  |  |           throw new Error(`Bad comment block: ${snippet.join('\n')}`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         tokens.push(line.substring(indent.length)); | 
					
						
							|  |  |  |         line = lines[++i]; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |       node.text = tokens.join('↵'); | 
					
						
							| 
									
										
										
										
											2021-01-12 12:14:27 -08:00
										 |  |  |       appendNode(indent, node); | 
					
						
							|  |  |  |       continue; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |     if (content.startsWith('---')) { | 
					
						
							|  |  |  |       /** @type {MarkdownNode} */ | 
					
						
							|  |  |  |       const node = { | 
					
						
							|  |  |  |         type: 'properties', | 
					
						
							|  |  |  |         lines: [], | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |       line = lines[++i]; | 
					
						
							|  |  |  |       while (!line.trim().startsWith('---')) { | 
					
						
							|  |  |  |         if (!line.startsWith(indent)) | 
					
						
							|  |  |  |           throw new Error('Bad header block ' + line); | 
					
						
							|  |  |  |         node.lines.push(line.substring(indent.length)); | 
					
						
							|  |  |  |         line = lines[++i]; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       appendNode(indent, node); | 
					
						
							|  |  |  |       continue; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     const liType = content.match(/^(-|1.|\*) /); | 
					
						
							|  |  |  |     const node = /** @type {MarkdownNode} */({ type: 'text', text: content }); | 
					
						
							|  |  |  |     if (liType) { | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |       const liNode = /** @type {MarkdownLiNode} */(node); | 
					
						
							|  |  |  |       liNode.type = 'li'; | 
					
						
							|  |  |  |       liNode.text = content.substring(liType[0].length); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       if (content.startsWith('1.')) | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |         liNode.liType = 'ordinal'; | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       else if (content.startsWith('*')) | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |         liNode.liType = 'bullet'; | 
					
						
							| 
									
										
										
										
											2021-01-06 12:41:17 -08:00
										 |  |  |       else | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |         liNode.liType = 'default'; | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |     const match = node.text?.match(/\*\*langs: (.*)\*\*(.*)/); | 
					
						
							| 
									
										
										
										
											2021-11-24 21:58:35 +01:00
										 |  |  |     if (match) { | 
					
						
							|  |  |  |       node.codeLang = match[1]; | 
					
						
							|  |  |  |       node.text = match[2]; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     appendNode(indent, node); | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  |   return root.children; | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-29 23:15:39 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {String} firstLine  | 
					
						
							|  |  |  |  * @returns {[string, string|undefined]} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function parseCodeBlockMetadata(firstLine) { | 
					
						
							|  |  |  |   const withoutBackticks = firstLine.substring(3); | 
					
						
							|  |  |  |   const match = withoutBackticks.match(/ title="(.+)"$/); | 
					
						
							|  |  |  |   if (match) | 
					
						
							|  |  |  |     return [withoutBackticks.substring(0, match.index), match[1]]; | 
					
						
							|  |  |  |   return [withoutBackticks, undefined]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {string} content | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function parse(content) { | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |   return buildTree(flattenWrappedLines(content)); | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode[]} nodes | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |  * @param {RenderOptions=} options | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  | function render(nodes, options) { | 
					
						
							| 
									
										
										
										
											2020-12-03 09:11:48 -08:00
										 |  |  |   const result = []; | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |   let lastNode; | 
					
						
							|  |  |  |   for (let node of nodes) { | 
					
						
							| 
									
										
										
										
											2022-06-10 16:34:31 -08:00
										 |  |  |     if (node.type === 'null') | 
					
						
							|  |  |  |       continue; | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |     innerRenderMdNode('', node, /** @type {MarkdownNode} */ (lastNode), result, options); | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |     lastNode = node; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |   if (!options?.omitLastCR && result[result.length - 1] !== '') | 
					
						
							|  |  |  |     result.push(''); | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |   return result.join('\n'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |  * @param {string} indent | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |  * @param {MarkdownNode} node | 
					
						
							|  |  |  |  * @param {MarkdownNode} lastNode | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |  * @param {RenderOptions=} options | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |  * @param {string[]} result | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  | function innerRenderMdNode(indent, node, lastNode, result, options) { | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |   const newLine = () => { | 
					
						
							| 
									
										
										
										
											2022-12-02 16:33:51 -08:00
										 |  |  |     if (result.length && (result[result.length - 1] || '').trim() !== '') | 
					
						
							|  |  |  |       result.push(indent); | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |   if (node.type.startsWith('h')) { | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |     const headerNode = /** @type {MarkdownHeaderNode} */ (node); | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |     newLine(); | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |     const depth = +node.type.substring(1); | 
					
						
							| 
									
										
										
										
											2022-09-27 08:27:23 -08:00
										 |  |  |     result.push(`${'#'.repeat(depth)} ${headerNode.text}`); | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |     let lastNode = node; | 
					
						
							| 
									
										
										
										
											2020-12-28 07:03:09 -08:00
										 |  |  |     for (const child of node.children || []) { | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |       innerRenderMdNode('', child, lastNode, result, options); | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |       lastNode = child; | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |   if (node.type === 'text') { | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     const bothTables = node.text.startsWith('|') && lastNode && lastNode.type === 'text' && lastNode.text.startsWith('|'); | 
					
						
							|  |  |  |     const bothGen = node.text.startsWith('<!--') && lastNode && lastNode.type === 'text' && lastNode.text.startsWith('<!--'); | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |     const bothComments = node.text.startsWith('>') && lastNode && lastNode.type === 'text' && lastNode.text.startsWith('>'); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     const bothLinks = node.text.match(/\[[^\]]+\]:/) && lastNode && lastNode.type === 'text' && lastNode.text.match(/\[[^\]]+\]:/); | 
					
						
							|  |  |  |     if (!bothTables && !bothGen && !bothComments && !bothLinks && lastNode && lastNode.text) | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |       newLine(); | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |       result.push(wrapText(node.text, options, indent)); | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |     return; | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |   if (node.type === 'code') { | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |     newLine(); | 
					
						
							| 
									
										
										
										
											2023-05-10 18:38:12 +02:00
										 |  |  |     result.push(`${indent}\`\`\`${node.codeLang}${(options?.renderCodeBlockTitlesInHeader && node.title) ? ' title="' + node.title + '"' : ''}`); | 
					
						
							|  |  |  |     if (!options?.renderCodeBlockTitlesInHeader && node.title) | 
					
						
							|  |  |  |       result.push(`${indent}// ${node.title}`); | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |     for (const line of node.lines) | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       result.push(indent + line); | 
					
						
							|  |  |  |     result.push(`${indent}\`\`\``); | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |     newLine(); | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-12 12:14:27 -08:00
										 |  |  |   if (node.type === 'note') { | 
					
						
							|  |  |  |     newLine(); | 
					
						
							|  |  |  |     result.push(`${indent}:::${node.noteType}`); | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |     result.push(wrapText(node.text, options, indent)); | 
					
						
							| 
									
										
										
										
											2021-01-12 12:14:27 -08:00
										 |  |  |     result.push(`${indent}:::`); | 
					
						
							|  |  |  |     newLine(); | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |   if (node.type === 'properties') { | 
					
						
							|  |  |  |     result.push(`${indent}---`); | 
					
						
							|  |  |  |     for (const line of node.lines) | 
					
						
							|  |  |  |       result.push(indent + line); | 
					
						
							|  |  |  |     result.push(`${indent}---`); | 
					
						
							|  |  |  |     newLine(); | 
					
						
							|  |  |  |     return; | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-12-04 18:05:35 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  |   if (node.type === 'li') { | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     let char; | 
					
						
							|  |  |  |     switch (node.liType) { | 
					
						
							|  |  |  |       case 'bullet': char = '*'; break; | 
					
						
							|  |  |  |       case 'default': char = '-'; break; | 
					
						
							|  |  |  |       case 'ordinal': char = '1.'; break; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |     result.push(wrapText(node.text, options, `${indent}${char} `)); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |     const newIndent = indent + ' '.repeat(char.length + 1); | 
					
						
							|  |  |  |     for (const child of node.children || []) { | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |       innerRenderMdNode(newIndent, child, lastNode, result, options); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       lastNode = child; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-12-03 18:05:36 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-29 12:12:46 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {string} text | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  | function tokenizeNoBreakLinks(text) { | 
					
						
							| 
									
										
										
										
											2020-12-29 12:12:46 -08:00
										 |  |  |   const links = []; | 
					
						
							|  |  |  |   // Don't wrap simple links with spaces.
 | 
					
						
							|  |  |  |   text = text.replace(/\[[^\]]+\]/g, match => { | 
					
						
							|  |  |  |     links.push(match); | 
					
						
							|  |  |  |     return `[${links.length - 1}]`; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   return text.split(' ').map(c => c.replace(/\[(\d+)\]/g, (_, p1) => links[+p1])); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {string} text | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |  * @param {RenderOptions|undefined} options | 
					
						
							|  |  |  |  * @param {string} prefix | 
					
						
							|  |  |  |  * @returns {string} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  |  function wrapText(text, options, prefix) { | 
					
						
							|  |  |  |   if (options?.flattenText) | 
					
						
							|  |  |  |     text = text.replace(/↵/g, ' '); | 
					
						
							|  |  |  |   const lines = text.split(/[\n↵]/); | 
					
						
							|  |  |  |   const result = /** @type {string[]} */([]); | 
					
						
							|  |  |  |   const indent = ' '.repeat(prefix.length); | 
					
						
							|  |  |  |   for (const line of lines) { | 
					
						
							|  |  |  |     result.push(wrapLine(line, options?.maxColumns, result.length ? indent : prefix)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return result.join('\n'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {string} textLine | 
					
						
							|  |  |  |  * @param {number|undefined} maxColumns | 
					
						
							|  |  |  |  * @param {string} prefix | 
					
						
							|  |  |  |  * @returns {string} | 
					
						
							| 
									
										
										
										
											2020-12-29 12:12:46 -08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  | function wrapLine(textLine, maxColumns, prefix) { | 
					
						
							| 
									
										
										
										
											2020-12-29 12:12:46 -08:00
										 |  |  |   if (!maxColumns) | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |     return prefix + textLine; | 
					
						
							|  |  |  |   if (textLine.trim().startsWith('|')) | 
					
						
							|  |  |  |     return prefix + textLine; | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |   const indent = ' '.repeat(prefix.length); | 
					
						
							| 
									
										
										
										
											2020-12-29 12:12:46 -08:00
										 |  |  |   const lines = []; | 
					
						
							|  |  |  |   maxColumns -= indent.length; | 
					
						
							| 
									
										
										
										
											2022-11-21 09:30:32 -08:00
										 |  |  |   const words = tokenizeNoBreakLinks(textLine); | 
					
						
							| 
									
										
										
										
											2020-12-29 12:12:46 -08:00
										 |  |  |   let line = ''; | 
					
						
							|  |  |  |   for (const word of words) { | 
					
						
							|  |  |  |     if (line.length && line.length + word.length < maxColumns) { | 
					
						
							|  |  |  |       line += ' ' + word; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       if (line) | 
					
						
							|  |  |  |         lines.push(line); | 
					
						
							| 
									
										
										
										
											2020-12-30 18:04:51 -08:00
										 |  |  |       line = (lines.length ? indent : prefix) + word; | 
					
						
							| 
									
										
										
										
											2020-12-29 12:12:46 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (line) | 
					
						
							|  |  |  |     lines.push(line); | 
					
						
							|  |  |  |   return lines.join('\n'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode} node | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  | function clone(node) { | 
					
						
							|  |  |  |   const copy = { ...node }; | 
					
						
							|  |  |  |   copy.children = copy.children ? copy.children.map(c => clone(c)) : undefined; | 
					
						
							|  |  |  |   return copy; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode[]} nodes | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |  * @param {function(MarkdownNode, number): void} visitor | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |  */ | 
					
						
							|  |  |  | function visitAll(nodes, visitor) { | 
					
						
							|  |  |  |   for (const node of nodes) | 
					
						
							|  |  |  |     visit(node, visitor); | 
					
						
							| 
									
										
										
										
											2020-12-23 19:35:43 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-26 14:31:41 -08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |  * @param {MarkdownNode} node | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |  * @param {function(MarkdownNode, number): void} visitor | 
					
						
							| 
									
										
										
										
											2020-12-26 14:31:41 -08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  | function visit(node, visitor, depth = 0) { | 
					
						
							|  |  |  |   visitor(node, depth); | 
					
						
							| 
									
										
										
										
											2020-12-28 17:38:00 -08:00
										 |  |  |   for (const n of node.children || []) | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |     visit(n, visitor, depth + 1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode[]} nodes | 
					
						
							| 
									
										
										
										
											2021-06-16 23:19:24 -07:00
										 |  |  |  * @param {boolean=} h3 | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |  * @returns {string} | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-06-16 23:19:24 -07:00
										 |  |  | function generateToc(nodes, h3) { | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |   const result = []; | 
					
						
							|  |  |  |   visitAll(nodes, (node, depth) => { | 
					
						
							| 
									
										
										
										
											2021-06-16 23:19:24 -07:00
										 |  |  |     if (node.type === 'h1' || node.type === 'h2' || (h3 && node.type === 'h3')) { | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |       let link = node.text.toLowerCase(); | 
					
						
							|  |  |  |       link = link.replace(/[ ]+/g, '-'); | 
					
						
							| 
									
										
										
										
											2021-01-11 09:34:49 -08:00
										 |  |  |       link = link.replace(/[^\w-_]/g, ''); | 
					
						
							| 
									
										
										
										
											2021-01-01 15:17:27 -08:00
										 |  |  |       result.push(`${' '.repeat(depth * 2)}- [${node.text}](#${link})`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   return result.join('\n'); | 
					
						
							| 
									
										
										
										
											2020-12-03 16:02:34 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-26 10:59:33 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode[]} nodes | 
					
						
							|  |  |  |  * @param {string} language | 
					
						
							|  |  |  |  * @return {MarkdownNode[]} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function filterNodesForLanguage(nodes, language) { | 
					
						
							|  |  |  |   const result = nodes.filter(node => { | 
					
						
							|  |  |  |     if (!node.children) | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     for (let i = 0; i < node.children.length; i++) { | 
					
						
							|  |  |  |       const child = node.children[i]; | 
					
						
							|  |  |  |       if (child.type !== 'li' || child.liType !== 'bullet' || !child.text.startsWith('langs:')) | 
					
						
							|  |  |  |         continue; | 
					
						
							| 
									
										
										
										
											2022-03-25 19:30:45 +01:00
										 |  |  |       const onlyText = child.text.substring('langs:'.length); | 
					
						
							|  |  |  |       if (!onlyText) | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |       const only = onlyText.split(',').map(l => l.trim()); | 
					
						
							| 
									
										
										
										
											2021-08-26 10:59:33 -07:00
										 |  |  |       node.children.splice(i, 1); | 
					
						
							|  |  |  |       return only.includes(language); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   result.forEach(n => { | 
					
						
							|  |  |  |     if (!n.children) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     n.children = filterNodesForLanguage(n.children, language); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-23 12:32:14 -08:00
										 |  |  | module.exports = { parse, render, clone, visitAll, visit, generateToc, filterNodesForLanguage, wrapText }; |