您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为 LinuxDo 设置 快速收藏、点击数可视化、图像缩放、小图显示、自定义徽标、去除模糊、详情展开、页面加宽、发帖时间显示 等功能。
当前为
// ==UserScript== // @name LinuxDo自定义🛠️ // @name:en LinuxDo Custom🛠️ // @name:zh-CN LinuxDo自定义🛠️ // @description 为 LinuxDo 设置 快速收藏、点击数可视化、图像缩放、小图显示、自定义徽标、去除模糊、详情展开、页面加宽、发帖时间显示 等功能。 // @description:en Adds customizable features such as logos, click count visualization, image resize, and quick bookmarking to LinuxDo // @description:zh-CN 为 LinuxDo 设置 快速收藏、点击数可视化、图像缩放、小图显示、自定义徽标、去除模糊、详情展开、页面加宽、发帖时间显示 等功能。 // @version 0.5.8 // @author Yearly // @match https://linux.do/* // @icon data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDggNDAgNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzExMSIgZD0iTTAgOGg0MHYxM0gweiIvPjxwYXRoIGZpbGw9IiNlZWUiIGQ9Ik0wIDIxaDQwdjEzSDB6Ii8+PHBhdGggZmlsbD0iI0ZiMCIgZD0iTTAgMzRoNDB2MTNIMHoiLz48cGF0aCBkPSJtOS4zIDMxLjMtMS40LTQuNi00LjgtMS4xLS43LjdoLjEwMWwzLjAxNSAyLjc3M3YxLjUzMWwtMS43LjMwMS0zLTIuOGgtLjFsLS43LjcgMS41IDQuNyA0LjcgMS4xMzUgMTMuMTE1IDEzLjI1IDMuMjU3LTMuMjQ2TDkuNzUgMzEuNzYyek0zLjgxOCA0NC42NDRsMy4yNTkgMy4yNDUgNC45MDctNC45MjItMy4yNDYtMy4yNTh6bTI0LjgzLTExLjEwOC0yLjA0NC0yLjA4LS42LjYtMi40LTIuNDQ5LS4wMDUtLjAwNWMtNC43ODQtNC44OS05LTQtOS00LjA0M2w0LjU3NiA0LjU5NC0zLjc1MyAzLjc2NCAzLjI0NiAzLjI1NyAzLjc1My0zLjc2NCAxLjUgMS41LS41ODIuNTcxIDIuMDQzIDIuMDgiIGZpbGw9IiMzOEQiLz48L3N2Zz4= // @license GPL-3.0 // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @namespace http://tampermonkey.net/ // @supportURL https://greasyfork.org/scripts/499029 // @homepageURL https://greasyfork.org/scripts/499029 // ==/UserScript== (function() { var settings = {}; const default_main_icon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGFyaWEtbGFiZWw9IkxpbnV4IiB2aWV3Qm94PSIwIDAgNTAwIDUwMCIgZmlsbD0iIzMzMyI+PHJlY3Qgd2lkdGg9IjQ1MCIgaGVpZ2h0PSI0NTAiIHJ4PSIxNSUiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTYxIDE5MGMtNiAxNC00OCA1OC00NCAxMDIgMTYgMTg0IDcyIDYwIDE1NiAxMDYgMCAwIDE1MC04NCAzMC0yMjAtMzQtNDgtNC04Ni0yNi0xMThzLTYwLTM0LTg4LTQgMTIgNzQtMjggMTM0Ii8+PHBhdGggZD0iTTMwOSAyODJzMTgtMzYtMTYtNjJjMzIgMzQgMTIgNjQgMTIgNjRoLTZjLTItNzAtMjAtMzItNDYtMTU2IDMwLTM0LTI4LTY0LTI4LThoLTE4YzItNDgtNDAtMjQtMTYgMTAtMiA3NC00NiAxMDQtNDYgMTU2LTE0LTM2IDEyLTY0IDEyLTY0cy0zNiAzMC0xNCA3NCA2MiAzNCAzNCA1NGM0NCAzMCAxMTIgMTAgMTEwLTU0IDItMTYgNDQtMTAgNDgtNnMtNi04LTI2LThNMTk3IDEyNmMtMTQtNC0xMC0yMi00LTIyczE2IDE0IDQgMjJtMzggMmMtMTAtMTQtMi0yOCA4LTI2czEwIDI2LTggMjYiIGZpbGw9IiNlZWUiLz48ZyBmaWxsPSIjZmMyIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMiI+PHBhdGggZD0ibTE0MyAzMDIgNDIgNjBjMjIgMTQgMTAgNzAtNTAgNDItMzQtMTAtNjItOC02Ni0yNnM4LTIwIDYtMjhjLTgtNDQgMjgtMjIgMzgtNDRzMTAtMzIgMzAtNG0yMjQgMjhjLTgtMTIgMC0zNC0yOC0zMi0xMiAyNC00NiA0OC00OCAwLTIwIDAtNiA0OC0xNCA3MC0xOCA1NCAzNCA1OCA1NiAzMmw1Mi0zNmM0LTYgMTAtMTItMTgtMzRNMTgzIDE0NmMtNi0xMiAyMi0yOCAzMi0yOHMyNCA4IDM4IDEyIDggMTggNCAyMC0yNiAyMC00MiAyMC0yMC0xNi0zMi0yNCIvPjxwYXRoIGQ9Ik0xODMgMTQ0YzE2IDEyIDM0IDIyIDcwLTYiLz48L2c+PHBhdGggZD0iTTIwNSAxMzJjLTQgMCAyLTQgNC0ybTE0IDJjMi0yLTItNC02LTIiLz48L3N2Zz4=" const default_wide_icon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMTAiIGhlaWdodD0iMTEwIiB2aWV3Ym94PSItNSAtNSAzMTAgMTEwIj48cmVjdCB3aWR0aD0iOTkiIGhlaWdodD0iOTkiIHJ4PSIxMDAlIiBmaWxsPSIjRjFGMUYxIi8+PHRleHQgeD0iMjQiIHk9Ijc2IiBmb250LXNpemU9Ijc3IiBmaWxsPSIjRmMzIiBmb250LXdlaWdodD0iYm9sZCI+TDwvdGV4dD48dGV4dCB4PSI3MCIgeT0iNzUiIGZvbnQtc2l6ZT0iNDYiIGZpbGw9IiM4ODgiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZXR0ZXItc3BhY2luZz0iNSI+SU5VWDwvdGV4dD48dGV4dCB4PSIyMDUiIHk9Ijc3IiBmb250LXNpemU9IjcwIiBmaWxsPSIjRUVFIiBmb250LXdlaWdodD0iNjYwIj5EbzwvdGV4dD48L3N2Zz4="; const settingsConfig = { class_label_topic: "话题内容相关:", quick_mark : { type: 'checkbox', label: '快速收藏 ', default: true, style:'', info:'在帖子上增加一个⭐用于快速收藏到书签' }, cnts_colorful : { type: 'checkbox', label: '点击数可视化', default: true, style:'', info:'点击数彩色高亮,数越大,颜色越红' }, image_view : { type: 'checkbox', label: '增强大图查看', default: true, style:'', info:'在大图查看时,支持滚轮缩放和鼠标拖动位置' }, spoiler_noblur: { type: 'checkbox', label: '去除模糊', default: false, style:'', info:'去除剧透字段的模糊,使其直接显示' }, details_open : { type: 'checkbox', label: '详情展开', default: false, style:'', info:'直接展开被折叠的详情' }, topic_scroll : { type: 'checkbox', label: '帖子限高', default: true, style:'', info:'帖子内容限高,太长的帖子会变成滚动查看的元素' }, show_floor_num: { type: 'checkbox', label: '显示楼层号', default: true, style:'', info:'在每层楼增加楼层号显示' }, auto_words_patch : { type: 'checkbox', label: '全自动隐藏式字数补丁', default: false, style:'', info:'自动添加不可见的字数补丁' }, class_label_list: "话题列表相关:", show_up_time : { type: 'checkbox', label: '显示发帖时间', default: true, style:'', info:'话题列表的帖子显示创建/更新时间,老的帖子会褪色泛黄' }, order_created : { type: 'checkbox', label: '按创建排序', default: true, style:'', info:'首页导航的[新]改成新创建排序' }, class_label_all: "通用:", goto_top_end : { type: 'checkbox', label: '一键顶部/底部', default: true, style:'', info:'右下角新增按钮,可点击到顶部/底部' }, wider_page : { type: 'checkbox', label: '超宽显示', default: false, style:'', info:'让页面显示尽量宽' }, thin_header : { type: 'checkbox', label: '窄的顶栏', default: false, style:'', info:'让(Header)顶栏变窄' }, image_mini : { type: 'checkbox', label: '显示小图', default: false, style:'', info:'让帖子中的图都变小,在鼠标悬停时显示大图' }, image_mini_H : { type: 'number', label: '  小图高度', default: "70", dependsOn: 'image_mini', style:'font-size:15px; margin-top:10px;' , info:'(单位px,建议设为大于50的数)' }, image_mini_W : { type: 'number', label: '  小图宽度', default: "100", dependsOn: 'image_mini', style:'font-size:15px; margin-top:10px;' , info:'(单位px,建议设为大于50的数)' }, icon_custom : { type: 'checkbox', label: '自定义图标', default: false, style:'' , info:'始皇说不建议这样,所以我让鼠标悬停时能看眼原LOGO' }, icon_main : { type: 'text', label: '  主图标URL', default: default_main_icon, dependsOn: 'icon_custom', style:'font-size:15px; margin-top:10px;', info:'' }, icon_wide : { type: 'text', label: '  宽图标URL', default: default_wide_icon, dependsOn: 'icon_custom', style:'font-size:15px; margin-top:10px;', info:'' }, class_label_end: "", }; Object.keys(settingsConfig).forEach(key => { settings[key] = GM_getValue(key, settingsConfig[key].default); }); GM_registerMenuCommand('Custom Settings', openSettings); function openSettings() { if (document.querySelector('div#linuxdo-custom-setting')) { return; } const shadow = document.createElement('div'); shadow.style = `position: fixed; top: 0%; left: 0%; z-index:8888; width:100vw; height:100vh; background: #6668;`; const panel = document.createElement('div'); panel.style = `position: fixed; top: 50%; left: 50%; z-index:9999; transform: translate(-50%, -50%); background: white; padding:15px 25px; border: 1px solid #ccc; color:#000; max-height: calc(90vh - 50px); overflow-y: auto;`; panel.id = "linuxdo-custom-setting" let html = ` <style type="text/css"> :scope label {color:#666; font-size:16px; display:flex; justify-content:space-between; align-items:center; margin:10px;} :scope label span {color:#6bc; font-size:12px; font-weight:normal; padding:0 6px; margin-right:auto;} :scope label input[type=text] {width:300px; padding:1px; margin:0 5px 0 0; font-size:14px;} :scope label input[type=number] {width:60px; padding:1px; margin:0 5px; text-align:center;} :scope label input[type=checkbox] {background:pink; margin:0 5px;} :scope label input[disabled] {background: #CCC;} :scope label button {user-select: none; color: #333; padding: 2px 10px; margin-top:10px; border-radius:5px;} :scope hr {display: block; height: 1px; margin: 0.5em 0; border: 0; border-top: 1px solid #3336; padding: 0;} </style> <h2 style="text-align:center; margin-top:.5rem;">LinuxDo Custom Settings</h2> `; Object.keys(settingsConfig).forEach(key => { const cfg = settingsConfig[key]; if(typeof(cfg) == 'string'){ html += `<hr><span style="margin-top:5px;">${cfg}</span>`; } else { const val = settings[key]; const checked = cfg.type === 'checkbox' && val ? 'checked' : ''; const disabled = cfg.dependsOn && !settings[cfg.dependsOn] ? 'disabled' : ''; html += `<label style="${cfg.style}">${cfg.label}<span>${cfg.info}</span><input type="${cfg.type}" id="ujs_set_${key}" value="${val}" ${checked} ${disabled} ></label>`; } }); html += ` <label><button id="ld_userjs_apply" style="font-weight: bold; background:#ACE">保存并刷新</button> <span></span><button id="ld_userjs_save">仅保存</button> <span></span><button id="ld_userjs_reset">重置</button> <span></span><button id="ld_userjs_close">取消</button></label>`; panel.innerHTML = html; document.body.append(shadow, panel); Object.keys(settingsConfig).forEach(key => { if (settingsConfig[key].dependsOn) { document.getElementById(`ujs_set_${settingsConfig[key].dependsOn}`).addEventListener('change', updateDependencies); } }); function updateDependencies() { Object.keys(settingsConfig).forEach(key => { if (settingsConfig[key].dependsOn) { document.getElementById(`ujs_set_${key}`).disabled = !document.getElementById(`ujs_set_${settingsConfig[key].dependsOn}`).checked; } }); } document.querySelector('button#ld_userjs_save').addEventListener('click', () => { Object.keys(settingsConfig).forEach(key => { const element = document.getElementById(`ujs_set_${key}`); if (element) { settings[key] = element.type === 'checkbox' ? element.checked : element.value; GM_setValue(key, settings[key]); } }); alert('Settings saved!'); panel.remove(); }); document.querySelector('button#ld_userjs_apply').addEventListener('click', () => { Object.keys(settingsConfig).forEach(key => { const element = document.getElementById(`ujs_set_${key}`); if (element) { settings[key] = element.type === 'checkbox' ? element.checked : element.value; GM_setValue(key, settings[key]); } }); window.location.reload(); }); document.querySelector('button#ld_userjs_reset').addEventListener('click', () => { Object.keys(settingsConfig).forEach(key => { GM_deleteValue(key); }); window.location.reload(); }); function setting_hide() { panel.remove(); shadow.remove(); } document.querySelector('button#ld_userjs_close').addEventListener('click', () => setting_hide()); shadow.onclick = () => setting_hide(); updateDependencies(); } // Function 1: Custom Logo if (settings.icon_custom) { GM_addStyle(` #site-logo { object-fit: scale-down; object-position: -999vw; background-size: cover; background-repeat: no-repeat; background-image: url('${settings.icon_main}'); opacity: 1; transition: opacity 0.5s ease; } #site-logo.logo-big { background-image: url('${settings.icon_wide}'); } #site-logo.logo-mobile { background-image: url('${settings.icon_wide}'); } #site-logo:hover { object-position: unset; background-image: none; }`); function replaceIcon() { document.querySelector('link[rel="icon"]').href = settings.icon_main; } const observer = new MutationObserver(replaceIcon); observer.observe(document.head, { childList: true, subtree: true }); replaceIcon(); } // Function 2: Click Counts Visualization if (settings.cnts_colorful) { (function countsColorful() { const badges = document.querySelectorAll("span.badge.badge-notification.clicks"); let values = Array.from(badges, badge => parseInt(badge.title || badge.textContent)); let maxValue = Math.max(...values); let minValue = Math.min(...values); if (maxValue < 100 || (maxValue - minValue < 10)) maxValue = maxValue * 1.5; badges.forEach(badge => { if (!badge.style.backgroundColor) { const number = parseInt(badge.title || badge.textContent); const hue = 180 - (number / maxValue) * 180; badge.style.backgroundColor = `hsl(${hue}, 50%, 50%)`; badge.style.color = "#fff"; const sl = document.createElement('span'); sl.style = `height: 1em; display: inline-block; float: right; background: hsl(${hue}, 50%, 50%); width: ${100 * (number / maxValue)}px;`; badge.after(sl); } }); setTimeout(countsColorful, 1500); })(); } // Function 3: Image Resize and Drag if (settings.image_view) { let sizePercent = 80; let isDragging = false; let startX, startY, initialX, initialY; function adjustSize(event) { let contentImg = document.querySelector('section#discourse-lightbox img'); if (contentImg) { let delta = event.deltaY > 0 ? -10 : 10; sizePercent += delta; if (sizePercent > 300) sizePercent = 300; if (sizePercent < 5) sizePercent = 5; contentImg.style.width = sizePercent + '%'; contentImg.style.maxWidth = sizePercent + '%'; contentImg.style.maxHeight = sizePercent + '200%'; } } function startDrag(event) { let contentImg = document.querySelector('section#discourse-lightbox img'); if (contentImg) { isDragging = true; startX = event.clientX; startY = event.clientY; initialX = contentImg.offsetLeft; initialY = contentImg.offsetTop; event.preventDefault(); } } function drag(event) { if (isDragging) { let contentImg = document.querySelector('section#discourse-lightbox img'); if (contentImg) { let dx = event.clientX - startX; let dy = event.clientY - startY; contentImg.style.left = (initialX + dx) + 'px'; contentImg.style.top = (initialY + dy) + 'px'; } } } function stopDrag(event) { isDragging = false; } let observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { let contentImg = document.querySelector('section#discourse-lightbox img'); if (contentImg) { document.querySelector('section#discourse-lightbox').onwheel = adjustSize; contentImg.onmousedown = startDrag; contentImg.onmouseup = stopDrag; contentImg.onmousemove = drag; contentImg.style.cursor = "move"; function stopClickEvent(event) { event.stopImmediatePropagation(); event.preventDefault(); } contentImg.addEventListener('click', stopClickEvent, true); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); } // Function 4: Quick Bookmark if (settings.quick_mark) { const starSvg = `<svg class="svg-icon" aria-hidden="true" style="text-indent: 1px; transform: scale(1); width:18px; height:18px;"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"> <path d="M259.3 17.8L194 150.2 47.9 171.5c-26.2 3.8-36.7 36.1-17.7 54.6l105.7 103-25 145.5c-4.5 26.3 23.2 46 46.4 33.7L288 439.6l130.7 68.7c23.2 12.2 50.9-7.4 46.4-33.7l-25-145.5 105.7-103c19-18.5 8.5-50.8-17.7-54.6L382 150.2 316.7 17.8c-11.7-23.6-45.6-23.9-57.4 0z"></path></svg></svg> `; let markMap = new Map(); function handleResponse(xhr, successCallback, errorCallback) { xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { successCallback(xhr); } else { errorCallback(xhr); } } }; } function deleteStarMark(mark_btn, data_id) { if (markMap.has(data_id)) { const mark_id = markMap.get(data_id); var xhr = new XMLHttpRequest(); xhr.open('DELETE', `/bookmarks/${mark_id}`, true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest'); xhr.setRequestHeader("x-csrf-token", document.head.querySelector("meta[name=csrf-token]")?.content); handleResponse(xhr, (xhr) => { mark_btn.style.color = '#777'; mark_btn.title = "收藏"; mark_btn.onclick = () => addStarMark(mark_btn, data_id); }, (xhr) => { alert('删除失败!' + xhr.statusText + "\n" + TryParseJson(xhr.responseText)); }); xhr.send(); } } function TryParseJson(str) { try { const jsonObj = JSON.parse(str); return JSON.stringify(jsonObj, null, 1); } catch (error) { return str; } } function addStarMark(mark_btn, data_id) { const xhr = new XMLHttpRequest(); xhr.open('POST', '/bookmarks', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest'); xhr.setRequestHeader('discourse-logged-in', ' true'); xhr.setRequestHeader('discourse-present', ' true'); xhr.setRequestHeader("x-csrf-token", document.head.querySelector("meta[name=csrf-token]")?.content); const postData = `name=%E6%94%B6%E8%97%8F&auto_delete_preference=3&bookmarkable_id=${data_id}&bookmarkable_type=Post`; handleResponse(xhr, (xhr) => { mark_btn.style.color = '#fdd459'; mark_btn.title = "删除收藏"; mark_btn.onclick = () => deleteStarMark(mark_btn, data_id); }, (xhr) => { alert('收藏失败!' + xhr.statusText + "\n" + TryParseJson(xhr.responseText)); }); xhr.send(postData); } function addMarkBtn() { let articles = document.querySelectorAll("article[data-post-id]"); if (articles.length <= 0) return; articles.forEach(article => { const target = article.querySelector("div.topic-body.clearfix > div.regular.contents > section > nav > div.actions"); if (target && !article.querySelector("div.topic-body.clearfix > div.regular.contents > section > nav > span.star-bookmark")) { const dataPostId = article.getAttribute('data-post-id'); const starButton = document.createElement('span'); starButton.innerHTML = starSvg; starButton.className = "star-bookmark"; starButton.style.cursor = 'pointer'; starButton.style.margin = '0px 12px'; if (markMap.has(dataPostId)) { starButton.style.color = '#fdd459'; starButton.title = "删除收藏"; starButton.onclick = () => deleteStarMark(starButton, dataPostId); } else { starButton.style.color = '#777'; starButton.title = "收藏"; starButton.onclick = () => addStarMark(starButton, dataPostId); } target.after(starButton); } }); } function getStarMark() { let articles = document.querySelectorAll("article[data-post-id]"); if (articles.length <= 0) return; const currentUserElement = document.querySelector('#current-user button > img[src]'); function extractUsername(srcString) { const regex = /\/user_avatar\/linux\.do\/([^\/]+)\/\d+\//; const match = srcString.match(regex); if (match && match[1]) { return match[1]; } else { return null; } } if(!currentUserElement) return; const currentUsername = extractUsername(currentUserElement.getAttribute('src')); if(!currentUsername) return; const xhr = new XMLHttpRequest(); xhr.open('GET', `/u/${currentUsername}/user-menu-bookmarks`, true); xhr.setRequestHeader("x-csrf-token", document.head.querySelector("meta[name=csrf-token]")?.content); handleResponse(xhr, (xhr) => { var response = JSON.parse(xhr.responseText); response.bookmarks.forEach(mark => { markMap.set(mark.bookmarkable_id.toString(), mark.id.toString()); }); addMarkBtn(); }, (xhr) => { console.error('GET请求失败:', xhr.statusText); }); xhr.send(); } let lastUpdateMarkTime = 0; let lastUpdateButnTime = 0; function mutationCallback() { const currentTime = Date.now(); if (currentTime - lastUpdateMarkTime > 9000) { setTimeout(getStarMark, 500); lastUpdateMarkTime = currentTime; } if (currentTime - lastUpdateButnTime > 1000) { setTimeout(addMarkBtn, 500); lastUpdateButnTime = currentTime; } } const mainNode = document.querySelector("#main-outlet"); if (mainNode) { const observer = new MutationObserver(mutationCallback); observer.observe(mainNode, { childList: true, subtree: true }); } getStarMark(); } // Function 5: mini article image show if (settings.image_mini) { let _H = parseInt(settings.image_mini_H); let _W = parseInt(settings.image_mini_W);// transition: max-width 0.5s ease-in-out, max-height 0.5s ease-in-out; GM_addStyle(` article div.topic-body div.regular.contents img:not(.thumbnail):not(.ytp-thumbnail-image):not(.emoji) { max-width : ${_W}px; max-height : ${_H}px; object-fit: contain; }`); var imageMiniTimer = setInterval(function() { var images = document.querySelectorAll('article div.topic-body div.regular.contents img:not(.thumbnail):not(.ytp-thumbnail-image):not(.emoji)'); if (images.length >= 1) { for (var i = 0; i < images.length; i++) { let img = images[i]; let image_src = null; let src_height = null; let urls = img.getAttribute('srcset') if (urls) { urls = urls.match(/https:\/\/[^,\s]+/g); image_src = urls[urls.length - 1]; }else{ image_src = img.src; } src_height = img.naturalHeight || img.height; if (img.parentElement.matches('a.lightbox')) { img = img.parentElement; if (!image_src) { image_src = img.getAttribute('href'); } } //console.log(image_src) img.image_src = image_src; img.src_height = src_height; let previewDiv = null; if (document.getElementById('hover-preview-img') == null) { previewDiv = document.createElement('div'); previewDiv.id = 'hover-preview-img'; previewDiv.style = 'position: fixed; z-index:999; top:-10px; max-width: 0px; max-height 0px; opacity: 0; transition: max-width 0.3s ease-in-out, max-height 0.3s ease-in-out, left 0.3s ease-in-out , opacity 0.3s ease-in-out , top 0.3s ease-in-out;'; // display:none; document.body.appendChild(previewDiv); let fullSizeImg = document.createElement('img'); fullSizeImg.className = 'full-size-image'; previewDiv.appendChild(fullSizeImg); } else { previewDiv = document.getElementById('hover-preview-img'); } img.addEventListener('mouseenter', function(event) { let previewDiv = document.getElementById('hover-preview-img'); let fullSizeImg = previewDiv.querySelector('.full-size-image'); previewDiv.style.display = 'block'; previewDiv.style.background="#FFFE"; previewDiv.style.boxShadow="1px 1px 5px #555"; previewDiv.style.padding="0px"; previewDiv.style.left= event.clientX + 20 + 'px'; previewDiv.style.maxWidth = '99vw'; previewDiv.style.maxHeight = '99vh'; this.title=""; fullSizeImg.src = this.image_src; fullSizeImg.style.width = ''; fullSizeImg.style.height = ''; fullSizeImg.style.maxWidth = '100%'; fullSizeImg.style.maxHeight = '100%'; previewDiv.style.top = event.clientY - this.src_height/2 + 'px'; previewDiv.style.opacity = 1; fullSizeImg.onload = function() { console.log(previewDiv.offsetTop , fullSizeImg.naturalHeight , window.innerHeight); if (previewDiv.offsetTop + fullSizeImg.naturalHeight > window.innerHeight - 5) { previewDiv.style.top = window.innerHeight - 5 - fullSizeImg.naturalHeight + 'px'; } }; }); img.addEventListener('mouseleave', function() { let previewDiv = document.getElementById('hover-preview-img'); previewDiv.style.top = "-10px"; previewDiv.style.maxWidth = "0px"; previewDiv.style.maxHeight = "0px"; previewDiv.style.opacity = 0; }); } } }, 1000); } // Function 6: remove spoiler blurred if (settings.spoiler_noblur) { GM_addStyle(` .spoiler-blurred { filter: drop-shadow(0px 0px 3px #BBB)!important; } .spoiler-blurred img { filter: drop-shadow(0px 0px 3px #BBB)!important; }`); } // Function 7: details open if (settings.details_open) { function open_detail() { let details = document.querySelectorAll("article details"); details.forEach(detail => { if (detail.opened != true) { detail.open = true; detail.opened = true; } }); setTimeout(open_detail, 990); } setTimeout(open_detail, 900); } // Function 8: wider page if (settings.wider_page) { GM_addStyle(` #main-outlet-wrapper { max-width: 100%!important; } body.has-sidebar-page header.d-header > div.wrap { max-width: 100%!important; } .topic-body { width: 100%!important; } :root { --d-max-width: 100%!important; } article .topic-map.--op { max-width: 100%; } div#reply-control .reply-area { width: calc(100% - 2em); } @media screen and (min-width: 925px) { #main-outlet .container.posts { grid-template-columns: auto 120px; } }`); } // Function 9: thin_header if (settings.thin_header) { GM_addStyle(` .d-header { height: 2.5em !important; } .d-header .extra-info-wrapper .title-wrapper { display: flex; flex-direction: row; } .d-header div.title-wrapper > h1.header-title { width: auto; font-size: large; } .d-header #site-logo { height: 2em !important; } .d-header .d-header-icons .icon img.avatar { height: 2em !important; }`); } // Function 10: topic contents scroll if (settings.topic_scroll) { GM_addStyle(` article div.topic-body .regular.contents .cooked { max-height: 60vh; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #aaaa #1111; } article div.topic-body .regular.contents .cooked ::-webkit-scrollbar-track { background: #1111; } article div.topic-body .regular.contents .cooked ::-webkit-scrollbar-thumb { background: #aaaa; } article div.topic-body .regular.contents .cooked ::-webkit-scrollbar-thumb:hover { background: #0008; }`); } // Function 11: order by Created if (settings.order_created) { function orderByCreated() { const a_new = document.querySelector("ul#navigation-bar > li.new.ember-view.nav-item_new > a"); if ( a_new && a_new.href.endsWith("/new")) { a_new.parentNode.title = "按最新创建排序"; a_new.href = a_new.href.replace("/new","/latest?order=created"); a_new.style.filter="drop-shadow(0px 0px 1px var(--quaternary))"; // #8FF8 } setTimeout(orderByCreated, 990); } setTimeout(orderByCreated, 900); } // Function 12: 显示发帖时间和最新回复时间 if (settings.show_up_time) { function getHue(date, currentDate) { const diff = Math.abs(currentDate - date); const baseday = 30 * 24 * 60 * 60 * 1000; // 30 day const diffRatio = Math.min( Math.log(diff / baseday + 1), 1); return 120 - (140 * diffRatio); // green to red } function formatDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${year} ${month} ${day} ${hours}:${minutes}`; } function parseDate(dateStr) { let parts; // Check if the string is in Chinese format if (dateStr.match(/(\d+) 年 (\d+) 月 (\d+) 日 (\d+):(\d+)/)) { parts = dateStr.match(/(\d+) 年 (\d+) 月 (\d+) 日 (\d+):(\d+)/); return new Date(parts[1], parts[2] - 1, parts[3], parts[4], parts[5]); } // Check if the string is in English format if (dateStr.match(/(\w+) (\d+), (\d+) (\d+):(\d+) (am|pm)/i)) { parts = dateStr.match(/(\w+) (\d+), (\d+) (\d+):(\d+) (am|pm)/i); const monthMap = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 }; let hour = parseInt(parts[4], 10); if (parts[6].toLowerCase() === 'pm' && hour < 12) { hour += 12; } else if (parts[6].toLowerCase() === 'am' && hour === 12) { hour = 0; } return new Date(parts[3], monthMap[parts[1]], parts[2], hour, parts[5]); } throw new Error('Unsupported date format'); } GM_addStyle(` .topic-list .topic-list-data.age.activity { width: 11em; padding: 0px 5px; } .topic-list .topic-list-data.age.activity > a.post-activity{ font-size: 14px; text-align: left; display: block; text-wrap: nowrap; padding: 8px 5px; }`); function creatTimeShow() { document.querySelectorAll(".num.topic-list-data.age.activity").forEach(function (item) { const timeSpan = item.querySelector("a.post-activity") if (timeSpan.innerText.length > 10) { return; } const timeInfo = item.title; let createDateString = timeInfo.match(/创建日期:([\d 年 月 日 :]+)/) || timeInfo.match(/Created: ([\w, \d:apm ]+)/); let updateDateString = timeInfo.match(/最新:([\d 年 月 日 :]+)/) || timeInfo.match(/Latest: ([\w, \d:apm ]+)/); createDateString = (createDateString[1] ?? '').trim(); const createDate = parseDate(createDateString); const currentDate = new Date(); const createHue = getHue(createDate, currentDate); const formatCreateDate = formatDate(createDate); timeSpan.innerHTML = `<span style="color: hsl(${createHue}, 35%, 50%);">创建:${formatCreateDate}</span><br>`; if (updateDateString) { updateDateString = (updateDateString[1] ?? '').trim(); const updateDate = parseDate(updateDateString); const updateHue = getHue(updateDate, currentDate); const formatNewDate = formatDate(updateDate); timeSpan.innerHTML += `<span style="color: hsl(${updateHue}, 35%, 50%);">最新:${formatNewDate}</span>` } else { timeSpan.innerHTML += `<span style="color:#888;">最新:暂无回复</span>` } const pastDays = Math.abs(createDate - currentDate) / (24 * 60 * 60 * 1000); const topicTitle = item.parentNode.querySelector(".main-link") const topicUsers = item.parentNode.querySelector(".topic-list-data.posters") if ( pastDays > 30) { topicTitle.style.opacity = 0.9; topicTitle.style.filter = "grayscale(10%) sepia(10%)"; if ( pastDays > 60) { topicTitle.style.opacity = 0.8; topicTitle.style.filter = "sepia(40%) brightness(85%)"; if(topicUsers) topicUsers.style.filter = "sepia(40%) brightness(85%)"; } if ( pastDays > 120) { topicTitle.style.filter = "sepia(90%) brightness(85%)"; if(topicUsers) topicUsers.style.filter = "sepia(90%) brightness(85%)"; } } }) setTimeout(creatTimeShow, 990); } setTimeout(creatTimeShow, 900); } // Function 13: 快速点击到顶部/底部 if (settings.goto_top_end) { function scrollTimeline(offset) { let element = document.querySelector(".timeline-padding"); if (!element) { window.scrollTo({ top: 9999 * offset, behavior: "smooth" }); if (window.location.href.includes("/t/topic/")) { if(offset < 1) window.location.href = window.location.href.replace(/\/t\/topic\/(\d+).*/, '/t/topic/$1') else window.location.href = window.location.href.replace(/\/t\/topic\/(\d+).*/, '/t/topic/$1/99999') } return; } const event = new MouseEvent('click', { bubbles: true, cancelable: true, clientX: element.getBoundingClientRect().left + 0, clientY: element.getBoundingClientRect().top + offset, }); element.dispatchEvent(event); } var toTop = document.createElement("button"); var toEnd = document.createElement("button"); var scrollBtns = document.createElement("div"); scrollBtns.className = "goto_top_end"; toTop.innerText = "⬆️顶部"; toEnd.innerText = "⬇️最新"; GM_addStyle(` .goto_top_end{ position: fixed; bottom: 2px; right: 65px; z-index: 1000; border:none; } .goto_top_end > button { background-color:#0005; color:#eee; border:none; padding: 10px; margin: 0px 5px; border-radius: 5px; cursor: pointer; } @media screen and (max-width: 924px) { .goto_top_end{ right: 160px; } } `); toTop.onclick = function() { scrollTimeline(0) }; toEnd.onclick = function() { scrollTimeline(500) }; document.body.appendChild(scrollBtns); scrollBtns.append(toTop, toEnd); } if (settings.show_floor_num) { GM_addStyle(`.post-info.floor-number {color: #9CD; margin-left: 1em;}`) function showPostNumber() { const posts = document.querySelectorAll('article[id^="post_"]'); posts.forEach(post => { let floorInfo = post.querySelector('.post-infos > .floor-number'); if(!floorInfo) { const postId = post.id; const floorNumber = postId.split('_')[1]; const postInfos = post.querySelector('.post-infos'); if (postInfos) { floorInfo = document.createElement('div'); floorInfo.className = 'post-info floor-number'; floorInfo.textContent = `#${floorNumber}`; postInfos.append(floorInfo); } } }); setTimeout(showPostNumber, 1990); } setTimeout(showPostNumber, 1900); } // 头像变大 settings.avatar_bigge = true; if (true) { GM_addStyle(` div.sidebar-sections .sidebar-section ul > li.sidebar-section-link-wrapper > a.sidebar-section-link { padding-left: 2.5em; } div#main-outlet-wrapper { --d-sidebar-width: 15em; } .topic-list td.topic-list-data.posters { height: auto; padding: 0.33em; width: 110px; } .topic-list td.posters.topic-list-data > a:first-child:not([style*="display: none"]) > img { width: 48px; height: 48px; } @media screen and (max-width: 850px) { .topic-list .topic-list-data.posters a.latest > img { width: 48px; height: 48px; } .topic-list td.topic-list-data.posters { width: 52px; } } `); function fristAvatarBigger() { document.querySelectorAll(`.topic-list td.posters.topic-list-data > a:first-child > img, .topic-list td.posters.topic-list-data > a.latest > img`).forEach(function (img) { if (img.src.includes("/24/")) { img.src = img.src.replace("/24/","/48/"); } }); setTimeout(fristAvatarBigger, 1990); } setTimeout(fristAvatarBigger, 900); } function link_wrapper_add_item(name, url, svg_id) { let sidebar = document.querySelector("ul#sidebar-section-content-外部链接"); if(!sidebar) return; if(sidebar.querySelector(`[data-link-name="${name}"]`)) return; let add_li = document.createElement('li'); sidebar.append(add_li); add_li.className = "sidebar-section-link-wrapper" add_li.innerHTML = ` <a href="${url}" rel="noopener noreferrer" target="_blank" data-link-name="${name}" class="sidebar-section-link sidebar-row" title="来自L站元宇宙,站内佬友提供服务,非官方服务"> <span class="sidebar-section-link-prefix icon" style="color: #7AA;"> <svg class="fa d-icon d-icon-far-eye svg-icon prefix-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use href="#${svg_id}"></use></svg> </span> <span class="sidebar-section-link-content-text">${name}</span> </a>`; } link_wrapper_add_item("WIKI", "https://wiki.linux.do/", "fab-wikipedia-w"); link_wrapper_add_item("导航", "https://nav.linux.do/", "rocket"); document.querySelector("header div.contents span.header-sidebar-toggle > button.btn-sidebar-toggle, header #toggle-hamburger-menu").addEventListener('click', () => { setTimeout(function(){ link_wrapper_add_item("WIKI", "https://wiki.linux.do/", "fab-wikipedia-w"); link_wrapper_add_item("导航", "https://nav.linux.do/", "rocket"); }, 150); }); if (settings.auto_words_patch) { const fillContent = '<div></div>\n\n'; function handleTextarea(textarea) { if (textarea.dataset.handled) return; textarea.dataset.handled = 'true'; let mask = document.querySelector("span#text-input-padding-mask"); if (!mask) { mask = document.createElement('span'); textarea.before(mask); mask.style = "width:95%; max-width:10em; margin: -2em 8px 4px; height:2.5em; background-color: var(--secondary); z-index:1; display:none"; mask.id = "text-input-padding-mask"; } let toolbar = document.querySelector('div.d-editor-button-bar[role="toolbar"]'); if (toolbar) toolbar.style.zIndex = 2; function updateContent() { const currentContent = textarea.value; if (document.querySelector('div.characters-required.ember-view').innerText.length > 0) { if (!textarea.value.startsWith(fillContent)) { textarea.value = fillContent + currentContent; } } if (textarea.value.startsWith(fillContent)) { textarea.style.marginTop = "-3.5em"; mask.style.display = ""; mask.style.borderBottom = "1px solid #fd58"; const contentWithoutFill = textarea.value.replace(fillContent, ''); if (contentWithoutFill.length === 0) { textarea.value = contentWithoutFill; textarea.style = ""; mask.style.display = "none"; mask.style.borderBottomColor = ""; } } else { textarea.style = ""; mask.style.display = "none"; mask.style.borderBottomColor = ""; } // 阻止光标进入 fillContent 区域 textarea.addEventListener('select', preventSelectionInFillContent); textarea.addEventListener('click', preventSelectionInFillContent); textarea.addEventListener('keydown', preventCursorInFillContent); } function preventSelectionInFillContent(e) { if (!textarea.value.startsWith(fillContent)) { return; } textarea.style.caretColor = "transparent"; let start = textarea.selectionStart; let end = textarea.selectionEnd; if (start < fillContent.length) start = fillContent.length; if (end < fillContent.length) end = fillContent.length; if ( start != textarea.selectionStart || end != textarea.selectionEnd) { textarea.setSelectionRange(start, end); } textarea.style.caretColor = ""; } function preventCursorInFillContent(e) { if (!textarea.value.startsWith(fillContent)) { return; } if (["ArrowUp","ArrowLeft","Home"].includes(e.key)) { textarea.style.caretColor = "transparent"; setTimeout(function() { let start = textarea.selectionStart; let end = textarea.selectionEnd; if (start < fillContent.length) start = fillContent.length; if (end < fillContent.length) end = fillContent.length; if ( start != textarea.selectionStart || end != textarea.selectionEnd) { textarea.setSelectionRange(start, end); } textarea.style.caretColor = ""; },0); } } updateContent(); textarea.addEventListener('input', updateContent); } function observeDOM() { const targetNode = document.body; const config = { childList: true, subtree: true }; const callback = function(mutationsList, observer) { for(let mutation of mutationsList) { if (mutation.type === 'childList') { const textarea = document.querySelector('#reply-control .d-editor-container textarea.ember-text-area.ember-view.d-editor-input'); if (textarea) { handleTextarea(textarea); } } } }; const observer = new MutationObserver(callback); observer.observe(targetNode, config); } observeDOM(); } })();