@edgedev/create-edge-app 1.1.23 → 1.1.26

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 (116) hide show
  1. package/.env +1 -0
  2. package/.env.dev +1 -0
  3. package/README.md +55 -20
  4. package/{agent.md → agents.md} +2 -0
  5. package/bin/cli.js +6 -6
  6. package/edge/components/auth/login.vue +384 -0
  7. package/edge/components/auth/register.vue +396 -0
  8. package/edge/components/auth.vue +108 -0
  9. package/edge/components/autoFileUpload.vue +215 -0
  10. package/edge/components/billing.vue +8 -0
  11. package/edge/components/buttonDivider.vue +14 -0
  12. package/edge/components/chip.vue +34 -0
  13. package/edge/components/clipboardButton.vue +42 -0
  14. package/edge/components/cms/block.vue +529 -0
  15. package/edge/components/cms/blockApi.vue +212 -0
  16. package/edge/components/cms/blockEditor.vue +725 -0
  17. package/edge/components/cms/blockInput.vue +66 -0
  18. package/edge/components/cms/blockPicker.vue +486 -0
  19. package/edge/components/cms/blockRender.vue +78 -0
  20. package/edge/components/cms/blockSheetContent.vue +28 -0
  21. package/edge/components/cms/codeEditor.vue +466 -0
  22. package/edge/components/cms/fontUpload.vue +327 -0
  23. package/edge/components/cms/htmlContent.vue +807 -0
  24. package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
  25. package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
  26. package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
  27. package/edge/components/cms/init_blocks/carousel.html +103 -0
  28. package/edge/components/cms/init_blocks/contact_us.html +69 -0
  29. package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
  30. package/edge/components/cms/init_blocks/footer.html +24 -0
  31. package/edge/components/cms/init_blocks/header_divider.html +7 -0
  32. package/edge/components/cms/init_blocks/hero.html +35 -0
  33. package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
  34. package/edge/components/cms/init_blocks/newsletter.html +117 -0
  35. package/edge/components/cms/init_blocks/post_content.html +7 -0
  36. package/edge/components/cms/init_blocks/post_title_header.html +21 -0
  37. package/edge/components/cms/init_blocks/posts_list.html +20 -0
  38. package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
  39. package/edge/components/cms/init_blocks/property_carousel.html +59 -0
  40. package/edge/components/cms/init_blocks/property_detail.html +112 -0
  41. package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
  42. package/edge/components/cms/init_blocks/property_results.html +137 -0
  43. package/edge/components/cms/init_blocks/property_search.html +75 -0
  44. package/edge/components/cms/init_blocks/simple_array.html +7 -0
  45. package/edge/components/cms/mediaCard.vue +116 -0
  46. package/edge/components/cms/mediaManager.vue +386 -0
  47. package/edge/components/cms/menu.vue +1103 -0
  48. package/edge/components/cms/optionsSelect.vue +107 -0
  49. package/edge/components/cms/page.vue +1785 -0
  50. package/edge/components/cms/posts.vue +1083 -0
  51. package/edge/components/cms/site.vue +1298 -0
  52. package/edge/components/cms/themeDefaultMenu.vue +548 -0
  53. package/edge/components/cms/themeEditor.vue +426 -0
  54. package/edge/components/dashboard.vue +776 -0
  55. package/edge/components/editor.vue +671 -0
  56. package/edge/components/fileTree.vue +72 -0
  57. package/edge/components/files.vue +89 -0
  58. package/edge/components/formSubtypes/myOrgs.vue +214 -0
  59. package/edge/components/formSubtypes/users.vue +336 -0
  60. package/edge/components/functionChips.vue +57 -0
  61. package/edge/components/gError.vue +98 -0
  62. package/edge/components/gHelper.vue +67 -0
  63. package/edge/components/gInput.vue +1331 -0
  64. package/edge/components/loggingIn.vue +41 -0
  65. package/edge/components/menu.vue +137 -0
  66. package/edge/components/menuContent.vue +132 -0
  67. package/edge/components/myAccount.vue +317 -0
  68. package/edge/components/myOrganizations.vue +75 -0
  69. package/edge/components/myProfile.vue +122 -0
  70. package/edge/components/orgSwitcher.vue +25 -0
  71. package/edge/components/organizationMembers.vue +522 -0
  72. package/edge/components/organizationSettings.vue +271 -0
  73. package/edge/components/shad/breadcrumbs.vue +35 -0
  74. package/edge/components/shad/button.vue +43 -0
  75. package/edge/components/shad/checkbox.vue +73 -0
  76. package/edge/components/shad/combobox.vue +238 -0
  77. package/edge/components/shad/datepicker.vue +184 -0
  78. package/edge/components/shad/dialog.vue +32 -0
  79. package/edge/components/shad/dropdownMenu.vue +54 -0
  80. package/edge/components/shad/dropdownMenuItem.vue +21 -0
  81. package/edge/components/shad/form.vue +59 -0
  82. package/edge/components/shad/html.vue +877 -0
  83. package/edge/components/shad/input.vue +139 -0
  84. package/edge/components/shad/number.vue +109 -0
  85. package/edge/components/shad/select.vue +151 -0
  86. package/edge/components/shad/selectTags.vue +278 -0
  87. package/edge/components/shad/switch.vue +67 -0
  88. package/edge/components/shad/tags.vue +137 -0
  89. package/edge/components/shad/textarea.vue +102 -0
  90. package/edge/components/shad/typeMoney.vue +167 -0
  91. package/edge/components/sideBar.vue +288 -0
  92. package/edge/components/sideBarContent.vue +268 -0
  93. package/edge/components/sidebarProvider.vue +33 -0
  94. package/edge/components/tooltip.vue +16 -0
  95. package/edge/components/userMenu.vue +148 -0
  96. package/edge/components/v/alert.vue +59 -0
  97. package/edge/components/v/alertTitle.vue +18 -0
  98. package/edge/components/v/card.vue +53 -0
  99. package/edge/components/v/cardActions.vue +18 -0
  100. package/edge/components/v/cardText.vue +18 -0
  101. package/edge/components/v/cardTitle.vue +20 -0
  102. package/edge/components/v/col.vue +56 -0
  103. package/edge/components/v/list.vue +46 -0
  104. package/edge/components/v/listItem.vue +26 -0
  105. package/edge/components/v/listItemTitle.vue +18 -0
  106. package/edge/components/v/row.vue +42 -0
  107. package/edge/components/v/toolbar.vue +24 -0
  108. package/edge/composables/global.ts +519 -0
  109. package/edge-pull.sh +2 -0
  110. package/edge-push.sh +1 -0
  111. package/edge-status.sh +14 -0
  112. package/firebase.json +5 -2
  113. package/firebase_init.sh +21 -6
  114. package/package.json +1 -1
  115. package/plugins/firebase.client.ts +1 -0
  116. package/edge-components-install.sh +0 -1
