@akinon/ui-editor 1.0.9 → 1.1.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 (35) hide show
  1. package/dist/cjs/tiptap/extensions/__tests__/comment-extension.test.js +8 -4
  2. package/dist/cjs/tiptap/extensions/__tests__/comment-extension.test.ts +10 -4
  3. package/dist/cjs/tiptap/utils/tiptap-extensions.js +1 -1
  4. package/dist/esm/tiptap/extensions/__tests__/comment-extension.test.js +8 -4
  5. package/dist/esm/tiptap/extensions/__tests__/comment-extension.test.ts +10 -4
  6. package/dist/esm/tiptap/utils/tiptap-extensions.js +1 -1
  7. package/package.json +9 -9
  8. package/dist/cjs/tiptap/utils/__tests__/format-html.test.d.ts +0 -2
  9. package/dist/cjs/tiptap/utils/__tests__/format-html.test.d.ts.map +0 -1
  10. package/dist/cjs/tiptap/utils/__tests__/format-html.test.js +0 -68
  11. package/dist/cjs/tiptap/utils/__tests__/format-html.test.ts +0 -91
  12. package/dist/cjs/tiptap/utils/__tests__/normalize-html.test.d.ts +0 -2
  13. package/dist/cjs/tiptap/utils/__tests__/normalize-html.test.d.ts.map +0 -1
  14. package/dist/cjs/tiptap/utils/__tests__/normalize-html.test.js +0 -53
  15. package/dist/cjs/tiptap/utils/__tests__/normalize-html.test.ts +0 -77
  16. package/dist/cjs/tiptap/utils/format-html.d.ts +0 -6
  17. package/dist/cjs/tiptap/utils/format-html.d.ts.map +0 -1
  18. package/dist/cjs/tiptap/utils/format-html.js +0 -108
  19. package/dist/cjs/tiptap/utils/normalize-html.d.ts +0 -7
  20. package/dist/cjs/tiptap/utils/normalize-html.d.ts.map +0 -1
  21. package/dist/cjs/tiptap/utils/normalize-html.js +0 -109
  22. package/dist/esm/tiptap/utils/__tests__/format-html.test.d.ts +0 -2
  23. package/dist/esm/tiptap/utils/__tests__/format-html.test.d.ts.map +0 -1
  24. package/dist/esm/tiptap/utils/__tests__/format-html.test.js +0 -68
  25. package/dist/esm/tiptap/utils/__tests__/format-html.test.ts +0 -91
  26. package/dist/esm/tiptap/utils/__tests__/normalize-html.test.d.ts +0 -2
  27. package/dist/esm/tiptap/utils/__tests__/normalize-html.test.d.ts.map +0 -1
  28. package/dist/esm/tiptap/utils/__tests__/normalize-html.test.js +0 -53
  29. package/dist/esm/tiptap/utils/__tests__/normalize-html.test.ts +0 -77
  30. package/dist/esm/tiptap/utils/format-html.d.ts +0 -6
  31. package/dist/esm/tiptap/utils/format-html.d.ts.map +0 -1
  32. package/dist/esm/tiptap/utils/format-html.js +0 -108
  33. package/dist/esm/tiptap/utils/normalize-html.d.ts +0 -7
  34. package/dist/esm/tiptap/utils/normalize-html.d.ts.map +0 -1
  35. package/dist/esm/tiptap/utils/normalize-html.js +0 -109
