bookish-press 0.4.23 → 0.4.25

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 (86) hide show
  1. package/components/Auth.svelte +11 -13
  2. package/components/BookPreview.svelte +12 -14
  3. package/components/app/Feedback.svelte +12 -10
  4. package/components/app/Head.svelte +4 -1
  5. package/components/app/Head.svelte.d.ts +6 -15
  6. package/components/app/Paragraph.svelte +1 -0
  7. package/components/app/{Writing.svelte → Section.svelte} +0 -1
  8. package/components/app/Section.svelte.d.ts +27 -0
  9. package/components/app/TextInput.svelte +1 -0
  10. package/components/chapter/Block.svelte +3 -1
  11. package/components/chapter/CaptionedCode.svelte +2 -2
  12. package/components/chapter/ChapterBody.svelte +2 -1
  13. package/components/chapter/ChapterBody.svelte.d.ts +1 -0
  14. package/components/chapter/Comment.svelte +2 -2
  15. package/components/chapter/Embed.svelte +3 -3
  16. package/components/chapter/Embed.svelte.d.ts +1 -0
  17. package/components/chapter/Figure.svelte +2 -2
  18. package/components/chapter/Format.svelte +5 -3
  19. package/components/chapter/Format.svelte.d.ts +1 -0
  20. package/components/chapter/InlineCode.svelte +2 -2
  21. package/components/chapter/Label.svelte +2 -2
  22. package/components/chapter/Link.svelte +2 -2
  23. package/components/chapter/Marginal.svelte +2 -2
  24. package/components/chapter/Paragraph.svelte +5 -0
  25. package/components/chapter/Table.svelte +2 -2
  26. package/components/chapter/Text.svelte +2 -2
  27. package/components/editor/BookishEditor.svelte +51 -28
  28. package/components/editor/BookishEditor.svelte.d.ts +4 -1
  29. package/components/editor/CaretView.svelte +3 -1
  30. package/components/editor/CaretView.svelte.d.ts +1 -0
  31. package/components/editor/ConfirmButton.svelte +1 -1
  32. package/components/editor/Note.svelte +1 -0
  33. package/components/editor/Permissions.svelte +95 -0
  34. package/components/editor/Permissions.svelte.d.ts +20 -0
  35. package/components/editor/Toolbar.svelte +0 -1
  36. package/components/editor/ToolbarGroup.svelte +0 -1
  37. package/components/page/Acknowledgements.svelte +13 -11
  38. package/components/page/Authors.svelte +5 -8
  39. package/components/page/Authors.svelte.d.ts +1 -0
  40. package/components/page/BookLoader.svelte +59 -0
  41. package/components/page/BookLoader.svelte.d.ts +16 -0
  42. package/components/page/BulkReferenceEditor.svelte +8 -8
  43. package/components/page/Chapter.svelte +43 -10
  44. package/components/page/Contexts.d.ts +14 -5
  45. package/components/page/Contexts.js +88 -6
  46. package/components/page/DefinitionView.svelte +18 -8
  47. package/components/page/Description.svelte +12 -9
  48. package/components/page/Edition.svelte +8 -8
  49. package/components/page/Edition.svelte.d.ts +0 -1
  50. package/components/page/EditionLoader.svelte +99 -0
  51. package/components/page/EditionLoader.svelte.d.ts +18 -0
  52. package/components/page/Editions.svelte +92 -143
  53. package/components/page/Glossary.svelte +13 -6
  54. package/components/page/Header.svelte +22 -20
  55. package/components/page/Header.svelte.d.ts +2 -0
  56. package/components/page/Index.svelte +6 -4
  57. package/components/page/Instructions.svelte +1 -2
  58. package/components/page/Instructions.svelte.d.ts +3 -1
  59. package/components/page/License.svelte +20 -12
  60. package/components/page/Media.svelte +14 -12
  61. package/components/page/Publish.svelte +120 -0
  62. package/components/page/Publish.svelte.d.ts +14 -0
  63. package/components/page/Reference.svelte +16 -17
  64. package/components/page/References.svelte +8 -3
  65. package/components/page/Search.svelte +3 -1
  66. package/components/page/Status.svelte +18 -19
  67. package/components/page/SubdomainEditor.svelte +3 -2
  68. package/components/page/TableOfContents.svelte +70 -30
  69. package/components/page/TableOfContentsRow.svelte +4 -4
  70. package/components/page/ThemeEditor.svelte +8 -5
  71. package/components/page/Unknown.svelte +5 -2
  72. package/models/CRUD.d.ts +19 -11
  73. package/models/CRUD.js +177 -122
  74. package/models/Firebase.js +12 -8
  75. package/models/book/Book.d.ts +19 -14
  76. package/models/book/Book.js +47 -54
  77. package/models/book/Chapter.d.ts +5 -1
  78. package/models/book/Chapter.js +24 -15
  79. package/models/book/Edition.d.ts +31 -7
  80. package/models/book/Edition.js +101 -32
  81. package/models/chapter/Parser.d.ts +2 -2
  82. package/models/chapter/Parser.js +26 -24
  83. package/package.json +18 -15
  84. package/components/app/Writing.svelte.d.ts +0 -27
  85. package/models/loadEdition.d.ts +0 -5
  86. package/models/loadEdition.js +0 -20
