bookish-press 0.4.0

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.
Files changed (309) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/components/Auth.svelte +52 -0
  4. package/components/Auth.svelte.d.ts +16 -0
  5. package/components/Authentication.d.ts +8 -0
  6. package/components/Authentication.js +1 -0
  7. package/components/BookPreview.svelte +87 -0
  8. package/components/BookPreview.svelte.d.ts +18 -0
  9. package/components/External.svelte +4 -0
  10. package/components/External.svelte.d.ts +18 -0
  11. package/components/Link.svelte +5 -0
  12. package/components/Link.svelte.d.ts +19 -0
  13. package/components/chapter/Atom.svelte +36 -0
  14. package/components/chapter/Atom.svelte.d.ts +19 -0
  15. package/components/chapter/Block.svelte +32 -0
  16. package/components/chapter/Block.svelte.d.ts +17 -0
  17. package/components/chapter/Callout.svelte +10 -0
  18. package/components/chapter/Callout.svelte.d.ts +17 -0
  19. package/components/chapter/CaptionedCode.svelte +34 -0
  20. package/components/chapter/CaptionedCode.svelte.d.ts +17 -0
  21. package/components/chapter/ChapterBody.svelte +23 -0
  22. package/components/chapter/ChapterBody.svelte.d.ts +18 -0
  23. package/components/chapter/Citations.svelte +66 -0
  24. package/components/chapter/Citations.svelte.d.ts +17 -0
  25. package/components/chapter/Code.svelte +35 -0
  26. package/components/chapter/Code.svelte.d.ts +22 -0
  27. package/components/chapter/Comment.svelte +46 -0
  28. package/components/chapter/Comment.svelte.d.ts +17 -0
  29. package/components/chapter/Definition.svelte +38 -0
  30. package/components/chapter/Definition.svelte.d.ts +20 -0
  31. package/components/chapter/Embed.svelte +122 -0
  32. package/components/chapter/Embed.svelte.d.ts +18 -0
  33. package/components/chapter/ErrorMessage.svelte +4 -0
  34. package/components/chapter/ErrorMessage.svelte.d.ts +17 -0
  35. package/components/chapter/Footnote.svelte +37 -0
  36. package/components/chapter/Footnote.svelte.d.ts +19 -0
  37. package/components/chapter/Format.svelte +57 -0
  38. package/components/chapter/Format.svelte.d.ts +18 -0
  39. package/components/chapter/InlineCode.svelte +12 -0
  40. package/components/chapter/InlineCode.svelte.d.ts +17 -0
  41. package/components/chapter/Label.svelte +20 -0
  42. package/components/chapter/Label.svelte.d.ts +17 -0
  43. package/components/chapter/Link.svelte +51 -0
  44. package/components/chapter/Link.svelte.d.ts +17 -0
  45. package/components/chapter/List.svelte +14 -0
  46. package/components/chapter/List.svelte.d.ts +17 -0
  47. package/components/chapter/Marginal.svelte +48 -0
  48. package/components/chapter/Marginal.svelte.d.ts +19 -0
  49. package/components/chapter/Paragraph.svelte +15 -0
  50. package/components/chapter/Paragraph.svelte.d.ts +18 -0
  51. package/components/chapter/Python.svelte +81 -0
  52. package/components/chapter/Python.svelte.d.ts +18 -0
  53. package/components/chapter/Quote.svelte +16 -0
  54. package/components/chapter/Quote.svelte.d.ts +17 -0
  55. package/components/chapter/Rule.svelte +4 -0
  56. package/components/chapter/Rule.svelte.d.ts +17 -0
  57. package/components/chapter/Table.svelte +29 -0
  58. package/components/chapter/Table.svelte.d.ts +17 -0
  59. package/components/chapter/Text.svelte +62 -0
  60. package/components/chapter/Text.svelte.d.ts +17 -0
  61. package/components/chapter/renderPosition.d.ts +3 -0
  62. package/components/chapter/renderPosition.js +4 -0
  63. package/components/editor/BookishEditor.svelte +755 -0
  64. package/components/editor/BookishEditor.svelte.d.ts +22 -0
  65. package/components/editor/CalloutEditor.svelte +10 -0
  66. package/components/editor/CalloutEditor.svelte.d.ts +17 -0
  67. package/components/editor/CaptionedCodeEditor.svelte +22 -0
  68. package/components/editor/CaptionedCodeEditor.svelte.d.ts +17 -0
  69. package/components/editor/CaretContext.d.ts +18 -0
  70. package/components/editor/CaretContext.js +1 -0
  71. package/components/editor/CaretState.d.ts +47 -0
  72. package/components/editor/CaretState.js +1 -0
  73. package/components/editor/CaretUtilities.d.ts +5 -0
  74. package/components/editor/CaretUtilities.js +1 -0
  75. package/components/editor/CitationsEditor.svelte +42 -0
  76. package/components/editor/CitationsEditor.svelte.d.ts +17 -0
  77. package/components/editor/Clipboard.d.ts +3 -0
  78. package/components/editor/Clipboard.js +1 -0
  79. package/components/editor/Command.d.ts +18 -0
  80. package/components/editor/Command.js +1 -0
  81. package/components/editor/Commands.d.ts +3 -0
  82. package/components/editor/Commands.js +951 -0
  83. package/components/editor/ConfirmButton.svelte +43 -0
  84. package/components/editor/ConfirmButton.svelte.d.ts +18 -0
  85. package/components/editor/DefinitionEditor.svelte +28 -0
  86. package/components/editor/DefinitionEditor.svelte.d.ts +17 -0
  87. package/components/editor/EmbedEditor.svelte +87 -0
  88. package/components/editor/EmbedEditor.svelte.d.ts +17 -0
  89. package/components/editor/ImageChooser.svelte +70 -0
  90. package/components/editor/ImageChooser.svelte.d.ts +18 -0
  91. package/components/editor/InlineCodeEditor.svelte +9 -0
  92. package/components/editor/InlineCodeEditor.svelte.d.ts +17 -0
  93. package/components/editor/LabelEditor.svelte +23 -0
  94. package/components/editor/LabelEditor.svelte.d.ts +17 -0
  95. package/components/editor/LanguageEditor.svelte +11 -0
  96. package/components/editor/LanguageEditor.svelte.d.ts +17 -0
  97. package/components/editor/LinkEditor.svelte +75 -0
  98. package/components/editor/LinkEditor.svelte.d.ts +17 -0
  99. package/components/editor/Note.svelte +9 -0
  100. package/components/editor/Note.svelte.d.ts +23 -0
  101. package/components/editor/PositionEditor.svelte +14 -0
  102. package/components/editor/PositionEditor.svelte.d.ts +18 -0
  103. package/components/editor/QuoteEditor.svelte +5 -0
  104. package/components/editor/QuoteEditor.svelte.d.ts +17 -0
  105. package/components/editor/Switch.svelte +56 -0
  106. package/components/editor/Switch.svelte.d.ts +20 -0
  107. package/components/editor/TextEditor.svelte +193 -0
  108. package/components/editor/TextEditor.svelte.d.ts +23 -0
  109. package/components/editor/Toggle.svelte +38 -0
  110. package/components/editor/Toggle.svelte.d.ts +19 -0
  111. package/components/editor/Toolbar.svelte +202 -0
  112. package/components/editor/Toolbar.svelte.d.ts +20 -0
  113. package/components/editor/ToolbarGroup.svelte +41 -0
  114. package/components/editor/ToolbarGroup.svelte.d.ts +19 -0
  115. package/components/editor/ToolbarIcon.svelte +22 -0
  116. package/components/editor/ToolbarIcon.svelte.d.ts +16 -0
  117. package/components/editor/ToolbarSpacer.svelte +1 -0
  118. package/components/editor/ToolbarSpacer.svelte.d.ts +19 -0
  119. package/components/editor/URLEditor.svelte +15 -0
  120. package/components/editor/URLEditor.svelte.d.ts +18 -0
  121. package/components/editor/UndoState.d.ts +8 -0
  122. package/components/editor/UndoState.js +1 -0
  123. package/components/page/Acknowledgements.svelte +29 -0
  124. package/components/page/Acknowledgements.svelte.d.ts +14 -0
  125. package/components/page/Alert.svelte +11 -0
  126. package/components/page/Alert.svelte.d.ts +23 -0
  127. package/components/page/Authors.svelte +62 -0
  128. package/components/page/Authors.svelte.d.ts +20 -0
  129. package/components/page/BulkReferenceEditor.svelte +32 -0
  130. package/components/page/BulkReferenceEditor.svelte.d.ts +14 -0
  131. package/components/page/Chapter.svelte +301 -0
  132. package/components/page/Chapter.svelte.d.ts +18 -0
  133. package/components/page/ChapterContext.d.ts +10 -0
  134. package/components/page/ChapterContext.js +1 -0
  135. package/components/page/Contexts.d.ts +27 -0
  136. package/components/page/Contexts.js +21 -0
  137. package/components/page/DefinitionView.svelte +129 -0
  138. package/components/page/DefinitionView.svelte.d.ts +18 -0
  139. package/components/page/Description.svelte +22 -0
  140. package/components/page/Description.svelte.d.ts +14 -0
  141. package/components/page/Edition.svelte +106 -0
  142. package/components/page/Edition.svelte.d.ts +21 -0
  143. package/components/page/Glossary.svelte +61 -0
  144. package/components/page/Glossary.svelte.d.ts +14 -0
  145. package/components/page/Header.svelte +105 -0
  146. package/components/page/Header.svelte.d.ts +27 -0
  147. package/components/page/Index.svelte +117 -0
  148. package/components/page/Index.svelte.d.ts +14 -0
  149. package/components/page/Instructions.svelte +30 -0
  150. package/components/page/Instructions.svelte.d.ts +16 -0
  151. package/components/page/License.svelte +29 -0
  152. package/components/page/License.svelte.d.ts +14 -0
  153. package/components/page/Loading.svelte +13 -0
  154. package/components/page/Loading.svelte.d.ts +19 -0
  155. package/components/page/Media.svelte +76 -0
  156. package/components/page/Media.svelte.d.ts +14 -0
  157. package/components/page/MediaPreview.svelte +11 -0
  158. package/components/page/MediaPreview.svelte.d.ts +19 -0
  159. package/components/page/Outline.svelte +173 -0
  160. package/components/page/Outline.svelte.d.ts +19 -0
  161. package/components/page/Page.svelte +53 -0
  162. package/components/page/Page.svelte.d.ts +19 -0
  163. package/components/page/PossibleChapter.svelte +14 -0
  164. package/components/page/PossibleChapter.svelte.d.ts +14 -0
  165. package/components/page/PossibleReference.svelte +16 -0
  166. package/components/page/PossibleReference.svelte.d.ts +19 -0
  167. package/components/page/Print.svelte +8 -0
  168. package/components/page/Print.svelte.d.ts +14 -0
  169. package/components/page/Reference.svelte +145 -0
  170. package/components/page/Reference.svelte.d.ts +17 -0
  171. package/components/page/References.svelte +58 -0
  172. package/components/page/References.svelte.d.ts +14 -0
  173. package/components/page/Revisions.svelte +124 -0
  174. package/components/page/Revisions.svelte.d.ts +14 -0
  175. package/components/page/Search.svelte +108 -0
  176. package/components/page/Search.svelte.d.ts +14 -0
  177. package/components/page/Status.svelte +49 -0
  178. package/components/page/Status.svelte.d.ts +19 -0
  179. package/components/page/SubdomainEditor.svelte +30 -0
  180. package/components/page/SubdomainEditor.svelte.d.ts +14 -0
  181. package/components/page/TableOfContents.svelte +234 -0
  182. package/components/page/TableOfContents.svelte.d.ts +14 -0
  183. package/components/page/TableOfContentsImage.svelte +15 -0
  184. package/components/page/TableOfContentsImage.svelte.d.ts +16 -0
  185. package/components/page/TableOfContentsRow.svelte +57 -0
  186. package/components/page/TableOfContentsRow.svelte.d.ts +24 -0
  187. package/components/page/ThemeEditor.svelte +52 -0
  188. package/components/page/ThemeEditor.svelte.d.ts +14 -0
  189. package/components/page/ThemeEditorPreview.svelte +37 -0
  190. package/components/page/ThemeEditorPreview.svelte.d.ts +17 -0
  191. package/components/page/ThemeSetEditor.svelte +17 -0
  192. package/components/page/ThemeSetEditor.svelte.d.ts +18 -0
  193. package/components/page/ThemeVariableEditor.svelte +28 -0
  194. package/components/page/ThemeVariableEditor.svelte.d.ts +18 -0
  195. package/components/page/Unknown.svelte +29 -0
  196. package/components/page/Unknown.svelte.d.ts +16 -0
  197. package/components/page/margins.d.ts +2 -0
  198. package/components/page/margins.js +132 -0
  199. package/models/BookLoader.d.ts +2 -0
  200. package/models/BookLoader.js +67 -0
  201. package/models/Firebase.d.ts +4 -0
  202. package/models/Firebase.js +26 -0
  203. package/models/Firestore.d.ts +22 -0
  204. package/models/Firestore.js +194 -0
  205. package/models/book/Book.d.ts +60 -0
  206. package/models/book/Book.js +135 -0
  207. package/models/book/BookMedia.d.ts +23 -0
  208. package/models/book/BookMedia.js +102 -0
  209. package/models/book/Chapter.d.ts +70 -0
  210. package/models/book/Chapter.js +221 -0
  211. package/models/book/ChapterID.d.ts +10 -0
  212. package/models/book/ChapterID.js +10 -0
  213. package/models/book/Definition.d.ts +6 -0
  214. package/models/book/Definition.js +1 -0
  215. package/models/book/Edition.d.ts +111 -0
  216. package/models/book/Edition.js +466 -0
  217. package/models/book/Theme.d.ts +106 -0
  218. package/models/book/Theme.js +76 -0
  219. package/models/book/ThemeColors.d.ts +15 -0
  220. package/models/book/ThemeColors.js +1 -0
  221. package/models/chapter/AtomNode.d.ts +18 -0
  222. package/models/chapter/AtomNode.js +31 -0
  223. package/models/chapter/BlockNode.d.ts +21 -0
  224. package/models/chapter/BlockNode.js +117 -0
  225. package/models/chapter/BlocksNode.d.ts +50 -0
  226. package/models/chapter/BlocksNode.js +709 -0
  227. package/models/chapter/BlocksNode.test.d.ts +1 -0
  228. package/models/chapter/BlocksNode.test.js +302 -0
  229. package/models/chapter/Bookkeeping.d.ts +4 -0
  230. package/models/chapter/Bookkeeping.js +1 -0
  231. package/models/chapter/CalloutNode.d.ts +17 -0
  232. package/models/chapter/CalloutNode.js +36 -0
  233. package/models/chapter/Caret.d.ts +21 -0
  234. package/models/chapter/Caret.js +69 -0
  235. package/models/chapter/ChapterNode.d.ts +34 -0
  236. package/models/chapter/ChapterNode.js +80 -0
  237. package/models/chapter/CitationsNode.d.ts +16 -0
  238. package/models/chapter/CitationsNode.js +17 -0
  239. package/models/chapter/CodeNode.d.ts +30 -0
  240. package/models/chapter/CodeNode.js +64 -0
  241. package/models/chapter/CommentNode.d.ts +18 -0
  242. package/models/chapter/CommentNode.js +32 -0
  243. package/models/chapter/DefinitionNode.d.ts +12 -0
  244. package/models/chapter/DefinitionNode.js +20 -0
  245. package/models/chapter/Edit.d.ts +7 -0
  246. package/models/chapter/Edit.js +1 -0
  247. package/models/chapter/EmbedNode.d.ts +40 -0
  248. package/models/chapter/EmbedNode.js +89 -0
  249. package/models/chapter/ErrorNode.d.ts +17 -0
  250. package/models/chapter/ErrorNode.js +21 -0
  251. package/models/chapter/FootnoteNode.d.ts +18 -0
  252. package/models/chapter/FootnoteNode.js +29 -0
  253. package/models/chapter/FormatNode.d.ts +60 -0
  254. package/models/chapter/FormatNode.js +614 -0
  255. package/models/chapter/FormatNode.test.d.ts +1 -0
  256. package/models/chapter/FormatNode.test.js +54 -0
  257. package/models/chapter/InlineCodeNode.d.ts +12 -0
  258. package/models/chapter/InlineCodeNode.js +18 -0
  259. package/models/chapter/LabelNode.d.ts +17 -0
  260. package/models/chapter/LabelNode.js +22 -0
  261. package/models/chapter/LinkNode.d.ts +12 -0
  262. package/models/chapter/LinkNode.js +16 -0
  263. package/models/chapter/ListNode.d.ts +51 -0
  264. package/models/chapter/ListNode.js +370 -0
  265. package/models/chapter/ListNode.test.d.ts +1 -0
  266. package/models/chapter/ListNode.test.js +49 -0
  267. package/models/chapter/MetadataNode.d.ts +17 -0
  268. package/models/chapter/MetadataNode.js +24 -0
  269. package/models/chapter/Node.d.ts +36 -0
  270. package/models/chapter/Node.js +173 -0
  271. package/models/chapter/Node.test.d.ts +1 -0
  272. package/models/chapter/Node.test.js +27 -0
  273. package/models/chapter/ParagraphNode.d.ts +33 -0
  274. package/models/chapter/ParagraphNode.js +93 -0
  275. package/models/chapter/ParagraphNode.test.d.ts +1 -0
  276. package/models/chapter/ParagraphNode.test.js +21 -0
  277. package/models/chapter/Parser.d.ts +75 -0
  278. package/models/chapter/Parser.js +824 -0
  279. package/models/chapter/Parser.test.d.ts +1 -0
  280. package/models/chapter/Parser.test.js +25 -0
  281. package/models/chapter/Position.d.ts +2 -0
  282. package/models/chapter/Position.js +1 -0
  283. package/models/chapter/QuoteNode.d.ts +25 -0
  284. package/models/chapter/QuoteNode.js +61 -0
  285. package/models/chapter/ReferenceNode.d.ts +29 -0
  286. package/models/chapter/ReferenceNode.js +48 -0
  287. package/models/chapter/RootNode.d.ts +5 -0
  288. package/models/chapter/RootNode.js +1 -0
  289. package/models/chapter/RuleNode.d.ts +15 -0
  290. package/models/chapter/RuleNode.js +16 -0
  291. package/models/chapter/TableNode.d.ts +35 -0
  292. package/models/chapter/TableNode.js +176 -0
  293. package/models/chapter/TableNode.test.d.ts +1 -0
  294. package/models/chapter/TableNode.test.js +31 -0
  295. package/models/chapter/TextNode.d.ts +36 -0
  296. package/models/chapter/TextNode.js +136 -0
  297. package/models/chapter/TextNode.test.d.ts +1 -0
  298. package/models/chapter/TextNode.test.js +6 -0
  299. package/models/loadEdition.d.ts +5 -0
  300. package/models/loadEdition.js +16 -0
  301. package/package.json +189 -0
  302. package/util/getSubdomain.d.ts +3 -0
  303. package/util/getSubdomain.js +22 -0
  304. package/util/isMobile.d.ts +2 -0
  305. package/util/isMobile.js +6 -0
  306. package/util/mineReference.d.ts +3 -0
  307. package/util/mineReference.js +62 -0
  308. package/util/scrollToEyeLevel.d.ts +2 -0
  309. package/util/scrollToEyeLevel.js +5 -0
