bookish-press 0.5.62 → 0.5.73
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.
- package/README.md +30 -1
- package/dist/components/app/Options.svelte +1 -0
- package/dist/components/app/Table.svelte +0 -1
- package/dist/components/chapter/CaptionedCode.svelte +21 -19
- package/dist/components/chapter/Figure.svelte +5 -3
- package/dist/components/chapter/Figure.svelte.d.ts +1 -0
- package/dist/components/chapter/Link.svelte +4 -0
- package/dist/components/chapter/Paragraph.svelte +18 -0
- package/dist/components/chapter/Table.svelte +1 -1
- package/dist/components/editor/BookishEditor.svelte +15 -6
- package/dist/components/editor/CaretView.svelte +8 -3
- package/dist/components/editor/Commands.js +22 -4
- package/dist/components/editor/LinkEditor.svelte +18 -5
- package/dist/components/editor/icons/header4.svg +7 -0
- package/dist/components/page/Analytics.svelte +19 -0
- package/dist/components/page/Analytics.svelte.d.ts +16 -0
- package/dist/components/page/BulkReferenceEditor.svelte +1 -1
- package/dist/components/page/Chapter.svelte +4 -5
- package/dist/components/page/DefinitionView.svelte +4 -3
- package/dist/components/page/Edition.svelte +2 -2
- package/dist/components/page/EditionLoader.svelte +9 -18
- package/dist/components/page/Editions.svelte +4 -3
- package/dist/components/page/Glossary.svelte +8 -2
- package/dist/components/page/Index.svelte +5 -2
- package/dist/components/page/Media.svelte +5 -2
- package/dist/components/page/Reference.svelte +24 -15
- package/dist/components/page/References.svelte +6 -2
- package/dist/components/page/Search.svelte +5 -3
- package/dist/components/page/SubdomainEditor.svelte +4 -3
- package/dist/components/page/TableOfContents.svelte +42 -11
- package/dist/components/page/TableOfContentsRow.svelte +3 -1
- package/dist/models/CRUD.d.ts +1 -2
- package/dist/models/CRUD.js +4 -6
- package/dist/models/book/BookMedia.js +8 -3
- package/dist/models/book/Chapter.js +2 -3
- package/dist/models/book/Edition.d.ts +10 -4
- package/dist/models/book/Edition.js +35 -34
- package/dist/models/chapter/ParagraphNode.js +4 -14
- package/dist/models/chapter/Parser.d.ts +1 -0
- package/dist/models/chapter/Parser.js +2 -1
- package/dist/models/chapter/TableNode.js +3 -1
- package/dist/util/mineReference.js +8 -6
- package/package.json +13 -10
|
@@ -231,7 +231,7 @@ function move(field, direction) {
|
|
|
231
231
|
</tr>
|
|
232
232
|
</Table>
|
|
233
233
|
{:else}
|
|
234
|
-
<!--
|
|
234
|
+
<!-- Otherwise, just render the reference. -->
|
|
235
235
|
<p class="reference">
|
|
236
236
|
{#if reference.authors}{reference.authors}{:else}<em>Authors</em>{/if}
|
|
237
237
|
{#if reference.year}({reference.year}){:else}<em>Year</em>{/if}. {#if reference.url === null || reference.url.length === 0}{#if reference.title}{reference.title}{:else}<em
|
|
@@ -247,26 +247,31 @@ function move(field, direction) {
|
|
|
247
247
|
</p>
|
|
248
248
|
{/if}
|
|
249
249
|
{#if edit && editable && !reference.short}
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
>
|
|
262
|
-
|
|
250
|
+
<div class="controls">
|
|
251
|
+
{#if editable}<Note>{reference.citationID}</Note>{/if}
|
|
252
|
+
<ConfirmButton
|
|
253
|
+
tooltip="delete reference {reference.title}"
|
|
254
|
+
confirm="delete reference"
|
|
255
|
+
command={() =>
|
|
256
|
+
$edition
|
|
257
|
+
? edition.set(
|
|
258
|
+
$edition.withoutReference(reference.citationID)
|
|
259
|
+
)
|
|
260
|
+
: undefined}>⨉</ConfirmButton
|
|
261
|
+
>
|
|
262
|
+
<Button
|
|
263
|
+
tooltip="stop editing reference"
|
|
264
|
+
command={() => (editing ? stopEditing() : startEditing())}
|
|
265
|
+
>{editing ? 'done' : 'edit'}</Button
|
|
266
|
+
>
|
|
267
|
+
</div>
|
|
263
268
|
{/if}
|
|
264
269
|
|
|
265
270
|
<style>
|
|
266
271
|
.reference {
|
|
267
272
|
line-height: var(--bookish-paragraph-line-height-tight);
|
|
268
273
|
margin-top: 0;
|
|
269
|
-
margin-
|
|
274
|
+
margin-top: var(--bookish-block-padding);
|
|
270
275
|
}
|
|
271
276
|
|
|
272
277
|
p {
|
|
@@ -289,4 +294,8 @@ function move(field, direction) {
|
|
|
289
294
|
td {
|
|
290
295
|
line-height: 1;
|
|
291
296
|
}
|
|
297
|
+
|
|
298
|
+
.controls {
|
|
299
|
+
margin-top: calc(var(--bookish-block-padding) / 2);
|
|
300
|
+
}
|
|
292
301
|
</style>
|
|
@@ -36,9 +36,13 @@ if (references !== null) {
|
|
|
36
36
|
<Outline
|
|
37
37
|
slot="outline"
|
|
38
38
|
previous={$edition.getPreviousChapterID(
|
|
39
|
-
ChapterIDs.ReferencesID
|
|
39
|
+
ChapterIDs.ReferencesID,
|
|
40
|
+
editable
|
|
41
|
+
)}
|
|
42
|
+
next={$edition.getNextChapterID(
|
|
43
|
+
ChapterIDs.ReferencesID,
|
|
44
|
+
editable
|
|
40
45
|
)}
|
|
41
|
-
next={$edition.getNextChapterID(ChapterIDs.ReferencesID)}
|
|
42
46
|
/>
|
|
43
47
|
</Header>
|
|
44
48
|
{#if editable}<BulkReferenceEditor />{/if}
|
|
@@ -5,7 +5,6 @@ import Link from '../Link.svelte';
|
|
|
5
5
|
import Chapter from '../../models/book/Chapter';
|
|
6
6
|
import ChapterIDs from '../../models/book/ChapterID';
|
|
7
7
|
import { getBase, getEdition, isEditionEditable } from './Contexts';
|
|
8
|
-
import TextInput from '../app/TextInput.svelte';
|
|
9
8
|
import ChapterTitle from './ChapterTitle.svelte';
|
|
10
9
|
import ChapterNumber from './ChapterNumber.svelte';
|
|
11
10
|
import PageHeader from './PageHeader.svelte';
|
|
@@ -76,8 +75,11 @@ $: {
|
|
|
76
75
|
>
|
|
77
76
|
<Outline
|
|
78
77
|
slot="outline"
|
|
79
|
-
previous={$edition.getPreviousChapterID(
|
|
80
|
-
|
|
78
|
+
previous={$edition.getPreviousChapterID(
|
|
79
|
+
ChapterIDs.SearchID,
|
|
80
|
+
false
|
|
81
|
+
)}
|
|
82
|
+
next={$edition.getNextChapterID(ChapterIDs.SearchID, false)}
|
|
81
83
|
/>
|
|
82
84
|
</Header>
|
|
83
85
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>import TextEditor from '../editor/TextEditor.svelte';
|
|
2
2
|
import { isSubdomainAvailable } from '../../models/CRUD';
|
|
3
|
-
import
|
|
3
|
+
import { getSubdomain } from '../../util/getSubdomain';
|
|
4
4
|
import { getBook } from './Contexts';
|
|
5
5
|
import Muted from './Muted.svelte';
|
|
6
6
|
let book = getBook();
|
|
@@ -22,9 +22,10 @@ const invalidNames = [
|
|
|
22
22
|
save={// Save the new domain
|
|
23
23
|
async (domain) => {
|
|
24
24
|
if ($book) {
|
|
25
|
-
const available = await isSubdomainAvailable(domain
|
|
25
|
+
const available = await isSubdomainAvailable(domain);
|
|
26
26
|
if (available) book.set($book.withSubdomain(domain));
|
|
27
|
-
else
|
|
27
|
+
else if ($book.getSubdomain() !== domain)
|
|
28
|
+
throw Error("Domain isn't available");
|
|
28
29
|
}
|
|
29
30
|
}}
|
|
30
31
|
placeholder="book name"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script>import Instructions from './Instructions.svelte';
|
|
2
|
-
import Parser from '../../models/chapter/Parser';
|
|
3
2
|
import TableOfContentsRow from './TableOfContentsRow.svelte';
|
|
4
3
|
import ChapterIDs from '../../models/book/ChapterID';
|
|
5
4
|
import Page from './Page.svelte';
|
|
@@ -95,7 +94,7 @@ onMount(() => {
|
|
|
95
94
|
<Outline
|
|
96
95
|
slot="outline"
|
|
97
96
|
previous={null}
|
|
98
|
-
next={$edition.getNextChapterID('')}
|
|
97
|
+
next={$edition.getNextChapterID('', editable)}
|
|
99
98
|
/>
|
|
100
99
|
<div slot="after">
|
|
101
100
|
<Authors
|
|
@@ -126,7 +125,7 @@ onMount(() => {
|
|
|
126
125
|
<Description />
|
|
127
126
|
|
|
128
127
|
<Instructions {editable}>
|
|
129
|
-
This will appear on the <Link to="
|
|
128
|
+
This will appear on the <Link to="/read">book browsing</Link>
|
|
130
129
|
page and in your table of contents. Write an informative description
|
|
131
130
|
of what your book is about.
|
|
132
131
|
</Instructions>
|
|
@@ -153,16 +152,20 @@ onMount(() => {
|
|
|
153
152
|
readingTime === undefined
|
|
154
153
|
? 'Forthcoming'
|
|
155
154
|
: readingTime < 5
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
? '<5 min read'
|
|
156
|
+
: readingTime < 60
|
|
157
|
+
? '~' +
|
|
158
|
+
Math.floor(readingTime / 5) * 5 +
|
|
159
|
+
' min read'
|
|
160
|
+
: '~' + Math.floor(readingTime / 60) + ' hour read'}
|
|
160
161
|
{@const section = chapter.getSection()}
|
|
161
162
|
{@const etc =
|
|
162
163
|
readingEstimate +
|
|
163
164
|
(!chapter.isForthcoming()
|
|
164
165
|
? getProgressDescription(
|
|
165
|
-
chapterID in progress
|
|
166
|
+
chapterID in progress
|
|
167
|
+
? progress[chapterID]
|
|
168
|
+
: null,
|
|
166
169
|
)
|
|
167
170
|
: '')}
|
|
168
171
|
|
|
@@ -184,7 +187,7 @@ onMount(() => {
|
|
|
184
187
|
setChapter(
|
|
185
188
|
edition,
|
|
186
189
|
chapter,
|
|
187
|
-
chapter.withSection(text)
|
|
190
|
+
chapter.withSection(text),
|
|
188
191
|
)}
|
|
189
192
|
/>
|
|
190
193
|
{:else if section}{section}{/if}
|
|
@@ -192,7 +195,7 @@ onMount(() => {
|
|
|
192
195
|
You can edit this chapter.
|
|
193
196
|
{/if}
|
|
194
197
|
</span>
|
|
195
|
-
<span slot="etc">
|
|
198
|
+
<span class="etc" slot="etc">
|
|
196
199
|
<Muted>
|
|
197
200
|
{#if editable}
|
|
198
201
|
<Toggle
|
|
@@ -201,7 +204,7 @@ onMount(() => {
|
|
|
201
204
|
setChapter(
|
|
202
205
|
edition,
|
|
203
206
|
chapter,
|
|
204
|
-
chapter.asForthcoming(on)
|
|
207
|
+
chapter.asForthcoming(on),
|
|
205
208
|
)}
|
|
206
209
|
>
|
|
207
210
|
{etc}
|
|
@@ -288,6 +291,28 @@ onMount(() => {
|
|
|
288
291
|
<Editions editors={false} />
|
|
289
292
|
{/if}
|
|
290
293
|
|
|
294
|
+
{#if editable && $edition}
|
|
295
|
+
<PageHeader id="editions">Analytics</PageHeader>
|
|
296
|
+
<Instructions {editable}
|
|
297
|
+
>Want to use Google Analytics to track traffic? Enter your tag
|
|
298
|
+
ID here.</Instructions
|
|
299
|
+
>
|
|
300
|
+
<TextEditor
|
|
301
|
+
label={'Google Analytics ag'}
|
|
302
|
+
text={$edition.gtagid ?? ''}
|
|
303
|
+
placeholder="gtag ID"
|
|
304
|
+
valid={() => undefined}
|
|
305
|
+
save={(text) =>
|
|
306
|
+
$edition
|
|
307
|
+
? edition.set(
|
|
308
|
+
$edition.withGTag(
|
|
309
|
+
text.length === 0 ? null : text,
|
|
310
|
+
),
|
|
311
|
+
)
|
|
312
|
+
: undefined}
|
|
313
|
+
/>
|
|
314
|
+
{/if}
|
|
315
|
+
|
|
291
316
|
{#if $book && (editable || isEditionPartiallyEditable())}
|
|
292
317
|
<PageHeader id="editors">Editors</PageHeader>
|
|
293
318
|
<Instructions {editable}>
|
|
@@ -310,3 +335,9 @@ onMount(() => {
|
|
|
310
335
|
{/if}
|
|
311
336
|
</Page>
|
|
312
337
|
{/if}
|
|
338
|
+
|
|
339
|
+
<style>
|
|
340
|
+
.etc {
|
|
341
|
+
white-space: nowrap;
|
|
342
|
+
}
|
|
343
|
+
</style>
|
|
@@ -82,7 +82,7 @@ $: chapterURL =
|
|
|
82
82
|
? edition.set(
|
|
83
83
|
$edition.withoutChapter(chapter.getID())
|
|
84
84
|
)
|
|
85
|
-
: undefined}
|
|
85
|
+
: undefined}>⨉</ConfirmButton
|
|
86
86
|
>
|
|
87
87
|
{/if}
|
|
88
88
|
</td>
|
|
@@ -102,6 +102,8 @@ $: chapterURL =
|
|
|
102
102
|
display: flex;
|
|
103
103
|
flex-direction: row;
|
|
104
104
|
flex-wrap: nowrap;
|
|
105
|
+
justify-content: end;
|
|
106
|
+
margin-inline-end: var(--app-chrome-padding);
|
|
105
107
|
gap: var(--app-chrome-padding);
|
|
106
108
|
}
|
|
107
109
|
|
package/dist/models/CRUD.d.ts
CHANGED
|
@@ -20,9 +20,8 @@ export declare function updateLock(edition: Edition, uid: string, content: strin
|
|
|
20
20
|
* returns a map of DocumentReferences for new chapters so they can be persisted client side.
|
|
21
21
|
*/
|
|
22
22
|
export declare function updateEdition(book: Book, previousEdition: Edition | undefined, newEdition: Edition): Promise<Map<string, DocumentReference>>;
|
|
23
|
-
export declare function deleteChapterText(ref: DocumentReference): Promise<void>;
|
|
24
23
|
export declare function updateBook(book: Book): Promise<void>;
|
|
25
|
-
export declare function isSubdomainAvailable(subdomain: string
|
|
24
|
+
export declare function isSubdomainAvailable(subdomain: string): Promise<boolean>;
|
|
26
25
|
export declare function getBookIDWithSubdomain(subdomain: string): Promise<string | null>;
|
|
27
26
|
export declare function publish(edition: Edition): Promise<string | {
|
|
28
27
|
url: string;
|
package/dist/models/CRUD.js
CHANGED
|
@@ -62,7 +62,8 @@ export function listenToChapters(bookID, editionID, react) {
|
|
|
62
62
|
const chapters = [];
|
|
63
63
|
// For each chapter in the book, if the doc exists and it's not a pending local write, send updated chapters to the front end.
|
|
64
64
|
docs.forEach((doc) => {
|
|
65
|
-
|
|
65
|
+
// We propagate pending writes to cycle back local edits to the client.
|
|
66
|
+
if (doc.exists())
|
|
66
67
|
// If the chapter text is somehow null, set it to an empty string.
|
|
67
68
|
// This accounts for previous defects that could lead to null.
|
|
68
69
|
chapters.push([doc.ref, doc.data().text ?? '']);
|
|
@@ -112,7 +113,7 @@ export async function createBook(userID) {
|
|
|
112
113
|
// Create a model to store it.
|
|
113
114
|
let book = Book.fromJSON(bookRef.id, newBookData);
|
|
114
115
|
// Make a new default edition with a single chapter with this user id.
|
|
115
|
-
let newEdition = new Edition(bookRef, undefined, [], 'Untitled', [], 1, '', null, {}, '', [], 'All rights reserved.', '', [], {}, {}, {}, {}, null, null, {}).withNewChapter();
|
|
116
|
+
let newEdition = new Edition(bookRef, undefined, [], 'Untitled', [], 1, '', null, {}, '', [], 'All rights reserved.', '', [], {}, {}, {}, {}, null, null, {}, null).withNewChapter();
|
|
116
117
|
// Add the edition to the editions collection in the database so we can get an edition ref.
|
|
117
118
|
const editionRef = await addDoc(collection(db, 'books', bookRef.id, 'editions'), newEdition.toObject());
|
|
118
119
|
// Remember the ref.
|
|
@@ -235,15 +236,12 @@ export async function updateEdition(book, previousEdition, newEdition) {
|
|
|
235
236
|
await setDoc(doc(db, editionPath(newEdition.bookRef.id, editionRef.id)), newEdition.toObject());
|
|
236
237
|
return newChapterRefs;
|
|
237
238
|
}
|
|
238
|
-
export async function deleteChapterText(ref) {
|
|
239
|
-
deleteDoc(ref);
|
|
240
|
-
}
|
|
241
239
|
export async function updateBook(book) {
|
|
242
240
|
if (!db)
|
|
243
241
|
throw Error("Can't update book, not connected to Firebase.");
|
|
244
242
|
await setDoc(doc(db, 'books', book.getID()), book.toJSON());
|
|
245
243
|
}
|
|
246
|
-
export async function isSubdomainAvailable(subdomain
|
|
244
|
+
export async function isSubdomainAvailable(subdomain) {
|
|
247
245
|
if (!db)
|
|
248
246
|
throw Error('Not connected to Firebase.');
|
|
249
247
|
return (await getBookIDWithSubdomain(subdomain)) === null;
|
|
@@ -85,17 +85,22 @@ export default class BookMedia {
|
|
|
85
85
|
// The canonical path format for Bookish images in the store is image/{bookid}/{imageid}
|
|
86
86
|
// where {bookid} is the Firestore document ID of the book being edited and {imageid} is just a random id.
|
|
87
87
|
const imageRef = ref(storage, `${this.getImagePath()}/${imageName}`);
|
|
88
|
-
const thumbnailRef = ref(storage, `${this.getImagePath()}/thumbnails/${imageName}`);
|
|
89
88
|
uploadBytes(imageRef, file)
|
|
90
89
|
.then((snapshot) => {
|
|
91
90
|
getDownloadURL(snapshot.ref)
|
|
92
91
|
// After we get the download URL for the uploaded image,
|
|
93
92
|
// try to get the thumbnail URL for the resized image on the server.
|
|
94
93
|
.then((url) => finishedHandler(this.stripTokenFromURL(url), this.makeThumbnailURL(this.stripTokenFromURL(url))))
|
|
95
|
-
.catch(() =>
|
|
94
|
+
.catch((error) => {
|
|
95
|
+
console.error(error);
|
|
96
|
+
errorHandler('Unable to get image URL.');
|
|
97
|
+
});
|
|
96
98
|
})
|
|
97
99
|
.then(() => this.cacheImages())
|
|
98
|
-
.catch(() =>
|
|
100
|
+
.catch((error) => {
|
|
101
|
+
console.error(error);
|
|
102
|
+
errorHandler('Unable to upload image.');
|
|
103
|
+
});
|
|
99
104
|
// This seems to be broken in Firebase right now. Switched above to a no-feedback approach.
|
|
100
105
|
// const uploadTask = uploadBytesResumable(imageRef, file);
|
|
101
106
|
// uploadTask.on("state_changed",
|
|
@@ -156,9 +156,8 @@ export default class Chapter {
|
|
|
156
156
|
}
|
|
157
157
|
getWordCount(edition) {
|
|
158
158
|
if (this._wordCount === undefined)
|
|
159
|
-
this._wordCount = this.getAST(edition)
|
|
160
|
-
|
|
161
|
-
.split(/\s+/).length;
|
|
159
|
+
this._wordCount = this.getAST(edition)?.toText().split(/\s+/)
|
|
160
|
+
.length;
|
|
162
161
|
return this._wordCount;
|
|
163
162
|
}
|
|
164
163
|
// Utility function
|
|
@@ -27,9 +27,13 @@ export type EditionSpecification = {
|
|
|
27
27
|
glossary: Record<string, Definition>;
|
|
28
28
|
theme: Theme | null;
|
|
29
29
|
base: string | null;
|
|
30
|
+
/** People who have edit access to this edition */
|
|
30
31
|
uids: string[];
|
|
32
|
+
/** All uids who have edit access to at least one chapter. Used in firestore rules to give permission to update book. */
|
|
31
33
|
chapteruids: string[];
|
|
32
34
|
active: Record<string, string>;
|
|
35
|
+
/** An optional Google Analytics tag ID */
|
|
36
|
+
gtagid: string | null;
|
|
33
37
|
};
|
|
34
38
|
export default class Edition {
|
|
35
39
|
readonly bookRef: DocumentReference;
|
|
@@ -53,7 +57,8 @@ export default class Edition {
|
|
|
53
57
|
readonly theme: Theme | null;
|
|
54
58
|
readonly base: string | null;
|
|
55
59
|
readonly active: Record<string, string>;
|
|
56
|
-
|
|
60
|
+
readonly gtagid: string | null;
|
|
61
|
+
constructor(bookRef: DocumentReference, editionRef: DocumentReference | undefined, uids: string[], title: string, authors: string[], number: number, summary: string, published: number | null, images: Record<string, string | null>, description: string, chapters: Chapter[], license: string, acknowledgements: string, tags: string[], sources: Record<string, string>, references: Record<string, string | string[]> | Record<string, FormatNode | Reference>, symbols: Record<string, string>, glossary: Record<string, Definition>, theme: Theme | null, base: string | null, active: Record<string, string>, gtagid: string | null);
|
|
57
62
|
static fromJSON(editionRef: DocumentReference | undefined, spec: EditionSpecification): Edition;
|
|
58
63
|
static parseReference(citationID: string, ref: string | string[], edition: Edition, short?: boolean): FormatNode | Reference;
|
|
59
64
|
toObject(): EditionSpecification;
|
|
@@ -78,7 +83,7 @@ export default class Edition {
|
|
|
78
83
|
getChapterPosition(chapterID: string): number | undefined;
|
|
79
84
|
getChapterCount(): number;
|
|
80
85
|
withChapters(chapters: Chapter[]): Edition;
|
|
81
|
-
withChapterText(
|
|
86
|
+
withChapterText(textByID: Map<string, string>): Edition;
|
|
82
87
|
withRevisedChapter(previous: Chapter, edited: Chapter): Edition;
|
|
83
88
|
withoutRefs(): Edition;
|
|
84
89
|
withIncrementedEditionNumber(): Edition;
|
|
@@ -123,6 +128,7 @@ export default class Edition {
|
|
|
123
128
|
hasLease(uid: string, content: string): boolean;
|
|
124
129
|
isLeased(contentID: string): boolean;
|
|
125
130
|
withLock(uid: string, content: string, lock: boolean): Edition;
|
|
131
|
+
withGTag(gtagid: string | null): Edition;
|
|
126
132
|
getBookReadingTime(): number | undefined;
|
|
127
133
|
getChapterNumber(chapterID: string): undefined;
|
|
128
134
|
getSource(sourceID: string): string | null;
|
|
@@ -130,8 +136,8 @@ export default class Edition {
|
|
|
130
136
|
getChapterSection(chapterID: string): string | undefined;
|
|
131
137
|
getFootnoteSymbol(number: number): string;
|
|
132
138
|
getBookIndex(): Record<string, Set<string>>;
|
|
133
|
-
getNextChapterID(chapterID: string): string | null;
|
|
134
|
-
getPreviousChapterID(chapterID: string): string | null;
|
|
139
|
+
getNextChapterID(chapterID: string, editable: boolean): string | null;
|
|
140
|
+
getPreviousChapterID(chapterID: string, editable: boolean): string | null;
|
|
135
141
|
getEmbeds(): {
|
|
136
142
|
embed: EmbedNode;
|
|
137
143
|
chapterID: string | undefined;
|
|
@@ -25,9 +25,10 @@ export default class Edition {
|
|
|
25
25
|
theme;
|
|
26
26
|
base;
|
|
27
27
|
active;
|
|
28
|
+
gtagid;
|
|
28
29
|
// Given an object with a valid specification and an object mapping chapter IDs to chapter text,
|
|
29
30
|
// construct an object representing a book.
|
|
30
|
-
constructor(bookRef, editionRef, uids, title, authors, number, summary, published, images, description, chapters, license, acknowledgements, tags, sources, references, symbols, glossary, theme, base, active) {
|
|
31
|
+
constructor(bookRef, editionRef, uids, title, authors, number, summary, published, images, description, chapters, license, acknowledgements, tags, sources, references, symbols, glossary, theme, base, active, gtagid) {
|
|
31
32
|
this.bookRef = bookRef;
|
|
32
33
|
this.editionRef = editionRef;
|
|
33
34
|
this.uids = uids;
|
|
@@ -55,11 +56,12 @@ export default class Edition {
|
|
|
55
56
|
this.theme = theme;
|
|
56
57
|
this.base = base;
|
|
57
58
|
this.active = active;
|
|
59
|
+
this.gtagid = gtagid;
|
|
58
60
|
}
|
|
59
61
|
static fromJSON(editionRef, spec) {
|
|
60
62
|
// Copy all of the specification metadata to fields.
|
|
61
63
|
// Choose suitable defaults if the spec is lacking a field.
|
|
62
|
-
return new Edition(spec.bookRef, editionRef, spec.uids, spec.title, spec.authors, spec.number, spec.summary, spec.published, spec.images, spec.description, spec.chapters.map((chap) => Chapter.fromJSON(chap)), spec.license, spec.acknowledgements, spec.tags ?? [], spec.sources ?? {}, spec.references ?? {}, spec.symbols ?? {}, spec.glossary ?? {}, spec.theme, spec.base, spec.active);
|
|
64
|
+
return new Edition(spec.bookRef, editionRef, spec.uids, spec.title, spec.authors, spec.number, spec.summary, spec.published, spec.images, spec.description, spec.chapters.map((chap) => Chapter.fromJSON(chap)), spec.license, spec.acknowledgements, spec.tags ?? [], spec.sources ?? {}, spec.references ?? {}, spec.symbols ?? {}, spec.glossary ?? {}, spec.theme, spec.base, spec.active, spec.gtagid);
|
|
63
65
|
}
|
|
64
66
|
static parseReference(citationID, ref, edition, short = false) {
|
|
65
67
|
if (typeof ref === 'string')
|
|
@@ -103,6 +105,7 @@ export default class Edition {
|
|
|
103
105
|
uids: this.uids.slice(),
|
|
104
106
|
chapteruids: this.getChapterUIDS(),
|
|
105
107
|
active: this.active,
|
|
108
|
+
gtagid: this.gtagid ?? null,
|
|
106
109
|
};
|
|
107
110
|
return editionJSON;
|
|
108
111
|
}
|
|
@@ -116,37 +119,37 @@ export default class Edition {
|
|
|
116
119
|
return Array.from(new Set(this.chapters.map((chapter) => chapter.uids).flat()));
|
|
117
120
|
}
|
|
118
121
|
withRef(editionRef) {
|
|
119
|
-
return new Edition(this.bookRef, editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
122
|
+
return new Edition(this.bookRef, editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
120
123
|
}
|
|
121
124
|
getTitle() {
|
|
122
125
|
return this.title;
|
|
123
126
|
}
|
|
124
127
|
withTitle(title) {
|
|
125
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
128
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
126
129
|
}
|
|
127
130
|
getDescription() {
|
|
128
131
|
return this.description;
|
|
129
132
|
}
|
|
130
133
|
setDescription(description) {
|
|
131
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
134
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
132
135
|
}
|
|
133
136
|
getAcknowledgements() {
|
|
134
137
|
return this.acknowledgements;
|
|
135
138
|
}
|
|
136
139
|
withAcknowledgements(acks) {
|
|
137
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, acks, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
140
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, acks, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
138
141
|
}
|
|
139
142
|
withSummary(summary) {
|
|
140
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
143
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
141
144
|
}
|
|
142
145
|
asPublished(published) {
|
|
143
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, published ? Date.now() : null, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
146
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, published ? Date.now() : null, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
144
147
|
}
|
|
145
148
|
getLicense() {
|
|
146
149
|
return this.license;
|
|
147
150
|
}
|
|
148
151
|
setLicense(license) {
|
|
149
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
152
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
150
153
|
}
|
|
151
154
|
getChapters() {
|
|
152
155
|
return this.chapters.slice();
|
|
@@ -172,22 +175,17 @@ export default class Edition {
|
|
|
172
175
|
return this.chapters.length;
|
|
173
176
|
}
|
|
174
177
|
withChapters(chapters) {
|
|
175
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
178
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
176
179
|
}
|
|
177
|
-
withChapterText(
|
|
178
|
-
// No need to make a new edition if no chapter was changed.
|
|
179
|
-
let modified = false;
|
|
180
|
+
withChapterText(textByID) {
|
|
180
181
|
const newChapters = this.chapters.map((chapter) => {
|
|
181
|
-
if (chapter.ref === undefined)
|
|
182
|
+
if (chapter.ref === undefined) {
|
|
182
183
|
return chapter;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (match)
|
|
187
|
-
modified = true;
|
|
188
|
-
return match ? chapter.withText(match[1]) : chapter;
|
|
184
|
+
}
|
|
185
|
+
const text = textByID.get(chapter.ref.id);
|
|
186
|
+
return text === undefined ? chapter : chapter.withText(text);
|
|
189
187
|
});
|
|
190
|
-
return
|
|
188
|
+
return this.withChapters(newChapters);
|
|
191
189
|
}
|
|
192
190
|
withRevisedChapter(previous, edited) {
|
|
193
191
|
const index = this.chapters.indexOf(previous);
|
|
@@ -203,7 +201,7 @@ export default class Edition {
|
|
|
203
201
|
return this.withRef(undefined).withChapters(this.chapters.map((chap) => chap.withoutRef()));
|
|
204
202
|
}
|
|
205
203
|
withIncrementedEditionNumber() {
|
|
206
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number + 1, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
204
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number + 1, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
207
205
|
}
|
|
208
206
|
withNewChapter() {
|
|
209
207
|
// Synthesize a chapter ID placeholder that doesn't overlap with existing chapter names
|
|
@@ -235,7 +233,7 @@ export default class Edition {
|
|
|
235
233
|
const temp = newChapters[index + increment];
|
|
236
234
|
newChapters[index + increment] = newChapters[index];
|
|
237
235
|
newChapters[index] = temp;
|
|
238
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, newChapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
236
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, newChapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
239
237
|
}
|
|
240
238
|
getSymbols() {
|
|
241
239
|
return this.symbols;
|
|
@@ -252,7 +250,7 @@ export default class Edition {
|
|
|
252
250
|
: undefined;
|
|
253
251
|
}
|
|
254
252
|
withReferences(newReferences) {
|
|
255
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, newReferences, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
253
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, newReferences, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
256
254
|
}
|
|
257
255
|
withNewReferences(references) {
|
|
258
256
|
const newReferences = Object.assign({}, this.references);
|
|
@@ -293,7 +291,7 @@ export default class Edition {
|
|
|
293
291
|
return id in this.glossary;
|
|
294
292
|
}
|
|
295
293
|
withGlossary(glossary) {
|
|
296
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, glossary, this.theme, this.base, this.active);
|
|
294
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, glossary, this.theme, this.base, this.active, this.gtagid);
|
|
297
295
|
}
|
|
298
296
|
withDefinition(id, phrase, definition, synonyms) {
|
|
299
297
|
const newGlossary = Object.assign({}, this.glossary);
|
|
@@ -330,7 +328,7 @@ export default class Edition {
|
|
|
330
328
|
return this.theme;
|
|
331
329
|
}
|
|
332
330
|
withTheme(theme) {
|
|
333
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, theme, this.base, this.active);
|
|
331
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, theme, this.base, this.active, this.gtagid);
|
|
334
332
|
}
|
|
335
333
|
withThemeValue(group, name, value) {
|
|
336
334
|
if (this.theme === null)
|
|
@@ -353,7 +351,7 @@ export default class Edition {
|
|
|
353
351
|
.join(', ');
|
|
354
352
|
}
|
|
355
353
|
withAuthors(authors) {
|
|
356
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
354
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
357
355
|
}
|
|
358
356
|
withAuthor(name) {
|
|
359
357
|
return this.withAuthors([...this.authors, name]);
|
|
@@ -376,7 +374,7 @@ export default class Edition {
|
|
|
376
374
|
]);
|
|
377
375
|
}
|
|
378
376
|
withEditors(uids) {
|
|
379
|
-
return new Edition(this.bookRef, this.editionRef, uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
377
|
+
return new Edition(this.bookRef, this.editionRef, uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
380
378
|
}
|
|
381
379
|
hasImage(id) {
|
|
382
380
|
return this.images[id] !== null;
|
|
@@ -387,7 +385,7 @@ export default class Edition {
|
|
|
387
385
|
withImage(id, embed) {
|
|
388
386
|
const newImages = Object.assign({}, this.images);
|
|
389
387
|
newImages[id] = embed;
|
|
390
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, newImages, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active);
|
|
388
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, newImages, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, this.gtagid);
|
|
391
389
|
}
|
|
392
390
|
isEditor(uid) {
|
|
393
391
|
return this.uids.includes(uid);
|
|
@@ -410,7 +408,10 @@ export default class Edition {
|
|
|
410
408
|
newActive[content] = uid;
|
|
411
409
|
else
|
|
412
410
|
delete newActive[content];
|
|
413
|
-
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, newActive);
|
|
411
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, newActive, this.gtagid);
|
|
412
|
+
}
|
|
413
|
+
withGTag(gtagid) {
|
|
414
|
+
return new Edition(this.bookRef, this.editionRef, this.uids, this.title, this.authors, this.number, this.summary, this.published, this.images, this.description, this.chapters, this.license, this.acknowledgements, this.tags, this.sources, this.references, this.symbols, this.glossary, this.theme, this.base, this.active, gtagid);
|
|
414
415
|
}
|
|
415
416
|
getBookReadingTime() {
|
|
416
417
|
return this.chapters
|
|
@@ -482,7 +483,7 @@ export default class Edition {
|
|
|
482
483
|
return bookIndex;
|
|
483
484
|
}
|
|
484
485
|
// Given the current chapter, find the available chapter after it.
|
|
485
|
-
getNextChapterID(chapterID) {
|
|
486
|
+
getNextChapterID(chapterID, editable) {
|
|
486
487
|
// Handle back matter chapters.
|
|
487
488
|
switch (chapterID) {
|
|
488
489
|
case ChapterIDs.ReferencesID:
|
|
@@ -508,7 +509,7 @@ export default class Edition {
|
|
|
508
509
|
if (chapter.getID() === chapterID)
|
|
509
510
|
after = true;
|
|
510
511
|
// If we're after the given chapter and it's not forthcoming.
|
|
511
|
-
else if (after && !chapter.isForthcoming())
|
|
512
|
+
else if (after && (!chapter.isForthcoming() || editable))
|
|
512
513
|
return chapter.getID();
|
|
513
514
|
}
|
|
514
515
|
// If the given ID was the last chapter, go to the next back matter chapter.
|
|
@@ -524,7 +525,7 @@ export default class Edition {
|
|
|
524
525
|
}
|
|
525
526
|
}
|
|
526
527
|
// Given a chapter id, find the available chapter before it.
|
|
527
|
-
getPreviousChapterID(chapterID) {
|
|
528
|
+
getPreviousChapterID(chapterID, editable) {
|
|
528
529
|
switch (chapterID) {
|
|
529
530
|
// Handle back matter chapters.
|
|
530
531
|
case ChapterIDs.ReferencesID:
|
|
@@ -556,7 +557,7 @@ export default class Edition {
|
|
|
556
557
|
if (chapter.getID() === chapterID)
|
|
557
558
|
before = true;
|
|
558
559
|
// If we're before the given chapter and it's not forthcoming.
|
|
559
|
-
else if (before && !chapter.isForthcoming())
|
|
560
|
+
else if (before && (!chapter.isForthcoming() || editable))
|
|
560
561
|
return chapter.getID();
|
|
561
562
|
}
|
|
562
563
|
// If the given ID was the last chapter, go to the next back matter chapter.
|