whispe.html
· 8.4 KiB · HTML
Raw
{{ with resources.Get "css/addon/whisper.css" }}
<style>
{{ .Content | safeCSS }}
</style>
{{ end }}
<body>
<div id="toots-content" class="toots-container">
<div id="toots"></div>
<div id="toots-loading" class="loading-container" style="display: none;">
<div class="spinner"></div>
</div>
<div class="pt-3 text-center flex justify-center">
<button id="toots-moreButton" onclick="tootsShowMore()">
加载更多
</button>
</div>
</div>
</body>
<script src="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js" crossorigin="anonymous"
defer></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
Fancybox.bind("[data-fancybox]", {});
});
// console.log("Fancybox is ready!");
</script>
<link rel="preload" href="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css" as="style"
crossorigin="anonymous">
<link rel="stylesheet" href="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css"
crossorigin="anonymous">
{{- with resources.Get "js/time-fmt.min.js" }}
<script data-swup-reload-script>
function initTimeFmt() {
(function () {
{{ .Content | safeJS }}
// 确保 formatTime 在全局作用域中可用
if (typeof formatTime === 'function') {
window.formatTime = formatTime;
} else {
console.error('formatTime is not defined in time-fmt.min.js');
}
})();
}
// 初始加载时执行
initTimeFmt();
// 监听 Swup 页面切换事件
document.addEventListener('swup:contentReplaced', initTimeFmt);
</script>
{{- end }}
<script data-swup-reload-script>
(function () {
let maxId = null;
let isFirst = true;
const cacheKey = 'mastodon_toots_cache';
const cacheExpiration = 5 * 60 * 1000; // 5 minutes
const tootsDiv = document.getElementById('toots');
const tootsMoreButton = document.getElementById('toots-moreButton');
const tootsLoading = document.getElementById('toots-loading');
const urlObject = new URL(window.location.href);
const idValue = urlObject.searchParams.get("id");
async function getPublicToots(forceRefresh = false) {
let limit = "{{ .Get 2 | default 5 }}";
const currentTime = new Date().getTime();
// Check cache for non-forced refresh
if (!forceRefresh && !maxId) {
const cachedData = localStorage.getItem(cacheKey);
if (cachedData) {
const { data, timestamp } = JSON.parse(cachedData);
if (currentTime - timestamp < cacheExpiration) {
console.log("Cache hit");
return data;
}
}
}
let toots;
if (idValue != null && isFirst) {
isFirst = false;
const response = await fetch("{{ .Site.Params.Whisper.instance }}/api/v1/statuses/" + idValue, {
headers: {
'Authorization': "Bearer {{ .Site.Params.bot_token }}"
}
});
toots = [await response.json()];
} else {
const queryParams = maxId ? (`?limit=${limit}&max_id=${maxId}`) : "?limit=" + limit;
const response = await fetch("{{ .Site.Params.Whisper.instance }}/api/v1/accounts/{{ .Site.Params.Whisper.user_id }}/statuses" + queryParams + "&exclude_replies=true", {
headers: {
'Authorization': "Bearer {{ .Site.Params.Whisper.bot_token }}"
}
});
toots = await response.json();
}
// Cache only the first page
if (!maxId) {
localStorage.setItem(cacheKey, JSON.stringify({
data: toots,
timestamp: currentTime
}));
}
return toots;
}
function createMediaGrid(mediaAttachments) {
if (mediaAttachments.length === 0) return '';
const mediaClass = mediaAttachments.length === 1 ? 'single' :
mediaAttachments.length === 2 ? 'double' :
mediaAttachments.length === 3 ? 'triple' :
mediaAttachments.length === 4 ? 'quad' : 'multi';
const mediaHtml = mediaAttachments.map(media => {
if (media.type === 'image') {
return `
<a href="${media.url}"
data-fancybox="gallery"
data-caption="${media.description || ''}"
class="padding-bottom: 15px">
<img src="${media.preview_url}"
alt="${media.description || ''}"
loading="lazy">
</a>
<figcaption class="text-center text-neutral-500 mt-2 md:mt-3 text-xs md:text-sm">
${media.description || ''}
</figcaption>`;
} else if (media.type === 'video') {
return `
<a href="${media.url}" data-fancybox="gallery" data-type="video" data-caption="${media.description || ''}">
<video src="${media.url}" preload="none" controls></video>
</a>`;
} else {
return ''; // 可以在这里添加对其他类型媒体的处理
}
}).join('');
return `<div class="toot-media ${mediaClass}">${mediaHtml}</div>`;
}
async function displayToots(forceRefresh = false) {
try {
tootsLoading.style.display = "block";
tootsMoreButton.style.display = 'none';
const toots = await getPublicToots(forceRefresh);
if (toots && toots.length > 0) {
toots.forEach(toot => {
const tootDiv = document.createElement("div");
tootDiv.classList.add("toot");
tootDiv.innerHTML = `
<div class="toot-info">
<div class="toot-avatar">
<img src="{{ .Site.Params.Author.avatar }}" alt="${toot.account.display_name}">
</div>
<div class="toot-profile">
<strong>${toot.account.display_name}</strong>
<a href="${toot.url}" target="_blank">@${toot.account.acct}</a>
<small>${window.formatTime(toot.created_at)}</small>
</div>
</div>
<div class="toot-content">
${toot.content}
</div>
${createMediaGrid(toot.media_attachments)}
<br>
<div class="toot-stats">
<span><i class="mdi--reply"></i>${toot.replies_count}</span>
<span><i class="mdi--twitter-retweet"></i>${toot.reblogs_count}</span>
<span><i class="mdi--star"></i>${toot.favourites_count}</span>
</div>
`;
tootsDiv.appendChild(tootDiv);
maxId = toot.id;
});
tootsMoreButton.style.display = 'block';
} else {
tootsMoreButton.style.display = 'none';
}
} catch (error) {
console.error('获取 Toots 时出错:', error);
tootsDiv.innerHTML += `<p>加载失败: ${error.message}</p>`;
}
tootsLoading.style.display = "none";
}
function tootsShowMore() {
displayToots();
}
// 将 tootsShowMore 绑定到 window 对象
window.tootsShowMore = tootsShowMore;
// 初始加载
const isForcedRefresh = performance.navigation.type === 1;
if (isForcedRefresh) {
localStorage.removeItem(cacheKey);
}
displayToots(isForcedRefresh);
window.ViewImage && ViewImage.init('.toot-content img');
})();
</script>
1 | {{ with resources.Get "css/addon/whisper.css" }} |
2 | <style> |
3 | {{ .Content | safeCSS }} |
4 | </style> |
5 | {{ end }} |
6 | |
7 | <body> |
8 | <div id="toots-content" class="toots-container"> |
9 | <div id="toots"></div> |
10 | <div id="toots-loading" class="loading-container" style="display: none;"> |
11 | <div class="spinner"></div> |
12 | </div> |
13 | <div class="pt-3 text-center flex justify-center"> |
14 | <button id="toots-moreButton" onclick="tootsShowMore()"> |
15 | 加载更多 |
16 | </button> |
17 | </div> |
18 | </div> |
19 | </body> |
20 | |
21 | <script src="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js" crossorigin="anonymous" |
22 | defer></script> |
23 | <script> |
24 | document.addEventListener("DOMContentLoaded", function () { |
25 | Fancybox.bind("[data-fancybox]", {}); |
26 | }); |
27 | // console.log("Fancybox is ready!"); |
28 | </script> |
29 | |
30 | <link rel="preload" href="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css" as="style" |
31 | crossorigin="anonymous"> |
32 | <link rel="stylesheet" href="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css" |
33 | crossorigin="anonymous"> |
34 | |
35 | {{- with resources.Get "js/time-fmt.min.js" }} |
36 | <script data-swup-reload-script> |
37 | function initTimeFmt() { |
38 | (function () { |
39 | {{ .Content | safeJS }} |
40 | // 确保 formatTime 在全局作用域中可用 |
41 | if (typeof formatTime === 'function') { |
42 | window.formatTime = formatTime; |
43 | } else { |
44 | console.error('formatTime is not defined in time-fmt.min.js'); |
45 | } |
46 | })(); |
47 | } |
48 | // 初始加载时执行 |
49 | initTimeFmt(); |
50 | // 监听 Swup 页面切换事件 |
51 | document.addEventListener('swup:contentReplaced', initTimeFmt); |
52 | </script> |
53 | {{- end }} |
54 | |
55 | <script data-swup-reload-script> |
56 | (function () { |
57 | let maxId = null; |
58 | let isFirst = true; |
59 | const cacheKey = 'mastodon_toots_cache'; |
60 | const cacheExpiration = 5 * 60 * 1000; // 5 minutes |
61 | const tootsDiv = document.getElementById('toots'); |
62 | const tootsMoreButton = document.getElementById('toots-moreButton'); |
63 | const tootsLoading = document.getElementById('toots-loading'); |
64 | const urlObject = new URL(window.location.href); |
65 | const idValue = urlObject.searchParams.get("id"); |
66 | |
67 | async function getPublicToots(forceRefresh = false) { |
68 | let limit = "{{ .Get 2 | default 5 }}"; |
69 | const currentTime = new Date().getTime(); |
70 | |
71 | // Check cache for non-forced refresh |
72 | if (!forceRefresh && !maxId) { |
73 | const cachedData = localStorage.getItem(cacheKey); |
74 | if (cachedData) { |
75 | const { data, timestamp } = JSON.parse(cachedData); |
76 | if (currentTime - timestamp < cacheExpiration) { |
77 | console.log("Cache hit"); |
78 | return data; |
79 | } |
80 | } |
81 | } |
82 | |
83 | let toots; |
84 | if (idValue != null && isFirst) { |
85 | isFirst = false; |
86 | const response = await fetch("{{ .Site.Params.Whisper.instance }}/api/v1/statuses/" + idValue, { |
87 | headers: { |
88 | 'Authorization': "Bearer {{ .Site.Params.bot_token }}" |
89 | } |
90 | }); |
91 | toots = [await response.json()]; |
92 | } else { |
93 | const queryParams = maxId ? (`?limit=${limit}&max_id=${maxId}`) : "?limit=" + limit; |
94 | const response = await fetch("{{ .Site.Params.Whisper.instance }}/api/v1/accounts/{{ .Site.Params.Whisper.user_id }}/statuses" + queryParams + "&exclude_replies=true", { |
95 | headers: { |
96 | 'Authorization': "Bearer {{ .Site.Params.Whisper.bot_token }}" |
97 | } |
98 | }); |
99 | toots = await response.json(); |
100 | } |
101 | |
102 | // Cache only the first page |
103 | if (!maxId) { |
104 | localStorage.setItem(cacheKey, JSON.stringify({ |
105 | data: toots, |
106 | timestamp: currentTime |
107 | })); |
108 | } |
109 | |
110 | return toots; |
111 | } |
112 | |
113 | function createMediaGrid(mediaAttachments) { |
114 | if (mediaAttachments.length === 0) return ''; |
115 | |
116 | const mediaClass = mediaAttachments.length === 1 ? 'single' : |
117 | mediaAttachments.length === 2 ? 'double' : |
118 | mediaAttachments.length === 3 ? 'triple' : |
119 | mediaAttachments.length === 4 ? 'quad' : 'multi'; |
120 | |
121 | const mediaHtml = mediaAttachments.map(media => { |
122 | if (media.type === 'image') { |
123 | return ` |
124 | <a href="${media.url}" |
125 | data-fancybox="gallery" |
126 | data-caption="${media.description || ''}" |
127 | class="padding-bottom: 15px"> |
128 | <img src="${media.preview_url}" |
129 | alt="${media.description || ''}" |
130 | loading="lazy"> |
131 | </a> |
132 | <figcaption class="text-center text-neutral-500 mt-2 md:mt-3 text-xs md:text-sm"> |
133 | ${media.description || ''} |
134 | </figcaption>`; |
135 | } else if (media.type === 'video') { |
136 | return ` |
137 | <a href="${media.url}" data-fancybox="gallery" data-type="video" data-caption="${media.description || ''}"> |
138 | <video src="${media.url}" preload="none" controls></video> |
139 | </a>`; |
140 | } else { |
141 | return ''; // 可以在这里添加对其他类型媒体的处理 |
142 | } |
143 | }).join(''); |
144 | |
145 | return `<div class="toot-media ${mediaClass}">${mediaHtml}</div>`; |
146 | } |
147 | |
148 | async function displayToots(forceRefresh = false) { |
149 | try { |
150 | tootsLoading.style.display = "block"; |
151 | tootsMoreButton.style.display = 'none'; |
152 | const toots = await getPublicToots(forceRefresh); |
153 | if (toots && toots.length > 0) { |
154 | toots.forEach(toot => { |
155 | const tootDiv = document.createElement("div"); |
156 | tootDiv.classList.add("toot"); |
157 | |
158 | tootDiv.innerHTML = ` |
159 | <div class="toot-info"> |
160 | <div class="toot-avatar"> |
161 | <img src="{{ .Site.Params.Author.avatar }}" alt="${toot.account.display_name}"> |
162 | </div> |
163 | <div class="toot-profile"> |
164 | <strong>${toot.account.display_name}</strong> |
165 | <a href="${toot.url}" target="_blank">@${toot.account.acct}</a> |
166 | <small>${window.formatTime(toot.created_at)}</small> |
167 | </div> |
168 | </div> |
169 | <div class="toot-content"> |
170 | ${toot.content} |
171 | </div> |
172 | ${createMediaGrid(toot.media_attachments)} |
173 | <br> |
174 | <div class="toot-stats"> |
175 | <span><i class="mdi--reply"></i>${toot.replies_count}</span> |
176 | <span><i class="mdi--twitter-retweet"></i>${toot.reblogs_count}</span> |
177 | <span><i class="mdi--star"></i>${toot.favourites_count}</span> |
178 | </div> |
179 | `; |
180 | |
181 | tootsDiv.appendChild(tootDiv); |
182 | maxId = toot.id; |
183 | }); |
184 | tootsMoreButton.style.display = 'block'; |
185 | } else { |
186 | tootsMoreButton.style.display = 'none'; |
187 | } |
188 | } catch (error) { |
189 | console.error('获取 Toots 时出错:', error); |
190 | tootsDiv.innerHTML += `<p>加载失败: ${error.message}</p>`; |
191 | } |
192 | tootsLoading.style.display = "none"; |
193 | } |
194 | |
195 | function tootsShowMore() { |
196 | displayToots(); |
197 | } |
198 | |
199 | // 将 tootsShowMore 绑定到 window 对象 |
200 | window.tootsShowMore = tootsShowMore; |
201 | |
202 | // 初始加载 |
203 | const isForcedRefresh = performance.navigation.type === 1; |
204 | if (isForcedRefresh) { |
205 | localStorage.removeItem(cacheKey); |
206 | } |
207 | displayToots(isForcedRefresh); |
208 | |
209 | window.ViewImage && ViewImage.init('.toot-content img'); |
210 | })(); |
211 | </script> |