quasar-ui-danx 0.5.0 → 0.5.2
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/.claude/settings.local.json +8 -0
- package/dist/danx.es.js +16119 -10641
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +202 -123
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +8 -1
- package/src/components/Utility/Buttons/ActionButton.vue +15 -5
- package/src/components/Utility/Code/CodeViewer.vue +41 -16
- package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
- package/src/components/Utility/Code/CodeViewerFooter.vue +3 -1
- package/src/components/Utility/Code/LanguageBadge.vue +278 -5
- package/src/components/Utility/Code/MarkdownContent.vue +31 -163
- 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 +233 -0
- package/src/components/Utility/Markdown/MarkdownEditorContent.vue +296 -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/Widgets/LabelPillWidget.vue +20 -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 +805 -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 +388 -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 +1077 -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/useCodeFormat.ts +17 -10
- package/src/composables/useCodeViewerEditor.spec.ts +655 -0
- package/src/composables/useCodeViewerEditor.ts +174 -20
- package/src/helpers/formats/highlightCSS.ts +236 -0
- package/src/helpers/formats/highlightHTML.ts +483 -0
- package/src/helpers/formats/highlightJavaScript.ts +346 -0
- package/src/helpers/formats/highlightSyntax.ts +15 -4
- package/src/helpers/formats/index.ts +3 -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 +425 -0
- package/src/helpers/formats/markdown/index.ts +7 -0
- package/src/helpers/formats/markdown/linePatterns.spec.ts +498 -0
- package/src/helpers/formats/markdown/linePatterns.ts +172 -0
- package/src/styles/danx.scss +3 -3
- package/src/styles/index.scss +5 -5
- package/src/styles/themes/danx/code.scss +257 -1
- package/src/styles/themes/danx/index.scss +10 -10
- package/src/styles/themes/danx/markdown.scss +59 -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/highlighters.test.ts +153 -0
- package/src/test/setup.test.ts +12 -0
- package/src/test/setup.ts +12 -0
- package/src/types/widgets.d.ts +2 -2
- package/vite.config.js +5 -1
- package/vitest.config.ts +19 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Code Viewer Theme
|
|
2
|
-
//
|
|
2
|
+
// Supports dark (default) and light themes with syntax highlighting for JSON/YAML
|
|
3
3
|
|
|
4
4
|
.dx-code-viewer {
|
|
5
5
|
width: 100%;
|
|
@@ -134,6 +134,58 @@
|
|
|
134
134
|
color: #808080;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// Comments - Muted green
|
|
138
|
+
.syntax-comment {
|
|
139
|
+
color: #6a9955;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// CSS/HTML specific
|
|
143
|
+
.syntax-selector {
|
|
144
|
+
color: #d7ba7d; // Gold/yellow
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.syntax-property {
|
|
148
|
+
color: #9cdcfe; // Light blue (same as key)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.syntax-value {
|
|
152
|
+
color: #ce9178; // Orange (same as number)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.syntax-at-rule {
|
|
156
|
+
color: #c586c0; // Purple/magenta
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// JavaScript specific
|
|
160
|
+
.syntax-keyword {
|
|
161
|
+
color: #569cd6; // Blue
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.syntax-operator {
|
|
165
|
+
color: #d4d4d4; // Gray/white
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.syntax-regex {
|
|
169
|
+
color: #d16969; // Red
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.syntax-template {
|
|
173
|
+
color: #ce9178; // Orange (same as string/number)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// HTML specific
|
|
177
|
+
.syntax-tag {
|
|
178
|
+
color: #569cd6; // Blue
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.syntax-attribute {
|
|
182
|
+
color: #9cdcfe; // Light blue
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.syntax-doctype {
|
|
186
|
+
color: #808080; // Gray
|
|
187
|
+
}
|
|
188
|
+
|
|
137
189
|
|
|
138
190
|
// ==========================================
|
|
139
191
|
// Editable mode (contenteditable)
|
|
@@ -155,4 +207,208 @@
|
|
|
155
207
|
// Caret color
|
|
156
208
|
caret-color: #d4d4d4;
|
|
157
209
|
}
|
|
210
|
+
|
|
211
|
+
// ==========================================
|
|
212
|
+
// LIGHT THEME VARIANT
|
|
213
|
+
// ==========================================
|
|
214
|
+
&.theme-light {
|
|
215
|
+
.code-collapsed {
|
|
216
|
+
background-color: #f5f5f5;
|
|
217
|
+
|
|
218
|
+
&:hover {
|
|
219
|
+
background-color: #ebebeb;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.code-collapsed-preview {
|
|
223
|
+
color: #333333;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.code-content {
|
|
228
|
+
background-color: #f8fafc;
|
|
229
|
+
color: #1e293b;
|
|
230
|
+
border: 1px solid #e2e8f0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.code-footer {
|
|
234
|
+
background-color: #f1f5f9;
|
|
235
|
+
border-top: 1px solid #e2e8f0;
|
|
236
|
+
|
|
237
|
+
&.has-error {
|
|
238
|
+
background-color: #fee2e2;
|
|
239
|
+
border-top: 1px solid #ef4444;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.language-badge {
|
|
244
|
+
background-color: #e2e8f0;
|
|
245
|
+
color: #64748b;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Light theme syntax highlighting
|
|
249
|
+
.syntax-key {
|
|
250
|
+
color: #0369a1;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.syntax-string {
|
|
254
|
+
color: #15803d;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.syntax-number {
|
|
258
|
+
color: #c2410c;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.syntax-boolean {
|
|
262
|
+
color: #7c3aed;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.syntax-null {
|
|
266
|
+
color: #7c3aed;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.syntax-punctuation {
|
|
270
|
+
color: #64748b;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Light theme - Comments
|
|
274
|
+
.syntax-comment {
|
|
275
|
+
color: #6a9955;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Light theme - CSS/HTML specific
|
|
279
|
+
.syntax-selector {
|
|
280
|
+
color: #b45309; // Amber/brown
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.syntax-property {
|
|
284
|
+
color: #0369a1; // Blue (same as key)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.syntax-value {
|
|
288
|
+
color: #c2410c; // Orange (same as number)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.syntax-at-rule {
|
|
292
|
+
color: #7c3aed; // Purple
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Light theme - JavaScript specific
|
|
296
|
+
.syntax-keyword {
|
|
297
|
+
color: #0369a1; // Blue
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.syntax-operator {
|
|
301
|
+
color: #64748b; // Gray
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.syntax-regex {
|
|
305
|
+
color: #be123c; // Red/rose
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.syntax-template {
|
|
309
|
+
color: #c2410c; // Orange
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Light theme - HTML specific
|
|
313
|
+
.syntax-tag {
|
|
314
|
+
color: #0369a1; // Blue
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.syntax-attribute {
|
|
318
|
+
color: #0891b2; // Cyan
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.syntax-doctype {
|
|
322
|
+
color: #64748b; // Gray
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.code-content.is-editable {
|
|
326
|
+
caret-color: #1e293b;
|
|
327
|
+
|
|
328
|
+
&:focus {
|
|
329
|
+
border-color: rgba(14, 165, 233, 0.6);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
&:hover:not(:focus) {
|
|
333
|
+
border-color: rgba(14, 165, 233, 0.3);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Light theme - Language badge component overrides
|
|
338
|
+
.dx-language-badge-container {
|
|
339
|
+
// Container needs no background changes
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.dx-language-badge {
|
|
343
|
+
background: rgba(0, 0, 0, 0.08);
|
|
344
|
+
color: #64748b;
|
|
345
|
+
|
|
346
|
+
&.is-active {
|
|
347
|
+
background: rgba(0, 0, 0, 0.12);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.dx-language-option {
|
|
352
|
+
background: rgba(0, 0, 0, 0.06);
|
|
353
|
+
color: #64748b;
|
|
354
|
+
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
|
355
|
+
|
|
356
|
+
&:hover {
|
|
357
|
+
background: rgba(0, 0, 0, 0.12);
|
|
358
|
+
color: #1e293b;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.dx-language-search-trigger {
|
|
363
|
+
// Inherits from dx-language-option
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.dx-language-search-panel {
|
|
367
|
+
background: #f8fafc;
|
|
368
|
+
border: 1px solid #e2e8f0;
|
|
369
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.dx-language-search-input {
|
|
373
|
+
background: #f1f5f9;
|
|
374
|
+
border-bottom: 1px solid #e2e8f0;
|
|
375
|
+
color: #1e293b;
|
|
376
|
+
|
|
377
|
+
&::placeholder {
|
|
378
|
+
color: #94a3b8;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
&:focus {
|
|
382
|
+
background: #e2e8f0;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.dx-language-search-list {
|
|
387
|
+
&::-webkit-scrollbar-track {
|
|
388
|
+
background: #f1f5f9;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
&::-webkit-scrollbar-thumb {
|
|
392
|
+
background: #cbd5e1;
|
|
393
|
+
|
|
394
|
+
&:hover {
|
|
395
|
+
background: #94a3b8;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.dx-language-search-item {
|
|
401
|
+
color: #64748b;
|
|
402
|
+
|
|
403
|
+
&:hover,
|
|
404
|
+
&.is-selected {
|
|
405
|
+
background: #e2e8f0;
|
|
406
|
+
color: #1e293b;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.dx-language-search-empty {
|
|
411
|
+
color: #94a3b8;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
158
414
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
@
|
|
2
|
-
@
|
|
3
|
-
@
|
|
4
|
-
@
|
|
5
|
-
@
|
|
6
|
-
@
|
|
7
|
-
@
|
|
8
|
-
@
|
|
9
|
-
@
|
|
10
|
-
@
|
|
1
|
+
@use "action-table";
|
|
2
|
+
@use "buttons";
|
|
3
|
+
@use "carousels";
|
|
4
|
+
@use "code";
|
|
5
|
+
@use "dialogs";
|
|
6
|
+
@use "forms";
|
|
7
|
+
@use "markdown";
|
|
8
|
+
@use "panels";
|
|
9
|
+
@use "sidebar";
|
|
10
|
+
@use "toolbar";
|
|
@@ -238,4 +238,63 @@
|
|
|
238
238
|
text-decoration: none;
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
+
|
|
242
|
+
// ==========================================
|
|
243
|
+
// LIGHT THEME VARIANT
|
|
244
|
+
// ==========================================
|
|
245
|
+
&.theme-light {
|
|
246
|
+
color: #1e293b;
|
|
247
|
+
|
|
248
|
+
// Inline code - light theme
|
|
249
|
+
code {
|
|
250
|
+
background: #f1f5f9;
|
|
251
|
+
color: #0f172a;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Code blocks - light theme
|
|
255
|
+
pre {
|
|
256
|
+
background: #f8fafc;
|
|
257
|
+
border: 1px solid #e2e8f0;
|
|
258
|
+
|
|
259
|
+
code {
|
|
260
|
+
background: transparent;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Blockquotes - light theme
|
|
265
|
+
blockquote {
|
|
266
|
+
border-left-color: #cbd5e1;
|
|
267
|
+
color: #475569;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Links - light theme
|
|
271
|
+
a {
|
|
272
|
+
color: #0369a1;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Horizontal rules - light theme
|
|
276
|
+
hr {
|
|
277
|
+
border-top-color: #e2e8f0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Tables - light theme
|
|
281
|
+
table {
|
|
282
|
+
th, td {
|
|
283
|
+
border-color: #e2e8f0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
th {
|
|
287
|
+
background: #f1f5f9;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
tr:nth-child(even) {
|
|
291
|
+
background: #f8fafc;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Highlight - light theme
|
|
296
|
+
mark {
|
|
297
|
+
background: rgba(250, 204, 21, 0.5);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
241
300
|
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { createTestEditor, TestEditorResult } from './editorTestUtils';
|
|
3
|
+
|
|
4
|
+
describe('editorTestUtils', () => {
|
|
5
|
+
let editor: TestEditorResult;
|
|
6
|
+
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
if (editor) {
|
|
9
|
+
editor.destroy();
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('createTestEditor', () => {
|
|
14
|
+
it('should create a contenteditable container', () => {
|
|
15
|
+
editor = createTestEditor('<p>Hello World</p>');
|
|
16
|
+
|
|
17
|
+
expect(editor.container).toBeInstanceOf(HTMLElement);
|
|
18
|
+
expect(editor.container.getAttribute('contenteditable')).toBe('true');
|
|
19
|
+
expect(editor.container.innerHTML).toBe('<p>Hello World</p>');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should trim whitespace from initial HTML', () => {
|
|
23
|
+
editor = createTestEditor(' <p>Test</p> ');
|
|
24
|
+
|
|
25
|
+
expect(editor.container.innerHTML).toBe('<p>Test</p>');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('getHtml', () => {
|
|
30
|
+
it('should return current HTML content', () => {
|
|
31
|
+
editor = createTestEditor('<p>Initial</p>');
|
|
32
|
+
|
|
33
|
+
expect(editor.getHtml()).toBe('<p>Initial</p>');
|
|
34
|
+
|
|
35
|
+
// Modify content
|
|
36
|
+
editor.container.innerHTML = '<p>Modified</p>';
|
|
37
|
+
expect(editor.getHtml()).toBe('<p>Modified</p>');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('getMarkdown', () => {
|
|
42
|
+
it('should convert HTML to markdown', () => {
|
|
43
|
+
editor = createTestEditor('<p>Hello <strong>bold</strong> text</p>');
|
|
44
|
+
|
|
45
|
+
const markdown = editor.getMarkdown();
|
|
46
|
+
expect(markdown).toBe('Hello **bold** text');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle headings', () => {
|
|
50
|
+
editor = createTestEditor('<h2>Heading Two</h2>');
|
|
51
|
+
|
|
52
|
+
expect(editor.getMarkdown()).toBe('## Heading Two');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle lists', () => {
|
|
56
|
+
editor = createTestEditor('<ul><li>Item 1</li><li>Item 2</li></ul>');
|
|
57
|
+
|
|
58
|
+
const markdown = editor.getMarkdown();
|
|
59
|
+
expect(markdown).toContain('- Item 1');
|
|
60
|
+
expect(markdown).toContain('- Item 2');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('getBlock and getBlocks', () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
editor = createTestEditor('<p>First</p><p>Second</p><p>Third</p>');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should get a block by index', () => {
|
|
70
|
+
const block0 = editor.getBlock(0);
|
|
71
|
+
const block1 = editor.getBlock(1);
|
|
72
|
+
const block2 = editor.getBlock(2);
|
|
73
|
+
|
|
74
|
+
expect(block0?.textContent).toBe('First');
|
|
75
|
+
expect(block1?.textContent).toBe('Second');
|
|
76
|
+
expect(block2?.textContent).toBe('Third');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return null for invalid index', () => {
|
|
80
|
+
expect(editor.getBlock(10)).toBeNull();
|
|
81
|
+
expect(editor.getBlock(-1)).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should get all blocks', () => {
|
|
85
|
+
const blocks = editor.getBlocks();
|
|
86
|
+
|
|
87
|
+
expect(blocks).toHaveLength(3);
|
|
88
|
+
expect(blocks[0].textContent).toBe('First');
|
|
89
|
+
expect(blocks[2].textContent).toBe('Third');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('setCursor and getCursorPosition', () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
editor = createTestEditor('<p>Hello World</p>');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should set cursor position in a text node', () => {
|
|
99
|
+
const textNode = editor.getBlock(0)?.firstChild;
|
|
100
|
+
if (!textNode) throw new Error('Text node not found');
|
|
101
|
+
|
|
102
|
+
editor.setCursor(textNode, 5);
|
|
103
|
+
|
|
104
|
+
const pos = editor.getCursorPosition();
|
|
105
|
+
expect(pos.node).toBe(textNode);
|
|
106
|
+
expect(pos.offset).toBe(5);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should return null node when no selection', () => {
|
|
110
|
+
// Clear any existing selection
|
|
111
|
+
window.getSelection()?.removeAllRanges();
|
|
112
|
+
|
|
113
|
+
const pos = editor.getCursorPosition();
|
|
114
|
+
expect(pos.node).toBeNull();
|
|
115
|
+
expect(pos.offset).toBe(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('setCursorInBlock and getCursorOffsetInBlock', () => {
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
editor = createTestEditor('<p>First paragraph</p><p>Second paragraph</p>');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should set cursor at offset within a block', () => {
|
|
125
|
+
editor.setCursorInBlock(0, 6);
|
|
126
|
+
|
|
127
|
+
const offset = editor.getCursorOffsetInBlock(0);
|
|
128
|
+
expect(offset).toBe(6); // After "First "
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should set cursor in second block', () => {
|
|
132
|
+
editor.setCursorInBlock(1, 7);
|
|
133
|
+
|
|
134
|
+
const offset = editor.getCursorOffsetInBlock(1);
|
|
135
|
+
expect(offset).toBe(7); // After "Second "
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should return -1 for invalid block index', () => {
|
|
139
|
+
editor.setCursorInBlock(0, 0);
|
|
140
|
+
|
|
141
|
+
expect(editor.getCursorOffsetInBlock(10)).toBe(-1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should return -1 when cursor is not in the specified block', () => {
|
|
145
|
+
editor.setCursorInBlock(0, 0);
|
|
146
|
+
|
|
147
|
+
expect(editor.getCursorOffsetInBlock(1)).toBe(-1);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('selectRange and selectInBlock', () => {
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
editor = createTestEditor('<p>Hello World</p>');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should select a text range', () => {
|
|
157
|
+
const textNode = editor.getBlock(0)?.firstChild;
|
|
158
|
+
if (!textNode) throw new Error('Text node not found');
|
|
159
|
+
|
|
160
|
+
editor.selectRange(textNode, 0, textNode, 5);
|
|
161
|
+
|
|
162
|
+
const sel = window.getSelection();
|
|
163
|
+
expect(sel?.toString()).toBe('Hello');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should select text within a block by offsets', () => {
|
|
167
|
+
editor.selectInBlock(0, 6, 11);
|
|
168
|
+
|
|
169
|
+
const sel = window.getSelection();
|
|
170
|
+
expect(sel?.toString()).toBe('World');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('pressKey', () => {
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
editor = createTestEditor('<p>Test</p>');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should dispatch keydown event', () => {
|
|
180
|
+
let receivedEvent: KeyboardEvent | null = null;
|
|
181
|
+
editor.container.addEventListener('keydown', (e) => {
|
|
182
|
+
receivedEvent = e;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
editor.pressKey('a');
|
|
186
|
+
|
|
187
|
+
expect(receivedEvent).not.toBeNull();
|
|
188
|
+
expect(receivedEvent!.key).toBe('a');
|
|
189
|
+
expect(receivedEvent!.code).toBe('KeyA');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should include modifier keys', () => {
|
|
193
|
+
let receivedEvent: KeyboardEvent | null = null;
|
|
194
|
+
editor.container.addEventListener('keydown', (e) => {
|
|
195
|
+
receivedEvent = e;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
editor.pressKey('b', { ctrl: true, shift: true });
|
|
199
|
+
|
|
200
|
+
expect(receivedEvent!.ctrlKey).toBe(true);
|
|
201
|
+
expect(receivedEvent!.shiftKey).toBe(true);
|
|
202
|
+
expect(receivedEvent!.altKey).toBe(false);
|
|
203
|
+
expect(receivedEvent!.metaKey).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should handle special keys', () => {
|
|
207
|
+
let receivedEvent: KeyboardEvent | null = null;
|
|
208
|
+
editor.container.addEventListener('keydown', (e) => {
|
|
209
|
+
receivedEvent = e;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
editor.pressKey('Enter');
|
|
213
|
+
|
|
214
|
+
expect(receivedEvent!.key).toBe('Enter');
|
|
215
|
+
expect(receivedEvent!.code).toBe('Enter');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('type', () => {
|
|
220
|
+
beforeEach(() => {
|
|
221
|
+
editor = createTestEditor('<p>Hello</p>');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should insert text at cursor position', () => {
|
|
225
|
+
const textNode = editor.getBlock(0)?.firstChild;
|
|
226
|
+
if (!textNode) throw new Error('Text node not found');
|
|
227
|
+
|
|
228
|
+
editor.setCursor(textNode, 5);
|
|
229
|
+
editor.type(' World');
|
|
230
|
+
|
|
231
|
+
expect(editor.container.textContent).toContain('Hello');
|
|
232
|
+
expect(editor.container.textContent).toContain('World');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should dispatch input event', () => {
|
|
236
|
+
let inputFired = false;
|
|
237
|
+
editor.container.addEventListener('input', () => {
|
|
238
|
+
inputFired = true;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const textNode = editor.getBlock(0)?.firstChild;
|
|
242
|
+
if (!textNode) throw new Error('Text node not found');
|
|
243
|
+
|
|
244
|
+
editor.setCursor(textNode, 0);
|
|
245
|
+
editor.type('X');
|
|
246
|
+
|
|
247
|
+
expect(inputFired).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('contentRef', () => {
|
|
252
|
+
it('should provide a Vue ref to the container', () => {
|
|
253
|
+
editor = createTestEditor('<p>Test</p>');
|
|
254
|
+
|
|
255
|
+
expect(editor.contentRef.value).toBe(editor.container);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('destroy', () => {
|
|
260
|
+
it('should remove the container from DOM', () => {
|
|
261
|
+
editor = createTestEditor('<p>Test</p>');
|
|
262
|
+
const container = editor.container;
|
|
263
|
+
|
|
264
|
+
expect(document.body.contains(container)).toBe(true);
|
|
265
|
+
|
|
266
|
+
editor.destroy();
|
|
267
|
+
|
|
268
|
+
expect(document.body.contains(container)).toBe(false);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('complex content handling', () => {
|
|
273
|
+
it('should handle nested inline formatting', () => {
|
|
274
|
+
editor = createTestEditor('<p>This is <strong><em>bold italic</em></strong> text</p>');
|
|
275
|
+
|
|
276
|
+
editor.selectInBlock(0, 8, 19);
|
|
277
|
+
|
|
278
|
+
const sel = window.getSelection();
|
|
279
|
+
expect(sel?.toString()).toBe('bold italic');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should handle multiple blocks with cursor operations', () => {
|
|
283
|
+
editor = createTestEditor('<p>Line 1</p><p>Line 2</p><p>Line 3</p>');
|
|
284
|
+
|
|
285
|
+
// Set cursor in middle block
|
|
286
|
+
editor.setCursorInBlock(1, 3);
|
|
287
|
+
|
|
288
|
+
const offset = editor.getCursorOffsetInBlock(1);
|
|
289
|
+
expect(offset).toBe(3);
|
|
290
|
+
|
|
291
|
+
// Verify cursor is not in other blocks
|
|
292
|
+
expect(editor.getCursorOffsetInBlock(0)).toBe(-1);
|
|
293
|
+
expect(editor.getCursorOffsetInBlock(2)).toBe(-1);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|