fix: restyle pager chips and restore theme ad slots
Build and Deploy / verify (push) Successful in 37s
Build and Deploy / deploy (push) Successful in 38s

Replace the loose page-count label with a SaBlog-style 6 / 316 chip, wire inject/stylevar HTML into list and post views, and flush page cache when ads are saved.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
gouki
2026-09-18 21:48:24 +00:00
co-authored by Cursor
parent ae47447062
commit 36c4ffc5b4
41 changed files with 936 additions and 81 deletions
+3
View File
@@ -2,6 +2,9 @@
## 2026-09-18 ## 2026-09-18
- Pager chip is now `6 / 316` in the old SaBlog bar style, not a loose “第 x / y 页” label.
- Theme inject stacks cover list/content/footer; stylevars and leftover HTML ads render. Saving inject flushes page cache. `sablog:import` copies `stylevars`.
- Schema snapshot: `docs/database/mysql-schema-20260918-214145.sql`.
- Homepage / lists have SaBlog-style **标准 | 列表** browse modes (`home_view_mode`, `?view=`, cookie). - 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. - 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 `&nbsp;`; highlighter / stacked docker `<div>` blocks become wrapped `<pre><code>`. - Imported SaBlog HTML no longer shows literal `&nbsp;`; highlighter / stacked docker `<div>` blocks become wrapped `<pre><code>`.
+8 -1
View File
@@ -61,11 +61,18 @@ See `README.md` and `docs/` for install, import, plugins, and themes.
## Pagination and imported HTML ## 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. - Public pagers use `onEachSide(1)` plus a compact `6 / 316` chip (old SaBlog `.p_bar` look), and the main column is CSS grid `minmax(0, 1fr)` so page chips stay in the content column.
- `SablogHtmlNormalizer` strips leftover `&nbsp;`, turns SyntaxHighlighter `codeText` / `dp-*` lists into `<pre><code>`, and wraps stacked docker/shell `<div>` lines. - `SablogHtmlNormalizer` strips leftover `&nbsp;`, 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`. - 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. - Code blocks wrap / scroll inside the column instead of stretching the page.
## Theme ads / inject
- All list and post views include `content_top` / `content_bottom` (and post body) stacks. Saving **主题注入** flushes page cache.
- Theme variables (old stylevars) render automatically: `ad_header` → header, leftover HTML vars such as `huangjinlian` → footer.
- `sablog:import` now copies the old `stylevars` table into `theme_vars_{theme}`.
- Do not add a third half-finished skin. Tighten `sablog` to match the old site; a new theme is a later, complete job.
## Media library (xpier) ## Media library (xpier)
- Package: `xpier/filament-media-library` (admin Media Library + MediaPicker). - Package: `xpier/filament-media-library` (admin Media Library + MediaPicker).
+1 -1
View File
@@ -67,7 +67,7 @@ php artisan sablog:fix-content --all
S3_ACCESS_KEY=... S3_BUCKET=... S3_ENDPOINT=... php artisan attachments:sync-s3 --legacy-dir=/path/to/old/attachments S3_ACCESS_KEY=... S3_BUCKET=... S3_ENDPOINT=... php artisan attachments:sync-s3 --legacy-dir=/path/to/old/attachments
``` ```
> 不迁移的过时功能:trackback(引用通告,垃圾来源)、搜索缓存表、验证码(由 AI 审核 + 蜜罐 + 频控取代)、WAP、广告位变量。 > 不迁移的过时功能:trackback(引用通告,垃圾来源)、搜索缓存表、验证码(由 AI 审核 + 蜜罐 + 频控取代)、WAP。老广告位 `stylevars` 会导入为当前主题变量并在对应注入点显示。
## 主题 ## 主题
+63
View File
@@ -34,6 +34,24 @@ class ThemeInjector
'body_end' => '页面末尾(</body> 前)', 'body_end' => '页面末尾(</body> 前)',
]; ];
/**
* Old SaBlog stylevar names → inject points. Unmapped HTML vars go to footer.
*
* @var array<string, list<string>>
*/
public const STYLEVAR_POINTS = [
'head' => ['ad_head', 'headad'],
'header' => ['headerad', 'header_ad', 'ad_header', 'topad', 'ad_top', 'sitead', 'site_ad'],
'sidebar_top' => ['sidebarad', 'sidebar_ad', 'ad_sidebar', 'leftad', 'ad_left'],
'sidebar_bottom' => ['ad_sidebar_bottom'],
'content_top' => ['indexad', 'ad_index', 'contentad', 'ad_content'],
'content_bottom' => ['ad_content_bottom'],
'post_content_top' => ['ad_post_top', 'insad'],
'post_content_bottom' => ['ad_post_bottom'],
'footer' => ['footerad', 'footer_ad', 'ad_footer', 'huangjinlian', 'huangjinlian2', 'huangjinlian3'],
'body_end' => ['ad_body_end'],
];
/** /**
* 读取某一注入点的所有注入块(多条,已排序、已过滤停用), * 读取某一注入点的所有注入块(多条,已排序、已过滤停用),
* 并把插件追加的 HTML 合并进来。 * 并把插件追加的 HTML 合并进来。
@@ -51,6 +69,10 @@ class ThemeInjector
->values() ->values()
->all(); ->all();
foreach (self::stylevarBlocks($point) as $html) {
$blocks[] = $html;
}
$extra = (string) app(PluginManager::class)->applyFilters("theme.inject.{$point}", ''); $extra = (string) app(PluginManager::class)->applyFilters("theme.inject.{$point}", '');
if ($extra !== '') { if ($extra !== '') {
@@ -75,4 +97,45 @@ class ThemeInjector
} }
}); });
} }
/**
* @return list<string>
*/
private static function stylevarBlocks(string $point): array
{
$vars = app(ThemeManager::class)->variables();
if ($vars === []) {
return [];
}
$html = [];
foreach (self::STYLEVAR_POINTS[$point] ?? [] as $key) {
$value = trim((string) ($vars[$key] ?? ''));
if ($value !== '') {
$html[] = $value;
}
}
if ($point !== 'footer') {
return $html;
}
$mapped = [];
foreach (self::STYLEVAR_POINTS as $keys) {
foreach ($keys as $key) {
$mapped[$key] = true;
}
}
foreach ($vars as $key => $value) {
$value = trim((string) $value);
if ($value === '' || isset($mapped[$key]) || ! str_contains($value, '<')) {
continue;
}
$html[] = $value;
}
return $html;
}
} }
+30 -1
View File
@@ -59,6 +59,7 @@ class SablogImport extends Command
} }
$this->importSettings(); $this->importSettings();
$this->importStylevars();
$this->importCategories(); $this->importCategories();
$this->importUsers(); $this->importUsers();
$this->importPosts(); $this->importPosts();
@@ -144,7 +145,7 @@ class SablogImport extends Command
* searchindex —— 搜索缓存,现代 SQL 全文检索足够 * searchindex —— 搜索缓存,现代 SQL 全文检索足够
* sessions —— Laravel 原生会话 * sessions —— Laravel 原生会话
* statistics —— 实时统计 * statistics —— 实时统计
* stylevars —— 老式广告位变量 * stylevars —— 导入为当前主题的 theme_vars(广告位)
* seccode / 验证码 —— 由 AI 审核 + 蜜罐 + 频控取代 * seccode / 验证码 —— 由 AI 审核 + 蜜罐 + 频控取代
*/ */
private function importSettings(): void private function importSettings(): void
@@ -182,6 +183,34 @@ class SablogImport extends Command
$this->report['settings'] = $count; $this->report['settings'] = $count;
} }
private function importStylevars(): void
{
if (! $this->hasTable('stylevars')) {
return;
}
$vars = [];
foreach ($this->rows('stylevars') as $row) {
$name = (string) ($row['title'] ?? $row['variable'] ?? $row['name'] ?? '');
$value = (string) ($row['value'] ?? $row['content'] ?? '');
if ($name === '' || $value === '') {
continue;
}
$vars[$name] = $value;
}
if ($vars === []) {
return;
}
$theme = (string) Setting::get('active_theme', 'sablog');
$existing = Setting::get('theme_vars_'.$theme, []);
$existing = is_array($existing) ? $existing : (json_decode((string) $existing, true) ?: []);
Setting::set('theme_vars_'.$theme, array_merge($existing, $vars));
$this->report['stylevars'] = count($vars);
}
private function importCategories(): void private function importCategories(): void
{ {
$count = 0; $count = 0;
+1
View File
@@ -119,6 +119,7 @@ class ThemeInject extends Page
Setting::set('inject.blocks', $blocks); Setting::set('inject.blocks', $blocks);
Setting::set('theme_vars_'.app(ThemeManager::class)->active(), $data['theme_vars'] ?? []); Setting::set('theme_vars_'.app(ThemeManager::class)->active(), $data['theme_vars'] ?? []);
\App\Blog\Support\PageCacheMiddleware::flush();
Notification::make()->title('注入内容已保存')->success()->send(); Notification::make()->title('注入内容已保存')->success()->send();
} }
@@ -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);
+1
View File
@@ -83,6 +83,7 @@ routes/web.php # 前台规范路由;routes/legacy.php 老 URL 301(新增
- 前台 RSS / Sitemap / llms.txt 开新窗口。`/llms.txt` 和 `/posts/{slug}.md` 是核心路由,不要做成静态文件,新文章要马上出现 - 前台 RSS / Sitemap / llms.txt 开新窗口。`/llms.txt` 和 `/posts/{slug}.md` 是核心路由,不要做成静态文件,新文章要马上出现
- 首页浏览模式:`home_view_mode` + `?view=list|full` + cookie `ll_home_view`。分页用 `blog_paginate()`(`onEachSide(1)`) - 首页浏览模式:`home_view_mode` + `?view=list|full` + cookie `ll_home_view`。分页用 `blog_paginate()`(`onEachSide(1)`)
- 老 sablog 正文清洗放 `SablogHtmlNormalizer`,导入 / 渲染 / `sablog:fix-content` 共用,不要各写一套 strip_tags - 老 sablog 正文清洗放 `SablogHtmlNormalizer`,导入 / 渲染 / `sablog:fix-content` 共用,不要各写一套 strip_tags
- 广告位用 `partials.theme-inject`。保存主题注入要清页面缓存。stylevars 跟 `theme_vars_{theme}` 走,不要再忽略这张表
## 9. 常用命令速查 ## 9. 常用命令速查
+1
View File
@@ -47,6 +47,7 @@ php artisan sablog:import \
| `links` | `links` | linkid 保留 | | `links` | `links` | linkid 保留 |
| `attachments` | medialibrary `media` | 文件 + `legacy_attachmentid` 映射(老 `[attach=xx]` 按此解析) | | `attachments` | medialibrary `media` | 文件 + `legacy_attachmentid` 映射(老 `[attach=xx]` 按此解析) |
| `settings` | `settings` | 键值映射为站点设置(可在后台「博客设置」继续改) | | `settings` | `settings` | 键值映射为站点设置(可在后台「博客设置」继续改) |
| `stylevars` | `theme_vars_{theme}` | 老广告位变量,按位置打到主题注入点 |
| `tags` | spatie tags | 老 aids 文本解析为独立标签 + 关联 | | `tags` | spatie tags | 老 aids 文本解析为独立标签 + 关联 |
**不迁移**:trackback/引用通告(垃圾来源,接口已关闭)、搜索缓存表、验证码(由 AI 审核 + 蜜罐 + 频控取代)。 **不迁移**:trackback/引用通告(垃圾来源,接口已关闭)、搜索缓存表、验证码(由 AI 审核 + 蜜罐 + 频控取代)。
+41 -29
View File
@@ -23,7 +23,18 @@ themes/{name}/
"description": "主题描述", "description": "主题描述",
"author": "作者", "author": "作者",
"screenshot": "screenshot.png", "screenshot": "screenshot.png",
"inject_points": ["head", "header", "sidebar_top", "sidebar_bottom", "content_top", "content_bottom", "post_content_top", "post_content_bottom", "footer", "body_end"] "inject_points": [
"head",
"header",
"sidebar_top",
"sidebar_bottom",
"content_top",
"content_bottom",
"post_content_top",
"post_content_bottom",
"footer",
"body_end"
]
} }
``` ```
@@ -69,21 +80,21 @@ themes/{name}/
## 页面视图清单(控制器会传的数据) ## 页面视图清单(控制器会传的数据)
| 视图 | 控制器 | 变量 | | 视图 | 控制器 | 变量 |
|------|--------|------| | ----------------------------------------------- | ------------------ | ------------------------------------- |
| `index` | HomeController | `$posts`(分页) | | `index` | HomeController | `$posts`(分页) |
| `show` | PostController | `$post, $comments, $contentHtml` | | `show` | PostController | `$post, $comments, $contentHtml` |
| `list` | Category/Archive | `$posts, $category? / $archiveTitle?` | | `list` | Category/Archive | `$posts, $category? / $archiveTitle?` |
| `archives` | ArchiveController | `$archives` | | `archives` | ArchiveController | `$archives` |
| `tags` | TagController | `$tags` | | `tags` | TagController | `$tags` |
| `tag` | TagController | `$tag, $posts` | | `tag` | TagController | `$tag, $posts` |
| `search` | SearchController | `$posts, $keyword` | | `search` | SearchController | `$posts, $keyword` |
| `links` | LinkController | `$links` | | `links` | LinkController | `$links` |
| `comments` | CommentController | `$comments` | | `comments` | CommentController | `$comments` |
| `login` / `register` / `profile` | AuthController | — | | `login` / `register` / `profile` | AuthController | — |
| `membership.index` / `membership.mine` | 会员插件 | `$plans / $subscriptions` | | `membership.index` / `membership.mine` | 会员插件 | `$plans / $subscriptions` |
| `membership.paywall` / `membership.paid-teaser` | 会员插件(付费墙) | `$post` | | `membership.paywall` / `membership.paid-teaser` | 会员插件(付费墙) | `$post` |
| `payments.sandbox` / `payments.result` | 支付插件 | `$payment` | | `payments.sandbox` / `payments.result` | 支付插件 | `$payment` |
## 付费墙(paywall)覆盖 ## 付费墙(paywall)覆盖
@@ -115,25 +126,26 @@ themes/{name}/
主题视图内置 10 个注入点(Blade `@stack`),后台「主题注入」页管理: 主题视图内置 10 个注入点(Blade `@stack`),后台「主题注入」页管理:
| 注入点 | 位置 | 典型用途 | | 注入点 | 位置 | 典型用途 |
|--------|------|----------| | --------------------------- | ------------ | -------------------- |
| `theme:head` | `</head>` 前 | 统计代码、广告、验证 | | `theme:head` | `</head>` 前 | 统计代码、广告、验证 |
| `theme:header` | 页头下方 | 顶部横幅/公告 | | `theme:header` | 页头下方 | 顶部横幅/公告 |
| `theme:sidebar_top` | 侧边栏顶部 | 首屏广告 | | `theme:sidebar_top` | 侧边栏顶部 | 首屏广告 |
| `theme:sidebar_bottom` | 侧边栏底部 | 广告/临时链接 | | `theme:sidebar_bottom` | 侧边栏底部 | 广告/临时链接 |
| `theme:content_top` | 内容区顶部 | 列表/文章上方横幅 | | `theme:content_top` | 内容区顶部 | 列表/文章上方横幅 |
| `theme:content_bottom` | 内容区底部 | 分页后广告 | | `theme:content_bottom` | 内容区底部 | 分页后广告 |
| `theme:post_content_top` | 文章正文前 | 正文顶部广告 | | `theme:post_content_top` | 文章正文前 | 正文顶部广告 |
| `theme:post_content_bottom` | 文章正文后 | 正文底部广告 | | `theme:post_content_bottom` | 文章正文后 | 正文底部广告 |
| `theme:footer` | 页脚前 | 版权上方声明 | | `theme:footer` | 页脚前 | 版权上方声明 |
| `theme:body_end` | `</body>` 前 | 统计代码 | | `theme:body_end` | `</body>` 前 | 统计代码 |
特性: 特性:
- **同一位置可添加多条**注入块(名称 + HTML + 排序 + 启停),按排序渲染 - **同一位置可添加多条**注入块(名称 + HTML + 排序 + 启停),按排序渲染
- 插件可通过 filter `theme.inject.{point}` 追加 - 插件可通过 filter `theme.inject.{point}` 追加
- 主题变量(stylevar 复活):后台「主题注入」→「主题变量」Tab,视图用 `{{ theme('var', 'key') }}` 引用任意 HTML - 主题变量(stylevar 复活):后台「主题注入」→「主题变量」Tab,视图用 `{{ theme('var', 'key') }}` 引用任意 HTML
新主题须在对应 partial/视图放置 `@stack('theme:xxx')` 才能接收注入。 新主题须在对应 partial/视图放置 `@include('partials.theme-inject', ['point' => 'xxx'])`(或 `@stack('theme:xxx')`)才能接收注入。主题变量里的 `ad_header` 等会自动打到对应位置,未映射且含 HTML 的变量落在页脚。保存「主题注入」会清前台整页缓存。
**声明注入点**:`theme.json` 的 `inject_points` 声明主题实现了哪些注入点(缺省视为全部支持),后台主题卡片会展示,切换主题时一目了然。 **声明注入点**:`theme.json` 的 `inject_points` 声明主题实现了哪些注入点(缺省视为全部支持),后台主题卡片会展示,切换主题时一目了然。
+21 -4
View File
@@ -444,18 +444,22 @@ a:hover {
max-width: 100%; max-width: 100%;
} }
.pagination-info { .pagination-info {
color: #888; background: #f5f7fb;
border: 1px solid #d5dbe6;
border-radius: 6px;
color: #556;
font-size: 13px; font-size: 13px;
margin-right: 6px; padding: 6px 10px;
margin-right: 4px;
white-space: nowrap; white-space: nowrap;
} }
.pagination-links a, .pagination-links a,
.pagination-links .current, .pagination-links .current,
.pagination-links .disabled { .pagination-links .disabled {
padding: 6px 12px; padding: 6px 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 13px;
background: #fff; background: #fff;
} }
.pagination-links .current { .pagination-links .current {
@@ -466,6 +470,19 @@ a:hover {
.pagination-links .disabled { .pagination-links .disabled {
color: #bbb; color: #bbb;
} }
.theme-inject {
margin: 10px 0;
max-width: 100%;
overflow: hidden;
}
.theme-inject:empty {
display: none;
}
.theme-inject img,
.theme-inject iframe {
max-width: 100%;
height: auto;
}
.auth-layout { .auth-layout {
display: flex; display: flex;
+20 -3
View File
@@ -366,18 +366,22 @@ a:hover {
max-width: 100%; max-width: 100%;
} }
.pagination-info { .pagination-info {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--muted); color: var(--muted);
font-size: 13px; font-size: 13px;
margin-right: 6px; padding: 6px 10px;
margin-right: 4px;
white-space: nowrap; white-space: nowrap;
} }
.pagination-links a, .pagination-links a,
.pagination-links .current, .pagination-links .current,
.pagination-links .disabled { .pagination-links .disabled {
padding: 6px 14px; padding: 6px 10px;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 13px;
background: var(--card); background: var(--card);
color: var(--text); color: var(--text);
} }
@@ -389,6 +393,19 @@ a:hover {
.pagination-links .disabled { .pagination-links .disabled {
color: var(--muted); color: var(--muted);
} }
.theme-inject {
margin: 12px 0;
max-width: 100%;
overflow: hidden;
}
.theme-inject:empty {
display: none;
}
.theme-inject img,
.theme-inject iframe {
max-width: 100%;
height: auto;
}
.m-footer { .m-footer {
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
+38 -9
View File
@@ -370,34 +370,63 @@ a:hover {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
min-width: 0; min-width: 0;
margin: 12px 0; margin: 14px 0;
clear: both;
} }
.pagination-links { .pagination-links {
display: flex; display: flex;
gap: 4px; gap: 1px;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
max-width: 100%; max-width: 100%;
margin: 12px 0; margin: 0;
} }
.pagination-info { .pagination-info {
color: #888; background: #f5fbff;
border: 1px solid #86b9d6;
color: #336;
font-size: 12px; font-size: 12px;
margin-right: 6px; padding: 2px 6px;
margin-right: 4px;
white-space: nowrap; white-space: nowrap;
} }
.pagination-links a, .pagination-links a,
.pagination-links .current, .pagination-links .current,
.pagination-links .disabled { .pagination-links .disabled {
padding: 2px 7px; padding: 2px 6px;
border: 1px solid #d8dfe6; border: 1px solid #dedeb8;
background: #fff; background: #fff;
font-size: 12px; font-size: 12px;
line-height: 1.4; line-height: 1.4;
min-width: 1.6em;
text-align: center;
text-decoration: none;
}
.pagination-links a:hover {
background: #f5fbff;
border-color: #86b9d6;
} }
.pagination-links .current { .pagination-links .current {
background: #1c62b0; background: #ffffd9;
color: #fff; color: #92a05a;
font-weight: 700;
border-color: #dedeb8;
}
.pagination-links .disabled {
color: #999;
}
.theme-inject {
margin: 8px 0;
max-width: 100%;
overflow: hidden;
}
.theme-inject:empty {
display: none;
}
.theme-inject img,
.theme-inject iframe {
max-width: 100%;
height: auto;
} }
#footer { #footer {
+2
View File
@@ -5,6 +5,7 @@
<div class="container main-layout"> <div class="container main-layout">
<main class="content"> <main class="content">
<h1 class="list-title">文章归档</h1> <h1 class="list-title">文章归档</h1>
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($archives as $year => $months) @forelse($archives as $year => $months)
<h2 class="archive-year">{{ $year }} 年</h2> <h2 class="archive-year">{{ $year }} 年</h2>
@@ -24,6 +25,7 @@
@empty @empty
<div class="empty">暂无归档</div> <div class="empty">暂无归档</div>
@endforelse @endforelse
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div> </div>
+2
View File
@@ -12,6 +12,7 @@
@endif @endif
@include('partials.view-mode-switcher') @include('partials.view-mode-switcher')
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($posts as $post) @forelse($posts as $post)
@include('partials.post-card', ['post' => $post]) @include('partials.post-card', ['post' => $post])
@@ -20,6 +21,7 @@
@endforelse @endforelse
@include('partials.pagination', ['paginator' => $posts]) @include('partials.pagination', ['paginator' => $posts])
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div> </div>
+2
View File
@@ -25,6 +25,7 @@
@include('partials.view-mode-switcher') @include('partials.view-mode-switcher')
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($posts as $post) @forelse($posts as $post)
@include('partials.post-card', ['post' => $post]) @include('partials.post-card', ['post' => $post])
@@ -33,6 +34,7 @@
@endforelse @endforelse
@include('partials.pagination', ['paginator' => $posts]) @include('partials.pagination', ['paginator' => $posts])
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div> </div>
+1 -1
View File
@@ -1,4 +1,4 @@
@stack('theme:footer') @include('partials.theme-inject', ['point' => 'footer'])
<footer class="site-footer"> <footer class="site-footer">
<div class="container"> <div class="container">
<p> <p>
+1 -1
View File
@@ -29,4 +29,4 @@
</nav> </nav>
</div> </div>
</header> </header>
@stack('theme:header') @include('partials.theme-inject', ['point' => 'header'])
@@ -1,6 +1,6 @@
@if ($paginator->hasPages()) @if ($paginator->hasPages())
<nav class="pagination-links" role="navigation" aria-label="分页"> <nav class="pagination-links" role="navigation" aria-label="分页">
<span class="pagination-info">第 {{ $paginator->currentPage() }} / {{ $paginator->lastPage() }} 页</span> <span class="pagination-info">{{ $paginator->currentPage() }} / {{ $paginator->lastPage() }}</span>
@if ($paginator->onFirstPage()) @if ($paginator->onFirstPage())
<span class="disabled">&laquo;</span> <span class="disabled">&laquo;</span>
+3 -3
View File
@@ -1,5 +1,5 @@
<aside class="sidebar"> <aside class="sidebar">
@stack('theme:sidebar_top') @include('partials.theme-inject', ['point' => 'sidebar_top'])
<section class="widget"> <section class="widget">
<h3 class="widget-title">分类</h3> <h3 class="widget-title">分类</h3>
<ul class="widget-list"> <ul class="widget-list">
@@ -84,5 +84,5 @@
<li>标签:{{ $blogStats['tags'] }} 个</li> <li>标签:{{ $blogStats['tags'] }} 个</li>
</ul> </ul>
</section> </section>
@stack('theme:sidebar_bottom') @include('partials.theme-inject', ['point' => 'sidebar_bottom'])
 </aside>
@@ -0,0 +1,6 @@
@php
$injected = trim($__env->yieldPushContent('theme:'.$point));
@endphp
@if($injected !== '')
<div class="theme-inject theme-inject-{{ $point }}">{!! $injected !!}</div>
@endif
+2
View File
@@ -11,6 +11,7 @@
</form> </form>
@include('partials.view-mode-switcher') @include('partials.view-mode-switcher')
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($posts as $post) @forelse($posts as $post)
@include('partials.post-card', ['post' => $post]) @include('partials.post-card', ['post' => $post])
@@ -19,6 +20,7 @@
@endforelse @endforelse
@include('partials.pagination', ['paginator' => $posts]) @include('partials.pagination', ['paginator' => $posts])
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div> </div>
+2 -1
View File
@@ -50,10 +50,11 @@
<span>· 阅读 {{ $post->views }}</span> <span>· 阅读 {{ $post->views }}</span>
</div> </div>
@stack('theme:post_content_top') @include('partials.theme-inject', ['point' => 'post_content_top'])
<div class="post-content"> <div class="post-content">
{!! $contentHtml !!} {!! $contentHtml !!}
</div> </div>
@include('partials.theme-inject', ['point' => 'post_content_bottom'])
@if($post->tags->isNotEmpty()) @if($post->tags->isNotEmpty())
<div class="post-tags"> <div class="post-tags">
+2
View File
@@ -7,6 +7,7 @@
<h1 class="list-title">标签:{{ $tag->name }}</h1> <h1 class="list-title">标签:{{ $tag->name }}</h1>
@include('partials.view-mode-switcher') @include('partials.view-mode-switcher')
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($posts as $post) @forelse($posts as $post)
@include('partials.post-card', ['post' => $post]) @include('partials.post-card', ['post' => $post])
@@ -15,6 +16,7 @@
@endforelse @endforelse
@include('partials.pagination', ['paginator' => $posts]) @include('partials.pagination', ['paginator' => $posts])
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div> </div>
+2 -2
View File
@@ -72,7 +72,7 @@ class HomeViewModeTest extends TestCase
$this->get('/') $this->get('/')
->assertOk() ->assertOk()
->assertSee('第 1 /', false) ->assertSee('pagination-info', false)
->assertSee('页', false); ->assertSee('1 /', false);
} }
} }
+3
View File
@@ -56,6 +56,7 @@ class SablogImportTest extends TestCase
$pdo->exec('CREATE TABLE sablog_comments (commentid INTEGER PRIMARY KEY, articleid INTEGER, author TEXT, url TEXT, dateline INTEGER, content TEXT, ipaddress TEXT, visible INTEGER)'); $pdo->exec('CREATE TABLE sablog_comments (commentid INTEGER PRIMARY KEY, articleid INTEGER, author TEXT, url TEXT, dateline INTEGER, content TEXT, ipaddress TEXT, visible INTEGER)');
$pdo->exec('CREATE TABLE sablog_links (linkid INTEGER PRIMARY KEY, displayorder INTEGER, name TEXT, url TEXT, note TEXT, visible INTEGER)'); $pdo->exec('CREATE TABLE sablog_links (linkid INTEGER PRIMARY KEY, displayorder INTEGER, name TEXT, url TEXT, note TEXT, visible INTEGER)');
$pdo->exec('CREATE TABLE sablog_tags (tagid INTEGER PRIMARY KEY, tag TEXT, usenum INTEGER, aids TEXT)'); $pdo->exec('CREATE TABLE sablog_tags (tagid INTEGER PRIMARY KEY, tag TEXT, usenum INTEGER, aids TEXT)');
$pdo->exec('CREATE TABLE sablog_stylevars (stylevarid INTEGER PRIMARY KEY, title TEXT, value TEXT, description TEXT)');
$pdo->exec('CREATE TABLE sablog_settings (title TEXT PRIMARY KEY, value TEXT)'); $pdo->exec('CREATE TABLE sablog_settings (title TEXT PRIMARY KEY, value TEXT)');
$pdo->exec('CREATE TABLE sablog_users (userid INTEGER PRIMARY KEY, username TEXT, password TEXT, logincount INTEGER, loginip TEXT, logintime INTEGER, url TEXT, articles INTEGER, regdateline INTEGER, regip TEXT, groupid INTEGER, lastpost INTEGER)'); $pdo->exec('CREATE TABLE sablog_users (userid INTEGER PRIMARY KEY, username TEXT, password TEXT, logincount INTEGER, loginip TEXT, logintime INTEGER, url TEXT, articles INTEGER, regdateline INTEGER, regip TEXT, groupid INTEGER, lastpost INTEGER)');
$pdo->exec('CREATE TABLE sablog_attachments (attachmentid INTEGER PRIMARY KEY, articleid INTEGER, dateline INTEGER, filename TEXT, filetype TEXT, filesize INTEGER, downloads INTEGER, filepath TEXT, thumb_filepath TEXT, thumb_width INTEGER, thumb_height INTEGER, isimage INTEGER)'); $pdo->exec('CREATE TABLE sablog_attachments (attachmentid INTEGER PRIMARY KEY, articleid INTEGER, dateline INTEGER, filename TEXT, filetype TEXT, filesize INTEGER, downloads INTEGER, filepath TEXT, thumb_filepath TEXT, thumb_width INTEGER, thumb_height INTEGER, isimage INTEGER)');
@@ -69,6 +70,7 @@ class SablogImportTest extends TestCase
$pdo->exec("INSERT INTO sablog_links VALUES (1, 0, '友链A', 'https://a.example.com', '', 1)"); $pdo->exec("INSERT INTO sablog_links VALUES (1, 0, '友链A', 'https://a.example.com', '', 1)");
$pdo->exec("INSERT INTO sablog_tags VALUES (1, 'laravel', 1, '10'), (2, '随笔', 1, '10')"); $pdo->exec("INSERT INTO sablog_tags VALUES (1, 'laravel', 1, '10'), (2, '随笔', 1, '10')");
$pdo->exec("INSERT INTO sablog_settings VALUES ('name', '老站名'), ('description', '老描述'), ('templatename', 'default')"); $pdo->exec("INSERT INTO sablog_settings VALUES ('name', '老站名'), ('description', '老描述'), ('templatename', 'default')");
$pdo->exec("INSERT INTO sablog_stylevars VALUES (1, 'ad_header', '<div class=\"legacy-ad\">老广告</div>', '')");
$pdo->exec("INSERT INTO sablog_attachments VALUES (1, 10, $now, 'photo.jpg', 'image/jpeg', 100, 3, '2020/09/photo.jpg', '', 0, 0, 1), (2, 10, $now, 'archive.zip', 'application/zip', 200, 1, '2020/09/archive.zip', '', 0, 0, 0)"); $pdo->exec("INSERT INTO sablog_attachments VALUES (1, 10, $now, 'photo.jpg', 'image/jpeg', 100, 3, '2020/09/photo.jpg', '', 0, 0, 1), (2, 10, $now, 'archive.zip', 'application/zip', 200, 1, '2020/09/archive.zip', '', 0, 0, 0)");
} }
@@ -116,6 +118,7 @@ class SablogImportTest extends TestCase
// 设置映射 // 设置映射
$this->assertSame('老站名', Setting::get('site_name')); $this->assertSame('老站名', Setting::get('site_name'));
$this->assertSame('sablog', Setting::get('active_theme')); $this->assertSame('sablog', Setting::get('active_theme'));
$this->assertSame('<div class="legacy-ad">老广告</div>', theme('var', 'ad_header', ''));
} }
public function test_import_converts_content_to_markdown(): void public function test_import_converts_content_to_markdown(): void
+19 -2
View File
@@ -57,7 +57,7 @@ class ThemeInjectTest extends TestCase
['point' => 'footer', 'title' => '第二条', 'html' => '<div id="inject-2">second</div>', 'sort' => 2, 'enabled' => true], ['point' => 'footer', 'title' => '第二条', 'html' => '<div id="inject-2">second</div>', 'sort' => 2, 'enabled' => true],
]); ]);
$html = $this->get('/posts/inject-test.shtml')->getContent(); $html = $this->get('/show-'.Post::where('slug', 'inject-test')->value('id').'.shtml')->getContent();
$pos1 = strpos($html, 'id="inject-1"'); $pos1 = strpos($html, 'id="inject-1"');
$pos2 = strpos($html, 'id="inject-2"'); $pos2 = strpos($html, 'id="inject-2"');
@@ -82,7 +82,7 @@ class ThemeInjectTest extends TestCase
['point' => 'post_content_bottom', 'title' => '正文后广告', 'html' => '<div id="post-bottom-ad">BOTTOM</div>', 'sort' => 1, 'enabled' => true], ['point' => 'post_content_bottom', 'title' => '正文后广告', 'html' => '<div id="post-bottom-ad">BOTTOM</div>', 'sort' => 1, 'enabled' => true],
]); ]);
$html = $this->get('/posts/inject-test.shtml')->getContent(); $html = $this->get('/show-'.Post::where('slug', 'inject-test')->value('id').'.shtml')->getContent();
$this->assertStringContainsString('post-top-ad', $html); $this->assertStringContainsString('post-top-ad', $html);
$this->assertStringContainsString('post-bottom-ad', $html); $this->assertStringContainsString('post-bottom-ad', $html);
@@ -97,4 +97,21 @@ class ThemeInjectTest extends TestCase
// theme('var') 直接可用 // theme('var') 直接可用
$this->assertSame('<div class="header-ad">头部广告</div>', theme('var', 'ad_header', '')); $this->assertSame('<div class="header-ad">头部广告</div>', theme('var', 'ad_header', ''));
} }
public function test_content_top_and_stylevars_render_on_home(): void
{
Setting::set('inject.blocks', [
['point' => 'content_top', 'title' => '首页广告', 'html' => '<div id="home-top-ad">首页广告位</div>', 'sort' => 1, 'enabled' => true],
]);
Setting::set('theme_vars_sablog', [
'ad_header' => '<div class="header-ad">头部广告</div>',
'huangjinlian' => '<div class="footer-ad">页脚广告</div>',
]);
$this->get('/')
->assertOk()
->assertSee('首页广告位', false)
->assertSee('头部广告', false)
->assertSee('页脚广告', false);
}
} }
+1 -1
View File
@@ -5,7 +5,7 @@
<p>由 LaraLog 驱动 · Demo 主题</p> <p>由 LaraLog 驱动 · Demo 主题</p>
</div> </div>
</footer> </footer>
@stack('theme:footer') @include('partials.theme-inject', ['point' => 'footer'])
@stack('theme:body_end') @stack('theme:body_end')
</body> </body>
</html> </html>
+1 -1
View File
@@ -23,4 +23,4 @@
</nav> </nav>
</div> </div>
</header> </header>
@stack('theme:header') @include('partials.theme-inject', ['point' => 'header'])
+20 -3
View File
@@ -366,18 +366,22 @@ a:hover {
max-width: 100%; max-width: 100%;
} }
.pagination-info { .pagination-info {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--muted); color: var(--muted);
font-size: 13px; font-size: 13px;
margin-right: 6px; padding: 6px 10px;
margin-right: 4px;
white-space: nowrap; white-space: nowrap;
} }
.pagination-links a, .pagination-links a,
.pagination-links .current, .pagination-links .current,
.pagination-links .disabled { .pagination-links .disabled {
padding: 6px 14px; padding: 6px 10px;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 13px;
background: var(--card); background: var(--card);
color: var(--text); color: var(--text);
} }
@@ -389,6 +393,19 @@ a:hover {
.pagination-links .disabled { .pagination-links .disabled {
color: var(--muted); color: var(--muted);
} }
.theme-inject {
margin: 12px 0;
max-width: 100%;
overflow: hidden;
}
.theme-inject:empty {
display: none;
}
.theme-inject img,
.theme-inject iframe {
max-width: 100%;
height: auto;
}
.m-footer { .m-footer {
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
@@ -1,4 +1,4 @@
@stack('theme:footer') @include('partials.theme-inject', ['point' => 'footer'])
<footer class="m-footer"> <footer class="m-footer">
<div class="m-container"> <div class="m-container">
<p>&copy; {{ date('Y') }} {{ $siteName }} <p>&copy; {{ date('Y') }} {{ $siteName }}
@@ -23,4 +23,4 @@
</nav> </nav>
</div> </div>
</header> </header>
@stack('theme:header') @include('partials.theme-inject', ['point' => 'header'])
+38 -9
View File
@@ -370,34 +370,63 @@ a:hover {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
min-width: 0; min-width: 0;
margin: 12px 0; margin: 14px 0;
clear: both;
} }
.pagination-links { .pagination-links {
display: flex; display: flex;
gap: 4px; gap: 1px;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
max-width: 100%; max-width: 100%;
margin: 12px 0; margin: 0;
} }
.pagination-info { .pagination-info {
color: #888; background: #f5fbff;
border: 1px solid #86b9d6;
color: #336;
font-size: 12px; font-size: 12px;
margin-right: 6px; padding: 2px 6px;
margin-right: 4px;
white-space: nowrap; white-space: nowrap;
} }
.pagination-links a, .pagination-links a,
.pagination-links .current, .pagination-links .current,
.pagination-links .disabled { .pagination-links .disabled {
padding: 2px 7px; padding: 2px 6px;
border: 1px solid #d8dfe6; border: 1px solid #dedeb8;
background: #fff; background: #fff;
font-size: 12px; font-size: 12px;
line-height: 1.4; line-height: 1.4;
min-width: 1.6em;
text-align: center;
text-decoration: none;
}
.pagination-links a:hover {
background: #f5fbff;
border-color: #86b9d6;
} }
.pagination-links .current { .pagination-links .current {
background: #1c62b0; background: #ffffd9;
color: #fff; color: #92a05a;
font-weight: 700;
border-color: #dedeb8;
}
.pagination-links .disabled {
color: #999;
}
.theme-inject {
margin: 8px 0;
max-width: 100%;
overflow: hidden;
}
.theme-inject:empty {
display: none;
}
.theme-inject img,
.theme-inject iframe {
max-width: 100%;
height: auto;
} }
#footer { #footer {
+2
View File
@@ -12,6 +12,7 @@
@endif @endif
@include('partials.view-mode-switcher') @include('partials.view-mode-switcher')
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($posts as $post) @forelse($posts as $post)
@include('partials.post-card', ['post' => $post]) @include('partials.post-card', ['post' => $post])
@@ -20,6 +21,7 @@
@endforelse @endforelse
@include('partials.pagination', ['paginator' => $posts]) @include('partials.pagination', ['paginator' => $posts])
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div>{{-- #page --}} </div>{{-- #page --}}
+2
View File
@@ -25,6 +25,7 @@
@include('partials.view-mode-switcher') @include('partials.view-mode-switcher')
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($posts as $post) @forelse($posts as $post)
@include('partials.post-card', ['post' => $post]) @include('partials.post-card', ['post' => $post])
@@ -33,6 +34,7 @@
@endforelse @endforelse
@include('partials.pagination', ['paginator' => $posts]) @include('partials.pagination', ['paginator' => $posts])
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div>{{-- #page --}} </div>{{-- #page --}}
@@ -1,4 +1,4 @@
@stack('theme:footer') @include('partials.theme-inject', ['point' => 'footer'])
<footer id="footer"> <footer id="footer">
<p>&copy; {{ date('Y') }} {{ $siteName }} <p>&copy; {{ date('Y') }} {{ $siteName }}
@if($siteIcp)<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">{{ $siteIcp }}</a>@endif @if($siteIcp)<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">{{ $siteIcp }}</a>@endif
@@ -4,7 +4,7 @@
<a href="{{ route('home') }}">{{ $siteName }}</a> <a href="{{ route('home') }}">{{ $siteName }}</a>
<p class="description">{{ $siteDescription }}</p> <p class="description">{{ $siteDescription }}</p>
</div> </div>
@stack('theme:header') @include('partials.theme-inject', ['point' => 'header'])
<nav id="nav"> <nav id="nav">
<ul> <ul>
<li class="{{ request()->routeIs('home') ? 'current_page_item' : '' }}"><a href="{{ route('home') }}">首页</a></li> <li class="{{ request()->routeIs('home') ? 'current_page_item' : '' }}"><a href="{{ route('home') }}">首页</a></li>
@@ -1,5 +1,5 @@
<aside class="sidebar"> <aside class="sidebar">
@stack('theme:sidebar_top') @include('partials.theme-inject', ['point' => 'sidebar_top'])
<div class="searchbox"> <div class="searchbox">
<form action="{{ route('search') }}" method="get"> <form action="{{ route('search') }}" method="get">
<input type="search" name="q" value="{{ request('q', request('keyword', '')) }}" placeholder="搜索文章"> <input type="search" name="q" value="{{ request('q', request('keyword', '')) }}" placeholder="搜索文章">
@@ -83,5 +83,5 @@
<li>标签 {{ $blogStats['tags'] }} 个</li> <li>标签 {{ $blogStats['tags'] }} 个</li>
</ul> </ul>
</section> </section>
@stack('theme:sidebar_bottom') @include('partials.theme-inject', ['point' => 'sidebar_bottom'])
</aside> </aside>
+2
View File
@@ -11,6 +11,7 @@
</form> </form>
@include('partials.view-mode-switcher') @include('partials.view-mode-switcher')
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($posts as $post) @forelse($posts as $post)
@include('partials.post-card', ['post' => $post]) @include('partials.post-card', ['post' => $post])
@@ -19,6 +20,7 @@
@endforelse @endforelse
@include('partials.pagination', ['paginator' => $posts]) @include('partials.pagination', ['paginator' => $posts])
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div>{{-- #page --}} </div>{{-- #page --}}
+2 -2
View File
@@ -48,11 +48,11 @@
<span>· 阅读 {{ $post->views }}</span> <span>· 阅读 {{ $post->views }}</span>
</div> </div>
@stack('theme:post_content_top') @include('partials.theme-inject', ['point' => 'post_content_top'])
<div class="post-content"> <div class="post-content">
{!! $contentHtml !!} {!! $contentHtml !!}
</div> </div>
@stack('theme:post_content_bottom') @include('partials.theme-inject', ['point' => 'post_content_bottom'])
@if($post->tags->isNotEmpty()) @if($post->tags->isNotEmpty())
<div class="post-tags"> <div class="post-tags">
+2
View File
@@ -7,6 +7,7 @@
<h1 class="list-title">标签:{{ $tag->name }}</h1> <h1 class="list-title">标签:{{ $tag->name }}</h1>
@include('partials.view-mode-switcher') @include('partials.view-mode-switcher')
@include('partials.theme-inject', ['point' => 'content_top'])
@forelse($posts as $post) @forelse($posts as $post)
@include('partials.post-card', ['post' => $post]) @include('partials.post-card', ['post' => $post])
@@ -15,6 +16,7 @@
@endforelse @endforelse
@include('partials.pagination', ['paginator' => $posts]) @include('partials.pagination', ['paginator' => $posts])
@include('partials.theme-inject', ['point' => 'content_bottom'])
</main> </main>
@include('partials.sidebar') @include('partials.sidebar')
</div>{{-- #page --}} </div>{{-- #page --}}