Compare commits

...

2 Commits
v1.0.5 ... main

Author SHA1 Message Date
60512830fe 1.侧边栏增加热门文章
2.优化侧边栏爱发电设置
3.优化编辑文章按钮权限控制
4.适配缩略图
5.更新twikoo资源文件到1.6.36版本
6.修复垂直菜单功能
7.最近评论增加头像服务镜像地址配置
2025-06-25 13:29:00 +08:00
2a04f6945b 1.适配朋友圈插件
2.更新版本号为1.0.6-ce
3.README增加朋友圈插件地址
2025-06-24 16:25:32 +08:00
31 changed files with 1898 additions and 81 deletions

View File

@ -4,7 +4,7 @@
<!-- 主题名称 --> <!-- 主题名称 -->
<h1>Halo-Theme-Hao</h1> <h1>Halo-Theme-Hao</h1>
</div> </div>
<!-- 主题预览图 -->
![](https://api.minio.uptoz.cn/blog/images/NsUInjCbZN.webp) ![](https://api.minio.uptoz.cn/blog/images/NsUInjCbZN.webp)
## 🔥 预览 ## 🔥 预览
@ -65,6 +65,7 @@
- Markdown / HTML 内容块插件 <a href="https://www.halo.run/store/apps/app-NgHnY" target="_blank" rel="noopener noreferrer">plugin-hybrid-edit-block</a> - Markdown / HTML 内容块插件 <a href="https://www.halo.run/store/apps/app-NgHnY" target="_blank" rel="noopener noreferrer">plugin-hybrid-edit-block</a>
- 爱发电插件 <a href="https://www.halo.run/store/apps/app-oXvZp" target="_blank" rel="noopener noreferrer">plugin-afdian</a> - 爱发电插件 <a href="https://www.halo.run/store/apps/app-oXvZp" target="_blank" rel="noopener noreferrer">plugin-afdian</a>
- 友链自助提交插件 <a href="https://www.halo.run/store/apps/app-glejqzwk" target="_blank" rel="noopener noreferrer">link-submit</a> - 友链自助提交插件 <a href="https://www.halo.run/store/apps/app-glejqzwk" target="_blank" rel="noopener noreferrer">link-submit</a>
- 朋友圈插件 <a href="https://www.halo.run/store/apps/app-yISsV" target="_blank" rel="noopener noreferrer">plugin-friends</a>
> 更多插件请参见https://www.halo.run/store/apps > 更多插件请参见https://www.halo.run/store/apps

View File

@ -627,7 +627,7 @@ spec:
- $formkit: group - $formkit: group
name: widgetss name: widgetss
label: 侧边栏内容 label: 侧边栏内容
help: 目前提供的小部件有profile个人卡片, wechat公众号, power爱发电赞助, welcome小板报, steamSteam卡片, music音乐卡片, friend-link通讯录, recent-posts最新文章, comments最新评论, categories文章分类, tags文章标签, stat统计, tags-stat标签&归档&统计), adbox广告区域。你可以随意组合或排序以逗号隔开。 help: 目前提供的小部件有profile个人卡片, wechat公众号, power爱发电赞助, welcome小板报, steamSteam卡片, music音乐卡片, friend-link通讯录, visit-posts热门文章, recent-posts最新文章, comments最新评论, categories文章分类, tags文章标签, stat统计, tags-stat标签&归档&统计), adbox广告区域。你可以随意组合或排序以逗号隔开。
value: value:
indexWidgets: profile,wechat,comments,recent-posts,categories,tags-stat indexWidgets: profile,wechat,comments,recent-posts,categories,tags-stat
postWidgets: profile,wechat,toc,comments,recent-posts,categories,tags-stat postWidgets: profile,wechat,toc,comments,recent-posts,categories,tags-stat
@ -750,7 +750,6 @@ spec:
label: 爱发电赞助 label: 爱发电赞助
value: value:
powerLink: / powerLink: /
url: /apis/api.plugin.halo.run/v1alpha1/plugins/plugin-afdian/afdian/getSponsorList
showNum: 3 showNum: 3
children: children:
- $formkit: url - $formkit: url
@ -761,10 +760,6 @@ spec:
name: powerLink name: powerLink
label: 赞助地址 label: 赞助地址
help: https://ifdian.net/a/uptoz help: https://ifdian.net/a/uptoz
- $formkit: text
name: url
label: 接口地址
help: 需要安装爱发电插件
- $formkit: number - $formkit: number
name: showNum name: showNum
label: 最大展示条数 label: 最大展示条数
@ -847,7 +842,7 @@ spec:
help: https://card.yuy1n.io/card/xxxxxx/gradient1,badge,bg-game-1111460,games,screenshots,badges help: https://card.yuy1n.io/card/xxxxxx/gradient1,badge,bg-game-1111460,games,screenshots,badges
- $formkit: number - $formkit: number
name: recentPost name: recentPost
label: 最新文章 label: 最新文章/热门文章
value: 4 value: 4
help: 文章数量 help: 文章数量
- $formkit: group - $formkit: group
@ -856,6 +851,7 @@ spec:
value: value:
newcommentUrl: /newest newcommentUrl: /newest
newcommentnumber: 5 newcommentnumber: 5
providerMirror: "https://cravatar.cn"
children: children:
- $formkit: url - $formkit: url
name: newcommentUrl name: newcommentUrl
@ -864,6 +860,10 @@ spec:
name: newcommentnumber name: newcommentnumber
label: 数量 label: 数量
help: 最新评论数量, 小于 0 则展示5条评论 help: 最新评论数量, 小于 0 则展示5条评论
- $formkit: text
label: 头像服务镜像地址
name: providerMirror
help: 使用官方源则留空, 示例https://gravatar.com
- $formkit: number - $formkit: number
name: categoryQuantity name: categoryQuantity
label: 文章分类 label: 文章分类
@ -1679,6 +1679,11 @@ spec:
height: 300px height: 300px
label: 底部显示内容 label: 底部显示内容
language: html language: html
- $formkit: text
name: fmomentsPageSize
label: 友链每页数量
help: "填写每页(滚动加载)展示的友链数量,"
value: 12
- group: fcircle - group: fcircle
label: 友链鱼塘 label: 友链鱼塘

BIN
templates/.DS_Store vendored

Binary file not shown.

View File

@ -28,7 +28,7 @@
<a class="article-sort-item-img" th:href="@{${post.status.permalink}}" <a class="article-sort-item-img" th:href="@{${post.status.permalink}}"
th:title="${post.spec.title}"> th:title="${post.spec.title}">
<img th:alt="${post.spec.title}" <img th:alt="${post.spec.title}"
th:src="${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : post.spec.cover}"> th:src="${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : thumbnail.gen(post.spec.cover, 's')}">
</a> </a>
<div class="article-sort-item-info"> <div class="article-sort-item-info">
<div class="article-sort-item-time"><i class="far fa-calendar-alt"></i> <div class="article-sort-item-time"><i class="far fa-calendar-alt"></i>

Binary file not shown.

View File

