@akinon/ui-editor 1.0.5 → 1.0.7-rc.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.
Files changed (61) hide show
  1. package/dist/cjs/i18n/translations/en.d.ts +1 -0
  2. package/dist/cjs/i18n/translations/en.d.ts.map +1 -1
  3. package/dist/cjs/i18n/translations/en.js +1 -0
  4. package/dist/cjs/i18n/translations/tr.d.ts +1 -0
  5. package/dist/cjs/i18n/translations/tr.d.ts.map +1 -1
  6. package/dist/cjs/i18n/translations/tr.js +1 -0
  7. package/dist/cjs/tiptap/components/__tests__/html-code-viewer.test.js +0 -8
  8. package/dist/cjs/tiptap/components/__tests__/html-code-viewer.test.tsx +0 -11
  9. package/dist/cjs/tiptap/components/__tests__/tiptap.test.js +53 -12
  10. package/dist/cjs/tiptap/components/__tests__/tiptap.test.tsx +79 -13
  11. package/dist/cjs/tiptap/components/html-code-viewer.d.ts.map +1 -1
  12. package/dist/cjs/tiptap/components/html-code-viewer.js +1 -24
  13. package/dist/cjs/tiptap/components/menu-bar/__tests__/action-toolbar.test.js +7 -7
  14. package/dist/cjs/tiptap/components/menu-bar/__tests__/action-toolbar.test.tsx +7 -0
  15. package/dist/cjs/tiptap/components/menu-bar/__tests__/index.test.js +1 -0
  16. package/dist/cjs/tiptap/components/menu-bar/__tests__/index.test.tsx +1 -0
  17. package/dist/cjs/tiptap/components/menu-bar/action-toolbar.d.ts +2 -1
  18. package/dist/cjs/tiptap/components/menu-bar/action-toolbar.d.ts.map +1 -1
  19. package/dist/cjs/tiptap/components/menu-bar/action-toolbar.js +10 -2
  20. package/dist/cjs/tiptap/components/menu-bar/index.d.ts +2 -1
  21. package/dist/cjs/tiptap/components/menu-bar/index.d.ts.map +1 -1
  22. package/dist/cjs/tiptap/components/menu-bar/index.js +2 -2
  23. package/dist/cjs/tiptap/components/tiptap.d.ts.map +1 -1
  24. package/dist/cjs/tiptap/components/tiptap.js +23 -9
  25. package/dist/cjs/tiptap/constants/editor-config.d.ts +1 -0
  26. package/dist/cjs/tiptap/constants/editor-config.d.ts.map +1 -1
  27. package/dist/cjs/tiptap/constants/editor-config.js +1 -0
  28. package/dist/cjs/tiptap/utils/__tests__/clean-html.test.js +26 -7
  29. package/dist/cjs/tiptap/utils/__tests__/clean-html.test.ts +29 -7
  30. package/dist/cjs/tiptap/utils/prettier-format.js +1 -1
  31. package/dist/esm/i18n/translations/en.d.ts +1 -0
  32. package/dist/esm/i18n/translations/en.d.ts.map +1 -1
  33. package/dist/esm/i18n/translations/en.js +1 -0
  34. package/dist/esm/i18n/translations/tr.d.ts +1 -0
  35. package/dist/esm/i18n/translations/tr.d.ts.map +1 -1
  36. package/dist/esm/i18n/translations/tr.js +1 -0
  37. package/dist/esm/tiptap/components/__tests__/html-code-viewer.test.js +0 -8
  38. package/dist/esm/tiptap/components/__tests__/html-code-viewer.test.tsx +0 -11
  39. package/dist/esm/tiptap/components/__tests__/tiptap.test.js +53 -12
  40. package/dist/esm/tiptap/components/__tests__/tiptap.test.tsx +79 -13
  41. package/dist/esm/tiptap/components/html-code-viewer.d.ts.map +1 -1
  42. package/dist/esm/tiptap/components/html-code-viewer.js +1 -24
  43. package/dist/esm/tiptap/components/menu-bar/__tests__/action-toolbar.test.js +7 -7
  44. package/dist/esm/tiptap/components/menu-bar/__tests__/action-toolbar.test.tsx +7 -0
  45. package/dist/esm/tiptap/components/menu-bar/__tests__/index.test.js +1 -0
  46. package/dist/esm/tiptap/components/menu-bar/__tests__/index.test.tsx +1 -0
  47. package/dist/esm/tiptap/components/menu-bar/action-toolbar.d.ts +2 -1
  48. package/dist/esm/tiptap/components/menu-bar/action-toolbar.d.ts.map +1 -1
  49. package/dist/esm/tiptap/components/menu-bar/action-toolbar.js +10 -2
  50. package/dist/esm/tiptap/components/menu-bar/index.d.ts +2 -1
  51. package/dist/esm/tiptap/components/menu-bar/index.d.ts.map +1 -1
  52. package/dist/esm/tiptap/components/menu-bar/index.js +2 -2
  53. package/dist/esm/tiptap/components/tiptap.d.ts.map +1 -1
  54. package/dist/esm/tiptap/components/tiptap.js +23 -9
  55. package/dist/esm/tiptap/constants/editor-config.d.ts +1 -0
  56. package/dist/esm/tiptap/constants/editor-config.d.ts.map +1 -1
  57. package/dist/esm/tiptap/constants/editor-config.js +1 -0
  58. package/dist/esm/tiptap/utils/__tests__/clean-html.test.js +26 -7
  59. package/dist/esm/tiptap/utils/__tests__/clean-html.test.ts +29 -7
  60. package/dist/esm/tiptap/utils/prettier-format.js +1 -1
  61. package/package.json +12 -12