@@ -0,0 +1,824 @@
1
+ import CalloutNode from "./CalloutNode";
2
+ import ChapterNode from "./ChapterNode";
3
+ import CitationsNode from "./CitationsNode";
4
+ import CodeNode from "./CodeNode";
5
+ import CommentNode from "./CommentNode";
6
+ import DefinitionNode from "./DefinitionNode";
7
+ import EmbedNode from "./EmbedNode";
8
+ import ErrorNode from "./ErrorNode";
9
+ import FootnoteNode from "./FootnoteNode";
10
+ import FormatNode from "./FormatNode";
11
+ import InlineCodeNode from "./InlineCodeNode";
12
+ import LabelNode from "./LabelNode";
13
+ import LinkNode from "./LinkNode";
14
+ import ListNode from "./ListNode";
15
+ import ParagraphNode from "./ParagraphNode";
16
+ import QuoteNode from "./QuoteNode";
17
+ import ReferenceNode from "./ReferenceNode";
18
+ import RuleNode from "./RuleNode";
19
+ import TableNode from "./TableNode";
20
+ import TextNode from "./TextNode";
21
+ const numberedRE = /^[0-9]+\.+/;
22
+ const bulletRE = /^\*+\s+/;
23
+ // TODO This grammar is slightly out of date.
24
+ // A simple recursive descent parser for this grammar.
25
+ // Embeds a tokenizer, since the lexical grammar is simple.
26
+ //
27
+ // CHAPTER :: SYMBOLS* (BLOCK\n)*
28
+ // SYMBOLS :: @name: BLOCK
29
+ // BLOCK :: (COMMENT | HEADER | RULE | EMBED | ORDERED | UNORDERED | CODE | QUOTE | PARAGRAPH)
30
+ // COMMENT :: %.+
31
+ // HEADER :: # CONTENT | ## CONTENT | ### CONTENT
32
+ // RULE :: ---
33
+ // EMBED :: |TEXT|TEXT|CONTENT|CONTENT|
34
+ // ORDERED :: (* CONTENT)+
35
+ // UNORDERED :: ([0-9]+. CONTENT)+
36
+ // CODE :: `\nTEXT`
37
+ // QUOTE :: "\nBLOCK*\n"CONTENT
38
+ // TABLE :: ((,CONTENT)+\n)+
39
+ // CODE :: `\nTEXT\n`
40
+ // PARAGRAPH :: CONTENT
41
+ // CONTENT :: FORMATTED | CITATIONS | ESCAPED | LINK | FOOTNOTE | SYMBOL | DEFINITION
42
+ // FORMATTED :: *CONTENT* | _CONTENT_ | `CONTENT`
43
+ // CITATIONS || <TEXT+,>
44
+ // ESCAPED :: \[char]
45
+ // LINK :: [CONTENT|TEXT]
46
+ // FOOTNOTE :: {TEXT}
47
+ // TEXT :: (.+)
48
+ export default class Parser {
49
+ book;
50
+ text;
51
+ index;
52
+ openedDoubleQuote;
53
+ // We pass this to all parsing functions to gather information strewn about the document.
54
+ metadata;
55
+ constructor(book, text) {
56
+ if (typeof text !== "string")
57
+ throw "Parser expected a string but received " + typeof text;
58
+ this.book = book;
59
+ this.text = text;
60
+ this.index = 0; // Start at the first character.
61
+ this.openedDoubleQuote = false; // Track most recently observed quotes.
62
+ this.metadata = {
63
+ symbols: {}
64
+ };
65
+ }
66
+ static preprocessSymbols(book, text) {
67
+ // Replace any remaining symbols with any definitions given.
68
+ if (book && book.getSymbols()) {
69
+ for (const [symbol, definition] of Object.entries(book.getSymbols())) {
70
+ // Don't replace escaped at symbols. Otherwise, be liberal in replacing any matching symbol that starts with the symbol
71
+ // name and ends with a word boundary.
72
+ text = text.replace(new RegExp("([^\\\\])@" + symbol + "\\b", "g"), "$1" + definition);
73
+ }
74
+ }
75
+ return text;
76
+ }
77
+ static parseChapter(book, text) {
78
+ return (new Parser(book, book === undefined ? text : Parser.preprocessSymbols(book, text))).parseChapter();
79
+ }
80
+ static parseFormat(book, text) {
81
+ return (new Parser(book, book === undefined ? text : Parser.preprocessSymbols(book, text))).parseFormat();
82
+ }
83
+ static parseEmbed(book, text) {
84
+ return (new Parser(book, book === undefined ? text : Parser.preprocessSymbols(book, text))).parseEmbed();
85
+ }
86
+ static parseReference(citationID, ref, edition, short = false) {
87
+ if (typeof ref === "string")
88
+ return Parser.parseFormat(edition, ref);
89
+ else if (Array.isArray(ref)) {
90
+ // APA Format. Could eventually support multiple formats.
91
+ if (ref.length >= 4) {
92
+ let authors = ref[0];
93
+ let year = ref[1];
94
+ let title = ref[2];
95
+ let source = ref[3];
96
+ let url = ref.length === 5 ? ref[4] : "";
97
+ let summary = ref.length === 6 ? ref[5] : "";
98
+ if (source.charAt(0) === "#") {
99
+ let src = edition.getSource(source);
100
+ if (src === null)
101
+ return new ErrorNode(undefined, "Unknown source " + source);
102
+ else
103
+ source = src;
104
+ }
105
+ return new ReferenceNode(citationID, authors, year, title, source, url, summary, short);
106
+ }
107
+ else
108
+ return new ErrorNode(undefined, "Expected at least 4 items in the reference array, but found " + ref.length + ": " + ref.toString());
109
+ }
110
+ else
111
+ return new ErrorNode(undefined, "Invalid reference: " + ref);
112
+ }
113
+ // Get the next character, if there is one, null otherwise.
114
+ peek() {
115
+ return this.more() ? this.text.charAt(this.index) : null;
116
+ }
117
+ // True if there are more characters to parse.
118
+ more() {
119
+ return this.index < this.text.length;
120
+ }
121
+ // Return the current character--if there is one-- and increment the index.
122
+ read(smarten = true) {
123
+ if (!this.more())
124
+ return null;
125
+ let char = this.text.charAt(this.index);
126
+ if (smarten) {
127
+ if (char === "\n")
128
+ this.openedDoubleQuote = false;
129
+ // As we read, replace straight quotes with smart quotes.
130
+ if (char === '"') {
131
+ // Replace left quotes after whitespace.
132
+ if (this.openedDoubleQuote) {
133
+ char = "\u201d";
134
+ }
135
+ else {
136
+ char = "\u201c";
137
+ }
138
+ this.openedDoubleQuote = !this.openedDoubleQuote;
139
+ }
140
+ else if (char === "'") {
141
+ // If there's whitespace before this, it's a left single quote.
142
+ if (/\s/.test(this.text.charAt(this.index - 1)))
143
+ char = "\u2018";
144
+ // Otherwise, it's a right single quote.
145
+ else {
146
+ char = "\u2019";
147
+ }
148
+ }
149
+ if (char === '-' && this.text.charAt(this.index + 1) === '-' && this.text.charAt(this.index) !== '\n') {
150
+ this.index++;
151
+ char = "\u2014";
152
+ }
153
+ }
154
+ // Advance to the next character in the document.
155
+ this.index++;
156
+ return char;
157
+ }
158
+ unread() {
159
+ this.index--;
160
+ }
161
+ // All of the text including and after the current index.
162
+ rest() {
163
+ return this.text.substring(this.index);
164
+ }
165
+ // The character before
166
+ charBeforeNext() {
167
+ return this.index === 0 ? null : this.text.charAt(this.index - 1);
168
+ }
169
+ // The character after
170
+ charAfterNext() {
171
+ return this.index === this.text.length - 1 ? null : this.text.charAt(this.index + 1);
172
+ }
173
+ // All the text until the next newline
174
+ restOfLine() {
175
+ const nextNewline = this.text.substring(this.index).indexOf("\n") + this.index;
176
+ if (nextNewline < 0)
177
+ return this.text.substring(this.index);
178
+ else
179
+ return this.text.substring(this.index, Math.max(this.index, nextNewline));
180
+ }
181
+ // True if the given string is occurs next in the text.
182
+ nextIs(text) {
183
+ if (!this.more())
184
+ return false;
185
+ return this.text.substring(this.index, this.index + text.length) === text;
186
+ }
187
+ // A critical list that tells inline formatting parsers to stop being greedy.
188
+ nextIsContentDelimiter() {
189
+ const next = this.peek();
190
+ const charAfterNext = this.charAfterNext();
191
+ return next === "\n" ||
192
+ next === "_" ||
193
+ next === "*" ||
194
+ next === "`" ||
195
+ next === "@" ||
196
+ next === "~" ||
197
+ (next === ":" && charAfterNext !== null && charAfterNext.match(/[a-z]/i) !== null) ||
198
+ next === "^" ||
199
+ next === "<" ||
200
+ next === "{" ||
201
+ (this.charBeforeNext() === " " && next === "%") ||
202
+ next === "[" ||
203
+ next === "\\";
204
+ }
205
+ // True if the next part of this string matches the given regular expression.
206
+ nextMatches(regex) {
207
+ if (!this.more())
208
+ return false;
209
+ return regex.test(this.rest());
210
+ }
211
+ // Returns true if all of the text between the current character and the next newline is whitespace.
212
+ isBlankLine() {
213
+ return this.restOfLine().trim() === "";
214
+ }
215
+ // Read until encountering something other than a tab or a space.
216
+ readWhitespace() {
217
+ let peek = this.peek();
218
+ while (peek !== null && /^[ \t]/.test(peek)) {
219
+ this.read();
220
+ peek = this.peek();
221
+ }
222
+ }
223
+ // Read until the end of the line.
224
+ readUntilNewLine() {
225
+ let text = "";
226
+ while (this.more() && this.peek() !== "\n")
227
+ text = text + this.read();
228
+ return text;
229
+ }
230
+ // Read until encountering the given string and return the read text.
231
+ readUntilNewlineOr(string) {
232
+ let text = "";
233
+ while (this.more() && !this.nextIs("\n") && !this.nextIs(string))
234
+ text = text + this.read();
235
+ return text;
236
+ }
237
+ parseChapter() {
238
+ let blocks = [];
239
+ // Read any symbols declared for this chapter.
240
+ this.parseSymbols();
241
+ // Get the remaining text.
242
+ let declarations = this.text.substring(0, this.index);
243
+ let rest = this.rest();
244
+ // First, replace any occurrences of the symbol with the symbol's text.
245
+ for (const [symbol, text] of Object.entries(this.metadata.symbols)) {
246
+ rest = rest.replace(new RegExp("([^\\\\])@" + symbol + "\\b", "g"), "$1" + text);
247
+ }
248
+ // Replace all of the remaining symbols using book symbol definitions.
249
+ if (this.book)
250
+ rest = Parser.preprocessSymbols(this.book, rest);
251
+ // Replace the text with the pre-processed text.
252
+ this.text = declarations + rest;
253
+ // While there's more text, parse a line.
254
+ let trailingNewlines = 0;
255
+ while (this.more()) {
256
+ trailingNewlines = 0;
257
+ // Read a blocka add it to the list if we parsed something.
258
+ blocks.push(this.parseBlock());
259
+ // Read whitespace until we find the next thing.
260
+ while (this.peek() === " " || this.peek() === "\t" || this.peek() === "\n") {
261
+ if (this.peek() === "\n")
262
+ trailingNewlines++;
263
+ this.read();
264
+ }
265
+ }
266
+ // If there are no blocks, or the chapter ended with two newlines, append an empty paragraph with an empty format node and an empty text node.
267
+ if (blocks.length === 0 || trailingNewlines > 1)
268
+ blocks.push(new ParagraphNode());
269
+ // Make the chapter node so we can pass it around.
270
+ return new ChapterNode(blocks, this.metadata);
271
+ }
272
+ parseSymbols() {
273
+ // Read whitespace before the symbols
274
+ this.readWhitespace();
275
+ // Keep reading symbols
276
+ while (this.nextIs("@")) {
277
+ // Read the @
278
+ this.read();
279
+ // Read the name.
280
+ let name = this.readUntilNewlineOr(":");
281
+ // Names need to be letter and numbers only.
282
+ if (!/^[a-zA-Z0-9]+$/.test(name)) {
283
+ new ErrorNode(this.readUntilNewLine(), name.trim() === "" ?
284
+ "Did you mean to declare a symbol? Use an @ symbol, then a name of only numbers and letters, then a colon, then whatever content you want it to represent." :
285
+ "'" + name + "' isn't a valid name for a symbol; letters and numbers only");
286
+ return;
287
+ }
288
+ // Read any whitespace until the colon.
289
+ this.readWhitespace();
290
+ // Name declarations need to be terminated with a colon before the block starts.
291
+ if (!this.nextIs(":")) {
292
+ new ErrorNode(this.readUntilNewLine(), "Symbol names are to be followed by a ':'");
293
+ return;
294
+ }
295
+ // Read the colon
296
+ this.read();
297
+ // Read any whitespace after the colon.
298
+ this.readWhitespace();
299
+ // Remember where the text starts
300
+ let startIndex = this.index;
301
+ // Parse a block, any block.
302
+ this.parseBlock();
303
+ // Remember the text we parsed.
304
+ this.metadata.symbols[name] = this.text.substring(startIndex, this.index).trim();
305
+ // Read whitespace until we find the next non-whitespace thing.
306
+ while (this.peek() === " " || this.peek() === "\t" || this.peek() === "\n")
307
+ this.read();
308
+ }
309
+ }
310
+ parseBlock() {
311
+ // Read whitespace before the block.
312
+ this.readWhitespace();
313
+ // Read comment lines
314
+ while (this.nextIs("%")) {
315
+ this.readUntilNewLine();
316
+ }
317
+ // Parse and return a header if it starts with a hash
318
+ if (this.nextIs("#"))
319
+ return this.parseHeader();
320
+ // Parse and return a horizontal rule if it starts with a dash
321
+ else if (this.nextIs("-"))
322
+ return this.parseRule();
323
+ // Parse and return an embed if it starts with a bar
324
+ else if (this.nextIs("|"))
325
+ return this.parseEmbed();
326
+ // Parse and return a bulleted or numbered list
327
+ else if (this.nextMatches(bulletRE) || this.nextMatches(numberedRE))
328
+ return this.parseList();
329
+ // Parse and return a code block if it starts with `, some optional letters or a !, some whitespace, and a newline
330
+ else if (this.nextMatches(/^`[a-zA-Z!]*[ \t]*\n/))
331
+ return this.parseCode();
332
+ // Parse and return a quote block if it starts with "
333
+ else if (this.nextMatches(/^"[ \t]*\n/))
334
+ return this.parseQuote();
335
+ // Parse and return a callout if the line starts with =
336
+ else if (this.nextMatches(/^=[ \t]*\n/))
337
+ return this.parseCallout();
338
+ // Parse and return a table if the line starts with a ,
339
+ else if (this.nextIs(","))
340
+ return this.parseTable();
341
+ // Parse the text as paragraph;
342
+ else
343
+ return this.parseParagraph();
344
+ }
345
+ parseParagraph() {
346
+ return new ParagraphNode(0, this.parseFormat());
347
+ }
348
+ parseHeader() {
349
+ // Read a sequence of hashes
350
+ let count = 0;
351
+ while (this.nextIs("#")) {
352
+ this.read();
353
+ count++;
354
+ }
355
+ // Read any whitespace after the hashes.
356
+ this.readWhitespace();
357
+ // Parse some content.
358
+ return new ParagraphNode(Math.min(3, count), this.parseFormat());
359
+ }
360
+ parseRule() {
361
+ // Read until the end of the line. Ignore all text that follows.
362
+ this.readUntilNewLine();
363
+ return new RuleNode();
364
+ }
365
+ parseList() {
366
+ let numbered = this.nextMatches(numberedRE);
367
+ const items = [];
368
+ let lastLevel = undefined;
369
+ let currentLevel = undefined;
370
+ // Keep parsing bullets until there aren't any. They must be of the same type, otherwise we stop.
371
+ while (this.nextMatches(numberedRE) || this.nextMatches(bulletRE)) {
372
+ numbered = this.nextMatches(numberedRE);
373
+ // Remember the last level
374
+ lastLevel = currentLevel;
375
+ // Start the current level at 0
376
+ currentLevel = 0;
377
+ let digits = undefined;
378
+ if (numbered) {
379
+ // Read the digits and remember how many there were so we can backtrack.
380
+ digits = this.index;
381
+ this.readUntilNewlineOr(".");
382
+ digits = this.index - digits;
383
+ // Figure out this level.
384
+ while (this.nextIs(".")) {
385
+ this.read();
386
+ currentLevel++;
387
+ }
388
+ }
389
+ else {
390
+ // Figure out this level.
391
+ currentLevel = 0;
392
+ while (this.nextIs("*")) {
393
+ this.read();
394
+ currentLevel++;
395
+ }
396
+ }
397
+ // If this is the first bullet, or its the same level, just parse another bullet.
398
+ if (lastLevel === undefined || lastLevel === currentLevel) {
399
+ // Read the whitespace after the bullet.
400
+ this.readWhitespace();
401
+ // Parse content after the bullet.
402
+ const format = this.parseFormat();
403
+ items.push(format.isEmpty() ? format.withSegmentAppended(new TextNode()) : format);
404
+ }
405
+ // Otherwise, unread the stars, then either stop parsing or parse nested list.
406
+ else {
407
+ // Unread the periods/stars.
408
+ let count = currentLevel;
409
+ while (count > 0) {
410
+ this.unread();
411
+ count--;
412
+ }
413
+ // Unread the digits if there are any
414
+ if (digits !== undefined) {
415
+ while (digits > 0) {
416
+ this.unread();
417
+ digits--;
418
+ }
419
+ }
420
+ // If this new bullet is a lower level, then stop reading bullets.
421
+ if (currentLevel < lastLevel)
422
+ break;
423
+ // Otherwise, it's greater, and we should read another list.
424
+ else {
425
+ items.push(this.parseList());
426
+ // Reset the current level to the last level, so we expect another bullet at the same level.
427
+ currentLevel = lastLevel;
428
+ }
429
+ }
430
+ // Read trailing whitespace and newlines.
431
+ this.readWhitespace();
432
+ while (this.peek() === "\n") {
433
+ // Read the newline
434
+ this.read();
435
+ // Read whitespace before the next block.
436
+ this.readWhitespace();
437
+ }
438
+ }
439
+ return new ListNode(items, numbered);
440
+ }
441
+ parseCode() {
442
+ // Parse the back tick
443
+ this.read();
444
+ // Parse through the next new line
445
+ let language = this.readUntilNewLine();
446
+ // Read the newline
447
+ this.read();
448
+ // Read until we encounter a closing back tick.
449
+ let code = "";
450
+ while (this.more() && !this.nextIs("`")) {
451
+ let next = this.read(false);
452
+ if (next === "\\") {
453
+ if (this.nextIs("`")) {
454
+ this.read();
455
+ next = "`";
456
+ }
457
+ }
458
+ code = code + next;
459
+ }
460
+ // Trim the code.
461
+ code = code.trim();
462
+ // Read the backtick.
463
+ if (this.nextIs("`"))
464
+ this.read();
465
+ let position = this.parsePosition();
466
+ // Read the caption. Note that parsing inline content stops at a newline,
467
+ // so if there's a line break after the last row, there won't be a caption.
468
+ const caption = this.parseFormat();
469
+ return new CodeNode(new TextNode(code), language, position, caption);
470
+ }
471
+ parsePosition() {
472
+ let position = "|";
473
+ // Is there a position indicator?
474
+ if (this.nextIs("<")) {
475
+ position = "<";
476
+ this.read();
477
+ }
478
+ else if (this.nextIs(">")) {
479
+ position = ">";
480
+ this.read();
481
+ }
482
+ return position;
483
+ }
484
+ parseQuote() {
485
+ const blocks = [];
486
+ // Parse the ", then any whitespace, then the newline
487
+ this.read();
488
+ // Then read any whitespace after the quote
489
+ this.readWhitespace();
490
+ // Then read the newline.
491
+ this.read();
492
+ while (this.more() && !this.nextIs("\"")) {
493
+ // Read a block
494
+ const block = this.parseBlock();
495
+ // Add it to the list if we parsed something.
496
+ if (block !== null)
497
+ blocks.push(block);
498
+ // Read whitespace until we find the next thing.
499
+ while (this.peek() === " " || this.peek() === "\t" || this.peek() === "\n")
500
+ this.read();
501
+ }
502
+ // Read the closing " and the whitespace that follows.
503
+ this.read();
504
+ // Is there a position indicator?
505
+ const position = this.parsePosition();
506
+ // Read any whitespace after the position indicator.
507
+ this.readWhitespace();
508
+ // Read the credit.
509
+ const credit = this.nextIs("\n") ? undefined : this.parseFormat();
510
+ return new QuoteNode(blocks, credit, position);
511
+ }
512
+ parseCallout() {
513
+ const blocks = [];
514
+ // Parse the = ...
515
+ this.read();
516
+ // ...then any whitespace
517
+ this.readWhitespace();
518
+ // ...then read the newline.
519
+ this.read();
520
+ // Then, read until we find a closing _
521
+ while (this.more() && !this.nextIs("=")) {
522
+ // Read a block
523
+ const block = this.parseBlock();
524
+ // Add it to the list if we parsed something.
525
+ if (block !== null)
526
+ blocks.push(block);
527
+ // Read whitespace until we find the next thing.
528
+ while (this.peek() === " " || this.peek() === "\t" || this.peek() === "\n")
529
+ this.read();
530
+ }
531
+ // Read the closing =
532
+ this.read();
533
+ // Is there a position indicator?
534
+ const position = this.parsePosition();
535
+ // Read whitespace that follows.
536
+ this.readWhitespace();
537
+ return new CalloutNode(blocks, position);
538
+ ;
539
+ }
540
+ parseTable() {
541
+ const rows = [];
542
+ // Parse rows until the lines stop starting with ,
543
+ while (this.more() && this.nextIs(",")) {
544
+ let row = [];
545
+ while (this.more() && !this.nextIs("\n")) {
546
+ // Read the starting , or next |
547
+ this.read();
548
+ // Read whitespace that follows.
549
+ this.readWhitespace();
550
+ // Read content until reaching another | or the end of the line.
551
+ row.push(this.parseFormat("|"));
552
+ }
553
+ // Add the row.
554
+ rows.push(row);
555
+ // Read the newline
556
+ this.read();
557
+ }
558
+ // Read the position indicator if there is one.
559
+ const position = this.parsePosition();
560
+ // Read the caption. Note that parsing inline content stops at a newline,
561
+ // so if there's a line break after the last row, there won't be a caption.
562
+ const caption = this.parseFormat();
563
+ // Return the new table.
564
+ return new TableNode(rows, position, caption);
565
+ }
566
+ // The "awaiting" argument keeps track of upstream formatting. We don't need a stack here
567
+ // because we don't allow infinite nesting of the same formatting type.
568
+ parseFormat(awaiting) {
569
+ const segments = [];
570
+ // Read until hitting a delimiter.
571
+ while (this.more() && !this.nextIs("\n")) {
572
+ const next = this.peek();
573
+ const charAfterNext = this.charAfterNext();
574
+ // Parse some bold, italic, subscript, superscript
575
+ if (next === "_" || next === "*" || next === "^")
576
+ segments.push(this.parseStyle(next));
577
+ // Parse unformatted text
578
+ else if (this.nextIs("`"))
579
+ segments.push(this.parseInlineCode());
580
+ // Parse a citation list
581
+ else if (this.nextIs("<"))
582
+ segments.push(this.parseCitations());
583
+ // Parse a footnote
584
+ else if (this.nextIs("{"))
585
+ segments.push(this.parseFootnote());
586
+ // Parse a definition
587
+ else if (this.nextIs("~"))
588
+ segments.push(this.parseDefinition());
589
+ // Parse an escaped character
590
+ else if (this.nextIs("\\"))
591
+ segments.push(this.parseEscaped());
592
+ // Parse a link
593
+ else if (this.nextIs("["))
594
+ segments.push(this.parseLink());
595
+ // Parse inline comments
596
+ else if (this.charBeforeNext() === " " && this.nextIs("%"))
597
+ segments.push(this.parseComment());
598
+ // Parse an unresolved symbol
599
+ else if (next === "@") {
600
+ // Read the @, then the symbol name.
601
+ this.read();
602
+ let symbol = "";
603
+ // Stop at the end of the name or file.
604
+ let next = this.peek();
605
+ while (next !== null && /[a-zA-Z0-9]/.test(next)) {
606
+ symbol = symbol + this.read();
607
+ next = this.peek();
608
+ }
609
+ segments.push(new ErrorNode(undefined, "Couldn't find symbol @" + symbol));
610
+ }
611
+ // Parse a label
612
+ else if (next === ":" && charAfterNext !== null && charAfterNext.match(/[a-z]/i)) {
613
+ segments.push(this.parseLabel());
614
+ }
615
+ // Keep reading text until finding a delimiter.
616
+ else {
617
+ let text = "";
618
+ while (this.more() && (!awaiting || !this.nextIs(awaiting)) && !this.nextIsContentDelimiter() && !this.nextIs("\n"))
619
+ text = text + this.read();
620
+ segments.push(new TextNode(text));
621
+ }
622
+ // If we've reached a delimiter we're waiting for, then stop parsing, so it can handle it. Otherwise, we'll keep reading.
623
+ if (this.peek() === awaiting)
624
+ break;
625
+ }
626
+ return new FormatNode("", segments);
627
+ }
628
+ parseEmbed() {
629
+ // Read |
630
+ this.read();
631
+ // Read the URL
632
+ const url = this.readUntilNewlineOr("|");
633
+ if (this.peek() !== "|")
634
+ return new ErrorNode(this.readUntilNewLine(), "Missing '|' after URL in embed");
635
+ // Read a |
636
+ this.read();
637
+ // Read the description
638
+ const description = this.readUntilNewlineOr("|");
639
+ if (this.peek() !== "|")
640
+ return new ErrorNode(this.readUntilNewLine(), "Missing '|' after description in embed");
641
+ // Read a |
642
+ this.read();
643
+ // Parse the caption
644
+ const caption = this.parseFormat("|");
645
+ if (this.peek() !== "|")
646
+ return new ErrorNode(this.readUntilNewLine(), "Missing '|' after caption in embed");
647
+ // Read a |
648
+ this.read();
649
+ // Parse the credit
650
+ const credit = this.parseFormat("|");
651
+ // Check for the closing delimeter
652
+ if (this.peek() !== "|")
653
+ return new ErrorNode(this.readUntilNewLine(), "Missing '|' after credit in embed.");
654
+ // Read a |
655
+ this.read();
656
+ // Is there a position indicator?
657
+ const position = this.parsePosition();
658
+ return new EmbedNode(url, description, caption, credit, position);
659
+ }
660
+ parseComment() {
661
+ // Consume the opening %
662
+ this.read();
663
+ // Consume everything until the next %.
664
+ const comment = this.parseFormat("%");
665
+ // Consume the closing %, if we didn't reach the end of input or a newline.
666
+ if (this.peek() === "%")
667
+ this.read();
668
+ return new CommentNode(comment);
669
+ }
670
+ parseLabel() {
671
+ // Consume the :
672
+ this.read();
673
+ // Consume everything until it's not a letter.
674
+ let id = "";
675
+ let next = this.peek();
676
+ while (next !== null && next.match(/[a-z]/i)) {
677
+ id = id + this.read();
678
+ next = this.peek();
679
+ }
680
+ return new LabelNode(id);
681
+ }
682
+ parseStyle(awaiting) {
683
+ // Remember what we're matching.
684
+ const delimeter = this.read();
685
+ let subscript = false;
686
+ const segments = [];
687
+ let text = "";
688
+ // Special case subscript, which has an extra delimeter.
689
+ if (this.peek() === "v") {
690
+ this.read();
691
+ subscript = true;
692
+ }
693
+ if (delimeter === null)
694
+ return new ErrorNode(undefined, "Somehow parsing formatted text at end of file.");
695
+ // Read some content until reaching the delimiter or the end of the line
696
+ while (this.more() && this.peek() !== delimeter && !this.nextIs("\n")) {
697
+ // If this is more formatted text, make a text node with whatever we've accumulated so far,
698
+ // then parse the formatted text, then reset the accumulator.
699
+ if (this.nextIsContentDelimiter()) {
700
+ // If the text is a non-empty string, make a text node with what we've accumulated.
701
+ if (text !== "")
702
+ segments.push(new TextNode(text));
703
+ // Parse the formatted content.
704
+ segments.push(this.parseFormat(awaiting));
705
+ // Reset the accumulator.
706
+ text = "";
707
+ }
708
+ // Add the next character to the accumulator.
709
+ else {
710
+ text = text + this.read();
711
+ }
712
+ }
713
+ // If there's more text, save it!
714
+ if (text !== "")
715
+ segments.push(new TextNode(text));
716
+ // Read the closing delimeter
717
+ if (this.nextIs(delimeter))
718
+ this.read();
719
+ // If it wasn't closed, add an error
720
+ else
721
+ segments.push(new ErrorNode(undefined, "Unclosed " + delimeter));
722
+ return new FormatNode(subscript ? "v" : delimeter, segments);
723
+ }
724
+ parseInlineCode() {
725
+ // Parse the back tick
726
+ this.read();
727
+ // Read until we encounter a closing back tick.
728
+ let code = "";
729
+ while (this.more() && !this.nextIs("`")) {
730
+ let next = this.read(false);
731
+ if (next === "\\") {
732
+ if (this.nextIs("`")) {
733
+ this.read();
734
+ next = "`";
735
+ }
736
+ }
737
+ code = code + next;
738
+ }
739
+ // Read the backtick.
740
+ if (this.nextIs("`"))
741
+ this.read();
742
+ // If there's a letter immediately after a backtick, it's a language for an inline code name
743
+ let language = "";
744
+ let next = this.peek();
745
+ if (next !== null && /^[a-z]$/.test(next)) {
746
+ do {
747
+ language = language + this.read();
748
+ next = this.peek();
749
+ } while (next !== null && /^[a-z]$/.test(next));
750
+ }
751
+ else
752
+ language = "plaintext";
753
+ return new InlineCodeNode(new TextNode(code), language);
754
+ }
755
+ parseCitations() {
756
+ let citations = "";
757
+ // Read the <
758
+ this.read();
759
+ // Read the citations.
760
+ citations = this.readUntilNewlineOr(">");
761
+ if (this.peek() === ">")
762
+ this.read();
763
+ // Trim any whitespace, then split by commas, then remove any empty IDs.
764
+ const citationList = citations.split(",").map(citation => citation.trim()).filter(citationID => citationID.length > 0);
765
+ return new CitationsNode(citationList);
766
+ }
767
+ parseFootnote() {
768
+ // Read the {
769
+ this.read();
770
+ // Read the footnote content.
771
+ const footnote = this.parseFormat("}");
772
+ // Read the closing }
773
+ this.read();
774
+ return new FootnoteNode(footnote);
775
+ }
776
+ parseDefinition() {
777
+ // Read the ~
778
+ this.read();
779
+ // Read the phrase
780
+ const text = this.readUntilNewlineOr("~");
781
+ // Read the closing ~
782
+ this.read();
783
+ // Read the glossary entry ID
784
+ let glossaryID = "";
785
+ // Stop at the end of the name or file.
786
+ let next = this.peek();
787
+ while (next !== null && /[a-zA-Z]/.test(next)) {
788
+ glossaryID = glossaryID + this.read();
789
+ next = this.peek();
790
+ }
791
+ return new DefinitionNode(new TextNode(text), glossaryID);
792
+ }
793
+ parseEscaped() {
794
+ // Skip the scape and just add the next character.
795
+ this.read();
796
+ const char = this.read();
797
+ if (char)
798
+ return new TextNode(char);
799
+ return new ErrorNode(undefined, "Unterminated escape.");
800
+ }
801
+ parseLink() {
802
+ // Read the [
803
+ this.read();
804
+ // Read pure text until funding |
805
+ const text = this.readUntilNewlineOr("|");
806
+ // Catch missing bars
807
+ if (this.peek() !== "|") {
808
+ this.readUntilNewLine();
809
+ return new ErrorNode(undefined, "Missing '|' in link");
810
+ }
811
+ // Read the |
812
+ this.read();
813
+ // Read the link
814
+ let url = this.readUntilNewlineOr("]");
815
+ // Catch missing closing
816
+ if (this.peek() !== "]") {
817
+ this.readUntilNewLine();
818
+ return new ErrorNode(undefined, "Missing ] in link");
819
+ }
820
+ // Read the ]
821
+ this.read();
822
+ return new LinkNode(new TextNode(text), url);
823
+ }
824
+ }