diff --git a/Plugin.php b/Plugin.php new file mode 100644 index 0000000..147a79e --- /dev/null +++ b/Plugin.php @@ -0,0 +1,2267 @@ +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 '
+

锐评插件 - 设置备份与恢复

+
'; + + // 备份设置 + $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 = << +(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); + } +})(); + +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'; + ?> + + 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 ''; + } + } + + /** + * 添加管理员底部 + */ + 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 << +// 锐评插件 - 后台功能 +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 ? + '❤ 取消锐评' : + '🤍 设为锐评'; + + 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('❤ 锐评'); + 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('
  • 锐评
  • '); + + // 找到垃圾菜单并插入到它后面 + 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 = '
  • '; + heartfeltActions += '
  • 设为锐评
  • '; + heartfeltActions += '
  • 取消锐评
  • '; + + \$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(); + + +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 '

    插件配置未找到,请重新保存插件设置

    '; + } + + $currentPage = isset($_GET['heartfelt_page']) ? max(1, intval($_GET['heartfelt_page'])) : 1; + + if ($config->debugMode && empty($config->commentIds)) { + return '

    调试:插件设置中未配置评论ID

    '; + } + + $commentIds = self::getCommentIds($config->commentIds); + if (empty($commentIds)) { + if ($config->debugMode) { + return '

    调试:解析后的评论ID为空,请检查ID格式(每行一个数字ID)

    '; + } + return '

    暂无锐评

    '; + } + + $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 = '
    '; + $debugInfo .= '

    调试信息:

    '; + $debugInfo .= '

    总评论数: ' . $total . '

    '; + $debugInfo .= '

    每页数量: ' . $limit . '

    '; + $debugInfo .= '

    总页数: ' . $totalPages . '

    '; + $debugInfo .= '

    当前页: ' . $currentPage . '

    '; + $debugInfo .= '

    当前页评论数量: ' . count($currentPageData) . '

    '; + $debugInfo .= '

    排序方式: 按时间倒序(最新的在最前面)

    '; + $debugInfo .= '
    '; + } + + $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 = '
    '; + + if ($currentPage > 1) { + $prevPage = $currentPage - 1; + $html .= '上一页'; + } + + $startPage = max(1, $currentPage - 2); + $endPage = min($totalPages, $currentPage + 2); + + for ($i = $startPage; $i <= $endPage; $i++) { + if ($i == $currentPage) { + $html .= '' . $i . ''; + } else { + $html .= '' . $i . ''; + } + } + + if ($currentPage < $totalPages) { + $nextPage = $currentPage + 1; + $html .= '下一页'; + } + + $html .= '
    '; + + 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 '

    暂无锐评

    '; + } + + $html = '
    '; + $html .= '
    '; + + foreach ($comments as $comment) { + $html .= self::generateCommentItem($comment, $config); + } + + $html .= '
    '; + $html .= '
    '; + + 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 = '
    '; + + $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 .= ''; + } else { + $html .= '
    ' . htmlspecialchars($content) . '
    '; + } + } + + $html .= '
    '; + + if ($config->showAuthor) { + $author = $comment['author'] ?: '匿名'; + $authorLinkInfo = self::getAuthorLink($comment, $config); + + if (!empty($authorLinkInfo['url'])) { + $html .= ''; + $html .= ''; + $html .= htmlspecialchars($author); + $html .= ''; + $html .= ''; + } else { + $html .= '' . htmlspecialchars($author) . ''; + } + } + + if ($config->showDate) { + $date = date('Y-m-d H:i', $comment['created']); + + $html .= ''; + $html .= $date; + + // 新增:显示原文链接(根据配置决定是否显示) + if ($config->showOriginal && $commentUrl) { + $target = $config->commentLink ? ' target="_blank"' : ''; + $html .= '原文'; + } + + $html .= ''; + } + + 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 .= ''; + $html .= ''; + $html .= htmlspecialchars($post['title']); + $html .= ''; + $html .= ''; + } + } + } catch (Exception $e) { + // 忽略文章获取错误 + } + } + + $html .= '
    '; + $html .= '
    '; + + 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 = << +/* 竖排锐评标识样式 */ +.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; +} + +CSS; + + $css .= $frontendCss; + } + + echo $css; + } + + /** + * 底部输出 + */ + public static function footer() + { + // 可以在这里添加其他页脚内容 + } + + /** + * 获取CSS样式 + */ + private static function getCss() + { + return << +/* 锐评插件核心样式 */ +.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; + } +} + +CSS; + } +} \ No newline at end of file