feat: restore sablog browse modes and clean imported HTML
Keep the public site readable after import (nbsp, highlighter, wrapping code, compact pager) and finish the admin extras already in this tree: sticky levels, preview, page cache, and live llms.txt. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,25 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-09-18
|
||||
|
||||
- Homepage / lists have SaBlog-style **标准 | 列表** browse modes (`home_view_mode`, `?view=`, cookie).
|
||||
- Pagination is compact (`第 x / y 页` + fewer page chips) and the content column uses CSS grid so it no longer sits under the sidebar.
|
||||
- Imported SaBlog HTML no longer shows literal ` `; highlighter / stacked docker `<div>` blocks become wrapped `<pre><code>`.
|
||||
- `php artisan sablog:fix-content --all` can persist that cleanup. Code no longer stretches the page.
|
||||
- Schema snapshot: `docs/database/mysql-schema-20260918-211824.sql`.
|
||||
- Frontend RSS / Sitemap / llms.txt links now open in a new tab.
|
||||
- `/llms.txt` is a core live route (plus `/posts/{slug}.md`) so agents can crawl new public posts without a static file.
|
||||
- Fixed edit-post crash: comments relation manager now uses Filament v5 `Filament\Actions\Action`.
|
||||
- Posts now have sticky levels 0–3 (none / light / strong / headline). Headline always stays at the top of public lists.
|
||||
- Edit and list screens have a **预览** button; admin/editor can open unpublished posts on the public site.
|
||||
- Admin top bar **网站** opens the frontend in a new tab. Sidebar **管理** now has **页面缓存** and **语言**.
|
||||
- Schema snapshot: `docs/database/mysql-schema-20260918-205818.sql`.
|
||||
- Post create/edit form is now left content 2 : right tools 1, locked for PC at every breakpoint (not only `lg`).
|
||||
- Slug is full width; category, format, and status share one row as single-choice buttons; cover sits above the taller body editor.
|
||||
- Right column: publish settings, then access settings (password + membership price fields).
|
||||
- Cover / media picker preview is a small thumbnail (max 220×124), not full column width.
|
||||
- Schema snapshot: `docs/database/mysql-schema-20260918-203827.sql`.
|
||||
|
||||
## 2026-09-17
|
||||
|
||||
- Admin top-right now shows the current environment (`local` / `prod`) and a **写文章** shortcut to create a post.
|
||||
|
||||
+43
-1
@@ -12,10 +12,31 @@ Modern blog migrated from SaBlog-X: Filament admin, themes, plugins, sablog impo
|
||||
|
||||
See `README.md` and `docs/` for install, import, plugins, and themes.
|
||||
|
||||
## Post editor layout
|
||||
|
||||
- Create/edit post uses **left 2 : right 1** on PC at every breakpoint (not only `lg`).
|
||||
- Left: title, full-width slug, category + format + status on one row (single-choice buttons), cover thumbnail (not full-width), then a tall body editor.
|
||||
- Right group 1: published at, sticky level (none / light / strong / headline), comment status, excerpt, keywords.
|
||||
- Right group 2: read password; membership plugin adds members-only + price here.
|
||||
- Plugins must inject via `PostForm::appendToSidebar()` / `appendToAccessSection()` so they do not break the 2:1 grid.
|
||||
|
||||
## Sticky levels
|
||||
|
||||
- `sticky_level`: 0 none, 1 light, 2 strong, 3 headline. Headline always stays first on home, category, tag, and search lists.
|
||||
- Saving a post keeps `is_sticky` in sync (`true` when level > 0) so older themes still work.
|
||||
- List badges show the Chinese label, not a generic “sticky”.
|
||||
|
||||
## Post preview
|
||||
|
||||
- Edit page and post table have **预览**, opening `/show-{id}.shtml` in a new tab.
|
||||
- Filament v5 actions are `Filament\Actions\Action`. The comments relation manager on the edit page must boot in tests, because a stale `Tables\Actions\Action` import only fails when that Livewire table renders.
|
||||
- Guests still get 404 for drafts. Admin and editor can open unpublished posts and see a preview banner.
|
||||
|
||||
## Admin chrome
|
||||
|
||||
- Top-right badge shows the current app environment (`local` / `prod` / others).
|
||||
- Top-right **写文章** button jumps to `/admin/posts/create`.
|
||||
- Top-right **网站** opens the public site in a new tab; **写文章** jumps to `/admin/posts/create`.
|
||||
- Sidebar **管理** includes **页面缓存** (view URLs, hit rate, flush) and **语言** (current locale + available packs). Article translations are not built yet; the language page says so.
|
||||
- Sidebar nav is compacted (Filament default `gap-y-7` / `py-8` is too tall for this site).
|
||||
|
||||
## Hello World plugin
|
||||
@@ -24,6 +45,27 @@ See `README.md` and `docs/` for install, import, plugins, and themes.
|
||||
- Plugin boot / hook failures are isolated so one plugin cannot take down the whole admin.
|
||||
- `/hello-world` stays available even if `hello_world_logs` is missing.
|
||||
|
||||
## Frontend feeds
|
||||
|
||||
- Header/footer **RSS / Sitemap / llms.txt** open in a new tab (`target="_blank"`).
|
||||
- `/llms.txt` is a core route (not a static file). Each request lists current published public posts so new articles show up immediately.
|
||||
- Full text for agents: `/posts/{slug}.md`. Password / members-only posts are omitted.
|
||||
- These machine-readable paths are excluded from page cache.
|
||||
|
||||
## Homepage list / full mode
|
||||
|
||||
- Same switch as old SaBlog: **浏览模式: 标准 | 列表**.
|
||||
- `list` = title + excerpt cards. `full` = full rendered body on the home / category / tag / search lists.
|
||||
- Query `?view=list` or `?view=full` wins, then cookie `ll_home_view`, then setting `home_view_mode`.
|
||||
- Page cache key includes the view mode so the two layouts are not mixed.
|
||||
|
||||
## Pagination and imported HTML
|
||||
|
||||
- Public pagers use `onEachSide(1)` plus “第 x / y 页”, and the main column is CSS grid `minmax(0, 1fr)` so page chips stay in the content column.
|
||||
- `SablogHtmlNormalizer` strips leftover ` `, turns SyntaxHighlighter `codeText` / `dp-*` lists into `<pre><code>`, and wraps stacked docker/shell `<div>` lines.
|
||||
- Render path always normalizes, so already-imported posts look right without rewriting the database. Persist with `php artisan sablog:fix-content --all`.
|
||||
- Code blocks wrap / scroll inside the column instead of stretching the page.
|
||||
|
||||
## Media library (xpier)
|
||||
|
||||
- Package: `xpier/filament-media-library` (admin Media Library + MediaPicker).
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
- **脚本迁移**:`php artisan sablog:import` 一键从老库迁入全部数据(文章/分类/评论/标签/友链/用户/附件),保留原始 ID,老 URL 全部 301 兼容
|
||||
- **多主题**:`themes/` 目录即皮肤,后台一键切换/上传 ZIP;内置 sablog 经典风 + modern 简约风两套
|
||||
- **插件系统**:`plugins/` 目录 + 钩子系统(action/filter),ZIP 安装 + 可选远程市场;启用时自动跑插件迁移
|
||||
- **后台顶栏**:右上角显示当前环境(local/prod)+ 一键写文章
|
||||
- **内置插件**:SEO & GEO(JSON-LD/OG/llms.txt/Markdown 导出)、AI 评论审核 + 内容润色(Workerman 异步)、支付(支付宝/微信/沙箱)、会员(套餐订阅 + 付费文章)
|
||||
- **后台顶栏**:右上角显示当前环境(local/prod)+ 打开网站 + 一键写文章
|
||||
- **文章编辑**:左正文 2 : 右功能 1(Slug 整行,分类/格式/状态同一行,封面在正文上方)
|
||||
- **内置插件**:SEO & GEO(JSON-LD/OG)、AI 评论审核 + 内容润色(Workerman 异步)、支付(支付宝/微信/沙箱)、会员(套餐订阅 + 付费文章)
|
||||
- **首页浏览模式**:和老 sablog 一样可切 **标准(全文) / 列表(摘要)**
|
||||
- **Agent 索引**:`/llms.txt`、`/posts/{slug}.md` 是核心路由,随已发布公开文章实时生成
|
||||
- **附件全走 S3**:R2 / COS / OSS 兼容,新上传零落盘,`attachments:sync-s3` 一键同步老附件(大文件 Multipart)
|
||||
|
||||
## 技术栈
|
||||
@@ -59,6 +62,8 @@ php artisan sablog:import ... --convert-markdown
|
||||
|
||||
# 事后单独转换 / 同步附件到 S3
|
||||
php artisan content:convert --all
|
||||
# 已导入正文里的 / 高亮块 / docker 分行,可先渲染时自动清洗,也可落库:
|
||||
php artisan sablog:fix-content --all
|
||||
S3_ACCESS_KEY=... S3_BUCKET=... S3_ENDPOINT=... php artisan attachments:sync-s3 --legacy-dir=/path/to/old/attachments
|
||||
```
|
||||
|
||||
@@ -118,7 +123,7 @@ php artisan workerman:serve stop
|
||||
|
||||
- 匿名 GET 页面整页缓存(首页/列表/文章/归档/标签等),TTL 默认 300 秒(`PAGE_CACHE_TTL`),命中时零数据库查询
|
||||
- 文章浏览量由独立 `/track-view` 端点统计,不受整页缓存影响(实时)
|
||||
- 后台「缓存管理」:清空页面/侧边栏/设置/全部缓存、重建编译缓存
|
||||
- 后台「管理 → 页面缓存」:查看已缓存地址、命中率,清空前台或全部缓存
|
||||
- 侧边栏数据与站点设置另有 1 小时缓存
|
||||
|
||||
## 对外 API
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Reminders
|
||||
|
||||
## Filament v5 actions
|
||||
|
||||
- Table / header / record actions use `Filament\Actions\Action`.
|
||||
- `Filament\Tables\Actions\Action` does not exist. A `use` line only blows up when the class is actually called (for example the comments relation manager on the edit-post page).
|
||||
- After changing Filament pages, boot the Livewire component, not only `GET` the HTML.
|
||||
@@ -34,11 +34,12 @@ class ArchiveController extends Controller
|
||||
$start = Carbon::create((int) $year, (int) $month, 1)->startOfMonth();
|
||||
$end = (clone $start)->endOfMonth();
|
||||
|
||||
$posts = Post::query()
|
||||
->whereBetween('published_at', [$start, $end])
|
||||
->published()
|
||||
->orderByDesc('published_at')
|
||||
->paginate((int) blog_setting('per_page', config('blog.per_page')));
|
||||
$posts = blog_paginate(
|
||||
Post::query()
|
||||
->whereBetween('published_at', [$start, $end])
|
||||
->published()
|
||||
->orderByDesc('published_at')
|
||||
);
|
||||
|
||||
return theme_view('list', compact('posts'))
|
||||
->with('archiveTitle', "{$year} 年 {$month} 月");
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Blog\Controllers;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
@@ -14,10 +12,9 @@ class CategoryController extends Controller
|
||||
{
|
||||
$category = Category::query()->where('slug', $slug)->orWhere('id', (int) $slug)->firstOrFail();
|
||||
|
||||
$posts = $category->posts()
|
||||
->published()
|
||||
->latest('published_at')
|
||||
->paginate((int) blog_setting('per_page', config('blog.per_page')));
|
||||
$posts = blog_paginate(
|
||||
$category->posts()->published()->orderedForList()
|
||||
);
|
||||
|
||||
return theme_view('list', compact('category', 'posts'));
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Blog\Controllers;
|
||||
|
||||
use App\Models\Post;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class FeedController extends Controller
|
||||
{
|
||||
@@ -54,7 +52,9 @@ class FeedController extends Controller
|
||||
foreach ($disallow as $path) {
|
||||
$lines[] = "Disallow: {$path}";
|
||||
}
|
||||
$lines[] = "Sitemap: ".url($sitemap);
|
||||
$lines[] = 'Sitemap: '.url($sitemap);
|
||||
$lines[] = 'Allow: /llms.txt';
|
||||
$lines[] = 'Allow: /posts/*.md';
|
||||
|
||||
return response(implode("\n", $lines)."\n")->header('Content-Type', 'text/plain; charset=utf-8');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Blog\Controllers;
|
||||
|
||||
use App\Models\Post;
|
||||
@@ -11,11 +10,9 @@ class HomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$posts = Post::query()
|
||||
->published()
|
||||
->orderByDesc('is_sticky')
|
||||
->latest('published_at')
|
||||
->paginate((int) blog_setting('per_page', config('blog.per_page')));
|
||||
$posts = blog_paginate(
|
||||
Post::query()->published()->orderedForList()
|
||||
);
|
||||
|
||||
return theme_view('index', compact('posts'));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Blog\Controllers;
|
||||
|
||||
use App\Models\Post;
|
||||
use Illuminate\Support\Str;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
class LlmsController extends Controller
|
||||
{
|
||||
/**
|
||||
* /llms.txt — live index for agents. Not a static file.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$siteName = (string) blog_setting('site_name', config('blog.name'));
|
||||
$siteDescription = (string) blog_setting('site_description', config('blog.description'));
|
||||
|
||||
$posts = Post::query()
|
||||
->published()
|
||||
->latest('published_at')
|
||||
->limit(200)
|
||||
->get();
|
||||
|
||||
$lines = [
|
||||
'# '.$siteName,
|
||||
'',
|
||||
'> '.$siteDescription,
|
||||
'',
|
||||
'## Feeds',
|
||||
'',
|
||||
'- [RSS]('.route('feed.rss').')',
|
||||
'- [Sitemap]('.route('feed.sitemap').')',
|
||||
'- [llms.txt]('.route('feed.llms').')',
|
||||
'',
|
||||
'## Articles',
|
||||
'',
|
||||
];
|
||||
|
||||
foreach ($posts as $post) {
|
||||
if ($this->isRestricted($post)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$summary = Str::limit(trim(preg_replace('/\s+/', ' ', strip_tags($post->excerpt_or_fallback)) ?? ''), 120);
|
||||
$markdownUrl = $this->markdownUrl($post);
|
||||
$htmlUrl = $post->url;
|
||||
$title = $post->title;
|
||||
|
||||
$lines[] = $markdownUrl
|
||||
? '- ['.$title.']('.$markdownUrl.') — '.$summary.' ([html]('.$htmlUrl.'))'
|
||||
: '- ['.$title.']('.$htmlUrl.') — '.$summary;
|
||||
}
|
||||
|
||||
return response(implode("\n", $lines)."\n", 200, [
|
||||
'Content-Type' => 'text/plain; charset=utf-8',
|
||||
'Cache-Control' => 'no-store',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* /posts/{slug}.md — full markdown for a published public post.
|
||||
*/
|
||||
public function postMarkdown(string $slug)
|
||||
{
|
||||
$post = Post::query()
|
||||
->where('slug', $slug)
|
||||
->orWhere('id', (int) $slug)
|
||||
->first();
|
||||
|
||||
abort_unless($post && $post->status === 'published' && ! $this->isRestricted($post), 404);
|
||||
|
||||
$body = $post->content_format === 'markdown'
|
||||
? (string) $post->content
|
||||
: $this->htmlToMarkdown((string) $post->content);
|
||||
|
||||
$out = '# '.$post->title."\n\n";
|
||||
if ($post->excerpt) {
|
||||
$out .= '> '.$post->excerpt."\n\n";
|
||||
}
|
||||
$out .= $body."\n";
|
||||
|
||||
return response($out, 200, [
|
||||
'Content-Type' => 'text/markdown; charset=utf-8',
|
||||
'Cache-Control' => 'no-store',
|
||||
]);
|
||||
}
|
||||
|
||||
private function isRestricted(Post $post): bool
|
||||
{
|
||||
if (filled($post->read_password)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! empty(($post->meta ?? [])['members_only']);
|
||||
}
|
||||
|
||||
private function markdownUrl(Post $post): ?string
|
||||
{
|
||||
$slug = trim((string) $post->slug);
|
||||
|
||||
if ($slug === '') {
|
||||
return route('geo.post.markdown', ['slug' => (string) $post->id]);
|
||||
}
|
||||
|
||||
return route('geo.post.markdown', ['slug' => $slug]);
|
||||
}
|
||||
|
||||
private function htmlToMarkdown(string $html): string
|
||||
{
|
||||
$converter = new HtmlConverter([
|
||||
'strip_tags' => false,
|
||||
'hard_break' => true,
|
||||
]);
|
||||
|
||||
return $converter->convert($html);
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,15 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Blog\Controllers;
|
||||
|
||||
use App\Blog\Services\PostContentRenderer;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
|
||||
class PostController extends Controller
|
||||
{
|
||||
public function __construct(private PostContentRenderer $renderer)
|
||||
{
|
||||
}
|
||||
public function __construct(private PostContentRenderer $renderer) {}
|
||||
|
||||
public function show(string $id, ?string $page = null)
|
||||
{
|
||||
@@ -21,13 +19,16 @@ class PostController extends Controller
|
||||
->orWhere('slug', $id)
|
||||
->first();
|
||||
|
||||
abort_unless($post && $post->status === 'published', 404);
|
||||
abort_unless($post, 404);
|
||||
|
||||
$canPreview = $this->canPreviewUnpublished();
|
||||
abort_unless($post->status === 'published' || $canPreview, 404);
|
||||
|
||||
$this->ensureReadable($post);
|
||||
|
||||
// sablog 式 /show-{id}-{page}.shtml 的评论分页
|
||||
if ($page !== null) {
|
||||
\Illuminate\Pagination\Paginator::currentPageResolver(fn () => max(1, (int) $page));
|
||||
Paginator::currentPageResolver(fn () => max(1, (int) $page));
|
||||
}
|
||||
|
||||
// 浏览量由 /track-view 端点统计(页面整页缓存下控制器不执行,避免双计)
|
||||
@@ -44,9 +45,17 @@ class PostController extends Controller
|
||||
'comments' => $comments,
|
||||
'contentHtml' => $rendered['html'],
|
||||
'toc' => $rendered['toc'],
|
||||
'isPreview' => $post->status !== 'published' && $canPreview,
|
||||
]);
|
||||
}
|
||||
|
||||
private function canPreviewUnpublished(): bool
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
return $user && ($user->isAdmin() || $user->hasRole('editor'));
|
||||
}
|
||||
|
||||
private function ensureReadable(Post $post): void
|
||||
{
|
||||
if (! $post->read_password) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Blog\Controllers;
|
||||
|
||||
use App\Models\Post;
|
||||
@@ -13,11 +12,9 @@ class SearchController extends Controller
|
||||
{
|
||||
$keyword = trim(request('q', request('keyword', '')));
|
||||
|
||||
$posts = Post::query()
|
||||
->published()
|
||||
->search($keyword)
|
||||
->orderByDesc('published_at')
|
||||
->paginate((int) blog_setting('per_page', config('blog.per_page')));
|
||||
$posts = blog_paginate(
|
||||
Post::query()->published()->search($keyword)->orderedForList()
|
||||
);
|
||||
|
||||
return theme_view('search', compact('posts', 'keyword'));
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Blog\Controllers;
|
||||
|
||||
use App\Models\Post;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\Tags\Tag;
|
||||
|
||||
class TagController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$counts = \Illuminate\Support\Facades\DB::table('taggables')
|
||||
$counts = DB::table('taggables')
|
||||
->selectRaw('tag_id, COUNT(*) as total')
|
||||
->where('taggable_type', Post::class)
|
||||
->groupBy('tag_id')
|
||||
@@ -37,11 +37,12 @@ class TagController extends Controller
|
||||
|
||||
abort_unless($tag, 404);
|
||||
|
||||
$posts = Post::query()
|
||||
->whereHas('tags', fn ($q) => $q->where('tags.id', $tag->id))
|
||||
->published()
|
||||
->latest('published_at')
|
||||
->paginate((int) blog_setting('per_page', config('blog.per_page')));
|
||||
$posts = blog_paginate(
|
||||
Post::query()
|
||||
->whereHas('tags', fn ($q) => $q->where('tags.id', $tag->id))
|
||||
->published()
|
||||
->orderedForList()
|
||||
);
|
||||
|
||||
return theme_view('tag', compact('tag', 'posts'));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ declare(strict_types=1);
|
||||
namespace App\Blog\Services;
|
||||
|
||||
use App\Blog\Support\MarkdownMediaEmbed;
|
||||
use App\Blog\Support\SablogHtmlNormalizer;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use League\CommonMark\Environment\Environment;
|
||||
@@ -38,7 +39,7 @@ class PostContentRenderer
|
||||
public function render(Post $post): string
|
||||
{
|
||||
$embed = app(MarkdownMediaEmbed::class);
|
||||
$raw = (string) $post->content;
|
||||
$raw = app(SablogHtmlNormalizer::class)->normalize((string) $post->content);
|
||||
|
||||
$content = $post->content_format === 'markdown'
|
||||
? $this->toHtml($embed->expandMarkdown($raw))
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Blog\Support;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Homepage / list browse mode, matching old SaBlog 标准 | 列表.
|
||||
*/
|
||||
class HomeViewMode
|
||||
{
|
||||
public const LIST = 'list';
|
||||
|
||||
public const FULL = 'full';
|
||||
|
||||
public const COOKIE = 'll_home_view';
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function options(): array
|
||||
{
|
||||
return [
|
||||
self::LIST => '列表',
|
||||
self::FULL => '标准',
|
||||
];
|
||||
}
|
||||
|
||||
public static function isValid(mixed $value): bool
|
||||
{
|
||||
return is_string($value) && in_array($value, [self::LIST, self::FULL], true);
|
||||
}
|
||||
|
||||
public static function resolve(?Request $request = null): string
|
||||
{
|
||||
$request ??= request();
|
||||
|
||||
foreach ([
|
||||
$request->query('view'),
|
||||
$request->cookie(self::COOKIE),
|
||||
blog_setting('home_view_mode', config('blog.home_view_mode', self::LIST)),
|
||||
] as $value) {
|
||||
if (self::isValid($value)) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return self::LIST;
|
||||
}
|
||||
|
||||
public static function current(): string
|
||||
{
|
||||
$request = request();
|
||||
$mode = self::resolve($request);
|
||||
|
||||
if (self::isValid($request->query('view'))) {
|
||||
cookie()->queue(cookie(self::COOKIE, (string) $request->query('view'), 60 * 24 * 30));
|
||||
}
|
||||
|
||||
return $mode;
|
||||
}
|
||||
|
||||
public static function url(string $mode): string
|
||||
{
|
||||
return request()->fullUrlWithQuery(['view' => $mode]);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class PageCacheMiddleware
|
||||
'body' => $response->getContent(),
|
||||
'type' => $response->headers->get('Content-Type') ?: 'text/html; charset=utf-8',
|
||||
], $ttl);
|
||||
self::rememberKey($key);
|
||||
self::rememberKey($key, $request->fullUrl());
|
||||
}
|
||||
|
||||
return $response;
|
||||
@@ -53,8 +53,8 @@ class PageCacheMiddleware
|
||||
|
||||
public static function flush(): void
|
||||
{
|
||||
foreach ((array) Cache::get(self::TAG.'_keys', []) as $key) {
|
||||
Cache::forget($key);
|
||||
foreach (array_keys((array) Cache::get(self::TAG.'_keys', [])) as $key) {
|
||||
Cache::forget((string) $key);
|
||||
}
|
||||
|
||||
Cache::forget(self::TAG.'_keys');
|
||||
@@ -88,16 +88,50 @@ class PageCacheMiddleware
|
||||
Cache::forget(self::TAG.'_misses');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{key: string, url: string, cached_at: string}>
|
||||
*/
|
||||
public static function entries(): array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
foreach ((array) Cache::get(self::TAG.'_keys', []) as $key => $meta) {
|
||||
$entries[] = [
|
||||
'key' => (string) $key,
|
||||
'url' => is_array($meta) ? (string) ($meta['url'] ?? $key) : (string) $key,
|
||||
'cached_at' => is_array($meta) ? (string) ($meta['cached_at'] ?? '') : '',
|
||||
];
|
||||
}
|
||||
|
||||
usort($entries, fn (array $left, array $right): int => strcmp($right['cached_at'], $left['cached_at']));
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public static function forget(string $key): void
|
||||
{
|
||||
Cache::forget($key);
|
||||
$keys = (array) Cache::get(self::TAG.'_keys', []);
|
||||
unset($keys[$key]);
|
||||
Cache::put(self::TAG.'_keys', $keys, now()->addDay());
|
||||
}
|
||||
|
||||
private static function increment(string $metric): void
|
||||
{
|
||||
$key = self::TAG.'_'.$metric;
|
||||
Cache::put($key, (int) Cache::get($key, 0) + 1, now()->addDay());
|
||||
}
|
||||
|
||||
private static function rememberKey(string $key): void
|
||||
private static function rememberKey(string $key, ?string $url = null): void
|
||||
{
|
||||
$keys = (array) Cache::get(self::TAG.'_keys', []);
|
||||
$keys[$key] = true;
|
||||
$previous = $keys[$key] ?? [];
|
||||
|
||||
$keys[$key] = [
|
||||
'url' => $url ?: (is_array($previous) ? (string) ($previous['url'] ?? '') : ''),
|
||||
'cached_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
Cache::put(self::TAG.'_keys', $keys, now()->addDay());
|
||||
}
|
||||
|
||||
@@ -117,16 +151,24 @@ class PageCacheMiddleware
|
||||
return false;
|
||||
}
|
||||
|
||||
// 动态端点
|
||||
// 动态端点、订阅和 agent 索引不进整页缓存,新文章要马上能看到
|
||||
if (str_starts_with($path, 'track-view')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($path, ['rss.xml', 'rss.php', 'sitemap.xml', 'robots.txt', 'llms.txt'], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str_ends_with($path, '.md')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function key(Request $request): string
|
||||
{
|
||||
return self::TAG.':'.md5($request->fullUrl());
|
||||
return self::TAG.':'.md5($request->fullUrl().'|view:'.HomeViewMode::resolve($request));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Blog\Support;
|
||||
|
||||
use App\Blog\Services\PostContentRenderer;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMXPath;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Cleans leftover SaBlog-X HTML so imported posts render without literal
|
||||
* and without SyntaxHighlighter markup stretching the page.
|
||||
*/
|
||||
class SablogHtmlNormalizer
|
||||
{
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function languageAliases(): array
|
||||
{
|
||||
return [
|
||||
'js' => 'javascript',
|
||||
'javascript' => 'javascript',
|
||||
'xml' => 'xml',
|
||||
'html' => 'xml',
|
||||
'xhtml' => 'xml',
|
||||
'css' => 'css',
|
||||
'php' => 'php',
|
||||
'sql' => 'sql',
|
||||
'java' => 'java',
|
||||
'py' => 'python',
|
||||
'python' => 'python',
|
||||
'c#' => 'csharp',
|
||||
'cs' => 'csharp',
|
||||
'bash' => 'bash',
|
||||
'shell' => 'bash',
|
||||
'sh' => 'bash',
|
||||
'text' => 'text',
|
||||
];
|
||||
}
|
||||
|
||||
public function normalize(string $content): string
|
||||
{
|
||||
if ($content === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = $this->decodeNbsp($content);
|
||||
|
||||
if (! preg_match('/<\s*(div|p|ol|ul|li|pre|code|span|br|table)\b/i', $content)) {
|
||||
return $this->recoverFlattenedShell($content);
|
||||
}
|
||||
|
||||
try {
|
||||
$content = $this->transformHtml($content);
|
||||
} catch (Throwable) {
|
||||
// Keep the decoded text when HTML parsing fails.
|
||||
}
|
||||
|
||||
return $this->recoverFlattenedShell($content);
|
||||
}
|
||||
|
||||
public function toMarkdown(string $html, HtmlConverter $converter, PostContentRenderer $renderer): string
|
||||
{
|
||||
return $converter->convert($renderer->toMarkdownSafeTokens($this->normalize($html)));
|
||||
}
|
||||
|
||||
public function plainText(string $content): string
|
||||
{
|
||||
$text = $this->decodeNbsp(strip_tags($content));
|
||||
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$text = str_replace("\xC2\xA0", ' ', $text);
|
||||
$text = preg_replace('/\s+/u', ' ', $text) ?? $text;
|
||||
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
public function decodeNbsp(string $content): string
|
||||
{
|
||||
$content = str_replace(
|
||||
['&nbsp;', ' ', ' ', ' ', ' ', "\xC2\xA0"],
|
||||
' ',
|
||||
$content
|
||||
);
|
||||
|
||||
return str_replace('&nbsp;', ' ', $content);
|
||||
}
|
||||
|
||||
private function transformHtml(string $html): string
|
||||
{
|
||||
$document = $this->loadDocument($html);
|
||||
$xpath = new DOMXPath($document);
|
||||
|
||||
$this->convertHighlighterBlocks($document, $xpath);
|
||||
$this->convertHighlighterLists($document, $xpath);
|
||||
$this->convertStackedCodeDivs($document);
|
||||
$this->removeEmptyBlocks($xpath);
|
||||
|
||||
return $this->innerHtml($document);
|
||||
}
|
||||
|
||||
private function convertHighlighterBlocks(DOMDocument $document, DOMXPath $xpath): void
|
||||
{
|
||||
$nodes = [];
|
||||
foreach ($xpath->query('//*[contains(concat(" ", normalize-space(@class), " "), " codeText ")]') ?: [] as $node) {
|
||||
if ($node instanceof DOMElement) {
|
||||
$nodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
if ($node->parentNode === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->replaceWithPre($document, $xpath, $node);
|
||||
}
|
||||
}
|
||||
|
||||
private function convertHighlighterLists(DOMDocument $document, DOMXPath $xpath): void
|
||||
{
|
||||
$lists = [];
|
||||
foreach ($xpath->query('//ol[contains(@class, "dp-")]') ?: [] as $node) {
|
||||
if (! $node instanceof DOMElement || $node->parentNode === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->closestTag($node, 'pre') !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lists[] = $node;
|
||||
}
|
||||
|
||||
foreach ($lists as $list) {
|
||||
$this->replaceWithPre($document, $xpath, $list);
|
||||
}
|
||||
}
|
||||
|
||||
private function convertStackedCodeDivs(DOMDocument $document): void
|
||||
{
|
||||
$xpath = new DOMXPath($document);
|
||||
$parents = [];
|
||||
foreach ($xpath->query('//*') ?: [] as $node) {
|
||||
if ($node instanceof DOMElement) {
|
||||
$parents[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($parents as $parent) {
|
||||
if ($parent->parentNode === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array(strtolower($parent->nodeName), ['pre', 'code'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$run = [];
|
||||
$flush = function () use (&$run, $document): void {
|
||||
if (count($run) < 2) {
|
||||
$run = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = array_map(fn (DOMElement $div): string => $this->plainText($this->innerHtmlOf($div)), $run);
|
||||
if (! $this->looksLikeCodeLines($lines)) {
|
||||
$run = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $run[0];
|
||||
$pre = $this->makePre($document, implode("\n", $lines), $this->detectLanguage('', implode("\n", $lines)));
|
||||
$first->parentNode?->insertBefore($pre, $first);
|
||||
foreach ($run as $div) {
|
||||
$div->parentNode?->removeChild($div);
|
||||
}
|
||||
$run = [];
|
||||
};
|
||||
|
||||
foreach (iterator_to_array($parent->childNodes) as $child) {
|
||||
if ($child instanceof DOMNode && $child->nodeType === XML_TEXT_NODE && trim((string) $child->textContent) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($child instanceof DOMElement && strtolower($child->nodeName) === 'div' && $this->isSimpleLineDiv($child)) {
|
||||
$run[] = $child;
|
||||
continue;
|
||||
}
|
||||
|
||||
$flush();
|
||||
}
|
||||
|
||||
$flush();
|
||||
}
|
||||
}
|
||||
|
||||
private function removeEmptyBlocks(DOMXPath $xpath): void
|
||||
{
|
||||
foreach ($xpath->query('//p|//div') ?: [] as $node) {
|
||||
if (! $node instanceof DOMElement || $node->parentNode === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node->getElementsByTagName('*')->length > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$text = trim(str_replace("\xC2\xA0", ' ', $node->textContent));
|
||||
if ($text === '') {
|
||||
$node->parentNode->removeChild($node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function replaceWithPre(DOMDocument $document, DOMXPath $xpath, DOMElement $node): void
|
||||
{
|
||||
$hint = '';
|
||||
if (preg_match('/dp-([a-z0-9#+]+)/i', $node->getAttribute('class'), $match)) {
|
||||
$hint = $match[1];
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
foreach ($xpath->query('.//li', $node) ?: [] as $item) {
|
||||
$lines[] = $this->plainText($document->saveHTML($item) ?: '');
|
||||
}
|
||||
|
||||
if ($lines === []) {
|
||||
foreach ($xpath->query('.//*[contains(@class, "codeHead")]', $node) ?: [] as $head) {
|
||||
$head->parentNode?->removeChild($head);
|
||||
}
|
||||
$lines = preg_split('/\R/u', $this->plainText($node->textContent)) ?: [];
|
||||
}
|
||||
|
||||
$code = implode("\n", $lines);
|
||||
$pre = $this->makePre($document, $code, $this->detectLanguage($hint, $code));
|
||||
$node->parentNode?->replaceChild($pre, $node);
|
||||
}
|
||||
|
||||
private function makePre(DOMDocument $document, string $code, string $language): DOMElement
|
||||
{
|
||||
$pre = $document->createElement('pre');
|
||||
$codeNode = $document->createElement('code');
|
||||
$codeNode->setAttribute('class', 'language-'.$language);
|
||||
$codeNode->appendChild($document->createTextNode($code));
|
||||
$pre->appendChild($codeNode);
|
||||
|
||||
return $pre;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $lines
|
||||
*/
|
||||
private function looksLikeCodeLines(array $lines): bool
|
||||
{
|
||||
$codeish = 0;
|
||||
foreach ($lines as $line) {
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/\\\\$/', $line)
|
||||
|| preg_match('/^\s*(docker|npm|npx|pnpm|yarn|git|curl|wget|sudo|ssh|kubectl|composer|php|python|pip|node)\b/i', $line)
|
||||
|| preg_match('/^\s+--[\w-]+/', $line)
|
||||
|| preg_match('/^\s*\|/', $line)
|
||||
|| preg_match('/[{};]$/', $line)
|
||||
) {
|
||||
$codeish++;
|
||||
}
|
||||
}
|
||||
|
||||
return $codeish >= 2;
|
||||
}
|
||||
|
||||
private function isSimpleLineDiv(DOMElement $div): bool
|
||||
{
|
||||
foreach ($div->childNodes as $child) {
|
||||
if ($child instanceof DOMElement && ! in_array(strtolower($child->nodeName), ['br', 'span', 'code', 'b', 'i', 'em', 'strong'], true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$text = $this->plainText($div->textContent);
|
||||
|
||||
return $text !== '';
|
||||
}
|
||||
|
||||
private function detectLanguage(string $hint, string $code): string
|
||||
{
|
||||
if (preg_match('/^\s*(docker|kubectl|npm|npx|pnpm|yarn|composer|curl|wget|sudo|ssh)\b/m', $code)) {
|
||||
return 'bash';
|
||||
}
|
||||
|
||||
$aliases = $this->languageAliases();
|
||||
$key = strtolower(trim($hint));
|
||||
|
||||
return $aliases[$key] ?? ($key !== '' ? $key : 'text');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover a docker/shell command that HtmlConverter flattened into one line.
|
||||
*/
|
||||
private function recoverFlattenedShell(string $content): string
|
||||
{
|
||||
if (str_contains($content, '<pre')) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'/(?:docker|sudo docker)\s+run\b(?:[^<\n]*\\\\)+\s*[^<\n]+/i',
|
||||
function (array $match): string {
|
||||
$code = trim($this->plainText($match[0]));
|
||||
$code = preg_replace('/\\\\\s+/', "\\\n", $code) ?? $code;
|
||||
|
||||
return '<pre><code class="language-bash">'.e($code).'</code></pre>';
|
||||
},
|
||||
$content
|
||||
) ?? $content;
|
||||
}
|
||||
|
||||
private function closestTag(DOMNode $node, string $tag): ?DOMElement
|
||||
{
|
||||
$current = $node->parentNode;
|
||||
while ($current instanceof DOMElement) {
|
||||
if (strtolower($current->nodeName) === $tag) {
|
||||
return $current;
|
||||
}
|
||||
$current = $current->parentNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function loadDocument(string $html): DOMDocument
|
||||
{
|
||||
$document = new DOMDocument('1.0', 'UTF-8');
|
||||
$previous = libxml_use_internal_errors(true);
|
||||
$wrapped = '<div id="ll-root">'.$html.'</div>';
|
||||
$document->loadHTML('<?xml encoding="UTF-8">'.$wrapped, LIBXML_NOERROR | LIBXML_HTML_NODEFDTD);
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($previous);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
private function innerHtml(DOMDocument $document): string
|
||||
{
|
||||
$root = $document->getElementById('ll-root');
|
||||
if (! $root instanceof DOMElement) {
|
||||
$root = (new DOMXPath($document))->query('//*[@id="ll-root"]')->item(0);
|
||||
}
|
||||
|
||||
if (! $root instanceof DOMElement) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->innerHtmlOf($root);
|
||||
}
|
||||
|
||||
private function innerHtmlOf(DOMElement $element): string
|
||||
{
|
||||
$html = '';
|
||||
foreach ($element->childNodes as $child) {
|
||||
$html .= $element->ownerDocument?->saveHTML($child) ?: '';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Blog\Support\SablogHtmlNormalizer;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Console\Command;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
@@ -36,6 +37,7 @@ class ConvertContent extends Command
|
||||
]);
|
||||
|
||||
$renderer = app(\App\Blog\Services\PostContentRenderer::class);
|
||||
$normalizer = app(SablogHtmlNormalizer::class);
|
||||
|
||||
$posts = $query->get();
|
||||
if ($posts->isEmpty()) {
|
||||
@@ -49,7 +51,7 @@ class ConvertContent extends Command
|
||||
|
||||
foreach ($posts as $post) {
|
||||
// [attach=xx] 是 markdown 保留字符,先换成令牌,渲染时再解析
|
||||
$markdown = $converter->convert($renderer->toMarkdownSafeTokens($post->content));
|
||||
$markdown = $normalizer->toMarkdown((string) $post->content, $converter, $renderer);
|
||||
|
||||
if ($this->option('dry-run')) {
|
||||
$this->newLine();
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Blog\Support\SablogHtmlNormalizer;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class FixImportedContent extends Command
|
||||
{
|
||||
protected $signature = 'sablog:fix-content
|
||||
{--all : Repair every post}
|
||||
{id? : Single post ID}
|
||||
{--dry-run : Preview only}';
|
||||
|
||||
protected $description = 'Clean leftover SaBlog HTML: literal , highlighter blocks, stacked docker divs';
|
||||
|
||||
public function handle(SablogHtmlNormalizer $normalizer): int
|
||||
{
|
||||
$query = Post::query();
|
||||
|
||||
if ($id = $this->argument('id')) {
|
||||
$query->where('id', $id);
|
||||
} elseif (! $this->option('all')) {
|
||||
$this->error('Pass a post ID or use --all');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$posts = $query->get();
|
||||
if ($posts->isEmpty()) {
|
||||
$this->info('No posts to repair');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$changed = 0;
|
||||
foreach ($posts as $post) {
|
||||
$cleaned = $normalizer->normalize((string) $post->content);
|
||||
if ($cleaned === $post->content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->option('dry-run')) {
|
||||
$this->line("[{$post->id}] {$post->title}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$post->content = $cleaned;
|
||||
$post->save();
|
||||
$changed++;
|
||||
}
|
||||
|
||||
$this->info($this->option('dry-run')
|
||||
? 'Preview only, nothing written'
|
||||
: "Repaired {$changed} posts");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,18 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Blog\Services\AttachmentImporter;
|
||||
use App\Models\Category;
|
||||
use App\Models\Comment;
|
||||
use App\Models\Link;
|
||||
use App\Blog\Services\PostContentRenderer;
|
||||
use App\Blog\Support\SablogHtmlNormalizer;
|
||||
use App\Models\Post;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Support\SlugGenerator;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
use PDO;
|
||||
use Spatie\Tags\Tag;
|
||||
|
||||
@@ -271,6 +270,7 @@ class SablogImport extends Command
|
||||
'keywords' => $row['keywords'] ?: null,
|
||||
'status' => $status,
|
||||
'is_sticky' => (bool) $row['stick'],
|
||||
'sticky_level' => ((bool) $row['stick']) ? 1 : 0,
|
||||
'close_comment' => (bool) $row['closecomment'],
|
||||
'read_password' => $row['readpassword'] ?: null,
|
||||
'views' => (int) $row['views'],
|
||||
@@ -362,15 +362,16 @@ class SablogImport extends Command
|
||||
{
|
||||
$this->info('转换文章内容为 Markdown ...');
|
||||
|
||||
$converter = new \League\HTMLToMarkdown\HtmlConverter([
|
||||
$converter = new HtmlConverter([
|
||||
'strip_tags' => false,
|
||||
'hard_break' => true,
|
||||
]);
|
||||
$renderer = app(\App\Blog\Services\PostContentRenderer::class);
|
||||
$renderer = app(PostContentRenderer::class);
|
||||
$normalizer = app(SablogHtmlNormalizer::class);
|
||||
|
||||
$count = 0;
|
||||
foreach (Post::query()->where('content_format', 'html')->cursor() as $post) {
|
||||
$post->content = $converter->convert($renderer->toMarkdownSafeTokens($post->content));
|
||||
$post->content = $normalizer->toMarkdown((string) $post->content, $converter, $renderer);
|
||||
$post->content_format = 'markdown';
|
||||
$post->save();
|
||||
$count++;
|
||||
|
||||
@@ -42,6 +42,7 @@ class BlogSettings extends Page
|
||||
// 外观
|
||||
'active_theme' => Setting::get('active_theme', config('themes.default')),
|
||||
'per_page' => Setting::get('per_page', config('blog.per_page')),
|
||||
'home_view_mode' => Setting::get('home_view_mode', config('blog.home_view_mode', 'list')),
|
||||
'list_shownum' => Setting::get('list_shownum', '50'),
|
||||
'listtime' => Setting::get('listtime', 'Y-m-d'),
|
||||
'normaltime' => Setting::get('normaltime', 'Y-m-d H:i'),
|
||||
@@ -102,6 +103,13 @@ class BlogSettings extends Page
|
||||
->columnSpanFull()
|
||||
->options(collect(app(\App\Blog\Support\ThemeManager::class)->all())->pluck('title', 'name')),
|
||||
TextInput::make('per_page')->label('首页每页文章数')->numeric(),
|
||||
Select::make('home_view_mode')
|
||||
->label('首页浏览模式')
|
||||
->options([
|
||||
'list' => '列表(标题 + 摘要)',
|
||||
'full' => '标准(全文)',
|
||||
])
|
||||
->helperText('前台右上角仍可临时切换,和老 sablog 的「标准 | 列表」一样'),
|
||||
TextInput::make('list_shownum')->label('列表每页数量')->numeric()->helperText('分类/标签/搜索等列表页'),
|
||||
TextInput::make('listtime')->label('列表时间格式')->placeholder('Y-m-d'),
|
||||
TextInput::make('normaltime')->label('详情时间格式')->placeholder('Y-m-d H:i'),
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class Languages extends Page
|
||||
{
|
||||
protected static \UnitEnum|string|null $navigationGroup = '管理';
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-language';
|
||||
|
||||
protected static ?string $navigationLabel = '语言';
|
||||
|
||||
protected static ?string $title = '语言';
|
||||
|
||||
protected static ?string $slug = 'languages';
|
||||
|
||||
protected static ?int $navigationSort = 21;
|
||||
|
||||
protected string $view = 'filament.pages.languages';
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* locale: string,
|
||||
* fallback: string,
|
||||
* faker: string,
|
||||
* app_files: list<string>,
|
||||
* filament_locales: list<string>,
|
||||
* content_i18n: bool
|
||||
* }
|
||||
*/
|
||||
public function getInfoProperty(): array
|
||||
{
|
||||
return [
|
||||
'locale' => app()->getLocale(),
|
||||
'fallback' => (string) config('app.fallback_locale'),
|
||||
'faker' => (string) config('app.faker_locale'),
|
||||
'app_files' => $this->appLanguageFiles(),
|
||||
'filament_locales' => $this->filamentLocales(),
|
||||
'content_i18n' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function appLanguageFiles(): array
|
||||
{
|
||||
$found = [];
|
||||
|
||||
foreach (File::glob(lang_path('*.json')) as $file) {
|
||||
$found[] = basename((string) $file, '.json');
|
||||
}
|
||||
|
||||
foreach (File::directories(lang_path()) as $directory) {
|
||||
$name = basename($directory);
|
||||
if ($name === 'vendor') {
|
||||
continue;
|
||||
}
|
||||
$found[] = $name;
|
||||
}
|
||||
|
||||
$found = array_values(array_unique($found));
|
||||
sort($found);
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function filamentLocales(): array
|
||||
{
|
||||
$path = lang_path('vendor/filament');
|
||||
if (! File::isDirectory($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$locales = array_map(
|
||||
static fn (string $directory): string => basename($directory),
|
||||
File::directories($path)
|
||||
);
|
||||
sort($locales);
|
||||
|
||||
return $locales;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Blog\Support\PageCacheMiddleware;
|
||||
use App\Models\Setting;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class PageCache extends Page
|
||||
{
|
||||
protected static \UnitEnum|string|null $navigationGroup = '管理';
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bolt';
|
||||
|
||||
protected static ?string $navigationLabel = '页面缓存';
|
||||
|
||||
protected static ?string $title = '页面缓存';
|
||||
|
||||
protected static ?string $slug = 'page-cache';
|
||||
|
||||
protected static ?int $navigationSort = 20;
|
||||
|
||||
protected string $view = 'filament.pages.page-cache';
|
||||
|
||||
/**
|
||||
* @return array{hits: int, misses: int, count: int, rate: float, ttl: int}
|
||||
*/
|
||||
public function getStatsProperty(): array
|
||||
{
|
||||
return [
|
||||
...PageCacheMiddleware::stats(),
|
||||
'ttl' => (int) config('blog.page_cache_ttl', 300),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{key: string, url: string, cached_at: string}>
|
||||
*/
|
||||
public function getEntriesProperty(): array
|
||||
{
|
||||
return PageCacheMiddleware::entries();
|
||||
}
|
||||
|
||||
public function flushPageCache(): void
|
||||
{
|
||||
PageCacheMiddleware::flush();
|
||||
Notification::make()->title('前台页面缓存已清空')->success()->send();
|
||||
}
|
||||
|
||||
public function flushAllCache(): void
|
||||
{
|
||||
PageCacheMiddleware::flush();
|
||||
Setting::flushCache();
|
||||
Cache::flush();
|
||||
Notification::make()->title('全部缓存已清空')->success()->send();
|
||||
}
|
||||
|
||||
public function resetStats(): void
|
||||
{
|
||||
PageCacheMiddleware::resetStats();
|
||||
Notification::make()->title('命中统计已重置')->success()->send();
|
||||
}
|
||||
|
||||
public function forgetEntry(string $key): void
|
||||
{
|
||||
PageCacheMiddleware::forget($key);
|
||||
Notification::make()->title('已删除这条缓存')->success()->send();
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('resetStats')
|
||||
->label('重置统计')
|
||||
->color('gray')
|
||||
->action(fn () => $this->resetStats()),
|
||||
Action::make('flushPageCache')
|
||||
->label('清空前台缓存')
|
||||
->color('warning')
|
||||
->action(fn () => $this->flushPageCache()),
|
||||
Action::make('flushAllCache')
|
||||
->label('清空全部缓存')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(fn () => $this->flushAllCache()),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,8 @@ class CategoryForm
|
||||
->label('封面图')
|
||||
->module('articles')
|
||||
->mediaType('image')
|
||||
->relationship('coverMedia'),
|
||||
->relationship('coverMedia')
|
||||
->extraFieldWrapperAttributes(['class' => 'll-media-picker-preview']),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Filament\Resources\Posts\Pages;
|
||||
|
||||
use App\Filament\Resources\Posts\PostResource;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
@@ -16,6 +16,12 @@ class EditPost extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('preview')
|
||||
->label('预览')
|
||||
->icon('heroicon-o-eye')
|
||||
->color('gray')
|
||||
->url(fn (): string => $this->record->url)
|
||||
->openUrlInNewTab(),
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\Posts\RelationManagers;
|
||||
|
||||
use App\Models\Comment;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -22,7 +23,7 @@ class CommentsRelationManager extends RelationManager
|
||||
|
||||
protected static ?string $pluralModelLabel = '评论';
|
||||
|
||||
public function form(\Filament\Schemas\Schema $schema): \Filament\Schemas\Schema
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
|
||||
@@ -5,129 +5,295 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\Posts\Schemas;
|
||||
|
||||
use App\Blog\Support\MarkdownMediaEmbed;
|
||||
use App\Models\Post;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\MarkdownEditor;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use ReflectionClass;
|
||||
use Xpier\FilamentMediaLibrary\Components\MediaPicker;
|
||||
use Xpier\FilamentMediaLibrary\Models\MediaLibrary;
|
||||
|
||||
class PostForm
|
||||
{
|
||||
public const SIDEBAR_KEY = 'post_form_sidebar';
|
||||
|
||||
public const ACCESS_KEY = 'post_form_access';
|
||||
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->columns(self::pcColumns(3))
|
||||
->components([
|
||||
\Filament\Schemas\Components\Section::make('内容')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->label('标题')
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
TextInput::make('slug')
|
||||
->label('Slug(留空自动生成)'),
|
||||
Select::make('category_id')
|
||||
->label('分类')
|
||||
->relationship('category', 'name'),
|
||||
Select::make('content_format')
|
||||
->label('内容格式')
|
||||
->options([
|
||||
'markdown' => 'Markdown',
|
||||
'html' => 'HTML',
|
||||
])
|
||||
->default('markdown')
|
||||
->live(),
|
||||
Select::make('status')
|
||||
->label('状态')
|
||||
->options([
|
||||
'published' => '已发布',
|
||||
'draft' => '草稿',
|
||||
'private' => '私密',
|
||||
])
|
||||
->default('published'),
|
||||
MarkdownEditor::make('content')
|
||||
->label('正文')
|
||||
->required()
|
||||
->helperText('Markdown 插入图片用 ,文件用 [文件名](media:ID)。不要把 CDN / 对象存储地址写进正文。')
|
||||
->fileAttachments(false)
|
||||
->columnSpanFull(),
|
||||
MediaPicker::make('insert_media_id')
|
||||
->label('从媒体库插入正文')
|
||||
->module('articles')
|
||||
->mediaType(null)
|
||||
->dehydrated(false)
|
||||
->helperText('选好媒体后点「插入到正文」。Markdown 写入 media:ID 引用,渲染时再换成真实地址。')
|
||||
->columnSpanFull(),
|
||||
Actions::make([
|
||||
Action::make('insertSelectedMedia')
|
||||
->label('插入到正文')
|
||||
->action(function (Get $get, Set $set): void {
|
||||
$mediaId = $get('insert_media_id');
|
||||
$media = is_numeric($mediaId)
|
||||
? MediaLibrary::query()->find((int) $mediaId)
|
||||
: null;
|
||||
|
||||
if (! $media instanceof MediaLibrary) {
|
||||
Notification::make()->title('请先从媒体库选择文件')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$snippet = app(MarkdownMediaEmbed::class)->snippet(
|
||||
$media,
|
||||
(string) ($get('content_format') ?: 'markdown')
|
||||
);
|
||||
$set('content', rtrim((string) $get('content'))."\n\n".$snippet."\n");
|
||||
$set('insert_media_id', null);
|
||||
Notification::make()->title('已写入正文引用')->success()->send();
|
||||
}),
|
||||
])->columnSpanFull(),
|
||||
]),
|
||||
\Filament\Schemas\Components\Section::make('封面与摘要')
|
||||
->columns(2)
|
||||
->schema([
|
||||
MediaPicker::make('cover_media_id')
|
||||
->label('封面图')
|
||||
->module('articles')
|
||||
->mediaType('image')
|
||||
->relationship('coverMedia')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('excerpt')
|
||||
->label('摘要(留空自动截取)')
|
||||
->rows(3),
|
||||
TextInput::make('keywords')
|
||||
->label('关键词(逗号分隔)'),
|
||||
]),
|
||||
\Filament\Schemas\Components\Section::make('发布设置')
|
||||
->columns(3)
|
||||
->schema([
|
||||
DateTimePicker::make('published_at')
|
||||
->label('发布时间')
|
||||
->default(now()),
|
||||
Toggle::make('is_sticky')
|
||||
->label('置顶'),
|
||||
Toggle::make('close_comment')
|
||||
->label('关闭评论'),
|
||||
TextInput::make('read_password')
|
||||
->label('阅读密码(留空公开)'),
|
||||
TextInput::make('views')
|
||||
->label('浏览量')
|
||||
->numeric()
|
||||
->disabled(),
|
||||
TextInput::make('comment_count')
|
||||
->label('评论数')
|
||||
->numeric()
|
||||
->disabled(),
|
||||
]),
|
||||
self::contentSection(),
|
||||
self::sidebarGroup(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append sections or fields into the right-hand sidebar (2:1 layout).
|
||||
*
|
||||
* @param array<Component> $components
|
||||
*/
|
||||
public static function appendToSidebar(Schema $schema, array $components): void
|
||||
{
|
||||
$sidebar = self::findKeyedComponent($schema->getComponents(), self::SIDEBAR_KEY);
|
||||
|
||||
if (! $sidebar instanceof Component) {
|
||||
$schema->components([
|
||||
...$schema->getComponents(),
|
||||
...$components,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$existing = $sidebar->getDefaultChildComponents();
|
||||
|
||||
if ($existing instanceof Schema) {
|
||||
$existing->components([
|
||||
...$existing->getComponents(),
|
||||
...$components,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sidebar->schema([
|
||||
...(is_array($existing) ? $existing : []),
|
||||
...$components,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append fields into the access group (password / members / price).
|
||||
*
|
||||
* @param array<Component> $fields
|
||||
*/
|
||||
public static function appendToAccessSection(Schema $schema, array $fields): void
|
||||
{
|
||||
$sidebar = self::findKeyedComponent($schema->getComponents(), self::SIDEBAR_KEY);
|
||||
$access = $sidebar instanceof Component
|
||||
? self::findKeyedComponent(self::childList($sidebar), self::ACCESS_KEY)
|
||||
: null;
|
||||
|
||||
if (! $access instanceof Component) {
|
||||
self::appendToSidebar($schema, [
|
||||
Section::make('访问设置')
|
||||
->key(self::ACCESS_KEY)
|
||||
->schema($fields),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$existing = $access->getDefaultChildComponents();
|
||||
|
||||
if ($existing instanceof Schema) {
|
||||
$existing->components([
|
||||
...$existing->getComponents(),
|
||||
...$fields,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$access->schema([
|
||||
...(is_array($existing) ? $existing : []),
|
||||
...$fields,
|
||||
]);
|
||||
}
|
||||
|
||||
private static function contentSection(): Section
|
||||
{
|
||||
return Section::make('正文')
|
||||
->columnSpan(self::pcColumns(2))
|
||||
->columns(self::pcColumns(3))
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->label('标题')
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
TextInput::make('slug')
|
||||
->label('Slug(留空自动生成)')
|
||||
->columnSpanFull(),
|
||||
Select::make('category_id')
|
||||
->label('分类')
|
||||
->relationship('category', 'name'),
|
||||
ToggleButtons::make('content_format')
|
||||
->label('内容格式')
|
||||
->options([
|
||||
'markdown' => 'Markdown',
|
||||
'html' => 'HTML',
|
||||
])
|
||||
->default('markdown')
|
||||
->inline()
|
||||
->grouped()
|
||||
->live(),
|
||||
ToggleButtons::make('status')
|
||||
->label('状态')
|
||||
->options([
|
||||
'published' => '已发布',
|
||||
'draft' => '草稿',
|
||||
'private' => '私密',
|
||||
])
|
||||
->default('published')
|
||||
->inline()
|
||||
->grouped(),
|
||||
MediaPicker::make('cover_media_id')
|
||||
->label('封面图')
|
||||
->module('articles')
|
||||
->mediaType('image')
|
||||
->relationship('coverMedia')
|
||||
->extraFieldWrapperAttributes(['class' => 'll-media-picker-preview'])
|
||||
->columnSpanFull(),
|
||||
MarkdownEditor::make('content')
|
||||
->label('正文')
|
||||
->required()
|
||||
->minHeight('32rem')
|
||||
->helperText('Markdown 插入图片用 ,文件用 [文件名](media:ID)。不要把 CDN / 对象存储地址写进正文。')
|
||||
->fileAttachments(false)
|
||||
->columnSpanFull(),
|
||||
MediaPicker::make('insert_media_id')
|
||||
->label('从媒体库插入正文')
|
||||
->module('articles')
|
||||
->mediaType(null)
|
||||
->dehydrated(false)
|
||||
->extraFieldWrapperAttributes(['class' => 'll-media-picker-preview'])
|
||||
->helperText('选好媒体后点「插入到正文」。Markdown 写入 media:ID 引用,渲染时再换成真实地址。')
|
||||
->columnSpanFull(),
|
||||
Actions::make([
|
||||
Action::make('insertSelectedMedia')
|
||||
->label('插入到正文')
|
||||
->action(function (Get $get, Set $set): void {
|
||||
$mediaId = $get('insert_media_id');
|
||||
$media = is_numeric($mediaId)
|
||||
? MediaLibrary::query()->find((int) $mediaId)
|
||||
: null;
|
||||
|
||||
if (! $media instanceof MediaLibrary) {
|
||||
Notification::make()->title('请先从媒体库选择文件')->warning()->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$snippet = app(MarkdownMediaEmbed::class)->snippet(
|
||||
$media,
|
||||
(string) ($get('content_format') ?: 'markdown')
|
||||
);
|
||||
$set('content', rtrim((string) $get('content'))."\n\n".$snippet."\n");
|
||||
$set('insert_media_id', null);
|
||||
Notification::make()->title('已写入正文引用')->success()->send();
|
||||
}),
|
||||
])->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function sidebarGroup(): Group
|
||||
{
|
||||
return Group::make([
|
||||
Section::make('发布设置')
|
||||
->schema([
|
||||
DateTimePicker::make('published_at')
|
||||
->label('发布时间')
|
||||
->default(now()),
|
||||
ToggleButtons::make('sticky_level')
|
||||
->label('置顶等级')
|
||||
->options(Post::stickyLevelOptions())
|
||||
->default(Post::STICKY_NONE)
|
||||
->inline()
|
||||
->grouped()
|
||||
->helperText('轻置顶压过普通文章,强置顶压过轻置顶,置顶头条永远排在最上面。'),
|
||||
ToggleButtons::make('close_comment')
|
||||
->label('评论状态')
|
||||
->boolean('关闭评论', '开放评论')
|
||||
->default(false)
|
||||
->inline()
|
||||
->grouped()
|
||||
->dehydrateStateUsing(fn (mixed $state): bool => (bool) $state),
|
||||
Textarea::make('excerpt')
|
||||
->label('摘要(留空自动截取)')
|
||||
->rows(4),
|
||||
TextInput::make('keywords')
|
||||
->label('关键词(逗号分隔)'),
|
||||
]),
|
||||
Section::make('访问设置')
|
||||
->key(self::ACCESS_KEY)
|
||||
->schema([
|
||||
TextInput::make('read_password')
|
||||
->label('阅读密码(留空公开)'),
|
||||
]),
|
||||
])
|
||||
->key(self::SIDEBAR_KEY)
|
||||
->columnSpan(self::pcColumns(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin writing is almost always on a PC. Filament's columns(3) only
|
||||
* applies from the lg breakpoint, so we pin every breakpoint.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
private static function pcColumns(int $count): array
|
||||
{
|
||||
return [
|
||||
'default' => $count,
|
||||
'sm' => $count,
|
||||
'md' => $count,
|
||||
'lg' => $count,
|
||||
'xl' => $count,
|
||||
'2xl' => $count,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $components
|
||||
*/
|
||||
private static function findKeyedComponent(array $components, string $key): ?Component
|
||||
{
|
||||
foreach ($components as $component) {
|
||||
if (! $component instanceof Component) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::rawKey($component) === $key) {
|
||||
return $component;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function rawKey(Component $component): ?string
|
||||
{
|
||||
$property = (new ReflectionClass($component))->getProperty('key');
|
||||
$key = $property->getValue($component);
|
||||
|
||||
return is_string($key) ? $key : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
private static function childList(Component $component): array
|
||||
{
|
||||
$children = $component->getDefaultChildComponents();
|
||||
|
||||
if ($children instanceof Schema) {
|
||||
return $children->getComponents();
|
||||
}
|
||||
|
||||
return is_array($children) ? $children : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Filament\Resources\Posts\Tables;
|
||||
|
||||
use App\Models\Post;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Enums\FiltersLayout;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PostsTable
|
||||
@@ -46,9 +46,17 @@ class PostsTable
|
||||
'draft' => 'warning',
|
||||
default => 'gray',
|
||||
}),
|
||||
IconColumn::make('is_sticky')
|
||||
TextColumn::make('sticky_level')
|
||||
->label('置顶')
|
||||
->boolean(),
|
||||
->badge()
|
||||
->sortable()
|
||||
->formatStateUsing(fn ($state) => Post::stickyLevelOptions()[(int) $state] ?? '不置顶')
|
||||
->color(fn ($state) => match ((int) $state) {
|
||||
Post::STICKY_HEADLINE => 'danger',
|
||||
Post::STICKY_STRONG => 'warning',
|
||||
Post::STICKY_LIGHT => 'success',
|
||||
default => 'gray',
|
||||
}),
|
||||
TextColumn::make('views')
|
||||
->label('阅读')
|
||||
->numeric()
|
||||
@@ -79,12 +87,17 @@ class PostsTable
|
||||
'markdown' => 'Markdown',
|
||||
'html' => 'HTML',
|
||||
]),
|
||||
TernaryFilter::make('is_sticky')
|
||||
->label('置顶'),
|
||||
], \Filament\Tables\Enums\FiltersLayout::AboveContent)
|
||||
SelectFilter::make('sticky_level')
|
||||
->label('置顶')
|
||||
->options(Post::stickyLevelOptions()),
|
||||
], FiltersLayout::AboveContent)
|
||||
->filtersFormColumns(3)
|
||||
->filtersFormWidth(\Filament\Support\Enums\Width::ThreeExtraLarge)
|
||||
->filtersFormWidth(Width::ThreeExtraLarge)
|
||||
->recordActions([
|
||||
Action::make('preview')
|
||||
->label('预览')
|
||||
->url(fn (Post $record): string => $record->url)
|
||||
->openUrlInNewTab(),
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
])
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Blog\Support\PageCacheMiddleware;
|
||||
use App\Filament\Pages\PageCache;
|
||||
use App\Models\Comment;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
@@ -17,7 +18,7 @@ class BlogStats extends BaseWidget
|
||||
|
||||
protected function getStats(): array
|
||||
{
|
||||
$cache = \App\Blog\Support\PageCacheMiddleware::stats();
|
||||
$cache = PageCacheMiddleware::stats();
|
||||
|
||||
return [
|
||||
Stat::make('文章', Post::published()->count())
|
||||
@@ -30,6 +31,7 @@ class BlogStats extends BaseWidget
|
||||
->icon('heroicon-o-users'),
|
||||
Stat::make('页面缓存', $cache['count'].' 条')
|
||||
->description('命中率 '.$cache['rate'].'%(命中 '.$cache['hits'].' / 未命中 '.$cache['misses'].',按日滚动)')
|
||||
->url(PageCache::getUrl())
|
||||
->icon('heroicon-o-bolt'),
|
||||
];
|
||||
}
|
||||
|
||||
+61
-7
@@ -2,25 +2,25 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Support\MediaDisk;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Xpier\FilamentMediaLibrary\Models\MediaLibrary;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
use Spatie\Tags\HasTags;
|
||||
use Xpier\FilamentMediaLibrary\Models\MediaLibrary;
|
||||
|
||||
class Post extends Model implements HasMedia
|
||||
{
|
||||
use HasFactory;
|
||||
use InteractsWithMedia;
|
||||
use HasTags;
|
||||
use InteractsWithMedia;
|
||||
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
@@ -34,6 +34,7 @@ class Post extends Model implements HasMedia
|
||||
'cover_media_id',
|
||||
'status',
|
||||
'is_sticky',
|
||||
'sticky_level',
|
||||
'close_comment',
|
||||
'read_password',
|
||||
'views',
|
||||
@@ -45,10 +46,63 @@ class Post extends Model implements HasMedia
|
||||
protected $casts = [
|
||||
'meta' => 'array',
|
||||
'is_sticky' => 'boolean',
|
||||
'sticky_level' => 'integer',
|
||||
'close_comment' => 'boolean',
|
||||
'published_at' => 'datetime',
|
||||
];
|
||||
|
||||
public const STICKY_NONE = 0;
|
||||
|
||||
public const STICKY_LIGHT = 1;
|
||||
|
||||
public const STICKY_STRONG = 2;
|
||||
|
||||
public const STICKY_HEADLINE = 3;
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function stickyLevelOptions(): array
|
||||
{
|
||||
return [
|
||||
self::STICKY_NONE => '不置顶',
|
||||
self::STICKY_LIGHT => '轻置顶',
|
||||
self::STICKY_STRONG => '强置顶',
|
||||
self::STICKY_HEADLINE => '置顶头条',
|
||||
];
|
||||
}
|
||||
|
||||
public function stickyLabel(): ?string
|
||||
{
|
||||
$level = (int) $this->sticky_level;
|
||||
|
||||
return $level > 0 ? (self::stickyLevelOptions()[$level] ?? '置顶') : null;
|
||||
}
|
||||
|
||||
public function isPinned(): bool
|
||||
{
|
||||
return (int) $this->sticky_level > 0 || $this->is_sticky;
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::saving(function (Post $post): void {
|
||||
$level = (int) ($post->sticky_level ?? 0);
|
||||
$post->sticky_level = max(self::STICKY_NONE, min(self::STICKY_HEADLINE, $level));
|
||||
|
||||
if ($post->is_sticky && $post->sticky_level < self::STICKY_LIGHT) {
|
||||
$post->sticky_level = self::STICKY_LIGHT;
|
||||
}
|
||||
|
||||
$post->is_sticky = $post->sticky_level > self::STICKY_NONE;
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeOrderedForList(Builder $query): Builder
|
||||
{
|
||||
return $query->orderByDesc('sticky_level')->latest('published_at');
|
||||
}
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
@@ -107,8 +161,8 @@ class Post extends Model implements HasMedia
|
||||
|
||||
public function registerMediaCollections(): void
|
||||
{
|
||||
$this->addMediaCollection('attachments')->useDisk(\App\Support\MediaDisk::name());
|
||||
$this->addMediaCollection('cover')->useDisk(\App\Support\MediaDisk::name())
|
||||
$this->addMediaCollection('attachments')->useDisk(MediaDisk::name());
|
||||
$this->addMediaCollection('cover')->useDisk(MediaDisk::name())
|
||||
->singleFile()
|
||||
->registerMediaConversions(function () {
|
||||
$this->addMediaConversion('card')->width(1200)->height(630)->sharpen(1);
|
||||
@@ -153,13 +207,13 @@ class Post extends Model implements HasMedia
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = strip_tags($this->content);
|
||||
$text = (new \App\Blog\Support\SablogHtmlNormalizer)->plainText((string) $this->content);
|
||||
// 付费块不进入摘要,避免付费内容泄漏到列表页/SEO meta
|
||||
$text = preg_replace('/\[paid\].*?\[\/paid\]/s', '', $text) ?? $text;
|
||||
$text = preg_replace('/!\[([^\]]*)\]\((?:media|attach):\d+\)/i', '$1', $text) ?? $text;
|
||||
$text = preg_replace('/\[([^\]]*)\]\((?:media|attach):\d+\)/i', '$1', $text) ?? $text;
|
||||
$text = preg_replace('/\[[^\]]*\]/', '', $text) ?? $text;
|
||||
|
||||
return mb_substr($text, 0, 200);
|
||||
return mb_substr(trim($text), 0, 200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Blog\Support\HomeViewMode;
|
||||
use App\Blog\Support\PageCacheMiddleware;
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Models\MarketPackage;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\View\PanelsRenderHook;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Plugins\Neatstudio\Payment\Models\Payment;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -21,16 +27,16 @@ class AppServiceProvider extends ServiceProvider
|
||||
|
||||
private function registerMarketPurchaseListener(): void
|
||||
{
|
||||
$manager = app(\App\Blog\Support\PluginManager::class);
|
||||
$manager = app(PluginManager::class);
|
||||
|
||||
if (! $manager->isEnabled('neatstudio.payment')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$manager->addAction('payment.paid', function (\Plugins\Neatstudio\Payment\Models\Payment $payment) {
|
||||
$manager->addAction('payment.paid', function (Payment $payment) {
|
||||
$payable = $payment->payable;
|
||||
|
||||
if ($payable instanceof \App\Models\MarketPackage) {
|
||||
if ($payable instanceof MarketPackage) {
|
||||
$payable->markPaid($payment);
|
||||
}
|
||||
}, 10);
|
||||
@@ -54,7 +60,20 @@ class AppServiceProvider extends ServiceProvider
|
||||
$this->registerMarketPurchaseListener();
|
||||
|
||||
// 前台页面缓存(匿名 GET,缓存含 SEO 注入后的最终 HTML)
|
||||
app('router')->pushMiddlewareToGroup('web', \App\Blog\Support\PageCacheMiddleware::class);
|
||||
app('router')->pushMiddlewareToGroup('web', PageCacheMiddleware::class);
|
||||
|
||||
View::composer([
|
||||
'index',
|
||||
'list',
|
||||
'tag',
|
||||
'search',
|
||||
'partials.post-card',
|
||||
'partials.view-mode-switcher',
|
||||
], function ($view): void {
|
||||
if (! $view->offsetExists('homeViewMode')) {
|
||||
$view->with('homeViewMode', HomeViewMode::current());
|
||||
}
|
||||
});
|
||||
|
||||
// 后台紧凑化:侧边栏 + 表格行 + 筛选区(Filament 无官方全局紧凑开关,用 CSS 收紧)
|
||||
Filament::serving(function () {
|
||||
@@ -96,6 +115,22 @@ class AppServiceProvider extends ServiceProvider
|
||||
/* 文章编辑框加大(MarkdownEditor 基于 tiptap) */
|
||||
.fi-fo-markdown-editor .tiptap, .fi-fo-markdown-editor .ProseMirror { min-height: 26rem !important; }
|
||||
|
||||
/* 封面/媒体预览:禁止按栏宽 100% 拉伸,否则大图会把编辑页撑烂 */
|
||||
.ll-media-picker-preview img,
|
||||
img.h-32.max-w-full.rounded-md.object-contain {
|
||||
max-width: 220px !important;
|
||||
max-height: 124px !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
object-fit: contain !important;
|
||||
display: block !important;
|
||||
}
|
||||
.ll-media-picker-preview .relative,
|
||||
.relative.rounded-lg:has(> img.h-32.max-w-full) {
|
||||
width: fit-content;
|
||||
max-width: 15rem;
|
||||
}
|
||||
|
||||
/* 筛选条件区:整体一行横排,重置紧跟应用按钮,控件紧凑 */
|
||||
.fi-ta-filters-above-content-ctn { padding: 0.4rem 0.9rem !important; }
|
||||
.fi-ta-filters-heading { display: none !important; }
|
||||
|
||||
@@ -33,6 +33,18 @@ if (! function_exists('theme')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('blog_paginate')) {
|
||||
/**
|
||||
* Front-end pager: fewer page chips so the sidebar column does not wrap under.
|
||||
*/
|
||||
function blog_paginate(\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query, ?int $perPage = null): \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
{
|
||||
$perPage = max(1, (int) ($perPage ?? blog_setting('per_page', config('blog.per_page'))));
|
||||
|
||||
return $query->paginate($perPage)->onEachSide(1)->withQueryString();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('blog_setting')) {
|
||||
/**
|
||||
* 读取博客设置(Setting 表,带缓存)。
|
||||
|
||||
@@ -25,6 +25,8 @@ return [
|
||||
|
||||
'per_page' => 10,
|
||||
|
||||
'home_view_mode' => 'list',
|
||||
|
||||
'rss_num' => 20,
|
||||
|
||||
'comment_min_len' => 2,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasColumn('posts', 'sticky_level')) {
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('sticky_level')->default(0)->after('is_sticky');
|
||||
});
|
||||
}
|
||||
|
||||
DB::table('posts')->where('is_sticky', true)->where('sticky_level', 0)->update(['sticky_level' => 1]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (Schema::hasColumn('posts', 'sticky_level')) {
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->dropColumn('sticky_level');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -392,6 +392,7 @@ CREATE TABLE `posts` (
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
|
||||
`is_sticky` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sticky_level` tinyint unsigned NOT NULL DEFAULT '0',
|
||||
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`views` bigint unsigned NOT NULL DEFAULT '0',
|
||||
@@ -580,3 +581,4 @@ INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_15_000
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_15_000004_add_custom_properties_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_09_16_000001_add_cover_media_id_to_posts_and_categories',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_09_16_000002_seed_default_media_folders',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (26,'2026_09_18_210000_add_sticky_level_to_posts_table',3);
|
||||
|
||||
@@ -33,6 +33,7 @@ class DatabaseSeeder extends Seeder
|
||||
'site_description' => config('blog.description'),
|
||||
'site_icp' => config('blog.icp'),
|
||||
'active_theme' => 'sablog',
|
||||
'home_view_mode' => 'list',
|
||||
'comment_audit' => '0',
|
||||
'comment_min_len' => (string) config('blog.comment_min_len'),
|
||||
'comment_max_len' => (string) config('blog.comment_max_len'),
|
||||
|
||||
@@ -27,7 +27,7 @@ app/
|
||||
Providers/ # ThemeServiceProvider、PluginManagerServiceProvider
|
||||
View/Composers/ # 侧边栏数据共享
|
||||
Filament/ # 后台(Resources/Pages/Widgets)
|
||||
Console/Commands/ # sablog:import / attachments:sync-s3 / workerman:serve / content:convert / theme:publish
|
||||
Console/Commands/ # sablog:import / sablog:fix-content / attachments:sync-s3 / workerman:serve / content:convert / theme:publish
|
||||
Models/ # Post/Category/Comment/Link/Setting/User...
|
||||
themes/ # 皮肤(theme.json + views + assets)
|
||||
plugins/ # 插件(plugin.json + ServiceProvider + 迁移/路由/视图)
|
||||
|
||||
@@ -0,0 +1,582 @@
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
DROP TABLE IF EXISTS `cache`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `cache_locks`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache_locks` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_locks_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `categories`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `categories` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(120) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`post_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `categories_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `comments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `comments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`post_id` bigint unsigned NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`author_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`author_email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`author_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`content` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`ip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`ai_review` json DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `comments_user_id_foreign` (`user_id`),
|
||||
KEY `comments_post_id_status_index` (`post_id`,`status`),
|
||||
KEY `comments_status_index` (`status`),
|
||||
CONSTRAINT `comments_post_id_foreign` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `comments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `failed_jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `failed_jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `hello_world_logs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `hello_world_logs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`comment_id` bigint unsigned DEFAULT NULL,
|
||||
`note` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `hello_world_logs_comment_id_foreign` (`comment_id`),
|
||||
CONSTRAINT `hello_world_logs_comment_id_foreign` FOREIGN KEY (`comment_id`) REFERENCES `comments` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `job_batches`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `job_batches` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`total_jobs` int NOT NULL,
|
||||
`pending_jobs` int NOT NULL,
|
||||
`failed_jobs` int NOT NULL,
|
||||
`failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`options` mediumtext COLLATE utf8mb4_unicode_ci,
|
||||
`cancelled_at` int DEFAULT NULL,
|
||||
`created_at` int NOT NULL,
|
||||
`finished_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`attempts` tinyint unsigned NOT NULL,
|
||||
`reserved_at` int unsigned DEFAULT NULL,
|
||||
`available_at` int unsigned NOT NULL,
|
||||
`created_at` int unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `jobs_queue_index` (`queue`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `links`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `links` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`note` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`visible` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `market_packages`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `market_packages` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`item_key` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`item_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`payment_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `market_packages_item_key_item_type_unique` (`item_key`,`item_type`),
|
||||
KEY `market_packages_payment_id_foreign` (`payment_id`),
|
||||
KEY `market_packages_user_id_foreign` (`user_id`),
|
||||
CONSTRAINT `market_packages_payment_id_foreign` FOREIGN KEY (`payment_id`) REFERENCES `payments` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `market_packages_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
`uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`collection_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`disk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`conversions_disk` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL,
|
||||
`manipulations` json NOT NULL,
|
||||
`custom_properties` json NOT NULL,
|
||||
`generated_conversions` json NOT NULL,
|
||||
`responsive_images` json NOT NULL,
|
||||
`order_column` int unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_uuid_unique` (`uuid`),
|
||||
KEY `media_model_type_model_id_index` (`model_type`,`model_id`),
|
||||
KEY `media_order_column_index` (`order_column`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_folders`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_folders` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`parent_id` bigint unsigned DEFAULT NULL,
|
||||
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`sort` int NOT NULL DEFAULT '0',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_folders_code_unique` (`code`),
|
||||
KEY `media_folders_parent_id_foreign` (`parent_id`),
|
||||
CONSTRAINT `media_folders_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `media_folders` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_library`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_library` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`disk` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
|
||||
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`original_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`extension` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`type` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'image',
|
||||
`source` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin',
|
||||
`folder` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`alt_text` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`custom_properties` json DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_library_path_unique` (`path`),
|
||||
KEY `media_library_user_id_foreign` (`user_id`),
|
||||
KEY `media_library_type_index` (`type`),
|
||||
KEY `media_library_folder_index` (`folder`),
|
||||
KEY `media_library_source_index` (`source`),
|
||||
CONSTRAINT `media_library_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `membership_plans`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `membership_plans` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`duration_days` int unsigned NOT NULL DEFAULT '30',
|
||||
`permissions` json DEFAULT NULL,
|
||||
`active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`sort` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `membership_plans_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `migrations` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`batch` int NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_roles` (
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`role_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `password_reset_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `password_reset_tokens` (
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `payments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `payments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`order_no` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`subject` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`amount` bigint unsigned NOT NULL,
|
||||
`channel` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'alipay',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`gateway_trade_no` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payload` json DEFAULT NULL,
|
||||
`paid_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`payable_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payable_id` bigint unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `payments_order_no_unique` (`order_no`),
|
||||
KEY `payments_user_id_status_index` (`user_id`,`status`),
|
||||
KEY `payments_payable_type_payable_id_index` (`payable_type`,`payable_id`),
|
||||
CONSTRAINT `payments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `permissions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `personal_access_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `personal_access_tokens` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tokenable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`tokenable_id` bigint unsigned NOT NULL,
|
||||
`name` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`abilities` text COLLATE utf8mb4_unicode_ci,
|
||||
`last_used_at` timestamp NULL DEFAULT NULL,
|
||||
`expires_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
|
||||
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`),
|
||||
KEY `personal_access_tokens_expires_at_index` (`expires_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `plugin_records`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `plugin_records` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`vendor` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`version` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `plugin_records_vendor_name_unique` (`vendor`,`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `posts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `posts` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`category_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`excerpt` text COLLATE utf8mb4_unicode_ci,
|
||||
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`content_format` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'markdown',
|
||||
`keywords` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
|
||||
`is_sticky` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`views` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`comment_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`meta` json DEFAULT NULL,
|
||||
`published_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `posts_slug_unique` (`slug`),
|
||||
KEY `posts_category_id_foreign` (`category_id`),
|
||||
KEY `posts_user_id_foreign` (`user_id`),
|
||||
KEY `posts_status_published_at_index` (`status`,`published_at`),
|
||||
KEY `posts_status_index` (`status`),
|
||||
KEY `posts_published_at_index` (`published_at`),
|
||||
CONSTRAINT `posts_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `posts_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `redirects`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `redirects` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`hash` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`target_url` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`clicks` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`last_clicked_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `redirects_hash_unique` (`hash`),
|
||||
KEY `redirects_target_url_index` (`target_url`(191))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `role_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `role_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`role_id`),
|
||||
KEY `role_has_permissions_role_id_foreign` (`role_id`),
|
||||
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `roles` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `sessions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `sessions` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`user_agent` text COLLATE utf8mb4_unicode_ci,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`last_activity` int NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `sessions_user_id_index` (`user_id`),
|
||||
KEY `sessions_last_activity_index` (`last_activity`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `settings`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `settings` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `subscriptions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `subscriptions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned NOT NULL,
|
||||
`membership_plan_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`starts_at` timestamp NULL DEFAULT NULL,
|
||||
`ends_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `subscriptions_membership_plan_id_foreign` (`membership_plan_id`),
|
||||
KEY `subscriptions_user_id_status_index` (`user_id`,`status`),
|
||||
CONSTRAINT `subscriptions_membership_plan_id_foreign` FOREIGN KEY (`membership_plan_id`) REFERENCES `membership_plans` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `subscriptions_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `taggables`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `taggables` (
|
||||
`tag_id` bigint unsigned NOT NULL,
|
||||
`taggable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`taggable_id` bigint unsigned NOT NULL,
|
||||
UNIQUE KEY `taggables_tag_id_taggable_id_taggable_type_unique` (`tag_id`,`taggable_id`,`taggable_type`),
|
||||
KEY `taggables_taggable_type_taggable_id_index` (`taggable_type`,`taggable_id`),
|
||||
CONSTRAINT `taggables_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `tags` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` json NOT NULL,
|
||||
`slug` json NOT NULL,
|
||||
`type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`order_column` int DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email_verified_at` timestamp NULL DEFAULT NULL,
|
||||
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`legacy_md5` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logincount` int unsigned NOT NULL DEFAULT '0',
|
||||
`loginip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`regip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logintime` timestamp NULL DEFAULT NULL,
|
||||
`lastpost_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `users_email_unique` (`email`),
|
||||
KEY `users_legacy_md5_index` (`legacy_md5`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (1,'0001_01_01_000000_create_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (2,'0001_01_01_000001_create_cache_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (3,'0001_01_01_000002_create_jobs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (4,'2026_08_11_092446_create_permission_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (5,'2026_08_11_092448_create_media_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (6,'2026_08_11_092449_create_tag_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (7,'2026_08_11_100000_create_membership_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (8,'2026_08_11_100000_create_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (9,'2026_08_11_100001_create_categories_and_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (10,'2026_08_11_100002_create_comments_and_trackbacks_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (11,'2026_08_11_100003_create_links_and_settings_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (12,'2026_08_11_100004_add_blog_columns_to_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (13,'2026_08_11_100005_add_content_format_to_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (14,'2026_08_11_113537_create_personal_access_tokens_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (15,'2026_08_12_000000_create_hello_world_logs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (16,'2026_08_12_100000_add_payable_to_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (17,'2026_08_12_200000_create_market_packages_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (18,'2026_08_12_300000_add_description_to_categories_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (19,'2026_08_12_400000_create_redirects_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (20,'2026_08_15_000001_create_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (21,'2026_08_15_000002_create_media_folders_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_15_000003_add_soft_deletes_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_15_000004_add_custom_properties_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_09_16_000001_add_cover_media_id_to_posts_and_categories',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_09_16_000002_seed_default_media_folders',2);
|
||||
@@ -0,0 +1,584 @@
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
DROP TABLE IF EXISTS `cache`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `cache_locks`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache_locks` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_locks_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `categories`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `categories` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(120) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`post_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `categories_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `comments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `comments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`post_id` bigint unsigned NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`author_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`author_email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`author_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`content` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`ip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`ai_review` json DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `comments_user_id_foreign` (`user_id`),
|
||||
KEY `comments_post_id_status_index` (`post_id`,`status`),
|
||||
KEY `comments_status_index` (`status`),
|
||||
CONSTRAINT `comments_post_id_foreign` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `comments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `failed_jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `failed_jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `hello_world_logs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `hello_world_logs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`comment_id` bigint unsigned DEFAULT NULL,
|
||||
`note` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `hello_world_logs_comment_id_foreign` (`comment_id`),
|
||||
CONSTRAINT `hello_world_logs_comment_id_foreign` FOREIGN KEY (`comment_id`) REFERENCES `comments` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `job_batches`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `job_batches` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`total_jobs` int NOT NULL,
|
||||
`pending_jobs` int NOT NULL,
|
||||
`failed_jobs` int NOT NULL,
|
||||
`failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`options` mediumtext COLLATE utf8mb4_unicode_ci,
|
||||
`cancelled_at` int DEFAULT NULL,
|
||||
`created_at` int NOT NULL,
|
||||
`finished_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`attempts` tinyint unsigned NOT NULL,
|
||||
`reserved_at` int unsigned DEFAULT NULL,
|
||||
`available_at` int unsigned NOT NULL,
|
||||
`created_at` int unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `jobs_queue_index` (`queue`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `links`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `links` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`note` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`visible` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `market_packages`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `market_packages` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`item_key` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`item_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`payment_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `market_packages_item_key_item_type_unique` (`item_key`,`item_type`),
|
||||
KEY `market_packages_payment_id_foreign` (`payment_id`),
|
||||
KEY `market_packages_user_id_foreign` (`user_id`),
|
||||
CONSTRAINT `market_packages_payment_id_foreign` FOREIGN KEY (`payment_id`) REFERENCES `payments` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `market_packages_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
`uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`collection_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`disk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`conversions_disk` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL,
|
||||
`manipulations` json NOT NULL,
|
||||
`custom_properties` json NOT NULL,
|
||||
`generated_conversions` json NOT NULL,
|
||||
`responsive_images` json NOT NULL,
|
||||
`order_column` int unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_uuid_unique` (`uuid`),
|
||||
KEY `media_model_type_model_id_index` (`model_type`,`model_id`),
|
||||
KEY `media_order_column_index` (`order_column`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_folders`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_folders` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`parent_id` bigint unsigned DEFAULT NULL,
|
||||
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`sort` int NOT NULL DEFAULT '0',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_folders_code_unique` (`code`),
|
||||
KEY `media_folders_parent_id_foreign` (`parent_id`),
|
||||
CONSTRAINT `media_folders_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `media_folders` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_library`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_library` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`disk` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
|
||||
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`original_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`extension` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`type` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'image',
|
||||
`source` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin',
|
||||
`folder` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`alt_text` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`custom_properties` json DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_library_path_unique` (`path`),
|
||||
KEY `media_library_user_id_foreign` (`user_id`),
|
||||
KEY `media_library_type_index` (`type`),
|
||||
KEY `media_library_folder_index` (`folder`),
|
||||
KEY `media_library_source_index` (`source`),
|
||||
CONSTRAINT `media_library_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `membership_plans`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `membership_plans` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`duration_days` int unsigned NOT NULL DEFAULT '30',
|
||||
`permissions` json DEFAULT NULL,
|
||||
`active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`sort` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `membership_plans_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `migrations` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`batch` int NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_roles` (
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`role_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `password_reset_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `password_reset_tokens` (
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `payments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `payments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`order_no` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`subject` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`amount` bigint unsigned NOT NULL,
|
||||
`channel` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'alipay',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`gateway_trade_no` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payload` json DEFAULT NULL,
|
||||
`paid_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`payable_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payable_id` bigint unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `payments_order_no_unique` (`order_no`),
|
||||
KEY `payments_user_id_status_index` (`user_id`,`status`),
|
||||
KEY `payments_payable_type_payable_id_index` (`payable_type`,`payable_id`),
|
||||
CONSTRAINT `payments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `permissions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `personal_access_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `personal_access_tokens` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tokenable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`tokenable_id` bigint unsigned NOT NULL,
|
||||
`name` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`abilities` text COLLATE utf8mb4_unicode_ci,
|
||||
`last_used_at` timestamp NULL DEFAULT NULL,
|
||||
`expires_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
|
||||
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`),
|
||||
KEY `personal_access_tokens_expires_at_index` (`expires_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `plugin_records`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `plugin_records` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`vendor` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`version` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `plugin_records_vendor_name_unique` (`vendor`,`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `posts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `posts` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`category_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`excerpt` text COLLATE utf8mb4_unicode_ci,
|
||||
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`content_format` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'markdown',
|
||||
`keywords` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
|
||||
`is_sticky` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sticky_level` tinyint unsigned NOT NULL DEFAULT '0',
|
||||
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`views` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`comment_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`meta` json DEFAULT NULL,
|
||||
`published_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `posts_slug_unique` (`slug`),
|
||||
KEY `posts_category_id_foreign` (`category_id`),
|
||||
KEY `posts_user_id_foreign` (`user_id`),
|
||||
KEY `posts_status_published_at_index` (`status`,`published_at`),
|
||||
KEY `posts_status_index` (`status`),
|
||||
KEY `posts_published_at_index` (`published_at`),
|
||||
CONSTRAINT `posts_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `posts_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `redirects`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `redirects` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`hash` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`target_url` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`clicks` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`last_clicked_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `redirects_hash_unique` (`hash`),
|
||||
KEY `redirects_target_url_index` (`target_url`(191))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `role_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `role_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`role_id`),
|
||||
KEY `role_has_permissions_role_id_foreign` (`role_id`),
|
||||
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `roles` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `sessions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `sessions` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`user_agent` text COLLATE utf8mb4_unicode_ci,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`last_activity` int NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `sessions_user_id_index` (`user_id`),
|
||||
KEY `sessions_last_activity_index` (`last_activity`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `settings`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `settings` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `subscriptions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `subscriptions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned NOT NULL,
|
||||
`membership_plan_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`starts_at` timestamp NULL DEFAULT NULL,
|
||||
`ends_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `subscriptions_membership_plan_id_foreign` (`membership_plan_id`),
|
||||
KEY `subscriptions_user_id_status_index` (`user_id`,`status`),
|
||||
CONSTRAINT `subscriptions_membership_plan_id_foreign` FOREIGN KEY (`membership_plan_id`) REFERENCES `membership_plans` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `subscriptions_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `taggables`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `taggables` (
|
||||
`tag_id` bigint unsigned NOT NULL,
|
||||
`taggable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`taggable_id` bigint unsigned NOT NULL,
|
||||
UNIQUE KEY `taggables_tag_id_taggable_id_taggable_type_unique` (`tag_id`,`taggable_id`,`taggable_type`),
|
||||
KEY `taggables_taggable_type_taggable_id_index` (`taggable_type`,`taggable_id`),
|
||||
CONSTRAINT `taggables_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `tags` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` json NOT NULL,
|
||||
`slug` json NOT NULL,
|
||||
`type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`order_column` int DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email_verified_at` timestamp NULL DEFAULT NULL,
|
||||
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`legacy_md5` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logincount` int unsigned NOT NULL DEFAULT '0',
|
||||
`loginip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`regip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logintime` timestamp NULL DEFAULT NULL,
|
||||
`lastpost_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `users_email_unique` (`email`),
|
||||
KEY `users_legacy_md5_index` (`legacy_md5`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (1,'0001_01_01_000000_create_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (2,'0001_01_01_000001_create_cache_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (3,'0001_01_01_000002_create_jobs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (4,'2026_08_11_092446_create_permission_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (5,'2026_08_11_092448_create_media_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (6,'2026_08_11_092449_create_tag_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (7,'2026_08_11_100000_create_membership_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (8,'2026_08_11_100000_create_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (9,'2026_08_11_100001_create_categories_and_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (10,'2026_08_11_100002_create_comments_and_trackbacks_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (11,'2026_08_11_100003_create_links_and_settings_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (12,'2026_08_11_100004_add_blog_columns_to_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (13,'2026_08_11_100005_add_content_format_to_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (14,'2026_08_11_113537_create_personal_access_tokens_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (15,'2026_08_12_000000_create_hello_world_logs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (16,'2026_08_12_100000_add_payable_to_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (17,'2026_08_12_200000_create_market_packages_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (18,'2026_08_12_300000_add_description_to_categories_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (19,'2026_08_12_400000_create_redirects_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (20,'2026_08_15_000001_create_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (21,'2026_08_15_000002_create_media_folders_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_15_000003_add_soft_deletes_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_15_000004_add_custom_properties_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_09_16_000001_add_cover_media_id_to_posts_and_categories',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_09_16_000002_seed_default_media_folders',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (26,'2026_09_18_210000_add_sticky_level_to_posts_table',3);
|
||||
@@ -0,0 +1,584 @@
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
DROP TABLE IF EXISTS `cache`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `cache_locks`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache_locks` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_locks_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `categories`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `categories` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(120) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`post_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `categories_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `comments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `comments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`post_id` bigint unsigned NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`author_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`author_email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`author_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`content` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`ip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`ai_review` json DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `comments_user_id_foreign` (`user_id`),
|
||||
KEY `comments_post_id_status_index` (`post_id`,`status`),
|
||||
KEY `comments_status_index` (`status`),
|
||||
CONSTRAINT `comments_post_id_foreign` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `comments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `failed_jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `failed_jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `hello_world_logs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `hello_world_logs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`comment_id` bigint unsigned DEFAULT NULL,
|
||||
`note` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `hello_world_logs_comment_id_foreign` (`comment_id`),
|
||||
CONSTRAINT `hello_world_logs_comment_id_foreign` FOREIGN KEY (`comment_id`) REFERENCES `comments` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `job_batches`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `job_batches` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`total_jobs` int NOT NULL,
|
||||
`pending_jobs` int NOT NULL,
|
||||
`failed_jobs` int NOT NULL,
|
||||
`failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`options` mediumtext COLLATE utf8mb4_unicode_ci,
|
||||
`cancelled_at` int DEFAULT NULL,
|
||||
`created_at` int NOT NULL,
|
||||
`finished_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`attempts` tinyint unsigned NOT NULL,
|
||||
`reserved_at` int unsigned DEFAULT NULL,
|
||||
`available_at` int unsigned NOT NULL,
|
||||
`created_at` int unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `jobs_queue_index` (`queue`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `links`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `links` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`note` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`visible` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `market_packages`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `market_packages` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`item_key` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`item_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`payment_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `market_packages_item_key_item_type_unique` (`item_key`,`item_type`),
|
||||
KEY `market_packages_payment_id_foreign` (`payment_id`),
|
||||
KEY `market_packages_user_id_foreign` (`user_id`),
|
||||
CONSTRAINT `market_packages_payment_id_foreign` FOREIGN KEY (`payment_id`) REFERENCES `payments` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `market_packages_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
`uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`collection_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`disk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`conversions_disk` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL,
|
||||
`manipulations` json NOT NULL,
|
||||
`custom_properties` json NOT NULL,
|
||||
`generated_conversions` json NOT NULL,
|
||||
`responsive_images` json NOT NULL,
|
||||
`order_column` int unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_uuid_unique` (`uuid`),
|
||||
KEY `media_model_type_model_id_index` (`model_type`,`model_id`),
|
||||
KEY `media_order_column_index` (`order_column`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_folders`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_folders` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`parent_id` bigint unsigned DEFAULT NULL,
|
||||
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`sort` int NOT NULL DEFAULT '0',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_folders_code_unique` (`code`),
|
||||
KEY `media_folders_parent_id_foreign` (`parent_id`),
|
||||
CONSTRAINT `media_folders_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `media_folders` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_library`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_library` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`disk` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
|
||||
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`original_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`extension` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`type` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'image',
|
||||
`source` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin',
|
||||
`folder` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`alt_text` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`custom_properties` json DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_library_path_unique` (`path`),
|
||||
KEY `media_library_user_id_foreign` (`user_id`),
|
||||
KEY `media_library_type_index` (`type`),
|
||||
KEY `media_library_folder_index` (`folder`),
|
||||
KEY `media_library_source_index` (`source`),
|
||||
CONSTRAINT `media_library_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `membership_plans`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `membership_plans` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`duration_days` int unsigned NOT NULL DEFAULT '30',
|
||||
`permissions` json DEFAULT NULL,
|
||||
`active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`sort` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `membership_plans_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `migrations` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`batch` int NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_roles` (
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`role_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `password_reset_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `password_reset_tokens` (
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `payments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `payments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`order_no` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`subject` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`amount` bigint unsigned NOT NULL,
|
||||
`channel` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'alipay',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`gateway_trade_no` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payload` json DEFAULT NULL,
|
||||
`paid_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`payable_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payable_id` bigint unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `payments_order_no_unique` (`order_no`),
|
||||
KEY `payments_user_id_status_index` (`user_id`,`status`),
|
||||
KEY `payments_payable_type_payable_id_index` (`payable_type`,`payable_id`),
|
||||
CONSTRAINT `payments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `permissions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `personal_access_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `personal_access_tokens` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tokenable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`tokenable_id` bigint unsigned NOT NULL,
|
||||
`name` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`abilities` text COLLATE utf8mb4_unicode_ci,
|
||||
`last_used_at` timestamp NULL DEFAULT NULL,
|
||||
`expires_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
|
||||
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`),
|
||||
KEY `personal_access_tokens_expires_at_index` (`expires_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `plugin_records`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `plugin_records` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`vendor` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`version` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `plugin_records_vendor_name_unique` (`vendor`,`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `posts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `posts` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`category_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`excerpt` text COLLATE utf8mb4_unicode_ci,
|
||||
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`content_format` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'markdown',
|
||||
`keywords` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
|
||||
`is_sticky` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sticky_level` tinyint unsigned NOT NULL DEFAULT '0',
|
||||
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`views` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`comment_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`meta` json DEFAULT NULL,
|
||||
`published_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `posts_slug_unique` (`slug`),
|
||||
KEY `posts_category_id_foreign` (`category_id`),
|
||||
KEY `posts_user_id_foreign` (`user_id`),
|
||||
KEY `posts_status_published_at_index` (`status`,`published_at`),
|
||||
KEY `posts_status_index` (`status`),
|
||||
KEY `posts_published_at_index` (`published_at`),
|
||||
CONSTRAINT `posts_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `posts_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `redirects`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `redirects` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`hash` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`target_url` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`clicks` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`last_clicked_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `redirects_hash_unique` (`hash`),
|
||||
KEY `redirects_target_url_index` (`target_url`(191))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `role_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `role_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`role_id`),
|
||||
KEY `role_has_permissions_role_id_foreign` (`role_id`),
|
||||
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `roles` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `sessions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `sessions` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`user_agent` text COLLATE utf8mb4_unicode_ci,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`last_activity` int NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `sessions_user_id_index` (`user_id`),
|
||||
KEY `sessions_last_activity_index` (`last_activity`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `settings`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `settings` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `subscriptions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `subscriptions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned NOT NULL,
|
||||
`membership_plan_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`starts_at` timestamp NULL DEFAULT NULL,
|
||||
`ends_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `subscriptions_membership_plan_id_foreign` (`membership_plan_id`),
|
||||
KEY `subscriptions_user_id_status_index` (`user_id`,`status`),
|
||||
CONSTRAINT `subscriptions_membership_plan_id_foreign` FOREIGN KEY (`membership_plan_id`) REFERENCES `membership_plans` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `subscriptions_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `taggables`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `taggables` (
|
||||
`tag_id` bigint unsigned NOT NULL,
|
||||
`taggable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`taggable_id` bigint unsigned NOT NULL,
|
||||
UNIQUE KEY `taggables_tag_id_taggable_id_taggable_type_unique` (`tag_id`,`taggable_id`,`taggable_type`),
|
||||
KEY `taggables_taggable_type_taggable_id_index` (`taggable_type`,`taggable_id`),
|
||||
CONSTRAINT `taggables_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `tags` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` json NOT NULL,
|
||||
`slug` json NOT NULL,
|
||||
`type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`order_column` int DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email_verified_at` timestamp NULL DEFAULT NULL,
|
||||
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`legacy_md5` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logincount` int unsigned NOT NULL DEFAULT '0',
|
||||
`loginip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`regip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logintime` timestamp NULL DEFAULT NULL,
|
||||
`lastpost_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `users_email_unique` (`email`),
|
||||
KEY `users_legacy_md5_index` (`legacy_md5`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (1,'0001_01_01_000000_create_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (2,'0001_01_01_000001_create_cache_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (3,'0001_01_01_000002_create_jobs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (4,'2026_08_11_092446_create_permission_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (5,'2026_08_11_092448_create_media_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (6,'2026_08_11_092449_create_tag_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (7,'2026_08_11_100000_create_membership_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (8,'2026_08_11_100000_create_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (9,'2026_08_11_100001_create_categories_and_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (10,'2026_08_11_100002_create_comments_and_trackbacks_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (11,'2026_08_11_100003_create_links_and_settings_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (12,'2026_08_11_100004_add_blog_columns_to_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (13,'2026_08_11_100005_add_content_format_to_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (14,'2026_08_11_113537_create_personal_access_tokens_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (15,'2026_08_12_000000_create_hello_world_logs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (16,'2026_08_12_100000_add_payable_to_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (17,'2026_08_12_200000_create_market_packages_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (18,'2026_08_12_300000_add_description_to_categories_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (19,'2026_08_12_400000_create_redirects_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (20,'2026_08_15_000001_create_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (21,'2026_08_15_000002_create_media_folders_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_15_000003_add_soft_deletes_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_15_000004_add_custom_properties_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_09_16_000001_add_cover_media_id_to_posts_and_categories',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_09_16_000002_seed_default_media_folders',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (26,'2026_09_18_210000_add_sticky_level_to_posts_table',3);
|
||||
@@ -0,0 +1,584 @@
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
DROP TABLE IF EXISTS `cache`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `cache_locks`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache_locks` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_locks_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `categories`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `categories` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(120) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`post_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `categories_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `comments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `comments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`post_id` bigint unsigned NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`author_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`author_email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`author_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`content` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`ip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`ai_review` json DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `comments_user_id_foreign` (`user_id`),
|
||||
KEY `comments_post_id_status_index` (`post_id`,`status`),
|
||||
KEY `comments_status_index` (`status`),
|
||||
CONSTRAINT `comments_post_id_foreign` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `comments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `failed_jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `failed_jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `hello_world_logs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `hello_world_logs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`comment_id` bigint unsigned DEFAULT NULL,
|
||||
`note` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `hello_world_logs_comment_id_foreign` (`comment_id`),
|
||||
CONSTRAINT `hello_world_logs_comment_id_foreign` FOREIGN KEY (`comment_id`) REFERENCES `comments` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `job_batches`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `job_batches` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`total_jobs` int NOT NULL,
|
||||
`pending_jobs` int NOT NULL,
|
||||
`failed_jobs` int NOT NULL,
|
||||
`failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`options` mediumtext COLLATE utf8mb4_unicode_ci,
|
||||
`cancelled_at` int DEFAULT NULL,
|
||||
`created_at` int NOT NULL,
|
||||
`finished_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`attempts` tinyint unsigned NOT NULL,
|
||||
`reserved_at` int unsigned DEFAULT NULL,
|
||||
`available_at` int unsigned NOT NULL,
|
||||
`created_at` int unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `jobs_queue_index` (`queue`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `links`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `links` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`note` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`visible` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `market_packages`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `market_packages` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`item_key` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`item_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`payment_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `market_packages_item_key_item_type_unique` (`item_key`,`item_type`),
|
||||
KEY `market_packages_payment_id_foreign` (`payment_id`),
|
||||
KEY `market_packages_user_id_foreign` (`user_id`),
|
||||
CONSTRAINT `market_packages_payment_id_foreign` FOREIGN KEY (`payment_id`) REFERENCES `payments` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `market_packages_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
`uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`collection_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`disk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`conversions_disk` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL,
|
||||
`manipulations` json NOT NULL,
|
||||
`custom_properties` json NOT NULL,
|
||||
`generated_conversions` json NOT NULL,
|
||||
`responsive_images` json NOT NULL,
|
||||
`order_column` int unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_uuid_unique` (`uuid`),
|
||||
KEY `media_model_type_model_id_index` (`model_type`,`model_id`),
|
||||
KEY `media_order_column_index` (`order_column`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_folders`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_folders` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`parent_id` bigint unsigned DEFAULT NULL,
|
||||
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`sort` int NOT NULL DEFAULT '0',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_folders_code_unique` (`code`),
|
||||
KEY `media_folders_parent_id_foreign` (`parent_id`),
|
||||
CONSTRAINT `media_folders_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `media_folders` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_library`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_library` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`disk` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
|
||||
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`original_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`extension` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`type` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'image',
|
||||
`source` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin',
|
||||
`folder` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`alt_text` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`custom_properties` json DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_library_path_unique` (`path`),
|
||||
KEY `media_library_user_id_foreign` (`user_id`),
|
||||
KEY `media_library_type_index` (`type`),
|
||||
KEY `media_library_folder_index` (`folder`),
|
||||
KEY `media_library_source_index` (`source`),
|
||||
CONSTRAINT `media_library_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `membership_plans`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `membership_plans` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`duration_days` int unsigned NOT NULL DEFAULT '30',
|
||||
`permissions` json DEFAULT NULL,
|
||||
`active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`sort` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `membership_plans_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `migrations` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`batch` int NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_roles` (
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`role_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `password_reset_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `password_reset_tokens` (
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `payments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `payments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`order_no` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`subject` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`amount` bigint unsigned NOT NULL,
|
||||
`channel` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'alipay',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`gateway_trade_no` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payload` json DEFAULT NULL,
|
||||
`paid_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`payable_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payable_id` bigint unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `payments_order_no_unique` (`order_no`),
|
||||
KEY `payments_user_id_status_index` (`user_id`,`status`),
|
||||
KEY `payments_payable_type_payable_id_index` (`payable_type`,`payable_id`),
|
||||
CONSTRAINT `payments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `permissions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `personal_access_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `personal_access_tokens` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tokenable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`tokenable_id` bigint unsigned NOT NULL,
|
||||
`name` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`abilities` text COLLATE utf8mb4_unicode_ci,
|
||||
`last_used_at` timestamp NULL DEFAULT NULL,
|
||||
`expires_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
|
||||
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`),
|
||||
KEY `personal_access_tokens_expires_at_index` (`expires_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `plugin_records`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `plugin_records` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`vendor` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`version` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `plugin_records_vendor_name_unique` (`vendor`,`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `posts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `posts` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`category_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`excerpt` text COLLATE utf8mb4_unicode_ci,
|
||||
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`content_format` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'markdown',
|
||||
`keywords` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
|
||||
`is_sticky` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sticky_level` tinyint unsigned NOT NULL DEFAULT '0',
|
||||
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`views` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`comment_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`meta` json DEFAULT NULL,
|
||||
`published_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `posts_slug_unique` (`slug`),
|
||||
KEY `posts_category_id_foreign` (`category_id`),
|
||||
KEY `posts_user_id_foreign` (`user_id`),
|
||||
KEY `posts_status_published_at_index` (`status`,`published_at`),
|
||||
KEY `posts_status_index` (`status`),
|
||||
KEY `posts_published_at_index` (`published_at`),
|
||||
CONSTRAINT `posts_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `posts_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `redirects`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `redirects` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`hash` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`target_url` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`clicks` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`last_clicked_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `redirects_hash_unique` (`hash`),
|
||||
KEY `redirects_target_url_index` (`target_url`(191))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `role_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `role_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`role_id`),
|
||||
KEY `role_has_permissions_role_id_foreign` (`role_id`),
|
||||
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `roles` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `sessions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `sessions` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`user_agent` text COLLATE utf8mb4_unicode_ci,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`last_activity` int NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `sessions_user_id_index` (`user_id`),
|
||||
KEY `sessions_last_activity_index` (`last_activity`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `settings`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `settings` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `subscriptions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `subscriptions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned NOT NULL,
|
||||
`membership_plan_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`starts_at` timestamp NULL DEFAULT NULL,
|
||||
`ends_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `subscriptions_membership_plan_id_foreign` (`membership_plan_id`),
|
||||
KEY `subscriptions_user_id_status_index` (`user_id`,`status`),
|
||||
CONSTRAINT `subscriptions_membership_plan_id_foreign` FOREIGN KEY (`membership_plan_id`) REFERENCES `membership_plans` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `subscriptions_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `taggables`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `taggables` (
|
||||
`tag_id` bigint unsigned NOT NULL,
|
||||
`taggable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`taggable_id` bigint unsigned NOT NULL,
|
||||
UNIQUE KEY `taggables_tag_id_taggable_id_taggable_type_unique` (`tag_id`,`taggable_id`,`taggable_type`),
|
||||
KEY `taggables_taggable_type_taggable_id_index` (`taggable_type`,`taggable_id`),
|
||||
CONSTRAINT `taggables_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `tags` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` json NOT NULL,
|
||||
`slug` json NOT NULL,
|
||||
`type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`order_column` int DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email_verified_at` timestamp NULL DEFAULT NULL,
|
||||
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`legacy_md5` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logincount` int unsigned NOT NULL DEFAULT '0',
|
||||
`loginip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`regip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logintime` timestamp NULL DEFAULT NULL,
|
||||
`lastpost_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `users_email_unique` (`email`),
|
||||
KEY `users_legacy_md5_index` (`legacy_md5`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (1,'0001_01_01_000000_create_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (2,'0001_01_01_000001_create_cache_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (3,'0001_01_01_000002_create_jobs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (4,'2026_08_11_092446_create_permission_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (5,'2026_08_11_092448_create_media_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (6,'2026_08_11_092449_create_tag_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (7,'2026_08_11_100000_create_membership_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (8,'2026_08_11_100000_create_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (9,'2026_08_11_100001_create_categories_and_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (10,'2026_08_11_100002_create_comments_and_trackbacks_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (11,'2026_08_11_100003_create_links_and_settings_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (12,'2026_08_11_100004_add_blog_columns_to_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (13,'2026_08_11_100005_add_content_format_to_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (14,'2026_08_11_113537_create_personal_access_tokens_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (15,'2026_08_12_000000_create_hello_world_logs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (16,'2026_08_12_100000_add_payable_to_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (17,'2026_08_12_200000_create_market_packages_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (18,'2026_08_12_300000_add_description_to_categories_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (19,'2026_08_12_400000_create_redirects_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (20,'2026_08_15_000001_create_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (21,'2026_08_15_000002_create_media_folders_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_15_000003_add_soft_deletes_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_15_000004_add_custom_properties_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_09_16_000001_add_cover_media_id_to_posts_and_categories',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_09_16_000002_seed_default_media_folders',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (26,'2026_09_18_210000_add_sticky_level_to_posts_table',3);
|
||||
@@ -0,0 +1,584 @@
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
DROP TABLE IF EXISTS `cache`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `cache_locks`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `cache_locks` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`expiration` int NOT NULL,
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `cache_locks_expiration_index` (`expiration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `categories`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `categories` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(120) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`post_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `categories_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `comments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `comments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`post_id` bigint unsigned NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`author_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`author_email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`author_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`content` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`ip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`ai_review` json DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `comments_user_id_foreign` (`user_id`),
|
||||
KEY `comments_post_id_status_index` (`post_id`,`status`),
|
||||
KEY `comments_status_index` (`status`),
|
||||
CONSTRAINT `comments_post_id_foreign` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `comments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `failed_jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `failed_jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `hello_world_logs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `hello_world_logs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`comment_id` bigint unsigned DEFAULT NULL,
|
||||
`note` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `hello_world_logs_comment_id_foreign` (`comment_id`),
|
||||
CONSTRAINT `hello_world_logs_comment_id_foreign` FOREIGN KEY (`comment_id`) REFERENCES `comments` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `job_batches`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `job_batches` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`total_jobs` int NOT NULL,
|
||||
`pending_jobs` int NOT NULL,
|
||||
`failed_jobs` int NOT NULL,
|
||||
`failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`options` mediumtext COLLATE utf8mb4_unicode_ci,
|
||||
`cancelled_at` int DEFAULT NULL,
|
||||
`created_at` int NOT NULL,
|
||||
`finished_at` int DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `jobs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `jobs` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`attempts` tinyint unsigned NOT NULL,
|
||||
`reserved_at` int unsigned DEFAULT NULL,
|
||||
`available_at` int unsigned NOT NULL,
|
||||
`created_at` int unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `jobs_queue_index` (`queue`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `links`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `links` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`note` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`display_order` tinyint NOT NULL DEFAULT '0',
|
||||
`visible` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `market_packages`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `market_packages` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`item_key` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`item_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`payment_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `market_packages_item_key_item_type_unique` (`item_key`,`item_type`),
|
||||
KEY `market_packages_payment_id_foreign` (`payment_id`),
|
||||
KEY `market_packages_user_id_foreign` (`user_id`),
|
||||
CONSTRAINT `market_packages_payment_id_foreign` FOREIGN KEY (`payment_id`) REFERENCES `payments` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `market_packages_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
`uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`collection_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`disk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`conversions_disk` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL,
|
||||
`manipulations` json NOT NULL,
|
||||
`custom_properties` json NOT NULL,
|
||||
`generated_conversions` json NOT NULL,
|
||||
`responsive_images` json NOT NULL,
|
||||
`order_column` int unsigned DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_uuid_unique` (`uuid`),
|
||||
KEY `media_model_type_model_id_index` (`model_type`,`model_id`),
|
||||
KEY `media_order_column_index` (`order_column`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_folders`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_folders` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`parent_id` bigint unsigned DEFAULT NULL,
|
||||
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`sort` int NOT NULL DEFAULT '0',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_folders_code_unique` (`code`),
|
||||
KEY `media_folders_parent_id_foreign` (`parent_id`),
|
||||
CONSTRAINT `media_folders_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `media_folders` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `media_library`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `media_library` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`disk` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
|
||||
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`original_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`extension` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`size` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`type` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'image',
|
||||
`source` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin',
|
||||
`folder` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`alt_text` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`custom_properties` json DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `media_library_path_unique` (`path`),
|
||||
KEY `media_library_user_id_foreign` (`user_id`),
|
||||
KEY `media_library_type_index` (`type`),
|
||||
KEY `media_library_folder_index` (`folder`),
|
||||
KEY `media_library_source_index` (`source`),
|
||||
CONSTRAINT `media_library_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `membership_plans`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `membership_plans` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` text COLLATE utf8mb4_unicode_ci,
|
||||
`price` bigint unsigned NOT NULL,
|
||||
`duration_days` int unsigned NOT NULL DEFAULT '30',
|
||||
`permissions` json DEFAULT NULL,
|
||||
`active` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`sort` int unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `membership_plans_slug_unique` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `migrations` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`batch` int NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `model_has_roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `model_has_roles` (
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`model_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`role_id`,`model_id`,`model_type`),
|
||||
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
|
||||
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `password_reset_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `password_reset_tokens` (
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `payments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `payments` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`order_no` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`subject` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`amount` bigint unsigned NOT NULL,
|
||||
`channel` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'alipay',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`gateway_trade_no` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payload` json DEFAULT NULL,
|
||||
`paid_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`payable_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`payable_id` bigint unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `payments_order_no_unique` (`order_no`),
|
||||
KEY `payments_user_id_status_index` (`user_id`,`status`),
|
||||
KEY `payments_payable_type_payable_id_index` (`payable_type`,`payable_id`),
|
||||
CONSTRAINT `payments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `permissions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `personal_access_tokens`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `personal_access_tokens` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tokenable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`tokenable_id` bigint unsigned NOT NULL,
|
||||
`name` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`abilities` text COLLATE utf8mb4_unicode_ci,
|
||||
`last_used_at` timestamp NULL DEFAULT NULL,
|
||||
`expires_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
|
||||
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`),
|
||||
KEY `personal_access_tokens_expires_at_index` (`expires_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `plugin_records`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `plugin_records` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`vendor` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`version` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `plugin_records_vendor_name_unique` (`vendor`,`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `posts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `posts` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`category_id` bigint unsigned DEFAULT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`excerpt` text COLLATE utf8mb4_unicode_ci,
|
||||
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`content_format` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'markdown',
|
||||
`keywords` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`cover_media_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
|
||||
`is_sticky` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sticky_level` tinyint unsigned NOT NULL DEFAULT '0',
|
||||
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`views` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`comment_count` int unsigned NOT NULL DEFAULT '0',
|
||||
`meta` json DEFAULT NULL,
|
||||
`published_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `posts_slug_unique` (`slug`),
|
||||
KEY `posts_category_id_foreign` (`category_id`),
|
||||
KEY `posts_user_id_foreign` (`user_id`),
|
||||
KEY `posts_status_published_at_index` (`status`,`published_at`),
|
||||
KEY `posts_status_index` (`status`),
|
||||
KEY `posts_published_at_index` (`published_at`),
|
||||
CONSTRAINT `posts_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `posts_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `redirects`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `redirects` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`hash` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`target_url` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`clicks` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`last_clicked_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `redirects_hash_unique` (`hash`),
|
||||
KEY `redirects_target_url_index` (`target_url`(191))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `role_has_permissions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `role_has_permissions` (
|
||||
`permission_id` bigint unsigned NOT NULL,
|
||||
`role_id` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`permission_id`,`role_id`),
|
||||
KEY `role_has_permissions_role_id_foreign` (`role_id`),
|
||||
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `roles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `roles` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `sessions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `sessions` (
|
||||
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` bigint unsigned DEFAULT NULL,
|
||||
`ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`user_agent` text COLLATE utf8mb4_unicode_ci,
|
||||
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`last_activity` int NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `sessions_user_id_index` (`user_id`),
|
||||
KEY `sessions_last_activity_index` (`last_activity`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `settings`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `settings` (
|
||||
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`value` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `subscriptions`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `subscriptions` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint unsigned NOT NULL,
|
||||
`membership_plan_id` bigint unsigned DEFAULT NULL,
|
||||
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||
`starts_at` timestamp NULL DEFAULT NULL,
|
||||
`ends_at` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `subscriptions_membership_plan_id_foreign` (`membership_plan_id`),
|
||||
KEY `subscriptions_user_id_status_index` (`user_id`,`status`),
|
||||
CONSTRAINT `subscriptions_membership_plan_id_foreign` FOREIGN KEY (`membership_plan_id`) REFERENCES `membership_plans` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `subscriptions_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `taggables`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `taggables` (
|
||||
`tag_id` bigint unsigned NOT NULL,
|
||||
`taggable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`taggable_id` bigint unsigned NOT NULL,
|
||||
UNIQUE KEY `taggables_tag_id_taggable_id_taggable_type_unique` (`tag_id`,`taggable_id`,`taggable_type`),
|
||||
KEY `taggables_taggable_type_taggable_id_index` (`taggable_type`,`taggable_id`),
|
||||
CONSTRAINT `taggables_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `tags` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` json NOT NULL,
|
||||
`slug` json NOT NULL,
|
||||
`type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`order_column` int DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`email_verified_at` timestamp NULL DEFAULT NULL,
|
||||
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`legacy_md5` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logincount` int unsigned NOT NULL DEFAULT '0',
|
||||
`loginip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`regip` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`logintime` timestamp NULL DEFAULT NULL,
|
||||
`lastpost_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `users_email_unique` (`email`),
|
||||
KEY `users_legacy_md5_index` (`legacy_md5`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (1,'0001_01_01_000000_create_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (2,'0001_01_01_000001_create_cache_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (3,'0001_01_01_000002_create_jobs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (4,'2026_08_11_092446_create_permission_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (5,'2026_08_11_092448_create_media_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (6,'2026_08_11_092449_create_tag_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (7,'2026_08_11_100000_create_membership_tables',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (8,'2026_08_11_100000_create_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (9,'2026_08_11_100001_create_categories_and_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (10,'2026_08_11_100002_create_comments_and_trackbacks_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (11,'2026_08_11_100003_create_links_and_settings_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (12,'2026_08_11_100004_add_blog_columns_to_users_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (13,'2026_08_11_100005_add_content_format_to_posts_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (14,'2026_08_11_113537_create_personal_access_tokens_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (15,'2026_08_12_000000_create_hello_world_logs_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (16,'2026_08_12_100000_add_payable_to_payments_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (17,'2026_08_12_200000_create_market_packages_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (18,'2026_08_12_300000_add_description_to_categories_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (19,'2026_08_12_400000_create_redirects_table',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (20,'2026_08_15_000001_create_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (21,'2026_08_15_000002_create_media_folders_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_15_000003_add_soft_deletes_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_15_000004_add_custom_properties_to_media_library_table',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_09_16_000001_add_cover_media_id_to_posts_and_categories',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_09_16_000002_seed_default_media_folders',2);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (26,'2026_09_18_210000_add_sticky_level_to_posts_table',3);
|
||||
+21
-11
@@ -35,17 +35,17 @@ routes/web.php # 前台规范路由;routes/legacy.php 老 URL 301(新增
|
||||
|
||||
### 钩子清单
|
||||
|
||||
| Hook | 类型 | 参数 | 用途 |
|
||||
|------|------|------|------|
|
||||
| `post.rendered` | filter | `(string $html, Post): string` | 文章渲染后处理 |
|
||||
| `comment.created` | action | `Comment` | 新评论创建 |
|
||||
| `payment.paid` | action | `Payment` | 支付成功分发 |
|
||||
| `filament.post_form` | action | `Schema` | 文章表单注入 |
|
||||
| `filament.post_table` | action | `Table` | 文章表格注入 |
|
||||
| `seo.structured_data` | filter | `(array): array` | JSON-LD 扩展 |
|
||||
| `theme.inject.{point}` | filter | `(string): string` | 注入点追加 HTML |
|
||||
| Hook | 类型 | 参数 | 用途 |
|
||||
| ---------------------- | ------ | ------------------------------ | --------------- |
|
||||
| `post.rendered` | filter | `(string $html, Post): string` | 文章渲染后处理 |
|
||||
| `comment.created` | action | `Comment` | 新评论创建 |
|
||||
| `payment.paid` | action | `Payment` | 支付成功分发 |
|
||||
| `filament.post_form` | action | `Schema` | 文章表单注入 |
|
||||
| `filament.post_table` | action | `Table` | 文章表格注入 |
|
||||
| `seo.structured_data` | filter | `(array): array` | JSON-LD 扩展 |
|
||||
| `theme.inject.{point}` | filter | `(string): string` | 注入点追加 HTML |
|
||||
|
||||
> `Schema::components()` / `Table::columns()` 是**替换**语义:表单用 `[...$schema->getComponents(), Section::make(...)]` 合并,表格用 `pushColumns()`。
|
||||
> 文章表单是左 2 右 1:插件用 `PostForm::appendToSidebar()` / `PostForm::appendToAccessSection()` 注入,不要往根节点追加整行 Section。表格仍用 `pushColumns()`。
|
||||
|
||||
## 4. 主题规范(详见 docs/themes.md)
|
||||
|
||||
@@ -74,7 +74,17 @@ routes/web.php # 前台规范路由;routes/legacy.php 老 URL 301(新增
|
||||
- 不提交:`.env`、密钥/证书、`storage/media-library/temp/`、构建产物
|
||||
- 完成后更新对应 `docs/` 文档
|
||||
|
||||
## 8. 常用命令速查
|
||||
## 8. 后台导航约定
|
||||
|
||||
- 顶栏 **网站** 打开前台新窗口,**写文章** 进新建文章
|
||||
- **管理 → 页面缓存**:查看/清理前台整页缓存,不要只藏在用户菜单里
|
||||
- **管理 → 语言**:只展示当前 `APP_LOCALE` 和已有语言包。文章还没有多语言版本,不要假装已经做了
|
||||
- 文章置顶用 `sticky_level`(0/1/2/3),列表排序 `orderedForList()`。3 = 置顶头条,永远最前
|
||||
- 前台 RSS / Sitemap / llms.txt 开新窗口。`/llms.txt` 和 `/posts/{slug}.md` 是核心路由,不要做成静态文件,新文章要马上出现
|
||||
- 首页浏览模式:`home_view_mode` + `?view=list|full` + cookie `ll_home_view`。分页用 `blog_paginate()`(`onEachSide(1)`)
|
||||
- 老 sablog 正文清洗放 `SablogHtmlNormalizer`,导入 / 渲染 / `sablog:fix-content` 共用,不要各写一套 strip_tags
|
||||
|
||||
## 9. 常用命令速查
|
||||
|
||||
```bash
|
||||
php artisan test # 全量测试
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Diary
|
||||
|
||||
## 2026-09-18
|
||||
|
||||
- Edit post crashed with `Class "Filament\Tables\Actions\Action" not found`.
|
||||
- Cause: `CommentsRelationManager` still imported the Filament v3 class. HTTP GET on the edit page can pass because the relation table only instantiates when its Livewire component boots.
|
||||
- Fix: `use Filament\Actions\Action`. Regression: `FilamentV5CompatTest`.
|
||||
+8
-1
@@ -65,7 +65,7 @@ php artisan sablog:import \
|
||||
|
||||
- 伪静态:`/show-12-1.html` → 新文章页;`/category-3-1.html` → 新分类页;`/archives-202008-1.html` → 新归档
|
||||
- 查询串:`/?action=show&id=12`、`/?action=search&keyword=x`、`/?action=tags&id=1` …全部映射
|
||||
- 稳定路径:`/rss.xml`、`/sitemap.xml`、`/robots.txt` 保持原样
|
||||
- 稳定路径:`/rss.xml`、`/sitemap.xml`、`/robots.txt`、`/llms.txt` 保持原样
|
||||
- 老入口:`attachment.php?id=` → 301 到 S3/本地媒体地址;`tburl.php`/`trackback.php` → 410
|
||||
|
||||
## 附件上 S3
|
||||
@@ -86,8 +86,15 @@ php artisan attachments:sync-s3 --legacy-dir=... --force
|
||||
php artisan content:convert --all # 全部 HTML 文章转 Markdown
|
||||
php artisan content:convert 12 # 单篇
|
||||
php artisan content:convert --all --dry-run # 先预览
|
||||
|
||||
# 清洗已导入正文:字面 、SyntaxHighlighter、docker 分行 div
|
||||
php artisan sablog:fix-content --all
|
||||
php artisan sablog:fix-content 3156
|
||||
php artisan sablog:fix-content --all --dry-run
|
||||
```
|
||||
|
||||
渲染时也会做同样的清洗,所以不跑修复命令,前台也能先看起来正常。`content:convert` 和 `--convert-markdown` 会先清洗再转 Markdown。
|
||||
|
||||
## 幂等与重跑
|
||||
|
||||
- 导入按**原始 ID** `updateOrInsert`:重复执行不会产生重复数据,只会覆盖更新
|
||||
|
||||
+8
-9
@@ -103,21 +103,20 @@ class ServiceProvider extends PluginServiceProvider
|
||||
|
||||
核心资源的表单/表格会触发 `filament.post_form` / `filament.post_table` 两个钩子,插件可追加自己的字段与列,停用插件后字段自动消失,核心零侵入。
|
||||
|
||||
> 注意:`Schema::components()` 与 `Table::columns()` 都是**整体替换**语义,追加要这样写:
|
||||
> 文章编辑页是 **左正文 2 : 右功能 1**。插件不要往根节点再追加整行 Section,否则会把 2:1 挤掉。
|
||||
>
|
||||
> - 表单:`$schema->components([...$schema->getComponents(), Section::make(...)])`
|
||||
> - 表单:用 `PostForm::appendToSidebar()` / `PostForm::appendToAccessSection()`
|
||||
> - 表格:`$table->pushColumns([...])`
|
||||
|
||||
示例(给文章加「付费设置」):
|
||||
示例(给右侧「访问设置」加会员字段):
|
||||
|
||||
```php
|
||||
use App\Filament\Resources\Posts\Schemas\PostForm;
|
||||
|
||||
$manager->addAction('filament.post_form', function (Schema $schema) {
|
||||
$schema->components([
|
||||
...$schema->getComponents(),
|
||||
Section::make('付费设置')->columns(3)->collapsible()->schema([
|
||||
TextInput::make('meta.price')->label('单篇价格(分)')->numeric()->default(0),
|
||||
Toggle::make('meta.members_only')->label('会员专享'),
|
||||
]),
|
||||
PostForm::appendToAccessSection($schema, [
|
||||
TextInput::make('meta.price')->label('价格')->numeric()->default(0),
|
||||
Toggle::make('meta.members_only')->label('会员专享'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -76,14 +76,14 @@ class ServiceProvider extends PluginServiceProvider
|
||||
|
||||
### 常用钩子
|
||||
|
||||
| Hook | 类型 | 参数 | 用途 |
|
||||
| --------------------- | ------ | ------------------------------- | --------------------------------------- |
|
||||
| `post.rendered` | filter | `(string $html, Post) : string` | 文章渲染后处理(短代码/付费过滤) |
|
||||
| `comment.created` | action | `Comment` | 新评论创建 |
|
||||
| `payment.paid` | action | `Payment` | 支付成功(按 `$payment->payable` 分发) |
|
||||
| `filament.post_form` | action | `Schema` | 文章编辑表单追加字段 |
|
||||
| `filament.post_table` | action | `Table` | 文章列表追加列 |
|
||||
| `seo.structured_data` | filter | `(array $data) : array` | 扩展 JSON-LD |
|
||||
| Hook | 类型 | 参数 | 用途 |
|
||||
| --------------------- | ------ | ------------------------------- | ------------------------------------------------ |
|
||||
| `post.rendered` | filter | `(string $html, Post) : string` | 文章渲染后处理(短代码/付费过滤) |
|
||||
| `comment.created` | action | `Comment` | 新评论创建 |
|
||||
| `payment.paid` | action | `Payment` | 支付成功(按 `$payment->payable` 分发) |
|
||||
| `filament.post_form` | action | `Schema` | 文章表单注入(用 `PostForm::appendToSidebar()`) |
|
||||
| `filament.post_table` | action | `Table` | 文章列表追加列 |
|
||||
| `seo.structured_data` | filter | `(array $data) : array` | 扩展 JSON-LD |
|
||||
|
||||
完整列表见 `docs/plugins.md`。
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Plugins\Demo\HelloWorld;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Blog\Support\PluginServiceProvider;
|
||||
use App\Filament\Resources\Posts\Schemas\PostForm;
|
||||
use App\Models\Comment;
|
||||
use App\Models\Post;
|
||||
use App\Models\Setting;
|
||||
@@ -53,8 +54,7 @@ class ServiceProvider extends PluginServiceProvider
|
||||
|
||||
// 5. 后台表单注入示例:给文章编辑页加一个演示字段(meta.demo_note)
|
||||
$manager->addAction('filament.post_form', function (Schema $schema) {
|
||||
$schema->components([
|
||||
...$schema->getComponents(),
|
||||
PostForm::appendToSidebar($schema, [
|
||||
Section::make('Hello World 演示')
|
||||
->collapsible()
|
||||
->schema([
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"title": "SEO & GEO",
|
||||
"version": "1.0.0",
|
||||
"description": "完善 SEO(结构化数据/OG 标签)+ GEO(llms.txt / Markdown 导出,面向 AI 搜索引擎优化)",
|
||||
"version": "1.0.1",
|
||||
"description": "完善 SEO(结构化数据/OG 标签)。llms.txt 和 Markdown 导出由核心路由提供。",
|
||||
"author": "LaraLog",
|
||||
"type": "core",
|
||||
"provider": "Plugins\\Neatstudio\\BlogSeo\\ServiceProvider",
|
||||
"usage": "无需额外配置,启用即生效:\n- SEO:页面 title/description/keywords、OG/Twitter 标签、JSON-LD(BlogPosting/WebSite)自动输出\n- GEO:/llms.txt 站点摘要、/posts/{slug}.md 文章 Markdown 导出\n- /sitemap.xml、/robots.txt 由核心路由提供\n站点名/描述等在「博客设置 → 站点」配置,社交账号在「博客设置 → 社交账号」配置"
|
||||
"usage": "无需额外配置,启用即生效:\n- SEO:页面 title/description/keywords、OG/Twitter 标签、JSON-LD(BlogPosting/WebSite)自动输出\n- GEO:核心路由 /llms.txt、/posts/{slug}.md(停用本插件后仍可用)\n- /sitemap.xml、/robots.txt 由核心路由提供\n站点名/描述等在「博客设置 → 站点」配置,社交账号在「博客设置 → 社交账号」配置"
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Plugins\Neatstudio\BlogSeo\Http\SeoController;
|
||||
|
||||
// GEO:面向 AI 搜索引擎(LLM 爬虫)
|
||||
Route::get('/llms.txt', [SeoController::class, 'llmsTxt'])->name('geo.llms');
|
||||
Route::get('/posts/{slug}.md', [SeoController::class, 'postMarkdown'])->where('slug', '[A-Za-z0-9\-]+')->name('geo.post.markdown');
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Plugins\Neatstudio\BlogSeo\Http;
|
||||
|
||||
use App\Models\Post;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SeoController
|
||||
{
|
||||
/**
|
||||
* GEO:/llms.txt — 面向 LLM 的站点索引
|
||||
*/
|
||||
public function llmsTxt()
|
||||
{
|
||||
$siteName = blog_setting('site_name', config('blog.name'));
|
||||
$siteDescription = blog_setting('site_description', config('blog.description'));
|
||||
|
||||
$posts = Post::published()->latest('published_at')->limit(200)->get();
|
||||
|
||||
$lines = [];
|
||||
$lines[] = '# '.$siteName;
|
||||
$lines[] = '';
|
||||
$lines[] = '> '.$siteDescription;
|
||||
$lines[] = '';
|
||||
$lines[] = '## 文章(Markdown 全文可访问 /posts/{slug}.md)';
|
||||
$lines[] = '';
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$lines[] = '- ['.$post->title.']('.route('posts.show', $post->id).') — '.Str::limit(strip_tags($post->excerpt_or_fallback), 120);
|
||||
}
|
||||
|
||||
return response(implode("\n", $lines)."\n")
|
||||
->header('Content-Type', 'text/plain; charset=utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* GEO:/posts/{slug}.md — 面向 AI 爬虫的纯 Markdown 导出
|
||||
*/
|
||||
public function postMarkdown(Request $request, string $slug)
|
||||
{
|
||||
$post = Post::query()->where('slug', $slug)->firstOrFail();
|
||||
|
||||
if ($post->status !== 'published') {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$body = $post->content_format === 'markdown' ? $post->content : $this->htmlToMarkdown($post->content);
|
||||
|
||||
$out = "# {$post->title}\n\n";
|
||||
if ($post->excerpt) {
|
||||
$out .= '> '.$post->excerpt."\n\n";
|
||||
}
|
||||
$out .= $body."\n";
|
||||
|
||||
return response($out)->header('Content-Type', 'text/markdown; charset=utf-8');
|
||||
}
|
||||
|
||||
private function htmlToMarkdown(string $html): string
|
||||
{
|
||||
$converter = new \League\HTMLToMarkdown\HtmlConverter([
|
||||
'strip_tags' => false,
|
||||
'hard_break' => true,
|
||||
]);
|
||||
|
||||
return $converter->convert($html);
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,16 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Plugins\Neatstudio\BlogSeo;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Blog\Support\PluginServiceProvider;
|
||||
use Plugins\Neatstudio\BlogSeo\Http\Middleware\SeoMiddleware;
|
||||
use Plugins\Neatstudio\BlogSeo\Http\SeoController;
|
||||
|
||||
class ServiceProvider extends PluginServiceProvider
|
||||
{
|
||||
protected function boot(PluginManager $manager): void
|
||||
{
|
||||
$this->loadRoutes(__DIR__.'/../routes/web.php');
|
||||
|
||||
app('router')->pushMiddlewareToGroup('web', SeoMiddleware::class);
|
||||
|
||||
$manager->addFilter('seo.structured_data', function (array $data) {
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Plugins\Neatstudio\Membership;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Blog\Support\PluginServiceProvider;
|
||||
use App\Filament\Resources\Posts\Schemas\PostForm;
|
||||
use App\Models\Post;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
@@ -53,7 +52,7 @@ class ServiceProvider extends PluginServiceProvider
|
||||
}, 10);
|
||||
|
||||
// 付费文章:[paid]...[/paid] —— 无权限访客替换为 teaser;有权限替换为付费内容块(避免原始标签裸露在页面)
|
||||
$manager->addFilter('post.rendered', function (string $html, \App\Models\Post $post) {
|
||||
$manager->addFilter('post.rendered', function (string $html, Post $post) {
|
||||
$service = app(Services\MembershipService::class);
|
||||
$user = auth()->user();
|
||||
|
||||
@@ -81,25 +80,18 @@ class ServiceProvider extends PluginServiceProvider
|
||||
return $html;
|
||||
}, 10);
|
||||
|
||||
// 后台文章表单注入:付费设置(meta.price 单篇价格 + meta.members_only 会员专享)
|
||||
// 后台文章表单注入:右侧「访问设置」追加会员专享 / 价格
|
||||
$manager->addAction('filament.post_form', function (Schema $schema) {
|
||||
$schema->components([
|
||||
...$schema->getComponents(),
|
||||
Section::make('付费设置')
|
||||
->description('需启用支付插件并配置支付渠道;0 = 免费')
|
||||
->columns(3)
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('meta.price')
|
||||
->label('单篇价格(分)')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0)
|
||||
->helperText('例:9900 = ¥99;填 0 表示单篇免费'),
|
||||
Toggle::make('meta.members_only')
|
||||
->label('会员专享')
|
||||
->helperText('整篇文章仅会员可读'),
|
||||
]),
|
||||
PostForm::appendToAccessSection($schema, [
|
||||
Toggle::make('meta.members_only')
|
||||
->label('会员专享')
|
||||
->helperText('整篇文章仅会员可读'),
|
||||
TextInput::make('meta.price')
|
||||
->label('价格')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0)
|
||||
->helperText('单位:分,例 9900 = ¥99;0 表示单篇免费'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
+509
-105
@@ -1,40 +1,112 @@
|
||||
/* ============ LaraLog 默认主题 ============ */
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||||
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
color: #333;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
a { color: #2d6cdf; text-decoration: none; }
|
||||
a:hover { color: #1a4fa8; }
|
||||
a {
|
||||
color: #2d6cdf;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: #1a4fa8;
|
||||
}
|
||||
|
||||
.container { max-width: 1100px; margin: 0 auto; padding: 0 16px; }
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.site-header { background: #fff; border-bottom: 1px solid #e5e7eb; padding: 18px 0; }
|
||||
.site-header .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
|
||||
.site-title { font-size: 26px; font-weight: 800; color: #111; }
|
||||
.site-desc { font-size: 13px; color: #888; margin-top: 2px; }
|
||||
.site-nav { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; font-size: 15px; }
|
||||
.site-nav a.active { color: #2d6cdf; font-weight: 600; }
|
||||
.site-search input { padding: 6px 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; width: 150px; }
|
||||
.inline-form { display: inline; }
|
||||
.link-btn { background: none; border: none; color: #2d6cdf; cursor: pointer; font-size: 15px; padding: 0; }
|
||||
.site-header {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 18px 0;
|
||||
}
|
||||
.site-header .container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.site-title {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #111;
|
||||
}
|
||||
.site-desc {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.site-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 15px;
|
||||
}
|
||||
.site-nav a.active {
|
||||
color: #2d6cdf;
|
||||
font-weight: 600;
|
||||
}
|
||||
.site-search input {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
width: 150px;
|
||||
}
|
||||
.inline-form {
|
||||
display: inline;
|
||||
}
|
||||
.link-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #2d6cdf;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.main-layout { display: grid; grid-template-columns: 1fr 320px; gap: 24px; padding: 24px 16px; align-items: start; }
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 320px;
|
||||
gap: 24px;
|
||||
padding: 24px 16px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 860px) {
|
||||
.main-layout { grid-template-columns: 1fr; }
|
||||
.main-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.content { min-width: 0; }
|
||||
.content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.post-card, .post-full, .widget, .auth-box, .profile-box, .links-list {
|
||||
.post-card,
|
||||
.post-full,
|
||||
.widget,
|
||||
.auth-box,
|
||||
.profile-box,
|
||||
.links-list {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
@@ -42,114 +114,446 @@ a:hover { color: #1a4fa8; }
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.post-card.sticky { border-left: 4px solid #f59e0b; }
|
||||
.post-card.sticky {
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
|
||||
.post-title { font-size: 20px; line-height: 1.4; margin-bottom: 8px; }
|
||||
.post-card .post-title a { color: #111; }
|
||||
.post-card .post-title a:hover { color: #2d6cdf; }
|
||||
.badge { background: #f59e0b; color: #fff; font-size: 12px; padding: 2px 6px; border-radius: 4px; margin-right: 6px; vertical-align: middle; }
|
||||
.post-title {
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.post-card .post-title a {
|
||||
color: #111;
|
||||
}
|
||||
.post-card .post-title a:hover {
|
||||
color: #2d6cdf;
|
||||
}
|
||||
.badge {
|
||||
background: #f59e0b;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.post-meta { font-size: 13px; color: #888; margin-bottom: 10px; }
|
||||
.post-excerpt { color: #555; margin-bottom: 10px; }
|
||||
.post-footer { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 14px; }
|
||||
.read-more { font-weight: 600; }
|
||||
.post-meta {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.post-excerpt {
|
||||
color: #555;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.post-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14px;
|
||||
}
|
||||
.read-more {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.post-full .post-title { font-size: 28px; }
|
||||
.post-cover { margin: -20px -24px 16px; border-radius: 10px 10px 0 0; overflow: hidden; }
|
||||
.post-cover img { width: 100%; height: auto; display: block; max-height: 420px; object-fit: cover; }
|
||||
.post-card .post-cover { margin: -20px -24px 12px; border-radius: 10px 10px 0 0; }
|
||||
.post-card .post-cover img { max-height: 260px; }
|
||||
.post-content { margin-top: 16px; font-size: 16.5px; }
|
||||
.post-content img { max-width: 100%; height: auto; border-radius: 6px; }
|
||||
.post-content pre { background: #1e1e2e; color: #e6e6e6; padding: 14px; border-radius: 8px; overflow-x: auto; margin: 12px 0; }
|
||||
.post-content code { background: #f0f1f3; padding: 2px 5px; border-radius: 4px; font-size: 14px; }
|
||||
.post-content pre code { background: none; padding: 0; }
|
||||
.post-content blockquote { border-left: 4px solid #2d6cdf; padding-left: 14px; color: #666; margin: 12px 0; }
|
||||
.post-content h1, .post-content h2, .post-content h3 { margin: 18px 0 8px; line-height: 1.3; }
|
||||
.post-content ul, .post-content ol { padding-left: 24px; margin: 8px 0; }
|
||||
.post-content table { border-collapse: collapse; width: 100%; margin: 12px 0; }
|
||||
.post-content th, .post-content td { border: 1px solid #ddd; padding: 6px 10px; }
|
||||
.post-full .post-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
.post-cover {
|
||||
margin: -20px -24px 16px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.post-cover img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
max-height: 420px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.post-card .post-cover {
|
||||
margin: -20px -24px 12px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
.post-card .post-cover img {
|
||||
max-height: 260px;
|
||||
}
|
||||
.post-content {
|
||||
margin-top: 16px;
|
||||
font-size: 16.5px;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.post-content pre,
|
||||
.post-content .codeText,
|
||||
.post-content ol[class^="dp-"] {
|
||||
background: #1e1e2e;
|
||||
color: #e6e6e6;
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
margin: 12px 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content code {
|
||||
background: #f0f1f3;
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
.post-content pre code {
|
||||
display: block;
|
||||
background: none;
|
||||
padding: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content blockquote {
|
||||
border-left: 4px solid #2d6cdf;
|
||||
padding-left: 14px;
|
||||
color: #666;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.post-content h1,
|
||||
.post-content h2,
|
||||
.post-content h3 {
|
||||
margin: 18px 0 8px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.post-content ul,
|
||||
.post-content ol {
|
||||
padding-left: 24px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.post-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.post-content th,
|
||||
.post-content td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.post-tags { margin-top: 16px; }
|
||||
.tag-link { display: inline-block; background: #eef2ff; color: #3b5bdb; border-radius: 4px; padding: 2px 8px; font-size: 13px; margin: 2px 4px 2px 0; }
|
||||
.tag-link:hover { background: #dbe4ff; }
|
||||
.post-tags {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.tag-link {
|
||||
display: inline-block;
|
||||
background: #eef2ff;
|
||||
color: #3b5bdb;
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
font-size: 13px;
|
||||
margin: 2px 4px 2px 0;
|
||||
}
|
||||
.tag-link:hover {
|
||||
background: #dbe4ff;
|
||||
}
|
||||
|
||||
/* Comments */
|
||||
.comments-section { background: #fff; border: 1px solid #e5e7eb; border-radius: 10px; padding: 20px 24px; margin-bottom: 20px; }
|
||||
.section-title { font-size: 18px; margin-bottom: 14px; }
|
||||
.comment { padding: 12px 0; border-bottom: 1px solid #f0f1f3; }
|
||||
.comment:last-of-type { border-bottom: none; }
|
||||
.comment-head { font-size: 14px; margin-bottom: 4px; }
|
||||
.comment-time { color: #999; }
|
||||
.comment-body { font-size: 15px; color: #444; }
|
||||
.comments-section {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.comment {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f1f3;
|
||||
}
|
||||
.comment:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
.comment-head {
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.comment-time {
|
||||
color: #999;
|
||||
}
|
||||
.comment-body {
|
||||
font-size: 15px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.comment-form { margin-top: 16px; }
|
||||
.form-row { margin-bottom: 12px; }
|
||||
.form-row label { display: block; font-size: 14px; margin-bottom: 4px; }
|
||||
.form-row input, .form-row textarea {
|
||||
width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 15px; font-family: inherit;
|
||||
.comment-form {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.form-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form-row label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.form-row input,
|
||||
.form-row textarea {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.honeypot {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
opacity: 0;
|
||||
}
|
||||
.error {
|
||||
color: #e03131;
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.honeypot { position: absolute; left: -9999px; opacity: 0; }
|
||||
.error { color: #e03131; font-size: 13px; margin-top: 4px; }
|
||||
|
||||
.btn {
|
||||
background: #2d6cdf; color: #fff; border: none; padding: 9px 20px; border-radius: 6px;
|
||||
font-size: 15px; cursor: pointer;
|
||||
background: #2d6cdf;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 9px 20px;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn:hover {
|
||||
background: #1a4fa8;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #2d6cdf;
|
||||
}
|
||||
.btn:hover { background: #1a4fa8; }
|
||||
.btn-primary { background: #2d6cdf; }
|
||||
|
||||
.alert { padding: 10px 16px; border-radius: 8px; margin-bottom: 14px; font-size: 14px; }
|
||||
.alert-success { background: #d3f9d8; color: #2b8a3e; }
|
||||
.alert-error { background: #ffe3e3; color: #c92a2a; }
|
||||
.alert {
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.alert-success {
|
||||
background: #d3f9d8;
|
||||
color: #2b8a3e;
|
||||
}
|
||||
.alert-error {
|
||||
background: #ffe3e3;
|
||||
color: #c92a2a;
|
||||
}
|
||||
.alert-warning {
|
||||
background: #fff3bf;
|
||||
color: #8a6d00;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar { position: sticky; top: 20px; }
|
||||
.widget { padding: 16px 20px; }
|
||||
.widget-title { font-size: 15px; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 2px solid #2d6cdf; }
|
||||
.widget-list { list-style: none; }
|
||||
.widget-list li { font-size: 14px; padding: 4px 0; }
|
||||
.count { color: #aaa; font-size: 13px; }
|
||||
.tag-cloud { line-height: 2; }
|
||||
.tag-cloud.big { line-height: 2.4; }
|
||||
.sidebar {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
.widget {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.widget-title {
|
||||
font-size: 15px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #2d6cdf;
|
||||
}
|
||||
.widget-list {
|
||||
list-style: none;
|
||||
}
|
||||
.widget-list li {
|
||||
font-size: 14px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.count {
|
||||
color: #aaa;
|
||||
font-size: 13px;
|
||||
}
|
||||
.tag-cloud {
|
||||
line-height: 2;
|
||||
}
|
||||
.tag-cloud.big {
|
||||
line-height: 2.4;
|
||||
}
|
||||
|
||||
/* Misc */
|
||||
.empty { color: #999; text-align: center; padding: 30px 0; }
|
||||
.list-title { font-size: 22px; margin-bottom: 16px; }
|
||||
.archive-year { font-size: 17px; margin: 14px 0 6px; }
|
||||
.archive-list { list-style: none; }
|
||||
.archive-posts { margin: 4px 0 10px 22px; }
|
||||
.archive-posts time { color: #aaa; font-size: 13px; }
|
||||
|
||||
.pagination { margin: 16px 0; }
|
||||
.pagination-links { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
||||
.pagination-links a, .pagination-links .current, .pagination-links .disabled {
|
||||
padding: 6px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; background: #fff;
|
||||
.empty {
|
||||
color: #999;
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
}
|
||||
.list-title {
|
||||
font-size: 22px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.archive-year {
|
||||
font-size: 17px;
|
||||
margin: 14px 0 6px;
|
||||
}
|
||||
.archive-list {
|
||||
list-style: none;
|
||||
}
|
||||
.archive-posts {
|
||||
margin: 4px 0 10px 22px;
|
||||
}
|
||||
.archive-posts time {
|
||||
color: #aaa;
|
||||
font-size: 13px;
|
||||
}
|
||||
.pagination-links .current { background: #2d6cdf; color: #fff; border-color: #2d6cdf; }
|
||||
.pagination-links .disabled { color: #bbb; }
|
||||
|
||||
.auth-layout { display: flex; justify-content: center; padding: 40px 16px; }
|
||||
.auth-box { width: 100%; max-width: 420px; }
|
||||
.auth-alt { margin-top: 14px; font-size: 14px; color: #666; text-align: center; }
|
||||
.view-mode-switcher {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.view-mode-switcher a.active {
|
||||
font-weight: 700;
|
||||
color: #2d6cdf;
|
||||
}
|
||||
|
||||
.links-list { list-style: none; }
|
||||
.links-list li { padding: 6px 0; }
|
||||
.link-note { color: #999; font-size: 14px; }
|
||||
.pagination {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.pagination-links {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
.pagination-info {
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
margin-right: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pagination-links a,
|
||||
.pagination-links .current,
|
||||
.pagination-links .disabled {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
.pagination-links .current {
|
||||
background: #2d6cdf;
|
||||
color: #fff;
|
||||
border-color: #2d6cdf;
|
||||
}
|
||||
.pagination-links .disabled {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.site-footer { background: #fff; border-top: 1px solid #e5e7eb; padding: 20px 0; margin-top: 24px; text-align: center; font-size: 13px; color: #888; }
|
||||
.powered { margin-top: 4px; }
|
||||
.auth-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 40px 16px;
|
||||
}
|
||||
.auth-box {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
}
|
||||
.auth-alt {
|
||||
margin-top: 14px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.links-list {
|
||||
list-style: none;
|
||||
}
|
||||
.links-list li {
|
||||
padding: 6px 0;
|
||||
}
|
||||
.link-note {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
background: #fff;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
padding: 20px 0;
|
||||
margin-top: 24px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
}
|
||||
.powered {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* TOC */
|
||||
.toc { background: #f8f9fa; border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px 16px; margin: 0 0 18px; font-size: 14px; }
|
||||
.toc summary { cursor: pointer; font-weight: 600; margin-bottom: 6px; }
|
||||
.toc-list { list-style: none; }
|
||||
.toc-list li { padding: 2px 0; }
|
||||
.toc-list a { color: #555; }
|
||||
.toc-list a:hover { color: #2d6cdf; }
|
||||
.toc-level-2 { padding-left: 0; }
|
||||
.toc-level-3 { padding-left: 14px; }
|
||||
.toc-level-4, .toc-level-5, .toc-level-6 { padding-left: 28px; }
|
||||
.toc {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin: 0 0 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.toc summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
}
|
||||
.toc-list li {
|
||||
padding: 2px 0;
|
||||
}
|
||||
.toc-list a {
|
||||
color: #555;
|
||||
}
|
||||
.toc-list a:hover {
|
||||
color: #2d6cdf;
|
||||
}
|
||||
.toc-level-2 {
|
||||
padding-left: 0;
|
||||
}
|
||||
.toc-level-3 {
|
||||
padding-left: 14px;
|
||||
}
|
||||
.toc-level-4,
|
||||
.toc-level-5,
|
||||
.toc-level-6 {
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
/* 屏幕阅读器专用(SEO h1 隐藏) */
|
||||
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
+760
-72
@@ -17,88 +17,776 @@
|
||||
--border: #2c2c2c;
|
||||
}
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 16px; line-height: 1.8; }
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { opacity: 0.8; }
|
||||
.m-container { max-width: 720px; margin: 0 auto; padding: 0 20px; }
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||||
"Microsoft YaHei", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
/* 页面统一宽度:页头/页脚与主体对齐(默认视图的 .container 也落在这里) */
|
||||
.container,
|
||||
.m-container {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.m-header { border-bottom: 1px solid var(--border); background: var(--card); }
|
||||
.m-header .m-container { display: flex; justify-content: space-between; align-items: center; padding: 18px 20px; }
|
||||
.m-logo { font-size: 22px; font-weight: 800; letter-spacing: 1px; color: var(--text); }
|
||||
.m-nav { display: flex; gap: 18px; font-size: 14px; align-items: center; }
|
||||
.m-nav a.active { font-weight: 700; }
|
||||
.m-link-btn { background: none; border: none; color: var(--accent); font-size: 14px; cursor: pointer; padding: 0; }
|
||||
.inline-form { display: inline; }
|
||||
.m-header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--card);
|
||||
}
|
||||
.m-header .m-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 18px 20px;
|
||||
}
|
||||
.m-logo {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text);
|
||||
}
|
||||
.m-nav {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
}
|
||||
.m-nav a.active {
|
||||
font-weight: 700;
|
||||
}
|
||||
.m-link-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--accent);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
.m-search input {
|
||||
padding: 7px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
width: 140px;
|
||||
}
|
||||
.inline-form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.main-layout { padding: 36px 20px; }
|
||||
.main-layout .content { max-width: 720px; margin: 0 auto; }
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 300px;
|
||||
gap: 32px;
|
||||
align-items: start;
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 36px 20px;
|
||||
}
|
||||
.main-layout .content {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.main-layout .sidebar {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.post-card, .post-full, .comments-section { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 26px 30px; margin-bottom: 22px; }
|
||||
.post-card.sticky { box-shadow: inset 3px 0 0 var(--accent); }
|
||||
.post-title { font-size: 20px; margin-bottom: 8px; line-height: 1.45; }
|
||||
.post-card .post-title a { color: var(--text); }
|
||||
.badge { background: var(--accent); color: #fff; font-size: 11px; padding: 2px 7px; border-radius: 3px; margin-right: 6px; vertical-align: 2px; }
|
||||
.post-meta { color: var(--muted); font-size: 13px; margin-bottom: 12px; }
|
||||
.post-excerpt { color: var(--muted); }
|
||||
.post-footer { margin-top: 12px; display: flex; gap: 12px; flex-wrap: wrap; font-size: 14px; }
|
||||
.read-more { font-weight: 600; }
|
||||
.post-card,
|
||||
.post-full,
|
||||
.comments-section {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 26px 30px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.post-card.sticky {
|
||||
box-shadow: inset 3px 0 0 var(--accent);
|
||||
}
|
||||
.post-title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.post-card .post-title a {
|
||||
color: var(--text);
|
||||
}
|
||||
.badge {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
padding: 2px 7px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
vertical-align: 2px;
|
||||
}
|
||||
.post-meta {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.post-excerpt {
|
||||
color: var(--muted);
|
||||
}
|
||||
.post-footer {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14px;
|
||||
}
|
||||
.read-more {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.post-full .post-title { font-size: 28px; }
|
||||
.post-content { margin-top: 14px; }
|
||||
.post-content img { max-width: 100%; border-radius: 8px; }
|
||||
.post-content pre { background: #0d1117; color: #c9d1d9; padding: 16px; border-radius: 8px; overflow-x: auto; margin: 14px 0; }
|
||||
.post-content code { background: rgba(128,128,128,.15); padding: 2px 6px; border-radius: 4px; font-size: 14px; }
|
||||
.post-content pre code { background: none; padding: 0; }
|
||||
.post-content blockquote { border-left: 3px solid var(--accent); padding-left: 16px; color: var(--muted); margin: 14px 0; }
|
||||
.post-content h1, .post-content h2, .post-content h3 { margin: 20px 0 8px; }
|
||||
.post-full .post-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
.post-content {
|
||||
margin-top: 14px;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.post-content pre,
|
||||
.post-content .codeText,
|
||||
.post-content ol[class^="dp-"] {
|
||||
background: #0d1117;
|
||||
color: #c9d1d9;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
margin: 14px 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content code {
|
||||
background: rgba(128, 128, 128, 0.15);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
.post-content pre code {
|
||||
display: block;
|
||||
background: none;
|
||||
padding: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content blockquote {
|
||||
border-left: 3px solid var(--accent);
|
||||
padding-left: 16px;
|
||||
color: var(--muted);
|
||||
margin: 14px 0;
|
||||
}
|
||||
.post-content h1,
|
||||
.post-content h2,
|
||||
.post-content h3 {
|
||||
margin: 20px 0 8px;
|
||||
}
|
||||
|
||||
.post-cover { margin: -26px -30px 18px; border-radius: 12px 12px 0 0; overflow: hidden; }
|
||||
.post-cover img { width: 100%; display: block; max-height: 380px; object-fit: cover; }
|
||||
.post-cover {
|
||||
margin: -26px -30px 18px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.post-cover img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
max-height: 380px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tag-link { color: var(--accent); font-size: 14px; margin-right: 8px; }
|
||||
.tag-link {
|
||||
color: var(--accent);
|
||||
font-size: 14px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.comments-section { margin-top: 8px; }
|
||||
.section-title { font-size: 17px; margin-bottom: 14px; }
|
||||
.comment { padding: 12px 0; border-bottom: 1px solid var(--border); }
|
||||
.comment:last-of-type { border-bottom: none; }
|
||||
.comment-head { font-size: 14px; margin-bottom: 4px; }
|
||||
.comment-time { color: var(--muted); font-size: 13px; }
|
||||
.comment-body { color: var(--text); font-size: 15px; }
|
||||
.comment-form { margin-top: 14px; }
|
||||
.form-row { margin-bottom: 12px; }
|
||||
.form-row label { display: block; font-size: 14px; margin-bottom: 4px; color: var(--muted); }
|
||||
.form-row input, .form-row textarea { width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 15px; font-family: inherit; }
|
||||
.honeypot { position: absolute; left: -9999px; opacity: 0; }
|
||||
.error { color: var(--accent); font-size: 13px; }
|
||||
.btn { background: var(--accent); color: #fff; border: none; padding: 10px 24px; border-radius: 8px; font-size: 15px; cursor: pointer; }
|
||||
.alert { padding: 12px 18px; border-radius: 8px; margin-bottom: 16px; font-size: 14px; }
|
||||
.alert-success { background: rgba(46,160,67,.12); color: #2ea043; }
|
||||
.alert-error { background: rgba(248,81,73,.12); color: #f85149; }
|
||||
.empty { color: var(--muted); text-align: center; padding: 40px 0; }
|
||||
.comments-section {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 17px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.comment {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.comment:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
.comment-head {
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.comment-time {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.comment-body {
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
}
|
||||
.comment-form {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.form-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form-row label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.form-row input,
|
||||
.form-row textarea {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.honeypot {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
opacity: 0;
|
||||
}
|
||||
.error {
|
||||
color: var(--accent);
|
||||
font-size: 13px;
|
||||
}
|
||||
.btn {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.alert {
|
||||
padding: 12px 18px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.alert-success {
|
||||
background: rgba(46, 160, 67, 0.12);
|
||||
color: #2ea043;
|
||||
}
|
||||
.alert-error {
|
||||
background: rgba(248, 81, 73, 0.12);
|
||||
color: #f85149;
|
||||
}
|
||||
.alert-warning {
|
||||
background: rgba(210, 153, 34, 0.12);
|
||||
color: #9a6700;
|
||||
}
|
||||
.empty {
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.list-title { font-size: 24px; margin-bottom: 20px; }
|
||||
.list-title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.archive-year { font-size: 16px; margin: 16px 0 6px; }
|
||||
.archive-list { list-style: none; }
|
||||
.archive-posts { margin: 4px 0 12px 22px; }
|
||||
.archive-posts time { color: var(--muted); font-size: 13px; }
|
||||
.archive-year {
|
||||
font-size: 16px;
|
||||
margin: 16px 0 6px;
|
||||
}
|
||||
.archive-list {
|
||||
list-style: none;
|
||||
}
|
||||
.archive-posts {
|
||||
margin: 4px 0 12px 22px;
|
||||
}
|
||||
.archive-posts time {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pagination { margin: 16px 0; }
|
||||
.pagination-links { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.pagination-links a, .pagination-links .current, .pagination-links .disabled { padding: 6px 14px; border: 1px solid var(--border); border-radius: 6px; font-size: 14px; background: var(--card); color: var(--text); }
|
||||
.pagination-links .current { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||||
.pagination-links .disabled { color: var(--muted); }
|
||||
.view-mode-switcher {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.view-mode-switcher a.active {
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.m-footer { border-top: 1px solid var(--border); padding: 26px 0 40px; margin-top: 30px; text-align: center; color: var(--muted); font-size: 13px; }
|
||||
.pagination {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.pagination-links {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
.pagination-info {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
margin-right: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pagination-links a,
|
||||
.pagination-links .current,
|
||||
.pagination-links .disabled {
|
||||
padding: 6px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
background: var(--card);
|
||||
color: var(--text);
|
||||
}
|
||||
.pagination-links .current {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.pagination-links .disabled {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.tag-cloud.big { line-height: 2.4; }
|
||||
.links-list { list-style: none; }
|
||||
.links-list li { padding: 6px 0; }
|
||||
.link-note { color: var(--muted); font-size: 14px; }
|
||||
.m-footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 26px 0 40px;
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.auth-layout { display: flex; justify-content: center; padding: 50px 20px; }
|
||||
.auth-box { width: 100%; max-width: 420px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 30px; }
|
||||
.auth-alt { margin-top: 14px; font-size: 14px; color: var(--muted); text-align: center; }
|
||||
.search-form { margin-bottom: 16px; display: flex; gap: 8px; }
|
||||
.search-form input { flex: 1; padding: 10px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 15px; }
|
||||
.tag-cloud.big {
|
||||
line-height: 2.4;
|
||||
}
|
||||
.links-list {
|
||||
list-style: none;
|
||||
}
|
||||
.links-list li {
|
||||
padding: 6px 0;
|
||||
}
|
||||
.link-note {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.auth-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 50px 20px;
|
||||
}
|
||||
.auth-box {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
}
|
||||
.auth-alt {
|
||||
margin-top: 14px;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.search-form input {
|
||||
flex: 1;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* ============ 侧边栏 ============ */
|
||||
.sidebar {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
.widget {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 18px 20px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.widget-title {
|
||||
font-size: 15px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
.widget-list {
|
||||
list-style: none;
|
||||
}
|
||||
.widget-list li {
|
||||
font-size: 14px;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* 社交账号:横向胶囊列表 */
|
||||
.social-links {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.social-links li {
|
||||
font-size: 12px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
.widget-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.count {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.tag-cloud {
|
||||
line-height: 2;
|
||||
}
|
||||
.tag-cloud a {
|
||||
display: inline-block;
|
||||
}
|
||||
.tag-cloud.big {
|
||||
line-height: 2.4;
|
||||
}
|
||||
|
||||
/* ============ 顶栏搜索(默认 header partial) ============ */
|
||||
.site-search input {
|
||||
padding: 7px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
width: 160px;
|
||||
}
|
||||
.link-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--accent);
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ============ 文章正文补充 ============ */
|
||||
.post-tags {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.post-content ul,
|
||||
.post-content ol {
|
||||
padding-left: 24px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.post-content h4,
|
||||
.post-content h5,
|
||||
.post-content h6 {
|
||||
margin: 16px 0 6px;
|
||||
}
|
||||
.post-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.post-content th,
|
||||
.post-content td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 6px 10px;
|
||||
}
|
||||
.post-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 18px 0;
|
||||
}
|
||||
|
||||
/* ============ 个人中心 ============ */
|
||||
.profile-box {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 24px 28px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.profile-box p {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ============ 按钮变体 ============ */
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
}
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* ============ 会员/支付 ============ */
|
||||
.plans-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.plan-card .plan-price {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: var(--accent);
|
||||
}
|
||||
.paid-teaser {
|
||||
background: rgba(248, 81, 73, 0.08);
|
||||
border: 1px dashed var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
/* ============ 移动端适配 ============ */
|
||||
@media (max-width: 768px) {
|
||||
.m-header .m-container {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.m-nav {
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.m-search {
|
||||
flex: 1;
|
||||
min-width: 100%;
|
||||
}
|
||||
.m-search input {
|
||||
width: 100%;
|
||||
}
|
||||
.main-layout {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
padding: 24px 16px;
|
||||
}
|
||||
.main-layout .content {
|
||||
max-width: 100%;
|
||||
}
|
||||
.main-layout .sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
.post-card,
|
||||
.post-full,
|
||||
.comments-section {
|
||||
padding: 20px;
|
||||
}
|
||||
.post-cover {
|
||||
margin: -20px -20px 16px;
|
||||
}
|
||||
.post-full .post-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
.auth-box {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
.pagination-links {
|
||||
gap: 6px;
|
||||
}
|
||||
.pagination-links a,
|
||||
.pagination-links .current,
|
||||
.pagination-links .disabled {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* TOC */
|
||||
.toc {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 14px 18px;
|
||||
margin: 0 0 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.toc summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
}
|
||||
.toc-list li {
|
||||
padding: 3px 0;
|
||||
}
|
||||
.toc-list a {
|
||||
color: var(--muted);
|
||||
}
|
||||
.toc-list a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
.toc-level-3 {
|
||||
padding-left: 14px;
|
||||
}
|
||||
.toc-level-4,
|
||||
.toc-level-5,
|
||||
.toc-level-6 {
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Paywall */
|
||||
.paywall {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 28px 22px;
|
||||
background: var(--bg);
|
||||
text-align: center;
|
||||
}
|
||||
.paywall-cover img {
|
||||
max-width: 100%;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.paywall-title {
|
||||
font-size: 22px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.paywall-excerpt {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.paywall-price {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
color: var(--accent);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.paywall-hint {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.paywall-actions .btn {
|
||||
margin: 0 6px;
|
||||
}
|
||||
.paid-teaser {
|
||||
border: 1px dashed var(--border);
|
||||
background: var(--bg-muted, #f6f6f6);
|
||||
color: var(--muted);
|
||||
border-radius: 10px;
|
||||
padding: 14px 16px;
|
||||
margin: 14px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.paid-teaser .btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 已解锁的付费内容块 */
|
||||
.paid-content {
|
||||
border: 1px solid var(--border);
|
||||
border-left: 4px solid var(--accent);
|
||||
background: var(--bg);
|
||||
border-radius: 10px;
|
||||
padding: 14px 16px;
|
||||
margin: 14px 0;
|
||||
}
|
||||
.paid-content-label {
|
||||
font-size: 12px;
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 分类页介绍区 */
|
||||
.category-intro {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px 18px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.category-cover {
|
||||
width: 128px;
|
||||
height: 84px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.category-description {
|
||||
margin: 0 0 6px;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
.category-meta {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
+657
-81
@@ -1,115 +1,691 @@
|
||||
/* ============ Sablog 经典主题 ============ */
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #eef2f5 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==) repeat-x top;
|
||||
background: #eef2f5
|
||||
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==)
|
||||
repeat-x top;
|
||||
font-family: Verdana, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
a { color: #1c62b0; text-decoration: none; }
|
||||
a:hover { color: #f60; text-decoration: underline; }
|
||||
a {
|
||||
color: #1c62b0;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: #f60;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#outmain { width: 960px; margin: 0 auto; }
|
||||
#outmain {
|
||||
width: 960px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#header { padding: 22px 10px 8px; }
|
||||
.logo a { font-size: 30px; font-weight: bold; color: #1c62b0; font-family: Georgia, serif; }
|
||||
.logo .description { color: #888; font-size: 12px; }
|
||||
#header {
|
||||
padding: 22px 10px 8px;
|
||||
}
|
||||
.logo a {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: #1c62b0;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
.logo .description {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#nav { margin-top: 14px; border-top: 1px solid #d8dfe6; border-bottom: 1px solid #d8dfe6; background: #f7f9fb; }
|
||||
#nav ul { list-style: none; display: flex; flex-wrap: wrap; }
|
||||
#nav li { border-right: 1px solid #d8dfe6; }
|
||||
#nav a { display: block; padding: 7px 18px; color: #333; font-weight: bold; font-size: 13px; }
|
||||
#nav a:hover { background: #1c62b0; color: #fff; text-decoration: none; }
|
||||
#nav .current_page_item a { background: #1c62b0; color: #fff; }
|
||||
#nav {
|
||||
margin-top: 14px;
|
||||
border-top: 1px solid #d8dfe6;
|
||||
border-bottom: 1px solid #d8dfe6;
|
||||
background: #f7f9fb;
|
||||
}
|
||||
#nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#nav li {
|
||||
border-right: 1px solid #d8dfe6;
|
||||
}
|
||||
#nav a {
|
||||
display: block;
|
||||
padding: 7px 18px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
}
|
||||
#nav a:hover {
|
||||
background: #1c62b0;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
#nav .current_page_item a {
|
||||
background: #1c62b0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#page { padding: 14px 10px; display: flex; gap: 18px; align-items: flex-start; }
|
||||
#page {
|
||||
padding: 14px 10px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 280px;
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#content { flex: 1; min-width: 0; }
|
||||
#content {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.post-card, .post-full, .comments-section, .auth-box, .profile-box, .links-list {
|
||||
.post-card,
|
||||
.post-full,
|
||||
.comments-section,
|
||||
.auth-box,
|
||||
.profile-box,
|
||||
.links-list {
|
||||
background: #fff;
|
||||
border: 1px solid #d8dfe6;
|
||||
padding: 18px 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.post-card.sticky { border-left: 4px solid #f60; }
|
||||
.post-title { font-size: 17px; margin-bottom: 6px; line-height: 1.4; }
|
||||
.post-card .post-title a { color: #1c62b0; }
|
||||
.badge { background: #f60; color: #fff; font-size: 11px; padding: 1px 5px; margin-right: 5px; }
|
||||
.post-meta { color: #999; font-size: 12px; margin-bottom: 8px; }
|
||||
.post-excerpt { color: #555; margin-bottom: 8px; }
|
||||
.post-footer { font-size: 12px; }
|
||||
.tag-link { color: #1c62b0; margin-right: 8px; }
|
||||
.post-card.sticky {
|
||||
border-left: 4px solid #f60;
|
||||
}
|
||||
.post-title {
|
||||
font-size: 17px;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.post-card .post-title a {
|
||||
color: #1c62b0;
|
||||
}
|
||||
.badge {
|
||||
background: #f60;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
padding: 1px 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.post-meta {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.post-excerpt {
|
||||
color: #555;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.post-footer {
|
||||
font-size: 12px;
|
||||
}
|
||||
.tag-link {
|
||||
color: #1c62b0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.post-full .post-title { font-size: 22px; color: #1c62b0; }
|
||||
.post-content { margin-top: 10px; font-size: 14px; }
|
||||
.post-content img { max-width: 100%; }
|
||||
.post-content pre { background: #f4f4f4; border: 1px solid #ddd; padding: 10px; overflow: auto; margin: 10px 0; }
|
||||
.post-content blockquote { border-left: 4px solid #1c62b0; padding-left: 12px; color: #666; margin: 10px 0; }
|
||||
.post-content h1, .post-content h2, .post-content h3 { margin: 14px 0 6px; }
|
||||
.post-full .post-title {
|
||||
font-size: 22px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
.post-content {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.post-content pre,
|
||||
.post-content .codeText,
|
||||
.post-content ol[class^="dp-"] {
|
||||
background: #f4f4f4;
|
||||
border: 1px solid #ddd;
|
||||
border-left: 3px solid #b3cbe1;
|
||||
padding: 10px;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
margin: 10px 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content code {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
.post-content pre code {
|
||||
display: block;
|
||||
background: none;
|
||||
padding: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content .codeText li {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content blockquote {
|
||||
border-left: 4px solid #1c62b0;
|
||||
padding-left: 12px;
|
||||
color: #666;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.post-content h1,
|
||||
.post-content h2,
|
||||
.post-content h3 {
|
||||
margin: 14px 0 6px;
|
||||
}
|
||||
|
||||
.post-cover { margin: -18px -20px 12px; }
|
||||
.post-cover img { width: 100%; display: block; max-height: 320px; object-fit: cover; }
|
||||
.post-cover {
|
||||
margin: -18px -20px 12px;
|
||||
}
|
||||
.post-cover img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
max-height: 320px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.comment { padding: 10px 0; border-bottom: 1px dashed #ddd; }
|
||||
.comment-head { font-size: 12px; margin-bottom: 3px; }
|
||||
.comment-time { color: #999; }
|
||||
.comment-body { font-size: 13px; }
|
||||
.comment-form { margin-top: 12px; }
|
||||
.form-row { margin-bottom: 8px; }
|
||||
.form-row label { display: block; font-size: 12px; margin-bottom: 3px; }
|
||||
.form-row input, .form-row textarea { width: 100%; padding: 6px 8px; border: 1px solid #ccc; font-size: 13px; font-family: inherit; }
|
||||
.honeypot { position: absolute; left: -9999px; opacity: 0; }
|
||||
.btn { background: #1c62b0; color: #fff; border: none; padding: 7px 18px; font-size: 13px; cursor: pointer; }
|
||||
.btn:hover { background: #f60; }
|
||||
.error { color: #c00; font-size: 12px; }
|
||||
.alert { padding: 8px 12px; margin-bottom: 12px; border: 1px solid; }
|
||||
.alert-success { background: #e8f5e9; border-color: #a5d6a7; color: #2e7d32; }
|
||||
.alert-error { background: #fdecea; border-color: #f5c6cb; color: #c62828; }
|
||||
.empty { color: #999; text-align: center; padding: 20px 0; }
|
||||
.list-title { font-size: 18px; margin-bottom: 12px; color: #1c62b0; }
|
||||
.comment {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px dashed #ddd;
|
||||
}
|
||||
.comment-head {
|
||||
font-size: 12px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.comment-time {
|
||||
color: #999;
|
||||
}
|
||||
.comment-body {
|
||||
font-size: 13px;
|
||||
}
|
||||
.comment-form {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.form-row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.form-row label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.form-row input,
|
||||
.form-row textarea {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.honeypot {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
opacity: 0;
|
||||
}
|
||||
.btn {
|
||||
background: #1c62b0;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 7px 18px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn:hover {
|
||||
background: #f60;
|
||||
}
|
||||
.error {
|
||||
color: #c00;
|
||||
font-size: 12px;
|
||||
}
|
||||
.alert {
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid;
|
||||
}
|
||||
.alert-success {
|
||||
background: #e8f5e9;
|
||||
border-color: #a5d6a7;
|
||||
color: #2e7d32;
|
||||
}
|
||||
.alert-error {
|
||||
background: #fdecea;
|
||||
border-color: #f5c6cb;
|
||||
color: #c62828;
|
||||
}
|
||||
.alert-warning {
|
||||
background: #fff8e1;
|
||||
border-color: #ffe082;
|
||||
color: #8a6d00;
|
||||
}
|
||||
.empty {
|
||||
color: #999;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.list-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 12px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
|
||||
.view-mode-switcher {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #d8dfe6;
|
||||
}
|
||||
.view-mode-switcher a.active {
|
||||
font-weight: 700;
|
||||
color: #1c62b0;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar { width: 280px; flex-shrink: 0; }
|
||||
.sidebar .searchbox { background: #fff; border: 1px solid #d8dfe6; padding: 10px; margin-bottom: 14px; }
|
||||
.sidebar .searchbox input[type=search] { width: 180px; padding: 4px; border: 1px solid #ccc; }
|
||||
.sidebar .widget { background: #fff; border: 1px solid #d8dfe6; margin-bottom: 14px; }
|
||||
.sidebar .widget h3 { background: #f7f9fb; border-bottom: 1px solid #d8dfe6; font-size: 13px; padding: 7px 12px; color: #1c62b0; }
|
||||
.sidebar .widget ul { list-style: none; padding: 6px 12px; }
|
||||
.sidebar .widget li { padding: 3px 0; font-size: 12px; border-bottom: 1px dotted #eee; }
|
||||
.sidebar .tagcloud { padding: 8px 12px; }
|
||||
.sidebar .tagcloud a { color: #1c62b0; margin-right: 6px; }
|
||||
.sidebar {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.sidebar .searchbox {
|
||||
background: #fff;
|
||||
border: 1px solid #d8dfe6;
|
||||
padding: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.sidebar .searchbox input[type="search"] {
|
||||
width: 180px;
|
||||
padding: 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.sidebar .widget {
|
||||
background: #fff;
|
||||
border: 1px solid #d8dfe6;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.sidebar .widget h3 {
|
||||
background: #f7f9fb;
|
||||
border-bottom: 1px solid #d8dfe6;
|
||||
font-size: 13px;
|
||||
padding: 7px 12px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
.sidebar .widget ul {
|
||||
list-style: none;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
.sidebar .widget li {
|
||||
padding: 3px 0;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px dotted #eee;
|
||||
}
|
||||
.sidebar .tagcloud {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.sidebar .tagcloud a {
|
||||
color: #1c62b0;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* 兜底页面(归档/标签云/友链/评论/登录等使用默认布局) */
|
||||
.main-layout { display: flex; gap: 18px; align-items: flex-start; padding: 14px 10px; }
|
||||
.main-layout .content { flex: 1; min-width: 0; }
|
||||
.main-layout .sidebar { width: 280px; }
|
||||
.container.main-layout {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 280px;
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
padding: 14px 10px;
|
||||
}
|
||||
.main-layout .content {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.main-layout .sidebar {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pagination-links { display: flex; gap: 6px; flex-wrap: wrap; margin: 12px 0; }
|
||||
.pagination-links a, .pagination-links .current, .pagination-links .disabled { padding: 4px 10px; border: 1px solid #d8dfe6; background: #fff; font-size: 12px; }
|
||||
.pagination-links .current { background: #1c62b0; color: #fff; }
|
||||
.pagination {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.pagination-links {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.pagination-info {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
margin-right: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pagination-links a,
|
||||
.pagination-links .current,
|
||||
.pagination-links .disabled {
|
||||
padding: 2px 7px;
|
||||
border: 1px solid #d8dfe6;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.pagination-links .current {
|
||||
background: #1c62b0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#footer { clear: both; text-align: center; padding: 14px 0 24px; color: #888; font-size: 12px; }
|
||||
#footer a { color: #1c62b0; }
|
||||
#footer {
|
||||
clear: both;
|
||||
text-align: center;
|
||||
padding: 14px 0 24px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
#footer a {
|
||||
color: #1c62b0;
|
||||
}
|
||||
|
||||
.archive-year { font-size: 15px; margin: 12px 0 4px; color: #1c62b0; }
|
||||
.archive-list { list-style: none; }
|
||||
.archive-posts { margin: 4px 0 10px 20px; }
|
||||
.archive-year {
|
||||
font-size: 15px;
|
||||
margin: 12px 0 4px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
.archive-list {
|
||||
list-style: none;
|
||||
}
|
||||
.archive-posts {
|
||||
margin: 4px 0 10px 20px;
|
||||
}
|
||||
|
||||
.links-list { list-style: none; }
|
||||
.links-list li { padding: 5px 0; }
|
||||
.link-note { color: #999; }
|
||||
.links-list {
|
||||
list-style: none;
|
||||
}
|
||||
.links-list li {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.link-note {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.tag-cloud.big { line-height: 2.4; }
|
||||
.tag-cloud.big {
|
||||
line-height: 2.4;
|
||||
}
|
||||
|
||||
.auth-layout { display: flex; justify-content: center; padding: 30px 10px; }
|
||||
.auth-box { width: 100%; max-width: 420px; }
|
||||
.auth-alt { margin-top: 12px; font-size: 12px; }
|
||||
.search-form { margin-bottom: 12px; }
|
||||
.search-form input { padding: 5px; border: 1px solid #ccc; width: 220px; }
|
||||
.auth-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 30px 10px;
|
||||
}
|
||||
.auth-box {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
}
|
||||
.auth-alt {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.search-form {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.search-form input {
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.inline-form { display: inline; }
|
||||
.link-btn { background: none; border: none; color: #1c62b0; cursor: pointer; font-size: 13px; padding: 0; font-weight: bold; }
|
||||
.inline-form {
|
||||
display: inline;
|
||||
}
|
||||
.link-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #1c62b0;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* ============ 移动端适配 ============ */
|
||||
@media (max-width: 768px) {
|
||||
#outmain {
|
||||
width: 100%;
|
||||
}
|
||||
#header {
|
||||
padding: 14px 10px;
|
||||
}
|
||||
.logo a {
|
||||
font-size: 24px;
|
||||
}
|
||||
#nav ul {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#nav a {
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
#page {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
#content {
|
||||
width: 100%;
|
||||
}
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
.main-layout {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.main-layout .content {
|
||||
width: 100%;
|
||||
}
|
||||
.main-layout .sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
.sidebar .searchbox input[type="search"] {
|
||||
width: 70%;
|
||||
}
|
||||
.post-card,
|
||||
.post-full,
|
||||
.comments-section {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
.post-cover {
|
||||
margin: -14px -16px 12px;
|
||||
}
|
||||
.post-full .post-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
.archive-posts {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* TOC */
|
||||
.toc {
|
||||
background: #f7f9fb;
|
||||
border: 1px solid #d8dfe6;
|
||||
padding: 10px 14px;
|
||||
margin: 0 0 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.toc summary {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
}
|
||||
.toc-list li {
|
||||
padding: 2px 0;
|
||||
}
|
||||
.toc-level-3 {
|
||||
padding-left: 14px;
|
||||
}
|
||||
.toc-level-4,
|
||||
.toc-level-5,
|
||||
.toc-level-6 {
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Paywall */
|
||||
.paywall {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 24px 20px;
|
||||
background: #fafafa;
|
||||
text-align: center;
|
||||
}
|
||||
.paywall-cover img {
|
||||
max-width: 100%;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.paywall-title {
|
||||
font-size: 20px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.paywall-excerpt {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
.paywall-price {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #f60;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.paywall-hint {
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.paywall-actions .btn {
|
||||
margin: 0 6px;
|
||||
}
|
||||
.paid-teaser {
|
||||
border: 1px dashed #f5c6cb;
|
||||
background: #fdecea;
|
||||
color: #c62828;
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
margin: 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.paid-teaser .btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 阅读全文链接 */
|
||||
.read-more {
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
.read-more:hover {
|
||||
color: #f60;
|
||||
}
|
||||
|
||||
/* 社交账号:横向胶囊列表 */
|
||||
.social-links {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.social-links li {
|
||||
font-size: 12px;
|
||||
background: #f7f9fb;
|
||||
border: 1px solid #d8dfe6;
|
||||
border-radius: 999px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
/* 已解锁的付费内容块 */
|
||||
.paid-content {
|
||||
border: 1px dashed #f5c6cb;
|
||||
border-left: 4px solid #f60;
|
||||
background: #fff8f0;
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.paid-content-label {
|
||||
font-size: 12px;
|
||||
color: #f60;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 分类页介绍区 */
|
||||
.category-intro {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
background: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.category-cover {
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.category-description {
|
||||
margin: 0 0 6px;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.category-meta {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,18 @@
|
||||
{{ $environmentName }}
|
||||
</x-filament::badge>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="{{ url('/') }}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
size="sm"
|
||||
color="gray"
|
||||
icon="heroicon-o-globe-alt"
|
||||
>
|
||||
网站
|
||||
</x-filament::button>
|
||||
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
:href="$createPostUrl"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<x-filament-panels::page>
|
||||
<x-filament::section heading="当前语言">
|
||||
<dl class="grid gap-3 sm:grid-cols-3">
|
||||
<div>
|
||||
<dt class="text-sm text-gray-500 dark:text-gray-400">站点语言</dt>
|
||||
<dd class="mt-1 text-lg font-medium">{{ $this->info['locale'] }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-gray-500 dark:text-gray-400">回退语言</dt>
|
||||
<dd class="mt-1 text-lg font-medium">{{ $this->info['fallback'] }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-gray-500 dark:text-gray-400">假数据语言</dt>
|
||||
<dd class="mt-1 text-lg font-medium">{{ $this->info['faker'] }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
改语言请编辑 <code>.env</code> 里的 <code>APP_LOCALE</code>,后台菜单文案本身是中文写死的。
|
||||
</p>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section heading="文章多语言" class="mt-6">
|
||||
<x-filament::badge color="warning">未开通</x-filament::badge>
|
||||
<p class="mt-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
现在每篇文章只有一份正文,没有中英两个版本可以切换。这个页面先让你看到现状,以后如果做文章翻译,会在这里列出语言和对应文章。
|
||||
</p>
|
||||
</x-filament::section>
|
||||
|
||||
<div class="mt-6 grid gap-6 lg:grid-cols-2">
|
||||
<x-filament::section heading="站点语言包">
|
||||
@if(count($this->info['app_files']) === 0)
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">还没有站点自己的语言文件。</p>
|
||||
@else
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach($this->info['app_files'] as $locale)
|
||||
<x-filament::badge>{{ $locale }}</x-filament::badge>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section heading="Filament 后台语言包">
|
||||
<p class="mb-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
这些是后台组件自带的翻译,不是文章内容翻译。
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach($this->info['filament_locales'] as $locale)
|
||||
<x-filament::badge color="gray">{{ $locale }}</x-filament::badge>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-filament::section>
|
||||
</div>
|
||||
</x-filament-panels::page>
|
||||
@@ -0,0 +1,64 @@
|
||||
<x-filament-panels::page>
|
||||
<div class="grid gap-4 md:grid-cols-4">
|
||||
<x-filament::section>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">当前缓存页</div>
|
||||
<div class="mt-1 text-2xl font-semibold">{{ $this->stats['count'] }} 条</div>
|
||||
</x-filament::section>
|
||||
<x-filament::section>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">命中率</div>
|
||||
<div class="mt-1 text-2xl font-semibold">{{ $this->stats['rate'] }}%</div>
|
||||
</x-filament::section>
|
||||
<x-filament::section>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">命中 / 未命中</div>
|
||||
<div class="mt-1 text-2xl font-semibold">{{ $this->stats['hits'] }} / {{ $this->stats['misses'] }}</div>
|
||||
</x-filament::section>
|
||||
<x-filament::section>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">过期时间</div>
|
||||
<div class="mt-1 text-2xl font-semibold">{{ $this->stats['ttl'] }} 秒</div>
|
||||
</x-filament::section>
|
||||
</div>
|
||||
|
||||
<x-filament::section heading="已缓存的前台页面" class="mt-6">
|
||||
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
只缓存未登录访客打开的页面。登录后看前台不会进缓存,所以这里可能暂时是空的。
|
||||
</p>
|
||||
|
||||
@if(count($this->entries) === 0)
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">当前没有前台页面缓存。</p>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 text-left dark:border-white/10">
|
||||
<th class="py-2 pr-4 font-medium">地址</th>
|
||||
<th class="py-2 pr-4 font-medium">缓存时间</th>
|
||||
<th class="py-2 font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($this->entries as $entry)
|
||||
<tr class="border-b border-gray-100 dark:border-white/5" wire:key="cache-{{ $entry['key'] }}">
|
||||
<td class="py-2 pr-4">
|
||||
<a href="{{ $entry['url'] }}" target="_blank" rel="noopener noreferrer" class="text-primary-600 hover:underline">
|
||||
{{ $entry['url'] }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="py-2 pr-4 whitespace-nowrap">{{ $entry['cached_at'] ?: '—' }}</td>
|
||||
<td class="py-2">
|
||||
<x-filament::button
|
||||
size="xs"
|
||||
color="danger"
|
||||
wire:click="forgetEntry('{{ $entry['key'] }}')"
|
||||
wire:confirm="删除这条缓存?"
|
||||
>
|
||||
删除
|
||||
</x-filament::button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</x-filament::section>
|
||||
</x-filament-panels::page>
|
||||
@@ -11,6 +11,8 @@
|
||||
<div class="alert alert-error">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
@include('partials.view-mode-switcher')
|
||||
|
||||
@forelse($posts as $post)
|
||||
@include('partials.post-card', ['post' => $post])
|
||||
@empty
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
@endif
|
||||
|
||||
|
||||
@include('partials.view-mode-switcher')
|
||||
|
||||
@forelse($posts as $post)
|
||||
@include('partials.post-card', ['post' => $post])
|
||||
@empty
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@php
|
||||
$asList = $asList ?? false;
|
||||
$feedLinks = [
|
||||
['label' => 'RSS', 'url' => route('feed.rss')],
|
||||
['label' => 'Sitemap', 'url' => route('feed.sitemap')],
|
||||
['label' => 'llms.txt', 'url' => route('feed.llms')],
|
||||
];
|
||||
@endphp
|
||||
@foreach($feedLinks as $feedLink)
|
||||
@if($asList)
|
||||
<li><a href="{{ $feedLink['url'] }}" target="_blank" rel="noopener noreferrer">{{ $feedLink['label'] }}</a></li>
|
||||
@else
|
||||
@if(! $loop->first) · @endif
|
||||
<a href="{{ $feedLink['url'] }}" target="_blank" rel="noopener noreferrer">{{ $feedLink['label'] }}</a>
|
||||
@endif
|
||||
@endforeach
|
||||
@@ -7,6 +7,7 @@
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">{{ $siteIcp }}</a>
|
||||
@endif
|
||||
</p>
|
||||
<p>@include('partials.feed-links')</p>
|
||||
<p class="powered">Powered by <a href="https://laravel.com" target="_blank" rel="noopener">Laravel</a> & <a href="https://filamentphp.com" target="_blank" rel="noopener">Filament</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<meta name="generator" content="LaraLog">
|
||||
<link rel="canonical" href="{{ url()->current() }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ $siteName }}" href="{{ route('feed.rss') }}">
|
||||
<link rel="alternate" type="text/plain" title="llms.txt" href="{{ route('feed.llms') }}">
|
||||
<link rel="stylesheet" href="{{ asset('css/blog.css') }}">
|
||||
@stack('head')
|
||||
@stack('theme:head')
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<a href="{{ route('tags') }}" class="{{ request()->routeIs('tags*') ? 'active' : '' }}">标签</a>
|
||||
<a href="{{ route('links') }}" class="{{ request()->routeIs('links') ? 'active' : '' }}">友链</a>
|
||||
<a href="{{ route('comments') }}" class="{{ request()->routeIs('comments') ? 'active' : '' }}">评论</a>
|
||||
@include('partials.feed-links')
|
||||
<form class="site-search" action="{{ route('search') }}" method="get">
|
||||
<input type="search" name="q" placeholder="搜索..." value="{{ request('q', request('keyword', '')) }}">
|
||||
</form>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
@if ($paginator->hasPages())
|
||||
<nav class="pagination-links" role="navigation">
|
||||
{{-- Previous --}}
|
||||
<nav class="pagination-links" role="navigation" aria-label="分页">
|
||||
<span class="pagination-info">第 {{ $paginator->currentPage() }} / {{ $paginator->lastPage() }} 页</span>
|
||||
|
||||
@if ($paginator->onFirstPage())
|
||||
<span class="disabled">« 上一页</span>
|
||||
<span class="disabled">«</span>
|
||||
@else
|
||||
<a href="{{ $paginator->previousPageUrl() }}" rel="prev">« 上一页</a>
|
||||
<a href="{{ $paginator->previousPageUrl() }}" rel="prev">«</a>
|
||||
@endif
|
||||
|
||||
{{-- Numbers --}}
|
||||
@foreach ($elements as $element)
|
||||
@if (is_string($element))
|
||||
<span class="disabled">{{ $element }}</span>
|
||||
@@ -23,11 +23,10 @@
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Next --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<a href="{{ $paginator->nextPageUrl() }}" rel="next">下一页 »</a>
|
||||
<a href="{{ $paginator->nextPageUrl() }}" rel="next">»</a>
|
||||
@else
|
||||
<span class="disabled">下一页 »</span>
|
||||
<span class="disabled">»</span>
|
||||
@endif
|
||||
</nav>
|
||||
@endif
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<article class="post-card {{ $post->is_sticky ? 'sticky' : '' }}">
|
||||
@php
|
||||
$viewMode = $homeViewMode ?? \App\Blog\Support\HomeViewMode::LIST;
|
||||
$showFull = $viewMode === \App\Blog\Support\HomeViewMode::FULL
|
||||
&& blank($post->read_password)
|
||||
&& empty(($post->meta ?? [])['members_only']);
|
||||
@endphp
|
||||
<article class="post-card {{ $post->isPinned() ? 'sticky' : '' }} post-card-{{ $viewMode }}">
|
||||
@if($url = $post->coverImageUrl())
|
||||
<a href="{{ route('posts.show', $post->id) }}" class="post-cover">
|
||||
<img src="{{ $url }}" alt="{{ $post->title }}" loading="lazy">
|
||||
</a>
|
||||
@endif
|
||||
<h2 class="post-title">
|
||||
@if($post->is_sticky)<span class="badge">置顶</span>@endif
|
||||
@if($label = $post->stickyLabel())<span class="badge">{{ $label }}</span>@endif
|
||||
<a href="{{ route('posts.show', $post->id) }}">{{ $post->title }}</a>
|
||||
</h2>
|
||||
<div class="post-meta">
|
||||
@@ -17,9 +23,15 @@
|
||||
<span>· 阅读 {{ $post->views }}</span>
|
||||
<span>· 评论 {{ $post->comment_count }}</span>
|
||||
</div>
|
||||
<div class="post-excerpt">
|
||||
{{ Str::limit(strip_tags($post->excerpt_or_fallback), 200) }}
|
||||
</div>
|
||||
@if($showFull)
|
||||
<div class="post-content">
|
||||
{!! app(\App\Blog\Services\PostContentRenderer::class)->render($post) !!}
|
||||
</div>
|
||||
@else
|
||||
<div class="post-excerpt">
|
||||
{{ \Illuminate\Support\Str::limit($post->excerpt_or_fallback, 200) }}
|
||||
</div>
|
||||
@endif
|
||||
<div class="post-footer">
|
||||
<a href="{{ route('posts.show', $post->id) }}" class="read-more">阅读全文 »</a>
|
||||
@foreach($post->tags as $tag)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
@if(! empty($isPreview))
|
||||
<div class="alert alert-warning">
|
||||
预览中:这篇文章还没发布,访客打不开。只有管理员和编辑能看到这个页面。
|
||||
</div>
|
||||
@endif
|
||||
@@ -0,0 +1,9 @@
|
||||
@php
|
||||
$viewMode = $homeViewMode ?? \App\Blog\Support\HomeViewMode::LIST;
|
||||
@endphp
|
||||
<div class="view-mode-switcher">
|
||||
浏览模式:
|
||||
<a href="{{ \App\Blog\Support\HomeViewMode::url(\App\Blog\Support\HomeViewMode::FULL) }}" class="{{ $viewMode === \App\Blog\Support\HomeViewMode::FULL ? 'active' : '' }}">标准</a>
|
||||
<span>|</span>
|
||||
<a href="{{ \App\Blog\Support\HomeViewMode::url(\App\Blog\Support\HomeViewMode::LIST) }}" class="{{ $viewMode === \App\Blog\Support\HomeViewMode::LIST ? 'active' : '' }}">列表</a>
|
||||
</div>
|
||||
@@ -10,6 +10,8 @@
|
||||
<button type="submit" class="btn">搜索</button>
|
||||
</form>
|
||||
|
||||
@include('partials.view-mode-switcher')
|
||||
|
||||
@forelse($posts as $post)
|
||||
@include('partials.post-card', ['post' => $post])
|
||||
@empty
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
@if(session('error'))
|
||||
<div class="alert alert-error">{{ session('error') }}</div>
|
||||
@endif
|
||||
@include('partials.post-preview-banner')
|
||||
|
||||
<article class="post-full">
|
||||
@if(! empty($toc))
|
||||
@@ -37,7 +38,11 @@
|
||||
@endif
|
||||
<h1 class="post-title">{{ $post->title }}</h1>
|
||||
<div class="post-meta">
|
||||
<time datetime="{{ $post->published_at->toIso8601String() }}">{{ $post->published_at->format(blog_setting('normaltime', 'Y-m-d H:i')) }}</time>
|
||||
@if($post->published_at)
|
||||
<time datetime="{{ $post->published_at->toIso8601String() }}">{{ $post->published_at->format(blog_setting('normaltime', 'Y-m-d H:i')) }}</time>
|
||||
@else
|
||||
<span>未发布</span>
|
||||
@endif
|
||||
@if($post->author)<span>· {{ $post->author->name }}</span>@endif
|
||||
@if($post->category)
|
||||
<span>· <a href="{{ route('category.show', $post->category->id) }}">{{ $post->category->name }}</a></span>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<main class="content">
|
||||
<h1 class="list-title">标签:{{ $tag->name }}</h1>
|
||||
|
||||
@include('partials.view-mode-switcher')
|
||||
|
||||
@forelse($posts as $post)
|
||||
@include('partials.post-card', ['post' => $post])
|
||||
@empty
|
||||
|
||||
+16
-8
@@ -10,10 +10,14 @@ use App\Blog\Controllers\FeedController;
|
||||
use App\Blog\Controllers\HomeController;
|
||||
use App\Blog\Controllers\LegacyController;
|
||||
use App\Blog\Controllers\LinkController;
|
||||
use App\Blog\Controllers\LlmsController;
|
||||
use App\Blog\Controllers\PostController;
|
||||
use App\Blog\Controllers\SearchController;
|
||||
use App\Blog\Controllers\TagController;
|
||||
use App\Blog\Controllers\ThemeAssetController;
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
@@ -33,18 +37,22 @@ Route::get('/', function () {
|
||||
|
||||
// sablog 首页分页格式:/index-{page}.shtml
|
||||
Route::get('/index-{page}.shtml', function (string $page) {
|
||||
\Illuminate\Pagination\Paginator::currentPageResolver(fn () => max(1, (int) $page));
|
||||
Paginator::currentPageResolver(fn () => max(1, (int) $page));
|
||||
|
||||
return app(HomeController::class)->index();
|
||||
})->whereNumber('page');
|
||||
|
||||
Route::get('/posts/{slug}.md', [LlmsController::class, 'postMarkdown'])
|
||||
->where('slug', '[^/]+')
|
||||
->name('geo.post.markdown');
|
||||
|
||||
Route::get('/posts/{slug}.shtml', function (string $slug) {
|
||||
$post = \App\Models\Post::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
|
||||
$post = Post::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
|
||||
|
||||
return $post ? redirect()->route('posts.show', $post->id, 301) : abort(404);
|
||||
})->where('slug', '[^/]+');
|
||||
Route::get('/posts/{slug}', function (string $slug) {
|
||||
$post = \App\Models\Post::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
|
||||
$post = Post::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
|
||||
|
||||
return $post ? redirect()->route('posts.show', $post->id, 301) : abort(404);
|
||||
})->where('slug', '[^/]+');
|
||||
@@ -55,16 +63,15 @@ Route::get('/show-{id}-{page}.shtml', [PostController::class, 'show'])->whereNum
|
||||
Route::post('/posts/{post}/comments', [CommentController::class, 'store'])->name('comments.store');
|
||||
|
||||
// 浏览量统计(独立端点,避开整页缓存,保证实时)
|
||||
Route::get('/track-view/{post}', fn (\App\Models\Post $post) => response($post->increment('views') ? '' : '', 204))->name('track-view');
|
||||
|
||||
Route::get('/track-view/{post}', fn (Post $post) => response($post->increment('views') ? '' : '', 204))->name('track-view');
|
||||
|
||||
Route::get('/category/{slug}.shtml', function (string $slug) {
|
||||
$category = \App\Models\Category::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
|
||||
$category = Category::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
|
||||
|
||||
return $category ? redirect()->route('category.show', $category->id, 301) : abort(404);
|
||||
})->where('slug', '[^/]+');
|
||||
Route::get('/category/{slug}', function (string $slug) {
|
||||
$category = \App\Models\Category::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
|
||||
$category = Category::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
|
||||
|
||||
return $category ? redirect()->route('category.show', $category->id, 301) : abort(404);
|
||||
})->where('slug', '[^/]+');
|
||||
@@ -120,7 +127,7 @@ Route::get('/profile', fn () => redirect()->route('profile', [], 301))->middlewa
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 稳定路径:RSS / Sitemap / Robots
|
||||
| 稳定路径:RSS / Sitemap / Robots / llms.txt
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@@ -128,6 +135,7 @@ Route::get('/rss.xml', [FeedController::class, 'rss'])->name('feed.rss');
|
||||
Route::get('/rss.php', [FeedController::class, 'rss']);
|
||||
Route::get('/sitemap.xml', [FeedController::class, 'sitemap'])->name('feed.sitemap');
|
||||
Route::get('/robots.txt', [FeedController::class, 'robots'])->name('robots');
|
||||
Route::get('/llms.txt', [LlmsController::class, 'index'])->name('feed.llms');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Filament\Support\AdminEnvironment;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Plugins\Demo\HelloWorld\ServiceProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AdminPagesTest extends TestCase
|
||||
@@ -32,12 +35,40 @@ class AdminPagesTest extends TestCase
|
||||
public function test_post_resource_loads(): void
|
||||
{
|
||||
$this->actingAs($this->admin)->get('/admin/posts')->assertOk();
|
||||
$this->actingAs($this->admin)->get('/admin/posts/create')->assertOk();
|
||||
$this->actingAs($this->admin)->get('/admin/posts/create')
|
||||
->assertOk()
|
||||
->assertSee('Slug(留空自动生成)', false)
|
||||
->assertSee('封面图', false)
|
||||
->assertSee('ll-media-picker-preview', false)
|
||||
->assertSee('评论状态', false)
|
||||
->assertSee('访问设置', false)
|
||||
->assertSee('会员专享', false)
|
||||
->assertSee('价格', false)
|
||||
->assertSee('置顶头条', false)
|
||||
->assertSee('轻置顶', false);
|
||||
}
|
||||
|
||||
public function test_settings_page_loads(): void
|
||||
{
|
||||
$this->actingAs($this->admin)->get('/admin/blog-settings')->assertOk();
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin/blog-settings')
|
||||
->assertOk()
|
||||
->assertSee('首页浏览模式', false);
|
||||
}
|
||||
|
||||
public function test_page_cache_and_language_pages_load(): void
|
||||
{
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin/page-cache')
|
||||
->assertOk()
|
||||
->assertSee('页面缓存', false)
|
||||
->assertSee('已缓存的前台页面', false);
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin/languages')
|
||||
->assertOk()
|
||||
->assertSee('文章多语言', false)
|
||||
->assertSee('未开通', false);
|
||||
}
|
||||
|
||||
public function test_comment_resource_loads(): void
|
||||
@@ -77,8 +108,8 @@ class AdminPagesTest extends TestCase
|
||||
$original = app()->environment();
|
||||
app()['env'] = 'production';
|
||||
|
||||
$this->assertSame('prod', \App\Filament\Support\AdminEnvironment::name());
|
||||
$this->assertSame('danger', \App\Filament\Support\AdminEnvironment::color());
|
||||
$this->assertSame('prod', AdminEnvironment::name());
|
||||
$this->assertSame('danger', AdminEnvironment::color());
|
||||
|
||||
app()['env'] = $original;
|
||||
}
|
||||
@@ -88,16 +119,17 @@ class AdminPagesTest extends TestCase
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin')
|
||||
->assertOk()
|
||||
->assertSee(\App\Filament\Support\AdminEnvironment::name(), false)
|
||||
->assertSee(AdminEnvironment::name(), false)
|
||||
->assertSee('写文章', false)
|
||||
->assertSee('网站', false)
|
||||
->assertSee('/admin/posts/create', false);
|
||||
}
|
||||
|
||||
public function test_hello_world_plugin_pages_load_when_enabled(): void
|
||||
{
|
||||
$manager = app(\App\Blog\Support\PluginManager::class);
|
||||
$manager = app(PluginManager::class);
|
||||
$manager->enable('demo.hello-world');
|
||||
\Plugins\Demo\HelloWorld\ServiceProvider::bootPlugin($manager);
|
||||
ServiceProvider::bootPlugin($manager);
|
||||
|
||||
$this->actingAs($this->admin)->get('/admin')->assertOk();
|
||||
$this->actingAs($this->admin)->get('/admin/posts/create')->assertOk();
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Filament\Resources\Posts\Pages\EditPost;
|
||||
use App\Filament\Resources\Posts\RelationManagers\CommentsRelationManager;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class FilamentV5CompatTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_no_legacy_filament_table_action_imports(): void
|
||||
{
|
||||
$legacy = [];
|
||||
|
||||
foreach ([app_path(), base_path('plugins')] as $root) {
|
||||
foreach (File::allFiles($root) as $file) {
|
||||
if ($file->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $file->getPathname();
|
||||
if (str_contains(File::get($path), 'Filament\\Tables\\Actions\\')) {
|
||||
$legacy[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame(
|
||||
[],
|
||||
$legacy,
|
||||
'Filament v5 table actions must use Filament\\Actions, not Filament\\Tables\\Actions'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_edit_post_boots_comments_relation_manager(): void
|
||||
{
|
||||
$this->seed();
|
||||
|
||||
$admin = User::query()->where('email', 'admin@laralog.test')->firstOrFail();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$post = Post::create([
|
||||
'user_id' => $admin->id,
|
||||
'title' => '评论关系测试',
|
||||
'slug' => 'comment-relation-test',
|
||||
'content' => '内容',
|
||||
'content_format' => 'markdown',
|
||||
'status' => 'published',
|
||||
'published_at' => now(),
|
||||
]);
|
||||
|
||||
$this->get('/admin/posts/'.$post->id.'/edit')->assertOk();
|
||||
|
||||
Livewire::test(EditPost::class, ['record' => $post->getRouteKey()])
|
||||
->assertSuccessful();
|
||||
|
||||
// The comments table only instantiates here. A GET of the edit page does not catch a bad Action import.
|
||||
Livewire::test(CommentsRelationManager::class, [
|
||||
'ownerRecord' => $post,
|
||||
'pageClass' => EditPost::class,
|
||||
])->assertSuccessful();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class HomeViewModeTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Post $post;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed();
|
||||
|
||||
$user = User::create(['name' => '作者', 'email' => 'view@test.com', 'password' => bcrypt('x')]);
|
||||
$category = Category::create(['name' => '技术', 'slug' => 'tech-view']);
|
||||
$this->post = Post::create([
|
||||
'user_id' => $user->id,
|
||||
'category_id' => $category->id,
|
||||
'title' => '浏览模式测试文',
|
||||
'slug' => 'view-mode-test',
|
||||
'excerpt' => '列表摘要文字',
|
||||
'content' => '这是完整正文内容 FULL_BODY_ONLY_TOKEN',
|
||||
'content_format' => 'markdown',
|
||||
'status' => 'published',
|
||||
'published_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_list_mode_shows_excerpt_not_full_body(): void
|
||||
{
|
||||
$this->get('/?view=list')
|
||||
->assertOk()
|
||||
->assertSee('浏览模式', false)
|
||||
->assertSee('列表摘要文字', false)
|
||||
->assertDontSee('FULL_BODY_ONLY_TOKEN', false);
|
||||
}
|
||||
|
||||
public function test_full_mode_shows_article_body(): void
|
||||
{
|
||||
$this->get('/?view=full')
|
||||
->assertOk()
|
||||
->assertSee('浏览模式', false)
|
||||
->assertSee('FULL_BODY_ONLY_TOKEN', false);
|
||||
}
|
||||
|
||||
public function test_compact_pagination_shows_page_info(): void
|
||||
{
|
||||
$userId = $this->post->user_id;
|
||||
$categoryId = $this->post->category_id;
|
||||
|
||||
for ($index = 1; $index <= 12; $index++) {
|
||||
Post::create([
|
||||
'user_id' => $userId,
|
||||
'category_id' => $categoryId,
|
||||
'title' => '分页文章 '.$index,
|
||||
'slug' => 'pager-'.$index,
|
||||
'content' => '正文 '.$index,
|
||||
'content_format' => 'markdown',
|
||||
'status' => 'published',
|
||||
'published_at' => now()->subMinutes($index),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->get('/')
|
||||
->assertOk()
|
||||
->assertSee('第 1 /', false)
|
||||
->assertSee('页', false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ImportedContentTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Post $post;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed();
|
||||
|
||||
$user = User::create(['name' => '作者', 'email' => 'import-html@test.com', 'password' => bcrypt('x')]);
|
||||
$category = Category::create(['name' => '运维', 'slug' => 'ops']);
|
||||
|
||||
$html = '<p> 不多说,上代码:</p>'
|
||||
.'<div class="codeText"><div class="codeHead">XML/HTML代码</div>'
|
||||
.'<ol class="dp-xml">'
|
||||
.'<li>docker run -d\\</li>'
|
||||
.'<li> --restart always \\</li>'
|
||||
.'<li> --name derper</li>'
|
||||
.'</ol></div>'
|
||||
.'<div><div>curl -fsSL example.com \\</div><div> | sh</div></div>';
|
||||
|
||||
$this->post = Post::create([
|
||||
'user_id' => $user->id,
|
||||
'category_id' => $category->id,
|
||||
'title' => '还是得derp',
|
||||
'slug' => 'derp-import',
|
||||
'excerpt' => '',
|
||||
'content' => $html,
|
||||
'content_format' => 'html',
|
||||
'status' => 'published',
|
||||
'published_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_show_page_hides_nbsp_and_wraps_code(): void
|
||||
{
|
||||
$this->get('/show-'.$this->post->id.'.shtml')
|
||||
->assertOk()
|
||||
->assertDontSee(' ', false)
|
||||
->assertDontSee('codeText', false)
|
||||
->assertSee('<pre', false)
|
||||
->assertSee('docker run', false)
|
||||
->assertSee('language-bash', false);
|
||||
}
|
||||
|
||||
public function test_homepage_excerpt_does_not_show_nbsp(): void
|
||||
{
|
||||
$this->get('/')
|
||||
->assertOk()
|
||||
->assertSee('不多说,上代码', false)
|
||||
->assertDontSee(' ', false);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ namespace Tests\Feature;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Comment;
|
||||
use App\Models\Post;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\Tags\Tag;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -23,7 +23,7 @@ class LegacyRoutesTest extends TestCase
|
||||
|
||||
$user = User::create(['name' => '作者', 'email' => 'author@test.com', 'password' => bcrypt('x')]);
|
||||
$category = Category::create(['name' => '技术', 'slug' => 'tech']);
|
||||
\Illuminate\Support\Facades\DB::table('posts')->insert([
|
||||
DB::table('posts')->insert([
|
||||
'id' => 12,
|
||||
'user_id' => $user->id,
|
||||
'category_id' => $category->id,
|
||||
@@ -93,6 +93,8 @@ class LegacyRoutesTest extends TestCase
|
||||
$this->get('/rss.xml')->assertOk();
|
||||
$this->get('/sitemap.xml')->assertOk();
|
||||
$this->get('/robots.txt')->assertOk();
|
||||
$this->get('/llms.txt')->assertOk();
|
||||
$this->get('/posts/legacy-post.md')->assertOk();
|
||||
}
|
||||
|
||||
public function test_missing_records_return_404(): void
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Blog\Support\PageCacheMiddleware;
|
||||
use App\Blog\Support\ThemeManager;
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LlmsFeedTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $author;
|
||||
|
||||
private Category $category;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed();
|
||||
|
||||
$this->author = User::query()->where('email', 'admin@laralog.test')->firstOrFail();
|
||||
$this->category = Category::create([
|
||||
'name' => '索引',
|
||||
'slug' => 'llms-cat',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_llms_txt_lists_new_published_posts_and_skips_drafts(): void
|
||||
{
|
||||
$published = $this->makePost('给 Agent 看的新文章', 'agent-new-post', 'published');
|
||||
$this->makePost('还没发布', 'agent-draft', 'draft');
|
||||
|
||||
$this->get('/llms.txt')
|
||||
->assertOk()
|
||||
->assertHeader('Content-Type', 'text/plain; charset=utf-8')
|
||||
->assertSee('给 Agent 看的新文章', false)
|
||||
->assertSee('/posts/agent-new-post.md', false)
|
||||
->assertSee($published->url, false)
|
||||
->assertDontSee('还没发布', false);
|
||||
}
|
||||
|
||||
public function test_markdown_export_is_live_and_hides_restricted_posts(): void
|
||||
{
|
||||
$this->makePost('Markdown 导出', 'md-export', 'published', '这里是正文。');
|
||||
|
||||
$this->get('/posts/md-export.md')
|
||||
->assertOk()
|
||||
->assertHeader('Content-Type', 'text/markdown; charset=utf-8')
|
||||
->assertSee('# Markdown 导出', false)
|
||||
->assertSee('这里是正文。', false);
|
||||
|
||||
$this->makePost('会员专享', 'members-only-md', 'published', '秘密', [
|
||||
'members_only' => true,
|
||||
]);
|
||||
|
||||
$this->get('/posts/members-only-md.md')->assertNotFound();
|
||||
$this->get('/llms.txt')->assertDontSee('会员专享', false);
|
||||
}
|
||||
|
||||
public function test_llms_and_feeds_are_not_page_cached(): void
|
||||
{
|
||||
$this->makePost('缓存外的文章', 'no-cache-llms', 'published');
|
||||
PageCacheMiddleware::flush();
|
||||
|
||||
$this->get('/llms.txt')->assertOk();
|
||||
$this->get('/rss.xml')->assertOk();
|
||||
$this->get('/posts/no-cache-llms.md')->assertOk();
|
||||
|
||||
$this->assertSame(0, PageCacheMiddleware::count());
|
||||
}
|
||||
|
||||
public function test_frontend_feed_links_open_in_a_new_tab(): void
|
||||
{
|
||||
app(ThemeManager::class)->activate('sablog');
|
||||
|
||||
$this->get('/')
|
||||
->assertOk()
|
||||
->assertSee('target="_blank"', false)
|
||||
->assertSee('rel="noopener noreferrer"', false)
|
||||
->assertSee(route('feed.rss'), false)
|
||||
->assertSee(route('feed.sitemap'), false)
|
||||
->assertSee(route('feed.llms'), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
private function makePost(string $title, string $slug, string $status, string $content = '内容', array $meta = []): Post
|
||||
{
|
||||
return Post::create([
|
||||
'user_id' => $this->author->id,
|
||||
'category_id' => $this->category->id,
|
||||
'title' => $title,
|
||||
'slug' => $slug,
|
||||
'content' => $content,
|
||||
'content_format' => 'markdown',
|
||||
'status' => $status,
|
||||
'meta' => $meta,
|
||||
'published_at' => $status === 'published' ? now() : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PageCacheTest extends TestCase
|
||||
@@ -44,6 +43,9 @@ class PageCacheTest extends TestCase
|
||||
// 第二次命中缓存
|
||||
$this->get('/')->assertOk();
|
||||
$this->assertSame(1, PageCacheMiddleware::count());
|
||||
|
||||
$this->get('/?view=full')->assertOk();
|
||||
$this->assertSame(2, PageCacheMiddleware::count());
|
||||
}
|
||||
|
||||
public function test_authenticated_user_not_cached(): void
|
||||
@@ -63,7 +65,7 @@ class PageCacheTest extends TestCase
|
||||
$post = Post::where('slug', 'cache-test')->first();
|
||||
$views = $post->views;
|
||||
|
||||
$this->get('/posts/cache-test.shtml')->assertOk();
|
||||
$this->get($post->url)->assertOk();
|
||||
$this->get('/track-view/'.$post->id)->assertStatus(204);
|
||||
|
||||
$post->refresh();
|
||||
|
||||
@@ -6,7 +6,11 @@ namespace Tests\Feature;
|
||||
|
||||
use App\Filament\Resources\Posts\Pages\EditPost;
|
||||
use App\Filament\Resources\Posts\PostResource;
|
||||
use App\Filament\Resources\Posts\Schemas\PostForm;
|
||||
use App\Models\Post;
|
||||
use Filament\Forms\Components\Field;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
@@ -18,31 +22,51 @@ class PostFormInjectionTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_post_form_includes_paid_section_from_membership_plugin(): void
|
||||
public function test_post_form_uses_two_to_one_layout(): void
|
||||
{
|
||||
$schema = Schema::make();
|
||||
PostResource::form($schema);
|
||||
|
||||
// Section 的标题在 heading 而非 label
|
||||
$headings = collect($schema->getComponents())
|
||||
->map(fn ($component) => method_exists($component, 'getHeading') ? $component->getHeading() : $component->getLabel())
|
||||
->all();
|
||||
$roots = $schema->getComponents();
|
||||
$this->assertCount(2, $roots);
|
||||
|
||||
$this->assertContains('付费设置', $headings);
|
||||
[$content, $sidebar] = $roots;
|
||||
$this->assertSame('正文', $content->getHeading());
|
||||
$this->assertSame(PostForm::SIDEBAR_KEY, $sidebar->getKey(isAbsolute: false));
|
||||
$this->assertSame(2, $content->getColumnSpan('default'));
|
||||
$this->assertSame(2, $content->getColumnSpan('lg'));
|
||||
$this->assertSame(1, $sidebar->getColumnSpan('default'));
|
||||
$this->assertSame(1, $sidebar->getColumnSpan('lg'));
|
||||
}
|
||||
|
||||
// 找到付费设置 Section;无 Livewire 环境下用反射读取默认内容槽(childComponents 按 schema key 分组)
|
||||
$section = collect($schema->getComponents())
|
||||
->first(fn ($component) => $component->getHeading() === '付费设置');
|
||||
public function test_post_form_includes_paid_fields_in_access_section(): void
|
||||
{
|
||||
$schema = Schema::make();
|
||||
PostResource::form($schema);
|
||||
|
||||
$property = (new ReflectionClass($section))->getProperty('childComponents');
|
||||
$fieldNames = collect($property->getValue($section)['default'] ?? [])
|
||||
->map(fn ($field) => $field->getName())
|
||||
->all();
|
||||
$access = $this->findKeyedComponent($schema->getComponents(), PostForm::ACCESS_KEY);
|
||||
$this->assertNotNull($access);
|
||||
$this->assertSame('访问设置', $access->getHeading());
|
||||
|
||||
$fieldNames = $this->fieldNames($access);
|
||||
$this->assertContains('read_password', $fieldNames);
|
||||
$this->assertContains('meta.price', $fieldNames);
|
||||
$this->assertContains('meta.members_only', $fieldNames);
|
||||
}
|
||||
|
||||
public function test_content_format_and_status_are_single_choice(): void
|
||||
{
|
||||
$schema = Schema::make();
|
||||
PostResource::form($schema);
|
||||
|
||||
$fields = $this->collectFields($schema->getComponents());
|
||||
|
||||
$this->assertInstanceOf(ToggleButtons::class, $fields['content_format'] ?? null);
|
||||
$this->assertInstanceOf(ToggleButtons::class, $fields['status'] ?? null);
|
||||
$this->assertInstanceOf(ToggleButtons::class, $fields['close_comment'] ?? null);
|
||||
$this->assertInstanceOf(ToggleButtons::class, $fields['sticky_level'] ?? null);
|
||||
}
|
||||
|
||||
public function test_post_table_includes_paid_badge_column(): void
|
||||
{
|
||||
// Table 构造器要求 HasTable Livewire 组件,测试中绕过构造器
|
||||
@@ -93,4 +117,81 @@ class PostFormInjectionTest extends TestCase
|
||||
$this->assertSame(9900, $post->meta['price']);
|
||||
$this->assertTrue($post->meta['members_only']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $components
|
||||
*/
|
||||
private function findKeyedComponent(array $components, string $key): ?Component
|
||||
{
|
||||
foreach ($components as $component) {
|
||||
if (! $component instanceof Component) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$property = (new ReflectionClass($component))->getProperty('key');
|
||||
$componentKey = $property->getValue($component);
|
||||
|
||||
if ($componentKey === $key) {
|
||||
return $component;
|
||||
}
|
||||
|
||||
$found = $this->findKeyedComponent($this->childComponents($component), $key);
|
||||
if ($found instanceof Component) {
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function fieldNames(Component $section): array
|
||||
{
|
||||
return collect($this->childComponents($section))
|
||||
->filter(fn ($field) => is_object($field) && method_exists($field, 'getName'))
|
||||
->map(fn ($field) => $field->getName())
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $components
|
||||
* @return array<string, Field>
|
||||
*/
|
||||
private function collectFields(array $components): array
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
foreach ($components as $component) {
|
||||
if ($component instanceof Field) {
|
||||
$fields[$component->getName()] = $component;
|
||||
}
|
||||
|
||||
if ($component instanceof Component) {
|
||||
$fields = [
|
||||
...$fields,
|
||||
...$this->collectFields($this->childComponents($component)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
private function childComponents(?Component $component): array
|
||||
{
|
||||
if (! $component instanceof Component) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$property = (new ReflectionClass($component))->getProperty('childComponents');
|
||||
$children = $property->getValue($component)['default'] ?? [];
|
||||
|
||||
return is_array($children) ? $children : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,30 @@ class SablogImportTest extends TestCase
|
||||
$this->assertSame('photo.jpg', $media->file_name);
|
||||
}
|
||||
|
||||
public function test_import_normalizes_highlighter_before_markdown(): void
|
||||
{
|
||||
$pdo = new PDO('sqlite:'.$this->legacyDb);
|
||||
$html = '<p> 不多说,上代码:</p>'
|
||||
.'<div class="codeText"><div class="codeHead">XML/HTML代码</div>'
|
||||
.'<ol class="dp-xml"><li>docker run -d\\</li><li> --name derper</li></ol></div>';
|
||||
$statement = $pdo->prepare('INSERT INTO sablog_articles VALUES (12, 2, 1, ?, ?, ?, ?, ?, 1, 0, ?, 0, 0, 0, 1, 0, ?)');
|
||||
$statement->execute(['还是得derp', '', $html, '', time(), '', '']);
|
||||
|
||||
$this->import(['--convert-markdown' => true]);
|
||||
|
||||
$post = Post::find(12);
|
||||
$this->assertNotNull($post);
|
||||
$this->assertSame('markdown', $post->content_format);
|
||||
$this->assertStringNotContainsString(' ', $post->content);
|
||||
$this->assertStringNotContainsString('codeText', $post->content);
|
||||
|
||||
$this->get('/show-12.shtml')
|
||||
->assertOk()
|
||||
->assertDontSee(' ', false)
|
||||
->assertSee('docker run', false)
|
||||
->assertSee('<pre', false);
|
||||
}
|
||||
|
||||
public function test_import_is_idempotent(): void
|
||||
{
|
||||
$this->import(['--sync-attachments' => true]);
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Blog\Support\PageCacheMiddleware;
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StickyPreviewTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $admin;
|
||||
|
||||
private Category $category;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed();
|
||||
|
||||
$this->admin = User::query()->where('email', 'admin@laralog.test')->firstOrFail();
|
||||
$this->category = Category::query()->first() ?? Category::create([
|
||||
'name' => '测试分类',
|
||||
'slug' => 'sticky-test-cat',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_sticky_headline_stays_on_top(): void
|
||||
{
|
||||
$headline = $this->makePost('头条文章', Post::STICKY_HEADLINE, now()->subDays(3));
|
||||
$strong = $this->makePost('强置顶文章', Post::STICKY_STRONG, now()->subDay());
|
||||
$normal = $this->makePost('普通文章', Post::STICKY_NONE, now());
|
||||
|
||||
$ids = Post::query()->published()->orderedForList()->pluck('id')->all();
|
||||
|
||||
$this->assertSame(
|
||||
[$headline->id, $strong->id, $normal->id],
|
||||
array_values(array_intersect($ids, [$headline->id, $strong->id, $normal->id]))
|
||||
);
|
||||
|
||||
$headline->refresh();
|
||||
$this->assertTrue($headline->is_sticky);
|
||||
$this->assertSame(Post::STICKY_HEADLINE, $headline->sticky_level);
|
||||
$this->assertSame('置顶头条', $headline->stickyLabel());
|
||||
}
|
||||
|
||||
public function test_guest_cannot_preview_draft_but_editor_can(): void
|
||||
{
|
||||
$draft = $this->makePost('草稿预览', Post::STICKY_NONE, null, 'draft');
|
||||
|
||||
$this->get($draft->url)->assertNotFound();
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->get($draft->url)
|
||||
->assertOk()
|
||||
->assertSee('草稿预览', false)
|
||||
->assertSee('预览中', false);
|
||||
|
||||
$editor = User::factory()->create([
|
||||
'email' => 'editor-preview@laralog.test',
|
||||
]);
|
||||
$editor->assignRole('editor');
|
||||
|
||||
$this->actingAs($editor)
|
||||
->get($draft->url)
|
||||
->assertOk()
|
||||
->assertSee('预览中', false);
|
||||
|
||||
$member = User::factory()->create([
|
||||
'email' => 'member-preview@laralog.test',
|
||||
]);
|
||||
$member->assignRole('member');
|
||||
|
||||
$this->actingAs($member)
|
||||
->get($draft->url)
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_edit_page_has_preview_action(): void
|
||||
{
|
||||
$post = $this->makePost('可预览文章', Post::STICKY_LIGHT, now());
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin/posts/'.$post->id.'/edit')
|
||||
->assertOk()
|
||||
->assertSee('预览', false)
|
||||
->assertSee($post->url, false);
|
||||
}
|
||||
|
||||
public function test_page_cache_entries_store_url(): void
|
||||
{
|
||||
PageCacheMiddleware::flush();
|
||||
|
||||
$this->get('/')->assertOk();
|
||||
|
||||
$entries = PageCacheMiddleware::entries();
|
||||
$this->assertNotEmpty($entries);
|
||||
$this->assertNotSame('', $entries[0]['url']);
|
||||
$this->assertSame(1, PageCacheMiddleware::count());
|
||||
}
|
||||
|
||||
private function makePost(string $title, int $level, mixed $publishedAt, string $status = 'published'): Post
|
||||
{
|
||||
return Post::create([
|
||||
'user_id' => $this->admin->id,
|
||||
'category_id' => $this->category->id,
|
||||
'title' => $title,
|
||||
'slug' => str()->slug($title).'-'.uniqid(),
|
||||
'content' => '内容',
|
||||
'content_format' => 'markdown',
|
||||
'status' => $status,
|
||||
'sticky_level' => $level,
|
||||
'published_at' => $publishedAt,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<footer class="d-footer">
|
||||
<div class="d-container">
|
||||
<p>© {{ date('Y') }} {{ $siteName }}@if($siteIcp) · <a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">{{ $siteIcp }}</a>@endif</p>
|
||||
<p>@include('partials.feed-links')</p>
|
||||
<p>由 LaraLog 驱动 · Demo 主题</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<meta name="generator" content="LaraLog">
|
||||
<link rel="canonical" href="{{ url()->current() }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ $siteName }}" href="{{ route('feed.rss') }}">
|
||||
<link rel="alternate" type="text/plain" title="llms.txt" href="{{ route('feed.llms') }}">
|
||||
{{-- 主题资产:/themes/demo/assets/style.css --}}
|
||||
<link rel="stylesheet" href="{{ theme('asset', 'style.css') }}">
|
||||
@stack('head')
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<a href="{{ route('archives') }}" class="{{ request()->routeIs('archives*') ? 'active' : '' }}">归档</a>
|
||||
<a href="{{ route('tags') }}" class="{{ request()->routeIs('tags*') ? 'active' : '' }}">标签</a>
|
||||
<a href="{{ route('links') }}" class="{{ request()->routeIs('links') ? 'active' : '' }}">友链</a>
|
||||
@include('partials.feed-links')
|
||||
<form class="d-search" action="{{ route('search') }}" method="get">
|
||||
<input type="search" name="q" placeholder="搜索..." value="{{ request('q', request('keyword', '')) }}">
|
||||
</form>
|
||||
|
||||
+732
-138
@@ -17,182 +17,776 @@
|
||||
--border: #2c2c2c;
|
||||
}
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 16px; line-height: 1.8; }
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { opacity: 0.8; }
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||||
"Microsoft YaHei", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
/* 页面统一宽度:页头/页脚与主体对齐(默认视图的 .container 也落在这里) */
|
||||
.container, .m-container { max-width: 1080px; margin: 0 auto; padding: 0 20px; }
|
||||
.container,
|
||||
.m-container {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.m-header { border-bottom: 1px solid var(--border); background: var(--card); }
|
||||
.m-header .m-container { display: flex; justify-content: space-between; align-items: center; padding: 18px 20px; }
|
||||
.m-logo { font-size: 22px; font-weight: 800; letter-spacing: 1px; color: var(--text); }
|
||||
.m-nav { display: flex; gap: 18px; font-size: 14px; align-items: center; }
|
||||
.m-nav a.active { font-weight: 700; }
|
||||
.m-link-btn { background: none; border: none; color: var(--accent); font-size: 14px; cursor: pointer; padding: 0; }
|
||||
.m-search input { padding: 7px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 14px; width: 140px; }
|
||||
.inline-form { display: inline; }
|
||||
.m-header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--card);
|
||||
}
|
||||
.m-header .m-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 18px 20px;
|
||||
}
|
||||
.m-logo {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text);
|
||||
}
|
||||
.m-nav {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
}
|
||||
.m-nav a.active {
|
||||
font-weight: 700;
|
||||
}
|
||||
.m-link-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--accent);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
.m-search input {
|
||||
padding: 7px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
width: 140px;
|
||||
}
|
||||
.inline-form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.main-layout { display: flex; gap: 32px; align-items: flex-start; max-width: 1080px; margin: 0 auto; padding: 36px 20px; }
|
||||
.main-layout .content { flex: 1; min-width: 0; max-width: 720px; }
|
||||
.main-layout .sidebar { width: 300px; flex-shrink: 0; }
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 300px;
|
||||
gap: 32px;
|
||||
align-items: start;
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 36px 20px;
|
||||
}
|
||||
.main-layout .content {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.main-layout .sidebar {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.post-card, .post-full, .comments-section { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 26px 30px; margin-bottom: 22px; }
|
||||
.post-card.sticky { box-shadow: inset 3px 0 0 var(--accent); }
|
||||
.post-title { font-size: 20px; margin-bottom: 8px; line-height: 1.45; }
|
||||
.post-card .post-title a { color: var(--text); }
|
||||
.badge { background: var(--accent); color: #fff; font-size: 11px; padding: 2px 7px; border-radius: 3px; margin-right: 6px; vertical-align: 2px; }
|
||||
.post-meta { color: var(--muted); font-size: 13px; margin-bottom: 12px; }
|
||||
.post-excerpt { color: var(--muted); }
|
||||
.post-footer { margin-top: 12px; display: flex; gap: 12px; flex-wrap: wrap; font-size: 14px; }
|
||||
.read-more { font-weight: 600; }
|
||||
.post-card,
|
||||
.post-full,
|
||||
.comments-section {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 26px 30px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.post-card.sticky {
|
||||
box-shadow: inset 3px 0 0 var(--accent);
|
||||
}
|
||||
.post-title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.post-card .post-title a {
|
||||
color: var(--text);
|
||||
}
|
||||
.badge {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
padding: 2px 7px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
vertical-align: 2px;
|
||||
}
|
||||
.post-meta {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.post-excerpt {
|
||||
color: var(--muted);
|
||||
}
|
||||
.post-footer {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14px;
|
||||
}
|
||||
.read-more {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.post-full .post-title { font-size: 28px; }
|
||||
.post-content { margin-top: 14px; }
|
||||
.post-content img { max-width: 100%; border-radius: 8px; }
|
||||
.post-content pre { background: #0d1117; color: #c9d1d9; padding: 16px; border-radius: 8px; overflow-x: auto; margin: 14px 0; }
|
||||
.post-content code { background: rgba(128,128,128,.15); padding: 2px 6px; border-radius: 4px; font-size: 14px; }
|
||||
.post-content pre code { background: none; padding: 0; }
|
||||
.post-content blockquote { border-left: 3px solid var(--accent); padding-left: 16px; color: var(--muted); margin: 14px 0; }
|
||||
.post-content h1, .post-content h2, .post-content h3 { margin: 20px 0 8px; }
|
||||
.post-full .post-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
.post-content {
|
||||
margin-top: 14px;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.post-content pre,
|
||||
.post-content .codeText,
|
||||
.post-content ol[class^="dp-"] {
|
||||
background: #0d1117;
|
||||
color: #c9d1d9;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
margin: 14px 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content code {
|
||||
background: rgba(128, 128, 128, 0.15);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
.post-content pre code {
|
||||
display: block;
|
||||
background: none;
|
||||
padding: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content blockquote {
|
||||
border-left: 3px solid var(--accent);
|
||||
padding-left: 16px;
|
||||
color: var(--muted);
|
||||
margin: 14px 0;
|
||||
}
|
||||
.post-content h1,
|
||||
.post-content h2,
|
||||
.post-content h3 {
|
||||
margin: 20px 0 8px;
|
||||
}
|
||||
|
||||
.post-cover { margin: -26px -30px 18px; border-radius: 12px 12px 0 0; overflow: hidden; }
|
||||
.post-cover img { width: 100%; display: block; max-height: 380px; object-fit: cover; }
|
||||
.post-cover {
|
||||
margin: -26px -30px 18px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.post-cover img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
max-height: 380px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tag-link { color: var(--accent); font-size: 14px; margin-right: 8px; }
|
||||
.tag-link {
|
||||
color: var(--accent);
|
||||
font-size: 14px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.comments-section { margin-top: 8px; }
|
||||
.section-title { font-size: 17px; margin-bottom: 14px; }
|
||||
.comment { padding: 12px 0; border-bottom: 1px solid var(--border); }
|
||||
.comment:last-of-type { border-bottom: none; }
|
||||
.comment-head { font-size: 14px; margin-bottom: 4px; }
|
||||
.comment-time { color: var(--muted); font-size: 13px; }
|
||||
.comment-body { color: var(--text); font-size: 15px; }
|
||||
.comment-form { margin-top: 14px; }
|
||||
.form-row { margin-bottom: 12px; }
|
||||
.form-row label { display: block; font-size: 14px; margin-bottom: 4px; color: var(--muted); }
|
||||
.form-row input, .form-row textarea { width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 15px; font-family: inherit; }
|
||||
.honeypot { position: absolute; left: -9999px; opacity: 0; }
|
||||
.error { color: var(--accent); font-size: 13px; }
|
||||
.btn { background: var(--accent); color: #fff; border: none; padding: 10px 24px; border-radius: 8px; font-size: 15px; cursor: pointer; }
|
||||
.alert { padding: 12px 18px; border-radius: 8px; margin-bottom: 16px; font-size: 14px; }
|
||||
.alert-success { background: rgba(46,160,67,.12); color: #2ea043; }
|
||||
.alert-error { background: rgba(248,81,73,.12); color: #f85149; }
|
||||
.empty { color: var(--muted); text-align: center; padding: 40px 0; }
|
||||
.comments-section {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 17px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.comment {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.comment:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
.comment-head {
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.comment-time {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.comment-body {
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
}
|
||||
.comment-form {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.form-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form-row label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.form-row input,
|
||||
.form-row textarea {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.honeypot {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
opacity: 0;
|
||||
}
|
||||
.error {
|
||||
color: var(--accent);
|
||||
font-size: 13px;
|
||||
}
|
||||
.btn {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.alert {
|
||||
padding: 12px 18px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.alert-success {
|
||||
background: rgba(46, 160, 67, 0.12);
|
||||
color: #2ea043;
|
||||
}
|
||||
.alert-error {
|
||||
background: rgba(248, 81, 73, 0.12);
|
||||
color: #f85149;
|
||||
}
|
||||
.alert-warning {
|
||||
background: rgba(210, 153, 34, 0.12);
|
||||
color: #9a6700;
|
||||
}
|
||||
.empty {
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.list-title { font-size: 24px; margin-bottom: 20px; }
|
||||
.list-title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.archive-year { font-size: 16px; margin: 16px 0 6px; }
|
||||
.archive-list { list-style: none; }
|
||||
.archive-posts { margin: 4px 0 12px 22px; }
|
||||
.archive-posts time { color: var(--muted); font-size: 13px; }
|
||||
.archive-year {
|
||||
font-size: 16px;
|
||||
margin: 16px 0 6px;
|
||||
}
|
||||
.archive-list {
|
||||
list-style: none;
|
||||
}
|
||||
.archive-posts {
|
||||
margin: 4px 0 12px 22px;
|
||||
}
|
||||
.archive-posts time {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pagination { margin: 16px 0; }
|
||||
.pagination-links { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.pagination-links a, .pagination-links .current, .pagination-links .disabled { padding: 6px 14px; border: 1px solid var(--border); border-radius: 6px; font-size: 14px; background: var(--card); color: var(--text); }
|
||||
.pagination-links .current { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||||
.pagination-links .disabled { color: var(--muted); }
|
||||
.view-mode-switcher {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.view-mode-switcher a.active {
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.m-footer { border-top: 1px solid var(--border); padding: 26px 0 40px; margin-top: 30px; text-align: center; color: var(--muted); font-size: 13px; }
|
||||
.pagination {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.pagination-links {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
.pagination-info {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
margin-right: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pagination-links a,
|
||||
.pagination-links .current,
|
||||
.pagination-links .disabled {
|
||||
padding: 6px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
background: var(--card);
|
||||
color: var(--text);
|
||||
}
|
||||
.pagination-links .current {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.pagination-links .disabled {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.tag-cloud.big { line-height: 2.4; }
|
||||
.links-list { list-style: none; }
|
||||
.links-list li { padding: 6px 0; }
|
||||
.link-note { color: var(--muted); font-size: 14px; }
|
||||
.m-footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 26px 0 40px;
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.auth-layout { display: flex; justify-content: center; padding: 50px 20px; }
|
||||
.auth-box { width: 100%; max-width: 420px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 30px; }
|
||||
.auth-alt { margin-top: 14px; font-size: 14px; color: var(--muted); text-align: center; }
|
||||
.search-form { margin-bottom: 16px; display: flex; gap: 8px; }
|
||||
.search-form input { flex: 1; padding: 10px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 15px; }
|
||||
.tag-cloud.big {
|
||||
line-height: 2.4;
|
||||
}
|
||||
.links-list {
|
||||
list-style: none;
|
||||
}
|
||||
.links-list li {
|
||||
padding: 6px 0;
|
||||
}
|
||||
.link-note {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.auth-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 50px 20px;
|
||||
}
|
||||
.auth-box {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
}
|
||||
.auth-alt {
|
||||
margin-top: 14px;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.search-form input {
|
||||
flex: 1;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* ============ 侧边栏 ============ */
|
||||
.sidebar { position: sticky; top: 20px; }
|
||||
.widget { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 18px 20px; margin-bottom: 18px; }
|
||||
.widget-title { font-size: 15px; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 2px solid var(--accent); }
|
||||
.widget-list { list-style: none; }
|
||||
.widget-list li { font-size: 14px; padding: 5px 0; border-bottom: 1px solid var(--border); }
|
||||
.sidebar {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
.widget {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 18px 20px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.widget-title {
|
||||
font-size: 15px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
.widget-list {
|
||||
list-style: none;
|
||||
}
|
||||
.widget-list li {
|
||||
font-size: 14px;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* 社交账号:横向胶囊列表 */
|
||||
.social-links { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.social-links li { font-size: 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 4px 10px; }
|
||||
.widget-list li:last-child { border-bottom: none; }
|
||||
.count { color: var(--muted); font-size: 13px; }
|
||||
.tag-cloud { line-height: 2; }
|
||||
.tag-cloud a { display: inline-block; }
|
||||
.tag-cloud.big { line-height: 2.4; }
|
||||
.social-links {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.social-links li {
|
||||
font-size: 12px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
.widget-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.count {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.tag-cloud {
|
||||
line-height: 2;
|
||||
}
|
||||
.tag-cloud a {
|
||||
display: inline-block;
|
||||
}
|
||||
.tag-cloud.big {
|
||||
line-height: 2.4;
|
||||
}
|
||||
|
||||
/* ============ 顶栏搜索(默认 header partial) ============ */
|
||||
.site-search input { padding: 7px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 14px; width: 160px; }
|
||||
.link-btn { background: none; border: none; color: var(--accent); font-size: 15px; cursor: pointer; padding: 0; }
|
||||
.site-search input {
|
||||
padding: 7px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
width: 160px;
|
||||
}
|
||||
.link-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--accent);
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ============ 文章正文补充 ============ */
|
||||
.post-tags { margin-top: 16px; }
|
||||
.post-content ul, .post-content ol { padding-left: 24px; margin: 8px 0; }
|
||||
.post-content h4, .post-content h5, .post-content h6 { margin: 16px 0 6px; }
|
||||
.post-content table { border-collapse: collapse; width: 100%; margin: 12px 0; }
|
||||
.post-content th, .post-content td { border: 1px solid var(--border); padding: 6px 10px; }
|
||||
.post-content hr { border: none; border-top: 1px solid var(--border); margin: 18px 0; }
|
||||
.post-tags {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.post-content ul,
|
||||
.post-content ol {
|
||||
padding-left: 24px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.post-content h4,
|
||||
.post-content h5,
|
||||
.post-content h6 {
|
||||
margin: 16px 0 6px;
|
||||
}
|
||||
.post-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.post-content th,
|
||||
.post-content td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 6px 10px;
|
||||
}
|
||||
.post-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 18px 0;
|
||||
}
|
||||
|
||||
/* ============ 个人中心 ============ */
|
||||
.profile-box { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 24px 28px; margin-bottom: 20px; }
|
||||
.profile-box p { margin-bottom: 8px; }
|
||||
.profile-box {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 24px 28px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.profile-box p {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ============ 按钮变体 ============ */
|
||||
.btn-primary { background: var(--accent); }
|
||||
.btn:hover { opacity: 0.9; }
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
}
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* ============ 会员/支付 ============ */
|
||||
.plans-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
|
||||
.plan-card .plan-price { font-size: 26px; font-weight: 800; color: var(--accent); }
|
||||
.paid-teaser { background: rgba(248,81,73,.08); border: 1px dashed var(--accent); border-radius: 8px; padding: 12px 16px; color: var(--muted); font-size: 14px; margin: 12px 0; }
|
||||
.plans-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.plan-card .plan-price {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: var(--accent);
|
||||
}
|
||||
.paid-teaser {
|
||||
background: rgba(248, 81, 73, 0.08);
|
||||
border: 1px dashed var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
/* ============ 移动端适配 ============ */
|
||||
@media (max-width: 768px) {
|
||||
.m-header .m-container { flex-direction: column; align-items: flex-start; gap: 12px; padding: 16px 20px; }
|
||||
.m-nav { flex-wrap: wrap; gap: 12px; width: 100%; }
|
||||
.m-search { flex: 1; min-width: 100%; }
|
||||
.m-search input { width: 100%; }
|
||||
.main-layout { flex-direction: column; padding: 24px 16px; }
|
||||
.main-layout .content { max-width: 100%; }
|
||||
.main-layout .sidebar { width: 100%; }
|
||||
.post-card, .post-full, .comments-section { padding: 20px; }
|
||||
.post-cover { margin: -20px -20px 16px; }
|
||||
.post-full .post-title { font-size: 24px; }
|
||||
.auth-box { padding: 24px 20px; }
|
||||
.pagination-links { gap: 6px; }
|
||||
.pagination-links a, .pagination-links .current, .pagination-links .disabled { padding: 5px 10px; }
|
||||
.m-header .m-container {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.m-nav {
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.m-search {
|
||||
flex: 1;
|
||||
min-width: 100%;
|
||||
}
|
||||
.m-search input {
|
||||
width: 100%;
|
||||
}
|
||||
.main-layout {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
padding: 24px 16px;
|
||||
}
|
||||
.main-layout .content {
|
||||
max-width: 100%;
|
||||
}
|
||||
.main-layout .sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
.post-card,
|
||||
.post-full,
|
||||
.comments-section {
|
||||
padding: 20px;
|
||||
}
|
||||
.post-cover {
|
||||
margin: -20px -20px 16px;
|
||||
}
|
||||
.post-full .post-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
.auth-box {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
.pagination-links {
|
||||
gap: 6px;
|
||||
}
|
||||
.pagination-links a,
|
||||
.pagination-links .current,
|
||||
.pagination-links .disabled {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* TOC */
|
||||
.toc { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 14px 18px; margin: 0 0 18px; font-size: 14px; }
|
||||
.toc summary { cursor: pointer; font-weight: 600; margin-bottom: 6px; }
|
||||
.toc-list { list-style: none; }
|
||||
.toc-list li { padding: 3px 0; }
|
||||
.toc-list a { color: var(--muted); }
|
||||
.toc-list a:hover { color: var(--accent); }
|
||||
.toc-level-3 { padding-left: 14px; }
|
||||
.toc-level-4, .toc-level-5, .toc-level-6 { padding-left: 28px; }
|
||||
.toc {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 14px 18px;
|
||||
margin: 0 0 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.toc summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
}
|
||||
.toc-list li {
|
||||
padding: 3px 0;
|
||||
}
|
||||
.toc-list a {
|
||||
color: var(--muted);
|
||||
}
|
||||
.toc-list a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
.toc-level-3 {
|
||||
padding-left: 14px;
|
||||
}
|
||||
.toc-level-4,
|
||||
.toc-level-5,
|
||||
.toc-level-6 {
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Paywall */
|
||||
.paywall { border: 1px solid var(--border); border-radius: 12px; padding: 28px 22px; background: var(--bg); text-align: center; }
|
||||
.paywall-cover img { max-width: 100%; border-radius: 10px; margin-bottom: 16px; }
|
||||
.paywall-title { font-size: 22px; margin: 0 0 10px; }
|
||||
.paywall-excerpt { color: var(--muted); font-size: 14px; margin: 0 0 16px; }
|
||||
.paywall-price { font-size: 24px; font-weight: 800; color: var(--accent); margin: 0 0 8px; }
|
||||
.paywall-hint { color: var(--muted); font-size: 13px; margin: 0 0 18px; }
|
||||
.paywall-actions .btn { margin: 0 6px; }
|
||||
.paid-teaser { border: 1px dashed var(--border); background: var(--bg-muted, #f6f6f6); color: var(--muted); border-radius: 10px; padding: 14px 16px; margin: 14px 0; font-size: 14px; }
|
||||
.paid-teaser .btn { margin-left: 8px; }
|
||||
.paywall {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 28px 22px;
|
||||
background: var(--bg);
|
||||
text-align: center;
|
||||
}
|
||||
.paywall-cover img {
|
||||
max-width: 100%;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.paywall-title {
|
||||
font-size: 22px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.paywall-excerpt {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.paywall-price {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
color: var(--accent);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.paywall-hint {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.paywall-actions .btn {
|
||||
margin: 0 6px;
|
||||
}
|
||||
.paid-teaser {
|
||||
border: 1px dashed var(--border);
|
||||
background: var(--bg-muted, #f6f6f6);
|
||||
color: var(--muted);
|
||||
border-radius: 10px;
|
||||
padding: 14px 16px;
|
||||
margin: 14px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.paid-teaser .btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 已解锁的付费内容块 */
|
||||
.paid-content { border: 1px solid var(--border); border-left: 4px solid var(--accent); background: var(--bg); border-radius: 10px; padding: 14px 16px; margin: 14px 0; }
|
||||
.paid-content-label { font-size: 12px; color: var(--accent); font-weight: 600; margin-bottom: 6px; }
|
||||
.paid-content {
|
||||
border: 1px solid var(--border);
|
||||
border-left: 4px solid var(--accent);
|
||||
background: var(--bg);
|
||||
border-radius: 10px;
|
||||
padding: 14px 16px;
|
||||
margin: 14px 0;
|
||||
}
|
||||
.paid-content-label {
|
||||
font-size: 12px;
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 分类页介绍区 */
|
||||
.category-intro { display: flex; gap: 14px; align-items: flex-start; background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; margin-bottom: 18px; }
|
||||
.category-cover { width: 128px; height: 84px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
|
||||
.category-description { margin: 0 0 6px; font-size: 14px; color: var(--muted); line-height: 1.7; }
|
||||
.category-meta { margin: 0; font-size: 12px; color: var(--muted); }
|
||||
.category-intro {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px 18px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.category-cover {
|
||||
width: 128px;
|
||||
height: 84px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.category-description {
|
||||
margin: 0 0 6px;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
.category-meta {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<p>© {{ date('Y') }} {{ $siteName }}
|
||||
@if($siteIcp)<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">{{ $siteIcp }}</a>@endif
|
||||
</p>
|
||||
<p>@include('partials.feed-links')</p>
|
||||
<p>LaraLog · 用 Markdown 书写,用 AI 审核</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<meta name="generator" content="LaraLog">
|
||||
<link rel="canonical" href="{{ url()->current() }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ $siteName }}" href="{{ route('feed.rss') }}">
|
||||
<link rel="alternate" type="text/plain" title="llms.txt" href="{{ route('feed.llms') }}">
|
||||
<link rel="stylesheet" href="{{ theme('asset', 'style.css') }}">
|
||||
@stack('head')
|
||||
@stack('theme:head')
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<a href="{{ route('archives') }}" class="{{ request()->routeIs('archives*') ? 'active' : '' }}">归档</a>
|
||||
<a href="{{ route('tags') }}" class="{{ request()->routeIs('tags*') ? 'active' : '' }}">标签</a>
|
||||
<a href="{{ route('links') }}" class="{{ request()->routeIs('links') ? 'active' : '' }}">友链</a>
|
||||
@include('partials.feed-links')
|
||||
<form class="m-search" action="{{ route('search') }}" method="get">
|
||||
<input type="search" name="q" placeholder="搜索..." value="{{ request('q', request('keyword', '')) }}">
|
||||
</form>
|
||||
|
||||
+640
-124
@@ -1,175 +1,691 @@
|
||||
/* ============ Sablog 经典主题 ============ */
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #eef2f5 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==) repeat-x top;
|
||||
background: #eef2f5
|
||||
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==)
|
||||
repeat-x top;
|
||||
font-family: Verdana, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
a { color: #1c62b0; text-decoration: none; }
|
||||
a:hover { color: #f60; text-decoration: underline; }
|
||||
a {
|
||||
color: #1c62b0;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: #f60;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#outmain { width: 960px; margin: 0 auto; }
|
||||
#outmain {
|
||||
width: 960px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#header { padding: 22px 10px 8px; }
|
||||
.logo a { font-size: 30px; font-weight: bold; color: #1c62b0; font-family: Georgia, serif; }
|
||||
.logo .description { color: #888; font-size: 12px; }
|
||||
#header {
|
||||
padding: 22px 10px 8px;
|
||||
}
|
||||
.logo a {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: #1c62b0;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
.logo .description {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#nav { margin-top: 14px; border-top: 1px solid #d8dfe6; border-bottom: 1px solid #d8dfe6; background: #f7f9fb; }
|
||||
#nav ul { list-style: none; display: flex; flex-wrap: wrap; }
|
||||
#nav li { border-right: 1px solid #d8dfe6; }
|
||||
#nav a { display: block; padding: 7px 18px; color: #333; font-weight: bold; font-size: 13px; }
|
||||
#nav a:hover { background: #1c62b0; color: #fff; text-decoration: none; }
|
||||
#nav .current_page_item a { background: #1c62b0; color: #fff; }
|
||||
#nav {
|
||||
margin-top: 14px;
|
||||
border-top: 1px solid #d8dfe6;
|
||||
border-bottom: 1px solid #d8dfe6;
|
||||
background: #f7f9fb;
|
||||
}
|
||||
#nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#nav li {
|
||||
border-right: 1px solid #d8dfe6;
|
||||
}
|
||||
#nav a {
|
||||
display: block;
|
||||
padding: 7px 18px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
}
|
||||
#nav a:hover {
|
||||
background: #1c62b0;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
#nav .current_page_item a {
|
||||
background: #1c62b0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#page { padding: 14px 10px; display: flex; gap: 18px; align-items: flex-start; }
|
||||
#page {
|
||||
padding: 14px 10px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 280px;
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#content { flex: 1; min-width: 0; }
|
||||
#content {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.post-card, .post-full, .comments-section, .auth-box, .profile-box, .links-list {
|
||||
.post-card,
|
||||
.post-full,
|
||||
.comments-section,
|
||||
.auth-box,
|
||||
.profile-box,
|
||||
.links-list {
|
||||
background: #fff;
|
||||
border: 1px solid #d8dfe6;
|
||||
padding: 18px 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.post-card.sticky { border-left: 4px solid #f60; }
|
||||
.post-title { font-size: 17px; margin-bottom: 6px; line-height: 1.4; }
|
||||
.post-card .post-title a { color: #1c62b0; }
|
||||
.badge { background: #f60; color: #fff; font-size: 11px; padding: 1px 5px; margin-right: 5px; }
|
||||
.post-meta { color: #999; font-size: 12px; margin-bottom: 8px; }
|
||||
.post-excerpt { color: #555; margin-bottom: 8px; }
|
||||
.post-footer { font-size: 12px; }
|
||||
.tag-link { color: #1c62b0; margin-right: 8px; }
|
||||
.post-card.sticky {
|
||||
border-left: 4px solid #f60;
|
||||
}
|
||||
.post-title {
|
||||
font-size: 17px;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.post-card .post-title a {
|
||||
color: #1c62b0;
|
||||
}
|
||||
.badge {
|
||||
background: #f60;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
padding: 1px 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.post-meta {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.post-excerpt {
|
||||
color: #555;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.post-footer {
|
||||
font-size: 12px;
|
||||
}
|
||||
.tag-link {
|
||||
color: #1c62b0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.post-full .post-title { font-size: 22px; color: #1c62b0; }
|
||||
.post-content { margin-top: 10px; font-size: 14px; }
|
||||
.post-content img { max-width: 100%; }
|
||||
.post-content pre { background: #f4f4f4; border: 1px solid #ddd; padding: 10px; overflow: auto; margin: 10px 0; }
|
||||
.post-content blockquote { border-left: 4px solid #1c62b0; padding-left: 12px; color: #666; margin: 10px 0; }
|
||||
.post-content h1, .post-content h2, .post-content h3 { margin: 14px 0 6px; }
|
||||
.post-full .post-title {
|
||||
font-size: 22px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
.post-content {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.post-content pre,
|
||||
.post-content .codeText,
|
||||
.post-content ol[class^="dp-"] {
|
||||
background: #f4f4f4;
|
||||
border: 1px solid #ddd;
|
||||
border-left: 3px solid #b3cbe1;
|
||||
padding: 10px;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
margin: 10px 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content code {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
.post-content pre code {
|
||||
display: block;
|
||||
background: none;
|
||||
padding: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content .codeText li {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.post-content blockquote {
|
||||
border-left: 4px solid #1c62b0;
|
||||
padding-left: 12px;
|
||||
color: #666;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.post-content h1,
|
||||
.post-content h2,
|
||||
.post-content h3 {
|
||||
margin: 14px 0 6px;
|
||||
}
|
||||
|
||||
.post-cover { margin: -18px -20px 12px; }
|
||||
.post-cover img { width: 100%; display: block; max-height: 320px; object-fit: cover; }
|
||||
.post-cover {
|
||||
margin: -18px -20px 12px;
|
||||
}
|
||||
.post-cover img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
max-height: 320px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.comment { padding: 10px 0; border-bottom: 1px dashed #ddd; }
|
||||
.comment-head { font-size: 12px; margin-bottom: 3px; }
|
||||
.comment-time { color: #999; }
|
||||
.comment-body { font-size: 13px; }
|
||||
.comment-form { margin-top: 12px; }
|
||||
.form-row { margin-bottom: 8px; }
|
||||
.form-row label { display: block; font-size: 12px; margin-bottom: 3px; }
|
||||
.form-row input, .form-row textarea { width: 100%; padding: 6px 8px; border: 1px solid #ccc; font-size: 13px; font-family: inherit; }
|
||||
.honeypot { position: absolute; left: -9999px; opacity: 0; }
|
||||
.btn { background: #1c62b0; color: #fff; border: none; padding: 7px 18px; font-size: 13px; cursor: pointer; }
|
||||
.btn:hover { background: #f60; }
|
||||
.error { color: #c00; font-size: 12px; }
|
||||
.alert { padding: 8px 12px; margin-bottom: 12px; border: 1px solid; }
|
||||
.alert-success { background: #e8f5e9; border-color: #a5d6a7; color: #2e7d32; }
|
||||
.alert-error { background: #fdecea; border-color: #f5c6cb; color: #c62828; }
|
||||
.empty { color: #999; text-align: center; padding: 20px 0; }
|
||||
.list-title { font-size: 18px; margin-bottom: 12px; color: #1c62b0; }
|
||||
.comment {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px dashed #ddd;
|
||||
}
|
||||
.comment-head {
|
||||
font-size: 12px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.comment-time {
|
||||
color: #999;
|
||||
}
|
||||
.comment-body {
|
||||
font-size: 13px;
|
||||
}
|
||||
.comment-form {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.form-row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.form-row label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.form-row input,
|
||||
.form-row textarea {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.honeypot {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
opacity: 0;
|
||||
}
|
||||
.btn {
|
||||
background: #1c62b0;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 7px 18px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn:hover {
|
||||
background: #f60;
|
||||
}
|
||||
.error {
|
||||
color: #c00;
|
||||
font-size: 12px;
|
||||
}
|
||||
.alert {
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid;
|
||||
}
|
||||
.alert-success {
|
||||
background: #e8f5e9;
|
||||
border-color: #a5d6a7;
|
||||
color: #2e7d32;
|
||||
}
|
||||
.alert-error {
|
||||
background: #fdecea;
|
||||
border-color: #f5c6cb;
|
||||
color: #c62828;
|
||||
}
|
||||
.alert-warning {
|
||||
background: #fff8e1;
|
||||
border-color: #ffe082;
|
||||
color: #8a6d00;
|
||||
}
|
||||
.empty {
|
||||
color: #999;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.list-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 12px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
|
||||
.view-mode-switcher {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #d8dfe6;
|
||||
}
|
||||
.view-mode-switcher a.active {
|
||||
font-weight: 700;
|
||||
color: #1c62b0;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar { width: 280px; flex-shrink: 0; }
|
||||
.sidebar .searchbox { background: #fff; border: 1px solid #d8dfe6; padding: 10px; margin-bottom: 14px; }
|
||||
.sidebar .searchbox input[type=search] { width: 180px; padding: 4px; border: 1px solid #ccc; }
|
||||
.sidebar .widget { background: #fff; border: 1px solid #d8dfe6; margin-bottom: 14px; }
|
||||
.sidebar .widget h3 { background: #f7f9fb; border-bottom: 1px solid #d8dfe6; font-size: 13px; padding: 7px 12px; color: #1c62b0; }
|
||||
.sidebar .widget ul { list-style: none; padding: 6px 12px; }
|
||||
.sidebar .widget li { padding: 3px 0; font-size: 12px; border-bottom: 1px dotted #eee; }
|
||||
.sidebar .tagcloud { padding: 8px 12px; }
|
||||
.sidebar .tagcloud a { color: #1c62b0; margin-right: 6px; }
|
||||
.sidebar {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.sidebar .searchbox {
|
||||
background: #fff;
|
||||
border: 1px solid #d8dfe6;
|
||||
padding: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.sidebar .searchbox input[type="search"] {
|
||||
width: 180px;
|
||||
padding: 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.sidebar .widget {
|
||||
background: #fff;
|
||||
border: 1px solid #d8dfe6;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.sidebar .widget h3 {
|
||||
background: #f7f9fb;
|
||||
border-bottom: 1px solid #d8dfe6;
|
||||
font-size: 13px;
|
||||
padding: 7px 12px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
.sidebar .widget ul {
|
||||
list-style: none;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
.sidebar .widget li {
|
||||
padding: 3px 0;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px dotted #eee;
|
||||
}
|
||||
.sidebar .tagcloud {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.sidebar .tagcloud a {
|
||||
color: #1c62b0;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* 兜底页面(归档/标签云/友链/评论/登录等使用默认布局) */
|
||||
.container.main-layout { max-width: 960px; margin: 0 auto; }
|
||||
.main-layout { display: flex; gap: 18px; align-items: flex-start; padding: 14px 10px; }
|
||||
.main-layout .content { flex: 1; min-width: 0; }
|
||||
.main-layout .sidebar { width: 280px; }
|
||||
.container.main-layout {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 280px;
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
padding: 14px 10px;
|
||||
}
|
||||
.main-layout .content {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.main-layout .sidebar {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pagination-links { display: flex; gap: 6px; flex-wrap: wrap; margin: 12px 0; }
|
||||
.pagination-links a, .pagination-links .current, .pagination-links .disabled { padding: 4px 10px; border: 1px solid #d8dfe6; background: #fff; font-size: 12px; }
|
||||
.pagination-links .current { background: #1c62b0; color: #fff; }
|
||||
.pagination {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.pagination-links {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.pagination-info {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
margin-right: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pagination-links a,
|
||||
.pagination-links .current,
|
||||
.pagination-links .disabled {
|
||||
padding: 2px 7px;
|
||||
border: 1px solid #d8dfe6;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.pagination-links .current {
|
||||
background: #1c62b0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#footer { clear: both; text-align: center; padding: 14px 0 24px; color: #888; font-size: 12px; }
|
||||
#footer a { color: #1c62b0; }
|
||||
#footer {
|
||||
clear: both;
|
||||
text-align: center;
|
||||
padding: 14px 0 24px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
#footer a {
|
||||
color: #1c62b0;
|
||||
}
|
||||
|
||||
.archive-year { font-size: 15px; margin: 12px 0 4px; color: #1c62b0; }
|
||||
.archive-list { list-style: none; }
|
||||
.archive-posts { margin: 4px 0 10px 20px; }
|
||||
.archive-year {
|
||||
font-size: 15px;
|
||||
margin: 12px 0 4px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
.archive-list {
|
||||
list-style: none;
|
||||
}
|
||||
.archive-posts {
|
||||
margin: 4px 0 10px 20px;
|
||||
}
|
||||
|
||||
.links-list { list-style: none; }
|
||||
.links-list li { padding: 5px 0; }
|
||||
.link-note { color: #999; }
|
||||
.links-list {
|
||||
list-style: none;
|
||||
}
|
||||
.links-list li {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.link-note {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.tag-cloud.big { line-height: 2.4; }
|
||||
.tag-cloud.big {
|
||||
line-height: 2.4;
|
||||
}
|
||||
|
||||
.auth-layout { display: flex; justify-content: center; padding: 30px 10px; }
|
||||
.auth-box { width: 100%; max-width: 420px; }
|
||||
.auth-alt { margin-top: 12px; font-size: 12px; }
|
||||
.search-form { margin-bottom: 12px; }
|
||||
.search-form input { padding: 5px; border: 1px solid #ccc; width: 220px; }
|
||||
.auth-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 30px 10px;
|
||||
}
|
||||
.auth-box {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
}
|
||||
.auth-alt {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.search-form {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.search-form input {
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.inline-form { display: inline; }
|
||||
.link-btn { background: none; border: none; color: #1c62b0; cursor: pointer; font-size: 13px; padding: 0; font-weight: bold; }
|
||||
.inline-form {
|
||||
display: inline;
|
||||
}
|
||||
.link-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #1c62b0;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* ============ 移动端适配 ============ */
|
||||
@media (max-width: 768px) {
|
||||
#outmain { width: 100%; }
|
||||
#header { padding: 14px 10px; }
|
||||
.logo a { font-size: 24px; }
|
||||
#nav ul { flex-wrap: wrap; }
|
||||
#nav a { padding: 8px 12px; font-size: 13px; }
|
||||
#page { flex-direction: column; }
|
||||
#content { width: 100%; }
|
||||
.sidebar { width: 100%; }
|
||||
.main-layout { flex-direction: column; }
|
||||
.main-layout .content { width: 100%; }
|
||||
.main-layout .sidebar { width: 100%; }
|
||||
.sidebar .searchbox input[type=search] { width: 70%; }
|
||||
.post-card, .post-full, .comments-section { padding: 14px 16px; }
|
||||
.post-cover { margin: -14px -16px 12px; }
|
||||
.post-full .post-title { font-size: 22px; }
|
||||
.archive-posts { margin-left: 10px; }
|
||||
#outmain {
|
||||
width: 100%;
|
||||
}
|
||||
#header {
|
||||
padding: 14px 10px;
|
||||
}
|
||||
.logo a {
|
||||
font-size: 24px;
|
||||
}
|
||||
#nav ul {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#nav a {
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
#page {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
#content {
|
||||
width: 100%;
|
||||
}
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
.main-layout {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.main-layout .content {
|
||||
width: 100%;
|
||||
}
|
||||
.main-layout .sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
.sidebar .searchbox input[type="search"] {
|
||||
width: 70%;
|
||||
}
|
||||
.post-card,
|
||||
.post-full,
|
||||
.comments-section {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
.post-cover {
|
||||
margin: -14px -16px 12px;
|
||||
}
|
||||
.post-full .post-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
.archive-posts {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* TOC */
|
||||
.toc { background: #f7f9fb; border: 1px solid #d8dfe6; padding: 10px 14px; margin: 0 0 14px; font-size: 13px; }
|
||||
.toc summary { cursor: pointer; font-weight: bold; margin-bottom: 4px; }
|
||||
.toc-list { list-style: none; }
|
||||
.toc-list li { padding: 2px 0; }
|
||||
.toc-level-3 { padding-left: 14px; }
|
||||
.toc-level-4, .toc-level-5, .toc-level-6 { padding-left: 28px; }
|
||||
.toc {
|
||||
background: #f7f9fb;
|
||||
border: 1px solid #d8dfe6;
|
||||
padding: 10px 14px;
|
||||
margin: 0 0 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.toc summary {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
}
|
||||
.toc-list li {
|
||||
padding: 2px 0;
|
||||
}
|
||||
.toc-level-3 {
|
||||
padding-left: 14px;
|
||||
}
|
||||
.toc-level-4,
|
||||
.toc-level-5,
|
||||
.toc-level-6 {
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Paywall */
|
||||
.paywall { border: 1px solid #e0e0e0; border-radius: 8px; padding: 24px 20px; background: #fafafa; text-align: center; }
|
||||
.paywall-cover img { max-width: 100%; border-radius: 6px; margin-bottom: 14px; }
|
||||
.paywall-title { font-size: 20px; margin: 0 0 10px; }
|
||||
.paywall-excerpt { color: #666; font-size: 14px; margin: 0 0 14px; }
|
||||
.paywall-price { font-size: 22px; font-weight: 800; color: #f60; margin: 0 0 8px; }
|
||||
.paywall-hint { color: #888; font-size: 13px; margin: 0 0 16px; }
|
||||
.paywall-actions .btn { margin: 0 6px; }
|
||||
.paid-teaser { border: 1px dashed #f5c6cb; background: #fdecea; color: #c62828; border-radius: 6px; padding: 12px 14px; margin: 12px 0; font-size: 14px; }
|
||||
.paid-teaser .btn { margin-left: 8px; }
|
||||
.paywall {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 24px 20px;
|
||||
background: #fafafa;
|
||||
text-align: center;
|
||||
}
|
||||
.paywall-cover img {
|
||||
max-width: 100%;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.paywall-title {
|
||||
font-size: 20px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.paywall-excerpt {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
.paywall-price {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #f60;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.paywall-hint {
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.paywall-actions .btn {
|
||||
margin: 0 6px;
|
||||
}
|
||||
.paid-teaser {
|
||||
border: 1px dashed #f5c6cb;
|
||||
background: #fdecea;
|
||||
color: #c62828;
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
margin: 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.paid-teaser .btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 阅读全文链接 */
|
||||
.read-more { display: inline-block; margin-top: 8px; font-size: 13px; color: #1c62b0; }
|
||||
.read-more:hover { color: #f60; }
|
||||
.read-more {
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #1c62b0;
|
||||
}
|
||||
.read-more:hover {
|
||||
color: #f60;
|
||||
}
|
||||
|
||||
/* 社交账号:横向胶囊列表 */
|
||||
.social-links { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; }
|
||||
.social-links li { font-size: 12px; background: #f7f9fb; border: 1px solid #d8dfe6; border-radius: 999px; padding: 3px 10px; }
|
||||
.social-links {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.social-links li {
|
||||
font-size: 12px;
|
||||
background: #f7f9fb;
|
||||
border: 1px solid #d8dfe6;
|
||||
border-radius: 999px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
/* 已解锁的付费内容块 */
|
||||
.paid-content { border: 1px dashed #f5c6cb; border-left: 4px solid #f60; background: #fff8f0; border-radius: 6px; padding: 12px 14px; margin: 12px 0; }
|
||||
.paid-content-label { font-size: 12px; color: #f60; font-weight: 700; margin-bottom: 6px; }
|
||||
.paid-content {
|
||||
border: 1px dashed #f5c6cb;
|
||||
border-left: 4px solid #f60;
|
||||
background: #fff8f0;
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.paid-content-label {
|
||||
font-size: 12px;
|
||||
color: #f60;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 分类页介绍区 */
|
||||
.category-intro { display: flex; gap: 14px; align-items: flex-start; background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; padding: 14px 16px; margin-bottom: 16px; }
|
||||
.category-cover { width: 120px; height: 80px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
|
||||
.category-description { margin: 0 0 6px; font-size: 14px; color: #555; line-height: 1.7; }
|
||||
.category-meta { margin: 0; font-size: 12px; color: #999; }
|
||||
.category-intro {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
background: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.category-cover {
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.category-description {
|
||||
margin: 0 0 6px;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.category-meta {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
<div class="alert alert-error">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
@include('partials.view-mode-switcher')
|
||||
|
||||
@forelse($posts as $post)
|
||||
@include('partials.post-card', ['post' => $post])
|
||||
@empty
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
@endif
|
||||
|
||||
|
||||
@include('partials.view-mode-switcher')
|
||||
|
||||
@forelse($posts as $post)
|
||||
@include('partials.post-card', ['post' => $post])
|
||||
@empty
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<p>© {{ date('Y') }} {{ $siteName }}
|
||||
@if($siteIcp)<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">{{ $siteIcp }}</a>@endif
|
||||
</p>
|
||||
<p>@include('partials.feed-links')</p>
|
||||
<p>Powered by LaraLog · Laravel · Filament</p>
|
||||
</footer>
|
||||
</div>{{-- #outmain --}}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<meta name="generator" content="LaraLog">
|
||||
<link rel="canonical" href="{{ url()->current() }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ $siteName }}" href="{{ route('feed.rss') }}">
|
||||
<link rel="alternate" type="text/plain" title="llms.txt" href="{{ route('feed.llms') }}">
|
||||
<link rel="stylesheet" href="{{ theme('asset', 'style.css') }}">
|
||||
@stack('head')
|
||||
@stack('theme:head')
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<li class="{{ request()->routeIs('tags*') ? 'current_page_item' : '' }}"><a href="{{ route('tags') }}">标签</a></li>
|
||||
<li class="{{ request()->routeIs('links') ? 'current_page_item' : '' }}"><a href="{{ route('links') }}">友链</a></li>
|
||||
<li class="{{ request()->routeIs('comments') ? 'current_page_item' : '' }}"><a href="{{ route('comments') }}">评论</a></li>
|
||||
<li><a href="{{ route('feed.rss') }}">RSS</a></li>
|
||||
@include('partials.feed-links', ['asList' => true])
|
||||
@auth
|
||||
<li><a href="{{ route('profile') }}">{{ auth()->user()->name }}</a></li>
|
||||
<li>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
<button type="submit" class="btn">搜索</button>
|
||||
</form>
|
||||
|
||||
@include('partials.view-mode-switcher')
|
||||
|
||||
@forelse($posts as $post)
|
||||
@include('partials.post-card', ['post' => $post])
|
||||
@empty
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
@if(session('error'))
|
||||
<div class="alert alert-error">{{ session('error') }}</div>
|
||||
@endif
|
||||
@include('partials.post-preview-banner')
|
||||
|
||||
<article class="post-full">
|
||||
@if(! empty($toc))
|
||||
@@ -35,7 +36,11 @@
|
||||
@endif
|
||||
<h1 class="post-title">{{ $post->title }}</h1>
|
||||
<div class="post-meta">
|
||||
<time datetime="{{ $post->published_at->toIso8601String() }}">{{ $post->published_at->format(blog_setting('normaltime', 'Y-m-d H:i')) }}</time>
|
||||
@if($post->published_at)
|
||||
<time datetime="{{ $post->published_at->toIso8601String() }}">{{ $post->published_at->format(blog_setting('normaltime', 'Y-m-d H:i')) }}</time>
|
||||
@else
|
||||
<span>未发布</span>
|
||||
@endif
|
||||
@if($post->author)<span>· {{ $post->author->name }}</span>@endif
|
||||
@if($post->category)
|
||||
<span>· <a href="{{ route('category.show', $post->category->id) }}">{{ $post->category->name }}</a></span>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<main id="content">
|
||||
<h1 class="list-title">标签:{{ $tag->name }}</h1>
|
||||
|
||||
@include('partials.view-mode-switcher')
|
||||
|
||||
@forelse($posts as $post)
|
||||
@include('partials.post-card', ['post' => $post])
|
||||
@empty
|
||||
|
||||
Reference in New Issue
Block a user