使用评论实现说说的功能
This commit is contained in:
浪子 2025-08-04 17:00:37 +08:00
parent 0017693c81
commit 4dcaefb291
7 changed files with 219 additions and 200 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
*.html
/.vercel
page-talks copy.php

View File

@ -171,7 +171,7 @@
<?php endif; ?>
</div>
<div class="t-sm c-sub">
<span><?php $comments->date('Y-m-d H:i:s'); ?></span>
<span><?php echo friendly_date($comments->created); ?></span>
<a rel="nofollow" class="hide-info animated bounceIn c-sub-a t-sm ml-1 comment-reply" href="javascript:void(0);" data-coid="<?php echo $comments->coid; ?>"><span class="comment-reply-text"><i class="fa fa-share-from-square"></i>回复</span></a>
</div>
</div>

View File

@ -92,6 +92,16 @@ function themeFields($layout) {
$layout->addItem($cover);
}
/** 头像镜像加速全局设置
* @param $email
* @param $size
* @param $default
* @return string
*/
$options = Typecho_Widget::widget('Widget_Options');
$gravatarPrefix = empty($options->cnavatar) ? 'https://cravatar.cn/avatar/' : $options->cnavatar;
define('__TYPECHO_GRAVATAR_PREFIX__', $gravatarPrefix);
/*
* 文章浏览数统计
*/
@ -1437,26 +1447,25 @@ function get_site_statistics() {
];
}
// 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;
//}
/**
* 友好时间显示函数
* @param $timestamp
* @return string
*/
function friendly_date($timestamp) {
$time = is_numeric($timestamp) ? $timestamp : strtotime($timestamp);
$diff = time() - $time;
if ($diff < 60) {
return '刚刚';
} elseif ($diff < 3600) {
return floor($diff / 60) . '分钟前';
} elseif ($diff < 86400) {
return floor($diff / 3600) . '小时前';
} elseif ($diff < 2592000) {
return floor($diff / 86400) . '天前';
} elseif ($diff < 31536000) {
return floor($diff / 2592000) . '月前';
} else {
return date('Y-m-d H:i:s', $time);
}
}

View File

@ -5,7 +5,7 @@
*
* @package Typecho Pouck Theme
* @author 老孙博客
* @version 1.2.2
* @version 1.2.3
* @link http://www.imsun.org
*/

View File

@ -16,7 +16,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
</div>
<div id="page">
<div class="row row-cols-1">
<div id="posts" class="col-12 animated fadeInLeft ">
<div id="posts" class="col-12">
<div class="puock-text no-style">
<p><?php $this->content(); ?></p>
</div>

182
page-says.php Normal file
View File

