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 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 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 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 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); | |