@@ -1,109 +0,0 @@
1
- import { HTML_ELEMENTS } from '../constants';
2
- /**
3
- * Normalizes HTML by removing formatting whitespace (newlines and indentation)
4
- * while preserving meaningful whitespace within text content.
5
- * Used when converting from source mode back to visual editor.
6
- */
7
- export const normalizeHtml = (html) => {
8
- const div = document.createElement(HTML_ELEMENTS.DIV);
9
- div.innerHTML = html;
10
- cleanScriptsAndStyles(div);
11
- convertCommentsToSpans(div);
12
- unwrapHrFromParagraphs(div);
13
- flattenEmptyParagraphs(div);
14
- mergeConsecutiveBreaks(div);
15
- mergeConsecutiveBreaks(div);
16
- return div.innerHTML;
17
- };
18
- const convertCommentsToSpans = (root) => {
19
- const iterator = document.createNodeIterator(root, NodeFilter.SHOW_COMMENT, null);
20
- let currentNode;
21
- const comments = [];
22
- while ((currentNode = iterator.nextNode())) {
23
- comments.push(currentNode);
24
- }
25
- comments.forEach(comment => {
26
- var _a;
27
- const span = document.createElement(HTML_ELEMENTS.SPAN);
28
- span.setAttribute('data-type', 'comment');
29
- span.setAttribute('data-content', comment.textContent || '');
30
- (_a = comment.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(span, comment);
31
- });
32
- };
33
- const cleanScriptsAndStyles = (root) => {
34
- const scripts = root.querySelectorAll(`${HTML_ELEMENTS.SCRIPT}, ${HTML_ELEMENTS.STYLE}`);
35
- scripts.forEach(el => {
36
- const content = el.textContent;
37
- if (content && content.trim()) {
38
- el.textContent = dedent(content);
39
- }
40
- });
41
- };
42
- const unwrapHrFromParagraphs = (root) => {
43
- const hrs = root.querySelectorAll(HTML_ELEMENTS.HR);
44
- hrs.forEach(hr => {
45
- var _a;
46
- const parent = hr.parentElement;
47
- if (parent && parent.tagName.toLowerCase() === HTML_ELEMENTS.P) {
48
- // If HR is the only child (or only meaningful child), replace the p with hr
49
- const siblings = Array.from(parent.childNodes).filter(node => {
50
- var _a;
51
- return node !== hr &&
52
- !(node.nodeType === Node.TEXT_NODE && !((_a = node.textContent) === null || _a === void 0 ? void 0 : _a.trim()));
53
- });
54
- if (siblings.length === 0) {
55
- (_a = parent.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(hr, parent);
56
- }
57
- }
58
- });
59
- };
60
- const flattenEmptyParagraphs = (root) => {
61
- // Remove empty paragraphs that only contain <br>
62
- const paragraphs = root.querySelectorAll(HTML_ELEMENTS.P);
63
- paragraphs.forEach(p => {
64
- var _a;
65
- const hasOnlyBrOrWhitespace = Array.from(p.childNodes).every(child => {
66
- var _a;
67
- return (child.nodeType === Node.ELEMENT_NODE &&
68
- child.tagName.toLowerCase() === HTML_ELEMENTS.BR) ||
69
- (child.nodeType === Node.TEXT_NODE && !((_a = child.textContent) === null || _a === void 0 ? void 0 : _a.trim()));
70
- });
71
- if (hasOnlyBrOrWhitespace && p.childNodes.length > 0) {
72
- // Replace <p><br></p> with just <br>
73
- const br = document.createElement(HTML_ELEMENTS.BR);
74
- (_a = p.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(br, p);
75
- }
76
- });
77
- };
78
- const mergeConsecutiveBreaks = (root) => {
79
- const brs = root.querySelectorAll(HTML_ELEMENTS.BR);
80
- brs.forEach(br => {
81
- var _a, _b;
82
- let next = br.nextSibling;
83
- // Skip whitespace nodes
84
- while (next &&
85
- next.nodeType === Node.TEXT_NODE &&
86
- !((_a = next.textContent) === null || _a === void 0 ? void 0 : _a.trim())) {
87
- next = next.nextSibling;
88
- }
89
- // If next is also a br, remove this one
90
- if (next &&
91
- next.nodeType === Node.ELEMENT_NODE &&
92
- next.tagName.toLowerCase() === HTML_ELEMENTS.BR) {
93
- (_b = br.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(br);
94
- }
95
- });
96
- };
97
- const dedent = (str) => {
98
- const lines = str.split('\n');
99
- const nonEmptyLines = lines.filter(line => line.trim());
100
- const LEADING_WHITESPACE_REGEX = /^\s*/;
101
- const minIndent = nonEmptyLines.reduce((min, line) => {
102
- const indent = line.match(LEADING_WHITESPACE_REGEX)[0].length;
103
- return Math.min(min, indent);
104
- }, Infinity);
105
- return lines
106
- .map(line => (line.length >= minIndent ? line.slice(minIndent) : line))
107
- .join('\n')
108
- .trim();
109
- };
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=format-html.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"format-html.test.d.ts","sourceRoot":"","sources":["../../../../../src/tiptap/utils/__tests__/format-html.test.ts"],"names":[],"mappings":""}
@@ -1,68 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { formatHtml } from '../format-html';
3
- describe('formatHtml', () => {
4
- it('should format simple nested divs with indentation', () => {
5
- const input = '<div><div>content</div></div>';
6
- const expected = `
7
- <div>
8
- <div>content</div>
9
- </div>
10
- `.trim();
11
- expect(formatHtml(input)).toBe(expected);
12
- });
13
- it('should handle attributes correctly', () => {
14
- const input = '<div class="foo" id="bar">content</div>';
15
- expect(formatHtml(input)).toBe(input.trim());
16
- });
17
- it('should handle void elements like br and hr', () => {
18
- const input = '<div>text<br />more text<hr /></div>';
19
- const output = formatHtml(input);
20
- expect(output).toContain('<br />');
21
- expect(output).toContain('<hr />');
22
- });
23
- it('should preserve whitespace in pre tags', () => {
24
- const input = '<pre> keep this space </pre>';
25
- expect(formatHtml(input)).toBe(input.trim());
26
- });
27
- it('should ensure newline before block elements', () => {
28
- const input = '<span>text</span><div>block</div>';
29
- const expected = `
30
- <span>text</span>
31
- <div>block</div>
32
- `.trim();
33
- expect(formatHtml(input)).toBe(expected);
34
- });
35
- it('should dedent script content', () => {
36
- const input = `<script>
37
- function foo() {
38
- return true;
39
- }
40
- </script>`;
41
- const output = formatHtml(input);
42
- expect(output).toContain('function foo() {');
43
- expect(output).toContain(' return true;');
44
- });
45
- it('should handle empty script tags', () => {
46
- const input = '<script></script>';
47
- expect(formatHtml(input)).toBe(input);
48
- });
49
- it('should convert comment spans back to HTML comments', () => {
50
- const input = '<span data-type="comment" data-content=" my comment "></span>';
51
- const expected = '<!-- my comment -->';
52
- expect(formatHtml(input)).toBe(expected);
53
- const inputMissingContent = '<span data-type="comment"></span>';
54
- const expectedMissingContent = '<!---->';
55
- expect(formatHtml(inputMissingContent)).toBe(expectedMissingContent);
56
- });
57
- it('should handle scripts with empty lines', () => {
58
- const input = `<script>
59
- var a = 1;
60
-
61
- var b = 2;
62
- </script>`;
63
- const output = formatHtml(input);
64
- expect(output).toContain('var a = 1;');
65
- expect(output).toContain('var b = 2;');
66
- expect(output).toMatch(/;\n\s*var b/);
67
- });
68
- });
@@ -1,91 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { formatHtml } from '../format-html';
4
-
5
- describe('formatHtml', () => {
6
- it('should format simple nested divs with indentation', () => {
7
- const input = '<div><div>content</div></div>';
8
- const expected = `
9
- <div>
10
- <div>content</div>
11
- </div>
12
- `.trim();
13
-
14
- expect(formatHtml(input)).toBe(expected);
15
- });
16
-
17
- it('should handle attributes correctly', () => {
18
- const input = '<div class="foo" id="bar">content</div>';
19
-
20
- expect(formatHtml(input)).toBe(input.trim());
21
- });
22
-
23
- it('should handle void elements like br and hr', () => {
24
- const input = '<div>text<br />more text<hr /></div>';
25
- const output = formatHtml(input);
26
-
27
- expect(output).toContain('<br />');
28
- expect(output).toContain('<hr />');
29
- });
30
-
31
- it('should preserve whitespace in pre tags', () => {
32
- const input = '<pre> keep this space </pre>';
33
-
34
- expect(formatHtml(input)).toBe(input.trim());
35
- });
36
-
37
- it('should ensure newline before block elements', () => {
38
- const input = '<span>text</span><div>block</div>';
39
- const expected = `
40
- <span>text</span>
41
- <div>block</div>
42
- `.trim();
43
- expect(formatHtml(input)).toBe(expected);
44
- });
45
-
46
- it('should dedent script content', () => {
47
- const input = `<script>
48
- function foo() {
49
- return true;
50
- }
51
- </script>`;
52
-
53
- const output = formatHtml(input);
54
-
55
- expect(output).toContain('function foo() {');
56
- expect(output).toContain(' return true;');
57
- });
58
-
59
- it('should handle empty script tags', () => {
60
- const input = '<script></script>';
61
-
62
- expect(formatHtml(input)).toBe(input);
63
- });
64
-
65
- it('should convert comment spans back to HTML comments', () => {
66
- const input =
67
- '<span data-type="comment" data-content=" my comment "></span>';
68
- const expected = '<!-- my comment -->';
69
-
70
- expect(formatHtml(input)).toBe(expected);
71
-
72
- const inputMissingContent = '<span data-type="comment"></span>';
73
- const expectedMissingContent = '<!---->';
74
-
75
- expect(formatHtml(inputMissingContent)).toBe(expectedMissingContent);
76
- });
77
-
78
- it('should handle scripts with empty lines', () => {
79
- const input = `<script>
80
- var a = 1;
81
-
82
- var b = 2;
83
- </script>`;
84
-
85
- const output = formatHtml(input);
86
-
87
- expect(output).toContain('var a = 1;');
88
- expect(output).toContain('var b = 2;');
89
- expect(output).toMatch(/;\n\s*var b/);
90
- });
91
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=normalize-html.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalize-html.test.d.ts","sourceRoot":"","sources":["../../../../../src/tiptap/utils/__tests__/normalize-html.test.ts"],"names":[],"mappings":""}
@@ -1,53 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { normalizeHtml } from '../normalize-html';
3
- describe('normalizeHtml', () => {
4
- it('should convert HTML comments to span placeholders', () => {
5
- const input = '<div><!-- my comment --></div>';
6
- const output = normalizeHtml(input);
7
- expect(output).toContain('<span data-type="comment" data-content=" my comment "></span>');
8
- const inputEmpty = '<!---->';
9
- expect(normalizeHtml(inputEmpty)).toContain('<span data-type="comment" data-content=""></span>');
10
- });
11
- it('should dedent script tags to avoid extra indentation', () => {
12
- const input = `
13
- <script>
14
- console.log('test');
15
- </script>
16
- `;
17
- const output = normalizeHtml(input);
18
- expect(output).toContain("console.log('test');");
19
- });
20
- it('should flatten empty paragraphs containing only breaks', () => {
21
- const input = '<p><br></p>';
22
- const output = normalizeHtml(input);
23
- expect(output).toBe('<br>');
24
- });
25
- it('should merge consecutive break tags if they are excessive', () => {
26
- const input = 'Line<br><br>Line';
27
- const output = normalizeHtml(input);
28
- expect(output).toBe('Line<br>Line');
29
- });
30
- it('should merge consecutive break tags separated by whitespace', () => {
31
- const input = 'Line<br> <br>Line';
32
- const output = normalizeHtml(input);
33
- expect(output).toBe('Line <br>Line');
34
- });
35
- it('should unwrap HR elements from paragraphs', () => {
36
- const input = '<p><hr></p>';
37
- const output = normalizeHtml(input);
38
- expect(output).toBe('<hr>');
39
- const inputWithSpace = '<p> <hr> </p>';
40
- const outputWithSpace = normalizeHtml(inputWithSpace);
41
- expect(outputWithSpace).toBe('<hr>');
42
- });
43
- it('should NOT unwrap HR from paragraphs if there is other content', () => {
44
- const input = '<p>Content<hr></p>';
45
- const output = normalizeHtml(input);
46
- expect(output).toBe('<p>Content<hr></p>');
47
- });
48
- it('should flatten empty paragraphs containing whitespace and breaks', () => {
49
- const input = '<p> <br> </p>';
50
- const output = normalizeHtml(input);
51
- expect(output).toBe('<br>');
52
- });
53
- });
@@ -1,77 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { normalizeHtml } from '../normalize-html';
4
-
5
- describe('normalizeHtml', () => {
6
- it('should convert HTML comments to span placeholders', () => {
7
- const input = '<div><!-- my comment --></div>';
8
- const output = normalizeHtml(input);
9
-
10
- expect(output).toContain(
11
- '<span data-type="comment" data-content=" my comment "></span>'
12
- );
13
-
14
- const inputEmpty = '<!---->';
15
- expect(normalizeHtml(inputEmpty)).toContain(
16
- '<span data-type="comment" data-content=""></span>'
17
- );
18
- });
19
-
20
- it('should dedent script tags to avoid extra indentation', () => {
21
- const input = `
22
- <script>
23
- console.log('test');
24
- </script>
25
- `;
26
- const output = normalizeHtml(input);
27
-
28
- expect(output).toContain("console.log('test');");
29
- });
30
-
31
- it('should flatten empty paragraphs containing only breaks', () => {
32
- const input = '<p><br></p>';
33
- const output = normalizeHtml(input);
34
-
35
- expect(output).toBe('<br>');
36
- });
37
-
38
- it('should merge consecutive break tags if they are excessive', () => {
39
- const input = 'Line<br><br>Line';
40
- const output = normalizeHtml(input);
41
-
42
- expect(output).toBe('Line<br>Line');
43
- });
44
-
45
- it('should merge consecutive break tags separated by whitespace', () => {
46
- const input = 'Line<br> <br>Line';
47
- const output = normalizeHtml(input);
48
-
49
- expect(output).toBe('Line <br>Line');
50
- });
51
-
52
- it('should unwrap HR elements from paragraphs', () => {
53
- const input = '<p><hr></p>';
54
- const output = normalizeHtml(input);
55
-
56
- expect(output).toBe('<hr>');
57
-
58
- const inputWithSpace = '<p> <hr> </p>';
59
- const outputWithSpace = normalizeHtml(inputWithSpace);
60
-
61
- expect(outputWithSpace).toBe('<hr>');
62
- });
63
-
64
- it('should NOT unwrap HR from paragraphs if there is other content', () => {
65
- const input = '<p>Content<hr></p>';
66
- const output = normalizeHtml(input);
67
-
68
- expect(output).toBe('<p>Content<hr></p>');
69
- });
70
-
71
- it('should flatten empty paragraphs containing whitespace and breaks', () => {
72
- const input = '<p> <br> </p>';
73
- const output = normalizeHtml(input);
74
-
75
- expect(output).toBe('<br>');
76
- });
77
- });
@@ -1,6 +0,0 @@
1
- /**
2
- * Formats HTML string with proper indentation and cleaner structure.
3
- * Uses browser DOM parsing to ensure valid HTML structure before formatting.
4
- */
5
- export declare const formatHtml: (html: string) => string;
6
- //# sourceMappingURL=format-html.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"format-html.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/utils/format-html.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,MAQzC,CAAC"}
@@ -1,108 +0,0 @@
1
- import { BLOCK_ELEMENTS, HTML_ELEMENTS, VOID_ELEMENTS } from '../constants';
2
- const TAB = ' ';
3
- const NEWLINE = '\n';
4
- /**
5
- * Formats HTML string with proper indentation and cleaner structure.
6
- * Uses browser DOM parsing to ensure valid HTML structure before formatting.
7
- */
8
- export const formatHtml = (html) => {
9
- const div = document.createElement(HTML_ELEMENTS.DIV);
10
- div.innerHTML = html.trim();
11
- const formatted = formatNode(div, 0).trim();
12
- // Normalize multiple newlines to single newline
13
- return formatted.replace(/\n\n+/g, NEWLINE);
14
- };
15
- /**
16
- * Removes common leading whitespace from a multi-line string.
17
- */
18
- const dedent = (str) => {
19
- const lines = str.split(NEWLINE);
20
- const nonEmptyLines = lines.filter(line => line.trim());
21
- const minIndent = nonEmptyLines.reduce((min, line) => {
22
- const indent = line.match(/^\s*/)[0].length;
23
- return Math.min(min, indent);
24
- }, Infinity);
25
- return lines
26
- .map(line => (line.length >= minIndent ? line.slice(minIndent) : line))
27
- .join(NEWLINE)
28
- .trim();
29
- };
30
- /**
31
- * Recursive function to traverse and format DOM nodes.
32
- */
33
- const formatNode = (node, level) => {
34
- const indentStr = TAB.repeat(level);
35
- let result = '';
36
- Array.from(node.childNodes).forEach(child => {
37
- if (child.nodeType === Node.TEXT_NODE) {
38
- result += formatTextNode(child);
39
- }
40
- else if (child.nodeType === Node.ELEMENT_NODE) {
41
- result += formatElementNode(child, level, indentStr);
42
- }
43
- });
44
- return result;
45
- };
46
- const formatTextNode = (node) => {
47
- return node.textContent;
48
- };
49
- const formatElementNode = (element, level, indentStr) => {
50
- const tagName = element.tagName.toLowerCase();
51
- const isSpan = tagName === HTML_ELEMENTS.SPAN;
52
- const isComment = element.getAttribute('data-type') === 'comment';
53
- if (isSpan && isComment) {
54
- const content = element.getAttribute('data-content') || '';
55
- return `<!--${content}-->`;
56
- }
57
- const isBlock = BLOCK_ELEMENTS.includes(tagName);
58
- const isVoid = VOID_ELEMENTS.includes(tagName);
59
- let output = '';
60
- // Handle block element spacing
61
- if (isBlock) {
62
- output += NEWLINE;
63
- output += indentStr;
64
- }
65
- // Construct opening tag with attributes
66
- const attrs = Array.from(element.attributes)
67
- .map(attr => ` ${attr.name}="${attr.value}"`)
68
- .join('');
69
- const openingTag = `<${tagName}${attrs}${isVoid ? ' />' : '>'}`;
70
- output += openingTag;
71
- // Handle content
72
- if (tagName === HTML_ELEMENTS.PRE) {
73
- output += element.innerHTML;
74
- output += `</${tagName}>`;
75
- }
76
- else if (!isVoid) {
77
- output += getElementChildren(element, tagName, level, isBlock, indentStr);
78
- output += `</${tagName}>`;
79
- }
80
- // Close block element with newline
81
- if (isBlock) {
82
- output += NEWLINE;
83
- }
84
- return output;
85
- };
86
- const getElementChildren = (element, tagName, level, isBlock, indentStr) => {
87
- const isScriptOrStyle = tagName === HTML_ELEMENTS.SCRIPT || tagName === HTML_ELEMENTS.STYLE;
88
- const content = isScriptOrStyle
89
- ? formatScriptOrStyleContent(element.textContent || '', indentStr)
90
- : formatNode(element, isBlock ? level + 1 : level);
91
- if (!content)
92
- return '';
93
- const hasBlockChildren = content.includes(NEWLINE) || isScriptOrStyle;
94
- if (isBlock && hasBlockChildren) {
95
- return `${NEWLINE}${content}${NEWLINE}${indentStr}`;
96
- }
97
- return content;
98
- };
99
- const formatScriptOrStyleContent = (content, baseIndent) => {
100
- if (!content.trim())
101
- return '';
102
- const dedented = dedent(content);
103
- // Indent each line relative to the parent script/style tag
104
- return dedented
105
- .split(NEWLINE)
106
- .map(line => (line ? `${baseIndent}${TAB}${line}` : ''))
107
- .join(NEWLINE);
108
- };
@@ -1,7 +0,0 @@
1
- /**
2
- * Normalizes HTML by removing formatting whitespace (newlines and indentation)
3
- * while preserving meaningful whitespace within text content.
4
- * Used when converting from source mode back to visual editor.
5
- */
6
- export declare const normalizeHtml: (html: string) => string;
7
- //# sourceMappingURL=normalize-html.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalize-html.d.ts","sourceRoot":"","sources":["../../../../src/tiptap/utils/normalize-html.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAY5C,CAAC"}
@@ -1,109 +0,0 @@
1
- import { HTML_ELEMENTS } from '../constants';
2
- /**
3
- * Normalizes HTML by removing formatting whitespace (newlines and indentation)
4
- * while preserving meaningful whitespace within text content.
5
- * Used when converting from source mode back to visual editor.
6
- */
7
- export const normalizeHtml = (html) => {
8
- const div = document.createElement(HTML_ELEMENTS.DIV);
9
- div.innerHTML = html;
10
- cleanScriptsAndStyles(div);
11
- convertCommentsToSpans(div);
12
- unwrapHrFromParagraphs(div);
13
- flattenEmptyParagraphs(div);
14
- mergeConsecutiveBreaks(div);
15
- mergeConsecutiveBreaks(div);
16
- return div.innerHTML;
17
- };
18
- const convertCommentsToSpans = (root) => {
19
- const iterator = document.createNodeIterator(root, NodeFilter.SHOW_COMMENT, null);
20
- let currentNode;
21
- const comments = [];
22
- while ((currentNode = iterator.nextNode())) {
23
- comments.push(currentNode);
24
- }
25
- comments.forEach(comment => {
26
- var _a;
27
- const span = document.createElement(HTML_ELEMENTS.SPAN);
28
- span.setAttribute('data-type', 'comment');
29
- span.setAttribute('data-content', comment.textContent || '');
30
- (_a = comment.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(span, comment);
31
- });
32
- };
33
- const cleanScriptsAndStyles = (root) => {
34
- const scripts = root.querySelectorAll(`${HTML_ELEMENTS.SCRIPT}, ${HTML_ELEMENTS.STYLE}`);
35
- scripts.forEach(el => {
36
- const content = el.textContent;
37
- if (content && content.trim()) {
38
- el.textContent = dedent(content);
39
- }
40
- });
41
- };
42
- const unwrapHrFromParagraphs = (root) => {
43
- const hrs = root.querySelectorAll(HTML_ELEMENTS.HR);
44
- hrs.forEach(hr => {
45
- var _a;
46
- const parent = hr.parentElement;
47
- if (parent && parent.tagName.toLowerCase() === HTML_ELEMENTS.P) {
48
- // If HR is the only child (or only meaningful child), replace the p with hr
49
- const siblings = Array.from(parent.childNodes).filter(node => {
50
- var _a;
51
- return node !== hr &&
52
- !(node.nodeType === Node.TEXT_NODE && !((_a = node.textContent) === null || _a === void 0 ? void 0 : _a.trim()));
53
- });
54
- if (siblings.length === 0) {
55
- (_a = parent.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(hr, parent);
56
- }
57
- }
58
- });
59
- };
60
- const flattenEmptyParagraphs = (root) => {
61
- // Remove empty paragraphs that only contain <br>
62
- const paragraphs = root.querySelectorAll(HTML_ELEMENTS.P);
63
- paragraphs.forEach(p => {
64
- var _a;
65
- const hasOnlyBrOrWhitespace = Array.from(p.childNodes).every(child => {
66
- var _a;
67
- return (child.nodeType === Node.ELEMENT_NODE &&
68
- child.tagName.toLowerCase() === HTML_ELEMENTS.BR) ||
69
- (child.nodeType === Node.TEXT_NODE && !((_a = child.textContent) === null || _a === void 0 ? void 0 : _a.trim()));
70
- });
71
- if (hasOnlyBrOrWhitespace && p.childNodes.length > 0) {
72
- // Replace <p><br></p> with just <br>
73
- const br = document.createElement(HTML_ELEMENTS.BR);
74
- (_a = p.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(br, p);
75
- }
76
- });
77
- };
78
- const mergeConsecutiveBreaks = (root) => {
79
- const brs = root.querySelectorAll(HTML_ELEMENTS.BR);
80
- brs.forEach(br => {
81
- var _a, _b;
82
- let next = br.nextSibling;
83
- // Skip whitespace nodes
84
- while (next &&
85
- next.nodeType === Node.TEXT_NODE &&
86
- !((_a = next.textContent) === null || _a === void 0 ? void 0 : _a.trim())) {
87
- next = next.nextSibling;
88
- }
89
- // If next is also a br, remove this one
90
- if (next &&
91
- next.nodeType === Node.ELEMENT_NODE &&
92
- next.tagName.toLowerCase() === HTML_ELEMENTS.BR) {
93
- (_b = br.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(br);
94
- }
95
- });
96
- };
97
- const dedent = (str) => {
98
- const lines = str.split('\n');
99
- const nonEmptyLines = lines.filter(line => line.trim());
100
- const LEADING_WHITESPACE_REGEX = /^\s*/;
101
- const minIndent = nonEmptyLines.reduce((min, line) => {
102
- const indent = line.match(LEADING_WHITESPACE_REGEX)[0].length;
103
- return Math.min(min, indent);
104
- }, Infinity);
105
- return lines
106
- .map(line => (line.length >= minIndent ? line.slice(minIndent) : line))
107
- .join('\n')
108
- .trim();
109
- };