|
|
|
|
@ -1,87 +1,306 @@
|
|
|
|
|
{# gq: #}
|
|
|
|
|
{# 1. 模板继承:复用网站基础布局(包含头部导航、底部信息、全局样式等公共组件)#}
|
|
|
|
|
{% extends 'share_layout/base.html' %}
|
|
|
|
|
|
|
|
|
|
{# 2. 加载自定义博客标签库:提供文章详情渲染、侧边栏加载等专属功能 #}
|
|
|
|
|
{% load blog_tags %}
|
|
|
|
|
{% load cache %}
|
|
|
|
|
|
|
|
|
|
{# 3. 重写header块:此处留空,说明文章详情页的头部元信息(如标题、SEO标签)#}
|
|
|
|
|
{# 可能通过其他方式(如SEO插件)动态生成,或在base.html中统一处理 #}
|
|
|
|
|
{% block header %}
|
|
|
|
|
<title>{{ article.title }} | {{ SITE_DESCRIPTION }}</title>
|
|
|
|
|
<meta name="description" content="{{ article.body|truncatewords:1 }}"/>
|
|
|
|
|
<meta name="keywords" content="{% keywords_to_str article %}"/>
|
|
|
|
|
<meta property="og:type" content="article"/>
|
|
|
|
|
<meta property="og:title" content="{{ article.title }}"/>
|
|
|
|
|
<meta property="og:description" content="{{ article.body|truncatewords:1 }}"/>
|
|
|
|
|
<meta property="og:url"
|
|
|
|
|
content="{{ article.get_full_url }}"/>
|
|
|
|
|
<meta property="article:published_time" content="{% datetimeformat article.pub_time %}"/>
|
|
|
|
|
<meta property="article:modified_time" content="{% datetimeformat article.last_modify_time %}"/>
|
|
|
|
|
<meta property="article:author" content="{{ article.author.get_full_url }}"/>
|
|
|
|
|
<meta property="article:section" content="{{ article.category.name }}"/>
|
|
|
|
|
{% for t in article.tags.all %}
|
|
|
|
|
<meta property="article:tag" content="{{ t.name }}"/>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
<link rel="canonical" href="{{ article.get_full_url }}"/>
|
|
|
|
|
<style type="text/css">
|
|
|
|
|
.like-button {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
border: none;
|
|
|
|
|
color: #333;
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
margin: 4px 2px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
min-width: 100px; /* 确保按钮有足够的宽度 */
|
|
|
|
|
visibility: visible !important; /* 强制确保按钮可见 */
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.like-button:hover {
|
|
|
|
|
background-color: #e0e0e0;
|
|
|
|
|
transform: scale(1.05); /* 调整缩放比例 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.like-button.liked {
|
|
|
|
|
background-color: #ff6b6b;
|
|
|
|
|
color: white;
|
|
|
|
|
box-shadow: 0 2px 4px rgba(255, 107, 107, 0.5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.like-button.loading {
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.likes-count {
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
margin-left: 5px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.entry-likes {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 确保点赞按钮区域总是可见 */
|
|
|
|
|
.entry-meta .entry-likes {
|
|
|
|
|
display: inline-block !important;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
{# 4. 重写content块:定义文章详情页的核心内容(文章主体、导航、评论区)#}
|
|
|
|
|
{% block content %}
|
|
|
|
|
<div id="primary" class="site-content"> {# 主内容区容器,应用网站全局样式 #}
|
|
|
|
|
<div id="content" role="main"> {# 核心内容容器,role属性提升无障碍访问性 #}
|
|
|
|
|
|
|
|
|
|
{# 渲染文章详情:调用自定义标签load_article_detail #}
|
|
|
|
|
{# 参数说明:#}
|
|
|
|
|
{# article:当前文章对象(包含标题、内容、发布时间等信息)#}
|
|
|
|
|
{# False:控制是否显示编辑按钮(此处为False,仅展示不允许编辑)#}
|
|
|
|
|
{# user:当前登录用户对象(用于权限判断,如作者才能编辑)#}
|
|
|
|
|
{% load_article_detail article False user %}
|
|
|
|
|
|
|
|
|
|
{# 文章导航(上一篇/下一篇):仅当文章类型为'a'(普通文章)时显示 #}
|
|
|
|
|
{% if article.type == 'a' %}
|
|
|
|
|
<nav class="nav-single"> {# 导航容器,应用单篇文章导航样式 #}
|
|
|
|
|
<h3 class="assistive-text">文章导航</h3> {# 辅助文本,提升无障碍访问性 #}
|
|
|
|
|
|
|
|
|
|
{# 下一篇文章链接:若存在下一篇(发布时间更早的文章)则显示 #}
|
|
|
|
|
{% if next_article %}
|
|
|
|
|
<span class="nav-previous">
|
|
|
|
|
<a href="{{ next_article.get_absolute_url }}" rel="prev"> {# rel="prev"告知搜索引擎是前序内容 #}
|
|
|
|
|
<span class="meta-nav">←</span> {# 左箭头符号 #}
|
|
|
|
|
{{ next_article.title }} {# 下一篇文章标题 #}
|
|
|
|
|
</a>
|
|
|
|
|
<div id="primary" class="site-content">
|
|
|
|
|
<div id="content" role="main">
|
|
|
|
|
<!-- 将点赞按钮移出缓存区域以确保它能正确加载 -->
|
|
|
|
|
<article id="post-{{ article.pk }}" class="post-{{ article.pk }} post type-post status-publish format-standard hentry">
|
|
|
|
|
<header class="entry-header">
|
|
|
|
|
<h1 class="entry-title">
|
|
|
|
|
{{ article.title }}
|
|
|
|
|
</h1>
|
|
|
|
|
<div class="entry-meta">
|
|
|
|
|
<span class="byline">
|
|
|
|
|
<i class="fa fa-user"></i>
|
|
|
|
|
<span class="author vcard">
|
|
|
|
|
<a class="url fn n" href="{{ article.author.get_absolute_url }}"
|
|
|
|
|
title="View all posts by {{ article.author.username }}"
|
|
|
|
|
rel="author">{{ article.author.username }}
|
|
|
|
|
</a>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="sep">|</span>
|
|
|
|
|
<span class="entry-date">
|
|
|
|
|
<i class="fa fa-calendar"></i>
|
|
|
|
|
<time class="entry-date updated" datetime="{{ article.pub_time }}">
|
|
|
|
|
{% datetimeformat article.pub_time %}
|
|
|
|
|
</time>
|
|
|
|
|
</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
{# 上一篇文章链接:若存在上一篇(发布时间更晚的文章)则显示 #}
|
|
|
|
|
{% if prev_article %}
|
|
|
|
|
<span class="nav-next">
|
|
|
|
|
<a href="{{ prev_article.get_absolute_url }}" rel="next"> {# rel="next"告知搜索引擎是后序内容 #}
|
|
|
|
|
{{ prev_article.title }} {# 上一篇文章标题 #}
|
|
|
|
|
<span class="meta-nav">→</span> {# 右箭头符号 #}
|
|
|
|
|
<span class="sep">|</span>
|
|
|
|
|
<span class="entry-category">
|
|
|
|
|
<i class="fa fa-folder-o"></i>
|
|
|
|
|
<a href="{{ article.category.get_absolute_url }}" rel="category tag">
|
|
|
|
|
{{ article.category.name }}
|
|
|
|
|
</a>
|
|
|
|
|
</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</nav><!-- .nav-single -->
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
</div><!-- #content -->
|
|
|
|
|
|
|
|
|
|
{# 评论区:仅当文章允许评论(comment_status == "o")且网站全局开启评论(OPEN_SITE_COMMENT为True)时显示 #}
|
|
|
|
|
{% if article.comment_status == "o" and OPEN_SITE_COMMENT %}
|
|
|
|
|
<span class="sep">|</span>
|
|
|
|
|
<span class="entry-views">
|
|
|
|
|
<i class="fa fa-eye"></i>
|
|
|
|
|
{{ article.views }} views
|
|
|
|
|
</span>
|
|
|
|
|
<span class="sep">|</span>
|
|
|
|
|
<span class="entry-likes">
|
|
|
|
|
<button id="like-button" class="like-button" data-article-id="{{ article.pk }}">
|
|
|
|
|
<i class="fa fa-heart"></i>
|
|
|
|
|
<span class="likes-text">Like</span>
|
|
|
|
|
<span class="likes-count">({{ article.likes }})</span>
|
|
|
|
|
</button>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
{% cache 36000 article_detail_content article.pk %}
|
|
|
|
|
<div class="entry-content">
|
|
|
|
|
{{ article.body|custom_markdown|escape|linebreaksbr }}
|
|
|
|
|
</div>
|
|
|
|
|
<footer class="entry-footer">
|
|
|
|
|
<div style="padding-bottom: 10px;">
|
|
|
|
|
<hr/>
|
|
|
|
|
<div class="post-like">
|
|
|
|
|
<i class="fa fa-tags"></i>
|
|
|
|
|
{% for tag in article.tags.all %}
|
|
|
|
|
<a href="{{ tag.get_absolute_url }}" rel="tag">{{ tag.name }}</a>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</footer>
|
|
|
|
|
{% endcache %}
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
{# 引入评论列表模板:展示已有评论 #}
|
|
|
|
|
<!-- You can start editing here. -->
|
|
|
|
|
{% include 'comments/tags/comment_list.html' %}
|
|
|
|
|
|
|
|
|
|
{# 评论表单:根据用户登录状态显示不同内容 #}
|
|
|
|
|
{% if user.is_authenticated %}
|
|
|
|
|
{# 已登录用户:显示评论提交表单 #}
|
|
|
|
|
{% include 'comments/tags/post_comment.html' %}
|
|
|
|
|
{% else %}
|
|
|
|
|
{# 未登录用户:提示登录后评论,并提供登录入口 #}
|
|
|
|
|
<div class="comments-area">
|
|
|
|
|
<h3 class="comment-meta">
|
|
|
|
|
您还没有登录,请您
|
|
|
|
|
{# 登录链接:附带next参数,登录后跳回当前文章页 #}
|
|
|
|
|
<a href="{% url "account:login" %}?next={{ request.get_full_path }}" rel="nofollow">登录</a>
|
|
|
|
|
后发表评论。
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
{# 第三方登录入口:加载OAuth登录标签,提供社交账号登录选项 #}
|
|
|
|
|
{% load oauth_tags %} {# 加载自定义OAuth标签库 #}
|
|
|
|
|
{% load_oauth_applications request %} {# 渲染第三方登录按钮(如GitHub、Google)#}
|
|
|
|
|
<div class="comments-area-title">
|
|
|
|
|
<h3 class="comments-title">评论</h3>
|
|
|
|
|
<div class="comment-form-login-notice">
|
|
|
|
|
<p>您必须<a href="{% url 'account:login' %}?next={{ request.get_full_path }}">登录</a>才能发表评论。</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div><!-- #primary -->
|
|
|
|
|
{% endblock %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
|
|
|
<script>
|
|
|
|
|
// 添加获取CSRF token的函数
|
|
|
|
|
function getCookie(name) {
|
|
|
|
|
let cookieValue = null;
|
|
|
|
|
if (document.cookie && document.cookie !== '') {
|
|
|
|
|
const cookies = document.cookie.split(';');
|
|
|
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
|
|
|
const cookie = cookies[i].trim();
|
|
|
|
|
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
|
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return cookieValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用一个全局变量来跟踪是否已经初始化了点赞按钮
|
|
|
|
|
let likeButtonInitialized = false;
|
|
|
|
|
|
|
|
|
|
// 创建一个初始化函数
|
|
|
|
|
function initializeLikeButton() {
|
|
|
|
|
// 如果已经初始化过,则不再执行
|
|
|
|
|
if (likeButtonInitialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const likeButton = document.getElementById('like-button');
|
|
|
|
|
|
|
|
|
|
// 添加调试信息
|
|
|
|
|
console.log('Like button element:', likeButton);
|
|
|
|
|
console.log('Article ID:', likeButton ? likeButton.getAttribute('data-article-id') : 'Not found');
|
|
|
|
|
|
|
|
|
|
if (likeButton) {
|
|
|
|
|
likeButtonInitialized = true;
|
|
|
|
|
|
|
|
|
|
const article_id = likeButton.getAttribute('data-article-id');
|
|
|
|
|
const likesCountElement = likeButton.querySelector('.likes-count');
|
|
|
|
|
const likesTextElement = likeButton.querySelector('.likes-text');
|
|
|
|
|
|
|
|
|
|
// 检查用户是否已点赞
|
|
|
|
|
axios.get(`/article/${article_id}/like/check/`, {
|
|
|
|
|
headers: {
|
|
|
|
|
'X-CSRFToken': getCookie('csrftoken')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.then(function(response) {
|
|
|
|
|
const data = response.data;
|
|
|
|
|
if (data.liked) {
|
|
|
|
|
likeButton.classList.add('liked');
|
|
|
|
|
likesTextElement.textContent = 'Liked'; // 确保更新按钮文本
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(function(error) {
|
|
|
|
|
console.error('Error checking like status:', error);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 点击事件处理
|
|
|
|
|
likeButton.addEventListener('click', function() {
|
|
|
|
|
// 添加加载状态
|
|
|
|
|
likeButton.classList.add('loading');
|
|
|
|
|
likeButton.disabled = true;
|
|
|
|
|
|
|
|
|
|
// 根据当前状态决定是点赞还是取消点赞
|
|
|
|
|
const isLiked = likeButton.classList.contains('liked');
|
|
|
|
|
const url = `/article/${article_id}/like/`;
|
|
|
|
|
|
|
|
|
|
// 发送相应请求
|
|
|
|
|
let requestPromise;
|
|
|
|
|
if (isLiked) {
|
|
|
|
|
// 取消点赞 - 发送 DELETE 请求
|
|
|
|
|
requestPromise = axios.delete(url, {
|
|
|
|
|
headers: {
|
|
|
|
|
'X-CSRFToken': getCookie('csrftoken')
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 点赞 - 发送 POST 请求
|
|
|
|
|
requestPromise = axios.post(url, {}, {
|
|
|
|
|
headers: {
|
|
|
|
|
'X-CSRFToken': getCookie('csrftoken')
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
requestPromise
|
|
|
|
|
.then(function(response) {
|
|
|
|
|
const data = response.data;
|
|
|
|
|
|
|
|
|
|
// 更新点赞数量
|
|
|
|
|
if (data.likes_count !== undefined) {
|
|
|
|
|
likesCountElement.textContent = '(' + data.likes_count + ')';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新按钮状态
|
|
|
|
|
if (data.liked !== undefined) {
|
|
|
|
|
if (data.liked) {
|
|
|
|
|
likeButton.classList.add('liked');
|
|
|
|
|
likesTextElement.textContent = 'Liked';
|
|
|
|
|
} else {
|
|
|
|
|
likeButton.classList.remove('liked');
|
|
|
|
|
likesTextElement.textContent = 'Like';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 移除加载状态
|
|
|
|
|
likeButton.classList.remove('loading');
|
|
|
|
|
likeButton.disabled = false;
|
|
|
|
|
})
|
|
|
|
|
.catch(function(error) {
|
|
|
|
|
// 移除加载状态
|
|
|
|
|
likeButton.classList.remove('loading');
|
|
|
|
|
likeButton.disabled = false;
|
|
|
|
|
|
|
|
|
|
if (error.response && error.response.status === 403) {
|
|
|
|
|
alert('请先登录再点赞');
|
|
|
|
|
window.location.href = '/accounts/login/?next=' + encodeURIComponent(window.location.pathname);
|
|
|
|
|
} else if (error.response && error.response.data) {
|
|
|
|
|
// 处理400错误返回的JSON数据
|
|
|
|
|
const data = error.response.data;
|
|
|
|
|
if (data.detail) {
|
|
|
|
|
alert(data.detail);
|
|
|
|
|
}
|
|
|
|
|
// 即使出现错误,也更新显示正确的点赞数
|
|
|
|
|
if (data.likes_count !== undefined) {
|
|
|
|
|
likesCountElement.textContent = '(' + data.likes_count + ')';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert('操作失败,请稍后再试');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Like button not found in the DOM.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{# 5. 重写sidebar块:加载文章详情页专属侧边栏 #}
|
|
|
|
|
{% block sidebar %}
|
|
|
|
|
{# 调用自定义标签load_sidebar,参数"p"可能指定侧边栏样式(如包含热门文章、分类等组件)#}
|
|
|
|
|
{% load_sidebar user "p" %}
|
|
|
|
|
// 使用更可靠的DOM加载完成检测方法
|
|
|
|
|
if (document.readyState === 'loading') {
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initializeLikeButton);
|
|
|
|
|
} else {
|
|
|
|
|
// DOM已经加载完成
|
|
|
|
|
initializeLikeButton();
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|