refactor: 外链中转拆为内置插件 neatstudio.link-tracker
- 插件承载全部逻辑:redirects 迁移(hasTable 兼容旧库)、Redirect 模型、UrlRedirector、/go 路由+控制器、Filament 外链中转资源 - 核心只留 hook 接入点: - tracked_url() 助手走 link.redirect 过滤器(评论正文/作者网址/友链模板统一调用) - 正文外链改写移到插件的 post.rendered 过滤器(优先级 20,晚于会员付费过滤) - 停用插件:链接恢复直连、内容不受影响;后台「管理 → 外链中转」查看点击量/启停 - 测试更新为插件上下文(含无过滤器降级断言);97 通过
This commit is contained in:
@@ -104,11 +104,13 @@ php artisan workerman:serve stop
|
|||||||
- 失败重试:`WORKERMAN_MAX_TRIES`(默认 3),超时 `WORKERMAN_TIMEOUT`(默认 60s),重试耗尽进 `failed_jobs` 表
|
- 失败重试:`WORKERMAN_MAX_TRIES`(默认 3),超时 `WORKERMAN_TIMEOUT`(默认 60s),重试耗尽进 `failed_jobs` 表
|
||||||
- 降级路径:`php artisan queue:work` 照常可用(同一队列)
|
- 降级路径:`php artisan queue:work` 照常可用(同一队列)
|
||||||
|
|
||||||
## 外链中转(/go)
|
## 外链中转(/go,内置插件)
|
||||||
|
|
||||||
|
内置插件 `neatstudio.link-tracker`(外链中转):
|
||||||
- 正文外链、评论正文 URL、评论作者网址、友链统一改写为 `/go/{hash}`:302 跳转 + **点击统计**、可**停用拦截**(返回 410)、隐藏真实链接、为未来广告位预留
|
- 正文外链、评论正文 URL、评论作者网址、友链统一改写为 `/go/{hash}`:302 跳转 + **点击统计**、可**停用拦截**(返回 410)、隐藏真实链接、为未来广告位预留
|
||||||
- 站内链接/相对路径/mailto/tel/锚点不中转;同一 URL 复用同一短码
|
- 站内链接/相对路径/mailto/tel/锚点不中转;同一 URL 复用同一短码
|
||||||
- 后台「外链中转」可查看点击量、按状态筛选、启停
|
- 后台「管理 → 外链中转」可查看点击量、启停、删除
|
||||||
|
- **插件化设计**:核心提供 `tracked_url()` 钩子(`link.redirect` 过滤器)+ 正文 `post.rendered` 过滤器接入点,停用插件后链接恢复直连、不影响内容显示
|
||||||
|
|
||||||
## 前台缓存
|
## 前台缓存
|
||||||
|
|
||||||
|
|||||||
@@ -46,31 +46,7 @@ class PostContentRenderer
|
|||||||
$html = app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post);
|
$html = app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post);
|
||||||
|
|
||||||
// 兜底:任何情况下不把裸 [paid] 标签输出到页面(如会员插件被停用时)
|
// 兜底:任何情况下不把裸 [paid] 标签输出到页面(如会员插件被停用时)
|
||||||
$html = preg_replace('/\[(\/?paid)\]/i', '', $html);
|
return preg_replace('/\[(\/?paid)\]/i', '', $html);
|
||||||
|
|
||||||
// 外链中转:站外链接改写为 /go/{hash}(点击统计/拦截)
|
|
||||||
return $this->rewriteExternalLinks($html);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 把正文里的站外 <a href> 改写为 /go 中转地址(站内/相对/锚点/mailto 不变)。
|
|
||||||
*/
|
|
||||||
private function rewriteExternalLinks(string $html): string
|
|
||||||
{
|
|
||||||
return preg_replace_callback(
|
|
||||||
'/<a\s+([^>]*?href=["\'])([^"\']+)(["\'][^>]*)>(.*?)<\/a>/is',
|
|
||||||
function (array $m): string {
|
|
||||||
$url = html_entity_decode(trim($m[2]));
|
|
||||||
$go = go_url($url);
|
|
||||||
|
|
||||||
if ($go === $url) {
|
|
||||||
return $m[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<a '.$m[1].e($go).$m[3].'>'.$m[4].'</a>';
|
|
||||||
},
|
|
||||||
$html
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -43,19 +43,20 @@ if (! function_exists('blog_setting')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('go_url')) {
|
if (! function_exists('tracked_url')) {
|
||||||
/**
|
/**
|
||||||
* 外链中转:站外链接改写为 /go/{hash}(点击统计/拦截),站内链接原样返回。
|
* 外链追踪钩子:核心视图统一调用,由插件(外链中转)改写为 /go/{hash};
|
||||||
|
* 插件停用时原样返回。站内链接等由插件自行判断。
|
||||||
*/
|
*/
|
||||||
function go_url(string $url): string
|
function tracked_url(string $url): string
|
||||||
{
|
{
|
||||||
return app(\App\Blog\Services\UrlRedirector::class)->goUrl($url);
|
return app(\App\Blog\Support\PluginManager::class)->applyFilters('link.redirect', $url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('comment_body')) {
|
if (! function_exists('comment_body')) {
|
||||||
/**
|
/**
|
||||||
* 评论正文:转义 + 自动链接 URL(走 /go 中转,nofollow)。
|
* 评论正文:转义 + 自动链接 URL(经 tracked_url 走中转,nofollow)。
|
||||||
*/
|
*/
|
||||||
function comment_body(string $text): string
|
function comment_body(string $text): string
|
||||||
{
|
{
|
||||||
@@ -63,7 +64,7 @@ if (! function_exists('comment_body')) {
|
|||||||
|
|
||||||
$text = preg_replace_callback(
|
$text = preg_replace_callback(
|
||||||
'#(https?://[^\s<>"\'()]+)#i',
|
'#(https?://[^\s<>"\'()]+)#i',
|
||||||
fn (array $m) => '<a href="'.e(go_url($m[1])).'" target="_blank" rel="nofollow noopener">'.$m[1].'</a>',
|
fn (array $m) => '<a href="'.e(tracked_url($m[1])).'" target="_blank" rel="nofollow noopener">'.$m[1].'</a>',
|
||||||
$text
|
$text
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ return [
|
|||||||
'neatstudio.ai-moderation',
|
'neatstudio.ai-moderation',
|
||||||
'neatstudio.payment',
|
'neatstudio.payment',
|
||||||
'neatstudio.membership',
|
'neatstudio.membership',
|
||||||
|
'neatstudio.link-tracker',
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
+5
@@ -10,6 +10,11 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
// 兼容已由核心迁移建过表的旧库
|
||||||
|
if (Schema::hasTable('redirects')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('redirects', function (Blueprint $table) {
|
Schema::create('redirects', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('hash', 16)->unique();
|
$table->string('hash', 16)->unique();
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"title": "外链中转",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "站外链接改写为 /go/{hash}:302 跳转 + 点击统计 + 可停用拦截 + 隐藏真实链接,为广告位预留",
|
||||||
|
"usage": "启用后自动生效:\n1. 正文外链、评论正文 URL、评论作者网址、友链统一改写为 /go/{hash}\n2. 点击 /go/{hash} 302 跳转并计数;后台「管理 → 外链中转」查看点击量/启停\n3. 停用插件后链接恢复直连(不影响正文显示)",
|
||||||
|
"author": "LaraLog",
|
||||||
|
"type": "core",
|
||||||
|
"provider": "Plugins\\Neatstudio\\LinkTracker\\ServiceProvider",
|
||||||
|
"filament_resources": [
|
||||||
|
"Plugins\\Neatstudio\\LinkTracker\\Filament\\Resources\\RedirectResource"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Plugins\Neatstudio\LinkTracker\Http\GoController;
|
||||||
|
|
||||||
|
Route::get('/go/{hash}', [GoController::class, 'show'])->name('go.show');
|
||||||
+2
-2
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Filament\Resources\RedirectResource\Pages;
|
namespace Plugins\Neatstudio\LinkTracker\Filament\Resources\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\RedirectResource;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
use Plugins\Neatstudio\LinkTracker\Filament\Resources\RedirectResource;
|
||||||
|
|
||||||
class ListRedirects extends ListRecords
|
class ListRedirects extends ListRecords
|
||||||
{
|
{
|
||||||
+3
-3
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace Plugins\Neatstudio\LinkTracker\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\RedirectResource\Pages\ListRedirects;
|
|
||||||
use App\Models\Redirect;
|
|
||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
@@ -14,6 +12,8 @@ use Filament\Schemas\Schema;
|
|||||||
use Filament\Support\Icons\Heroicon;
|
use Filament\Support\Icons\Heroicon;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use Plugins\Neatstudio\LinkTracker\Filament\Resources\Pages\ListRedirects;
|
||||||
|
use Plugins\Neatstudio\LinkTracker\Models\Redirect;
|
||||||
|
|
||||||
class RedirectResource extends Resource
|
class RedirectResource extends Resource
|
||||||
{
|
{
|
||||||
+2
-2
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Blog\Controllers;
|
namespace Plugins\Neatstudio\LinkTracker\Http;
|
||||||
|
|
||||||
use App\Models\Redirect;
|
use Plugins\Neatstudio\LinkTracker\Models\Redirect;
|
||||||
|
|
||||||
class GoController
|
class GoController
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Plugins\Neatstudio\LinkTracker\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Plugins\Neatstudio\LinkTracker;
|
||||||
|
|
||||||
|
use App\Blog\Support\PluginManager;
|
||||||
|
use App\Blog\Support\PluginServiceProvider;
|
||||||
|
use Plugins\Neatstudio\LinkTracker\Services\UrlRedirector;
|
||||||
|
|
||||||
|
class ServiceProvider extends PluginServiceProvider
|
||||||
|
{
|
||||||
|
protected function boot(PluginManager $manager): void
|
||||||
|
{
|
||||||
|
$this->loadRoutes(__DIR__.'/../routes/web.php');
|
||||||
|
|
||||||
|
// 核心视图(评论正文/作者网址/友链)通过 tracked_url() 走此过滤器改写
|
||||||
|
$manager->addFilter('link.redirect', function (string $url) {
|
||||||
|
return app(UrlRedirector::class)->goUrl($url);
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
// 正文外链改写:优先级 20,晚于会员付费内容过滤(10),保证 paywall 也覆盖
|
||||||
|
$manager->addFilter('post.rendered', function (string $html, \App\Models\Post $post) {
|
||||||
|
return app(UrlRedirector::class)->rewriteExternalLinks($html);
|
||||||
|
}, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-2
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Blog\Services;
|
namespace Plugins\Neatstudio\LinkTracker\Services;
|
||||||
|
|
||||||
use App\Models\Redirect;
|
|
||||||
use Illuminate\Database\UniqueConstraintViolationException;
|
use Illuminate\Database\UniqueConstraintViolationException;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Plugins\Neatstudio\LinkTracker\Models\Redirect;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 外链中转:把站外链接改写为 /go/{hash},用于点击统计、二次拦截与未来广告位。
|
* 外链中转:把站外链接改写为 /go/{hash},用于点击统计、二次拦截与未来广告位。
|
||||||
@@ -69,4 +69,25 @@ class UrlRedirector
|
|||||||
return $hash;
|
return $hash;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把 HTML 里的站外 <a href> 改写为 /go 中转地址(站内/相对/锚点/mailto 不变)。
|
||||||
|
*/
|
||||||
|
public function rewriteExternalLinks(string $html): string
|
||||||
|
{
|
||||||
|
return preg_replace_callback(
|
||||||
|
'/<a\s+([^>]*?href=["\'])([^"\']+)(["\'][^>]*)>(.*?)<\/a>/is',
|
||||||
|
function (array $m): string {
|
||||||
|
$url = html_entity_decode(trim($m[2]));
|
||||||
|
$go = $this->goUrl($url);
|
||||||
|
|
||||||
|
if ($go === $url) {
|
||||||
|
return $m[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<a '.$m[1].e($go).$m[3].'>'.$m[4].'</a>';
|
||||||
|
},
|
||||||
|
$html
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<ul class="links-list">
|
<ul class="links-list">
|
||||||
@forelse($links as $link)
|
@forelse($links as $link)
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ go_url($link->url) }}" target="_blank" rel="noopener nofollow">{{ $link->name }}</a>
|
<a href="{{ tracked_url($link->url) }}" target="_blank" rel="noopener nofollow">{{ $link->name }}</a>
|
||||||
@if($link->note)<span class="link-note">— {{ $link->note }}</span>@endif
|
@if($link->note)<span class="link-note">— {{ $link->note }}</span>@endif
|
||||||
</li>
|
</li>
|
||||||
@empty
|
@empty
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
<h3 class="widget-title">友情链接</h3>
|
<h3 class="widget-title">友情链接</h3>
|
||||||
<ul class="widget-list">
|
<ul class="widget-list">
|
||||||
@forelse($links as $link)
|
@forelse($links as $link)
|
||||||
<li><a href="{{ go_url($link->url) }}" target="_blank" rel="noopener">{{ $link->name }}</a></li>
|
<li><a href="{{ tracked_url($link->url) }}" target="_blank" rel="noopener">{{ $link->name }}</a></li>
|
||||||
@empty
|
@empty
|
||||||
<li>暂无链接</li>
|
<li>暂无链接</li>
|
||||||
@endforelse
|
@endforelse
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<div class="comment-head">
|
<div class="comment-head">
|
||||||
<strong>{{ $comment->author_name }}</strong>
|
<strong>{{ $comment->author_name }}</strong>
|
||||||
@if($comment->author_url)
|
@if($comment->author_url)
|
||||||
<span>· <a href="{{ go_url($comment->author_url) }}" target="_blank" rel="noopener nofollow">访问主页</a></span>
|
<span>· <a href="{{ tracked_url($comment->author_url) }}" target="_blank" rel="noopener nofollow">访问主页</a></span>
|
||||||
@endif
|
@endif
|
||||||
<span class="comment-time">· {{ $comment->created_at->format(blog_setting('comment_timeformat', 'Y-m-d H:i')) }}</span>
|
<span class="comment-time">· {{ $comment->created_at->format(blog_setting('comment_timeformat', 'Y-m-d H:i')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use App\Blog\Controllers\AuthController;
|
|||||||
use App\Blog\Controllers\CategoryController;
|
use App\Blog\Controllers\CategoryController;
|
||||||
use App\Blog\Controllers\CommentController;
|
use App\Blog\Controllers\CommentController;
|
||||||
use App\Blog\Controllers\FeedController;
|
use App\Blog\Controllers\FeedController;
|
||||||
use App\Blog\Controllers\GoController;
|
|
||||||
use App\Blog\Controllers\HomeController;
|
use App\Blog\Controllers\HomeController;
|
||||||
use App\Blog\Controllers\LegacyController;
|
use App\Blog\Controllers\LegacyController;
|
||||||
use App\Blog\Controllers\LinkController;
|
use App\Blog\Controllers\LinkController;
|
||||||
@@ -39,8 +38,6 @@ Route::post('/posts/{post}/comments', [CommentController::class, 'store'])->name
|
|||||||
// 浏览量统计(独立端点,避开整页缓存,保证实时)
|
// 浏览量统计(独立端点,避开整页缓存,保证实时)
|
||||||
Route::get('/track-view/{post}', fn (\App\Models\Post $post) => response($post->increment('views') ? '' : '', 204))->name('track-view');
|
Route::get('/track-view/{post}', fn (\App\Models\Post $post) => response($post->increment('views') ? '' : '', 204))->name('track-view');
|
||||||
|
|
||||||
// 外链中转:/go/{hash} 302 跳转,记录点击量;停用状态返回 410
|
|
||||||
Route::get('/go/{hash}', [GoController::class, 'show'])->name('go.show');
|
|
||||||
|
|
||||||
Route::get('/category/{slug}.shtml', [CategoryController::class, 'show'])->where('slug', '[^/]+')->name('category.show');
|
Route::get('/category/{slug}.shtml', [CategoryController::class, 'show'])->where('slug', '[^/]+')->name('category.show');
|
||||||
Route::get('/category/{slug}', fn (string $slug) => redirect()->route('category.show', $slug, 301))->where('slug', '[^/]+');
|
Route::get('/category/{slug}', fn (string $slug) => redirect()->route('category.show', $slug, 301))->where('slug', '[^/]+');
|
||||||
|
|||||||
@@ -5,25 +5,31 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Blog\Services\PostContentRenderer;
|
use App\Blog\Services\PostContentRenderer;
|
||||||
use App\Blog\Services\UrlRedirector;
|
|
||||||
use App\Models\Post;
|
use App\Models\Post;
|
||||||
use App\Models\Redirect;
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Plugins\Neatstudio\LinkTracker\Models\Redirect;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class GoRedirectTest extends TestCase
|
class GoRedirectTest extends TestCase
|
||||||
{
|
{
|
||||||
use RefreshDatabase;
|
use RefreshDatabase;
|
||||||
|
|
||||||
public function test_go_url_rewrites_external_but_keeps_internal(): void
|
public function test_tracked_url_rewrites_external_but_keeps_internal(): void
|
||||||
{
|
{
|
||||||
$go = app(UrlRedirector::class);
|
// 外链中转插件默认启用,link.redirect 过滤器生效
|
||||||
|
$this->assertStringContainsString('/go/', tracked_url('https://example.com/path'));
|
||||||
|
$this->assertSame('http://laralog.test/posts/1', tracked_url('http://laralog.test/posts/1'));
|
||||||
|
$this->assertSame('/posts/1', tracked_url('/posts/1'));
|
||||||
|
$this->assertSame('mailto:a@b.com', tracked_url('mailto:a@b.com'));
|
||||||
|
$this->assertSame('#anchor', tracked_url('#anchor'));
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertStringContainsString('/go/', $go->goUrl('https://example.com/path'));
|
public function test_tracked_url_returns_original_without_plugin_filter(): void
|
||||||
$this->assertSame('http://laralog.test/posts/1', $go->goUrl('http://laralog.test/posts/1'));
|
{
|
||||||
$this->assertSame('/posts/1', $go->goUrl('/posts/1'));
|
// 无插件注册 link.redirect 过滤器时(插件停用/未装),核心视图原样返回外链
|
||||||
$this->assertSame('mailto:a@b.com', $go->goUrl('mailto:a@b.com'));
|
$manager = new \App\Blog\Support\PluginManager();
|
||||||
$this->assertSame('#anchor', $go->goUrl('#anchor'));
|
|
||||||
|
$this->assertSame('https://example.com/path', $manager->applyFilters('link.redirect', 'https://example.com/path'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_go_redirect_counts_clicks(): void
|
public function test_go_redirect_counts_clicks(): void
|
||||||
@@ -54,8 +60,8 @@ class GoRedirectTest extends TestCase
|
|||||||
|
|
||||||
public function test_same_url_reuses_same_hash(): void
|
public function test_same_url_reuses_same_hash(): void
|
||||||
{
|
{
|
||||||
$a = app(UrlRedirector::class)->goUrl('https://example.com/dup');
|
$a = tracked_url('https://example.com/dup');
|
||||||
$b = app(UrlRedirector::class)->goUrl('https://example.com/dup');
|
$b = tracked_url('https://example.com/dup');
|
||||||
|
|
||||||
$this->assertSame($a, $b);
|
$this->assertSame($a, $b);
|
||||||
$this->assertSame(1, Redirect::query()->where('target_url', 'https://example.com/dup')->count());
|
$this->assertSame(1, Redirect::query()->where('target_url', 'https://example.com/dup')->count());
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
<h3>友情链接</h3>
|
<h3>友情链接</h3>
|
||||||
<ul>
|
<ul>
|
||||||
@forelse($links as $link)
|
@forelse($links as $link)
|
||||||
<li><a href="{{ go_url($link->url) }}" target="_blank" rel="noopener">{{ $link->name }}</a></li>
|
<li><a href="{{ tracked_url($link->url) }}" target="_blank" rel="noopener">{{ $link->name }}</a></li>
|
||||||
@empty
|
@empty
|
||||||
<li>暂无链接</li>
|
<li>暂无链接</li>
|
||||||
@endforelse
|
@endforelse
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<div class="comment" id="comment-{{ $comment->id }}">
|
<div class="comment" id="comment-{{ $comment->id }}">
|
||||||
<div class="comment-head">
|
<div class="comment-head">
|
||||||
<strong>{{ $comment->author_name }}</strong>
|
<strong>{{ $comment->author_name }}</strong>
|
||||||
@if($comment->author_url)<span>· <a href="{{ go_url($comment->author_url) }}" target="_blank" rel="noopener nofollow">访问主页</a></span>@endif
|
@if($comment->author_url)<span>· <a href="{{ tracked_url($comment->author_url) }}" target="_blank" rel="noopener nofollow">访问主页</a></span>@endif
|
||||||
<span class="comment-time">· {{ $comment->created_at->format(blog_setting('comment_timeformat', 'Y-m-d H:i')) }}</span>
|
<span class="comment-time">· {{ $comment->created_at->format(blog_setting('comment_timeformat', 'Y-m-d H:i')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-body">{!! comment_body($comment->content) !!}</div>
|
<div class="comment-body">{!! comment_body($comment->content) !!}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user