@blocknote/core 0.4.0 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/blocknote.js +12371 -12268
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +20 -20
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +2 -2
  7. package/src/BlockNoteEditor.ts +237 -14
  8. package/src/BlockNoteExtensions.ts +19 -15
  9. package/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +616 -0
  10. package/src/api/blockManipulation/blockManipulation.test.ts +172 -0
  11. package/src/api/blockManipulation/blockManipulation.ts +25 -14
  12. package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +346 -0
  13. package/src/api/formatConversions/formatConversions.test.ts +766 -0
  14. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +268 -0
  15. package/src/api/nodeConversions/nodeConversions.test.ts +244 -0
  16. package/src/api/nodeConversions/nodeConversions.ts +167 -58
  17. package/src/api/nodeConversions/testUtil.ts +61 -0
  18. package/src/api/util/nodeUtil.ts +38 -0
  19. package/src/editor.module.css +1 -0
  20. package/src/extensions/Blocks/api/blockTypes.ts +14 -9
  21. package/src/extensions/Blocks/api/cursorPositionTypes.ts +2 -0
  22. package/src/extensions/Blocks/api/inlineContentTypes.ts +27 -36
  23. package/src/extensions/Blocks/nodes/Block.module.css +39 -36
  24. package/src/extensions/Blocks/nodes/BlockContainer.ts +15 -14
  25. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.ts +149 -87
  26. package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +31 -0
  27. package/src/extensions/SlashMenu/SlashMenuExtension.ts +10 -7
  28. package/src/extensions/SlashMenu/{defaultSlashCommands.tsx → defaultSlashMenuItems.tsx} +59 -106
  29. package/src/extensions/SlashMenu/index.ts +3 -7
  30. package/src/index.ts +2 -3
  31. package/src/shared/plugins/suggestion/SuggestionItem.ts +2 -13
  32. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +31 -18
  33. package/src/shared/utils.ts +6 -0
  34. package/types/src/BlockNoteEditor.d.ts +82 -8
  35. package/types/src/BlockNoteExtensions.d.ts +5 -4
  36. package/types/src/api/Editor.d.ts +26 -6
  37. package/types/src/api/blockManipulation/blockManipulation.d.ts +5 -5
  38. package/types/src/api/blockManipulation/blockManipulation.test.d.ts +1 -0
  39. package/types/src/api/formatConversions/formatConversions.test.d.ts +1 -0
  40. package/types/src/api/nodeConversions/nodeConversions.d.ts +11 -4
  41. package/types/src/api/nodeConversions/nodeConversions.test.d.ts +1 -0
  42. package/types/src/api/nodeConversions/testUtil.d.ts +2 -0
  43. package/types/src/api/util/nodeUtil.d.ts +8 -0
  44. package/types/src/extensions/Blocks/api/blockTypes.d.ts +10 -9
  45. package/types/src/extensions/Blocks/api/cursorPositionTypes.d.ts +2 -0
  46. package/types/src/extensions/Blocks/api/inlineContentTypes.d.ts +25 -19
  47. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +15 -0
  48. package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +20 -0
  49. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +4 -2
  50. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +5 -0
  51. package/types/src/extensions/SlashMenu/index.d.ts +3 -3
  52. package/types/src/index.d.ts +2 -3
  53. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +3 -11
  54. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +4 -4
  55. package/types/src/shared/utils.d.ts +3 -0
  56. package/src/api/Editor.ts +0 -142
  57. package/src/extensions/SlashMenu/SlashMenuItem.ts +0 -34
  58. package/types/src/EditorElement.d.ts +0 -7
  59. package/types/src/api/Document.d.ts +0 -5
  60. package/types/src/api/removeUnderlinesRehypePlugin.d.ts +0 -6
  61. package/types/src/api/simplifyBlocksRehypePlugin.d.ts +0 -16
  62. package/types/src/extensions/Blocks/BlockAttributes.d.ts +0 -2
  63. package/types/src/extensions/Blocks/MultipleNodeSelection.d.ts +0 -24
  64. package/types/src/extensions/Blocks/api/apiTypes.d.ts +0 -18
  65. package/types/src/extensions/Blocks/api/styleTypes.d.ts +0 -22
  66. package/types/src/extensions/Blocks/apiTypes.d.ts +0 -16
  67. package/types/src/extensions/Blocks/nodes/Block.d.ts +0 -24
  68. package/types/src/extensions/Blocks/nodes/BlockContent/BlockContentTypes.d.ts +0 -4
  69. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContentTypes.d.ts +0 -4
  70. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContentTypes.d.ts +0 -2
  71. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContentTypes.d.ts +0 -2
  72. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContentTypes.d.ts +0 -2
  73. package/types/src/extensions/Blocks/nodes/BlockTypes/HeadingBlock/HeadingContent.d.ts +0 -8
  74. package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.d.ts +0 -8
  75. package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.d.ts +0 -2
  76. package/types/src/extensions/Blocks/nodes/BlockTypes/TextBlock/TextContent.d.ts +0 -6
  77. package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +0 -8
  78. package/types/src/extensions/BubbleMenu/BubbleMenuFactoryTypes.d.ts +0 -27
  79. package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +0 -44
  80. package/types/src/extensions/DraggableBlocks/BlockMenuFactoryTypes.d.ts +0 -12
  81. package/types/src/extensions/DraggableBlocks/DragMenuFactoryTypes.d.ts +0 -18
  82. package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +0 -8
  83. package/types/src/extensions/Hyperlinks/HyperlinkMenuFactoryTypes.d.ts +0 -11
  84. package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +0 -11
  85. package/types/src/extensions/Paragraph/FixedParagraph.d.ts +0 -1
  86. package/types/src/extensions/SlashMenu/defaultCommands.d.ts +0 -8
  87. package/types/src/utils.d.ts +0 -2
