quasar-ui-danx 0.4.99 → 0.5.1
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.
- package/dist/danx.es.js +17884 -12732
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +192 -118
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +11 -2
- package/scripts/publish.sh +76 -0
- package/src/components/Utility/Code/CodeViewer.vue +31 -14
- package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
- package/src/components/Utility/Code/CodeViewerFooter.vue +1 -1
- package/src/components/Utility/Code/LanguageBadge.vue +278 -5
- package/src/components/Utility/Code/MarkdownContent.vue +160 -6
- package/src/components/Utility/Code/index.ts +3 -0
- package/src/components/Utility/Markdown/ContextMenu.vue +314 -0
- package/src/components/Utility/Markdown/HotkeyHelpPopover.vue +259 -0
- package/src/components/Utility/Markdown/LineTypeMenu.vue +226 -0
- package/src/components/Utility/Markdown/LinkPopover.vue +331 -0
- package/src/components/Utility/Markdown/MarkdownEditor.vue +228 -0
- package/src/components/Utility/Markdown/MarkdownEditorContent.vue +235 -0
- package/src/components/Utility/Markdown/MarkdownEditorFooter.vue +50 -0
- package/src/components/Utility/Markdown/TablePopover.vue +420 -0
- package/src/components/Utility/Markdown/index.ts +11 -0
- package/src/components/Utility/Markdown/types.ts +27 -0
- package/src/components/Utility/index.ts +1 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/markdown/features/useBlockquotes.spec.ts +428 -0
- package/src/composables/markdown/features/useBlockquotes.ts +248 -0
- package/src/composables/markdown/features/useCodeBlockManager.ts +369 -0
- package/src/composables/markdown/features/useCodeBlocks.spec.ts +779 -0
- package/src/composables/markdown/features/useCodeBlocks.ts +774 -0
- package/src/composables/markdown/features/useContextMenu.ts +444 -0
- package/src/composables/markdown/features/useFocusTracking.ts +116 -0
- package/src/composables/markdown/features/useHeadings.spec.ts +834 -0
- package/src/composables/markdown/features/useHeadings.ts +290 -0
- package/src/composables/markdown/features/useInlineFormatting.spec.ts +705 -0
- package/src/composables/markdown/features/useInlineFormatting.ts +402 -0
- package/src/composables/markdown/features/useLineTypeMenu.ts +285 -0
- package/src/composables/markdown/features/useLinks.spec.ts +369 -0
- package/src/composables/markdown/features/useLinks.ts +374 -0
- package/src/composables/markdown/features/useLists.spec.ts +834 -0
- package/src/composables/markdown/features/useLists.ts +747 -0
- package/src/composables/markdown/features/usePopoverManager.ts +181 -0
- package/src/composables/markdown/features/useTables.spec.ts +1601 -0
- package/src/composables/markdown/features/useTables.ts +1107 -0
- package/src/composables/markdown/index.ts +16 -0
- package/src/composables/markdown/useMarkdownEditor.spec.ts +332 -0
- package/src/composables/markdown/useMarkdownEditor.ts +1068 -0
- package/src/composables/markdown/useMarkdownHotkeys.spec.ts +791 -0
- package/src/composables/markdown/useMarkdownHotkeys.ts +266 -0
- package/src/composables/markdown/useMarkdownSelection.ts +219 -0
- package/src/composables/markdown/useMarkdownSync.ts +549 -0
- package/src/composables/useCodeViewerEditor.spec.ts +655 -0
- package/src/composables/useCodeViewerEditor.ts +174 -20
- package/src/helpers/formats/index.ts +1 -1
- package/src/helpers/formats/markdown/escapeHtml.ts +15 -0
- package/src/helpers/formats/markdown/escapeSequences.ts +60 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/convertHeadings.ts +41 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.spec.ts +489 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +412 -0
- package/src/helpers/formats/markdown/index.ts +92 -0
- package/src/helpers/formats/markdown/linePatterns.spec.ts +495 -0
- package/src/helpers/formats/markdown/linePatterns.ts +172 -0
- package/src/helpers/formats/markdown/parseInline.ts +124 -0
- package/src/helpers/formats/markdown/render/index.ts +92 -0
- package/src/helpers/formats/markdown/render/renderFootnotes.ts +30 -0
- package/src/helpers/formats/markdown/render/renderList.ts +69 -0
- package/src/helpers/formats/markdown/render/renderTable.ts +38 -0
- package/src/helpers/formats/markdown/state.ts +58 -0
- package/src/helpers/formats/markdown/tokenize/extractDefinitions.ts +39 -0
- package/src/helpers/formats/markdown/tokenize/index.ts +139 -0
- package/src/helpers/formats/markdown/tokenize/parseBlockquote.ts +34 -0
- package/src/helpers/formats/markdown/tokenize/parseCodeBlock.ts +85 -0
- package/src/helpers/formats/markdown/tokenize/parseDefinitionList.ts +88 -0
- package/src/helpers/formats/markdown/tokenize/parseHeading.ts +65 -0
- package/src/helpers/formats/markdown/tokenize/parseHorizontalRule.ts +22 -0
- package/src/helpers/formats/markdown/tokenize/parseList.ts +119 -0
- package/src/helpers/formats/markdown/tokenize/parseParagraph.ts +59 -0
- package/src/helpers/formats/markdown/tokenize/parseTable.ts +70 -0
- package/src/helpers/formats/markdown/tokenize/parseTaskList.ts +47 -0
- package/src/helpers/formats/markdown/tokenize/utils.ts +25 -0
- package/src/helpers/formats/markdown/types.ts +63 -0
- package/src/styles/danx.scss +1 -0
- package/src/styles/themes/danx/markdown.scss +96 -0
- package/src/test/helpers/editorTestUtils.spec.ts +296 -0
- package/src/test/helpers/editorTestUtils.ts +253 -0
- package/src/test/helpers/index.ts +1 -0
- package/src/test/setup.test.ts +12 -0
- package/src/test/setup.ts +12 -0
- package/vitest.config.ts +19 -0
- package/src/helpers/formats/renderMarkdown.ts +0 -338
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type LineType = "paragraph" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "ul" | "ol" | "code" | "blockquote";
|
|
2
|
+
|
|
3
|
+
export interface LineTypeOption {
|
|
4
|
+
value: LineType;
|
|
5
|
+
label: string;
|
|
6
|
+
icon: string;
|
|
7
|
+
shortcut: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type ContextMenuContext = "table" | "list" | "code" | "text";
|
|
11
|
+
|
|
12
|
+
export interface ContextMenuItem {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
icon?: string;
|
|
16
|
+
shortcut?: string;
|
|
17
|
+
action?: () => void; // Optional - not needed if has children
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
children?: ContextMenuItem[]; // For nested submenus
|
|
20
|
+
divider?: boolean; // For visual dividers between items
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ContextMenuGroup {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
items: ContextMenuItem[];
|
|
27
|
+
}
|
package/src/composables/index.ts
CHANGED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { useBlockquotes } from "./useBlockquotes";
|
|
3
|
+
import { createTestEditor, TestEditorResult } from "../../../test/helpers/editorTestUtils";
|
|
4
|
+
|
|
5
|
+
describe("useBlockquotes", () => {
|
|
6
|
+
let editor: TestEditorResult;
|
|
7
|
+
let onContentChange: ReturnType<typeof vi.fn>;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (editor) {
|
|
11
|
+
editor.destroy();
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function createBlockquotes() {
|
|
16
|
+
return useBlockquotes({
|
|
17
|
+
contentRef: editor.contentRef,
|
|
18
|
+
onContentChange
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("toggleBlockquote", () => {
|
|
23
|
+
describe("wrapping in blockquote", () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
onContentChange = vi.fn();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("wraps paragraph in blockquote", () => {
|
|
29
|
+
editor = createTestEditor("<p>Hello world</p>");
|
|
30
|
+
const blockquotes = createBlockquotes();
|
|
31
|
+
editor.setCursorInBlock(0, 5);
|
|
32
|
+
|
|
33
|
+
blockquotes.toggleBlockquote();
|
|
34
|
+
|
|
35
|
+
expect(editor.getHtml()).toBe("<blockquote><p>Hello world</p></blockquote>");
|
|
36
|
+
expect(onContentChange).toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("wraps h1 in blockquote", () => {
|
|
40
|
+
editor = createTestEditor("<h1>Hello world</h1>");
|
|
41
|
+
const blockquotes = createBlockquotes();
|
|
42
|
+
editor.setCursorInBlock(0, 0);
|
|
43
|
+
|
|
44
|
+
blockquotes.toggleBlockquote();
|
|
45
|
+
|
|
46
|
+
expect(editor.getHtml()).toBe("<blockquote><h1>Hello world</h1></blockquote>");
|
|
47
|
+
expect(onContentChange).toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("wraps h2 in blockquote", () => {
|
|
51
|
+
editor = createTestEditor("<h2>Test heading</h2>");
|
|
52
|
+
const blockquotes = createBlockquotes();
|
|
53
|
+
editor.setCursorInBlock(0, 0);
|
|
54
|
+
|
|
55
|
+
blockquotes.toggleBlockquote();
|
|
56
|
+
|
|
57
|
+
expect(editor.getHtml()).toBe("<blockquote><h2>Test heading</h2></blockquote>");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("wraps div in blockquote", () => {
|
|
61
|
+
editor = createTestEditor("<div>Div content</div>");
|
|
62
|
+
const blockquotes = createBlockquotes();
|
|
63
|
+
editor.setCursorInBlock(0, 0);
|
|
64
|
+
|
|
65
|
+
blockquotes.toggleBlockquote();
|
|
66
|
+
|
|
67
|
+
expect(editor.getHtml()).toBe("<blockquote><div>Div content</div></blockquote>");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("preserves cursor position after wrapping", () => {
|
|
71
|
+
editor = createTestEditor("<p>Hello world</p>");
|
|
72
|
+
const blockquotes = createBlockquotes();
|
|
73
|
+
editor.setCursorInBlock(0, 5);
|
|
74
|
+
|
|
75
|
+
blockquotes.toggleBlockquote();
|
|
76
|
+
|
|
77
|
+
// Verify cursor is still at offset 5
|
|
78
|
+
const sel = window.getSelection();
|
|
79
|
+
expect(sel).not.toBeNull();
|
|
80
|
+
expect(sel!.rangeCount).toBeGreaterThan(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("only wraps current block, not siblings", () => {
|
|
84
|
+
editor = createTestEditor("<p>First</p><p>Second</p><p>Third</p>");
|
|
85
|
+
const blockquotes = createBlockquotes();
|
|
86
|
+
editor.setCursorInBlock(1, 0); // Cursor in second paragraph
|
|
87
|
+
|
|
88
|
+
blockquotes.toggleBlockquote();
|
|
89
|
+
|
|
90
|
+
expect(editor.getHtml()).toBe("<p>First</p><blockquote><p>Second</p></blockquote><p>Third</p>");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("unwrapping from blockquote", () => {
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
onContentChange = vi.fn();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("unwraps paragraph from blockquote", () => {
|
|
100
|
+
editor = createTestEditor("<blockquote><p>Hello world</p></blockquote>");
|
|
101
|
+
const blockquotes = createBlockquotes();
|
|
102
|
+
// Set cursor inside the paragraph within blockquote
|
|
103
|
+
const p = editor.container.querySelector("blockquote p");
|
|
104
|
+
if (p?.firstChild) {
|
|
105
|
+
editor.setCursor(p.firstChild, 5);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
blockquotes.toggleBlockquote();
|
|
109
|
+
|
|
110
|
+
expect(editor.getHtml()).toBe("<p>Hello world</p>");
|
|
111
|
+
expect(onContentChange).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("unwraps heading from blockquote", () => {
|
|
115
|
+
editor = createTestEditor("<blockquote><h1>Heading</h1></blockquote>");
|
|
116
|
+
const blockquotes = createBlockquotes();
|
|
117
|
+
const h1 = editor.container.querySelector("blockquote h1");
|
|
118
|
+
if (h1?.firstChild) {
|
|
119
|
+
editor.setCursor(h1.firstChild, 0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
blockquotes.toggleBlockquote();
|
|
123
|
+
|
|
124
|
+
expect(editor.getHtml()).toBe("<h1>Heading</h1>");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("preserves cursor position after unwrapping", () => {
|
|
128
|
+
editor = createTestEditor("<blockquote><p>Hello world</p></blockquote>");
|
|
129
|
+
const blockquotes = createBlockquotes();
|
|
130
|
+
const p = editor.container.querySelector("blockquote p");
|
|
131
|
+
if (p?.firstChild) {
|
|
132
|
+
editor.setCursor(p.firstChild, 5);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
blockquotes.toggleBlockquote();
|
|
136
|
+
|
|
137
|
+
const sel = window.getSelection();
|
|
138
|
+
expect(sel).not.toBeNull();
|
|
139
|
+
expect(sel!.rangeCount).toBeGreaterThan(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("unwraps only the blockquote, keeps other blocks", () => {
|
|
143
|
+
editor = createTestEditor("<p>Before</p><blockquote><p>Quoted</p></blockquote><p>After</p>");
|
|
144
|
+
const blockquotes = createBlockquotes();
|
|
145
|
+
const quotedP = editor.container.querySelector("blockquote p");
|
|
146
|
+
if (quotedP?.firstChild) {
|
|
147
|
+
editor.setCursor(quotedP.firstChild, 0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
blockquotes.toggleBlockquote();
|
|
151
|
+
|
|
152
|
+
expect(editor.getHtml()).toBe("<p>Before</p><p>Quoted</p><p>After</p>");
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("with inline formatting preserved", () => {
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
onContentChange = vi.fn();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("preserves bold formatting when wrapping", () => {
|
|
162
|
+
editor = createTestEditor("<p>Hello <strong>bold</strong> world</p>");
|
|
163
|
+
const blockquotes = createBlockquotes();
|
|
164
|
+
editor.setCursorInBlock(0, 0);
|
|
165
|
+
|
|
166
|
+
blockquotes.toggleBlockquote();
|
|
167
|
+
|
|
168
|
+
expect(editor.getHtml()).toBe("<blockquote><p>Hello <strong>bold</strong> world</p></blockquote>");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("preserves italic formatting when wrapping", () => {
|
|
172
|
+
editor = createTestEditor("<p>Hello <em>italic</em> world</p>");
|
|
173
|
+
const blockquotes = createBlockquotes();
|
|
174
|
+
editor.setCursorInBlock(0, 0);
|
|
175
|
+
|
|
176
|
+
blockquotes.toggleBlockquote();
|
|
177
|
+
|
|
178
|
+
expect(editor.getHtml()).toBe("<blockquote><p>Hello <em>italic</em> world</p></blockquote>");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("preserves nested formatting when wrapping", () => {
|
|
182
|
+
editor = createTestEditor("<p>Hello <strong><em>bold italic</em></strong> world</p>");
|
|
183
|
+
const blockquotes = createBlockquotes();
|
|
184
|
+
editor.setCursorInBlock(0, 0);
|
|
185
|
+
|
|
186
|
+
blockquotes.toggleBlockquote();
|
|
187
|
+
|
|
188
|
+
expect(editor.getHtml()).toBe("<blockquote><p>Hello <strong><em>bold italic</em></strong> world</p></blockquote>");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("preserves bold formatting when unwrapping", () => {
|
|
192
|
+
editor = createTestEditor("<blockquote><p>Hello <strong>bold</strong> world</p></blockquote>");
|
|
193
|
+
const blockquotes = createBlockquotes();
|
|
194
|
+
const p = editor.container.querySelector("blockquote p");
|
|
195
|
+
if (p?.firstChild) {
|
|
196
|
+
editor.setCursor(p.firstChild, 0);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
blockquotes.toggleBlockquote();
|
|
200
|
+
|
|
201
|
+
expect(editor.getHtml()).toBe("<p>Hello <strong>bold</strong> world</p>");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("preserves link formatting when wrapping", () => {
|
|
205
|
+
editor = createTestEditor("<p>Hello <a href=\"https://example.com\">link</a> world</p>");
|
|
206
|
+
const blockquotes = createBlockquotes();
|
|
207
|
+
editor.setCursorInBlock(0, 0);
|
|
208
|
+
|
|
209
|
+
blockquotes.toggleBlockquote();
|
|
210
|
+
|
|
211
|
+
expect(editor.getHtml()).toBe("<blockquote><p>Hello <a href=\"https://example.com\">link</a> world</p></blockquote>");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("preserves code formatting when wrapping", () => {
|
|
215
|
+
editor = createTestEditor("<p>Hello <code>code</code> world</p>");
|
|
216
|
+
const blockquotes = createBlockquotes();
|
|
217
|
+
editor.setCursorInBlock(0, 0);
|
|
218
|
+
|
|
219
|
+
blockquotes.toggleBlockquote();
|
|
220
|
+
|
|
221
|
+
expect(editor.getHtml()).toBe("<blockquote><p>Hello <code>code</code> world</p></blockquote>");
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("isInBlockquote", () => {
|
|
227
|
+
beforeEach(() => {
|
|
228
|
+
onContentChange = vi.fn();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("returns false when cursor is in plain paragraph", () => {
|
|
232
|
+
editor = createTestEditor("<p>Hello world</p>");
|
|
233
|
+
const blockquotes = createBlockquotes();
|
|
234
|
+
editor.setCursorInBlock(0, 5);
|
|
235
|
+
|
|
236
|
+
expect(blockquotes.isInBlockquote()).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("returns true when cursor is inside blockquote", () => {
|
|
240
|
+
editor = createTestEditor("<blockquote><p>Hello world</p></blockquote>");
|
|
241
|
+
const blockquotes = createBlockquotes();
|
|
242
|
+
const p = editor.container.querySelector("blockquote p");
|
|
243
|
+
if (p?.firstChild) {
|
|
244
|
+
editor.setCursor(p.firstChild, 5);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
expect(blockquotes.isInBlockquote()).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("returns false when cursor is outside blockquote", () => {
|
|
251
|
+
editor = createTestEditor("<p>Outside</p><blockquote><p>Inside</p></blockquote>");
|
|
252
|
+
const blockquotes = createBlockquotes();
|
|
253
|
+
editor.setCursorInBlock(0, 0); // Cursor in first paragraph
|
|
254
|
+
|
|
255
|
+
expect(blockquotes.isInBlockquote()).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("returns true for nested content inside blockquote", () => {
|
|
259
|
+
editor = createTestEditor("<blockquote><p><strong>Bold text</strong></p></blockquote>");
|
|
260
|
+
const blockquotes = createBlockquotes();
|
|
261
|
+
const strong = editor.container.querySelector("blockquote strong");
|
|
262
|
+
if (strong?.firstChild) {
|
|
263
|
+
editor.setCursor(strong.firstChild, 2);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
expect(blockquotes.isInBlockquote()).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("returns false when contentRef is null", () => {
|
|
270
|
+
editor = createTestEditor("<p>Hello world</p>");
|
|
271
|
+
const { isInBlockquote } = useBlockquotes({
|
|
272
|
+
contentRef: { value: null },
|
|
273
|
+
onContentChange
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(isInBlockquote()).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("returns false when no selection exists", () => {
|
|
280
|
+
editor = createTestEditor("<blockquote><p>Hello world</p></blockquote>");
|
|
281
|
+
const blockquotes = createBlockquotes();
|
|
282
|
+
window.getSelection()?.removeAllRanges();
|
|
283
|
+
|
|
284
|
+
expect(blockquotes.isInBlockquote()).toBe(false);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("edge cases", () => {
|
|
289
|
+
beforeEach(() => {
|
|
290
|
+
onContentChange = vi.fn();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("does nothing when contentRef is null", () => {
|
|
294
|
+
editor = createTestEditor("<p>Hello world</p>");
|
|
295
|
+
const { toggleBlockquote } = useBlockquotes({
|
|
296
|
+
contentRef: { value: null },
|
|
297
|
+
onContentChange
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
toggleBlockquote();
|
|
301
|
+
|
|
302
|
+
expect(editor.getHtml()).toBe("<p>Hello world</p>");
|
|
303
|
+
expect(onContentChange).not.toHaveBeenCalled();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("does nothing when selection is outside content area", () => {
|
|
307
|
+
editor = createTestEditor("<p>Hello world</p>");
|
|
308
|
+
const blockquotes = createBlockquotes();
|
|
309
|
+
|
|
310
|
+
// Create a selection outside the editor
|
|
311
|
+
const externalDiv = document.createElement("div");
|
|
312
|
+
externalDiv.textContent = "External text";
|
|
313
|
+
document.body.appendChild(externalDiv);
|
|
314
|
+
|
|
315
|
+
const range = document.createRange();
|
|
316
|
+
range.selectNodeContents(externalDiv);
|
|
317
|
+
const sel = window.getSelection();
|
|
318
|
+
sel?.removeAllRanges();
|
|
319
|
+
sel?.addRange(range);
|
|
320
|
+
|
|
321
|
+
blockquotes.toggleBlockquote();
|
|
322
|
+
|
|
323
|
+
expect(editor.getHtml()).toBe("<p>Hello world</p>");
|
|
324
|
+
expect(onContentChange).not.toHaveBeenCalled();
|
|
325
|
+
|
|
326
|
+
externalDiv.remove();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("does nothing when no selection exists", () => {
|
|
330
|
+
editor = createTestEditor("<p>Hello world</p>");
|
|
331
|
+
const blockquotes = createBlockquotes();
|
|
332
|
+
window.getSelection()?.removeAllRanges();
|
|
333
|
+
|
|
334
|
+
blockquotes.toggleBlockquote();
|
|
335
|
+
|
|
336
|
+
expect(editor.getHtml()).toBe("<p>Hello world</p>");
|
|
337
|
+
expect(onContentChange).not.toHaveBeenCalled();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("handles empty paragraph", () => {
|
|
341
|
+
editor = createTestEditor("<p></p>");
|
|
342
|
+
const blockquotes = createBlockquotes();
|
|
343
|
+
const p = editor.getBlock(0);
|
|
344
|
+
if (p) {
|
|
345
|
+
const range = document.createRange();
|
|
346
|
+
range.selectNodeContents(p);
|
|
347
|
+
range.collapse(true);
|
|
348
|
+
const sel = window.getSelection();
|
|
349
|
+
sel?.removeAllRanges();
|
|
350
|
+
sel?.addRange(range);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
blockquotes.toggleBlockquote();
|
|
354
|
+
|
|
355
|
+
expect(editor.getHtml()).toBe("<blockquote><p></p></blockquote>");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("handles whitespace-only content", () => {
|
|
359
|
+
editor = createTestEditor("<p> </p>");
|
|
360
|
+
const blockquotes = createBlockquotes();
|
|
361
|
+
editor.setCursorInBlock(0, 1);
|
|
362
|
+
|
|
363
|
+
blockquotes.toggleBlockquote();
|
|
364
|
+
|
|
365
|
+
expect(editor.getHtml()).toBe("<blockquote><p> </p></blockquote>");
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("round-trip toggle", () => {
|
|
370
|
+
beforeEach(() => {
|
|
371
|
+
onContentChange = vi.fn();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("toggle wrap then unwrap returns to original", () => {
|
|
375
|
+
editor = createTestEditor("<p>Hello world</p>");
|
|
376
|
+
const blockquotes = createBlockquotes();
|
|
377
|
+
editor.setCursorInBlock(0, 5);
|
|
378
|
+
|
|
379
|
+
// First toggle - wrap
|
|
380
|
+
blockquotes.toggleBlockquote();
|
|
381
|
+
expect(editor.getHtml()).toBe("<blockquote><p>Hello world</p></blockquote>");
|
|
382
|
+
|
|
383
|
+
// Set cursor again inside the blockquote
|
|
384
|
+
const p = editor.container.querySelector("blockquote p");
|
|
385
|
+
if (p?.firstChild) {
|
|
386
|
+
editor.setCursor(p.firstChild, 5);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Second toggle - unwrap
|
|
390
|
+
blockquotes.toggleBlockquote();
|
|
391
|
+
expect(editor.getHtml()).toBe("<p>Hello world</p>");
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("toggle unwrap then wrap returns to original", () => {
|
|
395
|
+
editor = createTestEditor("<blockquote><p>Hello world</p></blockquote>");
|
|
396
|
+
const blockquotes = createBlockquotes();
|
|
397
|
+
const p = editor.container.querySelector("blockquote p");
|
|
398
|
+
if (p?.firstChild) {
|
|
399
|
+
editor.setCursor(p.firstChild, 5);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// First toggle - unwrap
|
|
403
|
+
blockquotes.toggleBlockquote();
|
|
404
|
+
expect(editor.getHtml()).toBe("<p>Hello world</p>");
|
|
405
|
+
|
|
406
|
+
// Set cursor in the unwrapped paragraph
|
|
407
|
+
editor.setCursorInBlock(0, 5);
|
|
408
|
+
|
|
409
|
+
// Second toggle - wrap
|
|
410
|
+
blockquotes.toggleBlockquote();
|
|
411
|
+
expect(editor.getHtml()).toBe("<blockquote><p>Hello world</p></blockquote>");
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe("return type", () => {
|
|
416
|
+
beforeEach(() => {
|
|
417
|
+
onContentChange = vi.fn();
|
|
418
|
+
editor = createTestEditor("<p>test</p>");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("returns both functions", () => {
|
|
422
|
+
const blockquotes = createBlockquotes();
|
|
423
|
+
|
|
424
|
+
expect(typeof blockquotes.toggleBlockquote).toBe("function");
|
|
425
|
+
expect(typeof blockquotes.isInBlockquote).toBe("function");
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|