@akinon/ui-editor 1.0.3 → 1.0.5

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 (129) hide show
  1. package/dist/cjs/tiptap/components/__tests__/html-code-viewer.test.js +79 -3
  2. package/dist/cjs/tiptap/components/__tests__/html-code-viewer.test.tsx +106 -3
  3. package/dist/cjs/tiptap/components/__tests__/tiptap.test.js +42 -4
  4. package/dist/cjs/tiptap/components/__tests__/tiptap.test.tsx +57 -7
  5. package/dist/cjs/tiptap/components/format-bubble-menu.d.ts.map +1 -1
  6. package/dist/cjs/tiptap/components/format-bubble-menu.js +6 -5
  7. package/dist/cjs/tiptap/components/html-code-viewer.d.ts +2 -1
  8. package/dist/cjs/tiptap/components/html-code-viewer.d.ts.map +1 -1
  9. package/dist/cjs/tiptap/components/html-code-viewer.js +62 -22
  10. package/dist/cjs/tiptap/components/image-upload-placeholder.d.ts.map +1 -1
  11. package/dist/cjs/tiptap/components/image-upload-placeholder.js +5 -2
  12. package/dist/cjs/tiptap/components/secure-script-component.d.ts.map +1 -1
  13. package/dist/cjs/tiptap/components/secure-script-component.js +2 -5
  14. package/dist/cjs/tiptap/components/tiptap.d.ts +1 -1
  15. package/dist/cjs/tiptap/components/tiptap.d.ts.map +1 -1
  16. package/dist/cjs/tiptap/components/tiptap.js +84 -56
  17. package/dist/cjs/tiptap/constants/editor-config.d.ts +10 -1
  18. package/dist/cjs/tiptap/constants/editor-config.d.ts.map +1 -1
  19. package/dist/cjs/tiptap/constants/editor-config.js +11 -2
  20. package/dist/cjs/tiptap/constants/html-elements.d.ts +1 -0
  21. package/dist/cjs/tiptap/constants/html-elements.d.ts.map +1 -1
  22. package/dist/cjs/tiptap/constants/html-elements.js +2 -1
  23. package/dist/cjs/tiptap/extensions/comment-extension.d.ts.map +1 -1
  24. package/dist/cjs/tiptap/extensions/comment-extension.js +3 -3
  25. package/dist/cjs/tiptap/extensions/custom-table-cell-extension.d.ts.map +1 -1
  26. package/dist/cjs/tiptap/extensions/custom-table-cell-extension.js +11 -2
  27. package/dist/cjs/tiptap/extensions/custom-table-header-extension.d.ts.map +1 -1
  28. package/dist/cjs/tiptap/extensions/custom-table-header-extension.js +11 -2
  29. package/dist/cjs/tiptap/extensions/html-block-extension.d.ts.map +1 -1
  30. package/dist/cjs/tiptap/extensions/html-block-extension.js +11 -14
  31. package/dist/cjs/tiptap/extensions/html-inline-extension.d.ts.map +1 -1
  32. package/dist/cjs/tiptap/extensions/html-inline-extension.js +11 -14
  33. package/dist/cjs/tiptap/types/index.d.ts +12 -5
  34. package/dist/cjs/tiptap/types/index.d.ts.map +1 -1
  35. package/dist/cjs/tiptap/utils/__tests__/clean-html.test.d.ts +2 -0
  36. package/dist/cjs/tiptap/utils/__tests__/clean-html.test.d.ts.map +1 -0
  37. package/dist/cjs/tiptap/utils/__tests__/clean-html.test.js +93 -0
  38. package/dist/cjs/tiptap/utils/__tests__/clean-html.test.ts +129 -0
  39. package/dist/cjs/tiptap/utils/__tests__/data-attributes-helper.test.d.ts +2 -0
  40. package/dist/cjs/tiptap/utils/__tests__/data-attributes-helper.test.d.ts.map +1 -0
  41. package/dist/cjs/tiptap/utils/__tests__/data-attributes-helper.test.js +105 -0
  42. package/dist/cjs/tiptap/utils/__tests__/data-attributes-helper.test.ts +148 -0
  43. package/dist/cjs/tiptap/utils/__tests__/index.test.js +0 -2
  44. package/dist/cjs/tiptap/utils/__tests__/index.test.ts +0 -2
  45. package/dist/cjs/tiptap/utils/__tests__/prettier-format.test.d.ts +2 -0
  46. package/dist/cjs/tiptap/utils/__tests__/prettier-format.test.d.ts.map +1 -0
  47. package/dist/cjs/tiptap/utils/__tests__/prettier-format.test.js +33 -0
  48. package/dist/cjs/tiptap/utils/__tests__/prettier-format.test.ts +49 -0
  49. package/dist/cjs/tiptap/utils/__tests__/tiptap-extensions.test.js +157 -0
  50. package/dist/cjs/tiptap/utils/__tests__/tiptap-extensions.test.ts +234 -0
  51. package/dist/cjs/tiptap/utils/clean-html.d.ts +9 -0
  52. package/dist/cjs/tiptap/utils/clean-html.d.ts.map +1 -0
  53. package/dist/cjs/tiptap/utils/clean-html.js +75 -0
  54. package/dist/cjs/tiptap/utils/data-attributes-helper.d.ts +20 -0
  55. package/dist/cjs/tiptap/utils/data-attributes-helper.d.ts.map +1 -0
  56. package/dist/cjs/tiptap/utils/data-attributes-helper.js +68 -0
  57. package/dist/cjs/tiptap/utils/index.d.ts +3 -2
  58. package/dist/cjs/tiptap/utils/index.d.ts.map +1 -1
  59. package/dist/cjs/tiptap/utils/index.js +3 -2
  60. package/dist/cjs/tiptap/utils/prettier-format.d.ts +8 -0
  61. package/dist/cjs/tiptap/utils/prettier-format.d.ts.map +1 -0
  62. package/dist/cjs/tiptap/utils/prettier-format.js +26 -0
  63. package/dist/cjs/tiptap/utils/tiptap-extensions.d.ts.map +1 -1
  64. package/dist/cjs/tiptap/utils/tiptap-extensions.js +134 -14
  65. package/dist/esm/tiptap/components/__tests__/html-code-viewer.test.js +79 -3
  66. package/dist/esm/tiptap/components/__tests__/html-code-viewer.test.tsx +106 -3
  67. package/dist/esm/tiptap/components/__tests__/tiptap.test.js +42 -4
  68. package/dist/esm/tiptap/components/__tests__/tiptap.test.tsx +57 -7
  69. package/dist/esm/tiptap/components/format-bubble-menu.d.ts.map +1 -1
  70. package/dist/esm/tiptap/components/format-bubble-menu.js +6 -5
  71. package/dist/esm/tiptap/components/html-code-viewer.d.ts +2 -1
  72. package/dist/esm/tiptap/components/html-code-viewer.d.ts.map +1 -1
  73. package/dist/esm/tiptap/components/html-code-viewer.js +62 -22
  74. package/dist/esm/tiptap/components/image-upload-placeholder.d.ts.map +1 -1
  75. package/dist/esm/tiptap/components/image-upload-placeholder.js +5 -2
  76. package/dist/esm/tiptap/components/secure-script-component.d.ts.map +1 -1
  77. package/dist/esm/tiptap/components/secure-script-component.js +2 -5
  78. package/dist/esm/tiptap/components/tiptap.d.ts +1 -1
  79. package/dist/esm/tiptap/components/tiptap.d.ts.map +1 -1
  80. package/dist/esm/tiptap/components/tiptap.js +84 -56
  81. package/dist/esm/tiptap/constants/editor-config.d.ts +10 -1
  82. package/dist/esm/tiptap/constants/editor-config.d.ts.map +1 -1
  83. package/dist/esm/tiptap/constants/editor-config.js +11 -2
  84. package/dist/esm/tiptap/constants/html-elements.d.ts +1 -0
  85. package/dist/esm/tiptap/constants/html-elements.d.ts.map +1 -1
  86. package/dist/esm/tiptap/constants/html-elements.js +2 -1
  87. package/dist/esm/tiptap/extensions/comment-extension.d.ts.map +1 -1
  88. package/dist/esm/tiptap/extensions/comment-extension.js +3 -3
  89. package/dist/esm/tiptap/extensions/custom-table-cell-extension.d.ts.map +1 -1
  90. package/dist/esm/tiptap/extensions/custom-table-cell-extension.js +11 -2
  91. package/dist/esm/tiptap/extensions/custom-table-header-extension.d.ts.map +1 -1
  92. package/dist/esm/tiptap/extensions/custom-table-header-extension.js +11 -2
  93. package/dist/esm/tiptap/extensions/html-block-extension.d.ts.map +1 -1
  94. package/dist/esm/tiptap/extensions/html-block-extension.js +11 -14
  95. package/dist/esm/tiptap/extensions/html-inline-extension.d.ts.map +1 -1
  96. package/dist/esm/tiptap/extensions/html-inline-extension.js +11 -14
  97. package/dist/esm/tiptap/types/index.d.ts +12 -5
  98. package/dist/esm/tiptap/types/index.d.ts.map +1 -1
  99. package/dist/esm/tiptap/utils/__tests__/clean-html.test.d.ts +2 -0
  100. package/dist/esm/tiptap/utils/__tests__/clean-html.test.d.ts.map +1 -0
  101. package/dist/esm/tiptap/utils/__tests__/clean-html.test.js +93 -0
  102. package/dist/esm/tiptap/utils/__tests__/clean-html.test.ts +129 -0
  103. package/dist/esm/tiptap/utils/__tests__/data-attributes-helper.test.d.ts +2 -0
  104. package/dist/esm/tiptap/utils/__tests__/data-attributes-helper.test.d.ts.map +1 -0
  105. package/dist/esm/tiptap/utils/__tests__/data-attributes-helper.test.js +105 -0
  106. package/dist/esm/tiptap/utils/__tests__/data-attributes-helper.test.ts +148 -0
  107. package/dist/esm/tiptap/utils/__tests__/index.test.js +0 -2
  108. package/dist/esm/tiptap/utils/__tests__/index.test.ts +0 -2
  109. package/dist/esm/tiptap/utils/__tests__/prettier-format.test.d.ts +2 -0
  110. package/dist/esm/tiptap/utils/__tests__/prettier-format.test.d.ts.map +1 -0
  111. package/dist/esm/tiptap/utils/__tests__/prettier-format.test.js +33 -0
  112. package/dist/esm/tiptap/utils/__tests__/prettier-format.test.ts +49 -0
  113. package/dist/esm/tiptap/utils/__tests__/tiptap-extensions.test.js +157 -0
  114. package/dist/esm/tiptap/utils/__tests__/tiptap-extensions.test.ts +234 -0
  115. package/dist/esm/tiptap/utils/clean-html.d.ts +9 -0
  116. package/dist/esm/tiptap/utils/clean-html.d.ts.map +1 -0
  117. package/dist/esm/tiptap/utils/clean-html.js +75 -0
  118. package/dist/esm/tiptap/utils/data-attributes-helper.d.ts +20 -0
  119. package/dist/esm/tiptap/utils/data-attributes-helper.d.ts.map +1 -0
  120. package/dist/esm/tiptap/utils/data-attributes-helper.js +68 -0
  121. package/dist/esm/tiptap/utils/index.d.ts +3 -2
  122. package/dist/esm/tiptap/utils/index.d.ts.map +1 -1
  123. package/dist/esm/tiptap/utils/index.js +3 -2
  124. package/dist/esm/tiptap/utils/prettier-format.d.ts +8 -0
  125. package/dist/esm/tiptap/utils/prettier-format.d.ts.map +1 -0
  126. package/dist/esm/tiptap/utils/prettier-format.js +26 -0
  127. package/dist/esm/tiptap/utils/tiptap-extensions.d.ts.map +1 -1
  128. package/dist/esm/tiptap/utils/tiptap-extensions.js +134 -14
  129. package/package.json +13 -12
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Data Attributes Helper for Tiptap Extensions
3
+ *
4
+ * This helper provides utilities for adding data-* attribute support to Tiptap extensions.
5
+ */
6
+ var __rest = (this && this.__rest) || function (s, e) {
7
+ var t = {};
8
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
9
+ t[p] = s[p];
10
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
11
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
12
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
13
+ t[p[i]] = s[p[i]];
14
+ }
15
+ return t;
16
+ };
17
+ export const createDataAttributesConfig = () => ({
18
+ dataAttributes: {
19
+ default: {},
20
+ parseHTML: (element) => {
21
+ const dataAttrs = {};
22
+ Array.from(element.attributes).forEach(attr => {
23
+ if (attr.name.startsWith('data-')) {
24
+ dataAttrs[attr.name] = attr.value;
25
+ }
26
+ });
27
+ return Object.keys(dataAttrs).length > 0 ? dataAttrs : {};
28
+ },
29
+ renderHTML: (attributes) => {
30
+ return attributes.dataAttributes || {};
31
+ }
32
+ }
33
+ });
34
+ export const extractDataAttributes = (nodeAttrs) => {
35
+ const { dataAttributes } = nodeAttrs, otherAttrs = __rest(nodeAttrs, ["dataAttributes"]);
36
+ return {
37
+ dataAttributes: dataAttributes || {},
38
+ otherAttrs
39
+ };
40
+ };
41
+ export const addDataAttributesToParent = (parent) => {
42
+ const parentAttributes = (parent === null || parent === void 0 ? void 0 : parent()) || {};
43
+ return Object.assign(Object.assign(Object.assign({}, parentAttributes), { class: {
44
+ default: null,
45
+ parseHTML: (element) => element.getAttribute('class'),
46
+ renderHTML: (attributes) => {
47
+ if (!attributes.class)
48
+ return {};
49
+ return { class: attributes.class };
50
+ }
51
+ }, id: {
52
+ default: null,
53
+ parseHTML: (element) => element.getAttribute('id'),
54
+ renderHTML: (attributes) => {
55
+ if (!attributes.id)
56
+ return {};
57
+ return { id: attributes.id };
58
+ }
59
+ }, style: {
60
+ default: null,
61
+ parseHTML: (element) => element.getAttribute('style'),
62
+ renderHTML: (attributes) => {
63
+ if (!attributes.style)
64
+ return {};
65
+ return { style: attributes.style };
66
+ }
67
+ } }), createDataAttributesConfig());
68
+ };
@@ -1,5 +1,6 @@
1
- export * from './format-html';
1
+ export * from './clean-html';
2
+ export * from './data-attributes-helper';
2
3
  export * from './highlight-html';
