bookish-press 0.5.26 → 0.5.32
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/dist/components/BookPreview.svelte +1 -0
- package/dist/components/chapter/ExtendedPrism.d.ts +1 -1
- package/dist/components/chapter/List.svelte +16 -33
- package/dist/components/controls/DarkToggle.svelte +1 -1
- package/dist/components/editor/Commands.js +14 -14
- package/dist/components/editor/Permissions.svelte +2 -0
- package/dist/components/editor/Toggle.svelte +1 -1
- package/dist/components/page/Chapter.svelte +1 -1
- package/dist/components/page/EditionLoader.svelte +12 -3
- package/dist/components/page/Editions.svelte +1 -1
- package/dist/components/page/Outline.svelte +1 -1
- package/dist/components/page/TableOfContents.svelte +2 -2
- package/dist/models/CRUD.d.ts +1 -0
- package/dist/models/CRUD.js +17 -4
- package/dist/models/book/Chapter.d.ts +9 -7
- package/dist/models/book/Chapter.js +16 -6
- package/dist/models/book/Edition.d.ts +1 -0
- package/dist/models/book/Edition.js +12 -5
- package/dist/models/chapter/DefinitionNode.js +1 -1
- package/dist/models/chapter/FormatNode.js +4 -1
- package/dist/models/chapter/InlineCodeNode.js +1 -1
- package/dist/models/chapter/LinkNode.js +1 -1
- package/dist/models/chapter/ListNode.js +1 -1
- package/dist/models/chapter/Parser.js +3 -6
- package/package.json +16 -16
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export default Prism;
|
|
2
|
-
import Prism from
|
|
2
|
+
import Prism from 'prismjs';
|
|
@@ -24,50 +24,33 @@ export let node;
|
|
|
24
24
|
line-height: var(--bookish-paragraph-line-height-tight);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
counter-reset: item;
|
|
27
|
+
li {
|
|
28
|
+
margin-bottom: calc(0.5 * var(--bookish-paragraph-spacing));
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
/* Lists are decimal numbered */
|
|
32
|
+
ol {
|
|
33
|
+
list-style: decimal;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/* First level is decimal */
|
|
37
|
+
ol li {
|
|
38
|
+
counter-increment: list-item;
|
|
39
|
+
list-style-type: decimal;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
/* Second level is lowercase alpha */
|
|
43
|
+
ol ol li {
|
|
44
|
+
list-style-type: lower-alpha;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
/* Third level is uppercase alpha */
|
|
48
|
+
ol ol ol li {
|
|
49
|
+
list-style-type: upper-alpha;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
ol
|
|
49
|
-
ul > :global(ol > li::before),
|
|
50
|
-
ul > :global(ul > ol > li::before) {
|
|
51
|
-
content: counter(item) '. ';
|
|
52
|
+
ol li::marker {
|
|
52
53
|
color: var(--bookish-bullet-color);
|
|
53
|
-
display: inline-block;
|
|
54
|
-
width: 1em;
|
|
55
|
-
margin-left: -1.5rem;
|
|
56
|
-
margin-right: 0.5rem;
|
|
57
|
-
text-align: right;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
ol > :global(ol > li::before),
|
|
61
|
-
ul > :global(ol > ol > li::before) {
|
|
62
|
-
content: counter(item, lower-alpha) '. ';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
ol > :global(ol > ol > li:before) {
|
|
66
|
-
content: counter(item, upper-alpha) '. ';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
li {
|
|
70
|
-
margin-bottom: calc(0.5 * var(--bookish-paragraph-spacing));
|
|
71
54
|
}
|
|
72
55
|
|
|
73
56
|
ul li {
|
|
@@ -13,7 +13,7 @@ function change() {
|
|
|
13
13
|
class="dark-toggle"
|
|
14
14
|
role="button"
|
|
15
15
|
aria-label={dark === true ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
16
|
-
|
|
16
|
+
tabindex="0"
|
|
17
17
|
on:click={change}
|
|
18
18
|
on:keydown={(event) => {
|
|
19
19
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
@@ -835,19 +835,6 @@ const commands = [
|
|
|
835
835
|
return rootWithNode(context, context.blocks, edit.root, () => edit.range.start);
|
|
836
836
|
},
|
|
837
837
|
},
|
|
838
|
-
{
|
|
839
|
-
icon: IndentIcon,
|
|
840
|
-
description: 'indent list item',
|
|
841
|
-
category: 'list',
|
|
842
|
-
mutates: true,
|
|
843
|
-
control: false,
|
|
844
|
-
alt: false,
|
|
845
|
-
shift: false,
|
|
846
|
-
key: 'Tab',
|
|
847
|
-
visible: (context) => context.list !== undefined,
|
|
848
|
-
active: (context) => context.list !== undefined,
|
|
849
|
-
handler: (context) => rootWithNode(context, context.blocks, context.blocks?.withListsIndented(context.range, true)),
|
|
850
|
-
},
|
|
851
838
|
{
|
|
852
839
|
icon: UnindentIcon,
|
|
853
840
|
description: 'unindent list item',
|
|
@@ -862,6 +849,19 @@ const commands = [
|
|
|
862
849
|
context.list.isInside(context.root, ListNode),
|
|
863
850
|
handler: (context) => rootWithNode(context, context.blocks, context.blocks?.withListsIndented(context.range, false)),
|
|
864
851
|
},
|
|
852
|
+
{
|
|
853
|
+
icon: IndentIcon,
|
|
854
|
+
description: 'indent list item',
|
|
855
|
+
category: 'list',
|
|
856
|
+
mutates: true,
|
|
857
|
+
control: false,
|
|
858
|
+
alt: false,
|
|
859
|
+
shift: false,
|
|
860
|
+
key: 'Tab',
|
|
861
|
+
visible: (context) => context.list !== undefined,
|
|
862
|
+
active: (context) => context.list !== undefined,
|
|
863
|
+
handler: (context) => rootWithNode(context, context.blocks, context.blocks?.withListsIndented(context.range, true)),
|
|
864
|
+
},
|
|
865
865
|
{
|
|
866
866
|
icon: ClearIcon,
|
|
867
867
|
description: 'clear formatting',
|
|
@@ -1222,7 +1222,7 @@ const commands = [
|
|
|
1222
1222
|
const newTable = new TableNode(newRows, '|', new FormatNode('', [new TextNode()]));
|
|
1223
1223
|
return rootWithNode(context, context.blocks, context.blocks.withBlockInsertedBefore(context.paragraph, newTable), () => {
|
|
1224
1224
|
return {
|
|
1225
|
-
node: newTable.getRows()[0][0].
|
|
1225
|
+
node: newTable.getRows()[0][0].getFirstTextNode(),
|
|
1226
1226
|
index: 0,
|
|
1227
1227
|
};
|
|
1228
1228
|
});
|
|
@@ -24,7 +24,7 @@ function toggle() {
|
|
|
24
24
|
class={`bookish-app-interactive ${
|
|
25
25
|
saving === Status.Saving ? ' bookish-text-editor-saving' : ''
|
|
26
26
|
} ${saving === Status.Error ? ' bookish-text-editor-error' : ''}`}
|
|
27
|
-
|
|
27
|
+
tabindex="0"
|
|
28
28
|
role="switch"
|
|
29
29
|
aria-checked={on}
|
|
30
30
|
on:keydown={(event) =>
|
|
@@ -361,7 +361,7 @@ let editable = isBookEditable() ||
|
|
|
361
361
|
{/if}
|
|
362
362
|
|
|
363
363
|
{#if editable || isEditionPartiallyEditable()}
|
|
364
|
-
<PageHeader>Editors</PageHeader>
|
|
364
|
+
<PageHeader id="editors">Editors</PageHeader>
|
|
365
365
|
<Instructions {editable}
|
|
366
366
|
>These authors can edit this chapter. Add an email here to
|
|
367
367
|
provide chapter-level edit permissions.</Instructions
|
|
@@ -4,7 +4,7 @@ import Feedback from '../app/Feedback.svelte';
|
|
|
4
4
|
import { getBook, getEdition, getUser, getChapterText, } from './Contexts';
|
|
5
5
|
import { onDestroy } from 'svelte';
|
|
6
6
|
import { page } from '$app/stores';
|
|
7
|
-
import { listenToChapters, listenToEdition } from '../../models/CRUD';
|
|
7
|
+
import { deleteChapterText, listenToChapters, listenToEdition, } from '../../models/CRUD';
|
|
8
8
|
export let write;
|
|
9
9
|
let auth = getUser();
|
|
10
10
|
let book = getBook();
|
|
@@ -67,8 +67,17 @@ function listen() {
|
|
|
67
67
|
// Update the chapter text store.
|
|
68
68
|
chapterText.set(chapters);
|
|
69
69
|
// Update the chapter text in the current edition.
|
|
70
|
-
if ($edition)
|
|
71
|
-
|
|
70
|
+
if ($edition) {
|
|
71
|
+
const newEdition = $edition.withChapterText(chapters);
|
|
72
|
+
if (newEdition !== $edition)
|
|
73
|
+
edition.set(newEdition);
|
|
74
|
+
// Get all the chapters in the updated chapter list and
|
|
75
|
+
// delete the ones not referenced by the edition.
|
|
76
|
+
chapters
|
|
77
|
+
.filter(([ref]) => $edition &&
|
|
78
|
+
!$edition.hasChapterRefID(ref.id))
|
|
79
|
+
.forEach(([ref]) => deleteChapterText(ref));
|
|
80
|
+
}
|
|
72
81
|
});
|
|
73
82
|
}
|
|
74
83
|
}
|
|
@@ -129,7 +129,7 @@ let expandLabel = '\u2630';
|
|
|
129
129
|
aria-label={expanded
|
|
130
130
|
? 'Collapse navigation menu'
|
|
131
131
|
: 'Expand navigation menu'}
|
|
132
|
-
|
|
132
|
+
tabindex="0"
|
|
133
133
|
on:click={headers.length > 0 ? toggleExpanded : undefined}
|
|
134
134
|
on:keydown={(event) => {
|
|
135
135
|
if (event.key === 'Enter' || event.key === ' ')
|
|
@@ -138,7 +138,7 @@ onMount(() => {
|
|
|
138
138
|
</Instructions>
|
|
139
139
|
|
|
140
140
|
<PageHeader id="chapters"
|
|
141
|
-
>Chapters
|
|
141
|
+
>Chapters{#if editable} <Button
|
|
142
142
|
tooltip="add empty chapter"
|
|
143
143
|
disabled={waitingForChapter}
|
|
144
144
|
command={addChapter}>+ chapter</Button
|
|
@@ -289,7 +289,7 @@ onMount(() => {
|
|
|
289
289
|
{/if}
|
|
290
290
|
|
|
291
291
|
{#if $book && (editable || isEditionPartiallyEditable())}
|
|
292
|
-
<PageHeader>Editors</PageHeader>
|
|
292
|
+
<PageHeader id="editors">Editors</PageHeader>
|
|
293
293
|
<Instructions {editable}>
|
|
294
294
|
These authors can edit this edition and its chapters. To give
|
|
295
295
|
book-level permissions, edit the book editors on the <Link
|
package/dist/models/CRUD.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ 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>;
|
|
23
24
|
export declare function updateBook(book: Book): Promise<void>;
|
|
24
25
|
export declare function isSubdomainAvailable(subdomain: string, book: Book): Promise<boolean>;
|
|
25
26
|
export declare function getBookIDWithSubdomain(subdomain: string): Promise<string | null>;
|
package/dist/models/CRUD.js
CHANGED
|
@@ -60,8 +60,12 @@ export function listenToChapters(bookID, editionID, react) {
|
|
|
60
60
|
throw NO_DATABASE_CONNECTION;
|
|
61
61
|
return onSnapshot(collection(db, 'books', bookID, 'editions', editionID, 'chapters'), (docs) => {
|
|
62
62
|
const chapters = [];
|
|
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.
|
|
63
64
|
docs.forEach((doc) => {
|
|
64
|
-
|
|
65
|
+
if (doc.exists() && !doc.metadata.hasPendingWrites)
|
|
66
|
+
// If the chapter text is somehow null, set it to an empty string.
|
|
67
|
+
// This accounts for previous defects that could lead to null.
|
|
68
|
+
chapters.push([doc.ref, doc.data().text ?? '']);
|
|
65
69
|
});
|
|
66
70
|
react(chapters);
|
|
67
71
|
});
|
|
@@ -182,7 +186,9 @@ export async function updateEdition(book, previousEdition, newEdition) {
|
|
|
182
186
|
for (const newChapter of newEdition.chapters) {
|
|
183
187
|
if (newChapter.ref === undefined) {
|
|
184
188
|
// Make the new chapter's text.
|
|
185
|
-
const newChapterRef = await addDoc(collection(db, editionPath(newEdition.bookRef.id, editionRef.id), 'chapters'),
|
|
189
|
+
const newChapterRef = await addDoc(collection(db, editionPath(newEdition.bookRef.id, editionRef.id), 'chapters'),
|
|
190
|
+
// If the new chapter has null text, set it to empty string
|
|
191
|
+
{ text: newChapter.text ?? '' });
|
|
186
192
|
// Add the ref of the new chapter to the revised chapter.
|
|
187
193
|
newEdition = newEdition.withRevisedChapter(newChapter, newChapter.withRef(newChapterRef));
|
|
188
194
|
// Map the chapter ref to it's id.
|
|
@@ -193,8 +199,12 @@ export async function updateEdition(book, previousEdition, newEdition) {
|
|
|
193
199
|
const previousChapter = previousEdition === undefined
|
|
194
200
|
? undefined
|
|
195
201
|
: previousEdition.getChapterByRef(newChapter.ref);
|
|
196
|
-
if (
|
|
197
|
-
|
|
202
|
+
if (
|
|
203
|
+
// A null chapter text means that we haven't loaded it yet from the database, so we
|
|
204
|
+
// only set it if it's not null.
|
|
205
|
+
newChapter.text !== null &&
|
|
206
|
+
(previousChapter === undefined ||
|
|
207
|
+
newChapter.text !== previousChapter.text)) {
|
|
198
208
|
await setDoc(doc(db, editionPath(newEdition.bookRef.id, editionRef.id), 'chapters', newChapter.ref.id), { text: newChapter.text });
|
|
199
209
|
}
|
|
200
210
|
}
|
|
@@ -225,6 +235,9 @@ export async function updateEdition(book, previousEdition, newEdition) {
|
|
|
225
235
|
await setDoc(doc(db, editionPath(newEdition.bookRef.id, editionRef.id)), newEdition.toObject());
|
|
226
236
|
return newChapterRefs;
|
|
227
237
|
}
|
|
238
|
+
export async function deleteChapterText(ref) {
|
|
239
|
+
deleteDoc(ref);
|
|
240
|
+
}
|
|
228
241
|
export async function updateBook(book) {
|
|
229
242
|
if (!db)
|
|
230
243
|
throw Error("Can't update book, not connected to Firebase.");
|
|
@@ -10,7 +10,8 @@ export type ChapterSpecification = {
|
|
|
10
10
|
numbered: boolean;
|
|
11
11
|
forthcoming: boolean;
|
|
12
12
|
section?: string;
|
|
13
|
-
|
|
13
|
+
/** A string means that we know the chapter's text; null means we haven't loaded it yet from the database and should not be editing or saving it. */
|
|
14
|
+
text?: string | null;
|
|
14
15
|
uids: string[];
|
|
15
16
|
};
|
|
16
17
|
export type ChapterContent = {
|
|
@@ -30,7 +31,7 @@ export default class Chapter {
|
|
|
30
31
|
readonly numbered: boolean;
|
|
31
32
|
readonly forthcoming: boolean;
|
|
32
33
|
readonly section: string | undefined;
|
|
33
|
-
readonly text: string;
|
|
34
|
+
readonly text: string | null;
|
|
34
35
|
readonly uids: string[];
|
|
35
36
|
_ast: ChapterNode | undefined;
|
|
36
37
|
_index: Record<string, Array<Match>> | undefined;
|
|
@@ -41,15 +42,16 @@ export default class Chapter {
|
|
|
41
42
|
}[];
|
|
42
43
|
lastEdit: number;
|
|
43
44
|
listeners: Set<(text: string) => void>;
|
|
44
|
-
constructor(ref: DocumentReference | undefined, id: string, title: string, authors: string[], image: string | null, numbered: boolean, forthcoming: boolean, section: string | undefined, text: string, uids: string[]);
|
|
45
|
+
constructor(ref: DocumentReference | undefined, id: string, title: string, authors: string[], image: string | null, numbered: boolean, forthcoming: boolean, section: string | undefined, text: string | null, uids: string[]);
|
|
45
46
|
/**
|
|
46
47
|
* Convert a chapter spec and it's text into a chapter object.
|
|
47
48
|
* The spec may contain the text itself, but it may not, so we require it to be
|
|
48
49
|
* repeated here to guarantee its provided.
|
|
49
50
|
*/
|
|
50
|
-
static fromJSON(spec: ChapterSpecification
|
|
51
|
+
static fromJSON(spec: ChapterSpecification): Chapter;
|
|
51
52
|
toJSON(): ChapterSpecification;
|
|
52
53
|
getRef(): DocumentReference<import("@firebase/firestore").DocumentData> | undefined;
|
|
54
|
+
hasRefID(id: string): boolean;
|
|
53
55
|
withEditors(uids: string[]): Chapter;
|
|
54
56
|
withRef(ref: DocumentReference): Chapter;
|
|
55
57
|
withoutRef(): Chapter;
|
|
@@ -62,7 +64,7 @@ export default class Chapter {
|
|
|
62
64
|
isNumbered(): boolean;
|
|
63
65
|
asNumbered(numbered: boolean): Chapter;
|
|
64
66
|
isEditor(uid: string): boolean;
|
|
65
|
-
getText(): string;
|
|
67
|
+
getText(): string | null;
|
|
66
68
|
withText(text: string): Chapter;
|
|
67
69
|
withAST(ast: ChapterNode): Chapter;
|
|
68
70
|
getAuthors(): string[];
|
|
@@ -73,8 +75,8 @@ export default class Chapter {
|
|
|
73
75
|
withTitle(title: string): Chapter;
|
|
74
76
|
getImage(): string | null;
|
|
75
77
|
withImage(image: string | null): Chapter;
|
|
76
|
-
getAST(edition: Edition): ChapterNode;
|
|
77
|
-
getWordCount(edition: Edition): number;
|
|
78
|
+
getAST(edition: Edition): ChapterNode | undefined;
|
|
79
|
+
getWordCount(edition: Edition): number | undefined;
|
|
78
80
|
getReadingTime(edition: Edition): number | undefined;
|
|
79
81
|
getIndex(edition: Edition): Record<string, Match[]>;
|
|
80
82
|
toString(): string;
|
|
@@ -39,9 +39,9 @@ export default class Chapter {
|
|
|
39
39
|
* The spec may contain the text itself, but it may not, so we require it to be
|
|
40
40
|
* repeated here to guarantee its provided.
|
|
41
41
|
*/
|
|
42
|
-
static fromJSON(spec
|
|
42
|
+
static fromJSON(spec) {
|
|
43
43
|
// Copy the spec, filling in defaults as necessary.
|
|
44
|
-
return new Chapter(spec.ref, spec.id, spec.title, spec.authors ?? [], spec.image ?? null, spec.numbered === true || spec.numbered === undefined, spec.forthcoming === true, spec.section, spec.text ??
|
|
44
|
+
return new Chapter(spec.ref, spec.id, spec.title, spec.authors ?? [], spec.image ?? null, spec.numbered === true || spec.numbered === undefined, spec.forthcoming === true, spec.section, spec.text ?? null, spec.uids);
|
|
45
45
|
}
|
|
46
46
|
toJSON() {
|
|
47
47
|
// Note that we don't include the chapter text in the payload; that's stored in the chapters subcollection.
|
|
@@ -64,6 +64,9 @@ export default class Chapter {
|
|
|
64
64
|
getRef() {
|
|
65
65
|
return this.ref;
|
|
66
66
|
}
|
|
67
|
+
hasRefID(id) {
|
|
68
|
+
return this.ref !== undefined && this.ref.id === id;
|
|
69
|
+
}
|
|
67
70
|
withEditors(uids) {
|
|
68
71
|
return new Chapter(this.ref, this.id, this.title, this.authors, this.image, this.numbered, this.forthcoming, this.section, this.text, uids);
|
|
69
72
|
}
|
|
@@ -144,6 +147,8 @@ export default class Chapter {
|
|
|
144
147
|
return new Chapter(this.ref, this.id, this.title, this.authors, image, this.numbered, this.forthcoming, this.section, this.text, this.uids);
|
|
145
148
|
}
|
|
146
149
|
getAST(edition) {
|
|
150
|
+
if (this.text === null)
|
|
151
|
+
return undefined;
|
|
147
152
|
// Compute the cache if necessary
|
|
148
153
|
if (this._ast === undefined)
|
|
149
154
|
this._ast = Parser.parseChapter(edition, this.text);
|
|
@@ -151,18 +156,23 @@ export default class Chapter {
|
|
|
151
156
|
}
|
|
152
157
|
getWordCount(edition) {
|
|
153
158
|
if (this._wordCount === undefined)
|
|
154
|
-
this._wordCount = this.getAST(edition)
|
|
159
|
+
this._wordCount = this.getAST(edition)
|
|
160
|
+
?.toText()
|
|
161
|
+
.split(/\s+/).length;
|
|
155
162
|
return this._wordCount;
|
|
156
163
|
}
|
|
157
164
|
// Utility function
|
|
158
165
|
getReadingTime(edition) {
|
|
159
|
-
|
|
166
|
+
const wordCount = this.getWordCount(edition);
|
|
167
|
+
return wordCount === undefined || this.isForthcoming()
|
|
160
168
|
? undefined
|
|
161
|
-
: Math.max(1, Math.round(
|
|
169
|
+
: Math.max(1, Math.round(wordCount / 150));
|
|
162
170
|
}
|
|
163
171
|
getIndex(edition) {
|
|
164
172
|
if (this._index === undefined) {
|
|
165
173
|
const ast = this.getAST(edition);
|
|
174
|
+
if (ast === undefined)
|
|
175
|
+
return {};
|
|
166
176
|
// Build a list of common words
|
|
167
177
|
let commonWords = new Set();
|
|
168
178
|
CommonWords.forEach((word) => commonWords.add(word));
|
|
@@ -207,6 +217,6 @@ export default class Chapter {
|
|
|
207
217
|
return this._index;
|
|
208
218
|
}
|
|
209
219
|
toString() {
|
|
210
|
-
return `${this.title} by ${this.authors.join(', ')}: ${this.text
|
|
220
|
+
return `${this.title} by ${this.authors.join(', ')}: ${this.text?.substring(0, 32)}}`;
|
|
211
221
|
}
|
|
212
222
|
}
|
|
@@ -83,6 +83,7 @@ export default class Edition {
|
|
|
83
83
|
withoutRefs(): Edition;
|
|
84
84
|
withIncrementedEditionNumber(): Edition;
|
|
85
85
|
withNewChapter(): Edition;
|
|
86
|
+
hasChapterRefID(id: string): boolean;
|
|
86
87
|
withoutChapter(chapterID: string): Edition;
|
|
87
88
|
withMovedChapter(chapterID: string, increment: number): Edition;
|
|
88
89
|
getSymbols(): Record<string, string>;
|
|
@@ -59,7 +59,7 @@ export default class Edition {
|
|
|
59
59
|
static fromJSON(editionRef, spec) {
|
|
60
60
|
// Copy all of the specification metadata to fields.
|
|
61
61
|
// 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
|
|
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);
|
|
63
63
|
}
|
|
64
64
|
static parseReference(citationID, ref, edition, short = false) {
|
|
65
65
|
if (typeof ref === 'string')
|
|
@@ -175,15 +175,19 @@ export default class Edition {
|
|
|
175
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);
|
|
176
176
|
}
|
|
177
177
|
withChapterText(text) {
|
|
178
|
-
|
|
178
|
+
// No need to make a new edition if no chapter was changed.
|
|
179
|
+
let modified = false;
|
|
180
|
+
const newChapters = this.chapters.map((chapter) => {
|
|
179
181
|
if (chapter.ref === undefined)
|
|
180
182
|
return chapter;
|
|
181
183
|
const match = text.find(([ref, text]) => chapter.ref !== undefined &&
|
|
182
184
|
chapter.ref.id === ref.id &&
|
|
183
|
-
chapter.text !== text
|
|
184
|
-
|
|
185
|
+
chapter.text !== text);
|
|
186
|
+
if (match)
|
|
187
|
+
modified = true;
|
|
185
188
|
return match ? chapter.withText(match[1]) : chapter;
|
|
186
|
-
})
|
|
189
|
+
});
|
|
190
|
+
return modified ? this.withChapters(newChapters) : this;
|
|
187
191
|
}
|
|
188
192
|
withRevisedChapter(previous, edited) {
|
|
189
193
|
const index = this.chapters.indexOf(previous);
|
|
@@ -210,6 +214,9 @@ export default class Edition {
|
|
|
210
214
|
const newChapter = new Chapter(undefined, `chapter${number}`, 'Untitled Chapter', [], null, true, false, undefined, '', []);
|
|
211
215
|
return this.withChapters([...this.chapters, newChapter]);
|
|
212
216
|
}
|
|
217
|
+
hasChapterRefID(id) {
|
|
218
|
+
return this.chapters.some((chapter) => chapter.hasRefID(id));
|
|
219
|
+
}
|
|
213
220
|
withoutChapter(chapterID) {
|
|
214
221
|
let index = this.chapters.findIndex((chapter) => chapter.getID() === chapterID);
|
|
215
222
|
if (index < 0)
|
|
@@ -14,7 +14,7 @@ export default class DefinitionNode extends MetadataNode {
|
|
|
14
14
|
return `${this.getText().toHTML()}`;
|
|
15
15
|
}
|
|
16
16
|
toBookdown() {
|
|
17
|
-
return `~${this.getText().
|
|
17
|
+
return `~${this.getText().getText()}~${this.getMeta()}`;
|
|
18
18
|
}
|
|
19
19
|
copy() {
|
|
20
20
|
return new DefinitionNode(this.getText(), this.getMeta());
|
|
@@ -18,6 +18,9 @@ export default class FormatNode extends Node {
|
|
|
18
18
|
const previous = list[position - 1];
|
|
19
19
|
return !(previous instanceof TextNode && previous.getLength() === 0);
|
|
20
20
|
});
|
|
21
|
+
// Always ensure an empty text node.
|
|
22
|
+
if (this.#segments.length === 0)
|
|
23
|
+
this.#segments.push(new TextNode());
|
|
21
24
|
}
|
|
22
25
|
getType() {
|
|
23
26
|
return 'formatted';
|
|
@@ -149,7 +152,7 @@ export default class FormatNode extends Node {
|
|
|
149
152
|
const text = this.getTextNodes();
|
|
150
153
|
if (text.length === 0)
|
|
151
154
|
return undefined;
|
|
152
|
-
return { node: this.
|
|
155
|
+
return { node: this.getFirstTextNode(), index: 0 };
|
|
153
156
|
}
|
|
154
157
|
getLastCaret() {
|
|
155
158
|
const text = this.getTextNodes();
|
|
@@ -15,7 +15,7 @@ export default class InlineCodeNode extends MetadataNode {
|
|
|
15
15
|
}
|
|
16
16
|
toBookdown() {
|
|
17
17
|
return ('`' +
|
|
18
|
-
this.getText().
|
|
18
|
+
this.getText().getText().replace(/`/g, '\\`') +
|
|
19
19
|
'`' +
|
|
20
20
|
(this.getMeta() === 'plaintext' ? '' : this.getMeta()));
|
|
21
21
|
}
|
|
@@ -14,7 +14,7 @@ export default class LinkNode extends MetadataNode {
|
|
|
14
14
|
return `<a href="${this.getMeta()}">${this.getText().toHTML()}</a>`;
|
|
15
15
|
}
|
|
16
16
|
toBookdown() {
|
|
17
|
-
return `[${this.getText().
|
|
17
|
+
return `[${this.getText().getText()}|${this.getMeta()}]`;
|
|
18
18
|
}
|
|
19
19
|
copy() {
|
|
20
20
|
return new LinkNode(this.getText(), this.getMeta());
|
|
@@ -101,7 +101,7 @@ export default class ListNode extends BlockNode {
|
|
|
101
101
|
return (format !== undefined &&
|
|
102
102
|
caret.node.hasAncestor(root, this) &&
|
|
103
103
|
caret.index === 0 &&
|
|
104
|
-
format.
|
|
104
|
+
format.getFirstTextNode() === caret.node);
|
|
105
105
|
}
|
|
106
106
|
getItemContaining(caret) {
|
|
107
107
|
const match = this.#items.find((n) => n.getParentOf(caret.node) !== undefined);
|
|
@@ -739,12 +739,9 @@ export default class Parser {
|
|
|
739
739
|
let code = '';
|
|
740
740
|
while (this.more() && !this.nextIs('`')) {
|
|
741
741
|
let next = this.read(false);
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
next = '`';
|
|
746
|
-
}
|
|
747
|
-
}
|
|
742
|
+
// Skip backslash of escaped characters.
|
|
743
|
+
if (next === '\\')
|
|
744
|
+
next = this.read();
|
|
748
745
|
code = code + next;
|
|
749
746
|
}
|
|
750
747
|
// Read the backtick.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bookish-press",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.32",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A platform for reading and writing readable, accessible, beautiful online books.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -27,19 +27,19 @@
|
|
|
27
27
|
"merge": "git checkout main && git merge dev && git push origin main && git checkout dev"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@sveltejs/adapter-static": "^2.0
|
|
31
|
-
"@sveltejs/kit": "^1.
|
|
32
|
-
"@sveltejs/package": "^2.0
|
|
33
|
-
"@types/prismjs": "^1.26
|
|
30
|
+
"@sveltejs/adapter-static": "^2.0",
|
|
31
|
+
"@sveltejs/kit": "^1.2",
|
|
32
|
+
"@sveltejs/package": "^2.0",
|
|
33
|
+
"@types/prismjs": "^1.26",
|
|
34
34
|
"@types/smoothscroll-polyfill": "^0.3.1",
|
|
35
|
-
"@types/uuid": "^9.0
|
|
36
|
-
"prettier": "^2.8
|
|
37
|
-
"prettier-plugin-svelte": "^2.
|
|
35
|
+
"@types/uuid": "^9.0",
|
|
36
|
+
"prettier": "^2.8",
|
|
37
|
+
"prettier-plugin-svelte": "^2.10",
|
|
38
38
|
"svelte-adapter-firebase": "^0.15.0",
|
|
39
|
-
"svelte-check": "^3.
|
|
40
|
-
"svelte-preprocess": "^5.0
|
|
41
|
-
"tslib": "^2.5
|
|
42
|
-
"typescript": "^
|
|
39
|
+
"svelte-check": "^3.4",
|
|
40
|
+
"svelte-preprocess": "^5.0",
|
|
41
|
+
"tslib": "^2.5",
|
|
42
|
+
"typescript": "^5.1",
|
|
43
43
|
"vite": "^4.1.2",
|
|
44
44
|
"vitest": "^0.29.2"
|
|
45
45
|
},
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"ajv": "^8.12.0",
|
|
48
48
|
"ajv-formats": "^2.1.1",
|
|
49
49
|
"firebase": "latest",
|
|
50
|
-
"firebase-functions": "^4.
|
|
50
|
+
"firebase-functions": "^4.4.0",
|
|
51
51
|
"prismjs": "^1.29.0",
|
|
52
52
|
"smoothscroll-polyfill": "^0.4.4",
|
|
53
|
-
"svelte": "^3.
|
|
54
|
-
"@sveltejs/kit": "^1.
|
|
55
|
-
"uuid": "^9.0
|
|
53
|
+
"svelte": "^3.59",
|
|
54
|
+
"@sveltejs/kit": "^1.2",
|
|
55
|
+
"uuid": "^9.0"
|
|
56
56
|
},
|
|
57
57
|
"types": "./dist/index.d.ts",
|
|
58
58
|
"exports": {
|