2267 lines
72 KiB
PHP
2267 lines
72 KiB
PHP
|
|
<?php
|
|||
|
|
/**
|
|||
|
|
* 有意思、有启发、有温度评论集锦
|
|||
|
|
*
|
|||
|
|
* @package RuiPing
|
|||
|
|
* @author 石头厝
|
|||
|
|
* @version 2.5.1
|
|||
|
|
* @link https://www.shitoucuo.com
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
class RuiPing_Plugin implements Typecho_Plugin_Interface
|
|||
|
|
{
|
|||
|
|
/**
|
|||
|
|
* 激活插件
|
|||
|
|
*/
|
|||
|
|
public static function activate()
|
|||
|
|
{
|
|||
|
|
Typecho_Plugin::factory('Widget_Archive')->header = array('RuiPing_Plugin', 'header');
|
|||
|
|
Typecho_Plugin::factory('Widget_Archive')->footer = array('RuiPing_Plugin', 'footer');
|
|||
|
|
|
|||
|
|
// 在后台页面添加代码
|
|||
|
|
Typecho_Plugin::factory('admin/header.php')->header = array('RuiPing_Plugin', 'addAdminHeader');
|
|||
|
|
Typecho_Plugin::factory('admin/footer.php')->end = array('RuiPing_Plugin', 'addAdminFooter');
|
|||
|
|
|
|||
|
|
// 使用Typecho标准的方式处理AJAX
|
|||
|
|
Typecho_Plugin::factory('Widget_Archive')->handleInit = array('RuiPing_Plugin', 'handleInit');
|
|||
|
|
|
|||
|
|
// 添加前端评论标识钩子
|
|||
|
|
Typecho_Plugin::factory('Widget_Archive')->beforeRender = array('RuiPing_Plugin', 'beforeRender');
|
|||
|
|
|
|||
|
|
return _t('锐评插件已激活');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 禁用插件
|
|||
|
|
*/
|
|||
|
|
public static function deactivate()
|
|||
|
|
{
|
|||
|
|
return _t('锐评插件已禁用');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 插件配置面板
|
|||
|
|
*/
|
|||
|
|
public static function config(Typecho_Widget_Helper_Form $form)
|
|||
|
|
{
|
|||
|
|
// 锐评ID设置
|
|||
|
|
$commentIds = new Typecho_Widget_Helper_Form_Element_Textarea('commentIds',
|
|||
|
|
NULL,
|
|||
|
|
'',
|
|||
|
|
_t('锐评ID'),
|
|||
|
|
_t('每行一个评论ID,不限制数量。可以在评论管理页面找到评论ID'));
|
|||
|
|
$form->addInput($commentIds);
|
|||
|
|
|
|||
|
|
// 每页显示数量
|
|||
|
|
$limit = new Typecho_Widget_Helper_Form_Element_Text('limit',
|
|||
|
|
NULL,
|
|||
|
|
'10',
|
|||
|
|
_t('每页数量'),
|
|||
|
|
_t('每页显示的评论数量,超过此数量将分页显示'));
|
|||
|
|
$form->addInput($limit);
|
|||
|
|
|
|||
|
|
// 显示样式设置
|
|||
|
|
$showAuthor = new Typecho_Widget_Helper_Form_Element_Radio('showAuthor',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('显示'),
|
|||
|
|
'0' => _t('不显示')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('显示评论人'));
|
|||
|
|
$form->addInput($showAuthor);
|
|||
|
|
|
|||
|
|
$showDate = new Typecho_Widget_Helper_Form_Element_Radio('showDate',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('显示'),
|
|||
|
|
'0' => _t('不显示')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('显示评论时间'));
|
|||
|
|
$form->addInput($showDate);
|
|||
|
|
|
|||
|
|
$showContent = new Typecho_Widget_Helper_Form_Element_Radio('showContent',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('显示'),
|
|||
|
|
'0' => _t('不显示')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('显示评论内容'));
|
|||
|
|
$form->addInput($showContent);
|
|||
|
|
|
|||
|
|
$showSource = new Typecho_Widget_Helper_Form_Element_Radio('showSource',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('显示'),
|
|||
|
|
'0' => _t('不显示')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('显示来源文章'));
|
|||
|
|
$form->addInput($showSource);
|
|||
|
|
|
|||
|
|
// 新增:显示原文设置
|
|||
|
|
$showOriginal = new Typecho_Widget_Helper_Form_Element_Radio('showOriginal',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('显示'),
|
|||
|
|
'0' => _t('不显示')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('显示原文链接'));
|
|||
|
|
$form->addInput($showOriginal);
|
|||
|
|
|
|||
|
|
// 链接设置
|
|||
|
|
$authorLink = new Typecho_Widget_Helper_Form_Element_Radio('authorLink',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('新窗口打开'),
|
|||
|
|
'0' => _t('当前窗口打开')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('作者链接打开方式'));
|
|||
|
|
$form->addInput($authorLink);
|
|||
|
|
|
|||
|
|
$commentLink = new Typecho_Widget_Helper_Form_Element_Radio('commentLink',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('新窗口打开'),
|
|||
|
|
'0' => _t('当前窗口打开')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('评论链接打开方式'));
|
|||
|
|
$form->addInput($commentLink);
|
|||
|
|
|
|||
|
|
// 分页设置
|
|||
|
|
$showPagination = new Typecho_Widget_Helper_Form_Element_Radio('showPagination',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('显示'),
|
|||
|
|
'0' => _t('不显示')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('显示分页'));
|
|||
|
|
$form->addInput($showPagination);
|
|||
|
|
|
|||
|
|
// 调试模式
|
|||
|
|
$debugMode = new Typecho_Widget_Helper_Form_Element_Radio('debugMode',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('开启'),
|
|||
|
|
'0' => _t('关闭')
|
|||
|
|
),
|
|||
|
|
'0',
|
|||
|
|
_t('调试模式'),
|
|||
|
|
_t('开启后显示调试信息,用于排查问题'));
|
|||
|
|
$form->addInput($debugMode);
|
|||
|
|
|
|||
|
|
// 新增:前端锐评标识设置
|
|||
|
|
$showFrontendBadge = new Typecho_Widget_Helper_Form_Element_Radio('showFrontendBadge',
|
|||
|
|
array(
|
|||
|
|
'1' => _t('显示'),
|
|||
|
|
'0' => _t('不显示')
|
|||
|
|
),
|
|||
|
|
'1',
|
|||
|
|
_t('前端锐评标识'),
|
|||
|
|
_t('在文章评论区中为锐评评论显示标识'));
|
|||
|
|
$form->addInput($showFrontendBadge);
|
|||
|
|
|
|||
|
|
$frontendBadgePosition = new Typecho_Widget_Helper_Form_Element_Select('frontendBadgePosition',
|
|||
|
|
array(
|
|||
|
|
'content-top-right' => _t('内容右上角'),
|
|||
|
|
'content-top-left' => _t('内容左上角'),
|
|||
|
|
'comment-top-right' => _t('评论整体右上角'),
|
|||
|
|
'after-author' => _t('作者名后')
|
|||
|
|
),
|
|||
|
|
'content-top-right',
|
|||
|
|
_t('标识位置'));
|
|||
|
|
$form->addInput($frontendBadgePosition);
|
|||
|
|
|
|||
|
|
$frontendBadgeText = new Typecho_Widget_Helper_Form_Element_Text('frontendBadgeText',
|
|||
|
|
NULL,
|
|||
|
|
'❤ 锐评',
|
|||
|
|
_t('标识文字'));
|
|||
|
|
$form->addInput($frontendBadgeText);
|
|||
|
|
|
|||
|
|
$frontendBadgeColor = new Typecho_Widget_Helper_Form_Element_Text('frontendBadgeColor',
|
|||
|
|
NULL,
|
|||
|
|
'#d32f2f',
|
|||
|
|
_t('标识颜色'));
|
|||
|
|
$form->addInput($frontendBadgeColor);
|
|||
|
|
|
|||
|
|
// 备份和恢复设置
|
|||
|
|
echo '<div class="typecho-page-title">
|
|||
|
|
<h2>锐评插件 - 设置备份与恢复</h2>
|
|||
|
|
</div>';
|
|||
|
|
|
|||
|
|
// 备份设置
|
|||
|
|
$backupBtn = new Typecho_Widget_Helper_Form_Element_Submit('backupBtn', NULL, _t('备份当前设置'),
|
|||
|
|
_t('将当前插件设置备份到服务器'));
|
|||
|
|
$backupBtn->input->setAttribute('class', 'btn primary');
|
|||
|
|
$backupBtn->input->setAttribute('onclick', 'heartfeltBackupSettings()');
|
|||
|
|
$form->addItem($backupBtn);
|
|||
|
|
|
|||
|
|
// 恢复设置
|
|||
|
|
$restoreBtn = new Typecho_Widget_Helper_Form_Element_Submit('restoreBtn', NULL, _t('从备份恢复'),
|
|||
|
|
_t('从最近的备份恢复插件设置'));
|
|||
|
|
$restoreBtn->input->setAttribute('class', 'btn');
|
|||
|
|
$restoreBtn->input->setAttribute('onclick', 'heartfeltRestoreSettings()');
|
|||
|
|
$form->addItem($restoreBtn);
|
|||
|
|
|
|||
|
|
// 下载备份
|
|||
|
|
$downloadBtn = new Typecho_Widget_Helper_Form_Element_Submit('downloadBtn', NULL, _t('下载备份文件'),
|
|||
|
|
_t('下载当前设置的备份文件'));
|
|||
|
|
$downloadBtn->input->setAttribute('class', 'btn');
|
|||
|
|
$downloadBtn->input->setAttribute('onclick', 'heartfeltDownloadBackup()');
|
|||
|
|
$form->addItem($downloadBtn);
|
|||
|
|
|
|||
|
|
// 上传备份
|
|||
|
|
$uploadArea = new Typecho_Widget_Helper_Form_Element_Textarea('uploadBackup', NULL, '',
|
|||
|
|
_t('上传备份文件'),
|
|||
|
|
_t('粘贴备份文件内容或上传备份文件进行恢复'));
|
|||
|
|
$form->addInput($uploadArea);
|
|||
|
|
|
|||
|
|
// 添加JavaScript
|
|||
|
|
self::addBackupScript();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 个人用户配置面板
|
|||
|
|
*/
|
|||
|
|
public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理初始化
|
|||
|
|
*/
|
|||
|
|
public static function handleInit()
|
|||
|
|
{
|
|||
|
|
// 检查是否是我们的AJAX请求
|
|||
|
|
if (isset($_GET['action']) && $_GET['action'] == 'heartfelt-comments') {
|
|||
|
|
self::handleAjax();
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理获取锐评ID列表的请求(前端使用)
|
|||
|
|
if (isset($_GET['action']) && $_GET['action'] == 'ruiping-get-ids') {
|
|||
|
|
self::getHeartfeltIds();
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理备份相关的AJAX请求
|
|||
|
|
if (isset($_POST['heartfelt_backup_action'])) {
|
|||
|
|
self::handleBackupAjax();
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理下载备份请求
|
|||
|
|
if (isset($_GET['heartfelt_backup_action']) && $_GET['heartfelt_backup_action'] == 'download') {
|
|||
|
|
self::downloadBackup();
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理批量操作请求
|
|||
|
|
if (isset($_POST['heartfelt_batch_action'])) {
|
|||
|
|
self::handleBatchAction();
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理锐评筛选页面
|
|||
|
|
if (isset($_GET['heartfelt_filter'])) {
|
|||
|
|
self::handleHeartfeltFilter();
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 获取锐评ID列表(前端使用)
|
|||
|
|
*/
|
|||
|
|
private static function getHeartfeltIds()
|
|||
|
|
{
|
|||
|
|
header('Content-Type: application/json; charset=utf-8');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$config = $options->plugin('RuiPing');
|
|||
|
|
|
|||
|
|
if (!$config) {
|
|||
|
|
echo json_encode(array('success' => true, 'ids' => array()));
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$ids = self::getCommentIds($config->commentIds);
|
|||
|
|
|
|||
|
|
echo json_encode(array(
|
|||
|
|
'success' => true,
|
|||
|
|
'ids' => $ids,
|
|||
|
|
'showFrontendBadge' => isset($config->showFrontendBadge) ? $config->showFrontendBadge : '1',
|
|||
|
|
'badgeText' => isset($config->frontendBadgeText) ? $config->frontendBadgeText : '❤ 锐评',
|
|||
|
|
'badgePosition' => isset($config->frontendBadgePosition) ? $config->frontendBadgePosition : 'content-top-right',
|
|||
|
|
'badgeColor' => isset($config->frontendBadgeColor) ? $config->frontendBadgeColor : '#d32f2f',
|
|||
|
|
// 新增:跳转链接和新窗口配置
|
|||
|
|
'badgeLink' => 'https://www.shitoucuo.com/rp.html', // 固定跳转链接
|
|||
|
|
'openInNewWindow' => true // 固定新窗口打开
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
echo json_encode(array(
|
|||
|
|
'success' => false,
|
|||
|
|
'message' => $e->getMessage(),
|
|||
|
|
'ids' => array()
|
|||
|
|
));
|
|||
|
|
}
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 页面渲染前执行(添加前端标识)
|
|||
|
|
*/
|
|||
|
|
public static function beforeRender()
|
|||
|
|
{
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$config = $options->plugin('RuiPing');
|
|||
|
|
|
|||
|
|
// 检查是否启用前端标识
|
|||
|
|
if (!$config || (isset($config->showFrontendBadge) && $config->showFrontendBadge == '0')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 只在前台文章页面添加
|
|||
|
|
if (Typecho_Widget::widget('Widget_Archive')->is('single')) {
|
|||
|
|
// 添加JavaScript到页面
|
|||
|
|
$ajaxUrl = Typecho_Common::url('index.php?action=ruiping-get-ids', $options->siteUrl);
|
|||
|
|
$badgeText = isset($config->frontendBadgeText) ? htmlspecialchars($config->frontendBadgeText) : '❤ 锐评';
|
|||
|
|
$badgePosition = isset($config->frontendBadgePosition) ? $config->frontendBadgePosition : 'content-top-right';
|
|||
|
|
$badgeColor = isset($config->frontendBadgeColor) ? $config->frontendBadgeColor : '#d32f2f';
|
|||
|
|
$badgeLink = 'https://www.shitoucuo.com/rp.html'; // 固定跳转链接
|
|||
|
|
$openInNewWindow = true; // 固定新窗口打开
|
|||
|
|
|
|||
|
|
$js = <<<JS
|
|||
|
|
<script>
|
|||
|
|
(function() {
|
|||
|
|
// 等待页面加载完成
|
|||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|||
|
|
// 获取锐评ID列表
|
|||
|
|
fetch('{$ajaxUrl}')
|
|||
|
|
.then(response => response.json())
|
|||
|
|
.then(data => {
|
|||
|
|
if (data.success && data.ids && data.ids.length > 0) {
|
|||
|
|
// 标记锐评评论
|
|||
|
|
markHeartfeltComments(data.ids, {
|
|||
|
|
badgeText: data.badgeText || '{$badgeText}',
|
|||
|
|
badgePosition: data.badgePosition || '{$badgePosition}',
|
|||
|
|
badgeColor: data.badgeColor || '{$badgeColor}',
|
|||
|
|
badgeLink: data.badgeLink || '{$badgeLink}',
|
|||
|
|
openInNewWindow: data.openInNewWindow || {$openInNewWindow}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
console.log('锐评插件:获取ID失败', error);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 标记锐评评论
|
|||
|
|
* @param {Array} heartfeltIds 锐评ID数组
|
|||
|
|
* @param {Object} options 配置选项
|
|||
|
|
*/
|
|||
|
|
function markHeartfeltComments(heartfeltIds, options) {
|
|||
|
|
if (!heartfeltIds || heartfeltIds.length === 0) return;
|
|||
|
|
|
|||
|
|
heartfeltIds = heartfeltIds.map(id => parseInt(id));
|
|||
|
|
|
|||
|
|
// 查找所有评论项
|
|||
|
|
const commentItems = document.querySelectorAll('li[id^="comment-"], li.comment-body, .comment-body');
|
|||
|
|
|
|||
|
|
commentItems.forEach(item => {
|
|||
|
|
// 从ID或数据属性中提取评论ID
|
|||
|
|
let commentId = null;
|
|||
|
|
|
|||
|
|
// 尝试从id属性获取
|
|||
|
|
const idAttr = item.getAttribute('id');
|
|||
|
|
if (idAttr) {
|
|||
|
|
const match = idAttr.match(/comment-(\d+)/);
|
|||
|
|
if (match && match[1]) {
|
|||
|
|
commentId = parseInt(match[1]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果找到了评论ID且是锐评,添加标识
|
|||
|
|
if (commentId && heartfeltIds.includes(commentId)) {
|
|||
|
|
addHeartfeltBadge(item, options, commentId);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加锐评标识
|
|||
|
|
* @param {HTMLElement} commentElement 评论元素
|
|||
|
|
* @param {Object} options 配置选项
|
|||
|
|
* @param {Number} commentId 评论ID
|
|||
|
|
*/
|
|||
|
|
function addHeartfeltBadge(commentElement, options, commentId) {
|
|||
|
|
// 检查是否已经添加过标识
|
|||
|
|
if (document.querySelector('.heartfelt-frontend-badge[data-comment-id="' + commentId + '"]')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据位置选择目标元素
|
|||
|
|
const badgePosition = options.badgePosition || 'content-top-right';
|
|||
|
|
let targetElement = null;
|
|||
|
|
let isContentTarget = false;
|
|||
|
|
|
|||
|
|
if (badgePosition.includes('content')) {
|
|||
|
|
// 找评论内容容器 - 针对你的评论模板
|
|||
|
|
const contentElements = commentElement.querySelectorAll('.comment-content, .cm');
|
|||
|
|
if (contentElements.length > 0) {
|
|||
|
|
targetElement = contentElements[0];
|
|||
|
|
isContentTarget = true;
|
|||
|
|
} else {
|
|||
|
|
// 如果没有找到特定的内容容器,使用整个评论元素
|
|||
|
|
targetElement = commentElement;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
targetElement = commentElement;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建标识容器
|
|||
|
|
const badge = document.createElement('span');
|
|||
|
|
badge.className = 'heartfelt-frontend-badge';
|
|||
|
|
badge.setAttribute('data-comment-id', commentId);
|
|||
|
|
|
|||
|
|
// 判断是否有链接,如果有则创建a标签,否则直接显示文本
|
|||
|
|
if (options.badgeLink) {
|
|||
|
|
// 创建可点击的链接
|
|||
|
|
const link = document.createElement('a');
|
|||
|
|
link.href = options.badgeLink;
|
|||
|
|
if (options.openInNewWindow) {
|
|||
|
|
link.target = '_blank';
|
|||
|
|
link.rel = 'noopener noreferrer';
|
|||
|
|
}
|
|||
|
|
link.textContent = options.badgeText || '❤ 锐评';
|
|||
|
|
link.style.color = '#fff';
|
|||
|
|
link.style.textDecoration = 'none'; // 移除下划线装饰
|
|||
|
|
link.style.display = 'block';
|
|||
|
|
link.title = '点击可以跳转锐评汇总页面喔'; // 添加title提示
|
|||
|
|
|
|||
|
|
badge.appendChild(link);
|
|||
|
|
badge.style.cursor = 'pointer'; // 显示手型光标
|
|||
|
|
} else {
|
|||
|
|
// 没有链接,直接显示文本
|
|||
|
|
badge.textContent = options.badgeText || '❤ 锐评';
|
|||
|
|
badge.style.cursor = 'default';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
badge.style.backgroundColor = options.badgeColor || '#d32f2f';
|
|||
|
|
|
|||
|
|
// 设置标识位置
|
|||
|
|
switch (badgePosition) {
|
|||
|
|
case 'content-top-right':
|
|||
|
|
badge.style.position = 'absolute';
|
|||
|
|
badge.style.top = '8px';
|
|||
|
|
badge.style.right = '8px';
|
|||
|
|
badge.style.zIndex = '100';
|
|||
|
|
// 确保目标元素有相对定位
|
|||
|
|
if (window.getComputedStyle(targetElement).position === 'static') {
|
|||
|
|
targetElement.style.position = 'relative';
|
|||
|
|
}
|
|||
|
|
// 添加一些内边距避免标识覆盖内容
|
|||
|
|
const currentPadding = window.getComputedStyle(targetElement).paddingRight;
|
|||
|
|
if (currentPadding && currentPadding !== '0px') {
|
|||
|
|
const paddingValue = parseFloat(currentPadding);
|
|||
|
|
targetElement.style.paddingRight = (paddingValue + 50) + 'px';
|
|||
|
|
} else {
|
|||
|
|
targetElement.style.paddingRight = '50px';
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'content-top-left':
|
|||
|
|
badge.style.position = 'absolute';
|
|||
|
|
badge.style.top = '8px';
|
|||
|
|
badge.style.left = '8px';
|
|||
|
|
badge.style.zIndex = '100';
|
|||
|
|
if (window.getComputedStyle(targetElement).position === 'static') {
|
|||
|
|
targetElement.style.position = 'relative';
|
|||
|
|
}
|
|||
|
|
const currentPaddingLeft = window.getComputedStyle(targetElement).paddingLeft;
|
|||
|
|
if (currentPaddingLeft && currentPaddingLeft !== '0px') {
|
|||
|
|
const paddingValue = parseFloat(currentPaddingLeft);
|
|||
|
|
targetElement.style.paddingLeft = (paddingValue + 50) + 'px';
|
|||
|
|
} else {
|
|||
|
|
targetElement.style.paddingLeft = '50px';
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'comment-top-right':
|
|||
|
|
badge.style.position = 'absolute';
|
|||
|
|
badge.style.top = '10px';
|
|||
|
|
badge.style.right = '10px';
|
|||
|
|
badge.style.zIndex = '100';
|
|||
|
|
if (window.getComputedStyle(commentElement).position === 'static') {
|
|||
|
|
commentElement.style.position = 'relative';
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'after-author':
|
|||
|
|
// 找到作者名元素,添加到后面
|
|||
|
|
const authorElements = commentElement.querySelectorAll('.author, .author-name, .comment-author, .auhui .author-name');
|
|||
|
|
if (authorElements.length > 0) {
|
|||
|
|
targetElement = authorElements[0];
|
|||
|
|
badge.style.marginLeft = '8px';
|
|||
|
|
badge.style.display = 'inline-block';
|
|||
|
|
badge.style.verticalAlign = 'middle';
|
|||
|
|
} else {
|
|||
|
|
badge.style.position = 'relative';
|
|||
|
|
badge.style.display = 'inline-block';
|
|||
|
|
badge.style.marginBottom = '5px';
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 通用样式
|
|||
|
|
badge.style.color = '#fff';
|
|||
|
|
badge.style.padding = '3px 8px';
|
|||
|
|
badge.style.borderRadius = '4px';
|
|||
|
|
badge.style.fontSize = '12px';
|
|||
|
|
badge.style.fontWeight = '600';
|
|||
|
|
badge.style.lineHeight = '1.2';
|
|||
|
|
badge.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
|
|||
|
|
badge.style.transition = 'all 0.3s ease';
|
|||
|
|
badge.style.textShadow = '1px 1px 1px rgba(0,0,0,0.2)';
|
|||
|
|
badge.style.whiteSpace = 'nowrap';
|
|||
|
|
|
|||
|
|
// 添加悬停效果
|
|||
|
|
badge.addEventListener('mouseenter', function() {
|
|||
|
|
this.style.transform = 'scale(1.05)';
|
|||
|
|
this.style.boxShadow = '0 4px 10px rgba(0,0,0,0.3)';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
badge.addEventListener('mouseleave', function() {
|
|||
|
|
this.style.transform = 'scale(1)';
|
|||
|
|
this.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加到元素
|
|||
|
|
targetElement.appendChild(badge);
|
|||
|
|
|
|||
|
|
// 添加淡入动画
|
|||
|
|
badge.style.opacity = '0';
|
|||
|
|
badge.style.transform = 'translateY(-5px)';
|
|||
|
|
setTimeout(() => {
|
|||
|
|
badge.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
|||
|
|
badge.style.opacity = '1';
|
|||
|
|
badge.style.transform = 'translateY(0)';
|
|||
|
|
}, 10);
|
|||
|
|
}
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
JS;
|
|||
|
|
|
|||
|
|
// 输出JavaScript
|
|||
|
|
echo $js;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理锐评筛选
|
|||
|
|
*/
|
|||
|
|
public static function handleHeartfeltFilter()
|
|||
|
|
{
|
|||
|
|
$filter = isset($_GET['heartfelt_filter']) ? $_GET['heartfelt_filter'] : 'all';
|
|||
|
|
|
|||
|
|
if ($filter == 'heartfelt') {
|
|||
|
|
// 重定向到锐评专用页面
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$config = $options->plugin('RuiPing');
|
|||
|
|
|
|||
|
|
if ($config) {
|
|||
|
|
$commentIds = self::getCommentIds($config->commentIds);
|
|||
|
|
if (!empty($commentIds)) {
|
|||
|
|
// 构建评论ID参数
|
|||
|
|
$coidParams = '';
|
|||
|
|
foreach ($commentIds as $id) {
|
|||
|
|
$coidParams .= '&coid[]=' . $id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$redirectUrl = Typecho_Common::url('manage-comments.php', $options->adminUrl) . '?' . $coidParams;
|
|||
|
|
header('Location: ' . $redirectUrl);
|
|||
|
|
exit;
|
|||
|
|
} else {
|
|||
|
|
// 如果没有锐评,显示提示信息
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$redirectUrl = Typecho_Common::url('manage-comments.php', $options->adminUrl);
|
|||
|
|
header('Location: ' . $redirectUrl);
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 默认返回评论管理页面
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$redirectUrl = Typecho_Common::url('manage-comments.php', $options->adminUrl);
|
|||
|
|
header('Location: ' . $redirectUrl);
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理批量操作
|
|||
|
|
*/
|
|||
|
|
public static function handleBatchAction()
|
|||
|
|
{
|
|||
|
|
// 验证用户权限
|
|||
|
|
$user = Typecho_Widget::widget('Widget_User');
|
|||
|
|
if (!$user->hasLogin() || !$user->pass('administrator', true)) {
|
|||
|
|
self::jsonResponse(false, '权限不足');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$action = $_POST['heartfelt_batch_action'];
|
|||
|
|
$commentIds = isset($_POST['comment_ids']) ? $_POST['comment_ids'] : array();
|
|||
|
|
|
|||
|
|
if (empty($commentIds)) {
|
|||
|
|
self::jsonResponse(false, '请选择要操作的评论');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
|
|||
|
|
// 获取当前插件配置
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$config = $options->plugin('RuiPing');
|
|||
|
|
|
|||
|
|
if (!$config) {
|
|||
|
|
$config = new stdClass();
|
|||
|
|
$config->commentIds = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$currentIds = self::getCommentIds($config->commentIds);
|
|||
|
|
|
|||
|
|
switch ($action) {
|
|||
|
|
case 'add':
|
|||
|
|
foreach ($commentIds as $commentId) {
|
|||
|
|
$commentId = intval($commentId);
|
|||
|
|
if ($commentId > 0 && !in_array($commentId, $currentIds)) {
|
|||
|
|
$currentIds[] = $commentId;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
$message = '已批量设置为锐评';
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'remove':
|
|||
|
|
$currentIds = array_filter($currentIds, function($id) use ($commentIds) {
|
|||
|
|
return !in_array($id, $commentIds);
|
|||
|
|
});
|
|||
|
|
$message = '已批量取消锐评';
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
self::jsonResponse(false, '未知操作');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新配置
|
|||
|
|
$newValue = implode("\n", $currentIds);
|
|||
|
|
$existingConfig = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
|
|||
|
|
if ($existingConfig) {
|
|||
|
|
$existingConfigArray = unserialize($existingConfig['value']);
|
|||
|
|
$existingConfigArray['commentIds'] = $newValue;
|
|||
|
|
|
|||
|
|
$updateResult = $db->query($db->update('table.options')
|
|||
|
|
->rows(array('value' => serialize($existingConfigArray)))
|
|||
|
|
->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self::jsonResponse(true, $message, array(
|
|||
|
|
'action' => $action,
|
|||
|
|
'count' => count($commentIds)
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
self::jsonResponse(false, '操作失败: ' . $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理备份相关的AJAX请求
|
|||
|
|
*/
|
|||
|
|
public static function handleBackupAjax()
|
|||
|
|
{
|
|||
|
|
// 验证用户权限
|
|||
|
|
$user = Typecho_Widget::widget('Widget_User');
|
|||
|
|
if (!$user->hasLogin() || !$user->pass('administrator', true)) {
|
|||
|
|
self::jsonResponse(false, '权限不足');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$action = $_POST['heartfelt_backup_action'];
|
|||
|
|
|
|||
|
|
switch ($action) {
|
|||
|
|
case 'backup':
|
|||
|
|
self::backupSettings();
|
|||
|
|
break;
|
|||
|
|
case 'restore':
|
|||
|
|
self::restoreSettings();
|
|||
|
|
break;
|
|||
|
|
case 'upload':
|
|||
|
|
self::uploadBackup();
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
self::jsonResponse(false, '未知操作');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 备份设置
|
|||
|
|
*/
|
|||
|
|
private static function backupSettings()
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
|
|||
|
|
// 获取当前插件设置
|
|||
|
|
$config = $db->fetchRow($db->select()->from('table.options')
|
|||
|
|
->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
|
|||
|
|
if (!$config) {
|
|||
|
|
self::jsonResponse(false, '没有找到插件设置');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建备份
|
|||
|
|
$backupData = array(
|
|||
|
|
'plugin_config' => $config['value'],
|
|||
|
|
'backup_time' => time(),
|
|||
|
|
'backup_version' => '2.5.1',
|
|||
|
|
'site_url' => Helper::options()->siteUrl
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
$backupContent = serialize($backupData);
|
|||
|
|
|
|||
|
|
// 使用更短的名字避免字段长度限制
|
|||
|
|
$backupName = 'heartfelt_bak_' . date('Ymd_His');
|
|||
|
|
|
|||
|
|
// 保存备份到数据库
|
|||
|
|
$existingBackup = $db->fetchRow($db->select()->from('table.options')
|
|||
|
|
->where('name = ?', $backupName));
|
|||
|
|
|
|||
|
|
if ($existingBackup) {
|
|||
|
|
$db->query($db->update('table.options')
|
|||
|
|
->rows(array('value' => $backupContent))
|
|||
|
|
->where('name = ?', $backupName));
|
|||
|
|
} else {
|
|||
|
|
$db->query($db->insert('table.options')
|
|||
|
|
->rows(array(
|
|||
|
|
'name' => $backupName,
|
|||
|
|
'user' => '0',
|
|||
|
|
'value' => $backupContent
|
|||
|
|
)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理旧的备份,只保留最近的5个
|
|||
|
|
self::cleanOldBackups();
|
|||
|
|
|
|||
|
|
self::jsonResponse(true, '设置备份成功', array('backup_name' => $backupName));
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
self::jsonResponse(false, '备份失败: ' . $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 恢复设置
|
|||
|
|
*/
|
|||
|
|
private static function restoreSettings()
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
|
|||
|
|
// 获取最新的备份
|
|||
|
|
$backups = $db->fetchAll($db->select()->from('table.options')
|
|||
|
|
->where('name LIKE ?', 'heartfelt_bak_%')
|
|||
|
|
->order('name', Typecho_Db::SORT_DESC)
|
|||
|
|
->limit(1));
|
|||
|
|
|
|||
|
|
if (empty($backups)) {
|
|||
|
|
self::jsonResponse(false, '没有找到备份文件');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$backup = $backups[0];
|
|||
|
|
$backupData = unserialize($backup['value']);
|
|||
|
|
|
|||
|
|
if (!$backupData || !isset($backupData['plugin_config'])) {
|
|||
|
|
self::jsonResponse(false, '备份文件格式错误');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 恢复插件设置
|
|||
|
|
$existingConfig = $db->fetchRow($db->select()->from('table.options')
|
|||
|
|
->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
|
|||
|
|
if ($existingConfig) {
|
|||
|
|
$db->query($db->update('table.options')
|
|||
|
|
->rows(array('value' => $backupData['plugin_config']))
|
|||
|
|
->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
} else {
|
|||
|
|
$db->query($db->insert('table.options')
|
|||
|
|
->rows(array(
|
|||
|
|
'name' => 'plugin:RuiPing',
|
|||
|
|
'user' => '0',
|
|||
|
|
'value' => $backupData['plugin_config']
|
|||
|
|
)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self::jsonResponse(true, '设置恢复成功', array(
|
|||
|
|
'backup_time' => date('Y-m-d H:i:s', $backupData['backup_time'])
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
self::jsonResponse(false, '恢复失败: ' . $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 下载备份
|
|||
|
|
*/
|
|||
|
|
private static function downloadBackup()
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
// 验证用户权限
|
|||
|
|
$user = Typecho_Widget::widget('Widget_User');
|
|||
|
|
if (!$user->hasLogin() || !$user->pass('administrator', true)) {
|
|||
|
|
die('权限不足');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
|
|||
|
|
// 获取最新的备份
|
|||
|
|
$backups = $db->fetchAll($db->select()->from('table.options')
|
|||
|
|
->where('name LIKE ?', 'heartfelt_bak_%')
|
|||
|
|
->order('name', Typecho_Db::SORT_DESC)
|
|||
|
|
->limit(1));
|
|||
|
|
|
|||
|
|
if (empty($backups)) {
|
|||
|
|
die('没有找到备份文件');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$backup = $backups[0];
|
|||
|
|
$backupData = unserialize($backup['value']);
|
|||
|
|
|
|||
|
|
if (!$backupData || !isset($backupData['backup_time'])) {
|
|||
|
|
die('备份文件格式错误');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$filename = "heartfelt_backup_" . date('Y-m-d_H-i-s', $backupData['backup_time']) . ".dat";
|
|||
|
|
|
|||
|
|
header('Content-Type: application/octet-stream');
|
|||
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
|||
|
|
header('Content-Length: ' . strlen($backup['value']));
|
|||
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|||
|
|
header('Pragma: no-cache');
|
|||
|
|
header('Expires: 0');
|
|||
|
|
|
|||
|
|
echo $backup['value'];
|
|||
|
|
exit;
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
die('下载失败: ' . $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 上传备份
|
|||
|
|
*/
|
|||
|
|
private static function uploadBackup()
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$backupContent = isset($_POST['backup_content']) ? $_POST['backup_content'] : '';
|
|||
|
|
|
|||
|
|
if (empty($backupContent)) {
|
|||
|
|
self::jsonResponse(false, '备份内容为空');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证备份内容
|
|||
|
|
$backupData = @unserialize($backupContent);
|
|||
|
|
if (!$backupData || !isset($backupData['plugin_config'])) {
|
|||
|
|
self::jsonResponse(false, '备份文件格式错误');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
|
|||
|
|
// 保存备份
|
|||
|
|
$backupName = 'heartfelt_upload_' . date('Ymd_His');
|
|||
|
|
$db->query($db->insert('table.options')
|
|||
|
|
->rows(array(
|
|||
|
|
'name' => $backupName,
|
|||
|
|
'user' => '0',
|
|||
|
|
'value' => $backupContent
|
|||
|
|
)));
|
|||
|
|
|
|||
|
|
// 恢复设置
|
|||
|
|
$existingConfig = $db->fetchRow($db->select()->from('table.options')
|
|||
|
|
->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
|
|||
|
|
if ($existingConfig) {
|
|||
|
|
$db->query($db->update('table.options')
|
|||
|
|
->rows(array('value' => $backupData['plugin_config']))
|
|||
|
|
->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
} else {
|
|||
|
|
$db->query($db->insert('table.options')
|
|||
|
|
->rows(array(
|
|||
|
|
'name' => 'plugin:RuiPing',
|
|||
|
|
'user' => '0',
|
|||
|
|
'value' => $backupData['plugin_config']
|
|||
|
|
)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self::jsonResponse(true, '备份上传并恢复成功');
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
self::jsonResponse(false, '上传失败: ' . $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清理旧的备份
|
|||
|
|
*/
|
|||
|
|
private static function cleanOldBackups()
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
|
|||
|
|
$backups = $db->fetchAll($db->select()->from('table.options')
|
|||
|
|
->where('name LIKE ?', 'heartfelt_bak_%')
|
|||
|
|
->order('name', Typecho_Db::SORT_DESC));
|
|||
|
|
|
|||
|
|
if (count($backups) > 5) {
|
|||
|
|
for ($i = 5; $i < count($backups); $i++) {
|
|||
|
|
$db->query($db->delete('table.options')
|
|||
|
|
->where('name = ?', $backups[$i]['name']));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
// 忽略清理错误
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加备份功能相关的JavaScript
|
|||
|
|
*/
|
|||
|
|
private static function addBackupScript()
|
|||
|
|
{
|
|||
|
|
$ajaxUrl = Helper::options()->index . '/action/ruiping-backup';
|
|||
|
|
?>
|
|||
|
|
<script>
|
|||
|
|
function heartfeltBackupSettings() {
|
|||
|
|
if (!confirm('确定要备份当前设置吗?')) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var $btn = jQuery('#backupBtn-btn');
|
|||
|
|
var originalText = $btn.val();
|
|||
|
|
$btn.val('备份中...').prop('disabled', true);
|
|||
|
|
|
|||
|
|
jQuery.ajax({
|
|||
|
|
url: '<?php echo $ajaxUrl; ?>',
|
|||
|
|
type: 'POST',
|
|||
|
|
dataType: 'json',
|
|||
|
|
data: {
|
|||
|
|
heartfelt_backup_action: 'backup'
|
|||
|
|
},
|
|||
|
|
success: function(response) {
|
|||
|
|
if (response.success) {
|
|||
|
|
alert('设置备份成功!');
|
|||
|
|
} else {
|
|||
|
|
alert('备份失败: ' + response.message);
|
|||
|
|
}
|
|||
|
|
$btn.val(originalText).prop('disabled', false);
|
|||
|
|
},
|
|||
|
|
error: function() {
|
|||
|
|
alert('网络错误,请重试');
|
|||
|
|
$btn.val(originalText).prop('disabled', false);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function heartfeltRestoreSettings() {
|
|||
|
|
if (!confirm('确定要从备份恢复设置吗?当前设置将被覆盖。')) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var $btn = jQuery('#restoreBtn-btn');
|
|||
|
|
var originalText = $btn.val();
|
|||
|
|
$btn.val('恢复中...').prop('disabled', true);
|
|||
|
|
|
|||
|
|
jQuery.ajax({
|
|||
|
|
url: '<?php echo $ajaxUrl; ?>',
|
|||
|
|
type: 'POST',
|
|||
|
|
dataType: 'json',
|
|||
|
|
data: {
|
|||
|
|
heartfelt_backup_action: 'restore'
|
|||
|
|
},
|
|||
|
|
success: function(response) {
|
|||
|
|
if (response.success) {
|
|||
|
|
alert('设置恢复成功!');
|
|||
|
|
setTimeout(function() {
|
|||
|
|
window.location.reload();
|
|||
|
|
}, 1000);
|
|||
|
|
} else {
|
|||
|
|
alert('恢复失败: ' + response.message);
|
|||
|
|
$btn.val(originalText).prop('disabled', false);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
error: function() {
|
|||
|
|
alert('网络错误,请重试');
|
|||
|
|
$btn.val(originalText).prop('disabled', false);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function heartfeltDownloadBackup() {
|
|||
|
|
// 直接打开下载链接
|
|||
|
|
var downloadUrl = '<?php echo Helper::options()->index; ?>?heartfelt_backup_action=download';
|
|||
|
|
window.open(downloadUrl, '_blank');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加上传备份功能
|
|||
|
|
jQuery(document).ready(function($) {
|
|||
|
|
$('textarea[name="uploadBackup"]').after(
|
|||
|
|
'<button type="button" class="btn" onclick="heartfeltUploadBackup()" style="margin-top: 5px;">上传并恢复备份</button>'
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
function heartfeltUploadBackup() {
|
|||
|
|
var backupContent = jQuery('textarea[name="uploadBackup"]').val();
|
|||
|
|
|
|||
|
|
if (!backupContent) {
|
|||
|
|
alert('请先粘贴备份文件内容');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!confirm('确定要上传并恢复此备份吗?当前设置将被覆盖。')) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var $btn = jQuery('button[onclick="heartfeltUploadBackup()"]');
|
|||
|
|
var originalText = $btn.text();
|
|||
|
|
$btn.text('上传中...').prop('disabled', true);
|
|||
|
|
|
|||
|
|
jQuery.ajax({
|
|||
|
|
url: '<?php echo $ajaxUrl; ?>',
|
|||
|
|
type: 'POST',
|
|||
|
|
dataType: 'json',
|
|||
|
|
data: {
|
|||
|
|
heartfelt_backup_action: 'upload',
|
|||
|
|
backup_content: backupContent
|
|||
|
|
},
|
|||
|
|
success: function(response) {
|
|||
|
|
if (response.success) {
|
|||
|
|
alert('备份上传并恢复成功!');
|
|||
|
|
setTimeout(function() {
|
|||
|
|
window.location.reload();
|
|||
|
|
}, 1000);
|
|||
|
|
} else {
|
|||
|
|
alert('上传失败: ' + response.message);
|
|||
|
|
$btn.text(originalText).prop('disabled', false);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
error: function() {
|
|||
|
|
alert('网络错误,请重试');
|
|||
|
|
$btn.text(originalText).prop('disabled', false);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
<?php
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理AJAX请求
|
|||
|
|
*/
|
|||
|
|
public static function handleAjax()
|
|||
|
|
{
|
|||
|
|
// 验证用户权限
|
|||
|
|
$user = Typecho_Widget::widget('Widget_User');
|
|||
|
|
if (!$user->hasLogin() || !$user->pass('administrator', true)) {
|
|||
|
|
self::jsonResponse(false, '权限不足');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$commentId = isset($_POST['commentId']) ? intval($_POST['commentId']) : 0;
|
|||
|
|
$action = isset($_POST['action']) ? $_POST['action'] : '';
|
|||
|
|
|
|||
|
|
if ($commentId <= 0 || !in_array($action, array('set', 'unset'))) {
|
|||
|
|
self::jsonResponse(false, '参数错误');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证评论是否存在
|
|||
|
|
$comment = $db->fetchRow($db->select()->from('table.comments')->where('coid = ?', $commentId));
|
|||
|
|
if (!$comment) {
|
|||
|
|
self::jsonResponse(false, '评论不存在');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前插件配置
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$config = $options->plugin('RuiPing');
|
|||
|
|
|
|||
|
|
if (!$config) {
|
|||
|
|
$config = new stdClass();
|
|||
|
|
$config->commentIds = '';
|
|||
|
|
$config->limit = '10';
|
|||
|
|
$config->showAuthor = '1';
|
|||
|
|
$config->showDate = '1';
|
|||
|
|
$config->showContent = '1';
|
|||
|
|
$config->showSource = '1';
|
|||
|
|
$config->showOriginal = '1';
|
|||
|
|
$config->authorLink = '1';
|
|||
|
|
$config->commentLink = '1';
|
|||
|
|
$config->showPagination = '1';
|
|||
|
|
$config->debugMode = '0';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$currentIds = self::getCommentIds($config->commentIds);
|
|||
|
|
|
|||
|
|
if ($action == 'set') {
|
|||
|
|
if (!in_array($commentId, $currentIds)) {
|
|||
|
|
$currentIds[] = $commentId;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
$currentIds = array_filter($currentIds, function($id) use ($commentId) {
|
|||
|
|
return $id != $commentId;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$newValue = implode("\n", $currentIds);
|
|||
|
|
$existingConfig = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
|
|||
|
|
if ($existingConfig) {
|
|||
|
|
$existingConfigArray = unserialize($existingConfig['value']);
|
|||
|
|
$existingConfigArray['commentIds'] = $newValue;
|
|||
|
|
|
|||
|
|
$updateResult = $db->query($db->update('table.options')
|
|||
|
|
->rows(array('value' => serialize($existingConfigArray)))
|
|||
|
|
->where('name = ?', 'plugin:RuiPing'));
|
|||
|
|
} else {
|
|||
|
|
$newConfig = array(
|
|||
|
|
'commentIds' => $newValue,
|
|||
|
|
'limit' => $config->limit ?: '10',
|
|||
|
|
'showAuthor' => $config->showAuthor ?: '1',
|
|||
|
|
'showDate' => $config->showDate ?: '1',
|
|||
|
|
'showContent' => $config->showContent ?: '1',
|
|||
|
|
'showSource' => $config->showSource ?: '1',
|
|||
|
|
'showOriginal' => $config->showOriginal ?: '1',
|
|||
|
|
'authorLink' => $config->authorLink ?: '1',
|
|||
|
|
'commentLink' => $config->commentLink ?: '1',
|
|||
|
|
'showPagination' => $config->showPagination ?: '1',
|
|||
|
|
'debugMode' => $config->debugMode ?: '0',
|
|||
|
|
'showFrontendBadge' => isset($config->showFrontendBadge) ? $config->showFrontendBadge : '1',
|
|||
|
|
'frontendBadgePosition' => isset($config->frontendBadgePosition) ? $config->frontendBadgePosition : 'content-top-right',
|
|||
|
|
'frontendBadgeText' => isset($config->frontendBadgeText) ? $config->frontendBadgeText : '❤ 锐评',
|
|||
|
|
'frontendBadgeColor' => isset($config->frontendBadgeColor) ? $config->frontendBadgeColor : '#d32f2f'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
$updateResult = $db->query($db->insert('table.options')
|
|||
|
|
->rows(array(
|
|||
|
|
'name' => 'plugin:RuiPing',
|
|||
|
|
'user' => '0',
|
|||
|
|
'value' => serialize($newConfig)
|
|||
|
|
)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($updateResult) {
|
|||
|
|
self::jsonResponse(true, $action == 'set' ? '已设置为锐评' : '已取消锐评', array(
|
|||
|
|
'commentId' => $commentId,
|
|||
|
|
'action' => $action,
|
|||
|
|
'isHeartfelt' => $action == 'set'
|
|||
|
|
));
|
|||
|
|
} else {
|
|||
|
|
self::jsonResponse(false, '更新配置失败');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加管理员头部
|
|||
|
|
*/
|
|||
|
|
public static function addAdminHeader()
|
|||
|
|
{
|
|||
|
|
if (self::isCommentsManagementPage()) {
|
|||
|
|
echo '<script>window.heartfeltPluginActive = true;</script>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加管理员底部
|
|||
|
|
*/
|
|||
|
|
public static function addAdminFooter()
|
|||
|
|
{
|
|||
|
|
if (!self::isCommentsManagementPage()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$config = $options->plugin('RuiPing');
|
|||
|
|
|
|||
|
|
if (!$config) {
|
|||
|
|
$currentIds = array();
|
|||
|
|
} else {
|
|||
|
|
$currentIds = self::getCommentIds($config->commentIds);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$currentIdsStr = implode(',', $currentIds);
|
|||
|
|
$ajaxUrl = Typecho_Common::url('index.php', $options->siteUrl) . '?action=heartfelt-comments';
|
|||
|
|
$batchAjaxUrl = Typecho_Common::url('index.php', $options->siteUrl);
|
|||
|
|
$heartfeltUrl = Typecho_Common::url('index.php', $options->siteUrl) . '?heartfelt_filter=heartfelt';
|
|||
|
|
|
|||
|
|
echo <<<HTML
|
|||
|
|
<script>
|
|||
|
|
// 锐评插件 - 后台功能
|
|||
|
|
var heartfeltMenuAdded = false;
|
|||
|
|
|
|||
|
|
function initRuiPing() {
|
|||
|
|
var buttonCount = 0;
|
|||
|
|
var badgeCount = 0;
|
|||
|
|
|
|||
|
|
// 添加锐评菜单到现有筛选菜单(只添加一次)
|
|||
|
|
if (!heartfeltMenuAdded) {
|
|||
|
|
addRuiPingMenu();
|
|||
|
|
heartfeltMenuAdded = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加锐评操作到默认批量操作菜单
|
|||
|
|
addRuiPingBatchActions();
|
|||
|
|
|
|||
|
|
jQuery('tr[id^="comment-"]').each(function() {
|
|||
|
|
var \$tr = jQuery(this);
|
|||
|
|
var trId = \$tr.attr('id');
|
|||
|
|
var commentId = trId.replace('comment-', '');
|
|||
|
|
|
|||
|
|
if (commentId && !isNaN(commentId)) {
|
|||
|
|
var isHeartfelt = [{$currentIdsStr}].indexOf(parseInt(commentId)) > -1;
|
|||
|
|
|
|||
|
|
var operationDiv = \$tr.find('.operate');
|
|||
|
|
if (operationDiv.length === 0) {
|
|||
|
|
operationDiv = \$tr.find('.comment-action');
|
|||
|
|
}
|
|||
|
|
if (operationDiv.length === 0) {
|
|||
|
|
operationDiv = \$tr.find('td:last-child');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (operationDiv.length > 0) {
|
|||
|
|
if (!operationDiv.find('.heartfelt-btn').length) {
|
|||
|
|
var heartfeltBtn = isHeartfelt ?
|
|||
|
|
'<a href="javascript:;" class="heartfelt-btn" data-comment="' + commentId + '" data-action="unset" style="color: #d32f2f; margin-left: 8px;">❤ 取消锐评</a>' :
|
|||
|
|
'<a href="javascript:;" class="heartfelt-btn" data-comment="' + commentId + '" data-action="set" style="margin-left: 8px;">🤍 设为锐评</a>';
|
|||
|
|
|
|||
|
|
operationDiv.append(heartfeltBtn);
|
|||
|
|
buttonCount++;
|
|||
|
|
|
|||
|
|
bindSingleButton(operationDiv.find('.heartfelt-btn').last());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isHeartfelt) {
|
|||
|
|
var firstTd = \$tr.find('td:first-child');
|
|||
|
|
if (firstTd.length > 0 && !firstTd.find('.heartfelt-badge').length) {
|
|||
|
|
firstTd.css('position', 'relative');
|
|||
|
|
firstTd.append('<span class="heartfelt-badge">❤ 锐评</span>');
|
|||
|
|
badgeCount++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加锐评菜单到现有筛选菜单区域
|
|||
|
|
function addRuiPingMenu() {
|
|||
|
|
// 查找现有的筛选菜单 - 左边的菜单
|
|||
|
|
var \$typechoTabs = jQuery('.typecho-option-tabs').first();
|
|||
|
|
if (\$typechoTabs.length === 0) {
|
|||
|
|
console.log('未找到筛选菜单容器');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否已经添加过锐评菜单
|
|||
|
|
if (\$typechoTabs.find('.ruiping-menu-trigger').length > 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建锐评菜单项 - 添加到垃圾菜单后面
|
|||
|
|
var ruiPingMenuItem = jQuery('<li><a href="{$heartfeltUrl}" class="ruiping-menu-trigger">锐评</a></li>');
|
|||
|
|
|
|||
|
|
// 找到垃圾菜单并插入到它后面
|
|||
|
|
var \$spamMenu = \$typechoTabs.find('a[href*="spam"]').parent();
|
|||
|
|
if (\$spamMenu.length > 0) {
|
|||
|
|
\$spamMenu.after(ruiPingMenuItem);
|
|||
|
|
} else {
|
|||
|
|
// 如果找不到垃圾菜单,添加到末尾
|
|||
|
|
\$typechoTabs.append(ruiPingMenuItem);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查当前是否在锐评页面
|
|||
|
|
var currentUrl = window.location.href;
|
|||
|
|
if (currentUrl.indexOf('heartfelt_filter=heartfelt') > -1 || currentUrl.indexOf('coid[]') > -1) {
|
|||
|
|
ruiPingMenuItem.addClass('current');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加锐评操作到默认批量操作菜单
|
|||
|
|
function addRuiPingBatchActions() {
|
|||
|
|
var \$operateDiv = jQuery('.operate');
|
|||
|
|
if (\$operateDiv.length === 0) {
|
|||
|
|
console.log('未找到操作容器');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var \$dropdownMenu = \$operateDiv.find('.dropdown-menu');
|
|||
|
|
if (\$dropdownMenu.length === 0) {
|
|||
|
|
console.log('未找到下拉菜单');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否已经添加过锐评操作
|
|||
|
|
if (\$dropdownMenu.find('.heartfelt-batch-action').length > 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 在删除操作前添加锐评操作
|
|||
|
|
var heartfeltActions = '<li class="divider heartfelt-batch-action"></li>';
|
|||
|
|
heartfeltActions += '<li class="heartfelt-batch-action"><a href="javascript:;" onclick="heartfeltBatchAction(\'add\')">设为锐评</a></li>';
|
|||
|
|
heartfeltActions += '<li class="heartfelt-batch-action"><a href="javascript:;" onclick="heartfeltBatchAction(\'remove\')">取消锐评</a></li>';
|
|||
|
|
|
|||
|
|
\$dropdownMenu.find('a[href*="delete"]').parent().before(heartfeltActions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量操作
|
|||
|
|
function heartfeltBatchAction(action) {
|
|||
|
|
var selectedComments = [];
|
|||
|
|
jQuery('input[name="coid[]"]:checked').each(function() {
|
|||
|
|
var commentId = jQuery(this).val();
|
|||
|
|
if (commentId && !isNaN(commentId)) {
|
|||
|
|
selectedComments.push(parseInt(commentId));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (selectedComments.length === 0) {
|
|||
|
|
alert('请先选择要操作的评论');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var confirmMessage = '';
|
|||
|
|
switch (action) {
|
|||
|
|
case 'add':
|
|||
|
|
confirmMessage = '确定要将选中的 ' + selectedComments.length + ' 条评论设为锐评吗?';
|
|||
|
|
break;
|
|||
|
|
case 'remove':
|
|||
|
|
confirmMessage = '确定要取消选中的 ' + selectedComments.length + ' 条评论的锐评状态吗?';
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!confirm(confirmMessage)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
jQuery.ajax({
|
|||
|
|
url: '{$batchAjaxUrl}',
|
|||
|
|
type: 'POST',
|
|||
|
|
dataType: 'json',
|
|||
|
|
data: {
|
|||
|
|
heartfelt_batch_action: action,
|
|||
|
|
comment_ids: selectedComments
|
|||
|
|
},
|
|||
|
|
success: function(response) {
|
|||
|
|
if (response && response.success) {
|
|||
|
|
alert(response.message || '操作成功');
|
|||
|
|
window.location.reload();
|
|||
|
|
} else {
|
|||
|
|
var message = response && response.message ? response.message : '操作失败';
|
|||
|
|
alert('操作失败: ' + message);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
error: function(xhr, status, error) {
|
|||
|
|
var errorMsg = '网络错误,请重试。状态: ' + status;
|
|||
|
|
if (xhr.status === 404) {
|
|||
|
|
errorMsg = '请求的接口不存在 (404),请检查插件路由配置';
|
|||
|
|
} else if (xhr.status === 403) {
|
|||
|
|
errorMsg = '权限不足 (403),请重新登录';
|
|||
|
|
} else if (xhr.status === 500) {
|
|||
|
|
errorMsg = '服务器内部错误 (500)';
|
|||
|
|
}
|
|||
|
|
alert(errorMsg);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function bindSingleButton(btn) {
|
|||
|
|
btn.off('click').on('click', function(e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
|
|||
|
|
var \$btn = jQuery(this);
|
|||
|
|
var commentId = \$btn.data('comment');
|
|||
|
|
var action = \$btn.data('action');
|
|||
|
|
|
|||
|
|
if (!commentId) {
|
|||
|
|
alert('错误: 未找到评论ID');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var originalHtml = \$btn.html();
|
|||
|
|
\$btn.html('处理中...').prop('disabled', true).css('opacity', '0.6');
|
|||
|
|
|
|||
|
|
jQuery.ajax({
|
|||
|
|
url: '{$ajaxUrl}',
|
|||
|
|
type: 'POST',
|
|||
|
|
dataType: 'json',
|
|||
|
|
data: {
|
|||
|
|
commentId: commentId,
|
|||
|
|
action: action
|
|||
|
|
},
|
|||
|
|
success: function(response) {
|
|||
|
|
if (response && response.success) {
|
|||
|
|
alert(response.message || '操作成功');
|
|||
|
|
window.location.reload();
|
|||
|
|
} else {
|
|||
|
|
var message = response && response.message ? response.message : '操作失败';
|
|||
|
|
alert('操作失败: ' + message);
|
|||
|
|
\$btn.prop('disabled', false).css('opacity', '1').html(originalHtml);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
error: function(xhr, status, error) {
|
|||
|
|
var errorMsg = '网络错误,请重试。状态: ' + status;
|
|||
|
|
if (xhr.status === 404) {
|
|||
|
|
errorMsg = '请求的接口不存在 (404),请检查插件路由配置';
|
|||
|
|
} else if (xhr.status === 403) {
|
|||
|
|
errorMsg = '权限不足 (403),请重新登录';
|
|||
|
|
} else if (xhr.status === 500) {
|
|||
|
|
errorMsg = '服务器内部错误 (500)';
|
|||
|
|
}
|
|||
|
|
alert(errorMsg);
|
|||
|
|
\$btn.prop('disabled', false).css('opacity', '1').html(originalHtml);
|
|||
|
|
},
|
|||
|
|
timeout: 10000
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function heartfeltMainInit() {
|
|||
|
|
if (!window.jQuery) {
|
|||
|
|
var checkJQuery = setInterval(function() {
|
|||
|
|
if (window.jQuery) {
|
|||
|
|
clearInterval(checkJQuery);
|
|||
|
|
heartfeltMainInit();
|
|||
|
|
}
|
|||
|
|
}, 100);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
jQuery(document).ready(function($) {
|
|||
|
|
initRuiPing();
|
|||
|
|
|
|||
|
|
// 只初始化一次,避免重复
|
|||
|
|
setTimeout(function() {
|
|||
|
|
if (!heartfeltMenuAdded) {
|
|||
|
|
initRuiPing();
|
|||
|
|
}
|
|||
|
|
}, 500);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
heartfeltMainInit();
|
|||
|
|
</script>
|
|||
|
|
<style>
|
|||
|
|
table {
|
|||
|
|
font-size: 14px;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
.heartfelt-badge {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 50%;
|
|||
|
|
left: 10px;
|
|||
|
|
transform: translateY(-50%);
|
|||
|
|
background: #d32f2f;
|
|||
|
|
color: white;
|
|||
|
|
padding: 2px 6px;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: normal;
|
|||
|
|
z-index: 1;
|
|||
|
|
line-height: 1.2;
|
|||
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-btn {
|
|||
|
|
margin-left: 8px;
|
|||
|
|
text-decoration: none;
|
|||
|
|
font-size: 12px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 2px 6px;
|
|||
|
|
border-radius: 2px;
|
|||
|
|
border: 1px solid #ddd;
|
|||
|
|
background: #f9f9f9;
|
|||
|
|
transition: all 0.15s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-btn:hover {
|
|||
|
|
color: #d32f2f !important;
|
|||
|
|
text-decoration: none;
|
|||
|
|
background: #fff;
|
|||
|
|
border-color: #d32f2f;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-btn:disabled {
|
|||
|
|
opacity: 0.6;
|
|||
|
|
cursor: not-allowed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.typecho-table tr td:first-child {
|
|||
|
|
position: relative !important;
|
|||
|
|
padding-left: 60px !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ruiping-menu-trigger {
|
|||
|
|
color: #d32f2f !important;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
HTML;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查是否为评论管理页面
|
|||
|
|
*/
|
|||
|
|
private static function isCommentsManagementPage()
|
|||
|
|
{
|
|||
|
|
$pathInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
|
|||
|
|
$requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
|
|||
|
|
|
|||
|
|
if (strpos($pathInfo, 'comments') !== false ||
|
|||
|
|
strpos($requestUri, 'comments') !== false ||
|
|||
|
|
strpos($requestUri, 'comment') !== false ||
|
|||
|
|
(isset($_GET['__typecho_page']) && $_GET['__typecho_page'] === 'comments')) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 插件实现方法
|
|||
|
|
*/
|
|||
|
|
public static function render()
|
|||
|
|
{
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$config = $options->plugin('RuiPing');
|
|||
|
|
|
|||
|
|
if (!$config) {
|
|||
|
|
return '<p>插件配置未找到,请重新保存插件设置</p>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$currentPage = isset($_GET['heartfelt_page']) ? max(1, intval($_GET['heartfelt_page'])) : 1;
|
|||
|
|
|
|||
|
|
if ($config->debugMode && empty($config->commentIds)) {
|
|||
|
|
return '<p class="heartfelt-debug">调试:插件设置中未配置评论ID</p>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$commentIds = self::getCommentIds($config->commentIds);
|
|||
|
|
if (empty($commentIds)) {
|
|||
|
|
if ($config->debugMode) {
|
|||
|
|
return '<p class="heartfelt-debug">调试:解析后的评论ID为空,请检查ID格式(每行一个数字ID)</p>';
|
|||
|
|
}
|
|||
|
|
return '<p>暂无锐评</p>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$limit = intval($config->limit) ?: 10;
|
|||
|
|
|
|||
|
|
return self::getRuiPing($commentIds, $limit, $currentPage, $config);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* JSON响应
|
|||
|
|
*/
|
|||
|
|
private static function jsonResponse($success, $message = '', $data = array())
|
|||
|
|
{
|
|||
|
|
header('Content-Type: application/json; charset=utf-8');
|
|||
|
|
echo json_encode(array(
|
|||
|
|
'success' => $success,
|
|||
|
|
'message' => $message,
|
|||
|
|
'data' => $data
|
|||
|
|
));
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取评论ID数组
|
|||
|
|
*/
|
|||
|
|
private static function getCommentIds($commentIdsText)
|
|||
|
|
{
|
|||
|
|
if (empty($commentIdsText)) {
|
|||
|
|
return array();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$ids = explode("\n", $commentIdsText);
|
|||
|
|
$ids = array_map('trim', $ids);
|
|||
|
|
$ids = array_filter($ids, function($id) {
|
|||
|
|
return is_numeric($id) && $id > 0;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return $ids;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取锐评数据(带分页)- 修复排序问题
|
|||
|
|
*/
|
|||
|
|
private static function getRuiPing($commentIds, $limit, $currentPage, $config)
|
|||
|
|
{
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
|
|||
|
|
$offset = ($currentPage - 1) * $limit;
|
|||
|
|
$total = count($commentIds);
|
|||
|
|
$totalPages = ceil($total / $limit);
|
|||
|
|
|
|||
|
|
// 修复:先获取所有评论数据,按时间排序,然后再分页
|
|||
|
|
$select = $db->select()
|
|||
|
|
->from('table.comments')
|
|||
|
|
->where('coid IN ?', $commentIds)
|
|||
|
|
->order('created', Typecho_Db::SORT_DESC); // 先全局按时间排序
|
|||
|
|
|
|||
|
|
$allComments = $db->fetchAll($select);
|
|||
|
|
|
|||
|
|
// 如果获取的评论数量与ID数量不一致,重新匹配
|
|||
|
|
$allCommentsMap = array();
|
|||
|
|
foreach ($allComments as $comment) {
|
|||
|
|
$allCommentsMap[$comment['coid']] = $comment;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保所有ID都有对应的评论数据
|
|||
|
|
$validComments = array();
|
|||
|
|
foreach ($commentIds as $commentId) {
|
|||
|
|
if (isset($allCommentsMap[$commentId])) {
|
|||
|
|
$validComments[$commentId] = $allCommentsMap[$commentId];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按时间排序(最新的在前面)
|
|||
|
|
uasort($validComments, function($a, $b) {
|
|||
|
|
return $b['created'] - $a['created'];
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 分页:取当前页的数据
|
|||
|
|
$currentPageData = array_slice($validComments, $offset, $limit, true);
|
|||
|
|
|
|||
|
|
$debugInfo = '';
|
|||
|
|
if ($config->debugMode) {
|
|||
|
|
$debugInfo = '<div class="heartfelt-debug">';
|
|||
|
|
$debugInfo .= '<p><strong>调试信息:</strong></p>';
|
|||
|
|
$debugInfo .= '<p>总评论数: ' . $total . '</p>';
|
|||
|
|
$debugInfo .= '<p>每页数量: ' . $limit . '</p>';
|
|||
|
|
$debugInfo .= '<p>总页数: ' . $totalPages . '</p>';
|
|||
|
|
$debugInfo .= '<p>当前页: ' . $currentPage . '</p>';
|
|||
|
|
$debugInfo .= '<p>当前页评论数量: ' . count($currentPageData) . '</p>';
|
|||
|
|
$debugInfo .= '<p>排序方式: 按时间倒序(最新的在最前面)</p>';
|
|||
|
|
$debugInfo .= '</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html = $debugInfo;
|
|||
|
|
$html .= self::generateHtml($currentPageData, $config);
|
|||
|
|
|
|||
|
|
if ($config->showPagination && $totalPages > 1) {
|
|||
|
|
$html .= self::generatePagination($currentPage, $totalPages);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成分页HTML
|
|||
|
|
*/
|
|||
|
|
private static function generatePagination($currentPage, $totalPages)
|
|||
|
|
{
|
|||
|
|
$html = '<div class="heartfelt-pagination">';
|
|||
|
|
|
|||
|
|
if ($currentPage > 1) {
|
|||
|
|
$prevPage = $currentPage - 1;
|
|||
|
|
$html .= '<a href="?' . self::buildPageQuery($prevPage) . '" class="heartfelt-page-link heartfelt-page-prev">上一页</a>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$startPage = max(1, $currentPage - 2);
|
|||
|
|
$endPage = min($totalPages, $currentPage + 2);
|
|||
|
|
|
|||
|
|
for ($i = $startPage; $i <= $endPage; $i++) {
|
|||
|
|
if ($i == $currentPage) {
|
|||
|
|
$html .= '<span class="heartfelt-page-current">' . $i . '</span>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<a href="?' . self::buildPageQuery($i) . '" class="heartfelt-page-link">' . $i . '</a>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($currentPage < $totalPages) {
|
|||
|
|
$nextPage = $currentPage + 1;
|
|||
|
|
$html .= '<a href="?' . self::buildPageQuery($nextPage) . '" class="heartfelt-page-link heartfelt-page-next">下一页</a>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</div>';
|
|||
|
|
|
|||
|
|
return $html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构建分页查询参数
|
|||
|
|
*/
|
|||
|
|
private static function buildPageQuery($page)
|
|||
|
|
{
|
|||
|
|
$params = $_GET;
|
|||
|
|
$params['heartfelt_page'] = $page;
|
|||
|
|
|
|||
|
|
return http_build_query($params);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成HTML输出
|
|||
|
|
*/
|
|||
|
|
private static function generateHtml($comments, $config)
|
|||
|
|
{
|
|||
|
|
if (empty($comments)) {
|
|||
|
|
return '<p>暂无锐评</p>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html = '<div class="heartfelt-comments">';
|
|||
|
|
$html .= '<div class="heartfelt-comments-list">';
|
|||
|
|
|
|||
|
|
foreach ($comments as $comment) {
|
|||
|
|
$html .= self::generateCommentItem($comment, $config);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</div>';
|
|||
|
|
$html .= '</div>';
|
|||
|
|
|
|||
|
|
return $html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取文章永久链接
|
|||
|
|
*/
|
|||
|
|
private static function getPostPermalink($cid)
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$post = $db->fetchRow($db->select('slug', 'type')
|
|||
|
|
->from('table.contents')
|
|||
|
|
->where('cid = ?', $cid)
|
|||
|
|
->where('type = ?', 'post')
|
|||
|
|
->limit(1));
|
|||
|
|
|
|||
|
|
if ($post) {
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$permalink = Typecho_Router::url('post', $post, $options->index);
|
|||
|
|
return $permalink;
|
|||
|
|
}
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取评论链接
|
|||
|
|
*/
|
|||
|
|
private static function getCommentLink($comment, $config)
|
|||
|
|
{
|
|||
|
|
$postUrl = self::getPostPermalink($comment['cid']);
|
|||
|
|
if (!$postUrl) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$commentUrl = $postUrl . '#comment-' . $comment['coid'];
|
|||
|
|
|
|||
|
|
return $commentUrl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取作者链接
|
|||
|
|
*/
|
|||
|
|
private static function getAuthorLink($comment, $config)
|
|||
|
|
{
|
|||
|
|
if (!empty($comment['url'])) {
|
|||
|
|
$authorUrl = $comment['url'];
|
|||
|
|
if (!preg_match('/^https?:\/\//', $authorUrl)) {
|
|||
|
|
$authorUrl = 'http://' . $authorUrl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return array('url' => $authorUrl, 'target' => $config->authorLink ? '_blank' : '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return array('url' => '', 'target' => '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成单个评论项HTML
|
|||
|
|
*/
|
|||
|
|
private static function generateCommentItem($comment, $config)
|
|||
|
|
{
|
|||
|
|
$html = '<div class="heartfelt-comment-item">';
|
|||
|
|
|
|||
|
|
$commentUrl = self::getCommentLink($comment, $config);
|
|||
|
|
|
|||
|
|
if ($config->showContent) {
|
|||
|
|
$content = strip_tags($comment['text']);
|
|||
|
|
if (mb_strlen($content, 'UTF-8') > 200) {
|
|||
|
|
$content = mb_substr($content, 0, 200, 'UTF-8') . '...';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($commentUrl) {
|
|||
|
|
$target = $config->commentLink ? ' target="_blank"' : '';
|
|||
|
|
$html .= '<div class="heartfelt-comment-content">';
|
|||
|
|
$html .= '<a href="' . $commentUrl . '"' . $target . ' class="heartfelt-comment-link">';
|
|||
|
|
$html .= htmlspecialchars($content);
|
|||
|
|
$html .= '</a>';
|
|||
|
|
$html .= '</div>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<div class="heartfelt-comment-content">' . htmlspecialchars($content) . '</div>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '<div class="heartfelt-comment-meta">';
|
|||
|
|
|
|||
|
|
if ($config->showAuthor) {
|
|||
|
|
$author = $comment['author'] ?: '匿名';
|
|||
|
|
$authorLinkInfo = self::getAuthorLink($comment, $config);
|
|||
|
|
|
|||
|
|
if (!empty($authorLinkInfo['url'])) {
|
|||
|
|
$html .= '<span class="heartfelt-comment-author">';
|
|||
|
|
$html .= '<a href="' . $authorLinkInfo['url'] . '" target="' . $authorLinkInfo['target'] . '" class="heartfelt-author-link">';
|
|||
|
|
$html .= htmlspecialchars($author);
|
|||
|
|
$html .= '</a>';
|
|||
|
|
$html .= '</span>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<span class="heartfelt-comment-author">' . htmlspecialchars($author) . '</span>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($config->showDate) {
|
|||
|
|
$date = date('Y-m-d H:i', $comment['created']);
|
|||
|
|
|
|||
|
|
$html .= '<span class="heartfelt-comment-date">';
|
|||
|
|
$html .= $date;
|
|||
|
|
|
|||
|
|
// 新增:显示原文链接(根据配置决定是否显示)
|
|||
|
|
if ($config->showOriginal && $commentUrl) {
|
|||
|
|
$target = $config->commentLink ? ' target="_blank"' : '';
|
|||
|
|
$html .= '<a href="' . $commentUrl . '"' . $target . ' class="heartfelt-origin-link">原文</a>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($config->showSource && $comment['cid']) {
|
|||
|
|
try {
|
|||
|
|
$postUrl = self::getPostPermalink($comment['cid']);
|
|||
|
|
if ($postUrl) {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$post = $db->fetchRow($db->select('title')
|
|||
|
|
->from('table.contents')
|
|||
|
|
->where('cid = ?', $comment['cid'])
|
|||
|
|
->limit(1));
|
|||
|
|
|
|||
|
|
if ($post && $post['title']) {
|
|||
|
|
$target = $config->commentLink ? ' target="_blank"' : '';
|
|||
|
|
$html .= '<span class="heartfelt-comment-source">';
|
|||
|
|
$html .= '<a href="' . $postUrl . '"' . $target . ' class="heartfelt-source-link">';
|
|||
|
|
$html .= htmlspecialchars($post['title']);
|
|||
|
|
$html .= '</a>';
|
|||
|
|
$html .= '</span>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
// 忽略文章获取错误
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</div>';
|
|||
|
|
$html .= '</div>';
|
|||
|
|
|
|||
|
|
return $html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 头部输出
|
|||
|
|
*/
|
|||
|
|
public static function header()
|
|||
|
|
{
|
|||
|
|
$css = self::getCss();
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options');
|
|||
|
|
$config = $options->plugin('RuiPing');
|
|||
|
|
|
|||
|
|
// 添加前端标识的CSS
|
|||
|
|
if ($config && (!isset($config->showFrontendBadge) || $config->showFrontendBadge == '1')) {
|
|||
|
|
$badgeColor = isset($config->frontendBadgeColor) ? $config->frontendBadgeColor : '#d32f2f';
|
|||
|
|
|
|||
|
|
$frontendCss = <<<CSS
|
|||
|
|
<style>
|
|||
|
|
/* 竖排锐评标识样式 */
|
|||
|
|
.heartfelt-frontend-badge {
|
|||
|
|
background-color: #d32f2f;
|
|||
|
|
color: #fff;
|
|||
|
|
padding: 3px 8px;
|
|||
|
|
border-radius: 20px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
line-height: 1.2;
|
|||
|
|
z-index: 100;
|
|||
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
cursor: default;
|
|||
|
|
text-shadow: 1px 1px 1px rgba(0,0,0,0.2);
|
|||
|
|
white-space: nowrap;
|
|||
|
|
pointer-events: auto;
|
|||
|
|
|
|||
|
|
/* 竖排文字 */
|
|||
|
|
writing-mode: vertical-rl;
|
|||
|
|
text-orientation: mixed;
|
|||
|
|
height: auto;
|
|||
|
|
padding: 8px 3px !important; /* 调整padding,竖排时宽高互换 */
|
|||
|
|
letter-spacing: 1px;
|
|||
|
|
margin-top:15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-frontend-badge:hover {
|
|||
|
|
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 针对你的评论模板的特殊处理 */
|
|||
|
|
.comment-content .heartfelt-frontend-badge,
|
|||
|
|
.cm .heartfelt-frontend-badge {
|
|||
|
|
position: absolute !important;
|
|||
|
|
top: 25px !important;
|
|||
|
|
right: 15px !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 确保内容区域有相对定位 */
|
|||
|
|
.comment-content,
|
|||
|
|
.cm {
|
|||
|
|
position: relative !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 为内容区域添加padding,避免标识覆盖文字 - 调整为竖排时的宽度 */
|
|||
|
|
.comment-content[style*="padding-right"],
|
|||
|
|
.cm[style*="padding-right"] {
|
|||
|
|
padding-right: 30px !important; /* 竖排标识宽度更小 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.comment-content[style*="padding-left"],
|
|||
|
|
.cm[style*="padding-left"] {
|
|||
|
|
padding-left: 30px !important; /* 竖排标识宽度更小 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 暗色模式适配 */
|
|||
|
|
@media (prefers-color-scheme: dark) {
|
|||
|
|
.heartfelt-frontend-badge {
|
|||
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.4);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 修复嵌套评论的标识位置 */
|
|||
|
|
.comment-child .heartfelt-frontend-badge {
|
|||
|
|
font-size: 11px;
|
|||
|
|
padding: 6px 2px !important; /* 竖排调整 */
|
|||
|
|
top: 5px !important;
|
|||
|
|
right: 5px !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 响应式调整 */
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.heartfelt-frontend-badge {
|
|||
|
|
font-size: 11px;
|
|||
|
|
padding: 6px 2px !important; /* 竖排调整 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 移动端调整位置,避免太靠边 */
|
|||
|
|
.comment-content .heartfelt-frontend-badge,
|
|||
|
|
.cm .heartfelt-frontend-badge {
|
|||
|
|
top: 5px !important;
|
|||
|
|
right: 5px !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 调整内容区域的padding */
|
|||
|
|
.comment-content[style*="padding-right"],
|
|||
|
|
.cm[style*="padding-right"] {
|
|||
|
|
padding-right: 25px !important; /* 移动端竖排标识更窄 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.comment-content[style*="padding-left"],
|
|||
|
|
.cm[style*="padding-left"] {
|
|||
|
|
padding-left: 25px !important; /* 移动端竖排标识更窄 */
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 动画效果 */
|
|||
|
|
@keyframes heartfelt-badge-appear {
|
|||
|
|
0% {
|
|||
|
|
opacity: 0;
|
|||
|
|
transform: translateY(-10px) scale(0.8);
|
|||
|
|
}
|
|||
|
|
100% {
|
|||
|
|
opacity: 1;
|
|||
|
|
transform: translateY(0) scale(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-frontend-badge {
|
|||
|
|
animation: heartfelt-badge-appear 0.5s ease-out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 针对其他位置的竖排调整 */
|
|||
|
|
.heartfelt-frontend-badge[style*="content-top-left"] {
|
|||
|
|
writing-mode: vertical-rl;
|
|||
|
|
text-orientation: mixed;
|
|||
|
|
padding: 8px 3px !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-frontend-badge[style*="comment-top-right"],
|
|||
|
|
.heartfelt-frontend-badge[style*="comment-top-left"] {
|
|||
|
|
writing-mode: vertical-rl;
|
|||
|
|
text-orientation: mixed;
|
|||
|
|
padding: 8px 3px !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 作者名后的标识保持横排 */
|
|||
|
|
.heartfelt-frontend-badge[style*="display: inline-block"] {
|
|||
|
|
writing-mode: horizontal-tb !important;
|
|||
|
|
padding: 3px 8px !important;
|
|||
|
|
vertical-align: middle;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
CSS;
|
|||
|
|
|
|||
|
|
$css .= $frontendCss;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
echo $css;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 底部输出
|
|||
|
|
*/
|
|||
|
|
public static function footer()
|
|||
|
|
{
|
|||
|
|
// 可以在这里添加其他页脚内容
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取CSS样式
|
|||
|
|
*/
|
|||
|
|
private static function getCss()
|
|||
|
|
{
|
|||
|
|
return <<<CSS
|
|||
|
|
<style>
|
|||
|
|
/* 锐评插件核心样式 */
|
|||
|
|
.heartfelt-comments {
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
margin: 20px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-title {
|
|||
|
|
color: #d32f2f;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
font-size: 1.1em;
|
|||
|
|
text-align:center;
|
|||
|
|
padding: 30px;
|
|||
|
|
border-radius:10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comments-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-item {
|
|||
|
|
background:#eee;
|
|||
|
|
padding: 16px;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
position: relative;
|
|||
|
|
border: 1px solid rgba(0, 0, 0, 0);
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-item:hover {
|
|||
|
|
border:1px solid #f15a22;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-content {
|
|||
|
|
color: #333;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
font-size: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-link {
|
|||
|
|
color: #333;
|
|||
|
|
text-decoration: none;
|
|||
|
|
display: block;
|
|||
|
|
padding: 8px;
|
|||
|
|
margin: -8px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
transition: background-color 0.15s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-link:hover {
|
|||
|
|
background-color: #f8f9fa;
|
|||
|
|
color: #d32f2f;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-meta {
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 12px;
|
|||
|
|
font-size: 0.85em;
|
|||
|
|
color: #666;
|
|||
|
|
padding-top: 12px;
|
|||
|
|
border-top: 1px solid #faf8f1;
|
|||
|
|
}
|
|||
|
|
.dark .heartfelt-comment-meta{border-top: 1px solid hsl(0 0% 15%);}
|
|||
|
|
.heartfelt-comment-author,
|
|||
|
|
.heartfelt-comment-date,
|
|||
|
|
.heartfelt-comment-source {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-author-link,
|
|||
|
|
.heartfelt-source-link,
|
|||
|
|
.heartfelt-origin-link {
|
|||
|
|
color: #666;
|
|||
|
|
text-decoration: none;
|
|||
|
|
transition: color 0.15s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-author-link:hover,
|
|||
|
|
.heartfelt-source-link:hover,
|
|||
|
|
.heartfelt-origin-link:hover {
|
|||
|
|
color: #d32f2f;
|
|||
|
|
text-decoration: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-origin-link {
|
|||
|
|
padding: 2px 6px;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
background: #f0f0f0;
|
|||
|
|
margin-left: 8px;
|
|||
|
|
font-size: 0.8em;
|
|||
|
|
border: 1px solid #ddd;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-origin-link:hover {
|
|||
|
|
background: #f8f0f5;
|
|||
|
|
border-color: #d32f2f;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-pagination {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
margin-top: 24px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-page-link,
|
|||
|
|
.heartfelt-page-current {
|
|||
|
|
padding: 6px 12px;
|
|||
|
|
border-radius: 50px;
|
|||
|
|
text-decoration: none;
|
|||
|
|
transition: all 0.15s ease;
|
|||
|
|
font-size: 0.9em;
|
|||
|
|
min-width: 36px;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
.dark .heartfelt-page-link{
|
|||
|
|
background-color:rgb(10 12 25 / 1)!important;
|
|||
|
|
}
|
|||
|
|
.heartfelt-page-link {
|
|||
|
|
color: #495057;
|
|||
|
|
background: white;
|
|||
|
|
border: 1px solid #f15a22;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-page-link:hover {
|
|||
|
|
color: #d32f2f;
|
|||
|
|
background: #f8f0f5;
|
|||
|
|
border-color: #d32f2f;
|
|||
|
|
text-decoration: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-page-current {
|
|||
|
|
color: white;
|
|||
|
|
background: #d32f2f;
|
|||
|
|
border: 1px solid #d32f2f;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-debug {
|
|||
|
|
background: #fff3cd;
|
|||
|
|
border: 1px solid #ffeaa7;
|
|||
|
|
padding: 12px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
color: #856404;
|
|||
|
|
font-size: 0.9em;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-debug p {
|
|||
|
|
margin: 4px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-debug strong {
|
|||
|
|
color: #d32f2f;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-link::after {
|
|||
|
|
content: " →";
|
|||
|
|
opacity: 0;
|
|||
|
|
transition: opacity 0.15s ease;
|
|||
|
|
font-size: 0.85em;
|
|||
|
|
margin-left: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-link:hover::after {
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.heartfelt-comments {
|
|||
|
|
padding: 12px;
|
|||
|
|
margin: 12px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-meta {
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-content {
|
|||
|
|
font-size: 0.9em;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-comment-link {
|
|||
|
|
padding: 6px;
|
|||
|
|
margin: -6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-pagination {
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.heartfelt-page-link,
|
|||
|
|
.heartfelt-page-current {
|
|||
|
|
padding: 4px 8px;
|
|||
|
|
font-size: 0.8em;
|
|||
|
|
min-width: 30px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
CSS;
|
|||
|
|
}
|
|||
|
|
}
|