bookish-press 0.4.22 → 0.4.24
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/components/Auth.svelte +9 -11
- package/components/BookPreview.svelte +12 -14
- package/components/app/Feedback.svelte +12 -10
- package/components/app/Head.svelte +4 -1
- package/components/app/Head.svelte.d.ts +6 -15
- package/components/app/Paragraph.svelte +1 -0
- package/components/app/{Writing.svelte → Section.svelte} +0 -1
- package/components/app/Section.svelte.d.ts +27 -0
- package/components/app/TextInput.svelte +1 -0
- package/components/chapter/Block.svelte +3 -1
- package/components/chapter/CaptionedCode.svelte +2 -2
- package/components/chapter/ChapterBody.svelte +2 -1
- package/components/chapter/ChapterBody.svelte.d.ts +1 -0
- package/components/chapter/Comment.svelte +2 -2
- package/components/chapter/Embed.svelte +3 -3
- package/components/chapter/Embed.svelte.d.ts +1 -0
- package/components/chapter/Figure.svelte +2 -2
- package/components/chapter/Format.svelte +5 -3
- package/components/chapter/Format.svelte.d.ts +1 -0
- package/components/chapter/InlineCode.svelte +2 -2
- package/components/chapter/Label.svelte +2 -2
- package/components/chapter/Link.svelte +2 -2
- package/components/chapter/Marginal.svelte +2 -2
- package/components/chapter/Paragraph.svelte +5 -0
- package/components/chapter/Table.svelte +2 -2
- package/components/chapter/Text.svelte +2 -2
- package/components/editor/BookishEditor.svelte +51 -28
- package/components/editor/BookishEditor.svelte.d.ts +4 -1
- package/components/editor/CaretView.svelte +3 -1
- package/components/editor/CaretView.svelte.d.ts +1 -0
- package/components/editor/ConfirmButton.svelte +1 -1
- package/components/editor/Note.svelte +1 -0
- package/components/editor/Permissions.svelte +95 -0
- package/components/editor/Permissions.svelte.d.ts +20 -0
- package/components/editor/Toolbar.svelte +0 -1
- package/components/editor/ToolbarGroup.svelte +0 -1
- package/components/page/Acknowledgements.svelte +13 -11
- package/components/page/Authors.svelte +5 -8
- package/components/page/Authors.svelte.d.ts +1 -0
- package/components/page/BookLoader.svelte +59 -0
- package/components/page/BookLoader.svelte.d.ts +16 -0
- package/components/page/BulkReferenceEditor.svelte +8 -8
- package/components/page/Chapter.svelte +43 -10
- package/components/page/Contexts.d.ts +11 -2
- package/components/page/Contexts.js +85 -3
- package/components/page/DefinitionView.svelte +18 -8
- package/components/page/Description.svelte +12 -9
- package/components/page/Edition.svelte +15 -13
- package/components/page/Edition.svelte.d.ts +0 -1
- package/components/page/EditionLoader.svelte +99 -0
- package/components/page/EditionLoader.svelte.d.ts +18 -0
- package/components/page/Editions.svelte +92 -143
- package/components/page/Glossary.svelte +13 -6
- package/components/page/Header.svelte +22 -20
- package/components/page/Header.svelte.d.ts +2 -0
- package/components/page/Index.svelte +6 -4
- package/components/page/Instructions.svelte +1 -2
- package/components/page/Instructions.svelte.d.ts +3 -1
- package/components/page/License.svelte +20 -12
- package/components/page/Media.svelte +14 -12
- package/components/page/Publish.svelte +120 -0
- package/components/page/Publish.svelte.d.ts +14 -0
- package/components/page/Reference.svelte +16 -17
- package/components/page/References.svelte +8 -3
- package/components/page/Search.svelte +3 -1
- package/components/page/Status.svelte +18 -19
- package/components/page/SubdomainEditor.svelte +3 -2
- package/components/page/TableOfContents.svelte +70 -30
- package/components/page/TableOfContentsRow.svelte +4 -4
- package/components/page/ThemeEditor.svelte +8 -5
- package/components/page/Unknown.svelte +5 -2
- package/models/CRUD.d.ts +19 -11
- package/models/CRUD.js +177 -122
- package/models/Firebase.js +12 -8
- package/models/book/Book.d.ts +19 -14
- package/models/book/Book.js +47 -54
- package/models/book/Chapter.d.ts +5 -1
- package/models/book/Chapter.js +24 -15
- package/models/book/Edition.d.ts +31 -7
- package/models/book/Edition.js +101 -32
- package/models/chapter/Parser.d.ts +2 -2
- package/models/chapter/Parser.js +26 -24
- package/package.json +18 -15
- package/components/app/Writing.svelte.d.ts +0 -27
- package/models/loadEdition.d.ts +0 -5
- package/models/loadEdition.js +0 -20
|
@@ -16,17 +16,19 @@ import EmbedNode from '../../models/chapter/EmbedNode';
|
|
|
16
16
|
import ErrorNode from '../../models/chapter/ErrorNode';
|
|
17
17
|
import { caretRangeToIndexRange, indexRangeToCaretRange, } from '../../models/chapter/Caret';
|
|
18
18
|
import commands from './Commands';
|
|
19
|
-
import { getEdition } from '../page/Contexts';
|
|
20
19
|
import { afterUpdate, onMount, setContext } from 'svelte';
|
|
21
20
|
import { getCaret } from '../page/Contexts';
|
|
22
21
|
import CaretView from './CaretView.svelte';
|
|
23
22
|
const IDLE_TIME = 500;
|
|
24
|
-
export let
|
|
23
|
+
export let text;
|
|
24
|
+
export let parser;
|
|
25
25
|
export let save;
|
|
26
26
|
export let chapter;
|
|
27
27
|
export let autofocus = false;
|
|
28
28
|
export let component;
|
|
29
29
|
export let placeholder;
|
|
30
|
+
export let leasee;
|
|
31
|
+
export let lease;
|
|
30
32
|
let element = null;
|
|
31
33
|
let caretRange = undefined;
|
|
32
34
|
let caretCoordinate = undefined;
|
|
@@ -36,10 +38,19 @@ let editorFocused = false;
|
|
|
36
38
|
let undoStack = [];
|
|
37
39
|
let undoPosition = -1;
|
|
38
40
|
let clipboard = undefined;
|
|
39
|
-
let editedNode = ast;
|
|
40
41
|
let ignoredInput = false;
|
|
41
|
-
let edition = getEdition();
|
|
42
42
|
let activeEditor = getCaret();
|
|
43
|
+
let editedNode;
|
|
44
|
+
$: {
|
|
45
|
+
// Note: when the text changes externally, the caret will be lost. We either have
|
|
46
|
+
// to implement fancy operational transform revision control for collaborative editing
|
|
47
|
+
// or a basic chapter locking strategy to avoid data loss. I went with locking for maintenance simplicity,
|
|
48
|
+
// but it would be nice to implement operational transform in the future.
|
|
49
|
+
if (editedNode === undefined || editedNode.toBookdown() !== text) {
|
|
50
|
+
editedNode = parser(text);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
$: locked = typeof leasee === 'string';
|
|
43
54
|
onMount(() => {
|
|
44
55
|
const caret = editedNode.getFirstCaret();
|
|
45
56
|
if (caret)
|
|
@@ -357,17 +368,11 @@ function undo() {
|
|
|
357
368
|
// Grab the next state
|
|
358
369
|
let undoState = undoStack[undoPosition + 1];
|
|
359
370
|
// Restore the content of the chapter.
|
|
360
|
-
let node =
|
|
361
|
-
? Parser.parseChapter($edition, undoState.bookdown)
|
|
362
|
-
: ast instanceof FormatNode
|
|
363
|
-
? Parser.parseFormat($edition, undoState.bookdown)
|
|
364
|
-
: ast instanceof EmbedNode
|
|
365
|
-
? Parser.parseEmbed($edition, undoState.bookdown)
|
|
366
|
-
: undefined;
|
|
371
|
+
let node = parser(undoState.bookdown);
|
|
367
372
|
if (node === undefined || node instanceof ErrorNode)
|
|
368
373
|
return;
|
|
369
374
|
// Restore the view
|
|
370
|
-
|
|
375
|
+
updateRoot(node);
|
|
371
376
|
// Save the undo.
|
|
372
377
|
save(node);
|
|
373
378
|
// Move the undo state down a position.
|
|
@@ -383,16 +388,10 @@ function redo() {
|
|
|
383
388
|
// Grab the next state
|
|
384
389
|
let undoState = undoStack[undoPosition - 1];
|
|
385
390
|
// Restore the content of the chapter.
|
|
386
|
-
let node =
|
|
387
|
-
? Parser.parseChapter($edition, undoState.bookdown)
|
|
388
|
-
: ast instanceof FormatNode
|
|
389
|
-
? Parser.parseFormat($edition, undoState.bookdown)
|
|
390
|
-
: ast instanceof EmbedNode
|
|
391
|
-
? Parser.parseEmbed($edition, undoState.bookdown)
|
|
392
|
-
: undefined;
|
|
391
|
+
let node = parser(undoState.bookdown);
|
|
393
392
|
if (node === undefined)
|
|
394
393
|
return;
|
|
395
|
-
|
|
394
|
+
updateRoot(node);
|
|
396
395
|
// Move the undo state down a position.
|
|
397
396
|
if (undoPosition > 0)
|
|
398
397
|
undoPosition = undoPosition - 1;
|
|
@@ -588,6 +587,9 @@ function executeCommand(command, key) {
|
|
|
588
587
|
return false;
|
|
589
588
|
}
|
|
590
589
|
async function saveEdit(newRoot, newRange, command) {
|
|
590
|
+
// Don't save edits if locked.
|
|
591
|
+
if (locked)
|
|
592
|
+
return;
|
|
591
593
|
// Call the save callback.
|
|
592
594
|
save(newRoot);
|
|
593
595
|
// Initialize a new history if we don't have one.
|
|
@@ -605,7 +607,7 @@ async function saveEdit(newRoot, newRange, command) {
|
|
|
605
607
|
];
|
|
606
608
|
}
|
|
607
609
|
// Change the chapter's AST.
|
|
608
|
-
|
|
610
|
+
updateRoot(newRoot);
|
|
609
611
|
// Set the new undo stack, pre-pending the new command to the front.
|
|
610
612
|
const newTextRange = caretRangeToIndexRange(newRoot, newRange);
|
|
611
613
|
if (newTextRange !== undefined)
|
|
@@ -626,6 +628,10 @@ async function saveEdit(newRoot, newRange, command) {
|
|
|
626
628
|
// Update the caret
|
|
627
629
|
claimActiveEditor();
|
|
628
630
|
}
|
|
631
|
+
function updateRoot(newRoot) {
|
|
632
|
+
editedNode = newRoot;
|
|
633
|
+
text = editedNode.toBookdown();
|
|
634
|
+
}
|
|
629
635
|
function editNode(previous, edited) {
|
|
630
636
|
if (caretRange) {
|
|
631
637
|
const newRoot = editedNode.withNodeReplaced(previous, edited);
|
|
@@ -663,8 +669,13 @@ function isFocused() {
|
|
|
663
669
|
(element !== null && element?.contains(document.activeElement)) ||
|
|
664
670
|
toolbarIsFocused());
|
|
665
671
|
}
|
|
666
|
-
function
|
|
672
|
+
function handleFocus() {
|
|
673
|
+
editorFocused = isFocused();
|
|
674
|
+
lease(true);
|
|
675
|
+
}
|
|
676
|
+
function handleBlur() {
|
|
667
677
|
editorFocused = isFocused();
|
|
678
|
+
lease(false);
|
|
668
679
|
}
|
|
669
680
|
function handleCopy(node) {
|
|
670
681
|
// Set the editor's clipboard.
|
|
@@ -793,16 +804,23 @@ setContext('claimeditor', claimActiveEditor);
|
|
|
793
804
|
|
|
794
805
|
<div
|
|
795
806
|
class={`bookish-editor ${inAtom ? 'bookish-editor-atom-focused' : ''}`}
|
|
807
|
+
class:locked
|
|
808
|
+
aria-disabled={locked}
|
|
796
809
|
bind:this={element}
|
|
797
810
|
on:keydown={handleKey}
|
|
798
811
|
on:keypress={handlePress}
|
|
799
812
|
on:mousedown|stopPropagation={handleMouseDown}
|
|
800
|
-
on:focus={
|
|
801
|
-
on:blur={
|
|
813
|
+
on:focus={handleFocus}
|
|
814
|
+
on:blur={handleBlur}
|
|
802
815
|
tabIndex="0"
|
|
803
816
|
>
|
|
804
|
-
<!--
|
|
805
|
-
<svelte:component
|
|
817
|
+
<!-- A view of the root -->
|
|
818
|
+
<svelte:component
|
|
819
|
+
this={component}
|
|
820
|
+
node={editedNode}
|
|
821
|
+
{placeholder}
|
|
822
|
+
editable={true}
|
|
823
|
+
/>
|
|
806
824
|
<!-- The caret. We render our own since this view isn't contentEditable and we can't show a caret.
|
|
807
825
|
Customize the rendering based on the formatting applied to the text node. -->
|
|
808
826
|
{#if caretCoordinate && caretRange && !isAtom && isSelection === false && editorFocused}
|
|
@@ -815,14 +833,19 @@ setContext('claimeditor', claimActiveEditor);
|
|
|
815
833
|
linked={isLink}
|
|
816
834
|
bold={isBold}
|
|
817
835
|
italic={isItalic}
|
|
818
|
-
disabled={!editorFocused}
|
|
836
|
+
disabled={!editorFocused || locked}
|
|
837
|
+
{locked}
|
|
819
838
|
/>
|
|
820
839
|
{/if}
|
|
821
840
|
</div>
|
|
822
841
|
|
|
823
842
|
<style>
|
|
824
843
|
.bookish-editor {
|
|
825
|
-
min-height:
|
|
844
|
+
min-height: 1em;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.locked :global(.bookish-text) {
|
|
848
|
+
color: var(--app-muted-color);
|
|
826
849
|
}
|
|
827
850
|
|
|
828
851
|
.bookish-editor:focus {
|
|
@@ -2,12 +2,15 @@ import { SvelteComponentTyped } from "svelte";
|
|
|
2
2
|
import type RootNode from '../../models/chapter/RootNode';
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
|
-
|
|
5
|
+
text: string;
|
|
6
|
+
parser: (text: string) => RootNode;
|
|
6
7
|
save: (node: RootNode) => Promise<void> | void;
|
|
7
8
|
chapter: boolean;
|
|
8
9
|
autofocus?: boolean | undefined;
|
|
9
10
|
component: ConstructorOfATypedSvelteComponent;
|
|
10
11
|
placeholder: string;
|
|
12
|
+
leasee: string | boolean;
|
|
13
|
+
lease: (lock: boolean) => Promise<boolean> | boolean;
|
|
11
14
|
};
|
|
12
15
|
events: {
|
|
13
16
|
[evt: string]: CustomEvent<any>;
|
|
@@ -8,6 +8,7 @@ export let bold;
|
|
|
8
8
|
export let disabled;
|
|
9
9
|
export let ignored;
|
|
10
10
|
export let blink;
|
|
11
|
+
export let locked;
|
|
11
12
|
let element;
|
|
12
13
|
let headerHeight = 0;
|
|
13
14
|
// When the caret updates, make sure it's in view.
|
|
@@ -32,7 +33,8 @@ afterUpdate(() => {
|
|
|
32
33
|
style:height="{height}px"
|
|
33
34
|
style:scroll-margin-top="{headerHeight}px"
|
|
34
35
|
bind:this={element}
|
|
35
|
-
|
|
36
|
+
>{#if locked}🔒{/if}</div
|
|
37
|
+
>
|
|
36
38
|
|
|
37
39
|
<style>
|
|
38
40
|
.caret {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script>import { createUser, getUserEmails } from '../../models/CRUD';
|
|
2
|
+
import Button from '../app/Button.svelte';
|
|
3
|
+
import TextInput from '../app/TextInput.svelte';
|
|
4
|
+
import Note from './Note.svelte';
|
|
5
|
+
export let uids;
|
|
6
|
+
export let inheriteduids;
|
|
7
|
+
export let writable;
|
|
8
|
+
export let atleastone;
|
|
9
|
+
export let change;
|
|
10
|
+
let emails = null;
|
|
11
|
+
$: alluids = Array.from(new Set([...uids, ...inheriteduids]));
|
|
12
|
+
async function getEmails(userIDs) {
|
|
13
|
+
emails = await getUserEmails(userIDs);
|
|
14
|
+
}
|
|
15
|
+
$: {
|
|
16
|
+
if (emails === null ||
|
|
17
|
+
(emails.size > 0 &&
|
|
18
|
+
Array.from(emails.keys()).join() !== alluids.join()))
|
|
19
|
+
getEmails(alluids);
|
|
20
|
+
}
|
|
21
|
+
let newEditor = '';
|
|
22
|
+
let newEditorError = null;
|
|
23
|
+
let loading = undefined;
|
|
24
|
+
async function addEditor() {
|
|
25
|
+
const email = newEditor;
|
|
26
|
+
newEditor = '';
|
|
27
|
+
loading = email;
|
|
28
|
+
try {
|
|
29
|
+
const uid = await createUser(email);
|
|
30
|
+
if (uid === null)
|
|
31
|
+
newEditorError = "couldn't add editor";
|
|
32
|
+
else
|
|
33
|
+
change([...uids, uid]);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
loading = undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
{#if emails === null}
|
|
42
|
+
Loading editors...
|
|
43
|
+
{:else}
|
|
44
|
+
<section class="emails">
|
|
45
|
+
{#if writable}
|
|
46
|
+
<form on:submit|preventDefault={addEditor}>
|
|
47
|
+
<TextInput
|
|
48
|
+
type="email"
|
|
49
|
+
placeholder="email"
|
|
50
|
+
bind:text={newEditor}
|
|
51
|
+
disabled={false}
|
|
52
|
+
/>
|
|
53
|
+
<Button
|
|
54
|
+
tooltip="give {newEditor} editing rights"
|
|
55
|
+
disabled={newEditor.length === 0 ||
|
|
56
|
+
Array.from(emails.values()).includes(newEditor)}
|
|
57
|
+
command={addEditor}>+ editor</Button
|
|
58
|
+
>
|
|
59
|
+
{#if newEditorError !== null}{newEditorError}{/if}
|
|
60
|
+
</form>
|
|
61
|
+
{/if}
|
|
62
|
+
|
|
63
|
+
{#each uids as uid, index (uid)}
|
|
64
|
+
{@const email = emails.get(uid)}
|
|
65
|
+
<p>
|
|
66
|
+
{email}
|
|
67
|
+
{#if writable}
|
|
68
|
+
<Button
|
|
69
|
+
tooltip="remove {email} editing rights"
|
|
70
|
+
command={() =>
|
|
71
|
+
change([
|
|
72
|
+
...uids.slice(0, index),
|
|
73
|
+
...uids.slice(index + 1),
|
|
74
|
+
])}
|
|
75
|
+
disabled={atleastone && uids.length <= 1}>x</Button
|
|
76
|
+
>
|
|
77
|
+
{/if}
|
|
78
|
+
</p>
|
|
79
|
+
{/each}
|
|
80
|
+
{#each inheriteduids as uid}
|
|
81
|
+
<p><em>{emails.get(uid)}</em> <Note>book editor</Note></p>
|
|
82
|
+
{/each}
|
|
83
|
+
{#if loading}
|
|
84
|
+
<p>
|
|
85
|
+
adding {loading}
|
|
86
|
+
</p>
|
|
87
|
+
{/if}
|
|
88
|
+
</section>
|
|
89
|
+
{/if}
|
|
90
|
+
|
|
91
|
+
<style>
|
|
92
|
+
p {
|
|
93
|
+
font-family: var(--app-font);
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
uids: string[];
|
|
5
|
+
inheriteduids: string[];
|
|
6
|
+
writable: boolean;
|
|
7
|
+
atleastone: boolean;
|
|
8
|
+
change: (uids: string[]) => void;
|
|
9
|
+
};
|
|
10
|
+
events: {
|
|
11
|
+
[evt: string]: CustomEvent<any>;
|
|
12
|
+
};
|
|
13
|
+
slots: {};
|
|
14
|
+
};
|
|
15
|
+
export type PermissionsProps = typeof __propDef.props;
|
|
16
|
+
export type PermissionsEvents = typeof __propDef.events;
|
|
17
|
+
export type PermissionsSlots = typeof __propDef.slots;
|
|
18
|
+
export default class Permissions extends SvelteComponentTyped<PermissionsProps, PermissionsEvents, PermissionsSlots> {
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -2,37 +2,39 @@
|
|
|
2
2
|
import ChapterBody from '../chapter/ChapterBody.svelte';
|
|
3
3
|
import BookishEditor from '../editor/BookishEditor.svelte';
|
|
4
4
|
import Instructions from './Instructions.svelte';
|
|
5
|
-
import { getEdition,
|
|
5
|
+
import { getAuth, getEdition, getLeasee, isEditionEditable, lease, } from './Contexts';
|
|
6
6
|
import PageHeader from './PageHeader.svelte';
|
|
7
|
+
let auth = getAuth();
|
|
7
8
|
let edition = getEdition();
|
|
8
|
-
let editable =
|
|
9
|
+
let editable = isEditionEditable();
|
|
9
10
|
$: acknowledgements = $edition?.getAcknowledgements();
|
|
10
|
-
$: acksNode = acknowledgements
|
|
11
|
-
? Parser.parseChapter($edition, acknowledgements)
|
|
12
|
-
: undefined;
|
|
13
11
|
</script>
|
|
14
12
|
|
|
15
|
-
<Instructions>
|
|
16
|
-
|
|
13
|
+
<Instructions {editable}>
|
|
14
|
+
Do you have people, communities, or institutions to thank? Do it here. This
|
|
15
|
+
section is not shown if empty.
|
|
17
16
|
</Instructions>
|
|
18
17
|
|
|
19
18
|
<!-- If editable, show acknowledgements even if they're empty, otherwise hide -->
|
|
20
|
-
{#if $edition && acknowledgements
|
|
19
|
+
{#if $edition && acknowledgements}
|
|
21
20
|
{#if editable}
|
|
22
21
|
<BookishEditor
|
|
23
|
-
|
|
22
|
+
text={acknowledgements}
|
|
23
|
+
parser={(text) => Parser.parseChapter($edition, text)}
|
|
24
24
|
chapter={false}
|
|
25
25
|
component={ChapterBody}
|
|
26
26
|
placeholder="Who would you like to thank?"
|
|
27
27
|
save={(node) =>
|
|
28
28
|
$edition
|
|
29
29
|
? edition.set(
|
|
30
|
-
$edition.
|
|
30
|
+
$edition.withAcknowledgements(node.toBookdown())
|
|
31
31
|
)
|
|
32
32
|
: undefined}
|
|
33
|
+
leasee={getLeasee(auth, edition, `acks`)}
|
|
34
|
+
lease={(lock) => lease(auth, edition, `acks`, lock)}
|
|
33
35
|
/>
|
|
34
36
|
{:else if acknowledgements.length > 0}
|
|
35
37
|
<PageHeader id="acknowledgements">Acknowledgements</PageHeader>
|
|
36
|
-
<ChapterBody node={
|
|
38
|
+
<ChapterBody node={Parser.parseChapter($edition, acknowledgements)} />
|
|
37
39
|
{/if}
|
|
38
40
|
{/if}
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
import TextEditor from '../editor/TextEditor.svelte';
|
|
3
3
|
import Format from '../chapter/Format.svelte';
|
|
4
4
|
import { afterUpdate } from 'svelte';
|
|
5
|
-
import { getEdition,
|
|
5
|
+
import { getEdition, isBookEditable, isEditionEditable } from './Contexts';
|
|
6
6
|
import Note from '../editor/Note.svelte';
|
|
7
7
|
import Button from '../app/Button.svelte';
|
|
8
|
+
export let editable;
|
|
8
9
|
export let authors;
|
|
9
10
|
export let inheritedAuthors = undefined;
|
|
10
11
|
export let add;
|
|
11
12
|
export let edit;
|
|
12
13
|
export let remove;
|
|
13
|
-
let editable = isEditable();
|
|
14
14
|
let edition = getEdition();
|
|
15
15
|
let authorList = null;
|
|
16
16
|
let newAuthor = false;
|
|
@@ -47,7 +47,7 @@ afterUpdate(() => {
|
|
|
47
47
|
<TextEditor
|
|
48
48
|
text={author}
|
|
49
49
|
label={'Author name editor'}
|
|
50
|
-
placeholder="
|
|
50
|
+
placeholder="author"
|
|
51
51
|
valid={() => undefined}
|
|
52
52
|
save={(text) =>
|
|
53
53
|
/* If text is already empty, remove */ author.length >
|
|
@@ -66,9 +66,7 @@ afterUpdate(() => {
|
|
|
66
66
|
{/each}
|
|
67
67
|
{/if}
|
|
68
68
|
{#if editable}
|
|
69
|
-
<Button tooltip="
|
|
70
|
-
>+ author</Button
|
|
71
|
-
>
|
|
69
|
+
<Button tooltip="add author" command={addAuthor}>+ author</Button>
|
|
72
70
|
{/if}
|
|
73
71
|
</p>
|
|
74
72
|
|
|
@@ -76,7 +74,6 @@ afterUpdate(() => {
|
|
|
76
74
|
.bookish-authors {
|
|
77
75
|
font-family: var(--bookish-header-font-family);
|
|
78
76
|
font-style: italic;
|
|
79
|
-
margin
|
|
80
|
-
margin-bottom: var(--bookish-paragraph-spacing);
|
|
77
|
+
margin: 0;
|
|
81
78
|
}
|
|
82
79
|
</style>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script>import Loading from './Loading.svelte';
|
|
2
|
+
import Feedback from '../app/Feedback.svelte';
|
|
3
|
+
import { onDestroy } from 'svelte';
|
|
4
|
+
import { page } from '$app/stores';
|
|
5
|
+
import { getBook, getAuth } from './Contexts';
|
|
6
|
+
import { listenToBookWithID as listenToBooksWithID, listenToBooksByName as listenToBooksWithName, } from '../../models/CRUD';
|
|
7
|
+
let auth = getAuth();
|
|
8
|
+
let book = getBook();
|
|
9
|
+
// Keep track of the query updates
|
|
10
|
+
let booksByName = undefined;
|
|
11
|
+
let bookByID = undefined;
|
|
12
|
+
// Mege them together here
|
|
13
|
+
$: currentBook =
|
|
14
|
+
// Neither loaded? Current book isn't loaded.
|
|
15
|
+
booksByName === undefined && bookByID === undefined
|
|
16
|
+
? undefined
|
|
17
|
+
: booksByName !== undefined && booksByName.length > 0
|
|
18
|
+
? booksByName[0]
|
|
19
|
+
: bookByID ?? null;
|
|
20
|
+
// Whenever the merge changes, update the book store (updating the whole UI)
|
|
21
|
+
$: book.set(currentBook ?? undefined);
|
|
22
|
+
// Keep track of listeners to unsubscribe to on page changes.
|
|
23
|
+
let nameUnsub = undefined;
|
|
24
|
+
let idUnsub = undefined;
|
|
25
|
+
function unsub() {
|
|
26
|
+
if (nameUnsub)
|
|
27
|
+
nameUnsub();
|
|
28
|
+
if (idUnsub)
|
|
29
|
+
idUnsub();
|
|
30
|
+
}
|
|
31
|
+
// Keep track of any errors.
|
|
32
|
+
let error;
|
|
33
|
+
function listen() {
|
|
34
|
+
unsub();
|
|
35
|
+
try {
|
|
36
|
+
nameUnsub = listenToBooksWithName($page.params.bookid, (books) => (booksByName = books));
|
|
37
|
+
idUnsub = listenToBooksWithID($page.params.bookid, (book) => (bookByID = book));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
error = '' + err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// When page or auth changes and there's a user, update the listener.
|
|
44
|
+
$: if ($page && $auth && $auth.user !== null)
|
|
45
|
+
listen();
|
|
46
|
+
// When this is unmounted, unset them.
|
|
47
|
+
onDestroy(() => {
|
|
48
|
+
book.set(undefined);
|
|
49
|
+
unsub();
|
|
50
|
+
});
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
{#if error || currentBook === null}
|
|
54
|
+
<Feedback error>Unable to load book.</Feedback>
|
|
55
|
+
{:else if currentBook === undefined}
|
|
56
|
+
<Loading />
|
|
57
|
+
{:else}
|
|
58
|
+
<slot />
|
|
59
|
+
{/if}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: Record<string, never>;
|
|
4
|
+
events: {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
};
|
|
7
|
+
slots: {
|
|
8
|
+
default: {};
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type BookLoaderProps = typeof __propDef.props;
|
|
12
|
+
export type BookLoaderEvents = typeof __propDef.events;
|
|
13
|
+
export type BookLoaderSlots = typeof __propDef.slots;
|
|
14
|
+
export default class BookLoader extends SvelteComponentTyped<BookLoaderProps, BookLoaderEvents, BookLoaderSlots> {
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script>import Instructions from './Instructions.svelte';
|
|
2
2
|
import { getUniqueReferenceID, mineReference, } from '../../util/mineReference';
|
|
3
3
|
import Reference from '../../models/book/Reference';
|
|
4
|
-
import { getEdition } from './Contexts';
|
|
4
|
+
import { getEdition, isEditionEditable, isEditionPartiallyEditable, } from './Contexts';
|
|
5
5
|
import Button from '../app/Button.svelte';
|
|
6
6
|
let edition = getEdition();
|
|
7
7
|
let text = '';
|
|
8
|
+
let editable = isEditionEditable() || isEditionPartiallyEditable();
|
|
8
9
|
function handleBulkAdd() {
|
|
9
10
|
// Split by lines, skipping empty lines.
|
|
10
11
|
const references = text
|
|
@@ -24,17 +25,16 @@ function handleEmptyAdd() {
|
|
|
24
25
|
}
|
|
25
26
|
</script>
|
|
26
27
|
|
|
27
|
-
<Instructions>
|
|
28
|
-
To
|
|
29
|
-
or try pasting them in bulk, and
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
wrong!
|
|
28
|
+
<Instructions {editable}>
|
|
29
|
+
To add references to a chapter, add them here first. You can add one at a
|
|
30
|
+
time, or try pasting them in bulk, and this page will try to pull out author
|
|
31
|
+
lists, years, sources, and URLs. Be sure to verify it though — it's
|
|
32
|
+
likely to get some things wrong.
|
|
33
33
|
</Instructions>
|
|
34
34
|
|
|
35
35
|
<textarea rows={5} bind:value={text} style="width: 100%" />
|
|
36
36
|
<Button
|
|
37
|
-
tooltip="convert
|
|
37
|
+
tooltip="convert to references"
|
|
38
38
|
disabled={text.length === 0}
|
|
39
39
|
command={handleBulkAdd}>+ bulk references</Button
|
|
40
40
|
>
|