@grandaniel/vue-markdown-editor 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +29 -0
  3. package/dist/components/MarkdownEditor/Composable/HeadlineTypeMap.d.ts +8 -0
  4. package/dist/components/MarkdownEditor/Composable/useMarkdownEditor.d.ts +26 -0
  5. package/dist/components/MarkdownEditor/Composable/useMarkdownProcessor.d.ts +28 -0
  6. package/dist/components/MarkdownEditor/Composable/useReflectiveState.d.ts +20 -0
  7. package/dist/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenu.vue.d.ts +17 -0
  8. package/dist/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenuBlockItem.vue.d.ts +17 -0
  9. package/dist/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenuInlineItem.vue.d.ts +20 -0
  10. package/dist/components/MarkdownEditor/ContextMenu/MarkdownEditorImageContextMenu.vue.d.ts +11 -0
  11. package/dist/components/MarkdownEditor/ContextMenu/MarkdownEditorTextSelectionContextMenu.vue.d.ts +20 -0
  12. package/dist/components/MarkdownEditor/Factory/MarkdownNodeFactory.d.ts +14 -0
  13. package/dist/components/MarkdownEditor/MarkdownComponentRegistry.d.ts +15 -0
  14. package/dist/components/MarkdownEditor/MarkdownEditor.vue.d.ts +42 -0
  15. package/dist/components/MarkdownEditor/MarkdownEditorModal.vue.d.ts +23 -0
  16. package/dist/components/MarkdownEditor/MarkdownEditorModule.vue.d.ts +45 -0
  17. package/dist/components/MarkdownEditor/Modules/MarkdownModuleHeadline1.vue.d.ts +19 -0
  18. package/dist/components/MarkdownEditor/Modules/MarkdownModuleHeadline2.vue.d.ts +19 -0
  19. package/dist/components/MarkdownEditor/Modules/MarkdownModuleHeadline3.vue.d.ts +19 -0
  20. package/dist/components/MarkdownEditor/Modules/MarkdownModuleImage.vue.d.ts +14 -0
  21. package/dist/components/MarkdownEditor/Modules/MarkdownModuleImageState.d.ts +6 -0
  22. package/dist/components/MarkdownEditor/Modules/MarkdownModuleList.vue.d.ts +3 -0
  23. package/dist/components/MarkdownEditor/Modules/MarkdownModuleListState.d.ts +5 -0
  24. package/dist/components/MarkdownEditor/Modules/MarkdownModuleParagraph.vue.d.ts +19 -0
  25. package/dist/components/MarkdownEditor/Modules/MarkdownModuleTextState.d.ts +4 -0
  26. package/dist/components/MarkdownEditor/TipTap/SingleLineExtension.d.ts +4 -0
  27. package/dist/components/MarkdownEditor/Types/MarkdownAstNode.d.ts +14 -0
  28. package/dist/components/MarkdownEditor/Types/MarkdownAstNodeType.d.ts +11 -0
  29. package/dist/components/MarkdownEditor/Types/TextishEmits.d.ts +12 -0
  30. package/dist/components/MarkdownEditor/index.d.ts +5 -0
  31. package/dist/components/index.d.ts +1 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/vue-markdown-editor.css +2 -0
  34. package/dist/vue-markdown-editor.mjs +20524 -0
  35. package/package.json +53 -5
  36. package/eslint.config.ts +0 -0
  37. package/src/components/MarkdownEditor/Composable/HeadlineTypeMap.ts +0 -36
  38. package/src/components/MarkdownEditor/Composable/useMarkdownEditor.ts +0 -21
  39. package/src/components/MarkdownEditor/Composable/useMarkdownProcessor.ts +0 -186
  40. package/src/components/MarkdownEditor/Composable/useReflectiveState.ts +0 -84
  41. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenu.vue +0 -38
  42. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenuBlockItem.vue +0 -43
  43. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenuInlineItem.vue +0 -52
  44. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorImageContextMenu.vue +0 -35
  45. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorTextSelectionContextMenu.vue +0 -45
  46. package/src/components/MarkdownEditor/Factory/MarkdownNodeFactory.ts +0 -49
  47. package/src/components/MarkdownEditor/MarkdownComponentRegistry.ts +0 -48
  48. package/src/components/MarkdownEditor/MarkdownEditor.vue +0 -333
  49. package/src/components/MarkdownEditor/MarkdownEditorModal.vue +0 -169
  50. package/src/components/MarkdownEditor/MarkdownEditorModule.vue +0 -129
  51. package/src/components/MarkdownEditor/Modules/MarkdownModuleHeadline1.vue +0 -60
  52. package/src/components/MarkdownEditor/Modules/MarkdownModuleHeadline2.vue +0 -60
  53. package/src/components/MarkdownEditor/Modules/MarkdownModuleHeadline3.vue +0 -61
  54. package/src/components/MarkdownEditor/Modules/MarkdownModuleImage.vue +0 -185
  55. package/src/components/MarkdownEditor/Modules/MarkdownModuleImageState.ts +0 -10
  56. package/src/components/MarkdownEditor/Modules/MarkdownModuleList.vue +0 -7
  57. package/src/components/MarkdownEditor/Modules/MarkdownModuleListState.ts +0 -10
  58. package/src/components/MarkdownEditor/Modules/MarkdownModuleParagraph.vue +0 -54
  59. package/src/components/MarkdownEditor/Modules/MarkdownModuleTextState.ts +0 -8
  60. package/src/components/MarkdownEditor/Modules/__tests__/MarkdownModuleHeadline1.test.js +0 -20
  61. package/src/components/MarkdownEditor/Modules/__tests__/MarkdownModuleHeadline1.test.ts +0 -25
  62. package/src/components/MarkdownEditor/Modules/__tests__/__snapshots__/MarkdownModuleHeadline1.test.ts.snap +0 -7
  63. package/src/components/MarkdownEditor/Styles/Mixins.scss +0 -7
  64. package/src/components/MarkdownEditor/TipTap/SingleLineExtension.ts +0 -21
  65. package/src/components/MarkdownEditor/Types/MarkdownAstNode.ts +0 -22
  66. package/src/components/MarkdownEditor/Types/MarkdownAstNodeType.ts +0 -28
  67. package/src/components/MarkdownEditor/Types/TextishEmits.ts +0 -14
  68. package/src/components/MarkdownEditor/index.ts +0 -1
  69. package/src/components/index.ts +0 -1
  70. package/src/index.ts +0 -1
  71. package/tsconfig.app.json +0 -11
  72. package/tsconfig.json +0 -11
  73. package/tsconfig.node.json +0 -12
  74. package/vite.config.ts +0 -24
  75. package/vitest.config.ts +0 -21