@@ -0,0 +1,877 @@
1
+ <script setup>
2
+ // import { Extension } from '@tiptap/core'
3
+ import StarterKit from '@tiptap/starter-kit'
4
+ import TextStyle from '@tiptap/extension-text-style'
5
+ import Underline from '@tiptap/extension-underline'
6
+ import ImageExt from '@tiptap/extension-image'
7
+ import { Editor, EditorContent } from '@tiptap/vue-3'
8
+ import { useVModel } from '@vueuse/core'
9
+ import { onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
10
+ import {
11
+ AlignCenter,
12
+ AlignLeft,
13
+ AlignRight,
14
+ Bold,
15
+ Code,
16
+ Heading1,
17
+ Heading2,
18
+ Heading3,
19
+ Heading4,
20
+ Heading5,
21
+ Heading6,
22
+ Image,
23
+ Italic,
24
+ List,
25
+ ListOrdered,
26
+ ListTree,
27
+ Minus,
28
+ Pilcrow,
29
+ Redo,
30
+ RemoveFormatting,
31
+ SquareCode,
32
+ Strikethrough,
33
+ TextQuote,
34
+ Trash2,
35
+ Underline as UnderlineIcon,
36
+ Undo,
37
+ WrapText,
38
+ } from 'lucide-vue-next'
39
+ import ToggleGroupItem from '~/components/ui/toggle-group/ToggleGroupItem.vue'
40
+
41
+ const props = defineProps({
42
+ name: {
43
+ type: String,
44
+ required: true,
45
+ },
46
+ defaultValue: {
47
+ type: [String, Number],
48
+ required: false,
49
+ },
50
+ modelValue: {
51
+ type: String,
52
+ default: '',
53
+ },
54
+ class: {
55
+ type: null,
56
+ required: false,
57
+ },
58
+ placeholder: {
59
+ type: String,
60
+ required: false,
61
+ },
62
+ label: {
63
+ type: String,
64
+ required: false,
65
+ },
66
+ description: {
67
+ type: String,
68
+ required: false,
69
+ },
70
+ maskOptions: {
71
+ type: [Object],
72
+ required: false,
73
+ default: null,
74
+ },
75
+ disabled: {
76
+ type: Boolean,
77
+ required: false,
78
+ default: false,
79
+ },
80
+ enabledToggles: {
81
+ type: Array,
82
+ required: false,
83
+ default: () => ['bold', 'italic', 'strike', 'underline', 'code', 'codeBlock', 'heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6', 'bulletlist', 'orderedlist', 'blockquote', 'horizontalrule', 'hardbreak', 'image'],
84
+ },
85
+ heightClass: {
86
+ type: String,
87
+ required: false,
88
+ default: '',
89
+ },
90
+ })
91
+ const emits = defineEmits(['update:modelValue', 'request-image'])
92
+ const DEFAULT_IMAGE_WIDTH = 33
93
+ const DEFAULT_HEIGHT_CLASS = 'edge-editor-height-default'
94
+ const sizeWidths = {
95
+ small: 20,
96
+ medium: DEFAULT_IMAGE_WIDTH,
97
+ large: 50,
98
+ full: 100,
99
+ }
100
+
101
+ const EdgeImage = ImageExt.extend({
102
+ addAttributes() {
103
+ return {
104
+ ...this.parent?.(),
105
+ size: {
106
+ default: 'medium',
107
+ parseHTML: element => element.getAttribute('data-size') || 'medium',
108
+ renderHTML: attributes => ({
109
+ 'data-size': attributes.size || 'medium',
110
+ }),
111
+ },
112
+ float: {
113
+ default: 'left',
114
+ parseHTML: (element) => {
115
+ const styleFloat = element.style?.float || element.style?.cssFloat
116
+ return styleFloat || element.getAttribute('data-float') || 'left'
117
+ },
118
+ renderHTML: attributes => ({
119
+ 'data-float': attributes.float || 'left',
120
+ }),
121
+ },
122
+ width: {
123
+ default: DEFAULT_IMAGE_WIDTH,
124
+ parseHTML: (element) => {
125
+ const attr = element.getAttribute('data-width')
126
+ if (attr)
127
+ return Number.parseFloat(attr)
128
+ const styleWidth = element.style?.width
129
+ if (styleWidth && styleWidth.endsWith('%'))
130
+ return Number.parseFloat(styleWidth.replace('%', ''))
131
+ const sizeAttr = element.getAttribute('data-size')
132
+ if (sizeAttr && sizeWidths[sizeAttr])
133
+ return sizeWidths[sizeAttr]
134
+ return DEFAULT_IMAGE_WIDTH
135
+ },
136
+ renderHTML: (attributes) => {
137
+ const widthValue = Number.parseFloat(attributes.width)
138
+ const width = Number.isFinite(widthValue) ? widthValue : DEFAULT_IMAGE_WIDTH
139
+ const floatValue = attributes.float || 'left'
140
+ let marginValue = '0.75rem 1.5rem 1rem 0'
141
+ if (floatValue === 'right')
142
+ marginValue = '0.75rem 0 1rem 1.5rem'
143
+ else if (floatValue === 'none')
144
+ marginValue = '1.25rem auto'
145
+ const styleSegments = [`width: ${width}%;`, `float: ${floatValue};`, `margin: ${marginValue};`]
146
+ return {
147
+ 'data-width': width,
148
+ 'style': styleSegments.join(' '),
149
+ }
150
+ },
151
+ },
152
+ }
153
+ },
154
+ }).configure({
155
+ HTMLAttributes: {
156
+ class: 'edge-editor-image',
157
+ },
158
+ })
159
+
160
+ const modelValue = useVModel(props, 'modelValue', emits, {
161
+ passive: false,
162
+ prop: 'modelValue',
163
+ })
164
+
165
+ const editor = ref(null)
166
+ const imageState = reactive({
167
+ active: false,
168
+ size: 'medium',
169
+ float: 'left',
170
+ width: DEFAULT_IMAGE_WIDTH,
171
+ })
172
+
173
+ const appliedHeightClasses = ref([])
174
+
175
+ const resolveHeightClasses = () => {
176
+ const raw = (props.heightClass || '').trim()
177
+ if (!raw)
178
+ return [DEFAULT_HEIGHT_CLASS]
179
+ return raw.split(/\s+/).filter(Boolean)
180
+ }
181
+
182
+ const applyHeightClasses = () => {
183
+ const dom = editor.value?.view?.dom
184
+ if (!dom)
185
+ return
186
+ appliedHeightClasses.value.forEach(cls => dom.classList.remove(cls))
187
+ const nextClasses = resolveHeightClasses()
188
+ nextClasses.forEach(cls => dom.classList.add(cls))
189
+ appliedHeightClasses.value = nextClasses
190
+ }
191
+
192
+ const updateImageState = () => {
193
+ if (!editor.value) {
194
+ imageState.active = false
195
+ imageState.size = 'medium'
196
+ imageState.float = 'left'
197
+ imageState.width = DEFAULT_IMAGE_WIDTH
198
+ return
199
+ }
200
+ const isActive = editor.value.isActive('image')
201
+ imageState.active = isActive
202
+ if (isActive) {
203
+ const attrs = editor.value.getAttributes('image') || {}
204
+ imageState.size = attrs.size || 'medium'
205
+ imageState.float = attrs.float || 'left'
206
+ let widthVal = Number.parseFloat(attrs.width)
207
+ if (!Number.isFinite(widthVal) && sizeWidths[imageState.size])
208
+ widthVal = sizeWidths[imageState.size]
209
+ if (Number.isFinite(widthVal))
210
+ imageState.width = Math.min(100, Math.max(10, Math.round(widthVal)))
211
+ }
212
+ else {
213
+ imageState.size = 'medium'
214
+ imageState.float = 'left'
215
+ imageState.width = DEFAULT_IMAGE_WIDTH
216
+ }
217
+ }
218
+
219
+ watch(modelValue, () => {
220
+ if (!editor.value)
221
+ return
222
+ const isSame = editor.value.getHTML() === modelValue.value
223
+
224
+ // JSON
225
+ // const isSame = JSON.stringify(this.editor.getJSON()) === JSON.stringify(value)
226
+
227
+ if (isSame) {
228
+ return
229
+ }
230
+
231
+ editor.value.commands.setContent(modelValue.value, false)
232
+ updateImageState()
233
+ })
234
+
235
+ /* const PreventEnterSubmit = Extension.create({
236
+ name: 'preventEnterSubmit',
237
+
238
+ addKeyboardShortcuts() {
239
+ return {
240
+ Enter: (editor, event) => {
241
+ console.log('Enter pressed', event)
242
+ editor.commands.enter()
243
+ // event.stopPropagation() // Varsayılan davranışı engelle
244
+ return true // Editör içinde Enter'ın çalışmasını sağla
245
+ },
246
+ }
247
+ },
248
+ }) */
249
+
250
+ onMounted(() => {
251
+ editor.value = new Editor({
252
+ extensions: [
253
+ StarterKit,
254
+ TextStyle,
255
+ EdgeImage,
256
+ Underline,
257
+ // PreventEnterSubmit,
258
+ ],
259
+ /* onCreate({ editor }) {
260
+ // DOM olay dinleyicisi ekleme
261
+ console.log(editor)
262
+ editor.view.dom.addEventListener('keydown', (event) => {
263
+ if (event.key === 'Enter') {
264
+ console.log('stopping enter propagation')
265
+ // Formun submit olmasını engeller
266
+ event.stopPropagation()
267
+ }
268
+ })
269
+ }, */
270
+ content: modelValue.value,
271
+ onUpdate: () => {
272
+ // HTML
273
+ emits('update:modelValue', editor.value.getHTML())
274
+
275
+ // JSON
276
+ // this.$emit('update:modelValue', this.editor.getJSON())
277
+ updateImageState()
278
+ },
279
+ })
280
+ editor.value.on('selectionUpdate', updateImageState)
281
+ editor.value.on('transaction', updateImageState)
282
+ updateImageState()
283
+ applyHeightClasses()
284
+ })
285
+
286
+ onBeforeUnmount(() => {
287
+ if (editor.value) {
288
+ editor.value.off('selectionUpdate', updateImageState)
289
+ editor.value.off('transaction', updateImageState)
290
+ editor.value.destroy()
291
+ }
292
+ })
293
+
294
+ watch(() => props.heightClass, () => {
295
+ applyHeightClasses()
296
+ })
297
+
298
+ const addImage = () => {
299
+ emits('request-image')
300
+ }
301
+
302
+ const insertImage = (url) => {
303
+ if (!url || !editor.value) {
304
+ return
305
+ }
306
+ editor.value.chain().focus().setImage({
307
+ src: url,
308
+ size: 'medium',
309
+ float: 'left',
310
+ width: DEFAULT_IMAGE_WIDTH,
311
+ }).run()
312
+ updateImageState()
313
+ }
314
+
315
+ const setImageSize = (size) => {
316
+ if (!editor.value || !imageState.active)
317
+ return
318
+ const width = sizeWidths[size] ?? 33
319
+ const attrs = { size, width }
320
+ if (size === 'full')
321
+ attrs.float = 'none'
322
+ editor.value.chain().focus().updateAttributes('image', attrs).run()
323
+ updateImageState()
324
+ }
325
+
326
+ const setImageFloat = (float) => {
327
+ if (!editor.value || !imageState.active)
328
+ return
329
+ const attrs = { float }
330
+ if (float !== 'none' && (imageState.size === 'full' || imageState.width >= 90)) {
331
+ attrs.size = 'large'
332
+ attrs.width = 50
333
+ }
334
+ if (float === 'none' && (imageState.size !== 'full' && imageState.width >= 90)) {
335
+ attrs.size = 'full'
336
+ attrs.width = 100
337
+ }
338
+ editor.value.chain().focus().updateAttributes('image', attrs).run()
339
+ updateImageState()
340
+ }
341
+
342
+ const setImageWidth = (width) => {
343
+ if (!editor.value || !imageState.active)
344
+ return
345
+ const normalized = Math.min(100, Math.max(10, Math.round(width)))
346
+ let size = 'custom'
347
+ if (normalized <= 22)
348
+ size = 'small'
349
+ else if (normalized < 41)
350
+ size = 'medium'
351
+ else if (normalized < 75)
352
+ size = 'large'
353
+ else if (normalized >= 95)
354
+ size = 'full'
355
+ const attrs = { width: normalized, size }
356
+ if (size === 'full')
357
+ attrs.float = 'none'
358
+ editor.value.chain().focus().updateAttributes('image', attrs).run()
359
+ updateImageState()
360
+ }
361
+
362
+ const removeImage = () => {
363
+ if (!editor.value || !imageState.active)
364
+ return
365
+ editor.value.chain().focus().deleteSelection().run()
366
+ updateImageState()
367
+ }
368
+
369
+ defineExpose({
370
+ insertImage,
371
+ })
372
+ </script>
373
+
374
+ <template>
375
+ <div class="my-4">
376
+ <FormField v-slot="{ componentField }" :name="props.name">
377
+ <FormItem>
378
+ <FormLabel>
379
+ {{ props.label }}
380
+ <div class="ml-auto inline-block">
381
+ <slot />
382
+ </div>
383
+ </FormLabel>
384
+ <FormControl>
385
+ <div v-if="editor" class="relative w-full items-center">
386
+ <div class="flex flex-col w-full py-2 border border-secondary">
387
+ <div class="button-group w-full flex flex-wrap gap-2">
388
+ <ToggleGroup type="multiple">
389
+ <ToggleGroupItem
390
+ v-if="enabledToggles.includes('bold')"
391
+ value="bold"
392
+ :disabled="!editor.can().chain().focus().toggleBold().run()"
393
+ :data-state="editor.isActive('bold') ? 'on' : 'off'"
394
+ @click.prevent="editor.chain().focus().toggleBold().run()"
395
+ >
396
+ <Bold :size="16" />
397
+ </ToggleGroupItem>
398
+ <ToggleGroupItem
399
+ v-if="enabledToggles.includes('italic')"
400
+ value="italic"
401
+ :disabled="!editor.can().chain().focus().toggleItalic().run()"
402
+ :data-state="editor.isActive('italic') ? 'on' : 'off'"
403
+ @click.prevent="editor.chain().focus().toggleItalic().run()"
404
+ >
405
+ <Italic :size="16" />
406
+ </ToggleGroupItem>
407
+ <ToggleGroupItem
408
+ v-if="enabledToggles.includes('underline')"
409
+ value="underline"
410
+ :disabled="!editor.can().chain().focus().toggleUnderline().run()"
411
+ :data-state="editor.isActive('underline') ? 'on' : 'off'"
412
+ @click.prevent="editor.chain().focus().toggleUnderline().run()"
413
+ >
414
+ <UnderlineIcon :size="16" />
415
+ </ToggleGroupItem>
416
+ <ToggleGroupItem
417
+ v-if="enabledToggles.includes('strike')"
418
+ value="strike"
419
+ :disabled="!editor.can().chain().focus().toggleStrike().run()"
420
+ :data-state="editor.isActive('strike') ? 'on' : 'off'"
421
+ @click.prevent="editor.chain().focus().toggleStrike().run()"
422
+ >
423
+ <Strikethrough :size="16" />
424
+ </ToggleGroupItem>
425
+ </ToggleGroup>
426
+ <ToggleGroup v-if="enabledToggles.includes('code') || enabledToggles.includes('codeBlock')" type="single">
427
+ <ToggleGroupItem
428
+ v-if="enabledToggles.includes('code')"
429
+ value="code"
430
+ :disabled="!editor.can().chain().focus().toggleCode().run()"
431
+ :data-state="editor.isActive('code') ? 'on' : 'off'"
432
+ @click.prevent="editor.chain().focus().toggleCode().run()"
433
+ >
434
+ <Code :size="16" />
435
+ </ToggleGroupItem>
436
+ <ToggleGroupItem
437
+ v-if="enabledToggles.includes('codeBlock')"
438
+ value="codeBlock"
439
+ :disabled="!editor.can().chain().focus().toggleCodeBlock().run()"
440
+ :data-state="editor.isActive('codeBlock') ? 'on' : 'off'"
441
+ @click.prevent="editor.chain().focus().toggleCodeBlock().run()"
442
+ >
443
+ <SquareCode :size="16" />
444
+ </ToggleGroupItem>
445
+ </ToggleGroup>
446
+
447
+ <ToggleGroup
448
+ v-if="enabledToggles.includes('heading1') || enabledToggles.includes('heading2') || enabledToggles.includes('heading3') || enabledToggles.includes('heading4') || enabledToggles.includes('heading5') || enabledToggles.includes('heading6')"
449
+ type="single"
450
+ >
451
+ <ToggleGroupItem
452
+ v-if="enabledToggles.includes('heading1')"
453
+ value="heading1"
454
+ :disabled="!editor.can().chain().focus().toggleHeading({ level: 1 }).run()"
455
+ :data-state="editor.isActive('heading', { level: 1 }) ? 'on' : 'off'"
456
+ @click.prevent="editor.chain().focus().toggleHeading({ level: 1 }).run()"
457
+ >
458
+ <Heading1 :size="16" />
459
+ </ToggleGroupItem>
460
+ <ToggleGroupItem
461
+ v-if="enabledToggles.includes('heading2')"
462
+ value="heading2"
463
+ :disabled="!editor.can().chain().focus().toggleHeading({ level: 2 }).run()"
464
+ :data-state="editor.isActive('heading', { level: 2 }) ? 'on' : 'off'"
465
+ @click.prevent="editor.chain().focus().toggleHeading({ level: 2 }).run()"
466
+ >
467
+ <Heading2 :size="16" />
468
+ </ToggleGroupItem>
469
+ <ToggleGroupItem
470
+ v-if="enabledToggles.includes('heading3')"
471
+ value="heading3"
472
+ :disabled="!editor.can().chain().focus().toggleHeading({ level: 3 }).run()"
473
+ :data-state="editor.isActive('heading', { level: 3 }) ? 'on' : 'off'"
474
+ @click.prevent="editor.chain().focus().toggleHeading({ level: 3 }).run()"
475
+ >
476
+ <Heading3 :size="16" />
477
+ </ToggleGroupItem>
478
+ <ToggleGroupItem
479
+ v-if="enabledToggles.includes('heading4')"
480
+ value="heading4"
481
+ :disabled="!editor.can().chain().focus().toggleHeading({ level: 4 }).run()"
482
+ :data-state="editor.isActive('heading', { level: 4 }) ? 'on' : 'off'"
483
+ @click.prevent="editor.chain().focus().toggleHeading({ level: 4 }).run()"
484
+ >
485
+ <Heading4 :size="16" />
486
+ </ToggleGroupItem>
487
+ <ToggleGroupItem
488
+ v-if="enabledToggles.includes('heading5')"
489
+ value="heading5"
490
+ :disabled="!editor.can().chain().focus().toggleHeading({ level: 5 }).run()"
491
+ :data-state="editor.isActive('heading', { level: 5 }) ? 'on' : 'off'"
492
+ @click.prevent="editor.chain().focus().toggleHeading({ level: 5 }).run()"
493
+ >
494
+ <Heading5 :size="16" />
495
+ </ToggleGroupItem>
496
+ <ToggleGroupItem
497
+ v-if="enabledToggles.includes('heading6')"
498
+ value="heading6"
499
+ :disabled="!editor.can().chain().focus().toggleHeading({ level: 6 }).run()"
500
+ :data-state="editor.isActive('heading', { level: 6 }) ? 'on' : 'off'"
501
+ @click.prevent="editor.chain().focus().toggleHeading({ level: 6 }).run()"
502
+ >
503
+ <Heading6 :size="16" />
504
+ </ToggleGroupItem>
505
+ </ToggleGroup>
506
+
507
+ <Button
508
+ v-if="enabledToggles.includes('paragraph')"
509
+ variant="outline"
510
+ :data-state="editor.isActive('paragraph') ? 'on' : 'off'"
511
+ @click.prevent="editor.chain().focus().setParagraph().run()"
512
+ >
513
+ <Pilcrow :size="16" />
514
+ </Button>
515
+
516
+ <Button
517
+ v-if="enabledToggles.includes('image')"
518
+ variant="outline"
519
+ @click.prevent="addImage"
520
+ >
521
+ <Image :size="16" />
522
+ </Button>
523
+
524
+ <ToggleGroup type="single">
525
+ <ToggleGroupItem
526
+ v-if="enabledToggles.includes('bulletlist')"
527
+ value="bulletList"
528
+ :disabled="!editor.can().chain().focus().toggleBulletList().run()"
529
+ :data-state="editor.isActive('bulletList') ? 'on' : 'off'"
530
+ @click.prevent="editor.chain().focus().toggleBulletList().run()"
531
+ >
532
+ <List :size="16" />
533
+ </ToggleGroupItem>
534
+ <ToggleGroupItem
535
+ v-if="enabledToggles.includes('orderedlist')"
536
+ value="orderedList"
537
+ :disabled="!editor.can().chain().focus().toggleOrderedList().run()"
538
+ :data-state="editor.isActive('orderedList') ? 'on' : 'off'"
539
+ @click.prevent="editor.chain().focus().toggleOrderedList().run()"
540
+ >
541
+ <ListOrdered :size="16" />
542
+ </ToggleGroupItem>
543
+ </ToggleGroup>
544
+
545
+ <Button variant="outline" title="Clear formatting" @click.prevent="editor.chain().focus().unsetAllMarks().run()">
546
+ <RemoveFormatting :size="16" />
547
+ </Button>
548
+
549
+ <Button variant="outline" title="Clear / Flatten Structure" @click.prevent="editor.chain().focus().clearNodes().run()">
550
+ <ListTree :size="16" />
551
+ </Button>
552
+
553
+ <Toggle
554
+ v-if="enabledToggles.includes('blockquote')"
555
+ :disabled="!editor.can().chain().focus().toggleBlockquote().run()"
556
+ :data-state="editor.isActive('blockQuote') ? 'on' : 'off'"
557
+ @click.prevent="editor.chain().focus().toggleBlockquote().run()"
558
+ >
559
+ <TextQuote :size="16" />
560
+ </Toggle>
561
+
562
+ <Button
563
+ v-if="enabledToggles.includes('horizontalrule')"
564
+ variant="outline"
565
+ title="Horizontal Rule"
566
+ @click.prevent="editor.chain().focus().setHorizontalRule().run()"
567
+ >
568
+ <Minus :size="16" />
569
+ </Button>
570
+
571
+ <Button
572
+ v-if="enabledToggles.includes('hardbreak')"
573
+ variant="outline"
574
+ title="Hard Break"
575
+ @click.prevent="editor.chain().focus().setHardBreak().run()"
576
+ >
577
+ <WrapText :size="16" />
578
+ </Button>
579
+
580
+ <Button
581
+ variant="outline"
582
+ title="Undo"
583
+ :disabled="!editor.can().chain().focus().undo().run()"
584
+ @click.prevent="editor.chain().focus().undo().run()"
585
+ >
586
+ <Undo :size="16" />
587
+ </Button>
588
+ <Button
589
+ variant="outline"
590
+ title="Redo"
591
+ :disabled="!editor.can().chain().focus().redo().run()"
592
+ @click.prevent="editor.chain().focus().redo().run()"
593
+ >
594
+ <Redo :size="16" />
595
+ </Button>
596
+ </div>
597
+ <div
598
+ v-if="enabledToggles.includes('image') && imageState.active"
599
+ class="flex flex-wrap items-center gap-2 border-t border-secondary/60 px-2 pt-2 mt-2"
600
+ >
601
+ <ToggleGroup type="single">
602
+ <ToggleGroupItem
603
+ value="imageSizeSmall"
604
+ :data-state="imageState.size === 'small' ? 'on' : 'off'"
605
+ title="Small image"
606
+ @click.prevent="setImageSize('small')"
607
+ >
608
+ S
609
+ </ToggleGroupItem>
610
+ <ToggleGroupItem
611
+ value="imageSizeMedium"
612
+ :data-state="imageState.size === 'medium' ? 'on' : 'off'"
613
+ title="Medium image"
614
+ @click.prevent="setImageSize('medium')"
615
+ >
616
+ M
617
+ </ToggleGroupItem>
618
+ <ToggleGroupItem
619
+ value="imageSizeLarge"
620
+ :data-state="imageState.size === 'large' ? 'on' : 'off'"
621
+ title="Large image"
622
+ @click.prevent="setImageSize('large')"
623
+ >
624
+ L
625
+ </ToggleGroupItem>
626
+ <ToggleGroupItem
627
+ value="imageSizeFull"
628
+ :data-state="imageState.size === 'full' ? 'on' : 'off'"
629
+ title="Full width image"
630
+ @click.prevent="setImageSize('full')"
631
+ >
632
+ F
633
+ </ToggleGroupItem>
634
+ </ToggleGroup>
635
+ <ToggleGroup type="single">
636
+ <ToggleGroupItem
637
+ value="imageFloatLeft"
638
+ :data-state="imageState.float === 'left' ? 'on' : 'off'"
639
+ title="Float left"
640
+ @click.prevent="setImageFloat('left')"
641
+ >
642
+ <AlignLeft :size="16" />
643
+ </ToggleGroupItem>
644
+ <ToggleGroupItem
645
+ value="imageFloatNone"
646
+ :data-state="imageState.float === 'none' ? 'on' : 'off'"
647
+ title="No float"
648
+ @click.prevent="setImageFloat('none')"
649
+ >
650
+ <AlignCenter :size="16" />
651
+ </ToggleGroupItem>
652
+ <ToggleGroupItem
653
+ value="imageFloatRight"
654
+ :data-state="imageState.float === 'right' ? 'on' : 'off'"
655
+ title="Float right"
656
+ @click.prevent="setImageFloat('right')"
657
+ >
658
+ <AlignRight :size="16" />
659
+ </ToggleGroupItem>
660
+ </ToggleGroup>
661
+ <div class="flex items-center gap-2 px-2">
662
+ <span class="text-xs text-muted-foreground">
663
+ Width
664
+ </span>
665
+ <input
666
+ type="range"
667
+ min="10"
668
+ max="100"
669
+ step="1"
670
+ class="h-2 w-32 cursor-pointer"
671
+ :value="imageState.width"
672
+ @input="setImageWidth(Number(($event.target).value))"
673
+ >
674
+ <span class="text-xs w-12 text-right text-muted-foreground">
675
+ {{ `${imageState.width}%` }}
676
+ </span>
677
+ </div>
678
+ <Button
679
+ variant="outline"
680
+ title="Remove image"
681
+ @click.prevent="removeImage"
682
+ >
683
+ <Trash2 :size="16" />
684
+ </Button>
685
+ </div>
686
+ </div>
687
+ <EditorContent
688
+ class="border border-secondary bg-background"
689
+ :editor="editor"
690
+ v-bind="componentField"
691
+ />
692
+ <span class="absolute end-0 inset-y-0 flex items-center justify-center px-2">
693
+ <slot name="icon" />
694
+ </span>
695
+ </div>
696
+ </FormControl>
697
+ <FormDescription>
698
+ {{ props.description }}
699
+ <slot name="description" />
700
+ </FormDescription>
701
+ <FormMessage />
702
+ </FormItem>
703
+ </FormField>
704
+ </div>
705
+ </template>
706
+
707
+ <style lang="scss">
708
+ /* Basic editor styles */
709
+ .tiptap {
710
+ overflow-y: auto;
711
+
712
+ :first-child {
713
+ margin-top: 0;
714
+ }
715
+
716
+ /* List styles */
717
+ ul,
718
+ ol {
719
+ padding: 0 1rem;
720
+ margin: 1.25rem 1rem 1.25rem 0.4rem;
721
+
722
+ li p {
723
+ margin-top: 0.25em;
724
+ margin-bottom: 0.25em;
725
+ }
726
+ }
727
+ ol {
728
+ list-style-type: decimal;
729
+ list-style-position: outside;
730
+ }
731
+ ul {
732
+ li {
733
+ list-style-type: disc;
734
+ list-style-position: outside;
735
+ }
736
+ }
737
+
738
+ /* Heading styles */
739
+ h1,
740
+ h2,
741
+ h3,
742
+ h4,
743
+ h5,
744
+ h6 {
745
+ line-height: 1.1;
746
+ margin-top: 2.5rem;
747
+ text-wrap: pretty;
748
+ }
749
+
750
+ h1,
751
+ h2 {
752
+ margin-top: 3.5rem;
753
+ margin-bottom: 1.5rem;
754
+ }
755
+
756
+ h1 {
757
+ font-size: 1.4rem;
758
+ }
759
+
760
+ h2 {
761
+ font-size: 1.2rem;
762
+ }
763
+
764
+ h3 {
765
+ font-size: 1.1rem;
766
+ }
767
+
768
+ h4,
769
+ h5,
770
+ h6 {
771
+ font-size: 1rem;
772
+ }
773
+
774
+ /* Code and preformatted text styles */
775
+ code {
776
+ background-color: var(--purple-light);
777
+ border-radius: 0.4rem;
778
+ color: var(--black);
779
+ font-size: 0.85rem;
780
+ padding: 0.25em 0.3em;
781
+ }
782
+
783
+ pre {
784
+ background: var(--black);
785
+ border-radius: 0.5rem;
786
+ color: var(--white);
787
+ font-family: 'JetBrainsMono', monospace;
788
+ margin: 1.5rem 0;
789
+ padding: 0.75rem 1rem;
790
+
791
+ code {
792
+ background: none;
793
+ color: inherit;
794
+ font-size: 0.8rem;
795
+ padding: 0;
796
+ }
797
+ }
798
+
799
+ blockquote {
800
+ border-left: 3px solid var(--gray-3);
801
+ margin: 1.5rem 0;
802
+ padding-left: 1rem;
803
+ }
804
+
805
+ hr {
806
+ border: none;
807
+ border-top: 1px solid var(--gray-2);
808
+ margin: 2rem 0;
809
+ }
810
+
811
+ &::after {
812
+ content: '';
813
+ display: block;
814
+ clear: both;
815
+ }
816
+
817
+ img.edge-editor-image {
818
+ border-radius: 0.375rem;
819
+ display: inline-block;
820
+ height: auto;
821
+ max-width: 100%;
822
+ }
823
+
824
+ img.edge-editor-image[data-size='small'] {
825
+ width: 20%;
826
+ min-width: 120px;
827
+ max-width: 160px;
828
+ }
829
+
830
+ img.edge-editor-image[data-size='medium'] {
831
+ width: 33%;
832
+ min-width: 160px;
833
+ max-width: 320px;
834
+ }
835
+
836
+ img.edge-editor-image[data-size='large'] {
837
+ width: 50%;
838
+ min-width: 200px;
839
+ max-width: 480px;
840
+ }
841
+
842
+ img.edge-editor-image[data-size='full'] {
843
+ width: 100%;
844
+ max-width: 100%;
845
+ }
846
+
847
+ img.edge-editor-image[data-float='left'] {
848
+ float: left;
849
+ margin: 0.75rem 1.5rem 1rem 0;
850
+ }
851
+
852
+ img.edge-editor-image[data-float='right'] {
853
+ float: right;
854
+ margin: 0.75rem 0 1rem 1.5rem;
855
+ }
856
+
857
+ img.edge-editor-image[data-float='none'] {
858
+ float: none;
859
+ display: block;
860
+ margin: 1.25rem auto;
861
+ }
862
+
863
+ img.edge-editor-image.ProseMirror-selectednode {
864
+ border: 2px solid black;
865
+ }
866
+
867
+ img.edge-editor-image[data-size='full'][data-float='none'] {
868
+ margin-left: 0;
869
+ margin-right: 0;
870
+ }
871
+ }
872
+
873
+ .tiptap.edge-editor-height-default {
874
+ min-height: 300px;
875
+ max-height: 300px;
876
+ }
877
+ </style>