bookish-press 0.4.23 → 0.4.24

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 +9 -11
  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 +11 -2
  45. package/components/page/Contexts.js +85 -3
  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
@@ -0,0 +1,120 @@
1
+ <script>import Parser from '../../models/chapter/Parser';
2
+ import BookishEditor from '../editor/BookishEditor.svelte';
3
+ import Switch from '../editor/Switch.svelte';
4
+ import { getAuth, getEdition, getLeasee, isEditionEditable, lease, } from './Contexts';
5
+ import Format from '../chapter/Format.svelte';
6
+ import Note from '../editor/Note.svelte';
7
+ import Instructions from './Instructions.svelte';
8
+ import Link from '../Link.svelte';
9
+ import { PUBLIC_CONTEXT } from '$env/static/public';
10
+ import Button from '../app/Button.svelte';
11
+ import { publish } from '../../models/CRUD';
12
+ import Feedback from '../app/Feedback.svelte';
13
+ let auth = getAuth();
14
+ let edition = getEdition();
15
+ let editable = isEditionEditable();
16
+ let publishing = false;
17
+ let feedback = undefined;
18
+ async function requestPublish() {
19
+ if ($edition) {
20
+ publishing = true;
21
+ feedback = 'Assembling book...';
22
+ try {
23
+ feedback = await publish($edition);
24
+ }
25
+ catch (err) {
26
+ console.log(err);
27
+ feedback = 'Unable to publish the book :(';
28
+ }
29
+ finally {
30
+ publishing = false;
31
+ }
32
+ }
33
+ }
34
+ let publisher = undefined;
35
+ $: {
36
+ if ($auth && $auth.user)
37
+ updateUserClaims();
38
+ }
39
+ async function updateUserClaims() {
40
+ if ($auth === undefined || $auth.user === null)
41
+ return;
42
+ const token = await $auth.user.getIdTokenResult();
43
+ publisher =
44
+ 'publisher' in token.claims && token.claims.publisher === true;
45
+ }
46
+ </script>
47
+
48
+ {#if $edition && (publisher === true || PUBLIC_CONTEXT === 'local')}
49
+ <Instructions {editable}>
50
+ Ready to publish this edition? Write a summary then hit the switch.
51
+ </Instructions>
52
+
53
+ <div class="publisher">
54
+ <div class="summary">
55
+ <BookishEditor
56
+ text={$edition.summary}
57
+ parser={(text) =>
58
+ Parser.parseFormat(undefined, text).withTextIfEmpty()}
59
+ save={(newSummary) =>
60
+ $edition
61
+ ? edition.set(
62
+ $edition.withSummary(newSummary.toBookdown())
63
+ )
64
+ : undefined}
65
+ chapter={false}
66
+ component={Format}
67
+ placeholder="Summarize this edition's changes to publish."
68
+ leasee={getLeasee(auth, edition, 'summary')}
69
+ lease={(lock) => lease(auth, edition, 'summary', lock)}
70
+ />
71
+ </div>
72
+ <Switch
73
+ options={['hidden', 'published']}
74
+ enabled={$edition.published !== null || $edition.summary.length > 0}
75
+ value={$edition.published !== null ? 'published' : 'hidden'}
76
+ edit={(published) =>
77
+ $edition
78
+ ? edition.set(
79
+ $edition.asPublished(published === 'published')
80
+ )
81
+ : undefined}
82
+ />
83
+ <Button
84
+ tooltip="Publish the book online"
85
+ disabled={publishing}
86
+ command={() => requestPublish()}
87
+ >{publishing ? 'publishing...' : 'publish'}</Button
88
+ >
89
+ </div>
90
+ {#if feedback}
91
+ <Feedback error>{feedback}</Feedback>
92
+ {/if}
93
+ <div>
94
+ <Note
95
+ >{#if $edition.published}Last published {new Date(
96
+ $edition.published
97
+ ).toLocaleDateString('en-us')}{/if}</Note
98
+ >
99
+ </div>
100
+ {:else}
101
+ <Instructions {editable}>
102
+ You don't yet have publishing privileges. <Link
103
+ to="mailto:amy@amyjko.com">Write Amy</Link
104
+ > to schedule a chat about your book and ask for publishing privileges.
105
+ </Instructions>
106
+ {/if}
107
+
108
+ <style>
109
+ .publisher {
110
+ display: flex;
111
+ flex-direction: row;
112
+ flex-wrap: nowrap;
113
+ width: 100%;
114
+ align-items: baseline;
115
+ }
116
+
117
+ .summary {
118
+ flex: 1;
119
+ }
120
+ </style>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type PublishProps = typeof __propDef.props;
10
+ export type PublishEvents = typeof __propDef.events;
11
+ export type PublishSlots = typeof __propDef.slots;
12
+ export default class Publish extends SvelteComponentTyped<PublishProps, PublishEvents, PublishSlots> {
13
+ }
14
+ export {};
@@ -5,12 +5,11 @@ import Button from '../app/Button.svelte';
5
5
  import Table from '../app/Table.svelte';
6
6
  import ConfirmButton from '../editor/ConfirmButton.svelte';
7
7
  import TextEditor from '../editor/TextEditor.svelte';
8
- import { getEdition, isEditable } from './Contexts';
8
+ import { getEdition, isEditionEditable } from './Contexts';
9
9
  import Link from '../Link.svelte';
10
10
  import Note from '../editor/Note.svelte';
11
- import Parser from '../../models/chapter/Parser';
12
11
  export let reference;
13
- let editable = isEditable();
12
+ let editable = isEditionEditable();
14
13
  let edition = getEdition();
15
14
  let editing = false;
16
15
  function startEditing() {
@@ -94,8 +93,8 @@ function move(field, direction) {
94
93
  <td>
95
94
  <TextEditor
96
95
  text={reference.citationID}
97
- label={'Citation ID'}
98
- placeholder="Citation ID"
96
+ label={'reference ID'}
97
+ placeholder="reference ID"
99
98
  saveOnExit
100
99
  valid={(text) => {
101
100
  if (text.length === 0)
@@ -128,8 +127,8 @@ function move(field, direction) {
128
127
  <td>
129
128
  <TextEditor
130
129
  text={reference.authors}
131
- label={'Author list editor.'}
132
- placeholder="Authors"
130
+ label={'author list editor'}
131
+ placeholder="authors"
133
132
  valid={(text) => {
134
133
  if (text.length === 0) return "Authors can't be empty.";
135
134
  }}
@@ -151,8 +150,8 @@ function move(field, direction) {
151
150
  <td>
152
151
  <TextEditor
153
152
  text={reference.year}
154
- label={'Year editor.'}
155
- placeholder="Year"
153
+ label={'year editor'}
154
+ placeholder="year"
156
155
  valid={(text) => {
157
156
  if (text.length === 0) return "Year can't be empty";
158
157
  if (!/1?[0-9)[0-9]{2}/.test(text))
@@ -176,8 +175,8 @@ function move(field, direction) {
176
175
  <td>
177
176
  <TextEditor
178
177
  text={reference.title}
179
- label={'Title editor.'}
180
- placeholder="Title"
178
+ label={'title editor'}
179
+ placeholder="title"
181
180
  valid={(text) => {
182
181
  if (text.length === 0) return "Title can't be empty.";
183
182
  }}
@@ -200,8 +199,8 @@ function move(field, direction) {
200
199
  <em>
201
200
  <TextEditor
202
201
  text={reference.source}
203
- label={'Source editor.'}
204
- placeholder="Source"
202
+ label={'source editor'}
203
+ placeholder="source"
205
204
  valid={(text) => {
206
205
  if (text.length === 0)
207
206
  return "Source can't be empty";
@@ -225,8 +224,8 @@ function move(field, direction) {
225
224
  <td>
226
225
  <TextEditor
227
226
  text={reference.url}
228
- label={'URL editor.'}
229
- placeholder="URL"
227
+ label={'url editor'}
228
+ placeholder="url"
230
229
  valid={() => undefined}
231
230
  save={(text) =>
232
231
  $edition
@@ -245,8 +244,8 @@ function move(field, direction) {
245
244
  <td>
246
245
  <TextEditor
247
246
  text={reference.summary}
248
- label={'Summary editor.'}
249
- placeholder="Summary"
247
+ label={'summary editor'}
248
+ placeholder="summary"
250
249
  valid={() => undefined}
251
250
  save={(text) =>
252
251
  $edition
@@ -1,13 +1,13 @@
1
1
  <script>import Header from './Header.svelte';
2
2
  import Outline from './Outline.svelte';
3
3
  import Page from './Page.svelte';
4
- import Parser from '../../models/chapter/Parser';
5
4
  import ChapterIDs from '../../models/book/ChapterID';
6
5
  import BulkReferenceEditor from './BulkReferenceEditor.svelte';
7
6
  import PossibleReference from './PossibleReference.svelte';
8
- import { getEdition, isEditable } from './Contexts';
7
+ import { getAuth, getEdition, isEditionEditable } from './Contexts';
8
+ let auth = getAuth();
9
9
  let edition = getEdition();
10
- let editable = isEditable();
10
+ let editable = isEditionEditable();
11
11
  $: references = $edition?.hasReferences() ? $edition.getReferences() : null;
12
12
  // Otherwise, map references to a list with letter headers.
13
13
  if (references !== null) {
@@ -17,7 +17,12 @@ if (references !== null) {
17
17
  {#if $edition}
18
18
  <Page title={`${$edition.getTitle()} - References`}>
19
19
  <Header
20
+ editable={isEditionEditable() ||
21
+ ($auth !== undefined &&
22
+ $auth.user !== null &&
23
+ $edition.isChapterEditor($auth.user.uid))}
20
24
  label="References title"
25
+ id="references"
21
26
  getImage={() => $edition?.getImage(ChapterIDs.ReferencesID) ?? null}
22
27
  setImage={(embed) =>
23
28
  $edition
@@ -4,7 +4,7 @@ import Page from './Page.svelte';
4
4
  import Link from '../Link.svelte';
5
5
  import Chapter from '../../models/book/Chapter';
6
6
  import ChapterIDs from '../../models/book/ChapterID';
7
- import { getBase, getEdition } from './Contexts';
7
+ import { getBase, getEdition, isEditionEditable } from './Contexts';
8
8
  import TextInput from '../app/TextInput.svelte';
9
9
  import ChapterTitle from './ChapterTitle.svelte';
10
10
  import ChapterNumber from './ChapterNumber.svelte';
@@ -61,6 +61,7 @@ $: {
61
61
  {#if $edition}
62
62
  <Page title={`${$edition.getTitle()} - Search`}>
63
63
  <Header
64
+ editable={isEditionEditable()}
64
65
  label="Search title"
65
66
  getImage={() => $edition?.getImage(ChapterIDs.SearchID) ?? null}
66
67
  setImage={(embed) =>
@@ -70,6 +71,7 @@ $: {
70
71
  )
71
72
  : undefined}
72
73
  header="Search"
74
+ id="search"
73
75
  tags={$edition.getTags()}
74
76
  >
75
77
  <Outline
@@ -4,11 +4,11 @@ let status = getStatus();
4
4
  </script>
5
5
 
6
6
  <span
7
- class={`bookish-editor-status ${
7
+ class={`status ${
8
8
  $status === BookSaveStatus.Saving
9
- ? 'bookish-editor-status-saving'
9
+ ? 'saving'
10
10
  : $status === BookSaveStatus.Error
11
- ? 'bookish-editor-status-error'
11
+ ? 'error'
12
12
  : ''
13
13
  }`}
14
14
  >
@@ -19,39 +19,38 @@ let status = getStatus();
19
19
  : $status === BookSaveStatus.Saved
20
20
  ? '\u2713 Saved'
21
21
  : $status === BookSaveStatus.Error
22
- ? "\u2715 Couldn't save."
22
+ ? '\u2715 Not saved'
23
23
  : ''}
24
24
  </span>
25
25
 
26
26
  <style>
27
- .bookish-editor-status {
28
- color: var(--app-muted-color);
27
+ .status {
28
+ color: var(--app-font-color-inverted);
29
29
  font-family: var(--app-font);
30
30
  white-space: nowrap;
31
+ background-color: var(--app-interactive-color);
32
+ padding: calc(var(--app-chrome-padding) / 2);
33
+ border-radius: var(--app-chrome-roundedness);
31
34
  }
32
35
 
33
- .bookish-editor-status-error {
34
- color: var(--app-error-color);
36
+ .error {
37
+ background-color: var(--app-error-color);
35
38
  animation: failure 100ms infinite;
36
39
  }
37
40
 
38
- .bookish-editor-status-saving {
39
- animation: waiting 100ms infinite;
41
+ .saving {
42
+ animation: waiting 1s infinite;
40
43
  }
41
44
 
42
45
  @keyframes waiting {
43
- from,
44
- to {
45
- transform: scale(1, 1);
46
- }
47
- 25% {
48
- transform: scale(1, 1.2);
46
+ 0% {
47
+ transform: rotate(-3deg);
49
48
  }
50
49
  50% {
51
- transform: scale(1, 1);
50
+ transform: rotate(3deg);
52
51
  }
53
- 75% {
54
- transform: scale(1, 1.1);
52
+ 100% {
53
+ transform: rotate(-3deg);
55
54
  }
56
55
  }
57
56
  </style>
@@ -7,6 +7,7 @@ const invalidNames = [
7
7
  'email',
8
8
  'login',
9
9
  'confirm',
10
+ 'about',
10
11
  'read',
11
12
  'write',
12
13
  ];
@@ -25,7 +26,7 @@ const invalidNames = [
25
26
  else throw Error("Domain isn't available");
26
27
  }
27
28
  }}
28
- placeholder="URL name"
29
+ placeholder="book id"
29
30
  valid={(newDomain) =>
30
31
  // If not the empty string, must be a valid URL subdomain, and none of the existing routes.
31
32
  invalidNames.includes(newDomain) ||
@@ -33,7 +34,7 @@ const invalidNames = [
33
34
  !/^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/.test(
34
35
  newDomain
35
36
  ))
36
- ? `Book domains must be fewer than 63 characters, made of symbols a-z, A-Z, and 0-9, and not one of ${invalidNames.join(
37
+ ? `Book IDs must be fewer than 63 characters, made of symbols a-z, A-Z, and 0-9, and not one of ${invalidNames.join(
37
38
  ', '
38
39
  )}.`
39
40
  : undefined}
@@ -10,19 +10,23 @@ import SubdomainEditor from './SubdomainEditor.svelte';
10
10
  import Description from './Description.svelte';
11
11
  import Acknowledgements from './Acknowledgements.svelte';
12
12
  import License from './License.svelte';
13
- import Revisions from './Editions.svelte';
14
13
  import Authors from './Authors.svelte';
15
14
  import TextEditor from '../editor/TextEditor.svelte';
16
15
  import Toggle from '../editor/Toggle.svelte';
17
- import { getBase, getEdition, isEditable, setChapter } from './Contexts';
16
+ import { getAuth, getBase, getBook, getEdition, isBookEditable, isEditionEditable, isEditionPartiallyEditable, setChapter, } from './Contexts';
18
17
  import Muted from './Muted.svelte';
19
18
  import Button from '../app/Button.svelte';
20
19
  import PageHeader from './PageHeader.svelte';
21
20
  import Rows from './Rows.svelte';
22
21
  import PageParagraph from './PageParagraph.svelte';
22
+ import Note from '../editor/Note.svelte';
23
+ import Permissions from '../editor/Permissions.svelte';
24
+ import Publish from './Publish.svelte';
25
+ let auth = getAuth();
26
+ let book = getBook();
23
27
  let edition = getEdition();
24
28
  let base = getBase();
25
- let editable = isEditable();
29
+ let editable = isEditionEditable();
26
30
  function getProgressDescription(progress) {
27
31
  if (progress === null)
28
32
  return '';
@@ -59,9 +63,10 @@ function addChapter() {
59
63
  }
60
64
  </script>
61
65
 
62
- {#if $edition}
66
+ {#if $edition && $book}
63
67
  <Page title={$edition.getTitle()}>
64
68
  <Header
69
+ editable={isEditionEditable()}
65
70
  label="Book title"
66
71
  getImage={() => $edition?.getImage('cover') ?? null}
67
72
  setImage={(embed) =>
@@ -69,6 +74,7 @@ function addChapter() {
69
74
  ? edition.set($edition.withImage('cover', embed))
70
75
  : undefined}
71
76
  header={title}
77
+ id="toc"
72
78
  {subtitle}
73
79
  tags={$edition.getTags()}
74
80
  save={(text) =>
@@ -84,36 +90,44 @@ function addChapter() {
84
90
  previous={null}
85
91
  next={$edition.getNextChapterID('')}
86
92
  />
87
- <Authors
88
- slot="after"
89
- authors={$edition.getAuthors()}
90
- add={() =>
91
- $edition ? edition.set($edition.withAuthor('')) : undefined}
92
- edit={(index, text) =>
93
- $edition
94
- ? edition.set($edition.withAuthorName(index, text))
95
- : undefined}
96
- remove={(index) =>
97
- $edition
98
- ? edition.set($edition.withoutAuthor(index))
99
- : undefined}
100
- />
93
+ <div slot="after">
94
+ <Authors
95
+ editable={isBookEditable()}
96
+ authors={$edition.getAuthors()}
97
+ add={() =>
98
+ $edition
99
+ ? edition.set($edition.withAuthor(''))
100
+ : undefined}
101
+ edit={(index, text) =>
102
+ $edition
103
+ ? edition.set($edition.withAuthorName(index, text))
104
+ : undefined}
105
+ remove={(index) =>
106
+ $edition
107
+ ? edition.set($edition.withoutAuthor(index))
108
+ : undefined}
109
+ />
110
+ {#if $book.getPublishedEditionCount() > 1}
111
+ <Note>{$edition.getEditionLabel()} edition</Note>
112
+ {/if}
113
+ </div>
101
114
  </Header>
102
115
 
103
- <Instructions>
104
- Above you can edit your book's authors, title, and cover image.
116
+ <Instructions {editable}>
117
+ Edit this book's cover image, ID, title, and authors above. The book
118
+ ID appears in the book's URL (e.g., bookish.press/<em>bookid</em>).
105
119
  </Instructions>
106
120
 
107
121
  <Description />
108
122
 
109
- <Instructions>
123
+ <Instructions {editable}>
110
124
  This will appear on the <Link to={$base + 'read'}
111
125
  >book browsing</Link
112
126
  > page and in your table of contents. Write an informative description
113
127
  of what your book is about.
114
128
  </Instructions>
115
129
 
116
- <Instructions>
130
+ <Instructions {editable}>
117
131
  Add, remove, and reorder chapters here. You can add optional book
118
132
  sections to each chapter, toggle chapters as numbered/unnumbered or
119
133
  published/forthcoming. Click the title to edit the chapter.
@@ -160,7 +174,7 @@ function addChapter() {
160
174
  <TextEditor
161
175
  label={'Chapter section editor'}
162
176
  text={section ? section : ''}
163
- placeholder="Section"
177
+ placeholder="section"
164
178
  valid={() => undefined}
165
179
  save={(text) =>
166
180
  setChapter(
@@ -170,6 +184,9 @@ function addChapter() {
170
184
  )}
171
185
  />
172
186
  {:else if section}{section}{/if}
187
+ {#if !editable && $auth !== undefined && $auth.user !== null && chapter.uids.includes($auth.user.uid)}
188
+ You can edit this chapter.
189
+ {/if}
173
190
  </span>
174
191
  <span slot="etc">
175
192
  <Muted>
@@ -233,9 +250,9 @@ function addChapter() {
233
250
 
234
251
  <PageHeader id="print">Print</PageHeader>
235
252
 
236
- <Instructions>
237
- This offers a way for readers to print the entire book as a single
238
- page.
253
+ <Instructions {editable}>
254
+ This link will generate the entire book on a single page, suitable
255
+ for printing.
239
256
  </Instructions>
240
257
 
241
258
  <PageParagraph>
@@ -246,9 +263,8 @@ function addChapter() {
246
263
 
247
264
  <PageHeader id="citation">Citation</PageHeader>
248
265
 
249
- <Instructions>
250
- This citation is dynamically created from the current authors,
251
- title, and date.
266
+ <Instructions {editable}>
267
+ This citation is created from the current authors, title, and date.
252
268
  </Instructions>
253
269
 
254
270
  <!-- Book citation -->
@@ -264,6 +280,30 @@ function addChapter() {
264
280
  <em>retrieved {new Date().toLocaleDateString('en-US')}</em>.
265
281
  </PageParagraph>
266
282
 
267
- <Revisions />
283
+ {#if editable}
284
+ <PageHeader id="publish">Publish</PageHeader>
285
+ <Publish />
286
+ {/if}
287
+
288
+ {#if editable || isEditionPartiallyEditable()}
289
+ <PageHeader>Editors</PageHeader>
290
+ <Instructions {editable}>
291
+ These authors can edit this edition and its chapters. To give
292
+ book-level permissions, edit the book editors on the <Link
293
+ to="/write/{$book.ref.id}/editions">editions</Link
294
+ >
295
+ page. To give chapter-level permissions, navigate to the chapter.
296
+ </Instructions>
297
+ <Permissions
298
+ uids={$edition.uids}
299
+ inheriteduids={$book.uids}
300
+ atleastone={false}
301
+ writable={editable}
302
+ change={(uids) =>
303
+ $edition
304
+ ? edition.set($edition.withEditors(uids))
305
+ : undefined}
306
+ />
307
+ {/if}
268
308
  </Page>
269
309
  {/if}
@@ -1,7 +1,7 @@
1
1
  <script>import TableOfContentsImage from './TableOfContentsImage.svelte';
2
2
  import Toggle from '../editor/Toggle.svelte';
3
3
  import ConfirmButton from '../editor/ConfirmButton.svelte';
4
- import { getBase, getEdition, isEditable, setChapter } from './Contexts';
4
+ import { getBase, getEdition, isEditionEditable, setChapter, } from './Contexts';
5
5
  import Muted from './Muted.svelte';
6
6
  import ChapterNumber from './ChapterNumber.svelte';
7
7
  import Button from '../app/Button.svelte';
@@ -13,7 +13,7 @@ export let number = undefined;
13
13
  export let forthcoming = false;
14
14
  let edition = getEdition();
15
15
  let base = getBase();
16
- let editable = isEditable();
16
+ let editable = isEditionEditable();
17
17
  function moveUp() {
18
18
  if ($edition && chapter)
19
19
  edition.set($edition.withMovedChapter(chapter.getID(), -1));
@@ -63,13 +63,13 @@ function moveDown() {
63
63
  <td class="controls">
64
64
  {#if chapter}
65
65
  <Button
66
- tooltip="move chapter up"
66
+ tooltip="move chapter {title} up"
67
67
  disabled={$edition.getChapterPosition(chapter.getID()) ===
68
68
  0}
69
69
  command={moveUp}>{'↑'}</Button
70
70
  >
71
71
  <Button
72
- tooltip="move chapter down"
72
+ tooltip="move chapter {title} down"
73
73
  disabled={$edition.getChapterPosition(chapter.getID()) ===
74
74
  $edition.getChapterCount() - 1}
75
75
  command={moveDown}>{'↓'}</Button
@@ -6,7 +6,7 @@ import ConfirmButton from '../editor/ConfirmButton.svelte';
6
6
  import Instructions from './Instructions.svelte';
7
7
  import ThemeSetEditor from './ThemeSetEditor.svelte';
8
8
  import ThemeEditorPreview from './ThemeEditorPreview.svelte';
9
- import { getEdition } from './Contexts';
9
+ import { getEdition, isEditionEditable } from './Contexts';
10
10
  import TextEditor from '../editor/TextEditor.svelte';
11
11
  import Button from '../app/Button.svelte';
12
12
  import Switch from '../editor/Switch.svelte';
@@ -20,6 +20,7 @@ const themes = {
20
20
  Sketchy: SketchyTheme,
21
21
  Critical: CriticalTheme,
22
22
  };
23
+ let editable = isEditionEditable();
23
24
  let edition = getEdition();
24
25
  $: theme = $edition ? $edition.getTheme() : null;
25
26
  $: isDefault =
@@ -35,7 +36,9 @@ function getEmptyGroup(group) {
35
36
  {#if theme && $edition}
36
37
  <Page title={`${$edition?.getTitle()} - Theme`}>
37
38
  <Header
39
+ editable={isEditionEditable()}
38
40
  label="Theme"
41
+ id="theme"
39
42
  getImage={() => null}
40
43
  setImage={() => undefined}
41
44
  header="Theme"
@@ -43,7 +46,7 @@ function getEmptyGroup(group) {
43
46
  <Outline slot="outline" previous={null} next={null} />
44
47
  </Header>
45
48
 
46
- <Instructions>
49
+ <Instructions {editable}>
47
50
  This is the theme editor. You can use it to choose from existing
48
51
  themes or create a custom theme for your book's appearance. To use
49
52
  it, you'll need to know a bit about how to format CSS <Link
@@ -83,7 +86,7 @@ function getEmptyGroup(group) {
83
86
  >
84
87
  {:else}
85
88
  <ConfirmButton
86
- tooltip="Change the theme to the default Bookish theme."
89
+ tooltip="change theme to the default"
87
90
  commandLabel="- theme"
88
91
  confirmLabel="delete your theme?"
89
92
  command={() =>
@@ -139,7 +142,7 @@ function getEmptyGroup(group) {
139
142
  </td>
140
143
  <td style="text-align:right;">
141
144
  <Button
142
- tooltip="Add a CSS import"
145
+ tooltip="add CSS URL import"
143
146
  command={() => {
144
147
  if (theme === null) return;
145
148
  if (theme.imports === undefined)
@@ -171,7 +174,7 @@ function getEmptyGroup(group) {
171
174
  </td>
172
175
  <td style="text-align: right">
173
176
  <Button
174
- tooltip="Remove this CSS import"
177
+ tooltip="Remove CSS URL import"
175
178
  command={() => {
176
179
  theme?.imports?.splice(index, 1);
177
180
  if ($edition)