Skip to content

Commit 7b0cd95

Browse files
committed
feat: mastodon album
1 parent cdef012 commit 7b0cd95

File tree

4 files changed

+264
-71
lines changed

4 files changed

+264
-71
lines changed

content/album/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ aliases:
2222
- /gallery
2323
---
2424

25-
这是我的随手拍页面。因为我不太会拍照,所以就取名为「随手拍」,实在是称不上相册。通过自己部署的 [Mastodon](https://joinmastodon.org/) 实例 [e5n.cc](https://e5n.cc) 的 API 引用,当发布的嘟文是,媒体文件将自动解析到本页面,没有特别的用意。
25+
这是我的随手拍页面。因为我不太会拍照,所以就取名为「随手拍」,实在是称不上相册。通过自己部署的 [Mastodon](https://joinmastodon.org/) 实例 [e5n.cc](https://e5n.cc) 的 API 引用,当发布的嘟文时,媒体文件将自动解析到本页面,没有特别的用意。

example/album/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ aliases:
2222
- /gallery
2323
---
2424

25-
这是我的随手拍页面。因为我不太会拍照,所以就取名为「随手拍」,实在是称不上相册。通过自己部署的 [Mastodon](https://joinmastodon.org/) 实例 [e5n.cc](https://e5n.cc) 的 API 引用,当发布的嘟文是,媒体文件将自动解析到本页面,没有特别的用意。
25+
这是我的随手拍页面。因为我不太会拍照,所以就取名为「随手拍」,实在是称不上相册。通过自己部署的 [Mastodon](https://joinmastodon.org/) 实例 [e5n.cc](https://e5n.cc) 的 API 引用,当发布的嘟文时,媒体文件将自动解析到本页面,没有特别的用意。

layouts/_default/album.html

Lines changed: 260 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,125 @@
22
{{ .Scratch.Set "scope" "single" }}
33

44
<style>
5-
.waterfall {
6-
list-style: none;
7-
column-gap: 0;
8-
padding: 0;
5+
#waterfall {
96
column-count: 4;
7+
column-gap: 10px;
8+
position: relative;
109
}
11-
.waterfall .item {
12-
width: 100%;
13-
padding: 2px;
10+
11+
@media only screen and (max-width: 640px) {
12+
#waterfall {
13+
column-count: 2;
14+
}
15+
}
16+
17+
#waterfall figure a {
1418
margin: 0;
19+
display: grid;
20+
grid-template-rows: 1fr auto;
21+
margin-bottom: 10px;
1522
break-inside: avoid;
23+
position: relative;
24+
}
25+
26+
#waterfall figure a>img {
27+
grid-row: 1 / -1;
28+
grid-column: 1;
29+
margin-top: 0;
30+
margin-bottom: 0;
31+
border-radius: 0.5rem;
32+
transition: transform 0.3s ease, filter 0.3s ease;
33+
}
34+
35+
#waterfall figure:hover img {
36+
transform: scale(1.2);
37+
z-index: 1;
38+
border-bottom-left-radius: 0;
39+
border-bottom-right-radius: 0;
40+
}
41+
42+
.blur {
43+
filter: blur(5px);
1644
}
1745

18-
.waterfall .item img {
19-
width: 100%;
46+
#waterfall figure {
47+
position: relative;
48+
display: inline-block;
2049
margin-top: 0;
2150
margin-bottom: 0;
2251
}
2352

24-
.view-image-tools{opacity:1;animation:1s 1.5s forwards fadeOut}
25-
.view-image-tools:hover{opacity:1;animation:none;transition:opacity .2s}
26-
@keyframes fadeOut{to{opacity:0}}
53+
#waterfall figure figcaption {
54+
visibility: hidden;
55+
opacity: 0;
56+
position: absolute;
57+
left: 0;
58+
right: 0;
59+
background: rgba(0, 0, 0, 0.7);
60+
color: white;
61+
padding: 10px;
62+
padding-bottom: 0;
63+
margin: 0;
64+
text-align: center;
65+
transform: scale(1);
66+
z-index: 2;
67+
border-bottom-left-radius: 0.5rem;
68+
border-bottom-right-radius: 0.5rem;
69+
transition: transform 0.3s ease, opacity 0.3s ease;
70+
}
71+
72+
#waterfall figure figcaption p {
73+
margin-top: 0;
74+
margin-bottom: 0;
75+
font-size: 12px;
76+
text-align: left;
77+
}
78+
79+
#waterfall figure figcaption p a {
80+
color: white;
81+
}
82+
83+
#waterfall figure:hover>figcaption {
84+
visibility: visible;
85+
opacity: 1;
86+
transform: scale(1.2);
87+
}
88+
89+
/* Loading spinner */
90+
#waterfall>.loading-spinner {
91+
position: absolute;
92+
width: 3rem;
93+
height: 3rem;
94+
margin: auto;
95+
top: calc(50% - 0.5rem);
96+
right: calc(50% - 1.5rem);
97+
}
98+
99+
.loading-spinner {
100+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'%3E%3Cg%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 64 64' to='360 64 64' dur='1000ms' repeatCount='indefinite'/%3E%3Cpath d='M64 6.69a57.3 57.3 0 1 1 0 114.61A57.3 57.3 0 0 1 6.69 64' fill='none' stroke='%23404040' stroke-width='12'/%3E%3C/g%3E%3C/svg%3E");
101+
background-repeat: no-repeat;
102+
background-position: center center;
103+
background-color: transparent;
104+
background-size: min(2.5rem, calc(100% - 0.5rem));
105+
}
106+
107+
/* view image custom */
108+
.view-image-tools {
109+
opacity: 1;
110+
animation: 1s 1.5s forwards fadeOut
111+
}
112+
113+
.view-image-tools:hover {
114+
opacity: 1;
115+
animation: none;
116+
transition: opacity .2s
117+
}
118+
119+
@keyframes fadeOut {
120+
to {
121+
opacity: 0
122+
}
123+
}
27124
</style>
28125

