bookish-press 0.5.62 → 0.5.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -1
- package/dist/components/app/Options.svelte +1 -0
- package/dist/components/app/Table.svelte +0 -1
- package/dist/components/chapter/CaptionedCode.svelte +21 -19
- package/dist/components/chapter/Figure.svelte +5 -3
- package/dist/components/chapter/Figure.svelte.d.ts +1 -0
- package/dist/components/chapter/Link.svelte +4 -0
- package/dist/components/chapter/Paragraph.svelte +18 -0
- package/dist/components/chapter/Table.svelte +1 -1
- package/dist/components/editor/BookishEditor.svelte +15 -6
- package/dist/components/editor/CaretView.svelte +8 -3
- package/dist/components/editor/Commands.js +22 -4
- package/dist/components/editor/LinkEditor.svelte +18 -5
- package/dist/components/editor/icons/header4.svg +7 -0
- package/dist/components/page/Analytics.svelte +19 -0
- package/dist/components/page/Analytics.svelte.d.ts +16 -0
- package/dist/components/page/BulkReferenceEditor.svelte +1 -1
- package/dist/components/page/Chapter.svelte +4 -5
- package/dist/components/page/DefinitionView.svelte +4 -3
- package/dist/components/page/Edition.svelte +2 -2
- package/dist/components/page/EditionLoader.svelte +9 -18
- package/dist/components/page/Editions.svelte +4 -3
- package/dist/components/page/Glossary.svelte +8 -2
- package/dist/components/page/Index.svelte +5 -2
- package/dist/components/page/Media.svelte +5 -2
- package/dist/components/page/Reference.svelte +24 -15
- package/dist/components/page/References.svelte +6 -2
- package/dist/components/page/Search.svelte +5 -3
- package/dist/components/page/SubdomainEditor.svelte +4 -3
- package/dist/components/page/TableOfContents.svelte +42 -11
- package/dist/components/page/TableOfContentsRow.svelte +3 -1
- package/dist/models/CRUD.d.ts +1 -2
- package/dist/models/CRUD.js +4 -6
- package/dist/models/book/BookMedia.js +8 -3
- package/dist/models/book/Chapter.js +2 -3
- package/dist/models/book/Edition.d.ts +10 -4
- package/dist/models/book/Edition.js +35 -34
- package/dist/models/chapter/ParagraphNode.js +4 -14
- package/dist/models/chapter/Parser.d.ts +1 -0
- package/dist/models/chapter/Parser.js +2 -1
- package/dist/models/chapter/TableNode.js +3 -1
- package/dist/util/mineReference.js +8 -6
- package/package.json +13 -10
package/README.md
CHANGED
|
@@ -4,4 +4,33 @@ Bookish is a framework and platform for writing and publishing web-based books.
|
|
|
4
4
|
|
|
5
5
|
## Status
|
|
6
6
|
|
|
7
|
-
I'm working on a
|
|
7
|
+
I'm working on a 1.0 of the platform at [bookish.press](https://bookish.press) and expect to maintain the platform for the foreseeable future. Let me know if you'd like to help!
|
|
8
|
+
|
|
9
|
+
## Deployment notes
|
|
10
|
+
|
|
11
|
+
There are two major components to Bookish: the authoring platform and the reading front end. Changes to the authoring platform that do not affect reading can be verified and deployed to Firebase without any other coordination. However, changes to the reading experience have downstream dependencies that need to be managed. Here's the general deployment workflow, which I'm currently using as reminders for myself:
|
|
12
|
+
|
|
13
|
+
0. Ensure you're working in the `dev` branch
|
|
14
|
+
1. Fix bugs, add features
|
|
15
|
+
2. Ensure there are no type errors (`npm run check`) or failed tests (`npm run test`).
|
|
16
|
+
2. Test with emulator (`npm run emu`), if possible. (Some features aren't possible to perfectly emulate, such as authentication, hosting configurations, permissions.)
|
|
17
|
+
3. Test on the [staging server](https://bookish-dev-21ac3.web.app/`) (`npm run stage`).
|
|
18
|
+
4. Bump the version in `package.json`
|
|
19
|
+
5. Describe the changes in `CHANGELOG.md`, linking to any issues closed
|
|
20
|
+
6. Commit changes to `dev`, referring to any issues addressed
|
|
21
|
+
7. Release to production (`npm run release`)
|
|
22
|
+
8. Verify the change in production on [bookish.press](https://bookish.press)
|
|
23
|
+
9. Merge dev to main (`npm run merge`)
|
|
24
|
+
10. If any of the changes affected the reading experience (essentially if any `.svelte` file or its dependencies in `/src/lib/components` changed), then we need to update the reader package for authors who pre-build their books manually:
|
|
25
|
+
1. Update the bookish package with the Sveltekit package tooling (`npm run package`)
|
|
26
|
+
2. Publish the package to npm (`npm publish`)
|
|
27
|
+
3. Open the `bookish-reader` repository
|
|
28
|
+
4. Pull the revisions (`npm update`), ensuring you update the repository's `package.json` dependencies, in case there was a major version change
|
|
29
|
+
5. Update reader's `CHANGELOG.md` to describe any changes.
|
|
30
|
+
6. Bump the reader's version in `package.json`
|
|
31
|
+
7. Commit the changes directly to `main`, and sync them
|
|
32
|
+
8. For any of these changes to make it to pre-built books, authors need ot use the updated `bookish-reader` repo to rebuild their books. That generally involves:
|
|
33
|
+
1. Cloning the reader repo: `git clone https://github.com/amyjko/bookish-reader`
|
|
34
|
+
2. Entering the repo directory: `cd bookish-reader`
|
|
35
|
+
3. Binding the book: `bind.sh`
|
|
36
|
+
4. Deploying the bound book in the newly created `build` folder to wherever it's hosted.
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
width: 100%;
|
|
10
10
|
overflow-x: auto;
|
|
11
11
|
margin-top: var(--app-text-spacing);
|
|
12
|
-
margin-bottom: var(--app-text-spacing);
|
|
13
12
|
border-top: var(--app-chrome-border-size) solid var(--app-border-color);
|
|
14
13
|
border-bottom: var(--app-chrome-border-size) solid
|
|
15
14
|
var(--app-border-color);
|
|
@@ -5,35 +5,22 @@ import Python from './Python.svelte';
|
|
|
5
5
|
import Text from './Text.svelte';
|
|
6
6
|
import { isChapterEditable } from '../page/Contexts';
|
|
7
7
|
import Figure from './Figure.svelte';
|
|
8
|
-
import
|
|
8
|
+
import Button from '../app/Button.svelte';
|
|
9
9
|
export let node;
|
|
10
10
|
$: language = node.getLanguage();
|
|
11
11
|
let editable = isChapterEditable();
|
|
12
12
|
// const languages = [ "C", "C++", "CSS", "Go", "HTML", "Java", "JavaScript", "Markdown", "Plaintext", "Python", "TypeScript" ];
|
|
13
|
-
let
|
|
14
|
-
$: inside =
|
|
15
|
-
$caret?.range?.start.node === node.getCodeNode() ||
|
|
16
|
-
$caret?.range?.end.node === node.getCodeNode();
|
|
13
|
+
let editing = true;
|
|
17
14
|
</script>
|
|
18
15
|
|
|
19
|
-
<Figure {node} caption={node.getCaption()}>
|
|
20
|
-
{#if editable}
|
|
21
|
-
<div class="container">
|
|
16
|
+
<Figure {node} caption={node.getCaption()} focusable={false}>
|
|
17
|
+
{#if editable && editing}
|
|
18
|
+
<div class="container" class:editing>
|
|
22
19
|
<code
|
|
23
|
-
class={`bookish-code bookish-code-block language-${language}
|
|
24
|
-
!inside ? 'hidden' : ''
|
|
25
|
-
}`}
|
|
20
|
+
class={`bookish-code bookish-code-block language-${language}`}
|
|
26
21
|
>
|
|
27
22
|
<Text node={node.getCodeNode()} />
|
|
28
23
|
</code>
|
|
29
|
-
<div class="code" class:inside>
|
|
30
|
-
<Code
|
|
31
|
-
editable={false}
|
|
32
|
-
inline={false}
|
|
33
|
-
language={node.getLanguage()}
|
|
34
|
-
nodeID={node.getCodeNode().nodeID}>{node.getCode()}</Code
|
|
35
|
-
>
|
|
36
|
-
</div>
|
|
37
24
|
</div>
|
|
38
25
|
{:else if node.getLanguage() === 'python' && node.isExecutable()}
|
|
39
26
|
<Python {node} startCode={node.getCode()} />
|
|
@@ -45,6 +32,15 @@ $: inside =
|
|
|
45
32
|
nodeID={node.getCodeNode().nodeID}>{node.getCode()}</Code
|
|
46
33
|
>
|
|
47
34
|
{/if}
|
|
35
|
+
{#if editable}
|
|
36
|
+
<Button
|
|
37
|
+
tooltip={editing ? 'stop editing' : 'start editing'}
|
|
38
|
+
command={() => {
|
|
39
|
+
editing = !editing;
|
|
40
|
+
}}
|
|
41
|
+
>{#if editing}done{:else}edit{/if}</Button
|
|
42
|
+
>
|
|
43
|
+
{/if}
|
|
48
44
|
{#if node.getLanguage() !== 'plaintext'}<aside class="language"
|
|
49
45
|
>{node.getLanguage()}</aside
|
|
50
46
|
>{/if}
|
|
@@ -66,6 +62,12 @@ $: inside =
|
|
|
66
62
|
.container {
|
|
67
63
|
position: relative;
|
|
68
64
|
}
|
|
65
|
+
|
|
66
|
+
.container.editing {
|
|
67
|
+
border: 1px solid var(--app-interactive-color);
|
|
68
|
+
border-radius: var(--app-chrome-roundedness);
|
|
69
|
+
}
|
|
70
|
+
|
|
69
71
|
.code {
|
|
70
72
|
position: absolute;
|
|
71
73
|
top: 0;
|
|
@@ -6,6 +6,7 @@ import { getCaret, isChapterEditable } from '../page/Contexts';
|
|
|
6
6
|
export let node;
|
|
7
7
|
export let caption;
|
|
8
8
|
export let credit = undefined;
|
|
9
|
+
export let focusable = true;
|
|
9
10
|
let editable = isChapterEditable();
|
|
10
11
|
let caret = getCaret();
|
|
11
12
|
$: selected = $caret?.range?.start.node === node;
|
|
@@ -30,11 +31,12 @@ function focusFigure() {
|
|
|
30
31
|
|
|
31
32
|
<Positioned position={node.getPosition()}>
|
|
32
33
|
<figure class="bookish-figure" data-nodeid={node.nodeID}>
|
|
34
|
+
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
33
35
|
<div
|
|
34
36
|
class:selected
|
|
35
|
-
role=
|
|
36
|
-
tabindex=
|
|
37
|
-
on:mousedown={focusFigure}
|
|
37
|
+
role={focusable ? 'button' : undefined}
|
|
38
|
+
tabindex={focusable ? 0 : undefined}
|
|
39
|
+
on:mousedown={focusable ? focusFigure : null}
|
|
38
40
|
on:keydown={(event) =>
|
|
39
41
|
event.key === ' ' || event.key === 'Enter'
|
|
40
42
|
? focusFigure()
|
|
@@ -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,6 +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, { Reserved } from '../../models/chapter/Parser';
|
|
21
22
|
const IDLE_TIME = 500;
|
|
22
23
|
export let text;
|
|
23
24
|
export let parser;
|
|
@@ -698,14 +699,22 @@ function handleCopy(node) {
|
|
|
698
699
|
window.alert("Your browser doesn't support copying to the clipboard :(");
|
|
699
700
|
}
|
|
700
701
|
function handlePaste(context, text, save) {
|
|
702
|
+
// If we're pasting a node, see what will accept it.
|
|
701
703
|
if (text instanceof BookishNode)
|
|
702
704
|
return handlePasteNode(context, text, save);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
705
|
+
if (text.plain === undefined)
|
|
706
|
+
return undefined;
|
|
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', ' '));
|
|
715
|
+
const edit = handlePasteNode(context, chapter, save);
|
|
716
|
+
if (edit)
|
|
717
|
+
return edit;
|
|
709
718
|
return undefined;
|
|
710
719
|
}
|
|
711
720
|
function handlePasteNode(context, node, save) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script>import { afterUpdate } from 'svelte';
|
|
1
|
+
<script>import { afterUpdate, tick } from 'svelte';
|
|
2
2
|
export let left;
|
|
3
3
|
export let top;
|
|
4
4
|
export let height;
|
|
@@ -11,9 +11,14 @@ export let blink;
|
|
|
11
11
|
export let locked;
|
|
12
12
|
let element;
|
|
13
13
|
let headerHeight = 0;
|
|
14
|
-
// When the caret updates,
|
|
14
|
+
// When the caret updates position, scroll the element into view after the render is complete.
|
|
15
|
+
$: {
|
|
16
|
+
left;
|
|
17
|
+
top;
|
|
18
|
+
tick().then(() => element ? element.scrollIntoView({ block: 'nearest' }) : undefined);
|
|
19
|
+
}
|
|
20
|
+
/** Keep track of the header height, so we can snap to the caret position. */
|
|
15
21
|
afterUpdate(() => {
|
|
16
|
-
element?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
17
22
|
headerHeight =
|
|
18
23
|
document.querySelector('.bookish-app > .header')
|
|
19
24
|
?.offsetHeight ?? 0;
|
|
@@ -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',
|
|
@@ -1471,7 +1489,7 @@ const commands = [
|
|
|
1471
1489
|
const text = await blob.text();
|
|
1472
1490
|
contents.plain = text;
|
|
1473
1491
|
}
|
|
1474
|
-
context.handlePaste(context, contents, true);
|
|
1492
|
+
return context.handlePaste(context, contents, true);
|
|
1475
1493
|
}
|
|
1476
1494
|
catch (err) { }
|
|
1477
1495
|
}
|
|
@@ -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,19 @@
|
|
|
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 async src="https://www.googletagmanager.com/gtag/js?id={id}">
|
|
8
|
+
</script>
|
|
9
|
+
<script>
|
|
10
|
+
window.dataLayer = window.dataLayer || [];
|
|
11
|
+
|
|
12
|
+
function gtag() {
|
|
13
|
+
dataLayer.push(arguments);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
gtag('js', new Date());
|
|
17
|
+
gtag('config', id);
|
|
18
|
+
</script>
|
|
19
|
+
</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 {};
|
|
@@ -213,8 +213,8 @@ let editable = isBookEditable() ||
|
|
|
213
213
|
<Outline
|
|
214
214
|
slot="outline"
|
|
215
215
|
collapse={$marginal !== undefined}
|
|
216
|
-
previous={$edition.getPreviousChapterID(chapterID)}
|
|
217
|
-
next={$edition.getNextChapterID(chapterID)}
|
|
216
|
+
previous={$edition.getPreviousChapterID(chapterID, editable)}
|
|
217
|
+
next={$edition.getNextChapterID(chapterID, editable)}
|
|
218
218
|
listener={(expanded) => {
|
|
219
219
|
// If the outline is being expanded, hide the marginal, otherwise leave it alone.
|
|
220
220
|
if (expanded) marginal.set(undefined);
|
|
@@ -331,9 +331,8 @@ let editable = isBookEditable() ||
|
|
|
331
331
|
{:else}
|
|
332
332
|
<ChapterBody node={chapterAST} />
|
|
333
333
|
{/if}
|
|
334
|
-
{:else}
|
|
335
|
-
|
|
336
|
-
{/if}
|
|
334
|
+
{:else if $edition === undefined}Loading...{:else}Unable to load
|
|
335
|
+
chapter text{/if}
|
|
337
336
|
</section>
|
|
338
337
|
|
|
339
338
|
{#if citations && citations.size > 0}
|
|
@@ -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()} />
|
|
@@ -4,12 +4,13 @@ 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 {
|
|
7
|
+
import { listenToChapters, listenToEdition } from '../../models/CRUD';
|
|
8
8
|
export let write;
|
|
9
9
|
let auth = getUser();
|
|
10
10
|
let book = getBook();
|
|
11
11
|
let edition = getEdition();
|
|
12
|
-
|
|
12
|
+
/** Mapping from Firestore docuemnt IDs to chapter text */
|
|
13
|
+
let chapterText = new Map();
|
|
13
14
|
let editionUnsub = undefined;
|
|
14
15
|
let chaptersUnsub = undefined;
|
|
15
16
|
let error = undefined;
|
|
@@ -54,9 +55,7 @@ function listen() {
|
|
|
54
55
|
editionUnsub = listenToEdition($book.getID(), editionID, (ed) => {
|
|
55
56
|
if (ed) {
|
|
56
57
|
// Before setting the new edition, augmented it with the chapter text.
|
|
57
|
-
edition.set(
|
|
58
|
-
? ed.withChapterText($chapterText)
|
|
59
|
-
: ed);
|
|
58
|
+
edition.set(ed.withChapterText(chapterText));
|
|
60
59
|
}
|
|
61
60
|
else {
|
|
62
61
|
edition.set(undefined);
|
|
@@ -64,20 +63,12 @@ function listen() {
|
|
|
64
63
|
}
|
|
65
64
|
});
|
|
66
65
|
chaptersUnsub = listenToChapters($book.getID(), editionID, (chapters) => {
|
|
67
|
-
// Update the
|
|
68
|
-
|
|
66
|
+
// Update the text of the chapters we received.
|
|
67
|
+
for (const [ref, text] of chapters)
|
|
68
|
+
chapterText.set(ref.id, text);
|
|
69
69
|
// Update the chapter text in the current edition.
|
|
70
|
-
if ($edition)
|
|
71
|
-
|
|
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
|
-
}
|
|
70
|
+
if ($edition)
|
|
71
|
+
edition.set($edition.withChapterText(chapterText));
|
|
81
72
|
});
|
|
82
73
|
}
|
|
83
74
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script>import { createNewEdition } from '../../models/CRUD';
|
|
2
2
|
import Instructions from './Instructions.svelte';
|
|
3
3
|
import Note from '../editor/Note.svelte';
|
|
4
|
-
import { getBook, isBookEditable } from './Contexts';
|
|
4
|
+
import { getBook, getUser, isBookEditable } from './Contexts';
|
|
5
5
|
import Button from '../app/Button.svelte';
|
|
6
6
|
import Link from '../app/Link.svelte';
|
|
7
7
|
import Format from '../chapter/Format.svelte';
|
|
@@ -10,6 +10,7 @@ import Permissions from '../editor/Permissions.svelte';
|
|
|
10
10
|
export let editors = true;
|
|
11
11
|
let book = getBook();
|
|
12
12
|
let editable = isBookEditable();
|
|
13
|
+
let user = getUser();
|
|
13
14
|
$: editions = $book?.getEditions();
|
|
14
15
|
$: latestPublishedID = $book?.getLatestPublishedEditionID();
|
|
15
16
|
async function handleDraftEdition() {
|
|
@@ -48,7 +49,7 @@ async function handleDraftEdition() {
|
|
|
48
49
|
? 'rd'
|
|
49
50
|
: 'th')}
|
|
50
51
|
|
|
51
|
-
{#if $book && (editable || edition.published)}
|
|
52
|
+
{#if $book && (editable || edition.published || ($user?.user?.uid && edition.editionuids.includes($user.user.uid)))}
|
|
52
53
|
<section class="edition">
|
|
53
54
|
<h3
|
|
54
55
|
>{editionLabel}
|
|
@@ -68,7 +69,7 @@ async function handleDraftEdition() {
|
|
|
68
69
|
}`}
|
|
69
70
|
external>read</Link
|
|
70
71
|
>{/if}
|
|
71
|
-
{#if editable}
|
|
72
|
+
{#if editable || ($user?.user?.uid && edition.editionuids.includes($user?.user.uid))}
|
|
72
73
|
|
|
73
74
|
<Link
|
|
74
75
|
to={`/write/${$book.getID()}/${
|
|
@@ -50,8 +50,14 @@ function addEmptyDefinition() {
|
|
|
50
50
|
>
|
|
51
51
|
<Outline
|
|
52
52
|
slot="outline"
|
|
53
|
-
previous={$edition.getPreviousChapterID(
|
|
54
|
-
|
|
53
|
+
previous={$edition.getPreviousChapterID(
|
|
54
|
+
ChapterIDs.GlossaryID,
|
|
55
|
+
editable
|
|
56
|
+
)}
|
|
57
|
+
next={$edition.getNextChapterID(
|
|
58
|
+
ChapterIDs.GlossaryID,
|
|
59
|
+
editable
|
|
60
|
+
)}
|
|
55
61
|
/>
|
|
56
62
|
</Header>
|
|
57
63
|
{#if editable}
|
|
@@ -57,8 +57,11 @@ $: {
|
|
|
57
57
|
>
|
|
58
58
|
<Outline
|
|
59
59
|
slot="outline"
|
|
60
|
-
previous={$edition.getPreviousChapterID(
|
|
61
|
-
|
|
60
|
+
previous={$edition.getPreviousChapterID(
|
|
61
|
+
ChapterIDs.IndexID,
|
|
62
|
+
editable
|
|
63
|
+
)}
|
|
64
|
+
next={$edition.getNextChapterID(ChapterIDs.IndexID, editable)}
|
|
62
65
|
/>
|
|
63
66
|
</Header>
|
|
64
67
|
|
|
@@ -43,8 +43,11 @@ $: unused = images?.filter((image) => !embeds.some((embed) => image.url.includes
|
|
|
43
43
|
>
|
|
44
44
|
<Outline
|
|
45
45
|
slot="outline"
|
|
46
|
-
previous={$edition.getPreviousChapterID(
|
|
47
|
-
|
|
46
|
+
previous={$edition.getPreviousChapterID(
|
|
47
|
+
ChapterIDs.MediaID,
|
|
48
|
+
editable
|
|
49
|
+
)}
|
|
50
|
+
next={$edition.getNextChapterID(ChapterIDs.MediaID, editable)}
|
|
48
51
|
/>
|
|
49
52
|
</Header>
|
|
50
53
|
<Instructions {editable}>
|