@@ -1,48 +0,0 @@
1
- import type { Component } from "vue";
2
- import MarkdownModuleHeadline1 from "./Modules/MarkdownModuleHeadline1.vue";
3
- import MarkdownModuleHeadline2 from "./Modules/MarkdownModuleHeadline2.vue";
4
- import MarkdownModuleHeadline3 from "./Modules/MarkdownModuleHeadline3.vue";
5
- import MarkdownModuleImage from "./Modules/MarkdownModuleImage.vue";
6
- import MarkdownModuleImageState from "./Modules/MarkdownModuleImageState";
7
- import MarkdownModuleParagraph from "./Modules/MarkdownModuleParagraph.vue";
8
- import MarkdownModuleTextState from "./Modules/MarkdownModuleTextState";
9
- import type { MarkdownAstNode } from "./Types/MarkdownAstNode";
10
- import MarkdownNodeType from "./Types/MarkdownAstNodeType";
11
-
12
- type MarkdownComponentRegistryEntry = {
13
- component: Component;
14
- stateType: new (
15
- object: MarkdownModuleTextState | MarkdownModuleImageState,
16
- ) => MarkdownModuleTextState | MarkdownModuleImageState;
17
- };
18
-
19
- const registry: Record<MarkdownNodeType, MarkdownComponentRegistryEntry> = {
20
- [MarkdownNodeType.PARAGRAPH]: {
21
- component: MarkdownModuleParagraph,
22
- stateType: MarkdownModuleTextState,
23
- },
24
- [MarkdownNodeType.HEADLINE1]: {
25
- component: MarkdownModuleHeadline1,
26
- stateType: MarkdownModuleTextState,
27
- },
28
- [MarkdownNodeType.HEADLINE2]: {
29
- component: MarkdownModuleHeadline2,
30
- stateType: MarkdownModuleTextState,
31
- },
32
- [MarkdownNodeType.HEADLINE3]: {
33
- component: MarkdownModuleHeadline3,
34
- stateType: MarkdownModuleTextState,
35
- },
36
- [MarkdownNodeType.IMAGE]: {
37
- component: MarkdownModuleImage,
38
- stateType: MarkdownModuleImageState,
39
- },
40
- };
41
-
42
- export function isTextNodeState(
43
- state: MarkdownAstNode,
44
- ): state is MarkdownAstNode & { componentState: MarkdownModuleTextState } {
45
- return state.componentState instanceof MarkdownModuleTextState;
46
- }
47
-
48
- export default registry;
@@ -1,333 +0,0 @@
1
- <template>
2
- <div
3
- ref="editorContainerRef"
4
- class="markdown-editor"
5
- @click="handleClickBlankArea"
6
- @mouseup="handleMouseUp"
7
- >
8
- <MarkdownEditorModule
9
- v-for="(node, index) in markdownNodes"
10
- :key="node.id"
11
- :node="node"
12
- :focused="focusedNode === node"
13
- @click.stop
14
- @keydown="handleKeyDownOnNode(node, $event)"
15
- @focus="handleFocusOnNode(node)"
16
- @update:cursor-position="(pos) => handleUpdateCursorPosition(node, pos)"
17
- @change-type="handleChangeType"
18
- >
19
- <template #focus-controls>
20
- <span
21
- class="drag-handle"
22
- title="Drag to reorder"
23
- >
24
-
25
- </span>
26
- <button
27
- type="button"
28
- tabindex="-1"
29
- @click="deleteNode(index)"
30
- >
31
- 🗑️
32
- </button>
33
- <button
34
- type="button"
35
- tabindex="-1"
36
- @click="addBlankNode(index)"
37
- >
38
- +
39
- </button>
40
- </template>
41
- <template #after-controls>
42
- <slot name="after-controls" />
43
- </template>
44
- </MarkdownEditorModule>
45
-
46
- <MarkdownEditorTextSelectionContextMenu
47
- v-if="showContextMenu"
48
- :x="contextMenuPosition.x"
49
- :y="contextMenuPosition.y"
50
- :is-active="contextMenuActiveStates"
51
- @toggle-bold="toggleBold"
52
- @toggle-italic="toggleItalic"
53
- @toggle-underline="toggleUnderline"
54
- />
55
- </div>
56
- </template>
57
-
58
- <script lang="ts" setup>
59
- import { useSortable } from "@vueuse/integrations/useSortable";
60
- import { nextTick, ref, useTemplateRef, type PropType } from "vue";
61
- import type { MarkdownEditorInstance } from "./Composable/useMarkdownEditor";
62
- import MarkdownEditorTextSelectionContextMenu from "./ContextMenu/MarkdownEditorTextSelectionContextMenu.vue";
63
- import { isTextNodeState as isTextishNode } from "./MarkdownComponentRegistry";
64
- import MarkdownEditorModule from "./MarkdownEditorModule.vue";
65
- import type { MarkdownAstNode } from "./Types/MarkdownAstNode";
66
- import type MarkdownNodeType from "./Types/MarkdownAstNodeType";
67
-
68
- const props = defineProps({
69
- editor: {
70
- type: Object as PropType<MarkdownEditorInstance>,
71
- required: true,
72
- },
73
- focusedNode: {
74
- type: Object as PropType<MarkdownAstNode | null>,
75
- required: false,
76
- default: null,
77
- },
78
- });
79
-
80
- const emit = defineEmits<{
81
- (e: "update:focused-node", value: MarkdownAstNode | null): void;
82
- }>();
83
-
84
- const { markdownNodes, deleteNode, addBlankNode, replaceNodeType, moveNode } = props.editor;
85
-
86
- const editorContainerRef = useTemplateRef("editorContainerRef");
87
- useSortable(editorContainerRef, markdownNodes, {
88
- handle: ".drag-handle",
89
- animation: 150,
90
- onUpdate: (e) => {
91
- moveNode(e.oldIndex!, e.newIndex!);
92
- },
93
- });
94
- const focusedNode = ref<MarkdownAstNode | null>(props.focusedNode);
95
-
96
- // Context menu state
97
- const showContextMenu = ref(false);
98
- const contextMenuPosition = ref({ x: 0, y: 0 });
99
- const contextMenuActiveStates = ref({
100
- bold: false,
101
- italic: false,
102
- underline: false,
103
- });
104
- let selectionDebounceTimer: ReturnType<typeof setTimeout> | null = null;
105
-
106
- function handleUpdateCursorPosition(node: MarkdownAstNode, position: number) {
107
- node.editingState.cursorPosition = position;
108
- }
109
-
110
- function handleChangeType(node: MarkdownAstNode, newType: MarkdownNodeType) {
111
- const result = replaceNodeType(node, newType);
112
-
113
- if (result) {
114
- if (isTextishNode(result.newNode)) {
115
- result.newNode.componentState.text = node.componentState.text.slice(node.editingState.cursorPosition);
116
- }
117
- focusNodeByIndex(result.index);
118
- }
119
- }
120
-
121
- function handleKeyDownOnNode(node: MarkdownAstNode, event: KeyboardEvent) {
122
- const nodeIndex = markdownNodes.value.indexOf(node);
123
-
124
- if (event.key === "Enter") {
125
- handleEnter(nodeIndex, event);
126
- } else if (event.key === "Backspace") {
127
- handleBackspace(nodeIndex);
128
- } else if (event.key === "ArrowUp") {
129
- moveFocusOneUp();
130
- event.preventDefault();
131
- } else if (event.key === "ArrowDown") {
132
- moveFocusOneDown();
133
- event.preventDefault();
134
- } else if (event.key === "Delete") {
135
- handleDelete(nodeIndex);
136
- }
137
- }
138
-
139
- function handleFocusOnNode(node: MarkdownAstNode) {
140
- focusedNode.value = node;
141
- emit("update:focused-node", node);
142
- }
143
-
144
- function handleEnter(nodeIndex: number, event: KeyboardEvent) {
145
- addBlankNode(nodeIndex);
146
- moveFocusOneDown();
147
- event.preventDefault();
148
- }
149
-
150
- function handleBackspace(index: number) {
151
- const node = getNodeByIndex(index);
152
-
153
- if (!isTextishNode(node)) return;
154
- if (node.componentState.text !== "") return;
155
-
156
- deleteNode(index);
157
-
158
- nextTick(() => {
159
- const newIndex = index > 0 ? index - 1 : 0;
160
- focusNodeByIndex(newIndex);
161
- });
162
- }
163
-
164
- function handleDelete(index: number) {
165
- const node = getNodeByIndex(index);
166
-
167
- if (!isTextishNode(node)) return;
168
- if (node.componentState.text !== "") return;
169
-
170
- deleteNode(index);
171
- focusNodeByIndex(index);
172
- }
173
-
174
- function handleClickBlankArea() {
175
- const lastNode = markdownNodes.value[markdownNodes.value.length - 1];
176
- if (lastNode && isTextishNode(lastNode) && lastNode.componentState.text === "") {
177
- focusedNode.value = lastNode;
178
- return;
179
- }
180
-
181
- const newIndex = markdownNodes.value.length;
182
-
183
- addBlankNode(newIndex);
184
- focusNodeByIndex(newIndex);
185
- }
186
-
187
- function focusNodeByIndex(index: number) {
188
- const node = markdownNodes.value[index];
189
- if (!node) return;
190
- nextTick(() => {
191
- focusedNode.value = node;
192
- });
193
- }
194
-
195
- function getNodeByIndex(index: number): MarkdownAstNode {
196
- const node = markdownNodes.value[index];
197
- if (!node) throw new Error("Node not found at index " + index);
198
- return node;
199
- }
200
-
201
- function moveFocusOneUp() {
202
- if (!focusedNode.value) return;
203
-
204
- nextTick(() => {
205
- const currentNode = focusedNode.value;
206
- if (!currentNode) return;
207
-
208
- const index = markdownNodes.value.indexOf(currentNode);
209
- if (index > 0) {
210
- focusedNode.value = getNodeByIndex(index - 1);
211
- }
212
- });
213
- }
214
-
215
- function moveFocusOneDown() {
216
- if (!focusedNode.value) return;
217
-
218
- nextTick(() => {
219
- const currentNode = focusedNode.value;
220
- if (!currentNode) return;
221
- const index = markdownNodes.value.indexOf(currentNode);
222
- if (index < markdownNodes.value.length - 1) {
223
- focusedNode.value = getNodeByIndex(index + 1);
224
- }
225
- });
226
- }
227
-
228
- // Context menu handlers
229
- function handleMouseUp() {
230
- // Clear existing timer
231
- if (selectionDebounceTimer) {
232
- clearTimeout(selectionDebounceTimer);
233
- }
234
-
235
- const selection = window.getSelection();
236
-
237
- if (!selection || selection.isCollapsed || selection.toString().trim() === "") {
238
- showContextMenu.value = false;
239
- return;
240
- }
241
-
242
- // Debounce: wait 500ms before showing context menu
243
- selectionDebounceTimer = setTimeout(() => {
244
- const range = selection.getRangeAt(0);
245
- const rect = range.getBoundingClientRect();
246
-
247
- // Position the context menu above the selection
248
- contextMenuPosition.value = {
249
- x: rect.left + rect.width / 2 - 75, // Center and offset
250
- y: rect.top - 50, // Position above selection
251
- };
252
-
253
- // Detect active formatting states
254
- const parentElement = range.commonAncestorContainer.parentElement;
255
- if (parentElement) {
256
- contextMenuActiveStates.value = {
257
- bold: isElementOrParentTagName(parentElement, ["STRONG", "B"]),
258
- italic: isElementOrParentTagName(parentElement, ["EM", "I"]),
259
- underline: isElementOrParentTagName(parentElement, ["U"]),
260
- };
261
- }
262
-
263
- showContextMenu.value = true;
264
- }, 500);
265
- }
266
-
267
- function isElementOrParentTagName(element: HTMLElement, tagNames: string[]): boolean {
268
- let current: HTMLElement | null = element;
269
- while (current) {
270
- if (tagNames.includes(current.tagName)) {
271
- return true;
272
- }
273
- current = current.parentElement;
274
- }
275
- return false;
276
- }
277
-
278
- function toggleBold() {
279
- document.execCommand("bold");
280
- showContextMenu.value = false;
281
- }
282
-
283
- function toggleItalic() {
284
- document.execCommand("italic");
285
- showContextMenu.value = false;
286
- }
287
-
288
- function toggleUnderline() {
289
- document.execCommand("underline");
290
- showContextMenu.value = false;
291
- }
292
- </script>
293
-
294
- <style>
295
- /* Basic global style */
296
- p {
297
- margin: 0.5rem 0;
298
- }
299
-
300
- strong,
301
- b {
302
- font-weight: bold;
303
- }
304
-
305
- em,
306
- i {
307
- font-style: italic;
308
- }
309
-
310
- code {
311
- font-family: monospace;
312
- background: rgba(127, 127, 127, 0.15);
313
- padding: 0.1em 0.3em;
314
- border-radius: 3px;
315
- font-size: 0.9em;
316
- color: green;
317
- }
318
-
319
- h1 {
320
- font-size: 2em;
321
- margin: 0;
322
- }
323
-
324
- h2 {
325
- font-size: 1.5em;
326
- margin: 0;
327
- }
328
-
329
- h3 {
330
- font-size: 1.17em;
331
- margin: 0;
332
- }
333
- </style>
@@ -1,169 +0,0 @@
1
- <template>
2
- <Teleport to="body">
3
- <div
4
- v-if="show"
5
- class="markdown-editor-modal-overlay"
6
- @click.self="emit('close')"
7
- >
8
- <div class="markdown-editor-modal">
9
- <div class="markdown-editor-modal-header">
10
- <h3 class="markdown-editor-modal-title">
11
- {{ title }}
12
- </h3>
13
- <button
14
- type="button"
15
- class="markdown-editor-modal-close"
16
- @click="emit('close')"
17
- >
18
- ×
19
- </button>
20
- </div>
21
-
22
- <div class="markdown-editor-modal-body">
23
- <slot />
24
- </div>
25
-
26
- <div class="markdown-editor-modal-footer">
27
- <button
28
- type="button"
29
- class="markdown-editor-modal-button markdown-editor-modal-button-secondary"
30
- @click="emit('close')"
31
- >
32
- Cancel
33
- </button>
34
- <button
35
- type="button"
36
- class="markdown-editor-modal-button markdown-editor-modal-button-primary"
37
- @click="emit('confirm')"
38
- >
39
- Save
40
- </button>
41
- </div>
42
- </div>
43
- </div>
44
- </Teleport>
45
- </template>
46
-
47
- <script setup lang="ts">
48
- defineProps<{
49
- show: boolean;
50
- title: string;
51
- }>();
52
-
53
- const emit = defineEmits<{
54
- close: [];
55
- confirm: [];
56
- }>();
57
- </script>
58
-
59
- <style lang="scss" scoped>
60
- .markdown-editor-modal-overlay {
61
- position: fixed;
62
- top: 0;
63
- left: 0;
64
- right: 0;
65
- bottom: 0;
66
- background: rgba(0, 0, 0, 0.5);
67
- display: flex;
68
- align-items: center;
69
- justify-content: center;
70
- z-index: 9999;
71
- padding: 1rem;
72
- }
73
-
74
- .markdown-editor-modal {
75
- background: #ffffff;
76
- border-radius: 0.75rem;
77
- box-shadow:
78
- 0 20px 25px -5px rgba(0, 0, 0, 0.1),
79
- 0 10px 10px -5px rgba(0, 0, 0, 0.04);
80
- max-width: 32rem;
81
- width: 100%;
82
- max-height: 90vh;
83
- display: flex;
84
- flex-direction: column;
85
- }
86
-
87
- .markdown-editor-modal-header {
88
- display: flex;
89
- align-items: center;
90
- justify-content: space-between;
91
- padding: 1.25rem 1.5rem;
92
- border-bottom: 1px solid #e5e7eb;
93
- }
94
-
95
- .markdown-editor-modal-title {
96
- font-size: 1.125rem;
97
- font-weight: 600;
98
- color: #111827;
99
- margin: 0;
100
- }
101
-
102
- .markdown-editor-modal-close {
103
- background: transparent;
104
- border: none;
105
- font-size: 1.75rem;
106
- line-height: 1;
107
- color: #6b7280;
108
- cursor: pointer;
109
- padding: 0;
110
- width: 2rem;
111
- height: 2rem;
112
- display: flex;
113
- align-items: center;
114
- justify-content: center;
115
- border-radius: 0.375rem;
116
- transition: all 0.15s ease;
117
-
118
- &:hover {
119
- background: #f3f4f6;
120
- color: #111827;
121
- }
122
- }
123
-
124
- .markdown-editor-modal-body {
125
- padding: 1.5rem;
126
- overflow-y: auto;
127
- flex: 1;
128
- }
129
-
130
- .markdown-editor-modal-footer {
131
- display: flex;
132
- gap: 0.75rem;
133
- justify-content: flex-end;
134
- padding: 1.25rem 1.5rem;
135
- border-top: 1px solid #e5e7eb;
136
- }
137
-
138
- .markdown-editor-modal-button {
139
- padding: 0.625rem 1.25rem;
140
- font-size: 0.875rem;
141
- font-weight: 500;
142
- border-radius: 0.5rem;
143
- border: none;
144
- cursor: pointer;
145
- transition: all 0.15s ease;
146
-
147
- &:active {
148
- transform: scale(0.98);
149
- }
150
- }
151
-
152
- .markdown-editor-modal-button-secondary {
153
- background: #f3f4f6;
154
- color: #374151;
155
-
156
- &:hover {
157
- background: #e5e7eb;
158
- }
159
- }
160
-
161
- .markdown-editor-modal-button-primary {
162
- background: #3b82f6;
163
- color: #ffffff;
164
-
165
- &:hover {
166
- background: #2563eb;
167
- }
168
- }
169
- </style>
@@ -1,129 +0,0 @@
1
- <template>
2
- <div
3
- ref="markdownEditorModuleRef"
4
- class="markdown-editor-module"
5
- >
6
- <div class="markdown-editor-module-controls">
7
- <template v-if="focused || mouseOver">
8
- <slot name="focus-controls" />
9
- </template>
10
- </div>
11
- <div
12
- ref="contentRef"
13
- class="markdown-editor-module-content"
14
- :class="{
15
- 'markdown-editor-module-content-focused': props.focused,
16
- }"
17
- >
18
- <component
19
- :is="ComponentRegistry[props.node.type].component"
20
- v-if="ComponentRegistry[props.node.type]"
21
- ref="moduleComponentRef"
22
- :model-value="props.node.componentState"
23
- @update:model-value="emit('update:node', $event)"
24
- @change-type="(newType: MarkdownAstNodeType) => emit('change-type', props.node, newType)"
25
- @update:cursor-position="emit('update:cursor-position', $event)"
26
- @click="emit('focus')"
27
- />
28
- </div>
29
- <slot name="after-controls" />
30
- </div>
31
- </template>
32
-
33
- <script lang="ts" setup>
34
- import { useFocusWithin, useMouseInElement } from "@vueuse/core";
35
- import { computed, onMounted, ref, useTemplateRef, watch, type PropType } from "vue";
36
- import type { MarkdownAstNode } from "./Types/MarkdownAstNode";
37
-
38
- import ComponentRegistry from "./MarkdownComponentRegistry";
39
- import type MarkdownAstNodeType from "./Types/MarkdownAstNodeType";
40
-
41
- const props = defineProps({
42
- node: {
43
- type: Object as PropType<MarkdownAstNode>,
44
- required: true,
45
- },
46
- focused: {
47
- type: Boolean,
48
- required: true,
49
- },
50
- });
51
-
52
- onMounted(() => {
53
- if (moduleComponentRef.value && props.focused) {
54
- moduleComponentRef.value.focus();
55
- }
56
- });
57
-
58
- const emit = defineEmits<{
59
- "focus": [];
60
- "update:cursor-position": [number];
61
- "update:node": [Record<string, unknown>];
62
- "change-type": [MarkdownAstNode, number];
63
- }>();
64
-
65
- const markdownEditorModuleRef = useTemplateRef("markdownEditorModuleRef");
66
- const contentRef = useTemplateRef("contentRef");
67
- const moduleComponentRef = ref<{ focus: () => void } | null>(null);
68
- const innerElementFocus = useFocusWithin(contentRef);
69
- const mouseInTarget = useMouseInElement(markdownEditorModuleRef);
70
- const mouseOver = computed(() => !mouseInTarget.isOutside.value);
71
-
72
- // Handle external focus changes
73
- watch(
74
- () => props.focused,
75
- (nowFocused) => {
76
- if (nowFocused && moduleComponentRef.value) {
77
- moduleComponentRef.value.focus();
78
- }
79
- },
80
- );
81
-
82
- // Focus detection root
83
- watch(innerElementFocus.focused, (isFocused) => {
84
- if (isFocused) {
85
- emit("focus");
86
- }
87
- });
88
- </script>
89
-
90
- <style lang="scss" scoped>
91
- .markdown-editor-module {
92
- display: flex;
93
- flex-direction: row;
94
- gap: 1rem;
95
-
96
- :deep(.tiptap) {
97
- outline: none;
98
- }
99
-
100
- .markdown-editor-module-content {
101
- width: 100%;
102
- outline: none;
103
- }
104
-
105
- .drag-handle {
106
- cursor: grab;
107
- padding: 0 0.25rem;
108
- user-select: none;
109
- font-size: 1rem;
110
- opacity: 0.5;
111
-
112
- &:active {
113
- cursor: grabbing;
114
- }
115
- }
116
-
117
- .markdown-editor-module-controls {
118
- width: 6rem;
119
- display: flex;
120
- flex-direction: row;
121
- gap: 0.25rem;
122
- align-items: center;
123
- }
124
-
125
- .markdown-editor-module-content-focused {
126
- background-color: rgba(158, 149, 149, 0.05);
127
- }
128
- }
129
- </style>