@@ -33,45 +33,45 @@ describe('ActionToolbar', () => {
33
33
  });
34
34
  });
35
35
  it('should render all action buttons', () => {
36
- render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState(), isSourceMode: false, toggleSource: vi.fn() }));
36
+ render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState(), isSourceMode: false, toggleSource: vi.fn(), onFormat: vi.fn() }));
37
37
  expect(screen.getByTestId('action-button-format-undo')).toBeInTheDocument();
38
38
  expect(screen.getByTestId('action-button-format-redo')).toBeInTheDocument();
39
39
  expect(screen.getByTestId('action-button-eye-open')).toBeInTheDocument();
40
40
  });
41
41
  it('should handle undo click', () => {
42
42
  const focusSpy = vi.spyOn(editor, 'chain');
43
- render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState({ canUndo: true }), isSourceMode: false, toggleSource: vi.fn() }));
43
+ render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState({ canUndo: true }), isSourceMode: false, toggleSource: vi.fn(), onFormat: vi.fn() }));
44
44
  const undoButton = screen.getByTestId('action-button-format-undo');
45
45
  fireEvent.click(undoButton);
46
46
  expect(focusSpy).toHaveBeenCalled();
47
47
  });
48
48
  it('should handle redo click', () => {
49
49
  const focusSpy = vi.spyOn(editor, 'chain');
50
- render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState({ canRedo: true }), isSourceMode: false, toggleSource: vi.fn() }));
50
+ render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState({ canRedo: true }), isSourceMode: false, toggleSource: vi.fn(), onFormat: vi.fn() }));
51
51
  const redoButton = screen.getByTestId('action-button-format-redo');
52
52
  fireEvent.click(redoButton);
53
53
  expect(focusSpy).toHaveBeenCalled();
54
54
  });
55
55
  it('should handle toggle source click', () => {
56
56
  const toggleSource = vi.fn();
57
- render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState(), isSourceMode: false, toggleSource: toggleSource }));
57
+ render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState(), isSourceMode: false, toggleSource: toggleSource, onFormat: vi.fn() }));
58
58
  const sourceButton = screen.getByTestId('action-button-eye-open');
59
59
  fireEvent.click(sourceButton);
60
60
  expect(toggleSource).toHaveBeenCalled();
61
61
  });
62
62
  it('should disable undo and redo when in source mode', () => {
63
- render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState({ canUndo: true, canRedo: true }), isSourceMode: true, toggleSource: vi.fn() }));
63
+ render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState({ canUndo: true, canRedo: true }), isSourceMode: true, toggleSource: vi.fn(), onFormat: vi.fn() }));
64
64
  expect(screen.getByTestId('action-button-format-undo')).toBeDisabled();
65
65
  expect(screen.getByTestId('action-button-format-redo')).toBeDisabled();
66
66
  });
