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.
@@ -60,6 +60,7 @@ $: url =
60
60
  text-align: left;
61
61
  flex-wrap: wrap;
62
62
  height: 12em;
63
+ overflow-y: hidden;
63
64
  }
64
65
 
65
66
  .cover {
@@ -1,2 +1,2 @@
1
1
  export default Prism;
2
- import Prism from "prismjs";
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
- ol {
28
- list-style: none;
29
- counter-reset: item;
27
+ li {
28
+ margin-bottom: calc(0.5 * var(--bookish-paragraph-spacing));
30
29
  }
31
30
 
32
- ol > li {
33
- counter-increment: item;
31
+ /* Lists are decimal numbered */
32
+ ol {
33
+ list-style: decimal;
34
34
  }
35
35
 
36
- ol > li {
37
- list-style-type: none;
36
+ /* First level is decimal */
37
+ ol li {
38
+ counter-increment: list-item;
39
+ list-style-type: decimal;
38
40
  }
39
41
 
40
- ol > :global(ol > li) {
41
- list-style-type: none;
42
+ /* Second level is lowercase alpha */
43
+ ol ol li {
44
+ list-style-type: lower-alpha;
42
45
  }
43
46
 
44
- ol > :global(ol > ol > li) {
45
- list-style-type: none;
47
+ /* Third level is uppercase alpha */
48
+ ol ol ol li {
49
+ list-style-type: upper-alpha;
46
50
  }
47
51
 
48
- ol > :global(li::before),
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
- tabIndex="0"
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].getTextNodes()[0],
1225
+ node: newTable.getRows()[0][0].getFirstTextNode(),
1226
1226
  index: 0,
1227
1227
  };
1228
1228
  });
@@ -28,6 +28,8 @@ async function addEditor() {
28
28
  }
29
29
  }
30
30
  function hasEditor(email) {
31
+ if (email.length === 0)
32
+ return false;
31
33
  const match = Array.from($emails.entries()).find(([, e]) => e === email);
32
34
  if (match === undefined)
33
35
  return false;
@@ -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
- tabIndex="0"
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
- edition.set($edition.withChapterText(chapters));
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
  }
@@ -84,7 +84,7 @@ async function handleDraftEdition() {
84
84
  undefined,
85
85
  edition.summary
86
86
  )}
87
- />{:else}&mdash{/if}</p
87
+ />{:else}&mdash;{/if}</p
88
88
  >
89
89
  </section>
90
90
  {/if}
@@ -129,7 +129,7 @@ let expandLabel = '\u2630';
129
129
  aria-label={expanded
130
130
  ? 'Collapse navigation menu'
131
131
  : 'Expand navigation menu'}
132
- tabIndex="0"
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 {#if editable}<Button
141
+ >Chapters{#if editable}&nbsp;<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
@@ -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>;
@@ -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
- chapters.push([doc.ref, doc.data().text]);
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'), { text: newChapter.text });
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 (previousChapter === undefined ||
197
- newChapter.text !== previousChapter.text) {
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
- text?: string;
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, text: string): Chapter;
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, text) {
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 ?? text, spec.uids);
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).toText().split(/\s+/).length;
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
- return this.isForthcoming()
166
+ const wordCount = this.getWordCount(edition);
167
+ return wordCount === undefined || this.isForthcoming()
160
168
  ? undefined
161
- : Math.max(1, Math.round(this.getWordCount(edition) / 150));
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.substring(0, 32)}}`;
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, '')), spec.license, spec.acknowledgements, spec.tags ?? [], spec.sources ?? {}, spec.references ?? {}, spec.symbols ?? {}, spec.glossary ?? {}, spec.theme, spec.base, spec.active);
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
- return this.withChapters(this.chapters.map((chapter) => {
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
- text !== undefined);
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().toBookdown()}~${this.getMeta()}`;
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.getTextNodes()[0], index: 0 };
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().toBookdown().replace(/`/g, '\\`') +
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().toBookdown()}|${this.getMeta()}]`;
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.getTextNodes()[0] === caret.node);
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
- if (next === '\\') {
743
- if (this.nextIs('`')) {
744
- this.read();
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.26",
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.1",
31
- "@sveltejs/kit": "^1.11",
32
- "@sveltejs/package": "^2.0.2",
33
- "@types/prismjs": "^1.26.0",
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.0",
36
- "prettier": "^2.8.4",
37
- "prettier-plugin-svelte": "^2.9.0",
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.0.3",
40
- "svelte-preprocess": "^5.0.1",
41
- "tslib": "^2.5.0",
42
- "typescript": "^4.9.5",
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.2.1",
50
+ "firebase-functions": "^4.4.0",
51
51
  "prismjs": "^1.29.0",
52
52
  "smoothscroll-polyfill": "^0.4.4",
53
- "svelte": "^3.55.1",
54
- "@sveltejs/kit": "^1.7.2",
55
- "uuid": "^9.0.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": {