Last active 1727320678

whispe.html Raw
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>