Initial baseline: LaraBlog core with plugin commerce surface.
Captures the current working tree after theme slots, ArticleAccess, and the payment / paid-content plugins so subsequent work has a reviewable git history.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Concerns;
|
||||
|
||||
trait HasTranslatedLabels
|
||||
{
|
||||
/** Navigation / plural key under admin.nav and admin.models, e.g. articles */
|
||||
abstract protected static function navKey(): string;
|
||||
|
||||
/** Singular model key under admin.models, e.g. article */
|
||||
abstract protected static function modelKey(): string;
|
||||
|
||||
/** Group key under admin.groups: content|system|plugins */
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('admin.nav.'.static::navKey());
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('admin.models.'.static::modelKey());
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('admin.models.'.static::navKey());
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('admin.groups.'.static::groupKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Domain\Plugin\PluginManager;
|
||||
use App\Models\Plugin;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use UnitEnum;
|
||||
|
||||
class ManagePlugins extends Page
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedPuzzlePiece;
|
||||
|
||||
protected static ?int $navigationSort = 81;
|
||||
|
||||
protected string $view = 'filament.pages.manage-plugins';
|
||||
|
||||
/** @var array<int, array<string, mixed>> */
|
||||
public array $plugins = [];
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('admin.groups.system');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('admin.nav.plugins');
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('admin.pages.plugins_title');
|
||||
}
|
||||
|
||||
public function mount(PluginManager $manager): void
|
||||
{
|
||||
$manager->syncDiscoveredPlugins();
|
||||
$this->reload($manager);
|
||||
}
|
||||
|
||||
public function enable(string $name, PluginManager $manager): void
|
||||
{
|
||||
try {
|
||||
$manager->enable($name);
|
||||
$this->reload($manager);
|
||||
Notification::make()->title(__('admin.pages.enable').':'.$name)->success()->send();
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()->title($e->getMessage())->danger()->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function disable(string $name, PluginManager $manager): void
|
||||
{
|
||||
try {
|
||||
$manager->disable($name);
|
||||
$this->reload($manager);
|
||||
Notification::make()->title(__('admin.pages.disable').':'.$name)->success()->send();
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()->title($e->getMessage())->danger()->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function showDocs(string $name, PluginManager $manager): void
|
||||
{
|
||||
$docs = $manager->readDocs($name);
|
||||
if ($docs === null || trim($docs) === '') {
|
||||
Notification::make()->title(__('admin.messages.plugin_docs_missing'))->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(__('admin.pages.plugin_docs'))
|
||||
->body(str($docs)->limit(1800)->toString())
|
||||
->persistent()
|
||||
->info()
|
||||
->send();
|
||||
}
|
||||
|
||||
protected function reload(PluginManager $manager): void
|
||||
{
|
||||
$discovered = $manager->discover();
|
||||
$records = Plugin::query()->get()->keyBy('name');
|
||||
$accents = ['#0f8a7a', '#c45c26', '#2563eb', '#7c3aed', '#db2777', '#0891b2'];
|
||||
|
||||
$this->plugins = $discovered->values()->map(function (array $manifest, int $index) use ($records, $accents, $manager) {
|
||||
$name = (string) $manifest['name'];
|
||||
$titleKey = 'admin.plugins.'.$name.'.title';
|
||||
$descKey = 'admin.plugins.'.$name.'.description';
|
||||
$record = $records->get($name);
|
||||
$requires = (array) ($manifest['requires'] ?? []);
|
||||
|
||||
return [
|
||||
'name' => $name,
|
||||
'title' => __($titleKey) !== $titleKey ? __($titleKey) : ($manifest['title'] ?? $name),
|
||||
'version' => $manifest['version'] ?? '1.0.0',
|
||||
'description' => __($descKey) !== $descKey ? __($descKey) : ($manifest['description'] ?? ''),
|
||||
'enabled' => (bool) ($record?->enabled),
|
||||
'requires' => $requires,
|
||||
'has_docs' => $manager->docsPath($name) !== null,
|
||||
'accent' => $accents[$index % count($accents)],
|
||||
];
|
||||
})->all();
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('sync')
|
||||
->label(__('admin.pages.plugins_sync'))
|
||||
->action(function (PluginManager $manager): void {
|
||||
$manager->syncDiscoveredPlugins();
|
||||
$this->reload($manager);
|
||||
Notification::make()->title(__('admin.pages.plugins_sync'))->success()->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Domain\Theme\ThemeManager;
|
||||
use App\Settings\GeneralSettings;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use UnitEnum;
|
||||
|
||||
class ManageThemes extends Page
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedSwatch;
|
||||
|
||||
protected static ?int $navigationSort = 80;
|
||||
|
||||
protected string $view = 'filament.pages.manage-themes';
|
||||
|
||||
public string $activeTheme = 'default';
|
||||
|
||||
/** @var array<int, array<string, mixed>> */
|
||||
public array $themes = [];
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('admin.groups.system');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('admin.nav.themes');
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('admin.pages.themes_title');
|
||||
}
|
||||
|
||||
public function mount(ThemeManager $themes, GeneralSettings $settings): void
|
||||
{
|
||||
$this->reload($themes, $settings);
|
||||
}
|
||||
|
||||
public function activate(string $slug, ThemeManager $themes, GeneralSettings $settings): void
|
||||
{
|
||||
$themes->setActive($slug);
|
||||
Artisan::call('themes:publish', ['theme' => $slug]);
|
||||
$this->reload($themes, $settings);
|
||||
|
||||
$report = $themes->slotReport($slug);
|
||||
$notification = Notification::make()->title(__('admin.pages.activate').':'.$slug);
|
||||
|
||||
if (($report['status'] ?? '') === 'full') {
|
||||
$notification->success()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$notification
|
||||
->warning()
|
||||
->body(__('admin.messages.theme_slots_warn', [
|
||||
'label' => $report['label'] ?? __('admin.slots.status_undeclared'),
|
||||
]))
|
||||
->send();
|
||||
}
|
||||
|
||||
protected function reload(ThemeManager $themes, GeneralSettings $settings): void
|
||||
{
|
||||
$this->activeTheme = $settings->active_theme ?: 'default';
|
||||
$this->themes = $themes->discover()->map(function (array $theme) use ($themes) {
|
||||
$slug = (string) ($theme['slug'] ?? $theme['name'] ?? '');
|
||||
$titleKey = 'admin.themes.'.$slug.'.title';
|
||||
$descKey = 'admin.themes.'.$slug.'.description';
|
||||
$previewFile = base_path('themes/'.$slug.'/assets/preview.svg');
|
||||
$report = $theme['slot_report'] ?? $themes->slotReport($slug);
|
||||
|
||||
return [
|
||||
'slug' => $slug,
|
||||
'title' => __($titleKey) !== $titleKey ? __($titleKey) : ($theme['title'] ?? $slug),
|
||||
'description' => __($descKey) !== $descKey ? __($descKey) : ($theme['description'] ?? ''),
|
||||
'version' => $theme['version'] ?? '1.0.0',
|
||||
'preview' => is_file($previewFile)
|
||||
? url('/themes/'.$slug.'/preview.svg').'?v='.filemtime($previewFile)
|
||||
: null,
|
||||
'slots_status' => $report['status'] ?? 'undeclared',
|
||||
'slots_label' => $report['label'] ?? __('admin.slots.status_undeclared'),
|
||||
'slots_missing' => $report['missing_standard'] ?? [],
|
||||
'slots_declared_count' => count($report['declared'] ?? []),
|
||||
];
|
||||
})->values()->all();
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('refresh')
|
||||
->label(__('admin.pages.themes_refresh'))
|
||||
->action(fn (ThemeManager $themes, GeneralSettings $settings) => $this->reload($themes, $settings)),
|
||||
Action::make('publish')
|
||||
->label(__('admin.pages.themes_publish'))
|
||||
->action(function (): void {
|
||||
Artisan::call('themes:publish');
|
||||
Notification::make()->title(__('admin.pages.themes_publish'))->success()->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
class MembershipPluginPage extends PluginSkeletonPage
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedIdentification;
|
||||
|
||||
protected static ?int $navigationSort = 102;
|
||||
|
||||
protected static function pluginName(): string
|
||||
{
|
||||
return 'larablog/membership';
|
||||
}
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'membership';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
class PaymentPluginPage extends PluginSkeletonPage
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedCreditCard;
|
||||
|
||||
protected static ?int $navigationSort = 101;
|
||||
|
||||
protected static function pluginName(): string
|
||||
{
|
||||
return 'larablog/payment';
|
||||
}
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'payment';
|
||||
}
|
||||
|
||||
public static function shouldRegisterNavigation(): bool
|
||||
{
|
||||
// UI is owned by plugins/larablog/payment Filament resources/pages.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
class PluginMarketplacePage extends PluginSkeletonPage
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedShoppingBag;
|
||||
|
||||
protected static ?int $navigationSort = 103;
|
||||
|
||||
protected static function pluginName(): string
|
||||
{
|
||||
return 'larablog/plugin-marketplace';
|
||||
}
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'plugin_marketplace';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use BackedEnum;
|
||||
use Filament\Pages\Page;
|
||||
|
||||
abstract class PluginSkeletonPage extends Page
|
||||
{
|
||||
protected static ?int $navigationSort = 100;
|
||||
|
||||
protected string $view = 'filament.pages.plugin-skeleton';
|
||||
|
||||
abstract protected static function pluginName(): string;
|
||||
|
||||
abstract protected static function navKey(): string;
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('admin.groups.plugins');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('admin.nav.'.static::navKey());
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return static::translatedTitle();
|
||||
}
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return static::translatedTitle();
|
||||
}
|
||||
|
||||
protected static function translatedTitle(): string
|
||||
{
|
||||
$key = 'admin.plugins.'.static::pluginName().'.title';
|
||||
|
||||
return __($key) !== $key ? __($key) : __('admin.nav.'.static::navKey());
|
||||
}
|
||||
|
||||
protected static function translatedDescription(): string
|
||||
{
|
||||
$key = 'admin.plugins.'.static::pluginName().'.description';
|
||||
|
||||
return __($key) !== $key ? __($key) : '';
|
||||
}
|
||||
|
||||
public static function shouldRegisterNavigation(): bool
|
||||
{
|
||||
return Plugin::query()
|
||||
->where('name', static::pluginName())
|
||||
->where('enabled', true)
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function getViewData(): array
|
||||
{
|
||||
return [
|
||||
'pluginName' => static::pluginName(),
|
||||
'pluginTitle' => static::translatedTitle(),
|
||||
'pluginDescription' => static::translatedDescription(),
|
||||
'enabled' => Plugin::query()
|
||||
->where('name', static::pluginName())
|
||||
->where('enabled', true)
|
||||
->exists(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Domain\Blog\ContentFormat;
|
||||
use App\Domain\Theme\ThemeSlot;
|
||||
use App\Settings\AiSettings;
|
||||
use App\Settings\BlogSettings;
|
||||
use App\Settings\CommentSettings;
|
||||
use App\Settings\GeneralSettings;
|
||||
use App\Settings\SeoSettings;
|
||||
use App\Settings\SnippetSettings;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\EmbeddedSchema;
|
||||
use Filament\Schemas\Components\Form;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
/**
|
||||
* @property-read Schema $form
|
||||
*/
|
||||
class SiteSettings extends Page
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedCog6Tooth;
|
||||
|
||||
protected static ?int $navigationSort = 85;
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('admin.groups.system');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('admin.nav.settings');
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('admin.pages.settings_title');
|
||||
}
|
||||
|
||||
/** @var array<string, mixed>|null */
|
||||
public ?array $data = [];
|
||||
|
||||
public function mount(
|
||||
GeneralSettings $general,
|
||||
SeoSettings $seo,
|
||||
AiSettings $ai,
|
||||
BlogSettings $blog,
|
||||
CommentSettings $comment,
|
||||
SnippetSettings $snippets,
|
||||
): void {
|
||||
$this->form->fill([
|
||||
'site_name' => $general->site_name,
|
||||
'site_url' => $general->site_url,
|
||||
'site_description' => $general->site_description,
|
||||
'active_theme' => $general->active_theme,
|
||||
'attachments_url_prefix' => $general->attachments_url_prefix,
|
||||
'default_content_format' => $general->default_content_format,
|
||||
'import_content_format' => $general->import_content_format,
|
||||
'import_convert_html_to_markdown' => $general->import_convert_html_to_markdown,
|
||||
'posts_per_page' => $blog->posts_per_page,
|
||||
'allow_comments' => $blog->allow_comments,
|
||||
'comment_order' => $blog->comment_order,
|
||||
'show_views' => $blog->show_views,
|
||||
'show_author' => $blog->show_author,
|
||||
'date_format' => $blog->date_format,
|
||||
'close_comments_on_old_posts' => $blog->close_comments_on_old_posts,
|
||||
'close_comments_days' => $blog->close_comments_days,
|
||||
'guest_can_comment' => $comment->guest_can_comment,
|
||||
'require_moderation' => $comment->require_moderation,
|
||||
'rate_limit_per_minute' => $comment->rate_limit_per_minute,
|
||||
'enable_website_field' => $comment->enable_website_field,
|
||||
'forbidden_words' => $comment->forbidden_words,
|
||||
'meta_title_suffix' => $seo->meta_title_suffix,
|
||||
'default_description' => $seo->default_description,
|
||||
'default_keywords' => $seo->default_keywords,
|
||||
'json_ld_enabled' => $seo->json_ld_enabled,
|
||||
'robots_index' => $seo->robots_index,
|
||||
'twitter_site' => $seo->twitter_site,
|
||||
'canonical_force_https' => $seo->canonical_force_https,
|
||||
'ai_provider' => $ai->provider,
|
||||
'ai_api_base_url' => $ai->api_base_url,
|
||||
'ai_api_key' => $ai->api_key,
|
||||
'ai_model' => $ai->model,
|
||||
'comment_moderation_enabled' => $ai->comment_moderation_enabled,
|
||||
'content_optimization_enabled' => $ai->content_optimization_enabled,
|
||||
'analytics_head' => $snippets->analytics_head,
|
||||
'body_end' => $snippets->body_end,
|
||||
'ads_sidebar' => $snippets->ads_sidebar,
|
||||
'ads_article_top' => $snippets->ads_article_top,
|
||||
'ads_article_bottom' => $snippets->ads_article_bottom,
|
||||
'header_banner' => $snippets->header_banner,
|
||||
'custom_links_html' => $snippets->custom_links_html,
|
||||
'footer_html' => $snippets->footer_html,
|
||||
]);
|
||||
}
|
||||
|
||||
public function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
return $schema->statePath('data');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make('settings')
|
||||
->persistTabInQueryString()
|
||||
->columnSpanFull()
|
||||
->tabs([
|
||||
Tab::make(__('admin.settings.tabs.site'))
|
||||
->icon(Heroicon::OutlinedGlobeAlt)
|
||||
->schema([
|
||||
TextInput::make('site_name')->label(__('admin.settings.site_name'))->required(),
|
||||
TextInput::make('site_url')->label(__('admin.settings.site_url'))->required()->url(),
|
||||
Textarea::make('site_description')->label(__('admin.settings.site_description'))->rows(3),
|
||||
TextInput::make('active_theme')->label(__('admin.settings.active_theme'))->required(),
|
||||
TextInput::make('attachments_url_prefix')->label(__('admin.settings.attachments_url_prefix'))->required(),
|
||||
Select::make('default_content_format')
|
||||
->label(__('admin.settings.default_content_format'))
|
||||
->options([
|
||||
ContentFormat::MARKDOWN => __('admin.options.markdown'),
|
||||
ContentFormat::HTML => __('admin.options.html'),
|
||||
])->required(),
|
||||
Select::make('import_content_format')
|
||||
->label(__('admin.settings.import_content_format'))
|
||||
->options([
|
||||
ContentFormat::HTML => __('admin.options.html'),
|
||||
ContentFormat::MARKDOWN => __('admin.options.markdown'),
|
||||
])->required(),
|
||||
Toggle::make('import_convert_html_to_markdown')
|
||||
->label(__('admin.settings.import_convert_html_to_markdown')),
|
||||
]),
|
||||
Tab::make(__('admin.settings.tabs.reading'))
|
||||
->icon(Heroicon::OutlinedNewspaper)
|
||||
->schema([
|
||||
TextInput::make('posts_per_page')->label(__('admin.settings.posts_per_page'))->numeric()->required()->minValue(1)->maxValue(100),
|
||||
TextInput::make('date_format')->label(__('admin.settings.date_format'))->required(),
|
||||
Toggle::make('show_views')->label(__('admin.settings.show_views')),
|
||||
Toggle::make('show_author')->label(__('admin.settings.show_author')),
|
||||
Toggle::make('allow_comments')->label(__('admin.settings.allow_comments')),
|
||||
Select::make('comment_order')
|
||||
->label(__('admin.settings.comment_order'))
|
||||
->options([
|
||||
'asc' => __('admin.options.comment_order_asc'),
|
||||
'desc' => __('admin.options.comment_order_desc'),
|
||||
])
|
||||
->required(),
|
||||
Toggle::make('close_comments_on_old_posts')->label(__('admin.settings.close_comments_on_old_posts')),
|
||||
TextInput::make('close_comments_days')->label(__('admin.settings.close_comments_days'))->numeric()->minValue(1),
|
||||
]),
|
||||
Tab::make(__('admin.settings.tabs.comments'))
|
||||
->icon(Heroicon::OutlinedChatBubbleLeftRight)
|
||||
->schema([
|
||||
Toggle::make('guest_can_comment')->label(__('admin.settings.guest_can_comment')),
|
||||
Toggle::make('require_moderation')->label(__('admin.settings.require_moderation')),
|
||||
TextInput::make('rate_limit_per_minute')->label(__('admin.settings.rate_limit_per_minute'))->numeric()->minValue(1),
|
||||
Toggle::make('enable_website_field')->label(__('admin.settings.enable_website_field')),
|
||||
Textarea::make('forbidden_words')->label(__('admin.settings.forbidden_words'))->rows(3),
|
||||
]),
|
||||
Tab::make(__('admin.settings.tabs.seo'))
|
||||
->icon(Heroicon::OutlinedMagnifyingGlass)
|
||||
->schema([
|
||||
TextInput::make('meta_title_suffix')->label(__('admin.settings.meta_title_suffix')),
|
||||
Textarea::make('default_description')->label(__('admin.settings.default_description'))->rows(3),
|
||||
TextInput::make('default_keywords')->label(__('admin.settings.default_keywords')),
|
||||
Toggle::make('json_ld_enabled')->label(__('admin.settings.json_ld_enabled')),
|
||||
Toggle::make('robots_index')->label(__('admin.settings.robots_index')),
|
||||
TextInput::make('twitter_site')->label(__('admin.settings.twitter_site')),
|
||||
Toggle::make('canonical_force_https')->label(__('admin.settings.canonical_force_https')),
|
||||
]),
|
||||
Tab::make(__('admin.settings.tabs.storage'))
|
||||
->icon(Heroicon::OutlinedCloudArrowUp)
|
||||
->schema([
|
||||
TextInput::make('attachments_url_prefix')
|
||||
->label(__('admin.settings.attachments_legacy_prefix'))
|
||||
->helperText(__('admin.helpers.attachments_prefix'))
|
||||
->required(),
|
||||
]),
|
||||
Tab::make(__('admin.settings.tabs.ai'))
|
||||
->icon(Heroicon::OutlinedSparkles)
|
||||
->schema([
|
||||
Select::make('ai_provider')->label(__('admin.settings.ai_provider'))->options([
|
||||
'stub' => __('admin.options.ai_provider_stub'),
|
||||
'openai_compatible' => __('admin.options.ai_provider_openai'),
|
||||
])->required(),
|
||||
TextInput::make('ai_api_base_url')->label(__('admin.settings.ai_api_base_url')),
|
||||
TextInput::make('ai_api_key')->label(__('admin.settings.ai_api_key'))->password()->revealable(),
|
||||
TextInput::make('ai_model')->label(__('admin.settings.ai_model')),
|
||||
Toggle::make('comment_moderation_enabled')->label(__('admin.settings.comment_moderation_enabled')),
|
||||
Toggle::make('content_optimization_enabled')->label(__('admin.settings.content_optimization_enabled')),
|
||||
]),
|
||||
Tab::make(__('admin.settings.tabs.snippets'))
|
||||
->icon(Heroicon::OutlinedCodeBracket)
|
||||
->schema([
|
||||
Section::make(__('admin.settings.snippet_groups.analytics'))
|
||||
->description(__('admin.settings.snippet_groups.analytics_help'))
|
||||
->icon(Heroicon::OutlinedChartBar)
|
||||
->collapsible()
|
||||
->schema([
|
||||
Textarea::make('analytics_head')
|
||||
->label(__('admin.settings.analytics_head'))
|
||||
->helperText(ThemeSlot::hint(ThemeSlot::HEAD).' '.__('admin.helpers.analytics_extra'))
|
||||
->rows(5)
|
||||
->columnSpanFull(),
|
||||
Textarea::make('body_end')
|
||||
->label(__('admin.settings.body_end'))
|
||||
->helperText(ThemeSlot::hint(ThemeSlot::BODY_END))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Section::make(__('admin.settings.snippet_groups.ads'))
|
||||
->description(__('admin.settings.snippet_groups.ads_help'))
|
||||
->icon(Heroicon::OutlinedMegaphone)
|
||||
->collapsed()
|
||||
->schema([
|
||||
Textarea::make('header_banner')
|
||||
->label(__('admin.settings.header_banner'))
|
||||
->helperText(ThemeSlot::hint(ThemeSlot::HEADER_AFTER))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
Textarea::make('ads_sidebar')
|
||||
->label(__('admin.settings.ads_sidebar'))
|
||||
->helperText(ThemeSlot::hint(ThemeSlot::SIDEBAR).' '.__('admin.helpers.ads_sidebar_extra'))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
Textarea::make('ads_article_top')
|
||||
->label(__('admin.settings.ads_article_top'))
|
||||
->helperText(ThemeSlot::hint(ThemeSlot::ARTICLE_TOP))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
Textarea::make('ads_article_bottom')
|
||||
->label(__('admin.settings.ads_article_bottom'))
|
||||
->helperText(ThemeSlot::hint(ThemeSlot::ARTICLE_BOTTOM))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Section::make(__('admin.settings.snippet_groups.misc'))
|
||||
->description(__('admin.settings.snippet_groups.misc_help'))
|
||||
->icon(Heroicon::OutlinedLink)
|
||||
->collapsed()
|
||||
->schema([
|
||||
Textarea::make('custom_links_html')
|
||||
->label(__('admin.settings.custom_links_html'))
|
||||
->helperText(ThemeSlot::hint(ThemeSlot::SIDEBAR_AFTER))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
Textarea::make('footer_html')
|
||||
->label(__('admin.settings.footer_html'))
|
||||
->helperText(ThemeSlot::hint(ThemeSlot::FOOTER_BEFORE))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function content(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Form::make([EmbeddedSchema::make('form')])
|
||||
->id('form')
|
||||
->livewireSubmitHandler('save')
|
||||
->footer([
|
||||
Actions::make([
|
||||
Action::make('save')
|
||||
->label(__('admin.actions.save'))
|
||||
->submit('save'),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(
|
||||
GeneralSettings $general,
|
||||
SeoSettings $seo,
|
||||
AiSettings $ai,
|
||||
BlogSettings $blog,
|
||||
CommentSettings $comment,
|
||||
SnippetSettings $snippets,
|
||||
): void {
|
||||
$data = $this->form->getState();
|
||||
|
||||
$general->site_name = $data['site_name'];
|
||||
$general->site_url = $data['site_url'];
|
||||
$general->site_description = $data['site_description'] ?? null;
|
||||
$general->active_theme = $data['active_theme'];
|
||||
$general->attachments_url_prefix = $data['attachments_url_prefix'];
|
||||
$general->default_content_format = $data['default_content_format'];
|
||||
$general->import_content_format = $data['import_content_format'];
|
||||
$general->import_convert_html_to_markdown = (bool) $data['import_convert_html_to_markdown'];
|
||||
$general->save();
|
||||
config(['larablog.attachments_url_prefix' => $general->attachments_url_prefix]);
|
||||
|
||||
$blog->posts_per_page = (int) $data['posts_per_page'];
|
||||
$blog->allow_comments = (bool) $data['allow_comments'];
|
||||
$blog->comment_order = $data['comment_order'];
|
||||
$blog->show_views = (bool) $data['show_views'];
|
||||
$blog->show_author = (bool) $data['show_author'];
|
||||
$blog->date_format = $data['date_format'];
|
||||
$blog->close_comments_on_old_posts = (bool) $data['close_comments_on_old_posts'];
|
||||
$blog->close_comments_days = (int) $data['close_comments_days'];
|
||||
$blog->save();
|
||||
|
||||
$comment->guest_can_comment = (bool) $data['guest_can_comment'];
|
||||
$comment->require_moderation = (bool) $data['require_moderation'];
|
||||
$comment->rate_limit_per_minute = (int) $data['rate_limit_per_minute'];
|
||||
$comment->enable_website_field = (bool) $data['enable_website_field'];
|
||||
$comment->forbidden_words = (string) ($data['forbidden_words'] ?? '');
|
||||
$comment->save();
|
||||
|
||||
$seo->meta_title_suffix = $data['meta_title_suffix'] ?? null;
|
||||
$seo->default_description = $data['default_description'] ?? null;
|
||||
$seo->default_keywords = $data['default_keywords'] ?? null;
|
||||
$seo->json_ld_enabled = (bool) $data['json_ld_enabled'];
|
||||
$seo->robots_index = (bool) $data['robots_index'];
|
||||
$seo->twitter_site = $data['twitter_site'] ?? null;
|
||||
$seo->canonical_force_https = (bool) $data['canonical_force_https'];
|
||||
$seo->save();
|
||||
|
||||
$ai->provider = $data['ai_provider'];
|
||||
$ai->api_base_url = $data['ai_api_base_url'] ?? null;
|
||||
$ai->api_key = $data['ai_api_key'] ?? null;
|
||||
$ai->model = $data['ai_model'] ?? null;
|
||||
$ai->comment_moderation_enabled = (bool) $data['comment_moderation_enabled'];
|
||||
$ai->content_optimization_enabled = (bool) $data['content_optimization_enabled'];
|
||||
$ai->save();
|
||||
|
||||
$snippets->analytics_head = (string) ($data['analytics_head'] ?? '');
|
||||
$snippets->body_end = (string) ($data['body_end'] ?? '');
|
||||
$snippets->ads_sidebar = (string) ($data['ads_sidebar'] ?? '');
|
||||
$snippets->ads_article_top = (string) ($data['ads_article_top'] ?? '');
|
||||
$snippets->ads_article_bottom = (string) ($data['ads_article_bottom'] ?? '');
|
||||
$snippets->header_banner = (string) ($data['header_banner'] ?? '');
|
||||
$snippets->custom_links_html = (string) ($data['custom_links_html'] ?? '');
|
||||
$snippets->footer_html = (string) ($data['footer_html'] ?? '');
|
||||
$snippets->save();
|
||||
|
||||
Notification::make()->title(__('admin.messages.settings_saved'))->success()->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
class ThemeMarketplacePage extends PluginSkeletonPage
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedSwatch;
|
||||
|
||||
protected static ?int $navigationSort = 104;
|
||||
|
||||
protected static function pluginName(): string
|
||||
{
|
||||
return 'larablog/theme-marketplace';
|
||||
}
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'theme_marketplace';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Articles;
|
||||
|
||||
use App\Filament\Resources\Articles\Pages\CreateArticle;
|
||||
use App\Filament\Resources\Articles\Pages\EditArticle;
|
||||
use App\Filament\Resources\Articles\Pages\ListArticles;
|
||||
use App\Filament\Resources\Articles\Schemas\ArticleForm;
|
||||
use App\Filament\Resources\Articles\Tables\ArticlesTable;
|
||||
use App\Models\Article;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
use App\Filament\Concerns\HasTranslatedLabels;
|
||||
|
||||
class ArticleResource extends Resource
|
||||
{
|
||||
use HasTranslatedLabels;
|
||||
|
||||
protected static ?string $model = Article::class;
|
||||
|
||||
|
||||
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'articles';
|
||||
}
|
||||
|
||||
protected static function modelKey(): string
|
||||
{
|
||||
return 'article';
|
||||
}
|
||||
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedDocumentText;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return ArticleForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return ArticlesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListArticles::route('/'),
|
||||
'create' => CreateArticle::route('/create'),
|
||||
'edit' => EditArticle::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Articles\Pages;
|
||||
|
||||
use App\Domain\Plugin\Hook;
|
||||
use App\Filament\Resources\Articles\ArticleResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateArticle extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ArticleResource::class;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
$filtered = Hook::filter('filament.article.mutate_before_save', $data, null);
|
||||
|
||||
return is_array($filtered) ? $filtered : $data;
|
||||
}
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
Hook::dispatch('filament.article.after_save', $this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Articles\Pages;
|
||||
|
||||
use App\Domain\Ai\Jobs\OptimizeArticleContentJob;
|
||||
use App\Domain\Plugin\Hook;
|
||||
use App\Filament\Resources\Articles\ArticleResource;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditArticle extends EditRecord
|
||||
{
|
||||
protected static string $resource = ArticleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('aiOptimize')
|
||||
->label(__('admin.messages.ai_optimize'))
|
||||
->action(function (): void {
|
||||
OptimizeArticleContentJob::dispatch($this->record->getKey());
|
||||
Notification::make()
|
||||
->title(__('admin.messages.ai_optimize_queued'))
|
||||
->body(__('admin.messages.ai_optimize_queue_hint'))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
DeleteAction::make(),
|
||||
...Hook::collect('filament.article.actions'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
$filtered = Hook::filter('filament.article.mutate_before_fill', $data, $this->record);
|
||||
|
||||
return is_array($filtered) ? $filtered : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
$filtered = Hook::filter('filament.article.mutate_before_save', $data, $this->record);
|
||||
|
||||
return is_array($filtered) ? $filtered : $data;
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
Hook::dispatch('filament.article.after_save', $this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Articles\Pages;
|
||||
|
||||
use App\Filament\Resources\Articles\ArticleResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListArticles extends ListRecords
|
||||
{
|
||||
protected static string $resource = ArticleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Articles\Schemas;
|
||||
|
||||
use App\Domain\Blog\ContentFormat;
|
||||
use App\Domain\Plugin\Hook;
|
||||
use App\Settings\GeneralSettings;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class ArticleForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
$defaultFormat = ContentFormat::MARKDOWN;
|
||||
try {
|
||||
$defaultFormat = ContentFormat::normalize(app(GeneralSettings::class)->default_content_format, ContentFormat::MARKDOWN);
|
||||
} catch (\Throwable) {
|
||||
//
|
||||
}
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('category_id')
|
||||
->label(__('admin.fields.category'))
|
||||
->relationship('category', 'name')
|
||||
->required(),
|
||||
Select::make('user_id')
|
||||
->label(__('admin.fields.author'))
|
||||
->relationship('user', 'name')
|
||||
->required(),
|
||||
TextInput::make('title')
|
||||
->label(__('admin.fields.title'))
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
Select::make('content_format')
|
||||
->label(__('admin.fields.content_format'))
|
||||
->options([
|
||||
ContentFormat::MARKDOWN => __('admin.options.markdown_recommended'),
|
||||
ContentFormat::HTML => __('admin.options.html_legacy'),
|
||||
])
|
||||
->default($defaultFormat)
|
||||
->required()
|
||||
->live()
|
||||
->helperText(__('admin.helpers.article_content')),
|
||||
Textarea::make('content')
|
||||
->label(fn (Get $get): string => $get('content_format') === ContentFormat::MARKDOWN
|
||||
? __('admin.fields.content_markdown')
|
||||
: __('admin.fields.content_html'))
|
||||
->required()
|
||||
->rows(18)
|
||||
->columnSpanFull(),
|
||||
TextInput::make('description')
|
||||
->label(__('admin.fields.description')),
|
||||
TextInput::make('keywords')
|
||||
->label(__('admin.fields.keywords')),
|
||||
TextInput::make('slug')
|
||||
->label(__('admin.fields.slug')),
|
||||
DateTimePicker::make('published_at')
|
||||
->label(__('admin.fields.published_at'))
|
||||
->default(now()),
|
||||
Toggle::make('stick')
|
||||
->label(__('admin.fields.stick'))
|
||||
->default(false),
|
||||
Toggle::make('visible')
|
||||
->label(__('admin.fields.visible'))
|
||||
->default(true),
|
||||
Toggle::make('close_comment')
|
||||
->label(__('admin.fields.close_comment'))
|
||||
->default(false),
|
||||
TextInput::make('read_password')
|
||||
->label(__('admin.fields.read_password'))
|
||||
->password(),
|
||||
Textarea::make('ai_summary')
|
||||
->label(__('admin.fields.ai_summary'))
|
||||
->columnSpanFull(),
|
||||
...Hook::collect('filament.article.form'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Articles\Tables;
|
||||
|
||||
use App\Domain\Plugin\Hook;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ArticlesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('category.name')
|
||||
->label(__('admin.fields.category'))
|
||||
->searchable(),
|
||||
TextColumn::make('user.name')
|
||||
->label(__('admin.fields.author'))
|
||||
->searchable(),
|
||||
TextColumn::make('title')
|
||||
->label(__('admin.fields.title'))
|
||||
->searchable(),
|
||||
TextColumn::make('description')
|
||||
->label(__('admin.fields.description'))
|
||||
->searchable(),
|
||||
TextColumn::make('keywords')
|
||||
->label(__('admin.fields.keywords'))
|
||||
->searchable(),
|
||||
TextColumn::make('published_at')
|
||||
->label(__('admin.fields.published_at'))
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
TextColumn::make('views')
|
||||
->label(__('admin.fields.views'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('comments_count')
|
||||
->label(__('admin.fields.comments_count'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
IconColumn::make('stick')
|
||||
->label(__('admin.fields.stick'))
|
||||
->boolean(),
|
||||
IconColumn::make('visible')
|
||||
->label(__('admin.fields.visible'))
|
||||
->boolean(),
|
||||
IconColumn::make('close_comment')
|
||||
->label(__('admin.fields.close_comment'))
|
||||
->boolean(),
|
||||
TextColumn::make('created_at')
|
||||
->label(__('admin.fields.created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('admin.fields.updated_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('slug')
|
||||
->label(__('admin.fields.slug'))
|
||||
->searchable(),
|
||||
TextColumn::make('content_format')
|
||||
->label(__('admin.fields.content_format'))
|
||||
->searchable(),
|
||||
...Hook::collect('filament.article.table.columns'),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
...Hook::collect('filament.article.actions'),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Attachments;
|
||||
|
||||
use App\Filament\Resources\Attachments\Pages\CreateAttachment;
|
||||
use App\Filament\Resources\Attachments\Pages\EditAttachment;
|
||||
use App\Filament\Resources\Attachments\Pages\ListAttachments;
|
||||
use App\Filament\Resources\Attachments\Schemas\AttachmentForm;
|
||||
use App\Filament\Resources\Attachments\Tables\AttachmentsTable;
|
||||
use App\Models\Attachment;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Concerns\HasTranslatedLabels;
|
||||
|
||||
class AttachmentResource extends Resource
|
||||
{
|
||||
use HasTranslatedLabels;
|
||||
|
||||
protected static ?string $model = Attachment::class;
|
||||
|
||||
|
||||
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'attachments';
|
||||
}
|
||||
|
||||
protected static function modelKey(): string
|
||||
{
|
||||
return 'attachment';
|
||||
}
|
||||
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return AttachmentForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return AttachmentsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListAttachments::route('/'),
|
||||
'create' => CreateAttachment::route('/create'),
|
||||
'edit' => EditAttachment::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Attachments\Pages;
|
||||
|
||||
use App\Filament\Resources\Attachments\AttachmentResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CreateAttachment extends CreateRecord
|
||||
{
|
||||
protected static string $resource = AttachmentResource::class;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
$disk = config('larablog.attachments_disk', 'attachments');
|
||||
$path = $data['upload'] ?? null;
|
||||
unset($data['upload']);
|
||||
|
||||
if (! is_string($path) || $path === '') {
|
||||
throw new \InvalidArgumentException(__('admin.messages.upload_required'));
|
||||
}
|
||||
|
||||
$storage = Storage::disk($disk);
|
||||
$mime = method_exists($storage, 'mimeType') ? ($storage->mimeType($path) ?: null) : null;
|
||||
$allowed = config('larablog.allowed_attachment_mimes', []);
|
||||
if (is_string($mime) && $allowed !== [] && ! in_array($mime, $allowed, true)) {
|
||||
$storage->delete($path);
|
||||
throw new \InvalidArgumentException(__('admin.messages.mime_not_allowed', ['mime' => $mime]));
|
||||
}
|
||||
|
||||
$data['disk'] = $disk;
|
||||
$data['path'] = $path;
|
||||
$data['filename'] = $data['filename'] ?: basename($path);
|
||||
$data['mime'] = $mime;
|
||||
$data['size'] = $storage->size($path) ?: 0;
|
||||
$data['checksum'] = hash('sha256', (string) $storage->get($path));
|
||||
$data['synced_at'] = now();
|
||||
$data['visibility'] = $data['visibility'] ?? 'public';
|
||||
$data['downloads'] = (int) ($data['downloads'] ?? 0);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Attachments\Pages;
|
||||
|
||||
use App\Domain\Media\AttachmentStorageService;
|
||||
use App\Filament\Resources\Attachments\AttachmentResource;
|
||||
use App\Models\Attachment;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditAttachment extends EditRecord
|
||||
{
|
||||
protected static string $resource = AttachmentResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->before(function (Attachment $record, AttachmentStorageService $storage): void {
|
||||
$storage->deleteFromDisk($record);
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Attachments\Pages;
|
||||
|
||||
use App\Filament\Resources\Attachments\AttachmentResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListAttachments extends ListRecords
|
||||
{
|
||||
protected static string $resource = AttachmentResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Attachments\Schemas;
|
||||
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class AttachmentForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
$disk = config('larablog.attachments_disk', 'attachments');
|
||||
$mimes = config('larablog.allowed_attachment_mimes', []);
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('article_id')
|
||||
->label(__('admin.fields.article'))
|
||||
->relationship('article', 'title')
|
||||
->searchable()
|
||||
->preload(),
|
||||
FileUpload::make('upload')
|
||||
->label(__('admin.fields.upload'))
|
||||
->disk($disk)
|
||||
->directory(fn (): string => 'uploads/'.now()->format('Y/m'))
|
||||
->visibility('public')
|
||||
->acceptedFileTypes($mimes)
|
||||
->maxSize(20480)
|
||||
->required(fn (string $operation): bool => $operation === 'create')
|
||||
->dehydrated(fn ($state): bool => filled($state))
|
||||
->helperText(__('admin.helpers.attachment_upload')),
|
||||
TextInput::make('filename')
|
||||
->label(__('admin.fields.filename'))
|
||||
->maxLength(255),
|
||||
TextInput::make('visibility')
|
||||
->label(__('admin.fields.visibility'))
|
||||
->default('public')
|
||||
->required(),
|
||||
TextInput::make('legacy_filepath')
|
||||
->label(__('admin.fields.legacy_filepath'))
|
||||
->maxLength(255),
|
||||
TextInput::make('downloads')
|
||||
->label(__('admin.fields.downloads'))
|
||||
->numeric()
|
||||
->default(0)
|
||||
->disabled()
|
||||
->dehydrated(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Attachments\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class AttachmentsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('article.title')
|
||||
->label(__('admin.fields.article'))
|
||||
->searchable(),
|
||||
TextColumn::make('disk')
|
||||
->label(__('admin.fields.disk'))
|
||||
->searchable(),
|
||||
TextColumn::make('path')
|
||||
->label(__('admin.fields.path'))
|
||||
->searchable(),
|
||||
TextColumn::make('thumb_path')
|
||||
->label(__('admin.fields.thumb_path'))
|
||||
->searchable(),
|
||||
TextColumn::make('filename')
|
||||
->label(__('admin.fields.filename'))
|
||||
->searchable(),
|
||||
TextColumn::make('mime')
|
||||
->label(__('admin.fields.mime'))
|
||||
->searchable(),
|
||||
TextColumn::make('size')
|
||||
->label(__('admin.fields.size'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('checksum')
|
||||
->label(__('admin.fields.checksum'))
|
||||
->searchable(),
|
||||
TextColumn::make('visibility')
|
||||
->label(__('admin.fields.visibility'))
|
||||
->searchable(),
|
||||
TextColumn::make('legacy_filepath')
|
||||
->label(__('admin.fields.legacy_filepath'))
|
||||
->searchable(),
|
||||
TextColumn::make('synced_at')
|
||||
->label(__('admin.fields.synced_at'))
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
TextColumn::make('downloads')
|
||||
->label(__('admin.fields.downloads'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label(__('admin.fields.created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('admin.fields.updated_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Categories;
|
||||
|
||||
use App\Filament\Resources\Categories\Pages\CreateCategory;
|
||||
use App\Filament\Resources\Categories\Pages\EditCategory;
|
||||
use App\Filament\Resources\Categories\Pages\ListCategories;
|
||||
use App\Filament\Resources\Categories\Schemas\CategoryForm;
|
||||
use App\Filament\Resources\Categories\Tables\CategoriesTable;
|
||||
use App\Models\Category;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Concerns\HasTranslatedLabels;
|
||||
|
||||
class CategoryResource extends Resource
|
||||
{
|
||||
use HasTranslatedLabels;
|
||||
|
||||
protected static ?string $model = Category::class;
|
||||
|
||||
|
||||
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'categories';
|
||||
}
|
||||
|
||||
protected static function modelKey(): string
|
||||
{
|
||||
return 'category';
|
||||
}
|
||||
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return CategoryForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return CategoriesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListCategories::route('/'),
|
||||
'create' => CreateCategory::route('/create'),
|
||||
'edit' => EditCategory::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Categories\Pages;
|
||||
|
||||
use App\Filament\Resources\Categories\CategoryResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCategory extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Categories\Pages;
|
||||
|
||||
use App\Filament\Resources\Categories\CategoryResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCategory extends EditRecord
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Categories\Pages;
|
||||
|
||||
use App\Filament\Resources\Categories\CategoryResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListCategories extends ListRecords
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Categories\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class CategoryForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('admin.fields.name'))
|
||||
->required(),
|
||||
TextInput::make('display_order')
|
||||
->label(__('admin.fields.display_order'))
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
TextInput::make('articles_count')
|
||||
->label(__('admin.fields.articles_count'))
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Categories\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CategoriesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(__('admin.fields.name'))
|
||||
->searchable(),
|
||||
TextColumn::make('display_order')
|
||||
->label(__('admin.fields.display_order'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('articles_count')
|
||||
->label(__('admin.fields.articles_count'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label(__('admin.fields.created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('admin.fields.updated_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Comments;
|
||||
|
||||
use App\Filament\Resources\Comments\Pages\CreateComment;
|
||||
use App\Filament\Resources\Comments\Pages\EditComment;
|
||||
use App\Filament\Resources\Comments\Pages\ListComments;
|
||||
use App\Filament\Resources\Comments\Schemas\CommentForm;
|
||||
use App\Filament\Resources\Comments\Tables\CommentsTable;
|
||||
use App\Models\Comment;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Concerns\HasTranslatedLabels;
|
||||
|
||||
class CommentResource extends Resource
|
||||
{
|
||||
use HasTranslatedLabels;
|
||||
|
||||
protected static ?string $model = Comment::class;
|
||||
|
||||
|
||||
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'comments';
|
||||
}
|
||||
|
||||
protected static function modelKey(): string
|
||||
{
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return CommentForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return CommentsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListComments::route('/'),
|
||||
'create' => CreateComment::route('/create'),
|
||||
'edit' => EditComment::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Comments\Pages;
|
||||
|
||||
use App\Filament\Resources\Comments\CommentResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateComment extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CommentResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Comments\Pages;
|
||||
|
||||
use App\Filament\Resources\Comments\CommentResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditComment extends EditRecord
|
||||
{
|
||||
protected static string $resource = CommentResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Comments\Pages;
|
||||
|
||||
use App\Filament\Resources\Comments\CommentResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListComments extends ListRecords
|
||||
{
|
||||
protected static string $resource = CommentResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Comments\Schemas;
|
||||
|
||||
use App\Models\Comment;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class CommentForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('article_id')
|
||||
->label(__('admin.fields.article'))
|
||||
->relationship('article', 'title')
|
||||
->required(),
|
||||
TextInput::make('author')
|
||||
->label(__('admin.fields.author'))
|
||||
->required(),
|
||||
TextInput::make('url')
|
||||
->label(__('admin.fields.url'))
|
||||
->url(),
|
||||
Textarea::make('content')
|
||||
->label(__('admin.fields.content'))
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
TextInput::make('ip')
|
||||
->label(__('admin.fields.ip')),
|
||||
Select::make('moderation_status')
|
||||
->label(__('admin.fields.moderation_status'))
|
||||
->options([
|
||||
Comment::STATUS_PENDING => __('admin.options.moderation.pending'),
|
||||
Comment::STATUS_PENDING_AI => __('admin.options.moderation.pending_ai'),
|
||||
Comment::STATUS_APPROVED => __('admin.options.moderation.approved'),
|
||||
Comment::STATUS_REJECTED => __('admin.options.moderation.rejected'),
|
||||
Comment::STATUS_NEEDS_HUMAN => __('admin.options.moderation.needs_human'),
|
||||
])
|
||||
->required()
|
||||
->default(Comment::STATUS_PENDING),
|
||||
DateTimePicker::make('published_at')
|
||||
->label(__('admin.fields.published_at')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Comments\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CommentsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('article.title')
|
||||
->label(__('admin.fields.article'))
|
||||
->searchable(),
|
||||
TextColumn::make('author')
|
||||
->label(__('admin.fields.author'))
|
||||
->searchable(),
|
||||
TextColumn::make('url')
|
||||
->label(__('admin.fields.url'))
|
||||
->searchable(),
|
||||
TextColumn::make('ip')
|
||||
->label(__('admin.fields.ip'))
|
||||
->searchable(),
|
||||
TextColumn::make('moderation_status')
|
||||
->label(__('admin.fields.moderation_status'))
|
||||
->searchable(),
|
||||
TextColumn::make('published_at')
|
||||
->label(__('admin.fields.published_at'))
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label(__('admin.fields.created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('admin.fields.updated_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Links;
|
||||
|
||||
use App\Filament\Resources\Links\Pages\CreateLink;
|
||||
use App\Filament\Resources\Links\Pages\EditLink;
|
||||
use App\Filament\Resources\Links\Pages\ListLinks;
|
||||
use App\Filament\Resources\Links\Schemas\LinkForm;
|
||||
use App\Filament\Resources\Links\Tables\LinksTable;
|
||||
use App\Models\Link;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Concerns\HasTranslatedLabels;
|
||||
|
||||
class LinkResource extends Resource
|
||||
{
|
||||
use HasTranslatedLabels;
|
||||
|
||||
protected static ?string $model = Link::class;
|
||||
|
||||
|
||||
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'links';
|
||||
}
|
||||
|
||||
protected static function modelKey(): string
|
||||
{
|
||||
return 'link';
|
||||
}
|
||||
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return LinkForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return LinksTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListLinks::route('/'),
|
||||
'create' => CreateLink::route('/create'),
|
||||
'edit' => EditLink::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Links\Pages;
|
||||
|
||||
use App\Filament\Resources\Links\LinkResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateLink extends CreateRecord
|
||||
{
|
||||
protected static string $resource = LinkResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Links\Pages;
|
||||
|
||||
use App\Filament\Resources\Links\LinkResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditLink extends EditRecord
|
||||
{
|
||||
protected static string $resource = LinkResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Links\Pages;
|
||||
|
||||
use App\Filament\Resources\Links\LinkResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListLinks extends ListRecords
|
||||
{
|
||||
protected static string $resource = LinkResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Links\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class LinkForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('admin.fields.name'))
|
||||
->required(),
|
||||
TextInput::make('url')
|
||||
->label(__('admin.fields.url'))
|
||||
->url()
|
||||
->required(),
|
||||
Textarea::make('note')
|
||||
->label(__('admin.fields.note'))
|
||||
->columnSpanFull(),
|
||||
TextInput::make('display_order')
|
||||
->label(__('admin.fields.display_order'))
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Toggle::make('visible')
|
||||
->label(__('admin.fields.visible'))
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Links\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class LinksTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(__('admin.fields.name'))
|
||||
->searchable(),
|
||||
TextColumn::make('url')
|
||||
->label(__('admin.fields.url'))
|
||||
->searchable(),
|
||||
TextColumn::make('display_order')
|
||||
->label(__('admin.fields.display_order'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
IconColumn::make('visible')
|
||||
->label(__('admin.fields.visible'))
|
||||
->boolean(),
|
||||
TextColumn::make('created_at')
|
||||
->label(__('admin.fields.created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('admin.fields.updated_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Plugins\Pages;
|
||||
|
||||
use App\Filament\Resources\Plugins\PluginResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePlugin extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PluginResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Plugins\Pages;
|
||||
|
||||
use App\Filament\Resources\Plugins\PluginResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPlugin extends EditRecord
|
||||
{
|
||||
protected static string $resource = PluginResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Plugins\Pages;
|
||||
|
||||
use App\Filament\Resources\Plugins\PluginResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPlugins extends ListRecords
|
||||
{
|
||||
protected static string $resource = PluginResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Plugins;
|
||||
|
||||
use App\Filament\Resources\Plugins\Pages\CreatePlugin;
|
||||
use App\Filament\Resources\Plugins\Pages\EditPlugin;
|
||||
use App\Filament\Resources\Plugins\Pages\ListPlugins;
|
||||
use App\Filament\Resources\Plugins\Schemas\PluginForm;
|
||||
use App\Filament\Resources\Plugins\Tables\PluginsTable;
|
||||
use App\Models\Plugin;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PluginResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Plugin::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedPuzzlePiece;
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
public static function canCreate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return PluginForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return PluginsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListPlugins::route('/'),
|
||||
'create' => CreatePlugin::route('/create'),
|
||||
'edit' => EditPlugin::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Plugins\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class PluginForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('admin.fields.name'))
|
||||
->required(),
|
||||
TextInput::make('version')
|
||||
->label(__('admin.fields.version'))
|
||||
->required()
|
||||
->default('1.0.0'),
|
||||
Toggle::make('enabled')
|
||||
->label(__('admin.fields.enabled'))
|
||||
->required(),
|
||||
TextInput::make('path')
|
||||
->label(__('admin.fields.path'))
|
||||
->required(),
|
||||
Textarea::make('config')
|
||||
->label(__('admin.fields.config'))
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Plugins\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PluginsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(__('admin.fields.name'))
|
||||
->searchable(),
|
||||
TextColumn::make('version')
|
||||
->label(__('admin.fields.version'))
|
||||
->searchable(),
|
||||
IconColumn::make('enabled')
|
||||
->label(__('admin.fields.enabled'))
|
||||
->boolean(),
|
||||
TextColumn::make('path')
|
||||
->label(__('admin.fields.path'))
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->label(__('admin.fields.created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('admin.fields.updated_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Stylevars\Pages;
|
||||
|
||||
use App\Filament\Resources\Stylevars\StylevarResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateStylevar extends CreateRecord
|
||||
{
|
||||
protected static string $resource = StylevarResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Stylevars\Pages;
|
||||
|
||||
use App\Filament\Resources\Stylevars\StylevarResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditStylevar extends EditRecord
|
||||
{
|
||||
protected static string $resource = StylevarResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Stylevars\Pages;
|
||||
|
||||
use App\Filament\Resources\Stylevars\StylevarResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListStylevars extends ListRecords
|
||||
{
|
||||
protected static string $resource = StylevarResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Stylevars\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class StylevarForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('title')
|
||||
->label(__('admin.fields.title'))
|
||||
->required()
|
||||
->maxLength(120),
|
||||
Textarea::make('value')
|
||||
->label(__('admin.fields.value'))
|
||||
->rows(8)
|
||||
->columnSpanFull(),
|
||||
Toggle::make('visible')
|
||||
->label(__('admin.fields.visible'))
|
||||
->default(true),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Stylevars;
|
||||
|
||||
use App\Filament\Resources\Stylevars\Pages\CreateStylevar;
|
||||
use App\Filament\Resources\Stylevars\Pages\EditStylevar;
|
||||
use App\Filament\Resources\Stylevars\Pages\ListStylevars;
|
||||
use App\Filament\Resources\Stylevars\Schemas\StylevarForm;
|
||||
use App\Filament\Resources\Stylevars\Tables\StylevarsTable;
|
||||
use App\Models\Stylevar;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
use App\Filament\Concerns\HasTranslatedLabels;
|
||||
|
||||
class StylevarResource extends Resource
|
||||
{
|
||||
use HasTranslatedLabels;
|
||||
|
||||
protected static ?string $model = Stylevar::class;
|
||||
|
||||
|
||||
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'stylevars';
|
||||
}
|
||||
|
||||
protected static function modelKey(): string
|
||||
{
|
||||
return 'stylevar';
|
||||
}
|
||||
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedDocumentText;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return StylevarForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return StylevarsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListStylevars::route('/'),
|
||||
'create' => CreateStylevar::route('/create'),
|
||||
'edit' => EditStylevar::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Stylevars\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class StylevarsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label(__('admin.fields.id'))
|
||||
->sortable(),
|
||||
TextColumn::make('title')
|
||||
->label(__('admin.fields.title'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('value')
|
||||
->label(__('admin.fields.value'))
|
||||
->limit(60),
|
||||
IconColumn::make('visible')
|
||||
->label(__('admin.fields.visible'))
|
||||
->boolean(),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('admin.fields.updated_at'))
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Tags\Pages;
|
||||
|
||||
use App\Filament\Resources\Tags\TagResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateTag extends CreateRecord
|
||||
{
|
||||
protected static string $resource = TagResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Tags\Pages;
|
||||
|
||||
use App\Filament\Resources\Tags\TagResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTag extends EditRecord
|
||||
{
|
||||
protected static string $resource = TagResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Tags\Pages;
|
||||
|
||||
use App\Filament\Resources\Tags\TagResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTags extends ListRecords
|
||||
{
|
||||
protected static string $resource = TagResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Tags\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class TagForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('admin.fields.name'))
|
||||
->required(),
|
||||
TextInput::make('use_count')
|
||||
->label(__('admin.fields.use_count'))
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Tags\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class TagsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(__('admin.fields.name'))
|
||||
->searchable(),
|
||||
TextColumn::make('use_count')
|
||||
->label(__('admin.fields.use_count'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label(__('admin.fields.created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('admin.fields.updated_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Tags;
|
||||
|
||||
use App\Filament\Resources\Tags\Pages\CreateTag;
|
||||
use App\Filament\Resources\Tags\Pages\EditTag;
|
||||
use App\Filament\Resources\Tags\Pages\ListTags;
|
||||
use App\Filament\Resources\Tags\Schemas\TagForm;
|
||||
use App\Filament\Resources\Tags\Tables\TagsTable;
|
||||
use App\Models\Tag;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Concerns\HasTranslatedLabels;
|
||||
|
||||
class TagResource extends Resource
|
||||
{
|
||||
use HasTranslatedLabels;
|
||||
|
||||
protected static ?string $model = Tag::class;
|
||||
|
||||
|
||||
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'tags';
|
||||
}
|
||||
|
||||
protected static function modelKey(): string
|
||||
{
|
||||
return 'tag';
|
||||
}
|
||||
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return TagForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return TagsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListTags::route('/'),
|
||||
'create' => CreateTag::route('/create'),
|
||||
'edit' => EditTag::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Users\Pages;
|
||||
|
||||
use App\Filament\Resources\Users\UserResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Users\Pages;
|
||||
|
||||
use App\Filament\Resources\Users\UserResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Users\Pages;
|
||||
|
||||
use App\Filament\Resources\Users\UserResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Users\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class UserForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('admin.fields.display_name'))
|
||||
->required()
|
||||
->maxLength(120),
|
||||
TextInput::make('username')
|
||||
->label(__('admin.fields.username'))
|
||||
->required()
|
||||
->maxLength(40)
|
||||
->unique(ignoreRecord: true),
|
||||
TextInput::make('email')
|
||||
->label(__('admin.fields.email'))
|
||||
->email()
|
||||
->maxLength(120)
|
||||
->unique(ignoreRecord: true),
|
||||
TextInput::make('url')
|
||||
->label(__('admin.fields.website'))
|
||||
->url()
|
||||
->maxLength(255),
|
||||
TextInput::make('password')
|
||||
->label(__('admin.fields.password'))
|
||||
->password()
|
||||
->revealable()
|
||||
->dehydrated(fn (?string $state): bool => filled($state))
|
||||
->required(fn (string $operation): bool => $operation === 'create'),
|
||||
Select::make('roles')
|
||||
->label(__('admin.fields.roles'))
|
||||
->multiple()
|
||||
->relationship('roles', 'name')
|
||||
->preload(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Users\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class UsersTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label(__('admin.fields.id'))
|
||||
->sortable(),
|
||||
TextColumn::make('username')
|
||||
->label(__('admin.fields.username'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('name')
|
||||
->label(__('admin.fields.display_name'))
|
||||
->searchable(),
|
||||
TextColumn::make('email')
|
||||
->label(__('admin.fields.email'))
|
||||
->searchable(),
|
||||
TextColumn::make('roles.name')
|
||||
->badge()
|
||||
->label(__('admin.fields.roles')),
|
||||
TextColumn::make('login_at')
|
||||
->label(__('admin.fields.login_at'))
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label(__('admin.fields.created_at'))
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Users;
|
||||
|
||||
use App\Filament\Resources\Users\Pages\CreateUser;
|
||||
use App\Filament\Resources\Users\Pages\EditUser;
|
||||
use App\Filament\Resources\Users\Pages\ListUsers;
|
||||
use App\Filament\Resources\Users\Schemas\UserForm;
|
||||
use App\Filament\Resources\Users\Tables\UsersTable;
|
||||
use App\Models\User;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
use App\Filament\Concerns\HasTranslatedLabels;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
use HasTranslatedLabels;
|
||||
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
|
||||
|
||||
|
||||
protected static function navKey(): string
|
||||
{
|
||||
return 'users';
|
||||
}
|
||||
|
||||
protected static function modelKey(): string
|
||||
{
|
||||
return 'user';
|
||||
}
|
||||
|
||||
protected static function groupKey(): string
|
||||
{
|
||||
return 'system';
|
||||
}
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedUsers;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return UserForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return UsersTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListUsers::route('/'),
|
||||
'create' => CreateUser::route('/create'),
|
||||
'edit' => EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user