Compare commits

...

2 Commits

Author SHA1 Message Date
浪子 3bb53afcea 1.2.0 2025-07-10 19:48:37 +08:00
浪子 9f389fbb34 1.2.0 2025-07-10 18:58:47 +08:00
8 changed files with 190 additions and 136 deletions

View File

@ -32,8 +32,6 @@
- 增加系统显示设置
- 增加浏览器信息显示设置
- 修复pjax模式下的404跳转
---
- 2025.07.07 表情短代码解析集成
- 1.1.5
@ -54,4 +52,8 @@
- 新增支持[success]、[primary]、[danger]、[warning]、[info]、[dark]等alert类短代码自动渲染为对应的Bootstrap风格提示框。
- 新增支持[collapse title='xxx']内容[/collapse]折叠面板短代码自动渲染为带唯一ID的Bootstrap折叠结构。
- 新增支持[download file='xxx.zip' size='12MB']文件地址[/download]下载短代码,自动渲染为带文件名、大小、声明和下载地址的下载信息块。
- 新增支持[reply]隐藏内容[/reply]回复可见短代码,未满足条件时前端显示提示,已评论且审核通过后显示隐藏内容。
- 新增支持[reply]隐藏内容[/reply]回复可见短代码,未满足条件时前端显示提示,已评论且审核通过后显示隐藏内容。
- 1.2.0
- 新增支持首页登录

View File