3
- export * from './normalize-html';
4
+ export * from './prettier-format';
4
5
  export * from './tiptap-extensions';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
@@ -1,4 +1,5 @@
1
- export * from './format-html';
1
+ export * from './clean-html';
2
+ export * from './data-attributes-helper';
2
3
  export * from './highlight-html';
3
- export * from './normalize-html';
4
+ export * from './prettier-format';
4
5
  export * from './tiptap-extensions';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Formats HTML using Prettier
3
+ *
4
+ * @param html - Raw HTML string to format
5
+ * @returns Promise<formatted HTML string>
6
+ */
7
+ export declare const formatHtmlWithPrettier: (html: string) => Promise<string>;
8
+ //# sourceMappingURL=prettier-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prettier-format.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/utils/prettier-format.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,MAAM,CAiBzE,CAAC"}
@@ -0,0 +1,26 @@
1
+ import * as prettierPluginHtml from 'prettier/plugins/html';
2
+ import * as prettier from 'prettier/standalone';
3
+ /**
4
+ * Formats HTML using Prettier
5
+ *
6
+ * @param html - Raw HTML string to format
7
+ * @returns Promise<formatted HTML string>
8
+ */
9
+ export const formatHtmlWithPrettier = async (html) => {
10
+ try {
11
+ const formatted = await prettier.format(html, {
12
+ parser: 'html',
13
+ plugins: [prettierPluginHtml],
14
+ printWidth: 60,
15
+ tabWidth: 2,
16
+ useTabs: false,
17
+ htmlWhitespaceSensitivity: 'css',
18
+ singleAttributePerLine: false,
19
+ proseWrap: 'always'
20
+ });
21
+ return formatted.trim();
22
+ }
23
+ catch (_a) {
24
+ return html;
25
+ }
26
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"tiptap-extensions.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/utils/tiptap-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAwDzD,eAAO,MAAM,aAAa,GAAI,cAAc,MAAM,KAAG,YAAY,EAgGhE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,MAAM,IAAI,EAAE,MAAM,MAAM,SAqBvE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,MAAM,EACX,KAAK;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;IAC3C,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C,YAmCF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,YAW3C,CAAC"}
1
+ {"version":3,"file":"tiptap-extensions.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/utils/tiptap-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA6DzD,eAAO,MAAM,aAAa,GAAI,cAAc,MAAM,KAAG,YAAY,EA2NhE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,MAAM,IAAI,EAAE,MAAM,MAAM,SAqBvE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,MAAM,EACX,KAAK;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;IAC3C,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C,YAmCF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,YAW3C,CAAC"}
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import Blockquote from '@tiptap/extension-blockquote';
2
13
  import Bold from '@tiptap/extension-bold';
