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,466 @@
1
+ import Chapter from './Chapter';
2
+ import Parser from "../chapter/Parser";
3
+ import EmbedNode from "../chapter/EmbedNode";
4
+ import { addChapterInFirestore, removeChapterFromEditionInFirestore, updateEditionInFirestore } from "../Firestore";
5
+ import ChapterIDs from './ChapterID.js';
6
+ export var BookSaveStatus;
7
+ (function (BookSaveStatus) {
8
+ BookSaveStatus[BookSaveStatus["Changed"] = 0] = "Changed";
9
+ BookSaveStatus[BookSaveStatus["Saving"] = 1] = "Saving";
10
+ BookSaveStatus[BookSaveStatus["Saved"] = 2] = "Saved";
11
+ BookSaveStatus[BookSaveStatus["Error"] = 3] = "Error";
12
+ })(BookSaveStatus || (BookSaveStatus = {}));
13
+ export default class Edition {
14
+ specification;
15
+ book;
16
+ editionRef;
17
+ chapters;
18
+ chaptersByID;
19
+ listeners;
20
+ // A list of requested edits, stored as promises, resolved
21
+ // when the book is ready to save.
22
+ edits = [];
23
+ lastEdit = 0;
24
+ // Given an object with a valid specification and an object mapping chapter IDs to chapter text,
25
+ // construct an object representing a book.
26
+ constructor(book, editionRef, specification) {
27
+ if (typeof specification !== "object" && specification !== undefined)
28
+ throw Error("Expected a book specification object, but received " + specification);
29
+ this.book = book;
30
+ this.editionRef = editionRef;
31
+ // Copy all of the specification metadata to fields.
32
+ // Choose suitable defaults if the spec is lacking a field.
33
+ this.specification = {
34
+ title: specification ? specification.title : "Untitled",
35
+ symbols: specification && specification.symbols ? specification.symbols : {},
36
+ tags: specification && specification.tags ? specification.tags : [],
37
+ license: specification ? specification.license : "All rights reserved.",
38
+ references: specification && specification.references ? specification.references : {},
39
+ glossary: specification && specification.glossary ? specification.glossary : {},
40
+ authors: specification && specification.authors ? specification.authors : [],
41
+ description: specification ? specification.description : "",
42
+ acknowledgements: specification && specification.acknowledgements ? specification.acknowledgements : "",
43
+ revisions: specification && specification.revisions ? specification.revisions : [],
44
+ images: specification && specification.revisions ? specification.images : {},
45
+ sources: specification && specification.revisions ? specification.sources : {},
46
+ theme: specification && specification.theme ? specification.theme : null,
47
+ uids: specification && specification.uids ? specification.uids : [],
48
+ chapters: specification && specification.chapters ? specification.chapters : []
49
+ };
50
+ // No listeners yet
51
+ this.listeners = new Set();
52
+ // Create a list and dictionary of Chapter objects.
53
+ this.chapters = [];
54
+ this.chaptersByID = {};
55
+ // If there's a spec and it has chapters, process them.
56
+ if (this.specification.chapters.length > 0) {
57
+ // Initialize the chapters dictionary since parsing depends this index to detect whether a chapter exists.
58
+ this.specification.chapters.forEach(chapterSpec => this.chaptersByID[chapterSpec.id] = undefined);
59
+ this.specification.chapters.forEach(chapterSpec => {
60
+ const chap = new Chapter(this, chapterSpec);
61
+ this.chaptersByID[chapterSpec.id] = chap;
62
+ this.chapters.push(chap);
63
+ });
64
+ }
65
+ }
66
+ setRef(ref) {
67
+ if (this.editionRef)
68
+ throw Error("Can only set a ref of an edition when not yet set.");
69
+ this.editionRef = ref;
70
+ }
71
+ async saveEdits() {
72
+ // If it's been more than a second since our last edit and there
73
+ // are edits that haven't been saved, try updating the book,
74
+ // and if we succeed, resolve all of the edits, and if we fail,
75
+ // then reject them.
76
+ if (Date.now() - this.lastEdit > 1000 && this.edits.length > 0) {
77
+ // Tell listeners that this book model changed.
78
+ this.notifyListeners(BookSaveStatus.Saving);
79
+ return updateEditionInFirestore(this)
80
+ .then(() => {
81
+ // Approve the edits.
82
+ this.edits.forEach(edit => edit.resolve());
83
+ this.notifyListeners(BookSaveStatus.Saved);
84
+ })
85
+ .catch(() => {
86
+ // Reject the edits.
87
+ this.edits.forEach(edit => edit.reject());
88
+ this.notifyListeners(BookSaveStatus.Error);
89
+ })
90
+ .finally(() => {
91
+ // Reset the edit queue.
92
+ this.edits = [];
93
+ });
94
+ }
95
+ }
96
+ getBook() { return this.book; }
97
+ addListener(listener) { this.listeners.add(listener); }
98
+ removeListener(listener) { this.listeners.delete(listener); }
99
+ notifyListeners(status) { this.listeners.forEach(listener => listener.call(undefined, status)); }
100
+ // Adds a save request to the queue, to be resolved later after a period of
101
+ // inactivity. Returns a promise that will eventually be resolved.
102
+ requestSave() {
103
+ // Tell listeners that this book model changed.
104
+ this.notifyListeners(BookSaveStatus.Changed);
105
+ // Return a promise that will resolve or reject later after this model saves the edits to the database.
106
+ this.lastEdit = Date.now();
107
+ const promise = new Promise((resolve, reject) => {
108
+ this.edits.push({ resolve: resolve, reject: reject });
109
+ });
110
+ return promise;
111
+ }
112
+ // Convert the book to an object for storage.
113
+ toObject() {
114
+ // Deep copy, then let the chapter models handle the chapters.
115
+ const spec = JSON.parse(JSON.stringify(this.specification));
116
+ spec.chapters = this.chapters.map(chapter => chapter.toObject());
117
+ return spec;
118
+ }
119
+ getRef() { return this.editionRef; }
120
+ addUserID(uid) {
121
+ this.specification.uids.push(uid);
122
+ }
123
+ getTitle() { return this.specification.title; }
124
+ setTitle(title) {
125
+ // Update locally, then update on the server.
126
+ this.specification.title = title;
127
+ return this.requestSave();
128
+ }
129
+ getDescription() { return this.specification.description; }
130
+ setDescription(text) {
131
+ // Update locally, then update on the server.
132
+ this.specification.description = text;
133
+ return this.requestSave();
134
+ }
135
+ getAcknowledgements() { return this.specification.acknowledgements; }
136
+ setAcknowledgements(text) {
137
+ // Update locally, then update on the server.
138
+ this.specification.acknowledgements = text;
139
+ return this.requestSave();
140
+ }
141
+ getLicense() { return this.specification.license; }
142
+ setLicense(text) {
143
+ // Update locally, then update on the server.
144
+ this.specification.license = text;
145
+ return this.requestSave();
146
+ }
147
+ getChapters() { return this.chapters; }
148
+ hasChapter(chapterID) { return chapterID in this.chaptersByID || ["references", "glossary", "index", "search", "media"].includes(chapterID); }
149
+ getChapter(chapterID) { return this.hasChapter(chapterID) ? this.chaptersByID[chapterID] : undefined; }
150
+ getChapterPosition(chapterID) {
151
+ var position = 0;
152
+ for (; position < this.chapters.length; position++)
153
+ if (this.chapters[position].getChapterID() === chapterID)
154
+ return position;
155
+ return undefined;
156
+ }
157
+ getChapterCount() { return this.chapters.length; }
158
+ async addChapter() {
159
+ if (!this.editionRef)
160
+ throw Error("No edition ID, can't add chapter");
161
+ const editionID = this.editionRef;
162
+ // Synthesize a chapter ID placeholder that doesn't overlap with existing chapter names
163
+ let number = 1;
164
+ while (this.hasChapter("chapter" + number))
165
+ number++;
166
+ const chapterRef = await addChapterInFirestore(editionID, { text: "" });
167
+ // Create a default chapter on this model. Remember the document reference so we can modify it later.
168
+ const emptyChapter = {
169
+ ref: chapterRef,
170
+ id: `chapter${number}`,
171
+ title: "Untitled Chapter",
172
+ authors: [],
173
+ numbered: true,
174
+ forthcoming: true,
175
+ image: null
176
+ };
177
+ const chap = new Chapter(this, emptyChapter);
178
+ this.chapters.push(chap);
179
+ this.chaptersByID[emptyChapter.id] = chap;
180
+ // Ask the database to create the chapter, returning the promise
181
+ this.requestSave();
182
+ }
183
+ moveChapter(chapterID, increment) {
184
+ // Get the index of the chapter.
185
+ let index = this.chapters.findIndex(chapter => chapter.getChapterID() === chapterID);
186
+ if (index < 0)
187
+ throw Error(`Chapter with ID ${chapterID} doesn't exist, can't move it.`);
188
+ else if (index + increment < 0)
189
+ throw Error(`Can't move this chapter ${increment} chapters earlier.`);
190
+ else if (index + increment >= this.chapters.length)
191
+ throw Error(`Can't move this chapter ${increment} chapters later.`);
192
+ const temp = this.chapters[index + increment];
193
+ this.chapters[index + increment] = this.chapters[index];
194
+ this.chapters[index] = temp;
195
+ // Ask the database to update with this new order.
196
+ return this.requestSave();
197
+ }
198
+ async deleteChapter(chapterID) {
199
+ let index = this.chapters.findIndex(chapter => chapter.getChapterID() === chapterID);
200
+ if (index < 0)
201
+ throw Error(`Chapter with ID ${chapterID} doesn't exist, can't delete it.`);
202
+ const chapter = this.chapters[index];
203
+ this.chapters.splice(index, 1);
204
+ // Ask the database to update the new book metadata then delete the chapter.
205
+ return this.requestSave().then(() => removeChapterFromEditionInFirestore(chapter));
206
+ }
207
+ getSymbols() { return this.specification.symbols; }
208
+ hasReferences() { return this.specification.references && Object.keys(this.specification.references).length > 0; }
209
+ getReferences() { return this.specification.references; }
210
+ getReference(citationID) { return citationID in this.specification.references ? this.specification.references[citationID] : undefined; }
211
+ addReferences(references) {
212
+ // Generate a unique ID for the reference.
213
+ references.forEach(ref => this.specification.references[ref.citationID] = ref.toList());
214
+ return this.requestSave();
215
+ }
216
+ editReference(ref) {
217
+ if (!(ref.citationID in this.specification.references))
218
+ return;
219
+ this.specification.references[ref.citationID] = ref.toList();
220
+ return this.requestSave();
221
+ }
222
+ editReferenceID(newCitationID, ref) {
223
+ if (!(ref.citationID in this.specification.references))
224
+ return;
225
+ delete this.specification.references[ref.citationID];
226
+ this.specification.references[newCitationID] = ref.withCitationID(newCitationID).toList();
227
+ return this.requestSave();
228
+ }
229
+ removeReference(citationID) {
230
+ if (!(citationID in this.specification.references))
231
+ return;
232
+ delete this.specification.references[citationID];
233
+ return this.requestSave();
234
+ }
235
+ hasGlossary() { return this.specification.glossary && Object.keys(this.specification.glossary).length > 0; }
236
+ getGlossary() { return this.specification.glossary; }
237
+ hasDefinition(id) { return id in this.specification.glossary; }
238
+ addDefinition(id, phrase, definition, synonyms) {
239
+ this.specification.glossary[id] = {
240
+ phrase: phrase,
241
+ definition: definition,
242
+ synonyms: synonyms
243
+ };
244
+ return this.requestSave();
245
+ }
246
+ removeDefinition(id) {
247
+ delete this.specification.glossary[id];
248
+ return this.requestSave();
249
+ }
250
+ editDefinition(id, definition) {
251
+ this.specification.glossary[id] = definition;
252
+ return this.requestSave();
253
+ }
254
+ editDefinitionID(id, newID) {
255
+ if (!(id in this.specification.glossary))
256
+ return;
257
+ const definition = this.specification.glossary[id];
258
+ delete this.specification.glossary[id];
259
+ this.specification.glossary[newID] = definition;
260
+ return this.requestSave();
261
+ }
262
+ getTags() { return this.specification.tags; }
263
+ getTheme() { return this.specification.theme; }
264
+ setTheme(theme) {
265
+ this.specification.theme = theme;
266
+ return this.requestSave();
267
+ }
268
+ setThemeValue(group, name, value) {
269
+ if (this.specification.theme[group][name] === value)
270
+ return;
271
+ this.specification.theme[group][name] = value;
272
+ return this.requestSave();
273
+ }
274
+ // Don't let callers get their sneaky hands on this mutable array...
275
+ getAuthors() { return [...this.specification.authors]; }
276
+ addAuthor(name) {
277
+ this.specification.authors.push(name);
278
+ return this.requestSave();
279
+ }
280
+ setAuthor(index, name) {
281
+ if (index >= 0 && index < this.specification.authors.length)
282
+ this.specification.authors[index] = name;
283
+ return this.requestSave();
284
+ }
285
+ removeAuthor(index) {
286
+ if (index >= 0 && index < this.specification.authors.length)
287
+ this.specification.authors.splice(index, 1);
288
+ return this.requestSave();
289
+ }
290
+ getRevisions() { return [...this.specification.revisions]; }
291
+ hasImage(id) { return id in this.specification.images && this.specification.images[id] !== null; }
292
+ getImage(id) { return this.specification.images[id] ?? null; }
293
+ setImage(id, embed) {
294
+ if (this.specification.images[id] === embed)
295
+ return;
296
+ this.specification.images[id] = embed ?? null;
297
+ return this.requestSave();
298
+ }
299
+ getBookReadingTime() {
300
+ return this.chapters
301
+ .map(chapter => chapter.getReadingTime())
302
+ .reduce((total, time) => total === undefined ? 0 :
303
+ time === undefined ?
304
+ total : total + time, 0);
305
+ }
306
+ getChapterNumber(chapterID) {
307
+ let chapterNumber = 1;
308
+ let match = null;
309
+ this.chapters.forEach(chapter => {
310
+ // If we found a match...
311
+ if (chapter.getChapterID() === chapterID) {
312
+ match = chapterNumber;
313
+ // And it's an unnumbered chapter, set to null.
314
+ if (!chapter.isNumbered())
315
+ match = null;
316
+ }
317
+ // Otherwise, increment if it's numbered.
318
+ else if (chapter.isNumbered())
319
+ chapterNumber++;
320
+ });
321
+ // Remember the number if we found it.
322
+ if (match !== null)
323
+ return match;
324
+ else
325
+ return undefined;
326
+ }
327
+ getSource(sourceID) {
328
+ return sourceID.charAt(0) === "#" && sourceID.substring(1) in this.specification.sources ?
329
+ this.specification.sources[sourceID.substring(1)] :
330
+ null;
331
+ }
332
+ getChapterName(chapterID) {
333
+ const chapter = this.getChapter(chapterID);
334
+ return chapter?.getTitle();
335
+ }
336
+ getChapterSection(chapterID) {
337
+ const chapter = this.chaptersByID[chapterID];
338
+ return chapter?.getSection();
339
+ }
340
+ getFootnoteSymbol(number) {
341
+ let symbols = "abcdefghijklmnopqrstuvwxyz";
342
+ // Let's hope there are never more than 26^2 footnotes in a single chapter...
343
+ return number < symbols.length ?
344
+ symbols.charAt(number) :
345
+ symbols.charAt(Math.floor(number / symbols.length) - 1) + symbols.charAt(number % symbols.length);
346
+ }
347
+ getBookIndex() {
348
+ // A map from word to a map of chapters containing the word
349
+ const bookIndex = {};
350
+ // Construct the index by building a dictionary of chapters in which each word appears.
351
+ this.chapters.forEach(chapter => {
352
+ let index = chapter.getIndex();
353
+ if (index)
354
+ Object.keys(index).forEach(word => {
355
+ if (word !== "" && word.length > 2) {
356
+ if (!(word in bookIndex))
357
+ bookIndex[word] = new Set();
358
+ bookIndex[word].add(chapter.getChapterID());
359
+ }
360
+ });
361
+ });
362
+ return bookIndex;
363
+ }
364
+ // Given the current chapter, find the available chapter after it.
365
+ getNextChapterID(chapterID) {
366
+ // Handle back matter chapters.
367
+ switch (chapterID) {
368
+ case ChapterIDs.ReferencesID: return this.hasGlossary() ? ChapterIDs.GlossaryID : ChapterIDs.IndexID;
369
+ case ChapterIDs.GlossaryID: return ChapterIDs.IndexID;
370
+ case ChapterIDs.IndexID: return ChapterIDs.SearchID;
371
+ case ChapterIDs.SearchID: return ChapterIDs.MediaID;
372
+ case ChapterIDs.MediaID: return ChapterIDs.TableOfContentsID;
373
+ case ChapterIDs.TableOfContentsID: return this.chapters.length > 0 ? this.chapters[0].getChapterID() : null;
374
+ default:
375
+ let after = false;
376
+ for (let i = 0; i < this.chapters.length; i++) {
377
+ let chapter = this.chapters[i];
378
+ if (chapter.getChapterID() === chapterID)
379
+ after = true;
380
+ // If we're after the given chapter and it's not forthcoming.
381
+ else if (after && !chapter.isForthcoming())
382
+ return chapter.getChapterID();
383
+ }
384
+ // If the given ID was the last chapter, go to the next back matter chapter.
385
+ if (after)
386
+ return this.hasReferences() ? ChapterIDs.ReferencesID : this.hasGlossary() ? ChapterIDs.GlossaryID : ChapterIDs.IndexID;
387
+ // Otherwise, it wasn't a valid ID
388
+ else
389
+ return null;
390
+ }
391
+ }
392
+ // Given a chapter id, find the available chapter before it.
393
+ getPreviousChapterID(chapterID) {
394
+ switch (chapterID) {
395
+ // Handle back matter chapters.
396
+ case ChapterIDs.ReferencesID: return this.chapters.length > 0 ? this.chapters[this.chapters.length - 1].getChapterID() : null; // Last chapter of the book
397
+ case ChapterIDs.GlossaryID: return this.hasReferences() ? ChapterIDs.ReferencesID : this.chapters.length > 0 ? this.chapters[this.chapters.length - 1].getChapterID() : ChapterIDs.TableOfContentsID;
398
+ case ChapterIDs.IndexID: return this.hasGlossary() ? ChapterIDs.GlossaryID : this.hasReferences() ? ChapterIDs.ReferencesID : this.chapters.length > 0 ? this.chapters[this.chapters.length - 1].getChapterID() : ChapterIDs.TableOfContentsID;
399
+ case ChapterIDs.SearchID: return ChapterIDs.IndexID;
400
+ case ChapterIDs.MediaID: return ChapterIDs.SearchID;
401
+ default:
402
+ let before = false;
403
+ for (let i = this.chapters.length - 1; i >= 0; i--) {
404
+ let chapter = this.chapters[i];
405
+ if (chapter.getChapterID() === chapterID)
406
+ before = true;
407
+ // If we're before the given chapter and it's not forthcoming.
408
+ else if (before && !chapter.isForthcoming())
409
+ return chapter.getChapterID();
410
+ }
411
+ // If the given ID was the last chapter, go to the next back matter chapter.
412
+ if (before)
413
+ return ChapterIDs.TableOfContentsID;
414
+ // Otherwise, it wasn't a valid ID
415
+ else
416
+ return null;
417
+ }
418
+ }
419
+ // Get all of the embeds in the book
420
+ getEmbeds() {
421
+ let embeds = [];
422
+ // Add the book cover
423
+ const cover = this.getImage("cover");
424
+ if (cover) {
425
+ let coverNode = Parser.parseEmbed(this, cover);
426
+ if (coverNode instanceof EmbedNode)
427
+ embeds.push(coverNode);
428
+ }
429
+ // Add the cover and images from each chapter.
430
+ this.getChapters().forEach(c => {
431
+ const image = c.getImage();
432
+ let cover = image === null ? undefined : Parser.parseEmbed(this, image);
433
+ if (cover && cover instanceof EmbedNode)
434
+ embeds.push(cover);
435
+ // Get the chapter body's embeds
436
+ let bodyEmbeds = c?.getAST()?.getEmbeds();
437
+ if (bodyEmbeds)
438
+ bodyEmbeds.forEach((embed) => embeds.push(embed));
439
+ });
440
+ // Add the back matter covers
441
+ let backmatter = ["references", "glossary", "index", "search", "media"];
442
+ backmatter.forEach(id => {
443
+ const img = this.getImage(id);
444
+ if (img) {
445
+ let image = Parser.parseEmbed(this, img);
446
+ if (image instanceof EmbedNode)
447
+ embeds.push(image);
448
+ }
449
+ });
450
+ return embeds;
451
+ }
452
+ isLatestPublishedEdition() {
453
+ const latestEditionID = this.book?.getLatestPublishedEditionID();
454
+ return latestEditionID !== undefined && latestEditionID === this.editionRef?.id;
455
+ }
456
+ /* Edition numbers are 1 to N */
457
+ getEditionNumber() {
458
+ const revisions = this.book?.getRevisions();
459
+ if (revisions === undefined)
460
+ return undefined;
461
+ for (let i = 0; i < revisions.length; i++)
462
+ if (revisions[i].ref.id === this.editionRef?.id)
463
+ return revisions.length - i;
464
+ return undefined;
465
+ }
466
+ }
@@ -0,0 +1,106 @@
1
+ import type ThemeColors from "./ThemeColors";
2
+ declare type Theme = {
3
+ light: ThemeColors;
4
+ dark: ThemeColors;
5
+ fonts: {
6
+ paragraphFontFamily: string;
7
+ headerFontFamily: string;
8
+ codeFontFamily: string;
9
+ bulletFontFamily: string;
10
+ };
11
+ sizes: {
12
+ paragraphFontSize: string;
13
+ blockFontSize: string;
14
+ smallFontSize: string;
15
+ titleFontSize: string;
16
+ header1FontSize: string;
17
+ header2FontSize: string;
18
+ header3FontSize: string;
19
+ codeFontSize: string;
20
+ };
21
+ weights: {
22
+ headerFontWeight: string;
23
+ paragraphFontWeight: string;
24
+ boldFontWeight: string;
25
+ linkFontWeight: string;
26
+ codeFontWeight: string;
27
+ bulletFontWeight: string;
28
+ };
29
+ spacing: {
30
+ paragraphLineHeight: string;
31
+ paragraphLineHeightTight: string;
32
+ headerLineHeight: string;
33
+ paragraphSpacing: string;
34
+ headerSpacing: string;
35
+ indent: string;
36
+ inlinePadding: string;
37
+ blockPadding: string;
38
+ roundedness: string;
39
+ };
40
+ };
41
+ export declare const defaultTheme: {
42
+ light: {
43
+ backgroundColor: string;
44
+ blockBackgroundColor: string;
45
+ errorBackgroundColor: string;
46
+ borderColorLight: string;
47
+ borderColorBold: string;
48
+ paragraphColor: string;
49
+ mutedColor: string;
50
+ highlightColor: string;
51
+ commentColor: string;
52
+ errorColor: string;
53
+ linkColor: string;
54
+ bulletColor: string;
55
+ };
56
+ dark: {
57
+ backgroundColor: string;
58
+ blockBackgroundColor: string;
59
+ errorBackgroundColor: string;
60
+ borderColorLight: string;
61
+ borderColorBold: string;
62
+ paragraphColor: string;
63
+ mutedColor: string;
64
+ highlightColor: string;
65
+ commentColor: string;
66
+ errorColor: string;
67
+ linkColor: string;
68
+ bulletColor: string;
69
+ };
70
+ fonts: {
71
+ paragraphFontFamily: string;
72
+ headerFontFamily: string;
73
+ codeFontFamily: string;
74
+ bulletFontFamily: string;
75
+ };
76
+ sizes: {
77
+ paragraphFontSize: string;
78
+ blockFontSize: string;
79
+ smallFontSize: string;
80
+ titleFontSize: string;
81
+ header1FontSize: string;
82
+ header2FontSize: string;
83
+ header3FontSize: string;
84
+ codeFontSize: string;
85
+ };
86
+ weights: {
87
+ headerFontWeight: string;
88
+ paragraphFontWeight: string;
89
+ boldFontWeight: string;
90
+ linkFontWeight: string;
91
+ codeFontWeight: string;
92
+ bulletFontWeight: string;
93
+ };
94
+ spacing: {
95
+ paragraphLineHeight: string;
96
+ paragraphLineHeightTight: string;
97
+ headerLineHeight: string;
98
+ paragraphSpacing: string;
99
+ headerSpacing: string;
100
+ indent: string;
101
+ inlinePadding: string;
102
+ blockPadding: string;
103
+ roundedness: string;
104
+ };
105
+ };
106
+ export default Theme;
@@ -0,0 +1,76 @@
1
+ export const defaultTheme = {
2
+ // Background colors
3
+ light: {
4
+ // Background colors;
5
+ backgroundColor: "#FFFFFF",
6
+ blockBackgroundColor: "#FCFAFA",
7
+ errorBackgroundColor: "#F8D7DA",
8
+ // Border colors
9
+ borderColorLight: "#E0E0E0",
10
+ borderColorBold: "#000000",
11
+ // Foreground colors
12
+ paragraphColor: "#000000",
13
+ mutedColor: "#9AA1A7",
14
+ highlightColor: "#E8AF22",
15
+ commentColor: "#257F31",
16
+ errorColor: "#721C24",
17
+ linkColor: "#1B499C",
18
+ bulletColor: "#EB2C27"
19
+ },
20
+ dark: {
21
+ // Background colors;
22
+ backgroundColor: "#1C1C1C",
23
+ blockBackgroundColor: "#333333",
24
+ errorBackgroundColor: "#721C24",
25
+ // Border colors
26
+ borderColorLight: "#444444",
27
+ borderColorBold: "#DADADA",
28
+ // Foreground colors
29
+ paragraphColor: "#DADADA",
30
+ mutedColor: "#666666",
31
+ highlightColor: "#c5a248",
32
+ commentColor: "#1c4722",
33
+ errorColor: "#F8D7DA",
34
+ linkColor: "#73a3fa",
35
+ bulletColor: "#721C24"
36
+ },
37
+ // Fonts
38
+ fonts: {
39
+ paragraphFontFamily: '"Georgia", serif',
40
+ headerFontFamily: '"Verdana", serif',
41
+ codeFontFamily: '"Courier New", monospace',
42
+ bulletFontFamily: '"Verdana"'
43
+ },
44
+ // Font sizes
45
+ sizes: {
46
+ paragraphFontSize: "14pt",
47
+ blockFontSize: "11pt",
48
+ smallFontSize: "9pt",
49
+ titleFontSize: "2.4rem",
50
+ header1FontSize: "2rem",
51
+ header2FontSize: "1.5rem",
52
+ header3FontSize: "1rem",
53
+ codeFontSize: "11pt"
54
+ },
55
+ // Font weights
56
+ weights: {
57
+ headerFontWeight: "700",
58
+ paragraphFontWeight: "400",
59
+ boldFontWeight: "700",
60
+ linkFontWeight: "400",
61
+ codeFontWeight: "400",
62
+ bulletFontWeight: "500"
63
+ },
64
+ // Spacing
65
+ spacing: {
66
+ paragraphLineHeight: "1.8em",
67
+ paragraphLineHeightTight: "1.4em",
68
+ headerLineHeight: "1.4em",
69
+ paragraphSpacing: "1.8rem",
70
+ headerSpacing: "2rem",
71
+ indent: "10%",
72
+ inlinePadding: "0.25rem",
73
+ blockPadding: "1rem",
74
+ roundedness: "5px"
75
+ }
76
+ };
@@ -0,0 +1,15 @@
1
+ declare type ThemeColors = {
2
+ backgroundColor: string;
3
+ blockBackgroundColor: string;
4
+ errorBackgroundColor: string;
5
+ borderColorLight: string;
6
+ borderColorBold: string;
7
+ paragraphColor: string;
8
+ mutedColor: string;
9
+ highlightColor: string;
10
+ commentColor: string;
11
+ errorColor: string;
12
+ linkColor: string;
13
+ bulletColor: string;
14
+ };
15
+ export default ThemeColors;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import type Caret from "./Caret";
2
+ import FormatNode from "./FormatNode";
3
+ import Node from "./Node";
4
+ import type RootNode from "./RootNode";
5
+ export default abstract class AtomNode<MetadataType> extends Node {
6
+ #private;
7
+ constructor(meta: MetadataType);
8
+ getMeta(): MetadataType;
9
+ abstract getDefaultCaret(): Caret | undefined;
10
+ getChildren(): (MetadataType & Node)[];
11
+ nextWord(root: RootNode): Caret;
12
+ previousWord(root: RootNode): Caret;
13
+ getFormatRoot(root: Node): FormatNode | undefined;
14
+ getCaretPositionCount(): number;
15
+ abstract toBookdown(format?: FormatNode): string;
16
+ abstract toText(): string;
17
+ abstract withMeta(meta: MetadataType): AtomNode<MetadataType>;
18
+ }