@ -0,0 +1,708 @@
#fMomentsMessageBoard {
background: var(--heo-card-bg);
border-radius: 12px;
box-shadow: var(--heo-shadow-border);
border: var(--style-border);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Lato, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
margin: 16px 0;
padding: 16px;
animation: slideInUp 0.6s ease-out;
}
.fMomentsUpdatedTime {
text-align: center;
padding: 8px 0;
margin-bottom: 16px;
border-bottom: 1px solid var(--heo-border-color);
font-size: 14px;
color: var(--heo-secondtext);
font-weight: 500;
}
.fMomentsStatsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
}
.fMomentsStatCard {
background: linear-gradient(135deg, var(--heo-secondbg), var(--heo-mask-bg));
border-radius: 12px;
padding: 16px;
text-align: center;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid var(--heo-border-color);
position: relative;
overflow: hidden;
}
.fMomentsStatCard::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--heo-main), var(--heo-blue));
transform: scaleX(0);
transition: transform 0.3s ease;
}
.fMomentsStatCard:hover {
transform: translateY(-2px);
box-shadow: var(--heo-shadow-lightblack);
}
.fMomentsStatCard:hover::before {
transform: scaleX(1);
}
.fMomentsStatIcon {
font-size: 24px;
margin-bottom: 8px;
}
.fMomentsStatNumber {
font-size: 24px;
font-weight: 700;
color: var(--heo-fontcolor);
margin-bottom: 4px;
font-family: 'SAOUI', monospace;
}
.fMomentsStatLabel {
font-size: 12px;
color: var(--heo-secondtext);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
line-height: 1.2;
}
/* 控制面板 */
.fMomentsControlPanel {
background: var(--heo-card-bg);
border-radius: 16px;
padding: 24px;
margin: 20px 0;
box-shadow: var(--heo-shadow-border);
border: var(--style-border);
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: space-between;
}
.fMomentsSearchBox {
flex: 1;
min-width: 250px;
position: relative;
}
.fMomentsSearchInput {
width: 100%;
padding: 14px 16px 14px 44px;
border: 2px solid var(--heo-border-color);
border-radius: 28px;
background: var(--heo-secondbg);
color: var(--heo-fontcolor);
font-size: 14px;
transition: all 0.3s ease;
font-family: inherit;
}
.fMomentsSearchInput:focus {
outline: none;
border-color: var(--heo-main);
box-shadow: 0 0 0 3px rgba(var(--heo-main-rgb), 0.1);
}
.fMomentsSearchIcon {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--heo-secondtext);
font-size: 16px;
}
.fMomentsFilterGroup {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.fMomentsSelect {
padding: 12px 16px;
border: 2px solid var(--heo-border-color);
border-radius: 24px;
background: var(--heo-secondbg);
color: var(--heo-fontcolor);
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
min-width: 130px;
font-family: inherit;
}
.fMomentsSelect:focus {
outline: none;
border-color: var(--heo-main);
box-shadow: 0 0 0 3px rgba(var(--heo-main-rgb), 0.1);
}
.fMomentsToggle {
display: flex;
align-items: center;
gap: 12px;
font-size: 14px;
color: var(--heo-fontcolor);
font-weight: 500;
}
.fMomentsSwitch {
position: relative;
width: 52px;
height: 28px;
background: var(--heo-border-color);
border-radius: 28px;
cursor: pointer;
transition: all 0.3s ease;
}
.fMomentsSwitch.active {
background: var(--heo-main);
}
.fMomentsSwitch::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 24px;
height: 24px;
background: white;
border-radius: 50%;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.fMomentsSwitch.active::after {
transform: translateX(24px);
}
/* 显示数量状态 */
.fMomentsDisplayStatus {
background: var(--heo-card-bg);
border-radius: 12px;
padding: 16px 24px;
margin: 20px 0;
box-shadow: var(--heo-shadow-border);
border: var(--style-border);
text-align: center;
font-size: 14px;
color: var(--heo-secondtext);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 16px;
}
.fMomentsDisplayInfo {
display: flex;
align-items: center;
gap: 8px;
}
.fMomentsDisplayCount {
font-weight: 600;
color: var(--heo-main);
}
/* 加载状态 */
.fMomentsLoading {
text-align: center;
padding: 60px 20px;
background: var(--heo-card-bg);
border-radius: 16px;
margin: 20px 0;
box-shadow: var(--heo-shadow-border);
border: var(--style-border);
}
.fMomentsLoadingSpinner {
width: 40px;
height: 40px;
border: 4px solid var(--heo-border-color);
border-top: 4px solid var(--heo-main);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
.fMomentsLoadingText {
color: var(--heo-secondtext);
font-size: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 瀑布流布局 */
#fmomentsContainer {
font-family: 'SAOUI', -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Lato, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
margin: 20px 0;
min-height: 200px;
}
.fMomentsArticleItem {
background: var(--heo-card-bg);
border-radius: 16px;
overflow: hidden;
box-shadow: var(--heo-shadow-border);
border: var(--style-border);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
animation: fadeInUp 0.6s ease-out forwards;
height: 240px; /* 固定总高度: 72px(header) + 120px(content) + 48px(footer) */
display: flex;
flex-direction: column;
}
.fMomentsArticleItem:hover {
transform: translateY(-6px);
box-shadow: var(--heo-shadow-lightblack);
}
.fMomentsArticleHeader {
padding: 16px;
border-bottom: 1px solid var(--heo-border-color);
display: flex;
align-items: center;
gap: 12px;
height: 72px; /* 固定高度 */
overflow: hidden; /* 隐藏超出部分 */
}
.fMomentsAvatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
transition: transform 0.3s ease;
}
.fMomentsAvatar:hover {
transform: scale(1.1);
}
.fMomentsAuthorInfo {
flex: 1;
}
.fMomentsAuthorName {
font-size: 14px;
font-weight: 600;
color: var(--heo-fontcolor);
margin-bottom: 2px;
}
.fMomentsPublishTime {
font-size: 12px;
color: var(--heo-secondtext);
}
.fMomentsArticleContent {
padding: 16px;
height: 120px; /* 固定高度 */
overflow: hidden; /* 隐藏超出部分 */
display: flex;
flex-direction: column;
}
.fMomentsArticleTitle {
font-size: 16px;
font-weight: 600;
color: var(--heo-fontcolor);
line-height: 1.4;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-decoration: none;
transition: color 0.3s ease;
flex-shrink: 0; /* 防止标题被压缩 */
}
.fMomentsArticleTitle:hover {
color: var(--heo-main);
}
.fMomentsArticleDescription {
font-size: 14px;
color: var(--heo-secondtext);
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
flex: 1; /* 占用剩余空间 */
}
.fMomentsArticleFooter {
padding: 12px 16px;
border-top: 1px solid var(--heo-border-color);
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: var(--heo-secondtext);
height: 48px; /* 固定高度 */
overflow: hidden; /* 隐藏超出部分 */
flex-shrink: 0; /* 防止被压缩 */
}
/* 加载更多按钮 */
#fmomentsMoreBtn {
width: 100%;
max-width: 400px;
height: 54px;
margin: 30px auto;
background: linear-gradient(135deg, var(--heo-main), var(--heo-blue));
color: white;
border: none;
border-radius: 27px;
font-weight: 600;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 4px 15px rgba(var(--heo-main-rgb), 0.3);
font-family: inherit;
}
#fmomentsMoreBtn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(var(--heo-main-rgb), 0.4);
}
#fmomentsMoreBtn:disabled {
background: var(--heo-border-color);
color: var(--heo-secondtext);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.fMomentsNoMore {
text-align: center;
padding: 40px 20px;
color: var(--heo-secondtext);
font-size: 16px;
background: var(--heo-card-bg);
border-radius: 16px;
border: 2px dashed var(--heo-border-color);
margin: 20px 0;
}
.fMomentsNoMore i {
font-size: 48px;
color: var(--heo-main);
margin-bottom: 16px;
display: block;
}
/* 错误状态 */
.fMomentsErrorState {
text-align: center;
padding: 60px 20px;
color: var(--heo-secondtext);
background: var(--heo-card-bg);
border-radius: 16px;
border: 2px dashed #ff6b6b;
margin: 20px 0;
}
.fMomentsErrorState i {
font-size: 64px;
color: #ff6b6b;
margin-bottom: 20px;
display: block;
}
.fMomentsErrorState h3 {
font-size: 18px;
margin-bottom: 8px;
color: var(--heo-fontcolor);
}
.fMomentsRetryBtn {
margin-top: 20px;
padding: 12px 24px;
background: var(--heo-main);
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.fMomentsRetryBtn:hover {
background: var(--heo-blue);
transform: translateY(-2px);
}
/* 空状态 */
.fMomentsEmptyState {
text-align: center;
padding: 60px 20px;
color: var(--heo-secondtext);
background: var(--heo-card-bg);
border-radius: 16px;
border: 2px dashed var(--heo-border-color);
margin: 20px 0;
}
.fMomentsEmptyState i {
font-size: 64px;
color: var(--heo-border-color);
margin-bottom: 20px;
display: block;
}
.fMomentsEmptyState h3 {
font-size: 18px;
margin-bottom: 8px;
color: var(--heo-fontcolor);
}
.fMomentsEmptyState p {
font-size: 14px;
line-height: 1.6;
}
/* 响应式设计 */
@media (max-width: 768px) {
#fmomentsContainer {
grid-template-columns: 1fr;
gap: 16px;
}
.fMomentsControlPanel {
flex-direction: column;
align-items: stretch;
padding: 20px;
}
.fMomentsFilterGroup {
justify-content: center;
}
/* 移动端 fMomentsMessageBoard 优化 */
#fMomentsMessageBoard {
padding: 12px;
margin: 12px 0;
border-radius: 10px;
}
.fMomentsUpdatedTime {
padding: 6px 0;
margin-bottom: 12px;
font-size: 13px;
}
.fMomentsStatsGrid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.fMomentsStatCard {
padding: 12px 8px;
border-radius: 10px;
}
.fMomentsStatIcon {
font-size: 20px;
margin-bottom: 6px;
}
.fMomentsStatNumber {
font-size: 20px;
margin-bottom: 3px;
}
.fMomentsStatLabel {
font-size: 11px;
letter-spacing: 0.3px;
}
.fMomentsDisplayStatus {
flex-direction: column;
text-align: center;
}
.fMomentsArticleHeader {
height: 68px;
padding: 12px;
}
.fMomentsArticleContent {
height: 110px;
padding: 12px;
}
.fMomentsArticleFooter {
height: 44px;
padding: 10px 12px;
}
.fMomentsArticleItem {
height: 222px; /* 调整后的总高度 */
}
}
@media (max-width: 480px) {
/* 超小屏幕的 fMomentsMessageBoard 优化 */
#fMomentsMessageBoard {
padding: 10px;
margin: 10px 0;
}
.fMomentsUpdatedTime {
padding: 4px 0;
margin-bottom: 10px;
font-size: 12px;
}
.fMomentsStatsGrid {
grid-template-columns: repeat(2, 1fr);
gap: 6px;
}
.fMomentsStatCard {
padding: 10px 6px;
border-radius: 8px;
}
.fMomentsStatIcon {
font-size: 18px;
margin-bottom: 4px;
}
.fMomentsStatNumber {
font-size: 18px;
margin-bottom: 2px;
}
.fMomentsStatLabel {
font-size: 10px;
letter-spacing: 0.2px;
}
.fMomentsArticleFooter {
height: 40px;
padding: 8px 10px;
}
.fMomentsArticleContent {
height: 100px;
padding: 10px;
}
.fMomentsArticleHeader {
height: 64px;
padding: 10px;
}
.fMomentsArticleItem {
height: 204px; /* 调整后的总高度 */
}
.fMomentsFilterGroup {
flex-direction: column;
align-items: stretch;
}
.fMomentsSelect {
width: 100%;
}
.fMomentsSearchBox {
min-width: 200px;
}
}
/* 超小屏幕专用小于360px */
@media (max-width: 360px) {
#fMomentsMessageBoard {
padding: 8px;
}
.fMomentsStatsGrid {
grid-template-columns: 1fr 1fr;
gap: 4px;
}
.fMomentsStatCard {
padding: 8px 4px;
}
.fMomentsStatIcon {
font-size: 16px;
}
.fMomentsStatNumber {
font-size: 16px;
}
.fMomentsStatLabel {
font-size: 9px;
}
}
/* 动画效果 */
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fMomentsArticleItem:nth-child(2n) {
animation-delay: 0.1s;
}
.fMomentsArticleItem:nth-child(3n) {
animation-delay: 0.2s;
}
.fMomentsArticleItem:nth-child(4n) {
animation-delay: 0.3s;
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,776 @@
if (typeof window.FriendMomentsApp === 'undefined') {
class FriendMomentsApp {
constructor() {
if (window._friendMomentsInstance) {
// console.log('FriendMomentsApp instance already exists, destroying old one');
window._friendMomentsInstance.destroy();
// 等待销毁完成
if (window._friendMomentsInstance) {
return window._friendMomentsInstance;
}
}
window._friendMomentsInstance = this;
this.articles = [];
this.filteredArticles = [];
this.displayedArticles = [];
this.currentPage = 1;
this.pageSize = window.fmomentsConfig?.pageSize || 12;
this.sortField = 'pubDate';
this.sortOrder = 'desc';
this.authorFilter = '';
this.searchKeyword = '';
this.loading = false;
this.errorCount = 0;
this.maxRetries = 3;
// 新增分页相关属性
this.totalPages = 0;
this.totalCount = 0;
this.hasNext = false;
this.hasPrevious = false;
this.isFirst = true;
this.isLast = false;
// 存储事件监听器引用,用于清理
this.eventListeners = [];
this.initialized = false;
this.destroyed = false; // 新增:标记是否已销毁
this.initPromise = null; // 新增:防止重复初始化
// 生成唯一实例ID
this.instanceId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// console.log(`Creating FriendMomentsApp instance: ${this.instanceId}`);
// 不在构造函数中调用init改为外部调用
}
async init() {
// 防止重复初始化
if (this.initPromise) {
// console.log('Init already in progress, waiting...');
return this.initPromise;
}
if (this.initialized) {
// console.log('Already initialized');
return;
}
if (this.destroyed) {
// console.log('Instance has been destroyed, cannot initialize');
return;
}
// console.log(`Initializing FriendMomentsApp instance: ${this.instanceId}`);
this.initPromise = this._doInit();
return this.initPromise;
}
async _doInit() {
try {
this.cleanup(); // 清理之前的状态
this.showLoading();
// 检查是否在初始化过程中被销毁
if (this.destroyed) {
// console.log('Instance destroyed during initialization');
return;
}
// 重置分页状态
this.currentPage = 1;
this.articles = [];
await this.loadArticles();
// 再次检查是否被销毁
if (this.destroyed) {
// console.log('Instance destroyed after loading articles');
return;
}
this.setupEventListeners();
this.setupAuthorFilter();
this.calculateStats();
this.displayArticles(true);
this.updateDisplayStatus();
this.hideLoading();
this.initialized = true;
// console.log(`FriendMomentsApp instance ${this.instanceId} initialized successfully`);
} catch (error) {
if (!this.destroyed) {
this.handleError(error);
}
} finally {
this.initPromise = null;
}
}
// 清理方法
cleanup() {
if (this.destroyed) return;
// console.log(`Cleaning up FriendMomentsApp instance: ${this.instanceId}`);
// 清理事件监听器
this.removeEventListeners();
// 清理DOM内容
const container = document.getElementById('fmomentsContainer');
if (container) {
container.innerHTML = '';
}
// 清理作者筛选选项
const authorFilter = document.getElementById('authorFilter');
if (authorFilter) {
const defaultOption = authorFilter.querySelector('option[value=""]');
authorFilter.innerHTML = '';
if (defaultOption) {
authorFilter.appendChild(defaultOption);
} else {
const option = document.createElement('option');
option.value = '';
option.textContent = '全部作者';
authorFilter.appendChild(option);
}
}
// 重置搜索框
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.value = '';
}
// 重置状态
this.articles = [];
this.filteredArticles = [];
this.displayedArticles = [];
this.currentPage = 1;
this.authorFilter = '';
this.searchKeyword = '';
this.loading = false;
this.errorCount = 0;
// 重置分页状态
this.totalPages = 0;
this.totalCount = 0;
this.hasNext = false;
this.hasPrevious = false;
this.isFirst = true;
this.isLast = false;
}
// 销毁实例
destroy() {
if (this.destroyed) {
// console.log(`Instance ${this.instanceId} already destroyed`);
return;
}
// console.log(`Destroying FriendMomentsApp instance: ${this.instanceId}`);
this.destroyed = true;
this.initialized = false;
// 取消进行中的初始化
if (this.initPromise) {
this.initPromise = null;
}
this.cleanup();
// 清理全局引用
if (window._friendMomentsInstance === this) {
window._friendMomentsInstance = null;
}
// console.log(`FriendMomentsApp instance ${this.instanceId} destroyed`);
}
// 移除事件监听器
removeEventListeners() {
if (this.eventListeners.length > 0) {
// console.log(`Removing ${this.eventListeners.length} event listeners for instance ${this.instanceId}`);
this.eventListeners.forEach(({element, event, handler}) => {
if (element && element.removeEventListener) {
element.removeEventListener(event, handler);
}
});
this.eventListeners = [];
}
}
// 添加事件监听器的辅助方法
addEventListener(element, event, handler) {
if (this.destroyed) return;
if (element) {
element.addEventListener(event, handler);
this.eventListeners.push({element, event, handler});
// console.log(`Added ${event} listener for instance ${this.instanceId}`);
}
}
showLoading() {
if (this.destroyed) return;
const loading = document.getElementById('loadingIndicator');
const container = document.getElementById('fmomentsContainer');
const errorState = document.getElementById('errorState');
if (loading) loading.style.display = 'block';
if (container) container.style.display = 'none';
if (errorState) errorState.style.display = 'none';
}
hideLoading() {
if (this.destroyed) return;
const loading = document.getElementById('loadingIndicator');
const container = document.getElementById('fmomentsContainer');
if (loading) loading.style.display = 'none';
if (container) container.style.display = 'grid';
}
async loadArticles(page = 1) {
if (this.destroyed) {
// console.log('Instance destroyed, skipping loadArticles');
return;
}
// console.log(`Loading articles for instance ${this.instanceId}, page: ${page}`);
try {
const baseUrl = window.fmomentsConfig?.apiUrl || '/apis/api.friend.moony.la/v1alpha1/friendposts';
const url = new URL(baseUrl, window.location.origin);
url.searchParams.set('page', page.toString());
url.searchParams.set('size', this.pageSize.toString());
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
// 检查请求完成后实例是否还有效
if (this.destroyed) {
// console.log('Instance destroyed after fetch, ignoring response');
return;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// 再次检查实例是否还有效
if (this.destroyed) {
// console.log('Instance destroyed after parsing response, ignoring data');
return;
}
if (data.items && Array.isArray(data.items)) {
const newArticles = data.items.map(item => ({
id: item.metadata.name,
author: item.spec.author,
authorUrl: item.spec.authorUrl,
title: item.spec.title,
description: item.spec.description,
postLink: item.spec.postLink,
logo: item.spec.logo,
pubDate: new Date(item.spec.pubDate),
creationTime: new Date(item.metadata.creationTimestamp),
content: `${item.spec.title} ${item.spec.author} ${item.spec.description}`.toLowerCase()
}));
// 更新分页信息
this.totalPages = data.totalPages || 0;
this.totalCount = data.total || 0;
this.hasNext = data.hasNext || false;
this.hasPrevious = data.hasPrevious || false;
this.isFirst = data.first || false;
this.isLast = data.last || false;
// 如果是第一页,重置文章数组;否则追加
if (page === 1) {
this.articles = newArticles;
} else {
this.articles = this.articles.concat(newArticles);
}
// console.log(`Successfully loaded ${newArticles.length} articles for page ${page}, total articles: ${this.articles.length} for instance ${this.instanceId}`);
this.errorCount = 0;
} else {
throw new Error('API 返回数据格式错误');
}
} catch (error) {
if (this.destroyed) {
// console.log('Instance destroyed, ignoring loadArticles error');
return;
}
// console.error(`Loading articles failed for instance ${this.instanceId}:`, error);
this.errorCount++;
if (this.errorCount < this.maxRetries && !this.destroyed) {
// console.log(`Retry ${this.errorCount} for instance ${this.instanceId}`);
await new Promise(resolve => setTimeout(resolve, 1000 * this.errorCount));
return this.loadArticles(page);
}
throw error;
}
}
calculateStats() {
if (this.destroyed) return;
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
const totalArticles = this.totalCount || this.articles.length; // 优先使用API返回的总数
const totalAuthors = new Set(this.articles.map(a => a.author)).size;
const todayCount = this.articles.filter(a => a.pubDate >= today).length;
const weekCount = this.articles.filter(a => a.pubDate >= weekAgo).length;
this.updateStatNumber('totalArticles', totalArticles);
this.updateStatNumber('totalAuthors', totalAuthors);
this.updateStatNumber('todayCount', todayCount);
this.updateStatNumber('weekCount', weekCount);
const lastUpdateTime = document.getElementById('lastUpdateTime');
if (lastUpdateTime) {
lastUpdateTime.textContent = new Date().toLocaleString('zh-CN');
}
}
updateStatNumber(elementId, value) {
if (this.destroyed) return;
const element = document.getElementById(elementId);
if (element) {
const startValue = 0;
const duration = 1000;
const startTime = performance.now();
const animate = (currentTime) => {
if (this.destroyed) return; // 动画过程中检查实例状态
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const currentValue = Math.floor(startValue + (value - startValue) * progress);
element.textContent = currentValue;
if (progress < 1 && !this.destroyed) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
}
}
setupEventListeners() {
if (this.destroyed) return;
// console.log(`Setting up event listeners for instance ${this.instanceId}`);
// 搜索功能
const searchInput = document.getElementById('searchInput');
if (searchInput) {
let searchTimeout;
const searchHandler = (e) => {
if (this.destroyed) return;
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
if (this.destroyed) return;
this.searchKeyword = e.target.value.toLowerCase();
this.resetAndDisplay();
}, 300);
};
this.addEventListener(searchInput, 'input', searchHandler);
}
// 作者筛选
const authorFilter = document.getElementById('authorFilter');
if (authorFilter) {
const authorHandler = (e) => {
if (this.destroyed) return;
this.authorFilter = e.target.value;
this.resetAndDisplay();
};
this.addEventListener(authorFilter, 'change', authorHandler);
}
// 排序方式
const sortSelect = document.getElementById('sortSelect');
if (sortSelect) {
const sortHandler = (e) => {
if (this.destroyed) return;
this.sortField = e.target.value;
this.resetAndDisplay();
};
this.addEventListener(sortSelect, 'change', sortHandler);
}
// 排序顺序
const sortOrderSwitch = document.getElementById('sortOrderSwitch');
if (sortOrderSwitch) {
const sortOrderHandler = () => {
if (this.destroyed) return;
sortOrderSwitch.classList.toggle('active');
this.sortOrder = sortOrderSwitch.classList.contains('active') ? 'desc' : 'asc';
this.resetAndDisplay();
};
this.addEventListener(sortOrderSwitch, 'click', sortOrderHandler);
}
// 加载更多
const moreBtn = document.getElementById('fmomentsMoreBtn');
if (moreBtn) {
const moreBtnHandler = () => {
if (this.destroyed) return;
this.loadMore();
};
this.addEventListener(moreBtn, 'click', moreBtnHandler);
}
// 重试按钮
const retryBtn = document.getElementById('retryBtn');
if (retryBtn) {
const retryHandler = () => {
if (this.destroyed) return;
this.init();
};
this.addEventListener(retryBtn, 'click', retryHandler);
}
// 滚动加载
const scrollHandler = this.throttle(() => {
if (this.destroyed) return;
if (this.isNearBottom() && !this.loading && this.hasMoreContent()) {
this.loadMore();
}
}, 200);
this.addEventListener(window, 'scroll', scrollHandler);
}
setupAuthorFilter() {
if (this.destroyed) return;
const authorFilter = document.getElementById('authorFilter');
if (!authorFilter) return;
const existingOptions = Array.from(authorFilter.options).map(option => option.value);
const authors = [...new Set(this.articles.map(article => article.author))].sort();
authors.forEach(author => {
if (this.destroyed) return;
if (author && !existingOptions.includes(author)) {
const option = document.createElement('option');
option.value = author;
option.textContent = author;
authorFilter.appendChild(option);
}
});
}
filterAndSortArticles() {
if (this.destroyed) return [];
let filtered = this.articles.filter(article => {
if (this.authorFilter && article.author !== this.authorFilter) {
return false;
}
if (this.searchKeyword && !article.content.includes(this.searchKeyword)) {
return false;
}
return true;
});
filtered.sort((a, b) => {
let valueA, valueB;
switch (this.sortField) {
case 'pubDate':
valueA = a.pubDate;
valueB = b.pubDate;
break;
case 'creationTime':
valueA = a.creationTime;
valueB = b.creationTime;
break;
case 'author':
valueA = a.author.toLowerCase();
valueB = b.author.toLowerCase();
break;
case 'title':
valueA = a.title.toLowerCase();
valueB = b.title.toLowerCase();
break;
default:
return 0;
}
if (valueA < valueB) {
return this.sortOrder === 'asc' ? -1 : 1;
}
if (valueA > valueB) {
return this.sortOrder === 'asc' ? 1 : -1;
}
return 0;
});
this.filteredArticles = filtered;
return filtered;
}
displayArticles(isInitial = false) {
if (this.destroyed) return;
const container = document.getElementById('fmomentsContainer');
if (!container) return;
const filtered = this.filterAndSortArticles();
if (filtered.length === 0) {
this.showEmptyState();
return;
} else {
this.hideEmptyState();
}
if (isInitial) {
container.innerHTML = '';
this.displayedArticles = [];
}
// 显示所有已筛选的文章
const articlesToShow = filtered.slice(this.displayedArticles.length);
articlesToShow.forEach((article, index) => {
if (this.destroyed) return;
const articleEl = this.createArticleElement(article);
articleEl.style.animationDelay = `${index * 0.1}s`;
container.appendChild(articleEl);
});
this.displayedArticles = filtered;
this.updateDisplayStatus();
this.updateLoadingStatus();
}
createArticleElement(article) {
const articleEl = document.createElement('article');
articleEl.className = 'fMomentsArticleItem';
const safeLogoUrl = article.logo || window.fmomentsConfig?.errorImg || '/default-avatar.png';
const safeAuthorUrl = article.authorUrl || '#';
const safePostLink = article.postLink || '#';
const pubDateStr = article.pubDate.toLocaleDateString('zh-CN');
const pubTimeStr = article.pubDate.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
articleEl.innerHTML = `
<div class="fMomentsArticleHeader">
<img class="fMomentsAvatar" src="${safeLogoUrl}" alt="${article.author}"
onerror="this.src='${window.fmomentsConfig?.errorImg || '/default-avatar.png'}'">
<a href="${safeAuthorUrl}" target="_blank" rel="noopener nofollow"
style="color: var(--heo-main); text-decoration: none;"><div class="fMomentsAuthorInfo">
<div class="fMomentsAuthorName">${article.author}</div>
<div class="fMomentsPublishTime">${pubTimeStr}</div>
</div></a>
</div>
<div class="fMomentsArticleContent">
<a class="fMomentsArticleTitle" href="${safePostLink}" target="_blank" rel="noopener nofollow"
title="${article.title}">${article.title}</a>
<a class="fMomentsArticleTitle" href="${safePostLink}" target="_blank" rel="noopener nofollow"
title="${article.title}"><div class="fMomentsArticleDescription">${article.description}</div></a>
</div>
<div class="fMomentsArticleFooter">
<span>📅 ${pubDateStr}</span>
<span>🌐 <a href="${safeAuthorUrl}" target="_blank" rel="noopener nofollow">${article.author}</a></span>
</div>
`;
return articleEl;
}
updateDisplayStatus() {
if (this.destroyed) return;
const displayedCount = document.getElementById('displayedCount');
const totalCount = document.getElementById('totalCount');
const filterStatus = document.getElementById('filterStatus');
const filterText = document.getElementById('filterText');
if (displayedCount) displayedCount.textContent = this.displayedArticles.length;
if (totalCount) totalCount.textContent = this.totalCount || this.articles.length;
const hasFilter = this.authorFilter || this.searchKeyword;
if (filterStatus) {
filterStatus.style.display = hasFilter ? 'flex' : 'none';
}
if (filterText && hasFilter) {
let filterParts = [];
if (this.authorFilter) filterParts.push(`作者: ${this.authorFilter}`);
if (this.searchKeyword) filterParts.push(`搜索: ${this.searchKeyword}`);
filterText.textContent = filterParts.join(' | ');
}
}
resetAndDisplay() {
if (this.destroyed) return;
const container = document.getElementById('fmomentsContainer');
if (container) {
container.innerHTML = '';
}
this.displayedArticles = [];
this.displayArticles(true);
}
hasMoreContent() {
if (this.destroyed) return false;
// 基于API返回的分页信息判断是否还有更多内容
return this.hasNext && !this.isLast;
}
async loadMore() {
if (this.destroyed || this.loading || !this.hasMoreContent()) {
return;
}
this.loading = true;
try {
// 加载下一页
const nextPage = Math.floor(this.articles.length / this.pageSize) + 1;
await this.loadArticles(nextPage);
// 重新设置作者筛选选项(可能有新作者)
this.setupAuthorFilter();
// 重新计算统计信息
this.calculateStats();
// 显示新加载的文章
this.displayArticles();
} catch (error) {
if (!this.destroyed) {
console.error('Load more failed:', error);
// 可以选择显示错误提示,但不影响现有内容
}
} finally {
this.loading = false;
}
}
updateLoadingStatus() {
if (this.destroyed) return;
const moreBtn = document.getElementById('fmomentsMoreBtn');
const noMoreTip = document.getElementById('noMoreTip');
const loadingStatus = document.getElementById('loadingStatus');
const finalCount = document.getElementById('finalCount');
if (!loadingStatus) return;
if (!this.hasMoreContent()) {
if (moreBtn) moreBtn.style.display = 'none';
if (noMoreTip) noMoreTip.style.display = 'block';
if (finalCount) finalCount.textContent = this.displayedArticles.length;
loadingStatus.style.display = 'block';
} else {
if (moreBtn) moreBtn.style.display = 'flex';
if (noMoreTip) noMoreTip.style.display = 'none';
loadingStatus.style.display = 'block';
}
}
showEmptyState() {
if (this.destroyed) return;
const emptyState = document.getElementById('emptyState');
const loadingStatus = document.getElementById('loadingStatus');
if (emptyState) emptyState.style.display = 'block';
if (loadingStatus) loadingStatus.style.display = 'none';
}
hideEmptyState() {
if (this.destroyed) return;
const emptyState = document.getElementById('emptyState');
if (emptyState) emptyState.style.display = 'none';
}
handleError(error) {
if (this.destroyed) return;
console.error(`Friend Moments加载失败 for instance ${this.instanceId}:`, error);
const loading = document.getElementById('loadingIndicator');
const errorState = document.getElementById('errorState');
const errorMessage = document.getElementById('errorMessage');
const container = document.getElementById('fmomentsContainer');
if (loading) loading.style.display = 'none';
if (container) container.style.display = 'none';
if (errorState) errorState.style.display = 'block';
if (errorMessage) {
let message = '加载失败,请稍后重试';
if (error.message.includes('fetch')) {
message = '网络连接失败,请检查网络后重试';
} else if (error.message.includes('HTTP')) {
message = `服务器错误: ${error.message}`;
}
errorMessage.textContent = message;
}
}
throttle(func, limit) {
let inThrottle;
return function () {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
isNearBottom() {
return window.innerHeight + window.scrollY >= document.body.offsetHeight - 1000;
}
}
// 导出类
window.FriendMomentsApp = FriendMomentsApp;
}

View File

@ -376,7 +376,7 @@ let halo = {
getTopSponsors() { getTopSponsors() {
var address = GLOBAL_CONFIG.source.power.powerAddress; var address = GLOBAL_CONFIG.source.power.powerAddress;
var show_num = GLOBAL_CONFIG.source.power.showNum; var show_num = GLOBAL_CONFIG.source.power.showNum;
var url = GLOBAL_CONFIG.source.power.url; var url = "/apis/api.plugin.halo.run/v1alpha1/plugins/plugin-afdian/afdian/getSponsorList";
function getPower() { function getPower() {
let powerStar = document.getElementById("power-star") let powerStar = document.getElementById("power-star")

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -4658,7 +4658,7 @@ html {
transition: 0.3s; transition: 0.3s;
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
top: 35px; top: 45px;
} }
#nav .menus_items .menus_item .menus_item_child:hover { #nav .menus_items .menus_item .menus_item_child:hover {
@ -4753,7 +4753,7 @@ html {
/*---------竖向菜单--------*/ /*---------竖向菜单--------*/
#nav .menus_items .menus_item .menus_item_child.vertical_nav>.recursion_menus_item { #nav .menus_items .menus_item .menus_item_child.vertical_nav>.recursion_menus_item {
display: inline-flex; display: flex;
list-style: none; list-style: none;
border-radius: 5px; border-radius: 5px;
margin: 0 4px; margin: 0 4px;
@ -14398,6 +14398,7 @@ div#heo-footer-bar .footer-bar-description {
height: 200px; height: 200px;
background: var(--heo-card-bg); background: var(--heo-card-bg);
display: flex; display: flex;
border-radius: 12px;
} }
#body-wrap.page .errors .aside-list .aside-list-item .content .title { #body-wrap.page .errors .aside-list .aside-list-item .content .title {
@ -17810,7 +17811,7 @@ code:not([class]) {
#nav .menus_items .menus_item .menus_item_child { #nav .menus_items .menus_item .menus_item_child {
padding: 2px 2px 4px 2px; padding: 2px 2px 4px 2px;
border-radius: 55px; border-radius: 1.5rem;
} }
#nav .menus_items .menus_item .menus_item_child>.recursion_menus_item:hover>a { #nav .menus_items .menus_item .menus_item_child>.recursion_menus_item:hover>a {

View File

@ -38,7 +38,7 @@
th:href="@{${post.status.permalink}}" th:href="@{${post.status.permalink}}"
th:title="${post.spec.title}"><img th:title="${post.spec.title}"><img
loading="lazy" loading="lazy"
th:src='${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : post.spec.cover}' th:src='${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : thumbnail.gen(post.spec.cover, "m")}'
th:alt="${post.spec.title}"></a> th:alt="${post.spec.title}"></a>
<div class="content"> <div class="content">
<a class="title" th:href="@{${post.status.permalink}}" <a class="title" th:href="@{${post.status.permalink}}"

330
templates/friends.html Normal file
View File

@ -0,0 +1,330 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
th:replace="~{modules/layouts/layout :: layout(content = ~{::content}, htmlType = 'page',title = '朋友圈 | ' + ${site.title}, head = ~{::head})}">
<th:block th:fragment="head">
<th:block th:replace="~{modules/common/open-graph :: open-graph(_title = '朋友圈',
_permalink = '/moments',
_cover = '',
_excerpt = '友链朋友圈 - 发现更多精彩内容',
_type = 'website')}"></th:block>
<!-- 分离CSS文件 -->
<link rel="stylesheet" th:href="@{/assets/css/fmoments.css}" data-pjax>
</th:block>
<th:block th:fragment="content">
<div class="page" id="body-wrap">
<!-- 头部导航栏 -->
<header class="not-top-img" id="page-header">
<nav th:replace="~{modules/nav :: nav(title = '朋友圈')}"></nav>
</header>
<main class="layout hide-aside" id="content-inner">
<div id="page">
<!-- 朋友圈统计信息面板 -->
<div id="fMomentsMessageBoard">
<div class="fMomentsUpdatedTime">
<i class="fas fa-sync-alt"></i>
最近更新:<span id="lastUpdateTime">加载中...</span>
</div>
<div class="fMomentsStatsGrid">
<div class="fMomentsStatCard">
<div class="fMomentsStatIcon">📚</div>
<div class="fMomentsStatNumber" id="totalArticles">0</div>
<div class="fMomentsStatLabel">总文章数</div>
</div>
<div class="fMomentsStatCard">
<div class="fMomentsStatIcon">👥</div>
<div class="fMomentsStatNumber" id="totalAuthors">0</div>
<div class="fMomentsStatLabel">活跃作者</div>
</div>
<div class="fMomentsStatCard">
<div class="fMomentsStatIcon">🔥</div>
<div class="fMomentsStatNumber" id="todayCount">0</div>
<div class="fMomentsStatLabel">今日更新</div>
</div>
<div class="fMomentsStatCard">
<div class="fMomentsStatIcon"></div>
<div class="fMomentsStatNumber" id="weekCount">0</div>
<div class="fMomentsStatLabel">本周更新</div>
</div>
</div>
</div>
<!-- 控制面板 -->
<div class="fMomentsControlPanel">
<div class="fMomentsSearchBox">
<i class="fas fa-search fMomentsSearchIcon"></i>
<input type="text" class="fMomentsSearchInput" placeholder="搜索文章标题或作者..." id="searchInput">
</div>
<div class="fMomentsFilterGroup">
<select class="fMomentsSelect" id="authorFilter">
<option value="">全部作者</option>
</select>
<select class="fMomentsSelect" id="sortSelect">
<option value="pubDate">按发布时间</option>
<option value="creationTime">按创建时间</option>
<option value="author">按作者名称</option>
<option value="title">按标题</option>
</select>
<div class="fMomentsToggle">
<span>升序</span>
<div class="fMomentsSwitch active" id="sortOrderSwitch"></div>
<span>降序</span>
</div>
</div>
</div>
<!-- 显示数量状态 -->
<div class="fMomentsDisplayStatus">
<div class="fMomentsDisplayInfo">
<i class="fas fa-filter"></i>
<span>显示</span>
<span class="fMomentsDisplayCount" id="displayedCount">0</span>
<span>/</span>
<span class="fMomentsDisplayCount" id="totalCount">0</span>
<span>篇文章</span>
</div>
<div class="fMomentsDisplayInfo" id="filterStatus" style="display: none;">
<i class="fas fa-info-circle"></i>
<span id="filterText">已应用筛选条件</span>
</div>
</div>
<!-- 加载状态 -->
<div id="loadingIndicator" class="fMomentsLoading">
<div class="fMomentsLoadingSpinner"></div>
<div class="fMomentsLoadingText">正在加载动态内容...</div>
</div>
<!-- 朋友圈文章容器 -->
<div id="fmomentsContainer"></div>
<!-- 加载更多按钮 -->
<div id="loadingStatus" style="display: none;">
<button id="fmomentsMoreBtn" type="button">
<i class="fas fa-angle-double-down"></i>
<span>加载更多</span>
</button>
<div class="fMomentsNoMore" id="noMoreTip" style="display: none;">
<i class="fas fa-check-circle"></i>
<div>🎉 已显示全部内容</div>
<small>共找到 <span id="finalCount">0</span> 篇文章</small>
</div>
</div>
<!-- 错误状态 -->
<div class="fMomentsErrorState" id="errorState" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<h3>加载失败</h3>
<div id="errorMessage">请检查网络连接后重试</div>
<button class="fMomentsRetryBtn" id="retryBtn">重新加载</button>
</div>
<!-- 空状态 -->
<div class="fMomentsEmptyState" id="emptyState" style="display: none;">
<i class="fas fa-search"></i>
<h3>没有找到匹配的文章</h3>
<p>尝试更换搜索词或调整筛选条件</p>
</div>
</div>
</main>
<!-- 底部 -->
<footer th:replace="~{modules/footer}" />
<!-- 资源检查和动态加载脚本 -->
<script data-pjax th:inline="javascript">
// 资源动态加载器
window.MomentsResourceLoader = {
// 检查CSS是否已加载
isCSSLoaded: function (href) {
const links = document.getElementsByTagName('link');
for (let i = 0; i < links.length; i++) {
if (links[i].href && links[i].href.includes(href)) {
return true;
}
}
return false;
},
// 检查JS是否已加载
isJSLoaded: function (src) {
const scripts = document.getElementsByTagName('script');
for (let i = 0; i < scripts.length; i++) {
if (scripts[i].src && scripts[i].src.includes(src)) {
return true;
}
}
// 也检查是否已经定义了相关的全局变量/函数
return typeof FriendMomentsApp !== 'undefined';
},
// 动态加载CSS
loadCSS: function (href) {
return new Promise((resolve, reject) => {
if (this.isCSSLoaded(href)) {
// console.log('CSS already loaded:', href);
resolve();
return;
}
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.setAttribute('data-pjax', '');
link.onload = () => {
// console.log('CSS loaded successfully:', href);
resolve();
};
link.onerror = () => {
console.error('Failed to load CSS:', href);
reject(new Error(`Failed to load CSS: ${href}`));
};
document.head.appendChild(link);
});
},
// 动态加载JS
loadJS: function (src) {
return new Promise((resolve, reject) => {
if (this.isJSLoaded(src)) {
// console.log('JS already loaded:', src);
resolve();
return;
}
const script = document.createElement('script');
script.src = src;
script.setAttribute('data-pjax', '');
script.onload = () => {
// console.log('JS loaded successfully:', src);
resolve();
};
script.onerror = () => {
console.error('Failed to load JS:', src);
reject(new Error(`Failed to load JS: ${src}`));
};
document.head.appendChild(script);
});
},
// 加载所有必需的资源
loadRequiredResources: async function () {
const cssPath = /*[[@{/assets/css/fmoments.css}]]*/ '/assets/css/fmoments.css';
const jsPath = /*[[@{/assets/js/fmoments.js}]]*/ '/assets/js/fmoments.js';
try {
// console.log('Checking and loading required resources...');
// 并行加载CSS和JS
await Promise.all([
this.loadCSS(cssPath),
this.loadJS(jsPath)
]);
// console.log('All resources loaded successfully');
return true;
} catch (error) {
console.error('Error loading resources:', error);
return false;
}
}
};
</script>
<!-- 初始化脚本 -->
<script data-pjax th:inline="javascript">
// 朋友圈配置
// 从主题设置获取
window.fmomentsConfig = {
apiUrl: /*[[${theme.config.link.fmomentsApiUrl}]]*/ '/apis/api.friend.moony.la/v1alpha1/friendposts',
pageSize: /*[[${theme.config.link.fmomentsPageSize}]]*/ 12,
errorImg: /*[[${theme.config.other.error_404.background}]]*/ '/assets/images/404.gif'
};
// 全局初始化状态追踪
window._momentsInitializing = false;
// 初始化朋友圈应用
async function initMomentsApp() {
// 防止并发初始化
if (window._momentsInitializing) {
// console.log('Moments initialization already in progress');
return;
}
// console.log('Starting Moments initialization');
window._momentsInitializing = true;
try {
// 首先检查并加载必需的资源
const resourcesLoaded = await window.MomentsResourceLoader.loadRequiredResources();
if (!resourcesLoaded) {
console.error('Failed to load required resources');
return;
}
// 等待一小段时间确保资源完全加载
await new Promise(resolve => setTimeout(resolve, 100));
// 检查FriendMomentsApp是否可用
if (typeof FriendMomentsApp !== 'undefined') {
// 创建新实例(构造函数会处理旧实例的清理)
window.fMomentsApp = new FriendMomentsApp();
// 显式调用初始化
await window.fMomentsApp.init();
// console.log('FriendMomentsApp initialized successfully');
} else {
console.error('FriendMomentsApp is not available after loading resources');
}
} catch (error) {
console.error('Error initializing FriendMomentsApp:', error);
} finally {
window._momentsInitializing = false;
}
}
// PJAX开始时立即清理
document.addEventListener('pjax:start', function () {
// console.log('PJAX start - cleaning up Moments');
if (window._momentsInstance) {
window._momentsInstance.destroy();
}
window._momentsInitializing = false;
});
// DOM加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMomentsApp);
} else {
// DOM已经加载完成
initMomentsApp();
}
// PJAX完成后重新初始化
document.addEventListener('pjax:complete', function () {
if (document.getElementById('fmomentsContainer')) {
// 延迟一点时间确保DOM稳定
setTimeout(initMomentsApp, 150);
}
});
</script>
</div>
</th:block>
</html>

View File

@ -12,7 +12,7 @@
<div class="post_cover left_radius"> <div class="post_cover left_radius">
<a th:attr="title=${post.spec.title}" th:href="@{${post.status.permalink}}"> <a th:attr="title=${post.spec.title}" th:href="@{${post.status.permalink}}">
<img class="post_bg" <img class="post_bg"
th:with='img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : post.spec.cover}' th:with='img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : thumbnail.gen(post.spec.cover, "m")}'
th:alt="${post.spec.title}" th:data-lazy-src="${ isLazyload ? img : ''}" th:alt="${post.spec.title}" th:data-lazy-src="${ isLazyload ? img : ''}"
th:src="${isLazyload ? loadingImg : img}"> th:src="${isLazyload ? loadingImg : img}">
</a> </a>

Binary file not shown.

View File

@ -13,7 +13,7 @@
<div class="post_cover left_radius"> <div class="post_cover left_radius">
<a th:attr="title=${post.spec.title}" th:href="@{${post.status.permalink}}"> <a th:attr="title=${post.spec.title}" th:href="@{${post.status.permalink}}">
<img class="post_bg" <img class="post_bg"
th:with='img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : post.spec.cover}' th:with='img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : thumbnail.gen(post.spec.cover, "m")}'
th:alt="${post.spec.title}" th:alt="${post.spec.title}"
th:data-lazy-src="${ isLazyload ? img : ''}" th:data-lazy-src="${ isLazyload ? img : ''}"
th:src="${isLazyload ? loadingImg : img}"> th:src="${isLazyload ? loadingImg : img}">

View File

@ -17,7 +17,7 @@ postRandomImg=${#strings.contains(theme.config.layout.postRandomImg,'?') ? theme
<div th:if="${!containsTitle ? iterStat.index <6 : true}"> <div th:if="${!containsTitle ? iterStat.index <6 : true}">
<a th:href="@{${recommandPost.status.permalink}}" th:title="${recommandPost.spec.title}"> <a th:href="@{${recommandPost.status.permalink}}" th:title="${recommandPost.spec.title}">
<img alt="cover" class="cover" id="preimg" <img alt="cover" class="cover" id="preimg"
th:with="img = ${#strings.isEmpty(recommandPost.spec.cover) ? postRandomImg+recommandPost.spec.title : recommandPost.spec.cover}" th:with="img = ${#strings.isEmpty(recommandPost.spec.cover) ? postRandomImg+recommandPost.spec.title : thumbnail.gen(recommandPost.spec.cover, 'm')}"
th:src="${isLazyload ? loadingImg : img}" th:src="${isLazyload ? loadingImg : img}"
th:data-lazy-src="${ isLazyload ? img : ''}"> th:data-lazy-src="${ isLazyload ? img : ''}">
<div class="content is-center"> <div class="content is-center">
@ -37,7 +37,7 @@ postRandomImg=${#strings.contains(theme.config.layout.postRandomImg,'?') ? theme
<div th:if="${!containsTitle ? iterStat.index <2 : true}"> <div th:if="${!containsTitle ? iterStat.index <2 : true}">
<a th:href="@{${recommandPost.status.permalink}}" th:title="${recommandPost.spec.title}"> <a th:href="@{${recommandPost.status.permalink}}" th:title="${recommandPost.spec.title}">
<img class="cover" alt="cover" <img class="cover" alt="cover"
th:with="img = ${#strings.isEmpty(recommandPost.spec.cover) ? postRandomImg+recommandPost.spec.title : recommandPost.spec.cover}" th:with="img = ${#strings.isEmpty(recommandPost.spec.cover) ? postRandomImg+recommandPost.spec.title : thumbnail.gen(recommandPost.spec.cover, 'm')}"
th:src="${isLazyload ? loadingImg : img}" th:src="${isLazyload ? loadingImg : img}"
th:data-lazy-src="${ isLazyload ? img : ''}"> th:data-lazy-src="${ isLazyload ? img : ''}">

View File

@ -61,7 +61,7 @@
</th:block> </th:block>
<span class="sidebar-menu-item-title">标签</span> <span class="sidebar-menu-item-title">标签</span>
<div class="card-widget card-tags card-archives card-webinfo card-allinfo" <div class="card-widget card-tags card-archives card-webinfo card-allinfo"
th:with="tags = ${tagFinder.listAll()}, tagQuantity = ${#conversions.convert(theme.config.sidebar.tagQuantity, 'java.lang.Integer')}"> th:with="tags = ${tagFinder.list(1,theme.config.sidebar.tagQuantity)}">
<div class="item-headline"></div> <div class="item-headline"></div>
<div class="card-tag-cloud"> <div class="card-tag-cloud">
<a class="tag-item" style="font-size:1em" th:each="tag,iterStat : ${tags}" <a class="tag-item" style="font-size:1em" th:each="tag,iterStat : ${tags}"

View File

@ -58,7 +58,6 @@
power: { power: {
powerAddress: [[${theme.config.sidebar.power.powerAddress}]], powerAddress: [[${theme.config.sidebar.power.powerAddress}]],
powerLink: [[${theme.config.sidebar.power.powerLink}]], powerLink: [[${theme.config.sidebar.power.powerLink}]],
url: [[${theme.config.sidebar.power.url}]],
showNum: [[${theme.config.sidebar.power.showNum}]] showNum: [[${theme.config.sidebar.power.showNum}]]
}, },
links: { links: {

Binary file not shown.

View File

@ -16,7 +16,7 @@
<span th:text="${commentIndex}"></span> <span th:text="${commentIndex}"></span>
<a class="thumbnail" th:href="${url}" data-pjax-state=""> <a class="thumbnail" th:href="${url}" data-pjax-state="">
<img alt="头像" <img alt="头像"
th:with=" img =${#strings.isEmpty(comment.owner.avatar)?'https://cravatar.cn/avatar/?d=mp':comment.owner.avatar}" th:with=" img =${#strings.isEmpty(comment.owner.avatar)? theme.config.sidebar.newcomment.providerMirror+'/avatar/'+comment.spec.owner.annotations['email-hash'] :comment.owner.avatar}"
th:src="${isLazyload ? loadingImg : img}" th:data-lazy-src="${ isLazyload ? img : ''}"> th:src="${isLazyload ? loadingImg : img}" th:data-lazy-src="${ isLazyload ? img : ''}">
</a> </a>
<div class="content"> <div class="content">

View File

@ -1,12 +1,10 @@
<!-- 标签 --> <!-- 标签 -->
<th:block <th:block th:with="tags = ${tagFinder.list(1,theme.config.sidebar.tagQuantity)}">
th:with="tags = ${tagFinder.listAll()}, tagQuantity = ${#conversions.convert(theme.config.sidebar.tagQuantity, 'java.lang.Integer')}">
<div class="item-headline"></div> <div class="item-headline"></div>
<div class="card-tag-cloud"> <div class="card-tag-cloud">
<a class="tag-item" style="font-size:1em" th:each="tag,iterStat : ${tags}" <a class="tag-item" style="font-size:1em" th:each="tag,iterStat : ${tags}"
th:href="@{${tag.status.permalink}}" th:href="@{${tag.status.permalink}}"
th:if="${tagQuantity >= 0 && iterStat.index < tagQuantity || tagQuantity < 0}"
th:title="${tag.spec.displayName}"> th:title="${tag.spec.displayName}">
<!-- 角标 --> <!-- 角标 -->
[[${tag.spec.displayName}]]<sup th:text="${tag.status.visiblePostCount}"></sup> [[${tag.spec.displayName}]]<sup th:text="${tag.status.visiblePostCount}"></sup>

View File

@ -1,48 +1,22 @@
<!-- <div class="card-widget card-recent-post card-article" th:with='posts = ${postFinder.list(1,theme.config.sidebar.recentPost)}, -->
<div class="card-widget card-recent-post" th:with=' <div class="card-widget card-recent-post" th:with='
postRandomImg=${#strings.contains(theme.config.layout.postRandomImg,"?") ? theme.config.layout.postRandomImg+"&" : theme.config.layout.postRandomImg+"?"}'> postRandomImg=${#strings.contains(theme.config.layout.postRandomImg,"?") ? theme.config.layout.postRandomImg+"&" : theme.config.layout.postRandomImg+"?"}'>
<div class="item-headline"><i class="haofont hao-icon-eicon_map-2-line1"></i><span>最近发布</span></div> <div class="item-headline"><i class="haofont hao-icon-eicon_map-2-line1"></i><span>最近发布</span></div>
<div class="aside-list"> <div class="aside-list">
<!-- 最新文章,用户可以自定义展示数量 --> <!-- 最新文章,用户可以自定义展示数量 -->
<th:block th:with="archives = ${postFinder.archives(1,#conversions.convert(theme.config.sidebar.recentPost, 'java.lang.Integer'))}"> <div class="aside-list-item" th:each="post : ${postFinder.list({page: 1, size: theme.config.sidebar.recentPost, sort: {'spec.publishTime,desc'}})}">
<th:block th:each="archive : ${archives.items}">
<th:block th:each="month : ${archive.months}">
<div class="aside-list-item" th:each="post : ${month.posts}">
<a class="thumbnail" th:href="@{${post.status.permalink}}" th:title="${post.spec.title}"> <a class="thumbnail" th:href="@{${post.status.permalink}}" th:title="${post.spec.title}">
<img th:alt="${post.spec.title}" <img th:alt="${post.spec.title}"
th:with="img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : post.spec.cover}" th:with="img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : thumbnail.gen(post.spec.cover, 's')}"
th:src="${isLazyload ? loadingImg : img}" th:src="${isLazyload ? loadingImg : img}"
th:data-lazy-src="${ isLazyload ? img : ''}"> th:data-lazy-src="${ isLazyload ? img : ''}">
</a> </a>
<div class="content"> <div class="content">
<a class="title" th:href="@{${post.status.permalink}}" th:text="${post.spec.title}" th:title="${post.spec.title}"></a> <a class="title" th:href="@{${post.status.permalink}}" th:text="${post.spec.title}" th:title="${post.spec.title}"></a>
<time th:attr="datetime=${#dates.format(post.spec.publishTime, 'yyyy-MM-dd HH:mm:ss')}" <time th:attr="datetime=${#dates.format(post.spec.publishTime, 'yyyy-MM-dd HH:mm:ss')}"Add commentMore actions
th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd HH:mm:ss')}" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd HH:mm:ss')}"
th:title="${#dates.format(post.spec.publishTime,'yyyy-MM-dd HH:mm:ss')}"> th:title="${#dates.format(post.spec.publishTime,'yyyy-MM-dd HH:mm:ss')}">
</time> </time>
</div> </div>
</div> </div>
</th:block>
</th:block>
</th:block>
<!-- 以下代码会将置顶的文章也显示在最近发布 -->
<!-- <div class="aside-list-item" th:each="post : ${posts}">
<a class="thumbnail" th:href="@{${post.status.permalink}}" th:title="${post.spec.title}">
<img th:alt="${post.spec.title}"
th:with="img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : post.spec.cover}"
th:src="${isLazyload ? loadingImg : img}"
th:data-lazy-src="${ isLazyload ? img : ''}">
</a>
<div class="content">
<a class="title" th:href="@{${post.status.permalink}}" th:text="${post.spec.title}" th:title="${post.spec.title}"></a>
<time th:attr="datetime=${#dates.format(post.spec.publishTime, 'yyyy-MM-dd HH:mm:ss')}"
th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd HH:mm:ss')}"
th:title="${#dates.format(post.spec.publishTime,'yyyy-MM-dd HH:mm:ss')}">
</time>
</div>
</div> -->
</div> </div>
</div> </div>

View File

@ -0,0 +1,23 @@
<div class="card-widget card-recent-post" th:with='
postRandomImg=${#strings.contains(theme.config.layout.postRandomImg,"?") ? theme.config.layout.postRandomImg+"&" : theme.config.layout.postRandomImg+"?"}'>
<div class="item-headline"><i class="haofont hao-icon-rocket"></i><span>热门文章</span></div>
<div class="aside-list">
<!-- 热门文章,用户可以自定义展示数量 -->
<div class="aside-list-item" th:each="post : ${postFinder.list({page: 1, size: theme.config.sidebar.recentPost, sort: {'stats.visit,desc'}})}">
<a class="thumbnail" th:href="@{${post.status.permalink}}" th:title="${post.spec.title}">
<img th:alt="${post.spec.title}"
th:with="img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : thumbnail.gen(post.spec.cover, 's')}"
th:src="${isLazyload ? loadingImg : img}"
th:data-lazy-src="${ isLazyload ? img : ''}">
</a>
<div class="content">
<a class="title" th:href="@{${post.status.permalink}}" th:text="${post.spec.title}" th:title="${post.spec.title}"></a>
<time th:attr="datetime=${#dates.format(post.spec.publishTime, 'yyyy-MM-dd HH:mm:ss')}"
th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd HH:mm:ss')}"
th:title="${#dates.format(post.spec.publishTime,'yyyy-MM-dd HH:mm:ss')}">
</time>
</div>
</div>
</div>
</div>

View File

@ -50,11 +50,10 @@
<div class="author-content-item-tips">标签</div> <div class="author-content-item-tips">标签</div>
<span class="author-content-item-title">寻找感兴趣的领域</span> <span class="author-content-item-title">寻找感兴趣的领域</span>
</div> </div>
<div class="card-tag-cloud" th:with="tags = ${tagFinder.listAll()}, tagQuantity = ${#conversions.convert(theme.config.sidebar.tagQuantity, 'java.lang.Integer')}"> <div class="card-tag-cloud" th:with="tags = ${tagFinder.list(1,theme.config.sidebar.tagQuantity)}">
<a style="font-size:1em;color:#d3d3d3" <a style="font-size:1em;color:#d3d3d3"
th:each="tag,iterStat : ${tags}" th:each="tag,iterStat : ${tags}"
th:href="@{${tag.status.permalink}}" th:href="@{${tag.status.permalink}}">
th:if="${tagQuantity >= 0 && iterStat.index < tagQuantity || tagQuantity < 0}">
[[${tag.spec.displayName}]]<sup th:text="${tag.status.visiblePostCount}"></sup> [[${tag.spec.displayName}]]<sup th:text="${tag.status.visiblePostCount}"></sup>
</a> </a>
</div> </div>

View File

@ -11,7 +11,7 @@
<span class="recent-post-top-text" <span class="recent-post-top-text"
th:attr="onclick='pjax.loadUrl(\''+ @{${post.status.permalink}} +'\')'">荐</span> th:attr="onclick='pjax.loadUrl(\''+ @{${post.status.permalink}} +'\')'">荐</span>
<img class="post_bg" <img class="post_bg"
th:with=' img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : post.spec.cover}' th:with=' img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : thumbnail.gen(post.spec.cover, "s")}'
th:alt="${post.spec.title}" th:alt="${post.spec.title}"
th:src="${isLazyload ? loadingImg : img}" th:src="${isLazyload ? loadingImg : img}"
@ -38,7 +38,7 @@
<span class="recent-post-top-text" <span class="recent-post-top-text"
th:attr="onclick='pjax.loadUrl(\''+ @{${post.status.permalink}} +'\')'">荐</span> th:attr="onclick='pjax.loadUrl(\''+ @{${post.status.permalink}} +'\')'">荐</span>
<img class="post_bg" <img class="post_bg"
th:with='img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : post.spec.cover}' th:with='img = ${#strings.isEmpty(post.spec.cover) ? postRandomImg+post.spec.title : thumbnail.gen(post.spec.cover, "s")}'
th:alt="${post.spec.title}" th:alt="${post.spec.title}"
th:src="${isLazyload ? loadingImg : img}" th:src="${isLazyload ? loadingImg : img}"

View File

@ -33,7 +33,7 @@
<div class="comment-card" th:title="${comment.spec.content}" <div class="comment-card" th:title="${comment.spec.content}"
th:onclick="pjax.loadUrl([[${url}]])"> th:onclick="pjax.loadUrl([[${url}]])">
<div class="comment-info"> <div class="comment-info">
<img th:with=" img =${#strings.isEmpty(comment.owner.avatar)?'https://cravatar.cn/avatar/?d=mp':comment.owner.avatar}" <img th:with=" img =${#strings.isEmpty(comment.owner.avatar)? theme.config.sidebar.newcomment.providerMirror+'/avatar/'+comment.spec.owner.annotations['email-hash'] :comment.owner.avatar}"
th:alt="${comment.owner.displayName}" th:src="${isLazyload ? loadingImg : img}" th:alt="${comment.owner.displayName}" th:src="${isLazyload ? loadingImg : img}"
th:data-lazy-src="${ isLazyload ? img : ''}" class="no-lightbox nolazyload avatar"> th:data-lazy-src="${ isLazyload ? img : ''}" class="no-lightbox nolazyload avatar">
<div class="comment-information"> <div class="comment-information">

View File

@ -120,12 +120,15 @@
<i class="haofont hao-icon-spinner fa-spin"></i> <i class="haofont hao-icon-spinner fa-spin"></i>
</span> </span>
</a> </a>
<th:block th:with="contributorLogin = ${contributorFinder.getContributor(#authentication.name)}">
<a class="post-meta-editor" sec:authorize="isAuthenticated()" data-flag-title="编辑文章" <a class="post-meta-editor" sec:authorize="isAuthenticated()" data-flag-title="编辑文章"
title="编辑文章" title="编辑文章"
th:if="${contributorLogin.name} == ${post.spec.owner}"
th:attr="onclick='javascript:window.open(\''+ '/console/posts/editor?name='+${post.metadata.name}+'&returnToView=true\''+ ',' +'\'_self'+'\')'"> th:attr="onclick='javascript:window.open(\''+ '/console/posts/editor?name='+${post.metadata.name}+'&returnToView=true\''+ ',' +'\'_self'+'\')'">
<i style="margin-top: 2px;" class="haofont hao-icon-bianji post-meta-icon"></i> <i style="margin-top: 2px;" class="haofont hao-icon-bianji post-meta-icon"></i>
<span>编辑</span> <span>编辑</span>
</a> </a>
</th:block>
</div> </div>
</div> </div>
@ -198,7 +201,7 @@
<div th:if="${postCursor.hasPrevious()}" th:class="${postCursor.hasNext()==false} ? 'prev-post pull-full ' : 'prev-post pull-left'"> <div th:if="${postCursor.hasPrevious()}" th:class="${postCursor.hasNext()==false} ? 'prev-post pull-full ' : 'prev-post pull-left'">
<a th:if="${postCursor.hasPrevious()}" th:href="@{${postCursor.previous.status.permalink}}"> <a th:if="${postCursor.hasPrevious()}" th:href="@{${postCursor.previous.status.permalink}}">
<img alt="cover" id="preimg" class="nolazyload" <img alt="cover" id="preimg" class="nolazyload"
th:with="img = ${#strings.isEmpty(postCursor.previous.spec.cover) ? postRandomImg+postCursor.previous.spec.title : postCursor.previous.spec.cover}" th:with="img = ${#strings.isEmpty(postCursor.previous.spec.cover) ? postRandomImg+postCursor.previous.spec.title : thumbnail.gen(postCursor.previous.spec.cover, 'm')}"
th:src="${isLazyload ? loadingImg : img}" th:src="${isLazyload ? loadingImg : img}"
th:data-lazy-src="${ isLazyload ? img : ''}"> th:data-lazy-src="${ isLazyload ? img : ''}">
@ -211,7 +214,7 @@
<div th:if="${postCursor.hasNext()}" th:class="${postCursor.hasPrevious()==false} ? 'next-post pull-full ':'next-post pull-right'"> <div th:if="${postCursor.hasNext()}" th:class="${postCursor.hasPrevious()==false} ? 'next-post pull-full ':'next-post pull-right'">
<a th:if="${postCursor.hasNext()}" th:href="@{${postCursor.next.status.permalink}}"> <a th:if="${postCursor.hasNext()}" th:href="@{${postCursor.next.status.permalink}}">
<img alt="cover" id="preimg" class="nolazyload" <img alt="cover" id="preimg" class="nolazyload"
th:with="img = ${#strings.isEmpty(postCursor.next.spec.cover) ? postRandomImg+postCursor.next.spec.title : postCursor.next.spec.cover}" th:with="img = ${#strings.isEmpty(postCursor.next.spec.cover) ? postRandomImg+postCursor.next.spec.title : thumbnail.gen(postCursor.next.spec.cover, 'm')}"
th:src="${isLazyload ? loadingImg : img}" th:src="${isLazyload ? loadingImg : img}"
th:data-lazy-src="${ isLazyload ? img : ''}"> th:data-lazy-src="${ isLazyload ? img : ''}">
<div class="pagination-info"> <div class="pagination-info">

View File

@ -52,7 +52,7 @@ spec:
issues: https://gitee.com/uptoz/halo-theme-hao/issues issues: https://gitee.com/uptoz/halo-theme-hao/issues
settingName: "theme-hao-setting" settingName: "theme-hao-setting"
configMapName: "theme-hao-configMap" configMapName: "theme-hao-configMap"
version: "1.0.5-ce" version: "1.0.6-ce"
requires: ">=2.21.0" requires: ">=2.21.0"
license: license:
- name: "CC BY-SA 4.0" - name: "CC BY-SA 4.0"