@ -0,0 +1,182 @@
<?php
/**
* 说说页面
* @package custom
* @author 老孙
* @version 1.0
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php');
?>
<div id="breadcrumb" class="animated fadeInUp">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
<li class="breadcrumb-item active" aria-current="page"><?php $this->title() ?></li>
</ol>
</nav>
</div>
<div id="page-moments">
<div class="row">
<?php if ($this->options->showsidebar): ?>
<div id="comments" class="col-lg-8 col-md-12 animated fadeInLeft">
<?php else: ?>
<div id="comments" class="col-lg-12 col-md-12">
<?php endif; ?>
<?php
// 登录状态检测
if ($this->user->hasLogin()) {
$GLOBALS['isLogin'] = true;
} else {
$GLOBALS['isLogin'] = false;
}
// 评论回调函数
function threadedComments($comments, $options) {
$mail = $comments->mail;
$mailHash = md5(strtolower(trim($mail)));
$purl = $comments->url;
$nickname = $comments->author;
$cnavatar = Helper::options()->cnavatar ? Helper::options()->cnavatar : 'https://cravatar.cn/avatar/';
$avatarurl = rtrim($cnavatar, '/') . '/' . $mailHash . '?s=80&d=identicon';
$loadingImg = Helper::options()->themeUrl . '/assets/img/load.svg';
?>
<div class="mb20 puock-text moments-item">
<div class="row">
<div class="col-12 col-md-1">
<a class="meta ta3" href="<?php echo $purl; ?>" target="_blank">
<div class="avatar mb10">
<img src='<?php echo $loadingImg; ?>'
class='lazy md-avatar mt-1'
data-src='<?php echo $avatarurl; ?>'
>
</div>
<div class="t-line-1 info fs12"><?php echo $nickname; ?></div>
</a>
</div>
<div class="col-12 col-md-11">
<div class="p-block moment-content-box"> <span class="al"></span>
<div class="mt10 moment-content entry-content show-link-icon">
<?php if ($comments->parent) {echo getPermalinkFromCoid($comments->parent);} echo parse_smiley_shortcode($comments->content);?>
</div>
<div class="mt10 moment-footer p-flex-s-right">
<span class="t-sm c-sub">
<span class="mr-2"><i class="fa-regular fa-clock mr-1"></i><?php echo friendly_date($comments->created); ?></span>
</span>
</div>
</div>
</div>
</div>
</div>
<?php } ?>
<?php $this->comments()->to($comments); ?>
<?php if ($comments->have()): ?>
<?php if ($this->user->hasLogin() && $this->user->group == 'administrator') : ?>
<div class="p-block">
<input type="hidden" value="<?php $this->commentUrl() ?>">
<div>
<span class="t-lg border-bottom border-primary puock-text pb-2">
<i class="fa-regular fa-comments mr-1"></i>有什么新鲜事
</span>
</div>
<div class="mt20 clearfix" id="comment-form-box">
<form method="post" action="<?php $this->commentUrl() ?>" id="comment-form" class="mt10" role="form">
<div class="form-group">
<textarea rows="4" name="text" id="comment" class="form-control form-control-sm t-sm" placeholder="发表您的新鲜事儿..." required><?php $this->remember('text'); ?></textarea>
</div>
<input type="hidden" value="<?php $this->user->screenName(); ?>" name="author" />
<input type="hidden" value="<?php $this->user->mail(); ?>" name="mail" />
<input type="hidden" value="<?php $this->options->siteUrl(); ?>" name="url" />
<input type="hidden" name="_" value="<?php Typecho_Widget::widget('Widget_Security')->to($security);
echo $security->getToken($this->request->getRequestUrl()); ?>">
<div class="p-flex-sbc mt10">
<div class="form-foot">
<?php if($this->options->social): ?>
<button id="comment-insert-image" class="btn btn-outline-secondary btn-ssm pk-modal-toggle" type="button" title="插入图片">
<i class="fa-solid fa-image"></i>
</button>
<button id="comment-smiley" class="btn btn-outline-secondary btn-ssm pk-modal-toggle" type="button" title="表情" data-once-load="true"
data-url="<?php echo get_correct_url('/emoji/'); ?>">
<i class="fa-regular fa-face-smile t-md"></i>
</button>
<?php endif; ?>
<button type="submit" class="btn btn-primary btn-ssm"><i class="fa-regular fa-paper-plane"></i> 立即发表</button>
</div>
</div>
</form>
</div>
</div>
<?php endif; ?>
<!-- 评论列表 -->
<?php while ($comments->next()): ?>
<?php threadedComments($comments, $this->options); ?>
<?php endwhile; ?>
<!-- 分页导航 -->
<div class="mt20 p-flex-s-right" data-no-instant>
<?php $comments->pageNav('&laquo;', '&raquo;', 1, '...', array(
'wrapTag' => 'ul',
'wrapClass' => 'pagination comment-ajax-load',
'itemTag' => 'li',
'textTag' => 'span',
'currentClass' => 'active',
'prevClass' => 'prev',
'nextClass' => 'next'
)); ?>
</div>
</div>
<?php endif; ?>
<?php if ($this->options->showsidebar): ?>
<?php $this->need('sidebar.php'); ?>
<?php endif; ?>
</div>
</div>
<?php $this->need('footer.php'); ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
var btn = document.getElementById('comment-insert-image');
if (!btn) return;
btn.addEventListener('click', function() {
let modal = document.createElement('div');
modal.innerHTML = `
<div id="img-insert-modal" style="position:fixed;z-index:9999;left:0;top:0;width:100vw;height:100vh;background:rgba(0,0,0,0.3);display:flex;align-items:center;justify-content:center;">
<div style="background:#fff;padding:24px 32px;border-radius:8px;min-width:320px;box-shadow:0 2px 16px rgba(0,0,0,0.15);">
<div style="font-size:18px;font-weight:bold;margin-bottom:12px;">插入图片</div>
<div style="margin-bottom:10px;">
<label style="display:block;font-size:14px;margin-bottom:4px;">标题(可选)</label>
<input id="img-insert-title" type="text" style="width:100%;padding:6px 8px;border:1px solid #ccc;border-radius:4px;">
</div>
<div style="margin-bottom:10px;">
<label style="display:block;font-size:14px;margin-bottom:4px;">图片地址 <span style="color:red">*</span></label>
<input id="img-insert-url" type="text" style="width:100%;padding:6px 8px;border:1px solid #ccc;border-radius:4px;" required>
</div>
<div style="text-align:right;">
<button id="img-insert-cancel" style="margin-right:10px;padding:6px 16px;">取消</button>
<button id="img-insert-confirm" style="background:#007bff;color:#fff;padding:6px 16px;border:none;border-radius:4px;">插入</button>
</div>
</div>
</div>`;
document.body.appendChild(modal);
document.getElementById('img-insert-cancel').onclick = function() {
document.body.removeChild(modal);
};
document.getElementById('img-insert-confirm').onclick = function() {
let url = document.getElementById('img-insert-url').value.trim();
let title = document.getElementById('img-insert-title').value.trim();
if (!url) {
alert('图片地址不能为空!');
return;
}
let tag = `<img src=\"${url}\"` + (title ? ` alt=\"${title}\" title=\"${title}\"` : '') + ` />`;
let textarea = document.getElementById('comment');
if (textarea) {
let start = textarea.selectionStart, end = textarea.selectionEnd;
let val = textarea.value;
textarea.value = val.substring(0, start) + tag + val.substring(end);
textarea.focus();
textarea.selectionStart = textarea.selectionEnd = start + tag.length;
}
document.body.removeChild(modal);
};
});
});
</script>

View File

@ -1,174 +0,0 @@
<?php
/**
* 说说页面
*
* @package custom
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
<?php $this->need('header.php'); ?>
<div id="breadcrumb" class="animated fadeInUp">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
<li class="breadcrumb-item active " aria-current="page"><?php $this->title() ?></li>
</ol>
</nav>
<div id="page-moments">
<div class="row">
<?php if ($this->options->showsidebar): ?>
<div id="posts" class="col-lg-8 col-md-12 animated fadeInLeft ">
<?php else: ?>
<div id="posts" class="col-lg-12 col-md-12">
<?php endif; ?>
<?php $tooot = $this->fields->tooot ? $this->fields->tooot : 'https://www.imsun.org/toot.json'; ?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.12/marked.min.js" integrity="sha512-rCQgmUulW6f6QegOvTntKKb5IAoxTpGVCdWqYjkXEpzAns6XUFs8NKVqWe+KQpctp/EoRSFSuykVputqknLYMg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.5/css/lightbox.min.css" integrity="sha512-xtV3HfYNbQXS/1R1jP53KbFcU9WXiSA1RFKzl5hRlJgdOJm4OxHCWYpskm6lN0xp0XtKGpAfVShpbvlFH3MDAA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.5/js/lightbox.min.js" integrity="sha512-KbRFbjA5bwNan6DvPl1ODUolvTTZ/vckssnFhka5cG80JVa5zSlRPCr055xSgU/q6oMIGhZWLhcbgIC0fyw3RQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="tooot"></div>
</div>
<script>
function fetchAndDisplayToots() {
let offset = 0;
const limit = 20;
function formatHTML(toots) {
let htmlString = '';
toots.forEach(toot => {
const isReblog = toot.reblog && toot.reblog.content;
const content = isReblog ? toot.reblog.content : toot.content;
const url = isReblog ? toot.reblog.url : toot.url;
const account = isReblog ? toot.reblog.account : toot.account;
const created_at = isReblog ? toot.reblog.created_at : toot.created_at;
const media_attachments = isReblog ? toot.reblog.media_attachments : toot.media_attachments;
let mediaHTML = '';
if (media_attachments && media_attachments.length > 0) {
media_attachments.forEach(attachment => {
if (attachment.type === 'image') {
mediaHTML += `<a href="${attachment.url}" target="_blank" data-lightbox="image-set"><img src="${attachment.preview_url}" class="thumbnail-image img" ></a>`;
}
});
}
const htmlContent = marked.parse(content || '');
htmlString += `
<div class="mb20 puock-text moments-item">
<div class="row">
<div class="col-12 col-md-1">
<a class="meta ta3" href="${account.url}" target="_blank" rel="nofollow">
<div class="avatar mb10">
<img src='${account.avatar}'
class='lazy md-avatar mt-1'
data-src='${account.avatar}'
alt="${account.display_name}" title="${account.display_name}">
</div>
<div class="t-line-1 info fs12">${account.display_name}</div>
</a>
</div>
<div class="col-12 col-md-11">
<div class="p-block moment-content-box"> <span class="al"></span>
<div class="mt10 moment-content entry-content show-link-icon">
<p>${htmlContent}</p>
<div class="resimg">${mediaHTML}</div>
</div>
<div class="mt10 moment-footer p-flex-s-right"> <span class="t-sm c-sub">
<a class="c-sub-a" href="${url}">
<span class="mr-2"><i class="fa-regular fa-clock mr-1"></i>${new Date(created_at).toLocaleString()}</span>
</a>
</span>
</div>
</div>
</div>
</div>
</div>
`;
});
return htmlString;
}
function fetchToots() {
return fetch('<?php echo $tooot; ?>')
.then(response => response.json())
.catch(error => {
console.error('Error fetching toots:', error);
return [];
});
}
const memosContainer = document.getElementById('tooot');
if (memosContainer) memosContainer.innerHTML = '';
fetchToots().then(data => {
if (!Array.isArray(data)) {
console.error('toot.json is not an array:', data);
return;
}
const tootsToShow = data.slice(offset, offset + limit);
if (memosContainer) memosContainer.innerHTML += formatHTML(tootsToShow);
});
}
// 保证首次和 pjax 都能调用
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", fetchAndDisplayToots);
} else {
fetchAndDisplayToots();
}
document.addEventListener('pjax:end', fetchAndDisplayToots);
</script>
<style>
div pre code {
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-all;
word-break: break-word;
}
div p a {
word-break: break-all;
word-break: break-word;
}
.resimg {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 10px;
row-gap: 10px;
}
/* 单个缩略图的样式 */
.thumbnail-image {
width: 100%; /* 确保其宽度填满父容器 */
height: 200px; /* 固定高度 */
display: flex; /* 使用 flexbox 居中 */
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
overflow: hidden; /* 确保容器内的多余内容不会显示出来 */
border-radius: 4px; /* 圆角 */
transition: transform .3s ease; /* 鼠标悬停时的过渡效果 */
cursor: zoom-in; /* 鼠标指针变为放大镜 */
}
img {
object-fit: cover; /* 保持图片的纵横比,但会将图片裁剪以填充容器 */
object-position: center; /* 保证中央部分 */
}
/* 缩略图内的图片样式 */
.thumbnail-image img {
width: 100%;
min-height: 200px;
}
/* 当屏幕宽度小于732px时 */
@media (max-width: 732px) {
.resimg {
grid-template-columns: repeat(2, 1fr); /* 修改为两列 */
}
}
/* 当屏幕宽度小于400px时 */
@media (max-width: 400px) {
.resimg {
grid-template-columns: 1fr; /* 修改为一列 */
}
.thumbnail-image img {
width: 100%;
height: 480px;
}
}
</style>
<?php if ($this->options->showsidebar): ?>
<?php $this->need('sidebar.php'); ?>
<?php endif; ?>
</div>
</div>
<?php $this->need('footer.php'); ?>