3
14
  import Code from '@tiptap/extension-code';
@@ -24,10 +35,11 @@ import Typography from '@tiptap/extension-typography';
24
35
  import Underline from '@tiptap/extension-underline';
25
36
  import Youtube from '@tiptap/extension-youtube';
26
37
  import { Dropcursor, Gapcursor } from '@tiptap/extensions';
27
- import { ALLOWED_IMAGE_MIMES, EXTENSION_PRIORITIES, HEADING_LEVELS, LINK_CONFIG, TEXT_ALIGN_TYPES } from '../constants/editor-config';
38
+ import { ALLOWED_IMAGE_MIMES, EXTENSION_PRIORITIES, HEADING_LEVELS, LINK_CONFIG, TEXT_ALIGN_TYPES, TIPTAP_CONTENT_HOLE } from '../constants/editor-config';
28
39
  import { HTML_ELEMENTS } from '../constants/html-elements';
29
40
  import { CommentExtension, CustomTableCell, CustomTableHeader, HtmlBlockExtension, HtmlInlineExtension, IframeExtension, ImageUploadExtension, ResetMarksOnEnter } from '../extensions';
30
41
  import { SecureScriptExtension } from '../extensions/secure-script-extension';
42
+ import { addDataAttributesToParent, extractDataAttributes } from './data-attributes-helper';
31
43
  const DROP_CURSOR_COLOR = '#fff';
