bookish-press 0.5.2 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BookPreview.svelte +1 -0
- package/dist/components/chapter/Callout.svelte +0 -1
- package/dist/components/chapter/Embed.svelte +8 -6
- package/dist/components/chapter/ExtendedPrism.d.ts +1 -1
- package/dist/components/chapter/Figure.svelte +6 -1
- package/dist/components/chapter/Footnote.svelte +1 -0
- package/dist/components/chapter/List.svelte +16 -33
- package/dist/components/chapter/Marginal.svelte +4 -1
- package/dist/components/chapter/Paragraph.svelte +7 -2
- package/dist/components/chapter/Quote.svelte +6 -6
- package/dist/components/chapter/Table.svelte +4 -0
- package/dist/components/controls/DarkToggle.svelte +1 -1
- package/dist/components/editor/BookishEditor.svelte +21 -26
- package/dist/components/editor/Commands.js +14 -14
- package/dist/components/editor/ConfirmButton.svelte +8 -2
- package/dist/components/editor/EmbedEditor.svelte +3 -3
- package/dist/components/editor/ImageChooser.svelte +8 -27
- package/dist/components/editor/ImageThumbnail.svelte +65 -0
- package/dist/components/editor/ImageThumbnail.svelte.d.ts +19 -0
- package/dist/components/editor/Permissions.svelte +2 -0
- package/dist/components/editor/TextEditor.svelte +5 -2
- package/dist/components/editor/Toggle.svelte +1 -1
- package/dist/components/editor/Toolbar.svelte +2 -1
- package/dist/components/page/Chapter.svelte +1 -1
- package/dist/components/page/DefinitionView.svelte +43 -38
- package/dist/components/page/Edition.svelte +41 -68
- package/dist/components/page/EditionLoader.svelte +12 -3
- package/dist/components/page/Editions.svelte +1 -1
- package/dist/components/page/Header.svelte +73 -69
- package/dist/components/page/Media.svelte +5 -4
- package/dist/components/page/MediaPreview.svelte +8 -2
- package/dist/components/page/MediaPreview.svelte.d.ts +1 -0
- package/dist/components/page/Outline.svelte +1 -1
- package/dist/components/page/Page.svelte +6 -3
- package/dist/components/page/Reference.svelte +1 -1
- package/dist/components/page/Search.svelte +1 -1
- package/dist/components/page/TableOfContents.svelte +3 -6
- package/dist/components/page/TableOfContentsRow.svelte +1 -1
- package/dist/components/page/ThemeEditor.svelte +1 -1
- package/dist/models/CRUD.d.ts +2 -1
- package/dist/models/CRUD.js +18 -5
- package/dist/models/book/BookMedia.d.ts +3 -0
- package/dist/models/book/BookMedia.js +22 -5
- package/dist/models/book/Chapter.d.ts +9 -7
- package/dist/models/book/Chapter.js +16 -6
- package/dist/models/book/Edition.d.ts +6 -1
- package/dist/models/book/Edition.js +22 -10
- package/dist/models/book/Words.js +0 -1
- package/dist/models/chapter/ChapterNode.d.ts +1 -0
- package/dist/models/chapter/ChapterNode.js +3 -0
- package/dist/models/chapter/DefinitionNode.js +1 -1
- package/dist/models/chapter/FormatNode.js +4 -1
- package/dist/models/chapter/InlineCodeNode.js +1 -1
- package/dist/models/chapter/LinkNode.js +1 -1
- package/dist/models/chapter/ListNode.js +1 -1
- package/dist/models/chapter/Parser.js +3 -6
- package/package.json +25 -23
|
@@ -154,10 +154,11 @@ function handleKeyPress(event) {
|
|
|
154
154
|
<section
|
|
155
155
|
class="bookish-editor-toolbar"
|
|
156
156
|
bind:this={element}
|
|
157
|
+
role="button"
|
|
157
158
|
on:keypress={handleKeyPress}
|
|
158
159
|
on:keydown={handleKeyPress}
|
|
159
160
|
on:mousedown|stopPropagation={() => element?.focus()}
|
|
160
|
-
|
|
161
|
+
tabindex="0"
|
|
161
162
|
transition:slide={{ duration: 200 }}
|
|
162
163
|
>
|
|
163
164
|
{#if context && categories}
|
|
@@ -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
|
|
@@ -5,13 +5,13 @@ import BookishEditor from '../editor/BookishEditor.svelte';
|
|
|
5
5
|
import Format from '../chapter/Format.svelte';
|
|
6
6
|
import Note from '../editor/Note.svelte';
|
|
7
7
|
import { afterUpdate } from 'svelte';
|
|
8
|
-
import { getUser, getEdition, getLeasee, isChapterEditable, lease, } from './Contexts';
|
|
8
|
+
import { getUser, getEdition, getLeasee, isChapterEditable, lease, isEditionEditable, } from './Contexts';
|
|
9
9
|
import Button from '../app/Button.svelte';
|
|
10
10
|
export let id;
|
|
11
11
|
export let definition;
|
|
12
12
|
let auth = getUser();
|
|
13
13
|
let edition = getEdition();
|
|
14
|
-
let editable =
|
|
14
|
+
let editable = isEditionEditable();
|
|
15
15
|
// Focus after adding a new synonym.
|
|
16
16
|
let newSynonym = false;
|
|
17
17
|
let synonymsEditor = null;
|
|
@@ -36,6 +36,23 @@ function addSynonym() {
|
|
|
36
36
|
}
|
|
37
37
|
newSynonym = true;
|
|
38
38
|
}
|
|
39
|
+
function removeSynonym(index) {
|
|
40
|
+
if (definition.synonyms &&
|
|
41
|
+
index >= 0 &&
|
|
42
|
+
index < definition.synonyms.length)
|
|
43
|
+
updateSynonyms([
|
|
44
|
+
...definition.synonyms.slice(0, index),
|
|
45
|
+
...definition.synonyms.slice(index + 1),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
function updateSynonyms(synonyms) {
|
|
49
|
+
if ($edition)
|
|
50
|
+
edition.set($edition.withEditedDefinition(id, {
|
|
51
|
+
phrase: definition.phrase,
|
|
52
|
+
definition: definition.definition,
|
|
53
|
+
synonyms: synonyms,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
39
56
|
$: syns = definition.synonyms || [];
|
|
40
57
|
</script>
|
|
41
58
|
|
|
@@ -67,11 +84,11 @@ $: syns = definition.synonyms || [];
|
|
|
67
84
|
{#if editable}
|
|
68
85
|
<ConfirmButton
|
|
69
86
|
tooltip="delete definition of {definition.phrase}"
|
|
70
|
-
confirm="
|
|
87
|
+
confirm="delete entry"
|
|
71
88
|
command={() =>
|
|
72
89
|
$edition
|
|
73
90
|
? edition.set($edition.withoutDefinition(id))
|
|
74
|
-
: undefined}
|
|
91
|
+
: undefined}>⨉</ConfirmButton
|
|
75
92
|
>
|
|
76
93
|
{/if}
|
|
77
94
|
</dt>
|
|
@@ -111,40 +128,26 @@ $: syns = definition.synonyms || [];
|
|
|
111
128
|
{/if}
|
|
112
129
|
{#if edition && editable}
|
|
113
130
|
<span bind:this={synonymsEditor}>
|
|
114
|
-
{#
|
|
115
|
-
<Note>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
{
|
|
135
|
-
phrase: definition.phrase,
|
|
136
|
-
definition:
|
|
137
|
-
definition.definition,
|
|
138
|
-
synonyms: newSyns,
|
|
139
|
-
}
|
|
140
|
-
)
|
|
141
|
-
);
|
|
142
|
-
}}
|
|
143
|
-
/>
|
|
144
|
-
</Note>
|
|
145
|
-
{#if syns.length > 1 && index < syns.length - 1}, {/if}
|
|
146
|
-
{/each}
|
|
147
|
-
{/if}
|
|
131
|
+
{#each syns as syn, index}
|
|
132
|
+
<Note>
|
|
133
|
+
<TextEditor
|
|
134
|
+
text={syn}
|
|
135
|
+
label={'Synonym editor.'}
|
|
136
|
+
placeholder="synonym"
|
|
137
|
+
valid={() => undefined}
|
|
138
|
+
save={(text) => {
|
|
139
|
+
const newSyns = [...syns];
|
|
140
|
+
newSyns[index] = text;
|
|
141
|
+
updateSynonyms(newSyns);
|
|
142
|
+
}}
|
|
143
|
+
/>
|
|
144
|
+
<Button
|
|
145
|
+
tooltip="delete synonym"
|
|
146
|
+
command={() => removeSynonym(index)}>x</Button
|
|
147
|
+
>
|
|
148
|
+
</Note>
|
|
149
|
+
{#if syns.length > 1 && index < syns.length - 1}, {/if}
|
|
150
|
+
{/each}
|
|
148
151
|
</span> <Button tooltip="add synonym" command={addSynonym}
|
|
149
152
|
>+ synonym</Button
|
|
150
153
|
>
|
|
@@ -162,6 +165,8 @@ $: syns = definition.synonyms || [];
|
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
dt {
|
|
168
|
+
font-family: var(--bookish-header-font-family);
|
|
165
169
|
font-weight: bold;
|
|
170
|
+
vertical-align: baseline;
|
|
166
171
|
}
|
|
167
172
|
</style>
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import { goto } from '$app/navigation';
|
|
3
3
|
import { onMount, setContext, tick } from 'svelte';
|
|
4
4
|
import { writable } from 'svelte/store';
|
|
5
|
-
import { BASE, DARK_MODE, } from './Contexts';
|
|
5
|
+
import { BASE, DARK_MODE, getBook, } from './Contexts';
|
|
6
6
|
import { BookishTheme } from '../../models/book/Theme';
|
|
7
7
|
import { isDark, setDark } from '../../util/dark';
|
|
8
|
+
import { page } from '$app/stores';
|
|
8
9
|
// Poly fill smooth scrolling for Safari.
|
|
9
10
|
onMount(() => smoothscroll.polyfill());
|
|
10
11
|
// The book edition to render.
|
|
@@ -14,6 +15,7 @@ export let edition;
|
|
|
14
15
|
// but when the book is being viewed or edited in the Bookish app, it needs a prefix for the
|
|
15
16
|
// route in the app.
|
|
16
17
|
export let base = '';
|
|
18
|
+
let book = getBook();
|
|
17
19
|
// When the base changes, update the context.
|
|
18
20
|
let baseStore = writable(base);
|
|
19
21
|
$: setContext(BASE, baseStore);
|
|
@@ -29,6 +31,8 @@ $: {
|
|
|
29
31
|
// Set the theme, whatever it is, and change it when the edition changes
|
|
30
32
|
setTheme(edition.getTheme() ?? BookishTheme);
|
|
31
33
|
}
|
|
34
|
+
// The CSS to set on the edition.
|
|
35
|
+
let themeCSS;
|
|
32
36
|
// Set the theme on mount.
|
|
33
37
|
onMount(() => {
|
|
34
38
|
setTheme(edition.getTheme() ?? BookishTheme);
|
|
@@ -36,74 +40,25 @@ onMount(() => {
|
|
|
36
40
|
// Redirect old hash routes by simply replacing their hash before routing.
|
|
37
41
|
if (typeof window !== 'undefined' && window.location.hash.startsWith('#/'))
|
|
38
42
|
goto(location.hash.replace('#', ''));
|
|
39
|
-
/** Given a theme, sets the appropriate CSS rules in the browser to apply the theme
|
|
43
|
+
/** Given a theme, sets the appropriate CSS rules in the browser to apply the theme. */
|
|
40
44
|
function setTheme(theme) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// If the imports changed, update theme.
|
|
59
|
-
if (themeTagImportsValue !== newThemeTagImportsValue) {
|
|
60
|
-
// If it's being set, create a new style tag.
|
|
61
|
-
const newThemeImportsTag = document.createElement('style');
|
|
62
|
-
// Give it an ID so we can remove it later.
|
|
63
|
-
newThemeImportsTag.setAttribute('id', 'bookish-theme-imports');
|
|
64
|
-
newThemeImportsTag.dataset.imports = newThemeTagImportsValue;
|
|
65
|
-
// Insert any import statements, then any rules.
|
|
66
|
-
const css = `${(theme.imports ?? [])
|
|
67
|
-
.map((url) => `@import url(${url});`)
|
|
68
|
-
.join('\n')}`;
|
|
69
|
-
newThemeImportsTag.appendChild(document.createTextNode(css));
|
|
70
|
-
document.head.appendChild(newThemeImportsTag);
|
|
71
|
-
// Remove the old tag
|
|
72
|
-
themeTagImports?.remove();
|
|
73
|
-
}
|
|
74
|
-
// If the rules changed, update them.
|
|
75
|
-
if (themeTagCSSValue !== newThemeTagCSSValue) {
|
|
76
|
-
// If it's being set, create a new style tag.
|
|
77
|
-
const newThemeCSSTag = document.createElement('style');
|
|
78
|
-
// Give it an ID so we can remove it later.
|
|
79
|
-
newThemeCSSTag.setAttribute('id', 'bookish-theme');
|
|
80
|
-
newThemeCSSTag.dataset.css = newThemeTagCSSValue;
|
|
81
|
-
// Insert any import statements, then any rules.
|
|
82
|
-
const css = `
|
|
83
|
-
${(theme.imports ?? [])
|
|
84
|
-
.map((url) => `@import url(${url});`)
|
|
85
|
-
.join('\n')}
|
|
86
|
-
.bookish {
|
|
87
|
-
${theme.light ? toRules(theme.light) : ''}
|
|
88
|
-
${theme.fonts ? toRules(theme.fonts) : ''}
|
|
89
|
-
${theme.sizes ? toRules(theme.sizes) : ''}
|
|
90
|
-
${theme.weights ? toRules(theme.weights) : ''}
|
|
91
|
-
${theme.spacing ? toRules(theme.spacing) : ''}
|
|
92
|
-
}
|
|
93
|
-
.dark {
|
|
94
|
-
${theme.dark ? toRules(theme.dark) : ''}
|
|
95
|
-
}`;
|
|
96
|
-
newThemeCSSTag.appendChild(document.createTextNode(css));
|
|
97
|
-
document.head.appendChild(newThemeCSSTag);
|
|
98
|
-
// Remove the old tag
|
|
99
|
-
themeTagCSS?.remove();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
// Remove the old tags, unsetting the theme.
|
|
104
|
-
themeTagImports?.remove();
|
|
105
|
-
themeTagCSS?.remove();
|
|
106
|
-
}
|
|
45
|
+
if (theme === null)
|
|
46
|
+
theme = BookishTheme;
|
|
47
|
+
themeCSS = `
|
|
48
|
+
${(theme.imports ?? [])
|
|
49
|
+
.map((url) => `@import url(${url});`)
|
|
50
|
+
.join('\n')}
|
|
51
|
+
.bookish {
|
|
52
|
+
${theme.light ? toRules(theme.light) : ''}
|
|
53
|
+
${theme.fonts ? toRules(theme.fonts) : ''}
|
|
54
|
+
${theme.sizes ? toRules(theme.sizes) : ''}
|
|
55
|
+
${theme.weights ? toRules(theme.weights) : ''}
|
|
56
|
+
${theme.spacing ? toRules(theme.spacing) : ''}
|
|
57
|
+
}
|
|
58
|
+
.dark {
|
|
59
|
+
${theme.dark ? toRules(theme.dark) : ''}
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
107
62
|
}
|
|
108
63
|
function toRules(set) {
|
|
109
64
|
return Object.keys(set)
|
|
@@ -124,6 +79,24 @@ function toRules(set) {
|
|
|
124
79
|
}
|
|
125
80
|
</script>
|
|
126
81
|
|
|
82
|
+
<!-- Keep the imports up to date -->
|
|
83
|
+
<svelte:head>
|
|
84
|
+
{@html '<' + `style>${themeCSS}</style>`}
|
|
85
|
+
|
|
86
|
+
<meta property="og:title" content={edition.getTitle()} />
|
|
87
|
+
<meta property="og:image" content={edition.getImage('cover')} />
|
|
88
|
+
<meta property="og:description" content={edition.getDescription()} />
|
|
89
|
+
<meta property="og:type" content="book" />
|
|
90
|
+
<meta property="og:author" content={edition.getAuthorsText()} />
|
|
91
|
+
<meta
|
|
92
|
+
property="og:url"
|
|
93
|
+
content={edition.base ??
|
|
94
|
+
`https://bookish.press/${
|
|
95
|
+
$book && $book.domain ? $book.domain : $page.params.bookid
|
|
96
|
+
}`}
|
|
97
|
+
/>
|
|
98
|
+
</svelte:head>
|
|
99
|
+
|
|
127
100
|
<main class="bookish {$darkMode ? ' dark' : ''}">
|
|
128
101
|
<slot />
|
|
129
102
|
</main>
|
|
@@ -4,7 +4,7 @@ import Feedback from '../app/Feedback.svelte';
|
|
|
4
4
|
import { getBook, getEdition, getUser, getChapterText, } from './Contexts';
|
|
5
5
|
import { onDestroy } from 'svelte';
|
|
6
6
|
import { page } from '$app/stores';
|
|
7
|
-
import { listenToChapters, listenToEdition } from '../../models/CRUD';
|
|
7
|
+
import { deleteChapterText, listenToChapters, listenToEdition, } from '../../models/CRUD';
|
|
8
8
|
export let write;
|
|
9
9
|
let auth = getUser();
|
|
10
10
|
let book = getBook();
|
|
@@ -67,8 +67,17 @@ function listen() {
|
|
|
67
67
|
// Update the chapter text store.
|
|
68
68
|
chapterText.set(chapters);
|
|
69
69
|
// Update the chapter text in the current edition.
|
|
70
|
-
if ($edition)
|
|
71
|
-
|
|
70
|
+
if ($edition) {
|
|
71
|
+
const newEdition = $edition.withChapterText(chapters);
|
|
72
|
+
if (newEdition !== $edition)
|
|
73
|
+
edition.set(newEdition);
|
|
74
|
+
// Get all the chapters in the updated chapter list and
|
|
75
|
+
// delete the ones not referenced by the edition.
|
|
76
|
+
chapters
|
|
77
|
+
.filter(([ref]) => $edition &&
|
|
78
|
+
!$edition.hasChapterRefID(ref.id))
|
|
79
|
+
.forEach(([ref]) => deleteChapterText(ref));
|
|
80
|
+
}
|
|
72
81
|
});
|
|
73
82
|
}
|
|
74
83
|
}
|
|
@@ -54,80 +54,84 @@ let embed;
|
|
|
54
54
|
$: embed = getImage();
|
|
55
55
|
</script>
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{/if}
|
|
86
|
-
{#if !print}
|
|
87
|
-
<slot name="outline" />
|
|
88
|
-
{/if}
|
|
89
|
-
{#if editable}
|
|
90
|
-
{#if embed === null}
|
|
91
|
-
<Button tooltip="add cover image" command={addCover}
|
|
92
|
-
>+ cover image</Button
|
|
93
|
-
>
|
|
57
|
+
<!-- We key on the chapter ID to avoid laggy updates from image loading -->
|
|
58
|
+
{#key id}
|
|
59
|
+
<header class="bookish-chapter-header">
|
|
60
|
+
{#if embed}
|
|
61
|
+
<div class="bookish-figure-full">
|
|
62
|
+
{#if $edition && editable}
|
|
63
|
+
<BookishEditor
|
|
64
|
+
text={embed}
|
|
65
|
+
parser={(text) => Parser.parseEmbed($edition, text)}
|
|
66
|
+
save={(node) =>
|
|
67
|
+
setImage ? setImage(node.toBookdown()) : undefined}
|
|
68
|
+
chapter={false}
|
|
69
|
+
component={Embed}
|
|
70
|
+
placeholder=""
|
|
71
|
+
leasee={getLeasee(auth, edition, `embed-${id}`)}
|
|
72
|
+
lease={(lock) =>
|
|
73
|
+
lease(auth, edition, `embed-${id}`, lock)}
|
|
74
|
+
/>
|
|
75
|
+
{:else}
|
|
76
|
+
<Embed
|
|
77
|
+
node={Parser.parseEmbed($edition, embed)}
|
|
78
|
+
editable={false}
|
|
79
|
+
/>
|
|
80
|
+
{/if}
|
|
81
|
+
{#if !print && showReminder}
|
|
82
|
+
<div class="bookish-scroll-reminder" />
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
|
94
85
|
{:else}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
tooltip="remove cover image"
|
|
98
|
-
command={removeCover}>- cover image</ConfirmButton
|
|
99
|
-
>
|
|
86
|
+
<!-- Add a bit of space to account for the lack of an image. -->
|
|
87
|
+
<p> </p>
|
|
100
88
|
{/if}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
{#if
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
placeholder="title"
|
|
110
|
-
valid={(text) =>
|
|
111
|
-
text.length === 0
|
|
112
|
-
? 'Titles have to be at least one character long.'
|
|
113
|
-
: undefined}
|
|
114
|
-
{save}
|
|
115
|
-
/>
|
|
89
|
+
{#if !print}
|
|
90
|
+
<slot name="outline" />
|
|
91
|
+
{/if}
|
|
92
|
+
{#if editable}
|
|
93
|
+
{#if embed === null}
|
|
94
|
+
<Button tooltip="add cover image" command={addCover}
|
|
95
|
+
>+ cover image</Button
|
|
96
|
+
>
|
|
116
97
|
{:else}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
98
|
+
<ConfirmButton
|
|
99
|
+
confirm="delete image"
|
|
100
|
+
tooltip="delete cover image"
|
|
101
|
+
command={removeCover}>- cover image</ConfirmButton
|
|
102
|
+
>
|
|
120
103
|
{/if}
|
|
121
|
-
</Title>
|
|
122
|
-
<slot name="after" />
|
|
123
|
-
{#if tags}
|
|
124
|
-
<div
|
|
125
|
-
>{#each tags as tag}<span class="bookish-tag">{tag}</span
|
|
126
|
-
>{/each}</div
|
|
127
|
-
>
|
|
128
104
|
{/if}
|
|
129
|
-
|
|
130
|
-
|
|
105
|
+
<div bind:this={title} class="bookish-chapter-header-text">
|
|
106
|
+
<slot name="before" />
|
|
107
|
+
<Title>
|
|
108
|
+
{#if editable && save}
|
|
109
|
+
<TextEditor
|
|
110
|
+
{label}
|
|
111
|
+
text={header + (subtitle ? ': ' + subtitle : '')}
|
|
112
|
+
placeholder="title"
|
|
113
|
+
valid={(text) =>
|
|
114
|
+
text.length === 0
|
|
115
|
+
? 'Titles have to be at least one character long.'
|
|
116
|
+
: undefined}
|
|
117
|
+
{save}
|
|
118
|
+
/>
|
|
119
|
+
{:else}
|
|
120
|
+
{header}
|
|
121
|
+
{#if subtitle}<div class="bookish-subtitle">{subtitle}</div
|
|
122
|
+
>{/if}
|
|
123
|
+
{/if}
|
|
124
|
+
</Title>
|
|
125
|
+
<slot name="after" />
|
|
126
|
+
{#if tags}
|
|
127
|
+
<div
|
|
128
|
+
>{#each tags as tag}<span class="bookish-tag">{tag}</span
|
|
129
|
+
>{/each}</div
|
|
130
|
+
>
|
|
131
|
+
{/if}
|
|
132
|
+
</div>
|
|
133
|
+
</header>
|
|
134
|
+
{/key}
|
|
131
135
|
|
|
132
136
|
<style>
|
|
133
137
|
.bookish-subtitle {
|
|
@@ -24,7 +24,7 @@ onMount(() => {
|
|
|
24
24
|
media?.notify(updateImages);
|
|
25
25
|
return () => media?.stopNotifying(updateImages);
|
|
26
26
|
});
|
|
27
|
-
$: unused = images?.filter((image) => embeds.
|
|
27
|
+
$: unused = images?.filter((image) => !embeds.some((embed) => image.url.includes(embed.embed.getURL())));
|
|
28
28
|
</script>
|
|
29
29
|
|
|
30
30
|
{#if $edition}
|
|
@@ -59,9 +59,10 @@ $: unused = images?.filter((image) => embeds.find((embed) => embed.getURL() ===
|
|
|
59
59
|
{:else}
|
|
60
60
|
<p>These are the images in the book:</p>
|
|
61
61
|
{/if}
|
|
62
|
-
{#each embeds as embed}
|
|
62
|
+
{#each embeds as { embed, chapterID }}
|
|
63
63
|
{#if !embed.isVideo()}
|
|
64
64
|
<MediaPreview
|
|
65
|
+
{chapterID}
|
|
65
66
|
url={embed.isLocal()
|
|
66
67
|
? `${$base}/${embed.getSmallURL()}`
|
|
67
68
|
: embed.getSmallURL()}
|
|
@@ -89,10 +90,10 @@ $: unused = images?.filter((image) => embeds.find((embed) => embed.getURL() ===
|
|
|
89
90
|
be used in other editions of this book.
|
|
90
91
|
</Instructions>
|
|
91
92
|
{#each unused as image}
|
|
92
|
-
<MediaPreview url={image.url} alt={''}>
|
|
93
|
+
<MediaPreview chapterID={undefined} url={image.url} alt={''}>
|
|
93
94
|
<ConfirmButton
|
|
94
95
|
tooltip="delete unused image"
|
|
95
|
-
confirm="delete image
|
|
96
|
+
confirm="delete image"
|
|
96
97
|
command={() =>
|
|
97
98
|
media
|
|
98
99
|
?.remove(image)
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script>import Link from '../Link.svelte';
|
|
2
|
+
import { getBase } from './Contexts';
|
|
3
|
+
export let url;
|
|
2
4
|
export let alt;
|
|
5
|
+
export let chapterID;
|
|
6
|
+
let base = getBase();
|
|
3
7
|
let loaded;
|
|
4
8
|
let urlChecked;
|
|
5
9
|
$: {
|
|
@@ -18,7 +22,9 @@ $: {
|
|
|
18
22
|
|
|
19
23
|
<figure class={'media-preview'}>
|
|
20
24
|
{#if loaded}
|
|
21
|
-
<
|
|
25
|
+
<Link to={`${$base}/${chapterID}`} linked={chapterID !== undefined}
|
|
26
|
+
><img src={url.startsWith('http') ? url : `${url}`} {alt} /></Link
|
|
27
|
+
>
|
|
22
28
|
{:else if loaded === null}
|
|
23
29
|
<div class="missing">missing image</div>
|
|
24
30
|
{/if}
|
|
@@ -129,7 +129,7 @@ let expandLabel = '\u2630';
|
|
|
129
129
|
aria-label={expanded
|
|
130
130
|
? 'Collapse navigation menu'
|
|
131
131
|
: 'Expand navigation menu'}
|
|
132
|
-
|
|
132
|
+
tabindex="0"
|
|
133
133
|
on:click={headers.length > 0 ? toggleExpanded : undefined}
|
|
134
134
|
on:keydown={(event) => {
|
|
135
135
|
if (event.key === 'Enter' || event.key === ' ')
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import Loading from './Loading.svelte';
|
|
3
3
|
export let title;
|
|
4
4
|
export let afterLoaded = undefined;
|
|
5
|
-
let
|
|
5
|
+
let fontsLoaded = false;
|
|
6
|
+
let imagesLoaded = false;
|
|
6
7
|
let lastHeight = 0;
|
|
7
8
|
let intervalID = null;
|
|
8
9
|
let mountTime = Date.now();
|
|
10
|
+
$: loaded = fontsLoaded && imagesLoaded;
|
|
9
11
|
function watchLoading() {
|
|
10
12
|
const bodyHeight = document.body.clientHeight;
|
|
11
13
|
// If the height hasn't changed and images are loaded OR it's been 2 seconds, load.
|
|
@@ -14,7 +16,7 @@ function watchLoading() {
|
|
|
14
16
|
// Stop watching the images
|
|
15
17
|
stopWatching();
|
|
16
18
|
// Set to loaded to hide the overlay and notify callback if there is one.
|
|
17
|
-
|
|
19
|
+
imagesLoaded = true;
|
|
18
20
|
if (afterLoaded)
|
|
19
21
|
afterLoaded();
|
|
20
22
|
}
|
|
@@ -29,6 +31,7 @@ onMount(() => {
|
|
|
29
31
|
// Watch the height of the document and wait until it's been stable for a while before scrolling
|
|
30
32
|
// to any targets. We use the data below to monitor the document height over time.
|
|
31
33
|
intervalID = setInterval(() => watchLoading(), 50);
|
|
34
|
+
document.fonts.ready.then(() => (fontsLoaded = true));
|
|
32
35
|
return () => stopWatching();
|
|
33
36
|
});
|
|
34
37
|
function imagesAreLoaded() {
|
|
@@ -45,7 +48,7 @@ function imagesAreLoaded() {
|
|
|
45
48
|
<meta name="description" content={title} />
|
|
46
49
|
</svelte:head>
|
|
47
50
|
|
|
48
|
-
<section class={'bookish-page' + (
|
|
51
|
+
<section class={'bookish-page' + (imagesLoaded ? ' loaded' : '')}>
|
|
49
52
|
<slot />
|
|
50
53
|
</section>
|
|
51
54
|
<!-- Overlay loading feedback if loading, but still render the page. -->
|
|
@@ -249,7 +249,7 @@ function move(field, direction) {
|
|
|
249
249
|
{#if edit && editable && !reference.short}
|
|
250
250
|
<ConfirmButton
|
|
251
251
|
tooltip="delete reference {reference.title}"
|
|
252
|
-
confirm="delete reference
|
|
252
|
+
confirm="delete reference"
|
|
253
253
|
command={() =>
|
|
254
254
|
$edition
|
|
255
255
|
? edition.set($edition.withoutReference(reference.citationID))
|
|
@@ -138,7 +138,7 @@ onMount(() => {
|
|
|
138
138
|
</Instructions>
|
|
139
139
|
|
|
140
140
|
<PageHeader id="chapters"
|
|
141
|
-
>Chapters
|
|
141
|
+
>Chapters{#if editable} <Button
|
|
142
142
|
tooltip="add empty chapter"
|
|
143
143
|
disabled={waitingForChapter}
|
|
144
144
|
command={addChapter}>+ chapter</Button
|
|
@@ -273,10 +273,7 @@ onMount(() => {
|
|
|
273
273
|
|
|
274
274
|
<!-- Book citation -->
|
|
275
275
|
<PageParagraph>
|
|
276
|
-
{$edition
|
|
277
|
-
.getAuthors()
|
|
278
|
-
.map((author) => Parser.parseFormat($edition, author).toText())
|
|
279
|
-
.join(', ')} ({new Date().getFullYear()}).
|
|
276
|
+
{$edition.getAuthorsText()} ({new Date().getFullYear()}).
|
|
280
277
|
<em>{$edition.getTitle()}</em>. {bookURL},
|
|
281
278
|
<em>retrieved {new Date().toLocaleDateString('en-US')}</em>.
|
|
282
279
|
</PageParagraph>
|
|
@@ -292,7 +289,7 @@ onMount(() => {
|
|
|
292
289
|
{/if}
|
|
293
290
|
|
|
294
291
|
{#if $book && (editable || isEditionPartiallyEditable())}
|
|
295
|
-
<PageHeader>Editors</PageHeader>
|
|
292
|
+
<PageHeader id="editors">Editors</PageHeader>
|
|
296
293
|
<Instructions {editable}>
|
|
297
294
|
These authors can edit this edition and its chapters. To give
|
|
298
295
|
book-level permissions, edit the book editors on the <Link
|