- SignalDesk2小时前
Original Summary
平常手机和电脑都刷L站,但是习惯了手机主页最左侧显示发帖人头像的布局。 虽然电脑端的右边头像第一个也是发帖人但是太小了吗而且容易花眼,所以就开发了一个脚本用来在电脑端也像手机端一样在左侧显示发帖人头像。 另外,L提供了其他主题显示方式类似与手机端,但是主页看不到是否是新话题也看不到有多少回复,而且习惯了默认的风格就不想再去适应这个主题了。 [!note] 以下是油猴脚本的显示效果,同时支持调整显示头像的大小: // ==UserScript== // @name LINUX DO 楼主头像靠左放大 // @namespace local.linuxdo.author-avatar // @version 1.0.0 // @description 将主题列表的楼主头像显示在标题左侧,默认 48px,支持动态加载和油猴菜单调整大小。 // @match https://linux.do/ // @run-at document-idle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @noframes // @license MIT // ==/UserScript== (() => { 'use strict'; const DEFAULT_AVATAR_SIZE = 48; const SIZE_KEY = 'linuxdo-author-avatar-size'; const ROW_SELECTOR = '.topic-list tr.topic-list-item'; const CELL_CLASS = 'ldoa-topic-cell'; const AVATAR_CLASS = 'ldoa-author-avatar'; const ORIGINAL_CLASS = 'ldoa-original-author'; const STYLE_ID = 'ldoa-author-avatar-style'; const rows = new WeakMap(); let framePending = false; function validSize(value) { const size = Number(value); return Number.isInteger(size) && size >= 32 && size <= 80; } const storedSize = typeof GM_getValue === 'function' ? GM_getValue(SIZE_KEY, DEFAULT_AVATAR_SIZE) : DEFAULT_AVATAR_SIZE; let avatarSize = validSize(storedSize) ? Number(storedSize) : DEFAULT_AVATAR_SIZE; function updateStyle() { let style = document.getElementById(STYLE_ID); if (!style) { style = document.createElement('style'); style.id = STYLE_ID; (document.head || document.documentElement).append(style); } style.textContent =
.${AVATAR_CLASS} { display: none !important; } @media (min-width: 768px) { html:not(.mobile-view) .topic-list td.${CELL_CLASS} { position: relative !important; padding-left: ${avatarSize + 24}px !important; height: ${avatarSize + 20}px; box-sizing: border-box; } html:not(.mobile-view) .topic-list .${AVATAR_CLASS} { display: block !important; position: absolute !important; left: 10px !important; top: 50% !important; transform: translateY(-50%) !important; width: ${avatarSize}px !important; height: ${avatarSize}px !important; margin: 0 !important; padding: 0 !important; border-radius: 50%; line-height: 0; z-index: 1; } html:not(.mobile-view) .topic-list .${AVATAR_CLASS} img { display: block !important; width: ${avatarSize}px !important; height: ${avatarSize}px !important; min-width: ${avatarSize}px !important; max-width: none !important; margin: 0 !important; padding: 0 !important; border-radius: 50% !important; object-fit: cover; box-sizing: border-box; } html:not(.mobile-view) .topic-list .${AVATAR_CLASS}:focus-visible { outline: 2px solid var(--tertiary, #0088cc); outline-offset: 3px; } html:not(.mobile-view) .topic-list .posters .${ORIGINAL_CLASS} { display: none !important; } }; } // Only change the size segment of known Discourse avatar URLs. // Unknown image URLs are reused unchanged. function largerAvatarUrl(src) { try { const url = new URL(src, document.baseURI); if (!/^https?:$/.test(url.protocol)) { return src; } const imageSize = [96, 120, 144, 180].find((size) => size >= avatarSize 2); const path = url.pathname; url.pathname = path.replace( /(\/user_avatar\/[^/]+\/[^/]+\/)\d+(?=\/)/, (_match, prefix) =>${prefix}${imageSize}); if (url.pathname === path && path.includes('/letter_avatar_proxy/')) { url.pathname = path.replace(/\/\d+(?=(?:\.png)?$)/,/${imageSize}); } return url.href; } catch { return src; } } function clearRow(row) { const old = rows.get(row); if (old) { old.source.classList.remove(ORIGINAL_CLASS); old.cell.classList.remove(CELL_CLASS); old.avatar.remove(); rows.delete(row); } // Also remove a leftover clone if the page replaced just part of a row. row.querySelectorAll(.${AVATAR_CLASS}).forEach((avatar) => avatar.remove()); } function updateRow(row) { const cell = row.querySelector('td.main-link'); const source = Array.from(row.querySelectorAll('.posters a')).find( (link) => link.querySelector('img') ); const originalImage = source?.querySelector('img'); const src = originalImage?.getAttribute('src'); if (!cell || !source || !src) { clearRow(row); return; } const username = source.getAttribute('data-user-card') || originalImage.getAttribute('alt') || source.getAttribute('title') || ''; const href = source.getAttribute('href'); const signature = JSON.stringify([ src, originalImage.getAttribute('srcset'), href, username, avatarSize, ]); const old = rows.get(row); if (old && old.source === source && old.cell === cell && old.signature === signature && old.avatar.parentElement === cell) { if (!source.classList.contains(ORIGINAL_CLASS)) { source.classList.add(ORIGINAL_CLASS); } if (!cell.classList.contains(CELL_CLASS)) { cell.classList.add(CELL_CLASS); } return; } clearRow(row); // Keep the original nodes in place because Discourse manages their lifecycle. // A separate link on the left and CSS hiding on the right provide the move. const avatar = document.createElement('a'); avatar.className = AVATAR_CLASS; if (href) { avatar.setAttribute('href', href); } else if (source.getAttribute('data-user-card')) { avatar.setAttribute('href',/u/${encodeURIComponent(username)}); } if (source.hasAttribute('data-user-card')) { avatar.setAttribute('data-user-card', source.getAttribute('data-user-card')); } avatar.title = username ?楼主:${username}: '楼主'; avatar.setAttribute('aria-label', avatar.title); const image = originalImage.cloneNode(false); for (const name of ['id', 'style', 'srcset', 'sizes', 'title']) { image.removeAttribute(name); } image.className = 'avatar'; image.width = avatarSize; image.height = avatarSize; image.alt = username; image.decoding = 'async'; const largerSrc = largerAvatarUrl(src); image.addEventListener('error', () => { // Some hosts do not offer a larger size. Fall back to the working original. image.src = src; if (originalImage.hasAttribute('srcset')) { image.setAttribute('srcset', originalImage.getAttribute('srcset')); } }, { once: true }); image.src = largerSrc; avatar.append(image); cell.prepend(- 情报分类:硬件与数码
- 分类依据:内容涉及硬件、数码产品或通信卡
- 信息来源:服务器 / LINUX DO - 最新话题
- 发布时间:2026/10/18 10:08:31
- 暂无回复