29126
<article>
@@ -58,20 +155,26 @@ <h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral">
58155

59156
<section class="flex flex-col max-w-full mt-0 prose dark:prose-invert lg:flex-row">
60157

61-
<div class="min-w-0 min-h-0 max-w-fit">
158+
<div class="min-w-0 min-h-0 max-w-fit">
62159

63-
<div class="article-content max-w-full mb-20">
64-
{{ .Content }}
160+
<div class="article-content max-w-full mb-20">
161+
{{ .Content }}
65162

66-
<ul class="waterfall" id="waterfall" view-image>
67-
<!-- 图片将动态插入到这里 -->
68-
</ul>
69-
70-
<button id="load-more" type="button" class="text-neutral-50 bg-[#4285F4] hover:bg-[#4285F4]/90 focus:ring-4 focus:outline-none focus:ring-[#4285F4]/50 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-[#4285F4]/55 me-2 mb-2">Load More</button>
163+
<div id="waterfall" class="waterfall">
164+
<div class="loading-spinner"></div>
165+
<div id="list-1"></div>
166+
<div id="list-2"></div>
167+
<div id="list-3"></div>
168+
<div id="list-4"></div>
71169
</div>
72170

171+
<div class="flex justify-center">
172+
<button id="loadMore" type="button" class="text-neutral-50 bg-[#4285F4] hover:bg-[#4285F4]/90 focus:ring-4 focus:outline-none focus:ring-[#4285F4]/50 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-[#4285F4]/55 me-2 mt-20 mb-2">Load More</button>
173+
</div>
73174
</div>
74175

176+
</div>
177+
75178
</section>
76179

77180
</article>
@@ -93,58 +196,148 @@ <h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral">
93196
</script>
94197

