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,23 @@
1
+ import type { StorageReference } from "firebase/storage";
2
+ import type Book from "./Book";
3
+ export declare type Image = {
4
+ url: string;
5
+ ref: StorageReference;
6
+ };
7
+ export default class BookMedia {
8
+ readonly book: Book;
9
+ _images: Image[] | undefined;
10
+ readonly observers: Set<(images: Image[]) => void>;
11
+ constructor(book: Book);
12
+ notify(observer: (images: Image[]) => void): void;
13
+ stopNotifying(observer: (images: Image[]) => void): void;
14
+ /** Get all non-thumbnail images. */
15
+ getImages(): Promise<Image[]>;
16
+ getImagePath(): string;
17
+ /** Get a listing of all of the images for the book. */
18
+ cacheImages(): Promise<Image[]>;
19
+ retrieveImages(): Promise<(Image | undefined)[]>;
20
+ getThumbnailURL(url: string, thumbnailRef: StorageReference, error: (message: string) => void, finished: (url: string, thumbnailURL: string) => void, attempt?: number): void;
21
+ upload(file: File, progressHandler: (progress: number) => void, errorHandler: (message: string) => void, finishedHandler: (url: string, thumbnailURL: string) => void): void;
22
+ remove(image: Image): Promise<Image[] | undefined>;
23
+ }
@@ -0,0 +1,102 @@
1
+ import { deleteObject, getDownloadURL, listAll, ref, uploadBytes } from "firebase/storage";
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { storage } from "../Firebase";
4
+ export default class BookMedia {
5
+ book;
6
+ _images;
7
+ // A list of functions to call when the book media changes.
8
+ observers = new Set();
9
+ constructor(book) {
10
+ this.book = book;
11
+ }
12
+ notify(observer) {
13
+ this.observers.add(observer);
14
+ // Notify it right away!
15
+ this.getImages().then(images => observer(images));
16
+ }
17
+ stopNotifying(observer) {
18
+ this.observers.delete(observer);
19
+ }
20
+ /** Get all non-thumbnail images. */
21
+ async getImages() {
22
+ return this._images === undefined ? this.cacheImages() : this._images;
23
+ }
24
+ getImagePath() { return `images/${this.book.getRefID()}/`; }
25
+ /** Get a listing of all of the images for the book. */
26
+ async cacheImages() {
27
+ if (storage === undefined)
28
+ return [];
29
+ // Find all the prefixes and items.
30
+ this._images = (await this.retrieveImages()).filter(image => image !== undefined);
31
+ // Notify observers
32
+ for (const observer of this.observers)
33
+ observer(this._images.slice());
34
+ return this._images;
35
+ }
36
+ async retrieveImages() {
37
+ if (storage === undefined)
38
+ return [];
39
+ // Create a reference under which you want to list
40
+ const listRef = ref(storage, this.getImagePath());
41
+ // Find all the prefixes and items.
42
+ return listAll(listRef)
43
+ .then((res) => Promise.all(res.items.map(itemRef => getDownloadURL(itemRef)
44
+ .then((url) => {
45
+ const image = { url: url, description: "", ref: itemRef };
46
+ if (this._images !== undefined) {
47
+ this._images.push(image);
48
+ }
49
+ return image;
50
+ }))));
51
+ }
52
+ getThumbnailURL(url, thumbnailRef, error, finished, attempt = 0) {
53
+ getDownloadURL(thumbnailRef)
54
+ .then(thumbnail => finished(url, thumbnail))
55
+ // Try again in a bit.
56
+ .catch(() => attempt > 10 ? error("Couldn't upload image") : setTimeout(() => this.getThumbnailURL(url, thumbnailRef, error, finished, attempt + 1), 250));
57
+ }
58
+ upload(file, progressHandler, errorHandler, finishedHandler) {
59
+ progressHandler;
60
+ if (storage === undefined)
61
+ return;
62
+ // The canonical path format for Bookish images in the store is image/{bookid}/{imageid}
63
+ // where {bookid} is the Firestore ID of the book being edited and {imageid} is just a random id.
64
+ const imageName = `image-${uuidv4()}`;
65
+ const imageRef = ref(storage, `${this.getImagePath()}/${imageName}`);
66
+ const thumbnailRef = ref(storage, `${this.getImagePath()}/thumbnails/${imageName}`);
67
+ uploadBytes(imageRef, file)
68
+ .then((snapshot) => {
69
+ getDownloadURL(snapshot.ref)
70
+ .then(url => {
71
+ // Give some time for the cloud function to create a thumbnail, then try to get it's URL.
72
+ setTimeout(() => this.getThumbnailURL(url, thumbnailRef, errorHandler, finishedHandler), 250);
73
+ })
74
+ .catch(() => errorHandler("Unable to get image URL."));
75
+ })
76
+ .then(() => this.cacheImages());
77
+ // This seems to be broken in Firebase right now. Switched above to a no-feedback approach.
78
+ // const uploadTask = uploadBytesResumable(imageRef, file);
79
+ // uploadTask.on("state_changed",
80
+ // // During upload, notify of progress by sending an integer percent complete.
81
+ // (snapshot) => progressHandler(Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100)),
82
+ // // If there's an error, map it to a message.
83
+ // // https://firebase.google.com/docs/storage/web/handle-errors
84
+ // (error) => {
85
+ // console.error(error);
86
+ // errorHandler(
87
+ // error.code === "storage/unauthorized" ? "Wrong permissions to upload" :
88
+ // error.code === "storage/canceled" ? "Upload canceled" :
89
+ // error.code === "storage/quota-exceeded" ? "Storage limit reached" :
90
+ // error.code === "storage/unauthenticated" ? "Log in again to upload" :
91
+ // "Unknown upload error"
92
+ // )
93
+ // },
94
+ }
95
+ async remove(image) {
96
+ if (storage === undefined)
97
+ return;
98
+ const imageRef = ref(storage, image.ref.fullPath);
99
+ await deleteObject(imageRef);
100
+ return await this.cacheImages();
101
+ }
102
+ }
@@ -0,0 +1,70 @@
1
+ import type ChapterNode from "../chapter/ChapterNode";
2
+ import type Edition from "./Edition";
3
+ import type { DocumentReference } from "firebase/firestore";
4
+ export declare type ChapterSpecification = {
5
+ ref?: DocumentReference;
6
+ id: string;
7
+ title: string;
8
+ authors: string[];
9
+ image: string | null;
10
+ numbered: boolean;
11
+ forthcoming: boolean;
12
+ section?: string;
13
+ text?: string;
14
+ };
15
+ export declare type ChapterContent = {
16
+ text: string;
17
+ };
18
+ export declare type Match = {
19
+ left: string;
20
+ match: string;
21
+ right: string;
22
+ };
23
+ export default class Chapter {
24
+ readonly spec: ChapterSpecification;
25
+ edition: Edition;
26
+ ast: ChapterNode | undefined;
27
+ index: Record<string, Array<Match>> | undefined;
28
+ wordCount: number | undefined;
29
+ edits: {
30
+ resolve: Function;
31
+ reject: Function;
32
+ }[];
33
+ lastEdit: number;
34
+ listeners: Set<(text: string) => void>;
35
+ constructor(edition: Edition, spec: ChapterSpecification);
36
+ saveEdits(): void;
37
+ addListener(listener: (text: string) => void): void;
38
+ removeListener(listener: (text: string) => void): void;
39
+ notifyListeners(): void;
40
+ requestSave(): Promise<void>;
41
+ toObject(): any;
42
+ getBook(): Edition;
43
+ getRef(): DocumentReference<import("@firebase/firestore").DocumentData> | undefined;
44
+ getChapterID(): string;
45
+ setChapterID(id: string): Promise<void> | undefined;
46
+ getSection(): string | undefined;
47
+ setSection(section: string): Promise<void> | undefined;
48
+ isForthcoming(): boolean;
49
+ setForthcoming(forthcoming: boolean): Promise<void> | undefined;
50
+ isNumbered(): boolean;
51
+ setNumbered(numbered: boolean): Promise<void> | undefined;
52
+ getText(): string | undefined;
53
+ setText(text: string): void;
54
+ setAST(node: ChapterNode): Promise<void> | undefined;
55
+ addAuthor(name: string): Promise<void>;
56
+ getAuthors(): string[];
57
+ setAuthor(index: number, name: string): Promise<void> | undefined;
58
+ removeAuthor(index: number): Promise<void> | undefined;
59
+ getTitle(): string;
60
+ setTitle(title: string): Promise<void> | undefined;
61
+ getPosition(): number | undefined;
62
+ move(increment: number): Promise<void>;
63
+ delete(): Promise<void>;
64
+ getImage(): string | null;
65
+ setImage(embed: string | null): Promise<void> | undefined;
66
+ getIndex(): Record<string, Match[]> | undefined;
67
+ getAST(): ChapterNode | undefined;
68
+ getReadingTime(): number | undefined;
69
+ computeIndex(): Record<string, Match[]> | undefined;
70
+ }
@@ -0,0 +1,221 @@
1
+ import Parser from "../chapter/Parser";
2
+ import { BookSaveStatus } from "./Edition";
3
+ import { loadChapterTextFromFirestore, updateChapterTextInFirestore } from "../Firestore";
4
+ export default class Chapter {
5
+ // The metadata from the database.
6
+ spec;
7
+ edition;
8
+ // Caches
9
+ ast;
10
+ index;
11
+ wordCount;
12
+ // A list of requested edits, stored as promises, resolved
13
+ // when the book is ready to save.
14
+ edits = [];
15
+ lastEdit = 0;
16
+ // A set of listeners that are notified when the chapter text changes.
17
+ listeners = new Set();
18
+ constructor(edition, spec) {
19
+ this.edition = edition;
20
+ // Copy the spec, filling in defaults as necessary.
21
+ this.spec = {
22
+ ref: spec.ref,
23
+ id: spec.id,
24
+ title: spec.title,
25
+ authors: spec.authors ?? [],
26
+ image: spec.image ?? null,
27
+ numbered: spec.numbered === true || spec.numbered === undefined,
28
+ forthcoming: spec.forthcoming === true,
29
+ text: spec.text
30
+ };
31
+ // If the chapter has text, then parse it, count searchable words, and compute an index.
32
+ if (this.spec.text !== undefined)
33
+ this.setText(this.spec.text);
34
+ else if (this.spec.ref) {
35
+ loadChapterTextFromFirestore(this.spec.ref).then(text => this.setText(text.text));
36
+ }
37
+ // Otherwise, set them all to undefined.
38
+ else {
39
+ this.ast = undefined;
40
+ this.wordCount = 0;
41
+ this.index = undefined;
42
+ }
43
+ }
44
+ saveEdits() {
45
+ if (this.edition.editionRef && this.spec.ref && this.spec.text !== undefined) {
46
+ // If it's been more than a second since our last edit and there
47
+ // are edits that haven't been saved, try updating the book,
48
+ // and if we succeed, resolve all of the edits, and if we fail,
49
+ // then reject them.
50
+ if (Date.now() - this.lastEdit > 1000 && this.edits.length > 0) {
51
+ // Tell listeners that this book model changed.
52
+ this.edition.notifyListeners(BookSaveStatus.Saving);
53
+ updateChapterTextInFirestore(this.edition.editionRef, this.spec.ref, this.spec.text)
54
+ .then(() => {
55
+ // Approve the edits.
56
+ this.edits.forEach(edit => edit.resolve());
57
+ this.edition.notifyListeners(BookSaveStatus.Saved);
58
+ })
59
+ .catch(() => {
60
+ // Reject the edits.
61
+ this.edits.forEach(edit => edit.reject());
62
+ this.edition.notifyListeners(BookSaveStatus.Error);
63
+ })
64
+ .finally(() => {
65
+ // Reset the edit queue.
66
+ this.edits = [];
67
+ });
68
+ }
69
+ }
70
+ }
71
+ addListener(listener) { this.listeners.add(listener); }
72
+ removeListener(listener) { this.listeners.delete(listener); }
73
+ notifyListeners() {
74
+ this.listeners.forEach(listener => this.spec.text !== undefined ? listener.call(undefined, this.spec.text) : undefined);
75
+ }
76
+ // Adds a save request to the queue, to be resolved later after a period of
77
+ // inactivity. Returns a promise that will eventually be resolved.
78
+ requestSave() {
79
+ // Tell listeners that this chapter model changed.
80
+ this.edition.notifyListeners(BookSaveStatus.Changed);
81
+ // Return a promise that will resolve or reject later after this model saves the edits to the database.
82
+ this.lastEdit = Date.now();
83
+ const promise = new Promise((resolve, reject) => {
84
+ this.edits.push({ resolve: resolve, reject: reject });
85
+ });
86
+ return promise;
87
+ }
88
+ toObject() {
89
+ // Deep copy to prevent external mutations.
90
+ const payload = JSON.parse(JSON.stringify(this.spec));
91
+ // Reassign a copy of the Firebase ref, since the deep copy doesn't work for it.
92
+ payload.ref = this.spec.ref;
93
+ // Note that we don't include the chapter text in the payload; that's stored in the chapters subcollection.
94
+ // We just return the chapter metadata here, which is stored in the book collection.
95
+ delete payload.text;
96
+ return payload;
97
+ }
98
+ getBook() { return this.edition; }
99
+ getRef() { return this.spec.ref; }
100
+ getChapterID() { return this.spec.id; }
101
+ setChapterID(id) {
102
+ if (this.spec.id !== id) {
103
+ this.spec.id = id;
104
+ return this.edition.requestSave();
105
+ }
106
+ }
107
+ getSection() { return this.spec.section; }
108
+ setSection(section) {
109
+ if (this.spec.section !== section) {
110
+ this.spec.section = section;
111
+ return this.edition.requestSave();
112
+ }
113
+ }
114
+ isForthcoming() { return this.spec.forthcoming; }
115
+ setForthcoming(forthcoming) {
116
+ if (this.spec.forthcoming !== forthcoming) {
117
+ this.spec.forthcoming = forthcoming;
118
+ return this.edition.requestSave();
119
+ }
120
+ }
121
+ isNumbered() { return this.spec.numbered; }
122
+ setNumbered(numbered) {
123
+ if (numbered !== this.spec.numbered) {
124
+ this.spec.numbered = numbered;
125
+ return this.edition.requestSave();
126
+ }
127
+ }
128
+ getText() { return this.spec.text; }
129
+ setText(text) {
130
+ this.setAST(Parser.parseChapter(this.edition, text));
131
+ }
132
+ setAST(node) {
133
+ this.ast = node;
134
+ const newText = this.ast.toBookdown();
135
+ const changed = this.spec.text !== newText;
136
+ this.spec.text = newText;
137
+ this.wordCount = this.ast.toText().split(/\s+/).length;
138
+ this.index = this.computeIndex();
139
+ // Don't save if its the same. This is just an optimization.
140
+ if (changed) {
141
+ this.notifyListeners();
142
+ if (this.edition.editionRef && this.spec.ref)
143
+ return this.requestSave();
144
+ }
145
+ }
146
+ addAuthor(name) {
147
+ this.spec.authors.push(name);
148
+ return this.edition.requestSave();
149
+ }
150
+ getAuthors() { return this.spec.authors; }
151
+ setAuthor(index, name) {
152
+ if (index >= 0 && index < this.spec.authors.length) {
153
+ this.spec.authors[index] = name;
154
+ return this.edition.requestSave();
155
+ }
156
+ }
157
+ removeAuthor(index) {
158
+ if (index >= 0 && index < this.spec.authors.length) {
159
+ this.spec.authors.splice(index, 1);
160
+ return this.edition.requestSave();
161
+ }
162
+ }
163
+ getTitle() { return this.spec.title; }
164
+ setTitle(title) {
165
+ if (title !== this.spec.title) {
166
+ this.spec.title = title;
167
+ return this.edition.requestSave();
168
+ }
169
+ }
170
+ getPosition() { return this.edition.getChapterPosition(this.getChapterID()); }
171
+ move(increment) { return this.edition.moveChapter(this.getChapterID(), increment); }
172
+ delete() { return this.edition.deleteChapter(this.getChapterID()); }
173
+ getImage() { return this.spec.image; }
174
+ setImage(embed) {
175
+ if (this.spec.image === embed)
176
+ return;
177
+ this.spec.image = embed;
178
+ return this.edition.requestSave();
179
+ }
180
+ getIndex() { return this.index; }
181
+ getAST() { return this.ast; }
182
+ // Utility function
183
+ getReadingTime() { return this.isForthcoming() || !this.wordCount ? undefined : Math.max(1, Math.round(this.wordCount / 150)); }
184
+ computeIndex() {
185
+ if (!this.ast)
186
+ return;
187
+ // Build a list of common words
188
+ let commonWords = new Set();
189
+ ["a", "about", "all", "also", "and", "are", "as", "at", "be", "because", "but", "by", "can", "come", "could", "day", "do", "does", "even", "find", "first", "for", "from", "get", "give", "go", "has", "have", "he", "her", "here", "him", "his", "how", "I", "if", "in", "into", "is", "it", "its", "just", "know", "like", "look", "make", "man", "many", "me", "more", "my", "new", "no", "not", "now", "of", "on", "one", "only", "or", "other", "our", "out", "people", "say", "see", "she", "so", "some", "take", "tell", "than", "that", "the", "their", "them", "then", "there", "these", "they", "thing", "think", "this", "those", "time", "to", "two", "up", "use", "very", "want", "was", "way", "we", "well", "went", "what", "when", "which", "who", "will", "with", "would", "year", "yes", "you", "your"].forEach(word => commonWords.add(word));
190
+ // Get all the text in the chapter.
191
+ let text = this.ast.toText();
192
+ // Split by word boundaries.
193
+ const words = text.split(/\b/);
194
+ // Index the words
195
+ const index = {};
196
+ words.forEach((word, wordNumber) => {
197
+ // Skip non words. We keep them for search results.
198
+ if (!/[a-zA-Z\u2019]+/.test(word))
199
+ return;
200
+ word = word.toLowerCase();
201
+ // Should we include?
202
+ // • It shouldn't be a common word
203
+ // • It shouldn't have an apostrophe
204
+ // • It should be longer than two letters
205
+ // • It shouldn't end in 'ly'
206
+ if (!(commonWords.has(word)) && word.indexOf('\u2019') < 0 && (word.length > 2 && word.substring(word.length - 2, word.length) !== "ly")) {
207
+ // If we haven't started a list of occurrences, start one.
208
+ if (!(word in index))
209
+ index[word] = [];
210
+ let match = {
211
+ left: words.slice(Math.max(0, wordNumber - 10), Math.max(0, wordNumber - 1) + 1).join(""),
212
+ match: words[wordNumber],
213
+ right: words.slice(Math.min(words.length - 1, wordNumber + 1), Math.min(words.length - 1, wordNumber + 10) + 1).join("")
214
+ };
215
+ // Add the occurrence, but a lower case canonical version.
216
+ index[word.toLowerCase()].push(match);
217
+ }
218
+ });
219
+ return index;
220
+ }
221
+ }
@@ -0,0 +1,10 @@
1
+ declare const ChapterIDs: {
2
+ TableOfContentsID: string;
3
+ ReferencesID: string;
4
+ SearchID: string;
5
+ MediaID: string;
6
+ IndexID: string;
7
+ GlossaryID: string;
8
+ UnknownID: string;
9
+ };
10
+ export default ChapterIDs;
@@ -0,0 +1,10 @@
1
+ const ChapterIDs = {
2
+ TableOfContentsID: "",
3
+ ReferencesID: "references",
4
+ SearchID: "search",
5
+ MediaID: "media",
6
+ IndexID: "index",
7
+ GlossaryID: "glossary",
8
+ UnknownID: "unknown"
9
+ };
10
+ export default ChapterIDs;
@@ -0,0 +1,6 @@
1
+ declare type Definition = {
2
+ phrase: string;
3
+ definition: string;
4
+ synonyms?: string[];
5
+ };
6
+ export default Definition;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,111 @@
1
+ import Chapter from './Chapter';
2
+ import type { ChapterSpecification } from './Chapter';
3
+ import EmbedNode from "../chapter/EmbedNode";
4
+ import type ReferenceNode from "../chapter/ReferenceNode";
5
+ import type Theme from './Theme';
6
+ import type Definition from './Definition.js';
7
+ import type Book from './Book.js';
8
+ import type { DocumentReference } from 'firebase/firestore';
9
+ export declare type EditionSpecification = {
10
+ title: string;
11
+ authors: string[];
12
+ images: Record<string, string | null>;
13
+ description: string;
14
+ chapters: ChapterSpecification[];
15
+ license: string;
16
+ acknowledgements: string;
17
+ tags: string[];
18
+ revisions: [string, string][];
19
+ sources: Record<string, string>;
20
+ references: Record<string, string | string[]>;
21
+ symbols: Record<string, string>;
22
+ glossary: Record<string, Definition>;
23
+ theme: Theme | null;
24
+ uids: Array<string>;
25
+ bookRef?: DocumentReference;
26
+ };
27
+ export declare enum BookSaveStatus {
28
+ Changed = 0,
29
+ Saving = 1,
30
+ Saved = 2,
31
+ Error = 3
32
+ }
33
+ export default class Edition {
34
+ readonly specification: EditionSpecification;
35
+ readonly book: Book | undefined;
36
+ editionRef: DocumentReference | undefined;
37
+ chapters: Chapter[];
38
+ chaptersByID: Record<string, Chapter | undefined>;
39
+ listeners: Set<Function>;
40
+ edits: {
41
+ resolve: Function;
42
+ reject: Function;
43
+ }[];
44
+ lastEdit: number;
45
+ constructor(book?: Book, editionRef?: DocumentReference, specification?: EditionSpecification);
46
+ setRef(ref: DocumentReference): void;
47
+ saveEdits(): Promise<void>;
48
+ getBook(): Book | undefined;
49
+ addListener(listener: (status: BookSaveStatus) => void): void;
50
+ removeListener(listener: Function): void;
51
+ notifyListeners(status: BookSaveStatus): void;
52
+ requestSave(): Promise<void>;
53
+ toObject(): any;
54
+ getRef(): DocumentReference<import("@firebase/firestore").DocumentData> | undefined;
55
+ addUserID(uid: string): void;
56
+ getTitle(): string;
57
+ setTitle(title: string): Promise<void>;
58
+ getDescription(): string;
59
+ setDescription(text: string): Promise<void>;
60
+ getAcknowledgements(): string;
61
+ setAcknowledgements(text: string): Promise<void>;
62
+ getLicense(): string;
63
+ setLicense(text: string): Promise<void>;
64
+ getChapters(): Chapter[];
65
+ hasChapter(chapterID: string): boolean;
66
+ getChapter(chapterID: string): Chapter | undefined;
67
+ getChapterPosition(chapterID: string): number | undefined;
68
+ getChapterCount(): number;
69
+ addChapter(): Promise<void>;
70
+ moveChapter(chapterID: string, increment: number): Promise<void>;
71
+ deleteChapter(chapterID: string): Promise<void>;
72
+ getSymbols(): Record<string, string>;
73
+ hasReferences(): boolean;
74
+ getReferences(): Record<string, string | string[]>;
75
+ getReference(citationID: string): string | string[] | undefined;
76
+ addReferences(references: ReferenceNode[]): Promise<void>;
77
+ editReference(ref: ReferenceNode): Promise<void> | undefined;
78
+ editReferenceID(newCitationID: string, ref: ReferenceNode): Promise<void> | undefined;
79
+ removeReference(citationID: string): Promise<void> | undefined;
80
+ hasGlossary(): boolean;
81
+ getGlossary(): Record<string, Definition>;
82
+ hasDefinition(id: string): boolean;
83
+ addDefinition(id: string, phrase: string, definition: string, synonyms: string[]): Promise<void>;
84
+ removeDefinition(id: string): Promise<void>;
85
+ editDefinition(id: string, definition: Definition): Promise<void>;
86
+ editDefinitionID(id: string, newID: string): Promise<void> | undefined;
87
+ getTags(): string[];
88
+ getTheme(): Theme | null;
89
+ setTheme(theme: Theme | null): Promise<void>;
90
+ setThemeValue(group: string, name: string, value: string): Promise<void> | undefined;
91
+ getAuthors(): string[];
92
+ addAuthor(name: string): Promise<void>;
93
+ setAuthor(index: number, name: string): Promise<void>;
94
+ removeAuthor(index: number): Promise<void>;
95
+ getRevisions(): [string, string][];
96
+ hasImage(id: string): boolean;
97
+ getImage(id: string): string | null;
98
+ setImage(id: string, embed: string | null): Promise<void> | undefined;
99
+ getBookReadingTime(): number | undefined;
100
+ getChapterNumber(chapterID: string): undefined;
101
+ getSource(sourceID: string): string | null;
102
+ getChapterName(chapterID: string): string | undefined;
103
+ getChapterSection(chapterID: string): string | undefined;
104
+ getFootnoteSymbol(number: number): string;
105
+ getBookIndex(): Record<string, Set<string>>;
106
+ getNextChapterID(chapterID: string): string | null;
107
+ getPreviousChapterID(chapterID: string): string | null;
108
+ getEmbeds(): EmbedNode[];
109
+ isLatestPublishedEdition(): boolean;
110
+ getEditionNumber(): number | undefined;
111
+ }