@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.
Files changed (168) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +9 -0
  3. package/TEST_README.md +359 -0
  4. package/dist/editor/commands/index.d.ts +2 -0
  5. package/dist/editor/commands/index.js +96 -0
  6. package/dist/editor/extensions/codeblock.d.ts +30 -0
  7. package/dist/editor/extensions/codeblock.js +390 -0
  8. package/dist/editor/extensions/filesystem.d.ts +8 -0
  9. package/dist/editor/extensions/filesystem.js +54 -0
  10. package/dist/editor/extensions/link.d.ts +1 -0
  11. package/dist/editor/extensions/link.js +24 -0
  12. package/dist/editor/extensions/slash-commands.d.ts +17 -0
  13. package/dist/editor/extensions/slash-commands.js +324 -0
  14. package/dist/editor/extensions/taskitem.d.ts +8 -0
  15. package/dist/editor/extensions/taskitem.js +67 -0
  16. package/dist/editor/index.d.ts +19 -0
  17. package/dist/editor/index.js +68 -0
  18. package/dist/editor/styles.d.ts +2 -0
  19. package/dist/editor/styles.js +153 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +2 -0
  22. package/index.html +16 -0
  23. package/package.json +73 -0
  24. package/public/fonts/Source_Serif_4/OFL.txt +91 -0
  25. package/public/fonts/Source_Serif_4/README.txt +128 -0
  26. package/public/fonts/Source_Serif_4/SourceSerif4-Italic-VariableFont_opsz,wght.ttf +0 -0
  27. package/public/fonts/Source_Serif_4/SourceSerif4-VariableFont_opsz,wght.ttf +0 -0
  28. package/public/fonts/Source_Serif_4/static/SourceSerif4-Black.ttf +0 -0
  29. package/public/fonts/Source_Serif_4/static/SourceSerif4-BlackItalic.ttf +0 -0
  30. package/public/fonts/Source_Serif_4/static/SourceSerif4-Bold.ttf +0 -0
  31. package/public/fonts/Source_Serif_4/static/SourceSerif4-BoldItalic.ttf +0 -0
  32. package/public/fonts/Source_Serif_4/static/SourceSerif4-ExtraBold.ttf +0 -0
  33. package/public/fonts/Source_Serif_4/static/SourceSerif4-ExtraBoldItalic.ttf +0 -0
  34. package/public/fonts/Source_Serif_4/static/SourceSerif4-ExtraLight.ttf +0 -0
  35. package/public/fonts/Source_Serif_4/static/SourceSerif4-ExtraLightItalic.ttf +0 -0
  36. package/public/fonts/Source_Serif_4/static/SourceSerif4-Italic.ttf +0 -0
  37. package/public/fonts/Source_Serif_4/static/SourceSerif4-Light.ttf +0 -0
  38. package/public/fonts/Source_Serif_4/static/SourceSerif4-LightItalic.ttf +0 -0
  39. package/public/fonts/Source_Serif_4/static/SourceSerif4-Medium.ttf +0 -0
  40. package/public/fonts/Source_Serif_4/static/SourceSerif4-MediumItalic.ttf +0 -0
  41. package/public/fonts/Source_Serif_4/static/SourceSerif4-Regular.ttf +0 -0
  42. package/public/fonts/Source_Serif_4/static/SourceSerif4-SemiBold.ttf +0 -0
  43. package/public/fonts/Source_Serif_4/static/SourceSerif4-SemiBoldItalic.ttf +0 -0
  44. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Black.ttf +0 -0
  45. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-BlackItalic.ttf +0 -0
  46. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Bold.ttf +0 -0
  47. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-BoldItalic.ttf +0 -0
  48. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-ExtraBold.ttf +0 -0
  49. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-ExtraBoldItalic.ttf +0 -0
  50. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-ExtraLight.ttf +0 -0
  51. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-ExtraLightItalic.ttf +0 -0
  52. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Italic.ttf +0 -0
  53. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Light.ttf +0 -0
  54. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-LightItalic.ttf +0 -0
  55. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Medium.ttf +0 -0
  56. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-MediumItalic.ttf +0 -0
  57. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-Regular.ttf +0 -0
  58. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-SemiBold.ttf +0 -0
  59. package/public/fonts/Source_Serif_4/static/SourceSerif4_18pt-SemiBoldItalic.ttf +0 -0
  60. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Black.ttf +0 -0
  61. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-BlackItalic.ttf +0 -0
  62. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Bold.ttf +0 -0
  63. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-BoldItalic.ttf +0 -0
  64. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-ExtraBold.ttf +0 -0
  65. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-ExtraBoldItalic.ttf +0 -0
  66. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-ExtraLight.ttf +0 -0
  67. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-ExtraLightItalic.ttf +0 -0
  68. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Italic.ttf +0 -0
  69. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Light.ttf +0 -0
  70. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-LightItalic.ttf +0 -0
  71. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Medium.ttf +0 -0
  72. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-MediumItalic.ttf +0 -0
  73. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-Regular.ttf +0 -0
  74. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-SemiBold.ttf +0 -0
  75. package/public/fonts/Source_Serif_4/static/SourceSerif4_36pt-SemiBoldItalic.ttf +0 -0
  76. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Black.ttf +0 -0
  77. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-BlackItalic.ttf +0 -0
  78. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Bold.ttf +0 -0
  79. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-BoldItalic.ttf +0 -0
  80. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-ExtraBold.ttf +0 -0
  81. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-ExtraBoldItalic.ttf +0 -0
  82. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-ExtraLight.ttf +0 -0
  83. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-ExtraLightItalic.ttf +0 -0
  84. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Italic.ttf +0 -0
  85. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Light.ttf +0 -0
  86. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-LightItalic.ttf +0 -0
  87. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Medium.ttf +0 -0
  88. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-MediumItalic.ttf +0 -0
  89. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-Regular.ttf +0 -0
  90. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-SemiBold.ttf +0 -0
  91. package/public/fonts/Source_Serif_4/static/SourceSerif4_48pt-SemiBoldItalic.ttf +0 -0
  92. package/public/snapshot.bin +0 -0
  93. package/src/App.css +236 -0
  94. package/src/App.tsx +73 -0
  95. package/src/index.css +63 -0
  96. package/src/lib/editor/commands/index.ts +110 -0
  97. 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
  98. 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
  99. package/src/lib/editor/extensions/bullet-to-task.test.ts +38 -0
  100. package/src/lib/editor/extensions/codeblock.ts +447 -0
  101. package/src/lib/editor/extensions/filesystem.ts +68 -0
  102. package/src/lib/editor/extensions/link.ts +31 -0
  103. package/src/lib/editor/extensions/slash-commands.ts +383 -0
  104. package/src/lib/editor/extensions/taskitem.ts +86 -0
  105. package/src/lib/editor/index.ts +82 -0
  106. package/src/lib/editor/styles.ts +166 -0
  107. package/src/lib/index.ts +3 -0
  108. package/src/lib/types/css.d.ts +4 -0
  109. package/src/main.tsx +10 -0
  110. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-be-focusable-1.png +0 -0
  111. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-create-an-editor-instance-1.png +0 -0
  112. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-have-initial-content-1.png +0 -0
  113. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-render-in-the-DOM-1.png +0 -0
  114. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Browser-specific-Features-should-handle-copy-and-paste-operations-1.png +0 -0
  115. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Browser-specific-Features-should-handle-undo-and-redo-1.png +0 -0
  116. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Editor-State-and-Updates-should-maintain-state-across-content-changes-1.png +0 -0
  117. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Editor-State-and-Updates-should-trigger-update-callbacks-1.png +0 -0
  118. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Error-Handling-should-handle-invalid-markdown-gracefully-1.png +0 -0
  119. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Error-Handling-should-handle-very-long-content-1.png +0 -0
  120. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Keyboard-Shortcuts-should-handle-Ctrl-B-for-bold-1.png +0 -0
  121. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Keyboard-Shortcuts-should-handle-Ctrl-I-for-italic-1.png +0 -0
  122. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-get-markdown-content-1.png +0 -0
  123. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-handle-empty-content-1.png +0 -0
  124. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-set-markdown-content-1.png +0 -0
  125. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-bold-text-1.png +0 -0
  126. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-code-blocks-1.png +0 -0
  127. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-headings-1.png +0 -0
  128. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-inline-code-1.png +0 -0
  129. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-italic-text-1.png +0 -0
  130. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-lists-1.png +0 -0
  131. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-task-lists-1.png +0 -0
  132. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Selection-and-Cursor-Management-should-handle-cursor-positioning-1.png +0 -0
  133. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Selection-and-Cursor-Management-should-set-and-get-selection-1.png +0 -0
  134. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-handle-line-breaks-1.png +0 -0
  135. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-handle-typing-at-different-positions-1.png +0 -0
  136. package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-insert-text-at-cursor-position-1.png +0 -0
  137. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-code-blocks-without-language-specification-1.png +0 -0
  138. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-different-programming-languages-1.png +0 -0
  139. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-inline-code-1.png +0 -0
  140. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-render-code-blocks-with-syntax-highlighting-1.png +0 -0
  141. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Extension-Interactions-should-handle-multiple-extensions-working-together-1.png +0 -0
  142. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Extension-Interactions-should-maintain-editor-state-across-complex-operations-1.png +0 -0
  143. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-File-System-Integration-should-handle-file-references-in-code-blocks-1.png +0 -0
  144. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-File-System-Integration-should-maintain-file-system-state-1.png +0 -0
  145. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-auto-detect-URLs-1.png +0 -0
  146. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-handle-email-links-1.png +0 -0
  147. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-render-links-correctly-1.png +0 -0
  148. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Markdown-Storage-should-provide-markdown-storage-interface-1.png +0 -0
  149. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Markdown-Storage-should-sync-markdown-content-with-storage-1.png +0 -0
  150. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Slash-Commands-Extension-should-handle-heading-commands-1.png +0 -0
  151. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Slash-Commands-Extension-should-trigger-slash-commands-1.png +0 -0
  152. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Table-Extension-should-handle-table-navigation-1.png +0 -0
  153. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Table-Extension-should-render-tables-correctly-1.png +0 -0
  154. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-handle-nested-task-lists-1.png +0 -0
  155. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-render-task-lists-correctly-1.png +0 -0
  156. package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-toggle-task-completion-1.png +0 -0
  157. package/src/test/editor.test.ts +346 -0
  158. package/src/test/example.ts +92 -0
  159. package/src/test/extensions.test.ts +333 -0
  160. package/src/test/setup.ts +55 -0
  161. package/src/test/utils.ts +212 -0
  162. package/src/vite-env.d.ts +1 -0
  163. package/tsconfig.app.json +29 -0
  164. package/tsconfig.json +7 -0
  165. package/tsconfig.lib.json +17 -0
  166. package/tsconfig.node.json +24 -0
  167. package/vite.config.ts +89 -0
  168. 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
+ })
@@ -0,0 +1,3 @@
1
+ // Main library entry point
2
+ export * from './editor/index';
3
+ export type { MarkdownEditor, MarkdownEditorOptions } from './editor/index';
@@ -0,0 +1,4 @@
1
+ declare module '*.css' {
2
+ const content: string;
3
+ export default content;
4
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -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
+ })