Files
ThoughtsPlugin/Plugin.php

1471 lines
52 KiB
PHP
Raw Normal View History

2026-02-23 20:00:41 +08:00
<?php
/**
* 回想
*
* @package ThoughtsPlugin
* @author 石头厝
* @version 1.3.8
* @link https://www.shitoucuo.com
*/
class ThoughtsPlugin_Plugin implements Typecho_Plugin_Interface
{
/**
* 激活插件方法
*/
public static function activate()
{
// 创建感想表
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$sql = "CREATE TABLE IF NOT EXISTS `{$prefix}thoughts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cid` int(10) unsigned NOT NULL COMMENT '文章ID',
`authorId` int(10) unsigned NOT NULL COMMENT '用户ID',
`created` int(10) unsigned DEFAULT 0 COMMENT '创建时间',
`text` text COMMENT '感想内容',
`status` varchar(16) DEFAULT 'approved' COMMENT '状态',
PRIMARY KEY (`id`),
KEY `cid` (`cid`),
KEY `authorId` (`authorId`),
KEY `created` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
try {
$db->query($sql);
} catch (Exception $e) {
throw new Typecho_Plugin_Exception('创建感想表失败: ' . $e->getMessage());
}
// 挂载hook
Typecho_Plugin::factory('Widget_Feedback')->comment = array(__CLASS__, 'processComment');
Typecho_Plugin::factory('Widget_Archive')->footer = array(__CLASS__, 'footer');
Typecho_Plugin::factory('Widget_Archive')->header = array(__CLASS__, 'header');
Typecho_Plugin::factory('Widget_Feedback')->content = array(__CLASS__, 'renderCheckbox');
return _t('感想插件已激活,请进行配置');
}
/**
* 禁用插件方法
*/
public static function deactivate()
{
return _t('感想插件已禁用');
}
/**
* 插件配置方法
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
$label = new Typecho_Widget_Helper_Form_Element_Text(
'label_text',
NULL,
'发布为感想',
_t('勾选框标签文字'),
_t('显示在勾选框旁边的文字')
);
$form->addInput($label->addRule('required', _t('标签文字不能为空')));
// 添加前端卡片默认状态设置
$default_state = new Typecho_Widget_Helper_Form_Element_Radio(
'default_state',
array(
'collapsed' => '默认收起',
'expanded' => '默认展开'
),
'collapsed',
_t('感想卡片默认状态'),
_t('选择感想列表在前端的默认显示状态')
);
$form->addInput($default_state);
// 独立页面配置
$page_per_page = new Typecho_Widget_Helper_Form_Element_Text(
'page_per_page',
NULL,
'20',
_t('独立页面每页显示数量'),
_t('独立页面每页显示的感想数量')
);
$form->addInput($page_per_page->addRule('isInteger', _t('请输入整数')));
}
/**
* 个人用户的配置方法
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form){}
/**
* 处理评论提交 - 彻底拦截版
*/
public static function processComment($comment, $post)
{
$request = Typecho_Request::getInstance();
// 检查是否勾选了发布为感想
if ($request->isPost() && $request->get('thoughts') == '1') {
// 只有管理员可以发布感想
$user = Typecho_Widget::widget('Widget_User');
if ($user->hasLogin() && $user->pass('administrator', true)) {
// 保存到感想表
$db = Typecho_Db::get();
$insert = $db->insert('table.thoughts')
->rows(array(
'cid' => $comment['cid'],
'authorId' => $comment['authorId'],
'created' => $comment['created'],
'text' => $comment['text'],
'status' => 'approved'
));
$insertId = $db->query($insert);
// 关键直接输出结果并终止完全拦截Typecho的评论处理
self::outputSuccessResponse($post);
}
}
return $comment;
}
/**
* 输出成功响应并终止
*/
private static function outputSuccessResponse($post)
{
// 清空可能的输出缓冲区
if (ob_get_level()) {
ob_end_clean();
}
// 设置正确的Content-Type
header('Content-Type: text/html; charset=utf-8');
// 构建返回URL
$returnUrl = $post->permalink . '#thoughts';
// 输出包含JavaScript的简单HTML页面
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>发布成功</title>
<script>
alert("🎉 感想发布成功!");
window.location.href = "' . htmlspecialchars($returnUrl) . '";
</script>
</head>
<body>
<p>感想发布成功,正在返回文章页面...</p>
<p>如果页面没有自动跳转,请<a href="' . htmlspecialchars($returnUrl) . '">点击这里</a></p>
</body>
</html>';
// 立即终止执行防止Typecho继续处理
exit;
}
/**
* 在评论表单中渲染勾选框
*/
public static function renderCheckbox($content, $class)
{
// 只在文章页面且是管理员显示
if ($class->request->is('post') && self::isAdmin()) {
$options = Helper::options()->plugin('ThoughtsPlugin');
$labelText = $options->label_text ?: '发布为感想';
$checkbox = '<div class="thought-checkbox" style="margin: 15px 0; padding: 10px; background: #f8f9fa; border-radius: 4px;">';
$checkbox .= '<label style="display: flex; align-items: center; cursor: pointer; font-weight: normal;">';
$checkbox .= '<input type="checkbox" name="thoughts" value="1" style="margin-right: 8px;"> ';
$checkbox .= htmlspecialchars($labelText);
$checkbox .= '</label>';
$checkbox .= '</div>';
// 插入到评论框之前
$content = preg_replace('/(<textarea[^>]*name="text"[^>]*>)/i', $checkbox . '$1', $content);
}
return $content;
}
/**
* 在页面头部添加CSS - 美化版
*/
public static function header()
{
// 判断是否在独立页面中
$isThoughtsPage = false;
if (isset($_GET['thoughts_page']) ||
(isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], 'thoughts') !== false)) {
$isThoughtsPage = true;
}
// 独立页面CSS - 美化版
if ($isThoughtsPage) {
echo '<style>
/* 整体容器 */
.thoughts-page-container {
max-width: 900px;
margin: 0 auto;
padding: 30px 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
color: #333;
}
/* 页面头部 */
.thoughts-page-header {
margin-bottom: 50px;
text-align: center;
padding-bottom: 30px;
border-bottom: 3px solid #4a6cf7;
position: relative;
}
.thoughts-page-header:after {
content: "";
position: absolute;
bottom: -3px;
left: 50%;
transform: translateX(-50%);
width: 100px;
height: 3px;
background: linear-gradient(90deg, #4a6cf7, #6b8eff);
}
.dark .thoughts-page-header h1{background:.rgb(10 12 25 / var(--tw-bg-opacity);)}
.thoughts-page-header h1 {
color: #2d3748;
margin-bottom: 15px;
font-size: 2.5em;
font-weight: 700;
letter-spacing: -0.5px;
background: linear-gradient(135deg, #4a6cf7 0%, #6b8eff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.thoughts-page-header .subtitle {
color: #718096;
font-size: 1.2em;
font-weight: 300;
}
/* 感想总数徽章 */
.thoughts-total-count {
display: inline-block;
background: linear-gradient(135deg, #4a6cf7, #6b8eff);
color: white;
padding: 2px 10px;
border-radius: 30px;
font-size: 13px;
margin-left: 15px;
vertical-align: middle;
}
.thoughts-total-count:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(74, 108, 247, 0.4);
}
/* 感想卡片 */
.thoughts-page-item {
margin-bottom: 35px;
padding: 30px;
background: white;
border-radius: 15px;
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(226, 232, 240, 0.8);
transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
position: relative;
overflow: hidden;
}
.thoughts-page-item:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 5px;
height: 100%;
background: linear-gradient(to bottom, #4a6cf7, #6b8eff);
transition: width 0.3s ease;
}
.thoughts-page-item:hover {
transform: translateY(-8px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
border-color: rgba(74, 108, 247, 0.2);
}
.thoughts-page-item:hover:before {
width: 8px;
}
/* 感想元信息 */
.thoughts-page-meta {
margin-bottom: 25px;
font-size: 1em;
color: #4a5568;
line-height: 1.8;
padding-bottom: 15px;
border-bottom: 1px solid rgba(226, 232, 240, 0.8);
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 20px;
}
.thoughts-page-meta .date {
color: #4a6cf7;
font-weight: 600;
font-size: 1.05em;
padding: 6px 12px;
background: rgba(74, 108, 247, 0.1);
border-radius: 8px;
display: inline-flex;
align-items: center;
}
.thoughts-page-meta .date:before {
content: "📅";
margin-right: 8px;
font-size: 0.9em;
}
.thoughts-page-meta .user {
color: #ed64a6;
font-weight: 600;
font-size: 1.05em;
padding: 6px 12px;
background: rgba(237, 100, 166, 0.1);
border-radius: 8px;
display: inline-flex;
align-items: center;
}
.thoughts-page-meta .user:before {
content: "👤";
margin-right: 8px;
font-size: 0.9em;
}
.thoughts-page-meta .article {
color: #38b2ac;
font-weight: 500;
}
.thoughts-page-meta a {
color: #38b2ac;
text-decoration: none;
border-bottom: 2px dotted #38b2ac;
font-weight: 600;
padding: 2px 0;
transition: all 0.3s ease;
}
.thoughts-page-meta a:hover {
color: #319795;
border-bottom: 2px solid #319795;
}
/* 感想内容区域 */
.thoughts-page-content {
color: #2d3748;
line-height: 1.8;
padding: 25px;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 12px;
border-left: 4px solid #cbd5e0;
font-size: 1.1em;
position: relative;
}
.thoughts-page-content:before {
content: "💭";
position: absolute;
top: -15px;
left: -15px;
background: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
font-size: 1em;
}
.thoughts-page-content strong {
color: #4a5568;
display: block;
margin-bottom: 15px;
font-size: 1.15em;
font-weight: 600;
padding-left: 25px;
position: relative;
}
.thoughts-page-content strong:before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 15px;
height: 2px;
background: #4a6cf7;
}
/* 分页样式 */
.thoughts-pagination {
margin-top: 60px;
text-align: center;
padding-top: 40px;
border-top: 2px solid #e2e8f0;
}
.thoughts-pagination a,
.thoughts-pagination span {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 20px;
margin: 0 6px;
border: 2px solid #e2e8f0;
border-radius: 10px;
text-decoration: none;
color: #4a5568;
background: white;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
min-width: 45px;
height: 45px;
}
.thoughts-pagination a:hover {
background: linear-gradient(135deg, #4a6cf7, #6b8eff);
color: white;
border-color: #4a6cf7;
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(74, 108, 247, 0.3);
}
.thoughts-pagination .current {
background: linear-gradient(135deg, #4a6cf7, #6b8eff);
color: white;
border-color: #4a6cf7;
box-shadow: 0 5px 15px rgba(74, 108, 247, 0.3);
}
.thoughts-pagination .extend {
padding: 12px 25px;
font-weight: 600;
letter-spacing: 0.5px;
}
.thoughts-pagination .extend:hover {
background: linear-gradient(135deg, #ed64a6, #f687b3);
border-color: #ed64a6;
}
/* 空状态 */
.thoughts-empty-page {
text-align: center;
padding: 100px 40px;
color: #718096;
font-size: 1.3em;
background: white;
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
border: 2px dashed #e2e8f0;
transition: all 0.3s ease;
}
.thoughts-empty-page:hover {
border-color: #4a6cf7;
transform: translateY(-5px);
}
.thoughts-empty-page .icon {
font-size: 5em;
margin-bottom: 30px;
opacity: 0.2;
display: inline-block;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.thoughts-empty-page p {
margin: 0;
font-size: 1.2em;
color: #4a5568;
font-weight: 400;
}
.thoughts-empty-page .empty-action {
margin-top: 30px;
display: inline-block;
padding: 12px 30px;
background: linear-gradient(135deg, #4a6cf7, #6b8eff);
color: white;
text-decoration: none;
border-radius: 30px;
font-weight: 600;
transition: all 0.3s ease;
}
.thoughts-empty-page .empty-action:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(74, 108, 247, 0.4);
}
/* 响应式设计 */
@media (max-width: 992px) {
.thoughts-page-container {
padding: 25px 15px;
}
.thoughts-page-header h1 {
font-size: 2.2em;
}
.thoughts-page-item {
padding: 25px;
margin-bottom: 30px;
}
.thoughts-page-content {
padding: 20px;
}
}
@media (max-width: 768px) {
.thoughts-page-container {
padding: 20px 15px;
}
.thoughts-page-header {
margin-bottom: 40px;
padding-bottom: 25px;
}
.thoughts-page-header h1 {
font-size: 1.8em;
}
.thoughts-page-header .subtitle {
font-size: 1em;
}
.thoughts-page-item {
padding: 20px;
margin-bottom: 25px;
}
.thoughts-page-meta {
font-size: 0.95em;
gap: 15px;
flex-direction: column;
align-items: flex-start;
}
.thoughts-page-meta .date,
.thoughts-page-meta .user {
font-size: 1em;
}
.thoughts-page-content {
padding: 18px;
font-size: 1em;
}
.thoughts-page-content strong {
font-size: 1.1em;
}
.thoughts-pagination a,
.thoughts-pagination span {
padding: 10px 16px;
margin: 0 4px;
font-size: 0.9em;
min-width: 40px;
height: 40px;
}
.thoughts-pagination .extend {
padding: 10px 20px;
}
.thoughts-empty-page {
padding: 60px 25px;
}
.thoughts-empty-page .icon {
font-size: 4em;
}
}
@media (max-width: 480px) {
.thoughts-page-header h1 {
font-size: 1.6em;
}
.thoughts-page-item {
padding: 18px;
}
.thoughts-page-meta {
gap: 12px;
}
.thoughts-page-content {
padding: 16px;
}
.thoughts-pagination a,
.thoughts-pagination span {
padding: 8px 12px;
margin: 0 3px;
min-width: 35px;
height: 35px;
}
.thoughts-total-count {
margin-left: 10px;
padding: 6px 15px;
font-size: 0.85em;
}
}
/* 动画效果 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.thoughts-page-item {
animation: fadeInUp 0.6s ease-out;
animation-fill-mode: both;
}
.thoughts-page-item:nth-child(1) { animation-delay: 0.1s; }
.thoughts-page-item:nth-child(2) { animation-delay: 0.2s; }
.thoughts-page-item:nth-child(3) { animation-delay: 0.3s; }
.thoughts-page-item:nth-child(4) { animation-delay: 0.4s; }
.thoughts-page-item:nth-child(5) { animation-delay: 0.5s; }
.thoughts-page-item:nth-child(n+6) { animation-delay: 0.6s; }
</style>';
}
// 文章页面的原有样式保持不变,添加展开收起样式(使用更独特的类名)
if (Typecho_Widget::widget('Widget_Archive')->is('single')) {
echo '<style>
/* 感想列表容器 */
.thoughts-list-container {
padding: 18px 24px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
border-radius: 10px;
border: 1px solid #e1e8ed;
}
.dark .thoughts-list-container{background:rgb(10 12 25 / 1); border: 0px solid #e1e8ed;}
/* 标题区域 */
.thoughts-header {
margin-top: 0;
margin-bottom: 0;
color: #FFF;
font-size: 18px;
display: flex;
justify-content: space-between;
align-items:center;
cursor: pointer;
user-select: none;
font-weight:600;
}
/* 感想条数背景色 */
.dark .thoughts-count-badge {color:rgb(156 163 175 / var(--tw-text-opacity))!important;}
.thoughts-count-badge {
background-color: rgba(255, 255, 255, 0.2);
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 13px;
margin-left: 8px;
}
.dark .thoughts-toggle-btn {color:rgb(156 163 175 / var(--tw-text-opacity))!important;}
/* 切换按钮 */
.thoughts-toggle-btn {
font-size: 0.8em;
color: #fff;
display: flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
border-radius: 4px;
transition: all 0.3s ease;
font-weight: normal;
border: none;
cursor: pointer;
}
.thoughts-toggle-btn .toggle-arrow {
font-size: 14px;
line-height: 1;
transition: transform 0.3s ease;
display: inline-block;
}
.thoughts-toggle-btn.expanded .toggle-arrow {
transform: rotate(180deg);
}
/* 内容区域 */
.thoughts-items-wrapper {
transition: all 0.3s ease;
overflow: hidden;
}
.thoughts-items-wrapper.collapsed {
max-height: 0;
opacity: 0;
visibility: hidden;
}
.thoughts-items-wrapper.expanded {
max-height: 5000px;
opacity: 1;
visibility: visible;
margin-top: 20px;
}
/* 感想项 */
.dark .thought-item{
background-color:rgb(15 23 42 / 1);
}
.thought-item {
margin-bottom: 20px;
padding: 15px;
background: white;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.thought-item:last-child {
margin-bottom: 0;
}
.thought-meta {
margin-bottom: 10px;
font-size: 0.9em;
color: #7f8c8d;
}
.thought-meta span {
margin-right: 10px;
}
.thought-meta .thought-date {
color: #3498db;
font-weight: bold;
}
.thought-meta .thought-user {
color: #e74c3c;
}
.thought-content {
color: #34495e;
line-height: 1.6;
}
.thought-content p {
margin: 0;
}
/* 评论框勾选框 */
.thought-checkbox {
margin: 15px 0;
padding: 10px;
background: #f8f9fa;
border-radius: 4px;
border: 1px solid #ddd;
}
.thought-checkbox label {
display: flex;
align-items: center;
cursor: pointer;
font-weight: normal;
margin: 0;
}
.thought-checkbox input[type="checkbox"] {
margin-right: 8px;
width: 16px;
height: 16px;
}
/* 空状态 */
.thoughts-empty-state {
text-align: center;
color: #95a5a6;
font-style: italic;
padding: 20px;
}
</style>';
}
}
/**
* 在页面底部添加JS和自动渲染感想改为始终自动插入
*/
public static function footer()
{
if (!Typecho_Widget::widget('Widget_Archive')->is('single')) {
return;
}
// 获取感想HTML
$thoughtsHtml = self::renderThoughts();
// 如果有感想数据,始终自动插入(不再检查配置)
if ($thoughtsHtml) {
// 由于删除了位置设置,使用默认位置:文章内容之后
$position = 'after_post';
// 获取配置中的默认状态
$options = Helper::options()->plugin('ThoughtsPlugin');
$defaultState = isset($options->default_state) ? $options->default_state : 'collapsed';
$defaultIsExpanded = ($defaultState === 'expanded');
$js = '<script>
(function() {
// 使用立即执行函数避免全局污染
var thoughtsHtml = ' . json_encode($thoughtsHtml) . ';
var position = "' . $position . '";
var defaultExpanded = ' . ($defaultIsExpanded ? 'true' : 'false') . ';
document.addEventListener("DOMContentLoaded", function() {
var targetElement = null;
// 始终使用"文章内容之后"的位置
targetElement = document.querySelector(".post-content, .entry-content, .post-body");
if (targetElement) {
var div = document.createElement("div");
div.innerHTML = thoughtsHtml;
targetElement.parentNode.insertBefore(div, targetElement.nextSibling);
}
// 初始化感想列表展开收起功能
initThoughtsToggle(defaultExpanded);
// 锚点跳转
if (window.location.hash === "#thoughts") {
setTimeout(function() {
var thoughtsEl = document.querySelector(".thoughts-list-container");
if (thoughtsEl) {
thoughtsEl.scrollIntoView({ behavior: "smooth" });
// 跳转到感想列表时自动展开
expandThoughts();
}
}, 300);
}
});
// 初始化感想列表展开收起功能
function initThoughtsToggle(isExpandedByDefault) {
var thoughtsContainer = document.querySelector(".thoughts-list-container");
if (!thoughtsContainer) return;
// 确保只初始化一次
if (thoughtsContainer.dataset.initialized === "true") return;
thoughtsContainer.dataset.initialized = "true";
var header = thoughtsContainer.querySelector(".thoughts-header");
var itemsWrapper = thoughtsContainer.querySelector(".thoughts-items-wrapper");
if (!header || !itemsWrapper) {
console.log("找不到感想列表的元素");
return;
}
// 创建切换按钮(如果不存在)
var toggleBtn = header.querySelector(".thoughts-toggle-btn");
if (!toggleBtn) {
toggleBtn = document.createElement("button");
toggleBtn.className = "thoughts-toggle-btn " + (isExpandedByDefault ? "expanded" : "collapsed");
toggleBtn.innerHTML = \'<span class="toggle-arrow">\' + (isExpandedByDefault ? "" : "") + \'</span> <span class="toggle-text"></span>\';
header.appendChild(toggleBtn);
}
// 根据配置设置默认状态
if (isExpandedByDefault) {
itemsWrapper.className = "thoughts-items-wrapper expanded";
toggleBtn.className = "thoughts-toggle-btn expanded";
} else {
itemsWrapper.className = "thoughts-items-wrapper collapsed";
toggleBtn.className = "thoughts-toggle-btn collapsed";
}
// 绑定标题点击事件
header.addEventListener("click", function(e) {
if (e.target.closest(".thoughts-toggle-btn")) return;
toggleThoughts();
});
// 绑定按钮点击事件
toggleBtn.addEventListener("click", function(e) {
e.stopPropagation();
toggleThoughts();
});
// 保存引用
thoughtsContainer._thoughtsToggle = {
header: header,
itemsWrapper: itemsWrapper,
toggleBtn: toggleBtn
};
}
// 切换展开收起状态
function toggleThoughts() {
var thoughtsContainer = document.querySelector(".thoughts-list-container");
if (!thoughtsContainer || !thoughtsContainer._thoughtsToggle) return;
var itemsWrapper = thoughtsContainer._thoughtsToggle.itemsWrapper;
var toggleBtn = thoughtsContainer._thoughtsToggle.toggleBtn;
if (itemsWrapper.classList.contains("collapsed")) {
expandThoughts();
} else {
collapseThoughts();
}
}
// 展开感想列表
function expandThoughts() {
var thoughtsContainer = document.querySelector(".thoughts-list-container");
if (!thoughtsContainer || !thoughtsContainer._thoughtsToggle) return;
var itemsWrapper = thoughtsContainer._thoughtsToggle.itemsWrapper;
var toggleBtn = thoughtsContainer._thoughtsToggle.toggleBtn;
itemsWrapper.className = "thoughts-items-wrapper expanded";
toggleBtn.className = "thoughts-toggle-btn expanded";
if (toggleBtn.querySelector(".toggle-arrow")) {
toggleBtn.querySelector(".toggle-arrow").textContent = "";
}
}
// 收起感想列表
function collapseThoughts() {
var thoughtsContainer = document.querySelector(".thoughts-list-container");
if (!thoughtsContainer || !thoughtsContainer._thoughtsToggle) return;
var itemsWrapper = thoughtsContainer._thoughtsToggle.itemsWrapper;
var toggleBtn = thoughtsContainer._thoughtsToggle.toggleBtn;
itemsWrapper.className = "thoughts-items-wrapper collapsed";
toggleBtn.className = "thoughts-toggle-btn collapsed";
if (toggleBtn.querySelector(".toggle-arrow")) {
toggleBtn.querySelector(".toggle-arrow").textContent = "";
}
}
})();
</script>';
echo $js;
}
}
/**
* 获取感想列表(删除数量限制,改为全部显示)
*/
public static function getThoughts($cid = null)
{
if (!$cid) {
$widget = Typecho_Widget::widget('Widget_Archive');
$cid = $widget->cid;
}
if (!$cid) return array();
$db = Typecho_Db::get();
$select = $db->select()
->from('table.thoughts')
->where('cid = ?', $cid)
->where('status = ?', 'approved')
->order('created', Typecho_Db::SORT_DESC);
// 删除limit限制改为全部显示
return $db->fetchAll($select);
}
/**
* 显示感想列表的HTML有数据才显示
*/
public static function renderThoughts($cid = null)
{
if (!$cid) {
$widget = Typecho_Widget::widget('Widget_Archive');
$cid = $widget->cid;
}
if (!$cid) return '';
// 获取感想数据(全部显示)
$thoughts = self::getThoughts($cid);
// 如果没有感想数据,返回空字符串(不显示任何内容)
if (empty($thoughts)) {
return '';
}
// 获取感想条数
$thoughtsCount = count($thoughts);
$db = Typecho_Db::get();
// 构建正确的HTML结构
$html = '<div class="thoughts-list-container" id="thoughts">';
$html .= '<div class="thoughts-header">';
$html .= '<h3><!--📖 -->回读感想<span class="thoughts-count-badge">' . $thoughtsCount . '条</span></h3>';
$html .= '</div>';
$html .= '<div class="thoughts-items-wrapper collapsed">';
foreach ($thoughts as $thought) {
// 获取用户信息
$user = $db->fetchRow($db->select()
->from('table.users')
->where('uid = ?', $thought['authorId']));
$date = date('Y年m月d日 H:i', $thought['created']);
// 优先显示昵称
if ($user) {
$username = !empty($user['screenName']) ? $user['screenName'] : $user['name'];
} else {
$username = '匿名用户';
}
$html .= '<div class="thought-item">';
$html .= '<div class="thought-meta">';
$html .= '<span class="thought-date">' . $date . '</span>';
$html .= '<span class="thought-user">' . htmlspecialchars($username) . '</span>';
$html .= '<span class="thought-action">回读本文</span>';
$html .= '</div>';
$html .= '<div class="thought-content">';
$html .= '<p><strong>写下感想:</strong>' . nl2br(htmlspecialchars($thought['text'])) . '</p>';
$html .= '</div>';
$html .= '</div>';
}
$html .= '</div>';
$html .= '</div>';
return $html;
}
/**
* 获取所有感想(用于独立页面)
*/
public static function getAllThoughts($limit = 20, $offset = 0)
{
$db = Typecho_Db::get();
$select = $db->select()
->from('table.thoughts')
->where('status = ?', 'approved')
->order('created', Typecho_Db::SORT_DESC);
// 应用分页限制
if ($limit > 0) {
$select->limit($limit);
}
if ($offset > 0) {
$select->offset($offset);
}
return $db->fetchAll($select);
}
/**
* 获取感想总数
*/
public static function getTotalThoughtsCount()
{
$db = Typecho_Db::get();
$select = $db->select('COUNT(*) as count')
->from('table.thoughts')
->where('status = ?', 'approved');
$result = $db->fetchRow($select);
return $result ? intval($result['count']) : 0;
}
/**
* 获取文章信息(修复链接生成)
*/
public static function getPostInfo($cid)
{
$db = Typecho_Db::get();
$select = $db->select('title', 'slug', 'type')
->from('table.contents')
->where('cid = ?', $cid)
->where('type = ?', 'post')
->where('status = ?', 'publish');
$result = $db->fetchRow($select);
if ($result) {
// 正确构建文章URL
$result['url'] = Typecho_Common::url(
Typecho_Router::url('post', $result),
Helper::options()->index
);
}
return $result;
}
/**
* 渲染独立页面内容 - 修复分页404问题删除文章页感想分页功能
*/
public static function renderThoughtsPage($page = 1, $perPage = null)
{
if (!$perPage) {
$options = Helper::options()->plugin('ThoughtsPlugin');
$perPage = $options->page_per_page ? intval($options->page_per_page) : 20;
}
$page = max(1, intval($page));
$offset = ($page - 1) * $perPage;
// 获取感想数据
$thoughts = self::getAllThoughts($perPage, $offset);
$total = self::getTotalThoughtsCount();
$totalPages = ceil($total / $perPage);
// 如果请求的页码超过总页数,显示最后一页
if ($page > $totalPages && $totalPages > 0) {
$page = $totalPages;
$offset = ($page - 1) * $perPage;
$thoughts = self::getAllThoughts($perPage, $offset);
}
$db = Typecho_Db::get();
$html = '<div class="thoughts-page-container">';
$html .= '<div class="thoughts-page-header">';
$html .= '<h1>全部感想<span class="thoughts-total-count">' . $total . ' 条</span></h1>';
if ($totalPages > 1) {
$html .= '<div class="subtitle">第 ' . $page . ' 页,共 ' . $totalPages . ' 页</div>';
} else {
$html .= '<div class="subtitle">按时间倒序排列,每次思考都值得被记录</div>';
}
$html .= '</div>';
if (empty($thoughts)) {
$html .= '<div class="thoughts-empty-page">
<div class="icon">📝</div>
<p>暂时还没有感想记录</p>
<p>去文章中发表你的第一个感想吧!</p>
<a href="' . Helper::options()->index . '" class="empty-action">浏览文章</a>
</div>';
} else {
// 显示当前页数据范围
$startNum = $offset + 1;
$endNum = min($offset + count($thoughts), $total);
if ($totalPages > 1) {
$html .= '<div style="text-align: center; margin-bottom: 20px; color: #718096; font-size: 0.95em;">
显示第 ' . $startNum . ' - ' . $endNum . ' 条感想,按时间倒序排列
</div>';
}
foreach ($thoughts as $index => $thought) {
// 获取用户信息
$user = $db->fetchRow($db->select()
->from('table.users')
->where('uid = ?', $thought['authorId']));
// 获取文章信息
$post = self::getPostInfo($thought['cid']);
// 优先显示昵称
if ($user) {
$username = !empty($user['screenName']) ? $user['screenName'] : $user['name'];
} else {
$username = '匿名用户';
}
// 文章标题和链接
if ($post) {
$postTitle = $post['title'];
$postUrl = $post['url'];
} else {
$postTitle = '文章已被删除';
$postUrl = '#';
}
$html .= '<div class="thoughts-page-item">';
$html .= '<div class="thoughts-page-meta">';
$html .= '<span class="date">' . date('Y年m月d日 H:i', $thought['created']) . '</span>';
$html .= '<span class="user">' . htmlspecialchars($username) . '</span>';
$html .= '<span class="article">阅读了《<a href="' . htmlspecialchars($postUrl) . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($postTitle) . '</a>》</span>';
$html .= '</div>';
$html .= '<div class="thoughts-page-content">';
$html .= '<strong>发布感想内容为:</strong>';
$html .= nl2br(htmlspecialchars($thought['text']));
$html .= '</div>';
$html .= '</div>';
}
// 分页 - 修复使用JavaScript处理分页而不是URL参数保留独立页面分页
if ($totalPages > 1) {
$html .= '<div class="thoughts-pagination">';
// 上一页
if ($page > 1) {
$html .= '<a href="javascript:void(0)" onclick="goToPage(' . ($page - 1) . ')" class="extend" title="上一页">← 上一页</a>';
}
// 页码
$startPage = max(1, $page - 2);
$endPage = min($totalPages, $page + 2);
// 如果当前页靠近开头,显示更多后面的页码
if ($startPage == 1) {
$endPage = min($totalPages, $startPage + 4);
}
// 如果当前页靠近结尾,显示更多前面的页码
if ($endPage == $totalPages) {
$startPage = max(1, $endPage - 4);
}
for ($i = $startPage; $i <= $endPage; $i++) {
if ($i == $page) {
$html .= '<span class="current">' . $i . '</span>';
} else {
$html .= '<a href="javascript:void(0)" onclick="goToPage(' . $i . ')">' . $i . '</a>';
}
}
// 下一页
if ($page < $totalPages) {
$html .= '<a href="javascript:void(0)" onclick="goToPage(' . ($page + 1) . ')" class="extend" title="下一页">下一页 →</a>';
}
$html .= '</div>';
// 添加JavaScript处理分页
$html .= '<script>
function goToPage(page) {
if (page < 1) page = 1;
// 使用AJAX加载分页内容
loadPageContent(page);
}
function loadPageContent(page) {
// 显示加载状态
var container = document.querySelector(".thoughts-page-container");
if (!container) return;
container.style.opacity = "0.7";
container.style.pointerEvents = "none";
// 获取当前URL去掉可能的page参数
var currentUrl = window.location.pathname;
// 使用AJAX加载内容
var xhr = new XMLHttpRequest();
xhr.open("GET", currentUrl + "?thoughts_page=1&page=" + page, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
container.style.opacity = "1";
container.style.pointerEvents = "auto";
if (xhr.status === 200) {
// 解析HTML只替换内容部分
var parser = new DOMParser();
var doc = parser.parseFromString(xhr.responseText, "text/html");
var newContent = doc.querySelector(".thoughts-page-container");
if (newContent) {
container.innerHTML = newContent.innerHTML;
// 更新URL不刷新页面
if (page === 1) {
history.pushState({page: page}, "", currentUrl);
} else {
history.pushState({page: page}, "", currentUrl + "?page=" + page);
}
// 滚动到顶部
window.scrollTo({top: 0, behavior: "smooth"});
// 重新绑定分页事件
bindPaginationEvents();
}
}
}
};
xhr.send();
}
function bindPaginationEvents() {
// 重新绑定分页链接的点击事件
var pageLinks = document.querySelectorAll(".thoughts-pagination a[onclick]");
pageLinks.forEach(function(link) {
var oldOnClick = link.getAttribute("onclick");
link.removeAttribute("onclick");
link.addEventListener("click", function(e) {
e.preventDefault();
eval(oldOnClick);
});
});
}
// 初始化绑定
document.addEventListener("DOMContentLoaded", function() {
bindPaginationEvents();
// 处理浏览器前进后退
window.addEventListener("popstate", function(event) {
if (event.state && event.state.page) {
loadPageContent(event.state.page);
}
});
});
</script>';
}
}
$html .= '</div>';
return $html;
}
/**
* 检查当前用户是否为管理员
*/
public static function isAdmin()
{
$user = Typecho_Widget::widget('Widget_User');
return $user->hasLogin() && $user->pass('administrator', true);
}
/**
* 获取感想数量
*/
public static function getThoughtsCount($cid = null)
{
if (!$cid) {
$widget = Typecho_Widget::widget('Widget_Archive');
$cid = $widget->cid;
}
if (!$cid) return 0;
$db = Typecho_Db::get();
$select = $db->select('COUNT(*) as count')
->from('table.thoughts')
->where('cid = ?', $cid)
->where('status = ?', 'approved');
$result = $db->fetchRow($select);
return $result ? intval($result['count']) : 0;
}
}
/**
* 助手类,用于模板中调用
*/
class ThoughtsPlugin
{
/**
* 显示感想列表(文章页)
*/
public static function showThoughts($cid = null)
{
return ThoughtsPlugin_Plugin::renderThoughts($cid);
}
/**
* 显示独立页面(全部感想)- 修复分页参数传递
*/
public static function showThoughtsPage($page = 1, $perPage = null)
{
// 设置标记让CSS生效
$_GET['thoughts_page'] = true;
// 优先从URL参数获取页码解决Typecho路由问题
if (isset($_GET['page']) && is_numeric($_GET['page'])) {
$page = max(1, intval($_GET['page']));
}
return ThoughtsPlugin_Plugin::renderThoughtsPage($page, $perPage);
}
/**
* 检查是否是管理员
*/
public static function isAdmin()
{
return ThoughtsPlugin_Plugin::isAdmin();
}
/**
* 获取感想数量(单篇文章)
*/
public static function getCount($cid = null)
{
return ThoughtsPlugin_Plugin::getThoughtsCount($cid);
}
/**
* 获取感想总数(全部)
*/
public static function getTotalCount()
{
return ThoughtsPlugin_Plugin::getTotalThoughtsCount();
}
/**
* 获取感想列表数据
*/
public static function getList($cid = null)
{
// 删除limit参数改为全部显示
return ThoughtsPlugin_Plugin::getThoughts($cid);
}
}