@ -143,6 +143,10 @@ class Puock {
});
// form ajax submit
$(document).on("submit", ".ajax-form", (e) => {
// 如果是登录弹窗表单,允许原生提交
if ($(e.target).attr('id') === 'front-login-form') {
return true;
}
e.preventDefault();
const form = $(this.ct(e));
const formEls = form.find(":input")
@ -208,6 +212,32 @@ class Puock {
}
return false;
})
// 登录弹窗表单AJAX提交集成Puock插件接口
$(document).off('submit', '#front-login-form');
$(document).on('submit', '#front-login-form', function(e) {
e.preventDefault();
var $form = $(this);
var data = $form.serialize();
$.ajax({
url: '/index.php/ajaxlogin/',
type: 'POST',
data: data,
dataType: 'json',
success: function(res) {
if (res.success) {
window.Puock.toast(res.msg || '登录成功', TYPE_SUCCESS);
setTimeout(function() {
window.location.reload();
}, 800);
} else {
window.Puock.toast(res.msg || '登录失败', TYPE_DANGER);
}
},
error: function() {
window.Puock.toast('请求失败', TYPE_DANGER);
}
});
});
}
pageLinkBlankOpenInit() {
@ -415,7 +445,7 @@ class Puock {
this.pageLinkBlankOpenInit()
this.initGithubCard();
this.keyUpHandle();
this.loadHitokoto();
// this.loadHitokoto();
// this.asyncCacheViews();
this.swiperInit();
// this.validateInit();
@ -771,12 +801,14 @@ class Puock {
}
parseFormData(formEl, args = {}) {
// 先获取表单所有字段
const dataArr = formEl.serializeArray();
const data = {...args};
const data = {};
for (let i = 0; i < dataArr.length; i++) {
data[dataArr[i].name] = dataArr[i].value;
}
return jQuery.param(data);
// 合并额外参数
return jQuery.param(Object.assign(data, args));
}
eventCommentPreSubmit() {
@ -881,19 +913,26 @@ class Puock {
eventOpenCommentBox() {
$(document).off("click", ".comment-reply");
$(document).on("click", ".comment-reply", (e) => {
$(document).on("click", ".comment-reply", function(e) {
e.preventDefault();
this.data.comment.replyId = $(this.ct(e)).attr("data-coid");
if ($.trim(this.data.comment.replyId) === '') {
this.toast('结构有误', TYPE_DANGER);
const replyBtn = $(e.currentTarget);
const replyId = replyBtn.attr("data-coid");
if ($.trim(replyId) === '') {
window.Puock.toast('结构有误', TYPE_DANGER);
return;
}
const cf = $("#comment-form"),
commentLi = $(this.ct(e)).closest('.post-comment');
const cf = $("#comment-form");
const commentLi = replyBtn.closest('.post-comment');
// 只在表单在原位时插入占位符
if (!$("#comment-form-place-holder").length && cf.parent().attr("id") === "comment-form-box") {
cf.before('<div id="comment-form-place-holder"></div>');
}
// 每次都append到目标评论下方
commentLi.append(cf);
$("#comment-cancel").removeClass("d-none");
$("#comment").val("");
$("#comment_parent").val(this.data.comment.replyId);
$("#comment_parent").val(replyId);
window.Puock.data.comment.replyId = replyId;
// 滚动至表单
if (cf.length && cf[0].scrollIntoView) {
cf[0].scrollIntoView({behavior: "smooth", block: "center"});
@ -904,12 +943,17 @@ class Puock {
eventCloseCommentBox() {
$(document).off("click", "#comment-cancel");
$(document).on("click", "#comment-cancel", () => {
const cf = $("#comment-form"),
cb = $(".post-comment .box-sw").parent();
cf.removeClass("box-sw");
$("#comment-form-box").append(cf);
const cf = $("#comment-form");
const holder = $("#comment-form-place-holder");
if (holder.length) {
holder.before(cf);
holder.remove();
} else {
$("#comment-form-box").append(cf);
}
$("#comment-cancel").addClass("d-none");
this.data.comment.replyId = null;
$("#comment_parent").val('');
});
}
@ -1141,25 +1185,6 @@ class Puock {
});
}
loadHitokoto() {
setTimeout(() => {
$(".widget-puock-hitokoto").each((_, v) => {
const el = $(v);
const api = el.attr("data-api") || "https://v1.hitokoto.cn/"
$.get(api, (res) => {
el.find(".t").text(res.hitokoto ?? res.content ?? "无内容");
el.find('.f').text(res.from);
el.find('.fb').removeClass("d-none");
}, 'json').fail((err) => {
console.error(err)
el.find(".t").text("加载失败:" + err.responseText || err);
el.remove(".fb");
})
})
}, 300)
}
toast(msg, type = TYPE_PRIMARY, options = {}) {
options = Object.assign({
duration: 2600,

View File

@ -73,7 +73,7 @@ if ($pageprev == '1' && $this->have()):
'wrapClass' => 'pagination comment-ajax-load',
'itemTag' => 'li',
'textTag' => 'span',
'currentClass' => 'active',
'currentClass' => 'cur',
'prevClass' => 'prev',
'nextClass' => 'next'
)); ?>

View File

@ -59,7 +59,7 @@
<i class="fa-regular fa-face-smile t-md"></i>
</button>
<?php endif; ?>
<input type="hidden" name="parent" id="comment-parent" value="">
<input type="hidden" name="parent" id="comment_parent" value="">
<button type="submit" id="comment-submit" class="btn btn-primary btn-ssm">
<i class="fa-regular fa-paper-plane"></i>&nbsp;发布评论
</button>
@ -217,59 +217,4 @@
</div>
<?php endif; ?>
</li>
<?php } ?>
<script>
// 评论局部回复,表单移动到评论下方
document.addEventListener('DOMContentLoaded', function() {
// 监听评论区的回复按钮
document.body.addEventListener('click', function(e) {
var target = e.target;
if (
target.classList.contains('comment-reply') ||
(target.parentNode && target.parentNode.classList && target.parentNode.classList.contains('comment-reply'))
) {
e.preventDefault();
// 兼容span嵌套与a标签直接点击
var replyBtn = target.classList.contains('comment-reply') ? target : target.parentNode;
var commentId = replyBtn.getAttribute('data-coid');
var commentLi = replyBtn.closest('.post-comment');
var respondBox = document.getElementById('comment-form-box');
var commentForm = document.getElementById('comment-form');
var cancelBtn = document.getElementById('comment-cancel');
var parentInput = document.getElementById('comment-parent');
// 记录原位置
if (!document.getElementById('comment-form-place-holder')) {
var holder = document.createElement('div');
holder.id = 'comment-form-place-holder';
respondBox.parentNode.insertBefore(holder, respondBox);
}
// 移动表单
commentLi.appendChild(respondBox);
// 设置parent
if (parentInput) parentInput.value = commentId;
// 展示取消按钮
if(cancelBtn) cancelBtn.classList.remove('d-none');
// 聚焦文本域
var textarea = commentForm.querySelector('textarea');
if (textarea) textarea.focus();
// 滚动至表单
respondBox.scrollIntoView({behavior: "smooth", block: "center"});
return false;
}
// 取消回复
if (target.id === 'comment-cancel') {
e.preventDefault();
var respondBox = document.getElementById('comment-form-box');
var holder = document.getElementById('comment-form-place-holder');
var parentInput = document.getElementById('comment-parent');
if (holder) {
holder.parentNode.insertBefore(respondBox, holder);
holder.parentNode.removeChild(holder);
}
if (parentInput) parentInput.value = '';
target.classList.add('d-none');
return false;
}
});
});
</script>
<?php } ?>

View File

@ -186,14 +186,30 @@ function getAllCommenters() {
$db = Typecho_Db::get();
$commenters = array();
// 查询所有评论者信息并按邮箱分组统计
$query = $db->select('author, mail, COUNT(*) as comment_count, url')
$isSqlite = strpos(strtolower(get_class($db->getAdapter())), 'sqlite') !== false;
if ($isSqlite) {
// SQLite 不支持 ANY_VALUE
$query = $db->select('author', 'mail', 'COUNT(*) as comment_count', 'url')
->from('table.comments')
->where('status = ?', 'approved')
->where('authorId != ?', 1)
->group('mail')
->order('comment_count', Typecho_Db::SORT_DESC);
} else {
// MySQL 用 ANY_VALUE 兼容 ONLY_FULL_GROUP_BY
$query = $db->select(
'ANY_VALUE(author) as author',
'mail',
'COUNT(*) as comment_count',
'ANY_VALUE(url) as url'
)
->from('table.comments')
->where('status = ?', 'approved') // 只统计已通过审核的评论
->where('authorId != ?', 1) // 排除ID为1的管理员
->where('authorId != ?', 1)
->where('status = ?', 'approved')
->group('mail')
->order('comment_count', Typecho_Db::SORT_DESC);
}
$rows = $db->fetchAll($query);
// 获取 Gravatar 镜像设置
@ -964,8 +980,7 @@ function parse_smiley_shortcode($content) {
}
return $content;
}
?>
<?php
/**
* 短代码实现
*/
@ -1309,3 +1324,75 @@ function get_article_summary($post, $length = 100) {
}
return $text;
}
/**
* 获取站点统计信息(包含当前登录用户信息)
* @return array
*/
function get_site_statistics() {
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
// 获取当前登录用户对象
$currentUser = Typecho_Widget::widget('Widget_User');
// 如果用户未登录获取默认uid=1的用户
if (!$currentUser->hasLogin()) {
$defaultUser = $db->fetchRow($db->select()->from('table.users')->where('uid = ?', 1));
$email = $defaultUser['mail'];
$nickname = $defaultUser['screenName'];
} else {
// 用户已登录,使用当前用户信息
$email = $currentUser->mail;
$nickname = $currentUser->screenName;
}
// 获取用户设置的 Gravatar 镜像
$cnavatar = Helper::options()->cnavatar ? Helper::options()->cnavatar : 'https://cravatar.cn/avatar/';
$hash = md5($email);
$avatar = rtrim($cnavatar, '/') . '/' . $hash . '?s=80&d=identicon';
// 其余统计信息保持不变
$userCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.users'))->num;
$postCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.contents')->where('type = ?', 'post')->where('status = ?', 'publish'))->num;
$commentCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.comments'))->num;
$totalViews = $db->fetchObject(
$db->select(array('SUM(views)' => 'viewsum'))->from('table.contents')->where('type = ?', 'post')
)->viewsum;
if ($totalViews === null) $totalViews = 0;
return [
'email' => $email,
'nickname' => $nickname,
'avatar' => $avatar,
'userCount' => $userCount,
'postCount' => $postCount,
'commentCount' => $commentCount,
'totalViews' => $totalViews,
'isLogin' => $currentUser->hasLogin() // 添加一个是否登录的标志
];
}
// Typecho AJAX 登录接口,支持前端 AJAX 提交并返回 JSON
if (!empty($_GET['ajaxLogin']) && $_GET['ajaxLogin'] == 1) {
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'msg' => '请求方式错误']);
exit;
}
$name = isset($_POST['name']) ? trim($_POST['name']) : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';
$referer = isset($_POST['referer']) ? $_POST['referer'] : '/';
if (!$name || !$password) {
echo json_encode(['success' => false, 'msg' => '用户名或密码不能为空']);
exit;
}
$user = Typecho_Widget::widget('Widget_User');
try {
$user->login($name, $password, isset($_POST['remember']) ? 1 : 0);
echo json_encode(['success' => true, 'msg' => '登录成功', 'redirect' => $referer]);
} catch (Typecho_Exception $e) {
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
exit;
}

View File

@ -83,6 +83,15 @@
</a>
</li>
<?php endwhile; ?>
<?php if($this->user->hasLogin()): ?>
<li>
<a data-bs-toggle="tooltip" title="用户中心" href="/admin" target="_blank">
<img alt="用户中心" src="<?php $stats = get_site_statistics();echo $stats['avatar']; ?>" class="min-avatar">
</a>
</li>
<?php else: ?>
<li><a data-no-instant data-bs-toggle="tooltip" title="登入" data-title="登入" href="javascript:void(0)" class="pk-modal-toggle" data-once-load="true" data-url="<?php echo get_correct_url('/login/'); ?>"><i class="fa fa-right-to-bracket"></i></a></li>
<?php endif; ?>
<li><a class="colorMode" data-bs-toggle="tooltip" title="模式切换" href="javascript:void(0)"><i class="fa fa-circle-half-stroke"></i></a></li>
<li><a class="search-modal-btn" data-bs-toggle="tooltip" title="搜索" href="javascript:void(0)"><i class="fa fa-search"></i></a></li>
</ul>
@ -130,8 +139,8 @@
<?php endwhile; ?>
<li class='menu-item menu-item-type-post_type menu-item-object-page'>
<span><a href="#">分类</a>
<a href="#menu-sub-689" data-bs-toggle="collapse"><i class="fa fa-chevron-down t-sm ml-1 menu-sub-icon"></i></a></span>
<ul id="menu-sub-689" class="sub-menu collapse">
<a href="#menu" data-bs-toggle="collapse"><i class="fa fa-chevron-down t-sm ml-1 menu-sub-icon"></i></a></span>
<ul id="menu" class="sub-menu collapse">
<?php $categories = Typecho_Widget::widget('Widget_Metas_Category_List'); ?>
<?php while($categories->next()): ?>
<li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-child">
@ -143,6 +152,15 @@
</li>
<?php endwhile; ?>
</ul>
<?php if($this->user->hasLogin()): ?>
<li>
<a data-bs-toggle="tooltip" title="用户中心" href="/admin" target="_blank">
<img alt="用户中心" src="<?php $stats = get_site_statistics();echo $stats['avatar']; ?>" class="min-avatar">
</a>
</li>
<?php else: ?>
<li><a data-no-instant data-bs-toggle="tooltip" title="登入" data-title="登入" href="javascript:void(0)" class="pk-modal-toggle" data-once-load="true" data-url="<?php echo get_correct_url('/login/'); ?>"><i class="fa fa-right-to-bracket"></i></a></li>
<?php endif; ?>
</ul>
</nav>
</div>

View File

@ -4,7 +4,7 @@
*
* @package Typecho Pouck Theme
* @author 老孙博客
* @version 1.1.9
* @version 1.2.0
* @link http://www.imsun.org
*/
@ -82,7 +82,7 @@ if ($pageprev == '1' && $this->have()):
'wrapClass' => 'pagination comment-ajax-load',
'itemTag' => 'li',
'textTag' => 'span',
'currentClass' => 'active',
'currentClass' => 'cur',
'prevClass' => 'prev',
'nextClass' => 'next'
)); ?>

View File

@ -18,57 +18,34 @@
</div>
<?php endif; ?>
<!-- 个人信息 -->
<?php
// 获取数据库连接
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
// 1. 获取uid=1的用户信息
$user = $db->fetchRow($db->select()->from('table.users')->where('uid = ?', 1));
$email = $user['mail'];
$nickname = $user['screenName'];
// 获取用户设置的 Gravatar 镜像
$cnavatar = Helper::options()->cnavatar ? Helper::options()->cnavatar : 'https://cravatar.cn/avatar/';
$hash = md5($email);
$avatar = rtrim($cnavatar, '/') . '/' . $hash . '?s=80&d=identicon';
// 2. 获取用户总数
$userCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.users'))->num;
// 3. 获取文章总数(只统计 type='post' 且 status='publish'
$postCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.contents')->where('type = ?', 'post')->where('status = ?', 'publish'))->num;
// 4. 获取评论总数
$commentCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.comments'))->num;
// 5. 获取文章浏览总量(累加所有文章的 views 字段)
$totalViews = $db->fetchObject(
$db->select(array('SUM(views)' => 'viewsum'))->from('table.contents')->where('type = ?', 'post')
)->viewsum;
if ($totalViews === null) $totalViews = 0;
?>
<!-- 个人信息 -->
<?php $stats = get_site_statistics(); ?>
<?php if (!empty($this->options->sidebarBlock) && in_array('ShowAdmin', $this->options->sidebarBlock)): ?>
<div class="widget-puock-author widget">
<div class="header" style="background-image: url('<?php echo !empty($this->options->bgUrl) ? $this->options->bgUrl : $this->options->themeUrl('assets/img/cover.png'); ?>')">
<img src='<?php $this->options->themeUrl('assets/img/load.svg'); ?>' class='lazy avatar' data-src='<?php echo $avatar; ?>' >
<img src='<?php $this->options->themeUrl('assets/img/load.svg'); ?>' class='lazy avatar' data-src='<?php echo $stats['avatar']; ?>' >
</div>
<div class="content t-md puock-text">
<div class="text-center p-2">
<div class="t-lg"><?php echo $nickname; ?></div>
<div class="t-lg"><?php echo $stats['nickname']; ?></div>
<div class="mt10 t-sm"><?php $this->options->description(); ?></div>
</div>
<div class="row mt10">
<div class="col-3 text-center">
<div class="c-sub t-sm">用户数</div>
<div><?php echo $userCount; ?></div>
<div><?php echo $stats['userCount']; ?></div>
</div>
<div class="col-3 text-center">
<div class="c-sub t-sm">文章数</div>
<div><?php echo $postCount; ?></div>
<div><?php echo $stats['postCount']; ?></div>
</div>
<div class="col-3 text-center">
<div class="c-sub t-sm">评论数</div>
<div><?php echo $commentCount; ?></div>
<div><?php echo $stats['commentCount']; ?></div>
</div>
<div class="col-3 text-center">
<div class="c-sub t-sm">阅读量</div>
<div><?php echo $totalViews; ?></div>
<div><?php echo $stats['totalViews']; ?></div>
</div>
</div>
</div>