Refactor template preview components: Moved preview component placement after the CodeEditor in footer, header, and section template forms for improved user experience. Enhanced loading indicators in the template preview Blade view for better feedback during content processing.
This commit is contained in:
@@ -23,14 +23,6 @@ class FooterTemplateForm
|
||||
->maxLength(255)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Preview Component
|
||||
View::make('components.template-preview')
|
||||
->extraAttributes([
|
||||
'data-type' => 'footer',
|
||||
'data-field-name' => 'html_content',
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
CodeEditor::make('html_content')
|
||||
->label(__('footer-templates.field_html_content'))
|
||||
->required()
|
||||
@@ -42,6 +34,14 @@ class FooterTemplateForm
|
||||
'data-field-name' => 'html_content',
|
||||
]),
|
||||
|
||||
// Preview Component - placed after editor
|
||||
View::make('components.template-preview')
|
||||
->extraAttributes([
|
||||
'data-type' => 'footer',
|
||||
'data-field-name' => 'html_content',
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
Toggle::make('is_active')
|
||||
->label(__('footer-templates.field_is_active'))
|
||||
->default(true)
|
||||
|
||||
@@ -23,14 +23,6 @@ class HeaderTemplateForm
|
||||
->maxLength(255)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Preview Component
|
||||
View::make('components.template-preview')
|
||||
->extraAttributes([
|
||||
'data-type' => 'header',
|
||||
'data-field-name' => 'html_content',
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
CodeEditor::make('html_content')
|
||||
->label(__('header-templates.field_html_content'))
|
||||
->required()
|
||||
@@ -42,6 +34,14 @@ class HeaderTemplateForm
|
||||
'data-field-name' => 'html_content',
|
||||
]),
|
||||
|
||||
// Preview Component - placed after editor
|
||||
View::make('components.template-preview')
|
||||
->extraAttributes([
|
||||
'data-type' => 'header',
|
||||
'data-field-name' => 'html_content',
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
Toggle::make('is_active')
|
||||
->label(__('header-templates.field_is_active'))
|
||||
->default(true)
|
||||
|
||||
@@ -23,14 +23,6 @@ class SectionTemplateForm
|
||||
->maxLength(255)
|
||||
->columnSpanFull(),
|
||||
|
||||
// Preview Component
|
||||
View::make('components.template-preview')
|
||||
->extraAttributes([
|
||||
'data-type' => 'section',
|
||||
'data-field-name' => 'html_content',
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
CodeEditor::make('html_content')
|
||||
->label(__('section-templates.field_html_content'))
|
||||
->required()
|
||||
@@ -42,6 +34,14 @@ class SectionTemplateForm
|
||||
'data-field-name' => 'html_content',
|
||||
]),
|
||||
|
||||
// Preview Component - placed after editor
|
||||
View::make('components.template-preview')
|
||||
->extraAttributes([
|
||||
'data-type' => 'section',
|
||||
'data-field-name' => 'html_content',
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
Toggle::make('is_active')
|
||||
->label(__('section-templates.field_is_active'))
|
||||
->default(true)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@php
|
||||
$previewUrl = route('template.preview');
|
||||
// Get type and fieldName from component attributes or defaults
|
||||
$type = $attributes->get('data-type') ?? 'section';
|
||||
$fieldName = $attributes->get('data-field-name') ?? 'html_content';
|
||||
@endphp
|
||||
@@ -9,7 +8,7 @@
|
||||
x-data="templatePreview(@js($previewUrl), @js($type), @js($fieldName))"
|
||||
class="template-preview-wrapper w-full"
|
||||
style="width: 100%;"
|
||||
wire:ignore>
|
||||
wire:ignore.self>
|
||||
<div class="fi-fo-field-wrp-label">
|
||||
<label class="fi-fo-field-wrp-label-label text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ __('Preview') }}
|
||||
@@ -18,21 +17,34 @@
|
||||
|
||||
<div class="mt-2 border border-gray-300 dark:border-gray-700 rounded-lg overflow-hidden bg-white dark:bg-gray-800"
|
||||
style="min-height: 400px; position: relative; width: 100%;">
|
||||
<!-- Loading indicator -->
|
||||
<!-- Loading indicator with modern preloader -->
|
||||
<div
|
||||
x-show="isLoading || !iframeSrc"
|
||||
class="absolute inset-0 flex items-center justify-center bg-gray-50 dark:bg-gray-900"
|
||||
class="absolute inset-0 flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800"
|
||||
style="z-index: 10;"
|
||||
x-transition>
|
||||
<div class="text-center">
|
||||
<svg class="animate-spin h-8 w-8 text-gray-400 mx-auto" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span x-show="isLoading">Yükleniyor...</span>
|
||||
<span x-show="!isLoading && !iframeSrc">Önizleme için içerik girin</span>
|
||||
</p>
|
||||
x-transition
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0">
|
||||
<div class="text-center px-4">
|
||||
<!-- Modern animated loader -->
|
||||
<div class="relative inline-block mb-4">
|
||||
<div class="w-16 h-16 border-4 border-gray-200 dark:border-gray-700 rounded-full animate-pulse"></div>
|
||||
<div class="absolute top-0 left-0 w-16 h-16 border-4 border-transparent border-t-primary-600 dark:border-t-primary-400 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-300 animate-pulse">
|
||||
<span x-show="isLoading">Yükleniyor...</span>
|
||||
<span x-show="!isLoading && !iframeSrc">Önizleme hazırlanıyor</span>
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<span x-show="isLoading">İçerik işleniyor</span>
|
||||
<span x-show="!isLoading && !iframeSrc">Önizleme için HTML içeriği girin</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,176 +76,256 @@ function templatePreview(previewUrl, type, fieldName) {
|
||||
isLoading: false,
|
||||
content: '',
|
||||
debounceTimer: null,
|
||||
intervalId: null,
|
||||
codeMirrorView: null,
|
||||
|
||||
init() {
|
||||
// Watch Livewire events for Filament form updates
|
||||
if (window.Livewire) {
|
||||
// Listen for form state updates
|
||||
Livewire.on('filament-forms::update-state', ({ statePath, state }) => {
|
||||
if (statePath === this.fieldName || statePath?.endsWith(this.fieldName)) {
|
||||
this.content = state || '';
|
||||
this.debouncedUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for any Livewire updates
|
||||
document.addEventListener('livewire:update', () => {
|
||||
this.debouncedUpdate();
|
||||
});
|
||||
}
|
||||
console.log('[Preview] Init:', { previewUrl, type, fieldName });
|
||||
|
||||
// Watch CodeMirror editor directly
|
||||
this.watchCodeMirror();
|
||||
|
||||
// Watch for direct input changes (fallback for code editor)
|
||||
const observer = new MutationObserver(() => {
|
||||
this.debouncedUpdate();
|
||||
// Wait for everything to load
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.findCodeMirror();
|
||||
this.setupWatchers();
|
||||
this.updatePreview();
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// Observe form changes
|
||||
const form = document.querySelector('form[wire\\:submit]');
|
||||
if (form) {
|
||||
observer.observe(form, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Initial update after a short delay
|
||||
setTimeout(() => {
|
||||
this.updatePreview();
|
||||
}, 1000);
|
||||
|
||||
// Periodic update fallback (every 3 seconds)
|
||||
this.intervalId = setInterval(() => {
|
||||
this.updatePreview();
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
watchCodeMirror() {
|
||||
// Try to find CodeMirror editor instance
|
||||
const findCodeMirror = () => {
|
||||
// Look for CodeMirror editor instances
|
||||
const editors = document.querySelectorAll('.cm-editor, [data-code-editor]');
|
||||
editors.forEach(editor => {
|
||||
// Check if this is our field
|
||||
const fieldWrapper = editor.closest('[data-field-name], .fi-fo-field-wrp');
|
||||
if (fieldWrapper) {
|
||||
const fieldLabel = fieldWrapper.querySelector('label');
|
||||
if (fieldLabel && fieldLabel.textContent.includes('HTML Content')) {
|
||||
// Found our editor, try to get CodeMirror instance
|
||||
const cmInstance = editor.__cm || editor.cm || editor;
|
||||
if (cmInstance && cmInstance.getValue) {
|
||||
// Watch for changes
|
||||
if (cmInstance.on) {
|
||||
cmInstance.on('change', () => {
|
||||
this.content = cmInstance.getValue();
|
||||
this.debouncedUpdate();
|
||||
});
|
||||
findCodeMirror() {
|
||||
console.log('[Preview] Looking for CodeMirror editor for field:', this.fieldName);
|
||||
|
||||
// Try multiple wrapper selectors for Filament 4 compatibility
|
||||
const wrapperSelectors = [
|
||||
'.fi-fo-field-wrp',
|
||||
'.fi-input-wrp',
|
||||
'[wire\\:id]',
|
||||
];
|
||||
|
||||
// Debug: List all field wrappers
|
||||
let allWrappers = [];
|
||||
wrapperSelectors.forEach(selector => {
|
||||
const wrappers = document.querySelectorAll(selector);
|
||||
if (wrappers.length > 0) {
|
||||
console.log(`[Preview] Found ${wrappers.length} wrappers with selector: ${selector}`);
|
||||
allWrappers = Array.from(wrappers);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove duplicates
|
||||
allWrappers = Array.from(new Set(allWrappers));
|
||||
console.log('[Preview] Total unique field wrappers found:', allWrappers.length);
|
||||
|
||||
let targetWrapper = null;
|
||||
|
||||
// Method 1: Find by data-field-name attribute (anywhere in wrapper)
|
||||
const dataFieldElements = document.querySelectorAll(`[data-field-name="${this.fieldName}"]`);
|
||||
if (dataFieldElements.length > 0) {
|
||||
console.log('[Preview] Found', dataFieldElements.length, 'elements with data-field-name');
|
||||
// Try to find closest wrapper from any selector
|
||||
for (const selector of wrapperSelectors) {
|
||||
targetWrapper = dataFieldElements[0].closest(selector);
|
||||
if (targetWrapper) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Find by name attribute in textarea or input
|
||||
if (!targetWrapper) {
|
||||
const input = document.querySelector(`textarea[name="${this.fieldName}"], input[name="${this.fieldName}"]`);
|
||||
if (input) {
|
||||
console.log('[Preview] Found input/textarea with name:', this.fieldName);
|
||||
for (const selector of wrapperSelectors) {
|
||||
targetWrapper = input.closest(selector);
|
||||
if (targetWrapper) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Find all CodeMirror editors and check their wrappers
|
||||
if (!targetWrapper) {
|
||||
const cmEditors = document.querySelectorAll('.cm-editor');
|
||||
console.log('[Preview] Found', cmEditors.length, 'CodeMirror editors');
|
||||
cmEditors.forEach((cmEditor, idx) => {
|
||||
for (const selector of wrapperSelectors) {
|
||||
const wrapper = cmEditor.closest(selector);
|
||||
if (wrapper) {
|
||||
const label = wrapper.querySelector('label');
|
||||
console.log(`[Preview] CM Editor ${idx} wrapper label:`, label?.textContent?.substring(0, 30));
|
||||
|
||||
// If this is the only CodeMirror editor, use it
|
||||
// Or if label contains HTML/Content
|
||||
if (cmEditors.length === 1 || (label && (label.textContent.toLowerCase().includes('html') || label.textContent.toLowerCase().includes('content')))) {
|
||||
if (!targetWrapper) {
|
||||
targetWrapper = wrapper;
|
||||
console.log('[Preview] Selected wrapper for CM Editor', idx);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method 4: Find by label text (fallback)
|
||||
if (!targetWrapper && allWrappers.length > 0) {
|
||||
allWrappers.forEach((wrapper) => {
|
||||
const label = wrapper.querySelector('label');
|
||||
if (label) {
|
||||
const labelText = label.textContent.toLowerCase();
|
||||
if ((labelText.includes('html') || labelText.includes('content')) && !targetWrapper) {
|
||||
const hasEditor = wrapper.querySelector('.cm-editor, textarea');
|
||||
if (hasEditor) {
|
||||
targetWrapper = wrapper;
|
||||
console.log('[Preview] Selected wrapper by label:', labelText.substring(0, 30));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Try immediately and after a delay
|
||||
findCodeMirror();
|
||||
setTimeout(findCodeMirror, 1000);
|
||||
setTimeout(findCodeMirror, 2000);
|
||||
if (!targetWrapper) {
|
||||
console.warn('[Preview] Field wrapper not found. Available wrappers:', allWrappers.length);
|
||||
console.warn('[Preview] Trying to find any CodeMirror editor...');
|
||||
|
||||
// Last resort: Use first CodeMirror editor found
|
||||
const firstCmEditor = document.querySelector('.cm-editor');
|
||||
if (firstCmEditor) {
|
||||
for (const selector of wrapperSelectors) {
|
||||
targetWrapper = firstCmEditor.closest(selector);
|
||||
if (targetWrapper) break;
|
||||
}
|
||||
console.log('[Preview] Using first CodeMirror editor as fallback');
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetWrapper) {
|
||||
console.error('[Preview] Could not find field wrapper. Make sure the CodeEditor is rendered.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Preview] Found field wrapper');
|
||||
|
||||
// Try to find CodeMirror editor
|
||||
const cmEditor = targetWrapper.querySelector('.cm-editor');
|
||||
if (cmEditor) {
|
||||
console.log('[Preview] Found .cm-editor element');
|
||||
|
||||
// Try to get from cm-content (CodeMirror 6)
|
||||
const cmContent = cmEditor.querySelector('.cm-content');
|
||||
if (cmContent) {
|
||||
console.log('[Preview] Found .cm-content element');
|
||||
|
||||
// Watch for changes
|
||||
const observer = new MutationObserver(() => {
|
||||
const newContent = cmContent.textContent || '';
|
||||
if (newContent !== this.content) {
|
||||
this.content = newContent;
|
||||
console.log('[Preview] Content changed, length:', this.content.length);
|
||||
this.debouncedUpdate();
|
||||
}
|
||||
});
|
||||
observer.observe(cmContent, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
});
|
||||
|
||||
// Get initial content
|
||||
this.content = cmContent.textContent || '';
|
||||
console.log('[Preview] Initial content from .cm-content, length:', this.content.length);
|
||||
|
||||
if (this.content) {
|
||||
return; // Success!
|
||||
}
|
||||
}
|
||||
|
||||
// Try CodeMirror 6 View
|
||||
if (cmEditor.view || cmEditor._view) {
|
||||
const view = cmEditor.view || cmEditor._view;
|
||||
if (view.state && view.state.doc) {
|
||||
this.content = view.state.doc.toString();
|
||||
console.log('[Preview] Found CodeMirror 6 View, content length:', this.content.length);
|
||||
|
||||
if (view.dispatch) {
|
||||
// Watch for changes
|
||||
const originalDispatch = view.dispatch;
|
||||
view.dispatch = (tr) => {
|
||||
originalDispatch.call(view, tr);
|
||||
this.content = view.state.doc.toString();
|
||||
this.debouncedUpdate();
|
||||
};
|
||||
}
|
||||
|
||||
if (this.content) {
|
||||
return; // Success!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try CodeMirror 5
|
||||
const cmInstance = cmEditor.__cm || cmEditor.cm;
|
||||
if (cmInstance) {
|
||||
console.log('[Preview] Found CodeMirror 5 instance');
|
||||
if (cmInstance.getValue) {
|
||||
this.content = cmInstance.getValue();
|
||||
cmInstance.on('change', () => {
|
||||
this.content = cmInstance.getValue();
|
||||
this.debouncedUpdate();
|
||||
});
|
||||
console.log('[Preview] CodeMirror 5 content length:', this.content.length);
|
||||
if (this.content) {
|
||||
return; // Success!
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Try textarea
|
||||
const textarea = targetWrapper.querySelector('textarea');
|
||||
if (textarea) {
|
||||
console.log('[Preview] Found textarea');
|
||||
this.content = textarea.value || '';
|
||||
textarea.addEventListener('input', () => {
|
||||
this.content = textarea.value;
|
||||
this.debouncedUpdate();
|
||||
});
|
||||
console.log('[Preview] Textarea content length:', this.content.length);
|
||||
} else {
|
||||
console.warn('[Preview] No CodeMirror editor or textarea found in wrapper');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setupWatchers() {
|
||||
// Watch for Livewire updates
|
||||
if (window.Livewire) {
|
||||
document.addEventListener('livewire:update', () => {
|
||||
this.findCodeMirror();
|
||||
this.debouncedUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
// Periodic update
|
||||
this.intervalId = setInterval(() => {
|
||||
this.findCodeMirror();
|
||||
this.updatePreview();
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
debouncedUpdate() {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(() => {
|
||||
this.updatePreview();
|
||||
}, 500); // 500ms debounce
|
||||
}, 800);
|
||||
},
|
||||
|
||||
async updatePreview() {
|
||||
// Get content from Livewire - try different methods
|
||||
let content = this.content || '';
|
||||
|
||||
try {
|
||||
// Method 1: Try CodeMirror editor directly (most reliable for CodeEditor)
|
||||
// Find editor by data-field-name attribute
|
||||
const fieldWrapper = document.querySelector(`[data-field-name="${this.fieldName}"]`);
|
||||
if (fieldWrapper) {
|
||||
const cmEditor = fieldWrapper.querySelector('.cm-editor');
|
||||
if (cmEditor) {
|
||||
const cmInstance = cmEditor.__cm || cmEditor.cm || window.CodeMirror?.fromTextArea?.(cmEditor.querySelector('textarea'));
|
||||
if (cmInstance && typeof cmInstance.getValue === 'function') {
|
||||
content = cmInstance.getValue() || '';
|
||||
} else if (cmEditor.querySelector('.cm-content')) {
|
||||
// Fallback: get text from content div
|
||||
content = cmEditor.querySelector('.cm-content').textContent || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 1b: Try all CodeMirror editors if specific one not found
|
||||
if (!content) {
|
||||
const cmEditors = document.querySelectorAll('.cm-editor');
|
||||
for (const cmEditor of cmEditors) {
|
||||
const fieldWrapper = cmEditor.closest('[data-field-name]');
|
||||
if (fieldWrapper && fieldWrapper.getAttribute('data-field-name') === this.fieldName) {
|
||||
const cmInstance = cmEditor.__cm || cmEditor.cm;
|
||||
if (cmInstance && typeof cmInstance.getValue === 'function') {
|
||||
content = cmInstance.getValue() || '';
|
||||
if (content) break;
|
||||
} else if (cmEditor.querySelector('.cm-content')) {
|
||||
content = cmEditor.querySelector('.cm-content').textContent || '';
|
||||
if (content) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Try to find CodeEditor textarea/input directly
|
||||
if (!content) {
|
||||
const codeEditor = document.querySelector(`textarea[name="${this.fieldName}"],
|
||||
textarea[wire\\:model*="${this.fieldName}"],
|
||||
.fi-fo-field-wrp textarea,
|
||||
.cm-content`);
|
||||
|
||||
if (codeEditor) {
|
||||
content = codeEditor.value || codeEditor.textContent || '';
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Try Livewire $wire
|
||||
if (!content && window.Livewire) {
|
||||
// Find the Livewire component
|
||||
const livewireComponent = document.querySelector('[wire\\:id], form[wire\\:submit]');
|
||||
if (livewireComponent) {
|
||||
const wireId = livewireComponent.getAttribute('wire:id');
|
||||
if (wireId) {
|
||||
const livewire = window.Livewire.find(wireId);
|
||||
if (livewire && livewire.get) {
|
||||
content = livewire.get(this.fieldName) || '';
|
||||
}
|
||||
} else {
|
||||
// Try Alpine.js $wire
|
||||
const alpineData = Alpine.$data(livewireComponent);
|
||||
if (alpineData && alpineData.$wire && alpineData.$wire.get) {
|
||||
content = alpineData.$wire.get(this.fieldName) || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not get content:', e);
|
||||
// Try to refresh content
|
||||
if (!this.content) {
|
||||
this.findCodeMirror();
|
||||
}
|
||||
|
||||
// Use existing content if new fetch failed
|
||||
if (!content && this.content) {
|
||||
content = this.content;
|
||||
}
|
||||
|
||||
this.content = content;
|
||||
|
||||
if (!this.content || this.content.trim() === '') {
|
||||
if (!this.content || !this.content.trim()) {
|
||||
console.log('[Preview] No content, skipping update');
|
||||
if (this.iframeSrc) {
|
||||
URL.revokeObjectURL(this.iframeSrc);
|
||||
this.iframeSrc = '';
|
||||
@@ -244,13 +336,11 @@ function templatePreview(previewUrl, type, fieldName) {
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
// Create form data
|
||||
const formData = new FormData();
|
||||
formData.append('content', this.content);
|
||||
formData.append('type', this.type);
|
||||
formData.append('_token', document.querySelector('meta[name="csrf-token"]')?.content || '');
|
||||
|
||||
// Fetch preview HTML
|
||||
const response = await fetch(this.previewUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
@@ -261,28 +351,38 @@ function templatePreview(previewUrl, type, fieldName) {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Preview request failed');
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
|
||||
// Create blob URL for iframe
|
||||
const blob = new Blob([html], { type: 'text/html; charset=utf-8' });
|
||||
|
||||
// Revoke old URL if exists
|
||||
if (this.iframeSrc) {
|
||||
URL.revokeObjectURL(this.iframeSrc);
|
||||
}
|
||||
|
||||
const blob = new Blob([html], { type: 'text/html; charset=utf-8' });
|
||||
this.iframeSrc = URL.createObjectURL(blob);
|
||||
this.isLoading = false;
|
||||
|
||||
console.log('[Preview] Updated successfully, content length:', this.content.length);
|
||||
} catch (error) {
|
||||
console.error('Preview error:', error);
|
||||
console.error('[Preview] Error:', error);
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer);
|
||||
}
|
||||
if (this.iframeSrc) {
|
||||
URL.revokeObjectURL(this.iframeSrc);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
|
||||
Reference in New Issue
Block a user