@alpaca-editor/core 1.0.4045 → 1.0.4047

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 (41) hide show
  1. package/dist/editor/field-types/RichTextEditorComponent.js +3 -10
  2. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  3. package/dist/editor/field-types/richtext/components/ReactSlate.js +297 -342
  4. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  5. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js.map +1 -1
  6. package/dist/editor/field-types/richtext/components/SimpleToolbar.js +9 -9
  7. package/dist/editor/field-types/richtext/components/SimpleToolbar.js.map +1 -1
  8. package/dist/editor/field-types/richtext/config/pluginFactory.d.ts +7 -6
  9. package/dist/editor/field-types/richtext/config/pluginFactory.js +2 -1
  10. package/dist/editor/field-types/richtext/config/pluginFactory.js.map +1 -1
  11. package/dist/editor/field-types/richtext/hooks/useProfileCache.js +24 -18
  12. package/dist/editor/field-types/richtext/hooks/useProfileCache.js.map +1 -1
  13. package/dist/editor/field-types/richtext/hooks/useRichTextProfile.js +1 -1
  14. package/dist/editor/field-types/richtext/hooks/useRichTextProfile.js.map +1 -1
  15. package/dist/editor/field-types/richtext/types.d.ts +236 -90
  16. package/dist/editor/field-types/richtext/types.js +3 -3
  17. package/dist/editor/field-types/richtext/types.js.map +1 -1
  18. package/dist/editor/field-types/richtext/utils/conversion.d.ts +4 -2
  19. package/dist/editor/field-types/richtext/utils/conversion.js +79 -12
  20. package/dist/editor/field-types/richtext/utils/conversion.js.map +1 -1
  21. package/dist/editor/field-types/richtext/utils/plugins.d.ts +66 -39
  22. package/dist/editor/field-types/richtext/utils/plugins.js +377 -233
  23. package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
  24. package/dist/editor/field-types/richtext/utils/profileMapper.js +22 -2
  25. package/dist/editor/field-types/richtext/utils/profileMapper.js.map +1 -1
  26. package/dist/revision.d.ts +2 -2
  27. package/dist/revision.js +2 -2
  28. package/package.json +1 -1
  29. package/src/editor/field-types/RichTextEditorComponent.tsx +4 -10
  30. package/src/editor/field-types/richtext/components/ReactSlate.css +85 -24
  31. package/src/editor/field-types/richtext/components/ReactSlate.tsx +372 -427
  32. package/src/editor/field-types/richtext/components/SimpleRichTextEditor.tsx +4 -2
  33. package/src/editor/field-types/richtext/components/SimpleToolbar.tsx +3 -3
  34. package/src/editor/field-types/richtext/config/pluginFactory.tsx +2 -1
  35. package/src/editor/field-types/richtext/hooks/useProfileCache.ts +25 -19
  36. package/src/editor/field-types/richtext/hooks/useRichTextProfile.ts +1 -1
  37. package/src/editor/field-types/richtext/types.ts +150 -112
  38. package/src/editor/field-types/richtext/utils/conversion.ts +100 -27
  39. package/src/editor/field-types/richtext/utils/plugins.ts +469 -268
  40. package/src/editor/field-types/richtext/utils/profileMapper.ts +26 -3
  41. package/src/revision.ts +2 -2
@@ -11,6 +11,8 @@ import {
11
11
  SLATE_MARKS,
12
12
  SLATE_BLOCKS,
13
13
  SLATE_ALIGNMENTS,
14
+ MarkId,
15
+ BlockId,
14
16
  } from "../types";
15
17
  import { SimpleToolbar } from "./SimpleToolbar";
16
18
  import { classNames } from "primereact/utils";
