@bagelink/vue 1.9.83 โ 1.9.86
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/Avatar.vue.d.ts +1 -0
- package/dist/components/Avatar.vue.d.ts.map +1 -1
- package/dist/components/Badge.vue.d.ts +0 -1
- package/dist/components/Badge.vue.d.ts.map +1 -1
- package/dist/components/Btn.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/Loading.vue.d.ts +2 -1
- package/dist/components/Loading.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts +9 -9
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/index.cjs +163 -85
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +25737 -25545
- package/dist/plugins/useToast.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/utils/filterRef.d.ts +15 -0
- package/dist/utils/filterRef.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Avatar.vue +6 -2
- package/src/components/Badge.vue +14 -1084
- package/src/components/Btn.vue +37 -37
- package/src/components/Dropdown.vue +1 -1
- package/src/components/Loading.vue +15 -6
- package/src/components/form/inputs/RichText/index.vue +325 -221
- package/src/form-flow/FormFlow.vue +9 -0
- package/src/form-flow/form-flow.ts +13 -3
- package/src/index.ts +1 -1
- package/src/plugins/useToast.ts +14 -0
- package/src/styles/bagel.css +1 -0
- package/src/styles/base-colors.css +1429 -46
- package/src/styles/text.css +1755 -1755
- package/src/styles/toast-overrides.css +10 -0
- package/src/utils/filterRef.ts +133 -0
- package/src/styles/btnColors.css +0 -847
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ToolbarConfig } from './richTextTypes'
|
|
3
|
-
import { CodeEditor, copyText, Btn, Modal, BglVideo, Icon, Card, ColorInput } from '@bagelink/vue'
|
|
3
|
+
import { CodeEditor, copyText, Btn, Modal, BglVideo, Icon, Card, ColorInput, pathKeyToURL } from '@bagelink/vue'
|
|
4
4
|
import { watch, onUnmounted, onBeforeUnmount, onMounted, ref, computed, useAttrs } from 'vue'
|
|
5
5
|
import CheckInput from '../CheckInput.vue'
|
|
6
6
|
import NumberInput from '../NumberInput.vue'
|
|
7
7
|
import SelectInput from '../SelectInput.vue'
|
|
8
8
|
import TextInput from '../TextInput.vue'
|
|
9
|
+
import UploadInput from '../Upload/UploadInput.vue'
|
|
9
10
|
import EditorToolbar from './components/EditorToolbar.vue'
|
|
10
11
|
import { useCommands } from './composables/useCommands'
|
|
11
12
|
import { useEditor } from './composables/useEditor'
|
|
@@ -64,7 +65,6 @@ const currentInputColor = computed(() => {
|
|
|
64
65
|
if (typeof document !== 'undefined') {
|
|
65
66
|
const computedStyle = getComputedStyle(document.documentElement)
|
|
66
67
|
const color = computedStyle.getPropertyValue('--input-color').trim()
|
|
67
|
-
console.log('๐จ currentInputColor computed:', color, 'forceUpdate:', forceUpdate.value)
|
|
68
68
|
return color || '#000'
|
|
69
69
|
}
|
|
70
70
|
return '#000'
|
|
@@ -73,7 +73,6 @@ const currentInputColor = computed(() => {
|
|
|
73
73
|
const currentTextColor = computed(() => {
|
|
74
74
|
const inputColor = currentInputColor.value
|
|
75
75
|
const textColor = props.textColor || (inputColor && inputColor.length > 0 && inputColor !== '#000' ? inputColor : 'inherit')
|
|
76
|
-
console.log('๐จ currentTextColor computed:', textColor, 'from inputColor:', inputColor)
|
|
77
76
|
return textColor
|
|
78
77
|
})
|
|
79
78
|
|
|
@@ -96,7 +95,6 @@ onMounted(() => {
|
|
|
96
95
|
})
|
|
97
96
|
|
|
98
97
|
if (shouldUpdate) {
|
|
99
|
-
console.log('๐ Theme change detected, forcing update')
|
|
100
98
|
forceUpdate.value++
|
|
101
99
|
|
|
102
100
|
// Also directly update iframe if initialized
|
|
@@ -128,26 +126,10 @@ onBeforeUnmount(() => {
|
|
|
128
126
|
themeObserver?.disconnect()
|
|
129
127
|
})
|
|
130
128
|
|
|
131
|
-
//
|
|
132
|
-
if (typeof window !== 'undefined') {
|
|
133
|
-
(window as any).
|
|
134
|
-
|
|
135
|
-
currentInputColor: currentInputColor.value,
|
|
136
|
-
currentTextColor: currentTextColor.value,
|
|
137
|
-
hasInitialized: hasInitialized.value,
|
|
138
|
-
hasIframe: !!iframe.value,
|
|
139
|
-
cssVar: getComputedStyle(document.documentElement).getPropertyValue('--input-color')
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
if (hasInitialized.value) {
|
|
143
|
-
updateIframeColors()
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
(window as any).forceRichTextUpdate = () => {
|
|
148
|
-
console.log('๐ Forcing RichText update')
|
|
149
|
-
forceUpdate.value++
|
|
150
|
-
}
|
|
129
|
+
// Debug helpers โ only registered when debug prop is set
|
|
130
|
+
if (props.debug && typeof window !== 'undefined') {
|
|
131
|
+
; (window as any).forceRichTextUpdate = () => { forceUpdate.value++ }
|
|
132
|
+
; (window as any).debugRichText = debugShowContent
|
|
151
133
|
}
|
|
152
134
|
|
|
153
135
|
// Computed properties for UI control
|
|
@@ -353,59 +335,34 @@ const tablePreviewHtml = computed(() => {
|
|
|
353
335
|
// Initialize content from modelValue
|
|
354
336
|
editor.state.content = props.modelValue
|
|
355
337
|
|
|
356
|
-
//
|
|
338
|
+
// Matches Hebrew, Arabic, Syriac, Thaana, Arabic Supplement, Arabic Presentation Forms
|
|
339
|
+
const RTL_REGEX = /[\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\uFB1D-\uFDFD\uFE70-\uFEFC]/
|
|
340
|
+
|
|
357
341
|
function detectAndSetDirection() {
|
|
358
342
|
const { doc } = editor.state
|
|
359
|
-
if (!doc?.body) {
|
|
360
|
-
console.log('No doc.body found')
|
|
361
|
-
return
|
|
362
|
-
}
|
|
343
|
+
if (!doc?.body) { return }
|
|
363
344
|
|
|
364
|
-
// Get all text content from body
|
|
365
345
|
const allText = doc.body.textContent || ''
|
|
366
|
-
const firstChars = allText.trim().substring(0,
|
|
367
|
-
|
|
368
|
-
console.log('Checking text:', firstChars, 'Length:', firstChars.length)
|
|
369
|
-
|
|
370
|
-
// Hebrew regex
|
|
371
|
-
const hebrewRegex = /[\u0590-\u05FF]/
|
|
372
|
-
const hasHebrew = hebrewRegex.test(firstChars)
|
|
373
|
-
|
|
374
|
-
console.log('Has Hebrew:', hasHebrew)
|
|
375
|
-
|
|
376
|
-
// Only change direction if it's different from current
|
|
346
|
+
const firstChars = allText.trim().substring(0, 20)
|
|
377
347
|
const currentDir = doc.body.dir || 'ltr'
|
|
378
|
-
const
|
|
379
|
-
const newDirection = shouldBeRtl ? 'rtl' : 'ltr'
|
|
380
|
-
|
|
381
|
-
console.log('Current dir:', currentDir, 'Should be RTL:', shouldBeRtl)
|
|
348
|
+
const newDirection = RTL_REGEX.test(firstChars) ? 'rtl' : 'ltr'
|
|
382
349
|
|
|
383
350
|
if (newDirection !== currentDir) {
|
|
384
351
|
doc.body.dir = newDirection
|
|
385
352
|
doc.body.style.direction = newDirection
|
|
386
|
-
|
|
387
|
-
// Update all paragraphs to match the new direction
|
|
388
|
-
const paragraphs = doc.querySelectorAll('p')
|
|
389
|
-
paragraphs.forEach((p) => {
|
|
353
|
+
doc.querySelectorAll('p').forEach((p) => {
|
|
390
354
|
if (!p.classList.contains('placeholder')) {
|
|
391
355
|
p.setAttribute('dir', newDirection)
|
|
392
356
|
}
|
|
393
357
|
})
|
|
394
|
-
|
|
395
|
-
console.log(`โ
Switched to ${newDirection.toUpperCase()}`)
|
|
396
358
|
}
|
|
397
359
|
}
|
|
398
360
|
|
|
399
|
-
// Function to get current direction based on content
|
|
400
361
|
function getCurrentDirection() {
|
|
401
362
|
const { doc } = editor.state
|
|
402
363
|
if (!doc?.body) { return 'ltr' }
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const firstChars = allText.trim().substring(0, 10)
|
|
406
|
-
const hebrewRegex = /[\u0590-\u05FF]/
|
|
407
|
-
|
|
408
|
-
return hebrewRegex.test(firstChars) ? 'rtl' : 'ltr'
|
|
364
|
+
const firstChars = (doc.body.textContent || '').trim().substring(0, 20)
|
|
365
|
+
return RTL_REGEX.test(firstChars) ? 'rtl' : 'ltr'
|
|
409
366
|
}
|
|
410
367
|
|
|
411
368
|
// Helper function to update content with history tracking
|
|
@@ -437,33 +394,15 @@ function updateContentWithHistory(doc: Document, skipHistory = false) {
|
|
|
437
394
|
function debugShowContent() {
|
|
438
395
|
const { doc } = editor.state
|
|
439
396
|
if (doc) {
|
|
440
|
-
console.log('=== Current Editor Content ===')
|
|
441
|
-
console.log('HTML:', doc.body.innerHTML)
|
|
442
|
-
console.log('=== All iframes ===')
|
|
443
397
|
const iframes = doc.querySelectorAll('iframe')
|
|
444
398
|
iframes.forEach((iframe, index) => {
|
|
445
|
-
console.log(`Iframe ${index}:`, {
|
|
446
|
-
src: iframe.src,
|
|
447
|
-
width: iframe.width,
|
|
448
|
-
height: iframe.height,
|
|
449
|
-
className: iframe.className,
|
|
450
|
-
style: iframe.style.cssText
|
|
451
|
-
})
|
|
452
399
|
})
|
|
453
|
-
console.log('=== All figures ===')
|
|
454
400
|
const figures = doc.querySelectorAll('figure')
|
|
455
401
|
figures.forEach((figure, index) => {
|
|
456
|
-
console.log(`Figure ${index}:`, {
|
|
457
|
-
className: figure.className,
|
|
458
|
-
innerHTML: figure.innerHTML
|
|
459
|
-
})
|
|
460
402
|
})
|
|
461
403
|
}
|
|
462
404
|
}
|
|
463
405
|
|
|
464
|
-
// Make debug function available globally
|
|
465
|
-
; (window as any).debugRichText = debugShowContent
|
|
466
|
-
|
|
467
406
|
// Function to show inline toolbar
|
|
468
407
|
function showInlineToolbarForSelection() {
|
|
469
408
|
// Check if inline toolbar should be shown
|
|
@@ -723,9 +662,10 @@ function openImageModal(existingImage: HTMLElement | null = null) {
|
|
|
723
662
|
}
|
|
724
663
|
|
|
725
664
|
if (existingImage) {
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
|
|
665
|
+
// Handle both <figure><img></figure> (editor-created) and bare <img> (loaded content)
|
|
666
|
+
const img = existingImage.tagName === 'IMG'
|
|
667
|
+
? existingImage as HTMLImageElement
|
|
668
|
+
: existingImage.querySelector('img')
|
|
729
669
|
const figcaption = existingImage.querySelector('figcaption')
|
|
730
670
|
const credit = existingImage.getAttribute('data-credit') || img?.getAttribute('data-credit') || ''
|
|
731
671
|
|
|
@@ -894,9 +834,13 @@ function openEmbedModal(existingEmbed: HTMLElement | null = null) {
|
|
|
894
834
|
}
|
|
895
835
|
|
|
896
836
|
if (existingEmbed) {
|
|
897
|
-
//
|
|
898
|
-
const iframe = existingEmbed.
|
|
899
|
-
|
|
837
|
+
// Handle both figure wrappers and bare <iframe> elements
|
|
838
|
+
const iframe = existingEmbed.tagName === 'IFRAME'
|
|
839
|
+
? existingEmbed as HTMLIFrameElement
|
|
840
|
+
: existingEmbed.querySelector('iframe')
|
|
841
|
+
const caption = existingEmbed.tagName !== 'IFRAME'
|
|
842
|
+
? existingEmbed.querySelector('figcaption')
|
|
843
|
+
: null
|
|
900
844
|
embedForm.value = {
|
|
901
845
|
src: iframe?.src || '',
|
|
902
846
|
width: iframe?.width || iframe?.style.width?.replace('px', '') || '560',
|
|
@@ -951,14 +895,6 @@ function submitEmbed() {
|
|
|
951
895
|
iframe.setAttribute('data-media-type', 'embed')
|
|
952
896
|
iframe.className = 'embed-iframe'
|
|
953
897
|
|
|
954
|
-
// Debug: Log the iframe details
|
|
955
|
-
console.log('Creating embed iframe:', {
|
|
956
|
-
originalSrc: embedForm.value.src,
|
|
957
|
-
cleanUrl,
|
|
958
|
-
width: embedForm.value.width,
|
|
959
|
-
height: embedForm.value.height
|
|
960
|
-
})
|
|
961
|
-
|
|
962
898
|
// Set width and height - always set them
|
|
963
899
|
iframe.width = embedForm.value.width || '560'
|
|
964
900
|
iframe.height = embedForm.value.height || '315'
|
|
@@ -1003,10 +939,6 @@ function submitEmbed() {
|
|
|
1003
939
|
selection.addRange(range)
|
|
1004
940
|
}
|
|
1005
941
|
|
|
1006
|
-
// Debug: Log the final HTML
|
|
1007
|
-
console.log('Embed figure created:', figure.outerHTML)
|
|
1008
|
-
console.log('Editor content updated:', doc.body.innerHTML.includes('embed-figure'))
|
|
1009
|
-
|
|
1010
942
|
updateContentWithHistory(doc)
|
|
1011
943
|
showEmbedModal.value = false
|
|
1012
944
|
pendingEmbedData = null
|
|
@@ -1033,21 +965,27 @@ function openVideoModal(existingVideo: HTMLElement | null = null) {
|
|
|
1033
965
|
}
|
|
1034
966
|
|
|
1035
967
|
if (existingVideo) {
|
|
1036
|
-
//
|
|
968
|
+
// Handle both editor-internal figures (with .video-container) and bare <video>/<iframe> elements
|
|
1037
969
|
const container = existingVideo.querySelector('.video-container')
|
|
970
|
+
const bareVideo = existingVideo.tagName === 'VIDEO' ? existingVideo as HTMLVideoElement : null
|
|
971
|
+
const bareIframe = existingVideo.tagName === 'IFRAME' ? existingVideo as HTMLIFrameElement : null
|
|
1038
972
|
const isCustom = container?.getAttribute('data-custom-aspect-ratio') === 'true'
|
|
973
|
+
|
|
1039
974
|
videoForm.value = {
|
|
1040
|
-
src: container?.getAttribute('data-video-src')
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
975
|
+
src: container?.getAttribute('data-video-src')
|
|
976
|
+
|| bareVideo?.src
|
|
977
|
+
|| bareIframe?.src
|
|
978
|
+
|| '',
|
|
979
|
+
width: container?.getAttribute('data-width') || (bareVideo?.style.width ?? '') || '',
|
|
980
|
+
autoplay: container ? container.getAttribute('data-autoplay') === 'true' : (bareVideo?.autoplay ?? false),
|
|
981
|
+
mute: container ? container.getAttribute('data-mute') === 'true' : (bareVideo?.muted ?? false),
|
|
982
|
+
controls: container ? container.getAttribute('data-controls') === 'true' : (bareVideo?.controls ?? true),
|
|
983
|
+
loop: container ? container.getAttribute('data-loop') === 'true' : (bareVideo?.loop ?? false),
|
|
1046
984
|
aspectRatio: isCustom ? 'custom' : (container?.getAttribute('data-aspect-ratio') || '16:9'),
|
|
1047
985
|
customWidth: container?.getAttribute('data-custom-width') || '',
|
|
1048
986
|
customHeight: container?.getAttribute('data-custom-height') || '',
|
|
1049
|
-
caption: existingVideo.querySelector('figcaption')?.textContent || '',
|
|
1050
|
-
showCaption: !!existingVideo.querySelector('figcaption')
|
|
987
|
+
caption: existingVideo.querySelector?.('figcaption')?.textContent || '',
|
|
988
|
+
showCaption: !!existingVideo.querySelector?.('figcaption')
|
|
1051
989
|
}
|
|
1052
990
|
} else {
|
|
1053
991
|
// Reset form for new video
|
|
@@ -1277,8 +1215,6 @@ function detectTableAlignment(table: HTMLTableElement): 'left' | 'center' | 'rig
|
|
|
1277
1215
|
const { marginLeft } = table.style
|
|
1278
1216
|
const { marginRight } = table.style
|
|
1279
1217
|
|
|
1280
|
-
console.log('Table margins:', { marginLeft, marginRight })
|
|
1281
|
-
|
|
1282
1218
|
if (marginLeft === 'auto' && marginRight === 'auto') {
|
|
1283
1219
|
return 'center'
|
|
1284
1220
|
} if (marginLeft === 'auto' && marginRight === '0') {
|
|
@@ -1571,21 +1507,7 @@ function createNewTable(doc: Document) {
|
|
|
1571
1507
|
}
|
|
1572
1508
|
|
|
1573
1509
|
updateContentWithHistory(doc)
|
|
1574
|
-
|
|
1575
|
-
// Add edit button to the new table immediately
|
|
1576
1510
|
setupTableEditButtons(doc)
|
|
1577
|
-
|
|
1578
|
-
// Add edit button to the new table
|
|
1579
|
-
setTimeout(() => {
|
|
1580
|
-
console.log('Trying to add edit button to new table...')
|
|
1581
|
-
if (doc && (doc as any).__addEditButtonsToTables) {
|
|
1582
|
-
console.log('Calling __addEditButtonsToTables...')
|
|
1583
|
-
; (doc as any).__addEditButtonsToTables()
|
|
1584
|
-
} else {
|
|
1585
|
-
console.log('__addEditButtonsToTables not found, calling setupTableEditButtons...')
|
|
1586
|
-
setupTableEditButtons(doc)
|
|
1587
|
-
}
|
|
1588
|
-
}, 10)
|
|
1589
1511
|
}
|
|
1590
1512
|
|
|
1591
1513
|
// Function to delete table
|
|
@@ -1602,8 +1524,6 @@ function deleteTable() {
|
|
|
1602
1524
|
|
|
1603
1525
|
// Function to apply default table settings to externally created tables
|
|
1604
1526
|
function applyDefaultTableSettings(table: HTMLTableElement) {
|
|
1605
|
-
console.log('Applying default settings to table:', table)
|
|
1606
|
-
|
|
1607
1527
|
// Apply default styles from tableForm
|
|
1608
1528
|
const defaultSettings = {
|
|
1609
1529
|
fixedLayout: true,
|
|
@@ -1652,8 +1572,6 @@ function applyDefaultTableSettings(table: HTMLTableElement) {
|
|
|
1652
1572
|
|
|
1653
1573
|
// Set table direction
|
|
1654
1574
|
table.dir = defaultSettings.direction
|
|
1655
|
-
|
|
1656
|
-
console.log('Default settings applied successfully')
|
|
1657
1575
|
}
|
|
1658
1576
|
|
|
1659
1577
|
// Function to delete image
|
|
@@ -1680,6 +1598,136 @@ function deleteEmbed() {
|
|
|
1680
1598
|
pendingEmbedData = null
|
|
1681
1599
|
}
|
|
1682
1600
|
|
|
1601
|
+
// Serialize editor DOM to clean, vanilla HTML โ strips all editor chrome and converts
|
|
1602
|
+
// internal video placeholders to proper <video> / <iframe> tags.
|
|
1603
|
+
function getCleanHTML(doc: Document | null): string {
|
|
1604
|
+
if (!doc?.body) { return '' }
|
|
1605
|
+
|
|
1606
|
+
const clone = doc.body.cloneNode(true) as HTMLElement
|
|
1607
|
+
|
|
1608
|
+
// Strip editor-UI elements that must never appear in output
|
|
1609
|
+
clone.querySelectorAll('.table-edit-btn').forEach((btn) => { btn.remove() })
|
|
1610
|
+
clone.querySelectorAll('.media-selected').forEach((el) => { el.classList.remove('media-selected') })
|
|
1611
|
+
clone.querySelectorAll('.table-cell-selected').forEach((el) => { el.classList.remove('table-cell-selected') })
|
|
1612
|
+
|
|
1613
|
+
// Unwrap .table-wrapper divs: restore original table margin-based alignment
|
|
1614
|
+
clone.querySelectorAll('.table-wrapper').forEach((wrapper) => {
|
|
1615
|
+
const wrapperEl = wrapper as HTMLElement
|
|
1616
|
+
const parent = wrapper.parentNode
|
|
1617
|
+
if (!parent) { return }
|
|
1618
|
+
|
|
1619
|
+
const table = wrapper.querySelector('table') as HTMLElement | null
|
|
1620
|
+
if (table) {
|
|
1621
|
+
const { alignItems } = wrapperEl.style
|
|
1622
|
+
if (alignItems === 'center') {
|
|
1623
|
+
table.style.marginLeft = 'auto'
|
|
1624
|
+
table.style.marginRight = 'auto'
|
|
1625
|
+
} else if (alignItems === 'flex-end') {
|
|
1626
|
+
table.style.marginLeft = 'auto'
|
|
1627
|
+
table.style.marginRight = '0'
|
|
1628
|
+
} else {
|
|
1629
|
+
table.style.marginLeft = '0'
|
|
1630
|
+
table.style.marginRight = 'auto'
|
|
1631
|
+
}
|
|
1632
|
+
table.style.marginBottom = '1rem'
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
Array.from(wrapper.childNodes).forEach(child => parent.insertBefore(child, wrapper))
|
|
1636
|
+
wrapper.remove()
|
|
1637
|
+
})
|
|
1638
|
+
|
|
1639
|
+
// Clean editor-only attributes from tables
|
|
1640
|
+
clone.querySelectorAll('table').forEach((table) => {
|
|
1641
|
+
table.removeAttribute('data-edit-button-added')
|
|
1642
|
+
})
|
|
1643
|
+
|
|
1644
|
+
// Convert internal video placeholder divs โ real <video> or <iframe> HTML
|
|
1645
|
+
clone.querySelectorAll('.video-container[data-component="BglVideo"]').forEach((container) => {
|
|
1646
|
+
const el = container as HTMLElement
|
|
1647
|
+
const src = el.getAttribute('data-video-src') || ''
|
|
1648
|
+
if (!src) { return }
|
|
1649
|
+
|
|
1650
|
+
const autoplay = el.getAttribute('data-autoplay') === 'true'
|
|
1651
|
+
const muted = el.getAttribute('data-mute') === 'true'
|
|
1652
|
+
const controls = el.getAttribute('data-controls') !== 'false'
|
|
1653
|
+
const loop = el.getAttribute('data-loop') === 'true'
|
|
1654
|
+
const aspectRatio = el.getAttribute('data-aspect-ratio') || '16:9'
|
|
1655
|
+
const width = el.getAttribute('data-width') || ''
|
|
1656
|
+
|
|
1657
|
+
const widthStyle = width
|
|
1658
|
+
? `width: ${/[%pxvwemr]/.test(width) ? width : `${width}px`};`
|
|
1659
|
+
: 'width: 100%;'
|
|
1660
|
+
|
|
1661
|
+
const [ratioW, ratioH] = aspectRatio.split(':').map(Number)
|
|
1662
|
+
const paddingBottom = ratioW && ratioH ? `${(ratioH / ratioW) * 100}%` : '56.25%'
|
|
1663
|
+
|
|
1664
|
+
const makeResponsiveIframe = (iframeSrc: string): HTMLElement => {
|
|
1665
|
+
const wrapper = document.createElement('div')
|
|
1666
|
+
wrapper.style.cssText = `${widthStyle} position: relative; padding-bottom: ${paddingBottom}; height: 0; overflow: hidden;`
|
|
1667
|
+
const iframe = document.createElement('iframe')
|
|
1668
|
+
iframe.src = iframeSrc
|
|
1669
|
+
iframe.frameBorder = '0'
|
|
1670
|
+
iframe.setAttribute('allowfullscreen', '')
|
|
1671
|
+
iframe.style.cssText = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%;'
|
|
1672
|
+
wrapper.appendChild(iframe)
|
|
1673
|
+
return wrapper
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
let newEl: HTMLElement | null = null
|
|
1677
|
+
|
|
1678
|
+
if (src.includes('youtube.com') || src.includes('youtu.be')) {
|
|
1679
|
+
let embedUrl = src
|
|
1680
|
+
if (src.includes('youtube.com/watch?v=')) {
|
|
1681
|
+
const videoId = src.split('watch?v=')[1]?.split('&')[0]
|
|
1682
|
+
const params = []
|
|
1683
|
+
if (autoplay) { params.push('autoplay=1') }
|
|
1684
|
+
if (muted) { params.push('mute=1') }
|
|
1685
|
+
if (loop) { params.push(`loop=1&playlist=${videoId}`) }
|
|
1686
|
+
if (!controls) { params.push('controls=0') }
|
|
1687
|
+
embedUrl = `https://www.youtube.com/embed/${videoId}${params.length ? `?${params.join('&')}` : ''}`
|
|
1688
|
+
} else if (src.includes('youtu.be/')) {
|
|
1689
|
+
const videoId = src.split('youtu.be/')[1]?.split('?')[0]
|
|
1690
|
+
embedUrl = `https://www.youtube.com/embed/${videoId}`
|
|
1691
|
+
} else if (src.includes('youtube.com/shorts/')) {
|
|
1692
|
+
const videoId = src.split('shorts/')[1]?.split('?')[0]
|
|
1693
|
+
embedUrl = `https://www.youtube.com/embed/${videoId}`
|
|
1694
|
+
}
|
|
1695
|
+
newEl = makeResponsiveIframe(embedUrl)
|
|
1696
|
+
} else if (src.includes('vimeo.com')) {
|
|
1697
|
+
const match = src.match(/vimeo\.com\/(\d+)/)
|
|
1698
|
+
const embedUrl = match ? `https://player.vimeo.com/video/${match[1]}` : src
|
|
1699
|
+
newEl = makeResponsiveIframe(embedUrl)
|
|
1700
|
+
} else if (src.includes('<iframe')) {
|
|
1701
|
+
const srcMatch = src.match(/src=["']([^"']+)["']/)
|
|
1702
|
+
if (srcMatch) { newEl = makeResponsiveIframe(srcMatch[1]) }
|
|
1703
|
+
} else {
|
|
1704
|
+
// Direct video file
|
|
1705
|
+
const video = document.createElement('video')
|
|
1706
|
+
video.src = src
|
|
1707
|
+
if (controls) { video.setAttribute('controls', '') }
|
|
1708
|
+
if (autoplay) { video.setAttribute('autoplay', '') }
|
|
1709
|
+
if (muted) { video.setAttribute('muted', '') }
|
|
1710
|
+
if (loop) { video.setAttribute('loop', '') }
|
|
1711
|
+
video.style.cssText = `${widthStyle} height: auto; display: block;`
|
|
1712
|
+
newEl = video
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (newEl) { el.parentNode?.replaceChild(newEl, el) }
|
|
1716
|
+
})
|
|
1717
|
+
|
|
1718
|
+
// Strip internal placeholder content from video figures (the bgl_vid wrapper)
|
|
1719
|
+
// that remains after video-container conversion
|
|
1720
|
+
clone.querySelectorAll('.bgl_vid').forEach((el) => { el.remove() })
|
|
1721
|
+
|
|
1722
|
+
// Strip editor-only classes from <figure> elements so output is pure semantic HTML
|
|
1723
|
+
clone.querySelectorAll('figure').forEach((fig) => {
|
|
1724
|
+
fig.classList.remove('image-figure', 'video-figure', 'embed-figure')
|
|
1725
|
+
if (fig.classList.length === 0) { fig.removeAttribute('class') }
|
|
1726
|
+
})
|
|
1727
|
+
|
|
1728
|
+
return clone.innerHTML
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1683
1731
|
// Expose openLinkModal to editor state
|
|
1684
1732
|
; (editor.state as any).openLinkModal = openLinkModal
|
|
1685
1733
|
; (editor.state as any).showTooltipMessage = showTooltipMessage
|
|
@@ -1886,10 +1934,7 @@ function deleteRow() {
|
|
|
1886
1934
|
const tbody = table.querySelector('tbody') || table
|
|
1887
1935
|
const rows = tbody.querySelectorAll('tr')
|
|
1888
1936
|
|
|
1889
|
-
if (rows.length <= 1) {
|
|
1890
|
-
alert('Cannot delete the last row')
|
|
1891
|
-
return
|
|
1892
|
-
}
|
|
1937
|
+
if (rows.length <= 1) { return }
|
|
1893
1938
|
|
|
1894
1939
|
row.remove()
|
|
1895
1940
|
updateContentWithHistory(doc)
|
|
@@ -1958,10 +2003,7 @@ function deleteColumn() {
|
|
|
1958
2003
|
|
|
1959
2004
|
// Check if this is the only column
|
|
1960
2005
|
const firstRow = table.querySelector('tr')
|
|
1961
|
-
if (firstRow && firstRow.cells.length <= 1) {
|
|
1962
|
-
alert('Cannot delete the last column')
|
|
1963
|
-
return
|
|
1964
|
-
}
|
|
2006
|
+
if (firstRow && firstRow.cells.length <= 1) { return }
|
|
1965
2007
|
|
|
1966
2008
|
// Remove cell from each row at the same index
|
|
1967
2009
|
const rows = table.querySelectorAll('tr')
|
|
@@ -2029,7 +2071,7 @@ function handleTableContextMenu(event: MouseEvent) {
|
|
|
2029
2071
|
|
|
2030
2072
|
// Expose debug methods if debug mode is enabled
|
|
2031
2073
|
const debugMethods = computed(() => editor.state.debug)
|
|
2032
|
-
const hasRTL = computed(() =>
|
|
2074
|
+
const hasRTL = computed(() => RTL_REGEX.test(props.modelValue))
|
|
2033
2075
|
|
|
2034
2076
|
// Computed property to handle height prop
|
|
2035
2077
|
const editorHeight = computed(() => {
|
|
@@ -2047,17 +2089,11 @@ onUnmounted(() => {
|
|
|
2047
2089
|
})
|
|
2048
2090
|
|
|
2049
2091
|
function setupTableEditButtons(doc: Document) {
|
|
2050
|
-
console.log('setupTableEditButtons called with doc:', doc)
|
|
2051
|
-
|
|
2052
2092
|
// Simple function to add edit buttons to all tables
|
|
2053
2093
|
function addEditButtonsToTables() {
|
|
2054
|
-
console.log('Adding edit buttons to tables...')
|
|
2055
2094
|
const tables = doc.querySelectorAll('table:not([data-edit-button-added])') as NodeListOf<HTMLTableElement>
|
|
2056
|
-
console.log('Found tables:', tables.length)
|
|
2057
2095
|
|
|
2058
2096
|
tables.forEach((table, index) => {
|
|
2059
|
-
console.log(`Processing table ${index + 1}`)
|
|
2060
|
-
|
|
2061
2097
|
// Create edit button as a span element instead of button
|
|
2062
2098
|
const editBtn = doc.createElement('span')
|
|
2063
2099
|
editBtn.className = 'table-edit-btn'
|
|
@@ -2071,7 +2107,6 @@ function setupTableEditButtons(doc: Document) {
|
|
|
2071
2107
|
editBtn.style.userSelect = 'none'
|
|
2072
2108
|
|
|
2073
2109
|
editBtn.addEventListener('click', (e) => {
|
|
2074
|
-
console.log('Edit button clicked!')
|
|
2075
2110
|
e.preventDefault()
|
|
2076
2111
|
e.stopPropagation()
|
|
2077
2112
|
e.stopImmediatePropagation()
|
|
@@ -2137,8 +2172,6 @@ function setupTableEditButtons(doc: Document) {
|
|
|
2137
2172
|
table.style.marginBottom = '0'
|
|
2138
2173
|
|
|
2139
2174
|
table.setAttribute('data-edit-button-added', 'true')
|
|
2140
|
-
|
|
2141
|
-
console.log(`Added edit button to table ${index + 1}`)
|
|
2142
2175
|
})
|
|
2143
2176
|
}
|
|
2144
2177
|
|
|
@@ -2362,7 +2395,7 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2362
2395
|
}
|
|
2363
2396
|
|
|
2364
2397
|
// For RTL text, be more careful about splitting
|
|
2365
|
-
const isRTLContent =
|
|
2398
|
+
const isRTLContent = RTL_REGEX.test(paragraph.textContent || '')
|
|
2366
2399
|
|
|
2367
2400
|
// Prevent default and handle manually to avoid browser inconsistencies
|
|
2368
2401
|
e.preventDefault()
|
|
@@ -2374,14 +2407,6 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2374
2407
|
|
|
2375
2408
|
// Debug logging to understand the issue
|
|
2376
2409
|
if (props.debug) {
|
|
2377
|
-
console.log('Enter pressed:', {
|
|
2378
|
-
startContainer: range.startContainer,
|
|
2379
|
-
startOffset: range.startOffset,
|
|
2380
|
-
textBeforeCursor,
|
|
2381
|
-
textAfterCursor,
|
|
2382
|
-
paragraphContent: paragraph.textContent,
|
|
2383
|
-
isRTLContent
|
|
2384
|
-
})
|
|
2385
2410
|
}
|
|
2386
2411
|
|
|
2387
2412
|
// Create new paragraph with proper direction
|
|
@@ -2459,20 +2484,15 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2459
2484
|
else if (addedNode.nodeType === Node.ELEMENT_NODE) {
|
|
2460
2485
|
const element = addedNode as HTMLElement
|
|
2461
2486
|
if (element.tagName === 'TABLE') {
|
|
2462
|
-
console.log('MutationObserver detected new table:', element)
|
|
2463
2487
|
setTimeout(() => {
|
|
2464
|
-
console.log('Applying default table settings...')
|
|
2465
2488
|
applyDefaultTableSettings(element as HTMLTableElement)
|
|
2466
|
-
console.log('Adding edit buttons to newly detected table...')
|
|
2467
2489
|
setupTableEditButtons(doc)
|
|
2468
2490
|
}, 100)
|
|
2469
2491
|
}
|
|
2470
2492
|
// Apply direction to blockquotes and lists
|
|
2471
2493
|
else if (['BLOCKQUOTE', 'UL', 'OL'].includes(element.tagName)) {
|
|
2472
|
-
console.log('MutationObserver detected new', element.tagName, ':', element)
|
|
2473
2494
|
if (!element.dir) {
|
|
2474
2495
|
element.dir = doc.body.dir || 'ltr'
|
|
2475
|
-
console.log('Applied direction to', element.tagName, ':', element.dir)
|
|
2476
2496
|
}
|
|
2477
2497
|
|
|
2478
2498
|
// Also apply direction to list items if it's a list
|
|
@@ -2481,7 +2501,6 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2481
2501
|
listItems.forEach((li) => {
|
|
2482
2502
|
if (!li.dir) {
|
|
2483
2503
|
li.dir = element.dir
|
|
2484
|
-
console.log('Applied direction to new list item:', li.dir)
|
|
2485
2504
|
}
|
|
2486
2505
|
})
|
|
2487
2506
|
}
|
|
@@ -2507,37 +2526,57 @@ function setupAutoWrapping(doc: Document) {
|
|
|
2507
2526
|
doc.addEventListener('click', (e) => {
|
|
2508
2527
|
const target = e.target as HTMLElement
|
|
2509
2528
|
const link = target.closest('a')
|
|
2510
|
-
const videoFigure = target.closest('.video-figure')
|
|
2511
|
-
const imageFigure = target.closest('.image-figure')
|
|
2512
|
-
const embedFigure = target.closest('.embed-figure')
|
|
2513
2529
|
const table = target.closest('table')
|
|
2514
2530
|
|
|
2531
|
+
// Resolve media element to edit. Check figure wrappers first (editor-created),
|
|
2532
|
+
// then fall back to bare primitive elements (existing/loaded content).
|
|
2533
|
+
const figure = target.closest('figure') as HTMLElement | null
|
|
2534
|
+
let videoFigure: HTMLElement | null = null
|
|
2535
|
+
let imageFigure: HTMLElement | null = null
|
|
2536
|
+
let embedFigure: HTMLElement | null = null
|
|
2537
|
+
|
|
2538
|
+
if (figure?.querySelector('.video-container, video')) {
|
|
2539
|
+
videoFigure = figure
|
|
2540
|
+
} else if (target.closest('.video-figure')) {
|
|
2541
|
+
videoFigure = target.closest('.video-figure') as HTMLElement
|
|
2542
|
+
} else if (figure?.querySelector('img')) {
|
|
2543
|
+
imageFigure = figure
|
|
2544
|
+
} else if (target.closest('.image-figure')) {
|
|
2545
|
+
imageFigure = target.closest('.image-figure') as HTMLElement
|
|
2546
|
+
} else if (target.tagName === 'IMG') {
|
|
2547
|
+
// Bare <img> not in a figure โ pass it directly
|
|
2548
|
+
imageFigure = target
|
|
2549
|
+
} else if (figure?.querySelector('iframe')) {
|
|
2550
|
+
embedFigure = figure
|
|
2551
|
+
} else if (target.closest('.embed-figure')) {
|
|
2552
|
+
embedFigure = target.closest('.embed-figure') as HTMLElement
|
|
2553
|
+
} else if (target.tagName === 'VIDEO') {
|
|
2554
|
+
videoFigure = target
|
|
2555
|
+
} else if (target.tagName === 'IFRAME') {
|
|
2556
|
+
embedFigure = target
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// Clear previous media selection
|
|
2560
|
+
doc.querySelectorAll('.media-selected').forEach((el) => { el.classList.remove('media-selected') })
|
|
2561
|
+
|
|
2515
2562
|
if (table) {
|
|
2516
|
-
// Table clicks
|
|
2517
|
-
|
|
2518
|
-
}
|
|
2519
|
-
else if (videoFigure) {
|
|
2563
|
+
// Table clicks handled only by edit button
|
|
2564
|
+
} else if (videoFigure) {
|
|
2520
2565
|
e.preventDefault()
|
|
2521
2566
|
e.stopPropagation()
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
}
|
|
2526
|
-
else if (imageFigure) {
|
|
2567
|
+
videoFigure.classList.add('media-selected')
|
|
2568
|
+
openVideoModal(videoFigure)
|
|
2569
|
+
} else if (imageFigure) {
|
|
2527
2570
|
e.preventDefault()
|
|
2528
2571
|
e.stopPropagation()
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
}
|
|
2533
|
-
else if (embedFigure) {
|
|
2572
|
+
imageFigure.classList.add('media-selected')
|
|
2573
|
+
openImageModal(imageFigure)
|
|
2574
|
+
} else if (embedFigure) {
|
|
2534
2575
|
e.preventDefault()
|
|
2535
2576
|
e.stopPropagation()
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
}
|
|
2540
|
-
else if (link && link.href) {
|
|
2577
|
+
embedFigure.classList.add('media-selected')
|
|
2578
|
+
openEmbedModal(embedFigure)
|
|
2579
|
+
} else if (link && link.href) {
|
|
2541
2580
|
e.preventDefault()
|
|
2542
2581
|
e.stopPropagation()
|
|
2543
2582
|
|
|
@@ -2684,10 +2723,88 @@ async function initEditor() {
|
|
|
2684
2723
|
background: #0056b3;
|
|
2685
2724
|
}
|
|
2686
2725
|
.richtext-editor-content blockquote {
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
}
|
|
2726
|
+
padding: 8px !important;
|
|
2727
|
+
background: #f4f4f4 !important;
|
|
2728
|
+
border-inline-start: 4px solid #ccc !important;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
/* โโ Media editing affordances โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
2732
|
+
/* Figures wrapping media: ::after badge + hover ring.
|
|
2733
|
+
No display override โ <figure> is already block, <img>/<video> keep their natural display. */
|
|
2734
|
+
figure:has(img),
|
|
2735
|
+
figure:has(video),
|
|
2736
|
+
figure:has(iframe),
|
|
2737
|
+
figure.image-figure,
|
|
2738
|
+
figure.video-figure,
|
|
2739
|
+
figure.embed-figure {
|
|
2740
|
+
position: relative;
|
|
2741
|
+
cursor: pointer;
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
figure:has(img)::after,
|
|
2745
|
+
figure:has(video)::after,
|
|
2746
|
+
figure:has(iframe)::after,
|
|
2747
|
+
figure.image-figure::after,
|
|
2748
|
+
figure.video-figure::after,
|
|
2749
|
+
figure.embed-figure::after {
|
|
2750
|
+
content: 'โ Edit';
|
|
2751
|
+
position: absolute;
|
|
2752
|
+
top: 8px;
|
|
2753
|
+
right: 8px;
|
|
2754
|
+
background: rgba(0, 0, 0, 0.72);
|
|
2755
|
+
color: #fff;
|
|
2756
|
+
padding: 4px 10px;
|
|
2757
|
+
border-radius: 4px;
|
|
2758
|
+
font-size: 12px;
|
|
2759
|
+
font-family: sans-serif;
|
|
2760
|
+
line-height: 1.5;
|
|
2761
|
+
opacity: 0;
|
|
2762
|
+
transition: opacity 0.18s ease;
|
|
2763
|
+
pointer-events: none;
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
figure:has(img):hover::after,
|
|
2767
|
+
figure:has(video):hover::after,
|
|
2768
|
+
figure:has(iframe):hover::after,
|
|
2769
|
+
figure.image-figure:hover::after,
|
|
2770
|
+
figure.video-figure:hover::after,
|
|
2771
|
+
figure.embed-figure:hover::after {
|
|
2772
|
+
opacity: 1;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
figure:has(img):hover,
|
|
2776
|
+
figure:has(video):hover,
|
|
2777
|
+
figure:has(iframe):hover,
|
|
2778
|
+
figure.image-figure:hover,
|
|
2779
|
+
figure.video-figure:hover,
|
|
2780
|
+
figure.embed-figure:hover {
|
|
2781
|
+
outline: 2px solid rgba(0, 123, 204, 0.45);
|
|
2782
|
+
outline-offset: 2px;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
/* Bare <img> and <video> not inside a <figure> โ show outline + pointer only.
|
|
2786
|
+
::after is not available on replaced/void elements. */
|
|
2787
|
+
img:not(figure img),
|
|
2788
|
+
video:not(figure video) {
|
|
2789
|
+
cursor: pointer;
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
img:not(figure img):hover,
|
|
2793
|
+
video:not(figure video):hover {
|
|
2794
|
+
outline: 2px solid rgba(0, 123, 204, 0.45);
|
|
2795
|
+
outline-offset: 2px;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
/* Selected state โ applied via JS on click, stripped by getCleanHTML() */
|
|
2799
|
+
.media-selected {
|
|
2800
|
+
outline: 2px solid #007bff !important;
|
|
2801
|
+
outline-offset: 2px !important;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
.media-selected::after {
|
|
2805
|
+
opacity: 1 !important;
|
|
2806
|
+
background: #007bff !important;
|
|
2807
|
+
}
|
|
2691
2808
|
` // Create a complete HTML document with proper doctype and meta tags
|
|
2692
2809
|
const initialContent = props.modelValue || (props.placeholder ? `<p class="placeholder">${props.placeholder}</p>` : '')
|
|
2693
2810
|
const htmlContent = `
|
|
@@ -2741,13 +2858,10 @@ async function initEditor() {
|
|
|
2741
2858
|
|
|
2742
2859
|
// Apply direction to existing blockquotes and lists
|
|
2743
2860
|
const blockElements = doc.body.querySelectorAll('blockquote, ul, ol')
|
|
2744
|
-
console.log('Found existing block elements:', blockElements.length)
|
|
2745
2861
|
blockElements.forEach((element) => {
|
|
2746
2862
|
const htmlElement = element as HTMLElement
|
|
2747
|
-
console.log('Processing element:', htmlElement.tagName, 'current dir:', htmlElement.dir)
|
|
2748
2863
|
if (!htmlElement.dir) {
|
|
2749
2864
|
htmlElement.dir = doc.body.dir || 'ltr'
|
|
2750
|
-
console.log('Applied direction to existing', htmlElement.tagName, ':', htmlElement.dir)
|
|
2751
2865
|
}
|
|
2752
2866
|
|
|
2753
2867
|
// Also apply direction to list items
|
|
@@ -2757,7 +2871,6 @@ async function initEditor() {
|
|
|
2757
2871
|
const listItem = li as HTMLElement
|
|
2758
2872
|
if (!listItem.dir) {
|
|
2759
2873
|
listItem.dir = htmlElement.dir
|
|
2760
|
-
console.log('Applied direction to list item:', listItem.dir)
|
|
2761
2874
|
}
|
|
2762
2875
|
})
|
|
2763
2876
|
}
|
|
@@ -2889,9 +3002,7 @@ async function initEditor() {
|
|
|
2889
3002
|
setupAutoWrapping(doc)
|
|
2890
3003
|
|
|
2891
3004
|
// Setup table edit buttons
|
|
2892
|
-
console.log('About to setup table edit buttons...')
|
|
2893
3005
|
setupTableEditButtons(doc)
|
|
2894
|
-
console.log('Table edit buttons setup completed')
|
|
2895
3006
|
|
|
2896
3007
|
// Update state.content after cleanup
|
|
2897
3008
|
updateContentWithHistory(doc)
|
|
@@ -2936,16 +3047,7 @@ async function initEditor() {
|
|
|
2936
3047
|
|
|
2937
3048
|
// Function to update CSS colors in the iframe
|
|
2938
3049
|
function updateIframeColors() {
|
|
2939
|
-
|
|
2940
|
-
hasIframe: !!iframe.value,
|
|
2941
|
-
hasContentDocument: !!iframe.value?.contentDocument,
|
|
2942
|
-
hasInitialized: hasInitialized.value
|
|
2943
|
-
})
|
|
2944
|
-
|
|
2945
|
-
if (!iframe.value?.contentDocument || iframe.value.contentDocument === null) {
|
|
2946
|
-
console.warn('โ No iframe or contentDocument')
|
|
2947
|
-
return
|
|
2948
|
-
}
|
|
3050
|
+
if (!iframe.value?.contentDocument) { return }
|
|
2949
3051
|
|
|
2950
3052
|
const doc = iframe.value.contentDocument
|
|
2951
3053
|
let styleElement = doc.getElementById('editor-theme-styles') as HTMLStyleElement
|
|
@@ -2954,7 +3056,6 @@ function updateIframeColors() {
|
|
|
2954
3056
|
styleElement = doc.createElement('style')
|
|
2955
3057
|
styleElement.id = 'editor-theme-styles'
|
|
2956
3058
|
doc.head.appendChild(styleElement)
|
|
2957
|
-
console.log('โ
Created new style element')
|
|
2958
3059
|
}
|
|
2959
3060
|
|
|
2960
3061
|
const fontSize = props.fontSize ? (typeof props.fontSize === 'number' ? `${props.fontSize}px` : props.fontSize) : '16px'
|
|
@@ -2977,45 +3078,32 @@ function updateIframeColors() {
|
|
|
2977
3078
|
`
|
|
2978
3079
|
|
|
2979
3080
|
styleElement.textContent = css
|
|
2980
|
-
console.log('๐จ Updated iframe colors:', {
|
|
2981
|
-
inputColor: currentInputColor.value,
|
|
2982
|
-
textColor: currentTextColor.value,
|
|
2983
|
-
css: css.trim()
|
|
2984
|
-
})
|
|
2985
3081
|
}
|
|
2986
3082
|
|
|
2987
3083
|
// Watch for theme changes and update iframe colors
|
|
2988
|
-
watch([currentInputColor, currentTextColor], (
|
|
2989
|
-
console.log('๐ Colors changed:', {
|
|
2990
|
-
newValues,
|
|
2991
|
-
oldValues,
|
|
2992
|
-
hasInitialized: hasInitialized.value
|
|
2993
|
-
})
|
|
2994
|
-
|
|
3084
|
+
watch([currentInputColor, currentTextColor], () => {
|
|
2995
3085
|
if (hasInitialized.value) {
|
|
2996
3086
|
updateIframeColors()
|
|
2997
3087
|
}
|
|
2998
3088
|
}, { flush: 'post' })
|
|
2999
3089
|
|
|
3000
|
-
// Reset initialization state when content changes significantly
|
|
3090
|
+
// Reset initialization state when content changes significantly.
|
|
3091
|
+
// Compare incoming value against the CLEAN output (not raw DOM innerHTML) to avoid
|
|
3092
|
+
// false positives caused by editor chrome (table wrappers, edit buttons, etc.)
|
|
3001
3093
|
watch(() => props.modelValue, (newValue, oldValue) => {
|
|
3002
|
-
|
|
3003
|
-
|
|
3094
|
+
const cleanCurrent = editor.state.doc ? getCleanHTML(editor.state.doc ?? null) : ''
|
|
3095
|
+
if (newValue !== cleanCurrent) {
|
|
3004
3096
|
if (!oldValue || Math.abs(newValue.length - oldValue.length) > 50) {
|
|
3005
3097
|
hasInitialized.value = false
|
|
3006
|
-
// For external changes, update content directly but then push to history
|
|
3007
3098
|
editor.state.content = newValue
|
|
3008
3099
|
editor.updateState.content('html')
|
|
3009
|
-
// Add this external change to history after a brief delay
|
|
3010
3100
|
setTimeout(() => {
|
|
3011
3101
|
if (editor.state.doc) {
|
|
3012
3102
|
updateContentWithHistory(editor.state.doc)
|
|
3013
|
-
// Also setup table edit buttons for any new tables
|
|
3014
3103
|
setupTableEditButtons(editor.state.doc)
|
|
3015
3104
|
}
|
|
3016
3105
|
}, 100)
|
|
3017
3106
|
} else {
|
|
3018
|
-
// For minor changes, still check for new tables
|
|
3019
3107
|
setTimeout(() => {
|
|
3020
3108
|
if (editor.state.doc) {
|
|
3021
3109
|
setupTableEditButtons(editor.state.doc)
|
|
@@ -3025,11 +3113,12 @@ watch(() => props.modelValue, (newValue, oldValue) => {
|
|
|
3025
3113
|
}
|
|
3026
3114
|
})
|
|
3027
3115
|
|
|
3028
|
-
|
|
3116
|
+
// Always emit clean, vanilla HTML โ never the editor's internal DOM format
|
|
3117
|
+
watch(() => editor.state.content, () => {
|
|
3029
3118
|
if (editor.state.doc?.body) {
|
|
3030
3119
|
editor.state.doc.body.dir = hasRTL.value ? 'rtl' : 'ltr'
|
|
3031
3120
|
}
|
|
3032
|
-
emit('update:modelValue',
|
|
3121
|
+
emit('update:modelValue', getCleanHTML(editor.state.doc ?? null))
|
|
3033
3122
|
})
|
|
3034
3123
|
|
|
3035
3124
|
// Watch table alignment changes and update the table immediately
|
|
@@ -3076,6 +3165,16 @@ defineExpose({
|
|
|
3076
3165
|
editor,
|
|
3077
3166
|
commands
|
|
3078
3167
|
})
|
|
3168
|
+
|
|
3169
|
+
const imgTransform = computed({
|
|
3170
|
+
get: () => {
|
|
3171
|
+
return imageForm.value.src
|
|
3172
|
+
},
|
|
3173
|
+
set: (newVal: string) => {
|
|
3174
|
+
if (!newVal || imageForm.value.src === newVal) return
|
|
3175
|
+
imageForm.value.src = pathKeyToURL(newVal)
|
|
3176
|
+
}
|
|
3177
|
+
})
|
|
3079
3178
|
</script>
|
|
3080
3179
|
|
|
3081
3180
|
<template>
|
|
@@ -3270,6 +3369,7 @@ defineExpose({
|
|
|
3270
3369
|
v-model:visible="showImageModal" :title="pendingImageData?.existingImage ? 'Edit Image' : 'Insert Image'"
|
|
3271
3370
|
width="500"
|
|
3272
3371
|
>
|
|
3372
|
+
<UploadInput v-model="imgTransform" label="Image" type="image" @keydown.enter="submitImage" />
|
|
3273
3373
|
<TextInput
|
|
3274
3374
|
v-model="imageForm.src" label="Image URL" type="url" placeholder="https://example.com/image.jpg"
|
|
3275
3375
|
@keydown.enter="submitImage"
|
|
@@ -3334,7 +3434,10 @@ defineExpose({
|
|
|
3334
3434
|
</template>
|
|
3335
3435
|
</Modal>
|
|
3336
3436
|
<!-- Video Modal -->
|
|
3337
|
-
<Modal
|
|
3437
|
+
<Modal
|
|
3438
|
+
v-model:visible="showVideoModal" :title="pendingVideoData?.existingVideo ? 'Edit Video' : 'Insert Video'"
|
|
3439
|
+
width="500"
|
|
3440
|
+
>
|
|
3338
3441
|
<div class="grid gap-0">
|
|
3339
3442
|
<TextInput
|
|
3340
3443
|
v-model="videoForm.src" label="Video URL or Embed Code" type="url"
|
|
@@ -3578,6 +3681,7 @@ defineExpose({
|
|
|
3578
3681
|
min-width: calc(var(--input-height) * 3);
|
|
3579
3682
|
width: 100%;
|
|
3580
3683
|
}
|
|
3684
|
+
|
|
3581
3685
|
.rich-text-editor--basic .content-area:hover {
|
|
3582
3686
|
outline-color: rgba(0, 0, 0, 0.05);
|
|
3583
3687
|
box-shadow: inset 0 0 8px #00000018;
|