Son aktivite 1 week ago

将ech0说说展示到前端网站页面

LiuShen's Avatar LiuShen bu gisti düzenledi 1 week ago. Düzenlemeye git

1 file changed, 413 insertions, 337 deletions

ech0-shuoshuo.js

@@ -1,326 +1,364 @@
1 + (function () {
2 + const TALK_API_URL = 'https://mm.liushen.fun/api/echo/page';
3 + const TALK_CACHE_KEY = 'liushenEchoCacheV2';
4 + const TALK_CACHE_TIME_KEY = 'liushenEchoCacheTimeV2';
5 + const TALK_CACHE_DURATION = 30 * 60 * 1000;
6 + const TALK_AVATAR = 'https://p.liiiu.cn/i/2025/03/13/67d2fc82d329c.webp';
7 + const shuoshuoState = window.__liushenShuoshuoState || (window.__liushenShuoshuoState = {
8 + resizeHandler: null,
9 + afterRenderTimer: null,
10 + listenersBound: false
11 + });
12 +
13 + function cleanupShuoshuo() {
14 + if (shuoshuoState.afterRenderTimer) {
15 + window.clearTimeout(shuoshuoState.afterRenderTimer);
16 + shuoshuoState.afterRenderTimer = null;
17 + }
18 +
19 + if (shuoshuoState.resizeHandler) {
20 + window.removeEventListener('resize', shuoshuoState.resizeHandler);
21 + shuoshuoState.resizeHandler = null;
22 + }
23 + }
24 +
1 25 function renderTalks() {
26 + cleanupShuoshuo();
27 +
2 28 const talkContainer = document.querySelector('#talk');
3 29 if (!talkContainer) return;
30 +
4 31 talkContainer.innerHTML = '';
32 +
5 33 const generateIconSVG = () => {
6 - return `<svg viewBox="0 0 512 512"xmlns="http://www.w3.org/2000/svg"class="is-badge icon"><path d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z"fill="#1da1f2"></path></svg>`;
7 - }
8 - const waterfall = (a) => {
9 - function b(a, b) {
10 - var c = window.getComputedStyle(b);
11 - return parseFloat(c["margin" + a]) || 0
34 + return '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" class="is-badge icon"><path d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z" fill="#1da1f2"></path></svg>';
35 + };
36 +
37 + const waterfall = (container) => {
38 + function getMargin(side, element) {
39 + const styles = window.getComputedStyle(element);
40 + return parseFloat(styles[`margin${side}`]) || 0;
12 41 }
13 42
14 - function c(a) {
15 - return a + "px"
43 + function toPx(value) {
44 + return `${value}px`;
16 45 }
17 46
18 - function d(a) {
19 - return parseFloat(a.style.top)
47 + function getTop(element) {
48 + return parseFloat(element.style.top);
20 49 }
21 50
22 - function e(a) {
23 - return parseFloat(a.style.left)
51 + function getLeft(element) {
52 + return parseFloat(element.style.left);
24 53 }
25 54
26 - function f(a) {
27 - return a.clientWidth
55 + function getWidth(element) {
56 + return element.clientWidth;
28 57 }
29 58
30 - function g(a) {
31 - return a.clientHeight
59 + function getHeight(element) {
60 + return element.clientHeight;
32 61 }
33 62
34 - function h(a) {
35 - return d(a) + g(a) + b("Bottom", a)
63 + function getBottom(element) {
64 + return getTop(element) + getHeight(element) + getMargin('Bottom', element);
36 65 }
37 66
38 - function i(a) {
39 - return e(a) + f(a) + b("Right", a)
67 + function getRight(element) {
68 + return getLeft(element) + getWidth(element) + getMargin('Right', element);
40 69 }
41 70
42 - function j(a) {
43 - a = a.sort(function (a, b) {
44 - return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a)
45 - })
71 + function sortColumns(elements) {
72 + elements.sort((left, right) => {
73 + return getBottom(left) === getBottom(right)
74 + ? getLeft(right) - getLeft(left)
75 + : getBottom(right) - getBottom(left);
76 + });
46 77 }
47 78
48 - function k(b) {
49 - f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a))
79 + if (typeof container === 'string') {
80 + container = document.querySelector(container);
50 81 }
51 - "string" == typeof a && (a = document.querySelector(a));
52 - var l = [].map.call(a.children, function (a) {
53 - return a.style.position = "absolute", a
82 +
83 + if (!container) return;
84 +
85 + const items = Array.from(container.children).map(item => {
86 + item.style.position = 'absolute';
87 + return item;
54 88 });
55 - a.style.position = "relative";
56 - var m = [];
57 - l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0]));
58 - for (var n = 1; n < l.length; n++) {
59 - var o = l[n - 1],
60 - p = l[n],
61 - q = i(o) + f(p) <= f(a);
62 - if (!q) break;
63 - p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p)
89 +
90 + container.style.position = 'relative';
91 +
92 + const columns = [];
93 + if (items.length) {
94 + items[0].style.top = '0px';
95 + items[0].style.left = toPx(getMargin('Left', items[0]));
96 + columns.push(items[0]);
64 97 }
65 - for (; n < l.length; n++) {
66 - j(m);
67 - var p = l[n],
68 - r = m.pop();
69 - p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p)
98 +
99 + let index = 1;
100 + for (; index < items.length; index += 1) {
101 + const previous = items[index - 1];
102 + const current = items[index];
103 + const fits = getRight(previous) + getWidth(current) <= getWidth(container);
104 +
105 + if (!fits) break;
106 +
107 + current.style.top = previous.style.top;
108 + current.style.left = toPx(getRight(previous) + getMargin('Left', current));
109 + columns.push(current);
70 110 }
71 - j(m);
72 - var s = m[0];
73 - a.style.height = c(h(s) + b("Bottom", s));
74 - var t = f(a);
75 - window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k
76 - };
77 111
78 - const fetchAndRenderTalks = () => {
79 - const url = 'https://says.liushen.fun/api';
80 - const cacheKey = 'talksCache';
81 - const cacheTimeKey = 'talksCacheTime';
82 - const cacheDuration = 30 * 60 * 1000; // 半个小时 (30 分钟)
83 -
84 - const cachedData = localStorage.getItem(cacheKey);
85 - const cachedTime = localStorage.getItem(cacheTimeKey);
86 - const currentTime = new Date().getTime();
87 -
88 - // 判断缓存是否有效
89 - if (cachedData && cachedTime && (currentTime - cachedTime < cacheDuration)) {
90 - const data = JSON.parse(cachedData);
91 - renderTalks(data); // 使用缓存渲染数据
92 - } else {
93 - if (talkContainer) {
94 - talkContainer.innerHTML = '';
95 - fetch(url + "/messages/page", {
96 - method: 'POST',
97 - headers: {
98 - 'Content-Type': 'application/json',
99 - },
100 - body: JSON.stringify({
101 - page: 1,
102 - pageSize: 30
103 - })
104 - })
105 - .then(res => res.json())
106 - .then(data => {
107 - if (data.code === 1 && data.data && Array.isArray(data.data.items)) {
108 - // 缓存数据
109 - localStorage.setItem(cacheKey, JSON.stringify(data.data.items));
110 - localStorage.setItem(cacheTimeKey, currentTime.toString());
111 - renderTalks(data.data.items); // 渲染数据
112 - }
113 - })
114 - .catch(error => {
115 - console.error('Error fetching data:', error);
116 - });
117 - }
112 + for (; index < items.length; index += 1) {
113 + sortColumns(columns);
114 + const current = items[index];
115 + const column = columns.pop();
116 +
117 + current.style.top = toPx(getBottom(column) + getMargin('Top', current));
118 + current.style.left = toPx(getLeft(column));
119 + columns.push(current);
118 120 }
119 -
120 - // 渲染函数
121 - function renderTalks(list) {
122 - // 确保 data 是一个数组
123 - if (Array.isArray(list)) {
124 - let items = list.map(item => formatTalk(item, url));
125 - items.forEach(item => talkContainer.appendChild(generateTalkElement(item)));
126 - waterfall('#talk');
127 - } else {
128 - console.error('Data is not an array:', list);
121 +
122 + sortColumns(columns);
123 + const tallestColumn = columns[0];
124 + container.style.height = tallestColumn ? toPx(getBottom(tallestColumn) + getMargin('Bottom', tallestColumn)) : '0px';
125 +
126 + const currentWidth = getWidth(container);
127 + shuoshuoState.resizeHandler = () => {
128 + const currentContainer = document.querySelector('#talk');
129 + if (!currentContainer || !document.body.contains(currentContainer)) {
130 + cleanupShuoshuo();
131 + return;
132 + }
133 +
134 + if (getWidth(currentContainer) !== currentWidth) {
135 + waterfall(currentContainer);
129 136 }
137 + };
138 +
139 + window.addEventListener('resize', shuoshuoState.resizeHandler);
140 + };
141 +
142 + const parseMaybeJson = (value) => {
143 + return value && typeof value === 'object' ? value : null;
144 + };
145 +
146 + const getEchoExtension = (item) => {
147 + return parseMaybeJson(item?.extension);
148 + };
149 +
150 + const getEchoExtensionType = (item) => {
151 + return getEchoExtension(item)?.type || '';
152 + };
153 +
154 + const getEchoExtensionPayload = (item) => {
155 + const extension = getEchoExtension(item);
156 + return extension?.payload || null;
157 + };
158 +
159 + const getEchoImages = (item) => {
160 + if (!Array.isArray(item?.echo_files)) return [];
161 +
162 + return item.echo_files
163 + .map(entry => entry?.file || entry)
164 + .filter(file => {
165 + const category = String(file?.category || '').toLowerCase();
166 + const contentType = String(file?.content_type || '').toLowerCase();
167 + return category === 'image' || contentType.startsWith('image/');
168 + })
169 + .map(file => {
170 + let url = file?.url;
171 + if (!url) return null;
172 +
173 + // 如果是相对路径,补全为完整 URL
174 + if (url.startsWith('/')) {
175 + url = 'https://mm.liushen.fun' + url;
176 + } else if (!url.startsWith('http://') && !url.startsWith('https://')) {
177 + url = 'https://mm.liushen.fun/' + url;
178 + }
179 +
180 + return url;
181 + })
182 + .filter(Boolean);
183 + };
184 +
185 + const getEchoTags = (item) => {
186 + if (!Array.isArray(item?.tags) || !item.tags.length) return ['无标签'];
187 + return item.tags.map(tag => tag?.name || tag).filter(Boolean);
188 + };
189 +
190 + const formatTime = (time) => {
191 + // 如果是 Unix 时间戳(秒),转换为毫秒
192 + const timestamp = time < 10000000000 ? time * 1000 : time;
193 + const date = new Date(timestamp);
194 + const pad = value => String(value).padStart(2, '0');
195 + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
196 + };
197 +
198 + const renderTextContent = (content) => {
199 + return (content || '')
200 + .replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="nofollow noopener">@$1</a>')
201 + .replace(/- \[ \]/g, '[]')
202 + .replace(/- \[x\]/gi, '[x]')
203 + .replace(/\n/g, '<br>');
204 + };
205 +
206 + const buildImageHtml = (images) => {
207 + if (!images.length) return '';
208 +
209 + const imageLinks = images.map(url => {
210 + const safeUrl = `${url}?fmt=webp&q=75`;
211 + return `<a href="${safeUrl}" data-fancybox="gallery" class="fancybox"><img src="${safeUrl}" loading="lazy"></a>`;
212 + }).join('');
213 +
214 + return `<div class="zone_imgbox">${imageLinks}</div>`;
215 + };
216 +
217 + const getGithubTitle = (repoUrl) => {
218 + if (!repoUrl) return '';
219 +
220 + const match = repoUrl.match(/^https?:\/\/github\.com\/[^/]+\/([^/?#]+)/i);
221 + if (match) return match[1];
222 +
223 + try {
224 + const parts = new URL(repoUrl).pathname.split('/').filter(Boolean);
225 + return parts.pop() || repoUrl;
226 + } catch (error) {
227 + return repoUrl;
130 228 }
131 229 };
132 -
133 -
134 - const formatTalk = (item, url) => {
135 - let date = formatTime(new Date(item.created_at).toString());
136 - let content = item.content;
137 - const baseUrl = url;
138 - let imgs = Array.isArray(item.images) ? item.images.map(img => {
139 - const imageUrl = img.image_url;
140 - // 如果是相对地址(不以 http 或 https 开头),则拼接 baseUrl
141 - return /^https?:\/\//.test(imageUrl) ? imageUrl : `${baseUrl}${imageUrl}`;
142 - }) : [];
143 - let text = content;
144 - content = text.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2" target="_blank" rel="nofollow noopener noreferrer">@$1</a>`)
145 - .replace(/- \[ \]/g, '⚪')
146 - .replace(/- \[x\]/g, '⚫');
147 - // 保留换行符,转换 \n 为 <br>
148 - content = content.replace(/\n/g, '<br>');
149 - // 将content用一个类包裹,便于后续处理
150 - content = `<div class="talk_content_text">${content}</div>`;
151 - if (imgs.length > 0) {
152 - const imgDiv = document.createElement('div');
153 - imgDiv.className = 'zone_imgbox';
154 - imgs.forEach(e => {
155 - const imgLink = document.createElement('a');
156 - imgLink.href = e;
157 - imgLink.setAttribute('data-fancybox', 'gallery');
158 - imgLink.className = 'fancybox';
159 - imgLink.setAttribute('data-thumb', e);
160 - const imgTag = document.createElement('img');
161 - imgTag.src = e;
162 - imgLink.appendChild(imgTag);
163 - imgDiv.appendChild(imgLink);
164 - });
165 - content += imgDiv.outerHTML;
230 +
231 + const buildExternalHtml = (type, payload) => {
232 + if (!payload) return '';
233 +
234 + let siteUrl = '';
235 + let title = '';
236 + let background = 'https://p.liiiu.cn/i/2024/07/27/66a4632bbf06e.webp';
237 +
238 + if (type === 'WEBSITE') {
239 + siteUrl = payload.site || payload.url || '';
240 + title = payload.title || siteUrl;
166 241 }
167 242
168 - // 外链分享功能
169 - if (item.extension_type ==="WEBSITE") {
170 - let extension = item.extension ? JSON.parse(item.extension) : {};
171 - const externalUrl = extension.site;
172 - const externalTitle = extension.title;
173 - const externalFavicon = "https://p.liiiu.cn/i/2025/03/13/67d2fc82d329c.webp" // item.externalFavicon;
243 + if (type === 'GITHUBPROJ') {
244 + siteUrl = payload.repoUrl || payload.url || '';
245 + title = payload.title || getGithubTitle(siteUrl);
246 + background = 'https://p.liiiu.cn/i/2024/07/27/66a461a3098aa.webp';
247 + }
248 +
249 + if (!siteUrl) return '';
174 250
175 - const externalContainer = `
251 + return `
176 252 <div class="shuoshuo-external-link">
177 - <a class="external-link" href="${externalUrl}" target="_blank" rel="nofollow noopener noreferrer">
178 - <div class="external-link-left" style="background-image: url(${externalFavicon})"></div>
253 + <a class="external-link" href="${siteUrl}" target="_blank" rel="nofollow noopener">
254 + <div class="external-link-left" style="background-image:url(${background})"></div>
179 255 <div class="external-link-right">
180 - <div class="external-link-title">${externalTitle}</div>
256 + <div class="external-link-title">${title}</div>
181 257 <div>点击跳转<i class="fa-solid fa-angle-right"></i></div>
182 258 </div>
183 259 </a>
184 - </div>`;
260 + </div>
261 + `;
262 + };
263 +
264 + const getMusicInfo = (payload) => {
265 + const link = payload?.url;
266 + if (!link) return null;
267 +
268 + let server = '';
269 + if (link.includes('music.163.com')) server = 'netease';
270 + if (link.includes('y.qq.com')) server = 'tencent';
271 +
272 + const idMatch = link.match(/id=(\d+)/);
273 + if (!server || !idMatch) return null;
274 +
275 + return { server, id: idMatch[1] };
276 + };
185 277
186 - content += externalContainer;
278 + const buildMusicHtml = (payload) => {
279 + const music = getMusicInfo(payload);
280 + if (!music) return '';
281 +
282 + return `<meting-js server="${music.server}" type="song" id="${music.id}" api="https://met.liiiu.cn/meting/api?server=:server&type=:type&id=:id&auth=:auth&r=:r"></meting-js>`;
283 + };
284 +
285 + const getYoutubeVideoId = (value) => {
286 + if (!value) return '';
287 + if (/^[a-zA-Z0-9_-]{11}$/.test(value)) return value;
288 +
289 + try {
290 + const url = new URL(value);
291 + if (url.hostname.includes('youtu.be')) return url.pathname.replace('/', '');
292 + if (url.hostname.includes('youtube.com')) {
293 + return url.searchParams.get('v') || url.pathname.split('/').filter(Boolean).pop() || '';
294 + }
295 + } catch (error) {
296 + return '';
187 297 }
188 298
189 - // const ext = JSON.parse(item.ext || '{}');
190 -
191 - else if (item.extension_type === "MUSIC") {
192 - const musicUrl = item.extension;
193 - if (musicUrl && musicUrl.includes('music.163.com')) {
194 - const match = musicUrl.match(/id=(\d+)/);
195 - const musicServer = "netease";
196 - const musicType = "song";
197 - const musicId = match ? match[1] : '';
198 -
199 - content += `
200 - <meting-js server="${musicServer}" type="${musicType}" id="${musicId}" api="https://met.liiiu.cn/meting/api?server=:server&type=:type&id=:id&r=:r"></meting-js>
201 - `;
202 - } else {
203 - // 懒得适配,自己搞
299 + return '';
300 + };
301 +
302 + const buildVideoHtml = (payload) => {
303 + const rawValue = payload?.videoId || payload?.url || '';
304 +
305 + if (!rawValue) return '';
306 +
307 + let embedUrl = '';
308 +
309 + if (/^BV[0-9A-Za-z]+$/i.test(rawValue)) {
310 + embedUrl = `https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=${rawValue}&as_wide=1&high_quality=1&danmaku=0`;
311 + } else {
312 + const youtubeId = getYoutubeVideoId(rawValue);
313 + if (youtubeId) {
314 + embedUrl = `https://www.youtube.com/embed/${youtubeId}`;
204 315 }
205 316 }
206 317
207 - // if (ext.doubanMovie && ext.doubanMovie.id) {
208 - // const doubanMovie = ext.doubanMovie;
209 - // const doubanMovieUrl = doubanMovie.url;
210 - // const doubanTitle = doubanMovie.title;
211 - // // const doubanDesc = doubanMovie.desc || '暂无描述';
212 - // const doubanImage = doubanMovie.image;
213 - // const doubanDirector = doubanMovie.director || '未知导演';
214 - // const doubanRating = doubanMovie.rating || '暂无评分';
215 - // // const doubanReleaseDate = doubanMovie.releaseDate || '未知上映时间';
216 - // // const doubanActors = doubanMovie.actors || '未知演员';
217 - // const doubanRuntime = doubanMovie.runtime || '未知时长';
218 -
219 - // content += `
220 - // <a class="douban-card" href="${doubanMovieUrl}" target="_blank" rel="nofollow noopener noreferrer">
221 - // <div class="douban-card-bgimg" style="background-image: url('${doubanImage}');"></div>
222 - // <div class="douban-card-left">
223 - // <div class="douban-card-img" style="background-image: url('${doubanImage}');"></div>
224 - // </div>
225 - // <div class="douban-card-right">
226 - // <div class="douban-card-item"><span>电影名: </span><strong>${doubanTitle}</strong></div>
227 - // <div class="douban-card-item"><span>导演: </span><span>${doubanDirector}</span></div>
228 - // <div class="douban-card-item"><span>评分: </span><span>${doubanRating}</span></div>
229 - // <div class="douban-card-item"><span>时长: </span><span>${doubanRuntime}</span></div>
230 - // </div>
231 - // </a>
232 - // `;
233 - // }
234 -
235 - // if (ext.doubanBook && ext.doubanBook.id) {
236 - // const doubanBook = ext.doubanBook;
237 - // const bookUrl = doubanBook.url;
238 - // const bookTitle = doubanBook.title;
239 - // // const bookDesc = doubanBook.desc;
240 - // const bookImage = doubanBook.image;
241 - // const bookAuthor = doubanBook.author;
242 - // const bookRating = doubanBook.rating;
243 - // const bookPubDate = doubanBook.pubDate;
244 -
245 - // const bookTemplate = `
246 - // <a class="douban-card" href="${bookUrl}" target="_blank" rel="nofollow noopener noreferrer">
247 - // <div class="douban-card-bgimg" style="background-image: url('${bookImage}');"></div>
248 - // <div class="douban-card-left">
249 - // <div class="douban-card-img" style="background-image: url('${bookImage}');"></div>
250 - // </div>
251 - // <div class="douban-card-right">
252 - // <div class="douban-card-item">
253 - // <span>书名: </span><strong>${bookTitle}</strong>
254 - // </div>
255 - // <div class="douban-card-item">
256 - // <span>作者: </span><span>${bookAuthor}</span>
257 - // </div>
258 - // <div class="douban-card-item">
259 - // <span>出版年份: </span><span>${bookPubDate}</span>
260 - // </div>
261 - // <div class="douban-card-item">
262 - // <span>评分: </span><span>${bookRating}</span>
263 - // </div>
264 - // </div>
265 - // </a>
266 - // `;
267 -
268 - // content += bookTemplate;
269 - // }
270 -
271 - else if (item.extension_type === "VIDEO") {
272 - // const videoType = ext.video.type;
273 - const BVId = item.extension;
274 - // Bilibili 视频模板
275 - // const NewVideoUrl = videoUrl.replace("player.bilibili.com/player.html", "www.bilibili.com/blackboard/html5mobileplayer.html")
276 - const biliTemplate = `
277 - <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
278 - <iframe
279 - style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
280 - src="https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=${BVId}&as_wide=1&high_quality=1&danmaku=0"
281 - scrolling="no"
282 - border="0"
283 - frameborder="no"
284 - framespacing="0"
285 - allowfullscreen="true"
286 - loading="lazy"
287 - >
288 - </iframe>
289 - </div>
290 - `;
291 - // 将模板插入到 DOM 中
292 - content += biliTemplate;
293 -
294 -
295 - // else if (videoType === 'youtube') {
296 - // // YouTube 视频模板
297 - // // 从形如https://youtu.be/2V6lvCUPT8I?si=DVhUas6l6qlAr6Ru的链接中提取视频 ID2V6lvCUPT8I
298 - // const youtubeTemplate = `
299 - // <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
300 - // <iframe width="100%"
301 - // style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
302 - // src="${videoUrl}"
303 - // title="YouTube video player"
304 - // frameborder="0"
305 - // allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
306 - // referrerpolicy="strict-origin-when-cross-origin"
307 - // allowfullscreen>
308 - // </iframe>
309 - // </div>
310 - // `;
311 - // // 将模板插入到 DOM 中
312 - // content += youtubeTemplate;
313 - // }
318 + if (!embedUrl) return '';
319 +
320 + return `
321 + <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
322 + <iframe
323 + style="position:absolute;width:100%;height:100%;left:0;top:0;border-radius:12px;"
324 + src="${embedUrl}"
325 + frameborder="0"
326 + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
327 + allowfullscreen
328 + loading="lazy">
329 + </iframe>
330 + </div>
331 + `;
332 + };
333 +
334 + const normalizeTalk = (item) => {
335 + const extensionType = getEchoExtensionType(item);
336 + const extensionPayload = getEchoExtensionPayload(item);
337 + const textContent = item?.content || '';
338 + const images = getEchoImages(item);
339 +
340 + let content = `<div class="talk_content_text">${renderTextContent(textContent)}</div>`;
341 + content += buildImageHtml(images);
342 +
343 + if (extensionType === 'WEBSITE' || extensionType === 'GITHUBPROJ') {
344 + content += buildExternalHtml(extensionType, extensionPayload);
345 + }
346 +
347 + if (extensionType === 'MUSIC') {
348 + content += buildMusicHtml(extensionPayload);
349 + }
350 +
351 + if (extensionType === 'VIDEO') {
352 + content += buildVideoHtml(extensionPayload);
314 353 }
315 354
316 355 return {
317 - content: content,
318 - user: item.username || '匿名',
319 - avatar: 'https://p.liiiu.cn/i/2024/03/29/66061417537af.png',
320 - date: date,
321 - location: item.location || '陕西西安',
322 - tags: item.tags ? item.tags.split(',').filter(tag => tag.trim() !== '') : ['日常点滴'],
323 - text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]' + `${imgs.length ? '[图片]' : ''}`)
356 + content,
357 + user: item?.username || '匿名',
358 + avatar: TALK_AVATAR,
359 + date: formatTime(item?.created_at),
360 + tags: getEchoTags(item),
361 + quoteText: textContent
324 362 };
325 363 };
326 364
@@ -338,13 +376,18 @@ function renderTalks() {
338 376 const info = document.createElement('div');
339 377 info.className = 'info';
340 378
341 - const talkNick = document.createElement('span');
342 - talkNick.className = 'talk_nick';
343 - talkNick.innerHTML = `${item.user} ${generateIconSVG()}`;
379 + const nick = document.createElement('span');
380 + nick.className = 'talk_nick';
381 + nick.innerHTML = `${item.user} ${generateIconSVG()}`;
344 382
345 - const talkDate = document.createElement('span');
346 - talkDate.className = 'talk_date';
347 - talkDate.textContent = item.date;
383 + const date = document.createElement('span');
384 + date.className = 'talk_date';
385 + date.textContent = item.date;
386 +
387 + info.appendChild(nick);
388 + info.appendChild(date);
389 + talkMeta.appendChild(avatar);
390 + talkMeta.appendChild(info);
348 391
349 392 const talkContent = document.createElement('div');
350 393 talkContent.className = 'talk_content';
@@ -353,78 +396,111 @@ function renderTalks() {
353 396 const talkBottom = document.createElement('div');
354 397 talkBottom.className = 'talk_bottom';
355 398
356 - const TagContainer = document.createElement('div');
357 -
358 - const talkTag = document.createElement('span');
359 - talkTag.className = 'talk_tag';
360 - talkTag.textContent = `🏷️${item.tags}`;
361 -
362 - const locationTag = document.createElement('span');
363 - locationTag.className = 'location_tag';
364 - locationTag.textContent = `🌍${item.location}`;
365 -
366 - TagContainer.appendChild(talkTag);
367 - TagContainer.appendChild(locationTag);
399 + const tags = document.createElement('div');
400 + const tag = document.createElement('span');
401 + tag.className = 'talk_tag';
402 + tag.textContent = `# ${item.tags.join(' / ')}`;
403 + tags.appendChild(tag);
368 404
369 405 const commentLink = document.createElement('a');
370 406 commentLink.href = 'javascript:;';
371 - commentLink.onclick = () => goComment(item.text);
372 - const commentIcon = document.createElement('span');
373 - commentIcon.className = 'icon';
374 - const commentIconInner = document.createElement('i');
375 - commentIconInner.className = 'fa-solid fa-message fa-fw';
376 - commentIcon.appendChild(commentIconInner);
377 - commentLink.appendChild(commentIcon);
407 + commentLink.addEventListener('click', () => goComment(item.quoteText));
408 +
409 + const icon = document.createElement('span');
410 + icon.className = 'icon';
411 + icon.innerHTML = '<i class="fa-solid fa-message fa-fw"></i>';
412 + commentLink.appendChild(icon);
413 +
414 + talkBottom.appendChild(tags);
415 + talkBottom.appendChild(commentLink);
378 416
379 - talkMeta.appendChild(avatar);
380 - info.appendChild(talkNick);
381 - info.appendChild(talkDate);
382 - talkMeta.appendChild(info);
383 417 talkItem.appendChild(talkMeta);
384 418 talkItem.appendChild(talkContent);
385 - talkBottom.appendChild(TagContainer);
386 - talkBottom.appendChild(commentLink);
387 419 talkItem.appendChild(talkBottom);
388 420
389 421 return talkItem;
390 422 };
391 423
392 - const goComment = (e) => {
393 - const match = e.match(/<div class="talk_content_text">([\s\S]*?)<\/div>/);
394 - const textContent = match ? match[1] : "";
395 - const n = document.querySelector(".atk-textarea");
396 - n.value = `> ${textContent}\n\n`;
397 - n.focus();
398 - btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
399 - // const n = document.querySelector(".atk-textarea");
400 - // n.value = `> ${e}\n\n`;
401 - // n.focus();
402 - // btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
424 + const goComment = (text) => {
425 + const textarea = document.querySelector('.atk-textarea');
426 + if (!textarea) return;
427 +
428 + textarea.value = `> ${text || ''}\n\n`;
429 + textarea.focus();
430 +
431 + if (window.btf?.snackbarShow) {
432 + btf.snackbarShow('已为您引用该说说,删除空格效果更佳');
433 + }
403 434 };
404 435
405 - const formatTime = (time) => {
406 - const d = new Date(time);
407 - const ls = [
408 - d.getFullYear(),
409 - d.getMonth() + 1,
410 - d.getDate(),
411 - d.getHours(),
412 - d.getMinutes(),
413 - d.getSeconds(),
414 - ];
415 - const r = ls.map((a) => (a.toString().length === 1 ? '0' + a : a));
416 - return `${r[0]}-${r[1]}-${r[2]} ${r[3]}:${r[4]}`;
436 + const afterRender = () => {
437 + waterfall('#talk');
438 +
439 + if (window.btf?.loadLightbox) {
440 + btf.loadLightbox(document.querySelectorAll('#talk img:not(.no-lightbox)'));
441 + }
442 +
443 + if (window.lazyLoadInstance?.update) {
444 + lazyLoadInstance.update();
445 + }
417 446 };
418 447
419 - fetchAndRenderTalks();
448 + const renderTalksList = (list) => {
449 + list.map(normalizeTalk).forEach(item => talkContainer.appendChild(generateTalkElement(item)));
450 + afterRender();
451 +
452 + const media = talkContainer.querySelectorAll('img, iframe, meting-js');
453 + media.forEach(element => {
454 + element.addEventListener('load', afterRender, { once: true });
455 + });
456 +
457 + shuoshuoState.afterRenderTimer = window.setTimeout(afterRender, 300);
458 + };
459 +
460 + const fetchTalks = () => {
461 + const cachedData = localStorage.getItem(TALK_CACHE_KEY);
462 + const cachedTime = Number(localStorage.getItem(TALK_CACHE_TIME_KEY));
463 + const now = Date.now();
464 +
465 + if (cachedData && cachedTime && now - cachedTime < TALK_CACHE_DURATION) {
466 + renderTalksList(JSON.parse(cachedData));
467 + return;
468 + }
469 +
470 + fetch(TALK_API_URL, {
471 + method: 'POST',
472 + headers: { 'Content-Type': 'application/json' },
473 + body: JSON.stringify({ page: 1, pageSize: 30, search: '' })
474 + })
475 + .then(response => response.json())
476 + .then(data => {
477 + if (data?.code !== 1 || !Array.isArray(data?.data?.items)) {
478 + console.warn('Unexpected API response format:', data);
479 + renderTalksList([]);
480 + return;
481 + }
482 +
483 + localStorage.setItem(TALK_CACHE_KEY, JSON.stringify(data.data.items));
484 + localStorage.setItem(TALK_CACHE_TIME_KEY, now.toString());
485 + renderTalksList(data.data.items);
486 + })
487 + .catch(error => console.error('Error fetching data:', error));
488 + };
489 +
490 + fetchTalks();
420 491 }
421 492
422 - renderTalks();
493 + function initShuoshuoPage() {
494 + renderTalks();
495 + }
496 +
497 + window.initShuoshuoPage = initShuoshuoPage;
498 +
499 + if (!shuoshuoState.listenersBound) {
500 + document.addEventListener('pjax:send', cleanupShuoshuo);
501 + document.addEventListener('pjax:complete', initShuoshuoPage);
502 + shuoshuoState.listenersBound = true;
503 + }
423 504
424 - // function whenDOMReady() {
425 - // const talkContainer = document.querySelector('#talk');
426 - // talkContainer.innerHTML = '';
427 - // fetchAndRenderTalks();
428 - // }
429 - // whenDOMReady();
430 - // document.addEventListener("pjax:complete", whenDOMReady);
505 + initShuoshuoPage();
506 + })();

LiuShen's Avatar LiuShen bu gisti düzenledi 11 months ago. Düzenlemeye git

1 file changed, 8 insertions

twikoo-comments.js(dosya oluşturuldu)

@@ -0,0 +1,8 @@
1 + const goComment = (e) => {
2 + const n = document.querySelector(".el-textarea__inner");
3 + n.value = `> ${e}\n\n`;
4 + n.focus();
5 + btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
6 + };
7 +
8 + // 如果是twikoo评论区,请自行替换goComment函数,注意,如果评论区开了懒加载可能导致无法找到元素。

LiuShen's Avatar LiuShen bu gisti düzenledi 11 months ago. Düzenlemeye git

1 file changed, 3 insertions, 3 deletions

ech0-shuoshuo.js

@@ -76,7 +76,7 @@ function renderTalks() {
76 76 };
77 77
78 78 const fetchAndRenderTalks = () => {
79 - const url = 'https://says.liushen.fun/api/messages/page';
79 + const url = 'https://says.liushen.fun/api';
80 80 const cacheKey = 'talksCache';
81 81 const cacheTimeKey = 'talksCacheTime';
82 82 const cacheDuration = 30 * 60 * 1000; // 半个小时 (30 分钟)
@@ -92,7 +92,7 @@ function renderTalks() {
92 92 } else {
93 93 if (talkContainer) {
94 94 talkContainer.innerHTML = '';
95 - fetch(url, {
95 + fetch(url + "/messages/page", {
96 96 method: 'POST',
97 97 headers: {
98 98 'Content-Type': 'application/json',
@@ -134,7 +134,7 @@ function renderTalks() {
134 134 const formatTalk = (item, url) => {
135 135 let date = formatTime(new Date(item.created_at).toString());
136 136 let content = item.content;
137 - const baseUrl = new URL(url).origin;
137 + const baseUrl = url;
138 138 let imgs = Array.isArray(item.images) ? item.images.map(img => {
139 139 const imageUrl = img.image_url;
140 140 // 如果是相对地址(不以 http 或 https 开头),则拼接 baseUrl

LiuShen's Avatar LiuShen bu gisti düzenledi 11 months ago. Düzenlemeye git

1 file changed, 8 insertions, 3 deletions

ech0-shuoshuo.js

@@ -121,7 +121,7 @@ function renderTalks() {
121 121 function renderTalks(list) {
122 122 // 确保 data 是一个数组
123 123 if (Array.isArray(list)) {
124 - let items = list.map(item => formatTalk(item));
124 + let items = list.map(item => formatTalk(item, url));
125 125 items.forEach(item => talkContainer.appendChild(generateTalkElement(item)));
126 126 waterfall('#talk');
127 127 } else {
@@ -131,10 +131,15 @@ function renderTalks() {
131 131 };
132 132
133 133
134 - const formatTalk = (item) => {
134 + const formatTalk = (item, url) => {
135 135 let date = formatTime(new Date(item.created_at).toString());
136 136 let content = item.content;
137 - let imgs = Array.isArray(item.images) ? item.images.map(img => img.image_url) : [];
137 + const baseUrl = new URL(url).origin;
138 + let imgs = Array.isArray(item.images) ? item.images.map(img => {
139 + const imageUrl = img.image_url;
140 + // 如果是相对地址(不以 http 或 https 开头),则拼接 baseUrl
141 + return /^https?:\/\//.test(imageUrl) ? imageUrl : `${baseUrl}${imageUrl}`;
142 + }) : [];
138 143 let text = content;
139 144 content = text.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2" target="_blank" rel="nofollow noopener noreferrer">@$1</a>`)
140 145 .replace(/- \[ \]/g, '⚪')

LiuShen's Avatar LiuShen bu gisti düzenledi 11 months ago. Düzenlemeye git

1 file changed, 425 insertions

ech0-shuoshuo.js(dosya oluşturuldu)

@@ -0,0 +1,425 @@
1 + function renderTalks() {
2 + const talkContainer = document.querySelector('#talk');
3 + if (!talkContainer) return;
4 + talkContainer.innerHTML = '';
5 + const generateIconSVG = () => {
6 + return `<svg viewBox="0 0 512 512"xmlns="http://www.w3.org/2000/svg"class="is-badge icon"><path d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z"fill="#1da1f2"></path></svg>`;
7 + }
8 + const waterfall = (a) => {
9 + function b(a, b) {
10 + var c = window.getComputedStyle(b);
11 + return parseFloat(c["margin" + a]) || 0
12 + }
13 +
14 + function c(a) {
15 + return a + "px"
16 + }
17 +
18 + function d(a) {
19 + return parseFloat(a.style.top)
20 + }
21 +
22 + function e(a) {
23 + return parseFloat(a.style.left)
24 + }
25 +
26 + function f(a) {
27 + return a.clientWidth
28 + }
29 +
30 + function g(a) {
31 + return a.clientHeight
32 + }
33 +
34 + function h(a) {
35 + return d(a) + g(a) + b("Bottom", a)
36 + }
37 +
38 + function i(a) {
39 + return e(a) + f(a) + b("Right", a)
40 + }
41 +
42 + function j(a) {
43 + a = a.sort(function (a, b) {
44 + return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a)
45 + })
46 + }
47 +
48 + function k(b) {
49 + f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a))
50 + }
51 + "string" == typeof a && (a = document.querySelector(a));
52 + var l = [].map.call(a.children, function (a) {
53 + return a.style.position = "absolute", a
54 + });
55 + a.style.position = "relative";
56 + var m = [];
57 + l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0]));
58 + for (var n = 1; n < l.length; n++) {
59 + var o = l[n - 1],
60 + p = l[n],
61 + q = i(o) + f(p) <= f(a);
62 + if (!q) break;
63 + p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p)
64 + }
65 + for (; n < l.length; n++) {
66 + j(m);
67 + var p = l[n],
68 + r = m.pop();
69 + p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p)
70 + }
71 + j(m);
72 + var s = m[0];
73 + a.style.height = c(h(s) + b("Bottom", s));
74 + var t = f(a);
75 + window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k
76 + };
77 +
78 + const fetchAndRenderTalks = () => {
79 + const url = 'https://says.liushen.fun/api/messages/page';
80 + const cacheKey = 'talksCache';
81 + const cacheTimeKey = 'talksCacheTime';
82 + const cacheDuration = 30 * 60 * 1000; // 半个小时 (30 分钟)
83 +
84 + const cachedData = localStorage.getItem(cacheKey);
85 + const cachedTime = localStorage.getItem(cacheTimeKey);
86 + const currentTime = new Date().getTime();
87 +
88 + // 判断缓存是否有效
89 + if (cachedData && cachedTime && (currentTime - cachedTime < cacheDuration)) {
90 + const data = JSON.parse(cachedData);
91 + renderTalks(data); // 使用缓存渲染数据
92 + } else {
93 + if (talkContainer) {
94 + talkContainer.innerHTML = '';
95 + fetch(url, {
96 + method: 'POST',
97 + headers: {
98 + 'Content-Type': 'application/json',
99 + },
100 + body: JSON.stringify({
101 + page: 1,
102 + pageSize: 30
103 + })
104 + })
105 + .then(res => res.json())
106 + .then(data => {
107 + if (data.code === 1 && data.data && Array.isArray(data.data.items)) {
108 + // 缓存数据
109 + localStorage.setItem(cacheKey, JSON.stringify(data.data.items));
110 + localStorage.setItem(cacheTimeKey, currentTime.toString());
111 + renderTalks(data.data.items); // 渲染数据
112 + }
113 + })
114 + .catch(error => {
115 + console.error('Error fetching data:', error);
116 + });
117 + }
118 + }
119 +
120 + // 渲染函数
121 + function renderTalks(list) {
122 + // 确保 data 是一个数组
123 + if (Array.isArray(list)) {
124 + let items = list.map(item => formatTalk(item));
125 + items.forEach(item => talkContainer.appendChild(generateTalkElement(item)));
126 + waterfall('#talk');
127 + } else {
128 + console.error('Data is not an array:', list);
129 + }
130 + }
131 + };
132 +
133 +
134 + const formatTalk = (item) => {
135 + let date = formatTime(new Date(item.created_at).toString());
136 + let content = item.content;
137 + let imgs = Array.isArray(item.images) ? item.images.map(img => img.image_url) : [];
138 + let text = content;
139 + content = text.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2" target="_blank" rel="nofollow noopener noreferrer">@$1</a>`)
140 + .replace(/- \[ \]/g, '⚪')
141 + .replace(/- \[x\]/g, '⚫');
142 + // 保留换行符,转换 \n 为 <br>
143 + content = content.replace(/\n/g, '<br>');
144 + // 将content用一个类包裹,便于后续处理
145 + content = `<div class="talk_content_text">${content}</div>`;
146 + if (imgs.length > 0) {
147 + const imgDiv = document.createElement('div');
148 + imgDiv.className = 'zone_imgbox';
149 + imgs.forEach(e => {
150 + const imgLink = document.createElement('a');
151 + imgLink.href = e;
152 + imgLink.setAttribute('data-fancybox', 'gallery');
153 + imgLink.className = 'fancybox';
154 + imgLink.setAttribute('data-thumb', e);
155 + const imgTag = document.createElement('img');
156 + imgTag.src = e;
157 + imgLink.appendChild(imgTag);
158 + imgDiv.appendChild(imgLink);
159 + });
160 + content += imgDiv.outerHTML;
161 + }
162 +
163 + // 外链分享功能
164 + if (item.extension_type ==="WEBSITE") {
165 + let extension = item.extension ? JSON.parse(item.extension) : {};
166 + const externalUrl = extension.site;
167 + const externalTitle = extension.title;
168 + const externalFavicon = "https://p.liiiu.cn/i/2025/03/13/67d2fc82d329c.webp" // item.externalFavicon;
169 +
170 + const externalContainer = `
171 + <div class="shuoshuo-external-link">
172 + <a class="external-link" href="${externalUrl}" target="_blank" rel="nofollow noopener noreferrer">
173 + <div class="external-link-left" style="background-image: url(${externalFavicon})"></div>
174 + <div class="external-link-right">
175 + <div class="external-link-title">${externalTitle}</div>
176 + <div>点击跳转<i class="fa-solid fa-angle-right"></i></div>
177 + </div>
178 + </a>
179 + </div>`;
180 +
181 + content += externalContainer;
182 + }
183 +
184 + // const ext = JSON.parse(item.ext || '{}');
185 +
186 + else if (item.extension_type === "MUSIC") {
187 + const musicUrl = item.extension;
188 + if (musicUrl && musicUrl.includes('music.163.com')) {
189 + const match = musicUrl.match(/id=(\d+)/);
190 + const musicServer = "netease";
191 + const musicType = "song";
192 + const musicId = match ? match[1] : '';
193 +
194 + content += `
195 + <meting-js server="${musicServer}" type="${musicType}" id="${musicId}" api="https://met.liiiu.cn/meting/api?server=:server&type=:type&id=:id&r=:r"></meting-js>
196 + `;
197 + } else {
198 + // 懒得适配,自己搞
199 + }
200 + }
201 +
202 + // if (ext.doubanMovie && ext.doubanMovie.id) {
203 + // const doubanMovie = ext.doubanMovie;
204 + // const doubanMovieUrl = doubanMovie.url;
205 + // const doubanTitle = doubanMovie.title;
206 + // // const doubanDesc = doubanMovie.desc || '暂无描述';
207 + // const doubanImage = doubanMovie.image;
208 + // const doubanDirector = doubanMovie.director || '未知导演';
209 + // const doubanRating = doubanMovie.rating || '暂无评分';
210 + // // const doubanReleaseDate = doubanMovie.releaseDate || '未知上映时间';
211 + // // const doubanActors = doubanMovie.actors || '未知演员';
212 + // const doubanRuntime = doubanMovie.runtime || '未知时长';
213 +
214 + // content += `
215 + // <a class="douban-card" href="${doubanMovieUrl}" target="_blank" rel="nofollow noopener noreferrer">
216 + // <div class="douban-card-bgimg" style="background-image: url('${doubanImage}');"></div>
217 + // <div class="douban-card-left">
218 + // <div class="douban-card-img" style="background-image: url('${doubanImage}');"></div>
219 + // </div>
220 + // <div class="douban-card-right">
221 + // <div class="douban-card-item"><span>电影名: </span><strong>${doubanTitle}</strong></div>
222 + // <div class="douban-card-item"><span>导演: </span><span>${doubanDirector}</span></div>
223 + // <div class="douban-card-item"><span>评分: </span><span>${doubanRating}</span></div>
224 + // <div class="douban-card-item"><span>时长: </span><span>${doubanRuntime}</span></div>
225 + // </div>
226 + // </a>
227 + // `;
228 + // }
229 +
230 + // if (ext.doubanBook && ext.doubanBook.id) {
231 + // const doubanBook = ext.doubanBook;
232 + // const bookUrl = doubanBook.url;
233 + // const bookTitle = doubanBook.title;
234 + // // const bookDesc = doubanBook.desc;
235 + // const bookImage = doubanBook.image;
236 + // const bookAuthor = doubanBook.author;
237 + // const bookRating = doubanBook.rating;
238 + // const bookPubDate = doubanBook.pubDate;
239 +
240 + // const bookTemplate = `
241 + // <a class="douban-card" href="${bookUrl}" target="_blank" rel="nofollow noopener noreferrer">
242 + // <div class="douban-card-bgimg" style="background-image: url('${bookImage}');"></div>
243 + // <div class="douban-card-left">
244 + // <div class="douban-card-img" style="background-image: url('${bookImage}');"></div>
245 + // </div>
246 + // <div class="douban-card-right">
247 + // <div class="douban-card-item">
248 + // <span>书名: </span><strong>${bookTitle}</strong>
249 + // </div>
250 + // <div class="douban-card-item">
251 + // <span>作者: </span><span>${bookAuthor}</span>
252 + // </div>
253 + // <div class="douban-card-item">
254 + // <span>出版年份: </span><span>${bookPubDate}</span>
255 + // </div>
256 + // <div class="douban-card-item">
257 + // <span>评分: </span><span>${bookRating}</span>
258 + // </div>
259 + // </div>
260 + // </a>
261 + // `;
262 +
263 + // content += bookTemplate;
264 + // }
265 +
266 + else if (item.extension_type === "VIDEO") {
267 + // const videoType = ext.video.type;
268 + const BVId = item.extension;
269 + // Bilibili 视频模板
270 + // const NewVideoUrl = videoUrl.replace("player.bilibili.com/player.html", "www.bilibili.com/blackboard/html5mobileplayer.html")
271 + const biliTemplate = `
272 + <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
273 + <iframe
274 + style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
275 + src="https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=${BVId}&as_wide=1&high_quality=1&danmaku=0"
276 + scrolling="no"
277 + border="0"
278 + frameborder="no"
279 + framespacing="0"
280 + allowfullscreen="true"
281 + loading="lazy"
282 + >
283 + </iframe>
284 + </div>
285 + `;
286 + // 将模板插入到 DOM 中
287 + content += biliTemplate;
288 +
289 +
290 + // else if (videoType === 'youtube') {
291 + // // YouTube 视频模板
292 + // // 从形如https://youtu.be/2V6lvCUPT8I?si=DVhUas6l6qlAr6Ru的链接中提取视频 ID2V6lvCUPT8I
293 + // const youtubeTemplate = `
294 + // <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
295 + // <iframe width="100%"
296 + // style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
297 + // src="${videoUrl}"
298 + // title="YouTube video player"
299 + // frameborder="0"
300 + // allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
301 + // referrerpolicy="strict-origin-when-cross-origin"
302 + // allowfullscreen>
303 + // </iframe>
304 + // </div>
305 + // `;
306 + // // 将模板插入到 DOM 中
307 + // content += youtubeTemplate;
308 + // }
309 + }
310 +
311 + return {
312 + content: content,
313 + user: item.username || '匿名',
314 + avatar: 'https://p.liiiu.cn/i/2024/03/29/66061417537af.png',
315 + date: date,
316 + location: item.location || '陕西西安',
317 + tags: item.tags ? item.tags.split(',').filter(tag => tag.trim() !== '') : ['日常点滴'],
318 + text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]' + `${imgs.length ? '[图片]' : ''}`)
319 + };
320 + };
321 +
322 + const generateTalkElement = (item) => {
323 + const talkItem = document.createElement('div');
324 + talkItem.className = 'talk_item';
325 +
326 + const talkMeta = document.createElement('div');
327 + talkMeta.className = 'talk_meta';
328 +
329 + const avatar = document.createElement('img');
330 + avatar.className = 'no-lightbox avatar';
331 + avatar.src = item.avatar;
332 +
333 + const info = document.createElement('div');
334 + info.className = 'info';
335 +
336 + const talkNick = document.createElement('span');
337 + talkNick.className = 'talk_nick';
338 + talkNick.innerHTML = `${item.user} ${generateIconSVG()}`;
339 +
340 + const talkDate = document.createElement('span');
341 + talkDate.className = 'talk_date';
342 + talkDate.textContent = item.date;
343 +
344 + const talkContent = document.createElement('div');
345 + talkContent.className = 'talk_content';
346 + talkContent.innerHTML = item.content;
347 +
348 + const talkBottom = document.createElement('div');
349 + talkBottom.className = 'talk_bottom';
350 +
351 + const TagContainer = document.createElement('div');
352 +
353 + const talkTag = document.createElement('span');
354 + talkTag.className = 'talk_tag';
355 + talkTag.textContent = `🏷️${item.tags}`;
356 +
357 + const locationTag = document.createElement('span');
358 + locationTag.className = 'location_tag';
359 + locationTag.textContent = `🌍${item.location}`;
360 +
361 + TagContainer.appendChild(talkTag);
362 + TagContainer.appendChild(locationTag);
363 +
364 + const commentLink = document.createElement('a');
365 + commentLink.href = 'javascript:;';
366 + commentLink.onclick = () => goComment(item.text);
367 + const commentIcon = document.createElement('span');
368 + commentIcon.className = 'icon';
369 + const commentIconInner = document.createElement('i');
370 + commentIconInner.className = 'fa-solid fa-message fa-fw';
371 + commentIcon.appendChild(commentIconInner);
372 + commentLink.appendChild(commentIcon);
373 +
374 + talkMeta.appendChild(avatar);
375 + info.appendChild(talkNick);
376 + info.appendChild(talkDate);
377 + talkMeta.appendChild(info);
378 + talkItem.appendChild(talkMeta);
379 + talkItem.appendChild(talkContent);
380 + talkBottom.appendChild(TagContainer);
381 + talkBottom.appendChild(commentLink);
382 + talkItem.appendChild(talkBottom);
383 +
384 + return talkItem;
385 + };
386 +
387 + const goComment = (e) => {
388 + const match = e.match(/<div class="talk_content_text">([\s\S]*?)<\/div>/);
389 + const textContent = match ? match[1] : "";
390 + const n = document.querySelector(".atk-textarea");
391 + n.value = `> ${textContent}\n\n`;
392 + n.focus();
393 + btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
394 + // const n = document.querySelector(".atk-textarea");
395 + // n.value = `> ${e}\n\n`;
396 + // n.focus();
397 + // btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
398 + };
399 +
400 + const formatTime = (time) => {
401 + const d = new Date(time);
402 + const ls = [
403 + d.getFullYear(),
404 + d.getMonth() + 1,
405 + d.getDate(),
406 + d.getHours(),
407 + d.getMinutes(),
408 + d.getSeconds(),
409 + ];
410 + const r = ls.map((a) => (a.toString().length === 1 ? '0' + a : a));
411 + return `${r[0]}-${r[1]}-${r[2]} ${r[3]}:${r[4]}`;
412 + };
413 +
414 + fetchAndRenderTalks();
415 + }
416 +
417 + renderTalks();
418 +
419 + // function whenDOMReady() {
420 + // const talkContainer = document.querySelector('#talk');
421 + // talkContainer.innerHTML = '';
422 + // fetchAndRenderTalks();
423 + // }
424 + // whenDOMReady();
425 + // document.addEventListener("pjax:complete", whenDOMReady);
Daha yeni Daha eski