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:
ak
2026-08-12 01:15:38 +08:00
commit 263b98b218
337 changed files with 31393 additions and 0 deletions
@@ -0,0 +1,82 @@
<x-filament-panels::page>
<style>
.lb-admin-card-grid {
display: grid;
gap: 1.25rem;
grid-template-columns: repeat(1, minmax(0, 1fr));
align-items: stretch;
}
@media (min-width: 768px) {
.lb-admin-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1280px) {
.lb-admin-card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.lb-admin-card-grid > .lb-admin-card {
min-width: 0;
height: 100%;
}
.lb-admin-card-grid .fi-section {
height: 100%;
}
</style>
@if (count($plugins) === 0)
<x-filament::section>
<x-filament::empty-state
:heading="__('admin.pages.no_plugins')"
icon="heroicon-o-puzzle-piece"
/>
</x-filament::section>
@else
<div class="lb-admin-card-grid">
@foreach ($plugins as $plugin)
<div class="lb-admin-card">
<x-filament::section
:heading="$plugin['title']"
:description="$plugin['name'].' · '.__('admin.pages.version').' '.$plugin['version']"
:icon="$plugin['enabled'] ? 'heroicon-o-check-circle' : 'heroicon-o-puzzle-piece'"
:icon-color="$plugin['enabled'] ? 'success' : 'gray'"
>
<x-slot name="afterHeader">
@if ($plugin['enabled'])
<x-filament::badge color="success">{{ __('admin.pages.enabled') }}</x-filament::badge>
@else
<x-filament::badge color="gray">{{ __('admin.pages.disabled') }}</x-filament::badge>
@endif
</x-slot>
<p class="text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $plugin['description'] }}
</p>
@if (! empty($plugin['requires']))
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
{{ __('admin.pages.plugin_requires') }}:
{{ implode(', ', $plugin['requires']) }}
</p>
@endif
<x-slot name="footer">
<div class="flex flex-wrap justify-end gap-2">
@if ($plugin['has_docs'] ?? false)
<x-filament::button color="gray" size="sm" outlined wire:click="showDocs('{{ $plugin['name'] }}')">
{{ __('admin.pages.plugin_docs') }}
</x-filament::button>
@endif
@if ($plugin['enabled'])
<x-filament::button color="gray" size="sm" wire:click="disable('{{ $plugin['name'] }}')">
{{ __('admin.pages.disable') }}
</x-filament::button>
@else
<x-filament::button size="sm" wire:click="enable('{{ $plugin['name'] }}')">
{{ __('admin.pages.enable') }}
</x-filament::button>
@endif
</div>
</x-slot>
</x-filament::section>
</div>
@endforeach
</div>
@endif
</x-filament-panels::page>
@@ -0,0 +1,130 @@
<x-filament-panels::page>
<style>
.lb-admin-card-grid {
display: grid;
gap: 1.25rem;
grid-template-columns: repeat(1, minmax(0, 1fr));
align-items: stretch;
}
@media (min-width: 768px) {
.lb-admin-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1280px) {
.lb-admin-card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.lb-admin-card-grid > .lb-admin-card {
min-width: 0;
height: 100%;
}
.lb-admin-card-grid .fi-section {
height: 100%;
}
.lb-admin-card-preview {
overflow: hidden;
border-radius: 0.75rem;
background: rgba(128, 128, 128, 0.08);
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
}
.lb-admin-card-preview__frame {
aspect-ratio: 16 / 10;
}
.lb-admin-card-preview__frame img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.lb-admin-card-preview__empty {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
font-size: 0.875rem;
color: rgba(128, 128, 128, 0.9);
}
</style>
@if (count($themes) === 0)
<x-filament::section>
<x-filament::empty-state
:heading="__('admin.pages.no_themes')"
icon="heroicon-o-swatch"
/>
</x-filament::section>
@else
<div class="lb-admin-card-grid">
@foreach ($themes as $theme)
@php
$isActive = $theme['slug'] === $activeTheme;
$slotColor = match ($theme['slots_status'] ?? 'undeclared') {
'full' => 'success',
'partial', 'mismatch' => 'warning',
default => 'danger',
};
@endphp
<div class="lb-admin-card">
<x-filament::section
:heading="$theme['title']"
:description="$theme['slug'].' · '.__('admin.pages.version').' '.$theme['version']"
:icon="$isActive ? 'heroicon-o-check-badge' : 'heroicon-o-swatch'"
:icon-color="$isActive ? 'primary' : 'gray'"
>
<x-slot name="afterHeader">
@if ($isActive)
<x-filament::badge color="primary">{{ __('admin.pages.current') }}</x-filament::badge>
@endif
<x-filament::badge :color="$slotColor">
{{ $theme['slots_label'] ?? __('admin.slots.status_undeclared') }}
@if (($theme['slots_declared_count'] ?? 0) > 0)
· {{ $theme['slots_declared_count'] }}
@endif
</x-filament::badge>
</x-slot>
<div style="display:grid;gap:0.75rem">
<div class="lb-admin-card-preview">
<div class="lb-admin-card-preview__frame">
@if (! empty($theme['preview']))
<img
src="{{ $theme['preview'] }}"
alt="{{ $theme['title'] }} {{ __('admin.pages.preview') }}"
>
@else
<div class="lb-admin-card-preview__empty">
{{ __('admin.pages.preview') }}
</div>
@endif
</div>
</div>
<p class="text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $theme['description'] }}
</p>
@if (! empty($theme['slots_missing']))
<p class="text-xs text-warning-600 dark:text-warning-400">
{{ __('admin.messages.slots_missing_prefix') }}{{ implode(', ', array_slice($theme['slots_missing'], 0, 6)) }}{{ count($theme['slots_missing']) > 6 ? '…' : '' }}
</p>
@endif
</div>
<x-slot name="footer">
<div class="flex items-center justify-between gap-2">
<x-filament::link href="{{ url('/') }}" target="_blank" size="sm">
{{ __('admin.pages.preview') }}
</x-filament::link>
@if ($isActive)
<x-filament::badge color="success">{{ __('admin.pages.current') }}</x-filament::badge>
@else
<x-filament::button size="sm" wire:click="activate('{{ $theme['slug'] }}')">
{{ __('admin.pages.activate') }}
</x-filament::button>
@endif
</div>
</x-slot>
</x-filament::section>
</div>
@endforeach
</div>
@endif
</x-filament-panels::page>
@@ -0,0 +1,20 @@
<x-filament-panels::page>
<x-filament::section
:heading="$pluginTitle"
:description="$pluginName"
icon="heroicon-o-puzzle-piece"
>
<x-slot name="afterHeader">
@if ($enabled)
<x-filament::badge color="success">{{ __('admin.pages.enabled') }}</x-filament::badge>
@else
<x-filament::badge color="gray">{{ __('admin.pages.disabled') }}</x-filament::badge>
@endif
</x-slot>
<div class="space-y-3 text-sm leading-6 text-gray-600 dark:text-gray-300">
<p>{{ $pluginDescription }}</p>
<p class="text-gray-500 dark:text-gray-400">{{ __('admin.pages.skeleton_note') }}</p>
</div>
</x-filament::section>
</x-filament-panels::page>
@@ -0,0 +1,4 @@
<x-filament-panels::page>
{{-- SiteSettings renders via Page::content() schema; this view is unused fallback. --}}
{{ $this->content }}
</x-filament-panels::page>
@@ -0,0 +1,5 @@
<div class="fi-clear-cache-button">
{{ $this->clearCacheAction }}
<x-filament-actions::modals />
</div>
+25
View File
@@ -0,0 +1,25 @@
<?php
/** @var \Illuminate\Support\Collection|\App\Models\Article[] $articles */
$access = app(\App\Domain\Blog\ArticleAccess::class);
?>
<rss version="2.0">
<channel>
<title>{{ $settings->site_name ?? config('app.name') }}</title>
<link>{{ url('/') }}</link>
<description>{{ $settings->site_description ?? '' }}</description>
<language>{{ app()->getLocale() }}</language>
@foreach($articles as $article)
@php
$decision = $access->resolve($article, null, []);
$body = $access->publicHtml($article, $decision);
@endphp
<item>
<title>{{ $article->title }}</title>
<link>{{ url('/show-'.$article->id.'.shtml') }}</link>
<guid isPermaLink="true">{{ url('/show-'.$article->id.'.shtml') }}</guid>
<pubDate>{{ optional($article->published_at)->toRfc2822String() }}</pubDate>
<description><![CDATA[{!! $body !!}]]></description>
</item>
@endforeach
</channel>
</rss>
File diff suppressed because one or more lines are too long