@bagelink/vue 1.4.149 → 1.4.153
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/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/index.cjs +14 -14
- package/dist/index.mjs +14 -14
- package/dist/style.css +1 -1
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/utils/BagelFormUtils.d.ts +1 -0
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/form/inputs/RichText/index.vue +616 -432
- package/src/components/layout/AppContent.vue +37 -25
- package/src/types/BagelForm.ts +0 -9
- package/src/utils/BagelFormUtils.ts +1 -5
|
@@ -2,53 +2,54 @@
|
|
|
2
2
|
import type { ToolbarConfig } from './richTextTypes'
|
|
3
3
|
import { CodeEditor, copyText, Btn, Modal, BglVideo, Icon, Card, ColorInput } from '@bagelink/vue'
|
|
4
4
|
import { watch, onUnmounted, onBeforeUnmount, ref, computed, useAttrs } from 'vue'
|
|
5
|
+
import CheckInput from '../CheckInput.vue'
|
|
6
|
+
import NumberInput from '../NumberInput.vue'
|
|
7
|
+
import SelectInput from '../SelectInput.vue'
|
|
8
|
+
import TextInput from '../TextInput.vue'
|
|
5
9
|
import EditorToolbar from './components/EditorToolbar.vue'
|
|
6
10
|
import { useCommands } from './composables/useCommands'
|
|
7
11
|
import { useEditor } from './composables/useEditor'
|
|
8
12
|
import { useEditorKeyboard } from './composables/useEditorKeyboard'
|
|
9
|
-
import TextInput from '../TextInput.vue'
|
|
10
|
-
import CheckInput from '../CheckInput.vue'
|
|
11
|
-
import SelectInput from '../SelectInput.vue'
|
|
12
|
-
import NumberInput from '../NumberInput.vue'
|
|
13
13
|
|
|
14
14
|
// Disable automatic inheritance of non-prop attributes
|
|
15
15
|
defineOptions({
|
|
16
16
|
inheritAttrs: false
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
-
const attrs = useAttrs()
|
|
20
|
-
|
|
21
19
|
const props = defineProps<{
|
|
22
|
-
modelValue: string
|
|
23
|
-
toolbarConfig?: ToolbarConfig
|
|
24
|
-
debug?: boolean
|
|
25
|
-
label?: string
|
|
26
|
-
height?: number | string
|
|
27
|
-
basic?: boolean
|
|
28
|
-
simple?: boolean
|
|
29
|
-
placeholder?: string
|
|
20
|
+
modelValue: string
|
|
21
|
+
toolbarConfig?: ToolbarConfig
|
|
22
|
+
debug?: boolean
|
|
23
|
+
label?: string
|
|
24
|
+
height?: number | string
|
|
25
|
+
basic?: boolean
|
|
26
|
+
simple?: boolean
|
|
27
|
+
placeholder?: string
|
|
30
28
|
// Manual hide options
|
|
31
|
-
hideToolbar?: boolean
|
|
32
|
-
hideInlineToolbar?: boolean
|
|
33
|
-
hideImages?: boolean
|
|
34
|
-
hideVideos?: boolean
|
|
35
|
-
hideEmbed?: boolean
|
|
36
|
-
hideTables?: boolean
|
|
37
|
-
hideAlignment?: boolean
|
|
38
|
-
hideDirections?: boolean
|
|
39
|
-
hideH5H6?: boolean
|
|
29
|
+
hideToolbar?: boolean
|
|
30
|
+
hideInlineToolbar?: boolean
|
|
31
|
+
hideImages?: boolean
|
|
32
|
+
hideVideos?: boolean
|
|
33
|
+
hideEmbed?: boolean
|
|
34
|
+
hideTables?: boolean
|
|
35
|
+
hideAlignment?: boolean
|
|
36
|
+
hideDirections?: boolean
|
|
37
|
+
hideH5H6?: boolean
|
|
40
38
|
// Simple array-based hide option
|
|
41
|
-
hide?: string[]
|
|
39
|
+
hide?: string[]
|
|
42
40
|
// Control autofocus behavior
|
|
43
41
|
autofocus?: boolean
|
|
44
42
|
}>()
|
|
43
|
+
|
|
45
44
|
const emit = defineEmits(['update:modelValue'])
|
|
46
45
|
|
|
46
|
+
const attrs = useAttrs()
|
|
47
|
+
|
|
47
48
|
const iframe = ref<HTMLIFrameElement>()
|
|
48
49
|
const editor = useEditor()
|
|
49
50
|
const isInitializing = ref(false)
|
|
50
51
|
const hasInitialized = ref(false)
|
|
51
|
-
|
|
52
|
+
const commands = useCommands(editor.state, editor.state.debug)
|
|
52
53
|
// Computed properties for UI control
|
|
53
54
|
const shouldShowToolbar = computed(() => {
|
|
54
55
|
if (props.hideToolbar) return false
|
|
@@ -73,7 +74,7 @@ const effectiveHideArray = computed(() => {
|
|
|
73
74
|
// If simple mode is enabled, add the simple mode hide items
|
|
74
75
|
if (props.simple) {
|
|
75
76
|
const simpleHideItems = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'image', 'video', 'embed', 'ul', 'ol', 'blockquote', 'direction', 'table', 'alignment']
|
|
76
|
-
simpleHideItems.forEach(item => {
|
|
77
|
+
simpleHideItems.forEach((item) => {
|
|
77
78
|
if (!hideArray.includes(item)) {
|
|
78
79
|
hideArray.push(item)
|
|
79
80
|
}
|
|
@@ -90,7 +91,7 @@ const linkForm = ref({
|
|
|
90
91
|
openInNewTab: true
|
|
91
92
|
})
|
|
92
93
|
|
|
93
|
-
// Tooltip state
|
|
94
|
+
// Tooltip state
|
|
94
95
|
const showTooltip = ref(false)
|
|
95
96
|
const tooltipData = ref({
|
|
96
97
|
message: '',
|
|
@@ -99,8 +100,8 @@ const tooltipData = ref({
|
|
|
99
100
|
})
|
|
100
101
|
|
|
101
102
|
let pendingLinkData: {
|
|
102
|
-
selection: Selection
|
|
103
|
-
range: Range
|
|
103
|
+
selection: Selection
|
|
104
|
+
range: Range
|
|
104
105
|
existingLink: HTMLAnchorElement | null
|
|
105
106
|
} | null = null
|
|
106
107
|
|
|
@@ -115,8 +116,8 @@ const imageForm = ref({
|
|
|
115
116
|
figcaption: false
|
|
116
117
|
})
|
|
117
118
|
let pendingImageData: {
|
|
118
|
-
selection: Selection
|
|
119
|
-
range: Range
|
|
119
|
+
selection: Selection
|
|
120
|
+
range: Range
|
|
120
121
|
existingImage: HTMLElement | null
|
|
121
122
|
} | null = null
|
|
122
123
|
|
|
@@ -129,8 +130,8 @@ const embedForm = ref({
|
|
|
129
130
|
alt: ''
|
|
130
131
|
})
|
|
131
132
|
let pendingEmbedData: {
|
|
132
|
-
selection: Selection
|
|
133
|
-
range: Range
|
|
133
|
+
selection: Selection
|
|
134
|
+
range: Range
|
|
134
135
|
existingEmbed: HTMLElement | null
|
|
135
136
|
} | null = null
|
|
136
137
|
|
|
@@ -155,8 +156,8 @@ const videoForm = ref({
|
|
|
155
156
|
showCaption: false
|
|
156
157
|
})
|
|
157
158
|
let pendingVideoData: {
|
|
158
|
-
selection: Selection
|
|
159
|
-
range: Range
|
|
159
|
+
selection: Selection
|
|
160
|
+
range: Range
|
|
160
161
|
existingVideo: HTMLElement | null
|
|
161
162
|
} | null = null
|
|
162
163
|
|
|
@@ -188,8 +189,8 @@ const contextMenuPosition = ref({ x: 0, y: 0 })
|
|
|
188
189
|
const contextMenuCell = ref<HTMLTableCellElement | null>(null)
|
|
189
190
|
|
|
190
191
|
let pendingTableData: {
|
|
191
|
-
selection: Selection
|
|
192
|
-
range: Range
|
|
192
|
+
selection: Selection
|
|
193
|
+
range: Range
|
|
193
194
|
existingTable: HTMLTableElement | null
|
|
194
195
|
} | null = null
|
|
195
196
|
|
|
@@ -197,7 +198,7 @@ let pendingTableData: {
|
|
|
197
198
|
const tablePreviewHtml = computed(() => {
|
|
198
199
|
const form = tableForm.value
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
const tableStyle = `
|
|
201
202
|
width: ${form.width}%;
|
|
202
203
|
border-collapse: collapse;
|
|
203
204
|
margin-bottom: 1rem;
|
|
@@ -236,9 +237,9 @@ const tablePreviewHtml = computed(() => {
|
|
|
236
237
|
border: ${form.borderWidth}px solid ${form.borderColor};
|
|
237
238
|
text-align: ${form.alignment};
|
|
238
239
|
${form.fixedLayout ? `width: ${100 / form.cols}%;` : ''}
|
|
239
|
-
${form.alternateRows && i % 2 === 1
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
${form.alternateRows && i % 2 === 1
|
|
241
|
+
? `background-color: ${form.alternateRowBgColor}; color: ${form.alternateRowTextColor};`
|
|
242
|
+
: `background-color: ${form.cellBgColor}; color: ${form.cellTextColor};`}
|
|
242
243
|
`
|
|
243
244
|
html += `<td style="${tdStyle}">Cell ${i + 1},${j + 1}</td>`
|
|
244
245
|
}
|
|
@@ -254,7 +255,7 @@ editor.state.content = props.modelValue
|
|
|
254
255
|
|
|
255
256
|
// Function to detect Hebrew text and set direction
|
|
256
257
|
function detectAndSetDirection() {
|
|
257
|
-
const doc = editor.state
|
|
258
|
+
const { doc } = editor.state
|
|
258
259
|
if (!doc?.body) {
|
|
259
260
|
console.log('No doc.body found')
|
|
260
261
|
return
|
|
@@ -285,7 +286,7 @@ function detectAndSetDirection() {
|
|
|
285
286
|
|
|
286
287
|
// Update all paragraphs to match the new direction
|
|
287
288
|
const paragraphs = doc.querySelectorAll('p')
|
|
288
|
-
paragraphs.forEach(p => {
|
|
289
|
+
paragraphs.forEach((p) => {
|
|
289
290
|
if (!p.classList.contains('placeholder')) {
|
|
290
291
|
p.setAttribute('dir', newDirection)
|
|
291
292
|
}
|
|
@@ -297,7 +298,7 @@ function detectAndSetDirection() {
|
|
|
297
298
|
|
|
298
299
|
// Function to get current direction based on content
|
|
299
300
|
function getCurrentDirection() {
|
|
300
|
-
const doc = editor.state
|
|
301
|
+
const { doc } = editor.state
|
|
301
302
|
if (!doc?.body) return 'ltr'
|
|
302
303
|
|
|
303
304
|
const allText = doc.body.textContent || ''
|
|
@@ -334,7 +335,7 @@ function updateContentWithHistory(doc: Document, skipHistory = false) {
|
|
|
334
335
|
|
|
335
336
|
// Debug function to show current content
|
|
336
337
|
function debugShowContent() {
|
|
337
|
-
const doc = editor.state
|
|
338
|
+
const { doc } = editor.state
|
|
338
339
|
if (doc) {
|
|
339
340
|
console.log('=== Current Editor Content ===')
|
|
340
341
|
console.log('HTML:', doc.body.innerHTML)
|
|
@@ -370,7 +371,7 @@ function showInlineToolbarForSelection() {
|
|
|
370
371
|
return
|
|
371
372
|
}
|
|
372
373
|
|
|
373
|
-
const doc = editor.state
|
|
374
|
+
const { doc } = editor.state
|
|
374
375
|
if (!doc) return
|
|
375
376
|
|
|
376
377
|
const selection = doc.getSelection()
|
|
@@ -413,8 +414,8 @@ function showInlineToolbarForSelection() {
|
|
|
413
414
|
const rangeClone = range.cloneRange()
|
|
414
415
|
inlineToolbarSelection.value = selection
|
|
415
416
|
|
|
416
|
-
|
|
417
|
-
|
|
417
|
+
// Store the cloned range for later use
|
|
418
|
+
; (inlineToolbarSelection.value as any)._storedRange = rangeClone
|
|
418
419
|
|
|
419
420
|
showInlineToolbar.value = true
|
|
420
421
|
}
|
|
@@ -430,7 +431,7 @@ function hideInlineToolbar() {
|
|
|
430
431
|
function runInlineAction(actionName: string) {
|
|
431
432
|
if (!inlineToolbarSelection.value) return
|
|
432
433
|
|
|
433
|
-
const doc = editor.state
|
|
434
|
+
const { doc } = editor.state
|
|
434
435
|
if (!doc) return
|
|
435
436
|
|
|
436
437
|
// Get the stored range
|
|
@@ -518,7 +519,7 @@ function submitLink() {
|
|
|
518
519
|
|
|
519
520
|
const { selection, range, existingLink } = pendingLinkData
|
|
520
521
|
const { url, openInNewTab } = linkForm.value
|
|
521
|
-
const doc = editor.state
|
|
522
|
+
const { doc } = editor.state
|
|
522
523
|
|
|
523
524
|
if (!doc) return
|
|
524
525
|
|
|
@@ -573,7 +574,7 @@ function visitLink() {
|
|
|
573
574
|
|
|
574
575
|
// Add https:// if no protocol is specified
|
|
575
576
|
if (!url.match(/^https?:\/\//)) {
|
|
576
|
-
url =
|
|
577
|
+
url = `https://${url}`
|
|
577
578
|
}
|
|
578
579
|
|
|
579
580
|
if (linkForm.value.openInNewTab) {
|
|
@@ -588,12 +589,12 @@ function isValidUrl(url: string): boolean {
|
|
|
588
589
|
if (!url || url.trim() === '') return false
|
|
589
590
|
|
|
590
591
|
try {
|
|
591
|
-
|
|
592
|
+
URL.canParse(url)
|
|
592
593
|
return true
|
|
593
594
|
} catch {
|
|
594
595
|
// Try with https:// prefix if it's missing
|
|
595
596
|
try {
|
|
596
|
-
|
|
597
|
+
URL.canParse(`https://${url}`)
|
|
597
598
|
return true
|
|
598
599
|
} catch {
|
|
599
600
|
return false
|
|
@@ -603,7 +604,7 @@ function isValidUrl(url: string): boolean {
|
|
|
603
604
|
|
|
604
605
|
// Function to open image modal
|
|
605
606
|
function openImageModal(existingImage: HTMLElement | null = null) {
|
|
606
|
-
const doc = editor.state
|
|
607
|
+
const { doc } = editor.state
|
|
607
608
|
if (!doc) return
|
|
608
609
|
|
|
609
610
|
// Get current selection for new images
|
|
@@ -633,7 +634,7 @@ function openImageModal(existingImage: HTMLElement | null = null) {
|
|
|
633
634
|
alt: img?.alt || '',
|
|
634
635
|
width: img?.getAttribute('data-width') || img?.style.width || '',
|
|
635
636
|
height: img?.getAttribute('data-height') || img?.style.height || '',
|
|
636
|
-
credit
|
|
637
|
+
credit,
|
|
637
638
|
figcaption: !!figcaption
|
|
638
639
|
}
|
|
639
640
|
} else {
|
|
@@ -655,7 +656,7 @@ function openImageModal(existingImage: HTMLElement | null = null) {
|
|
|
655
656
|
function submitImage() {
|
|
656
657
|
if (!imageForm.value.src || !pendingImageData) return
|
|
657
658
|
|
|
658
|
-
const doc = editor.state
|
|
659
|
+
const { doc } = editor.state
|
|
659
660
|
if (!doc) return
|
|
660
661
|
|
|
661
662
|
const { existingImage } = pendingImageData
|
|
@@ -671,7 +672,7 @@ function submitImage() {
|
|
|
671
672
|
if (imageForm.value.width.includes('%') || imageForm.value.width.includes('px') || imageForm.value.width.includes('vw') || imageForm.value.width.includes('rem') || imageForm.value.width.includes('em')) {
|
|
672
673
|
img.style.width = imageForm.value.width
|
|
673
674
|
} else {
|
|
674
|
-
img.style.width = imageForm.value.width
|
|
675
|
+
img.style.width = `${imageForm.value.width}px`
|
|
675
676
|
}
|
|
676
677
|
} else {
|
|
677
678
|
img.style.width = '100%'
|
|
@@ -682,7 +683,7 @@ function submitImage() {
|
|
|
682
683
|
if (imageForm.value.height.includes('%') || imageForm.value.height.includes('px') || imageForm.value.height.includes('vh') || imageForm.value.height.includes('rem') || imageForm.value.height.includes('em') || imageForm.value.height === 'auto') {
|
|
683
684
|
img.style.height = imageForm.value.height
|
|
684
685
|
} else {
|
|
685
|
-
img.style.height = imageForm.value.height
|
|
686
|
+
img.style.height = `${imageForm.value.height}px`
|
|
686
687
|
}
|
|
687
688
|
} else {
|
|
688
689
|
img.style.height = 'auto'
|
|
@@ -700,7 +701,7 @@ function submitImage() {
|
|
|
700
701
|
|
|
701
702
|
// Always add credit if it exists (with lighter styling)
|
|
702
703
|
if (imageForm.value.credit) {
|
|
703
|
-
captionHTML +=
|
|
704
|
+
captionHTML += `${captionHTML ? ' • ' : ''}<span class="photo-credit">${imageForm.value.credit}</span>`
|
|
704
705
|
}
|
|
705
706
|
|
|
706
707
|
figcaption.innerHTML = captionHTML
|
|
@@ -774,7 +775,7 @@ function submitImage() {
|
|
|
774
775
|
|
|
775
776
|
// Function to open embed modal
|
|
776
777
|
function openEmbedModal(existingEmbed: HTMLElement | null = null) {
|
|
777
|
-
const doc = editor.state
|
|
778
|
+
const { doc } = editor.state
|
|
778
779
|
if (!doc) return
|
|
779
780
|
|
|
780
781
|
// Get current selection for new embeds
|
|
@@ -836,7 +837,7 @@ function extractEmbedUrl(input: string): string {
|
|
|
836
837
|
function submitEmbed() {
|
|
837
838
|
if (!embedForm.value.src || !pendingEmbedData) return
|
|
838
839
|
|
|
839
|
-
const doc = editor.state
|
|
840
|
+
const { doc } = editor.state
|
|
840
841
|
if (!doc) return
|
|
841
842
|
|
|
842
843
|
const { existingEmbed } = pendingEmbedData
|
|
@@ -853,7 +854,7 @@ function submitEmbed() {
|
|
|
853
854
|
// Debug: Log the iframe details
|
|
854
855
|
console.log('Creating embed iframe:', {
|
|
855
856
|
originalSrc: embedForm.value.src,
|
|
856
|
-
cleanUrl
|
|
857
|
+
cleanUrl,
|
|
857
858
|
width: embedForm.value.width,
|
|
858
859
|
height: embedForm.value.height
|
|
859
860
|
})
|
|
@@ -861,8 +862,8 @@ function submitEmbed() {
|
|
|
861
862
|
// Set width and height - always set them
|
|
862
863
|
iframe.width = embedForm.value.width || '560'
|
|
863
864
|
iframe.height = embedForm.value.height || '315'
|
|
864
|
-
iframe.style.width =
|
|
865
|
-
iframe.style.height =
|
|
865
|
+
iframe.style.width = `${embedForm.value.width || '560'}px`
|
|
866
|
+
iframe.style.height = `${embedForm.value.height || '315'}px`
|
|
866
867
|
|
|
867
868
|
// Create or update figure
|
|
868
869
|
let figure: HTMLElement
|
|
@@ -913,7 +914,7 @@ function submitEmbed() {
|
|
|
913
914
|
|
|
914
915
|
// Function to open video modal
|
|
915
916
|
function openVideoModal(existingVideo: HTMLElement | null = null) {
|
|
916
|
-
const doc = editor.state
|
|
917
|
+
const { doc } = editor.state
|
|
917
918
|
if (!doc) return
|
|
918
919
|
|
|
919
920
|
// Get current selection for new videos
|
|
@@ -998,7 +999,7 @@ function isValidVideoUrl(url: string): boolean {
|
|
|
998
999
|
}
|
|
999
1000
|
|
|
1000
1001
|
// Basic URL validation
|
|
1001
|
-
|
|
1002
|
+
URL.canParse(url)
|
|
1002
1003
|
return true
|
|
1003
1004
|
} catch {
|
|
1004
1005
|
return false
|
|
@@ -1007,13 +1008,13 @@ function isValidVideoUrl(url: string): boolean {
|
|
|
1007
1008
|
function submitVideo() {
|
|
1008
1009
|
if (!videoForm.value.src || !pendingVideoData) return
|
|
1009
1010
|
|
|
1010
|
-
const doc = editor.state
|
|
1011
|
+
const { doc } = editor.state
|
|
1011
1012
|
if (!doc) return
|
|
1012
1013
|
|
|
1013
1014
|
const { existingVideo } = pendingVideoData
|
|
1014
1015
|
|
|
1015
1016
|
// Calculate aspect ratio
|
|
1016
|
-
let aspectRatio = videoForm.value
|
|
1017
|
+
let { aspectRatio } = videoForm.value
|
|
1017
1018
|
const isCustom = videoForm.value.aspectRatio === 'custom'
|
|
1018
1019
|
if (isCustom && videoForm.value.customWidth && videoForm.value.customHeight) {
|
|
1019
1020
|
aspectRatio = `${videoForm.value.customWidth}:${videoForm.value.customHeight}`
|
|
@@ -1045,7 +1046,7 @@ function submitVideo() {
|
|
|
1045
1046
|
if (videoForm.value.width) {
|
|
1046
1047
|
videoContainer.style.width = videoForm.value.width.includes('%') || videoForm.value.width.includes('px')
|
|
1047
1048
|
? videoForm.value.width
|
|
1048
|
-
: videoForm.value.width
|
|
1049
|
+
: `${videoForm.value.width}px`
|
|
1049
1050
|
}
|
|
1050
1051
|
|
|
1051
1052
|
// Create the video placeholder/thumbnail instead of actual video
|
|
@@ -1173,8 +1174,8 @@ function deleteVideo() {
|
|
|
1173
1174
|
// Function to detect table alignment from table or wrapper
|
|
1174
1175
|
function detectTableAlignment(table: HTMLTableElement): 'left' | 'center' | 'right' {
|
|
1175
1176
|
// Check table margins to determine alignment
|
|
1176
|
-
const marginLeft = table.style
|
|
1177
|
-
const marginRight = table.style
|
|
1177
|
+
const { marginLeft } = table.style
|
|
1178
|
+
const { marginRight } = table.style
|
|
1178
1179
|
|
|
1179
1180
|
console.log('Table margins:', { marginLeft, marginRight })
|
|
1180
1181
|
|
|
@@ -1188,7 +1189,7 @@ function detectTableAlignment(table: HTMLTableElement): 'left' | 'center' | 'rig
|
|
|
1188
1189
|
}
|
|
1189
1190
|
|
|
1190
1191
|
function openTableEditor(existingTable: HTMLTableElement | null = null) {
|
|
1191
|
-
const doc = editor.state
|
|
1192
|
+
const { doc } = editor.state
|
|
1192
1193
|
if (!doc) return
|
|
1193
1194
|
|
|
1194
1195
|
// Get current selection for new tables
|
|
@@ -1239,8 +1240,8 @@ function openTableEditor(existingTable: HTMLTableElement | null = null) {
|
|
|
1239
1240
|
tableForm.value = {
|
|
1240
1241
|
rows: tbody ? tbody.rows.length : 3,
|
|
1241
1242
|
cols: tbody && tbody.rows.length > 0 ? tbody.rows[0].cells.length : 3,
|
|
1242
|
-
width: parseInt(existingTable.style.width) || 100,
|
|
1243
|
-
borderWidth: parseInt(existingTable.style.borderWidth) || 1,
|
|
1243
|
+
width: Number.parseInt(existingTable.style.width) || 100,
|
|
1244
|
+
borderWidth: Number.parseInt(existingTable.style.borderWidth) || 1,
|
|
1244
1245
|
borderColor: existingTable.style.borderColor || '#dddddd',
|
|
1245
1246
|
cellPadding: 8, // default, hard to extract
|
|
1246
1247
|
showHeaders: !!thead,
|
|
@@ -1250,7 +1251,7 @@ function openTableEditor(existingTable: HTMLTableElement | null = null) {
|
|
|
1250
1251
|
cellTextColor: tbody ? getComputedStyle(tbody.querySelector('td') || tbody).color || '#333333' : '#333333',
|
|
1251
1252
|
alternateRows: hasAlternatingRows,
|
|
1252
1253
|
alternateRowBgColor: alternateRowBg,
|
|
1253
|
-
alternateRowTextColor
|
|
1254
|
+
alternateRowTextColor,
|
|
1254
1255
|
fixedLayout: existingTable.style.tableLayout === 'fixed' || true, // ברירת מחדל true
|
|
1255
1256
|
alignment: detectTableAlignment(existingTable),
|
|
1256
1257
|
direction: existingTable.dir || 'ltr'
|
|
@@ -1283,7 +1284,7 @@ function openTableEditor(existingTable: HTMLTableElement | null = null) {
|
|
|
1283
1284
|
function submitTable() {
|
|
1284
1285
|
if (!pendingTableData) return
|
|
1285
1286
|
|
|
1286
|
-
const doc = editor.state
|
|
1287
|
+
const { doc } = editor.state
|
|
1287
1288
|
if (!doc) return
|
|
1288
1289
|
|
|
1289
1290
|
if (pendingTableData.existingTable) {
|
|
@@ -1316,7 +1317,7 @@ function submitTable() {
|
|
|
1316
1317
|
|
|
1317
1318
|
// Update border and padding for all cells
|
|
1318
1319
|
const allCells = table.querySelectorAll('td, th')
|
|
1319
|
-
allCells.forEach(cell => {
|
|
1320
|
+
allCells.forEach((cell) => {
|
|
1320
1321
|
const cellEl = cell as HTMLElement
|
|
1321
1322
|
cellEl.style.padding = `${tableForm.value.cellPadding}px`
|
|
1322
1323
|
cellEl.style.border = `${tableForm.value.borderWidth}px solid ${tableForm.value.borderColor}`
|
|
@@ -1330,7 +1331,7 @@ function submitTable() {
|
|
|
1330
1331
|
|
|
1331
1332
|
// Update header styles if headers exist
|
|
1332
1333
|
const headers = table.querySelectorAll('th')
|
|
1333
|
-
headers.forEach(th => {
|
|
1334
|
+
headers.forEach((th) => {
|
|
1334
1335
|
const thEl = th as HTMLElement
|
|
1335
1336
|
thEl.style.backgroundColor = tableForm.value.headerBgColor
|
|
1336
1337
|
thEl.style.color = tableForm.value.headerTextColor
|
|
@@ -1342,7 +1343,7 @@ function submitTable() {
|
|
|
1342
1343
|
const rows = Array.from(tbody.querySelectorAll('tr'))
|
|
1343
1344
|
rows.forEach((row, i) => {
|
|
1344
1345
|
const cells = row.querySelectorAll('td')
|
|
1345
|
-
cells.forEach(cell => {
|
|
1346
|
+
cells.forEach((cell) => {
|
|
1346
1347
|
const cellEl = cell as HTMLElement
|
|
1347
1348
|
if (tableForm.value.alternateRows && i % 2 === 1) {
|
|
1348
1349
|
// Alternate rows: use special colors
|
|
@@ -1479,7 +1480,7 @@ function createNewTable(doc: Document) {
|
|
|
1479
1480
|
console.log('Trying to add edit button to new table...')
|
|
1480
1481
|
if (doc && (doc as any).__addEditButtonsToTables) {
|
|
1481
1482
|
console.log('Calling __addEditButtonsToTables...')
|
|
1482
|
-
|
|
1483
|
+
; (doc as any).__addEditButtonsToTables()
|
|
1483
1484
|
} else {
|
|
1484
1485
|
console.log('__addEditButtonsToTables not found, calling setupTableEditButtons...')
|
|
1485
1486
|
setupTableEditButtons(doc)
|
|
@@ -1520,7 +1521,7 @@ function applyDefaultTableSettings(table: HTMLTableElement) {
|
|
|
1520
1521
|
|
|
1521
1522
|
if (headers.length > 0) {
|
|
1522
1523
|
const colWidth = `${100 / headers.length}%`
|
|
1523
|
-
headers.forEach(th => {
|
|
1524
|
+
headers.forEach((th) => {
|
|
1524
1525
|
if (!(th as HTMLElement).style.width) {
|
|
1525
1526
|
(th as HTMLElement).style.width = colWidth
|
|
1526
1527
|
}
|
|
@@ -1533,7 +1534,7 @@ function applyDefaultTableSettings(table: HTMLTableElement) {
|
|
|
1533
1534
|
if (firstRow) {
|
|
1534
1535
|
const colCount = firstRow.querySelectorAll('td').length
|
|
1535
1536
|
const colWidth = `${100 / colCount}%`
|
|
1536
|
-
cells.forEach(td => {
|
|
1537
|
+
cells.forEach((td) => {
|
|
1537
1538
|
if (!(td as HTMLElement).style.width) {
|
|
1538
1539
|
(td as HTMLElement).style.width = colWidth
|
|
1539
1540
|
}
|
|
@@ -1544,7 +1545,7 @@ function applyDefaultTableSettings(table: HTMLTableElement) {
|
|
|
1544
1545
|
|
|
1545
1546
|
// Set cell text alignment
|
|
1546
1547
|
const allCells = table.querySelectorAll('td, th')
|
|
1547
|
-
allCells.forEach(cell => {
|
|
1548
|
+
allCells.forEach((cell) => {
|
|
1548
1549
|
const cellEl = cell as HTMLElement
|
|
1549
1550
|
cellEl.style.textAlign = defaultSettings.alignment
|
|
1550
1551
|
})
|
|
@@ -1581,17 +1582,17 @@ function deleteEmbed() {
|
|
|
1581
1582
|
|
|
1582
1583
|
// Expose openLinkModal to editor state
|
|
1583
1584
|
; (editor.state as any).openLinkModal = openLinkModal
|
|
1584
|
-
|
|
1585
|
+
; (editor.state as any).showTooltipMessage = showTooltipMessage
|
|
1585
1586
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1587
|
+
// Expose openTableEditor to editor state
|
|
1588
|
+
; (editor.state as any).openTableEditor = openTableEditor
|
|
1589
|
+
; (editor.state as any).openImageModal = openImageModal
|
|
1590
|
+
; (editor.state as any).openEmbedModal = openEmbedModal
|
|
1591
|
+
; (editor.state as any).openVideoModal = openVideoModal
|
|
1591
1592
|
|
|
1592
1593
|
// Table manipulation functions
|
|
1593
1594
|
function mergeCellRight() {
|
|
1594
|
-
const doc = editor.state
|
|
1595
|
+
const { doc } = editor.state
|
|
1595
1596
|
if (!doc || !contextMenuCell.value) return
|
|
1596
1597
|
|
|
1597
1598
|
const cell = contextMenuCell.value
|
|
@@ -1607,12 +1608,12 @@ function mergeCellRight() {
|
|
|
1607
1608
|
if (targetCell && targetIndex >= 0 && targetIndex < row.cells.length) {
|
|
1608
1609
|
// Combine content
|
|
1609
1610
|
if (targetCell.innerHTML.trim()) {
|
|
1610
|
-
cell.innerHTML +=
|
|
1611
|
+
cell.innerHTML += ` ${targetCell.innerHTML}`
|
|
1611
1612
|
}
|
|
1612
1613
|
|
|
1613
1614
|
// Update colspan
|
|
1614
|
-
const currentColspan = parseInt(cell.getAttribute('colspan') || '1')
|
|
1615
|
-
const targetColspan = parseInt(targetCell.getAttribute('colspan') || '1')
|
|
1615
|
+
const currentColspan = Number.parseInt(cell.getAttribute('colspan') || '1')
|
|
1616
|
+
const targetColspan = Number.parseInt(targetCell.getAttribute('colspan') || '1')
|
|
1616
1617
|
cell.setAttribute('colspan', (currentColspan + targetColspan).toString())
|
|
1617
1618
|
|
|
1618
1619
|
// Remove the target cell
|
|
@@ -1623,7 +1624,7 @@ function mergeCellRight() {
|
|
|
1623
1624
|
}
|
|
1624
1625
|
|
|
1625
1626
|
function mergeCellDown() {
|
|
1626
|
-
const doc = editor.state
|
|
1627
|
+
const { doc } = editor.state
|
|
1627
1628
|
if (!doc || !contextMenuCell.value) return
|
|
1628
1629
|
|
|
1629
1630
|
const cell = contextMenuCell.value
|
|
@@ -1642,12 +1643,12 @@ function mergeCellDown() {
|
|
|
1642
1643
|
if (targetCell) {
|
|
1643
1644
|
// Combine content
|
|
1644
1645
|
if (targetCell.innerHTML.trim()) {
|
|
1645
|
-
cell.innerHTML +=
|
|
1646
|
+
cell.innerHTML += `<br>${targetCell.innerHTML}`
|
|
1646
1647
|
}
|
|
1647
1648
|
|
|
1648
1649
|
// Update rowspan
|
|
1649
|
-
const currentRowspan = parseInt(cell.getAttribute('rowspan') || '1')
|
|
1650
|
-
const targetRowspan = parseInt(targetCell.getAttribute('rowspan') || '1')
|
|
1650
|
+
const currentRowspan = Number.parseInt(cell.getAttribute('rowspan') || '1')
|
|
1651
|
+
const targetRowspan = Number.parseInt(targetCell.getAttribute('rowspan') || '1')
|
|
1651
1652
|
cell.setAttribute('rowspan', (currentRowspan + targetRowspan).toString())
|
|
1652
1653
|
|
|
1653
1654
|
// Remove the target cell
|
|
@@ -1659,20 +1660,19 @@ function mergeCellDown() {
|
|
|
1659
1660
|
}
|
|
1660
1661
|
|
|
1661
1662
|
function splitCell() {
|
|
1662
|
-
const doc = editor.state
|
|
1663
|
+
const { doc } = editor.state
|
|
1663
1664
|
if (!doc || !contextMenuCell.value) return
|
|
1664
1665
|
|
|
1665
1666
|
const cell = contextMenuCell.value
|
|
1666
1667
|
const table = cell.closest('table') as HTMLTableElement
|
|
1667
1668
|
const isRTL = table?.dir === 'rtl'
|
|
1668
|
-
const colspan = parseInt(cell.getAttribute('colspan') || '1')
|
|
1669
|
-
const rowspan = parseInt(cell.getAttribute('rowspan') || '1')
|
|
1669
|
+
const colspan = Number.parseInt(cell.getAttribute('colspan') || '1')
|
|
1670
|
+
const rowspan = Number.parseInt(cell.getAttribute('rowspan') || '1')
|
|
1670
1671
|
|
|
1671
1672
|
if (colspan > 1) {
|
|
1672
1673
|
// Split horizontally
|
|
1673
1674
|
cell.setAttribute('colspan', '1')
|
|
1674
1675
|
const row = cell.parentElement as HTMLTableRowElement
|
|
1675
|
-
const cellIndex = Array.from(row.cells).indexOf(cell)
|
|
1676
1676
|
|
|
1677
1677
|
// Add new cells to the right (or left in RTL)
|
|
1678
1678
|
for (let i = 1; i < colspan; i++) {
|
|
@@ -1721,7 +1721,7 @@ function splitCell() {
|
|
|
1721
1721
|
}
|
|
1722
1722
|
|
|
1723
1723
|
function insertRowAbove() {
|
|
1724
|
-
const doc = editor.state
|
|
1724
|
+
const { doc } = editor.state
|
|
1725
1725
|
if (!doc || !contextMenuCell.value) return
|
|
1726
1726
|
|
|
1727
1727
|
const cell = contextMenuCell.value
|
|
@@ -1730,7 +1730,7 @@ function insertRowAbove() {
|
|
|
1730
1730
|
// Create new row with same number of columns
|
|
1731
1731
|
const newRow = doc.createElement('tr')
|
|
1732
1732
|
const columnCount = Array.from(row.cells).reduce((total, cell) => {
|
|
1733
|
-
return total + parseInt(cell.getAttribute('colspan') || '1')
|
|
1733
|
+
return total + Number.parseInt(cell.getAttribute('colspan') || '1')
|
|
1734
1734
|
}, 0)
|
|
1735
1735
|
|
|
1736
1736
|
for (let i = 0; i < columnCount; i++) {
|
|
@@ -1748,7 +1748,7 @@ function insertRowAbove() {
|
|
|
1748
1748
|
}
|
|
1749
1749
|
|
|
1750
1750
|
function insertRowBelow() {
|
|
1751
|
-
const doc = editor.state
|
|
1751
|
+
const { doc } = editor.state
|
|
1752
1752
|
if (!doc || !contextMenuCell.value) return
|
|
1753
1753
|
|
|
1754
1754
|
const cell = contextMenuCell.value
|
|
@@ -1757,7 +1757,7 @@ function insertRowBelow() {
|
|
|
1757
1757
|
// Create new row with same number of columns
|
|
1758
1758
|
const newRow = doc.createElement('tr')
|
|
1759
1759
|
const columnCount = Array.from(row.cells).reduce((total, cell) => {
|
|
1760
|
-
return total + parseInt(cell.getAttribute('colspan') || '1')
|
|
1760
|
+
return total + Number.parseInt(cell.getAttribute('colspan') || '1')
|
|
1761
1761
|
}, 0)
|
|
1762
1762
|
|
|
1763
1763
|
for (let i = 0; i < columnCount; i++) {
|
|
@@ -1775,7 +1775,7 @@ function insertRowBelow() {
|
|
|
1775
1775
|
}
|
|
1776
1776
|
|
|
1777
1777
|
function deleteRow() {
|
|
1778
|
-
const doc = editor.state
|
|
1778
|
+
const { doc } = editor.state
|
|
1779
1779
|
if (!doc || !contextMenuCell.value) return
|
|
1780
1780
|
|
|
1781
1781
|
const cell = contextMenuCell.value
|
|
@@ -1796,7 +1796,7 @@ function deleteRow() {
|
|
|
1796
1796
|
}
|
|
1797
1797
|
|
|
1798
1798
|
function insertColumnLeft() {
|
|
1799
|
-
const doc = editor.state
|
|
1799
|
+
const { doc } = editor.state
|
|
1800
1800
|
if (!doc || !contextMenuCell.value) return
|
|
1801
1801
|
|
|
1802
1802
|
const cell = contextMenuCell.value
|
|
@@ -1805,7 +1805,7 @@ function insertColumnLeft() {
|
|
|
1805
1805
|
|
|
1806
1806
|
// Add new cell to each row at the same index
|
|
1807
1807
|
const rows = table.querySelectorAll('tr')
|
|
1808
|
-
rows.forEach(row => {
|
|
1808
|
+
rows.forEach((row) => {
|
|
1809
1809
|
const newCell = doc.createElement(row.parentElement?.tagName === 'THEAD' ? 'th' : 'td')
|
|
1810
1810
|
newCell.innerHTML = ' '
|
|
1811
1811
|
newCell.style.padding = '8px'
|
|
@@ -1822,7 +1822,7 @@ function insertColumnLeft() {
|
|
|
1822
1822
|
}
|
|
1823
1823
|
|
|
1824
1824
|
function insertColumnRight() {
|
|
1825
|
-
const doc = editor.state
|
|
1825
|
+
const { doc } = editor.state
|
|
1826
1826
|
if (!doc || !contextMenuCell.value) return
|
|
1827
1827
|
|
|
1828
1828
|
const cell = contextMenuCell.value
|
|
@@ -1831,7 +1831,7 @@ function insertColumnRight() {
|
|
|
1831
1831
|
|
|
1832
1832
|
// Add new cell to each row after the current index
|
|
1833
1833
|
const rows = table.querySelectorAll('tr')
|
|
1834
|
-
rows.forEach(row => {
|
|
1834
|
+
rows.forEach((row) => {
|
|
1835
1835
|
const newCell = doc.createElement(row.parentElement?.tagName === 'THEAD' ? 'th' : 'td')
|
|
1836
1836
|
newCell.innerHTML = ' '
|
|
1837
1837
|
newCell.style.padding = '8px'
|
|
@@ -1849,7 +1849,7 @@ function insertColumnRight() {
|
|
|
1849
1849
|
}
|
|
1850
1850
|
|
|
1851
1851
|
function deleteColumn() {
|
|
1852
|
-
const doc = editor.state
|
|
1852
|
+
const { doc } = editor.state
|
|
1853
1853
|
if (!doc || !contextMenuCell.value) return
|
|
1854
1854
|
|
|
1855
1855
|
const cell = contextMenuCell.value
|
|
@@ -1865,7 +1865,7 @@ function deleteColumn() {
|
|
|
1865
1865
|
|
|
1866
1866
|
// Remove cell from each row at the same index
|
|
1867
1867
|
const rows = table.querySelectorAll('tr')
|
|
1868
|
-
rows.forEach(row => {
|
|
1868
|
+
rows.forEach((row) => {
|
|
1869
1869
|
if (row.cells[cellIndex]) {
|
|
1870
1870
|
row.cells[cellIndex].remove()
|
|
1871
1871
|
}
|
|
@@ -1899,8 +1899,8 @@ const canMergeDown = computed(() => {
|
|
|
1899
1899
|
const canSplit = computed(() => {
|
|
1900
1900
|
if (!contextMenuCell.value) return false
|
|
1901
1901
|
const cell = contextMenuCell.value
|
|
1902
|
-
const colspan = parseInt(cell.getAttribute('colspan') || '1')
|
|
1903
|
-
const rowspan = parseInt(cell.getAttribute('rowspan') || '1')
|
|
1902
|
+
const colspan = Number.parseInt(cell.getAttribute('colspan') || '1')
|
|
1903
|
+
const rowspan = Number.parseInt(cell.getAttribute('rowspan') || '1')
|
|
1904
1904
|
return colspan > 1 || rowspan > 1
|
|
1905
1905
|
})
|
|
1906
1906
|
|
|
@@ -1923,17 +1923,15 @@ function closeTableContextMenu() {
|
|
|
1923
1923
|
}
|
|
1924
1924
|
|
|
1925
1925
|
// Handle clicks outside context menu
|
|
1926
|
-
function handleDocumentClick(event: MouseEvent) {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
const commands = useCommands(editor.state, editor.state.debug)
|
|
1926
|
+
// function handleDocumentClick(event: MouseEvent) {
|
|
1927
|
+
// if (showTableContextMenu.value) {
|
|
1928
|
+
// const target = event.target as HTMLElement
|
|
1929
|
+
// const contextMenu = target.closest('.table-context-menu')
|
|
1930
|
+
// if (!contextMenu) {
|
|
1931
|
+
// closeTableContextMenu()
|
|
1932
|
+
// }
|
|
1933
|
+
// }
|
|
1934
|
+
// }
|
|
1937
1935
|
|
|
1938
1936
|
// Expose debug methods if debug mode is enabled
|
|
1939
1937
|
const debugMethods = $computed(() => editor.state.debug)
|
|
@@ -2115,14 +2113,15 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2115
2113
|
const walker = doc.createTreeWalker(
|
|
2116
2114
|
doc.body,
|
|
2117
2115
|
NodeFilter.SHOW_TEXT,
|
|
2118
|
-
node => {
|
|
2116
|
+
(node) => {
|
|
2119
2117
|
// Only accept text nodes that are direct children of body
|
|
2120
2118
|
// AND have meaningful content
|
|
2121
2119
|
const parent = node.parentNode as HTMLElement
|
|
2122
|
-
return (parent === doc.body
|
|
2123
|
-
node.textContent?.trim()
|
|
2124
|
-
node.textContent.trim().length > 0)
|
|
2125
|
-
NodeFilter.FILTER_ACCEPT
|
|
2120
|
+
return (parent === doc.body
|
|
2121
|
+
&& node.textContent?.trim()
|
|
2122
|
+
&& node.textContent.trim().length > 0)
|
|
2123
|
+
? NodeFilter.FILTER_ACCEPT
|
|
2124
|
+
: NodeFilter.FILTER_REJECT
|
|
2126
2125
|
}
|
|
2127
2126
|
)
|
|
2128
2127
|
|
|
@@ -2133,17 +2132,16 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2133
2132
|
}
|
|
2134
2133
|
|
|
2135
2134
|
// Wrap loose text nodes very carefully, preserving existing structure
|
|
2136
|
-
textNodes.forEach(textNode => {
|
|
2135
|
+
textNodes.forEach((textNode) => {
|
|
2137
2136
|
// Triple check the node is still valid and not corrupted
|
|
2138
|
-
if (textNode.parentNode === doc.body
|
|
2139
|
-
textNode.textContent?.trim()
|
|
2140
|
-
textNode.nodeType === Node.TEXT_NODE) {
|
|
2141
|
-
|
|
2137
|
+
if (textNode.parentNode === doc.body
|
|
2138
|
+
&& textNode.textContent?.trim()
|
|
2139
|
+
&& textNode.nodeType === Node.TEXT_NODE) {
|
|
2142
2140
|
const p = doc.createElement('p')
|
|
2143
2141
|
p.dir = doc.body.dir || 'ltr'
|
|
2144
2142
|
|
|
2145
2143
|
// Clone the text content to avoid any reference issues
|
|
2146
|
-
const textContent = textNode
|
|
2144
|
+
const { textContent } = textNode
|
|
2147
2145
|
p.textContent = textContent
|
|
2148
2146
|
|
|
2149
2147
|
// Replace the text node with the paragraph
|
|
@@ -2184,7 +2182,6 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2184
2182
|
}
|
|
2185
2183
|
}
|
|
2186
2184
|
}
|
|
2187
|
-
|
|
2188
2185
|
} finally {
|
|
2189
2186
|
// Always clean up the processing flag
|
|
2190
2187
|
delete doc.body.dataset.normalizing
|
|
@@ -2209,7 +2206,7 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2209
2206
|
}
|
|
2210
2207
|
updateContentWithHistory(doc)
|
|
2211
2208
|
return
|
|
2212
|
-
}
|
|
2209
|
+
} // Don't normalize during normal typing - only on paste/drop
|
|
2213
2210
|
const inputEvent = e as InputEvent
|
|
2214
2211
|
const normalizeInputTypes = ['insertFromPaste', 'insertFromDrop']
|
|
2215
2212
|
|
|
@@ -2248,7 +2245,7 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2248
2245
|
if (li || listContainer) {
|
|
2249
2246
|
// We're in a list context - let browser handle everything
|
|
2250
2247
|
return
|
|
2251
|
-
}
|
|
2248
|
+
} // Find the paragraph we're in (only for confirmed non-list content)
|
|
2252
2249
|
let paragraph = range.startContainer
|
|
2253
2250
|
if (paragraph.nodeType === Node.TEXT_NODE) {
|
|
2254
2251
|
paragraph = paragraph.parentElement!
|
|
@@ -2260,9 +2257,9 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2260
2257
|
// Check if current paragraph is empty and previous paragraph is also empty
|
|
2261
2258
|
const currentIsEmpty = !paragraph.textContent?.trim()
|
|
2262
2259
|
const prevSibling = (paragraph as Element).previousElementSibling as HTMLElement
|
|
2263
|
-
const prevIsEmpty = prevSibling
|
|
2264
|
-
(prevSibling.tagName === 'P')
|
|
2265
|
-
!prevSibling.textContent?.trim()
|
|
2260
|
+
const prevIsEmpty = prevSibling
|
|
2261
|
+
&& (prevSibling.tagName === 'P')
|
|
2262
|
+
&& !prevSibling.textContent?.trim()
|
|
2266
2263
|
|
|
2267
2264
|
// If both current and previous paragraphs are empty, don't create another empty paragraph
|
|
2268
2265
|
if (currentIsEmpty && prevIsEmpty) {
|
|
@@ -2303,7 +2300,7 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2303
2300
|
const textNode = range.startContainer as Text
|
|
2304
2301
|
|
|
2305
2302
|
// Store the original parent element for formatting preservation
|
|
2306
|
-
const parentElement = textNode
|
|
2303
|
+
const { parentElement } = textNode
|
|
2307
2304
|
|
|
2308
2305
|
// Keep text before cursor in current paragraph
|
|
2309
2306
|
textNode.textContent = textBeforeCursor
|
|
@@ -2353,10 +2350,10 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2353
2350
|
for (const mutation of mutations) {
|
|
2354
2351
|
if (mutation.type === 'childList') {
|
|
2355
2352
|
// Check if any loose text nodes were added to body
|
|
2356
|
-
Array.from(mutation.addedNodes).forEach(addedNode => {
|
|
2357
|
-
if (addedNode.nodeType === Node.TEXT_NODE
|
|
2358
|
-
addedNode.parentNode === doc.body
|
|
2359
|
-
addedNode.textContent?.trim()) {
|
|
2353
|
+
Array.from(mutation.addedNodes).forEach((addedNode) => {
|
|
2354
|
+
if (addedNode.nodeType === Node.TEXT_NODE
|
|
2355
|
+
&& addedNode.parentNode === doc.body
|
|
2356
|
+
&& addedNode.textContent?.trim()) {
|
|
2360
2357
|
// Wrap loose text node immediately
|
|
2361
2358
|
const p = doc.createElement('p')
|
|
2362
2359
|
p.dir = doc.body.dir || 'ltr'
|
|
@@ -2387,7 +2384,7 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2387
2384
|
// Also apply direction to list items if it's a list
|
|
2388
2385
|
if (element.tagName === 'UL' || element.tagName === 'OL') {
|
|
2389
2386
|
const listItems = element.querySelectorAll('li')
|
|
2390
|
-
listItems.forEach(li => {
|
|
2387
|
+
listItems.forEach((li) => {
|
|
2391
2388
|
if (!li.dir) {
|
|
2392
2389
|
li.dir = element.dir
|
|
2393
2390
|
console.log('Applied direction to new list item:', li.dir)
|
|
@@ -2508,7 +2505,7 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2508
2505
|
})
|
|
2509
2506
|
}
|
|
2510
2507
|
|
|
2511
|
-
|
|
2508
|
+
async function initEditor() {
|
|
2512
2509
|
if (isInitializing.value || !iframe.value || hasInitialized.value) {
|
|
2513
2510
|
return
|
|
2514
2511
|
}
|
|
@@ -2517,7 +2514,7 @@ const initEditor = async () => {
|
|
|
2517
2514
|
|
|
2518
2515
|
try {
|
|
2519
2516
|
// Use basic embedded styles for better compatibility
|
|
2520
|
-
|
|
2517
|
+
const editorStylesContent = `
|
|
2521
2518
|
body {
|
|
2522
2519
|
margin: 0;
|
|
2523
2520
|
padding: 8px;
|
|
@@ -2585,7 +2582,7 @@ const initEditor = async () => {
|
|
|
2585
2582
|
background: #f4f4f4 !important;
|
|
2586
2583
|
border-inline-start: 4px solid #ccc !important;
|
|
2587
2584
|
}
|
|
2588
|
-
`
|
|
2585
|
+
` // Create a complete HTML document with proper doctype and meta tags
|
|
2589
2586
|
const initialContent = props.modelValue || (props.placeholder ? `<p class="placeholder">${props.placeholder}</p>` : '')
|
|
2590
2587
|
const htmlContent = `
|
|
2591
2588
|
<!DOCTYPE html>
|
|
@@ -2639,7 +2636,7 @@ const initEditor = async () => {
|
|
|
2639
2636
|
// Apply direction to existing blockquotes and lists
|
|
2640
2637
|
const blockElements = doc.body.querySelectorAll('blockquote, ul, ol')
|
|
2641
2638
|
console.log('Found existing block elements:', blockElements.length)
|
|
2642
|
-
blockElements.forEach(element => {
|
|
2639
|
+
blockElements.forEach((element) => {
|
|
2643
2640
|
const htmlElement = element as HTMLElement
|
|
2644
2641
|
console.log('Processing element:', htmlElement.tagName, 'current dir:', htmlElement.dir)
|
|
2645
2642
|
if (!htmlElement.dir) {
|
|
@@ -2650,7 +2647,7 @@ const initEditor = async () => {
|
|
|
2650
2647
|
// Also apply direction to list items
|
|
2651
2648
|
if (htmlElement.tagName === 'UL' || htmlElement.tagName === 'OL') {
|
|
2652
2649
|
const listItems = htmlElement.querySelectorAll('li')
|
|
2653
|
-
listItems.forEach(li => {
|
|
2650
|
+
listItems.forEach((li) => {
|
|
2654
2651
|
const listItem = li as HTMLElement
|
|
2655
2652
|
if (!listItem.dir) {
|
|
2656
2653
|
listItem.dir = htmlElement.dir
|
|
@@ -2671,7 +2668,7 @@ const initEditor = async () => {
|
|
|
2671
2668
|
setTimeout(() => {
|
|
2672
2669
|
// Clear all formatting by removing styles and replacing with plain text
|
|
2673
2670
|
const elements = doc.body.querySelectorAll('*')
|
|
2674
|
-
elements.forEach(el => {
|
|
2671
|
+
elements.forEach((el) => {
|
|
2675
2672
|
if (el.tagName !== 'P' && el.tagName !== 'BR') {
|
|
2676
2673
|
const textContent = el.textContent || ''
|
|
2677
2674
|
if (textContent.trim()) {
|
|
@@ -2683,7 +2680,7 @@ const initEditor = async () => {
|
|
|
2683
2680
|
}
|
|
2684
2681
|
} else if (el.tagName === 'P') {
|
|
2685
2682
|
// Remove all attributes from paragraphs
|
|
2686
|
-
Array.from(el.attributes).forEach(attr => {
|
|
2683
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
2687
2684
|
if (attr.name !== 'dir') {
|
|
2688
2685
|
el.removeAttribute(attr.name)
|
|
2689
2686
|
}
|
|
@@ -2696,12 +2693,12 @@ const initEditor = async () => {
|
|
|
2696
2693
|
|
|
2697
2694
|
// Add inline toolbar selection listener
|
|
2698
2695
|
doc.addEventListener('selectionchange', () => {
|
|
2699
|
-
setTimeout(() => showInlineToolbarForSelection(), 10)
|
|
2696
|
+
setTimeout(() => { showInlineToolbarForSelection() }, 10)
|
|
2700
2697
|
})
|
|
2701
2698
|
|
|
2702
2699
|
// Also listen for mouseup to handle manual selections
|
|
2703
2700
|
doc.addEventListener('mouseup', () => {
|
|
2704
|
-
setTimeout(() => showInlineToolbarForSelection(), 10)
|
|
2701
|
+
setTimeout(() => { showInlineToolbarForSelection() }, 10)
|
|
2705
2702
|
})
|
|
2706
2703
|
|
|
2707
2704
|
// Hide inline toolbar when clicking outside or when selection is lost
|
|
@@ -2768,7 +2765,7 @@ const initEditor = async () => {
|
|
|
2768
2765
|
// Add click listener to highlight selected table cell
|
|
2769
2766
|
doc.addEventListener('click', (e) => {
|
|
2770
2767
|
// Remove previous highlights
|
|
2771
|
-
doc.querySelectorAll('.table-cell-selected').forEach(cell => {
|
|
2768
|
+
doc.querySelectorAll('.table-cell-selected').forEach((cell) => {
|
|
2772
2769
|
cell.classList.remove('table-cell-selected')
|
|
2773
2770
|
})
|
|
2774
2771
|
|
|
@@ -2884,7 +2881,7 @@ watch(() => tableForm.value.alignment, (newAlignment) => {
|
|
|
2884
2881
|
})
|
|
2885
2882
|
|
|
2886
2883
|
// Close context menu when clicking outside
|
|
2887
|
-
|
|
2884
|
+
function handleGlobalClick() {
|
|
2888
2885
|
showTableContextMenu.value = false
|
|
2889
2886
|
}
|
|
2890
2887
|
|
|
@@ -2908,301 +2905,489 @@ defineExpose({
|
|
|
2908
2905
|
</script>
|
|
2909
2906
|
|
|
2910
2907
|
<template>
|
|
2911
|
-
<div class="bagel-input" v-bind="attrs">
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
</Btn>
|
|
2947
|
-
<Btn thin color="gray" icon="content_copy" @click="copyText(debugMethods?.exportDebugWithPrompt() || '')">
|
|
2948
|
-
Copy Log
|
|
2949
|
-
</Btn>
|
|
2950
|
-
</div>
|
|
2951
|
-
</div>
|
|
2952
|
-
|
|
2953
|
-
<!-- Inline Toolbar -->
|
|
2954
|
-
<div v-if="showInlineToolbar" class="inline-toolbar" :style="{
|
|
2955
|
-
position: 'fixed',
|
|
2956
|
-
top: inlineToolbarPosition.top + 'px',
|
|
2957
|
-
left: inlineToolbarPosition.left + 'px',
|
|
2958
|
-
zIndex: 99,
|
|
2959
|
-
}">
|
|
2960
|
-
<div class="inline-toolbar-content">
|
|
2961
|
-
<Btn thin flat icon="format_bold" @click="runInlineAction('bold')" :class="{ active: editor.state.selectedStyles.has('bold') }" />
|
|
2962
|
-
<Btn thin flat icon="format_italic" @click="runInlineAction('italic')" :class="{ active: editor.state.selectedStyles.has('italic') }" />
|
|
2963
|
-
<Btn thin flat icon="format_underlined" @click="runInlineAction('underline')" :class="{ active: editor.state.selectedStyles.has('underline') }" />
|
|
2964
|
-
<span class="separator">|</span>
|
|
2965
|
-
<Btn thin flat icon="add_link" @click="runInlineAction('link')" />
|
|
2966
|
-
</div>
|
|
2967
|
-
</div>
|
|
2968
|
-
<!-- Table Context Menu -->
|
|
2969
|
-
<div v-if="showTableContextMenu" class="table-context-menu" :style="{
|
|
2970
|
-
position: 'fixed',
|
|
2971
|
-
top: contextMenuPosition.y + 'px',
|
|
2972
|
-
left: contextMenuPosition.x + 'px',
|
|
2973
|
-
zIndex: 1001
|
|
2974
|
-
}">
|
|
2975
|
-
<div class="context-menu-content" @click.stop>
|
|
2976
|
-
<div class="menu-header">
|
|
2977
|
-
<span>Table Actions</span>
|
|
2978
|
-
<button class="close-btn" @click="showTableContextMenu = false">×</button>
|
|
2908
|
+
<div class="bagel-input" v-bind="attrs">
|
|
2909
|
+
<label v-if="label">{{ label }}</label>
|
|
2910
|
+
|
|
2911
|
+
<div
|
|
2912
|
+
class="rounded" :class="[
|
|
2913
|
+
{
|
|
2914
|
+
'rich-text-editor pt-05 px-05 pb-075': !basic,
|
|
2915
|
+
'rich-text-editor--basic': basic,
|
|
2916
|
+
'fullscreen-mode': editor.state.isFullscreen,
|
|
2917
|
+
},
|
|
2918
|
+
]"
|
|
2919
|
+
>
|
|
2920
|
+
<EditorToolbar
|
|
2921
|
+
v-if="editor.state.hasInit && shouldShowToolbar" :config="effectiveToolbarConfig"
|
|
2922
|
+
:selectedStyles="editor.state.selectedStyles" :hide-images="hideImages" :hide-videos="hideVideos"
|
|
2923
|
+
:hide-embeds="hideEmbed" :hide-tables="hideTables" :hide-alignment="hideAlignment"
|
|
2924
|
+
:hide-directions="hideDirections" :hide-h5-h6="hideH5H6" :hide="effectiveHideArray"
|
|
2925
|
+
@action="commands.execute"
|
|
2926
|
+
/>
|
|
2927
|
+
<div class="editor-container" :class="{ 'split-view': editor.state.isSplitView }">
|
|
2928
|
+
<div
|
|
2929
|
+
class="content-area radius-1"
|
|
2930
|
+
:style="{ height: editor.state.isFullscreen ? 'calc(100vh - 4rem)' : editorHeight }"
|
|
2931
|
+
>
|
|
2932
|
+
<iframe
|
|
2933
|
+
id="rich-text-iframe" ref="iframe" class="editableContent" title="Editor" srcdoc=""
|
|
2934
|
+
sandbox="allow-same-origin allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts allow-top-navigation allow-top-navigation-by-user-activation"
|
|
2935
|
+
@load="initEditor" @contextmenu="handleTableContextMenu"
|
|
2936
|
+
/>
|
|
2937
|
+
</div>
|
|
2938
|
+
<CodeEditor
|
|
2939
|
+
v-if="editor.state.isSplitView" v-model="editor.state.content" language="html"
|
|
2940
|
+
:height="editor.state.isFullscreen ? 'calc(100vh - 4rem)' : editorHeight"
|
|
2941
|
+
@update:modelValue="editor.updateState.content('html')"
|
|
2942
|
+
/>
|
|
2979
2943
|
</div>
|
|
2980
|
-
<div class="
|
|
2981
|
-
<
|
|
2982
|
-
|
|
2983
|
-
</
|
|
2984
|
-
<Btn
|
|
2985
|
-
|
|
2986
|
-
</Btn>
|
|
2987
|
-
<Btn v-if="canSplit" full-width align-txt="start" thin flat icon="call_split" @click="splitCell(); showTableContextMenu = false" class="context-menu-btn">
|
|
2988
|
-
Split Cell
|
|
2989
|
-
</Btn>
|
|
2990
|
-
<div class="context-menu-separator" v-if="canMergeRight || canMergeDown || canSplit"></div>
|
|
2991
|
-
<Btn full-width align-txt="start" thin flat icon="add_row_above" @click="insertRowAbove(); showTableContextMenu = false" class="context-menu-btn">
|
|
2992
|
-
Insert Row Above
|
|
2993
|
-
</Btn>
|
|
2994
|
-
<Btn full-width align-txt="start" thin flat icon="add_row_below" @click="insertRowBelow(); showTableContextMenu = false" class="context-menu-btn">
|
|
2995
|
-
Insert Row Below
|
|
2944
|
+
<div v-if="debug" class="flex pt-05">
|
|
2945
|
+
<p class="txt12 txt-gray mb-0 p-0">
|
|
2946
|
+
Debug
|
|
2947
|
+
</p>
|
|
2948
|
+
<Btn thin color="gray" icon="visibility" @click="debugShowContent">
|
|
2949
|
+
Show Content
|
|
2996
2950
|
</Btn>
|
|
2997
|
-
<Btn
|
|
2998
|
-
|
|
2951
|
+
<Btn thin color="gray" icon="delete" @click="debugMethods?.clearSession">
|
|
2952
|
+
Clear Session
|
|
2999
2953
|
</Btn>
|
|
3000
|
-
<
|
|
3001
|
-
|
|
3002
|
-
Insert Column Left
|
|
2954
|
+
<Btn thin color="gray" icon="download" @click="debugMethods?.downloadSession">
|
|
2955
|
+
Download Log
|
|
3003
2956
|
</Btn>
|
|
3004
|
-
<Btn
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
2957
|
+
<Btn
|
|
2958
|
+
thin color="gray" icon="content_copy"
|
|
2959
|
+
@click="copyText(debugMethods?.exportDebugWithPrompt() || '')"
|
|
2960
|
+
>
|
|
2961
|
+
Copy Log
|
|
3009
2962
|
</Btn>
|
|
3010
2963
|
</div>
|
|
3011
2964
|
</div>
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
</div>
|
|
3040
|
-
<TextInput label="Photo Credit" v-model="imageForm.credit" placeholder="Photographer name (optional)" />
|
|
3041
|
-
<CheckInput label="Show alt text as caption" v-model="imageForm.figcaption" help="Photo credit will always be shown if provided" />
|
|
3042
|
-
<template #footer>
|
|
3043
|
-
<div class="flex gap-05 w-100 ">
|
|
3044
|
-
<Btn @click="showImageModal = false" value="Cancel" flat thin />
|
|
3045
|
-
<Btn v-if="pendingImageData?.existingImage" @click="deleteImage" value="Delete Image" color="red" flat thin icon="delete" />
|
|
3046
|
-
<Btn class="ms-auto" @click="submitImage" :value="pendingImageData?.existingImage ? 'Save Changes' : 'Insert Image'" :disabled="!imageForm.src" />
|
|
3047
|
-
</div>
|
|
3048
|
-
</template>
|
|
3049
|
-
</Modal>
|
|
3050
|
-
|
|
3051
|
-
<!-- Embed Modal -->
|
|
3052
|
-
<Modal v-model:visible="showEmbedModal" :title="pendingEmbedData?.existingEmbed ? 'Edit Embed' : 'Insert Embed'" width="500">
|
|
3053
|
-
<TextInput label="Embed URL or Code" v-model="embedForm.src" type="url" placeholder="https://www.youtube.com/embed/... or paste iframe code" @keydown.enter="submitEmbed" />
|
|
3054
|
-
<div class="flex gap-1">
|
|
3055
|
-
<TextInput label="Width" v-model="embedForm.width" placeholder="560" />
|
|
3056
|
-
<TextInput label="Height" v-model="embedForm.height" placeholder="315" />
|
|
3057
|
-
</div>
|
|
3058
|
-
<TextInput label="Caption" v-model="embedForm.alt" placeholder="Enter caption (optional)" />
|
|
3059
|
-
<template #footer>
|
|
3060
|
-
<div class="flex gap-05 w-100">
|
|
3061
|
-
<Btn @click="showEmbedModal = false" value="Cancel" flat thin />
|
|
3062
|
-
<Btn v-if="pendingEmbedData?.existingEmbed" @click="deleteEmbed" value="Delete Embed" color="red" flat thin icon="delete" />
|
|
3063
|
-
<Btn @click="submitEmbed" :value="pendingEmbedData?.existingEmbed ? 'Save Changes' : 'Insert Embed'" :disabled="!embedForm.src" class="ms-auto" />
|
|
2965
|
+
|
|
2966
|
+
<!-- Inline Toolbar -->
|
|
2967
|
+
<div
|
|
2968
|
+
v-if="showInlineToolbar" class="inline-toolbar" :style="{
|
|
2969
|
+
position: 'fixed',
|
|
2970
|
+
top: `${inlineToolbarPosition.top}px`,
|
|
2971
|
+
left: `${inlineToolbarPosition.left}px`,
|
|
2972
|
+
zIndex: 99,
|
|
2973
|
+
}"
|
|
2974
|
+
>
|
|
2975
|
+
<div class="inline-toolbar-content">
|
|
2976
|
+
<Btn
|
|
2977
|
+
thin flat icon="format_bold" :class="{ active: editor.state.selectedStyles.has('bold') }"
|
|
2978
|
+
@click="runInlineAction('bold')"
|
|
2979
|
+
/>
|
|
2980
|
+
<Btn
|
|
2981
|
+
thin flat icon="format_italic" :class="{ active: editor.state.selectedStyles.has('italic') }"
|
|
2982
|
+
@click="runInlineAction('italic')"
|
|
2983
|
+
/>
|
|
2984
|
+
<Btn
|
|
2985
|
+
thin flat icon="format_underlined"
|
|
2986
|
+
:class="{ active: editor.state.selectedStyles.has('underline') }"
|
|
2987
|
+
@click="runInlineAction('underline')"
|
|
2988
|
+
/>
|
|
2989
|
+
<span class="separator">|</span>
|
|
2990
|
+
<Btn thin flat icon="add_link" @click="runInlineAction('link')" />
|
|
2991
|
+
</div>
|
|
3064
2992
|
</div>
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
<
|
|
3075
|
-
|
|
2993
|
+
<!-- Table Context Menu -->
|
|
2994
|
+
<div
|
|
2995
|
+
v-if="showTableContextMenu" class="table-context-menu" :style="{
|
|
2996
|
+
position: 'fixed',
|
|
2997
|
+
top: `${contextMenuPosition.y}px`,
|
|
2998
|
+
left: `${contextMenuPosition.x}px`,
|
|
2999
|
+
zIndex: 1001,
|
|
3000
|
+
}"
|
|
3001
|
+
>
|
|
3002
|
+
<div class="context-menu-content" @click.stop>
|
|
3003
|
+
<div class="menu-header">
|
|
3004
|
+
<span>Table Actions</span>
|
|
3005
|
+
<button class="close-btn" @click="showTableContextMenu = false">
|
|
3006
|
+
×
|
|
3007
|
+
</button>
|
|
3008
|
+
</div>
|
|
3009
|
+
<div class="px-025">
|
|
3010
|
+
<Btn
|
|
3011
|
+
v-if="canMergeRight" full-width align-txt="start" thin flat icon="start"
|
|
3012
|
+
class="context-menu-btn" @click="mergeCellRight(); showTableContextMenu = false"
|
|
3013
|
+
>
|
|
3014
|
+
Merge Right
|
|
3015
|
+
</Btn>
|
|
3016
|
+
<Btn
|
|
3017
|
+
v-if="canMergeDown" full-width align-txt="start" thin flat icon="text_select_move_down"
|
|
3018
|
+
class="context-menu-btn" @click="mergeCellDown(); showTableContextMenu = false"
|
|
3019
|
+
>
|
|
3020
|
+
Merge Down
|
|
3021
|
+
</Btn>
|
|
3022
|
+
<Btn
|
|
3023
|
+
v-if="canSplit" full-width align-txt="start" thin flat icon="call_split"
|
|
3024
|
+
class="context-menu-btn" @click="splitCell(); showTableContextMenu = false"
|
|
3025
|
+
>
|
|
3026
|
+
Split Cell
|
|
3027
|
+
</Btn>
|
|
3028
|
+
<div v-if="canMergeRight || canMergeDown || canSplit" class="context-menu-separator" />
|
|
3029
|
+
<Btn
|
|
3030
|
+
full-width align-txt="start" thin flat icon="add_row_above" class="context-menu-btn"
|
|
3031
|
+
@click="insertRowAbove(); showTableContextMenu = false"
|
|
3032
|
+
>
|
|
3033
|
+
Insert Row Above
|
|
3034
|
+
</Btn>
|
|
3035
|
+
<Btn
|
|
3036
|
+
full-width align-txt="start" thin flat icon="add_row_below" class="context-menu-btn"
|
|
3037
|
+
@click="insertRowBelow(); showTableContextMenu = false"
|
|
3038
|
+
>
|
|
3039
|
+
Insert Row Below
|
|
3040
|
+
</Btn>
|
|
3041
|
+
<Btn
|
|
3042
|
+
full-width align-txt="start" thin flat icon="remove" class="context-menu-btn"
|
|
3043
|
+
@click="deleteRow(); showTableContextMenu = false"
|
|
3044
|
+
>
|
|
3045
|
+
Delete Row
|
|
3046
|
+
</Btn>
|
|
3047
|
+
<div class="context-menu-separator" />
|
|
3048
|
+
<Btn
|
|
3049
|
+
full-width align-txt="start" thin flat icon="add_column_left" class="context-menu-btn"
|
|
3050
|
+
@click="insertColumnLeft(); showTableContextMenu = false"
|
|
3051
|
+
>
|
|
3052
|
+
Insert Column Left
|
|
3053
|
+
</Btn>
|
|
3054
|
+
<Btn
|
|
3055
|
+
full-width align-txt="start" thin flat icon="add_column_right" class="context-menu-btn"
|
|
3056
|
+
@click="insertColumnRight(); showTableContextMenu = false"
|
|
3057
|
+
>
|
|
3058
|
+
Insert Column Right
|
|
3059
|
+
</Btn>
|
|
3060
|
+
<Btn
|
|
3061
|
+
full-width align-txt="start" thin flat icon="remove" class="context-menu-btn"
|
|
3062
|
+
@click="deleteColumn(); showTableContextMenu = false"
|
|
3063
|
+
>
|
|
3064
|
+
Delete Column
|
|
3065
|
+
</Btn>
|
|
3066
|
+
</div>
|
|
3067
|
+
</div>
|
|
3076
3068
|
</div>
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
<
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
<div v-if="videoForm.aspectRatio === 'custom'" class="grid grid-wrap-2 gap-05">
|
|
3090
|
-
<TextInput label="Width ratio" v-model="videoForm.customWidth" placeholder="16" type="number" />
|
|
3091
|
-
<TextInput label="Height ratio" v-model="videoForm.customHeight" placeholder="9" type="number" />
|
|
3069
|
+
</div>
|
|
3070
|
+
<!-- Link Modal -->
|
|
3071
|
+
<Modal v-model:visible="showLinkModal" title="Add Link" width="400">
|
|
3072
|
+
<div class="flex gap-05 align-items-end">
|
|
3073
|
+
<TextInput
|
|
3074
|
+
v-model="linkForm.url" label="URL" type="url" placeholder="https://example.com"
|
|
3075
|
+
@keydown.enter="submitLink"
|
|
3076
|
+
/>
|
|
3077
|
+
<Btn
|
|
3078
|
+
icon="open_in_new" class="mb-05 radius-1" flat :disabled="!isValidUrl(linkForm.url)"
|
|
3079
|
+
@click="visitLink"
|
|
3080
|
+
/>
|
|
3092
3081
|
</div>
|
|
3082
|
+
<CheckInput v-model="linkForm.openInNewTab" label="Open in new tab" type="checkbox" class="mb-2 mt-05" />
|
|
3083
|
+
<template #footer>
|
|
3084
|
+
<Btn value="Cancel" flat thin @click="showLinkModal = false" />
|
|
3085
|
+
<Btn value="Add Link" :disabled="!isValidUrl(linkForm.url)" @click="submitLink" />
|
|
3086
|
+
</template>
|
|
3087
|
+
</Modal>
|
|
3088
|
+
|
|
3089
|
+
<!-- Tooltip -->
|
|
3090
|
+
<div v-if="showTooltip" class="editor-tooltip" :style="{ left: `${tooltipData.x}px`, top: `${tooltipData.y}px` }">
|
|
3091
|
+
{{ tooltipData.message }}
|
|
3092
|
+
</div>
|
|
3093
3093
|
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3094
|
+
<!-- Image Modal -->
|
|
3095
|
+
<Modal
|
|
3096
|
+
v-model:visible="showImageModal" :title="pendingImageData?.existingImage ? 'Edit Image' : 'Insert Image'"
|
|
3097
|
+
width="500"
|
|
3098
|
+
>
|
|
3099
|
+
<TextInput
|
|
3100
|
+
v-model="imageForm.src" label="Image URL" type="url" placeholder="https://example.com/image.jpg"
|
|
3101
|
+
@keydown.enter="submitImage"
|
|
3102
|
+
/>
|
|
3103
|
+
<TextInput v-model="imageForm.alt" label="Alt Text" placeholder="Describe the image" />
|
|
3104
|
+
<div class="flex gap-1">
|
|
3105
|
+
<TextInput
|
|
3106
|
+
v-model="imageForm.width" label="Width" placeholder="100% or 500px or auto"
|
|
3107
|
+
help="Examples: 100%, 500px, auto"
|
|
3108
|
+
/>
|
|
3109
|
+
<TextInput
|
|
3110
|
+
v-model="imageForm.height" label="Height" placeholder="auto or 300px"
|
|
3111
|
+
help="Examples: auto, 300px"
|
|
3112
|
+
/>
|
|
3101
3113
|
</div>
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3114
|
+
<TextInput v-model="imageForm.credit" label="Photo Credit" placeholder="Photographer name (optional)" />
|
|
3115
|
+
<CheckInput
|
|
3116
|
+
v-model="imageForm.figcaption" label="Show alt text as caption"
|
|
3117
|
+
help="Photo credit will always be shown if provided"
|
|
3118
|
+
/>
|
|
3119
|
+
<template #footer>
|
|
3120
|
+
<div class="flex gap-05 w-100 ">
|
|
3121
|
+
<Btn value="Cancel" flat thin @click="showImageModal = false" />
|
|
3122
|
+
<Btn
|
|
3123
|
+
v-if="pendingImageData?.existingImage" value="Delete Image" color="red" flat thin icon="delete"
|
|
3124
|
+
@click="deleteImage"
|
|
3125
|
+
/>
|
|
3126
|
+
<Btn
|
|
3127
|
+
class="ms-auto" :value="pendingImageData?.existingImage ? 'Save Changes' : 'Insert Image'"
|
|
3128
|
+
:disabled="!imageForm.src" @click="submitImage"
|
|
3129
|
+
/>
|
|
3110
3130
|
</div>
|
|
3111
|
-
</
|
|
3112
|
-
</
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3131
|
+
</template>
|
|
3132
|
+
</Modal>
|
|
3133
|
+
|
|
3134
|
+
<!-- Embed Modal -->
|
|
3135
|
+
<Modal
|
|
3136
|
+
v-model:visible="showEmbedModal" :title="pendingEmbedData?.existingEmbed ? 'Edit Embed' : 'Insert Embed'"
|
|
3137
|
+
width="500"
|
|
3138
|
+
>
|
|
3139
|
+
<TextInput
|
|
3140
|
+
v-model="embedForm.src" label="Embed URL or Code" type="url"
|
|
3141
|
+
placeholder="https://www.youtube.com/embed/... or paste iframe code" @keydown.enter="submitEmbed"
|
|
3142
|
+
/>
|
|
3143
|
+
<div class="flex gap-1">
|
|
3144
|
+
<TextInput v-model="embedForm.width" label="Width" placeholder="560" />
|
|
3145
|
+
<TextInput v-model="embedForm.height" label="Height" placeholder="315" />
|
|
3118
3146
|
</div>
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
<
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3147
|
+
<TextInput v-model="embedForm.alt" label="Caption" placeholder="Enter caption (optional)" />
|
|
3148
|
+
<template #footer>
|
|
3149
|
+
<div class="flex gap-05 w-100">
|
|
3150
|
+
<Btn value="Cancel" flat thin @click="showEmbedModal = false" />
|
|
3151
|
+
<Btn
|
|
3152
|
+
v-if="pendingEmbedData?.existingEmbed" value="Delete Embed" color="red" flat thin icon="delete"
|
|
3153
|
+
@click="deleteEmbed"
|
|
3154
|
+
/>
|
|
3155
|
+
<Btn
|
|
3156
|
+
:value="pendingEmbedData?.existingEmbed ? 'Save Changes' : 'Insert Embed'"
|
|
3157
|
+
:disabled="!embedForm.src" class="ms-auto" @click="submitEmbed"
|
|
3158
|
+
/>
|
|
3131
3159
|
</div>
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3160
|
+
</template>
|
|
3161
|
+
</Modal>
|
|
3162
|
+
<!-- Video Modal -->
|
|
3163
|
+
<Modal v-model:visible="showVideoModal" title="Insert Video" width="500">
|
|
3164
|
+
<div class="grid gap-0">
|
|
3165
|
+
<TextInput
|
|
3166
|
+
v-model="videoForm.src" label="Video URL or Embed Code" type="url"
|
|
3167
|
+
placeholder="Paste YouTube URL, video file URL, or iframe embed code..."
|
|
3168
|
+
:class="{ error: videoForm.src && !isValidVideoUrl(videoForm.src) }" @keydown.enter="submitVideo"
|
|
3169
|
+
/>
|
|
3170
|
+
|
|
3171
|
+
<div v-if="videoForm.src && !isValidVideoUrl(videoForm.src)" class="flex gap-025 opacity-5 -mt-05">
|
|
3172
|
+
<Icon name="warning" />
|
|
3173
|
+
<p class="txt12">
|
|
3174
|
+
Please enter a valid video URL or iframe embed code
|
|
3175
|
+
</p>
|
|
3142
3176
|
</div>
|
|
3143
3177
|
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3178
|
+
<TextInput
|
|
3179
|
+
v-model="videoForm.width" label="Width" placeholder="100% or 500px or 50vw"
|
|
3180
|
+
help="Examples: 100%, 500px, 50vw, 300"
|
|
3181
|
+
/>
|
|
3182
|
+
|
|
3183
|
+
<SelectInput
|
|
3184
|
+
v-model="videoForm.aspectRatio" label="Aspect Ratio" :options="[
|
|
3185
|
+
{ value: '16:9', label: '16:9 (Standard)' },
|
|
3186
|
+
{ value: '4:3', label: '4:3 (Classic)' },
|
|
3187
|
+
{ value: '9:16', label: '9:16 (Vertical/Shorts)' },
|
|
3188
|
+
{ value: '21:9', label: '21:9 (Cinematic)' },
|
|
3189
|
+
{ value: '1:1', label: '1:1 (Square)' },
|
|
3190
|
+
{ value: 'custom', label: 'Custom' },
|
|
3191
|
+
]"
|
|
3192
|
+
/>
|
|
3193
|
+
|
|
3194
|
+
<div v-if="videoForm.aspectRatio === 'custom'" class="grid grid-wrap-2 gap-05">
|
|
3195
|
+
<TextInput v-model="videoForm.customWidth" label="Width ratio" placeholder="16" type="number" />
|
|
3196
|
+
<TextInput v-model="videoForm.customHeight" label="Height ratio" placeholder="9" type="number" />
|
|
3157
3197
|
</div>
|
|
3158
|
-
<NumberInput v-model="tableForm.width" label="Width (%)" :min="25" :max="100" />
|
|
3159
|
-
<NumberInput v-model="tableForm.cellPadding" label="Cell Padding (px)" :min="2" :max="20" />
|
|
3160
|
-
<NumberInput v-model="tableForm.borderWidth" label="Border Width (px)" :min="0" :max="5" />
|
|
3161
|
-
<ColorInput v-model="tableForm.borderColor" label="Border Color" />
|
|
3162
|
-
<CheckInput v-model="tableForm.fixedLayout" label="Fixed cell width (doesn't change by content)" class="grid-span-4 m_grid-span-2" />
|
|
3163
3198
|
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
<
|
|
3167
|
-
<
|
|
3168
|
-
<
|
|
3199
|
+
<div class="grid grid-wrap-2 gap-05 py-1">
|
|
3200
|
+
<CheckInput v-model="videoForm.controls" label="Show controls" />
|
|
3201
|
+
<CheckInput v-model="videoForm.autoplay" label="Autoplay" />
|
|
3202
|
+
<CheckInput v-model="videoForm.mute" label="Mute" />
|
|
3203
|
+
<CheckInput v-model="videoForm.loop" label="Loop" />
|
|
3204
|
+
<CheckInput v-model="videoForm.showCaption" class="grid-span-2" label="Show caption below video" />
|
|
3205
|
+
<TextInput
|
|
3206
|
+
v-if="videoForm.showCaption" v-model="videoForm.caption" label="Caption" class="grid-span-2"
|
|
3207
|
+
placeholder="Describe the video content"
|
|
3208
|
+
/>
|
|
3169
3209
|
</div>
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
<div class="
|
|
3210
|
+
|
|
3211
|
+
<!-- Video Preview -->
|
|
3212
|
+
<Card v-if="videoForm.src && isValidVideoUrl(videoForm.src)" frame thin class="bg-gray-20">
|
|
3213
|
+
<p class="label">
|
|
3214
|
+
Preview:
|
|
3215
|
+
</p>
|
|
3216
|
+
<div class="overflow-hidden flex justify-content-center">
|
|
3217
|
+
<BglVideo
|
|
3218
|
+
:src="videoForm.src" class=""
|
|
3219
|
+
:aspect-ratio="videoForm.aspectRatio === 'custom' && videoForm.customWidth && videoForm.customHeight ? `${videoForm.customWidth}:${videoForm.customHeight}` : videoForm.aspectRatio"
|
|
3220
|
+
:autoplay="false" :mute="videoForm.mute" :controls="videoForm.controls"
|
|
3221
|
+
:loop="videoForm.loop"
|
|
3222
|
+
/>
|
|
3223
|
+
</div>
|
|
3224
|
+
</Card>
|
|
3225
|
+
</div>
|
|
3226
|
+
<template #footer>
|
|
3227
|
+
<div class="flex gap-05 w-100 ">
|
|
3228
|
+
<Btn value="Cancel" flat thin @click="showVideoModal = false" />
|
|
3229
|
+
<Btn
|
|
3230
|
+
v-if="pendingVideoData?.existingVideo" value="Delete Video" color="red" flat thin icon="delete"
|
|
3231
|
+
@click="deleteVideo"
|
|
3232
|
+
/>
|
|
3233
|
+
<Btn
|
|
3234
|
+
value="Insert Video" class="ms-auto" :disabled="!videoForm.src || !isValidVideoUrl(videoForm.src)"
|
|
3235
|
+
@click="submitVideo"
|
|
3236
|
+
/>
|
|
3177
3237
|
</div>
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3238
|
+
</template>
|
|
3239
|
+
</Modal>
|
|
3240
|
+
|
|
3241
|
+
<!-- Table Editor Modal -->
|
|
3242
|
+
<Modal
|
|
3243
|
+
v-model:visible="showTableEditor" :title="pendingTableData?.existingTable ? 'Edit Table' : 'Insert Table'"
|
|
3244
|
+
width="700"
|
|
3245
|
+
>
|
|
3246
|
+
<template #default>
|
|
3247
|
+
<div class="grid grid-wrap-4 m_grid-wrap-2 gap-col-1 table-editor testMe1">
|
|
3248
|
+
<!-- Structure Section -->
|
|
3249
|
+
<div class="flex gap-05 white-space mt-0 grid-span-4 m_grid-span-2 pt-1 pb-05">
|
|
3250
|
+
<div class="line" />
|
|
3251
|
+
<p class="label grid-span-4 m_grid-span-2">
|
|
3252
|
+
Table Structure
|
|
3253
|
+
</p>
|
|
3254
|
+
<div class="line" />
|
|
3255
|
+
</div>
|
|
3256
|
+
<NumberInput v-model="tableForm.rows" :min="1" :max="20" label="Rows" />
|
|
3257
|
+
<NumberInput v-model="tableForm.cols" :min="1" :max="10" label="Columns" />
|
|
3258
|
+
<!-- Cell Text Alignment -->
|
|
3259
|
+
<div class="grid-span-1">
|
|
3260
|
+
<label class="label">Cell Text Alignment</label>
|
|
3261
|
+
<div
|
|
3262
|
+
class="flex gap-025 mt-025 radius-1 p-05 w-fit"
|
|
3263
|
+
style="height: var(--input-height); background: var(--input-bg);"
|
|
3264
|
+
>
|
|
3265
|
+
<Btn
|
|
3266
|
+
:class="{ activeBtn: tableForm.alignment === 'left' }" flat thin icon="format_align_left"
|
|
3267
|
+
title="Align Left" @click="tableForm.alignment = 'left'"
|
|
3268
|
+
/>
|
|
3269
|
+
<Btn
|
|
3270
|
+
:class="{ activeBtn: tableForm.alignment === 'center' }" flat thin
|
|
3271
|
+
icon="format_align_center" title="Align Center" @click="tableForm.alignment = 'center'"
|
|
3272
|
+
/>
|
|
3273
|
+
<Btn
|
|
3274
|
+
:class="{ activeBtn: tableForm.alignment === 'right' }" flat thin icon="format_align_right"
|
|
3275
|
+
title="Align Right" @click="tableForm.alignment = 'right'"
|
|
3276
|
+
/>
|
|
3277
|
+
</div>
|
|
3278
|
+
</div>
|
|
3279
|
+
|
|
3280
|
+
<!-- Text Direction -->
|
|
3281
|
+
<div class="grid-span-1">
|
|
3282
|
+
<label class="label">Text Direction</label>
|
|
3283
|
+
<div
|
|
3284
|
+
class="flex gap-025 mt-025 radius-1 p-025 w-fit"
|
|
3285
|
+
style="height: var(--input-height); background: var(--input-bg);"
|
|
3286
|
+
>
|
|
3287
|
+
<Btn
|
|
3288
|
+
:class="{ activeBtn: tableForm.direction === 'ltr' }" flat thin value="LTR"
|
|
3289
|
+
title="Left to Right" @click="tableForm.direction = 'ltr'"
|
|
3290
|
+
/>
|
|
3291
|
+
<Btn
|
|
3292
|
+
:class="{ activeBtn: tableForm.direction === 'rtl' }" flat thin value="RTL"
|
|
3293
|
+
title="Right to Left" @click="tableForm.direction = 'rtl'"
|
|
3294
|
+
/>
|
|
3295
|
+
</div>
|
|
3296
|
+
</div>
|
|
3297
|
+
<!-- Style Section -->
|
|
3298
|
+
<div class="flex gap-05 white-space mt-0 grid-span-4 m_grid-span-2 pt-1 pb-05">
|
|
3299
|
+
<div class="line" />
|
|
3300
|
+
<p class="label grid-span-4 m_grid-span-2">
|
|
3301
|
+
Table Style
|
|
3302
|
+
</p>
|
|
3303
|
+
<div class="line" />
|
|
3304
|
+
</div>
|
|
3305
|
+
<NumberInput v-model="tableForm.width" label="Width (%)" :min="25" :max="100" />
|
|
3306
|
+
<NumberInput v-model="tableForm.cellPadding" label="Cell Padding (px)" :min="2" :max="20" />
|
|
3307
|
+
<NumberInput v-model="tableForm.borderWidth" label="Border Width (px)" :min="0" :max="5" />
|
|
3308
|
+
<ColorInput v-model="tableForm.borderColor" label="Border Color" />
|
|
3309
|
+
<CheckInput
|
|
3310
|
+
v-model="tableForm.fixedLayout" label="Fixed cell width (doesn't change by content)"
|
|
3311
|
+
class="grid-span-4 m_grid-span-2"
|
|
3312
|
+
/>
|
|
3313
|
+
|
|
3314
|
+
<!-- Cell Colors -->
|
|
3315
|
+
<div class="flex gap-05 white-space mt-0 grid-span-4 m_grid-span-2 pt-1 pb-05">
|
|
3316
|
+
<div class="line" />
|
|
3317
|
+
<p class="label grid-span-4 m_grid-span-2">
|
|
3318
|
+
Cell Colors:
|
|
3319
|
+
</p>
|
|
3320
|
+
<div class="line" />
|
|
3321
|
+
</div>
|
|
3322
|
+
<ColorInput
|
|
3323
|
+
v-model="tableForm.cellBgColor" class="grid-span-2 m_grid-span-1"
|
|
3324
|
+
label="Background Color"
|
|
3325
|
+
/>
|
|
3326
|
+
<ColorInput v-model="tableForm.cellTextColor" class="grid-span-2 m_grid-span-1" label="Text Color" />
|
|
3327
|
+
<!-- Alternating Rows -->
|
|
3328
|
+
<div class="flex gap-05 white-space mt-0 grid-span-4 m_grid-span-2 pt-1 pb-05">
|
|
3329
|
+
<div class="line" />
|
|
3330
|
+
<p class="label grid-span-4 m_grid-span-2">
|
|
3331
|
+
Row Styles:
|
|
3332
|
+
</p>
|
|
3333
|
+
<div class="line" />
|
|
3334
|
+
</div>
|
|
3335
|
+
<CheckInput v-model="tableForm.showHeaders" label="Show header row" class="grid-span-4 m_grid-span-2" />
|
|
3336
|
+
<!-- Header Colors -->
|
|
3337
|
+
<div
|
|
3338
|
+
v-if="tableForm.showHeaders"
|
|
3339
|
+
class="grid-span-4 m_grid-span-2 grid-wrap-4 m_grid-wrap-2 grid gap-col-1 border-bottom pb-05 mb-05"
|
|
3340
|
+
>
|
|
3341
|
+
<ColorInput
|
|
3342
|
+
v-model="tableForm.headerBgColor" class="grid-span-2 m_grid-span-1"
|
|
3343
|
+
label="Header Background Color"
|
|
3344
|
+
/>
|
|
3345
|
+
<ColorInput
|
|
3346
|
+
v-model="tableForm.headerTextColor" class="grid-span-2 m_grid-span-1"
|
|
3347
|
+
label="Header Text Color"
|
|
3348
|
+
/>
|
|
3349
|
+
</div>
|
|
3350
|
+
<CheckInput
|
|
3351
|
+
v-model="tableForm.alternateRows" label="Alternating Row Colors"
|
|
3352
|
+
class="grid-span-4 m_grid-span-2"
|
|
3353
|
+
/>
|
|
3354
|
+
<div
|
|
3355
|
+
v-if="tableForm.alternateRows"
|
|
3356
|
+
class="grid grid-wrap-4 m_grid-wrap-2 gap-col-1 grid-span-4 m_grid-span-2 border-bottom pb-05 mb-05"
|
|
3357
|
+
>
|
|
3358
|
+
<ColorInput
|
|
3359
|
+
v-model="tableForm.alternateRowBgColor" class="grid-span-2 m_grid-span-1"
|
|
3360
|
+
label="Alternate Row Background:"
|
|
3361
|
+
/>
|
|
3362
|
+
<ColorInput
|
|
3363
|
+
v-model="tableForm.alternateRowTextColor" class="grid-span-2 m_grid-span-1"
|
|
3364
|
+
label="Alternate Row Text:"
|
|
3365
|
+
/>
|
|
3366
|
+
</div>
|
|
3183
3367
|
</div>
|
|
3184
|
-
|
|
3185
|
-
<div
|
|
3186
|
-
<
|
|
3187
|
-
<
|
|
3368
|
+
<!-- Table Preview -->
|
|
3369
|
+
<div class="flex gap-05 white-space mt-0 grid-span-4 m_grid-span-2 pt-1 pb-05">
|
|
3370
|
+
<div class="line" />
|
|
3371
|
+
<p class="label grid-span-4 m_grid-span-2">
|
|
3372
|
+
Preview
|
|
3373
|
+
</p>
|
|
3374
|
+
<div class="line" />
|
|
3188
3375
|
</div>
|
|
3189
|
-
|
|
3190
|
-
</
|
|
3191
|
-
|
|
3192
|
-
<
|
|
3193
|
-
<
|
|
3194
|
-
<
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
</template>
|
|
3205
|
-
</Modal>
|
|
3376
|
+
<div style="zoom: 0.8;" class="opacity-7 user-select-none pointer-events-none" v-html="tablePreviewHtml" />
|
|
3377
|
+
</template>
|
|
3378
|
+
|
|
3379
|
+
<template #footer>
|
|
3380
|
+
<Btn value="Cancel" flat thin @click="showTableEditor = false" />
|
|
3381
|
+
<Btn
|
|
3382
|
+
v-if="pendingTableData?.existingTable" value="Delete Table" color="red" flat thin icon="delete"
|
|
3383
|
+
@click="deleteTable"
|
|
3384
|
+
/>
|
|
3385
|
+
<Btn
|
|
3386
|
+
:value="pendingTableData?.existingTable ? 'Save' : 'Insert Table'" class="ms-auto"
|
|
3387
|
+
@click="submitTable"
|
|
3388
|
+
/>
|
|
3389
|
+
</template>
|
|
3390
|
+
</Modal>
|
|
3206
3391
|
</template>
|
|
3207
3392
|
|
|
3208
3393
|
<style>
|
|
@@ -3239,7 +3424,6 @@ defineExpose({
|
|
|
3239
3424
|
position: relative;
|
|
3240
3425
|
}
|
|
3241
3426
|
|
|
3242
|
-
|
|
3243
3427
|
.content-area table:hover {
|
|
3244
3428
|
outline: 2px solid rgba(0, 123, 204, 0.3);
|
|
3245
3429
|
outline-offset: 2px;
|