Add all files
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Str;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources\KnowledgeBaseResource\Pages;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Models\Article;
|
||||
|
||||
class KnowledgeBaseResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Article::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-m-academic-cap';
|
||||
|
||||
protected static ?string $navigationGroup = 'Documentation';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\Select::make('category_id')
|
||||
->label('Category')
|
||||
->relationship('category', 'name')
|
||||
->searchable()
|
||||
->required()
|
||||
->createOptionForm([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('slug')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Textarea::make('description'),
|
||||
])
|
||||
->placeholder('Select a category'),
|
||||
|
||||
Forms\Components\TextInput::make('title')
|
||||
->label('Title')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
|
||||
if (($get('slug') ?? '') !== Str::slug($old)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$set('slug', Str::slug($state));
|
||||
})
|
||||
->placeholder('Fill in the name for your article'),
|
||||
|
||||
Forms\Components\TextInput::make('slug')
|
||||
->label('Slug')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->placeholder('Fill in the name for your article'),
|
||||
|
||||
Forms\Components\TextInput::make('description')
|
||||
->label('Description')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->placeholder('Small description'),
|
||||
|
||||
Forms\Components\DateTimePicker::make('published_at')
|
||||
->label('Published at')
|
||||
->required()
|
||||
->placeholder('Date and time of publishment'),
|
||||
|
||||
Forms\Components\Toggle::make('is_active')
|
||||
->label('Published')
|
||||
->default(false),
|
||||
|
||||
Forms\Components\RichEditor::make('content')
|
||||
->columnSpanFull()
|
||||
->label('Content')
|
||||
->required()
|
||||
->placeholder('Enter the content of the article'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('category.name')
|
||||
->label('Category')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('published_at')
|
||||
->searchable()
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\IconColumn::make('is_active')
|
||||
->label('Published')
|
||||
->boolean()
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListKnowledgeBase::route('/'),
|
||||
'create' => Pages\CreateKnowledgeBase::route('/create'),
|
||||
'edit' => Pages\EditKnowledgeBase::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources\KnowledgeBaseResource\Pages;
|
||||
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources\KnowledgeBaseResource;
|
||||
|
||||
class CreateKnowledgeBase extends CreateRecord
|
||||
{
|
||||
protected static string $resource = KnowledgeBaseResource::class;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources\KnowledgeBaseResource\Pages;
|
||||
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources\KnowledgeBaseResource;
|
||||
|
||||
class EditKnowledgeBase extends EditRecord
|
||||
{
|
||||
protected static string $resource = KnowledgeBaseResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources\KnowledgeBaseResource\Pages;
|
||||
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources\KnowledgeBaseResource;
|
||||
|
||||
class ListKnowledgeBase extends ListRecords
|
||||
{
|
||||
protected static string $resource = KnowledgeBaseResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
104
extensions/Others/KnowledgeBase/KnowledgeBase.php
Normal file
104
extensions/Others/KnowledgeBase/KnowledgeBase.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase;
|
||||
|
||||
use App\Classes\Extension\Extension;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Admin\Resources\KnowledgeBaseResource;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Models\Article;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Models\Category;
|
||||
|
||||
class KnowledgeBase extends Extension
|
||||
{
|
||||
public function getConfig($values = [])
|
||||
{
|
||||
try {
|
||||
return [
|
||||
[
|
||||
'name' => 'Notice',
|
||||
'type' => 'placeholder',
|
||||
'label' => new HtmlString(
|
||||
'Use this extension to create articles to help your customers without needing to chat with them.
|
||||
Make and manage your articles at <a class="text-primary-600" href="' . KnowledgeBaseResource::getUrl() . '">KnowledgeBase</a>.
|
||||
<br><br><small><i>Do you need support? Join our discord server at <a href="https://discord.gg/V3nJ3jVURN">https://discord.gg/V3nJ3jVURN</a></small></i>'
|
||||
),
|
||||
],
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
[
|
||||
'name' => 'Notice',
|
||||
'type' => 'placeholder',
|
||||
'label' => new HtmlString(
|
||||
'Use this extension to create articles to help your customers without needing to chat with them. You need to enable it before you can use it.
|
||||
<br><br><small><i>Do you need support? Join our discord server at <a href="https://discord.gg/V3nJ3jVURN">https://discord.gg/V3nJ3jVURN</a></small></i>'
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function enabled()
|
||||
{
|
||||
|
||||
Artisan::call('migrate', [
|
||||
'--path' => 'extensions/Others/KnowledgeBase/database/migrations',
|
||||
'--force' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
|
||||
require __DIR__ . '/routes/web.php';
|
||||
View::addNamespace('knowledgebase', __DIR__ . '/resources/views');
|
||||
|
||||
// Livewire componenten
|
||||
\Livewire\Livewire::component('knowledgebase.index', \Paymenter\Extensions\Others\KnowledgeBase\Livewire\Index::class);
|
||||
\Livewire\Livewire::component('knowledgebase.show', \Paymenter\Extensions\Others\KnowledgeBase\Livewire\Show::class);
|
||||
\Livewire\Livewire::component('knowledgebase.category', \Paymenter\Extensions\Others\KnowledgeBase\Livewire\Category::class);
|
||||
\Livewire\Livewire::component('knowledgebase.widget', \Paymenter\Extensions\Others\KnowledgeBase\Livewire\Widget::class);
|
||||
|
||||
// adminpagina Os
|
||||
Event::listen('navigation', function () {
|
||||
if (Article::where('is_active', true)->count() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => 'Documentation',
|
||||
'route' => 'knowledgebase.index',
|
||||
'icon' => 'ri-book-open',
|
||||
'separator' => true,
|
||||
'children' => [],
|
||||
];
|
||||
});
|
||||
|
||||
// Homepage
|
||||
Event::listen('pages.home', function () {
|
||||
return [
|
||||
'view' => view('knowledgebase::index', [
|
||||
'articles' => Article::where('is_active', true)
|
||||
->orderBy('published_at', 'desc')
|
||||
->get(),
|
||||
'categories' => Category::all(),
|
||||
]),
|
||||
];
|
||||
});
|
||||
|
||||
// klanten Dashboard
|
||||
Event::listen('pages.dashboard', function () {
|
||||
return [
|
||||
'view' => view('knowledgebase::widget', [
|
||||
'articles' => Article::where('is_active', true)
|
||||
->orderBy('published_at', 'desc')
|
||||
->take(3)
|
||||
->get(),
|
||||
]),
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Livewire\KnowledgeBase;
|
||||
|
||||
use Livewire\Component;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Models\Article;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public function mount()
|
||||
{
|
||||
if (Article::where('is_active', true)->where('published_at', '<=', now())->count() == 0) {
|
||||
return abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('knowledgebase::index', [
|
||||
'articles' => Article::where('is_active', true)
|
||||
->where('published_at', '<=', now())
|
||||
->latest('published_at')
|
||||
->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Livewire\KnowledgeBase;
|
||||
|
||||
use Livewire\Component;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Models\Article;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public Article $article;
|
||||
|
||||
public function mount(Article $article)
|
||||
{
|
||||
if (!$article->is_active || $article->published_at > now()) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$this->article = $article;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('knowledgebase::show', [
|
||||
'article' => $this->article,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Livewire\KnowledgeBase;
|
||||
|
||||
use Livewire\Component;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Models\Article;
|
||||
|
||||
class Widget extends Component
|
||||
{
|
||||
public function mount()
|
||||
{
|
||||
if (Article::where('is_active', true)->where('published_at', '<=', now())->count() == 0) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('knowledgebase::widget', [
|
||||
'articles' => Article::where('is_active', true)
|
||||
->where('published_at', '<=', now())
|
||||
->latest('published_at')
|
||||
->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
extensions/Others/KnowledgeBase/Models/Article.php
Normal file
36
extensions/Others/KnowledgeBase/Models/Article.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Article extends Model
|
||||
{
|
||||
protected $table = 'ext_kb_articles';
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'content',
|
||||
'description',
|
||||
'published_at',
|
||||
'is_active',
|
||||
'slug',
|
||||
'category_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'published_at' => 'datetime',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
public function getRouteKeyName()
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
}
|
||||
22
extensions/Others/KnowledgeBase/Models/Category.php
Normal file
22
extensions/Others/KnowledgeBase/Models/Category.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Others\KnowledgeBase\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
protected $table = 'ext_kb_categories';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
];
|
||||
|
||||
public function articles(): HasMany
|
||||
{
|
||||
return $this->hasMany(Article::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ext_kb_categories', function (Blueprint $table) {
|
||||
$table->engine = 'InnoDB';
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('slug')->unique();
|
||||
$table->text('description')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ext_kb_categories');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ext_kb_articles', function (Blueprint $table) {
|
||||
$table->engine = 'InnoDB';
|
||||
$table->id();
|
||||
$table->foreignId('category_id')
|
||||
->constrained('ext_kb_categories')
|
||||
->onDelete('cascade');
|
||||
$table->string('title');
|
||||
$table->string('description');
|
||||
$table->text('content');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->dateTime('published_at')->nullable();
|
||||
$table->string('slug')->unique();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ext_kb_articles');
|
||||
}
|
||||
};
|
||||
116
extensions/Others/KnowledgeBase/resources/views/index.blade.php
Normal file
116
extensions/Others/KnowledgeBase/resources/views/index.blade.php
Normal file
@@ -0,0 +1,116 @@
|
||||
@if(count($articles) > 0)
|
||||
<div class="mx-auto container mt-8">
|
||||
@php
|
||||
$onIndexPage = Route::currentRouteName() === 'knowledgebase.index';
|
||||
$displayedArticles = $onIndexPage ? $articles : $articles->take(3);
|
||||
@endphp
|
||||
|
||||
{{-- Header Section --}}
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="bg-success/10 text-success p-3 rounded-full shadow-md">
|
||||
<x-ri-book-open-fill class="size-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-color-base">{{ __('Documentation') }}</h1>
|
||||
<p class="text-color-muted text-sm">{{ __('Explore guides, runbooks, and product tutorials.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden md:flex items-center gap-2 text-color-muted">
|
||||
<x-ri-time-fill class="size-4" />
|
||||
<span class="text-sm">{{ count($articles) }} {{ Str::plural('article', count($articles)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Documentation Grid --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@foreach($displayedArticles as $index => $article)
|
||||
<div class="relative group bg-gradient-to-br from-background-secondary/50 to-background-secondary/30 border border-neutral/50 rounded-2xl shadow-lg hover:shadow-xl transform transition-all duration-300 hover:scale-[1.02] overflow-hidden animate-fade-in-up" style="animation-delay: {{ $index * 0.1 }}s;">
|
||||
{{-- Card Header --}}
|
||||
<div class="p-6 pb-4">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="size-2 bg-success rounded-full animate-pulse"></div>
|
||||
<span class="inline-flex items-center gap-1 text-xs font-medium text-success uppercase tracking-wide">
|
||||
<x-ri-book-open-fill class="size-3" />
|
||||
{{ $article->category->name ?? __('General') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-color-muted group-hover:text-success transition-colors duration-300">
|
||||
<x-ri-arrow-right-up-fill class="size-4 transform transition-transform duration-300 group-hover:scale-110" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-bold text-color-base mb-3 group-hover:text-success transition-colors duration-300 line-clamp-2">
|
||||
{{ $article->title }}
|
||||
</h2>
|
||||
|
||||
<div class="text-color-muted text-sm mb-4 line-clamp-3 leading-relaxed">
|
||||
{{ $article->description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Card Footer --}}
|
||||
<div class="px-6 pb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2 text-color-muted text-xs">
|
||||
<x-ri-calendar-fill class="size-3" />
|
||||
<span>{{ optional($article->published_at)->format('M d, Y') ?? $article->created_at->format('M d, Y') }}</span>
|
||||
</div>
|
||||
|
||||
<a href="{{ route('knowledgebase.show', $article) }}" wire:navigate class="group/btn inline-flex items-center gap-2 bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg font-medium transition-all duration-300 hover:shadow-lg hover:scale-105">
|
||||
{{ __('general.view') }}
|
||||
<x-ri-arrow-right-fill class="size-4 transform transition-transform duration-300 group-hover/btn:translate-x-1" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Hover Overlay --}}
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-success/5 to-success/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if(!$onIndexPage)
|
||||
{{-- View More Button --}}
|
||||
<div class="mt-10 text-center">
|
||||
<a href="{{ route('knowledgebase.index') }}" class="inline-flex items-center gap-2 bg-primary hover:bg-primary/90 text-white px-6 py-3 rounded-lg font-medium transition-all duration-300 hover:shadow-lg hover:scale-105">
|
||||
{{ __('View More Articles') }}
|
||||
<x-ri-arrow-right-line class="size-5 transform transition-transform duration-300 hover:translate-x-1" />
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<style>
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
animation: fadeInUp 0.6s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
@endif
|
||||
168
extensions/Others/KnowledgeBase/resources/views/show.blade.php
Normal file
168
extensions/Others/KnowledgeBase/resources/views/show.blade.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<div class="mx-auto container mt-8 mb-12">
|
||||
<div class="mb-6">
|
||||
<a href="{{ route('knowledgebase.index') }}" wire:navigate
|
||||
class="inline-flex items-center text-primary hover:text-primary-dark transition-colors duration-200 group">
|
||||
<x-ri-arrow-left-line class="size-4 mr-2 transform transition-transform duration-300 group-hover:-translate-x-1" />
|
||||
<span class="font-medium">{{ __('Back to Documentation') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-background-secondary border border-neutral rounded-2xl shadow-xl overflow-hidden">
|
||||
<div class="relative p-6 sm:p-8 lg:p-10">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-success/5 to-success/10 opacity-70 z-0"></div>
|
||||
<div class="relative z-10 flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div class="flex-1 space-y-3">
|
||||
<div class="inline-flex items-center gap-2 bg-success/10 text-success px-3 py-1 rounded-full text-xs font-semibold uppercase tracking-wide">
|
||||
<x-ri-book-open-fill class="size-4" />
|
||||
<span>{{ $article->category->name ?? __('General') }}</span>
|
||||
</div>
|
||||
<h1 class="text-3xl sm:text-4xl font-extrabold text-color-base leading-tight">
|
||||
{{ $article->title }}
|
||||
</h1>
|
||||
<div class="flex flex-wrap items-center gap-4 text-color-muted text-sm">
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<x-ri-calendar-line class="size-4" />
|
||||
{{ optional($article->published_at)->format('F d, Y') ?? $article->created_at->format('F d, Y') }}
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<x-ri-time-line class="size-4" />
|
||||
{{ optional($article->published_at)->diffForHumans() ?? $article->created_at->diffForHumans() }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 sm:p-8 lg:p-10 prose dark:prose-invert max-w-full text-color-base leading-relaxed" id="article-content">
|
||||
{!! $article->content !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.code-block-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-code-button {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
padding: 6px 12px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.copy-code-button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.copy-code-button.copied {
|
||||
background-color: rgba(34, 197, 94, 0.8);
|
||||
border-color: rgba(34, 197, 94, 1);
|
||||
}
|
||||
|
||||
.dark .copy-code-button {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.dark .copy-code-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.dark .copy-code-button.copied {
|
||||
background-color: rgba(34, 197, 94, 0.3);
|
||||
border-color: rgba(34, 197, 94, 0.6);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Find all code blocks within pre tags
|
||||
const codeBlocks = document.querySelectorAll('#article-content pre');
|
||||
|
||||
codeBlocks.forEach((preBlock) => {
|
||||
// Create wrapper if not already wrapped
|
||||
if (!preBlock.parentElement.classList.contains('code-block-wrapper')) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'code-block-wrapper';
|
||||
preBlock.parentNode.insertBefore(wrapper, preBlock);
|
||||
wrapper.appendChild(preBlock);
|
||||
}
|
||||
|
||||
const wrapper = preBlock.parentElement;
|
||||
|
||||
// Create copy button
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.className = 'copy-code-button';
|
||||
copyButton.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
<span class="button-text">Copy</span>
|
||||
`;
|
||||
|
||||
copyButton.addEventListener('click', async function() {
|
||||
// Get the code content
|
||||
const codeElement = preBlock.querySelector('code') || preBlock;
|
||||
const code = codeElement.textContent;
|
||||
|
||||
try {
|
||||
// Copy to clipboard
|
||||
await navigator.clipboard.writeText(code);
|
||||
|
||||
// Update button to show success
|
||||
copyButton.classList.add('copied');
|
||||
const buttonText = copyButton.querySelector('.button-text');
|
||||
const originalText = buttonText.textContent;
|
||||
buttonText.textContent = 'Copied!';
|
||||
|
||||
copyButton.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
<span class="button-text">Copied!</span>
|
||||
`;
|
||||
|
||||
// Reset button after 2 seconds
|
||||
setTimeout(() => {
|
||||
copyButton.classList.remove('copied');
|
||||
copyButton.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
<span class="button-text">Copy</span>
|
||||
`;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy code:', err);
|
||||
const buttonText = copyButton.querySelector('.button-text');
|
||||
buttonText.textContent = 'Failed';
|
||||
|
||||
setTimeout(() => {
|
||||
buttonText.textContent = 'Copy';
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
// Add button to wrapper
|
||||
wrapper.appendChild(copyButton);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,40 @@
|
||||
@if($articles->count() > 10000000)
|
||||
<!-- hidden -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="bg-background-secondary border border-neutral p-2 rounded-lg">
|
||||
|
||||
</div>
|
||||
<h2 class="text-xl font-semibold">{{ __('Knowledge Base') }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
@foreach($articles as $article)
|
||||
<a href="{{ route('knowledgebase.show', $article) }}" wire:navigate>
|
||||
<div
|
||||
class="bg-background-secondary hover:bg-background-secondary/80 border border-neutral p-4 rounded-lg mb-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="bg-secondary/10 p-2 rounded-lg">
|
||||
<x-ri-bookmark-line class="size-5 fill-secondary" />
|
||||
</div>
|
||||
<span class="font-medium">{{ $article->title }}</span>
|
||||
</div>
|
||||
<div class="prose dark:prose-invert text-sm">
|
||||
{{ $article->published_at->diffForHumans() }}
|
||||
</div>
|
||||
</div>
|
||||
<p class="prose dark:prose-invert text-sm">{{ $article->description }}</p>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
<x-navigation.link
|
||||
class="bg-background-secondary hover:bg-background-secondary/80 border border-neutral flex items-center justify-center rounded-lg"
|
||||
:href="route('knowledgebase.index')">
|
||||
{{ __('dashboard.view_all') }}
|
||||
<x-ri-arrow-right-fill class="size-5" />
|
||||
</x-navigation.link>
|
||||
</div>
|
||||
@endif
|
||||
10
extensions/Others/KnowledgeBase/routes/web.php
Normal file
10
extensions/Others/KnowledgeBase/routes/web.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Livewire\KnowledgeBase\Index;
|
||||
use Paymenter\Extensions\Others\KnowledgeBase\Livewire\KnowledgeBase\Show;
|
||||
|
||||
Route::group(['middleware' => ['web']], function () {
|
||||
Route::get('/documentation', Index::class)->name('knowledgebase.index');
|
||||
Route::get('/documentation/{article:slug}', Show::class)->name('knowledgebase.show');
|
||||
});
|
||||
Reference in New Issue
Block a user