95198
<script>
96-
const waterfall_container = document.getElementById('waterfall');
97-
const loadMoreButton = document.getElementById('load-more');
98-
let lastMaxId = localStorage.getItem('lastMaxId') || '';
99-
100-
function fetchImages(maxId = '') {
101-
fetch(`https://e5n.cc/api/v1/accounts/111136231674527355/statuses?only_media=true&limit=10&exclude_replies=true&exclude_reblogs=true&max_id=${maxId}`)
102-
.then(response => response.json())
103-
.then(data => {
104-
if (data.length > 0) {
105-
const newLastMaxId = data[data.length - 1].id;
106-
localStorage.setItem('lastMaxId', lastMaxId);
107-
data.forEach(status => {
108-
status.media_attachments.forEach(media => {
109-
const imgUrl = media.preview_url;
110-
const hrefUrl = media.url;
111-
const existingItem = Array.from(waterfall_container.children).find(item => item.querySelector('img').src === imgUrl);
112-
113-
if (!existingItem) {
114-
const item = document.createElement('li');
115-
item.className = 'item';
116-
const original = document.createElement('a');
117-
original.href = hrefUrl
118-
const img = document.createElement('img');
119-
img.src = imgUrl;
120-
original.appendChild(img);
121-
item.appendChild(original);
122-
waterfall_container.appendChild(item);
123-
img.style.aspectRatio = media.meta.small.aspect; // 设置图片的宽高比
124-
} else {
125-
// 删除重复内容
126-
const newItem = document.createElement('li');
127-
newItem.className = 'item';
128-
const newImg = document.createElement('img');
129-
newImg.src = imgUrl;
130-
newItem.appendChild(newImg);
131-
waterfall_container.replaceChild(newItem, existingItem);
132-
}
133-
});
134-
});
135-
}
136-
})
137-
.catch(error => console.error('Error fetching data:', error));
199+
const apiUrl = 'https://e5n.cc/api/v1/accounts/111136231674527355/statuses';
200+
let maxId = null;
201+
202+
async function fetchImages(limit, maxId = null) {
203+
const url = new URL(apiUrl);
204+
url.searchParams.append('tagged', 'ealbum');
205+
url.searchParams.append('only_media', 'true');
206+
url.searchParams.append('limit', limit);
207+
url.searchParams.append('exclude_replies', 'true');
208+
url.searchParams.append('exclude_reblogs', 'true');
209+
if (maxId) {
210+
url.searchParams.append('max_id', maxId);
138211
}
139212

140-
loadMoreButton.addEventListener('click', () => {
141-
fetchImages(lastMaxId);
213+
const response = await fetch(url);
214+
const data = await response.json();
215+
return data;
216+
}
217+
218+
function getShortestList() {
219+
const lists = [
220+
document.getElementById('list-1'),
221+
document.getElementById('list-2'),
222+
document.getElementById('list-3'),
223+
document.getElementById('list-4')
224+
];
225+
let shortestList = lists[0];
226+
lists.forEach(list => {
227+
if (list.offsetHeight < shortestList.offsetHeight) {
228+
shortestList = list;
229+
}
230+
});
231+
return shortestList;
232+
}
233+
234+
function extractTextFromHTML(htmlString) {
235+
// 使用正则表达式去掉 <a> 标签及其内容
236+
const strippedString = htmlString.replace(/<a[^>]*>(.*?)<\/a>/g, '$1');
237+
238+
// 去掉其他 HTML 标签
239+
const textOnly = strippedString.replace(/<[^>]+>/g, '');
240+
241+
return textOnly;
242+
}
243+
function renderImages(data) {
244+
const waterfall = document.getElementById('waterfall');
245+
data.forEach((item, index) => {
246+
item.media_attachments.forEach(media => {
247+
const listId = `list-${(index % 4) + 1}`;
248+
const list = document.getElementById(listId);
249+
250+
const figure = document.createElement('figure')
251+
252+
const a = document.createElement('a');
253+
a.href = media.url;
254+
255+
const img = document.createElement('img');
256+
img.src = media.preview_url;
257+
img.style.width = '100%';
258+
img.style.height = 'auto';
259+
img.style.objectFit = 'cover';
260+
261+
const figcaption = document.createElement('figcaption');
262+
figcaption.innerHTML = '<p><a href="' + item.url + '" target="_blank" no-view>' + extractTextFromHTML(item.content) + '</a></p>';
263+
264+
const loading = document.querySelector('.loading-spinner');
265+
loading.style.display = 'none';
266+
267+
a.appendChild(img);
268+
figure.append(a, figcaption);
269+
list.appendChild(figure);
270+
});
142271
});
272+
}
273+
274+
async function loadInitialImages() {
275+
const data = await fetchImages(20);
276+
renderImages(data);
277+
if (data.length > 0) {
278+
maxId = data[data.length - 1].id;
279+
}
280+
addHoverEffects();
281+
}
282+
283+
async function loadMoreImages() {
284+
const data = await fetchImages(12, maxId);
285+
if (data.length > 0) {
286+
// Insert the first item into the shortest list
287+
const shortestList = getShortestList();
288+
const firstItem = data[0];
289+
const firstItemMedia = data[0].media_attachments[0];
290+
291+
const figure = document.createElement('figure')
292+
293+
const a = document.createElement('a');
294+
a.href = firstItemMedia.url;
295+
296+
const img = document.createElement('img');
297+
img.src = firstItemMedia.preview_url;
298+
img.style.width = '100%';
299+
img.style.height = 'auto';
300+
img.style.objectFit = 'cover';
301+
302+
const figcaption = document.createElement('figcaption')
303+
figcaption.innerHTML = '<p><a href="' + firstItem.url + '" target="_blank" no-view>' + extractTextFromHTML(firstItem.content) + '</a></p>';
304+
305+
a.appendChild(img);
306+
figure.append(a, figcaption);
307+
shortestList.appendChild(figure);
308+
309+
// Render the rest of the items
310+
renderImages(data.slice(1));
311+
312+
maxId = data[data.length - 1].id;
313+
addHoverEffects();
314+
}
315+
}
316+
317+
function addHoverEffects() {
318+
const images = document.querySelectorAll('#waterfall figure');
319+
images.forEach(img => {
320+
img.addEventListener('mouseenter', () => {
321+
images.forEach(otherImg => {
322+
if (otherImg !== img) {
323+
otherImg.classList.add('blur');
324+
}
325+
});
326+
});
327+
img.addEventListener('mouseleave', () => {
328+
images.forEach(otherImg => {
329+
otherImg.classList.remove('blur');
330+
});
331+
});
332+
});
333+
}
334+
335+
document.getElementById('loadMore').addEventListener('click', loadMoreImages);
143336

144-
window.ViewImage && ViewImage.init('.waterfall a');
337+
window.ViewImage && ViewImage.init('figure a');
145338

146-
// 初始加载
147-
fetchImages();
339+
// Load initial images when the page loads
340+
window.onload = loadInitialImages;
148341
</script>
149342

150343
{{ end }}

wrangler.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "eallion-com-cf-pages-origin"
22
pages_build_output_dir = "./public"
33

44
[vars]
5-
HUGO_VERSION = "v0.136.2"
5+
HUGO_VERSION = "v0.136.3"
66

77
[env.production.vars]
8-
HUGO_VERSION = "v0.136.2"
8+
HUGO_VERSION = "v0.136.3"

0 commit comments

Comments
 (0)