@blocknote/core 0.38.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BlockNoteSchema-DmZ6UQfY.cjs +11 -0
- package/dist/BlockNoteSchema-DmZ6UQfY.cjs.map +1 -0
- package/dist/BlockNoteSchema-oR047ACf.js +4275 -0
- package/dist/BlockNoteSchema-oR047ACf.js.map +1 -0
- package/dist/blocknote.cjs +4 -12
- package/dist/blocknote.cjs.map +1 -1
- package/dist/blocknote.js +3296 -7187
- package/dist/blocknote.js.map +1 -1
- package/dist/blocks.cjs +2 -0
- package/dist/blocks.cjs.map +1 -0
- package/dist/blocks.js +71 -0
- package/dist/blocks.js.map +1 -0
- package/dist/style.css +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +19 -16
- package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +1 -1
- package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +2 -2
- package/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +34 -25
- package/src/api/blockManipulation/setupTestEnv.ts +0 -1
- package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +4 -8
- package/src/api/clipboard/toClipboard/copyExtension.ts +1 -1
- package/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +128 -28
- package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +101 -41
- package/src/api/pmUtil.ts +1 -1
- package/src/api/positionMapping.test.ts +58 -15
- package/src/api/positionMapping.ts +2 -4
- package/src/blocks/Audio/block.ts +174 -0
- package/src/blocks/BlockNoteSchema.ts +59 -0
- package/src/blocks/Code/block.ts +303 -0
- package/src/blocks/Code/shiki.ts +73 -0
- package/src/blocks/File/block.ts +98 -0
- package/src/blocks/{FileBlockContent → File}/helpers/render/createAddFileButton.ts +5 -2
- package/src/blocks/{FileBlockContent → File}/helpers/render/createFileBlockWrapper.ts +15 -6
- package/src/blocks/{FileBlockContent → File}/helpers/render/createFileNameWithIcon.ts +15 -2
- package/src/blocks/{FileBlockContent → File}/helpers/render/createResizableFileBlockWrapper.ts +21 -2
- package/src/blocks/Heading/block.ts +138 -0
- package/src/blocks/Image/block.ts +190 -0
- package/src/blocks/ListItem/BulletListItem/block.ts +116 -0
- package/src/blocks/ListItem/CheckListItem/block.ts +175 -0
- package/src/blocks/ListItem/NumberedListItem/IndexingPlugin.ts +173 -0
- package/src/blocks/ListItem/NumberedListItem/block.ts +133 -0
- package/src/blocks/ListItem/ToggleListItem/block.ts +78 -0
- package/src/blocks/PageBreak/block.ts +72 -0
- package/src/blocks/{PageBreakBlockContent → PageBreak}/getPageBreakSlashMenuItems.ts +9 -7
- package/src/blocks/Paragraph/block.ts +80 -0
- package/src/blocks/Quote/block.ts +90 -0
- package/src/blocks/{TableBlockContent/TableBlockContent.ts → Table/block.ts} +169 -51
- package/src/blocks/ToggleWrapper/createToggleWrapper.ts +1 -1
- package/src/blocks/Video/block.ts +143 -0
- package/src/blocks/defaultBlockHelpers.ts +2 -2
- package/src/blocks/defaultBlockTypeGuards.ts +143 -174
- package/src/blocks/defaultBlocks.ts +107 -35
- package/src/blocks/defaultProps.ts +145 -4
- package/src/blocks/index.ts +26 -0
- package/src/blocks/utils/listItemEnterHandler.ts +42 -0
- package/src/editor/Block.css +54 -18
- package/src/editor/BlockNoteEditor.ts +251 -210
- package/src/editor/BlockNoteExtension.ts +92 -0
- package/src/editor/BlockNoteExtensions.ts +18 -17
- package/src/editor/defaultColors.ts +2 -2
- package/src/exporter/Exporter.ts +1 -1
- package/src/exporter/mapping.ts +1 -1
- package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +3 -20
- package/src/extensions/BackgroundColor/BackgroundColorMark.ts +6 -8
- package/src/extensions/BlockChange/BlockChangePlugin.ts +2 -1
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json +2 -2
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json +2 -2
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html +1 -1
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html +1 -1
- package/src/extensions/Collaboration/schemaMigration/SchemaMigrationPlugin.ts +52 -0
- package/src/extensions/Collaboration/schemaMigration/migrationRules/index.ts +4 -0
- package/src/extensions/Collaboration/schemaMigration/migrationRules/migrationRule.ts +4 -0
- package/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts +78 -0
- package/src/extensions/Comments/CommentsPlugin.ts +1 -1
- package/src/extensions/FilePanel/FilePanelPlugin.ts +5 -10
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +1 -1
- package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +4 -3
- package/src/extensions/Placeholder/PlaceholderPlugin.ts +2 -2
- package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +1 -23
- package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +0 -5
- package/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts +6 -2
- package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +24 -17
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +2 -2
- package/src/extensions/TextAlignment/TextAlignmentExtension.ts +5 -11
- package/src/extensions/TextColor/TextColorExtension.ts +3 -17
- package/src/extensions/TextColor/TextColorMark.ts +4 -9
- package/src/extensions/UniqueID/UniqueID.ts +6 -13
- package/src/index.ts +2 -28
- package/src/schema/blocks/createSpec.ts +342 -169
- package/src/schema/blocks/internal.ts +77 -138
- package/src/schema/blocks/types.ts +264 -94
- package/src/schema/index.ts +1 -0
- package/src/schema/inlineContent/createSpec.ts +99 -21
- package/src/schema/inlineContent/internal.ts +16 -7
- package/src/schema/inlineContent/types.ts +24 -2
- package/src/schema/propTypes.ts +15 -9
- package/src/schema/schema.ts +209 -0
- package/src/schema/styles/createSpec.ts +79 -31
- package/src/schema/styles/internal.ts +61 -2
- package/src/schema/styles/types.ts +17 -3
- package/src/util/topo-sort.test.ts +125 -0
- package/src/util/topo-sort.ts +160 -0
- package/types/src/api/blockManipulation/commands/splitBlock/splitBlock.d.ts +2 -1
- package/types/src/api/blockManipulation/selections/selection.d.ts +1 -1
- package/types/src/api/blockManipulation/setupTestEnv.d.ts +29 -543
- package/types/src/api/exporters/html/util/serializeBlocksExternalHTML.d.ts +1 -1
- package/types/src/api/exporters/html/util/serializeBlocksInternalHTML.d.ts +1 -1
- package/types/src/api/pmUtil.d.ts +1 -1
- package/types/src/blocks/Audio/block.d.ts +58 -0
- package/types/src/blocks/BlockNoteSchema.d.ts +18 -0
- package/types/src/blocks/{CodeBlockContent/CodeBlockContent.d.ts → Code/block.d.ts} +25 -26
- package/types/src/blocks/Code/shiki.d.ts +4 -0
- package/types/src/blocks/File/block.d.ts +37 -0
- package/types/src/blocks/File/helpers/render/createAddFileButton.d.ts +6 -0
- package/types/src/blocks/File/helpers/render/createFileBlockWrapper.d.ts +25 -0
- package/types/src/blocks/{FileBlockContent → File}/helpers/render/createFileNameWithIcon.d.ts +6 -2
- package/types/src/blocks/File/helpers/render/createResizableFileBlockWrapper.d.ts +31 -0
- package/types/src/blocks/Heading/block.d.ts +71 -0
- package/types/src/blocks/Image/block.d.ts +102 -0
- package/types/src/blocks/ListItem/BulletListItem/block.d.ts +25 -0
- package/types/src/blocks/ListItem/CheckListItem/block.d.ts +33 -0
- package/types/src/blocks/ListItem/NumberedListItem/IndexingPlugin.d.ts +8 -0
- package/types/src/blocks/ListItem/NumberedListItem/block.d.ts +33 -0
- package/types/src/blocks/ListItem/ToggleListItem/block.d.ts +25 -0
- package/types/src/blocks/PageBreak/block.d.ts +11 -0
- package/types/src/blocks/{PageBreakBlockContent → PageBreak}/getPageBreakSlashMenuItems.d.ts +4 -2
- package/types/src/blocks/Paragraph/block.d.ts +25 -0
- package/types/src/blocks/Quote/block.d.ts +17 -0
- package/types/src/blocks/Table/block.d.ts +21 -0
- package/types/src/blocks/Video/block.d.ts +67 -0
- package/types/src/blocks/defaultBlockHelpers.d.ts +1 -1
- package/types/src/blocks/defaultBlockTypeGuards.d.ts +15 -36
- package/types/src/blocks/defaultBlocks.d.ts +221 -1060
- package/types/src/blocks/defaultProps.d.ts +17 -1
- package/types/src/blocks/index.d.ts +24 -0
- package/types/src/blocks/utils/listItemEnterHandler.d.ts +2 -0
- package/types/src/editor/BlockNoteEditor.d.ts +33 -66
- package/types/src/editor/BlockNoteExtension.d.ts +67 -0
- package/types/src/editor/BlockNoteExtensions.d.ts +1 -1
- package/types/src/editor/defaultColors.d.ts +8 -76
- package/types/src/exporter/Exporter.d.ts +1 -1
- package/types/src/exporter/mapping.d.ts +1 -1
- package/types/src/extensions/BackgroundColor/BackgroundColorMark.d.ts +4 -1
- package/types/src/extensions/Collaboration/schemaMigration/SchemaMigrationPlugin.d.ts +7 -0
- package/types/src/extensions/Collaboration/schemaMigration/migrationRules/index.d.ts +3 -0
- package/types/src/extensions/Collaboration/schemaMigration/migrationRules/migrationRule.d.ts +3 -0
- package/types/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.d.ts +2 -0
- package/types/src/extensions/Comments/CommentsPlugin.d.ts +1 -1
- package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +4 -4
- package/types/src/extensions/TextColor/TextColorMark.d.ts +4 -1
- package/types/src/index.d.ts +2 -25
- package/types/src/schema/blocks/createSpec.d.ts +16 -36
- package/types/src/schema/blocks/internal.d.ts +11 -33
- package/types/src/schema/blocks/types.d.ts +181 -57
- package/types/src/schema/index.d.ts +1 -0
- package/types/src/schema/inlineContent/createSpec.d.ts +36 -2
- package/types/src/schema/inlineContent/internal.d.ts +7 -15
- package/types/src/schema/inlineContent/types.d.ts +15 -1
- package/types/src/schema/propTypes.d.ts +4 -4
- package/types/src/schema/schema.d.ts +40 -0
- package/types/src/schema/styles/createSpec.d.ts +6 -4
- package/types/src/schema/styles/internal.d.ts +6 -3
- package/types/src/schema/styles/types.d.ts +11 -2
- package/types/src/util/topo-sort.d.ts +18 -0
- package/types/src/util/topo-sort.test.d.ts +1 -0
- package/src/blocks/AudioBlockContent/AudioBlockContent.ts +0 -144
- package/src/blocks/CodeBlockContent/CodeBlockContent.ts +0 -445
- package/src/blocks/FileBlockContent/FileBlockContent.ts +0 -100
- package/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +0 -159
- package/src/blocks/ImageBlockContent/ImageBlockContent.ts +0 -159
- package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +0 -134
- package/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +0 -299
- package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts +0 -86
- package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +0 -172
- package/src/blocks/ListItemBlockContent/ToggleListItemBlockContent/ToggleListItemBlockContent.ts +0 -104
- package/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts +0 -49
- package/src/blocks/PageBreakBlockContent/schema.ts +0 -40
- package/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +0 -78
- package/src/blocks/QuoteBlockContent/QuoteBlockContent.ts +0 -121
- package/src/blocks/VideoBlockContent/VideoBlockContent.ts +0 -158
- package/src/editor/BlockNoteSchema.ts +0 -107
- package/src/editor/BlockNoteTipTapEditor.ts +0 -335
- package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +0 -99
- package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +0 -90
- package/types/src/blocks/FileBlockContent/helpers/render/createAddFileButton.d.ts +0 -6
- package/types/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.d.ts +0 -9
- package/types/src/blocks/FileBlockContent/helpers/render/createResizableFileBlockWrapper.d.ts +0 -9
- package/types/src/blocks/HeadingBlockContent/HeadingBlockContent.d.ts +0 -67
- package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +0 -131
- package/types/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +0 -46
- package/types/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.d.ts +0 -55
- package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.d.ts +0 -2
- package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +0 -58
- package/types/src/blocks/ListItemBlockContent/ToggleListItemBlockContent/ToggleListItemBlockContent.d.ts +0 -46
- package/types/src/blocks/PageBreakBlockContent/PageBreakBlockContent.d.ts +0 -31
- package/types/src/blocks/PageBreakBlockContent/schema.d.ts +0 -86
- package/types/src/blocks/ParagraphBlockContent/ParagraphBlockContent.d.ts +0 -52
- package/types/src/blocks/QuoteBlockContent/QuoteBlockContent.d.ts +0 -52
- package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +0 -39
- package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +0 -131
- package/types/src/editor/BlockNoteSchema.d.ts +0 -34
- package/types/src/editor/BlockNoteTipTapEditor.d.ts +0 -43
- /package/src/blocks/{AudioBlockContent → Audio}/parseAudioElement.ts +0 -0
- /package/src/blocks/{FileBlockContent → File}/helpers/parse/parseEmbedElement.ts +0 -0
- /package/src/blocks/{FileBlockContent → File}/helpers/parse/parseFigureElement.ts +0 -0
- /package/src/blocks/{FileBlockContent → File}/helpers/toExternalHTML/createFigureWithCaption.ts +0 -0
- /package/src/blocks/{FileBlockContent → File}/helpers/toExternalHTML/createLinkWithCaption.ts +0 -0
- /package/src/blocks/{FileBlockContent → File/helpers}/uploadToTmpFilesDotOrg_DEV_ONLY.ts +0 -0
- /package/src/blocks/{ImageBlockContent → Image}/parseImageElement.ts +0 -0
- /package/src/blocks/{ListItemBlockContent → ListItem}/ListItemKeyboardShortcuts.ts +0 -0
- /package/src/blocks/{ListItemBlockContent → ListItem}/getListItemContent.ts +0 -0
- /package/src/blocks/{TableBlockContent → Table}/TableExtension.ts +0 -0
- /package/src/blocks/{VideoBlockContent → Video}/parseVideoElement.ts +0 -0
- /package/types/src/blocks/{AudioBlockContent → Audio}/parseAudioElement.d.ts +0 -0
- /package/types/src/blocks/{FileBlockContent → File}/helpers/parse/parseEmbedElement.d.ts +0 -0
- /package/types/src/blocks/{FileBlockContent → File}/helpers/parse/parseFigureElement.d.ts +0 -0
- /package/types/src/blocks/{FileBlockContent → File}/helpers/toExternalHTML/createFigureWithCaption.d.ts +0 -0
- /package/types/src/blocks/{FileBlockContent → File}/helpers/toExternalHTML/createLinkWithCaption.d.ts +0 -0
- /package/types/src/blocks/{FileBlockContent → File/helpers}/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +0 -0
- /package/types/src/blocks/{ImageBlockContent → Image}/parseImageElement.d.ts +0 -0
- /package/types/src/blocks/{ListItemBlockContent → ListItem}/ListItemKeyboardShortcuts.d.ts +0 -0
- /package/types/src/blocks/{ListItemBlockContent → ListItem}/getListItemContent.d.ts +0 -0
- /package/types/src/blocks/{TableBlockContent → Table}/TableExtension.d.ts +0 -0
- /package/types/src/blocks/{VideoBlockContent → Video}/parseVideoElement.d.ts +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Node } from "@tiptap/core";
|
|
2
2
|
import { PropSchema, Props } from "../propTypes.js";
|
|
3
3
|
import { StyleSchema, Styles } from "../styles/types.js";
|
|
4
|
+
import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
5
|
+
import { ViewMutationRecord } from "prosemirror-view";
|
|
4
6
|
|
|
5
7
|
export type CustomInlineContentConfig = {
|
|
6
8
|
type: string;
|
|
7
9
|
content: "styled" | "none"; // | "plain"
|
|
8
|
-
draggable?: boolean;
|
|
9
10
|
readonly propSchema: PropSchema;
|
|
10
|
-
// content: "inline" | "none" | "table";
|
|
11
11
|
};
|
|
12
12
|
// InlineContentConfig contains the "schema" info about an InlineContent type
|
|
13
13
|
// i.e. what props it supports, what content it supports, etc.
|
|
@@ -20,7 +20,29 @@ export type InlineContentImplementation<T extends InlineContentConfig> =
|
|
|
20
20
|
T extends "link" | "text"
|
|
21
21
|
? undefined
|
|
22
22
|
: {
|
|
23
|
+
meta?: {
|
|
24
|
+
draggable?: boolean;
|
|
25
|
+
};
|
|
23
26
|
node: Node;
|
|
27
|
+
toExternalHTML?: (
|
|
28
|
+
inlineContent: any,
|
|
29
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
30
|
+
) =>
|
|
31
|
+
| {
|
|
32
|
+
dom: HTMLElement | DocumentFragment;
|
|
33
|
+
contentDOM?: HTMLElement;
|
|
34
|
+
}
|
|
35
|
+
| undefined;
|
|
36
|
+
render: (
|
|
37
|
+
inlineContent: any,
|
|
38
|
+
updateInlineContent: (update: any) => void,
|
|
39
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
40
|
+
) => {
|
|
41
|
+
dom: HTMLElement | DocumentFragment;
|
|
42
|
+
contentDOM?: HTMLElement;
|
|
43
|
+
ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
|
|
44
|
+
destroy?: () => void;
|
|
45
|
+
};
|
|
24
46
|
};
|
|
25
47
|
|
|
26
48
|
export type InlineContentSchemaWithInlineContent<
|
package/src/schema/propTypes.ts
CHANGED
|
@@ -34,17 +34,23 @@ export type Props<PSchema extends PropSchema> = {
|
|
|
34
34
|
// for required props, get type from type of "default" value,
|
|
35
35
|
// and if values are specified, get type from values
|
|
36
36
|
[PName in keyof PSchema]: (
|
|
37
|
-
PSchema[PName] extends
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
NonNullable<PSchema[PName]> extends
|
|
38
|
+
| { default: boolean }
|
|
39
|
+
| { type: "boolean" }
|
|
40
|
+
? NonNullable<PSchema[PName]>["values"] extends readonly boolean[]
|
|
41
|
+
? NonNullable<PSchema[PName]>["values"][number]
|
|
40
42
|
: boolean
|
|
41
|
-
: PSchema[PName] extends
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
: NonNullable<PSchema[PName]> extends
|
|
44
|
+
| { default: number }
|
|
45
|
+
| { type: "number" }
|
|
46
|
+
? NonNullable<PSchema[PName]>["values"] extends readonly number[]
|
|
47
|
+
? NonNullable<PSchema[PName]>["values"][number]
|
|
44
48
|
: number
|
|
45
|
-
: PSchema[PName] extends
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
: NonNullable<PSchema[PName]> extends
|
|
50
|
+
| { default: string }
|
|
51
|
+
| { type: "string" }
|
|
52
|
+
? NonNullable<PSchema[PName]>["values"] extends readonly string[]
|
|
53
|
+
? NonNullable<PSchema[PName]>["values"][number]
|
|
48
54
|
: string
|
|
49
55
|
: never
|
|
50
56
|
) extends infer T
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
|
|
2
|
+
import { createDependencyGraph, toposortReverse } from "../util/topo-sort.js";
|
|
3
|
+
import {
|
|
4
|
+
BlockNoDefaults,
|
|
5
|
+
BlockSchema,
|
|
6
|
+
BlockSpecs,
|
|
7
|
+
InlineContentConfig,
|
|
8
|
+
InlineContentSchema,
|
|
9
|
+
InlineContentSpec,
|
|
10
|
+
InlineContentSpecs,
|
|
11
|
+
LooseBlockSpec,
|
|
12
|
+
PartialBlockNoDefaults,
|
|
13
|
+
StyleSchema,
|
|
14
|
+
StyleSpecs,
|
|
15
|
+
addNodeAndExtensionsToSpec,
|
|
16
|
+
getInlineContentSchemaFromSpecs,
|
|
17
|
+
getStyleSchemaFromSpecs,
|
|
18
|
+
} from "./index.js";
|
|
19
|
+
|
|
20
|
+
function removeUndefined<T extends Record<string, any> | undefined>(obj: T): T {
|
|
21
|
+
if (!obj) {
|
|
22
|
+
return obj;
|
|
23
|
+
}
|
|
24
|
+
return Object.fromEntries(
|
|
25
|
+
Object.entries(obj).filter(([, value]) => value !== undefined),
|
|
26
|
+
) as T;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class CustomBlockNoteSchema<
|
|
30
|
+
BSchema extends BlockSchema,
|
|
31
|
+
ISchema extends InlineContentSchema,
|
|
32
|
+
SSchema extends StyleSchema,
|
|
33
|
+
> {
|
|
34
|
+
// Helper so that you can use typeof schema.BlockNoteEditor
|
|
35
|
+
public readonly BlockNoteEditor: BlockNoteEditor<BSchema, ISchema, SSchema> =
|
|
36
|
+
"only for types" as any;
|
|
37
|
+
|
|
38
|
+
public readonly Block: BlockNoDefaults<BSchema, ISchema, SSchema> =
|
|
39
|
+
"only for types" as any;
|
|
40
|
+
|
|
41
|
+
public readonly PartialBlock: PartialBlockNoDefaults<
|
|
42
|
+
BSchema,
|
|
43
|
+
ISchema,
|
|
44
|
+
SSchema
|
|
45
|
+
> = "only for types" as any;
|
|
46
|
+
|
|
47
|
+
public inlineContentSpecs: InlineContentSpecs;
|
|
48
|
+
public styleSpecs: StyleSpecs;
|
|
49
|
+
public blockSpecs: {
|
|
50
|
+
[K in keyof BSchema]: K extends string
|
|
51
|
+
? LooseBlockSpec<K, BSchema[K]["propSchema"], BSchema[K]["content"]>
|
|
52
|
+
: never;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
public blockSchema: BSchema;
|
|
56
|
+
public inlineContentSchema: ISchema;
|
|
57
|
+
public styleSchema: SSchema;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
private opts: {
|
|
61
|
+
blockSpecs: BlockSpecs;
|
|
62
|
+
inlineContentSpecs: InlineContentSpecs;
|
|
63
|
+
styleSpecs: StyleSpecs;
|
|
64
|
+
},
|
|
65
|
+
) {
|
|
66
|
+
const {
|
|
67
|
+
blockSpecs,
|
|
68
|
+
inlineContentSpecs,
|
|
69
|
+
styleSpecs,
|
|
70
|
+
blockSchema,
|
|
71
|
+
inlineContentSchema,
|
|
72
|
+
styleSchema,
|
|
73
|
+
} = this.init();
|
|
74
|
+
this.blockSpecs = blockSpecs;
|
|
75
|
+
this.styleSpecs = styleSpecs;
|
|
76
|
+
this.styleSchema = styleSchema;
|
|
77
|
+
this.inlineContentSpecs = inlineContentSpecs;
|
|
78
|
+
this.blockSchema = blockSchema;
|
|
79
|
+
this.inlineContentSchema = inlineContentSchema;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private init() {
|
|
83
|
+
const dag = createDependencyGraph();
|
|
84
|
+
const defaultSet = new Set<string>();
|
|
85
|
+
dag.set("default", defaultSet);
|
|
86
|
+
|
|
87
|
+
for (const [key, specDef] of Object.entries(this.opts.blockSpecs)) {
|
|
88
|
+
if (specDef.implementation.runsBefore) {
|
|
89
|
+
dag.set(key, new Set(specDef.implementation.runsBefore));
|
|
90
|
+
} else {
|
|
91
|
+
defaultSet.add(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const sortedSpecs = toposortReverse(dag);
|
|
95
|
+
const defaultIndex = sortedSpecs.findIndex((set) => set.has("default"));
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The priority of a block is described relative to the "default" block (an arbitrary block which can be used as the reference)
|
|
99
|
+
*
|
|
100
|
+
* Since blocks are topologically sorted, we can see what their relative position is to the "default" block
|
|
101
|
+
* Each layer away from the default block is 10 priority points (arbitrarily chosen)
|
|
102
|
+
* The default block is fixed at 101 (1 point higher than any tiptap extension, giving priority to custom blocks than any defaults)
|
|
103
|
+
*
|
|
104
|
+
* This is a bit of a hack, but it's a simple way to ensure that custom blocks are always rendered with higher priority than default blocks
|
|
105
|
+
* and that custom blocks are rendered in the order they are defined in the schema
|
|
106
|
+
*/
|
|
107
|
+
const getPriority = (key: string) => {
|
|
108
|
+
const index = sortedSpecs.findIndex((set) => set.has(key));
|
|
109
|
+
// the default index should map to 101
|
|
110
|
+
// one before the default index is 91
|
|
111
|
+
// one after is 111
|
|
112
|
+
return 91 + (index + defaultIndex) * 10;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const blockSpecs = Object.fromEntries(
|
|
116
|
+
Object.entries(this.opts.blockSpecs).map(([key, blockSpec]) => {
|
|
117
|
+
return [
|
|
118
|
+
key,
|
|
119
|
+
addNodeAndExtensionsToSpec(
|
|
120
|
+
blockSpec.config,
|
|
121
|
+
blockSpec.implementation,
|
|
122
|
+
blockSpec.extensions,
|
|
123
|
+
getPriority(key),
|
|
124
|
+
),
|
|
125
|
+
];
|
|
126
|
+
}),
|
|
127
|
+
) as {
|
|
128
|
+
[K in keyof BSchema]: K extends string
|
|
129
|
+
? LooseBlockSpec<K, BSchema[K]["propSchema"], BSchema[K]["content"]>
|
|
130
|
+
: never;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
blockSpecs,
|
|
135
|
+
blockSchema: Object.fromEntries(
|
|
136
|
+
Object.entries(blockSpecs).map(([key, blockDef]) => {
|
|
137
|
+
return [key, blockDef.config];
|
|
138
|
+
}),
|
|
139
|
+
) as any,
|
|
140
|
+
inlineContentSpecs: removeUndefined(this.opts.inlineContentSpecs),
|
|
141
|
+
styleSpecs: removeUndefined(this.opts.styleSpecs),
|
|
142
|
+
inlineContentSchema: getInlineContentSchemaFromSpecs(
|
|
143
|
+
this.opts.inlineContentSpecs,
|
|
144
|
+
) as any,
|
|
145
|
+
styleSchema: getStyleSchemaFromSpecs(this.opts.styleSpecs) as any,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Adds additional block specs to the current schema in a builder pattern.
|
|
151
|
+
* This method allows extending the schema after it has been created.
|
|
152
|
+
*
|
|
153
|
+
* @param additionalBlockSpecs - Additional block specs to add to the schema
|
|
154
|
+
* @returns The current schema instance for chaining
|
|
155
|
+
*/
|
|
156
|
+
public extend<
|
|
157
|
+
AdditionalBlockSpecs extends BlockSpecs = Record<string, never>,
|
|
158
|
+
AdditionalInlineContentSpecs extends Record<
|
|
159
|
+
string,
|
|
160
|
+
InlineContentSpec<InlineContentConfig>
|
|
161
|
+
> = Record<string, never>,
|
|
162
|
+
AdditionalStyleSpecs extends StyleSpecs = Record<string, never>,
|
|
163
|
+
>(opts: {
|
|
164
|
+
blockSpecs?: AdditionalBlockSpecs;
|
|
165
|
+
inlineContentSpecs?: AdditionalInlineContentSpecs;
|
|
166
|
+
styleSpecs?: AdditionalStyleSpecs;
|
|
167
|
+
}): CustomBlockNoteSchema<
|
|
168
|
+
AdditionalBlockSpecs extends undefined | Record<string, never>
|
|
169
|
+
? BSchema
|
|
170
|
+
: BSchema & {
|
|
171
|
+
[K in keyof AdditionalBlockSpecs]: K extends string
|
|
172
|
+
? AdditionalBlockSpecs[K]["config"]
|
|
173
|
+
: never;
|
|
174
|
+
},
|
|
175
|
+
AdditionalInlineContentSpecs extends undefined | Record<string, never>
|
|
176
|
+
? ISchema
|
|
177
|
+
: ISchema & {
|
|
178
|
+
[K in keyof AdditionalInlineContentSpecs]: AdditionalInlineContentSpecs[K]["config"];
|
|
179
|
+
},
|
|
180
|
+
AdditionalStyleSpecs extends undefined | Record<string, never>
|
|
181
|
+
? SSchema
|
|
182
|
+
: SSchema & {
|
|
183
|
+
[K in keyof AdditionalStyleSpecs]: AdditionalStyleSpecs[K]["config"];
|
|
184
|
+
}
|
|
185
|
+
> {
|
|
186
|
+
// Merge the new specs with existing ones
|
|
187
|
+
Object.assign(this.opts.blockSpecs, opts.blockSpecs);
|
|
188
|
+
Object.assign(this.opts.inlineContentSpecs, opts.inlineContentSpecs);
|
|
189
|
+
Object.assign(this.opts.styleSpecs, opts.styleSpecs);
|
|
190
|
+
|
|
191
|
+
// Reinitialize the block specs with the merged specs
|
|
192
|
+
const {
|
|
193
|
+
blockSpecs,
|
|
194
|
+
inlineContentSpecs,
|
|
195
|
+
styleSpecs,
|
|
196
|
+
blockSchema,
|
|
197
|
+
inlineContentSchema,
|
|
198
|
+
styleSchema,
|
|
199
|
+
} = this.init();
|
|
200
|
+
this.blockSpecs = blockSpecs;
|
|
201
|
+
this.styleSpecs = styleSpecs;
|
|
202
|
+
this.styleSchema = styleSchema;
|
|
203
|
+
this.inlineContentSpecs = inlineContentSpecs;
|
|
204
|
+
this.blockSchema = blockSchema;
|
|
205
|
+
this.inlineContentSchema = inlineContentSchema;
|
|
206
|
+
|
|
207
|
+
return this as any;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Mark } from "@tiptap/core";
|
|
2
2
|
|
|
3
|
-
import { ParseRule } from "@tiptap/pm/model";
|
|
4
|
-
import { UnreachableCaseError } from "../../util/typescript.js";
|
|
3
|
+
import { ParseRule, TagParseRule } from "@tiptap/pm/model";
|
|
5
4
|
import {
|
|
6
5
|
addStyleAttributes,
|
|
7
6
|
createInternalStyleSpec,
|
|
@@ -10,21 +9,26 @@ import {
|
|
|
10
9
|
import { StyleConfig, StyleSpec } from "./types.js";
|
|
11
10
|
|
|
12
11
|
export type CustomStyleImplementation<T extends StyleConfig> = {
|
|
13
|
-
render: T["propSchema"] extends "boolean"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
render: (value: T["propSchema"] extends "boolean" ? undefined : string) => {
|
|
13
|
+
dom: HTMLElement;
|
|
14
|
+
contentDOM?: HTMLElement;
|
|
15
|
+
};
|
|
16
|
+
toExternalHTML?: (
|
|
17
|
+
value: T["propSchema"] extends "boolean" ? undefined : string,
|
|
18
|
+
) => {
|
|
19
|
+
dom: HTMLElement;
|
|
20
|
+
contentDOM?: HTMLElement;
|
|
21
|
+
};
|
|
22
|
+
parse?: (
|
|
23
|
+
element: HTMLElement,
|
|
24
|
+
) => (T["propSchema"] extends "boolean" ? true : string) | undefined;
|
|
22
25
|
};
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
export function getStyleParseRules<T extends StyleConfig>(
|
|
28
|
+
config: T,
|
|
29
|
+
customParseFunction?: CustomStyleImplementation<T>["parse"],
|
|
30
|
+
): ParseRule[] {
|
|
31
|
+
const rules: TagParseRule[] = [
|
|
28
32
|
{
|
|
29
33
|
tag: `[data-style-type="${config.type}"]`,
|
|
30
34
|
contentElement: (element) => {
|
|
@@ -38,9 +42,29 @@ export function getStyleParseRules(config: StyleConfig): ParseRule[] {
|
|
|
38
42
|
},
|
|
39
43
|
},
|
|
40
44
|
];
|
|
45
|
+
|
|
46
|
+
if (customParseFunction) {
|
|
47
|
+
rules.push({
|
|
48
|
+
tag: "*",
|
|
49
|
+
getAttrs(node: string | HTMLElement) {
|
|
50
|
+
if (typeof node === "string") {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const stringValue = customParseFunction?.(node);
|
|
55
|
+
|
|
56
|
+
if (stringValue === undefined) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { stringValue };
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return rules;
|
|
41
65
|
}
|
|
42
66
|
|
|
43
|
-
export function createStyleSpec<T extends StyleConfig>(
|
|
67
|
+
export function createStyleSpec<const T extends StyleConfig>(
|
|
44
68
|
styleConfig: T,
|
|
45
69
|
styleImplementation: CustomStyleImplementation<T>,
|
|
46
70
|
): StyleSpec<T> {
|
|
@@ -52,25 +76,14 @@ export function createStyleSpec<T extends StyleConfig>(
|
|
|
52
76
|
},
|
|
53
77
|
|
|
54
78
|
parseHTML() {
|
|
55
|
-
return getStyleParseRules(styleConfig);
|
|
79
|
+
return getStyleParseRules(styleConfig, styleImplementation.parse);
|
|
56
80
|
},
|
|
57
81
|
|
|
58
82
|
renderHTML({ mark }) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
};
|
|
83
|
+
const renderResult = (
|
|
84
|
+
styleImplementation.toExternalHTML || styleImplementation.render
|
|
85
|
+
)(mark.attrs.stringValue);
|
|
63
86
|
|
|
64
|
-
if (styleConfig.propSchema === "boolean") {
|
|
65
|
-
// @ts-ignore not sure why this is complaining
|
|
66
|
-
renderResult = styleImplementation.render();
|
|
67
|
-
} else if (styleConfig.propSchema === "string") {
|
|
68
|
-
renderResult = styleImplementation.render(mark.attrs.stringValue);
|
|
69
|
-
} else {
|
|
70
|
-
throw new UnreachableCaseError(styleConfig.propSchema);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// const renderResult = styleImplementation.render();
|
|
74
87
|
return addStyleAttributes(
|
|
75
88
|
renderResult,
|
|
76
89
|
styleConfig.type,
|
|
@@ -78,9 +91,44 @@ export function createStyleSpec<T extends StyleConfig>(
|
|
|
78
91
|
styleConfig.propSchema,
|
|
79
92
|
);
|
|
80
93
|
},
|
|
94
|
+
|
|
95
|
+
addMarkView() {
|
|
96
|
+
return ({ mark }) => {
|
|
97
|
+
const renderResult = styleImplementation.render(mark.attrs.stringValue);
|
|
98
|
+
|
|
99
|
+
return addStyleAttributes(
|
|
100
|
+
renderResult,
|
|
101
|
+
styleConfig.type,
|
|
102
|
+
mark.attrs.stringValue,
|
|
103
|
+
styleConfig.propSchema,
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
},
|
|
81
107
|
});
|
|
82
108
|
|
|
83
109
|
return createInternalStyleSpec(styleConfig, {
|
|
84
110
|
mark,
|
|
111
|
+
render: (value) => {
|
|
112
|
+
const renderResult = styleImplementation.render(value as any);
|
|
113
|
+
|
|
114
|
+
return addStyleAttributes(
|
|
115
|
+
renderResult,
|
|
116
|
+
styleConfig.type,
|
|
117
|
+
value,
|
|
118
|
+
styleConfig.propSchema,
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
toExternalHTML: (value) => {
|
|
122
|
+
const renderResult = (
|
|
123
|
+
styleImplementation.toExternalHTML || styleImplementation.render
|
|
124
|
+
)(value as any);
|
|
125
|
+
|
|
126
|
+
return addStyleAttributes(
|
|
127
|
+
renderResult,
|
|
128
|
+
styleConfig.type,
|
|
129
|
+
value,
|
|
130
|
+
styleConfig.propSchema,
|
|
131
|
+
);
|
|
132
|
+
},
|
|
85
133
|
});
|
|
86
134
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Attributes, Mark } from "@tiptap/core";
|
|
2
|
+
import { DOMSerializer } from "@tiptap/pm/model";
|
|
2
3
|
import {
|
|
3
4
|
StyleConfig,
|
|
4
5
|
StyleImplementation,
|
|
@@ -55,7 +56,7 @@ export function addStyleAttributes<
|
|
|
55
56
|
element.dom.setAttribute("data-value", styleValue as string);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
if (element.contentDOM
|
|
59
|
+
if (element.contentDOM) {
|
|
59
60
|
element.contentDOM.setAttribute("data-editable", "");
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -66,7 +67,7 @@ export function addStyleAttributes<
|
|
|
66
67
|
// config and implementation that conform to the type of Config
|
|
67
68
|
export function createInternalStyleSpec<T extends StyleConfig>(
|
|
68
69
|
config: T,
|
|
69
|
-
implementation: StyleImplementation
|
|
70
|
+
implementation: StyleImplementation<T>,
|
|
70
71
|
) {
|
|
71
72
|
return {
|
|
72
73
|
config,
|
|
@@ -85,6 +86,64 @@ export function createStyleSpecFromTipTapMark<
|
|
|
85
86
|
},
|
|
86
87
|
{
|
|
87
88
|
mark,
|
|
89
|
+
render(value, editor) {
|
|
90
|
+
const toDOM = editor.pmSchema.marks[mark.name].spec.toDOM;
|
|
91
|
+
|
|
92
|
+
if (toDOM === undefined) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"This block has no default HTML serialization as its corresponding TipTap node doesn't implement `renderHTML`.",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const markInstance = editor.pmSchema.mark(mark.name, {
|
|
99
|
+
stringValue: value,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const renderSpec = DOMSerializer.renderSpec(
|
|
103
|
+
document,
|
|
104
|
+
toDOM(markInstance, true),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (typeof renderSpec !== "object" || !("dom" in renderSpec)) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Cannot use this block's default HTML serialization as its corresponding TipTap mark's `renderHTML` function does not return an object with the `dom` property.",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return renderSpec as {
|
|
114
|
+
dom: HTMLElement;
|
|
115
|
+
contentDOM?: HTMLElement;
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
toExternalHTML(value, editor) {
|
|
119
|
+
const toDOM = editor.pmSchema.marks[mark.name].spec.toDOM;
|
|
120
|
+
|
|
121
|
+
if (toDOM === undefined) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"This block has no default HTML serialization as its corresponding TipTap node doesn't implement `renderHTML`.",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const markInstance = editor.pmSchema.mark(mark.name, {
|
|
128
|
+
stringValue: value,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const renderSpec = DOMSerializer.renderSpec(
|
|
132
|
+
document,
|
|
133
|
+
toDOM(markInstance, true),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (typeof renderSpec !== "object" || !("dom" in renderSpec)) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
"Cannot use this block's default HTML serialization as its corresponding TipTap mark's `renderHTML` function does not return an object with the `dom` property.",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return renderSpec as {
|
|
143
|
+
dom: HTMLElement;
|
|
144
|
+
contentDOM?: HTMLElement;
|
|
145
|
+
};
|
|
146
|
+
},
|
|
88
147
|
},
|
|
89
148
|
);
|
|
90
149
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Mark } from "@tiptap/core";
|
|
2
|
+
import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
2
3
|
|
|
3
4
|
export type StylePropSchema = "boolean" | "string"; // TODO: use PropSchema as name? Use objects as type similar to blocks?
|
|
4
5
|
|
|
@@ -7,20 +8,33 @@ export type StylePropSchema = "boolean" | "string"; // TODO: use PropSchema as n
|
|
|
7
8
|
export type StyleConfig = {
|
|
8
9
|
type: string;
|
|
9
10
|
readonly propSchema: StylePropSchema;
|
|
10
|
-
// content: "inline" | "none" | "table";
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// StyleImplementation contains the "implementation" info about a Style element.
|
|
14
14
|
// Currently, the implementation is always a TipTap Mark
|
|
15
|
-
export type StyleImplementation = {
|
|
15
|
+
export type StyleImplementation<T extends StyleConfig> = {
|
|
16
16
|
mark: Mark;
|
|
17
|
+
render: (
|
|
18
|
+
value: T["propSchema"] extends "boolean" ? undefined : string,
|
|
19
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
20
|
+
) => {
|
|
21
|
+
dom: HTMLElement;
|
|
22
|
+
contentDOM?: HTMLElement;
|
|
23
|
+
};
|
|
24
|
+
toExternalHTML?: (
|
|
25
|
+
value: T["propSchema"] extends "boolean" ? undefined : string,
|
|
26
|
+
editor: BlockNoteEditor<any, any, any>,
|
|
27
|
+
) => {
|
|
28
|
+
dom: HTMLElement;
|
|
29
|
+
contentDOM?: HTMLElement;
|
|
30
|
+
};
|
|
17
31
|
};
|
|
18
32
|
|
|
19
33
|
// Container for both the config and implementation of a Style,
|
|
20
34
|
// and the type of `implementation` is based on that of the config
|
|
21
35
|
export type StyleSpec<T extends StyleConfig> = {
|
|
22
36
|
config: T;
|
|
23
|
-
implementation: StyleImplementation
|
|
37
|
+
implementation: StyleImplementation<T>;
|
|
24
38
|
};
|
|
25
39
|
|
|
26
40
|
// A Schema contains all the types (Configs) supported in an editor
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { toposort as batchingToposort, toposortReverse } from "./topo-sort.js";
|
|
3
|
+
|
|
4
|
+
describe("toposort", () => {
|
|
5
|
+
it("toposorts an empty graph", () => {
|
|
6
|
+
expect(batchingToposort(new Map())).toEqual([]);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("toposorts a simple DAG", () => {
|
|
10
|
+
expect(
|
|
11
|
+
batchingToposort(
|
|
12
|
+
new Map([
|
|
13
|
+
["a", ["b"]],
|
|
14
|
+
["b", ["c"]],
|
|
15
|
+
["c", []],
|
|
16
|
+
]),
|
|
17
|
+
),
|
|
18
|
+
).toEqual([new Set(["a"]), new Set(["b"]), new Set(["c"])]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("toposorts a richer DAG", () => {
|
|
22
|
+
expect(
|
|
23
|
+
batchingToposort(
|
|
24
|
+
new Map([
|
|
25
|
+
["a", ["c"]],
|
|
26
|
+
["b", ["c"]],
|
|
27
|
+
["c", []],
|
|
28
|
+
]),
|
|
29
|
+
),
|
|
30
|
+
).toEqual([new Set(["a", "b"]), new Set(["c"])]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("toposorts a complex DAG", () => {
|
|
34
|
+
expect(
|
|
35
|
+
batchingToposort(
|
|
36
|
+
new Map([
|
|
37
|
+
["a", ["c", "f"]],
|
|
38
|
+
["b", ["d", "e"]],
|
|
39
|
+
["c", ["f"]],
|
|
40
|
+
["d", ["f", "g"]],
|
|
41
|
+
["e", ["h"]],
|
|
42
|
+
["f", ["i"]],
|
|
43
|
+
["g", ["j"]],
|
|
44
|
+
["h", ["j"]],
|
|
45
|
+
["i", []],
|
|
46
|
+
["j", []],
|
|
47
|
+
]),
|
|
48
|
+
),
|
|
49
|
+
).toEqual([
|
|
50
|
+
new Set(["a", "b"]),
|
|
51
|
+
new Set(["c", "d", "e"]),
|
|
52
|
+
new Set(["f", "g", "h"]),
|
|
53
|
+
new Set(["i", "j"]),
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("errors on a small cyclic graph", () => {
|
|
58
|
+
const dg = new Map([
|
|
59
|
+
["a", ["b"]],
|
|
60
|
+
["b", ["a"]],
|
|
61
|
+
["c", []],
|
|
62
|
+
]);
|
|
63
|
+
const sortCyclicGraph = () => {
|
|
64
|
+
batchingToposort(dg);
|
|
65
|
+
};
|
|
66
|
+
expect(sortCyclicGraph).toThrowError(Error);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("errors on a larger cyclic graph", () => {
|
|
70
|
+
const dg = new Map([
|
|
71
|
+
["a", ["b", "c"]],
|
|
72
|
+
["b", ["c"]],
|
|
73
|
+
["c", ["d", "e"]],
|
|
74
|
+
["d", ["b"]],
|
|
75
|
+
["e", []],
|
|
76
|
+
]);
|
|
77
|
+
const sortCyclicGraph = () => {
|
|
78
|
+
batchingToposort(dg);
|
|
79
|
+
};
|
|
80
|
+
expect(sortCyclicGraph).toThrowError(Error);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("can sort a graph with missing dependencies", () => {
|
|
84
|
+
const dg = new Map([
|
|
85
|
+
["a", ["non-existent-node"]],
|
|
86
|
+
["b", ["c"]],
|
|
87
|
+
["c", []],
|
|
88
|
+
]);
|
|
89
|
+
const result = batchingToposort(dg);
|
|
90
|
+
expect(result).toEqual([
|
|
91
|
+
new Set(["a", "b"]),
|
|
92
|
+
new Set(["non-existent-node", "c"]),
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("toposortReverse", () => {
|
|
98
|
+
it("can sort stuff", () => {
|
|
99
|
+
const graph = new Map([
|
|
100
|
+
["floss", ["brushTeeth"]],
|
|
101
|
+
["drinkCoffee", ["wakeUp"]],
|
|
102
|
+
["wakeUp", []],
|
|
103
|
+
["brushTeeth", ["drinkCoffee", "eatBreakfast"]],
|
|
104
|
+
["eatBreakfast", ["wakeUp"]],
|
|
105
|
+
]);
|
|
106
|
+
const result = toposortReverse(graph);
|
|
107
|
+
expect(result).toMatchInlineSnapshot(`
|
|
108
|
+
[
|
|
109
|
+
Set {
|
|
110
|
+
"wakeUp",
|
|
111
|
+
},
|
|
112
|
+
Set {
|
|
113
|
+
"drinkCoffee",
|
|
114
|
+
"eatBreakfast",
|
|
115
|
+
},
|
|
116
|
+
Set {
|
|
117
|
+
"brushTeeth",
|
|
118
|
+
},
|
|
119
|
+
Set {
|
|
120
|
+
"floss",
|
|
121
|
+
},
|
|
122
|
+
]
|
|
123
|
+
`);
|
|
124
|
+
});
|
|
125
|
+
});
|