bookish-press 0.5.71 → 0.5.74
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/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 +9 -3
- package/dist/components/editor/Commands.js +21 -3
- package/dist/components/editor/LinkEditor.svelte +18 -5
- package/dist/components/editor/icons/header4.svg +7 -0
- package/dist/components/page/Analytics.svelte +21 -0
- package/dist/components/page/Analytics.svelte.d.ts +16 -0
- package/dist/components/page/DefinitionView.svelte +4 -3
- package/dist/components/page/Edition.svelte +2 -2
- package/dist/components/page/TableOfContents.svelte +33 -8
- package/dist/models/CRUD.js +1 -1
- package/dist/models/book/Edition.d.ts +5 -1
- package/dist/models/book/Edition.js +25 -19
- 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/package.json +3 -3
|
@@ -18,6 +18,10 @@ function isInvalidChapterLink() {
|
|
|
18
18
|
let parts = chapter.split(':');
|
|
19
19
|
chapter = parts[0];
|
|
20
20
|
}
|
|
21
|
+
else if (url.indexOf('#') >= 0) {
|
|
22
|
+
let parts = chapter.split('#');
|
|
23
|
+
chapter = parts[0];
|
|
24
|
+
}
|
|
21
25
|
if (chapter !== '' && !$edition?.hasChapter(chapter))
|
|
22
26
|
return true;
|
|
23
27
|
}
|
|
@@ -98,4 +98,22 @@ $: dropcap =
|
|
|
98
98
|
font-size: var(--bookish-header-3-font-size);
|
|
99
99
|
font-style: italic;
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
h5 {
|
|
103
|
+
font-size: var(--bookish-paragraph-font-size);
|
|
104
|
+
display: inline;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
h5::after {
|
|
108
|
+
content: '. ';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
h5 + :global(p) {
|
|
112
|
+
display: inline;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
h5 + :global(p::after) {
|
|
116
|
+
content: '\0A';
|
|
117
|
+
display: block;
|
|
118
|
+
}
|
|
101
119
|
</style>
|
|
@@ -9,7 +9,7 @@ let editable = isChapterEditable();
|
|
|
9
9
|
$: rows = node.getRows();
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
<Figure {node} caption={node.getCaption()}>
|
|
12
|
+
<Figure {node} caption={node.getCaption()} focusable={false}>
|
|
13
13
|
<Rows>
|
|
14
14
|
{#each rows as row, index}
|
|
15
15
|
<tr class={editable ? 'editable' : null}>
|
|
@@ -18,7 +18,7 @@ import commands from './Commands';
|
|
|
18
18
|
import { afterUpdate, onMount, setContext } from 'svelte';
|
|
19
19
|
import { getCaret } from '../page/Contexts';
|
|
20
20
|
import CaretView from './CaretView.svelte';
|
|
21
|
-
import Parser from '../../models/chapter/Parser';
|
|
21
|
+
import Parser, { Reserved } from '../../models/chapter/Parser';
|
|
22
22
|
const IDLE_TIME = 500;
|
|
23
23
|
export let text;
|
|
24
24
|
export let parser;
|
|
@@ -704,8 +704,14 @@ function handlePaste(context, text, save) {
|
|
|
704
704
|
return handlePasteNode(context, text, save);
|
|
705
705
|
if (text.plain === undefined)
|
|
706
706
|
return undefined;
|
|
707
|
-
//
|
|
708
|
-
const
|
|
707
|
+
// Escape all reserved Bookdown characters.
|
|
708
|
+
const escaped = text.plain.replaceAll(Reserved, '\\$1');
|
|
709
|
+
const chapter =
|
|
710
|
+
// If we're pasting text into a paragraph node, then we parse it as a series of paragraphs.
|
|
711
|
+
context.block instanceof ParagraphNode
|
|
712
|
+
? Parser.parseChapter(undefined, escaped)
|
|
713
|
+
: // Otherwise parse as just a format node, replacing all text newlines with a space.
|
|
714
|
+
Parser.parseFormat(undefined, escaped.replaceAll('\n', ' '));
|
|
709
715
|
const edit = handlePasteNode(context, chapter, save);
|
|
710
716
|
if (edit)
|
|
711
717
|
return edit;
|
|
@@ -35,6 +35,7 @@ import FootnoteIcon from './icons/footnote.svg?raw';
|
|
|
35
35
|
import Header1Icon from './icons/header1.svg?raw';
|
|
36
36
|
import Header2Icon from './icons/header2.svg?raw';
|
|
37
37
|
import Header3Icon from './icons/header3.svg?raw';
|
|
38
|
+
import Header4Icon from './icons/header4.svg?raw';
|
|
38
39
|
import IndentIcon from './icons/indent.svg?raw';
|
|
39
40
|
import ItalicIcon from './icons/italic.svg?raw';
|
|
40
41
|
import LabelIcon from './icons/label.svg?raw';
|
|
@@ -79,10 +80,10 @@ function deleteTableRowColumn(context, table, format, row) {
|
|
|
79
80
|
? table.withoutRow(location.row)
|
|
80
81
|
: table.withoutColumn(location.column), (t) => {
|
|
81
82
|
return (row
|
|
82
|
-
? t.getCell(location.row === table.getRowCount()
|
|
83
|
+
? t.getCell(location.row === table.getRowCount() - 1
|
|
83
84
|
? location.row - 1
|
|
84
85
|
: location.row, location.column)
|
|
85
|
-
: t.getCell(location.row, location.column === table.getColumnCount()
|
|
86
|
+
: t.getCell(location.row, location.column === table.getColumnCount() - 1
|
|
86
87
|
? location.column - 1
|
|
87
88
|
: location.column))?.getFirstCaret();
|
|
88
89
|
});
|
|
@@ -1109,7 +1110,10 @@ const commands = [
|
|
|
1109
1110
|
active: (context) => context.chapter &&
|
|
1110
1111
|
context.atom === undefined &&
|
|
1111
1112
|
context.meta === undefined,
|
|
1112
|
-
handler: (context) => context.root.withSegmentAtSelection(
|
|
1113
|
+
handler: (context) => context.root.withSegmentAtSelection({
|
|
1114
|
+
start: context.root.sortRange(context.range).end,
|
|
1115
|
+
end: context.root.sortRange(context.range).end,
|
|
1116
|
+
}, (text) => new CommentNode(new FormatNode('', [new TextNode(text)]))),
|
|
1113
1117
|
},
|
|
1114
1118
|
{
|
|
1115
1119
|
icon: ParagraphIcon,
|
|
@@ -1172,6 +1176,20 @@ const commands = [
|
|
|
1172
1176
|
context.paragraph.getLevel() !== 3,
|
|
1173
1177
|
handler: (context) => rootWithNode(context, context.paragraph, context.paragraph?.withLevel(3)),
|
|
1174
1178
|
},
|
|
1179
|
+
{
|
|
1180
|
+
icon: Header4Icon,
|
|
1181
|
+
description: 'format as 4rd level header',
|
|
1182
|
+
category: 'level',
|
|
1183
|
+
mutates: true,
|
|
1184
|
+
control: true,
|
|
1185
|
+
alt: true,
|
|
1186
|
+
shift: false,
|
|
1187
|
+
code: 'Digit4',
|
|
1188
|
+
visible: (context) => context.paragraph !== undefined,
|
|
1189
|
+
active: (context) => context.paragraph !== undefined &&
|
|
1190
|
+
context.paragraph.getLevel() !== 4,
|
|
1191
|
+
handler: (context) => rootWithNode(context, context.paragraph, context.paragraph?.withLevel(4)),
|
|
1192
|
+
},
|
|
1175
1193
|
{
|
|
1176
1194
|
icon: RuleIcon,
|
|
1177
1195
|
description: 'insert horizontal rule',
|
|
@@ -55,12 +55,25 @@ $: {
|
|
|
55
55
|
// Add an option for each chapter and it's labels.
|
|
56
56
|
if ($edition) {
|
|
57
57
|
for (const chap of $edition?.getChapters() ?? []) {
|
|
58
|
+
// Include the chapter itself
|
|
58
59
|
options.push([`Chapter: ${chap.getTitle()}`, chap.getID()]);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
const root = chap.getAST($edition);
|
|
61
|
+
if (root) {
|
|
62
|
+
// Include the chapter's headers
|
|
63
|
+
const headers = root.getHeaders();
|
|
64
|
+
for (let number = 0; number < headers.length; number++) {
|
|
65
|
+
options.push([
|
|
66
|
+
chap.getTitle() + ': ' + headers[number].toText(),
|
|
67
|
+
`${chap.getID()}#header-${number}`,
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
// Include all of the labels in the chapter
|
|
71
|
+
for (const label of root.getLabels())
|
|
72
|
+
options.push([
|
|
73
|
+
chap.getTitle() + ': ' + label.getMeta(),
|
|
74
|
+
`${chap.getID()}:${label.getMeta()}`,
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
79
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg role="presentation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
|
|
2
|
+
<g transform="scale(0.5 0.5)" transform-origin="7 12">
|
|
3
|
+
<line x1="3" y1="2" x2="3" y2="12" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></line>
|
|
4
|
+
<line x1="3" y1="7" x2="11" y2="7" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></line>
|
|
5
|
+
<line x1="11" y1="2" x2="11" y2="12" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></line>
|
|
6
|
+
</g>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script>/** By default, we use the Bookish.press Google Analytics tag */
|
|
2
|
+
export let gtagid = null;
|
|
3
|
+
$: id = gtagid ?? 'G-BGFDV229BN';
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<svelte:head>
|
|
7
|
+
<script
|
|
8
|
+
async
|
|
9
|
+
src="https://www.googletagmanager.com/gtag/js?id={id}"
|
|
10
|
+
></script>
|
|
11
|
+
{@html `<script>
|
|
12
|
+
window.dataLayer = window.dataLayer || [];
|
|
13
|
+
|
|
14
|
+
function gtag() {
|
|
15
|
+
dataLayer.push(arguments);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
gtag('js', new Date());
|
|
19
|
+
gtag('config', "${id}");
|
|
20
|
+
</script>`}
|
|
21
|
+
</svelte:head>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
/** By default, we use the Bookish.press Google Analytics tag */ gtagid?: string | null | undefined;
|
|
5
|
+
};
|
|
6
|
+
events: {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
};
|
|
9
|
+
slots: {};
|
|
10
|
+
};
|
|
11
|
+
export type AnalyticsProps = typeof __propDef.props;
|
|
12
|
+
export type AnalyticsEvents = typeof __propDef.events;
|
|
13
|
+
export type AnalyticsSlots = typeof __propDef.slots;
|
|
14
|
+
export default class Analytics extends SvelteComponent<AnalyticsProps, AnalyticsEvents, AnalyticsSlots> {
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -62,6 +62,7 @@ $: syns = definition.synonyms || [];
|
|
|
62
62
|
{#if editable}
|
|
63
63
|
<TextEditor
|
|
64
64
|
text={definition.phrase}
|
|
65
|
+
saveOnExit
|
|
65
66
|
label={'Glossary phrase editor.'}
|
|
66
67
|
placeholder="phrase"
|
|
67
68
|
valid={(text) => {
|
|
@@ -74,7 +75,7 @@ $: syns = definition.synonyms || [];
|
|
|
74
75
|
phrase: text,
|
|
75
76
|
definition: definition.definition,
|
|
76
77
|
synonyms: definition.synonyms,
|
|
77
|
-
})
|
|
78
|
+
}),
|
|
78
79
|
)
|
|
79
80
|
: undefined}
|
|
80
81
|
/>
|
|
@@ -106,7 +107,7 @@ $: syns = definition.synonyms || [];
|
|
|
106
107
|
phrase: definition.phrase,
|
|
107
108
|
definition: node.toBookdown(),
|
|
108
109
|
synonyms: definition.synonyms,
|
|
109
|
-
})
|
|
110
|
+
}),
|
|
110
111
|
)
|
|
111
112
|
: undefined}
|
|
112
113
|
chapter={false}
|
|
@@ -122,7 +123,7 @@ $: syns = definition.synonyms || [];
|
|
|
122
123
|
<Format
|
|
123
124
|
node={Parser.parseFormat(
|
|
124
125
|
undefined,
|
|
125
|
-
definition.definition
|
|
126
|
+
definition.definition,
|
|
126
127
|
).withTextIfEmpty()}
|
|
127
128
|
/>
|
|
128
129
|
{/if}
|
|
@@ -8,7 +8,7 @@ import { isDark, setDark } from '../../util/dark';
|
|
|
8
8
|
import { page } from '$app/stores';
|
|
9
9
|
// Poly fill smooth scrolling for Safari.
|
|
10
10
|
onMount(() => smoothscroll.polyfill());
|
|
11
|
-
// The book edition to render
|
|
11
|
+
// The book and edition to render
|
|
12
12
|
export let edition;
|
|
13
13
|
// The base path allows links to adjust to different routing contexts in which a book is placed.
|
|
14
14
|
// For example, when the book is hosted alone, all routes might start with the bare root "/",
|
|
@@ -79,8 +79,8 @@ function toRules(set) {
|
|
|
79
79
|
}
|
|
80
80
|
</script>
|
|
81
81
|
|
|
82
|
-
<!-- Keep the imports up to date -->
|
|
83
82
|
<svelte:head>
|
|
83
|
+
<!-- Keep the imports up to date -->
|
|
84
84
|
{@html '<' + `style>${themeCSS}</style>`}
|
|
85
85
|
|
|
86
86
|
<meta property="og:title" content={edition.getTitle()} />
|
|
@@ -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';
|
|
@@ -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}
|
|
@@ -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}>
|
package/dist/models/CRUD.js
CHANGED
|
@@ -113,7 +113,7 @@ export async function createBook(userID) {
|
|
|
113
113
|
// Create a model to store it.
|
|
114
114
|
let book = Book.fromJSON(bookRef.id, newBookData);
|
|
115
115
|
// Make a new default edition with a single chapter with this user id.
|
|
116
|
-
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();
|
|
117
117
|
// Add the edition to the editions collection in the database so we can get an edition ref.
|
|
118
118
|
const editionRef = await addDoc(collection(db, 'books', bookRef.id, 'editions'), newEdition.toObject());
|
|
119
119
|
// Remember the ref.
|
|
@@ -32,6 +32,8 @@ export type EditionSpecification = {
|
|
|
32
32
|
/** All uids who have edit access to at least one chapter. Used in firestore rules to give permission to update book. */
|
|
33
33
|
chapteruids: string[];
|
|
34
34
|
active: Record<string, string>;
|
|
35
|
+
/** An optional Google Analytics tag ID */
|
|
36
|
+
gtagid: string | null;
|
|
35
37
|
};
|
|
36
38
|
export default class Edition {
|
|
37
39
|
readonly bookRef: DocumentReference;
|
|
@@ -55,7 +57,8 @@ export default class Edition {
|
|
|
55
57
|
readonly theme: Theme | null;
|
|
56
58
|
readonly base: string | null;
|
|
57
59
|
readonly active: Record<string, string>;
|
|
58
|
-
|
|
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);
|
|
59
62
|
static fromJSON(editionRef: DocumentReference | undefined, spec: EditionSpecification): Edition;
|
|
60
63
|
static parseReference(citationID: string, ref: string | string[], edition: Edition, short?: boolean): FormatNode | Reference;
|
|
61
64
|
toObject(): EditionSpecification;
|
|
@@ -125,6 +128,7 @@ export default class Edition {
|
|
|
125
128
|
hasLease(uid: string, content: string): boolean;
|
|
126
129
|
isLeased(contentID: string): boolean;
|
|
127
130
|
withLock(uid: string, content: string, lock: boolean): Edition;
|
|
131
|
+
withGTag(gtagid: string | null): Edition;
|
|
128
132
|
getBookReadingTime(): number | undefined;
|
|
129
133
|
getChapterNumber(chapterID: string): undefined;
|
|
130
134
|
getSource(sourceID: string): string | null;
|
|
@@ -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,7 +175,7 @@ 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
180
|
withChapterText(textByID) {
|
|
178
181
|
const newChapters = this.chapters.map((chapter) => {
|
|
@@ -198,7 +201,7 @@ export default class Edition {
|
|
|
198
201
|
return this.withRef(undefined).withChapters(this.chapters.map((chap) => chap.withoutRef()));
|
|
199
202
|
}
|
|
200
203
|
withIncrementedEditionNumber() {
|
|
201
|
-
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);
|
|
202
205
|
}
|
|
203
206
|
withNewChapter() {
|
|
204
207
|
// Synthesize a chapter ID placeholder that doesn't overlap with existing chapter names
|
|
@@ -230,7 +233,7 @@ export default class Edition {
|
|
|
230
233
|
const temp = newChapters[index + increment];
|
|
231
234
|
newChapters[index + increment] = newChapters[index];
|
|
232
235
|
newChapters[index] = temp;
|
|
233
|
-
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);
|
|
234
237
|
}
|
|
235
238
|
getSymbols() {
|
|
236
239
|
return this.symbols;
|
|
@@ -247,7 +250,7 @@ export default class Edition {
|
|
|
247
250
|
: undefined;
|
|
248
251
|
}
|
|
249
252
|
withReferences(newReferences) {
|
|
250
|
-
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);
|
|
251
254
|
}
|
|
252
255
|
withNewReferences(references) {
|
|
253
256
|
const newReferences = Object.assign({}, this.references);
|
|
@@ -288,7 +291,7 @@ export default class Edition {
|
|
|
288
291
|
return id in this.glossary;
|
|
289
292
|
}
|
|
290
293
|
withGlossary(glossary) {
|
|
291
|
-
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);
|
|
292
295
|
}
|
|
293
296
|
withDefinition(id, phrase, definition, synonyms) {
|
|
294
297
|
const newGlossary = Object.assign({}, this.glossary);
|
|
@@ -325,7 +328,7 @@ export default class Edition {
|
|
|
325
328
|
return this.theme;
|
|
326
329
|
}
|
|
327
330
|
withTheme(theme) {
|
|
328
|
-
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);
|
|
329
332
|
}
|
|
330
333
|
withThemeValue(group, name, value) {
|
|
331
334
|
if (this.theme === null)
|
|
@@ -348,7 +351,7 @@ export default class Edition {
|
|
|
348
351
|
.join(', ');
|
|
349
352
|
}
|
|
350
353
|
withAuthors(authors) {
|
|
351
|
-
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);
|
|
352
355
|
}
|
|
353
356
|
withAuthor(name) {
|
|
354
357
|
return this.withAuthors([...this.authors, name]);
|
|
@@ -371,7 +374,7 @@ export default class Edition {
|
|
|
371
374
|
]);
|
|
372
375
|
}
|
|
373
376
|
withEditors(uids) {
|
|
374
|
-
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);
|
|
375
378
|
}
|
|
376
379
|
hasImage(id) {
|
|
377
380
|
return this.images[id] !== null;
|
|
@@ -382,7 +385,7 @@ export default class Edition {
|
|
|
382
385
|
withImage(id, embed) {
|
|
383
386
|
const newImages = Object.assign({}, this.images);
|
|
384
387
|
newImages[id] = embed;
|
|
385
|
-
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);
|
|
386
389
|
}
|
|
387
390
|
isEditor(uid) {
|
|
388
391
|
return this.uids.includes(uid);
|
|
@@ -405,7 +408,10 @@ export default class Edition {
|
|
|
405
408
|
newActive[content] = uid;
|
|
406
409
|
else
|
|
407
410
|
delete newActive[content];
|
|
408
|
-
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);
|
|
409
415
|
}
|
|
410
416
|
getBookReadingTime() {
|
|
411
417
|
return this.chapters
|
|
@@ -68,22 +68,12 @@ export default class ParagraphNode extends BlockNode {
|
|
|
68
68
|
return this.#format.toText();
|
|
69
69
|
}
|
|
70
70
|
toBookdown() {
|
|
71
|
-
return
|
|
72
|
-
?
|
|
73
|
-
: this.#level
|
|
74
|
-
? '## '
|
|
75
|
-
: this.#level === 3
|
|
76
|
-
? '### '
|
|
77
|
-
: '') + this.#format.toBookdown());
|
|
71
|
+
return this.#level === 0
|
|
72
|
+
? this.#format.toBookdown()
|
|
73
|
+
: `${'#'.repeat(this.#level)} ${this.#format.toBookdown()}`;
|
|
78
74
|
}
|
|
79
75
|
toHTML() {
|
|
80
|
-
const tag = this.#level ===
|
|
81
|
-
? 'h1'
|
|
82
|
-
: this.#level === 2
|
|
83
|
-
? 'h2'
|
|
84
|
-
: this.#level === 3
|
|
85
|
-
? 'h2'
|
|
86
|
-
: 'p';
|
|
76
|
+
const tag = this.#level === 0 ? 'p' : `h${this.#level}`;
|
|
87
77
|
return `<${tag}>${this.#format.toHTML()}</${tag}>`;
|
|
88
78
|
}
|
|
89
79
|
withLevel(level) {
|
|
@@ -22,6 +22,7 @@ import type Position from './Position';
|
|
|
22
22
|
import type BlockNode from './BlockNode';
|
|
23
23
|
import type Bookkeeping from './Bookkeeping';
|
|
24
24
|
import LineBreakNode from './LineBreakNode';
|
|
25
|
+
export declare const Reserved: RegExp;
|
|
25
26
|
export default class Parser {
|
|
26
27
|
book: Edition | undefined;
|
|
27
28
|
text: string;
|
|
@@ -46,6 +46,7 @@ const bulletRE = /^\*+\s+/;
|
|
|
46
46
|
// LINK :: [CONTENT|TEXT]
|
|
47
47
|
// FOOTNOTE :: {TEXT}
|
|
48
48
|
// TEXT :: (.+)
|
|
49
|
+
export const Reserved = /([_*`@~:^<>{}\[\]])/g;
|
|
49
50
|
export default class Parser {
|
|
50
51
|
book;
|
|
51
52
|
text;
|
|
@@ -355,7 +356,7 @@ export default class Parser {
|
|
|
355
356
|
// Read any whitespace after the hashes.
|
|
356
357
|
this.readWhitespace();
|
|
357
358
|
// Parse some content.
|
|
358
|
-
return new ParagraphNode(Math.min(
|
|
359
|
+
return new ParagraphNode(Math.min(4, count), this.parseFormat());
|
|
359
360
|
}
|
|
360
361
|
parseRule() {
|
|
361
362
|
// Read until the end of the line. Ignore all text that follows.
|
|
@@ -168,8 +168,10 @@ export default class TableNode extends BlockNode {
|
|
|
168
168
|
if (index < 0 || index >= this.getColumnCount())
|
|
169
169
|
return;
|
|
170
170
|
const newRows = this.#rows.slice();
|
|
171
|
-
for (let r = 0; r < this.#rows.length; r++)
|
|
171
|
+
for (let r = 0; r < this.#rows.length; r++) {
|
|
172
|
+
newRows[r] = newRows[r].slice();
|
|
172
173
|
newRows[r].splice(index, 1);
|
|
174
|
+
}
|
|
173
175
|
return new TableNode(newRows, this.#position, this.#caption);
|
|
174
176
|
}
|
|
175
177
|
withNewColumn(index) {
|
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.74",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A platform for reading and writing readable, accessible, beautiful online books.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"//env": "echo 'This copies the .env file corresponding to the current Firebase project specified with firebase use, defaulting to local'",
|
|
21
21
|
"env": "cp .env.${GCLOUD_PROJECT:-example} .env",
|
|
22
22
|
"//stage": "echo 'switch to the dev project, deploy, relying on Firebase's predeploy hook to run the build script above before release'",
|
|
23
|
-
"stage": "firebase use dev && firebase deploy",
|
|
23
|
+
"stage": "firebase use dev && cp .env.bookish-dev-21ac3 .env && firebase deploy",
|
|
24
24
|
"//release": "echo 'switch to the prod project, deploy, relying on Firebase's predeploy hook to run the build script above before release'",
|
|
25
|
-
"release": "firebase use prod && firebase deploy",
|
|
25
|
+
"release": "firebase use prod && cp .env.bookish-prod .env && firebase deploy",
|
|
26
26
|
"//merge": "merge dev into main, push to remote",
|
|
27
27
|
"merge": "git checkout main && git merge dev && git push origin main && git checkout dev"
|
|
28
28
|
},
|