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.
Files changed (54) hide show
  1. package/dist/components/BookPreview.svelte +1 -0
  2. package/dist/components/chapter/Callout.svelte +0 -1
  3. package/dist/components/chapter/Embed.svelte +4 -3
  4. package/dist/components/chapter/ExtendedPrism.d.ts +1 -1
  5. package/dist/components/chapter/Figure.svelte +0 -1
  6. package/dist/components/chapter/Footnote.svelte +1 -0
  7. package/dist/components/chapter/List.svelte +16 -33
  8. package/dist/components/chapter/Paragraph.svelte +7 -2
  9. package/dist/components/chapter/Quote.svelte +6 -6
  10. package/dist/components/controls/DarkToggle.svelte +1 -1
  11. package/dist/components/editor/BookishEditor.svelte +9 -16
  12. package/dist/components/editor/Commands.js +14 -14
  13. package/dist/components/editor/ConfirmButton.svelte +1 -1
  14. package/dist/components/editor/EmbedEditor.svelte +3 -3
  15. package/dist/components/editor/ImageChooser.svelte +6 -26
  16. package/dist/components/editor/ImageThumbnail.svelte +61 -0
  17. package/dist/components/editor/ImageThumbnail.svelte.d.ts +19 -0
  18. package/dist/components/editor/Permissions.svelte +2 -0
  19. package/dist/components/editor/TextEditor.svelte +5 -2
  20. package/dist/components/editor/Toggle.svelte +1 -1
  21. package/dist/components/page/Chapter.svelte +1 -1
  22. package/dist/components/page/DefinitionView.svelte +43 -38
  23. package/dist/components/page/Edition.svelte +11 -4
  24. package/dist/components/page/EditionLoader.svelte +12 -3
  25. package/dist/components/page/Editions.svelte +1 -1
  26. package/dist/components/page/Header.svelte +73 -69
  27. package/dist/components/page/Media.svelte +5 -4
  28. package/dist/components/page/MediaPreview.svelte +8 -2
  29. package/dist/components/page/MediaPreview.svelte.d.ts +1 -0
  30. package/dist/components/page/Outline.svelte +1 -1
  31. package/dist/components/page/Page.svelte +6 -3
  32. package/dist/components/page/Reference.svelte +1 -1
  33. package/dist/components/page/Search.svelte +1 -1
  34. package/dist/components/page/TableOfContents.svelte +3 -6
  35. package/dist/components/page/TableOfContentsRow.svelte +1 -1
  36. package/dist/components/page/ThemeEditor.svelte +1 -1
  37. package/dist/models/CRUD.d.ts +1 -0
  38. package/dist/models/CRUD.js +17 -4
  39. package/dist/models/book/BookMedia.d.ts +3 -0
  40. package/dist/models/book/BookMedia.js +22 -5
  41. package/dist/models/book/Chapter.d.ts +9 -7
  42. package/dist/models/book/Chapter.js +16 -6
  43. package/dist/models/book/Edition.d.ts +6 -1
  44. package/dist/models/book/Edition.js +22 -10
  45. package/dist/models/book/Words.js +0 -1
  46. package/dist/models/chapter/ChapterNode.d.ts +1 -0
  47. package/dist/models/chapter/ChapterNode.js +3 -0
  48. package/dist/models/chapter/DefinitionNode.js +1 -1
  49. package/dist/models/chapter/FormatNode.js +4 -1
  50. package/dist/models/chapter/InlineCodeNode.js +1 -1
  51. package/dist/models/chapter/LinkNode.js +1 -1
  52. package/dist/models/chapter/ListNode.js +1 -1
  53. package/dist/models/chapter/Parser.js +3 -6
  54. package/package.json +20 -18
@@ -60,6 +60,7 @@ $: url =
60
60
  text-align: left;
61
61
  flex-wrap: wrap;
62
62
  height: 12em;
63
+ overflow-y: hidden;
63
64
  }
64
65
 
65
66
  .cover {
@@ -25,7 +25,6 @@ export let node;
25
25
  padding-bottom: var(--bookish-paragraph-spacing);
26
26
  line-height: var(--bookish-paragraph-line-height-tight);
27
27
  clear: both;
28
- position: relative;
29
28
  }
30
29
 
31
30
  .bookish-callout :global(h2),