32
44
  export const getExtensions = (placeholder) => [
33
45
  Document.extend({
@@ -36,11 +48,15 @@ export const getExtensions = (placeholder) => [
36
48
  Text,
37
49
  Paragraph.extend({
38
50
  addAttributes() {
39
- return {
40
- style: { default: null },
41
- class: { default: null },
42
- id: { default: null }
43
- };
51
+ return addDataAttributesToParent(this.parent);
52
+ },
53
+ renderHTML({ HTMLAttributes, node }) {
54
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
55
+ return [
56
+ HTML_ELEMENTS.P,
57
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), otherAttrs), dataAttributes),
58
+ TIPTAP_CONTENT_HOLE
59
+ ];
44
60
  }
45
61
  }),
46
62
  HardBreak,
@@ -60,11 +76,55 @@ export const getExtensions = (placeholder) => [
60
76
  };
61
77
  }
62
78
  }),
63
- Heading.configure({
79
+ Heading.extend({
80
+ addAttributes() {
81
+ return addDataAttributesToParent(this.parent);
82
+ },
83
+ renderHTML({ node, HTMLAttributes }) {
84
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
85
+ const hasLevel = this.options.levels.includes(node.attrs.level);
86
+ const level = hasLevel ? node.attrs.level : this.options.levels[0];
87
+ // Remove 'level' from attributes
88
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
89
+ const { level: _level } = otherAttrs, cleanAttrs = __rest(otherAttrs, ["level"]);
90
+ return [
91
+ `h${level}`,
92
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), cleanAttrs), dataAttributes),
93
+ TIPTAP_CONTENT_HOLE
94
+ ];
95
+ }
96
+ }).configure({
64
97
  levels: [...HEADING_LEVELS]
65
98
  }),