67
67
  it('should show eye-close icon and normal mode tooltip when isSourceMode is true', () => {
68
- render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState(), isSourceMode: true, toggleSource: vi.fn() }));
68
+ render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState(), isSourceMode: true, toggleSource: vi.fn(), onFormat: vi.fn() }));
69
69
  const sourceButton = screen.getByTestId('action-button-eye-close');
70
70
  expect(sourceButton).toBeInTheDocument();
71
71
  expect(sourceButton).toHaveAttribute('aria-label', 'Normal Mode');
72
72
  });
73
73
  it('should apply active class to source mode button when active', () => {
74
- render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState(), isSourceMode: true, toggleSource: vi.fn() }));
74
+ render(React.createElement(ActionToolbar, { editor: editor, editorState: createEditorState(), isSourceMode: true, toggleSource: vi.fn(), onFormat: vi.fn() }));
75
75
  const sourceButton = screen.getByTestId('action-button-eye-close');
76
76
  expect(sourceButton).toHaveClass('active');
77
77
  });
@@ -105,6 +105,7 @@ describe('ActionToolbar', () => {
105
105
  editorState={createEditorState()}
106
106
  isSourceMode={false}
107
107
  toggleSource={vi.fn()}
108
+ onFormat={vi.fn()}
108
109
  />
109
110
  );
110
111
 
@@ -122,6 +123,7 @@ describe('ActionToolbar', () => {
122
123
  editorState={createEditorState({ canUndo: true })}
123
124
  isSourceMode={false}
124
125
  toggleSource={vi.fn()}
126
+ onFormat={vi.fn()}
125
127
  />
126
128
  );
127
129
 
@@ -140,6 +142,7 @@ describe('ActionToolbar', () => {
140
142
  editorState={createEditorState({ canRedo: true })}
141
143
  isSourceMode={false}
142
144
  toggleSource={vi.fn()}
145
+ onFormat={vi.fn()}
143
146
  />
144
147
  );
145
148
 
@@ -158,6 +161,7 @@ describe('ActionToolbar', () => {
158
161
  editorState={createEditorState()}
159
162
  isSourceMode={false}
160
163
  toggleSource={toggleSource}
164
+ onFormat={vi.fn()}
161
165
  />
162
166
  );
163
167
 
@@ -174,6 +178,7 @@ describe('ActionToolbar', () => {
174
178
  editorState={createEditorState({ canUndo: true, canRedo: true })}
175
179
  isSourceMode={true}
176
180
  toggleSource={vi.fn()}
181
+ onFormat={vi.fn()}
177
182
  />
178
183
  );
179
184
 
@@ -188,6 +193,7 @@ describe('ActionToolbar', () => {
188
193
  editorState={createEditorState()}
189
194
  isSourceMode={true}
190
195
  toggleSource={vi.fn()}
196
+ onFormat={vi.fn()}
191
197
  />
192
198
  );
193
199
 
@@ -203,6 +209,7 @@ describe('ActionToolbar', () => {
203
209
  editorState={createEditorState()}
204
210
  isSourceMode={true}
205
211
  toggleSource={vi.fn()}
212
+ onFormat={vi.fn()}
206
213
  />
207
214
  );
208
215
 
@@ -89,6 +89,7 @@ describe('MenuBar', () => {
89
89
  const defaultProps = {
90
90
  isSourceMode: false,
91
91
  toggleSource: vi.fn(),
92
+ onFormat: vi.fn(),
92
93
  onSave: vi.fn()
93
94
  };
94
95
  beforeEach(() => {
@@ -99,6 +99,7 @@ describe('MenuBar', () => {
99
99
  const defaultProps = {
100
100
  isSourceMode: false,
101
101
  toggleSource: vi.fn(),
102
+ onFormat: vi.fn(),
102
103
  onSave: vi.fn()
103
104
  };
104
105
 
@@ -7,9 +7,10 @@ interface ActionToolbarProps {
7
7
  editorState: EditorState;
8
8
  isSourceMode: boolean;
9
9
  toggleSource: () => void;
10
+ onFormat: () => Promise<void>;
10
11
  disabled?: boolean;
11
12
  hiddenButtons?: Array<ToolbarButtonId>;
12
13
  }
13
- export declare const ActionToolbar: ({ editor, editorState, isSourceMode, toggleSource, disabled, hiddenButtons }: ActionToolbarProps) => React.JSX.Element;
14
+ export declare const ActionToolbar: ({ editor, editorState, isSourceMode, toggleSource, onFormat, disabled, hiddenButtons }: ActionToolbarProps) => React.JSX.Element;
14
15
  export {};
15
16
  //# sourceMappingURL=action-toolbar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"action-toolbar.d.ts","sourceRoot":"","sources":["../../../../../src/tiptap/components/menu-bar/action-toolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhE,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACxC;AAYD,eAAO,MAAM,aAAa,GAAI,8EAO3B,kBAAkB,sBAmDpB,CAAC"}
1
+ {"version":3,"file":"action-toolbar.d.ts","sourceRoot":"","sources":["../../../../../src/tiptap/components/menu-bar/action-toolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhE,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACxC;AAYD,eAAO,MAAM,aAAa,GAAI,wFAQ3B,kBAAkB,sBA6DpB,CAAC"}
@@ -3,7 +3,7 @@ import { Button } from '@akinon/ui-button';
3
3
  import React from 'react';
4
4
  import { i18n } from '../../../i18n';
5
5
  import { TOOLBAR_BUTTON_IDS } from '../../constants';
6
- export const ActionToolbar = ({ editor, editorState, isSourceMode, toggleSource, disabled = false, hiddenButtons = [] }) => {
6
+ export const ActionToolbar = ({ editor, editorState, isSourceMode, toggleSource, onFormat, disabled = false, hiddenButtons = [] }) => {
7
7
  const activeCls = 'active';
8
8
  const actionButtons = [
9
9
  {
@@ -23,8 +23,16 @@ export const ActionToolbar = ({ editor, editorState, isSourceMode, toggleSource,
23
23
  disabled: disabled || isSourceMode || !editorState.canRedo
24
24
  },
25
25
  {
26
- id: TOOLBAR_BUTTON_IDS.SOURCE_MODE,
26
+ id: TOOLBAR_BUTTON_IDS.FORMAT,
27
27
  key: 3,
28
+ icon: 'format-beautify',
29
+ tooltip: i18n.t('editor.toolbar.format'),
30
+ onClick: onFormat,
31
+ disabled: !isSourceMode
32
+ },
33
+ {
34
+ id: TOOLBAR_BUTTON_IDS.SOURCE_MODE,
35
+ key: 4,
28
36
  icon: isSourceMode ? 'eye-close' : 'eye-open',
29
37
  tooltip: isSourceMode
30
38
  ? i18n.t('editor.toolbar.normal_mode')
@@ -5,13 +5,14 @@ interface MenuBarProps {
5
5
  editor: Editor;
6
6
  isSourceMode: boolean;
7
7
  toggleSource: () => void;
8
+ onFormat: () => Promise<void>;
8
9
  onSave?: () => void;
9
10
  className?: string;
10
11
  toolbar?: ToolbarConfig;
11
12
  readOnly?: boolean;
12
13
  }
13
14
  export declare const MenuBar: {
14
- ({ editor, isSourceMode, toggleSource, className, toolbar, readOnly }: MenuBarProps): React.JSX.Element;
15
+ ({ editor, isSourceMode, toggleSource, onFormat, className, toolbar, readOnly }: MenuBarProps): React.JSX.Element;
15
16
  displayName: string;
16
17
  };
17
18
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/tiptap/components/menu-bar/index.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,aAAa,CAAC;AAS9D,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,OAAO;2EAOjB,YAAY;;CAqMd,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/tiptap/components/menu-bar/index.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,aAAa,CAAC;AAS9D,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,OAAO;qFAQjB,YAAY;;CAsMd,CAAC"}
@@ -12,7 +12,7 @@ import { LinkPopover } from './link-popover';
12
12
  import { ListAndBlockToolbar } from './list-and-block-toolbar';
13
13
  import { MediaToolbar } from './media-toolbar';
14
14
  import { TextFormattingToolbar } from './text-formatting-toolbar';
15
- export const MenuBar = ({ editor, isSourceMode, toggleSource, className, toolbar, readOnly = false }) => {
15
+ export const MenuBar = ({ editor, isSourceMode, toggleSource, onFormat, className, toolbar, readOnly = false }) => {
16
16
  var _a, _b, _c, _d, _e, _f, _g;
17
17
  const { getPrefixCls, theme } = React.useContext(ConfigProvider.ConfigContext);
18
18
  const { token, hashId } = useToken();
@@ -126,6 +126,6 @@ export const MenuBar = ({ editor, isSourceMode, toggleSource, className, toolbar
126
126
  !((_d = toolbar === null || toolbar === void 0 ? void 0 : toolbar.hiddenGroups) === null || _d === void 0 ? void 0 : _d.includes(TOOLBAR_GROUP_IDS.LINK)) && (React.createElement(LinkPopover, { editor: editor, editorState: editorState, disabled: isDisabled })),
127
127
  !((_e = toolbar === null || toolbar === void 0 ? void 0 : toolbar.hiddenGroups) === null || _e === void 0 ? void 0 : _e.includes(TOOLBAR_GROUP_IDS.MEDIA)) && (React.createElement(MediaToolbar, { editor: editor, editorState: editorState, disabled: isDisabled, hiddenButtons: toolbar === null || toolbar === void 0 ? void 0 : toolbar.hiddenButtons })),
128
128
  !((_f = toolbar === null || toolbar === void 0 ? void 0 : toolbar.hiddenGroups) === null || _f === void 0 ? void 0 : _f.includes(TOOLBAR_GROUP_IDS.ALIGNMENT)) && (React.createElement(AlignmentToolbar, { editor: editor, editorState: editorState, disabled: isDisabled })),
129
- !((_g = toolbar === null || toolbar === void 0 ? void 0 : toolbar.hiddenGroups) === null || _g === void 0 ? void 0 : _g.includes(TOOLBAR_GROUP_IDS.HISTORY)) && (React.createElement(ActionToolbar, { editor: editor, editorState: editorState, isSourceMode: isSourceMode, toggleSource: toggleSource, disabled: readOnly, hiddenButtons: toolbar === null || toolbar === void 0 ? void 0 : toolbar.hiddenButtons })))));
129
+ !((_g = toolbar === null || toolbar === void 0 ? void 0 : toolbar.hiddenGroups) === null || _g === void 0 ? void 0 : _g.includes(TOOLBAR_GROUP_IDS.HISTORY)) && (React.createElement(ActionToolbar, { editor: editor, editorState: editorState, isSourceMode: isSourceMode, toggleSource: toggleSource, onFormat: onFormat, disabled: readOnly, hiddenButtons: toolbar === null || toolbar === void 0 ? void 0 : toolbar.hiddenButtons })))));
130
130
  };
131
131
  MenuBar.displayName = 'MenuBar';
@@ -1 +1 @@
1
- {"version":3,"file":"tiptap.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/components/tiptap.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAQvE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAOhD,eAAO,MAAM,MAAM,GAAI,iHAUpB,eAAe,sBAiZjB,CAAC"}
1
+ {"version":3,"file":"tiptap.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/components/tiptap.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAQvE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAWhD,eAAO,MAAM,MAAM,GAAI,iHAUpB,eAAe,sBAmajB,CAAC"}
@@ -8,7 +8,7 @@ import clsx from 'clsx';
8
8
  import React, { useCallback, useEffect, useMemo, useRef } from 'react';
9
9
  import { DragHandleIcon } from '../assets/icons';
10
10
  import { EDITOR_CONTENT_HEIGHT, getTableCellDefaults, getTableHeaderDefaults } from '../constants';
11
- import { cleanDuplicateSpans, getExtensions } from '../utils';
11
+ import { cleanDuplicateSpans, formatHtmlWithPrettier, getExtensions } from '../utils';
12
12
  import { FormatBubbleMenu } from './format-bubble-menu';
13
13
  import { HtmlCodeViewer } from './html-code-viewer';
14
14
  import { MenuBar } from './menu-bar';
@@ -47,19 +47,27 @@ export const Tiptap = ({ value, defaultValue, onChange, readOnly = false, classN
47
47
  const editor = useEditor({
48
48
  editable: !readOnly,
49
49
  content: defaultValue !== undefined ? defaultValue : value,
50
- onUpdate: ({ editor }) => {
50
+ extensions,
51
+ editorProps
52
+ });
53
+ useEffect(() => {
54
+ if (!editor)
55
+ return;
56
+ const handleUpdate = () => {
51
57
  var _a;
52
58
  (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, {
53
59
  html: editor.getHTML(),
54
60
  json: editor.getJSON()
55
61
  });
56
- },
57
- extensions,
58
- editorProps
59
- });
62
+ };
63
+ editor.on('update', handleUpdate);
64
+ return () => {
65
+ editor.off('update', handleUpdate);
66
+ };
67
+ }, [editor]);
60
68
  useEffect(() => {
61
69
  if (editor) {
62
- editor.setEditable(!readOnly);
70
+ editor.setEditable(!readOnly, false);
63
71
  }
64
72
  }, [editor, readOnly]);
65
73
  useEffect(() => {
@@ -67,7 +75,7 @@ export const Tiptap = ({ value, defaultValue, onChange, readOnly = false, classN
67
75
  value !== undefined &&
68
76
  value !== prevValueRef.current &&
69
77
  value !== editor.getHTML()) {
70
- editor.commands.setContent(value);
78
+ editor.commands.setContent(value, { emitUpdate: false });
71
79
  if (isSourceMode) {
72
80
  setRawHTML(value);
73
81
  }
@@ -127,6 +135,12 @@ export const Tiptap = ({ value, defaultValue, onChange, readOnly = false, classN
127
135
  }
128
136
  };
129
137
  }, []);
138
+ const handleFormat = useCallback(async () => {
139
+ const formatted = await formatHtmlWithPrettier(rawHTML);
140
+ if (formatted !== rawHTML) {
141
+ setRawHTML(formatted);
142
+ }
143
+ }, [rawHTML]);
130
144
  const useStyle = useStyleRegister({
131
145
  token: token,
132
146
  path: ['TiptapEditor', String(contentHeight)],
@@ -330,7 +344,7 @@ export const Tiptap = ({ value, defaultValue, onChange, readOnly = false, classN
330
344
  });
331
345
  return useStyle(React.createElement("div", { className: `${baseCls} ${hashId}` },
332
346
  editor && (React.createElement(React.Fragment, null,
333
- React.createElement(MenuBar, { isSourceMode: isSourceMode, toggleSource: toggleSource, editor: editor, onSave: getCleanHtml, className: classNames === null || classNames === void 0 ? void 0 : classNames.editorToolbar, toolbar: toolbar, readOnly: readOnly }),
347
+ React.createElement(MenuBar, { isSourceMode: isSourceMode, toggleSource: toggleSource, onFormat: handleFormat, editor: editor, onSave: getCleanHtml, className: classNames === null || classNames === void 0 ? void 0 : classNames.editorToolbar, toolbar: toolbar, readOnly: readOnly }),
334
348
  React.createElement(FormatBubbleMenu, { editor: editor }),
335
349
  React.createElement(TableBubbleMenu, { editor: editor }),
336
350
  React.createElement(DragHandle, { editor: editor },
@@ -66,6 +66,7 @@ export declare const TOOLBAR_BUTTON_IDS: {
66
66
  readonly HEADING: "heading";
67
67
  readonly UNDO: "undo";
68
68
  readonly REDO: "redo";
69
+ readonly FORMAT: "format";
69
70
  readonly SOURCE_MODE: "sourceMode";
70
71
  readonly YOUTUBE: "youtube";
71
72
  };
@@ -1 +1 @@
1
- {"version":3,"file":"editor-config.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/constants/editor-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,eAAO,MAAM,aAAa,UAqBzB,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;EAMlB,CAAC;AAEX,eAAO,MAAM,UAAU;IAErB,SAAA,CAAC,aAAa,CAAC,EAAE,CAAC,KAAI;IACtB,SAAA,CAAC,aAAa,CAAC,EAAE,CAAC,KAAI;IACtB,SAAA,CAAC,aAAa,CAAC,EAAE,CAAC,KAAI;IACtB,SAAA,CAAC,aAAa,CAAC,EAAE,CAAC,KAAI;;CACd,CAAC;AAEX,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BrB,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;;;;;;CAQpB,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;;;CAK7B,CAAC;AAEF,eAAO,MAAM,mBAAmB,UAK/B,CAAC;AAEF,eAAO,MAAM,cAAc,uBAAwB,CAAC;AAEpD,eAAO,MAAM,gBAAgB,6BAG5B,CAAC;AAEF,eAAO,MAAM,WAAW;;;;sBAIE,MAAM,EAAE;yBACL,MAAM,EAAE;CACpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,MAAM,CAAC"}
1
+ {"version":3,"file":"editor-config.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/constants/editor-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,eAAO,MAAM,aAAa,UAqBzB,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;EAMlB,CAAC;AAEX,eAAO,MAAM,UAAU;IAErB,SAAA,CAAC,aAAa,CAAC,EAAE,CAAC,KAAI;IACtB,SAAA,CAAC,aAAa,CAAC,EAAE,CAAC,KAAI;IACtB,SAAA,CAAC,aAAa,CAAC,EAAE,CAAC,KAAI;IACtB,SAAA,CAAC,aAAa,CAAC,EAAE,CAAC,KAAI;;CACd,CAAC;AAEX,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BrB,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;;;;;;CAQpB,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;;;CAK7B,CAAC;AAEF,eAAO,MAAM,mBAAmB,UAK/B,CAAC;AAEF,eAAO,MAAM,cAAc,uBAAwB,CAAC;AAEpD,eAAO,MAAM,gBAAgB,6BAG5B,CAAC;AAEF,eAAO,MAAM,WAAW;;;;sBAIE,MAAM,EAAE;yBACL,MAAM,EAAE;CACpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,MAAM,CAAC"}
@@ -73,6 +73,7 @@ export const TOOLBAR_BUTTON_IDS = {
73
73
  HEADING: 'heading',
74
74
  UNDO: 'undo',
75
75
  REDO: 'redo',
76
+ FORMAT: 'format',
76
77
  SOURCE_MODE: 'sourceMode',
77
78
  YOUTUBE: 'youtube'
78
79
  };
@@ -77,17 +77,36 @@ describe('clean-html', () => {
77
77
  const result = cleanDuplicateSpans(html);
78
78
  expect(result).toBe('<div><script>console.log("test")</script>Content</div>');
79
79
  });
80
- it('should handle mixed content with text nodes', () => {
81
- // This test covers line 87: return node.textContent || '';
82
- // Testing text nodes between elements
80
+ it('should handle mixed content with text nodes inside elements', () => {
83
81
  const html = '<div>Start<p>Middle</p>End</div>';
84
82
  const result = cleanDuplicateSpans(html);
85
83
  expect(result).toBe('<div>Start<p>Middle</p>End</div>');
86
84
  });
87
- it('should handle empty text content fallback', () => {
88
- // Edge case: elements with only whitespace text nodes
89
- const html = '<div> </div>';
85
+ it('should handle top-level text nodes (covers line 87)', () => {
86
+ // text nodes at the root of template.content hit line 87
87
+ const html = 'plain text only';
90
88
  const result = cleanDuplicateSpans(html);
91
- expect(result).toBe('<div> </div>');
89
+ expect(result).toBe('plain text only');
90
+ });
91
+ it('should handle mixed top-level text nodes and elements', () => {
92
+ // "before " and " after" are top-level text nodes, hitting line 87
93
+ const html = 'before <div>element</div> after';
94
+ const result = cleanDuplicateSpans(html);
95
+ expect(result).toBe('before <div>element</div> after');
96
+ });
97
+ it("should handle empty text content fallback (|| '' branch)", () => {
98
+ // A whitespace-only top-level text node: textContent is ' ' (truthy)
99
+ // but tests the same code path; empty-string case uses the || '' fallback
100
+ const html = ' ';
101
+ const result = cleanDuplicateSpans(html);
102
+ expect(result).toBe(' ');
103
+ });
104
+ it("should return '' for top-level empty comment nodes (covers || '' branch on line 87)", () => {
105
+ // An empty HTML comment (<!---->) creates a top-level Comment node.
106
+ // Comment nodes are not ELEMENT_NODEs, so they hit line 87.
107
+ // Their textContent is '' (empty string / falsy), triggering the || '' fallback.
108
+ const html = '<!---->';
109
+ const result = cleanDuplicateSpans(html);
110
+ expect(result).toBe('');
92
111
  });
93
112
  });
@@ -112,18 +112,40 @@ describe('clean-html', () => {
112
112
  );
113
113
  });
114
114
 
115
- it('should handle mixed content with text nodes', () => {
116
- // This test covers line 87: return node.textContent || '';
117
- // Testing text nodes between elements
115
+ it('should handle mixed content with text nodes inside elements', () => {
118
116
  const html = '<div>Start<p>Middle</p>End</div>';
119
117
  const result = cleanDuplicateSpans(html);
120
118
  expect(result).toBe('<div>Start<p>Middle</p>End</div>');
121
119
  });
122
120
 
123
- it('should handle empty text content fallback', () => {
124
- // Edge case: elements with only whitespace text nodes
125
- const html = '<div> </div>';
121
+ it('should handle top-level text nodes (covers line 87)', () => {
122
+ // text nodes at the root of template.content hit line 87
123
+ const html = 'plain text only';
126
124
  const result = cleanDuplicateSpans(html);
127
- expect(result).toBe('<div> </div>');
125
+ expect(result).toBe('plain text only');
126
+ });
127
+
128
+ it('should handle mixed top-level text nodes and elements', () => {
129
+ // "before " and " after" are top-level text nodes, hitting line 87
130
+ const html = 'before <div>element</div> after';
131
+ const result = cleanDuplicateSpans(html);
132
+ expect(result).toBe('before <div>element</div> after');
133
+ });
134
+
135
+ it("should handle empty text content fallback (|| '' branch)", () => {
136
+ // A whitespace-only top-level text node: textContent is ' ' (truthy)
137
+ // but tests the same code path; empty-string case uses the || '' fallback
138
+ const html = ' ';
139
+ const result = cleanDuplicateSpans(html);
140
+ expect(result).toBe(' ');
141
+ });
142
+
143
+ it("should return '' for top-level empty comment nodes (covers || '' branch on line 87)", () => {
144
+ // An empty HTML comment (<!---->) creates a top-level Comment node.
145
+ // Comment nodes are not ELEMENT_NODEs, so they hit line 87.
146
+ // Their textContent is '' (empty string / falsy), triggering the || '' fallback.
147
+ const html = '<!---->';
148
+ const result = cleanDuplicateSpans(html);
149
+ expect(result).toBe('');
128
150
  });
129
151
  });
@@ -14,7 +14,7 @@ export const formatHtmlWithPrettier = async (html) => {
14
14
  printWidth: 60,
15
15
  tabWidth: 2,
16
16
  useTabs: false,
17
- htmlWhitespaceSensitivity: 'css',
17
+ htmlWhitespaceSensitivity: 'ignore',
18
18
  singleAttributePerLine: false,
19
19
  proseWrap: 'always'
20
20
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/ui-editor",
3
- "version": "1.0.5",
3
+ "version": "1.0.7-rc.0",
4
4
  "private": false,
5
5
  "description": "A headless lightweight text editor for Akinon.",
6
6
  "type": "module",
@@ -53,23 +53,23 @@
53
53
  "prettier": "^3.6.2",
54
54
  "y-protocols": "^1.0.7",
55
55
  "yjs": "^13.6.29",
56
- "@akinon/ui-button": "1.4.5",
57
- "@akinon/akilocale": "1.2.4",
58
- "@akinon/ui-color-picker": "1.1.4",
59
- "@akinon/ui-input": "1.2.4",
60
- "@akinon/ui-select": "1.4.5",
61
- "@akinon/ui-theme": "1.2.4",
62
- "@akinon/ui-upload": "1.4.5",
63
- "@akinon/ui-popover": "1.1.4",
64
- "@akinon/ui-dropdown": "1.1.4"
56
+ "@akinon/akilocale": "1.2.5",
57
+ "@akinon/ui-button": "1.4.7-rc.0",
58
+ "@akinon/ui-input": "1.2.5",
59
+ "@akinon/ui-dropdown": "1.1.5",
60
+ "@akinon/ui-popover": "1.1.5",
61
+ "@akinon/ui-color-picker": "1.1.5",
62
+ "@akinon/ui-select": "1.4.7-rc.0",
63
+ "@akinon/ui-theme": "1.2.5",
64
+ "@akinon/ui-upload": "1.4.7-rc.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "clean-package": "^2.2.0",
68
68
  "copyfiles": "^2.4.1",
69
69
  "rimraf": "^5.0.10",
70
70
  "typescript": "*",
71
- "@akinon/typescript-config": "1.1.4",
72
- "@akinon/vitest-config": "1.1.4"
71
+ "@akinon/typescript-config": "1.1.5",
72
+ "@akinon/vitest-config": "1.1.5"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "react": "^18 || ^19",