@@ -11,7 +11,7 @@ import ChapterBody from '../chapter/ChapterBody.svelte';
11
11
  import PossibleReference from './PossibleReference.svelte';
12
12
  import ChapterNumber from './ChapterNumber.svelte';
13
13
  import Problem from '../chapter/Problem.svelte';
14
- import { CHAPTER, getBook, getEdition, isEditable, setChapter, } from './Contexts';
14
+ import { CHAPTER, getUser, getBook, getEdition, isBookEditable, isChapterEditable, isEditionPartiallyEditable, getLeasee, lease, setChapter, isEditionEditable, } from './Contexts';
15
15
  import { writable } from 'svelte/store';
16
16
  import { onMount, setContext } from 'svelte';
17
17
  import { hideOutlineIfObscured, layoutMarginals } from './margins';
@@ -20,11 +20,13 @@ import { goto } from '$app/navigation';
20
20
  import ChapterNode from '../../models/chapter/ChapterNode';
21
21
  import Muted from './Muted.svelte';
22
22
  import Title from './Title.svelte';
23
+ import PageHeader from './PageHeader.svelte';
24
+ import Permissions from '../editor/Permissions.svelte';
23
25
  export let chapter;
24
26
  export let print = false;
25
27
  let book = getBook();
26
28
  let edition = getEdition();
27
- let editable = isEditable();
29
+ let auth = getUser();
28
30
  // Keep track of the scroll position to facilitate reading during reloads.
