bookish-press 0.4.0 → 0.4.16
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/README.md +3 -1
- package/components/Auth.svelte +19 -14
- package/components/Auth.svelte.d.ts +4 -4
- package/components/Authentication.d.ts +2 -2
- package/components/BookPreview.svelte +77 -59
- package/components/BookPreview.svelte.d.ts +5 -5
- package/components/External.svelte +1 -1
- package/components/External.svelte.d.ts +3 -3
- package/components/Link.svelte +49 -3
- package/components/Link.svelte.d.ts +3 -4
- package/components/app/BookList.svelte +19 -0
- package/components/app/BookList.svelte.d.ts +18 -0
- package/components/app/Button.svelte +49 -0
- package/components/app/Button.svelte.d.ts +22 -0
- package/components/app/Feedback.svelte +45 -0
- package/components/app/Feedback.svelte.d.ts +18 -0
- package/components/app/Head.svelte +12 -0
- package/components/app/Head.svelte.d.ts +27 -0
- package/components/app/Large.svelte +8 -0
- package/components/app/Large.svelte.d.ts +27 -0
- package/components/app/Lead.svelte +12 -0
- package/components/app/Lead.svelte.d.ts +27 -0
- package/components/app/Link.svelte +24 -0
- package/components/app/Link.svelte.d.ts +20 -0
- package/components/app/Options.svelte +24 -0
- package/components/app/Options.svelte.d.ts +19 -0
- package/components/app/Paragraph.svelte +19 -0
- package/components/app/Paragraph.svelte.d.ts +18 -0
- package/components/app/Table.svelte +32 -0
- package/components/app/Table.svelte.d.ts +27 -0
- package/components/app/TextInput.svelte +37 -0
- package/components/app/TextInput.svelte.d.ts +22 -0
- package/components/app/Writing.svelte +13 -0
- package/components/app/Writing.svelte.d.ts +27 -0
- package/components/chapter/Atom.svelte +20 -6
- package/components/chapter/Atom.svelte.d.ts +3 -3
- package/components/chapter/Block.svelte +23 -20
- package/components/chapter/Block.svelte.d.ts +4 -4
- package/components/chapter/Callout.svelte +38 -7
- package/components/chapter/Callout.svelte.d.ts +4 -4
- package/components/chapter/CaptionedCode.svelte +66 -23
- package/components/chapter/CaptionedCode.svelte.d.ts +4 -4
- package/components/chapter/ChapterBody.svelte +61 -9
- package/components/chapter/ChapterBody.svelte.d.ts +4 -4
- package/components/chapter/Citations.svelte +86 -20
- package/components/chapter/Citations.svelte.d.ts +4 -4
- package/components/chapter/Code.svelte +152 -21
- package/components/chapter/Code.svelte.d.ts +3 -3
- package/components/chapter/Comment.svelte +38 -16
- package/components/chapter/Comment.svelte.d.ts +3 -3
- package/components/chapter/Definition.svelte +52 -18
- package/components/chapter/Definition.svelte.d.ts +5 -8
- package/components/chapter/Embed.svelte +154 -66
- package/components/chapter/Embed.svelte.d.ts +5 -4
- package/components/chapter/ErrorMessage.svelte +7 -2
- package/components/chapter/ErrorMessage.svelte.d.ts +4 -4
- package/components/chapter/ExtendedPrism.d.ts +2 -0
- package/components/chapter/ExtendedPrism.js +69 -0
- package/components/chapter/Figure.svelte +90 -0
- package/components/chapter/Figure.svelte.d.ts +24 -0
- package/components/chapter/Footnote.svelte +75 -12
- package/components/chapter/Footnote.svelte.d.ts +4 -4
- package/components/chapter/Format.svelte +60 -37
- package/components/chapter/Format.svelte.d.ts +4 -4
- package/components/chapter/InlineCode.svelte +15 -5
- package/components/chapter/InlineCode.svelte.d.ts +4 -4
- package/components/chapter/Label.svelte +37 -8
- package/components/chapter/Label.svelte.d.ts +4 -4
- package/components/chapter/Link.svelte +23 -18
- package/components/chapter/Link.svelte.d.ts +4 -4
- package/components/chapter/List.svelte +75 -8
- package/components/chapter/List.svelte.d.ts +4 -4
- package/components/chapter/Marginal.svelte +169 -27
- package/components/chapter/Marginal.svelte.d.ts +6 -3
- package/components/chapter/Paragraph.svelte +74 -6
- package/components/chapter/Paragraph.svelte.d.ts +4 -4
- package/components/chapter/Positioned.svelte +33 -0
- package/components/chapter/Positioned.svelte.d.ts +19 -0
- package/components/chapter/Problem.svelte +15 -0
- package/components/chapter/Problem.svelte.d.ts +27 -0
- package/components/chapter/Python.svelte +68 -21
- package/components/chapter/Python.svelte.d.ts +4 -4
- package/components/chapter/Quote.svelte +64 -11
- package/components/chapter/Quote.svelte.d.ts +4 -4
- package/components/chapter/Rule.svelte +12 -1
- package/components/chapter/Rule.svelte.d.ts +4 -4
- package/components/chapter/Table.svelte +36 -25
- package/components/chapter/Table.svelte.d.ts +4 -4
- package/components/chapter/Text.svelte +61 -36
- package/components/chapter/Text.svelte.d.ts +5 -4
- package/components/editor/BookishEditor.svelte +343 -237
- package/components/editor/BookishEditor.svelte.d.ts +5 -5
- package/components/editor/CalloutEditor.svelte +5 -5
- package/components/editor/CalloutEditor.svelte.d.ts +4 -4
- package/components/editor/CaptionedCodeEditor.svelte +16 -12
- package/components/editor/CaptionedCodeEditor.svelte.d.ts +4 -4
- package/components/editor/CaretContext.d.ts +48 -16
- package/components/editor/CaretState.d.ts +16 -44
- package/components/editor/CaretUtilities.d.ts +2 -2
- package/components/editor/CaretView.svelte +80 -0
- package/components/editor/CaretView.svelte.d.ts +24 -0
- package/components/editor/CitationsEditor.svelte +36 -25
- package/components/editor/CitationsEditor.svelte.d.ts +4 -4
- package/components/editor/Clipboard.d.ts +2 -2
- package/components/editor/Command.d.ts +13 -12
- package/components/editor/Commands.d.ts +1 -1
- package/components/editor/Commands.js +1114 -571
- package/components/editor/ConfirmButton.svelte +18 -12
- package/components/editor/ConfirmButton.svelte.d.ts +5 -4
- package/components/editor/DefinitionEditor.svelte +29 -21
- package/components/editor/DefinitionEditor.svelte.d.ts +4 -4
- package/components/editor/EmbedEditor.svelte +93 -43
- package/components/editor/EmbedEditor.svelte.d.ts +3 -3
- package/components/editor/Icon.svelte +17 -0
- package/components/editor/Icon.svelte.d.ts +16 -0
- package/components/editor/ImageChooser.svelte +53 -34
- package/components/editor/ImageChooser.svelte.d.ts +3 -3
- package/components/editor/InlineCodeEditor.svelte +7 -4
- package/components/editor/InlineCodeEditor.svelte.d.ts +4 -4
- package/components/editor/LabelEditor.svelte +20 -11
- package/components/editor/LabelEditor.svelte.d.ts +4 -4
- package/components/editor/LanguageEditor.svelte +10 -7
- package/components/editor/LanguageEditor.svelte.d.ts +3 -3
- package/components/editor/LinkEditor.svelte +64 -39
- package/components/editor/LinkEditor.svelte.d.ts +4 -4
- package/components/editor/Note.svelte +3 -3
- package/components/editor/Note.svelte.d.ts +6 -2
- package/components/editor/PositionEditor.svelte +9 -6
- package/components/editor/PositionEditor.svelte.d.ts +4 -4
- package/components/editor/QuoteEditor.svelte +7 -2
- package/components/editor/QuoteEditor.svelte.d.ts +4 -4
- package/components/editor/Switch.svelte +55 -28
- package/components/editor/Switch.svelte.d.ts +4 -4
- package/components/editor/TableEditor.svelte +10 -0
- package/components/editor/TableEditor.svelte.d.ts +17 -0
- package/components/editor/TextEditor.svelte +83 -45
- package/components/editor/TextEditor.svelte.d.ts +5 -4
- package/components/editor/Toggle.svelte +11 -8
- package/components/editor/Toggle.svelte.d.ts +4 -4
- package/components/editor/Toolbar.svelte +192 -144
- package/components/editor/Toolbar.svelte.d.ts +5 -8
- package/components/editor/ToolbarGroup.svelte +18 -23
- package/components/editor/ToolbarGroup.svelte.d.ts +3 -4
- package/components/editor/ToolbarSpacer.svelte +8 -1
- package/components/editor/ToolbarSpacer.svelte.d.ts +6 -2
- package/components/editor/URLEditor.svelte +6 -8
- package/components/editor/URLEditor.svelte.d.ts +3 -3
- package/components/editor/UndoState.d.ts +3 -3
- package/components/editor/icons/annotate.svg +1 -0
- package/components/editor/icons/block.svg +1 -0
- package/components/editor/icons/bold.svg +1 -0
- package/components/editor/icons/bullets.svg +1 -0
- package/components/editor/icons/callout.svg +1 -0
- package/components/editor/icons/citation.svg +8 -0
- package/components/editor/icons/clear.svg +1 -0
- package/components/editor/icons/clipboard.svg +1 -0
- package/components/editor/icons/code.svg +1 -0
- package/components/editor/icons/columnafter.svg +1 -0
- package/components/editor/icons/columnbefore.svg +1 -0
- package/components/editor/icons/comment.svg +1 -0
- package/components/editor/icons/copy.svg +1 -0
- package/components/editor/icons/cut.svg +1 -0
- package/components/editor/icons/define.svg +7 -0
- package/components/editor/icons/deletecolumn.svg +1 -0
- package/components/editor/icons/deleterow.svg +1 -0
- package/components/editor/icons/footnote.svg +8 -0
- package/components/editor/icons/header1.svg +7 -0
- package/components/editor/icons/header2.svg +7 -0
- package/components/editor/icons/header3.svg +7 -0
- package/components/editor/icons/home.svg +1 -0
- package/components/editor/icons/indent.svg +1 -0
- package/components/editor/icons/italic.svg +1 -0
- package/components/editor/icons/label.svg +1 -0
- package/components/editor/icons/left.svg +1 -0
- package/components/editor/icons/level.svg +1 -0
- package/components/editor/icons/link.svg +1 -0
- package/components/editor/icons/list.svg +1 -0
- package/components/editor/icons/media.svg +1 -0
- package/components/editor/icons/middle.svg +1 -0
- package/components/editor/icons/numbers.svg +10 -0
- package/components/editor/icons/paragraph.svg +1 -0
- package/components/editor/icons/paste.svg +10 -0
- package/components/editor/icons/quote.svg +1 -0
- package/components/editor/icons/redo.svg +1 -0
- package/components/editor/icons/right.svg +1 -0
- package/components/editor/icons/rowabove.svg +1 -0
- package/components/editor/icons/rowbelow.svg +1 -0
- package/components/editor/icons/rule.svg +1 -0
- package/components/editor/icons/sub.svg +1 -0
- package/components/editor/icons/super.svg +1 -0
- package/components/editor/icons/table.svg +1 -0
- package/components/editor/icons/text.svg +1 -0
- package/components/editor/icons/time.svg +1 -0
- package/components/editor/icons/undo.svg +1 -0
- package/components/editor/icons/unindent.svg +1 -0
- package/components/editor/icons/unlink.svg +1 -0
- package/components/page/Acknowledgements.svelte +31 -22
- package/components/page/Acknowledgements.svelte.d.ts +4 -4
- package/components/page/Authors.svelte +34 -14
- package/components/page/Authors.svelte.d.ts +6 -6
- package/components/page/BulkReferenceEditor.svelte +53 -22
- package/components/page/BulkReferenceEditor.svelte.d.ts +4 -4
- package/components/page/Chapter.svelte +196 -141
- package/components/page/Chapter.svelte.d.ts +3 -3
- package/components/page/ChapterContext.d.ts +4 -4
- package/components/page/ChapterNumber.svelte +9 -0
- package/components/page/ChapterNumber.svelte.d.ts +27 -0
- package/components/page/ChapterTitle.svelte +16 -0
- package/components/page/ChapterTitle.svelte.d.ts +18 -0
- package/components/page/Contexts.d.ts +31 -20
- package/components/page/Contexts.js +47 -17
- package/components/page/DefinitionView.svelte +115 -86
- package/components/page/DefinitionView.svelte.d.ts +3 -3
- package/components/page/Description.svelte +27 -18
- package/components/page/Description.svelte.d.ts +4 -4
- package/components/page/Edition.svelte +127 -74
- package/components/page/Edition.svelte.d.ts +4 -4
- package/components/page/Editions.svelte +144 -0
- package/components/page/Editions.svelte.d.ts +14 -0
- package/components/page/Glossary.svelte +57 -48
- package/components/page/Glossary.svelte.d.ts +4 -4
- package/components/page/Header.svelte +129 -44
- package/components/page/Header.svelte.d.ts +6 -6
- package/components/page/Index.svelte +114 -80
- package/components/page/Index.svelte.d.ts +4 -4
- package/components/page/Instructions.svelte +18 -16
- package/components/page/Instructions.svelte.d.ts +4 -4
- package/components/page/License.svelte +29 -21
- package/components/page/License.svelte.d.ts +4 -4
- package/components/page/Loading.svelte +102 -12
- package/components/page/Loading.svelte.d.ts +6 -2
- package/components/page/Media.svelte +77 -51
- package/components/page/Media.svelte.d.ts +4 -4
- package/components/page/MediaPreview.svelte +26 -8
- package/components/page/MediaPreview.svelte.d.ts +3 -3
- package/components/page/Muted.svelte +10 -0
- package/components/page/Muted.svelte.d.ts +27 -0
- package/components/page/Outline.svelte +279 -59
- package/components/page/Outline.svelte.d.ts +3 -3
- package/components/page/Page.svelte +40 -9
- package/components/page/Page.svelte.d.ts +3 -3
- package/components/page/PageHeader.svelte +14 -0
- package/components/page/PageHeader.svelte.d.ts +18 -0
- package/components/page/PageParagraph.svelte +18 -0
- package/components/page/PageParagraph.svelte.d.ts +27 -0
- package/components/page/PossibleChapter.svelte +7 -7
- package/components/page/PossibleChapter.svelte.d.ts +4 -4
- package/components/page/PossibleReference.svelte +6 -11
- package/components/page/PossibleReference.svelte.d.ts +6 -7
- package/components/page/Print.svelte +27 -3
- package/components/page/Print.svelte.d.ts +4 -4
- package/components/page/Reference.svelte +256 -113
- package/components/page/Reference.svelte.d.ts +5 -5
- package/components/page/References.svelte +39 -47
- package/components/page/References.svelte.d.ts +4 -4
- package/components/page/Rows.svelte +54 -0
- package/components/page/Rows.svelte.d.ts +27 -0
- package/components/page/Search.svelte +86 -63
- package/components/page/Search.svelte.d.ts +4 -4
- package/components/page/Status.svelte +43 -35
- package/components/page/Status.svelte.d.ts +4 -9
- package/components/page/SubdomainEditor.svelte +40 -27
- package/components/page/SubdomainEditor.svelte.d.ts +4 -4
- package/components/page/TableOfContents.svelte +225 -190
- package/components/page/TableOfContents.svelte.d.ts +4 -4
- package/components/page/TableOfContentsImage.svelte +8 -8
- package/components/page/TableOfContentsImage.svelte.d.ts +3 -3
- package/components/page/TableOfContentsRow.svelte +96 -33
- package/components/page/TableOfContentsRow.svelte.d.ts +4 -4
- package/components/page/ThemeEditor.svelte +174 -39
- package/components/page/ThemeEditor.svelte.d.ts +4 -4
- package/components/page/ThemeEditorPreview.svelte +35 -23
- package/components/page/ThemeEditorPreview.svelte.d.ts +5 -5
- package/components/page/ThemeSetEditor.svelte +15 -13
- package/components/page/ThemeSetEditor.svelte.d.ts +4 -4
- package/components/page/ThemeVariableEditor.svelte +24 -15
- package/components/page/ThemeVariableEditor.svelte.d.ts +3 -3
- package/components/page/Title.svelte +12 -0
- package/components/page/Title.svelte.d.ts +27 -0
- package/components/page/Unknown.svelte +25 -21
- package/components/page/Unknown.svelte.d.ts +4 -4
- package/components/page/margins.js +50 -33
- package/index.d.ts +14 -0
- package/index.js +14 -0
- package/models/CRUD.d.ts +17 -0
- package/models/CRUD.js +194 -0
- package/models/Firebase.js +17 -15
- package/models/book/Book.d.ts +24 -28
- package/models/book/Book.js +120 -113
- package/models/book/BookMedia.d.ts +3 -3
- package/models/book/BookMedia.js +22 -16
- package/models/book/BookSaveStatus.d.ts +7 -0
- package/models/book/BookSaveStatus.js +8 -0
- package/models/book/Chapter.d.ts +46 -39
- package/models/book/Chapter.js +180 -198
- package/models/book/ChapterID.js +7 -7
- package/models/book/Definition.d.ts +1 -1
- package/models/book/Edition.d.ts +62 -60
- package/models/book/Edition.js +385 -303
- package/models/book/Reference.d.ts +24 -0
- package/models/book/Reference.js +78 -0
- package/models/book/Schema.d.ts +169 -0
- package/models/book/Schema.js +114 -0
- package/models/book/Theme.d.ts +44 -37
- package/models/book/Theme.js +452 -52
- package/models/book/ThemeColors.d.ts +1 -1
- package/models/book/Words.d.ts +2 -0
- package/models/book/Words.js +110 -0
- package/models/chapter/AtomNode.d.ts +4 -4
- package/models/chapter/AtomNode.js +20 -13
- package/models/chapter/BlockNode.d.ts +9 -10
- package/models/chapter/BlockNode.js +32 -13
- package/models/chapter/BlocksNode.d.ts +10 -10
- package/models/chapter/BlocksNode.js +315 -120
- package/models/chapter/BlocksNode.test.js +494 -228
- package/models/chapter/Bookkeeping.d.ts +1 -1
- package/models/chapter/CalloutNode.d.ts +3 -3
- package/models/chapter/CalloutNode.js +34 -12
- package/models/chapter/Caret.d.ts +9 -7
- package/models/chapter/Caret.js +11 -6
- package/models/chapter/ChapterNode.d.ts +11 -11
- package/models/chapter/ChapterNode.js +70 -33
- package/models/chapter/CitationsNode.d.ts +3 -3
- package/models/chapter/CitationsNode.js +31 -12
- package/models/chapter/CodeNode.d.ts +11 -6
- package/models/chapter/CodeNode.js +143 -34
- package/models/chapter/CommentNode.d.ts +5 -5
- package/models/chapter/CommentNode.js +29 -13
- package/models/chapter/DefinitionNode.d.ts +2 -2
- package/models/chapter/DefinitionNode.js +15 -7
- package/models/chapter/Edit.d.ts +3 -3
- package/models/chapter/EmbedNode.d.ts +9 -5
- package/models/chapter/EmbedNode.js +108 -35
- package/models/chapter/ErrorNode.d.ts +2 -2
- package/models/chapter/ErrorNode.js +34 -12
- package/models/chapter/FootnoteNode.d.ts +5 -5
- package/models/chapter/FootnoteNode.js +34 -16
- package/models/chapter/FormatNode.d.ts +10 -10
- package/models/chapter/FormatNode.js +237 -106
- package/models/chapter/FormatNode.test.js +87 -45
- package/models/chapter/InlineCodeNode.d.ts +2 -2
- package/models/chapter/InlineCodeNode.js +22 -9
- package/models/chapter/LabelNode.d.ts +4 -4
- package/models/chapter/LabelNode.js +31 -13
- package/models/chapter/LinkNode.d.ts +2 -2
- package/models/chapter/LinkNode.js +21 -9
- package/models/chapter/ListNode.d.ts +8 -8
- package/models/chapter/ListNode.js +99 -38
- package/models/chapter/ListNode.test.js +29 -24
- package/models/chapter/MetadataNode.d.ts +3 -3
- package/models/chapter/MetadataNode.js +13 -5
- package/models/chapter/Node.d.ts +5 -3
- package/models/chapter/Node.js +76 -29
- package/models/chapter/Node.test.js +20 -18
- package/models/chapter/ParagraphNode.d.ts +6 -6
- package/models/chapter/ParagraphNode.js +84 -29
- package/models/chapter/ParagraphNode.test.js +22 -13
- package/models/chapter/Parser.d.ts +25 -25
- package/models/chapter/Parser.js +201 -189
- package/models/chapter/Parser.test.js +16 -16
- package/models/chapter/Position.d.ts +1 -1
- package/models/chapter/QuoteNode.d.ts +6 -6
- package/models/chapter/QuoteNode.js +61 -23
- package/models/chapter/RootNode.d.ts +4 -4
- package/models/chapter/RuleNode.d.ts +2 -2
- package/models/chapter/RuleNode.js +31 -11
- package/models/chapter/TableNode.d.ts +8 -6
- package/models/chapter/TableNode.js +89 -34
- package/models/chapter/TableNode.test.js +22 -24
- package/models/chapter/TextNode.d.ts +6 -6
- package/models/chapter/TextNode.js +72 -32
- package/models/chapter/TextNode.test.js +4 -4
- package/models/loadEdition.d.ts +1 -1
- package/models/loadEdition.js +10 -6
- package/package.json +104 -25
- package/util/getSubdomain.js +8 -6
- package/util/isMobile.js +1 -1
- package/util/mineReference.d.ts +3 -2
- package/util/mineReference.js +67 -29
- package/util/scrollToEyeLevel.js +6 -1
- package/components/chapter/renderPosition.d.ts +0 -3
- package/components/chapter/renderPosition.js +0 -4
- package/components/editor/ToolbarIcon.svelte +0 -22
- package/components/editor/ToolbarIcon.svelte.d.ts +0 -16
- package/components/page/Alert.svelte +0 -11
- package/components/page/Alert.svelte.d.ts +0 -23
- package/components/page/Revisions.svelte +0 -124
- package/components/page/Revisions.svelte.d.ts +0 -14
- package/models/BookLoader.d.ts +0 -2
- package/models/BookLoader.js +0 -67
- package/models/Firestore.d.ts +0 -22
- package/models/Firestore.js +0 -194
- package/models/chapter/ReferenceNode.d.ts +0 -29
- package/models/chapter/ReferenceNode.js +0 -48
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
<script>import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import { afterUpdate, onMount, setContext } from
|
|
21
|
-
import {
|
|
1
|
+
<script>import BookishNode from '../../models/chapter/Node';
|
|
2
|
+
import ChapterNode from '../../models/chapter/ChapterNode';
|
|
3
|
+
import Parser from '../../models/chapter/Parser';
|
|
4
|
+
import LinkNode from '../../models/chapter/LinkNode';
|
|
5
|
+
import ParagraphNode from '../../models/chapter/ParagraphNode';
|
|
6
|
+
import TextNode from '../../models/chapter/TextNode';
|
|
7
|
+
import AtomNode from '../../models/chapter/AtomNode';
|
|
8
|
+
import FormatNode from '../../models/chapter/FormatNode';
|
|
9
|
+
import BlocksNode from '../../models/chapter/BlocksNode';
|
|
10
|
+
import ListNode from '../../models/chapter/ListNode';
|
|
11
|
+
import TableNode from '../../models/chapter/TableNode';
|
|
12
|
+
import MetadataNode from '../../models/chapter/MetadataNode';
|
|
13
|
+
import CodeNode from '../../models/chapter/CodeNode';
|
|
14
|
+
import BlockNode from '../../models/chapter/BlockNode';
|
|
15
|
+
import EmbedNode from '../../models/chapter/EmbedNode';
|
|
16
|
+
import ErrorNode from '../../models/chapter/ErrorNode';
|
|
17
|
+
import { caretRangeToIndexRange, indexRangeToCaretRange, } from '../../models/chapter/Caret';
|
|
18
|
+
import commands from './Commands';
|
|
19
|
+
import { getEdition } from '../page/Contexts';
|
|
20
|
+
import { afterUpdate, onMount, setContext } from 'svelte';
|
|
21
|
+
import { getCaret } from '../page/Contexts';
|
|
22
|
+
import CaretView from './CaretView.svelte';
|
|
22
23
|
const IDLE_TIME = 500;
|
|
23
24
|
export let ast;
|
|
24
25
|
export let save;
|
|
@@ -38,6 +39,7 @@ let clipboard = undefined;
|
|
|
38
39
|
let editedNode = ast;
|
|
39
40
|
let ignoredInput = false;
|
|
40
41
|
let edition = getEdition();
|
|
42
|
+
let activeEditor = getCaret();
|
|
41
43
|
onMount(() => {
|
|
42
44
|
const caret = editedNode.getFirstCaret();
|
|
43
45
|
if (caret)
|
|
@@ -47,15 +49,15 @@ onMount(() => {
|
|
|
47
49
|
element.focus();
|
|
48
50
|
}
|
|
49
51
|
// Listen to selection changes
|
|
50
|
-
document.addEventListener(
|
|
52
|
+
document.addEventListener('selectionchange', handleSelectionChange);
|
|
51
53
|
// Stop listening to selection changes
|
|
52
54
|
return () => {
|
|
53
|
-
document.removeEventListener(
|
|
55
|
+
document.removeEventListener('selectionchange', handleSelectionChange);
|
|
54
56
|
};
|
|
55
57
|
});
|
|
56
58
|
onMount(() => {
|
|
57
59
|
// When input happens, set a timer and then see if idle time has elapsed. If it has, track time since last keystroke to control caret blinking behavior.
|
|
58
|
-
const keystrokeTimer =
|
|
60
|
+
const keystrokeTimer = setInterval(() => {
|
|
59
61
|
// Remember that we're idle so we can render things accordingly.
|
|
60
62
|
const isIdle = Date.now() - lastInputTime > IDLE_TIME;
|
|
61
63
|
if (isIdle) {
|
|
@@ -83,19 +85,26 @@ afterUpdate(() => {
|
|
|
83
85
|
const sortedRange = caretRange;
|
|
84
86
|
let startNode = document.querySelector(`[data-nodeid='${sortedRange.start.node.nodeID}`);
|
|
85
87
|
let endNode = document.querySelector(`[data-nodeid='${sortedRange.end.node.nodeID}`);
|
|
86
|
-
// If we found both selected nodes and we have a reference to the DOM, update the
|
|
88
|
+
// If we found both selected nodes and we have a reference to the DOM, update the
|
|
87
89
|
// browser's selection if necessary and measure it's position so we can draw our own caret.
|
|
88
90
|
if (startNode && endNode && element) {
|
|
89
91
|
// If the current selection isn't different from the new one, then we don't do any of this.
|
|
90
|
-
// This prevents an infinite loop when, for example, the user clicks, the browser selection changes,
|
|
92
|
+
// This prevents an infinite loop when, for example, the user clicks, the browser selection changes,
|
|
91
93
|
// and we update the caret range, which updates the browser selection, ad infinitum.
|
|
92
94
|
let rangeChanged = true;
|
|
93
95
|
let currentRange = undefined;
|
|
94
96
|
if (docSelection.rangeCount > 0) {
|
|
95
97
|
currentRange = docSelection.getRangeAt(0);
|
|
96
98
|
// If our caret range is non-empty TextNodes at a particular index and equal to the browser's caret range, then nothing's changed, so we shouldn't update the selection.
|
|
97
|
-
const startIsEqual =
|
|
98
|
-
|
|
99
|
+
const startIsEqual = startNode.childNodes.length > 0 &&
|
|
100
|
+
currentRange.startContainer ===
|
|
101
|
+
startNode.childNodes[0] &&
|
|
102
|
+
currentRange.startOffset ===
|
|
103
|
+
sortedRange.start.index;
|
|
104
|
+
const endIsEqual = endNode.childNodes.length > 0 &&
|
|
105
|
+
currentRange.endContainer ===
|
|
106
|
+
endNode.childNodes[0] &&
|
|
107
|
+
currentRange.endOffset === sortedRange.end.index;
|
|
99
108
|
if (startIsEqual && endIsEqual)
|
|
100
109
|
rangeChanged = false;
|
|
101
110
|
}
|
|
@@ -105,7 +114,8 @@ afterUpdate(() => {
|
|
|
105
114
|
// We only do this to the end node if it's different from the start node.
|
|
106
115
|
// We leave them in there so that the "rangeChanged" guard below can track them.
|
|
107
116
|
const emptyStart = startNode.childNodes.length === 0;
|
|
108
|
-
const emptyEnd = startNode !== endNode &&
|
|
117
|
+
const emptyEnd = startNode !== endNode &&
|
|
118
|
+
endNode.childNodes.length === 0;
|
|
109
119
|
if (emptyStart)
|
|
110
120
|
startNode.appendChild(document.createTextNode('\ufeff'));
|
|
111
121
|
if (emptyEnd)
|
|
@@ -113,7 +123,9 @@ afterUpdate(() => {
|
|
|
113
123
|
// If we're just after a new line, set the selection to the character after.
|
|
114
124
|
const afterNewLine = sortedRange.start.node instanceof TextNode &&
|
|
115
125
|
sortedRange.start.index > 0 &&
|
|
116
|
-
sortedRange.start.node
|
|
126
|
+
sortedRange.start.node
|
|
127
|
+
.getText()
|
|
128
|
+
.charAt(sortedRange.start.index - 1) === '\n';
|
|
117
129
|
// If the range has changed, set the selection so we can measure it's location.
|
|
118
130
|
// We guard this to avoid triggering unnecessary selection change events that don't actually modify the selection.
|
|
119
131
|
if (rangeChanged) {
|
|
@@ -122,7 +134,7 @@ afterUpdate(() => {
|
|
|
122
134
|
}
|
|
123
135
|
catch (e) {
|
|
124
136
|
console.error(e);
|
|
125
|
-
|
|
137
|
+
console.error(`Error setting caret range, trying to set to ${startNode.childNodes[0]}:${sortedRange.start.index} - ${endNode.childNodes[0]}:${sortedRange.end.index}`);
|
|
126
138
|
}
|
|
127
139
|
currentRange = docSelection.getRangeAt(0);
|
|
128
140
|
}
|
|
@@ -130,26 +142,35 @@ afterUpdate(() => {
|
|
|
130
142
|
if (currentRange) {
|
|
131
143
|
const rangeRect = currentRange.getBoundingClientRect();
|
|
132
144
|
const textRect = startNode.getBoundingClientRect();
|
|
133
|
-
const lineHeightString = window
|
|
134
|
-
|
|
145
|
+
const lineHeightString = window
|
|
146
|
+
.getComputedStyle(startNode)
|
|
147
|
+
.getPropertyValue('line-height');
|
|
148
|
+
const lineHeight = lineHeightString.endsWith('px')
|
|
149
|
+
? parseInt(lineHeightString.substring(0, lineHeightString.length - 2))
|
|
150
|
+
: undefined;
|
|
135
151
|
// Is the caret in relatively positioned ancestors above the editor? Undo their offsets.
|
|
136
|
-
let caretAncestor = startNode.closest(
|
|
152
|
+
let caretAncestor = startNode.closest('.bookish-editor');
|
|
137
153
|
let relativeX = 0;
|
|
138
154
|
let relativeY = 0;
|
|
139
155
|
while (caretAncestor != null) {
|
|
140
|
-
if (window.getComputedStyle(caretAncestor)
|
|
156
|
+
if (window.getComputedStyle(caretAncestor)
|
|
157
|
+
.position === 'relative') {
|
|
141
158
|
relativeX += caretAncestor.offsetLeft;
|
|
142
159
|
relativeY += caretAncestor.offsetTop;
|
|
143
160
|
}
|
|
144
161
|
caretAncestor = caretAncestor.parentElement;
|
|
145
162
|
}
|
|
146
163
|
// If we're after a new line, calculate the correct position, since selections don't actually render to the next line.
|
|
147
|
-
const left = afterNewLine
|
|
148
|
-
|
|
164
|
+
const left = afterNewLine
|
|
165
|
+
? textRect.left
|
|
166
|
+
: rangeRect.left;
|
|
167
|
+
const top = afterNewLine && lineHeight
|
|
168
|
+
? rangeRect.top + lineHeight
|
|
169
|
+
: rangeRect.top;
|
|
149
170
|
const position = {
|
|
150
171
|
x: left + window.scrollX - relativeX,
|
|
151
172
|
y: top + window.scrollY - relativeY,
|
|
152
|
-
height: rangeRect.height
|
|
173
|
+
height: rangeRect.height,
|
|
153
174
|
};
|
|
154
175
|
newCaretPosition = position;
|
|
155
176
|
}
|
|
@@ -161,38 +182,30 @@ afterUpdate(() => {
|
|
|
161
182
|
}
|
|
162
183
|
// Save the new caret position so we render it.
|
|
163
184
|
caretCoordinate = newCaretPosition;
|
|
164
|
-
// If the caret position is outside the window, and the editor is focused, scroll to make the caret visible.
|
|
165
|
-
if (newCaretPosition && editorFocused) {
|
|
166
|
-
const caretTop = newCaretPosition.y;
|
|
167
|
-
const caretBottom = caretTop + newCaretPosition.height;
|
|
168
|
-
const windowHeight = window.innerHeight;
|
|
169
|
-
const toolbar = element?.querySelector(".bookish-editor-toolbar");
|
|
170
|
-
const toolbarHeight = toolbar ? toolbar.clientHeight : 0;
|
|
171
|
-
const buffer = newCaretPosition.height * 5;
|
|
172
|
-
if (caretTop < window.scrollY + buffer + toolbarHeight) {
|
|
173
|
-
window.scrollTo({ top: caretTop - buffer, behavior: 'smooth' });
|
|
174
|
-
}
|
|
175
|
-
else if (caretBottom > window.scrollY + windowHeight - buffer) {
|
|
176
|
-
window.scrollTo({ top: caretBottom - (windowHeight - buffer), behavior: 'smooth' });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
185
|
});
|
|
180
186
|
function rangeToCaret(domNode, rangeIndex) {
|
|
181
187
|
// If it's a text node, find the closest TextNode parent
|
|
182
188
|
if (domNode.nodeType === Node.TEXT_NODE) {
|
|
183
189
|
let parent = domNode.parentNode;
|
|
184
190
|
// Keep looking up until we find the TextNode span with the node ID.
|
|
185
|
-
while (parent &&
|
|
191
|
+
while (parent &&
|
|
192
|
+
!(parent instanceof HTMLElement &&
|
|
193
|
+
parent.classList.contains('bookish-text')))
|
|
186
194
|
parent = parent.parentNode;
|
|
187
195
|
if (parent && parent.dataset.nodeid) {
|
|
188
196
|
const node = editedNode.getNode(parseInt(parent.dataset.nodeid));
|
|
189
197
|
if (node instanceof TextNode)
|
|
190
198
|
// Account for the zero-width spaces that we insert in order to make selections possible on empty text nodes.
|
|
191
|
-
return {
|
|
199
|
+
return {
|
|
200
|
+
node: node,
|
|
201
|
+
index: Math.min(rangeIndex, node.getLength()),
|
|
202
|
+
};
|
|
192
203
|
}
|
|
193
204
|
}
|
|
194
205
|
// If it's an element, see if it has a nodeID and handle it accordingly.
|
|
195
|
-
else if (domNode.nodeType === Node.ELEMENT_NODE &&
|
|
206
|
+
else if (domNode.nodeType === Node.ELEMENT_NODE &&
|
|
207
|
+
domNode instanceof HTMLElement &&
|
|
208
|
+
domNode.dataset.nodeid) {
|
|
196
209
|
const node = editedNode.getNode(parseInt(domNode.dataset.nodeid));
|
|
197
210
|
// These assume that triple clicks on paragraphs in the browser choose text nodes for the selection start
|
|
198
211
|
// and spans or paragraph nodes for the end.
|
|
@@ -213,7 +226,7 @@ function handleSelectionChange() {
|
|
|
213
226
|
keyboardIdle = false;
|
|
214
227
|
// Map the browser selection to our caret range model.
|
|
215
228
|
const selection = document.getSelection();
|
|
216
|
-
if (selection && selection.anchorNode && selection.
|
|
229
|
+
if (selection && selection.anchorNode && selection.focusNode) {
|
|
217
230
|
const start = rangeToCaret(selection.anchorNode, selection.anchorOffset);
|
|
218
231
|
const end = rangeToCaret(selection.focusNode, selection.focusOffset);
|
|
219
232
|
// If we found to text nodes, set the selection
|
|
@@ -234,9 +247,17 @@ function getCaretCoordinate(caret) {
|
|
|
234
247
|
range.setStart(domNode.childNodes.length === 0 ? domNode : domNode.childNodes[0], domNode.childNodes.length === 0 ? 0 : caret.index);
|
|
235
248
|
const characterRect = range.getBoundingClientRect();
|
|
236
249
|
if (characterRect.left > 0)
|
|
237
|
-
return {
|
|
250
|
+
return {
|
|
251
|
+
top: characterRect.top,
|
|
252
|
+
left: characterRect.left,
|
|
253
|
+
height: characterRect.height,
|
|
254
|
+
};
|
|
238
255
|
const elementRect = domNode.getBoundingClientRect();
|
|
239
|
-
return {
|
|
256
|
+
return {
|
|
257
|
+
top: elementRect.top,
|
|
258
|
+
left: elementRect.left,
|
|
259
|
+
height: elementRect.height,
|
|
260
|
+
};
|
|
240
261
|
}
|
|
241
262
|
function getCaretOnLine(caret, below) {
|
|
242
263
|
if (caret.node instanceof TextNode || caret.node instanceof AtomNode) {
|
|
@@ -248,7 +269,8 @@ function getCaretOnLine(caret, below) {
|
|
|
248
269
|
let previousCandidate = undefined;
|
|
249
270
|
let previousCoordinate = undefined;
|
|
250
271
|
if (startCoordinate) {
|
|
251
|
-
while (candidate.node instanceof TextNode ||
|
|
272
|
+
while (candidate.node instanceof TextNode ||
|
|
273
|
+
candidate.node instanceof AtomNode) {
|
|
252
274
|
const candidateCoordinate = getCaretCoordinate(candidate);
|
|
253
275
|
// Did we make it to the next line and the character before/after? If we're going down,
|
|
254
276
|
// we're looking for a top that's below the bottom. If we're going up, we're looking for a bottom
|
|
@@ -256,21 +278,43 @@ function getCaretOnLine(caret, below) {
|
|
|
256
278
|
if (candidateCoordinate) {
|
|
257
279
|
const startTop = startCoordinate.top;
|
|
258
280
|
const candidateTop = candidateCoordinate.top;
|
|
259
|
-
const closerCandidate = previousCoordinate &&
|
|
281
|
+
const closerCandidate = previousCoordinate &&
|
|
282
|
+
previousCandidate &&
|
|
283
|
+
Math.abs(startCoordinate.left - previousCoordinate.left) <
|
|
284
|
+
Math.abs(startCoordinate.left -
|
|
285
|
+
candidateCoordinate.left)
|
|
286
|
+
? previousCandidate
|
|
287
|
+
: candidate;
|
|
260
288
|
if (below) {
|
|
261
289
|
// If this candidate is on the line below the start line, then return the last candidate on the line below the start line.
|
|
262
|
-
if (previousCandidate &&
|
|
290
|
+
if (previousCandidate &&
|
|
291
|
+
previousCoordinate &&
|
|
292
|
+
previousCoordinate.top >
|
|
293
|
+
startTop + startCoordinate.height &&
|
|
294
|
+
candidateTop >
|
|
295
|
+
previousCoordinate.top +
|
|
296
|
+
previousCoordinate.height)
|
|
263
297
|
return previousCandidate;
|
|
264
298
|
// If this is just below the start position, stop and return the closer of the current and previous candidates.
|
|
265
|
-
else if (candidateTop >
|
|
299
|
+
else if (candidateTop >
|
|
300
|
+
startTop + startCoordinate.height &&
|
|
301
|
+
candidateCoordinate.left >= startCoordinate.left)
|
|
266
302
|
return closerCandidate;
|
|
267
303
|
}
|
|
268
304
|
else {
|
|
269
305
|
// If this candidate is on the live above the line above the start line, then return the last candidate on the line above the start line.
|
|
270
|
-
if (previousCandidate &&
|
|
306
|
+
if (previousCandidate &&
|
|
307
|
+
previousCoordinate &&
|
|
308
|
+
previousCoordinate.top +
|
|
309
|
+
previousCoordinate.height <
|
|
310
|
+
startTop &&
|
|
311
|
+
candidateTop + candidateCoordinate.height <
|
|
312
|
+
previousCoordinate.top)
|
|
271
313
|
return previousCandidate;
|
|
272
314
|
// If this is just above the start position, stop and return the closer of the current and previous candidates.
|
|
273
|
-
if (candidateTop + candidateCoordinate.height <
|
|
315
|
+
if (candidateTop + candidateCoordinate.height <
|
|
316
|
+
startTop &&
|
|
317
|
+
candidateCoordinate.left <= startCoordinate.left)
|
|
274
318
|
return closerCandidate;
|
|
275
319
|
}
|
|
276
320
|
}
|
|
@@ -279,7 +323,8 @@ function getCaretOnLine(caret, below) {
|
|
|
279
323
|
if (nextCandidate === undefined)
|
|
280
324
|
break;
|
|
281
325
|
// If the caret didn't move, we stop searching, something is wrong.
|
|
282
|
-
if (nextCandidate.node === candidate.node &&
|
|
326
|
+
if (nextCandidate.node === candidate.node &&
|
|
327
|
+
nextCandidate.index === candidate.index) {
|
|
283
328
|
break;
|
|
284
329
|
}
|
|
285
330
|
// Otherwise, we advance to the next candidate.
|
|
@@ -301,17 +346,24 @@ function atParagraphStart() {
|
|
|
301
346
|
caretRange.start.index !== caretRange.end.index ||
|
|
302
347
|
!(caretRange.start.node instanceof TextNode))
|
|
303
348
|
return false;
|
|
304
|
-
const firstCaret = caretRange.start.node
|
|
305
|
-
|
|
349
|
+
const firstCaret = caretRange.start.node
|
|
350
|
+
.getFormatRoot(editedNode)
|
|
351
|
+
?.getFirstCaret();
|
|
352
|
+
return (firstCaret !== undefined &&
|
|
353
|
+
firstCaret.node === caretRange.start.node &&
|
|
354
|
+
firstCaret.index === caretRange.start.index);
|
|
306
355
|
}
|
|
307
356
|
function undo() {
|
|
308
357
|
// Grab the next state
|
|
309
358
|
let undoState = undoStack[undoPosition + 1];
|
|
310
359
|
// Restore the content of the chapter.
|
|
311
|
-
let node = ast instanceof ChapterNode
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
360
|
+
let node = ast instanceof ChapterNode
|
|
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;
|
|
315
367
|
if (node === undefined || node instanceof ErrorNode)
|
|
316
368
|
return;
|
|
317
369
|
// Restore the view
|
|
@@ -331,10 +383,13 @@ function redo() {
|
|
|
331
383
|
// Grab the next state
|
|
332
384
|
let undoState = undoStack[undoPosition - 1];
|
|
333
385
|
// Restore the content of the chapter.
|
|
334
|
-
let node = ast instanceof ChapterNode
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
386
|
+
let node = ast instanceof ChapterNode
|
|
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;
|
|
338
393
|
if (node === undefined)
|
|
339
394
|
return;
|
|
340
395
|
editedNode = node;
|
|
@@ -354,39 +409,50 @@ function getCaretContext() {
|
|
|
354
409
|
const nodes = editedNode.getNodes();
|
|
355
410
|
let inside = false;
|
|
356
411
|
let includesList = false;
|
|
357
|
-
nodes.forEach(n => {
|
|
412
|
+
nodes.forEach((n) => {
|
|
358
413
|
if (caretRange === undefined)
|
|
359
414
|
return;
|
|
360
415
|
if (n === caretRange.start.node)
|
|
361
416
|
inside = true;
|
|
362
|
-
if (inside &&
|
|
417
|
+
if (inside &&
|
|
418
|
+
n.getClosestParentMatching(editedNode, (p) => p instanceof ListNode) !== undefined)
|
|
363
419
|
includesList = true;
|
|
364
420
|
if (n === caretRange.end.node)
|
|
365
421
|
inside = false;
|
|
366
422
|
});
|
|
367
|
-
const parents = editedNode
|
|
423
|
+
const parents = editedNode
|
|
424
|
+
.getParentsOf(caretRange.start.node)
|
|
425
|
+
?.reverse();
|
|
368
426
|
return {
|
|
369
427
|
// We make a new range so that setCaretRange always causes a re-render
|
|
370
428
|
chapter: chapter,
|
|
371
429
|
range: { start: caretRange.start, end: caretRange.end },
|
|
372
430
|
start: caretRange.start,
|
|
373
431
|
end: caretRange.end,
|
|
374
|
-
isSelection: caretRange.start.node !== caretRange.end.node ||
|
|
432
|
+
isSelection: caretRange.start.node !== caretRange.end.node ||
|
|
433
|
+
caretRange.start.index !== caretRange.end.index,
|
|
375
434
|
root: editedNode,
|
|
376
|
-
blocks: parents?.find(n => n instanceof BlocksNode),
|
|
377
|
-
paragraph: parents?.find(n => n instanceof ParagraphNode),
|
|
378
|
-
block: parents?.find(n => n instanceof BlockNode),
|
|
379
|
-
code: parents?.find(n => n instanceof CodeNode),
|
|
380
|
-
list: parents?.find(n => n instanceof ListNode),
|
|
381
|
-
atom: parents?.find(n => n instanceof AtomNode),
|
|
382
|
-
meta: parents?.find(n => n instanceof MetadataNode),
|
|
435
|
+
blocks: parents?.find((n) => n instanceof BlocksNode),
|
|
436
|
+
paragraph: parents?.find((n) => n instanceof ParagraphNode),
|
|
437
|
+
block: parents?.find((n) => n instanceof BlockNode),
|
|
438
|
+
code: parents?.find((n) => n instanceof CodeNode),
|
|
439
|
+
list: parents?.find((n) => n instanceof ListNode),
|
|
440
|
+
atom: parents?.find((n) => n instanceof AtomNode),
|
|
441
|
+
meta: parents?.find((n) => n instanceof MetadataNode),
|
|
383
442
|
includesList: includesList,
|
|
384
|
-
table: parents?.find(n => n instanceof TableNode),
|
|
385
|
-
format:
|
|
443
|
+
table: parents?.find((n) => n instanceof TableNode),
|
|
444
|
+
format: caretRange.end.node instanceof TextNode ||
|
|
445
|
+
caretRange.end.node instanceof AtomNode
|
|
446
|
+
? caretRange.end.node.getFormatRoot(editedNode)
|
|
447
|
+
: undefined,
|
|
386
448
|
startIsText: caretRange.start.node instanceof TextNode,
|
|
387
449
|
endIsText: caretRange.end.node instanceof TextNode,
|
|
388
|
-
startIsTextOrAtom: caretRange.start.node instanceof TextNode ||
|
|
389
|
-
|
|
450
|
+
startIsTextOrAtom: caretRange.start.node instanceof TextNode ||
|
|
451
|
+
caretRange.start.node instanceof AtomNode ||
|
|
452
|
+
caretRange.start.node instanceof EmbedNode,
|
|
453
|
+
endIsTextOrAtom: caretRange.end.node instanceof TextNode ||
|
|
454
|
+
caretRange.end.node instanceof AtomNode ||
|
|
455
|
+
caretRange.start.node instanceof EmbedNode,
|
|
390
456
|
atParagraphStart: atParagraphStart(),
|
|
391
457
|
undoStack: undoStack,
|
|
392
458
|
undoPosition: undoPosition,
|
|
@@ -394,15 +460,18 @@ function getCaretContext() {
|
|
|
394
460
|
redo: redo,
|
|
395
461
|
clipboard: clipboard,
|
|
396
462
|
handleCopy: handleCopy,
|
|
397
|
-
handlePaste: handlePaste
|
|
463
|
+
handlePaste: handlePaste,
|
|
398
464
|
};
|
|
399
465
|
}
|
|
400
466
|
function getUtilities() {
|
|
401
467
|
return {
|
|
402
|
-
getCaretOnLine: getCaretOnLine
|
|
468
|
+
getCaretOnLine: getCaretOnLine,
|
|
403
469
|
};
|
|
404
470
|
}
|
|
405
|
-
function
|
|
471
|
+
function handlePress(event) {
|
|
472
|
+
handleKey(event, true);
|
|
473
|
+
}
|
|
474
|
+
function handleKey(event, press = false) {
|
|
406
475
|
// Only handle keystrokes when this is focused.
|
|
407
476
|
// Otherwise, we let any focusable elements in this editor handle them.
|
|
408
477
|
if (!editorFocused)
|
|
@@ -417,35 +486,51 @@ function handleKeyDown(event) {
|
|
|
417
486
|
if (context === undefined)
|
|
418
487
|
return;
|
|
419
488
|
// Loop through the commands to see if there's a match.
|
|
420
|
-
const unmatched = commands.every(command => {
|
|
489
|
+
const unmatched = commands.every((command) => {
|
|
421
490
|
// If the keystroke and caret position matches the command signature, execute the command and update the caret range.
|
|
422
|
-
if ((command.
|
|
423
|
-
command.
|
|
424
|
-
|
|
425
|
-
(command.
|
|
491
|
+
if ((command.press === undefined || command.press === press) &&
|
|
492
|
+
(command.shift === undefined ||
|
|
493
|
+
command.shift === event.shiftKey) &&
|
|
494
|
+
(command.alt === undefined || command.alt === event.altKey) &&
|
|
495
|
+
(command.control === undefined ||
|
|
496
|
+
command.control === (event.ctrlKey || event.metaKey)) &&
|
|
497
|
+
(command.key === undefined ||
|
|
498
|
+
(Array.isArray(command.key) &&
|
|
499
|
+
command.key.includes(event.key)) ||
|
|
500
|
+
(command.key instanceof Function &&
|
|
501
|
+
command.key(event.key)) ||
|
|
502
|
+
command.key === event.key) &&
|
|
426
503
|
(command.code === undefined || command.code === event.code) &&
|
|
427
|
-
(command.active === true ||
|
|
428
|
-
|
|
429
|
-
|
|
504
|
+
(command.active === true ||
|
|
505
|
+
(command.active instanceof Function &&
|
|
506
|
+
command.active.call(undefined, context, event.key)))) {
|
|
430
507
|
// Execute the command
|
|
431
|
-
executeCommand(command, event.key);
|
|
508
|
+
const handled = executeCommand(command, event.key);
|
|
509
|
+
if (handled) {
|
|
510
|
+
event.preventDefault();
|
|
511
|
+
event.stopPropagation();
|
|
512
|
+
}
|
|
432
513
|
// Stop searching for a matching command.
|
|
433
|
-
return
|
|
514
|
+
return handled;
|
|
434
515
|
}
|
|
435
516
|
// Continue searching for a matching command.
|
|
436
517
|
return true;
|
|
437
518
|
});
|
|
438
519
|
if (unmatched) {
|
|
439
520
|
// Toolbar navigation
|
|
440
|
-
if (event.key ===
|
|
441
|
-
// If we've selected a FootnoteNode or Comment, navigate to the footnote or comment text.
|
|
442
|
-
if (caretRange.start.node instanceof AtomNode &&
|
|
443
|
-
|
|
521
|
+
if (event.key === 'Escape') {
|
|
522
|
+
// If we've selected a FootnoteNode or Comment, navigate to the footnote or comment text to edit it.
|
|
523
|
+
if (caretRange.start.node instanceof AtomNode &&
|
|
524
|
+
caretRange.start.node.getMeta() instanceof FormatNode) {
|
|
525
|
+
const firstCaret = caretRange.start.node
|
|
526
|
+
.getMeta()
|
|
527
|
+
.getFirstCaret();
|
|
444
528
|
caretRange = { start: firstCaret, end: firstCaret };
|
|
445
529
|
event.preventDefault();
|
|
446
530
|
event.stopPropagation();
|
|
447
531
|
return true;
|
|
448
532
|
}
|
|
533
|
+
// If we're inside an atom node, select the atom itself.
|
|
449
534
|
else if (caretRange.start.node.isInside(editedNode, AtomNode)) {
|
|
450
535
|
const atom = caretRange.start.node.getClosestParentOfType(editedNode, AtomNode);
|
|
451
536
|
if (atom) {
|
|
@@ -456,19 +541,19 @@ function handleKeyDown(event) {
|
|
|
456
541
|
return true;
|
|
457
542
|
}
|
|
458
543
|
}
|
|
459
|
-
if
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
544
|
+
// Otherwise, if we have a reference to this editor's DOM element, find the nearest focusable element in it's toolbar and focus on that.
|
|
545
|
+
const controls = [
|
|
546
|
+
document.querySelector('.bookish-editor-toolbar [tabindex="0"]'),
|
|
547
|
+
document.querySelector('.bookish-editor-toolbar input'),
|
|
548
|
+
document.querySelector('.bookish-editor-toolbar select'),
|
|
549
|
+
document.querySelector('.bookish-editor-toolbar button'),
|
|
550
|
+
];
|
|
551
|
+
const match = controls.find((control) => control && control instanceof HTMLElement);
|
|
552
|
+
if (match && match instanceof HTMLElement) {
|
|
553
|
+
match.focus();
|
|
554
|
+
event.preventDefault();
|
|
555
|
+
event.stopPropagation();
|
|
556
|
+
return true;
|
|
472
557
|
}
|
|
473
558
|
}
|
|
474
559
|
}
|
|
@@ -483,20 +568,26 @@ function executeCommand(command, key) {
|
|
|
483
568
|
if (results !== undefined) {
|
|
484
569
|
const { root, range } = results;
|
|
485
570
|
const newRange = { start: range.start, end: range.end };
|
|
486
|
-
if (root === editedNode && command.
|
|
571
|
+
if (root === editedNode && command.mutates)
|
|
487
572
|
console.error(`Warning: immutability violation on ${command.description}`);
|
|
488
573
|
// Set the range to force a rerender, assuming something in the document changed.
|
|
489
574
|
caretRange = newRange;
|
|
490
575
|
// Save the copy in the undo stack if this isn't a navigation or selection state.
|
|
491
|
-
if (command.category !==
|
|
576
|
+
if (command.category !== 'navigation' &&
|
|
577
|
+
command.category !== 'selection' &&
|
|
578
|
+
command.category !== 'history') {
|
|
492
579
|
saveEdit(root, newRange, command);
|
|
493
580
|
}
|
|
581
|
+
return true;
|
|
494
582
|
}
|
|
495
|
-
else
|
|
583
|
+
else {
|
|
496
584
|
ignoredInput = true;
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
497
587
|
}
|
|
588
|
+
return false;
|
|
498
589
|
}
|
|
499
|
-
function saveEdit(newRoot, newRange, command) {
|
|
590
|
+
async function saveEdit(newRoot, newRange, command) {
|
|
500
591
|
// Call the save callback.
|
|
501
592
|
save(newRoot);
|
|
502
593
|
// Initialize a new history if we don't have one.
|
|
@@ -505,25 +596,35 @@ function saveEdit(newRoot, newRange, command) {
|
|
|
505
596
|
if (undoStack.length === 0 && caretRange !== undefined) {
|
|
506
597
|
const currentRange = caretRangeToIndexRange(editedNode, caretRange);
|
|
507
598
|
if (currentRange !== undefined)
|
|
508
|
-
newStack = [
|
|
599
|
+
newStack = [
|
|
600
|
+
{
|
|
509
601
|
bookdown: editedNode.toBookdown(),
|
|
510
602
|
command: undefined,
|
|
511
|
-
range: currentRange
|
|
512
|
-
}
|
|
603
|
+
range: currentRange,
|
|
604
|
+
},
|
|
605
|
+
];
|
|
513
606
|
}
|
|
514
607
|
// Change the chapter's AST.
|
|
515
608
|
editedNode = newRoot;
|
|
516
609
|
// Set the new undo stack, pre-pending the new command to the front.
|
|
517
610
|
const newTextRange = caretRangeToIndexRange(newRoot, newRange);
|
|
518
611
|
if (newTextRange !== undefined)
|
|
519
|
-
undoStack = [
|
|
612
|
+
undoStack = [
|
|
613
|
+
{
|
|
520
614
|
bookdown: newRoot.toBookdown(),
|
|
521
615
|
command: command,
|
|
522
|
-
range: newTextRange
|
|
616
|
+
range: newTextRange,
|
|
523
617
|
// If the undo position is beyond the front, clear everything before it, because we're changing history.
|
|
524
|
-
},
|
|
618
|
+
},
|
|
619
|
+
...(undoPosition > 0 ? newStack.slice(undoPosition) : newStack),
|
|
620
|
+
];
|
|
525
621
|
// Set the undo position to the last index.
|
|
526
622
|
undoPosition = 0;
|
|
623
|
+
// Focus the editor on the new caret location, unless the toolbar is focused.
|
|
624
|
+
if (!toolbarIsFocused())
|
|
625
|
+
element?.focus();
|
|
626
|
+
// Update the caret
|
|
627
|
+
claimActiveEditor();
|
|
527
628
|
}
|
|
528
629
|
function editNode(previous, edited) {
|
|
529
630
|
if (caretRange) {
|
|
@@ -533,8 +634,12 @@ function editNode(previous, edited) {
|
|
|
533
634
|
saveEdit(newRoot, { start: caretRange.start, end: caretRange.end });
|
|
534
635
|
// Update the range if the current range contains the previous node. This will generally
|
|
535
636
|
// be true any time a selected atom node is edited.
|
|
536
|
-
if (caretRange.start.node === previous &&
|
|
537
|
-
|
|
637
|
+
if (caretRange.start.node === previous &&
|
|
638
|
+
(edited instanceof AtomNode || edited instanceof TextNode))
|
|
639
|
+
caretRange = {
|
|
640
|
+
start: { node: edited, index: 0 },
|
|
641
|
+
end: { node: edited, index: 0 },
|
|
642
|
+
};
|
|
538
643
|
}
|
|
539
644
|
}
|
|
540
645
|
function handleMouseDown() {
|
|
@@ -546,20 +651,20 @@ function handleMouseDown() {
|
|
|
546
651
|
if (caretRange)
|
|
547
652
|
caretRange = { start: caretRange.start, end: caretRange.end };
|
|
548
653
|
// If we've selected a non-TextNode, release it, so the browser is free to select a text node.
|
|
549
|
-
if (caretRange &&
|
|
654
|
+
if (caretRange &&
|
|
655
|
+
!(caretRange.start.node instanceof TextNode ||
|
|
656
|
+
caretRange.start.node instanceof AtomNode ||
|
|
657
|
+
caretRange.start.node instanceof EmbedNode)) {
|
|
550
658
|
caretRange = undefined;
|
|
551
659
|
}
|
|
552
660
|
}
|
|
553
|
-
function
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (caretRange !== undefined) {
|
|
558
|
-
caretRange = { start: caretRange.start, end: caretRange.end };
|
|
559
|
-
}
|
|
661
|
+
function isFocused() {
|
|
662
|
+
return (document.activeElement === element ||
|
|
663
|
+
(element !== null && element?.contains(document.activeElement)) ||
|
|
664
|
+
toolbarIsFocused());
|
|
560
665
|
}
|
|
561
666
|
function updateFocus() {
|
|
562
|
-
editorFocused =
|
|
667
|
+
editorFocused = isFocused();
|
|
563
668
|
}
|
|
564
669
|
function handleCopy(node) {
|
|
565
670
|
// Set the editor's clipboard.
|
|
@@ -568,15 +673,30 @@ function handleCopy(node) {
|
|
|
568
673
|
if (navigator.clipboard) {
|
|
569
674
|
navigator.clipboard.write([
|
|
570
675
|
new ClipboardItem({
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
676
|
+
'text/plain': new Blob([node.toText()], {
|
|
677
|
+
type: 'text/plain',
|
|
678
|
+
}),
|
|
679
|
+
'text/html': new Blob([node.toHTML()], {
|
|
680
|
+
type: 'text/html',
|
|
681
|
+
}),
|
|
682
|
+
}),
|
|
574
683
|
]);
|
|
575
684
|
}
|
|
576
685
|
else
|
|
577
686
|
window.alert("Your browser doesn't support copying to the clipboard :(");
|
|
578
687
|
}
|
|
579
|
-
function handlePaste(context,
|
|
688
|
+
function handlePaste(context, text, save) {
|
|
689
|
+
if (text instanceof BookishNode)
|
|
690
|
+
return handlePasteNode(context, text, save);
|
|
691
|
+
const plain = text.plain === undefined ? undefined : new TextNode(text.plain);
|
|
692
|
+
if (plain) {
|
|
693
|
+
const edit = handlePasteNode(context, plain, save);
|
|
694
|
+
if (edit)
|
|
695
|
+
return edit;
|
|
696
|
+
}
|
|
697
|
+
return undefined;
|
|
698
|
+
}
|
|
699
|
+
function handlePasteNode(context, node, save) {
|
|
580
700
|
// Track revisions in these two variables.
|
|
581
701
|
let newRoot = context.root;
|
|
582
702
|
let newCaret = context.start;
|
|
@@ -619,112 +739,103 @@ function handlePaste(context, node, save) {
|
|
|
619
739
|
return { root: newChapter, range: edit.range };
|
|
620
740
|
}
|
|
621
741
|
}
|
|
742
|
+
function toolbarIsFocused() {
|
|
743
|
+
const toolbar = document.querySelector('.bookish-editor-toolbar');
|
|
744
|
+
return toolbar !== null && toolbar.contains(document.activeElement);
|
|
745
|
+
}
|
|
622
746
|
$: isAtom = caretRange && caretRange.start.node instanceof AtomNode;
|
|
623
|
-
$: inAtom =
|
|
624
|
-
|
|
625
|
-
$:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
747
|
+
$: inAtom =
|
|
748
|
+
caretRange && caretRange.start.node.isInside(editedNode, AtomNode);
|
|
749
|
+
$: isSelection =
|
|
750
|
+
caretRange !== undefined &&
|
|
751
|
+
(caretRange.start.node !== caretRange.end.node ||
|
|
752
|
+
caretRange.start.index !== caretRange.end.index);
|
|
753
|
+
$: isItalic =
|
|
754
|
+
caretRange !== undefined &&
|
|
755
|
+
isSelection === false &&
|
|
756
|
+
caretRange.start.node instanceof TextNode &&
|
|
757
|
+
caretRange.start.node.isItalic(editedNode);
|
|
758
|
+
$: isBold =
|
|
759
|
+
caretRange !== undefined &&
|
|
760
|
+
isSelection === false &&
|
|
761
|
+
caretRange.start.node instanceof TextNode &&
|
|
762
|
+
caretRange.start.node.isBold(editedNode);
|
|
763
|
+
$: isLink =
|
|
764
|
+
caretRange !== undefined &&
|
|
765
|
+
isSelection === false &&
|
|
766
|
+
caretRange.start.node.getClosestParentMatching(editedNode, (p) => p instanceof LinkNode) !== undefined;
|
|
767
|
+
// When the caret context or editor focus changes, update the active editor.
|
|
629
768
|
$: {
|
|
630
|
-
caretRange
|
|
631
|
-
|
|
632
|
-
|
|
769
|
+
if ((caretRange || editorFocused) && isFocused())
|
|
770
|
+
claimActiveEditor();
|
|
771
|
+
else
|
|
772
|
+
activeEditor.set(undefined);
|
|
633
773
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
774
|
+
function claimActiveEditor() {
|
|
775
|
+
activeEditor.set({
|
|
776
|
+
range: caretRange,
|
|
777
|
+
coordinate: caretCoordinate,
|
|
778
|
+
setCaret: (range) => {
|
|
779
|
+
caretRange = range;
|
|
780
|
+
element?.focus();
|
|
781
|
+
},
|
|
782
|
+
edit: editNode,
|
|
783
|
+
executor: executeCommand,
|
|
784
|
+
context: getCaretContext(),
|
|
785
|
+
root: editedNode,
|
|
786
|
+
focused: editorFocused,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
// Pass the function above to all children so they can claim the active editor for non-focus reasons.
|
|
790
|
+
// (Drag and drop, for example.)
|
|
791
|
+
setContext('claimeditor', claimActiveEditor);
|
|
647
792
|
</script>
|
|
648
793
|
|
|
649
|
-
<div
|
|
650
|
-
class={`bookish-editor ${inAtom ?
|
|
794
|
+
<div
|
|
795
|
+
class={`bookish-editor ${inAtom ? 'bookish-editor-atom-focused' : ''}`}
|
|
651
796
|
bind:this={element}
|
|
652
|
-
on:keydown={
|
|
653
|
-
on:
|
|
797
|
+
on:keydown={handleKey}
|
|
798
|
+
on:keypress={handlePress}
|
|
654
799
|
on:mousedown|stopPropagation={handleMouseDown}
|
|
655
800
|
on:focus={updateFocus}
|
|
656
801
|
on:blur={updateFocus}
|
|
657
|
-
|
|
658
|
-
tabIndex=0
|
|
802
|
+
tabIndex="0"
|
|
659
803
|
>
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
<!-- Draw a caret. We draw our own since this view isn't contentEditable and we can't show a caret.
|
|
804
|
+
<!-- The chapter content -->
|
|
805
|
+
<svelte:component this={component} node={editedNode} {placeholder} />
|
|
806
|
+
<!-- The caret. We render our own since this view isn't contentEditable and we can't show a caret.
|
|
664
807
|
Customize the rendering based on the formatting applied to the text node. -->
|
|
665
|
-
{#if caretCoordinate && caretRange && !isAtom &&
|
|
666
|
-
<
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
808
|
+
{#if caretCoordinate && caretRange && !isAtom && isSelection === false && editorFocused}
|
|
809
|
+
<CaretView
|
|
810
|
+
left={caretCoordinate.x}
|
|
811
|
+
top={caretCoordinate.y}
|
|
812
|
+
height={caretCoordinate.height}
|
|
813
|
+
blink={editorFocused && keyboardIdle}
|
|
814
|
+
ignored={ignoredInput}
|
|
815
|
+
linked={isLink}
|
|
816
|
+
bold={isBold}
|
|
817
|
+
italic={isItalic}
|
|
818
|
+
disabled={!editorFocused}
|
|
819
|
+
/>
|
|
670
820
|
{/if}
|
|
671
|
-
<svelte:component this={component} node={editedNode} placeholder={placeholder} />
|
|
672
821
|
</div>
|
|
673
822
|
|
|
674
823
|
<style>
|
|
824
|
+
.bookish-editor {
|
|
825
|
+
min-height: 2em;
|
|
826
|
+
}
|
|
827
|
+
|
|
675
828
|
.bookish-editor:focus {
|
|
676
|
-
outline:
|
|
677
|
-
|
|
678
|
-
|
|
829
|
+
outline: var(--app-chrome-border-size) solid
|
|
830
|
+
var(--app-interactive-color);
|
|
831
|
+
outline-offset: var(--app-chrome-padding);
|
|
832
|
+
border-radius: var(--app-chrome-roundedness);
|
|
679
833
|
}
|
|
680
834
|
|
|
681
835
|
.bookish-editor.bookish-editor-atom-focused:focus {
|
|
682
836
|
outline: none;
|
|
683
837
|
}
|
|
684
838
|
|
|
685
|
-
.bookish-editor-caret {
|
|
686
|
-
left: 0;
|
|
687
|
-
top: 0;
|
|
688
|
-
width: 0px;
|
|
689
|
-
background: none;
|
|
690
|
-
outline: 1px solid var(--bookish-paragraph-color);
|
|
691
|
-
display: inline-block;
|
|
692
|
-
box-sizing: border-box;
|
|
693
|
-
position: absolute;
|
|
694
|
-
z-index: 1;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
@keyframes bookish-editor-caret-blink {
|
|
698
|
-
100% {
|
|
699
|
-
outline: none;
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
.bookish-editor-caret-disabled {
|
|
704
|
-
outline-color: var(--bookish-app-chrome-muted);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
.bookish-editor-caret-blink {
|
|
708
|
-
animation: bookish-editor-caret-blink 1s steps(2) infinite;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
.bookish-editor-caret-italic {
|
|
712
|
-
transform: skew(-10deg);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
.bookish-editor-caret-bold {
|
|
716
|
-
outline-width: 2px;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
.bookish-editor-caret-ignored {
|
|
720
|
-
animation: failure 100ms 10;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
.bookish-editor-caret-linked {
|
|
724
|
-
outline-width: 1px;
|
|
725
|
-
outline-color: var(--bookish-link-color);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
839
|
.bookish-editor-inline-editor {
|
|
729
840
|
display: inline-block;
|
|
730
841
|
position: relative;
|
|
@@ -743,13 +854,8 @@ $: caretStore.set({
|
|
|
743
854
|
font-weight: bold;
|
|
744
855
|
}
|
|
745
856
|
|
|
746
|
-
:global(.bookish-editor .bookish-table table td) {
|
|
747
|
-
border: var(--bookish-app-chrome-border-width) dashed var(--bookish-app-chrome-border-color);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
857
|
.bookish-editor :global(hr) {
|
|
751
858
|
margin-top: 1em;
|
|
752
|
-
margin-bottom: 1em;
|
|
859
|
+
margin-bottom: 1em;
|
|
753
860
|
}
|
|
754
|
-
|
|
755
|
-
</style>
|
|
861
|
+
</style>
|