@joinezco/markdown-editor 0.0.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/LICENSE +661 -0
- package/README.md +9 -0
- package/TEST_README.md +359 -0
- package/dist/editor/commands/index.d.ts +2 -0
- package/dist/editor/commands/index.js +96 -0
- package/dist/editor/extensions/codeblock.d.ts +30 -0
- package/dist/editor/extensions/codeblock.js +390 -0
- package/dist/editor/extensions/filesystem.d.ts +8 -0
- package/dist/editor/extensions/filesystem.js +54 -0
- package/dist/editor/extensions/link.d.ts +1 -0
- package/dist/editor/extensions/link.js +24 -0
- package/dist/editor/extensions/slash-commands.d.ts +17 -0
- package/dist/editor/extensions/slash-commands.js +324 -0
- package/dist/editor/extensions/taskitem.d.ts +8 -0
- package/dist/editor/extensions/taskitem.js +67 -0
- package/dist/editor/index.d.ts +19 -0
- package/dist/editor/index.js +68 -0
- package/dist/editor/styles.d.ts +2 -0
- package/dist/editor/styles.js +153 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/index.html +16 -0
- package/package.json +73 -0
- package/public/fonts/Source_Serif_4/OFL.txt +91 -0
- package/public/fonts/Source_Serif_4/README.txt +128 -0
- package/public/fonts/Source_Serif_4/SourceSerif4-Italic-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Source_Serif_4/SourceSerif4-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-Black.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-BlackItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-Bold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-BoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-ExtraBold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-ExtraBoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-ExtraLight.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-ExtraLightItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-Italic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-Light.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-LightItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-Medium.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-MediumItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-Regular.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-SemiBold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4-SemiBoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Black.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-BlackItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Bold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-BoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-ExtraBold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-ExtraBoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-ExtraLight.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-ExtraLightItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Italic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Light.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-LightItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Medium.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-MediumItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Regular.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-SemiBold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-SemiBoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Black.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-BlackItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Bold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-BoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-ExtraBold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-ExtraBoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-ExtraLight.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-ExtraLightItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Italic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Light.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-LightItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Medium.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-MediumItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Regular.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-SemiBold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-SemiBoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Black.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-BlackItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Bold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-BoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-ExtraBold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-ExtraBoldItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-ExtraLight.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-ExtraLightItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Italic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Light.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-LightItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Medium.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-MediumItalic.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Regular.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-SemiBold.ttf +0 -0
- package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-SemiBoldItalic.ttf +0 -0
- package/public/snapshot.bin +0 -0
- package/src/App.css +236 -0
- package/src/App.tsx +73 -0
- package/src/index.css +63 -0
- package/src/lib/editor/commands/index.ts +110 -0
- package/src/lib/editor/extensions/__screenshots__/bullet-to-task.test.ts/BulletToTaskConverter-should-convert-bullet-list-item-to-checked-task-item-when--x--is-typed-1.png +0 -0
- package/src/lib/editor/extensions/__screenshots__/bullet-to-task.test.ts/BulletToTaskConverter-should-convert-bullet-list-item-to-task-item-when-----is-typed-1.png +0 -0
- package/src/lib/editor/extensions/bullet-to-task.test.ts +38 -0
- package/src/lib/editor/extensions/codeblock.ts +447 -0
- package/src/lib/editor/extensions/filesystem.ts +68 -0
- package/src/lib/editor/extensions/link.ts +31 -0
- package/src/lib/editor/extensions/slash-commands.ts +383 -0
- package/src/lib/editor/extensions/taskitem.ts +86 -0
- package/src/lib/editor/index.ts +82 -0
- package/src/lib/editor/styles.ts +166 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/types/css.d.ts +4 -0
- package/src/main.tsx +10 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-be-focusable-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-create-an-editor-instance-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-have-initial-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-render-in-the-DOM-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Browser-specific-Features-should-handle-copy-and-paste-operations-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Browser-specific-Features-should-handle-undo-and-redo-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Editor-State-and-Updates-should-maintain-state-across-content-changes-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Editor-State-and-Updates-should-trigger-update-callbacks-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Error-Handling-should-handle-invalid-markdown-gracefully-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Error-Handling-should-handle-very-long-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Keyboard-Shortcuts-should-handle-Ctrl-B-for-bold-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Keyboard-Shortcuts-should-handle-Ctrl-I-for-italic-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-get-markdown-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-handle-empty-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-set-markdown-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-bold-text-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-code-blocks-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-headings-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-inline-code-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-italic-text-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-lists-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-task-lists-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Selection-and-Cursor-Management-should-handle-cursor-positioning-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Selection-and-Cursor-Management-should-set-and-get-selection-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-handle-line-breaks-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-handle-typing-at-different-positions-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-insert-text-at-cursor-position-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-code-blocks-without-language-specification-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-different-programming-languages-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-inline-code-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-render-code-blocks-with-syntax-highlighting-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Extension-Interactions-should-handle-multiple-extensions-working-together-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Extension-Interactions-should-maintain-editor-state-across-complex-operations-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-File-System-Integration-should-handle-file-references-in-code-blocks-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-File-System-Integration-should-maintain-file-system-state-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-auto-detect-URLs-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-handle-email-links-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-render-links-correctly-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Markdown-Storage-should-provide-markdown-storage-interface-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Markdown-Storage-should-sync-markdown-content-with-storage-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Slash-Commands-Extension-should-handle-heading-commands-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Slash-Commands-Extension-should-trigger-slash-commands-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Table-Extension-should-handle-table-navigation-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Table-Extension-should-render-tables-correctly-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-handle-nested-task-lists-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-render-task-lists-correctly-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-toggle-task-completion-1.png +0 -0
- package/src/test/editor.test.ts +346 -0
- package/src/test/example.ts +92 -0
- package/src/test/extensions.test.ts +333 -0
- package/src/test/setup.ts +55 -0
- package/src/test/utils.ts +212 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +29 -0
- package/tsconfig.json +7 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.node.json +24 -0
- package/vite.config.ts +89 -0
- package/vitest.config.ts +65 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { StyleModule } from 'style-mod';
|
|
2
|
+
|
|
3
|
+
const darkModeStyles = {
|
|
4
|
+
'--ezco-mde-code-bg': 'var(--ezco-mde-code-bg-dark)',
|
|
5
|
+
'--ezco-mde-bg': 'var(--ezco-mde-bg-dark)',
|
|
6
|
+
'--ezco-mde-table-bg': 'var(--cm-toolbar-bg-dark)',
|
|
7
|
+
}
|
|
8
|
+
export const styleModule: StyleModule = new StyleModule({
|
|
9
|
+
':root[data-theme="dark"], [data-theme="dark"] .ezco-mde, .ezco-mde[data-theme="dark"]': darkModeStyles,
|
|
10
|
+
'@media (prefers-color-scheme: dark)': {
|
|
11
|
+
'div.ezco-mde': darkModeStyles
|
|
12
|
+
},
|
|
13
|
+
':root, :root[data-theme="light"], [data-theme="light"] .ezco-mde, .ezco-mde[data-theme="light"]': {
|
|
14
|
+
// Light/dark mode vars
|
|
15
|
+
'--ezco-mde-code-bg-light': '#f1f1f1',
|
|
16
|
+
'--ezco-mde-code-bg-dark': '#2c2c2c',
|
|
17
|
+
'--ezco-mde-bg-light': '#ffffff',
|
|
18
|
+
'--ezco-mde-bg-dark': '#1e1e1e',
|
|
19
|
+
'--ezco-mde-link-color': '#5861ff',
|
|
20
|
+
'--ezco-mde-link-color-hover': '#383ea3',
|
|
21
|
+
|
|
22
|
+
// Default to light mode, overridden by media query
|
|
23
|
+
'--ezco-mde-code-bg': 'var(--ezco-mde-code-bg-light)',
|
|
24
|
+
'--ezco-mde-bg': 'var(--ezco-mde-bg-light)',
|
|
25
|
+
'--ezco-mde-table-bg': 'var(--cm-toolbar-bg-light)',
|
|
26
|
+
},
|
|
27
|
+
'.ezco-mde': {
|
|
28
|
+
|
|
29
|
+
// Base editor styles
|
|
30
|
+
'background': 'transparent',
|
|
31
|
+
|
|
32
|
+
'& a': {
|
|
33
|
+
color: 'var(--ezco-mde-link-color)',
|
|
34
|
+
'text-decoration': 'inherit',
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
'& a:hover': {
|
|
38
|
+
color: 'var(--ezco-mde-link-color-hover)',
|
|
39
|
+
cursor: 'pointer',
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Codeblock styles
|
|
43
|
+
'& .cm-editor': {
|
|
44
|
+
margin: '2rem 0',
|
|
45
|
+
border: '2px solid var(--ezco-mde-table-bg)'
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Inline code styles
|
|
49
|
+
'& > :not(.cm-editor) code': {
|
|
50
|
+
'font-family': 'monospace',
|
|
51
|
+
background: 'var(--ezco-mde-code-bg)',
|
|
52
|
+
padding: '0.1em 0.3em',
|
|
53
|
+
'border-radius': '3px',
|
|
54
|
+
},
|
|
55
|
+
// Table styles
|
|
56
|
+
'&.tableWrapper': {
|
|
57
|
+
margin: '1.5rem 0',
|
|
58
|
+
'overflow-x': 'auto'
|
|
59
|
+
},
|
|
60
|
+
'& table': {
|
|
61
|
+
"border-collapse": "collapse",
|
|
62
|
+
"width": "100%",
|
|
63
|
+
"margin": "2em 0",
|
|
64
|
+
border: '2px solid var(--ezco-mde-table-bg)',
|
|
65
|
+
overflow: 'hidden',
|
|
66
|
+
'table-layout': 'fixed',
|
|
67
|
+
'& p': {
|
|
68
|
+
margin: 0
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
'& > .column-resize-handle': {
|
|
72
|
+
'background-color': 'red',
|
|
73
|
+
bottom: '-2px',
|
|
74
|
+
'pointer-events': 'none',
|
|
75
|
+
position: 'absolute',
|
|
76
|
+
right: '-2px',
|
|
77
|
+
top: 0,
|
|
78
|
+
width: '4px',
|
|
79
|
+
},
|
|
80
|
+
'& th': {
|
|
81
|
+
'font-weight': 'bold',
|
|
82
|
+
'background-color': 'var(--ezco-mde-table-bg)',
|
|
83
|
+
'text-align': 'left',
|
|
84
|
+
},
|
|
85
|
+
'& th, & td': {
|
|
86
|
+
border: 'none',
|
|
87
|
+
padding: '0.5em',
|
|
88
|
+
'vertical-align': 'top',
|
|
89
|
+
position: 'relative',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
'& .selectedCell::after': {
|
|
93
|
+
'z-index': 2,
|
|
94
|
+
position: 'absolute',
|
|
95
|
+
content: '""',
|
|
96
|
+
left: 0,
|
|
97
|
+
right: 0,
|
|
98
|
+
top: 0,
|
|
99
|
+
bottom: 0,
|
|
100
|
+
background: 'rgba(0, 123, 255, 0.1)',
|
|
101
|
+
'pointer-events': 'none',
|
|
102
|
+
},
|
|
103
|
+
'&.resize-cursor': {
|
|
104
|
+
'&': {
|
|
105
|
+
cursor: 'ew-resize',
|
|
106
|
+
},
|
|
107
|
+
cursor: 'col-resize',
|
|
108
|
+
},
|
|
109
|
+
// Tight list styles
|
|
110
|
+
'& .tight': {
|
|
111
|
+
margin: '0 18px',
|
|
112
|
+
'& li': {
|
|
113
|
+
'padding-left': '2px',
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
// List styles
|
|
117
|
+
'& ul, & ol, & menu': {
|
|
118
|
+
padding: 0,
|
|
119
|
+
},
|
|
120
|
+
// List item styles
|
|
121
|
+
'& li > p': {
|
|
122
|
+
'margin-top': 0,
|
|
123
|
+
'margin-bottom': 0,
|
|
124
|
+
},
|
|
125
|
+
// Task list styles
|
|
126
|
+
'& li[data-checked="true"]>div>p': {
|
|
127
|
+
"text-decoration": "line-through",
|
|
128
|
+
"color": "#888",
|
|
129
|
+
},
|
|
130
|
+
'& ul[data-type="taskList"]': {
|
|
131
|
+
'list-style': 'none',
|
|
132
|
+
'padding': 0,
|
|
133
|
+
'margin': 0,
|
|
134
|
+
|
|
135
|
+
'& p': {
|
|
136
|
+
'margin': 0,
|
|
137
|
+
},
|
|
138
|
+
'& p + ul[data-type="taskList"]': {
|
|
139
|
+
'margin-top': '0.25em'
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
'& p + p': {
|
|
143
|
+
'margin-top': '0.25em',
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
'& li': {
|
|
147
|
+
display: 'flex',
|
|
148
|
+
'align-items': 'flex-start',
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
'& li + li': {
|
|
152
|
+
'margin-top': '0.25em'
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
'& li > label': {
|
|
156
|
+
'margin-right': '6px'
|
|
157
|
+
},
|
|
158
|
+
'& li > label > input': {
|
|
159
|
+
margin: 0
|
|
160
|
+
},
|
|
161
|
+
'& li > div': {
|
|
162
|
+
flex: 1
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
})
|
package/src/lib/index.ts
ADDED
package/src/main.tsx
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { MarkdownEditor } from '../lib/editor'
|
|
3
|
+
import {
|
|
4
|
+
createTestContainer,
|
|
5
|
+
createTestEditor,
|
|
6
|
+
pressKey,
|
|
7
|
+
waitFor,
|
|
8
|
+
cleanupEditor
|
|
9
|
+
} from './utils'
|
|
10
|
+
|
|
11
|
+
describe('MarkdownEditor', () => {
|
|
12
|
+
let container: HTMLElement
|
|
13
|
+
let editor: MarkdownEditor
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
container = createTestContainer()
|
|
17
|
+
editor = await createTestEditor(container)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
cleanupEditor(editor, container)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('Basic Editor Functionality', () => {
|
|
25
|
+
it('should create an editor instance', () => {
|
|
26
|
+
expect(editor).toBeDefined()
|
|
27
|
+
if (!editor) return
|
|
28
|
+
expect(editor.view).toBeDefined()
|
|
29
|
+
expect(editor.view.dom).toBeDefined()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should render in the DOM', () => {
|
|
33
|
+
expect(container.querySelector('.ProseMirror')).toBeTruthy()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should have initial content', () => {
|
|
37
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
38
|
+
expect(content).toContain('# Test Document')
|
|
39
|
+
expect(content).toContain('This is a test document.')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should be focusable', () => {
|
|
43
|
+
editor.commands.focus()
|
|
44
|
+
expect(editor.isFocused).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('Markdown Content Management', () => {
|
|
49
|
+
it('should get markdown content', () => {
|
|
50
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
51
|
+
expect(typeof content).toBe('string')
|
|
52
|
+
expect(content.length).toBeGreaterThan(0)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should set markdown content', () => {
|
|
56
|
+
const newContent = '# New Heading\n\nNew paragraph content.'
|
|
57
|
+
editor.commands.setContent(newContent)
|
|
58
|
+
|
|
59
|
+
const retrievedContent = editor.storage.markdown.getMarkdown()
|
|
60
|
+
expect(retrievedContent).toContain('# New Heading')
|
|
61
|
+
expect(retrievedContent).toContain('New paragraph content.')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should handle empty content', () => {
|
|
65
|
+
editor.commands.setContent('')
|
|
66
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
67
|
+
expect(content.trim()).toBe('')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('Text Input and Editing', () => {
|
|
72
|
+
it('should insert text at cursor position', () => {
|
|
73
|
+
editor.commands.focus()
|
|
74
|
+
|
|
75
|
+
editor.commands.insertContentAt(0, 'Inserted text: ')
|
|
76
|
+
|
|
77
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
78
|
+
expect(content).toMatch(/^Inserted text:/)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should handle typing at different positions', () => {
|
|
82
|
+
// Set a simpler test content to avoid position calculation issues
|
|
83
|
+
editor.commands.setContent('This is a test document.')
|
|
84
|
+
editor.commands.focus()
|
|
85
|
+
|
|
86
|
+
const originalContent = editor.storage.markdown.getMarkdown()
|
|
87
|
+
const insertPosition = originalContent.indexOf('This is')
|
|
88
|
+
|
|
89
|
+
// Ensure we found the target text
|
|
90
|
+
expect(insertPosition).toBe(0) // Should be at the beginning
|
|
91
|
+
|
|
92
|
+
editor.commands.setTextSelection(insertPosition)
|
|
93
|
+
const { from } = editor.state.selection
|
|
94
|
+
editor.commands.insertContentAt(from, 'INSERTED ')
|
|
95
|
+
|
|
96
|
+
const newContent = editor.storage.markdown.getMarkdown()
|
|
97
|
+
expect(newContent).toContain('INSERTED This is')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should handle line breaks', () => {
|
|
101
|
+
editor.commands.focus()
|
|
102
|
+
editor.commands.setTextSelection(0)
|
|
103
|
+
const { from } = editor.state.selection
|
|
104
|
+
editor.commands.insertContentAt(from, 'First line\nSecond line\n')
|
|
105
|
+
|
|
106
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
107
|
+
expect(content).toContain('First line')
|
|
108
|
+
expect(content).toContain('Second line')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('Markdown Formatting', () => {
|
|
113
|
+
it('should handle bold text', () => {
|
|
114
|
+
editor.commands.setContent('This is **bold** text.')
|
|
115
|
+
|
|
116
|
+
const strongElement = editor.view.dom.querySelector('strong')
|
|
117
|
+
expect(strongElement).toBeTruthy()
|
|
118
|
+
expect(strongElement?.textContent).toBe('bold')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should handle italic text', () => {
|
|
122
|
+
editor.commands.setContent('This is *italic* text.')
|
|
123
|
+
|
|
124
|
+
const emElement = editor.view.dom.querySelector('em')
|
|
125
|
+
expect(emElement).toBeTruthy()
|
|
126
|
+
expect(emElement?.textContent).toBe('italic')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('should handle headings', () => {
|
|
130
|
+
editor.commands.setContent('# Heading 1\n## Heading 2\n### Heading 3')
|
|
131
|
+
|
|
132
|
+
const h1Element = editor.view.dom.querySelector('h1')
|
|
133
|
+
const h2Element = editor.view.dom.querySelector('h2')
|
|
134
|
+
const h3Element = editor.view.dom.querySelector('h3')
|
|
135
|
+
|
|
136
|
+
expect(h1Element).toBeTruthy()
|
|
137
|
+
expect(h1Element?.textContent).toBe('Heading 1')
|
|
138
|
+
|
|
139
|
+
expect(h2Element).toBeTruthy()
|
|
140
|
+
expect(h2Element?.textContent).toBe('Heading 2')
|
|
141
|
+
|
|
142
|
+
expect(h3Element).toBeTruthy()
|
|
143
|
+
expect(h3Element?.textContent).toBe('Heading 3')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should handle code blocks', () => {
|
|
147
|
+
editor.commands.setContent('```javascript\nconsole.log("Hello");\n```')
|
|
148
|
+
|
|
149
|
+
const preElement = editor.view.dom.querySelector('pre')
|
|
150
|
+
expect(preElement).toBeTruthy()
|
|
151
|
+
|
|
152
|
+
const codeContent = preElement?.textContent || ''
|
|
153
|
+
expect(codeContent).toContain('console.log')
|
|
154
|
+
expect(codeContent).toContain('Hello')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should handle inline code', () => {
|
|
158
|
+
editor.commands.setContent('This is `inline code` text.')
|
|
159
|
+
|
|
160
|
+
const codeElement = editor.view.dom.querySelector('code')
|
|
161
|
+
expect(codeElement).toBeTruthy()
|
|
162
|
+
expect(codeElement?.textContent).toBe('inline code')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should handle lists', () => {
|
|
166
|
+
editor.commands.setContent('- Item 1\n- Item 2\n- Item 3')
|
|
167
|
+
|
|
168
|
+
const ulElement = editor.view.dom.querySelector('ul')
|
|
169
|
+
expect(ulElement).toBeTruthy()
|
|
170
|
+
|
|
171
|
+
const liElements = ulElement?.querySelectorAll('li')
|
|
172
|
+
expect(liElements?.length).toBe(3)
|
|
173
|
+
|
|
174
|
+
expect(liElements?.[0].textContent?.trim()).toBe('Item 1')
|
|
175
|
+
expect(liElements?.[1].textContent?.trim()).toBe('Item 2')
|
|
176
|
+
expect(liElements?.[2].textContent?.trim()).toBe('Item 3')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('should handle task lists', () => {
|
|
180
|
+
editor.commands.setContent('- [ ] Unchecked task\n- [x] Checked task')
|
|
181
|
+
|
|
182
|
+
const uncheckedTask = editor.view.dom.querySelector('[data-checked="false"]')
|
|
183
|
+
const checkedTask = editor.view.dom.querySelector('[data-checked="true"]')
|
|
184
|
+
|
|
185
|
+
expect(uncheckedTask).toBeTruthy()
|
|
186
|
+
expect(uncheckedTask?.textContent).toContain('Unchecked task')
|
|
187
|
+
|
|
188
|
+
expect(checkedTask).toBeTruthy()
|
|
189
|
+
expect(checkedTask?.textContent).toContain('Checked task')
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('Selection and Cursor Management', () => {
|
|
194
|
+
it('should set and get selection', () => {
|
|
195
|
+
editor.commands.focus()
|
|
196
|
+
editor.commands.setTextSelection({ from: 5, to: 10 })
|
|
197
|
+
const selection = editor.state.selection
|
|
198
|
+
expect(selection.from).toBe(5)
|
|
199
|
+
expect(selection.to).toBe(10)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('should handle cursor positioning', () => {
|
|
203
|
+
editor.commands.focus()
|
|
204
|
+
editor.commands.setTextSelection(0)
|
|
205
|
+
const selection = editor.state.selection
|
|
206
|
+
expect(selection.from).toBe(0)
|
|
207
|
+
expect(selection.to).toBe(0)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe('Keyboard Shortcuts', () => {
|
|
212
|
+
it('should handle Ctrl+B for bold', () => {
|
|
213
|
+
editor.commands.focus()
|
|
214
|
+
editor.commands.setTextSelection(0)
|
|
215
|
+
const { from } = editor.state.selection
|
|
216
|
+
editor.commands.insertContentAt(from, 'bold text')
|
|
217
|
+
editor.commands.setTextSelection({ from: 0, to: 10 }) // Select "bold text"
|
|
218
|
+
|
|
219
|
+
pressKey(editor, 'b', { ctrl: true })
|
|
220
|
+
|
|
221
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
222
|
+
expect(content).toContain('**bold text**')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should handle Ctrl+I for italic', () => {
|
|
226
|
+
editor.commands.focus()
|
|
227
|
+
editor.commands.setTextSelection(0)
|
|
228
|
+
const { from } = editor.state.selection
|
|
229
|
+
editor.commands.insertContentAt(from, 'italic text')
|
|
230
|
+
editor.commands.setTextSelection({ from: 0, to: 12 }) // Select "italic text"
|
|
231
|
+
|
|
232
|
+
pressKey(editor, 'i', { ctrl: true })
|
|
233
|
+
|
|
234
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
235
|
+
expect(content).toContain('*italic text*')
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
describe('Editor State and Updates', () => {
|
|
240
|
+
it('should trigger update callbacks', async () => {
|
|
241
|
+
let updateCount = 0
|
|
242
|
+
let lastContent = ''
|
|
243
|
+
|
|
244
|
+
// Create a new editor with update callback
|
|
245
|
+
cleanupEditor(editor, container)
|
|
246
|
+
container = createTestContainer('test-editor-2')
|
|
247
|
+
|
|
248
|
+
editor = await createTestEditor(container, {
|
|
249
|
+
onUpdate: ({ editor }) => {
|
|
250
|
+
updateCount++
|
|
251
|
+
lastContent = (editor as MarkdownEditor).storage.markdown.getMarkdown()
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
editor.commands.focus()
|
|
256
|
+
const { from } = editor.state.selection
|
|
257
|
+
editor.commands.insertContentAt(from, 'New content')
|
|
258
|
+
|
|
259
|
+
// Wait for update to be processed
|
|
260
|
+
await waitFor(() => updateCount > 0, 2000)
|
|
261
|
+
|
|
262
|
+
expect(updateCount).toBeGreaterThan(0)
|
|
263
|
+
expect(lastContent).toContain('New content')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('should maintain state across content changes', () => {
|
|
267
|
+
const initialContent = editor.storage.markdown.getMarkdown()
|
|
268
|
+
|
|
269
|
+
editor.commands.setContent('# Changed Content')
|
|
270
|
+
expect(editor.storage.markdown.getMarkdown()).toContain('# Changed Content')
|
|
271
|
+
|
|
272
|
+
editor.commands.setContent(initialContent)
|
|
273
|
+
expect(editor.storage.markdown.getMarkdown()).toContain('# Test Document')
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
describe('Error Handling', () => {
|
|
278
|
+
it('should handle invalid markdown gracefully', () => {
|
|
279
|
+
expect(() => {
|
|
280
|
+
editor.commands.setContent('# Heading\n\n```\nUnclosed code block')
|
|
281
|
+
}).not.toThrow()
|
|
282
|
+
|
|
283
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
284
|
+
expect(content).toContain('Unclosed code block')
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('should handle very long content', () => {
|
|
288
|
+
const longContent = '# Long Content\n\n' + 'A'.repeat(10000)
|
|
289
|
+
|
|
290
|
+
expect(() => {
|
|
291
|
+
editor.commands.setContent(longContent)
|
|
292
|
+
}).not.toThrow()
|
|
293
|
+
|
|
294
|
+
const content = editor.storage.markdown.getMarkdown()
|
|
295
|
+
expect(content.length).toBeGreaterThan(10000)
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
describe('Browser-specific Features', () => {
|
|
300
|
+
it('should handle copy and paste operations', async () => {
|
|
301
|
+
editor.commands.focus()
|
|
302
|
+
editor.commands.setTextSelection(0)
|
|
303
|
+
const { from } = editor.state.selection
|
|
304
|
+
editor.commands.insertContentAt(from, 'Text to copy')
|
|
305
|
+
editor.commands.setTextSelection({ from: 0, to: 12 }) // Select "Text to copy"
|
|
306
|
+
|
|
307
|
+
// Simulate copy
|
|
308
|
+
pressKey(editor, 'c', { ctrl: true })
|
|
309
|
+
|
|
310
|
+
// Move cursor and paste
|
|
311
|
+
editor.commands.setTextSelection(12)
|
|
312
|
+
const { from: newFrom } = editor.state.selection
|
|
313
|
+
editor.commands.insertContentAt(newFrom, '\n\nPasted: ')
|
|
314
|
+
pressKey(editor, 'v', { ctrl: true })
|
|
315
|
+
|
|
316
|
+
// Note: Actual clipboard operations may not work in test environment
|
|
317
|
+
// This test verifies the key events are handled without errors
|
|
318
|
+
expect(() => {
|
|
319
|
+
pressKey(editor, 'c', { ctrl: true })
|
|
320
|
+
pressKey(editor, 'v', { ctrl: true })
|
|
321
|
+
}).not.toThrow()
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('should handle undo and redo', () => {
|
|
325
|
+
editor.commands.focus()
|
|
326
|
+
const originalContent = editor.storage.markdown.getMarkdown()
|
|
327
|
+
|
|
328
|
+
editor.commands.setTextSelection(0)
|
|
329
|
+
const { from } = editor.state.selection
|
|
330
|
+
editor.commands.insertContentAt(from, 'Added text')
|
|
331
|
+
|
|
332
|
+
// Undo
|
|
333
|
+
pressKey(editor, 'z', { ctrl: true })
|
|
334
|
+
|
|
335
|
+
// Content should be closer to original (undo may not be perfect due to test setup)
|
|
336
|
+
const undoContent = editor.storage.markdown.getMarkdown()
|
|
337
|
+
expect(undoContent).toBeDefined()
|
|
338
|
+
|
|
339
|
+
// Redo
|
|
340
|
+
pressKey(editor, 'y', { ctrl: true })
|
|
341
|
+
|
|
342
|
+
const redoContent = editor.storage.markdown.getMarkdown()
|
|
343
|
+
expect(redoContent).toBeDefined()
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
})
|