Files
ThoughtsPlugin/Plugin.php
2026-02-23 20:00:41 +08:00

1471 lines
52 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
}