29
31
  function rememberPosition() {
30
32
  localStorage.setItem('scrollposition', '' + window.scrollY);
@@ -170,7 +172,7 @@ $: chapterSection = $edition?.getChapterSection(chapterID);
170
172
  $: chapterAST = $edition ? chapter.getAST($edition) : undefined;
171
173
  $: citations = chapterAST ? chapterAST.getCitations() : undefined;
172
174
  $: editionNumber =
173
- $book && $edition ? $edition.getEditionNumber($book) : undefined;
175
+ $book && $edition ? $edition.getEditionNumber() : undefined;
174
176
  let chapterStore = writable();
175
177
  setContext(CHAPTER, chapterStore);
176
178
  $: chapterStore.set({
@@ -180,6 +182,12 @@ $: chapterStore.set({
180
182
  marginal: marginal,
181
183
  layoutMarginals: layoutMarginals,
182
184
  });
185
+ let editable = isBookEditable() ||
186
+ isEditionEditable() ||
187
+ isChapterEditable() ||
188
+ ($auth !== undefined &&
189
+ $auth.user !== null &&
190
+ chapter.isEditor($auth.user.uid));
183
191
  </script>
184
192
 
185
193
  {#if $edition}
@@ -188,6 +196,8 @@ $: chapterStore.set({
188
196
  title={`${$edition.getTitle()} - ${chapter.getTitle()}`}
189
197
  >
190
198
  <Header
199
+ editable={isChapterEditable()}
200
+ id={chapter.id}
191
201
  header={chapter.getTitle()}
192
202
  label="Chapter title"
193
203
  tags={$edition.getTags()}
@@ -266,6 +276,7 @@ $: chapterStore.set({
266
276
  </svelte:fragment>
267
277
  <!-- If there are chapter authors, render those, otherwise use the book authors -->
268
278
  <Authors
279
+ editable={isChapterEditable()}
269
280
  slot="after"
270
281
  authors={chapter.getAuthors()}
271
282
  inheritedAuthors={$edition.getAuthors()}
@@ -281,13 +292,13 @@ $: chapterStore.set({
281
292
  />
282
293
  </Header>
283
294
 
284
- <Instructions>
295
+ <Instructions {editable}>
285
296
  Edit your chapter's title, authors, and cover image above. You can
286
- also change the ID of the chapter, which appears in it's URL. Write
287
- your chapter text below, using the many formatting options in the
288
- toolbar above to format text, add headers, lists, tables, comments,
297
+ also change the ID of the chapter, which is the text that appears in
298
+ the chapter URL. Write your chapter text below, formatting options
299
+ in the toolbar above to add headers, lists, tables, comments,
289
300
  citations, footnotes, and more. It's okay to try things, you can
290
- always undo! Saves are automatic, each time you stop typing.
301
+ always undo. Saves are automatic.
291
302
  </Instructions>
292
303
 
293
304
  <!-- Render the chapter body, passing some context -->
@@ -295,7 +306,8 @@ $: chapterStore.set({
295
306
  {#if chapterAST}
296
307
  {#if editable}
297
308
  <BookishEditor
298
- ast={chapterAST}
309
+ text={chapterAST.toBookdown()}
310
+ parser={(text) => Parser.parseChapter($edition, text)}
299
311
  save={(ast) =>
300
312
  ast instanceof ChapterNode
301
313
  ? setChapter(
@@ -307,7 +319,9 @@ $: chapterStore.set({
307
319
  chapter={true}
308
320
  autofocus
309
321
  component={ChapterBody}
310
- placeholder="Type here"
322
+ placeholder="write here"
323
+ leasee={getLeasee(auth, edition, chapter.id)}
324
+ lease={(lock) => lease(auth, edition, chapter.id, lock)}
311
325
  />
312
326
  {:else}
313
327
  <ChapterBody node={chapterAST} />
@@ -340,6 +354,25 @@ $: chapterStore.set({
340
354
  {/each}
341
355
  </ol>
342
356
  {/if}
357
+
358
+ {#if editable || isEditionPartiallyEditable()}
359
+ <PageHeader>Editors</PageHeader>
360
+ <Instructions {editable}
361
+ >These authors can edit this chapter. Add an email here to
362
+ provide chapter-level edit permissions.</Instructions
363
+ >
364
+
365
+ <Permissions
366
+ uids={chapter.uids}
367
+ inheriteduids={Array.from(
368
+ new Set([...$edition.uids, ...($book ? $book.uids : [])])
369
+ )}
370
+ atleastone={false}
371
+ writable={isChapterEditable()}
372
+ change={(uids) =>
373
+ setChapter(edition, chapter, chapter.withEditors(uids))}
374
+ />
375
+ {/if}
343
376
  </Page>
344
377
  {/if}
345
378
 
@@ -6,18 +6,23 @@ import type Edition from '../../models/book/Edition';
6
6
  import type Authentication from '../Authentication';
7
7
  import type CaretState from '../editor/CaretState';
8
8
  import type ChapterContext from './ChapterContext';
9
+ import type { DocumentReference } from 'firebase/firestore';
9
10
  export type DarkModeStore = Writable<boolean>;
10
11
  export declare const DARK_MODE: unique symbol;
11
12
  export declare function getDarkMode(): DarkModeStore;
12
- export type AuthStore = Writable<Authentication | undefined>;
13
- export declare const AUTH: unique symbol;
14
- export declare function getAuth(): AuthStore;
13
+ export type UserStore = Writable<Authentication | undefined>;
14
+ export declare const USER: unique symbol;
15
+ export declare function getUser(): UserStore;
15
16
  export type BookStore = Writable<Book | undefined>;
16
17
  export declare const BOOK: unique symbol;
17
18
  export declare function getBook(): BookStore;
18
19
  export type EditionStore = Writable<Edition | undefined>;
19
20
  export declare const EDITION: unique symbol;
20
21
  export declare function getEdition(): EditionStore;
22
+ export type ChapterText = [DocumentReference, string][];
23
+ export type ChapterTextStore = Writable<ChapterText | undefined>;
24
+ export declare const CHAPTERTEXT: unique symbol;
25
+ export declare function getChapterText(): ChapterTextStore;
21
26
  export type ChapterStore = Writable<ChapterContext>;
22
27
  export declare const CHAPTER: unique symbol;
23
28
  export declare function getChapter(): ChapterStore;
@@ -26,8 +31,10 @@ export declare function getChapter(): ChapterStore;
26
31
  * global edition store with the new edition, propogating the changes everywhere.
27
32
  */
28
33
  export declare function setChapter(store: EditionStore, previous: Chapter, chapter: Chapter): void;
29
- export declare const EDITABLE: unique symbol;
30
- export declare function isEditable(): boolean;
34
+ export declare function isChapterEditable(): boolean;
35
+ export declare function isEditionEditable(): boolean;
36
+ export declare function isEditionPartiallyEditable(): boolean;
37
+ export declare function isBookEditable(): boolean;
31
38
  export declare const BASE: unique symbol;
32
39
  export type BaseStore = Writable<string>;
33
40
  export declare function getBase(): BaseStore;
@@ -36,3 +43,5 @@ export declare function getCaret(): Writable<CaretState | undefined>;
36
43
  export declare const STATUS: unique symbol;
37
44
  export type StatusStore = Writable<BookSaveStatus>;
38
45
  export declare function getStatus(): StatusStore;
46
+ export declare function lease(authStore: UserStore, editionStore: EditionStore, contentID: string, lock: boolean): Promise<boolean>;
47
+ export declare function getLeasee(authStore: UserStore, editionStore: EditionStore, contentID: string): string | boolean;
@@ -4,13 +4,15 @@
4
4
  // as the resulting value is coerced to Type even if it's not that value.
5
5
  import { getContext } from 'svelte';
6
6
  import { get } from 'svelte/store';
7
+ import { page } from '$app/stores';
8
+ import { updateLock } from '../../models/CRUD';
7
9
  export const DARK_MODE = Symbol('dark');
8
10
  export function getDarkMode() {
9
11
  return getContext(DARK_MODE);
10
12
  }
11
- export const AUTH = Symbol('auth');
12
- export function getAuth() {
13
- return getContext(AUTH);
13
+ export const USER = Symbol('user');
14
+ export function getUser() {
15
+ return getContext(USER);
14
16
  }
15
17
  export const BOOK = Symbol('book');
16
18
  export function getBook() {
@@ -20,6 +22,10 @@ export const EDITION = Symbol('edition');
20
22
  export function getEdition() {
21
23
  return getContext(EDITION);
22
24
  }
25
+ export const CHAPTERTEXT = Symbol('chaptertext');
26
+ export function getChapterText() {
27
+ return getContext(CHAPTERTEXT);
28
+ }
23
29
  export const CHAPTER = Symbol('chapter');
24
30
  export function getChapter() {
25
31
  return getContext(CHAPTER);
@@ -33,9 +39,53 @@ export function setChapter(store, previous, chapter) {
33
39
  if (edition)
34
40
  store.set(edition.withRevisedChapter(previous, chapter));
35
41
  }
36
- export const EDITABLE = Symbol('editable');
37
- export function isEditable() {
38
- return getContext(EDITABLE);
42
+ export function isChapterEditable() {
43
+ if (!get(page).route.id?.startsWith('/write'))
44
+ return false;
45
+ const auth = get(getUser());
46
+ const chapter = get(getChapter());
47
+ const edition = get(getEdition());
48
+ const book = get(getBook());
49
+ return (chapter !== undefined &&
50
+ edition !== undefined &&
51
+ book !== undefined &&
52
+ auth !== undefined &&
53
+ auth.user !== null &&
54
+ (chapter.chapter.isEditor(auth.user.uid) ||
55
+ edition.isEditor(auth.user.uid) ||
56
+ book.isEditor(auth.user.uid)));
57
+ }
58
+ export function isEditionEditable() {
59
+ if (!get(page).route.id?.startsWith('/write'))
60
+ return false;
61
+ const auth = get(getUser());
62
+ const edition = get(getEdition());
63
+ const book = get(getBook());
64
+ return (edition !== undefined &&
65
+ book !== undefined &&
66
+ auth !== undefined &&
67
+ auth.user !== null &&
68
+ (edition.isEditor(auth.user.uid) || book.isEditor(auth.user.uid)));
69
+ }
70
+ export function isEditionPartiallyEditable() {
71
+ if (!get(page).route.id?.startsWith('/write'))
72
+ return false;
73
+ const auth = get(getUser());
74
+ const edition = get(getEdition());
75
+ return (edition !== undefined &&
76
+ auth !== undefined &&
77
+ auth.user !== null &&
78
+ edition.isChapterEditor(auth.user.uid));
79
+ }
80
+ export function isBookEditable() {
81
+ if (!get(page).route.id?.startsWith('/write'))
82
+ return false;
83
+ const auth = get(getUser());
84
+ const book = get(getBook());
85
+ return (book !== undefined &&
86
+ auth !== undefined &&
87
+ auth.user !== null &&
88
+ book.isEditor(auth.user.uid));
39
89
  }
40
90
  export const BASE = Symbol('base');
41
91
  export function getBase() {
@@ -49,3 +99,35 @@ export const STATUS = Symbol('status');
49
99
  export function getStatus() {
50
100
  return getContext(STATUS);
51
101
  }
102
+ export async function lease(authStore, editionStore, contentID, lock) {
103
+ const edition = get(editionStore);
104
+ const auth = get(authStore);
105
+ if (edition && auth && auth.user) {
106
+ await updateLock(edition, auth.user.uid, contentID, lock);
107
+ const latestEdition = get(editionStore);
108
+ const latestAuth = get(authStore);
109
+ if (latestEdition && latestAuth && latestAuth.user) {
110
+ editionStore.set(latestEdition.withLock(latestAuth.user.uid, contentID, lock));
111
+ return true;
112
+ }
113
+ else
114
+ return false;
115
+ }
116
+ else
117
+ return false;
118
+ }
119
+ export function getLeasee(authStore, editionStore, contentID) {
120
+ const edition = get(editionStore);
121
+ const auth = get(authStore);
122
+ if (edition !== undefined && auth !== undefined && auth.user !== null) {
123
+ const leasee = edition.getLeasee(contentID);
124
+ if (leasee === undefined)
125
+ return false;
126
+ else if (leasee === auth.user.uid)
127
+ return true;
128
+ else
129
+ return leasee;
130
+ }
131
+ else
132
+ return false;
133
+ }
@@ -5,12 +5,13 @@ import BookishEditor from '../editor/BookishEditor.svelte';
5
5
  import Format from '../chapter/Format.svelte';
6
6
  import Note from '../editor/Note.svelte';
7
7
  import { afterUpdate } from 'svelte';
8
- import { getEdition, isEditable } from './Contexts';
8
+ import { getUser, getEdition, getLeasee, isChapterEditable, lease, } from './Contexts';
9
9
  import Button from '../app/Button.svelte';
10
10
  export let id;
11
11
  export let definition;
12
+ let auth = getUser();
12
13
  let edition = getEdition();
13
- let editable = isEditable();
14
+ let editable = isChapterEditable();
14
15
  // Focus after adding a new synonym.
15
16
  let newSynonym = false;
16
17
  let synonymsEditor = null;
@@ -25,7 +26,6 @@ afterUpdate(() => {
25
26
  newSynonym = false;
26
27
  }
27
28
  });
28
- $: format = Parser.parseFormat(undefined, definition.definition).withTextIfEmpty();
29
29
  function addSynonym() {
30
30
  if ($edition) {
31
31
  edition.set($edition.withEditedDefinition(id, {
@@ -46,7 +46,7 @@ $: syns = definition.synonyms || [];
46
46
  <TextEditor
47
47
  text={definition.phrase}
48
48
  label={'Glossary phrase editor.'}
49
- placeholder="Phrase"
49
+ placeholder="phrase"
50
50
  valid={(text) => {
51
51
  if (text.length === 0) return "phrase can't be empty";
52
52
  }}
@@ -80,7 +80,9 @@ $: syns = definition.synonyms || [];
80
80
  <dd class="term">
81
81
  {#if editable && edition}
82
82
  <BookishEditor
83
- ast={format}
83
+ text={definition.definition}
84
+ parser={(text) =>
85
+ Parser.parseFormat(undefined, text).withTextIfEmpty()}
84
86
  save={(node) =>
85
87
  $edition
86
88
  ? edition.set(
@@ -93,12 +95,20 @@ $: syns = definition.synonyms || [];
93
95
  : undefined}
94
96
  chapter={false}
95
97
  component={Format}
96
- placeholder="write a definition"
98
+ placeholder="definition"
99
+ leasee={getLeasee(auth, edition, `definition-${id}`)}
100
+ lease={(lock) =>
101
+ lease(auth, edition, `definition-${id}`, lock)}
97
102
  />
98
103
  {:else if definition.definition === ''}
99
104
  <em>Definition</em>
100
105
  {:else}
101
- <Format node={format} />
106
+ <Format
107
+ node={Parser.parseFormat(
108
+ undefined,
109
+ definition.definition
110
+ ).withTextIfEmpty()}
111
+ />
102
112
  {/if}
103
113
  {#if edition && editable}
104
114
  <span bind:this={synonymsEditor}>
@@ -110,7 +120,7 @@ $: syns = definition.synonyms || [];
110
120
  <TextEditor
111
121
  text={syn}
112
122
  label={'Synonym editor.'}
113
- placeholder="Synonym"
123
+ placeholder="synonym"
114
124
  valid={() => undefined}
115
125
  save={(text) => {
116
126
  const newSyns = [...syns];
@@ -1,19 +1,18 @@
1
1
  <script>import Parser from '../../models/chapter/Parser';
2
2
  import ChapterBody from '../chapter/ChapterBody.svelte';
3
3
  import BookishEditor from '../editor/BookishEditor.svelte';
4
- import { getEdition, isEditable } from './Contexts';
5
- let editable = isEditable();
4
+ import { getUser, getEdition, getLeasee, isEditionEditable, lease, } from './Contexts';
5
+ let auth = getUser();
6
+ let editable = isEditionEditable();
6
7
  let edition = getEdition();
7
- $: descriptionNode = $edition
8
- ? Parser.parseChapter($edition, $edition.getDescription())
9
- : undefined;
10
8
  </script>
11
9
 
12
- {#if descriptionNode}
10
+ {#if $edition}
13
11
  <section class="bookish-description">
14
12
  {#if editable}
15
13
  <BookishEditor
16
- ast={descriptionNode}
14
+ text={$edition.getDescription()}
15
+ parser={(text) => Parser.parseChapter($edition, text)}
17
16
  save={(node) =>
18
17
  $edition
19
18
  ? edition.set(
@@ -22,10 +21,14 @@ $: descriptionNode = $edition
22
21
  : undefined}
23
22
  chapter={false}
24
23
  component={ChapterBody}
25
- placeholder="What is this book about?"
24
+ placeholder="book description"
25
+ leasee={getLeasee(auth, edition, `description`)}
26
+ lease={(lock) => lease(auth, edition, `description`, lock)}
26
27
  />
27
28
  {:else}
28
- <ChapterBody node={descriptionNode} />
29
+ <ChapterBody
30
+ node={Parser.parseChapter($edition, $edition.getDescription())}
31
+ />
29
32
  {/if}
30
33
  </section>
31
34
  {/if}
@@ -2,14 +2,10 @@
2
2
  import { goto } from '$app/navigation';
3
3
  import { onMount, setContext } from 'svelte';
4
4
  import { writable } from 'svelte/store';
5
- import { BASE, DARK_MODE, EDITABLE, } from './Contexts';
5
+ import { BASE, DARK_MODE, } from './Contexts';
6
6
  import { BookishTheme } from '../../models/book/Theme';
7
7
  // Poly fill smooth scrolling for Safari.
8
8
  onMount(() => smoothscroll.polyfill());
9
- // True if the edition should be editable. Distributed as context.
10
- export let editable = false;
11
- // When editable changes, update the context.
12
- $: setContext(EDITABLE, editable);
13
9
  // The base path allows links to adjust to different routing contexts in which a book is placed.
14
10
  // For example, when the book is hosted alone, all routes might start with the bare root "/",
15
11
  // but when the book is being viewed or edited in the Bookish app, it needs a prefix for the
@@ -32,20 +28,24 @@ let darkMode = writable(typeof localStorage !== 'undefined' &&
32
28
  setContext(DARK_MODE, darkMode);
33
29
  // When dark mode changes, update the body's class list.
34
30
  $: {
35
- if (typeof document !== undefined) {
31
+ if (typeof document !== 'undefined') {
36
32
  if ($darkMode)
37
33
  document.body.classList.add('dark');
38
34
  else
39
35
  document.body.classList.remove('dark');
40
36
  if (typeof localStorage !== 'undefined')
41
37
  localStorage.setItem('dark', $darkMode ? 'true' : 'false');
38
+ // Set the theme, whatever it is, and change it when the edition changes
39
+ setTheme(edition.getTheme() ?? BookishTheme);
42
40
  }
43
41
  }
42
+ // Set the theme on mount.
43
+ onMount(() => {
44
+ setTheme(edition.getTheme() ?? BookishTheme);
45
+ });
44
46
  // Redirect old hash routes by simply replacing their hash before routing.
45
47
  if (typeof window !== 'undefined' && window.location.hash.startsWith('#/'))
46
48
  goto(location.hash.replace('#', ''));
47
- // Set the theme, whatever it is, and change it when the edition changes.
48
- $: setTheme(edition.getTheme() ?? BookishTheme);
49
49
  /** Given a theme, sets the appropriate CSS rules in the browser to apply the theme.*/
50
50
  function setTheme(theme) {
51
51
  // If the theme is being unset, make sure we've removed any overrding style declaration.
@@ -2,7 +2,6 @@ import { SvelteComponentTyped } from "svelte";
2
2
  import type EditionModel from '../../models/book/Edition';
3
3
  declare const __propDef: {
4
4
  props: {
5
- editable?: boolean | undefined;
6
5
  base?: string | undefined;
7
6
  edition: EditionModel;
8
7
  };
@@ -0,0 +1,99 @@
1
+ <script>import Loading from './Loading.svelte';
2
+ import Edition from './Edition.svelte';
3
+ import Feedback from '../app/Feedback.svelte';
4
+ import { getBook, getEdition, getUser, CHAPTERTEXT, getChapterText, } from './Contexts';
5
+ import { onDestroy } from 'svelte';
6
+ import { page } from '$app/stores';
7
+ import { listenToChapters, listenToEdition } from '../../models/CRUD';
8
+ export let write;
9
+ let auth = getUser();
10
+ let book = getBook();
11
+ let edition = getEdition();
12
+ let chapterText = getChapterText();
13
+ let editionUnsub = undefined;
14
+ let chaptersUnsub = undefined;
15
+ let error = undefined;
16
+ function unsub() {
17
+ // Unsubscribe to the old edition document if we were listening to one.
18
+ if (editionUnsub)
19
+ editionUnsub();
20
+ if (chaptersUnsub)
21
+ chaptersUnsub();
22
+ }
23
+ function listen() {
24
+ unsub();
25
+ error = undefined;
26
+ // If we no longer have a book, we can't get an edition.
27
+ if ($book === undefined) {
28
+ edition.set(undefined);
29
+ error = 'Unknown book.';
30
+ return;
31
+ }
32
+ else {
33
+ const editionNumber = $page.params.editionid;
34
+ const latestPublished = !write;
35
+ // Figure out which edition to load.
36
+ let editionID;
37
+ if (editionNumber === undefined) {
38
+ if (latestPublished) {
39
+ editionID = $book.getLatestPublishedEditionID();
40
+ if (editionID === undefined)
41
+ error = 'There is no published edition of this book.';
42
+ }
43
+ else {
44
+ editionID = $book.getLatestEditionID();
45
+ }
46
+ }
47
+ else {
48
+ editionID = $book.getEditionNumberID(parseFloat(editionNumber));
49
+ if (editionID === undefined)
50
+ error = `There is no ${editionNumber} edition of this book`;
51
+ }
52
+ // Listen to the doc for changes and listen to all of its chapters.
53
+ if (editionID !== undefined) {
54
+ editionUnsub = listenToEdition($book.ref.id, editionID, (ed) => {
55
+ if (ed) {
56
+ // Before setting the new edition, augmented it with the chapter text.
57
+ edition.set($chapterText
58
+ ? ed.withChapterText($chapterText)
59
+ : ed);
60
+ }
61
+ else {
62
+ edition.set(undefined);
63
+ error = 'Unable to load edition';
64
+ }
65
+ });
66
+ chaptersUnsub = listenToChapters($book.ref.id, editionID, (chapters) => {
67
+ // Update the chapter text store.
68
+ chapterText.set(chapters);
69
+ // Update the chapter text in the current edition.
70
+ if ($edition)
71
+ edition.set($edition.withChapterText(chapters));
72
+ });
73
+ }
74
+ }
75
+ }
76
+ // Whenever the book or page changes, change the listener.
77
+ $: if ($page && $auth && $auth.user !== null)
78
+ listen();
79
+ // When unmounted, unset the stores — no longer viewing a book.
80
+ onDestroy(() => {
81
+ edition.set(undefined);
82
+ unsub();
83
+ });
84
+ </script>
85
+
86
+ {#if $book === undefined || error}
87
+ <Feedback error>Unable to load edition.</Feedback>
88
+ {:else if $edition === undefined}
89
+ <Loading />
90
+ {:else}
91
+ <Edition
92
+ edition={$edition}
93
+ base={write
94
+ ? `/write/${$book.getRefID()}/${$edition.getEditionNumber()}/`
95
+ : `/${$book.getSubdomain() ?? $book.getRefID()}/`}
96
+ >
97
+ <slot />
98
+ </Edition>
99
+ {/if}
@@ -0,0 +1,18 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ write: boolean;
5
+ };
6
+ events: {
7
+ [evt: string]: CustomEvent<any>;
8
+ };
9
+ slots: {
10
+ default: {};
11
+ };
12
+ };
13
+ export type EditionLoaderProps = typeof __propDef.props;
14
+ export type EditionLoaderEvents = typeof __propDef.events;
15
+ export type EditionLoaderSlots = typeof __propDef.slots;
16
+ export default class EditionLoader extends SvelteComponentTyped<EditionLoaderProps, EditionLoaderEvents, EditionLoaderSlots> {
17
+ }
18
+ export {};