bookish-press 0.5.25 → 0.5.32
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 +4 -3
- package/dist/components/chapter/ExtendedPrism.d.ts +1 -1
- package/dist/components/chapter/Figure.svelte +0 -1
- package/dist/components/chapter/Footnote.svelte +1 -0
- package/dist/components/chapter/List.svelte +16 -33
- package/dist/components/chapter/Paragraph.svelte +7 -2
- package/dist/components/chapter/Quote.svelte +6 -6
- package/dist/components/controls/DarkToggle.svelte +1 -1
- package/dist/components/editor/BookishEditor.svelte +9 -16
- package/dist/components/editor/Commands.js +14 -14
- package/dist/components/editor/ConfirmButton.svelte +1 -1
- package/dist/components/editor/EmbedEditor.svelte +3 -3
- package/dist/components/editor/ImageChooser.svelte +6 -26
- package/dist/components/editor/ImageThumbnail.svelte +61 -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/page/Chapter.svelte +1 -1
- package/dist/components/page/DefinitionView.svelte +43 -38
- package/dist/components/page/Edition.svelte +11 -4
- 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 +1 -0
- package/dist/models/CRUD.js +17 -4
- 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 +20 -18
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<svelte:options immutable={true} />
|
|
2
2
|
|
|
3
3
|
<script>import { storage } from '../../models/Firebase';
|
|
4
|
-
import { getCaret, getBook, getBase } from '../page/Contexts';
|
|
4
|
+
import { getCaret, getBook, getBase, isEditionEditable, } from '../page/Contexts';
|
|
5
5
|
import Figure from './Figure.svelte';
|
|
6
6
|
import { getContext } from 'svelte';
|
|
7
7
|
export let node;
|
|
8
|
-
export let editable =
|
|
8
|
+
export let editable = true;
|
|
9
9
|
export let placeholder = '';
|
|
10
10
|
export let imageOnly = false;
|
|
11
11
|
$: url = node.getURL();
|
|
@@ -13,6 +13,7 @@ $: description = node.getDescription();
|
|
|
13
13
|
let caret = getCaret();
|
|
14
14
|
let book = getBook();
|
|
15
15
|
let base = getBase();
|
|
16
|
+
let editionEditable = isEditionEditable();
|
|
16
17
|
// Bookish editor should have passed down a way to set the active editor.
|
|
17
18
|
let claimCaret = getContext('claimeditor');
|
|
18
19
|
let dragging = false;
|
|
@@ -94,7 +95,7 @@ function handleClick() {
|
|
|
94
95
|
credit={imageOnly ? undefined : node.getCredit()}
|
|
95
96
|
>
|
|
96
97
|
{#if url.trim().length === 0}
|
|
97
|
-
{#if editable}
|
|
98
|
+
{#if editable && editionEditable}
|
|
98
99
|
<p
|
|
99
100
|
class={`bookish-figure-unspecified ${
|
|
100
101
|
dragging ? 'bookish-figure-dragging' : ''
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export default Prism;
|
|
2
|
-
import Prism from
|
|
2
|
+
import Prism from 'prismjs';
|
|
@@ -24,50 +24,33 @@ export let node;
|
|
|
24
24
|
line-height: var(--bookish-paragraph-line-height-tight);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
counter-reset: item;
|
|
27
|
+
li {
|
|
28
|
+
margin-bottom: calc(0.5 * var(--bookish-paragraph-spacing));
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
/* Lists are decimal numbered */
|
|
32
|
+
ol {
|
|
33
|
+
list-style: decimal;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/* First level is decimal */
|
|
37
|
+
ol li {
|
|
38
|
+
counter-increment: list-item;
|
|
39
|
+
list-style-type: decimal;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
/* Second level is lowercase alpha */
|
|
43
|
+
ol ol li {
|
|
44
|
+
list-style-type: lower-alpha;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
/* Third level is uppercase alpha */
|
|
48
|
+
ol ol ol li {
|
|
49
|
+
list-style-type: upper-alpha;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
ol
|
|
49
|
-
ul > :global(ol > li::before),
|
|
50
|
-
ul > :global(ul > ol > li::before) {
|
|
51
|
-
content: counter(item) '. ';
|
|
52
|
+
ol li::marker {
|
|
52
53
|
color: var(--bookish-bullet-color);
|
|
53
|
-
display: inline-block;
|
|
54
|
-
width: 1em;
|
|
55
|
-
margin-left: -1.5rem;
|
|
56
|
-
margin-right: 0.5rem;
|
|
57
|
-
text-align: right;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
ol > :global(ol > li::before),
|
|
61
|
-
ul > :global(ol > ol > li::before) {
|
|
62
|
-
content: counter(item, lower-alpha) '. ';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
ol > :global(ol > ol > li:before) {
|
|
66
|
-
content: counter(item, upper-alpha) '. ';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
li {
|
|
70
|
-
margin-bottom: calc(0.5 * var(--bookish-paragraph-spacing));
|
|
71
54
|
}
|
|
72
55
|
|
|
73
56
|
ul li {
|
|
@@ -19,10 +19,15 @@ $: classes =
|
|
|
19
19
|
($chapter && $chapter.highlightedID === id
|
|
20
20
|
? ' bookish-content-highlight'
|
|
21
21
|
: '');
|
|
22
|
+
$: firstParagraph = $root.getFirstParagraph();
|
|
23
|
+
$: dropcap =
|
|
24
|
+
placeholder === undefined &&
|
|
25
|
+
node === firstParagraph &&
|
|
26
|
+
/^[a-zA-Z]/.test(firstParagraph.getFirstTextNode().getText());
|
|
22
27
|
</script>
|
|
23
28
|
|
|
24
29
|
{#if level === 0}
|
|
25
|
-
<p data-nodeid={node.nodeID} class:placeholder
|
|
30
|
+
<p data-nodeid={node.nodeID} class:placeholder class:dropcap
|
|
26
31
|
><Format node={node.getFormat()} placeholder={placeholder ?? '¶'} /></p
|
|
27
32
|
>
|
|
28
33
|
{:else}
|
|
@@ -54,7 +59,7 @@ $: classes =
|
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/* This implements a drop cap in the first letter of the first text in the first format of the first paragraph of a chapter (but only chapters). */
|
|
57
|
-
|
|
62
|
+
p.dropcap::first-letter {
|
|
58
63
|
padding: 0 0.25rem;
|
|
59
64
|
margin: 0 0.25rem 0 0;
|
|
60
65
|
font-size: 4rem;
|
|
@@ -37,12 +37,11 @@ $: credit = node.getCredit();
|
|
|
37
37
|
font-family: var(--bookish-header-font-family);
|
|
38
38
|
font-weight: 400;
|
|
39
39
|
font-style: italic;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
position: relative;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding-top: 0;
|
|
42
|
+
padding-bottom: var(--bookish-paragraph-spacing);
|
|
43
|
+
padding-left: var(--bookish-indent);
|
|
44
|
+
padding-right: var(--bookish-indent);
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
.quote > :global(p) {
|
|
@@ -52,6 +51,7 @@ $: credit = node.getCredit();
|
|
|
52
51
|
|
|
53
52
|
.caption {
|
|
54
53
|
margin-top: 0.5em;
|
|
54
|
+
margin-right: var(--bookish-indent);
|
|
55
55
|
text-align: right;
|
|
56
56
|
font-family: var(--bookish-header-font-family);
|
|
57
57
|
font-style: italic;
|
|
@@ -13,7 +13,7 @@ function change() {
|
|
|
13
13
|
class="dark-toggle"
|
|
14
14
|
role="button"
|
|
15
15
|
aria-label={dark === true ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
16
|
-
|
|
16
|
+
tabindex="0"
|
|
17
17
|
on:click={change}
|
|
18
18
|
on:keydown={(event) => {
|
|
19
19
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
@@ -16,9 +16,8 @@ import ErrorNode from '../../models/chapter/ErrorNode';
|
|
|
16
16
|
import { caretRangeToIndexRange, indexRangeToCaretRange, } from '../../models/chapter/Caret';
|
|
17
17
|
import commands from './Commands';
|
|
18
18
|
import { afterUpdate, onMount, setContext } from 'svelte';
|
|
19
|
-
import { getCaret
|
|
19
|
+
import { getCaret } from '../page/Contexts';
|
|
20
20
|
import CaretView from './CaretView.svelte';
|
|
21
|
-
import { writable } from 'svelte/store';
|
|
22
21
|
const IDLE_TIME = 500;
|
|
23
22
|
export let text;
|
|
24
23
|
export let parser;
|
|
@@ -159,25 +158,18 @@ afterUpdate(() => {
|
|
|
159
158
|
const lineHeight = lineHeightString.endsWith('px')
|
|
160
159
|
? parseInt(lineHeightString.substring(0, lineHeightString.length - 2))
|
|
161
160
|
: undefined;
|
|
162
|
-
//
|
|
163
|
-
let
|
|
161
|
+
// Figure out the relative position of this editor.
|
|
162
|
+
let editorAncestor = element;
|
|
164
163
|
let relativeX = 0;
|
|
165
164
|
let relativeY = 0;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
while (caretAncestor != null) {
|
|
169
|
-
const style = window.getComputedStyle(caretAncestor);
|
|
165
|
+
while (editorAncestor != null) {
|
|
166
|
+
const style = window.getComputedStyle(editorAncestor);
|
|
170
167
|
if (style.position === 'relative') {
|
|
171
|
-
relativeX +=
|
|
172
|
-
relativeY +=
|
|
168
|
+
relativeX += editorAncestor.offsetLeft;
|
|
169
|
+
relativeY += editorAncestor.offsetTop;
|
|
173
170
|
}
|
|
174
|
-
|
|
175
|
-
fixedX = caretAncestor.offsetLeft;
|
|
176
|
-
fixedY = caretAncestor.offsetTop;
|
|
177
|
-
}
|
|
178
|
-
caretAncestor = caretAncestor.parentElement;
|
|
171
|
+
editorAncestor = editorAncestor.parentElement;
|
|
179
172
|
}
|
|
180
|
-
// Is the caret in a fixed position marginal? Get it's position.
|
|
181
173
|
// If we're after a new line, calculate the correct position, since selections don't actually render to the next line.
|
|
182
174
|
const left = afterNewLine
|
|
183
175
|
? textRect.left
|
|
@@ -185,6 +177,7 @@ afterUpdate(() => {
|
|
|
185
177
|
const top = afterNewLine && lineHeight
|
|
186
178
|
? rangeRect.top + lineHeight
|
|
187
179
|
: rangeRect.top;
|
|
180
|
+
// Compute the final position of the caret.
|
|
188
181
|
const position = {
|
|
189
182
|
x: left + window.scrollX - relativeX,
|
|
190
183
|
y: top + window.scrollY - relativeY,
|
|
@@ -835,19 +835,6 @@ const commands = [
|
|
|
835
835
|
return rootWithNode(context, context.blocks, edit.root, () => edit.range.start);
|
|
836
836
|
},
|
|
837
837
|
},
|
|
838
|
-
{
|
|
839
|
-
icon: IndentIcon,
|
|
840
|
-
description: 'indent list item',
|
|
841
|
-
category: 'list',
|
|
842
|
-
mutates: true,
|
|
843
|
-
control: false,
|
|
844
|
-
alt: false,
|
|
845
|
-
shift: false,
|
|
846
|
-
key: 'Tab',
|
|
847
|
-
visible: (context) => context.list !== undefined,
|
|
848
|
-
active: (context) => context.list !== undefined,
|
|
849
|
-
handler: (context) => rootWithNode(context, context.blocks, context.blocks?.withListsIndented(context.range, true)),
|
|
850
|
-
},
|
|
851
838
|
{
|
|
852
839
|
icon: UnindentIcon,
|
|
853
840
|
description: 'unindent list item',
|
|
@@ -862,6 +849,19 @@ const commands = [
|
|
|
862
849
|
context.list.isInside(context.root, ListNode),
|
|
863
850
|
handler: (context) => rootWithNode(context, context.blocks, context.blocks?.withListsIndented(context.range, false)),
|
|
864
851
|
},
|
|
852
|
+
{
|
|
853
|
+
icon: IndentIcon,
|
|
854
|
+
description: 'indent list item',
|
|
855
|
+
category: 'list',
|
|
856
|
+
mutates: true,
|
|
857
|
+
control: false,
|
|
858
|
+
alt: false,
|
|
859
|
+
shift: false,
|
|
860
|
+
key: 'Tab',
|
|
861
|
+
visible: (context) => context.list !== undefined,
|
|
862
|
+
active: (context) => context.list !== undefined,
|
|
863
|
+
handler: (context) => rootWithNode(context, context.blocks, context.blocks?.withListsIndented(context.range, true)),
|
|
864
|
+
},
|
|
865
865
|
{
|
|
866
866
|
icon: ClearIcon,
|
|
867
867
|
description: 'clear formatting',
|
|
@@ -1222,7 +1222,7 @@ const commands = [
|
|
|
1222
1222
|
const newTable = new TableNode(newRows, '|', new FormatNode('', [new TextNode()]));
|
|
1223
1223
|
return rootWithNode(context, context.blocks, context.blocks.withBlockInsertedBefore(context.paragraph, newTable), () => {
|
|
1224
1224
|
return {
|
|
1225
|
-
node: newTable.getRows()[0][0].
|
|
1225
|
+
node: newTable.getRows()[0][0].getFirstTextNode(),
|
|
1226
1226
|
index: 0,
|
|
1227
1227
|
};
|
|
1228
1228
|
});
|
|
@@ -9,7 +9,7 @@ let confirming = false;
|
|
|
9
9
|
{#if !confirming}
|
|
10
10
|
<Button {tooltip} command={() => (confirming = true)}><slot /></Button>
|
|
11
11
|
{:else}
|
|
12
|
-
<Button tooltip="no" command={() => (confirming = false)}>
|
|
12
|
+
<Button tooltip="no" command={() => (confirming = false)}>cancel</Button
|
|
13
13
|
> <Button tooltip="yes" {command}>{confirm}</Button>
|
|
14
14
|
{/if}
|
|
15
15
|
</div>
|
|
@@ -43,10 +43,10 @@ function handleImageChange(event) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
function handleImageSelection(image) {
|
|
46
|
-
// Toggle the embed
|
|
47
46
|
$caret?.edit(embed, embed.getURL() === image.url
|
|
48
|
-
? embed
|
|
49
|
-
|
|
47
|
+
? // Toggle the embed if choosing the same image
|
|
48
|
+
embed.withURL('').withDescription('')
|
|
49
|
+
: embed.withURLs(image.url, image.thumb));
|
|
50
50
|
}
|
|
51
51
|
</script>
|
|
52
52
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script>import { onMount } from 'svelte';
|
|
2
|
-
import { getBook
|
|
2
|
+
import { getBook } from '../page/Contexts';
|
|
3
|
+
import ImageThumbnail from './ImageThumbnail.svelte';
|
|
3
4
|
export let select;
|
|
4
5
|
export let selection;
|
|
5
|
-
let editor = getCaret();
|
|
6
6
|
let book = getBook();
|
|
7
7
|
let images = undefined;
|
|
8
8
|
let expanded = false;
|
|
@@ -37,18 +37,10 @@ onMount(() => {
|
|
|
37
37
|
<!-- Sort the images by their URL. There's probably a more meaningful sort,
|
|
38
38
|
such as placing unused images at the front of the list. -->
|
|
39
39
|
{#each images.sort((a, b) => a.url.localeCompare(b.url)) as image}
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
src={image.url}
|
|
45
|
-
alt={`Image named ${image.url}`}
|
|
46
|
-
tabIndex="0"
|
|
47
|
-
on:click|stopPropagation={() => select(image)}
|
|
48
|
-
on:keydown={(event) =>
|
|
49
|
-
event.key === 'Enter' || event.key === ' '
|
|
50
|
-
? select(image)
|
|
51
|
-
: undefined}
|
|
40
|
+
<ImageThumbnail
|
|
41
|
+
{image}
|
|
42
|
+
selected={image.url === selection}
|
|
43
|
+
{select}
|
|
52
44
|
/>
|
|
53
45
|
{:else}
|
|
54
46
|
No images uploaded
|
|
@@ -74,16 +66,4 @@ onMount(() => {
|
|
|
74
66
|
height: auto;
|
|
75
67
|
overflow-y: auto;
|
|
76
68
|
}
|
|
77
|
-
|
|
78
|
-
.bookish-image-chooser-image {
|
|
79
|
-
display: inline-block;
|
|
80
|
-
height: 2em;
|
|
81
|
-
cursor: pointer;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.bookish-image-chooser-image.selected {
|
|
85
|
-
outline: var(--app-chrome-border-size) solid
|
|
86
|
-
var(--app-interactive-color);
|
|
87
|
-
outline-offset: calc(-1 * var(--app-chrome-border-size));
|
|
88
|
-
}
|
|
89
69
|
</style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script>export let image;
|
|
2
|
+
export let selected;
|
|
3
|
+
export let select;
|
|
4
|
+
let retries = 3;
|
|
5
|
+
let error = false;
|
|
6
|
+
let loading = true;
|
|
7
|
+
function loadImage(url) {
|
|
8
|
+
retries--;
|
|
9
|
+
let img = new Image();
|
|
10
|
+
img.onerror = () => {
|
|
11
|
+
if (retries > 0) {
|
|
12
|
+
setTimeout(() => loadImage(url), 1000);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
loading = false;
|
|
16
|
+
error = true;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
img.onload = () => {
|
|
20
|
+
loading = false;
|
|
21
|
+
};
|
|
22
|
+
img.src = url;
|
|
23
|
+
}
|
|
24
|
+
loadImage(image.thumb);
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{#if loading}
|
|
28
|
+
<div class="thumbnail loading" />
|
|
29
|
+
{:else}
|
|
30
|
+
<img
|
|
31
|
+
class="thumbnail"
|
|
32
|
+
class:selected
|
|
33
|
+
src={error ? image.url : image.thumb}
|
|
34
|
+
alt={`Image named ${image.url}`}
|
|
35
|
+
tabIndex="0"
|
|
36
|
+
on:click|stopPropagation={() => select(image)}
|
|
37
|
+
on:keydown={(event) =>
|
|
38
|
+
event.key === 'Enter' || event.key === ' '
|
|
39
|
+
? select(image)
|
|
40
|
+
: undefined}
|
|
41
|
+
/>
|
|
42
|
+
{/if}
|
|
43
|
+
|
|
44
|
+
<style>
|
|
45
|
+
.thumbnail {
|
|
46
|
+
display: inline-block;
|
|
47
|
+
height: 2em;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.thumbnail.selected {
|
|
52
|
+
outline: var(--app-chrome-border-size) solid
|
|
53
|
+
var(--app-interactive-color);
|
|
54
|
+
outline-offset: calc(-1 * var(--app-chrome-border-size));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.loading {
|
|
58
|
+
width: 2.5em;
|
|
59
|
+
background: var(--app-chrome-background);
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
import type { Image } from '../../models/book/BookMedia';
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: {
|
|
5
|
+
image: Image;
|
|
6
|
+
selected: boolean;
|
|
7
|
+
select: (image: Image) => void;
|
|
8
|
+
};
|
|
9
|
+
events: {
|
|
10
|
+
[evt: string]: CustomEvent<any>;
|
|
11
|
+
};
|
|
12
|
+
slots: {};
|
|
13
|
+
};
|
|
14
|
+
export type ImageThumbnailProps = typeof __propDef.props;
|
|
15
|
+
export type ImageThumbnailEvents = typeof __propDef.events;
|
|
16
|
+
export type ImageThumbnailSlots = typeof __propDef.slots;
|
|
17
|
+
export default class ImageThumbnail extends SvelteComponentTyped<ImageThumbnailProps, ImageThumbnailEvents, ImageThumbnailSlots> {
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -24,7 +24,7 @@ $: {
|
|
|
24
24
|
// them with non-breaking spaces.
|
|
25
25
|
const sizedText = showPlaceholder()
|
|
26
26
|
? placeholder
|
|
27
|
-
: text.replace(/\s
|
|
27
|
+
: text.replace(/\s$/g, '\u00a0');
|
|
28
28
|
// Clip the text to prevent this editor from getting too long when editing. If it goes past one line,
|
|
29
29
|
// the measurements and layout are way off.
|
|
30
30
|
const trimmedText = width !== undefined &&
|
|
@@ -111,7 +111,6 @@ function handleKeyPress(event) {
|
|
|
111
111
|
bind:this={field}
|
|
112
112
|
bind:value={text}
|
|
113
113
|
required
|
|
114
|
-
role="textbox"
|
|
115
114
|
aria-invalid={error !== undefined}
|
|
116
115
|
aria-label={label}
|
|
117
116
|
aria-placeholder={label}
|
|
@@ -136,6 +135,8 @@ function handleKeyPress(event) {
|
|
|
136
135
|
<style>
|
|
137
136
|
.text-editor {
|
|
138
137
|
position: relative;
|
|
138
|
+
display: inline-block;
|
|
139
|
+
max-width: 100%;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
input[type='text'] {
|
|
@@ -175,6 +176,8 @@ function handleKeyPress(event) {
|
|
|
175
176
|
|
|
176
177
|
/* This ensures the error message always appears below empty text editors */
|
|
177
178
|
.sizer {
|
|
179
|
+
display: inline-block;
|
|
180
|
+
max-width: 100%;
|
|
178
181
|
min-height: 1em;
|
|
179
182
|
opacity: 0;
|
|
180
183
|
border: none;
|
|
@@ -24,7 +24,7 @@ function toggle() {
|
|
|
24
24
|
class={`bookish-app-interactive ${
|
|
25
25
|
saving === Status.Saving ? ' bookish-text-editor-saving' : ''
|
|
26
26
|
} ${saving === Status.Error ? ' bookish-text-editor-error' : ''}`}
|
|
27
|
-
|
|
27
|
+
tabindex="0"
|
|
28
28
|
role="switch"
|
|
29
29
|
aria-checked={on}
|
|
30
30
|
on:keydown={(event) =>
|
|
@@ -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>
|