@@ -1,3 +1,4 @@
1
+ import { Mark } from "@tiptap/pm/model";
1
2
  import { Node, Schema } from "prosemirror-model";
2
3
  import {
3
4
  Block,
@@ -5,12 +6,104 @@ import {
5
6
  PartialBlock,
6
7
  } from "../../extensions/Blocks/api/blockTypes";
7
8
  import {
9
+ ColorStyles,
8
10
  InlineContent,
9
- Style,
11
+ Link,
12
+ PartialInlineContent,
13
+ PartialLink,
14
+ StyledText,
15
+ Styles,
16
+ ToggledStyles,
10
17
  } from "../../extensions/Blocks/api/inlineContentTypes";
11
18
  import { getBlockInfoFromPos } from "../../extensions/Blocks/helpers/getBlockInfoFromPos";
12
19
  import UniqueID from "../../extensions/UniqueID/UniqueID";
20
+ import { UnreachableCaseError } from "../../shared/utils";
21
+
22
+ const toggleStyles = new Set<ToggledStyles>([
23
+ "bold",
24
+ "italic",
25
+ "underline",
26
+ "strike",
27
+ ]);
28
+ const colorStyles = new Set<ColorStyles>(["textColor", "backgroundColor"]);
29
+
30
+ /**
31
+ * Convert a StyledText inline element to a
32
+ * prosemirror text node with the appropriate marks
33
+ */
34
+ function styledTextToNode(styledText: StyledText, schema: Schema): Node {
35
+ const marks: Mark[] = [];
36
+
37
+ for (const [style, value] of Object.entries(styledText.styles)) {
38
+ if (toggleStyles.has(style as ToggledStyles)) {
39
+ marks.push(schema.mark(style));
40
+ } else if (colorStyles.has(style as ColorStyles)) {
41
+ marks.push(schema.mark(style, { color: value }));
42
+ }
43
+ }
44
+
45
+ return schema.text(styledText.text, marks);
46
+ }
47
+
48
+ /**
49
+ * Converts a Link inline content element to
50
+ * prosemirror text nodes with the appropriate marks
51
+ */
52
+ function linkToNodes(link: PartialLink, schema: Schema): Node[] {
53
+ const linkMark = schema.marks.link.create({
54
+ href: link.href,
55
+ });
56
+
57
+ return styledTextArrayToNodes(link.content, schema).map((node) => {
58
+ return node.mark([...node.marks, linkMark]);
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Converts an array of StyledText inline content elements to
64
+ * prosemirror text nodes with the appropriate marks
65
+ */
66
+ function styledTextArrayToNodes(
67
+ content: string | StyledText[],
68
+ schema: Schema
69
+ ): Node[] {
70
+ let nodes: Node[] = [];
71
+
72
+ if (typeof content === "string") {
73
+ nodes.push(schema.text(content));
74
+ return nodes;
75
+ }
13
76
 
77
+ for (const styledText of content) {
78
+ nodes.push(styledTextToNode(styledText, schema));
79
+ }
80
+ return nodes;
81
+ }
82
+
83
+ /**
84
+ * converts an array of inline content elements to prosemirror nodes
85
+ */
86
+ export function inlineContentToNodes(
87
+ blockContent: PartialInlineContent[],
88
+ schema: Schema
89
+ ): Node[] {
90
+ let nodes: Node[] = [];
91
+
92
+ for (const content of blockContent) {
93
+ if (content.type === "link") {
94
+ nodes.push(...linkToNodes(content, schema));
95
+ } else if (content.type === "text") {
96
+ nodes.push(...styledTextArrayToNodes([content], schema));
97
+ } else {
98
+ throw new UnreachableCaseError(content);
99
+ }
100
+ }
101
+ return nodes;
102
+ }
103
+
104
+ /**
105
+ * Converts a BlockNote block to a TipTap node.
106
+ */
14
107
  export function blockToNode(block: PartialBlock, schema: Schema) {
15
108
  let id = block.id;
16
109
 
@@ -18,24 +111,26 @@ export function blockToNode(block: PartialBlock, schema: Schema) {
18
111
  id = UniqueID.options.generateID();
19
112
  }
20
113
 
21
- let content: Node[] = [];
114
+ let type = block.type;
22
115
 
23
- if (typeof block.content === "string") {
24
- content.push(schema.text(block.content));
25
- } else if (typeof block.content === "object") {
26
- for (const styledText of block.content) {
27
- const marks = [];
116
+ if (type === undefined) {
117
+ type = "paragraph";
118
+ }
28
119
 
29
- for (const style of styledText.styles) {
30
- marks.push(schema.mark(style.type, style.props));
31
- }
120
+ let contentNode: Node;
32
121
 
33
- content.push(schema.text(styledText.text, marks));
34
- }
122
+ if (!block.content) {
123
+ contentNode = schema.nodes[type].create(block.props);
124
+ } else if (typeof block.content === "string") {
125
+ contentNode = schema.nodes[type].create(
126
+ block.props,
127
+ schema.text(block.content)
128
+ );
129
+ } else {
130
+ const nodes = inlineContentToNodes(block.content, schema);
131
+ contentNode = schema.nodes[type].create(block.props, nodes);
35
132
  }
36
133
 
37
- const contentNode = schema.nodes[block.type].create(block.props, content);
38
-
39
134
  const children: Node[] = [];
40
135
 
41
136
  if (block.children) {
@@ -55,40 +150,69 @@ export function blockToNode(block: PartialBlock, schema: Schema) {
55
150
  );
56
151
  }
57
152
 
58
- export function getNodeById(
59
- id: string,
60
- doc: Node
61
- ): { node: Node; posBeforeNode: number } {
62
- let targetNode: Node | undefined = undefined;
63
- let posBeforeNode: number | undefined = undefined;
64
-
65
- doc.firstChild!.descendants((node, pos) => {
66
- // Skips traversing nodes after node with target ID has been found.
67
- if (targetNode) {
68
- return false;
69
- }
70
-
71
- // Keeps traversing nodes if block with target ID has not been found.
72
- if (node.type.name !== "blockContainer" || node.attrs.id !== id) {
73
- return true;
74
- }
153
+ /**
154
+ * Converts an internal (prosemirror) content node to a BlockNote InlineContent array.
155
+ */
156
+ function contentNodeToInlineContent(contentNode: Node) {
157
+ const content: InlineContent[] = [];
75
158
 
76
- targetNode = node;
77
- posBeforeNode = pos + 1;
159
+ let currentLink: Link | undefined = undefined;
78
160
 
79
- return false;
80
- });
161
+ // Most of the logic below is for handling links because in ProseMirror links are marks
162
+ // while in BlockNote links are a type of inline content
163
+ contentNode.content.forEach((node) => {
164
+ const styles: Styles = {};
81
165
 
82
- if (targetNode === undefined || posBeforeNode === undefined) {
83
- throw Error("Could not find block in the editor with matching ID.");
84
- }
166
+ let linkMark: Mark | undefined;
167
+ for (const mark of node.marks) {
168
+ if (mark.type.name === "link") {
169
+ linkMark = mark;
170
+ } else if (toggleStyles.has(mark.type.name as ToggledStyles)) {
171
+ styles[mark.type.name as ToggledStyles] = true;
172
+ } else if (colorStyles.has(mark.type.name as ColorStyles)) {
173
+ styles[mark.type.name as ColorStyles] = mark.attrs.color;
174
+ } else {
175
+ throw Error("Mark is of an unrecognized type: " + mark.type.name);
176
+ }
177
+ }
85
178
 
86
- return {
87
- node: targetNode,
88
- posBeforeNode: posBeforeNode,
89
- };
179
+ if (linkMark && currentLink && linkMark.attrs.href === currentLink.href) {
180
+ // if the node is a link that matches the current link, add it to the current link
181
+ currentLink.content.push({
182
+ type: "text",
183
+ text: node.textContent,
184
+ styles,
185
+ });
186
+ } else if (linkMark) {
187
+ // if the node is a link that doesn't match the current link, create a new link
188
+ currentLink = {
189
+ type: "link",
190
+ href: linkMark.attrs.href,
191
+ content: [
192
+ {
193
+ type: "text",
194
+ text: node.textContent,
195
+ styles,
196
+ },
197
+ ],
198
+ };
199
+ content.push(currentLink);
200
+ } else {
201
+ // if the node is not a link, add it to the content
202
+ content.push({
203
+ type: "text",
204
+ text: node.textContent,
205
+ styles,
206
+ });
207
+ currentLink = undefined;
208
+ }
209
+ });
210
+ return content;
90
211
  }
91
212
 
213
+ /**
214
+ * Convert a TipTap node to a BlockNote block.
215
+ */
92
216
  export function nodeToBlock(
93
217
  node: Node,
94
218
  blockCache?: WeakMap<Node, Block>
@@ -134,22 +258,7 @@ export function nodeToBlock(
134
258
  }
135
259
  }
136
260
 
137
- const content: InlineContent[] = [];
138
- blockInfo.contentNode.content.forEach((node) => {
139
- const styles: Style[] = [];
140
-
141
- for (const mark of node.marks) {
142
- styles.push({
143
- type: mark.type.name,
144
- props: mark.attrs,
145
- } as Style);
146
- }
147
-
148
- content.push({
149
- text: node.textContent,
150
- styles,
151
- });
152
- });
261
+ const content = contentNodeToInlineContent(blockInfo.contentNode);
153
262
 
154
263
  const children: Block[] = [];
155
264
  for (let i = 0; i < blockInfo.numChildBlocks; i++) {
@@ -0,0 +1,61 @@
1
+ import { Block, PartialBlock } from "../../extensions/Blocks/api/blockTypes";
2
+ import {
3
+ InlineContent,
4
+ PartialInlineContent,
5
+ StyledText,
6
+ } from "../../extensions/Blocks/api/inlineContentTypes";
7
+
8
+ function textShorthandToStyledText(
9
+ content: string | StyledText[] = ""
10
+ ): StyledText[] {
11
+ if (typeof content === "string") {
12
+ return [
13
+ {
14
+ type: "text",
15
+ text: content,
16
+ styles: {},
17
+ },
18
+ ];
19
+ }
20
+ return content;
21
+ }
22
+
23
+ function partialContentToInlineContent(
24
+ content: string | PartialInlineContent[] = ""
25
+ ): InlineContent[] {
26
+ if (typeof content === "string") {
27
+ return textShorthandToStyledText(content);
28
+ }
29
+
30
+ return content.map((partialContent) => {
31
+ if (partialContent.type === "link") {
32
+ return {
33
+ ...partialContent,
34
+ content: textShorthandToStyledText(partialContent.content),
35
+ };
36
+ } else {
37
+ return partialContent;
38
+ }
39
+ });
40
+ }
41
+
42
+ export function partialBlockToBlockForTesting(
43
+ partialBlock: PartialBlock
44
+ ): Block {
45
+ const withDefaults = {
46
+ id: "",
47
+ type: "paragraph" as any,
48
+ // because at this point we don't have an easy way to access default props at runtime,
49
+ // partialBlockToBlockForTesting will not set them.
50
+ props: {} as any,
51
+ content: [],
52
+ children: [],
53
+ ...partialBlock,
54
+ };
55
+
56
+ return {
57
+ ...withDefaults,
58
+ content: partialContentToInlineContent(withDefaults.content),
59
+ children: withDefaults.children.map(partialBlockToBlockForTesting),
60
+ };
61
+ }
@@ -0,0 +1,38 @@
1
+ import { Node } from "prosemirror-model";
2
+
3
+ /**
4
+ * Get a TipTap node by id
5
+ */
6
+ export function getNodeById(
7
+ id: string,
8
+ doc: Node
9
+ ): { node: Node; posBeforeNode: number } {
10
+ let targetNode: Node | undefined = undefined;
11
+ let posBeforeNode: number | undefined = undefined;
12
+
13
+ doc.firstChild!.descendants((node, pos) => {
14
+ // Skips traversing nodes after node with target ID has been found.
15
+ if (targetNode) {
16
+ return false;
17
+ }
18
+
19
+ // Keeps traversing nodes if block with target ID has not been found.
20
+ if (node.type.name !== "blockContainer" || node.attrs.id !== id) {
21
+ return true;
22
+ }
23
+
24
+ targetNode = node;
25
+ posBeforeNode = pos + 1;
26
+
27
+ return false;
28
+ });
29
+
30
+ if (targetNode === undefined || posBeforeNode === undefined) {
31
+ throw Error("Could not find block in the editor with matching ID.");
32
+ }
33
+
34
+ return {
35
+ node: targetNode,
36
+ posBeforeNode: posBeforeNode,
37
+ };
38
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  .bnEditor {
4
4
  outline: none;
5
+ margin-left: 50px;
5
6
  }
6
7
 
7
8
  /*
@@ -1,6 +1,6 @@
1
1
  /** Define the main block types **/
2
2
 
3
- import { InlineContent } from "./inlineContentTypes";
3
+ import { InlineContent, PartialInlineContent } from "./inlineContentTypes";
4
4
 
5
5
  export type BlockTemplate<
6
6
  // Type of the block.
@@ -17,7 +17,7 @@ export type BlockTemplate<
17
17
  children: Block[];
18
18
  };
19
19
 
20
- export type GlobalProps = {
20
+ export type DefaultBlockProps = {
21
21
  backgroundColor: string;
22
22
  textColor: string;
23
23
  textAlignment: "left" | "center" | "right" | "justify";
@@ -25,19 +25,22 @@ export type GlobalProps = {
25
25
 
26
26
  export type NumberedListItemBlock = BlockTemplate<
27
27
  "numberedListItem",
28
- GlobalProps
28
+ DefaultBlockProps
29
29
  >;
30
30
 
31
- export type BulletListItemBlock = BlockTemplate<"bulletListItem", GlobalProps>;
31
+ export type BulletListItemBlock = BlockTemplate<
32
+ "bulletListItem",
33
+ DefaultBlockProps
34
+ >;
32
35
 
33
36
  export type HeadingBlock = BlockTemplate<
34
37
  "heading",
35
- GlobalProps & {
38
+ DefaultBlockProps & {
36
39
  level: "1" | "2" | "3";
37
40
  }
38
41
  >;
39
42
 
40
- export type ParagraphBlock = BlockTemplate<"paragraph", GlobalProps>;
43
+ export type ParagraphBlock = BlockTemplate<"paragraph", DefaultBlockProps>;
41
44
 
42
45
  export type Block =
43
46
  | ParagraphBlock
@@ -45,12 +48,14 @@ export type Block =
45
48
  | BulletListItemBlock
46
49
  | NumberedListItemBlock;
47
50
 
51
+ export type BlockIdentifier = string | Block;
52
+
48
53
  /** Define "Partial Blocks", these are for updating or creating blocks */
49
54
  export type PartialBlockTemplate<B extends Block> = B extends Block
50
55
  ? Partial<Omit<B, "props" | "children" | "content" | "type">> & {
51
- type: B["type"];
56
+ type?: B["type"];
52
57
  props?: Partial<B["props"]>;
53
- content?: string | B["content"];
58
+ content?: string | PartialInlineContent[];
54
59
  children?: PartialBlock[];
55
60
  }
56
61
  : never;
@@ -66,7 +71,7 @@ export type BlockPropsTemplate<Props> = Props extends Block["props"]
66
71
  * will change anyway once we allow for custom blocks
67
72
  */
68
73
 
69
- export const globalProps: Array<keyof GlobalProps> = [
74
+ export const globalProps: Array<keyof DefaultBlockProps> = [
70
75
  "backgroundColor",
71
76
  "textColor",
72
77
  "textAlignment",
@@ -2,4 +2,6 @@ import { Block } from "./blockTypes";
2
2
 
3
3
  export type TextCursorPosition = {
4
4
  block: Block;
5
+ prevBlock: Block | undefined;
6
+ nextBlock: Block | undefined;
5
7
  };
@@ -1,44 +1,35 @@
1
- export type StyleTemplate<
2
- // Type of the style.
3
- // Examples might include: "bold", "italic", or "textColor".
4
- Type extends string,
5
- // Changeable props which affect the style's appearance.
6
- // An example might be: { color: string } for a textColor style.
7
- Props extends Record<string, string>
8
- > = {
9
- type: Type;
10
- props: Props;
1
+ export type Styles = {
2
+ bold?: true;
3
+ italic?: true;
4
+ underline?: true;
5
+ strike?: true;
6
+ textColor?: string;
7
+ backgroundColor?: string;
11
8
  };
12
9
 
13
- export type Bold = StyleTemplate<"bold", {}>;
10
+ export type ToggledStyles = {
11
+ [K in keyof Styles]-?: Required<Styles>[K] extends true ? K : never;
12
+ }[keyof Styles];
14
13
 
15
- export type Italic = StyleTemplate<"italic", {}>;
16
-
17
- export type Underline = StyleTemplate<"underline", {}>;
18
-
19
- export type Strikethrough = StyleTemplate<"strike", {}>;
20
-
21
- export type TextColor = StyleTemplate<"textColor", { color: string }>;
22
-
23
- export type BackgroundColor = StyleTemplate<
24
- "backgroundColor",
25
- { color: string }
26
- >;
27
-
28
- export type Link = StyleTemplate<"link", { href: string }>;
29
-
30
- export type Style =
31
- | Bold
32
- | Italic
33
- | Underline
34
- | Strikethrough
35
- | TextColor
36
- | BackgroundColor
37
- | Link;
14
+ export type ColorStyles = {
15
+ [K in keyof Styles]-?: Required<Styles>[K] extends string ? K : never;
16
+ }[keyof Styles];
38
17
 
39
18
  export type StyledText = {
19
+ type: "text";
40
20
  text: string;
41
- styles: Style[];
21
+ styles: Styles;
22
+ };
23
+
24
+ export type Link = {
25
+ type: "link";
26
+ href: string;
27
+ content: StyledText[];
28
+ };
29
+
30
+ export type PartialLink = Omit<Link, "content"> & {
31
+ content: string | Link["content"];
42
32
  };
43
33
 
44
- export type InlineContent = StyledText;
34
+ export type InlineContent = StyledText | Link;
35
+ export type PartialInlineContent = StyledText | PartialLink;
@@ -35,6 +35,7 @@ BASIC STYLES
35
35
  .blockContent h2,
36
36
  .blockContent h3,
37
37
  .blockContent li {
38
+ all: unset;
38
39
  margin: 0;
39
40
  padding: 0;
40
41
  font-size: inherit;
@@ -150,75 +151,79 @@ NESTED BLOCKS
150
151
 
151
152
  /* Ordered */
152
153
  [data-content-type="numberedListItem"] {
153
- --index: attr(data-index)
154
+ --index: attr(data-index);
154
155
  }
155
156
 
156
157
  [data-prev-type="numberedListItem"] {
157
- --prev-index: attr(data-prev-index)
158
+ --prev-index: attr(data-prev-index);
158
159
  }
159
160
 
160
161
  .blockOuter[data-prev-type="numberedListItem"]:not([data-prev-index="none"])
161
- > .block
162
- > .blockContent::before {
162
+ > .block
163
+ > .blockContent::before {
163
164
  margin-right: 1.2em;
164
- content: var(--prev-index)".";
165
+ content: var(--prev-index) ".";
165
166
  }
166
167
 
167
168
  .blockOuter:not([data-prev-type])
168
- > .block
169
- > .blockContent[data-content-type="numberedListItem"]::before {
169
+ > .block
170
+ > .blockContent[data-content-type="numberedListItem"]::before {
170
171
  margin-right: 1.2em;
171
- content: var(--index)".";
172
+ content: var(--index) ".";
172
173
  }
173
174
 
174
175
  /* Unordered */
175
176
  /* No list nesting */
176
- .blockOuter[data-prev-type="bulletListItem"]
177
- > .block
178
- > .blockContent::before {
177
+ .blockOuter[data-prev-type="bulletListItem"] > .block > .blockContent::before {
179
178
  margin-right: 1.2em;
180
179
  content: "•";
181
180
  }
182
181
 
183
182
  .blockOuter:not([data-prev-type])
184
- > .block
185
- > .blockContent[data-content-type="bulletListItem"]::before {
183
+ > .block
184
+ > .blockContent[data-content-type="bulletListItem"]::before {
186
185
  margin-right: 1.2em;
187
186
  content: "•";
188
187
  }
189
188
 
190
189
  /* 1 level of list nesting */
191
- [data-content-type="bulletListItem"]~.blockGroup
192
- > .blockOuter[data-prev-type="bulletListItem"]
193
- > .block
194
- > .blockContent::before {
190
+ [data-content-type="bulletListItem"]
191
+ ~ .blockGroup
192
+ > .blockOuter[data-prev-type="bulletListItem"]
193
+ > .block
194
+ > .blockContent::before {
195
195
  margin-right: 1.2em;
196
196
  content: "◦";
197
197
  }
198
198
 
199
- [data-content-type="bulletListItem"]~.blockGroup
200
- > .blockOuter:not([data-prev-type])
201
- > .block
202
- > .blockContent[data-content-type="bulletListItem"]::before {
199
+ [data-content-type="bulletListItem"]
200
+ ~ .blockGroup
201
+ > .blockOuter:not([data-prev-type])
202
+ > .block
203
+ > .blockContent[data-content-type="bulletListItem"]::before {
203
204
  margin-right: 1.2em;
204
205
  content: "◦";
205
206
  }
206
207
 
207
208
  /* 2 levels of list nesting */
208
- [data-content-type="bulletListItem"]~.blockGroup
209
- [data-content-type="bulletListItem"]~.blockGroup
210
- > .blockOuter[data-prev-type="bulletListItem"]
211
- > .block
212
- > .blockContent::before {
209
+ [data-content-type="bulletListItem"]
210
+ ~ .blockGroup
211
+ [data-content-type="bulletListItem"]
212
+ ~ .blockGroup
213
+ > .blockOuter[data-prev-type="bulletListItem"]
214
+ > .block
215
+ > .blockContent::before {
213
216
  margin-right: 1.2em;
214
217
  content: "▪";
215
218
  }
216
219
 
217
- [data-content-type="bulletListItem"]~.blockGroup
218
- [data-content-type="bulletListItem"]~.blockGroup
219
- > .blockOuter:not([data-prev-type])
220
- > .block
221
- > .blockContent[data-content-type="bulletListItem"]::before {
220
+ [data-content-type="bulletListItem"]
221
+ ~ .blockGroup
222
+ [data-content-type="bulletListItem"]
223
+ ~ .blockGroup
224
+ > .blockOuter:not([data-prev-type])
225
+ > .block
226
+ > .blockContent[data-content-type="bulletListItem"]::before {
222
227
  margin-right: 1.2em;
223
228
  content: "▪";
224
229
  }
@@ -251,15 +256,13 @@ NESTED BLOCKS
251
256
  content: "Type to filter";
252
257
  }
253
258
 
254
- .blockContent[data-content-type="heading"].isEmpty
255
- > :first-child::before {
259
+ .blockContent[data-content-type="heading"].isEmpty > :first-child::before {
256
260
  content: "Heading";
257
261
  }
258
262
 
259
- .blockContent[data-content-type="bulletListItem"].isEmpty
260
- > :first-child:before,
263
+ .blockContent[data-content-type="bulletListItem"].isEmpty > :first-child:before,
261
264
  .blockContent[data-content-type="numberedListItem"].isEmpty
262
- > :first-child:before {
265
+ > :first-child:before {
263
266
  content: "List";
264
267
  }
265
268