66
- Blockquote,
67
- CodeBlock,
99
+ Blockquote.extend({
100
+ addAttributes() {
101
+ return addDataAttributesToParent(this.parent);
102
+ },
103
+ renderHTML({ HTMLAttributes, node }) {
104
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
105
+ return [
106
+ HTML_ELEMENTS.BLOCKQUOTE,
107
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), otherAttrs), dataAttributes),
108
+ TIPTAP_CONTENT_HOLE
109
+ ];
110
+ }
111
+ }),
112
+ CodeBlock.extend({
113
+ addAttributes() {
114
+ return addDataAttributesToParent(this.parent);
115
+ },
116
+ renderHTML({ HTMLAttributes, node }) {
117
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
118
+ // Remove 'language' from pre attributes
119
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
120
+ const { language: _language } = otherAttrs, cleanAttrs = __rest(otherAttrs, ["language"]);
121
+ return [
122
+ HTML_ELEMENTS.PRE,
123
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), cleanAttrs), dataAttributes),
124
+ [HTML_ELEMENTS.CODE, {}, TIPTAP_CONTENT_HOLE]
125
+ ];
126
+ }
127
+ }),
68
128
  HtmlBlockExtension,
69
129
  HtmlInlineExtension,
70
130
  IframeExtension,
@@ -81,9 +141,45 @@ export const getExtensions = (placeholder) => [
81
141
  TextAlign.configure({
82
142
  types: [...TEXT_ALIGN_TYPES]
83
143
  }),
84
- BulletList,
85
- OrderedList,
86
- ListItem,
144
+ BulletList.extend({
145
+ addAttributes() {
146
+ return addDataAttributesToParent(this.parent);
147
+ },
148
+ renderHTML({ HTMLAttributes, node }) {
149
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
150
+ return [
151
+ HTML_ELEMENTS.UL,
152
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), otherAttrs), dataAttributes),
153
+ 0
154
+ ];
155
+ }
156
+ }),
157
+ OrderedList.extend({
158
+ addAttributes() {
159
+ return addDataAttributesToParent(this.parent);
160
+ },
161
+ renderHTML({ HTMLAttributes, node }) {
162
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
163
+ return [
164
+ HTML_ELEMENTS.OL,
165
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), otherAttrs), dataAttributes),
166
+ TIPTAP_CONTENT_HOLE
167
+ ];
168
+ }
169
+ }),
170
+ ListItem.extend({
171
+ addAttributes() {
172
+ return addDataAttributesToParent(this.parent);
173
+ },
174
+ renderHTML({ HTMLAttributes, node }) {
175
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
176
+ return [
177
+ HTML_ELEMENTS.LI,
178
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), otherAttrs), dataAttributes),
179
+ TIPTAP_CONTENT_HOLE
180
+ ];
181
+ }
182
+ }),
87
183
  TaskList,
88
184
  TaskItem.configure({
89
185
  nested: true
@@ -101,8 +197,32 @@ export const getExtensions = (placeholder) => [
101
197
  files.forEach(file => handleImageFile(currentEditor, file));
102
198
  }
103
199
  }),
104
- Table.configure({ resizable: true }),
105
- TableRow,
200
+ Table.extend({
201
+ addAttributes() {
202
+ return addDataAttributesToParent(this.parent);
203
+ },
204
+ renderHTML({ HTMLAttributes, node }) {
205
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
206
+ return [
207
+ HTML_ELEMENTS.TABLE,
208
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), otherAttrs), dataAttributes),
209
+ TIPTAP_CONTENT_HOLE
210
+ ];
211
+ }
212
+ }).configure({ resizable: true }),
213
+ TableRow.extend({
214
+ addAttributes() {
215
+ return addDataAttributesToParent(this.parent);
216
+ },
217
+ renderHTML({ HTMLAttributes, node }) {
218
+ const { dataAttributes, otherAttrs } = extractDataAttributes(node.attrs);
219
+ return [
220
+ HTML_ELEMENTS.TR,
221
+ Object.assign(Object.assign(Object.assign({}, HTMLAttributes), otherAttrs), dataAttributes),
222
+ TIPTAP_CONTENT_HOLE
223
+ ];
224
+ }
225
+ }),
106
226
  CustomTableHeader,
107
227
  CustomTableCell,