@@ -97,7 +99,7 @@ export const SimpleRichTextEditor = forwardRef<HTMLDivElement, ReactSlateProps>(
97
99
 
98
100
  // Check for active marks
99
101
  Object.keys(SLATE_MARKS).forEach((markId) => {
100
- const markConfig = SLATE_MARKS[markId];
102
+ const markConfig = SLATE_MARKS[markId as MarkId];
101
103
  if (document.queryCommandState(getExecCommandForMark(markId))) {
102
104
  activeFormats.add(markId);
103
105
  }
@@ -235,7 +237,7 @@ export const SimpleRichTextEditor = forwardRef<HTMLDivElement, ReactSlateProps>(
235
237
  if (blockId === "no-tag") {
236
238
  document.execCommand("formatBlock", false, "div");
237
239
  } else {
238
- const blockConfig = SLATE_BLOCKS[blockId];
240
+ const blockConfig = SLATE_BLOCKS[blockId as BlockId];
239
241
  if (blockConfig && blockConfig.htmlTag !== "no-tag") {
240
242
  document.execCommand(
241
243
  "formatBlock",
@@ -115,7 +115,7 @@ export const SimpleToolbar: React.FC<SimpleToolbarProps> = ({
115
115
  onSelect: () => {},
116
116
  };
117
117
 
118
- case "strip":
118
+ /*case "strip":
119
119
  return {
120
120
  id: "strip-formatting",
121
121
  type: "strip",
@@ -123,7 +123,7 @@ export const SimpleToolbar: React.FC<SimpleToolbarProps> = ({
123
123
  icon: "✗",
124
124
  isActive: false,
125
125
  onSelect: () => stripFormatting(),
126
- };
126
+ };*/
127
127
 
128
128
  default:
129
129
  return null;
@@ -292,7 +292,7 @@ export const SimpleToolbar: React.FC<SimpleToolbarProps> = ({
292
292
  acc[row].push(group);
293
293
  return acc;
294
294
  },
295
- {} as Record<number, typeof profile.toolbar.groups>,
295
+ {} as Record<number, ToolbarGroupConfig[]>,
296
296
  );
297
297
 
298
298
  // Create strip formatting option that's always available
@@ -1,5 +1,5 @@
1
1
  import { Editor } from 'slate';
2
- import { withMark, withBlock, withAlignment, withLink, withList, withNormalization } from '../utils/plugins';
2
+ import { withMark, withBlock, withAlignment, withLink, withList, withNormalization, withInsertion } from '../utils/plugins';
3
3
 
4
4
  // Create plugins from configuration
5
5
  export const createPluginsFromConfig = (editor: Editor) => {
@@ -10,6 +10,7 @@ export const createPluginsFromConfig = (editor: Editor) => {
10
10
  enhancedEditor = withAlignment(enhancedEditor);
11
11
  enhancedEditor = withList(enhancedEditor);
12
12
  enhancedEditor = withLink(enhancedEditor);
13
+ enhancedEditor = withInsertion(enhancedEditor);
13
14
 
14
15
  // Add normalization plugin last to ensure it can normalize all other plugin behaviors
15
16
  enhancedEditor = withNormalization(enhancedEditor);
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useMemo } from 'react';
2
- import { RichTextEditorProfile, SimplifiedProfile } from '../types';
2
+ import { RichTextEditorProfile, SimplifiedProfile, MarkId, BlockId, AlignmentId } from '../types';
3
3
 
4
4
  // Cache for simplified profiles converted from complex profiles
5
5
  const simplifiedProfileCache = new Map<string, SimplifiedProfile>();
@@ -40,49 +40,55 @@ const convertToSimplifiedProfile = (profile: RichTextEditorProfile | null): Simp
40
40
  };
41
41
  }
42
42
 
43
- const simplifiedProfile: SimplifiedProfile = {
44
- marks: [],
45
- blocks: [],
46
- alignment: [],
47
- lists: false,
48
- links: false
49
- };
43
+ // Build arrays separately to avoid readonly conflicts
44
+ const marks: MarkId[] = [];
45
+ const blocks: BlockId[] = [];
46
+ const alignment: AlignmentId[] = [];
47
+ let lists = false;
48
+ let links = false;
50
49
 
51
50
  // Extract capabilities from the profile groups
52
51
  profile.toolbar.groups.forEach(group => {
53
52
  group.options.forEach(option => {
54
53
  switch (option.type) {
55
54
  case 'mark':
56
- if (!simplifiedProfile.marks.includes(option.id)) {
57
- simplifiedProfile.marks.push(option.id);
55
+ if (!marks.includes(option.id)) {
56
+ marks.push(option.id);
58
57
  }
59
58
  break;
60
59
  case 'block':
61
- if (!simplifiedProfile.blocks.includes(option.id)) {
62
- simplifiedProfile.blocks.push(option.id);
60
+ if (!blocks.includes(option.id)) {
61
+ blocks.push(option.id);
63
62
  }
64
63
  break;
65
64
  case 'alignment':
66
- if (!simplifiedProfile.alignment.includes(option.id)) {
67
- simplifiedProfile.alignment.push(option.id);
65
+ if (!alignment.includes(option.id)) {
66
+ alignment.push(option.id);
68
67
  }
69
68
  break;
70
69
  case 'list':
71
- simplifiedProfile.lists = true;
70
+ lists = true;
72
71
  break;
73
72
  case 'link':
74
- simplifiedProfile.links = true;
73
+ links = true;
75
74
  break;
76
75
  }
77
76
  });
78
77
  });
79
78
 
80
79
  // Ensure we have at least one block type
81
- if (simplifiedProfile.blocks.length === 0) {
82
- simplifiedProfile.blocks.push('paragraph');
80
+ if (blocks.length === 0) {
81
+ blocks.push('paragraph');
83
82
  }
84
83
 
85
- return simplifiedProfile;
84
+ // Construct the final object
85
+ return {
86
+ marks,
87
+ blocks,
88
+ alignment,
89
+ lists,
90
+ links
91
+ };
86
92
  };
87
93
 
88
94
  /**
@@ -31,7 +31,7 @@ export function useRichTextProfile(
31
31
  });
32
32
 
33
33
  useEffect(() => {
34
- if (!profilePath) {
34
+ if (profilePath === undefined) {
35
35
  setState({
36
36
  profile: null,
37
37
  loading: false,
@@ -5,20 +5,71 @@ import { Link } from "../../LinkEditorDialog";
5
5
  import { HistoryEditor } from "slate-history";
6
6
  import { Field } from "../../pageModel";
7
7
 
8
+ // Strict type definitions for mark IDs
9
+ export type MarkId = keyof typeof SLATE_MARKS;
10
+ export type BlockId = keyof typeof SLATE_BLOCKS;
11
+ export type AlignmentId = keyof typeof SLATE_ALIGNMENTS;
12
+
13
+ // Strict toolbar option types
14
+ export type MarkOptionConfig = {
15
+ readonly type: "mark";
16
+ readonly id: MarkId;
17
+ };
18
+
19
+ export type BlockOptionConfig = {
20
+ readonly type: "block";
21
+ readonly id: BlockId;
22
+ };
23
+
24
+ export type AlignmentOptionConfig = {
25
+ readonly type: "alignment";
26
+ readonly id: AlignmentId;
27
+ };
28
+
29
+ export type LinkOptionConfig = {
30
+ readonly type: "link";
31
+ readonly id: "link";
32
+ };
33
+
34
+ export type ListOptionConfig = {
35
+ readonly type: "list";
36
+ readonly id: "unordered-list" | "ordered-list";
37
+ };
38
+
39
+ export type DividerOptionConfig = {
40
+ readonly type: "divider";
41
+ readonly id: "divider";
42
+ };
43
+
44
+ export type InsertionOptionConfig = {
45
+ readonly type: "insertion";
46
+ readonly id: "horizontal-rule";
47
+ };
48
+
49
+ // Union type for all possible toolbar options
50
+ export type ToolbarOptionConfig =
51
+ | MarkOptionConfig
52
+ | BlockOptionConfig
53
+ | AlignmentOptionConfig
54
+ | LinkOptionConfig
55
+ | ListOptionConfig
56
+ | DividerOptionConfig
57
+ | InsertionOptionConfig;
58
+
8
59
  declare module "slate" {
9
60
  interface CustomTypes {
10
61
  Editor: BaseEditor &
11
62
  ReactEditor &
12
63
  HistoryEditor & {
13
- // Plugin methods
14
- isMarkActive(format: string): boolean;
15
- toggleMark(format: string): void;
64
+ // Plugin methods with strict typing
65
+ isMarkActive(format: MarkId): boolean;
66
+ toggleMark(format: MarkId): void;
16
67
 
17
- isBlockActive(format: string): boolean;
18
- toggleBlock(format: string): void;
68
+ isBlockActive(format: BlockId): boolean;
69
+ toggleBlock(format: BlockId): void;
19
70
 
20
- isAlignActive(align: string): boolean;
21
- toggleAlign(align: string): void;
71
+ isAlignActive(align: Alignment): boolean;
72
+ toggleAlign(align: Alignment): void;
22
73
 
23
74
  isLinkActive(): boolean;
24
75
  insertLink(options?: {
@@ -29,132 +80,134 @@ declare module "slate" {
29
80
  toggleList(listType: "unordered" | "ordered"): void;
30
81
  indentList(): void;
31
82
  outdentList(): void;
83
+
84
+ insertHorizontalRule(): void;
32
85
  };
33
86
  Element: CustomElement;
34
87
  Text: CustomText;
35
88
  }
36
89
  }
37
90
 
38
- // Custom text type
91
+ // Custom text type with strict mark typing
39
92
  export type CustomText = {
40
- text: string;
41
- bold?: boolean;
42
- italic?: boolean;
43
- underline?: boolean;
44
- strikethrough?: boolean;
45
- subscript?: boolean;
46
- superscript?: boolean;
47
- extrabold?: boolean;
48
- isRawHtml?: boolean; // Flag to indicate this text should be preserved as raw HTML
93
+ readonly text: string;
94
+ // Mark properties - all optional booleans
95
+ readonly bold?: boolean;
96
+ readonly italic?: boolean;
97
+ readonly underline?: boolean;
98
+ readonly strikethrough?: boolean;
99
+ readonly subscript?: boolean;
100
+ readonly superscript?: boolean;
101
+ readonly extrabold?: boolean;
102
+ readonly isRawHtml?: boolean; // Flag to indicate this text should be preserved as raw HTML
49
103
  };
50
104
 
51
105
  export type Alignment = "left" | "center" | "right" | "justify";
52
106
 
53
107
  // Generic element that can have any type
54
108
  export type CustomElement = {
55
- type: string;
56
- align?: Alignment;
57
- listType?: "unordered" | "ordered";
58
- indent?: number;
59
- children: (CustomText | CustomElement)[];
60
- [key: string]: any; // For any custom properties
109
+ readonly type: string;
110
+ readonly align?: Alignment;
111
+ readonly listType?: "unordered" | "ordered";
112
+ readonly indent?: number;
113
+ readonly children: readonly (CustomText | CustomElement)[];
114
+ readonly [key: string]: any; // For any custom properties
61
115
  };
62
116
 
63
117
  // Preserved element type for unsupported HTML elements
64
118
  export type PreservedElement = CustomElement & {
65
- type: "preserved-element";
66
- originalTag: string;
67
- originalHtml: string;
68
- attributes: Record<string, any>;
119
+ readonly type: "preserved-element";
120
+ readonly originalTag: string;
121
+ readonly originalHtml: string;
122
+ readonly attributes: Record<string, any>;
69
123
  };
70
124
 
71
125
  // Preserved inline element type for unsupported inline HTML elements
72
126
  export type PreservedInlineElement = CustomElement & {
73
- type: "preserved-inline";
74
- originalTag: string;
75
- originalHtml: string;
76
- attributes: Record<string, any>;
127
+ readonly type: "preserved-inline";
128
+ readonly originalTag: string;
129
+ readonly originalHtml: string;
130
+ readonly attributes: Record<string, any>;
77
131
  };
78
132
 
79
133
  export type LinkElement = CustomElement & {
80
- type: "link";
81
- link: Link;
134
+ readonly type: "link";
135
+ readonly link: Link;
82
136
  };
83
137
 
84
138
  export interface ReactSlateProps {
85
- value?: string;
86
- onChange?: (value: string) => void;
87
- onFocus?: () => void;
88
- onBlur?: () => void;
89
- readOnly?: boolean;
90
- placeholder?: string;
91
- className?: string;
92
- profile?: RichTextEditorProfile;
139
+ readonly value?: string;
140
+ readonly onChange?: (value: string) => void;
141
+ readonly onFocus?: () => void;
142
+ readonly onBlur?: () => void;
143
+ readonly readOnly?: boolean;
144
+ readonly placeholder?: string;
145
+ readonly className?: string;
146
+ readonly profile?: RichTextEditorProfile;
93
147
  }
94
148
 
149
+ // Strict option interface
95
150
  export interface CustomOption {
96
- id: string;
97
- label?: string;
98
- icon?: React.ReactNode;
99
- isActive?: (editor: Editor) => boolean;
100
- toggle?: (editor: Editor, event: React.MouseEvent) => void;
151
+ readonly id: string;
152
+ readonly label?: string;
153
+ readonly icon?: React.ReactNode;
154
+ readonly isActive?: (editor: Editor) => boolean;
155
+ readonly toggle?: (editor: Editor, event: React.MouseEvent) => void;
101
156
  }
102
157
 
103
- export type DropdownOption<T> = {
104
- value: T;
105
- label?: string;
106
- icon?: React.ReactNode;
107
- style?: React.CSSProperties;
108
- isActive: (editor: Editor) => boolean;
109
- onSelect: (editor: Editor, event: React.MouseEvent) => void;
158
+ // Strict dropdown option type
159
+ export type DropdownOption<T = CustomOption> = {
160
+ readonly value: T;
161
+ readonly label?: string;
162
+ readonly icon?: React.ReactNode;
163
+ readonly style?: React.CSSProperties;
164
+ readonly isActive: (editor: Editor) => boolean;
165
+ readonly onSelect: (editor: Editor, event: React.MouseEvent) => void;
110
166
  };
111
167
 
112
168
  export interface ToolbarButtonProps {
113
- icon: React.ReactNode;
114
- active: boolean;
115
- onMouseDown?: (event: React.MouseEvent<HTMLButtonElement>) => void;
169
+ readonly icon: React.ReactNode;
170
+ readonly active: boolean;
171
+ readonly onMouseDown?: (event: React.MouseEvent<HTMLButtonElement>) => void;
116
172
  }
117
173
 
174
+ // Strict toolbar group configuration
118
175
  export interface ToolbarGroupConfig {
119
- id: string;
120
- label?: string;
121
- display?: "buttons" | "dropdown";
122
- showIconsOnly?: boolean;
123
- options: ToolbarOptionConfig[];
124
- row?: number; // Optional row identifier for grouping
125
- }
126
-
127
- export interface ToolbarOptionConfig {
128
- type: "mark" | "block" | "alignment" | "link" | "list" | "divider" | string;
129
- id: string; // Reference to the option in the corresponding collection
176
+ readonly id: string;
177
+ readonly label?: string;
178
+ readonly display?: "buttons" | "dropdown";
179
+ readonly showIconsOnly?: boolean;
180
+ readonly options: readonly ToolbarOptionConfig[];
181
+ readonly row?: number; // Optional row identifier for grouping
130
182
  }
131
183
 
184
+ // Strict profile types
132
185
  export type RichTextEditorProfile = {
133
- toolbar: {
134
- groups: ToolbarGroupConfig[];
186
+ readonly toolbar: {
187
+ readonly groups: readonly ToolbarGroupConfig[];
135
188
  };
136
189
  };
137
190
 
138
191
  // Simplified profile structure that maps directly to Slate capabilities
139
192
  export interface SimplifiedProfile {
140
- marks: string[]; // ['bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'extrabold']
141
- blocks: string[]; // ['paragraph', 'no-tag', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6']
142
- alignment: string[]; // ['left', 'center', 'right', 'justify']
143
- lists: boolean; // true if lists are enabled
144
- links: boolean; // true if links are enabled
193
+ readonly marks: readonly MarkId[]; // Strict typing for marks
194
+ readonly blocks: readonly BlockId[]; // Strict typing for blocks
195
+ readonly alignment: readonly AlignmentId[]; // Strict typing for alignment
196
+ readonly lists: boolean; // true if lists are enabled
197
+ readonly links: boolean; // true if links are enabled
145
198
  }
146
199
 
147
- // Built-in Slate mark configurations
148
- export const SLATE_MARKS: Record<
149
- string,
150
- {
151
- hotkey?: string;
152
- htmlTag: string;
153
- label: string;
154
- icon: string;
155
- renderHtml: (text: string) => string;
156
- }
157
- > = {
200
+ // Conversion result types for better error handling
201
+ export type HtmlToSlateResult =
202
+ | { readonly success: true; readonly data: import("slate").Descendant[] }
203
+ | { readonly success: false; readonly error: Error; readonly fallback: import("slate").Descendant[] };
204
+
205
+ export type SlateToHtmlResult =
206
+ | { readonly success: true; readonly data: string }
207
+ | { readonly success: false; readonly error: Error; readonly fallback: string };
208
+
209
+ // Built-in Slate mark configurations with strict typing
210
+ export const SLATE_MARKS = {
158
211
  bold: {
159
212
  hotkey: "mod+b",
160
213
  htmlTag: "strong",
@@ -200,18 +253,10 @@ export const SLATE_MARKS: Record<
200
253
  icon: "𝐄𝐁",
201
254
  renderHtml: (text: string) => `<span class="extrabold">${text}</span>`,
202
255
  },
203
- };
256
+ } as const;
204
257
 
205
- // Built-in Slate block configurations
206
- export const SLATE_BLOCKS: Record<
207
- string,
208
- {
209
- htmlTag: string;
210
- label: string;
211
- icon: string;
212
- renderHtml: (text: string, alignStyle: string) => string;
213
- }
214
- > = {
258
+ // Built-in Slate block configurations with strict typing
259
+ export const SLATE_BLOCKS = {
215
260
  paragraph: {
216
261
  htmlTag: "p",
217
262
  label: "Paragraph",
@@ -267,23 +312,16 @@ export const SLATE_BLOCKS: Record<
267
312
  renderHtml: (text: string, alignStyle: string) =>
268
313
  `<h6${alignStyle}>${text}</h6>`,
269
314
  },
270
- };
315
+ } as const;
271
316
 
272
- // Built-in alignment configurations
273
- export const SLATE_ALIGNMENTS: Record<
274
- string,
275
- {
276
- label: string;
277
- icon: string;
278
- value: Alignment;
279
- }
280
- > = {
281
- left: { label: "Align Left", icon: "←", value: "left" },
282
- center: { label: "Align Center", icon: "↔", value: "center" },
283
- right: { label: "Align Right", icon: "→", value: "right" },
284
- justify: { label: "Justify", icon: "≡", value: "justify" },
285
- };
317
+ // Built-in alignment configurations with strict typing
318
+ export const SLATE_ALIGNMENTS = {
319
+ left: { label: "Align Left", icon: "←", value: "left" as const },
320
+ center: { label: "Align Center", icon: "↔", value: "center" as const },
321
+ right: { label: "Align Right", icon: "→", value: "right" as const },
322
+ justify: { label: "Justify", icon: "≡", value: "justify" as const },
323
+ } as const;
286
324
 
287
325
  export type RichTextField = Field & {
288
- source: string;
326
+ readonly source: string;
289
327
  };