@@ -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 = false;
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 "prismjs";
2
+ import Prism from 'prismjs';
@@ -70,7 +70,6 @@ function focusFigure() {
70
70
 
71
71
  .caption {
72
72
  text-align: center;
73
- position: relative;
74
73
  color: var(--bookish-muted-color);
75
74
  line-height: var(--bookish-paragraph-line-height-tight);
76
75
  font-size: var(--bookish-block-font-size);
@@ -67,6 +67,7 @@ afterUpdate(() => $chapter?.layoutMarginals());
67
67
  font-weight: 300;
68
68
  font-size: var(--bookish-small-font-size);
69
69
  /* line-height: 1.5em; */
70
+ text-align: left;
70
71
  }
71
72
 
72
73
  .bookish-footnote-symbol {
@@ -24,50 +24,33 @@ export let node;
24
24
  line-height: var(--bookish-paragraph-line-height-tight);
25
25
  }
26
26
 
27
- ol {
28
- list-style: none;
29
- counter-reset: item;
27
+ li {
28
+ margin-bottom: calc(0.5 * var(--bookish-paragraph-spacing));
30
29
  }
31
30
 
32
- ol > li {
33
- counter-increment: item;
31
+ /* Lists are decimal numbered */
32
+ ol {
33
+ list-style: decimal;
34
34
  }
35
35
 
36
- ol > li {
37
- list-style-type: none;
36
+ /* First level is decimal */
37
+ ol li {
38
+ counter-increment: list-item;
39
+ list-style-type: decimal;
38
40
  }
39
41
 
40
- ol > :global(ol > li) {
41
- list-style-type: none;
42
+ /* Second level is lowercase alpha */
43
+ ol ol li {
44
+ list-style-type: lower-alpha;
42
45
  }
43
46
 
44
- ol > :global(ol > ol > li) {
45
- list-style-type: none;
47
+ /* Third level is uppercase alpha */
48
+ ol ol ol li {
49
+ list-style-type: upper-alpha;
46
50
  }
47
51
 
48
- ol > :global(li::before),
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
- :global(.chapter-content) p:first-of-type:not(.placeholder)::first-letter {
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
- padding: 0;
41
- margin-top: var(--bookish-paragraph-spacing);
42
- margin-bottom: var(--bookish-paragraph-spacing);
43
- margin-left: var(--bookish-indent);
44
- margin-right: var(--bookish-indent);
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
- tabIndex="0"
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, getRoot } from '../page/Contexts';
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
- // Is the caret in relatively positioned ancestors above the editor? Undo their offsets.
163
- let caretAncestor = startNode.parentElement;
161
+ // Figure out the relative position of this editor.
162
+ let editorAncestor = element;
164
163
  let relativeX = 0;
165
164
  let relativeY = 0;
166
- let fixedX = undefined;
167
- let fixedY = undefined;
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 += caretAncestor.offsetLeft;
172
- relativeY += caretAncestor.offsetTop;
168
+ relativeX += editorAncestor.offsetLeft;
169
+ relativeY += editorAncestor.offsetTop;
173
170
  }
174
- else if (style.position === 'fixed') {
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].getTextNodes()[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)}>no</Button
12
+ <Button tooltip="no" command={() => (confirming = false)}>cancel</Button
13
13
  >&nbsp;<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.withURL('').withDescription('')
49
- : embed.withURL(image.url));
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, getCaret } from '../page/Contexts';
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
- <img
41
- class={`bookish-image-chooser-image ${
42
- image.url === selection ? 'selected' : ''
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 {};
@@ -28,6 +28,8 @@ async function addEditor() {
28
28
  }
29
29
  }
30
30
  function hasEditor(email) {
31
+ if (email.length === 0)
32
+ return false;
31
33
  const match = Array.from($emails.entries()).find(([, e]) => e === email);
32
34
  if (match === undefined)
33
35
  return false;
@@ -24,7 +24,7 @@ $: {
24
24
  // them with non-breaking spaces.
25
25
  const sizedText = showPlaceholder()
26
26
  ? placeholder
27
- : text.replace(/\s/g, '\u00a0');
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
- tabIndex="0"
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 = isChapterEditable();
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="remove definition?"
87
+ confirm="delete entry"
71
88
  command={() =>
72
89
  $edition
73
90
  ? edition.set($edition.withoutDefinition(id))
74
- : undefined}>- definition</ConfirmButton
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
- {#if syns.length === 0}
115
- <Note>no synonyms</Note>
116
- {:else}
117
- {#each syns as syn, index}
118
- <Note>
119
- <TextEditor
120
- text={syn}
121
- label={'Synonym editor.'}
122
- placeholder="synonym"
123
- valid={() => undefined}
124
- save={(text) => {
125
- const newSyns = [...syns];
126
- // Remove the synonym if it's empty but wasn't before
127
- if (syn.length > 0 && text.length === 0)
128
- newSyns.splice(index, 1);
129
- else newSyns[index] = text;
130
- if ($edition)
131
- edition.set(
132
- $edition.withEditedDefinition(
133
- id,
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},&nbsp;{/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},&nbsp;{/if}
150
+ {/each}
148
151
  </span>&nbsp;<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>