108
228
  Link.configure({
@@ -1,8 +1,9 @@
1
1
  import '@testing-library/jest-dom';
2
- import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { createEvent, fireEvent, render, screen, waitFor } from '@testing-library/react';
3
3
  import { ConfigProvider } from 'antd';
4
4
  import React from 'react';
5
5
  import { beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import { formatHtmlWithPrettier } from '../../utils';
6
7
  import { HtmlCodeViewer } from '../html-code-viewer';
7
8
  vi.mock('@ant-design/cssinjs', () => ({
8
9
  useStyleRegister: vi.fn((_config, cb) => {
@@ -11,7 +12,14 @@ vi.mock('@ant-design/cssinjs', () => ({
11
12
  })
12
13
  }));
13
14
  vi.mock('@akinon/ui-theme', () => ({
14
- useToken: () => ({ token: {}, hashId: 'hash-id' })
15
+ useToken: () => ({ token: {}, hashId: 'hash-id' }),
16
+ getSafeCustomTokens: vi.fn(() => ({
17
+ border: { borderWidthThick: '2px', borderSolid: 'solid' }
18
+ }))
19
+ }));
20
+ vi.mock('../../utils', () => ({
21
+ highlightHtml: (html) => html,
22
+ formatHtmlWithPrettier: vi.fn((html) => Promise.resolve(html))
15
23
  }));
16
24
  describe('HtmlCodeViewer', () => {
17
25
  const defaultProps = {
@@ -76,7 +84,11 @@ describe('HtmlCodeViewer', () => {
76
84
  neutral: {
77
85
  '50': '#fff',
78
86
  '1000': '#000'
79
- }
87
+ },
88
+ red: { '825': '#f00', '999': '#a00', '815': '#d00' },
89
+ blue: { '999': '#00f' },
90
+ green: { '825': '#0f0' },
91
+ ebonyClay: { '550': '#123' }
80
92
  }
81
93
  };
82
94
  render(
@@ -86,4 +98,68 @@ describe('HtmlCodeViewer', () => {
86
98
  const textarea = screen.getByRole('textbox');
87
99
  expect(textarea).toBeInTheDocument();
88
100
  });
101
+ it('should format code on mount', async () => {
102
+ const formatted = '<p> Formatted </p>';
103
+ vi.mocked(formatHtmlWithPrettier).mockResolvedValue(formatted);
104
+ render(React.createElement(HtmlCodeViewer, Object.assign({}, defaultProps, { value: "<p>Raw</p>" })));
105
+ await waitFor(() => {
106
+ expect(defaultProps.onChange).toHaveBeenCalledWith(formatted);
107
+ });
108
+ });
109
+ it('should handle paste event', async () => {
110
+ const formatted = '<p>Formatted Paste</p>';
111
+ vi.mocked(formatHtmlWithPrettier).mockResolvedValue(formatted);
112
+ render(React.createElement(HtmlCodeViewer, Object.assign({}, defaultProps, { readOnly: false })));
113
+ const textarea = screen.getByRole('textbox');
114
+ const pasteData = 'Pasted Text';
115
+ const pasteEvent = createEvent.paste(textarea, {
116
+ clipboardData: {
117
+ getData: vi.fn().mockReturnValue(pasteData)
118
+ }
119
+ });
120
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
+ pasteEvent.preventDefault = vi.fn();
122
+ fireEvent(textarea, pasteEvent);
123
+ await waitFor(() => {
124
+ var _a;
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ expect(pasteEvent.preventDefault).toHaveBeenCalled();
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ expect((_a = pasteEvent.clipboardData) === null || _a === void 0 ? void 0 : _a.getData).toHaveBeenCalledWith('text/plain');
129
+ expect(formatHtmlWithPrettier).toHaveBeenCalled();
130
+ expect(defaultProps.onChange).toHaveBeenCalledWith(formatted);
131
+ });
132
+ });
133
+ it('should not handle paste event if readOnly is true', () => {
134
+ render(React.createElement(HtmlCodeViewer, Object.assign({}, defaultProps, { readOnly: true })));
135
+ const textarea = screen.getByRole('textbox');
136
+ const pasteEvent = createEvent.paste(textarea, {
137
+ clipboardData: {
138
+ getData: vi.fn().mockReturnValue('Some Text')
139
+ }
140
+ });
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
+ pasteEvent.preventDefault = vi.fn();
143
+ fireEvent(textarea, pasteEvent);
144
+ expect(formatHtmlWithPrettier).not.toHaveBeenCalled();
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ expect(pasteEvent.preventDefault).not.toHaveBeenCalled();
147
+ });
148
+ it('should not handle paste event if clipboard text is empty', () => {
149
+ render(React.createElement(HtmlCodeViewer, Object.assign({}, defaultProps)));
150
+ // Clear mock because formatOnMount triggers it
151
+ vi.mocked(formatHtmlWithPrettier).mockClear();
152
+ const textarea = screen.getByRole('textbox');
153
+ const pasteEvent = createEvent.paste(textarea, {
154
+ clipboardData: {
155
+ getData: vi.fn().mockReturnValue('')
156
+ }
157
+ });
158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
+ pasteEvent.preventDefault = vi.fn();
160
+ fireEvent(textarea, pasteEvent);
161
+ expect(formatHtmlWithPrettier).not.toHaveBeenCalled();
162
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
+ expect(pasteEvent.preventDefault).toHaveBeenCalled(); // preventDefault is called before check? No, line 83 calls preventDefault.
164
+ });
89
165
  });
@@ -1,10 +1,17 @@
1
1
  import '@testing-library/jest-dom';
2
2
 
3
- import { fireEvent, render, screen } from '@testing-library/react';
3
+ import {
4
+ createEvent,
5
+ fireEvent,
6
+ render,
7
+ screen,
8
+ waitFor
9
+ } from '@testing-library/react';
4
10
  import { ConfigProvider } from 'antd';
5
11
  import React from 'react';
6
12
  import { beforeEach, describe, expect, it, vi } from 'vitest';
7
13
 
14
+ import { formatHtmlWithPrettier } from '../../utils';
8
15
  import { HtmlCodeViewer } from '../html-code-viewer';
9
16
 
10
17
  vi.mock('@ant-design/cssinjs', () => ({
@@ -15,7 +22,15 @@ vi.mock('@ant-design/cssinjs', () => ({
15
22
  }));
16
23
 
17
24
  vi.mock('@akinon/ui-theme', () => ({
18
- useToken: () => ({ token: {}, hashId: 'hash-id' })
25
+ useToken: () => ({ token: {}, hashId: 'hash-id' }),
26
+ getSafeCustomTokens: vi.fn(() => ({
27
+ border: { borderWidthThick: '2px', borderSolid: 'solid' }
28
+ }))
29
+ }));
30
+
31
+ vi.mock('../../utils', () => ({
32
+ highlightHtml: (html: string) => html,
33
+ formatHtmlWithPrettier: vi.fn((html: string) => Promise.resolve(html))
19
34
  }));
20
35
 
21
36
  describe('HtmlCodeViewer', () => {
@@ -109,7 +124,11 @@ describe('HtmlCodeViewer', () => {
109
124
  neutral: {
110
125
  '50': '#fff',
111
126
  '1000': '#000'
112
- }
127
+ },
128
+ red: { '825': '#f00', '999': '#a00', '815': '#d00' },
129
+ blue: { '999': '#00f' },
130
+ green: { '825': '#0f0' },
131
+ ebonyClay: { '550': '#123' }
113
132
  }
114
133
  };
115
134
 
@@ -123,4 +142,88 @@ describe('HtmlCodeViewer', () => {
123
142
  const textarea = screen.getByRole('textbox');
124
143
  expect(textarea).toBeInTheDocument();
125
144
  });
145
+
146
+ it('should format code on mount', async () => {
147
+ const formatted = '<p> Formatted </p>';
148
+ vi.mocked(formatHtmlWithPrettier).mockResolvedValue(formatted);
149
+
150
+ render(<HtmlCodeViewer {...defaultProps} value="<p>Raw</p>" />);
151
+
152
+ await waitFor(() => {
153
+ expect(defaultProps.onChange).toHaveBeenCalledWith(formatted);
154
+ });
155
+ });
156
+
157
+ it('should handle paste event', async () => {
158
+ const formatted = '<p>Formatted Paste</p>';
159
+ vi.mocked(formatHtmlWithPrettier).mockResolvedValue(formatted);
160
+
161
+ render(<HtmlCodeViewer {...defaultProps} readOnly={false} />);
162
+ const textarea = screen.getByRole('textbox');
163
+
164
+ const pasteData = 'Pasted Text';
165
+
166
+ const pasteEvent = createEvent.paste(textarea, {
167
+ clipboardData: {
168
+ getData: vi.fn().mockReturnValue(pasteData)
169
+ }
170
+ });
171
+
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
+ (pasteEvent as any).preventDefault = vi.fn();
174
+
175
+ fireEvent(textarea, pasteEvent);
176
+
177
+ await waitFor(() => {
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ expect((pasteEvent as any).preventDefault).toHaveBeenCalled();
180
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
181
+ expect((pasteEvent as any).clipboardData?.getData).toHaveBeenCalledWith(
182
+ 'text/plain'
183
+ );
184
+ expect(formatHtmlWithPrettier).toHaveBeenCalled();
185
+ expect(defaultProps.onChange).toHaveBeenCalledWith(formatted);
186
+ });
187
+ });
188
+
189
+ it('should not handle paste event if readOnly is true', () => {
190
+ render(<HtmlCodeViewer {...defaultProps} readOnly={true} />);
191
+ const textarea = screen.getByRole('textbox');
192
+
193
+ const pasteEvent = createEvent.paste(textarea, {
194
+ clipboardData: {
195
+ getData: vi.fn().mockReturnValue('Some Text')
196
+ }
197
+ });
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
+ (pasteEvent as any).preventDefault = vi.fn();
200
+
201
+ fireEvent(textarea, pasteEvent);
202
+
203
+ expect(formatHtmlWithPrettier).not.toHaveBeenCalled();
204
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
205
+ expect((pasteEvent as any).preventDefault).not.toHaveBeenCalled();
206
+ });
207
+
208
+ it('should not handle paste event if clipboard text is empty', () => {
209
+ render(<HtmlCodeViewer {...defaultProps} />);
210
+ // Clear mock because formatOnMount triggers it
211
+ vi.mocked(formatHtmlWithPrettier).mockClear();
212
+
213
+ const textarea = screen.getByRole('textbox');
214
+
215
+ const pasteEvent = createEvent.paste(textarea, {
216
+ clipboardData: {
217
+ getData: vi.fn().mockReturnValue('')
218
+ }
219
+ });
220
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
221
+ (pasteEvent as any).preventDefault = vi.fn();
222
+
223
+ fireEvent(textarea, pasteEvent);
224
+
225
+ expect(formatHtmlWithPrettier).not.toHaveBeenCalled();
226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
+ expect((pasteEvent as any).preventDefault).toHaveBeenCalled(); // preventDefault is called before check? No, line 83 calls preventDefault.
228
+ });
126
229
  });
@@ -9,6 +9,7 @@ const { mockUseEditor, mockChangeLanguage } = vi.hoisted(() => ({
9
9
  mockUseEditor: vi.fn(),
10
10
  mockChangeLanguage: vi.fn()
11
11
  }));
12
+ vi.useFakeTimers();
12
13
  vi.mock('@tiptap/react', () => ({
13
14
  useEditor: mockUseEditor,
14
15
  useEditorState: vi.fn(() => ({})),
@@ -42,8 +43,7 @@ vi.mock('../../assets/icons', () => ({
42
43
  }));
43
44
  vi.mock('../../utils', () => ({
44
45
  getExtensions: vi.fn(() => []),
45
- formatHtml: vi.fn(html => `formatted-${html}`),
46
- normalizeHtml: vi.fn(html => `normalized-${html}`)
46
+ cleanDuplicateSpans: vi.fn(html => html)
47
47
  }));
48
48
  vi.mock('@akinon/akilocale/react', () => ({
49
49
  useTranslation: () => ({
@@ -53,7 +53,8 @@ vi.mock('@akinon/akilocale/react', () => ({
53
53
  }));
54
54
  vi.mock('@akinon/ui-theme', () => ({
55
55
  getSafeCustomTokens: vi.fn(() => ({
56
- typography: { fontWeightMedium: 500 }
56
+ typography: { fontWeightMedium: 500 },
57
+ border: { borderWidthThick: '2px', borderSolid: 'solid' }
57
58
  })),
58
59
  useToken: () => ({
59
60
  token: { colorBorder: '#ccc' },
@@ -83,6 +84,10 @@ describe('Tiptap', () => {
83
84
  }
84
85
  };
85
86
  mockUseEditor.mockReturnValue(mockEditor);
87
+ vi.useFakeTimers();
88
+ });
89
+ afterEach(() => {
90
+ vi.useRealTimers();
86
91
  });
87
92
  const renderComponent = (props = {}) => {
88
93
  return render(React.createElement(Tiptap, Object.assign({ value: "<p>Test</p>", onChange: onChange }, props)));
@@ -116,7 +121,9 @@ describe('Tiptap', () => {
116
121
  expect(screen.getByTestId('html-code-viewer')).toBeInTheDocument();
117
122
  fireEvent.click(toggleButton);
118
123
  expect(screen.getByTestId('editor-content')).toBeInTheDocument();
119
- expect(mockEditor.commands.setContent).toHaveBeenCalledWith('normalized-formatted-<p>Initial Content</p>', { emitUpdate: false });
124
+ expect(mockEditor.commands.setContent).toHaveBeenCalledWith('<p>Test</p>', {
125
+ emitUpdate: false
126
+ });
120
127
  fireEvent.click(toggleButton);
121
128
  expect(screen.getByTestId('html-code-viewer')).toBeInTheDocument();
122
129
  });
@@ -124,6 +131,7 @@ describe('Tiptap', () => {
124
131
  renderComponent();
125
132
  const viewer = screen.getByTestId('html-code-viewer');
126
133
  fireEvent.change(viewer, { target: { value: '<p>New Content</p>' } });
134
+ vi.advanceTimersByTime(1000);
127
135
  expect(onChange).toHaveBeenCalledWith({
128
136
  html: '<p>New Content</p>',
129
137
  json: expect.any(Object)
@@ -272,4 +280,34 @@ describe('Tiptap', () => {
272
280
  expect(resultDiv).toBe(false);
273
281
  expect(preventDefault).not.toHaveBeenCalled();
274
282
  });
283
+ it('should debounce source code changes and clear previous timeout', () => {
284
+ renderComponent();
285
+ const viewer = screen.getByTestId('html-code-viewer');
286
+ fireEvent.change(viewer, { target: { value: '<p>Change 1</p>' } });
287
+ fireEvent.change(viewer, { target: { value: '<p>Change 2</p>' } });
288
+ vi.advanceTimersByTime(300);
289
+ expect(onChange).toHaveBeenCalledWith({
290
+ html: '<p>Change 2</p>',
291
+ json: expect.any(Object)
292
+ });
293
+ expect(onChange).toHaveBeenCalledTimes(1);
294
+ });
295
+ it('should initialize with empty string if value and defaultValue are undefined', () => {
296
+ render(React.createElement(Tiptap, { onChange: onChange }));
297
+ const viewer = screen.getByTestId('html-code-viewer');
298
+ expect(viewer).toHaveValue('');
299
+ });
300
+ it('should remove type="text/inert" from script tags when switching to source mode', () => {
301
+ // Start in visual mode
302
+ render(React.createElement(Tiptap, { defaultSourceMode: false, onChange: onChange }));
303
+ // Mock editor.getHTML() to return content with inert script
304
+ mockEditor.getHTML.mockReturnValue('<script type="text/inert">console.log("test")</script>');
305
+ // Switch to source mode
306
+ const toggleButton = screen.getByTestId('toggle-source');
307
+ fireEvent.click(toggleButton);
308
+ // Verify source mode content (cleanDuplicateSpans is mocked to return input)
309
+ // The setRawHTML update triggers a re-render of HtmlCodeViewer
310
+ const viewer = screen.getByTestId('html-code-viewer');
311
+ expect(viewer).toHaveValue('<script>console.log("test")</script>');
312
+ });
275
313
  });