10-04-2026
This commit is contained in:
parent
4d6b4930b2
commit
4bb89aad8c
836 changed files with 52961 additions and 5950 deletions
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace FluxCms\Components;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Livewire\Livewire;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
use ReflectionClass;
|
||||
|
||||
class FluxCmsComponentsServiceProvider extends ServiceProvider
|
||||
|
|
@ -33,7 +33,7 @@ class FluxCmsComponentsServiceProvider extends ServiceProvider
|
|||
*/
|
||||
protected function bootViews(): void
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'flux-cms-components');
|
||||
$this->loadViewsFrom(__DIR__.'/../resources/views', 'flux-cms-components');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,12 +44,12 @@ class FluxCmsComponentsServiceProvider extends ServiceProvider
|
|||
if ($this->app->runningInConsole()) {
|
||||
// Publish views
|
||||
$this->publishes([
|
||||
__DIR__ . '/../resources/views' => resource_path('views/vendor/flux-cms-components'),
|
||||
__DIR__.'/../resources/views' => resource_path('views/vendor/flux-cms-components'),
|
||||
], 'flux-cms-components-views');
|
||||
|
||||
// Publish assets
|
||||
$this->publishes([
|
||||
__DIR__ . '/../resources/assets' => public_path('vendor/flux-cms-components'),
|
||||
__DIR__.'/../resources/assets' => public_path('vendor/flux-cms-components'),
|
||||
], 'flux-cms-components-assets');
|
||||
}
|
||||
}
|
||||
|
|
@ -59,22 +59,22 @@ class FluxCmsComponentsServiceProvider extends ServiceProvider
|
|||
*/
|
||||
protected function bootLivewireComponents(): void
|
||||
{
|
||||
$this->registerLivewireComponentsFrom(__DIR__ . '/Livewire/Backend', 'FluxCms\\Components\\Livewire\\Backend', 'flux-cms::');
|
||||
$this->registerLivewireComponentsFrom(__DIR__ . '/Livewire/Frontend', 'FluxCms\\Components\\Livewire\\Frontend', 'flux-cms::');
|
||||
$this->registerLivewireComponentsFrom(__DIR__.'/Livewire/Backend', 'FluxCms\\Components\\Livewire\\Backend', 'flux-cms::');
|
||||
$this->registerLivewireComponentsFrom(__DIR__.'/Livewire/Frontend', 'FluxCms\\Components\\Livewire\\Frontend', 'flux-cms::');
|
||||
}
|
||||
|
||||
protected function registerLivewireComponentsFrom(string $path, string $namespace, string $aliasPrefix = ''): void
|
||||
{
|
||||
$filesystem = new Filesystem();
|
||||
if (!$filesystem->isDirectory($path)) {
|
||||
$filesystem = new Filesystem;
|
||||
if (! $filesystem->isDirectory($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($filesystem->allFiles($path) as $file) {
|
||||
$class = $namespace . '\\' . str_replace(['/', '.php'], ['\\', ''], $file->getRelativePathname());
|
||||
$class = $namespace.'\\'.str_replace(['/', '.php'], ['\\', ''], $file->getRelativePathname());
|
||||
|
||||
if (class_exists($class) && is_subclass_of($class, \Livewire\Component::class) && !(new ReflectionClass($class))->isAbstract()) {
|
||||
$alias = $aliasPrefix . Str::kebab(class_basename($class));
|
||||
if (class_exists($class) && is_subclass_of($class, \Livewire\Component::class) && ! (new ReflectionClass($class))->isAbstract()) {
|
||||
$alias = $aliasPrefix.Str::kebab(class_basename($class));
|
||||
Livewire::component($alias, $class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Backend;
|
||||
|
||||
use Livewire\Component;
|
||||
use FluxCms\Core\Models\BlogPost;
|
||||
use Spatie\Tags\Tag;
|
||||
use Livewire\Component;
|
||||
|
||||
class BlogEditor extends Component
|
||||
{
|
||||
public BlogPost $post;
|
||||
|
||||
public string $tags = '';
|
||||
|
||||
public function mount(BlogPost $post)
|
||||
|
|
|
|||
|
|
@ -2,21 +2,26 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Backend;
|
||||
|
||||
use FluxCms\Core\Models\BlogPost;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use FluxCms\Core\Models\BlogPost;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BlogManager extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $domainKey;
|
||||
|
||||
public array $availableLanguages = [];
|
||||
|
||||
public string $currentLocale = 'de';
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public string $filterStatus = 'all';
|
||||
|
||||
public bool $showCreateModal = false;
|
||||
|
||||
public ?BlogPost $editingPost = null;
|
||||
|
||||
// Form data
|
||||
|
|
@ -48,12 +53,12 @@ class BlogManager extends Component
|
|||
$query = BlogPost::forDomain($this->domainKey);
|
||||
|
||||
// Search filter
|
||||
if (!empty($this->search)) {
|
||||
if (! empty($this->search)) {
|
||||
$query->where(function ($q) {
|
||||
$q->where('title->de', 'like', '%' . $this->search . '%')
|
||||
->orWhere('title->en', 'like', '%' . $this->search . '%')
|
||||
->orWhere('content->de', 'like', '%' . $this->search . '%')
|
||||
->orWhere('content->en', 'like', '%' . $this->search . '%');
|
||||
$q->where('title->de', 'like', '%'.$this->search.'%')
|
||||
->orWhere('title->en', 'like', '%'.$this->search.'%')
|
||||
->orWhere('content->de', 'like', '%'.$this->search.'%')
|
||||
->orWhere('content->en', 'like', '%'.$this->search.'%');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +180,7 @@ class BlogManager extends Component
|
|||
session()->flash('success', $message);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error saving blog post: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error saving blog post: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +196,7 @@ class BlogManager extends Component
|
|||
session()->flash('success', 'Blog post deleted successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error deleting blog post: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error deleting blog post: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +218,7 @@ class BlogManager extends Component
|
|||
session()->flash('success', $message);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error updating publish status: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error updating publish status: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,12 +230,12 @@ class BlogManager extends Component
|
|||
try {
|
||||
$post = BlogPost::find($postId);
|
||||
if ($post) {
|
||||
$post->update(['is_featured' => !$post->is_featured]);
|
||||
$post->update(['is_featured' => ! $post->is_featured]);
|
||||
$message = $post->is_featured ? 'Post marked as featured.' : 'Post removed from featured.';
|
||||
session()->flash('success', $message);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error updating featured status: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error updating featured status: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,14 +254,14 @@ class BlogManager extends Component
|
|||
// Update title to indicate it's a copy
|
||||
$titles = $duplicate->getTranslations('title');
|
||||
foreach ($titles as $locale => $title) {
|
||||
$titles[$locale] = $title . ' (Copy)';
|
||||
$titles[$locale] = $title.' (Copy)';
|
||||
}
|
||||
$duplicate->title = $titles;
|
||||
|
||||
// Update slugs to avoid conflicts
|
||||
$slugs = $duplicate->getTranslations('slug');
|
||||
foreach ($slugs as $locale => $slug) {
|
||||
$slugs[$locale] = $slug . '-copy-' . time();
|
||||
$slugs[$locale] = $slug.'-copy-'.time();
|
||||
}
|
||||
$duplicate->slug = $slugs;
|
||||
|
||||
|
|
@ -264,7 +269,7 @@ class BlogManager extends Component
|
|||
session()->flash('success', 'Blog post duplicated successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error duplicating blog post: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error duplicating blog post: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +281,7 @@ class BlogManager extends Component
|
|||
$title = $this->postData['title'][$locale] ?? '';
|
||||
if ($title) {
|
||||
$slug = \Illuminate\Support\Str::slug($title);
|
||||
$this->postData['slug'][$locale] = '/' . $slug;
|
||||
$this->postData['slug'][$locale] = '/'.$slug;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -315,4 +320,4 @@ class BlogManager extends Component
|
|||
'featured' => BlogPost::forDomain($this->domainKey)->featured()->count(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,22 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Backend;
|
||||
|
||||
use Livewire\Component;
|
||||
use FluxCms\Core\Models\PageComponent;
|
||||
use FluxCms\Core\Services\ComponentRegistry;
|
||||
use Livewire\Component;
|
||||
|
||||
class ComponentEditor extends Component
|
||||
{
|
||||
public PageComponent $component;
|
||||
|
||||
public array $content = [];
|
||||
|
||||
public array $availableLanguages = [];
|
||||
|
||||
public string $currentLocale = 'de';
|
||||
|
||||
public bool $expanded = false;
|
||||
|
||||
public array $validationErrors = [];
|
||||
|
||||
protected ComponentRegistry $componentRegistry;
|
||||
|
|
@ -56,7 +61,7 @@ class ComponentEditor extends Component
|
|||
*/
|
||||
public function toggleExpanded()
|
||||
{
|
||||
$this->expanded = !$this->expanded;
|
||||
$this->expanded = ! $this->expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -85,21 +90,22 @@ class ComponentEditor extends Component
|
|||
{
|
||||
$this->validateContent();
|
||||
|
||||
if (!empty($this->validationErrors)) {
|
||||
if (! empty($this->validationErrors)) {
|
||||
session()->flash('error', 'Please correct validation errors.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->component->update([
|
||||
'content' => $this->content
|
||||
'content' => $this->content,
|
||||
]);
|
||||
|
||||
session()->flash('success', 'Component saved successfully.');
|
||||
$this->dispatch('component-saved', componentId: $this->component->id);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error saving component: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error saving component: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +117,7 @@ class ComponentEditor extends Component
|
|||
if (empty($this->validationErrors)) {
|
||||
try {
|
||||
$this->component->update([
|
||||
'content' => $this->content
|
||||
'content' => $this->content,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
// Silent fail for auto-save
|
||||
|
|
@ -189,6 +195,7 @@ class ComponentEditor extends Component
|
|||
public function hasFieldError(string $fieldKey, ?string $locale = null): bool
|
||||
{
|
||||
$errorKey = $locale ? "{$fieldKey}.{$locale}" : $fieldKey;
|
||||
|
||||
return isset($this->validationErrors[$errorKey]);
|
||||
}
|
||||
|
||||
|
|
@ -198,6 +205,7 @@ class ComponentEditor extends Component
|
|||
public function getFieldErrors(string $fieldKey, ?string $locale = null): array
|
||||
{
|
||||
$errorKey = $locale ? "{$fieldKey}.{$locale}" : $fieldKey;
|
||||
|
||||
return $this->validationErrors[$errorKey] ?? [];
|
||||
}
|
||||
|
||||
|
|
@ -225,4 +233,4 @@ class ComponentEditor extends Component
|
|||
$this->content = $this->component->getTranslations('content');
|
||||
$this->validationErrors = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,24 +2,32 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Backend;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Livewire\WithPagination;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
class MediaManager extends Component
|
||||
{
|
||||
use WithFileUploads, WithPagination;
|
||||
|
||||
public bool $showModal = false;
|
||||
|
||||
public ?string $targetComponentId = null;
|
||||
|
||||
public ?string $targetFieldKey = null;
|
||||
|
||||
public ?string $targetLocale = null;
|
||||
|
||||
public array $uploadingFiles = [];
|
||||
|
||||
public string $searchTerm = '';
|
||||
|
||||
public string $filterType = 'all';
|
||||
|
||||
public array $selectedMedia = [];
|
||||
|
||||
public bool $multiSelect = false;
|
||||
|
||||
protected $paginationTheme = 'simple-bootstrap';
|
||||
|
|
@ -71,7 +79,7 @@ class MediaManager extends Component
|
|||
public function uploadFiles()
|
||||
{
|
||||
$this->validate([
|
||||
'uploadingFiles.*' => 'file|max:' . config('flux-cms.media.max_file_size', 10240),
|
||||
'uploadingFiles.*' => 'file|max:'.config('flux-cms.media.max_file_size', 10240),
|
||||
]);
|
||||
|
||||
try {
|
||||
|
|
@ -83,7 +91,7 @@ class MediaManager extends Component
|
|||
session()->flash('success', 'Files uploaded successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error uploading files: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error uploading files: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +102,10 @@ class MediaManager extends Component
|
|||
{
|
||||
// Create a temporary model for media library
|
||||
// In real implementation, you'd use a dedicated media model
|
||||
$mediaModel = new class extends \Illuminate\Database\Eloquent\Model implements \Spatie\MediaLibrary\HasMedia {
|
||||
$mediaModel = new class extends \Illuminate\Database\Eloquent\Model implements \Spatie\MediaLibrary\HasMedia
|
||||
{
|
||||
use \Spatie\MediaLibrary\InteractsWithMedia;
|
||||
|
||||
protected $table = 'flux_cms_media'; // Would exist in real implementation
|
||||
};
|
||||
|
||||
|
|
@ -114,7 +124,7 @@ class MediaManager extends Component
|
|||
{
|
||||
if ($this->multiSelect) {
|
||||
if (in_array($mediaId, $this->selectedMedia)) {
|
||||
$this->selectedMedia = array_filter($this->selectedMedia, fn($id) => $id !== $mediaId);
|
||||
$this->selectedMedia = array_filter($this->selectedMedia, fn ($id) => $id !== $mediaId);
|
||||
} else {
|
||||
$this->selectedMedia[] = $mediaId;
|
||||
}
|
||||
|
|
@ -165,7 +175,7 @@ class MediaManager extends Component
|
|||
session()->flash('success', 'Media deleted successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error deleting media: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error deleting media: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,13 +187,13 @@ class MediaManager extends Component
|
|||
$query = Media::query()->orderBy('created_at', 'desc');
|
||||
|
||||
// Search filter
|
||||
if (!empty($this->searchTerm)) {
|
||||
$query->where('name', 'like', '%' . $this->searchTerm . '%');
|
||||
if (! empty($this->searchTerm)) {
|
||||
$query->where('name', 'like', '%'.$this->searchTerm.'%');
|
||||
}
|
||||
|
||||
// Type filter
|
||||
if ($this->filterType !== 'all') {
|
||||
$query->where('mime_type', 'like', $this->filterType . '%');
|
||||
$query->where('mime_type', 'like', $this->filterType.'%');
|
||||
}
|
||||
|
||||
return $query->paginate(20);
|
||||
|
|
@ -271,6 +281,6 @@ class MediaManager extends Component
|
|||
|
||||
$bytes /= (1 << (10 * $pow));
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$pow];
|
||||
return round($bytes, 2).' '.$units[$pow];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,35 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Backend;
|
||||
|
||||
use Livewire\Component;
|
||||
use FluxCms\Core\Models\Navigation;
|
||||
use FluxCms\Core\Models\NavigationItem;
|
||||
use FluxCms\Core\Models\Page;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class NavigationManager extends Component
|
||||
{
|
||||
public string $domainKey;
|
||||
|
||||
public Collection $navigations;
|
||||
|
||||
public ?Navigation $selectedNavigation = null;
|
||||
|
||||
public Collection $navigationItems;
|
||||
|
||||
public array $availableLanguages = [];
|
||||
|
||||
public string $currentLocale = 'de';
|
||||
|
||||
public bool $showCreateModal = false;
|
||||
|
||||
public bool $showItemModal = false;
|
||||
|
||||
public ?NavigationItem $editingItem = null;
|
||||
|
||||
// Form data
|
||||
public array $navigationData = [];
|
||||
|
||||
public array $itemData = [];
|
||||
|
||||
public function mount(string $domainKey)
|
||||
|
|
@ -58,8 +67,9 @@ class NavigationManager extends Component
|
|||
*/
|
||||
public function loadNavigationItems()
|
||||
{
|
||||
if (!$this->selectedNavigation) {
|
||||
if (! $this->selectedNavigation) {
|
||||
$this->navigationItems = collect();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +134,7 @@ class NavigationManager extends Component
|
|||
session()->flash('success', 'Navigation created successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error creating navigation: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error creating navigation: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +182,7 @@ class NavigationManager extends Component
|
|||
// Validate that either page or external URL is provided
|
||||
if (empty($this->itemData['page_id']) && empty($this->itemData['external_url'])) {
|
||||
$this->addError('itemData.page_id', 'Either select a page or provide an external URL.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +216,7 @@ class NavigationManager extends Component
|
|||
session()->flash('success', $message);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error saving navigation item: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error saving navigation item: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +233,7 @@ class NavigationManager extends Component
|
|||
session()->flash('success', 'Navigation item deleted successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error deleting navigation item: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error deleting navigation item: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -234,11 +245,11 @@ class NavigationManager extends Component
|
|||
try {
|
||||
$item = NavigationItem::find($itemId);
|
||||
if ($item) {
|
||||
$item->update(['is_active' => !$item->is_active]);
|
||||
$item->update(['is_active' => ! $item->is_active]);
|
||||
$this->loadNavigationItems();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error toggling navigation item: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error toggling navigation item: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -256,7 +267,7 @@ class NavigationManager extends Component
|
|||
session()->flash('success', 'Navigation order updated successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error updating order: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error updating order: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +290,7 @@ class NavigationManager extends Component
|
|||
session()->flash('success', 'Navigation deleted successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error deleting navigation: ' . $e->getMessage());
|
||||
session()->flash('error', 'Error deleting navigation: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -288,7 +299,7 @@ class NavigationManager extends Component
|
|||
*/
|
||||
public function getAvailableParentsProperty(): Collection
|
||||
{
|
||||
if (!$this->selectedNavigation) {
|
||||
if (! $this->selectedNavigation) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
|
|
@ -327,4 +338,4 @@ class NavigationManager extends Component
|
|||
$this->navigationData = [];
|
||||
$this->itemData = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,29 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Backend;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\Attributes\On;
|
||||
use FluxCms\Core\Models\Page;
|
||||
use FluxCms\Core\Models\PageComponent;
|
||||
use FluxCms\Core\Services\ComponentRegistry;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class PageEditor extends Component
|
||||
{
|
||||
public Page $page;
|
||||
|
||||
public Collection $components;
|
||||
|
||||
public array $availableLanguages = [];
|
||||
|
||||
public string $currentLocale = 'de';
|
||||
|
||||
public bool $showComponentModal = false;
|
||||
|
||||
public array $availableComponents = [];
|
||||
|
||||
public string $selectedCategory = 'all';
|
||||
|
||||
public bool $isLoading = false;
|
||||
|
||||
protected ComponentRegistry $componentRegistry;
|
||||
|
|
@ -39,7 +46,7 @@ class PageEditor extends Component
|
|||
public function render()
|
||||
{
|
||||
return view('flux-cms-components::livewire.backend.page-editor')
|
||||
->layout('flux-cms-components::layouts.admin');
|
||||
->layout('flux-cms-components::layouts.admin');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -99,8 +106,9 @@ class PageEditor extends Component
|
|||
*/
|
||||
public function addComponent(string $componentClass)
|
||||
{
|
||||
if (!$this->componentRegistry->isValidComponent($componentClass)) {
|
||||
if (! $this->componentRegistry->isValidComponent($componentClass)) {
|
||||
$this->addError('component', 'Invalid component selected.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +129,7 @@ class PageEditor extends Component
|
|||
session()->flash('success', 'Component added successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('component', 'Error adding component: ' . $e->getMessage());
|
||||
$this->addError('component', 'Error adding component: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,8 +141,9 @@ class PageEditor extends Component
|
|||
try {
|
||||
$component = $this->components->firstWhere('id', $componentId);
|
||||
|
||||
if (!$component) {
|
||||
if (! $component) {
|
||||
$this->addError('component', 'Component not found.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +154,7 @@ class PageEditor extends Component
|
|||
session()->flash('success', 'Component deleted successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('component', 'Error deleting component: ' . $e->getMessage());
|
||||
$this->addError('component', 'Error deleting component: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,8 +166,9 @@ class PageEditor extends Component
|
|||
try {
|
||||
$component = $this->components->firstWhere('id', $componentId);
|
||||
|
||||
if (!$component) {
|
||||
if (! $component) {
|
||||
$this->addError('component', 'Component not found.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +179,7 @@ class PageEditor extends Component
|
|||
session()->flash('success', 'Component duplicated successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('component', 'Error duplicating component: ' . $e->getMessage());
|
||||
$this->addError('component', 'Error duplicating component: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,15 +191,15 @@ class PageEditor extends Component
|
|||
try {
|
||||
$component = $this->components->firstWhere('id', $componentId);
|
||||
|
||||
if (!$component) {
|
||||
if (! $component) {
|
||||
return;
|
||||
}
|
||||
|
||||
$component->update(['is_active' => !$component->is_active]);
|
||||
$component->update(['is_active' => ! $component->is_active]);
|
||||
$this->loadComponents();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('component', 'Error toggling component: ' . $e->getMessage());
|
||||
$this->addError('component', 'Error toggling component: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +218,7 @@ class PageEditor extends Component
|
|||
session()->flash('success', 'Component order updated.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('order', 'Error updating order: ' . $e->getMessage());
|
||||
$this->addError('order', 'Error updating order: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +247,7 @@ class PageEditor extends Component
|
|||
}
|
||||
|
||||
foreach ($config['fields'] as $field) {
|
||||
if (!$field instanceof \FluxCms\Core\FieldTypes\BaseField) {
|
||||
if (! $field instanceof \FluxCms\Core\FieldTypes\BaseField) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +281,7 @@ class PageEditor extends Component
|
|||
session()->flash('success', 'Page data saved successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('page', 'Error saving page: ' . $e->getMessage());
|
||||
$this->addError('page', 'Error saving page: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -292,21 +302,21 @@ class PageEditor extends Component
|
|||
session()->flash('success', $message);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('publish', 'Error updating publish status: ' . $e->getMessage());
|
||||
$this->addError('publish', 'Error updating publish status: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create version
|
||||
*/
|
||||
public function createVersion(string $description = null)
|
||||
public function createVersion(?string $description = null)
|
||||
{
|
||||
try {
|
||||
$this->page->createVersion($description, auth()->id());
|
||||
session()->flash('success', 'Version created successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('version', 'Error creating version: ' . $e->getMessage());
|
||||
$this->addError('version', 'Error creating version: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -320,10 +330,11 @@ class PageEditor extends Component
|
|||
|
||||
if (empty($slug)) {
|
||||
$this->addError('preview', 'No slug available for current language.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$url = $this->page->getUrl($locale) . '?preview=1';
|
||||
$url = $this->page->getUrl($locale).'?preview=1';
|
||||
$this->dispatch('open-preview', url: $url);
|
||||
}
|
||||
|
||||
|
|
@ -351,6 +362,7 @@ class PageEditor extends Component
|
|||
foreach ($this->availableComponents as $category => $categoryComponents) {
|
||||
$components = array_merge($components, $categoryComponents);
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +374,7 @@ class PageEditor extends Component
|
|||
*/
|
||||
public function getPageStatusProperty(): string
|
||||
{
|
||||
if (!$this->page->is_published) {
|
||||
if (! $this->page->is_published) {
|
||||
return 'draft';
|
||||
}
|
||||
|
||||
|
|
@ -372,4 +384,4 @@ class PageEditor extends Component
|
|||
|
||||
return 'published';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,34 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Frontend;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use FluxCms\Core\Models\BlogPost;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class BlogList extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $domainKey;
|
||||
|
||||
public int $perPage = 12;
|
||||
|
||||
public bool $showFeatured = true;
|
||||
|
||||
public bool $showPagination = true;
|
||||
|
||||
public string $orderBy = 'published_at';
|
||||
|
||||
public string $orderDirection = 'desc';
|
||||
|
||||
public array $classes = [];
|
||||
|
||||
// Filtering
|
||||
public string $search = '';
|
||||
|
||||
public array $tags = [];
|
||||
|
||||
public ?string $category = null;
|
||||
|
||||
protected $paginationTheme = 'simple-bootstrap';
|
||||
|
|
@ -63,12 +71,12 @@ class BlogList extends Component
|
|||
$query = BlogPost::forDomain($this->domainKey)->published();
|
||||
|
||||
// Search filter
|
||||
if (!empty($this->search)) {
|
||||
if (! empty($this->search)) {
|
||||
$locale = app()->getLocale();
|
||||
$query->where(function ($q) use ($locale) {
|
||||
$q->where("title->{$locale}", 'like', '%' . $this->search . '%')
|
||||
->orWhere("excerpt->{$locale}", 'like', '%' . $this->search . '%')
|
||||
->orWhere("content->{$locale}", 'like', '%' . $this->search . '%');
|
||||
$q->where("title->{$locale}", 'like', '%'.$this->search.'%')
|
||||
->orWhere("excerpt->{$locale}", 'like', '%'.$this->search.'%')
|
||||
->orWhere("content->{$locale}", 'like', '%'.$this->search.'%');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +127,7 @@ class BlogList extends Component
|
|||
public function getPostTitle(BlogPost $post): string
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $post->getTranslation('title', $locale);
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +194,7 @@ class BlogList extends Component
|
|||
{
|
||||
$defaultClasses = ['flux-cms-blog-list', 'blog-list'];
|
||||
$allClasses = array_merge($defaultClasses, $this->classes);
|
||||
|
||||
return implode(' ', $allClasses);
|
||||
}
|
||||
|
||||
|
|
@ -228,6 +238,7 @@ class BlogList extends Component
|
|||
public function getSearchPlaceholder(): string
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $locale === 'de' ? 'Blog durchsuchen...' : 'Search blog...';
|
||||
}
|
||||
|
||||
|
|
@ -238,10 +249,10 @@ class BlogList extends Component
|
|||
{
|
||||
$locale = app()->getLocale();
|
||||
|
||||
if (!empty($this->search)) {
|
||||
if (! empty($this->search)) {
|
||||
return $locale === 'de'
|
||||
? 'Keine Artikel für "' . $this->search . '" gefunden.'
|
||||
: 'No posts found for "' . $this->search . '".';
|
||||
? 'Keine Artikel für "'.$this->search.'" gefunden.'
|
||||
: 'No posts found for "'.$this->search.'".';
|
||||
}
|
||||
|
||||
return $locale === 'de'
|
||||
|
|
@ -262,4 +273,4 @@ class BlogList extends Component
|
|||
|
||||
return $minutes === 1 ? '1 min read' : "{$minutes} min read";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,24 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Frontend;
|
||||
|
||||
use Livewire\Component;
|
||||
use FluxCms\Core\Models\BlogPost as BlogPostModel;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class BlogPost extends Component
|
||||
{
|
||||
public BlogPostModel $post;
|
||||
|
||||
public string $domainKey;
|
||||
|
||||
public bool $showRelated = true;
|
||||
|
||||
public bool $showAuthor = true;
|
||||
|
||||
public bool $showMeta = true;
|
||||
|
||||
public bool $showSocial = true;
|
||||
|
||||
public array $classes = [];
|
||||
|
||||
public function mount(
|
||||
|
|
@ -65,7 +71,7 @@ class BlogPost extends Component
|
|||
$locale = app()->getLocale();
|
||||
|
||||
return [
|
||||
'title' => $this->getTitle() . ' - Blog',
|
||||
'title' => $this->getTitle().' - Blog',
|
||||
'description' => $this->getExcerpt(160),
|
||||
'keywords' => $this->post->getTranslation('meta_keywords', $locale),
|
||||
'og_title' => $this->getTitle(),
|
||||
|
|
@ -85,6 +91,7 @@ class BlogPost extends Component
|
|||
public function getTitle(): string
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $this->post->getTranslation('title', $locale);
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +101,7 @@ class BlogPost extends Component
|
|||
public function getContent(): string
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $this->post->getTranslation('content', $locale);
|
||||
}
|
||||
|
||||
|
|
@ -228,6 +236,7 @@ class BlogPost extends Component
|
|||
public function getRelatedPostTitle(BlogPostModel $relatedPost): string
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $relatedPost->getTranslation('title', $locale);
|
||||
}
|
||||
|
||||
|
|
@ -311,4 +320,4 @@ class BlogPost extends Component
|
|||
'next' => $nextPost,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,24 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Frontend;
|
||||
|
||||
use Livewire\Component;
|
||||
use FluxCms\Core\Models\Navigation;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class NavigationRenderer extends Component
|
||||
{
|
||||
public string $domainKey;
|
||||
|
||||
public string $navigationName;
|
||||
|
||||
public ?Navigation $navigation = null;
|
||||
|
||||
public Collection $navigationItems;
|
||||
|
||||
public string $currentUrl = '';
|
||||
|
||||
public array $classes = [];
|
||||
|
||||
public bool $showInactive = false;
|
||||
|
||||
public function mount(
|
||||
|
|
@ -49,7 +55,7 @@ class NavigationRenderer extends Component
|
|||
$this->navigationItems = $this->navigation->getHierarchicalItems();
|
||||
|
||||
// Filter inactive items if needed
|
||||
if (!$this->showInactive) {
|
||||
if (! $this->showInactive) {
|
||||
$this->navigationItems = $this->navigationItems->where('is_active', true);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -79,6 +85,7 @@ class NavigationRenderer extends Component
|
|||
public function getItemLabel($item): string
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $item->getTranslation('label', $locale);
|
||||
}
|
||||
|
||||
|
|
@ -95,9 +102,10 @@ class NavigationRenderer extends Component
|
|||
*/
|
||||
public function getChildren($item): Collection
|
||||
{
|
||||
if (!$this->showInactive) {
|
||||
if (! $this->showInactive) {
|
||||
return $item->children->where('is_active', true);
|
||||
}
|
||||
|
||||
return $item->children;
|
||||
}
|
||||
|
||||
|
|
@ -114,8 +122,9 @@ class NavigationRenderer extends Component
|
|||
*/
|
||||
public function getNavigationClasses(): string
|
||||
{
|
||||
$defaultClasses = ['flux-cms-navigation', 'navigation-' . $this->navigationName];
|
||||
$defaultClasses = ['flux-cms-navigation', 'navigation-'.$this->navigationName];
|
||||
$allClasses = array_merge($defaultClasses, $this->classes);
|
||||
|
||||
return implode(' ', $allClasses);
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +147,7 @@ class NavigationRenderer extends Component
|
|||
$classes[] = 'nav-item--has-children';
|
||||
}
|
||||
|
||||
if (!$item->is_active) {
|
||||
if (! $item->is_active) {
|
||||
$classes[] = 'nav-item--inactive';
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +186,7 @@ class NavigationRenderer extends Component
|
|||
$attributeStrings = [];
|
||||
|
||||
foreach ($attributes as $key => $value) {
|
||||
$attributeStrings[] = $key . '="' . htmlspecialchars($value) . '"';
|
||||
$attributeStrings[] = $key.'="'.htmlspecialchars($value).'"';
|
||||
}
|
||||
|
||||
return implode(' ', $attributeStrings);
|
||||
|
|
@ -239,11 +248,12 @@ class NavigationRenderer extends Component
|
|||
*/
|
||||
public function getNavigationDisplayName(): string
|
||||
{
|
||||
if (!$this->navigation) {
|
||||
if (! $this->navigation) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $this->navigation->getTranslation('display_name', $locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,18 @@
|
|||
|
||||
namespace FluxCms\Components\Livewire\Frontend;
|
||||
|
||||
use Livewire\Component;
|
||||
use FluxCms\Core\Models\Page;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class PageRenderer extends Component
|
||||
{
|
||||
public Page $page;
|
||||
|
||||
public Collection $components;
|
||||
|
||||
public bool $isPreview = false;
|
||||
|
||||
public array $seoData = [];
|
||||
|
||||
public function mount(Page $page, bool $isPreview = false)
|
||||
|
|
@ -24,10 +27,10 @@ class PageRenderer extends Component
|
|||
public function render()
|
||||
{
|
||||
return view('flux-cms-components::livewire.frontend.page-renderer')
|
||||
->layout('flux-cms-components::layouts.frontend', [
|
||||
'seoData' => $this->seoData,
|
||||
'page' => $this->page,
|
||||
]);
|
||||
->layout('flux-cms-components::layouts.frontend', [
|
||||
'seoData' => $this->seoData,
|
||||
'page' => $this->page,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,6 +87,7 @@ class PageRenderer extends Component
|
|||
public function getComponentContent(PageComponent $component): array
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $component->getTranslatedContent($locale);
|
||||
}
|
||||
|
||||
|
|
@ -93,23 +97,24 @@ class PageRenderer extends Component
|
|||
public function renderComponent(PageComponent $component): string
|
||||
{
|
||||
try {
|
||||
if (!$this->canRenderComponent($component)) {
|
||||
if (! $this->canRenderComponent($component)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = $this->getComponentContent($component);
|
||||
|
||||
// Check if component class exists
|
||||
if (!class_exists($component->component_class)) {
|
||||
if (! class_exists($component->component_class)) {
|
||||
if ($this->isPreview) {
|
||||
return $this->renderComponentError($component, 'Component class not found');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Render component
|
||||
$componentHtml = \Livewire\Livewire::mount($component->component_class, [
|
||||
'content' => $component->getTranslations('content')
|
||||
'content' => $component->getTranslations('content'),
|
||||
])->html();
|
||||
|
||||
// Wrap component if enabled
|
||||
|
|
@ -123,7 +128,7 @@ class PageRenderer extends Component
|
|||
\Log::error('Error rendering component', [
|
||||
'component_id' => $component->id,
|
||||
'component_class' => $component->component_class,
|
||||
'error' => $e->getMessage()
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
if ($this->isPreview) {
|
||||
|
|
@ -141,10 +146,10 @@ class PageRenderer extends Component
|
|||
{
|
||||
$classes = [
|
||||
'flux-cms-component',
|
||||
'flux-cms-component--' . class_basename($component->component_class),
|
||||
'flux-cms-component--'.class_basename($component->component_class),
|
||||
];
|
||||
|
||||
if (!$component->is_active) {
|
||||
if (! $component->is_active) {
|
||||
$classes[] = 'flux-cms-component--inactive';
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +162,7 @@ class PageRenderer extends Component
|
|||
}
|
||||
|
||||
$attributeString = collect($attributes)
|
||||
->map(fn($value, $key) => "{$key}=\"{$value}\"")
|
||||
->map(fn ($value, $key) => "{$key}=\"{$value}\"")
|
||||
->implode(' ');
|
||||
|
||||
$classString = implode(' ', $classes);
|
||||
|
|
@ -196,9 +201,9 @@ class PageRenderer extends Component
|
|||
public function getRelatedPages(): Collection
|
||||
{
|
||||
return Page::forDomain($this->page->domain_key)
|
||||
->published()
|
||||
->where('id', '!=', $this->page->id)
|
||||
->limit(3)
|
||||
->get();
|
||||
->published()
|
||||
->where('id', '!=', $this->page->id)
|
||||
->limit(3)
|
||||
->get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace FluxCms\Components\Tests\Feature\Backend;
|
||||
|
||||
use Livewire\Livewire;
|
||||
use FluxCms\Core\Models\BlogPost;
|
||||
use FluxCms\Components\Livewire\Backend\BlogEditor;
|
||||
use FluxCms\Core\Models\BlogPost;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
use Orchestra\Testbench\TestCase;
|
||||
|
||||
class BlogEditorTest extends TestCase
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue