bookish-press 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/components/Auth.svelte +52 -0
- package/components/Auth.svelte.d.ts +16 -0
- package/components/Authentication.d.ts +8 -0
- package/components/Authentication.js +1 -0
- package/components/BookPreview.svelte +87 -0
- package/components/BookPreview.svelte.d.ts +18 -0
- package/components/External.svelte +4 -0
- package/components/External.svelte.d.ts +18 -0
- package/components/Link.svelte +5 -0
- package/components/Link.svelte.d.ts +19 -0
- package/components/chapter/Atom.svelte +36 -0
- package/components/chapter/Atom.svelte.d.ts +19 -0
- package/components/chapter/Block.svelte +32 -0
- package/components/chapter/Block.svelte.d.ts +17 -0
- package/components/chapter/Callout.svelte +10 -0
- package/components/chapter/Callout.svelte.d.ts +17 -0
- package/components/chapter/CaptionedCode.svelte +34 -0
- package/components/chapter/CaptionedCode.svelte.d.ts +17 -0
- package/components/chapter/ChapterBody.svelte +23 -0
- package/components/chapter/ChapterBody.svelte.d.ts +18 -0
- package/components/chapter/Citations.svelte +66 -0
- package/components/chapter/Citations.svelte.d.ts +17 -0
- package/components/chapter/Code.svelte +35 -0
- package/components/chapter/Code.svelte.d.ts +22 -0
- package/components/chapter/Comment.svelte +46 -0
- package/components/chapter/Comment.svelte.d.ts +17 -0
- package/components/chapter/Definition.svelte +38 -0
- package/components/chapter/Definition.svelte.d.ts +20 -0
- package/components/chapter/Embed.svelte +122 -0
- package/components/chapter/Embed.svelte.d.ts +18 -0
- package/components/chapter/ErrorMessage.svelte +4 -0
- package/components/chapter/ErrorMessage.svelte.d.ts +17 -0
- package/components/chapter/Footnote.svelte +37 -0
- package/components/chapter/Footnote.svelte.d.ts +19 -0
- package/components/chapter/Format.svelte +57 -0
- package/components/chapter/Format.svelte.d.ts +18 -0
- package/components/chapter/InlineCode.svelte +12 -0
- package/components/chapter/InlineCode.svelte.d.ts +17 -0
- package/components/chapter/Label.svelte +20 -0
- package/components/chapter/Label.svelte.d.ts +17 -0
- package/components/chapter/Link.svelte +51 -0
- package/components/chapter/Link.svelte.d.ts +17 -0
- package/components/chapter/List.svelte +14 -0
- package/components/chapter/List.svelte.d.ts +17 -0
- package/components/chapter/Marginal.svelte +48 -0
- package/components/chapter/Marginal.svelte.d.ts +19 -0
- package/components/chapter/Paragraph.svelte +15 -0
- package/components/chapter/Paragraph.svelte.d.ts +18 -0
- package/components/chapter/Python.svelte +81 -0
- package/components/chapter/Python.svelte.d.ts +18 -0
- package/components/chapter/Quote.svelte +16 -0
- package/components/chapter/Quote.svelte.d.ts +17 -0
- package/components/chapter/Rule.svelte +4 -0
- package/components/chapter/Rule.svelte.d.ts +17 -0
- package/components/chapter/Table.svelte +29 -0
- package/components/chapter/Table.svelte.d.ts +17 -0
- package/components/chapter/Text.svelte +62 -0
- package/components/chapter/Text.svelte.d.ts +17 -0
- package/components/chapter/renderPosition.d.ts +3 -0
- package/components/chapter/renderPosition.js +4 -0
- package/components/editor/BookishEditor.svelte +755 -0
- package/components/editor/BookishEditor.svelte.d.ts +22 -0
- package/components/editor/CalloutEditor.svelte +10 -0
- package/components/editor/CalloutEditor.svelte.d.ts +17 -0
- package/components/editor/CaptionedCodeEditor.svelte +22 -0
- package/components/editor/CaptionedCodeEditor.svelte.d.ts +17 -0
- package/components/editor/CaretContext.d.ts +18 -0
- package/components/editor/CaretContext.js +1 -0
- package/components/editor/CaretState.d.ts +47 -0
- package/components/editor/CaretState.js +1 -0
- package/components/editor/CaretUtilities.d.ts +5 -0
- package/components/editor/CaretUtilities.js +1 -0
- package/components/editor/CitationsEditor.svelte +42 -0
- package/components/editor/CitationsEditor.svelte.d.ts +17 -0
- package/components/editor/Clipboard.d.ts +3 -0
- package/components/editor/Clipboard.js +1 -0
- package/components/editor/Command.d.ts +18 -0
- package/components/editor/Command.js +1 -0
- package/components/editor/Commands.d.ts +3 -0
- package/components/editor/Commands.js +951 -0
- package/components/editor/ConfirmButton.svelte +43 -0
- package/components/editor/ConfirmButton.svelte.d.ts +18 -0
- package/components/editor/DefinitionEditor.svelte +28 -0
- package/components/editor/DefinitionEditor.svelte.d.ts +17 -0
- package/components/editor/EmbedEditor.svelte +87 -0
- package/components/editor/EmbedEditor.svelte.d.ts +17 -0
- package/components/editor/ImageChooser.svelte +70 -0
- package/components/editor/ImageChooser.svelte.d.ts +18 -0
- package/components/editor/InlineCodeEditor.svelte +9 -0
- package/components/editor/InlineCodeEditor.svelte.d.ts +17 -0
- package/components/editor/LabelEditor.svelte +23 -0
- package/components/editor/LabelEditor.svelte.d.ts +17 -0
- package/components/editor/LanguageEditor.svelte +11 -0
- package/components/editor/LanguageEditor.svelte.d.ts +17 -0
- package/components/editor/LinkEditor.svelte +75 -0
- package/components/editor/LinkEditor.svelte.d.ts +17 -0
- package/components/editor/Note.svelte +9 -0
- package/components/editor/Note.svelte.d.ts +23 -0
- package/components/editor/PositionEditor.svelte +14 -0
- package/components/editor/PositionEditor.svelte.d.ts +18 -0
- package/components/editor/QuoteEditor.svelte +5 -0
- package/components/editor/QuoteEditor.svelte.d.ts +17 -0
- package/components/editor/Switch.svelte +56 -0
- package/components/editor/Switch.svelte.d.ts +20 -0
- package/components/editor/TextEditor.svelte +193 -0
- package/components/editor/TextEditor.svelte.d.ts +23 -0
- package/components/editor/Toggle.svelte +38 -0
- package/components/editor/Toggle.svelte.d.ts +19 -0
- package/components/editor/Toolbar.svelte +202 -0
- package/components/editor/Toolbar.svelte.d.ts +20 -0
- package/components/editor/ToolbarGroup.svelte +41 -0
- package/components/editor/ToolbarGroup.svelte.d.ts +19 -0
- package/components/editor/ToolbarIcon.svelte +22 -0
- package/components/editor/ToolbarIcon.svelte.d.ts +16 -0
- package/components/editor/ToolbarSpacer.svelte +1 -0
- package/components/editor/ToolbarSpacer.svelte.d.ts +19 -0
- package/components/editor/URLEditor.svelte +15 -0
- package/components/editor/URLEditor.svelte.d.ts +18 -0
- package/components/editor/UndoState.d.ts +8 -0
- package/components/editor/UndoState.js +1 -0
- package/components/page/Acknowledgements.svelte +29 -0
- package/components/page/Acknowledgements.svelte.d.ts +14 -0
- package/components/page/Alert.svelte +11 -0
- package/components/page/Alert.svelte.d.ts +23 -0
- package/components/page/Authors.svelte +62 -0
- package/components/page/Authors.svelte.d.ts +20 -0
- package/components/page/BulkReferenceEditor.svelte +32 -0
- package/components/page/BulkReferenceEditor.svelte.d.ts +14 -0
- package/components/page/Chapter.svelte +301 -0
- package/components/page/Chapter.svelte.d.ts +18 -0
- package/components/page/ChapterContext.d.ts +10 -0
- package/components/page/ChapterContext.js +1 -0
- package/components/page/Contexts.d.ts +27 -0
- package/components/page/Contexts.js +21 -0
- package/components/page/DefinitionView.svelte +129 -0
- package/components/page/DefinitionView.svelte.d.ts +18 -0
- package/components/page/Description.svelte +22 -0
- package/components/page/Description.svelte.d.ts +14 -0
- package/components/page/Edition.svelte +106 -0
- package/components/page/Edition.svelte.d.ts +21 -0
- package/components/page/Glossary.svelte +61 -0
- package/components/page/Glossary.svelte.d.ts +14 -0
- package/components/page/Header.svelte +105 -0
- package/components/page/Header.svelte.d.ts +27 -0
- package/components/page/Index.svelte +117 -0
- package/components/page/Index.svelte.d.ts +14 -0
- package/components/page/Instructions.svelte +30 -0
- package/components/page/Instructions.svelte.d.ts +16 -0
- package/components/page/License.svelte +29 -0
- package/components/page/License.svelte.d.ts +14 -0
- package/components/page/Loading.svelte +13 -0
- package/components/page/Loading.svelte.d.ts +19 -0
- package/components/page/Media.svelte +76 -0
- package/components/page/Media.svelte.d.ts +14 -0
- package/components/page/MediaPreview.svelte +11 -0
- package/components/page/MediaPreview.svelte.d.ts +19 -0
- package/components/page/Outline.svelte +173 -0
- package/components/page/Outline.svelte.d.ts +19 -0
- package/components/page/Page.svelte +53 -0
- package/components/page/Page.svelte.d.ts +19 -0
- package/components/page/PossibleChapter.svelte +14 -0
- package/components/page/PossibleChapter.svelte.d.ts +14 -0
- package/components/page/PossibleReference.svelte +16 -0
- package/components/page/PossibleReference.svelte.d.ts +19 -0
- package/components/page/Print.svelte +8 -0
- package/components/page/Print.svelte.d.ts +14 -0
- package/components/page/Reference.svelte +145 -0
- package/components/page/Reference.svelte.d.ts +17 -0
- package/components/page/References.svelte +58 -0
- package/components/page/References.svelte.d.ts +14 -0
- package/components/page/Revisions.svelte +124 -0
- package/components/page/Revisions.svelte.d.ts +14 -0
- package/components/page/Search.svelte +108 -0
- package/components/page/Search.svelte.d.ts +14 -0
- package/components/page/Status.svelte +49 -0
- package/components/page/Status.svelte.d.ts +19 -0
- package/components/page/SubdomainEditor.svelte +30 -0
- package/components/page/SubdomainEditor.svelte.d.ts +14 -0
- package/components/page/TableOfContents.svelte +234 -0
- package/components/page/TableOfContents.svelte.d.ts +14 -0
- package/components/page/TableOfContentsImage.svelte +15 -0
- package/components/page/TableOfContentsImage.svelte.d.ts +16 -0
- package/components/page/TableOfContentsRow.svelte +57 -0
- package/components/page/TableOfContentsRow.svelte.d.ts +24 -0
- package/components/page/ThemeEditor.svelte +52 -0
- package/components/page/ThemeEditor.svelte.d.ts +14 -0
- package/components/page/ThemeEditorPreview.svelte +37 -0
- package/components/page/ThemeEditorPreview.svelte.d.ts +17 -0
- package/components/page/ThemeSetEditor.svelte +17 -0
- package/components/page/ThemeSetEditor.svelte.d.ts +18 -0
- package/components/page/ThemeVariableEditor.svelte +28 -0
- package/components/page/ThemeVariableEditor.svelte.d.ts +18 -0
- package/components/page/Unknown.svelte +29 -0
- package/components/page/Unknown.svelte.d.ts +16 -0
- package/components/page/margins.d.ts +2 -0
- package/components/page/margins.js +132 -0
- package/models/BookLoader.d.ts +2 -0
- package/models/BookLoader.js +67 -0
- package/models/Firebase.d.ts +4 -0
- package/models/Firebase.js +26 -0
- package/models/Firestore.d.ts +22 -0
- package/models/Firestore.js +194 -0
- package/models/book/Book.d.ts +60 -0
- package/models/book/Book.js +135 -0
- package/models/book/BookMedia.d.ts +23 -0
- package/models/book/BookMedia.js +102 -0
- package/models/book/Chapter.d.ts +70 -0
- package/models/book/Chapter.js +221 -0
- package/models/book/ChapterID.d.ts +10 -0
- package/models/book/ChapterID.js +10 -0
- package/models/book/Definition.d.ts +6 -0
- package/models/book/Definition.js +1 -0
- package/models/book/Edition.d.ts +111 -0
- package/models/book/Edition.js +466 -0
- package/models/book/Theme.d.ts +106 -0
- package/models/book/Theme.js +76 -0
- package/models/book/ThemeColors.d.ts +15 -0
- package/models/book/ThemeColors.js +1 -0
- package/models/chapter/AtomNode.d.ts +18 -0
- package/models/chapter/AtomNode.js +31 -0
- package/models/chapter/BlockNode.d.ts +21 -0
- package/models/chapter/BlockNode.js +117 -0
- package/models/chapter/BlocksNode.d.ts +50 -0
- package/models/chapter/BlocksNode.js +709 -0
- package/models/chapter/BlocksNode.test.d.ts +1 -0
- package/models/chapter/BlocksNode.test.js +302 -0
- package/models/chapter/Bookkeeping.d.ts +4 -0
- package/models/chapter/Bookkeeping.js +1 -0
- package/models/chapter/CalloutNode.d.ts +17 -0
- package/models/chapter/CalloutNode.js +36 -0
- package/models/chapter/Caret.d.ts +21 -0
- package/models/chapter/Caret.js +69 -0
- package/models/chapter/ChapterNode.d.ts +34 -0
- package/models/chapter/ChapterNode.js +80 -0
- package/models/chapter/CitationsNode.d.ts +16 -0
- package/models/chapter/CitationsNode.js +17 -0
- package/models/chapter/CodeNode.d.ts +30 -0
- package/models/chapter/CodeNode.js +64 -0
- package/models/chapter/CommentNode.d.ts +18 -0
- package/models/chapter/CommentNode.js +32 -0
- package/models/chapter/DefinitionNode.d.ts +12 -0
- package/models/chapter/DefinitionNode.js +20 -0
- package/models/chapter/Edit.d.ts +7 -0
- package/models/chapter/Edit.js +1 -0
- package/models/chapter/EmbedNode.d.ts +40 -0
- package/models/chapter/EmbedNode.js +89 -0
- package/models/chapter/ErrorNode.d.ts +17 -0
- package/models/chapter/ErrorNode.js +21 -0
- package/models/chapter/FootnoteNode.d.ts +18 -0
- package/models/chapter/FootnoteNode.js +29 -0
- package/models/chapter/FormatNode.d.ts +60 -0
- package/models/chapter/FormatNode.js +614 -0
- package/models/chapter/FormatNode.test.d.ts +1 -0
- package/models/chapter/FormatNode.test.js +54 -0
- package/models/chapter/InlineCodeNode.d.ts +12 -0
- package/models/chapter/InlineCodeNode.js +18 -0
- package/models/chapter/LabelNode.d.ts +17 -0
- package/models/chapter/LabelNode.js +22 -0
- package/models/chapter/LinkNode.d.ts +12 -0
- package/models/chapter/LinkNode.js +16 -0
- package/models/chapter/ListNode.d.ts +51 -0
- package/models/chapter/ListNode.js +370 -0
- package/models/chapter/ListNode.test.d.ts +1 -0
- package/models/chapter/ListNode.test.js +49 -0
- package/models/chapter/MetadataNode.d.ts +17 -0
- package/models/chapter/MetadataNode.js +24 -0
- package/models/chapter/Node.d.ts +36 -0
- package/models/chapter/Node.js +173 -0
- package/models/chapter/Node.test.d.ts +1 -0
- package/models/chapter/Node.test.js +27 -0
- package/models/chapter/ParagraphNode.d.ts +33 -0
- package/models/chapter/ParagraphNode.js +93 -0
- package/models/chapter/ParagraphNode.test.d.ts +1 -0
- package/models/chapter/ParagraphNode.test.js +21 -0
- package/models/chapter/Parser.d.ts +75 -0
- package/models/chapter/Parser.js +824 -0
- package/models/chapter/Parser.test.d.ts +1 -0
- package/models/chapter/Parser.test.js +25 -0
- package/models/chapter/Position.d.ts +2 -0
- package/models/chapter/Position.js +1 -0
- package/models/chapter/QuoteNode.d.ts +25 -0
- package/models/chapter/QuoteNode.js +61 -0
- package/models/chapter/ReferenceNode.d.ts +29 -0
- package/models/chapter/ReferenceNode.js +48 -0
- package/models/chapter/RootNode.d.ts +5 -0
- package/models/chapter/RootNode.js +1 -0
- package/models/chapter/RuleNode.d.ts +15 -0
- package/models/chapter/RuleNode.js +16 -0
- package/models/chapter/TableNode.d.ts +35 -0
- package/models/chapter/TableNode.js +176 -0
- package/models/chapter/TableNode.test.d.ts +1 -0
- package/models/chapter/TableNode.test.js +31 -0
- package/models/chapter/TextNode.d.ts +36 -0
- package/models/chapter/TextNode.js +136 -0
- package/models/chapter/TextNode.test.d.ts +1 -0
- package/models/chapter/TextNode.test.js +6 -0
- package/models/loadEdition.d.ts +5 -0
- package/models/loadEdition.js +16 -0
- package/package.json +189 -0
- package/util/getSubdomain.d.ts +3 -0
- package/util/getSubdomain.js +22 -0
- package/util/isMobile.d.ts +2 -0
- package/util/isMobile.js +6 -0
- package/util/mineReference.d.ts +3 -0
- package/util/mineReference.js +62 -0
- package/util/scrollToEyeLevel.d.ts +2 -0
- package/util/scrollToEyeLevel.js +5 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import MetadataNode from "./MetadataNode";
|
|
2
|
+
import TextNode from "./TextNode";
|
|
3
|
+
export default class InlineCodeNode extends MetadataNode<string> {
|
|
4
|
+
constructor(code?: TextNode, language?: string);
|
|
5
|
+
getType(): string;
|
|
6
|
+
toText(): string;
|
|
7
|
+
toHTML(): string;
|
|
8
|
+
toBookdown(): string;
|
|
9
|
+
copy(): this;
|
|
10
|
+
withMeta(meta: string): MetadataNode<string>;
|
|
11
|
+
withText(text: TextNode): MetadataNode<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import MetadataNode from "./MetadataNode";
|
|
2
|
+
import TextNode from "./TextNode";
|
|
3
|
+
export default class InlineCodeNode extends MetadataNode {
|
|
4
|
+
constructor(code, language = "plaintext") {
|
|
5
|
+
super(code === undefined ? new TextNode() : code, language);
|
|
6
|
+
}
|
|
7
|
+
getType() { return "inline-code"; }
|
|
8
|
+
toText() {
|
|
9
|
+
return this.getText().getText();
|
|
10
|
+
}
|
|
11
|
+
toHTML() { return `<code>${this.getMeta()}</code>`; }
|
|
12
|
+
toBookdown() {
|
|
13
|
+
return "`" + this.getText().toBookdown().replace(/`/g, '\\`') + "`" + (this.getMeta() === "plaintext" ? "" : this.getMeta());
|
|
14
|
+
}
|
|
15
|
+
copy() { return new InlineCodeNode(this.getText(), this.getMeta()); }
|
|
16
|
+
withMeta(meta) { return new InlineCodeNode(this.getText(), meta); }
|
|
17
|
+
withText(text) { return new InlineCodeNode(text, this.getMeta()); }
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import AtomNode from "./AtomNode";
|
|
2
|
+
import type Caret from "./Caret";
|
|
3
|
+
import type FormatNode from "./FormatNode";
|
|
4
|
+
import type Node from "./Node";
|
|
5
|
+
export default class LabelNode extends AtomNode<string> {
|
|
6
|
+
constructor(newID: string);
|
|
7
|
+
getType(): string;
|
|
8
|
+
getDefaultCaret(): Caret;
|
|
9
|
+
toText(): string;
|
|
10
|
+
toHTML(): string;
|
|
11
|
+
toBookdown(format?: FormatNode): string;
|
|
12
|
+
getParentOf(): Node | undefined;
|
|
13
|
+
copy(): this;
|
|
14
|
+
withMeta(newID: string): LabelNode;
|
|
15
|
+
withChildReplaced(): undefined;
|
|
16
|
+
withContentInRange(): this | undefined;
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import AtomNode from "./AtomNode";
|
|
2
|
+
const ERROR_PLACEHOLDER = "LABELNOIDERROR";
|
|
3
|
+
export default class LabelNode extends AtomNode {
|
|
4
|
+
constructor(newID) {
|
|
5
|
+
super(newID === ERROR_PLACEHOLDER ? "" : newID);
|
|
6
|
+
}
|
|
7
|
+
getType() { return "label"; }
|
|
8
|
+
getDefaultCaret() { return { node: this, index: 0 }; }
|
|
9
|
+
toText() { return ""; }
|
|
10
|
+
toHTML() { return ""; }
|
|
11
|
+
toBookdown(format) {
|
|
12
|
+
const id = this.getMeta();
|
|
13
|
+
const nextText = format?.getNextTextOrAtom(this)?.toBookdown();
|
|
14
|
+
// The trailing space after enables parsing. The error placeholder handles invalid empty label IDs.
|
|
15
|
+
return `:${id === "" ? ERROR_PLACEHOLDER : id}${nextText?.startsWith(" ") ? "" : " "}`;
|
|
16
|
+
}
|
|
17
|
+
getParentOf() { return undefined; }
|
|
18
|
+
copy() { return new LabelNode(this.getMeta()); }
|
|
19
|
+
withMeta(newID) { return new LabelNode(newID); }
|
|
20
|
+
withChildReplaced() { return undefined; }
|
|
21
|
+
withContentInRange() { return this.copy(); }
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import MetadataNode from "./MetadataNode";
|
|
2
|
+
import TextNode from "./TextNode";
|
|
3
|
+
export default class LinkNode extends MetadataNode<string> {
|
|
4
|
+
constructor(text?: TextNode, url?: string);
|
|
5
|
+
getType(): string;
|
|
6
|
+
toText(): string;
|
|
7
|
+
toHTML(): string;
|
|
8
|
+
toBookdown(): string;
|
|
9
|
+
copy(): this;
|
|
10
|
+
withMeta(meta: string): MetadataNode<string>;
|
|
11
|
+
withText(text: TextNode): MetadataNode<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import MetadataNode from "./MetadataNode";
|
|
2
|
+
import TextNode from "./TextNode";
|
|
3
|
+
export default class LinkNode extends MetadataNode {
|
|
4
|
+
constructor(text, url = "") {
|
|
5
|
+
super(text === undefined ? new TextNode() : text, url);
|
|
6
|
+
}
|
|
7
|
+
getType() { return "link"; }
|
|
8
|
+
toText() { return this.getText().toText(); }
|
|
9
|
+
toHTML() { return `<a href="${this.getMeta()}">${this.getText().toHTML()}</a>`; }
|
|
10
|
+
toBookdown() { return `[${this.getText().toBookdown()}|${this.getMeta()}]`; }
|
|
11
|
+
copy() {
|
|
12
|
+
return new LinkNode(this.getText(), this.getMeta());
|
|
13
|
+
}
|
|
14
|
+
withMeta(meta) { return new LinkNode(this.getText(), meta); }
|
|
15
|
+
withText(text) { return new LinkNode(text, this.getMeta()); }
|
|
16
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type Node from "./Node";
|
|
2
|
+
import FormatNode from "./FormatNode";
|
|
3
|
+
import type { Format } from "./FormatNode";
|
|
4
|
+
import type Caret from "./Caret";
|
|
5
|
+
import { type CaretRange } from "./Caret";
|
|
6
|
+
import BlockNode from "./BlockNode";
|
|
7
|
+
import type Edit from "./Edit";
|
|
8
|
+
export declare type ListNodeType = FormatNode | ListNode;
|
|
9
|
+
export default class ListNode extends BlockNode {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(items: Array<FormatNode | ListNode>, numbered: boolean);
|
|
12
|
+
getType(): string;
|
|
13
|
+
isNumbered(): boolean;
|
|
14
|
+
isEmpty(): boolean;
|
|
15
|
+
getItems(): ListNodeType[];
|
|
16
|
+
getChildren(): ListNodeType[];
|
|
17
|
+
getListItems(): ListNode[];
|
|
18
|
+
getLength(): number;
|
|
19
|
+
getLastItem(): FormatNode | undefined;
|
|
20
|
+
getFirstItem(): FormatNode | undefined;
|
|
21
|
+
getFormats(): FormatNode[];
|
|
22
|
+
getFirstLastItem(first: boolean): FormatNode | undefined;
|
|
23
|
+
getLevel(root: Node): number;
|
|
24
|
+
toText(): string;
|
|
25
|
+
toBookdown(level?: number): string;
|
|
26
|
+
toHTML(): string;
|
|
27
|
+
getParentOf(node: Node): Node | undefined;
|
|
28
|
+
atBeginningOfItem(root: Node, caret: Caret): boolean;
|
|
29
|
+
getItemContaining(caret: Caret): number | undefined;
|
|
30
|
+
getItem(index: number): ListNodeType | undefined;
|
|
31
|
+
getFirstCaret(): Caret | undefined;
|
|
32
|
+
copy(): this;
|
|
33
|
+
withStyle(numbered: boolean): ListNode;
|
|
34
|
+
withItemAppended(item: ListNodeType): ListNode;
|
|
35
|
+
withItemPrepended(item: ListNodeType): ListNode;
|
|
36
|
+
withItemAt(item: ListNodeType, index: number): ListNode | undefined;
|
|
37
|
+
withItemAfter(item: ListNodeType, anchor: ListNodeType): ListNode | undefined;
|
|
38
|
+
withItemBefore(item: ListNodeType, anchor: ListNodeType): ListNode | undefined;
|
|
39
|
+
withItemReplaced(index: number, item: ListNodeType): ListNode | undefined;
|
|
40
|
+
withoutItemAt(index: number): ListNode | undefined;
|
|
41
|
+
withoutItem(item: FormatNode): ListNode | undefined;
|
|
42
|
+
withListAppended(list: ListNode): ListNode;
|
|
43
|
+
withItemMergedBackwards(index: number): [ListNode, Caret] | undefined;
|
|
44
|
+
withItemSplit(caret: Caret): ListNode | undefined;
|
|
45
|
+
withItemIndented(format: FormatNode): ListNode | undefined;
|
|
46
|
+
withItemUnindented(format: FormatNode): ListNode | undefined;
|
|
47
|
+
withChildReplaced(node: ListNodeType, replacement: ListNodeType | undefined): this | undefined;
|
|
48
|
+
withContentInRange(range: CaretRange): this | undefined;
|
|
49
|
+
withNodeInserted(caret: Caret, node: Node): Edit;
|
|
50
|
+
withRangeFormatted(range: CaretRange, format: Format | undefined): Edit;
|
|
51
|
+
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import FormatNode from "./FormatNode";
|
|
2
|
+
import { caretToIndex, indexToCaret } from "./Caret";
|
|
3
|
+
import BlockNode from "./BlockNode";
|
|
4
|
+
export default class ListNode extends BlockNode {
|
|
5
|
+
#numbered;
|
|
6
|
+
#items;
|
|
7
|
+
constructor(items, numbered) {
|
|
8
|
+
super();
|
|
9
|
+
this.#numbered = numbered;
|
|
10
|
+
let adjustedItems = items
|
|
11
|
+
// Make sure all format nodes have an empty text.
|
|
12
|
+
.map(i => i instanceof FormatNode ? i.withTextIfEmpty() : i)
|
|
13
|
+
// Strip all empty sublists.
|
|
14
|
+
.filter(i => !i.isEmpty());
|
|
15
|
+
this.#items = adjustedItems;
|
|
16
|
+
}
|
|
17
|
+
getType() { return "list"; }
|
|
18
|
+
isNumbered() { return this.#numbered; }
|
|
19
|
+
isEmpty() { return this.#items.length === 0 || this.#items.every(i => i instanceof ListNode && i.isEmpty()); }
|
|
20
|
+
getItems() { return this.#items; }
|
|
21
|
+
getChildren() { return this.#items; }
|
|
22
|
+
getListItems() { return this.#items.filter(i => i instanceof ListNode); }
|
|
23
|
+
getLength() { return this.#items.length; }
|
|
24
|
+
getLastItem() { return this.getFirstLastItem(false); }
|
|
25
|
+
getFirstItem() { return this.getFirstLastItem(true); }
|
|
26
|
+
getFormats() {
|
|
27
|
+
return this.#items.reduce((previous, current) => previous.concat(current instanceof FormatNode ? [current] : current.getFormats()), []);
|
|
28
|
+
}
|
|
29
|
+
getFirstLastItem(first) {
|
|
30
|
+
if (this.#items.length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
// If it's a format, return it.
|
|
33
|
+
const last = this.#items[first ? 0 : this.getLength() - 1];
|
|
34
|
+
if (last instanceof FormatNode)
|
|
35
|
+
return last;
|
|
36
|
+
// Otherwise, recurse into the last item's list.
|
|
37
|
+
return last.getFirstLastItem(first);
|
|
38
|
+
}
|
|
39
|
+
getLevel(root) {
|
|
40
|
+
const parent = root.getParentOf(this);
|
|
41
|
+
if (parent instanceof ListNode)
|
|
42
|
+
return parent.getLevel(root) + 1;
|
|
43
|
+
else
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
toText() {
|
|
47
|
+
return this.#items.map(item => item.toText()).join(" ");
|
|
48
|
+
}
|
|
49
|
+
toBookdown(level = 1) {
|
|
50
|
+
return this.isNumbered() ?
|
|
51
|
+
this.#items.map((item, number) => (item instanceof ListNode ? "" : ((number + 1) + ".".repeat(level)) + " ") + item.toBookdown(level + 1)).join("\n") :
|
|
52
|
+
this.#items.map(item => (item instanceof ListNode ? "" : "*".repeat(level) + " ") + item.toBookdown(level + 1)).join("\n");
|
|
53
|
+
}
|
|
54
|
+
toHTML() {
|
|
55
|
+
return this.isNumbered() ?
|
|
56
|
+
`<ol>${this.#items.map(i => `<li>${i.toHTML()}</li>`).join("")}</ol>` :
|
|
57
|
+
`<ul>${this.#items.map(i => `<li>${i.toHTML()}</li>`).join("")}</ul>`;
|
|
58
|
+
}
|
|
59
|
+
getParentOf(node) {
|
|
60
|
+
return this.#items.map(b => b === node ? this : b.getParentOf(node)).find(b => b !== undefined);
|
|
61
|
+
}
|
|
62
|
+
atBeginningOfItem(root, caret) {
|
|
63
|
+
const format = caret.node.getFarthestParentMatching(root, n => n instanceof FormatNode);
|
|
64
|
+
return format !== undefined &&
|
|
65
|
+
caret.node.hasAncestor(root, this) &&
|
|
66
|
+
caret.index === 0 &&
|
|
67
|
+
format.getTextNodes()[0] === caret.node;
|
|
68
|
+
}
|
|
69
|
+
getItemContaining(caret) {
|
|
70
|
+
const match = this.#items.find(n => n.getParentOf(caret.node) !== undefined);
|
|
71
|
+
if (match === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const index = this.#items.indexOf(match);
|
|
74
|
+
if (index === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
return index;
|
|
77
|
+
}
|
|
78
|
+
getItem(index) {
|
|
79
|
+
if (index < 0 || index >= this.#items.length)
|
|
80
|
+
return;
|
|
81
|
+
return this.#items[index];
|
|
82
|
+
}
|
|
83
|
+
getFirstCaret() { return this.#items.length === 0 ? undefined : this.#items[0].getFirstCaret(); }
|
|
84
|
+
copy() {
|
|
85
|
+
return new ListNode(this.#items.map(i => i.copy()), this.#numbered);
|
|
86
|
+
}
|
|
87
|
+
withStyle(numbered) {
|
|
88
|
+
return new ListNode(this.#items, numbered);
|
|
89
|
+
}
|
|
90
|
+
withItemAppended(item) {
|
|
91
|
+
return new ListNode([...this.#items, item], this.#numbered);
|
|
92
|
+
}
|
|
93
|
+
withItemPrepended(item) {
|
|
94
|
+
return new ListNode([item, ...this.#items], this.#numbered);
|
|
95
|
+
}
|
|
96
|
+
withItemAt(item, index) {
|
|
97
|
+
if (index < 0 || index > this.#items.length)
|
|
98
|
+
return;
|
|
99
|
+
const newItems = this.#items.slice();
|
|
100
|
+
newItems.splice(index, 0, item);
|
|
101
|
+
return new ListNode(newItems, this.#numbered);
|
|
102
|
+
}
|
|
103
|
+
withItemAfter(item, anchor) {
|
|
104
|
+
const index = this.#items.indexOf(anchor);
|
|
105
|
+
if (index < 0)
|
|
106
|
+
return;
|
|
107
|
+
return this.withItemAt(item, index + 1);
|
|
108
|
+
}
|
|
109
|
+
withItemBefore(item, anchor) {
|
|
110
|
+
const index = this.#items.indexOf(anchor);
|
|
111
|
+
if (index < 0)
|
|
112
|
+
return;
|
|
113
|
+
return this.withItemAt(item, index);
|
|
114
|
+
}
|
|
115
|
+
withItemReplaced(index, item) {
|
|
116
|
+
if (index < 0 || index >= this.#items.length)
|
|
117
|
+
return;
|
|
118
|
+
const newItems = this.#items.slice();
|
|
119
|
+
newItems[index] = item;
|
|
120
|
+
return new ListNode(newItems, this.#numbered);
|
|
121
|
+
}
|
|
122
|
+
withoutItemAt(index) {
|
|
123
|
+
if (index < 0 || index >= this.#items.length)
|
|
124
|
+
return;
|
|
125
|
+
const newItems = this.#items.slice();
|
|
126
|
+
newItems.splice(index, 1);
|
|
127
|
+
return new ListNode(newItems, this.#numbered);
|
|
128
|
+
}
|
|
129
|
+
withoutItem(item) {
|
|
130
|
+
const index = this.#items.indexOf(item);
|
|
131
|
+
if (index < 0)
|
|
132
|
+
return;
|
|
133
|
+
return this.withoutItemAt(index);
|
|
134
|
+
}
|
|
135
|
+
withListAppended(list) {
|
|
136
|
+
return new ListNode([...this.#items, ...list.#items], this.#numbered);
|
|
137
|
+
}
|
|
138
|
+
withItemMergedBackwards(index) {
|
|
139
|
+
if (index <= 0 || index >= this.#items.length)
|
|
140
|
+
return;
|
|
141
|
+
const deletedItem = this.#items[index];
|
|
142
|
+
let previousItem = this.#items[index - 1];
|
|
143
|
+
// If we're merging a sublist back into this list, move its
|
|
144
|
+
// remaining format into the previous item.
|
|
145
|
+
if (deletedItem instanceof ListNode) {
|
|
146
|
+
if (previousItem instanceof ListNode)
|
|
147
|
+
return;
|
|
148
|
+
const itemToMerge = deletedItem.getFirstItem();
|
|
149
|
+
if (itemToMerge === undefined)
|
|
150
|
+
return;
|
|
151
|
+
const newSubList = deletedItem.withChildReplaced(itemToMerge, undefined);
|
|
152
|
+
if (newSubList === undefined)
|
|
153
|
+
return;
|
|
154
|
+
const listWithRevisedSublist = this.withChildReplaced(deletedItem, newSubList);
|
|
155
|
+
if (listWithRevisedSublist === undefined)
|
|
156
|
+
return;
|
|
157
|
+
const lastCaret = previousItem.getLastCaret();
|
|
158
|
+
if (lastCaret === undefined)
|
|
159
|
+
return;
|
|
160
|
+
const textIndex = caretToIndex(previousItem, lastCaret);
|
|
161
|
+
if (textIndex === undefined)
|
|
162
|
+
return;
|
|
163
|
+
const mergedItem = previousItem.withSegmentsAppended(itemToMerge);
|
|
164
|
+
const listWithMergedItem = listWithRevisedSublist.withChildReplaced(previousItem, mergedItem);
|
|
165
|
+
if (listWithMergedItem === undefined)
|
|
166
|
+
return;
|
|
167
|
+
const newCaret = indexToCaret(mergedItem, textIndex);
|
|
168
|
+
if (newCaret === undefined)
|
|
169
|
+
return;
|
|
170
|
+
return [listWithMergedItem, newCaret];
|
|
171
|
+
}
|
|
172
|
+
// If the previous item is a list, find it's last item.
|
|
173
|
+
else if (previousItem instanceof ListNode) {
|
|
174
|
+
const lastItem = previousItem.getLastItem();
|
|
175
|
+
if (lastItem === undefined)
|
|
176
|
+
return;
|
|
177
|
+
const lastCaret = lastItem.getLastCaret();
|
|
178
|
+
if (lastCaret === undefined)
|
|
179
|
+
return;
|
|
180
|
+
const newCaretIndex = caretToIndex(lastItem, lastCaret);
|
|
181
|
+
if (newCaretIndex === undefined)
|
|
182
|
+
return;
|
|
183
|
+
const mergedItem = lastItem.withSegmentsAppended(deletedItem);
|
|
184
|
+
const newSublist = previousItem.withChildReplaced(lastItem, mergedItem);
|
|
185
|
+
if (newSublist === undefined)
|
|
186
|
+
return;
|
|
187
|
+
const newList = this.withChildReplaced(previousItem, newSublist)?.withoutItemAt(index);
|
|
188
|
+
if (newList === undefined)
|
|
189
|
+
return;
|
|
190
|
+
const newCaret = indexToCaret(mergedItem, newCaretIndex);
|
|
191
|
+
if (newCaret === undefined)
|
|
192
|
+
return;
|
|
193
|
+
return [newList, newCaret];
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const lastCaret = previousItem.getLastCaret();
|
|
197
|
+
if (lastCaret === undefined)
|
|
198
|
+
return;
|
|
199
|
+
const newCaretIndex = caretToIndex(previousItem, lastCaret);
|
|
200
|
+
if (newCaretIndex === undefined)
|
|
201
|
+
return;
|
|
202
|
+
const mergedItem = previousItem.withSegmentsAppended(deletedItem);
|
|
203
|
+
const newList = this.withItemReplaced(index - 1, mergedItem)?.withoutItemAt(index);
|
|
204
|
+
if (newList === undefined)
|
|
205
|
+
return;
|
|
206
|
+
const newCaret = indexToCaret(mergedItem, newCaretIndex);
|
|
207
|
+
if (newCaret === undefined)
|
|
208
|
+
return;
|
|
209
|
+
return [newList, newCaret];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
withItemSplit(caret) {
|
|
213
|
+
// Which item contains the caret?
|
|
214
|
+
const item = this.#items.find(i => i instanceof FormatNode && i.contains(caret.node));
|
|
215
|
+
if (item === undefined)
|
|
216
|
+
return;
|
|
217
|
+
const parts = item.split(caret);
|
|
218
|
+
if (parts === undefined)
|
|
219
|
+
return;
|
|
220
|
+
const [first, second] = parts;
|
|
221
|
+
return this.withItemAfter(second, item)?.withItemAfter(first, item)?.withoutItem(item);
|
|
222
|
+
}
|
|
223
|
+
// Recursively search for the given item and indent it, constructing a new list.
|
|
224
|
+
withItemIndented(format) {
|
|
225
|
+
// Check each item in the list to see if it matches...
|
|
226
|
+
for (let index = 0; index < this.#items.length; index++) {
|
|
227
|
+
const item = this.#items[index];
|
|
228
|
+
// Base case: does this list contain the item?
|
|
229
|
+
if (item === format) {
|
|
230
|
+
const beforeList = this.#items[index - 1];
|
|
231
|
+
const afterList = this.#items[index + 1];
|
|
232
|
+
// If the format is just after a list, move the format to the end of the list.
|
|
233
|
+
if (beforeList instanceof ListNode)
|
|
234
|
+
return this.withItemReplaced(index - 1, beforeList.withItemAppended(format))?.withoutItemAt(index);
|
|
235
|
+
// If the format is just before a list, move the format to the beginning of the list.
|
|
236
|
+
else if (afterList instanceof ListNode)
|
|
237
|
+
return this.withItemReplaced(index + 1, afterList.withItemPrepended(format))?.withoutItemAt(index);
|
|
238
|
+
// Otherwise, make a new sub list node, add the format to it.
|
|
239
|
+
else
|
|
240
|
+
return this.withItemReplaced(index, new ListNode([format], this.#numbered));
|
|
241
|
+
}
|
|
242
|
+
// If this is a sublist, see if it contains the item, and if so,
|
|
243
|
+
// replace this list with list that has the item indented.
|
|
244
|
+
else if (item instanceof ListNode) {
|
|
245
|
+
const newList = item.withItemIndented(format);
|
|
246
|
+
if (newList !== undefined)
|
|
247
|
+
return this.withItemReplaced(index, newList);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Otherwise, return nothing.
|
|
251
|
+
}
|
|
252
|
+
// Recursively search for the item and unindent it, constructing a new list.
|
|
253
|
+
withItemUnindented(format) {
|
|
254
|
+
// Check each sublist in the list to see if any has the item so we can unindent it into this.
|
|
255
|
+
for (let index = 0; index < this.#items.length; index++) {
|
|
256
|
+
const list = this.#items[index];
|
|
257
|
+
if (list instanceof ListNode) {
|
|
258
|
+
const match = list.#items.indexOf(format);
|
|
259
|
+
// If we found it in this sublist, extract it here!
|
|
260
|
+
if (match >= 0) {
|
|
261
|
+
const before = new ListNode(list.#items.slice(0, match), this.#numbered);
|
|
262
|
+
const after = new ListNode(list.#items.slice(match + 1), this.#numbered);
|
|
263
|
+
let newItems = this.#items.slice(0, index);
|
|
264
|
+
if (before.getLength() > 0)
|
|
265
|
+
newItems.push(before);
|
|
266
|
+
newItems.push(format);
|
|
267
|
+
if (after.getLength() > 0)
|
|
268
|
+
newItems.push(after);
|
|
269
|
+
newItems = newItems.concat(this.#items.slice(index + 1));
|
|
270
|
+
return new ListNode(newItems, this.#numbered);
|
|
271
|
+
}
|
|
272
|
+
// If the sublist doesn't have it, see if it's sublist does
|
|
273
|
+
else {
|
|
274
|
+
const unindented = list.withItemUnindented(format);
|
|
275
|
+
if (unindented !== undefined)
|
|
276
|
+
return this.withItemReplaced(index, unindented);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Otherwise, return nothing, indicating no match.
|
|
281
|
+
}
|
|
282
|
+
withChildReplaced(node, replacement) {
|
|
283
|
+
const index = this.#items.indexOf(node);
|
|
284
|
+
if (index < 0)
|
|
285
|
+
return;
|
|
286
|
+
return new ListNode(replacement === undefined ?
|
|
287
|
+
[...this.#items.slice(0, index), ...this.#items.slice(index + 1)] :
|
|
288
|
+
[...this.#items.slice(0, index), replacement, ...this.#items.slice(index + 1)], this.#numbered);
|
|
289
|
+
}
|
|
290
|
+
withContentInRange(range) {
|
|
291
|
+
const containsStart = this.contains(range.start.node);
|
|
292
|
+
const containsEnd = this.contains(range.end.node);
|
|
293
|
+
if (!containsStart && !containsEnd)
|
|
294
|
+
return this.copy();
|
|
295
|
+
const newItems = [];
|
|
296
|
+
let foundStart = false;
|
|
297
|
+
let foundEnd = false;
|
|
298
|
+
for (let i = 0; i < this.#items.length; i++) {
|
|
299
|
+
if (this.#items[i].contains(range.start.node))
|
|
300
|
+
foundStart = true;
|
|
301
|
+
if (((containsStart && foundStart) || !containsStart) &&
|
|
302
|
+
((containsEnd && !foundEnd) || !containsEnd)) {
|
|
303
|
+
const newItem = this.#items[i].withContentInRange(range);
|
|
304
|
+
if (newItem === undefined)
|
|
305
|
+
return;
|
|
306
|
+
newItems.push(newItem);
|
|
307
|
+
}
|
|
308
|
+
if (this.#items[i].contains(range.end.node))
|
|
309
|
+
foundEnd = true;
|
|
310
|
+
}
|
|
311
|
+
return new ListNode(newItems, this.#numbered);
|
|
312
|
+
}
|
|
313
|
+
withNodeInserted(caret, node) {
|
|
314
|
+
if (!this.contains(caret.node))
|
|
315
|
+
return;
|
|
316
|
+
if (!(node instanceof ListNode))
|
|
317
|
+
return;
|
|
318
|
+
// Find the anchor item.
|
|
319
|
+
const anchor = this.#items.find(i => i.contains(caret.node));
|
|
320
|
+
if (anchor === undefined)
|
|
321
|
+
return;
|
|
322
|
+
const items = node.getItems();
|
|
323
|
+
let lastItem = anchor;
|
|
324
|
+
let newList = this;
|
|
325
|
+
while (items.length > 0) {
|
|
326
|
+
let item = items.shift();
|
|
327
|
+
if (item === undefined)
|
|
328
|
+
return;
|
|
329
|
+
// Insert all of the node's items into this list, merging the first.
|
|
330
|
+
if (lastItem === anchor) {
|
|
331
|
+
const edit = anchor.withNodeInserted(caret, item);
|
|
332
|
+
if (edit === undefined || !(edit.root instanceof FormatNode))
|
|
333
|
+
return;
|
|
334
|
+
item = edit.root;
|
|
335
|
+
newList = newList.withChildReplaced(anchor, item);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
newList = newList.withItemAfter(item, lastItem);
|
|
339
|
+
}
|
|
340
|
+
if (newList === undefined)
|
|
341
|
+
return;
|
|
342
|
+
// Remember where we inserted.
|
|
343
|
+
lastItem = item;
|
|
344
|
+
}
|
|
345
|
+
// The caret should be the last item inserted.
|
|
346
|
+
const newCaret = lastItem instanceof FormatNode ? lastItem.getLastCaret() : lastItem.getLastItem()?.getLastCaret();
|
|
347
|
+
if (newCaret === undefined)
|
|
348
|
+
return;
|
|
349
|
+
return { root: newList, range: { start: newCaret, end: newCaret } };
|
|
350
|
+
}
|
|
351
|
+
withRangeFormatted(range, format) {
|
|
352
|
+
const sortedRange = this.sortRange(range);
|
|
353
|
+
const itemStart = this.getItemContaining(sortedRange.start);
|
|
354
|
+
const itemEnd = this.getItemContaining(sortedRange.end);
|
|
355
|
+
const edit = super.withRangeFormatted(sortedRange, format);
|
|
356
|
+
// If we were deleting and two list items were involved, merge them.
|
|
357
|
+
if (edit && format === undefined && itemStart !== undefined && itemEnd !== undefined && itemStart !== itemEnd) {
|
|
358
|
+
const newList = edit.root;
|
|
359
|
+
const itemIndex = itemEnd - (this.#items.length - newList.#items.length);
|
|
360
|
+
if (itemIndex > 0 && newList.getLength() > 1) {
|
|
361
|
+
// Merge the items, accounting for the new index of the last item.
|
|
362
|
+
const mergedEdit = newList.withItemMergedBackwards(itemIndex);
|
|
363
|
+
if (mergedEdit === undefined)
|
|
364
|
+
return;
|
|
365
|
+
return { root: mergedEdit[0], range: { start: mergedEdit[1], end: mergedEdit[1] } };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return edit;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import FormatNode from "./FormatNode";
|
|
3
|
+
import ListNode from "./ListNode";
|
|
4
|
+
import TextNode from "./TextNode";
|
|
5
|
+
const oneText = new TextNode("one");
|
|
6
|
+
const oneFormat = new FormatNode("", [oneText]);
|
|
7
|
+
const twoText = new TextNode("two");
|
|
8
|
+
const twoFormat = new FormatNode("", [twoText]);
|
|
9
|
+
const threeText = new TextNode("three");
|
|
10
|
+
const threeFormat = new FormatNode("", [threeText]);
|
|
11
|
+
const fourText = new TextNode("four");
|
|
12
|
+
const fourFormat = new FormatNode("", [fourText]);
|
|
13
|
+
const fiveText = new TextNode("five");
|
|
14
|
+
const fiveFormat = new FormatNode("", [fiveText]);
|
|
15
|
+
const shortList = new ListNode([oneFormat, twoFormat, threeFormat], false);
|
|
16
|
+
const list = new ListNode([oneFormat, new ListNode([twoFormat, threeFormat, fourFormat], false), fiveFormat], false);
|
|
17
|
+
test("Insert items", () => {
|
|
18
|
+
expect(shortList.withItemAppended(fourFormat)?.toBookdown()).toBe(`* one\n* two\n* three\n* four`);
|
|
19
|
+
expect(shortList.withItemPrepended(fourFormat)?.toBookdown()).toBe(`* four\n* one\n* two\n* three`);
|
|
20
|
+
expect(shortList.withItemAt(fourFormat, 9)).toBeUndefined();
|
|
21
|
+
expect(shortList.withItemAt(fourFormat, 1)?.toBookdown()).toBe(`* one\n* four\n* two\n* three`);
|
|
22
|
+
expect(shortList.withItemAfter(fourFormat, oneFormat)?.toBookdown()).toBe(`* one\n* four\n* two\n* three`);
|
|
23
|
+
expect(shortList.withItemBefore(fourFormat, twoFormat)?.toBookdown()).toBe(`* one\n* four\n* two\n* three`);
|
|
24
|
+
expect(shortList.withListAppended(new ListNode([fourFormat, fiveFormat], true))?.toBookdown()).toBe(`* one\n* two\n* three\n* four\n* five`);
|
|
25
|
+
});
|
|
26
|
+
test("Remove items", () => {
|
|
27
|
+
expect(shortList.withItemReplaced(1, fourFormat)?.toBookdown()).toBe(`* one\n* four\n* three`);
|
|
28
|
+
expect(shortList.withoutItemAt(0)?.toBookdown()).toBe(`* two\n* three`);
|
|
29
|
+
});
|
|
30
|
+
test("Split", () => {
|
|
31
|
+
expect(shortList.withItemSplit({ node: threeText, index: 2 })?.toBookdown())
|
|
32
|
+
.toBe("* one\n* two\n* th\n* ree");
|
|
33
|
+
});
|
|
34
|
+
test("Merge", () => {
|
|
35
|
+
const merged = shortList.withItemMergedBackwards(1);
|
|
36
|
+
expect(merged).not.toBeUndefined();
|
|
37
|
+
if (merged !== undefined)
|
|
38
|
+
expect(merged[0].toBookdown()).toBe("* onetwo\n* three");
|
|
39
|
+
});
|
|
40
|
+
test("Indent", () => {
|
|
41
|
+
expect(list.withItemIndented(oneFormat)?.toBookdown()).toBe(`** one\n** two\n** three\n** four\n* five`);
|
|
42
|
+
expect(list.withItemIndented(twoFormat)?.toBookdown()).toBe(`* one\n*** two\n** three\n** four\n* five`);
|
|
43
|
+
expect(list.withItemIndented(fiveFormat)?.toBookdown()).toBe(`* one\n** two\n** three\n** four\n** five`);
|
|
44
|
+
});
|
|
45
|
+
test("Unindent", () => {
|
|
46
|
+
expect(list.withItemUnindented(twoFormat)?.toBookdown()).toBe(`* one\n* two\n** three\n** four\n* five`);
|
|
47
|
+
expect(list.withItemUnindented(oneFormat)?.toBookdown()).toBeUndefined();
|
|
48
|
+
expect(list.withItemUnindented(threeFormat)?.toBookdown()).toBe(`* one\n** two\n* three\n** four\n* five`);
|
|
49
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CaretRange } from "./Caret";
|
|
2
|
+
import Node from "./Node";
|
|
3
|
+
import type TextNode from "./TextNode";
|
|
4
|
+
export default abstract class MetadataNode<MetaType> extends Node {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(text: TextNode, meta: MetaType);
|
|
7
|
+
getMeta(): MetaType;
|
|
8
|
+
getText(): TextNode;
|
|
9
|
+
abstract withMeta(meta: MetaType): MetadataNode<MetaType>;
|
|
10
|
+
abstract withText(text: TextNode): MetadataNode<MetaType>;
|
|
11
|
+
abstract toText(): string;
|
|
12
|
+
abstract toBookdown(): string;
|
|
13
|
+
getChildren(): TextNode[];
|
|
14
|
+
getParentOf(node: Node): Node | undefined;
|
|
15
|
+
withChildReplaced(node: TextNode, replacement: TextNode | undefined): this | undefined;
|
|
16
|
+
withContentInRange(range: CaretRange): this | undefined;
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Node from "./Node";
|
|
2
|
+
export default class MetadataNode extends Node {
|
|
3
|
+
#text;
|
|
4
|
+
#meta;
|
|
5
|
+
constructor(text, meta) {
|
|
6
|
+
super();
|
|
7
|
+
this.#text = text;
|
|
8
|
+
this.#meta = meta;
|
|
9
|
+
}
|
|
10
|
+
getMeta() { return this.#meta; }
|
|
11
|
+
getText() { return this.#text; }
|
|
12
|
+
getChildren() { return [this.#text]; }
|
|
13
|
+
getParentOf(node) { return this.#text === node ? this : undefined; }
|
|
14
|
+
withChildReplaced(node, replacement) {
|
|
15
|
+
if (this.#text === node && replacement !== undefined)
|
|
16
|
+
return this.withText(replacement);
|
|
17
|
+
}
|
|
18
|
+
withContentInRange(range) {
|
|
19
|
+
const selectedText = this.#text.withContentInRange(range);
|
|
20
|
+
if (selectedText === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
return this.withText(selectedText);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type Caret from "./Caret";
|
|
2
|
+
import type { CaretRange } from "./Caret";
|
|
3
|
+
import type Edit from "./Edit";
|
|
4
|
+
export default abstract class Node {
|
|
5
|
+
readonly nodeID: number;
|
|
6
|
+
constructor();
|
|
7
|
+
abstract toText(): string;
|
|
8
|
+
abstract toBookdown(): string;
|
|
9
|
+
abstract toHTML(): string;
|
|
10
|
+
abstract getType(): string;
|
|
11
|
+
abstract copy(): this;
|
|
12
|
+
abstract withChildReplaced(node: Node, replacement: Node | undefined): this | undefined;
|
|
13
|
+
abstract getChildren(): Node[];
|
|
14
|
+
abstract getParentOf(node: Node): Node | undefined;
|
|
15
|
+
getID(): number;
|
|
16
|
+
traverse(fn: (node: Node, parents: Node[]) => void, parents?: Node[]): void;
|
|
17
|
+
traverseChildren(fn: (node: Node, parents: Node[]) => void, parents: Node[]): void;
|
|
18
|
+
getNodes(): Node[];
|
|
19
|
+
sortRange(range: CaretRange): CaretRange;
|
|
20
|
+
getNode(id: number): Node | undefined;
|
|
21
|
+
getIndexOf(node: Node): number | undefined;
|
|
22
|
+
contains(node: Node): boolean;
|
|
23
|
+
getParentsOf(node: Node): Node[] | undefined;
|
|
24
|
+
getParent(root: Node): Node | undefined;
|
|
25
|
+
hasAncestor(root: Node, node: Node): boolean;
|
|
26
|
+
getClosestParentOfType<T extends Node>(root: Node, type: Function): T | undefined;
|
|
27
|
+
isInside(root: Node, type: Function): boolean;
|
|
28
|
+
getClosestParentMatching(root: Node, match: (node: Node) => boolean): Node | undefined;
|
|
29
|
+
getFarthestParentMatching(root: Node, match: (node: Node) => boolean): Node | undefined;
|
|
30
|
+
getAncestors(root: Node): Node[];
|
|
31
|
+
getCommonAncestor(root: Node, node: Node): Node | undefined;
|
|
32
|
+
withNodeReplaced(node: Node, replacement: Node | undefined): this | undefined;
|
|
33
|
+
abstract withContentInRange(range: CaretRange): this | undefined;
|
|
34
|
+
withNodeInserted(caret: Caret, node: Node): Edit;
|
|
35
|
+
copyRange(range: CaretRange): Node | undefined;
|
|
36
|
+
}
|