bookish-press 0.9.4 → 0.10.0
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/Auth.svelte +37 -21
- package/dist/components/BookPreview.svelte +4 -4
- package/dist/components/BookPreview.svelte.d.ts +1 -1
- package/dist/components/Link.svelte +19 -12
- package/dist/components/app/Button.svelte +1 -8
- package/dist/components/app/Link.svelte +1 -7
- package/dist/components/app/Options.svelte +1 -6
- package/dist/components/app/TextInput.svelte +2 -8
- package/dist/components/chapter/Atom.svelte +2 -1
- package/dist/components/chapter/Atom.svelte.d.ts +1 -1
- package/dist/components/chapter/Block.svelte +9 -9
- package/dist/components/chapter/Block.svelte.d.ts +1 -1
- package/dist/components/chapter/Callout.svelte.d.ts +1 -1
- package/dist/components/chapter/CaptionedCode.svelte +1 -0
- package/dist/components/chapter/CaptionedCode.svelte.d.ts +1 -1
- package/dist/components/chapter/ChapterBody.svelte +5 -2
- package/dist/components/chapter/ChapterBody.svelte.d.ts +1 -1
- package/dist/components/chapter/ChapterBody.test.js +3 -3
- package/dist/components/chapter/Citations.svelte +17 -16
- package/dist/components/chapter/Citations.svelte.d.ts +1 -1
- package/dist/components/chapter/Code.svelte +8 -14
- package/dist/components/chapter/Comment.svelte +7 -12
- package/dist/components/chapter/Comment.svelte.d.ts +1 -1
- package/dist/components/chapter/Definition.svelte +8 -9
- package/dist/components/chapter/Definition.svelte.d.ts +1 -1
- package/dist/components/chapter/Embed.svelte +41 -61
- package/dist/components/chapter/Embed.svelte.d.ts +1 -1
- package/dist/components/chapter/ErrorMessage.svelte.d.ts +1 -1
- package/dist/components/chapter/Figure.svelte +14 -14
- package/dist/components/chapter/Figure.svelte.d.ts +4 -4
- package/dist/components/chapter/Footnote.svelte +11 -17
- package/dist/components/chapter/Footnote.svelte.d.ts +1 -1
- package/dist/components/chapter/Format.svelte +11 -11
- package/dist/components/chapter/Format.svelte.d.ts +1 -1
- package/dist/components/chapter/InlineCode.svelte.d.ts +1 -1
- package/dist/components/chapter/Label.svelte +1 -1
- package/dist/components/chapter/Label.svelte.d.ts +1 -1
- package/dist/components/chapter/LineBreak.svelte +1 -0
- package/dist/components/chapter/LineBreak.svelte.d.ts +1 -1
- package/dist/components/chapter/Link.svelte +14 -12
- package/dist/components/chapter/Link.svelte.d.ts +1 -1
- package/dist/components/chapter/List.svelte +1 -1
- package/dist/components/chapter/List.svelte.d.ts +1 -1
- package/dist/components/chapter/Marginal.svelte +36 -42
- package/dist/components/chapter/Paragraph.svelte +4 -4
- package/dist/components/chapter/Paragraph.svelte.d.ts +1 -1
- package/dist/components/chapter/Positioned.svelte +1 -0
- package/dist/components/chapter/Positioned.svelte.d.ts +1 -1
- package/dist/components/chapter/Python.svelte +42 -31
- package/dist/components/chapter/Python.svelte.d.ts +1 -1
- package/dist/components/chapter/Quote.svelte.d.ts +1 -1
- package/dist/components/chapter/Rule.svelte +1 -0
- package/dist/components/chapter/Rule.svelte.d.ts +1 -1
- package/dist/components/chapter/Table.svelte.d.ts +1 -1
- package/dist/components/chapter/Text.svelte +41 -50
- package/dist/components/chapter/Text.svelte.d.ts +1 -1
- package/dist/components/controls/DarkToggle.svelte +4 -4
- package/dist/components/editor/BookishEditor.svelte +605 -569
- package/dist/components/editor/BookishEditor.svelte.d.ts +1 -1
- package/dist/components/editor/CalloutEditor.svelte +1 -1
- package/dist/components/editor/CalloutEditor.svelte.d.ts +1 -1
- package/dist/components/editor/CaptionedCodeEditor.svelte +1 -1
- package/dist/components/editor/CaptionedCodeEditor.svelte.d.ts +1 -1
- package/dist/components/editor/CaretContext.d.ts +14 -14
- package/dist/components/editor/CaretState.d.ts +3 -3
- package/dist/components/editor/CaretView.svelte +8 -19
- package/dist/components/editor/CitationsEditor.svelte +10 -18
- package/dist/components/editor/CitationsEditor.svelte.d.ts +1 -1
- package/dist/components/editor/Clipboard.d.ts +1 -1
- package/dist/components/editor/Command.d.ts +1 -1
- package/dist/components/editor/Commands.js +19 -19
- package/dist/components/editor/ConfirmButton.svelte +1 -6
- package/dist/components/editor/DefinitionEditor.svelte +9 -7
- package/dist/components/editor/DefinitionEditor.svelte.d.ts +1 -1
- package/dist/components/editor/EmbedEditor.svelte +25 -34
- package/dist/components/editor/EmbedEditor.svelte.d.ts +1 -1
- package/dist/components/editor/ImageChooser.svelte +8 -7
- package/dist/components/editor/ImageChooser.svelte.d.ts +1 -1
- package/dist/components/editor/ImageThumbnail.svelte +21 -18
- package/dist/components/editor/InlineCodeEditor.svelte +1 -1
- package/dist/components/editor/InlineCodeEditor.svelte.d.ts +1 -1
- package/dist/components/editor/LabelEditor.svelte +2 -2
- package/dist/components/editor/LabelEditor.svelte.d.ts +1 -1
- package/dist/components/editor/LanguageEditor.svelte +8 -1
- package/dist/components/editor/LinkEditor.svelte +42 -44
- package/dist/components/editor/LinkEditor.svelte.d.ts +1 -1
- package/dist/components/editor/Permissions.svelte +23 -29
- package/dist/components/editor/PositionEditor.svelte +1 -1
- package/dist/components/editor/PositionEditor.svelte.d.ts +1 -1
- package/dist/components/editor/QuoteEditor.svelte +1 -1
- package/dist/components/editor/QuoteEditor.svelte.d.ts +1 -1
- package/dist/components/editor/Switch.svelte +5 -11
- package/dist/components/editor/TableEditor.svelte +1 -1
- package/dist/components/editor/TableEditor.svelte.d.ts +1 -1
- package/dist/components/editor/TextEditor.svelte +61 -58
- package/dist/components/editor/Toggle.svelte +16 -14
- package/dist/components/editor/Toolbar.svelte +81 -94
- package/dist/components/editor/UndoState.d.ts +2 -2
- package/dist/components/page/Acknowledgements.svelte +2 -8
- package/dist/components/page/Authors.svelte +12 -21
- package/dist/components/page/BookLoader.svelte +32 -34
- package/dist/components/page/BulkReferenceEditor.svelte +21 -41
- package/dist/components/page/Chapter.svelte +139 -114
- package/dist/components/page/Chapter.svelte.d.ts +1 -1
- package/dist/components/page/ChapterTitle.svelte +1 -1
- package/dist/components/page/Contexts.js +3 -1
- package/dist/components/page/DefinitionView.svelte +30 -42
- package/dist/components/page/DefinitionView.svelte.d.ts +1 -1
- package/dist/components/page/Description.svelte +2 -8
- package/dist/components/page/DownloadEPUB.svelte +83 -41
- package/dist/components/page/EditableHeader.svelte.d.ts +1 -1
- package/dist/components/page/Edition.svelte +44 -30
- package/dist/components/page/Edition.svelte.d.ts +1 -1
- package/dist/components/page/EditionLoader.svelte +101 -100
- package/dist/components/page/Editions.svelte +13 -13
- package/dist/components/page/Glossary.svelte +9 -10
- package/dist/components/page/Header.svelte +15 -28
- package/dist/components/page/Index.svelte +17 -18
- package/dist/components/page/License.svelte +2 -8
- package/dist/components/page/Media.svelte +7 -14
- package/dist/components/page/MediaPreview.svelte +14 -17
- package/dist/components/page/Outline.svelte +74 -66
- package/dist/components/page/Page.svelte +23 -17
- package/dist/components/page/PossibleChapter.svelte +2 -2
- package/dist/components/page/PossibleReference.svelte +1 -1
- package/dist/components/page/PossibleReference.svelte.d.ts +2 -2
- package/dist/components/page/Print.svelte +5 -8
- package/dist/components/page/Publish.svelte +33 -39
- package/dist/components/page/Reference.svelte +9 -11
- package/dist/components/page/Reference.svelte.d.ts +1 -1
- package/dist/components/page/References.svelte +1 -1
- package/dist/components/page/Search.svelte +39 -40
- package/dist/components/page/SubdomainEditor.svelte +11 -10
- package/dist/components/page/TableOfContents.svelte +52 -48
- package/dist/components/page/TableOfContentsImage.svelte +2 -2
- package/dist/components/page/TableOfContentsRow.svelte +9 -22
- package/dist/components/page/TableOfContentsRow.svelte.d.ts +2 -2
- package/dist/components/page/ThemeEditor.svelte +10 -17
- package/dist/components/page/ThemeEditorPreview.svelte +3 -6
- package/dist/components/page/ThemeEditorPreview.svelte.d.ts +1 -1
- package/dist/components/page/ThemeVariableEditor.svelte +6 -6
- package/dist/components/page/Unknown.svelte +1 -1
- package/dist/epub/build.d.ts +6 -0
- package/dist/epub/build.js +16 -3
- package/dist/epub/build.test.js +65 -0
- package/dist/epub/images.d.ts +7 -5
- package/dist/epub/images.js +15 -10
- package/dist/epub/images.test.js +8 -6
- package/dist/epub/package.js +25 -3
- package/dist/models/FirebaseAvailable.d.ts +9 -0
- package/dist/models/FirebaseAvailable.js +10 -0
- package/dist/models/book/BookMedia.d.ts +2 -1
- package/dist/models/book/BookMedia.js +33 -9
- package/package.json +18 -14
|
@@ -2,55 +2,53 @@
|
|
|
2
2
|
import Feedback from "../app/Feedback.svelte";
|
|
3
3
|
import { onDestroy } from "svelte";
|
|
4
4
|
import { page } from "$app/state";
|
|
5
|
-
import { getBook, getUser } from "./Contexts";
|
|
6
|
-
import {
|
|
7
|
-
listenToBookWithID as listenToBooksWithID,
|
|
8
|
-
listenToBooksByName as listenToBooksWithName
|
|
9
|
-
} from "../../models/CRUD";
|
|
5
|
+
import { getBook, getUser } from "./Contexts.js";
|
|
6
|
+
import { listenToBookWithID as listenToBooksWithID, listenToBooksByName as listenToBooksWithName } from "../../models/CRUD.js";
|
|
10
7
|
let { children } = $props();
|
|
11
8
|
let auth = getUser();
|
|
12
9
|
let book = getBook();
|
|
13
|
-
|
|
14
|
-
let
|
|
10
|
+
// Keep track of the query updates
|
|
11
|
+
let booksByName = $state(undefined);
|
|
12
|
+
let bookByID = $state(undefined);
|
|
13
|
+
// Merge them together here
|
|
15
14
|
let currentBook = $derived.by(() => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// Neither loaded? Current book isn't loaded.
|
|
16
|
+
if (booksByName === undefined && bookByID === undefined) return undefined;
|
|
17
|
+
return booksByName !== undefined && booksByName.length > 0 ? booksByName[0] : bookByID ?? null;
|
|
19
18
|
});
|
|
19
|
+
// Whenever the merge changes, update the book store (updating the whole UI)
|
|
20
20
|
$effect.pre(() => {
|
|
21
|
-
|
|
21
|
+
book.set(currentBook ?? undefined);
|
|
22
22
|
});
|
|
23
|
-
|
|
24
|
-
let
|
|
23
|
+
// Keep track of listeners to unsubscribe to on page changes.
|
|
24
|
+
let nameUnsub = undefined;
|
|
25
|
+
let idUnsub = undefined;
|
|
25
26
|
function unsub() {
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
if (nameUnsub) nameUnsub();
|
|
28
|
+
if (idUnsub) idUnsub();
|
|
28
29
|
}
|
|
30
|
+
// Keep track of any errors.
|
|
29
31
|
let error = $state();
|
|
30
32
|
function listen() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
bookid,
|
|
41
|
-
(book2) => bookByID = book2
|
|
42
|
-
);
|
|
43
|
-
} catch (err) {
|
|
44
|
-
error = "" + err;
|
|
45
|
-
}
|
|
33
|
+
unsub();
|
|
34
|
+
const bookid = page.params.bookid;
|
|
35
|
+
if (bookid === undefined) return;
|
|
36
|
+
try {
|
|
37
|
+
nameUnsub = listenToBooksWithName(bookid, (books) => booksByName = books);
|
|
38
|
+
idUnsub = listenToBooksWithID(bookid, (book) => bookByID = book);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
error = "" + err;
|
|
41
|
+
}
|
|
46
42
|
}
|
|
43
|
+
// When the book id or auth changes and there's a user, update the listener.
|
|
47
44
|
$effect(() => {
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
void page.params.bookid;
|
|
46
|
+
if ($auth && $auth.user !== null) listen();
|
|
50
47
|
});
|
|
48
|
+
// When this is unmounted, unset them.
|
|
51
49
|
onDestroy(() => {
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
book.set(undefined);
|
|
51
|
+
unsub();
|
|
54
52
|
});
|
|
55
53
|
</script>
|
|
56
54
|
|
|
@@ -1,52 +1,32 @@
|
|
|
1
1
|
<script lang="ts">import Instructions from "./Instructions.svelte";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "../../util/mineReference";
|
|
6
|
-
import Reference from "../../models/book/Reference";
|
|
7
|
-
import {
|
|
8
|
-
getEdition,
|
|
9
|
-
isEditionEditable,
|
|
10
|
-
isEditionPartiallyEditable
|
|
11
|
-
} from "./Contexts";
|
|
2
|
+
import { getUniqueReferenceID, mineReference } from "../../util/mineReference.js";
|
|
3
|
+
import Reference from "../../models/book/Reference.js";
|
|
4
|
+
import { getEdition, isEditionEditable, isEditionPartiallyEditable } from "./Contexts";
|
|
12
5
|
import Button from "../app/Button.svelte";
|
|
13
6
|
let edition = getEdition();
|
|
14
7
|
let text = $state("");
|
|
15
8
|
let editable = isEditionEditable() || isEditionPartiallyEditable();
|
|
16
9
|
function handleBulkAdd() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
if ($edition) {
|
|
34
|
-
edition.set($edition.withNewReferences(newReferences));
|
|
35
|
-
text = "";
|
|
36
|
-
}
|
|
10
|
+
// Split by lines, skipping empty lines.
|
|
11
|
+
const chunks = text.split(/\n+/);
|
|
12
|
+
const newReferences = [];
|
|
13
|
+
for (const chunk of chunks) {
|
|
14
|
+
newReferences.push(mineReference(
|
|
15
|
+
// Pass the current IDs and the new reference IDs
|
|
16
|
+
Array.from(new Set([...Object.keys($edition?.getReferences() ?? []), ...newReferences.map((ref) => ref.citationID)])),
|
|
17
|
+
chunk
|
|
18
|
+
));
|
|
19
|
+
}
|
|
20
|
+
// Handle possible duplicate IDs.
|
|
21
|
+
if ($edition) {
|
|
22
|
+
edition.set($edition.withNewReferences(newReferences));
|
|
23
|
+
text = "";
|
|
24
|
+
}
|
|
37
25
|
}
|
|
38
26
|
function handleEmptyAdd() {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
new Reference(
|
|
43
|
-
getUniqueReferenceID(
|
|
44
|
-
Object.keys($edition.getReferences())
|
|
45
|
-
)
|
|
46
|
-
)
|
|
47
|
-
])
|
|
48
|
-
);
|
|
49
|
-
}
|
|
27
|
+
if ($edition) {
|
|
28
|
+
edition.set($edition.withNewReferences([new Reference(getUniqueReferenceID(Object.keys($edition.getReferences())))]));
|
|
29
|
+
}
|
|
50
30
|
}
|
|
51
31
|
</script>
|
|
52
32
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">import Parser from "../../models/chapter/Parser";
|
|
1
|
+
<script lang="ts">import Parser from "../../models/chapter/Parser.js";
|
|
2
2
|
import Header from "./Header.svelte";
|
|
3
3
|
import Authors from "./Authors.svelte";
|
|
4
4
|
import Outline from "./Outline.svelte";
|
|
@@ -11,25 +11,13 @@ import ChapterBody from "../chapter/ChapterBody.svelte";
|
|
|
11
11
|
import PossibleReference from "./PossibleReference.svelte";
|
|
12
12
|
import ChapterNumber from "./ChapterNumber.svelte";
|
|
13
13
|
import Problem from "../chapter/Problem.svelte";
|
|
14
|
-
import {
|
|
15
|
-
CHAPTER,
|
|
16
|
-
getUser,
|
|
17
|
-
getBook,
|
|
18
|
-
getEdition,
|
|
19
|
-
isBookEditable,
|
|
20
|
-
isChapterEditable,
|
|
21
|
-
isEditionPartiallyEditable,
|
|
22
|
-
getLeasee,
|
|
23
|
-
lease,
|
|
24
|
-
setChapter,
|
|
25
|
-
isEditionEditable
|
|
26
|
-
} from "./Contexts";
|
|
14
|
+
import { CHAPTER, getUser, getBook, getEdition, isBookEditable, isChapterEditable, isEditionPartiallyEditable, getLeasee, lease, setChapter, isEditionEditable } from "./Contexts";
|
|
27
15
|
import { writable } from "svelte/store";
|
|
28
16
|
import { onMount, setContext } from "svelte";
|
|
29
17
|
import { hideOutlineIfObscured, layoutMarginals } from "./margins";
|
|
30
18
|
import scrollToEyeLevel from "../../util/scrollToEyeLevel";
|
|
31
19
|
import { goto } from "$app/navigation";
|
|
32
|
-
import ChapterNode from "../../models/chapter/ChapterNode";
|
|
20
|
+
import ChapterNode from "../../models/chapter/ChapterNode.js";
|
|
33
21
|
import Muted from "./Muted.svelte";
|
|
34
22
|
import Title from "./Title.svelte";
|
|
35
23
|
import PageHeader from "./PageHeader.svelte";
|
|
@@ -38,127 +26,164 @@ let { chapter, print = false } = $props();
|
|
|
38
26
|
let book = getBook();
|
|
39
27
|
let edition = getEdition();
|
|
40
28
|
let auth = getUser();
|
|
29
|
+
// Keep track of the scroll position to facilitate reading during reloads.
|
|
41
30
|
function rememberPosition() {
|
|
42
|
-
|
|
31
|
+
localStorage.setItem("scrollposition", "" + window.scrollY);
|
|
43
32
|
}
|
|
44
|
-
|
|
45
|
-
let
|
|
33
|
+
// The currently selected marginal; we only do one at a time.
|
|
34
|
+
let marginal = writable(undefined);
|
|
35
|
+
// Keep track of which hash mark is scrolled to
|
|
36
|
+
let highlightedID = $state(undefined);
|
|
37
|
+
// When this component is mounted...
|
|
38
|
+
// 1) Subscribe and unsubscribe to window listeners
|
|
39
|
+
// 2) Do initial layout of marginals
|
|
40
|
+
// 3) Start observing any elements corresponding to the ID in the URL's hash string
|
|
41
|
+
// 4) Clean up our mess after unmounting.
|
|
46
42
|
onMount(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
43
|
+
// On render or when scrolling, see if the URL hash has changed and update state accordingly.
|
|
44
|
+
// We also track whether we've scrolled to it.
|
|
45
|
+
const observeHash = () => {
|
|
46
|
+
// Get the ID without the hash and the corresponding element
|
|
47
|
+
const elementID = window.location.hash.substring(1);
|
|
48
|
+
const element = elementID ? document.getElementById(elementID) : null;
|
|
49
|
+
// If it's not empty, observe it, and remember what we're observing for highlighting
|
|
50
|
+
if (element) {
|
|
51
|
+
highlightedID = elementID;
|
|
52
|
+
// Start watching this a few seconds after we see it, giving the for in-page scrolling.
|
|
53
|
+
setTimeout(() => observer.observe(element), 2500);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
// Do things to do when scrolling
|
|
57
|
+
const handleScroll = () => {
|
|
58
|
+
// See if the outline is obscured by an image
|
|
59
|
+
hideOutlineIfObscured();
|
|
60
|
+
// See if there are any new hashes to observe. This is because
|
|
61
|
+
// we're using react-router-hash-link, which doesn't trigger hashchange events for some reason.
|
|
62
|
+
observeHash();
|
|
63
|
+
};
|
|
64
|
+
// Lay things out when the window or scroll changes.
|
|
65
|
+
window.addEventListener("scroll", handleScroll);
|
|
66
|
+
// When the chapter view resizes, the responsive layout might cause the marginals to move to the footer.
|
|
67
|
+
// when this happens, we want to immediately remove all of the explicit positioning.
|
|
68
|
+
const resize = new ResizeObserver(() => requestLayout());
|
|
69
|
+
resize.observe(document.body);
|
|
70
|
+
// Remember the scroll position before a refresh.
|
|
71
|
+
// We have to do all of these because browser support varies.
|
|
72
|
+
window.addEventListener("beforeunload", rememberPosition);
|
|
73
|
+
window.addEventListener("visibilitychange", rememberPosition);
|
|
74
|
+
window.addEventListener("pagehide", rememberPosition);
|
|
75
|
+
// Start listening for hash changes, since we want to remove them upon scrolling away from them.
|
|
76
|
+
window.addEventListener("pushstate", observeHash);
|
|
77
|
+
window.addEventListener("popstate", observeHash);
|
|
78
|
+
window.addEventListener("hashchange", observeHash);
|
|
79
|
+
// Make an intersection observer to monitor the location of elements selected by the URL hash ID.
|
|
80
|
+
const observer = new IntersectionObserver((entries) => {
|
|
81
|
+
// Go through all of the monitored elements
|
|
82
|
+
entries.forEach((entry) => {
|
|
83
|
+
// If the observed element is out of view and in the hash, remove the hash
|
|
84
|
+
if (entry.intersectionRatio === 0 && entry.target.id === window.location.hash.substring(1)) {
|
|
85
|
+
history.pushState("", document.title, window.location.pathname + window.location.search);
|
|
86
|
+
observer.unobserve(entry.target);
|
|
87
|
+
highlightedID = undefined;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
// Do an initial check the hash to see if there's an ID to scroll to
|
|
92
|
+
observeHash();
|
|
93
|
+
// Position the marginals, since there's new content.
|
|
94
|
+
layoutMarginals();
|
|
95
|
+
// On cleanup, unsubscribe from everything above.
|
|
96
|
+
return () => {
|
|
97
|
+
window.removeEventListener("scroll", handleScroll);
|
|
98
|
+
resize.unobserve(document.body);
|
|
99
|
+
// Stop listening to page visibility changes.
|
|
100
|
+
window.removeEventListener("beforeunload", rememberPosition);
|
|
101
|
+
window.removeEventListener("visibilitychange", rememberPosition);
|
|
102
|
+
window.removeEventListener("pagehide", rememberPosition);
|
|
103
|
+
// Delete the remembered position so that the next page scrolls to top.
|
|
104
|
+
localStorage.removeItem("scrollposition");
|
|
105
|
+
// Stop listening for hash changes
|
|
106
|
+
window.removeEventListener("pushstate", observeHash);
|
|
107
|
+
window.removeEventListener("popstate", observeHash);
|
|
108
|
+
window.removeEventListener("hashchange", observeHash);
|
|
109
|
+
// Stop observing everything on unmount
|
|
110
|
+
observer.disconnect();
|
|
111
|
+
};
|
|
95
112
|
});
|
|
113
|
+
// Position the marginals on mount.
|
|
96
114
|
onMount(() => layoutMarginals());
|
|
115
|
+
// This gets called after the page is done loading. There are various things we scroll to.
|
|
97
116
|
function scrollToLastLocation() {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// In case loading changed marginal positions
|
|
118
|
+
layoutMarginals();
|
|
119
|
+
// If there's a word we're trying to highlight in the URL path, scroll to the corresponding match.
|
|
120
|
+
if (location.search) {
|
|
121
|
+
const highlights = document.getElementsByClassName("bookish-text bookish-content-highlight");
|
|
122
|
+
const num = getHighlightedNumber();
|
|
123
|
+
if (num && highlights.length > 0 && num < highlights.length && num >= 0) {
|
|
124
|
+
scrollToEyeLevel(highlights[num]);
|
|
125
|
+
}
|
|
126
|
+
} else if (highlightedID) {
|
|
127
|
+
const el = document.getElementById(highlightedID);
|
|
128
|
+
// If we found the element, remove the hash any time after a few seconds. See handleScroll for the removal logic.
|
|
129
|
+
if (el) {
|
|
130
|
+
scrollToEyeLevel(el);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
// If we got to this page through a refresh, scroll to
|
|
134
|
+
const entries = performance.getEntriesByType("navigation");
|
|
135
|
+
const position = localStorage.getItem("scrollposition");
|
|
136
|
+
if (position !== null && entries.length > 0 && entries[entries.length - 1].type === "reload") {
|
|
137
|
+
// Scroll to the previous position, if there was one, so that refresh preserves position.
|
|
138
|
+
window.scrollTo(0, parseInt(position));
|
|
139
|
+
} else {
|
|
140
|
+
window.scrollTo(0, 0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
121
143
|
}
|
|
144
|
+
/** Account for static builds, where location isn't defined */
|
|
122
145
|
function getLocation() {
|
|
123
|
-
|
|
146
|
+
return typeof location === "undefined" ? undefined : location;
|
|
124
147
|
}
|
|
125
148
|
function getHighlightedWord() {
|
|
126
|
-
|
|
149
|
+
return getLocation()?.search.split("&")[0]?.split("=")[1] ?? undefined;
|
|
127
150
|
}
|
|
128
151
|
function getHighlightedNumber() {
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
const number = getLocation()?.search.split("&")[1]?.split("=")[1];
|
|
153
|
+
return number === undefined ? undefined : parseInt(number);
|
|
131
154
|
}
|
|
155
|
+
// Prepare to render the chapter by getting some data from the chapter and book.
|
|
132
156
|
let chapterID = $derived(chapter.getID());
|
|
133
157
|
let chapterNumber = $derived($edition?.getChapterNumber(chapterID));
|
|
134
158
|
let chapterSection = $derived($edition?.getChapterSection(chapterID));
|
|
135
|
-
let chapterAST = $derived($edition ? chapter.getAST($edition) :
|
|
136
|
-
let citations = $derived(chapterAST ? chapterAST.getCitations() :
|
|
137
|
-
let editionNumber = $derived($edition ? $edition.getEditionNumber() :
|
|
159
|
+
let chapterAST = $derived($edition ? chapter.getAST($edition) : undefined);
|
|
160
|
+
let citations = $derived(chapterAST ? chapterAST.getCitations() : undefined);
|
|
161
|
+
let editionNumber = $derived($edition ? $edition.getEditionNumber() : undefined);
|
|
162
|
+
// Coalesce marginal layout requests from descendants into a single
|
|
163
|
+
// layout pass per animation frame. Marginal components request a pass
|
|
164
|
+
// whenever their rendered content changes.
|
|
138
165
|
let layoutRequested = false;
|
|
139
166
|
function requestLayout() {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
167
|
+
if (typeof requestAnimationFrame === "undefined") return;
|
|
168
|
+
if (layoutRequested) return;
|
|
169
|
+
layoutRequested = true;
|
|
170
|
+
requestAnimationFrame(() => {
|
|
171
|
+
layoutRequested = false;
|
|
172
|
+
layoutMarginals();
|
|
173
|
+
});
|
|
147
174
|
}
|
|
148
175
|
let chapterStore = writable();
|
|
149
176
|
setContext(CHAPTER, chapterStore);
|
|
150
177
|
$effect.pre(() => {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
178
|
+
chapterStore.set({
|
|
179
|
+
chapter,
|
|
180
|
+
highlightedWord: getHighlightedWord(),
|
|
181
|
+
highlightedID,
|
|
182
|
+
marginal,
|
|
183
|
+
requestLayout
|
|
184
|
+
});
|
|
158
185
|
});
|
|
159
|
-
let editable = $derived(
|
|
160
|
-
!print && (isBookEditable() || isEditionEditable() || isChapterEditable() || $auth !== void 0 && $auth.user !== null && chapter.isEditor($auth.user.uid))
|
|
161
|
-
);
|
|
186
|
+
let editable = $derived(!print && (isBookEditable() || isEditionEditable() || isChapterEditable() || $auth !== undefined && $auth.user !== null && chapter.isEditor($auth.user.uid)));
|
|
162
187
|
</script>
|
|
163
188
|
|
|
164
189
|
{#if $edition}
|
|
@@ -215,7 +240,7 @@ let editable = $derived(
|
|
|
215
240
|
goto(
|
|
216
241
|
`/write/${$book.getID()}/${editionNumber}/${newChapterID}`,
|
|
217
242
|
{
|
|
218
|
-
|
|
243
|
+
reset: false,
|
|
219
244
|
},
|
|
220
245
|
);
|
|
221
246
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import { getContext } from 'svelte';
|
|
6
6
|
import { get } from 'svelte/store';
|
|
7
7
|
import { page } from '$app/state';
|
|
8
|
-
import { updateLock } from '../../models/CRUD';
|
|
9
8
|
export const DARK_MODE = Symbol('dark');
|
|
10
9
|
export function getDarkMode() {
|
|
11
10
|
return getContext(DARK_MODE);
|
|
@@ -119,6 +118,9 @@ export async function lease(authStore, editionStore, contentID, lock) {
|
|
|
119
118
|
const edition = get(editionStore);
|
|
120
119
|
const auth = get(authStore);
|
|
121
120
|
if (edition && auth && auth.user) {
|
|
121
|
+
// Loaded here rather than at the top so bound books, which never
|
|
122
|
+
// lease, don't load Firebase. See FirebaseAvailable.ts.
|
|
123
|
+
const { updateLock } = await import('../../models/CRUD');
|
|
122
124
|
await updateLock(edition, auth.user.uid, contentID, lock);
|
|
123
125
|
const latestEdition = get(editionStore);
|
|
124
126
|
const latestAuth = get(authStore);
|
|
@@ -1,64 +1,52 @@
|
|
|
1
|
-
<script lang="ts">import Parser from "../../models/chapter/Parser";
|
|
1
|
+
<script lang="ts">import Parser from "../../models/chapter/Parser.js";
|
|
2
2
|
import ConfirmButton from "../editor/ConfirmButton.svelte";
|
|
3
3
|
import TextEditor from "../editor/TextEditor.svelte";
|
|
4
4
|
import BookishEditor from "../editor/BookishEditor.svelte";
|
|
5
5
|
import Format from "../chapter/Format.svelte";
|
|
6
6
|
import Note from "../editor/Note.svelte";
|
|
7
|
-
import {
|
|
8
|
-
getUser,
|
|
9
|
-
getEdition,
|
|
10
|
-
getLeasee,
|
|
11
|
-
isChapterEditable,
|
|
12
|
-
lease,
|
|
13
|
-
isEditionEditable
|
|
14
|
-
} from "./Contexts";
|
|
7
|
+
import { getUser, getEdition, getLeasee, isChapterEditable, lease, isEditionEditable } from "./Contexts";
|
|
15
8
|
import Button from "../app/Button.svelte";
|
|
16
9
|
let { id, definition } = $props();
|
|
17
10
|
let auth = getUser();
|
|
18
11
|
let edition = getEdition();
|
|
19
12
|
let editable = isEditionEditable();
|
|
13
|
+
// Focus after adding a new synonym.
|
|
20
14
|
let newSynonym = false;
|
|
21
15
|
let synonymsEditor = $state(null);
|
|
16
|
+
// After the definition re-renders with a newly added synonym,
|
|
17
|
+
// focus its editor.
|
|
22
18
|
$effect(() => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
void definition;
|
|
20
|
+
if (newSynonym && synonymsEditor) {
|
|
21
|
+
const editors = synonymsEditor.querySelectorAll("input");
|
|
22
|
+
if (editors.length > 0) {
|
|
23
|
+
const last = editors[editors.length - 1];
|
|
24
|
+
// Defer past the click's default action, which would
|
|
25
|
+
// otherwise return focus to the add-synonym button.
|
|
26
|
+
if (last instanceof HTMLElement) setTimeout(() => last.focus(), 0);
|
|
27
|
+
}
|
|
28
|
+
newSynonym = false;
|
|
29
|
+
}
|
|
33
30
|
});
|
|
34
31
|
function addSynonym() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
newSynonym = true;
|
|
32
|
+
if ($edition) {
|
|
33
|
+
edition.set($edition.withEditedDefinition(id, {
|
|
34
|
+
phrase: definition.phrase,
|
|
35
|
+
definition: definition.definition,
|
|
36
|
+
synonyms: [...definition.synonyms ?? [], ""]
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
newSynonym = true;
|
|
45
40
|
}
|
|
46
41
|
function removeSynonym(index) {
|
|
47
|
-
|
|
48
|
-
updateSynonyms([
|
|
49
|
-
...definition.synonyms.slice(0, index),
|
|
50
|
-
...definition.synonyms.slice(index + 1)
|
|
51
|
-
]);
|
|
42
|
+
if (definition.synonyms && index >= 0 && index < definition.synonyms.length) updateSynonyms([...definition.synonyms.slice(0, index), ...definition.synonyms.slice(index + 1)]);
|
|
52
43
|
}
|
|
53
44
|
function updateSynonyms(synonyms) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
synonyms
|
|
60
|
-
})
|
|
61
|
-
);
|
|
45
|
+
if ($edition) edition.set($edition.withEditedDefinition(id, {
|
|
46
|
+
phrase: definition.phrase,
|
|
47
|
+
definition: definition.definition,
|
|
48
|
+
synonyms
|
|
49
|
+
}));
|
|
62
50
|
}
|
|
63
51
|
let syns = $derived(definition.synonyms || []);
|
|
64
52
|
</script>
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
<script lang="ts">import Parser from "../../models/chapter/Parser";
|
|
1
|
+
<script lang="ts">import Parser from "../../models/chapter/Parser.js";
|
|
2
2
|
import ChapterBody from "../chapter/ChapterBody.svelte";
|
|
3
3
|
import BookishEditor from "../editor/BookishEditor.svelte";
|
|
4
|
-
import {
|
|
5
|
-
getUser,
|
|
6
|
-
getEdition,
|
|
7
|
-
getLeasee,
|
|
8
|
-
isEditionEditable,
|
|
9
|
-
lease
|
|
10
|
-
} from "./Contexts";
|
|
4
|
+
import { getUser, getEdition, getLeasee, isEditionEditable, lease } from "./Contexts";
|
|
11
5
|
let auth = getUser();
|
|
12
6
|
let editable = isEditionEditable();
|
|
13
7
|
let edition = getEdition();
|