Add template save event handling and update preview functionality: Implemented afterSave method in Footer, Header, Menu, and Section templates to dispatch a 'template-saved' event for Livewire. Updated template preview components to listen for this event, ensuring previews are refreshed after template modifications. Enhanced viewData method in template forms for better record ID handling.

This commit is contained in:
Ümit Tunç
2025-11-04 06:10:35 -03:00
parent a892fa7866
commit 6060fe1b23
11 changed files with 175 additions and 104 deletions
@@ -52,4 +52,11 @@ class EditFooterTemplate extends EditRecord
return $data;
}
protected function afterSave(): void
{
// Kaydetme sonrası preview'ı güncelle
// Livewire event dispatch et
$this->dispatch('template-saved');
}
}
@@ -50,13 +50,11 @@ class FooterTemplateForm
// Preview Component - placed after editor
View::make('components.template-preview')
->viewData(function ($record) {
return [
'type' => 'footer',
'fieldName' => 'html_content',
'recordId' => $record?->id,
];
})
->viewData([
'type' => 'footer',
'fieldName' => 'html_content',
'recordId' => null, // Will be extracted from URL by JavaScript
])
->columnSpanFull(),
Toggle::make('is_active')
@@ -52,4 +52,11 @@ class EditHeaderTemplate extends EditRecord
return $data;
}
protected function afterSave(): void
{
// Kaydetme sonrası preview'ı güncelle
// Livewire event dispatch et (JavaScript tarafında Livewire.on() ile dinlenecek)
$this->dispatch('template-saved');
}
}
@@ -49,13 +49,11 @@ class HeaderTemplateForm
// Preview Component - placed after editor
View::make('components.template-preview')
->viewData(function ($record) {
return [
'type' => 'header',
'fieldName' => 'html_content',
'recordId' => $record?->id,
];
})
->viewData([
'type' => 'header',
'fieldName' => 'html_content',
'recordId' => null, // Will be extracted from URL by JavaScript
])
->columnSpanFull(),
Toggle::make('is_active')
@@ -28,4 +28,11 @@ class EditMenuTemplate extends EditRecord
RestoreAction::make(),
];
}
protected function afterSave(): void
{
// Kaydetme sonrası preview'ı güncelle
// Livewire event dispatch et
$this->dispatch('template-saved');
}
}
@@ -46,13 +46,11 @@ class MenuTemplateForm
// Preview Component
View::make('components.menu-template-preview')
->viewData(function ($record) {
return [
'type' => 'menu',
'fieldName' => 'html_content',
'recordId' => $record?->id,
];
})
->viewData([
'type' => 'menu',
'fieldName' => 'html_content',
'recordId' => null, // Will be extracted from URL by JavaScript
])
->columnSpanFull(),
Toggle::make('is_active')
@@ -52,4 +52,11 @@ class EditSectionTemplate extends EditRecord
return $data;
}
protected function afterSave(): void
{
// Kaydetme sonrası preview'ı güncelle
// Livewire event dispatch et
$this->dispatch('template-saved');
}
}
@@ -49,13 +49,11 @@ class SectionTemplateForm
// Preview Component - placed after editor
View::make('components.template-preview')
->viewData(function ($record) {
return [
'type' => 'section',
'fieldName' => 'html_content',
'recordId' => $record?->id,
];
})
->viewData([
'type' => 'section',
'fieldName' => 'html_content',
'recordId' => null, // Will be extracted from URL by JavaScript
])
->columnSpanFull(),
Toggle::make('is_active')
@@ -116,6 +116,27 @@ function templatePreview(previewUrl, type, fieldName, recordIdFromView = null) {
} catch (e) {
console.log('[Preview] Could not get ID from Livewire');
}
// Livewire event listener ekle (Livewire 3.x için)
if (window.Livewire) {
document.addEventListener('livewire:init', () => {
Livewire.on('template-saved', () => {
this.updatePreview();
});
});
// Eğer Livewire zaten yüklendiyse direkt dinle
if (window.Livewire.hook) {
Livewire.on('template-saved', () => {
this.updatePreview();
});
}
}
// Window event listener ekle (alternatif - Livewire event'lerini de yakalar)
window.addEventListener('template-saved', () => {
this.updatePreview();
});
},
async updatePreview() {
@@ -85,7 +85,7 @@
size="sm"
color="gray"
outlined
@click="insertPlaceholder('{{ $fieldName }}', type + '.' + example)"
x-on:click="window.insertPlaceholder('{{ $fieldName }}', type + '.' + example)"
x-bind:tooltip="'{{ __('placeholder-picker.click_to_insert') }}'"
>
<code class="text-xs font-mono" x-text="'{' + type + '.' + example + '}'"></code>
@@ -105,7 +105,7 @@
size="sm"
color="primary"
outlined
@click="insertPlaceholder('{{ $fieldName }}', 'menu')"
x-on:click="window.insertPlaceholder('{{ $fieldName }}', 'menu')"
>
<code class="text-xs font-mono">{menu}</code>
</x-filament::button>
@@ -113,7 +113,7 @@
size="sm"
color="primary"
outlined
@click="insertPlaceholder('{{ $fieldName }}', 'staticMenu')"
x-on:click="window.insertPlaceholder('{{ $fieldName }}', 'staticMenu')"
>
<code class="text-xs font-mono">{staticMenu}</code>
</x-filament::button>
@@ -179,25 +179,37 @@
// Monaco Editor instance'ını bul
const editorContainer = document.querySelector('[data-field-name=\'' + fieldName + '\']');
let monacoEditorInstance = null;
let currentValue = '';
let cursorPosition = null;
// Monaco Editor'dan cursor pozisyonunu al
if (editorContainer && window.monaco) {
// Monaco Editor instance'ını bul
if (editorContainer && window.monaco && window.monaco.editor) {
// Tüm Monaco Editor instance'larını kontrol et
const editorInstances = window.monaco.editor.getEditors();
for (let editor of editorInstances) {
const container = editor.getContainerDomNode();
if (container && container.closest && container.closest('[data-field-name=\'' + fieldName + '\']')) {
monacoEditorInstance = editor;
const model = editor.getModel();
currentValue = model.getValue();
const selection = editor.getSelection();
if (selection) {
cursorPosition = model.getOffsetAt(selection.getStartPosition());
} else {
cursorPosition = currentValue.length;
try {
const container = editor.getContainerDomNode();
if (container) {
// Container'ın parent'larını kontrol et
let parent = container;
while (parent && parent !== document.body) {
if (parent.hasAttribute && parent.hasAttribute('data-field-name') &&
parent.getAttribute('data-field-name') === fieldName) {
monacoEditorInstance = editor;
break;
}
parent = parent.parentElement;
}
// Veya closest metodunu kullan
if (!monacoEditorInstance && container.closest) {
const closest = container.closest('[data-field-name=\'' + fieldName + '\']');
if (closest) {
monacoEditorInstance = editor;
break;
}
}
}
break;
} catch (e) {
console.debug('Monaco Editor container check failed:', e);
}
}
}
@@ -205,93 +217,90 @@
// Monaco Editor varsa, doğrudan onu kullan
if (monacoEditorInstance) {
const model = monacoEditorInstance.getModel();
const newValue = currentValue.slice(0, cursorPosition) + placeholderText + currentValue.slice(cursorPosition);
const currentValue = model.getValue();
// Monaco Editor'da değeri güncelle
monacoEditorInstance.executeEdits('placeholder-insert', [{
range: new window.monaco.Range(
model.getPositionAt(cursorPosition).lineNumber,
model.getPositionAt(cursorPosition).column,
model.getPositionAt(cursorPosition).lineNumber,
model.getPositionAt(cursorPosition).column
),
text: placeholderText
}]);
// Cursor pozisyonunu güncelle
const newPosition = model.getPositionAt(cursorPosition + placeholderText.length);
monacoEditorInstance.setPosition(newPosition);
// Monaco Editor'ı focus et (önce focus et ki cursor pozisyonu doğru alınsın)
monacoEditorInstance.focus();
// Filament form state'ini güncellemek için Monaco Editor'ın change event'ini tetikle
// Filament CodeEditor otomatik olarak değişiklikleri algılar
// Kısa bir delay ile cursor pozisyonunu al (focus işleminin tamamlanması için)
setTimeout(() => {
// Monaco Editor model değişikliğini tetikle
// Bu, Filament'in CodeEditor component'inin otomatik olarak algılamasını sağlar
if (monacoEditorInstance) {
// Model'in değerini tekrar set et (zaten set ettik ama event'i tetiklemek için)
const model = monacoEditorInstance.getModel();
// Monaco Editor'ın onDidChangeContent event'ini manuel tetikle
// Filament CodeEditor bu event'i dinliyor ve form state'ini güncelliyor
model.onDidChangeContent(() => {
// Bu event zaten tetiklenmiş olacak, sadece emin olmak için
});
// Cursor pozisyonunu al (selection varsa onu kullan, yoksa cursor pozisyonunu)
let selection = monacoEditorInstance.getSelection();
let cursorPosition;
if (selection && !selection.isEmpty()) {
// Seçili metin varsa, başlangıç pozisyonunu kullan
cursorPosition = model.getOffsetAt(selection.getStartPosition());
} else {
// Seçim yoksa cursor pozisyonunu kullan
const position = monacoEditorInstance.getPosition();
if (position) {
cursorPosition = model.getOffsetAt(position);
} else {
// Cursor pozisyonu alınamazsa sona ekle
cursorPosition = currentValue.length;
}
}
// Placeholder'ı cursor pozisyonuna ekle
const position = model.getPositionAt(cursorPosition);
const range = new window.monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column
);
// Edit işlemini yap
monacoEditorInstance.executeEdits('placeholder-insert', [{
range: range,
text: placeholderText
}]);
// Cursor pozisyonunu güncelle (placeholder'ın sonuna)
const newCursorPosition = model.getPositionAt(cursorPosition + placeholderText.length);
monacoEditorInstance.setPosition(newCursorPosition);
// Focus'u koru
monacoEditorInstance.focus();
// Filament form state'ini güncellemek için güncel değeri al
const updatedValue = model.getValue();
// Filament form state'ini güncelle
setTimeout(() => {
// Hidden input'u bul ve güncelle (eğer varsa)
const hiddenInput = editorContainer.querySelector('input[type=\'hidden\']');
if (hiddenInput) {
hiddenInput.value = newValue;
hiddenInput.value = updatedValue;
hiddenInput.dispatchEvent(new Event('input', { bubbles: true }));
hiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
}
// Filament form'unu bul ve güncelle
// Form component'ini bulmak için editor container'ın parent'larını kontrol et
let formComponent = null;
const formWireElement = editorContainer.closest('form [wire\\:id], [wire\\:id][wire\\:key*="form"]');
if (formWireElement && window.Livewire) {
const wireId = formWireElement.getAttribute('wire:id');
const component = window.Livewire.find(wireId);
// Component'in form component'i olup olmadığını kontrol et
// Sadece field path'lerini kontrol et, 'data' property'sine direkt erişme
if (component && component.get) {
const fieldPath = 'data.' + fieldName;
try {
// Field path'i kontrol et
if (component.get(fieldPath) !== undefined) {
formComponent = component;
component.set(fieldPath, updatedValue);
} else if (component.get('data.html_content') !== undefined) {
formComponent = component;
component.set('data.html_content', updatedValue);
} else if (component.get('html_content') !== undefined) {
formComponent = component;
component.set('html_content', updatedValue);
}
} catch (e) {
// Property kontrolü sırasında hata alırsak, component'i kullanma
console.debug('Livewire form state update skipped:', e.message);
}
}
}
// Sadece form component'i bulunduysa güncelle
if (formComponent) {
try {
const fieldPath = 'data.' + fieldName;
if (formComponent.get(fieldPath) !== undefined) {
formComponent.set(fieldPath, newValue);
} else if (formComponent.get('data.html_content') !== undefined) {
formComponent.set('data.html_content', newValue);
} else if (formComponent.get('html_content') !== undefined) {
formComponent.set('html_content', newValue);
}
} catch (e) {
// Hata durumunda sadece console'a yaz, Monaco Editor zaten güncellenmiş
console.debug('Livewire form state update skipped:', e.message);
}
}
}
}, 150);
}, 100);
}, 50);
return;
}
@@ -116,6 +116,27 @@ function templatePreview(previewUrl, type, fieldName, recordIdFromView = null) {
} catch (e) {
console.log('[Preview] Could not get ID from Livewire');
}
// Livewire event listener ekle (Livewire 3.x için)
if (window.Livewire) {
document.addEventListener('livewire:init', () => {
Livewire.on('template-saved', () => {
this.updatePreview();
});
});
// Eğer Livewire zaten yüklendiyse direkt dinle
if (window.Livewire.hook) {
Livewire.on('template-saved', () => {
this.updatePreview();
});
}
}
// Window event listener ekle (alternatif - Livewire event'lerini de yakalar)
window.addEventListener('template-saved', () => {
this.updatePreview();
});
},
async updatePreview() {