您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows preview of the linked questions/answers on hover
当前为
// ==UserScript== // @name SE Preview on hover // @description Shows preview of the linked questions/answers on hover // @version 0.1.4 // @author wOxxOm // @namespace wOxxOm.scripts // @license MIT License // @match *://*.stackoverflow.com/* // @match *://*.superuser.com/* // @match *://*.serverfault.com/* // @match *://*.askubuntu.com/* // @match *://*.stackapps.com/* // @match *://*.mathoverflow.net/* // @match *://*.stackexchange.com/* // @require https://gf.qytechs.cn/scripts/12228/code/setMutationHandler.js // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect stackoverflow.com // @connect superuser.com // @connect serverfault.com // @connect askubuntu.com // @connect stackapps.com // @connect mathoverflow.net // @connect stackexchange.com // @connect cdn.sstatic.net // @run-at document-end // @noframes // ==/UserScript== /* jshint lastsemic:true, multistr:true, laxbreak:true, -W030, -W041, -W084 */ const PREVIEW_DELAY = 100; const COLORS = { question: { backRGB: '80, 133, 195', foreRGB: '#265184', }, answer: { backRGB: '112, 195, 80', foreRGB: '#3f7722', foreInv: 'white', }, }; let xhr; let preview = { frame: null, link: null, hover: {x:0, y:0}, timer: 0, CSScache: {}, stylesOverride: '', }; const rxPreviewable = getURLregexForMatchedSites(); const thisPageUrls = getPageBaseUrls(location.href); initStyles(); initPolyfills(); setMutationHandler('a', onLinkAdded, {processExisting: true}); /**************************************************************/ function onLinkAdded(links) { for (let i = 0, link; (link = links[i++]); ) { if (isLinkPreviewable(link)) { link.removeAttribute('title'); link.addEventListener('mouseover', onLinkHovered); } } } function onLinkHovered(e) { if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) return; preview.link = this; preview.link.addEventListener('mousemove', onLinkMouseMove); preview.link.addEventListener('mouseout', abortPreview); preview.link.addEventListener('mousedown', abortPreview); restartPreviewTimer(this); } function onLinkMouseMove(e) { let stoppedMoving = Math.abs(preview.hover.x - e.clientX) < 2 && Math.abs(preview.hover.y - e.clientY) < 2; if (!stoppedMoving) return; preview.hover.x = e.clientX; preview.hover.y = e.clientY; restartPreviewTimer(this); } function restartPreviewTimer(link) { clearTimeout(preview.timer); preview.timer = setTimeout(() => { preview.timer = 0; link.removeEventListener('mousemove', onLinkMouseMove); if (link.matches(':hover')) downloadPreview(link.href); }, PREVIEW_DELAY); } function abortPreview(e) { releaseLinkListeners(this); preview.timer = setTimeout(link => { if (link == preview.link && preview.frame && !preview.frame.matches(':hover')) { releaseLinkListeners(link); hideAndRemove(preview.frame); } }, PREVIEW_DELAY * 3, this); if (xhr) xhr.abort(); } function releaseLinkListeners(link) { link.removeEventListener('mousemove', onLinkMouseMove); link.removeEventListener('mouseout', abortPreview); link.removeEventListener('mousedown', abortPreview); clearTimeout(preview.timer); } function hideAndRemove(element, transition) { if (transition) { element.style.transition = typeof transition == 'number' ? `opacity ${transition}s ease-in-out` : transition; return setTimeout(hideAndRemove, 0, element); } element.style.opacity = 0; element.addEventListener('transitionend', function remove() { element.removeEventListener('transitionend', remove); element.remove(); }); } function downloadPreview(url) { xhr = GM_xmlhttpRequest({ method: 'GET', url: httpsUrl(url), onload: showPreview, }); } function showPreview(data) { let doc = data.SEpreviewDoc || new DOMParser().parseFromString(data.responseText, 'text/html'); if (!doc || !doc.head) { error('empty document received:', data); return; } if (!$(doc, 'base')) doc.head.insertAdjacentHTML('afterbegin', `<base href="${data.finalUrl}">`); const answerIdMatch = data.finalUrl.match(/questions\/.+?\/(\d+)/); const isQuestion = !answerIdMatch; let postId = answerIdMatch ? '#answer-' + answerIdMatch[1] : '#question'; let post = $(doc, postId + ' .post-text'); if (!post) return error('No parsable post found', doc); const title = $(doc, 'meta[property="og:title"]').content; let status = isQuestion && $(doc, '.question-status'); let comments = $(doc, `${postId} .comments`); let commentsHidden = +$(comments, 'tbody').dataset.remainingCommentsCount; let commentsShowLink = commentsHidden && $(doc, `${postId} .js-show-link.comments-link`); let externalsReady = [preview.stylesOverride]; let externalsToGet = new Set(); let afterBodyHtml = ''; fetchExternals(); maybeRender(); function fetchExternals() { let codeBlocks = $$(post, 'pre code'); if (codeBlocks.length) { codeBlocks.forEach(e => e.parentElement.classList.add('prettyprint')); externalsReady.push( '<script> StackExchange = {}; </script>', '<script src="https://cdn.sstatic.net/Js/prettify-full.en.js"></script>' ); afterBodyHtml += '<script> prettyPrint(); </script>'; } $$(doc, 'style, link[rel="stylesheet"]').forEach(e => { if (e.localName == 'style') externalsReady.push(e.outerHTML); else if (e.href in preview.CSScache) externalsReady.push(preview.CSScache[e.href]); else { externalsToGet.add(e.href); GM_xmlhttpRequest({ method: 'GET', url: e.href, onload: data => { externalsReady.push(preview.CSScache[e.href] = '<style>' + data.responseText + '</style>'); externalsToGet.delete(e.href); maybeRender(); }, }); } }); } function maybeRender() { if (externalsToGet.size) return; if (!preview.frame) { preview.frame = document.createElement('iframe'); preview.frame.id = 'SEpreview'; } preview.frame.classList.toggle('SEpreviewIsAnswer', !!answerIdMatch); document.body.appendChild(preview.frame); const answers = $$(doc, '.answer'); const answersShown = answers.length > (isQuestion ? 0 : 1); if (answersShown) { afterBodyHtml += '<div id="SEpreviewAnswers">Answers: ' + answers.map((e, index) => `<a href="${$(e, '.short-link').href.replace(/(\d+)\/\d+/, '$1')}" title="${ $text(e, '.user-details a') + ' (' + $text(e, '.reputation-score') + ') ' + $text(e, '.user-action-time') + $text(e, '.vote-count-post').replace(/\d+/, s => !s ? '' : ', votes: ' + s)}" class="${e.matches(postId) ? 'SEpreviewed' : ''}" >${index + 1}</a>` ).join('') + '</div>'; } $$remove(doc, 'script'); let html = `<head>${externalsReady.join('')}</head> <body${answerIdMatch ? ' class="SEpreviewIsAnswer"' : ''}> <a id="SEpreviewTitle" href="${ isQuestion ? data.finalUrl : data.finalUrl.replace(/\/\d+[^\/]*$/, '') }">${title}</a> <div id="SEpreviewBody">${ [post.parentElement, comments, commentsShowLink, status] .map(e => e ? e.outerHTML : '').join('') }</div> ${afterBodyHtml} </body>`; try { let pvDoc = preview.frame.contentDocument; pvDoc.open(); pvDoc.write(html); pvDoc.close(); } catch(e) { preview.frame.srcdoc = `<html>${html}</html>`; } onFrameReady(preview.frame, function() { this.onload = null; this.style.opacity = 1; this.contentDocument.addEventListener('mouseover', retainMainScrollPos); this.contentDocument.addEventListener('click', interceptLinks); }); } function interceptLinks(e) { const link = e.target; if (link.localName != 'a') return; if (link.matches('.js-show-link.comments-link')) { hideAndRemove(link, 0.5); downloadComments(); } else if (e.button || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) return (link.target = '_blank'); else if (link.matches('#SEpreviewAnswers a, a#SEpreviewTitle')) showPreview({ finalUrl: link.href.includes('/questions/') ? link.href : data.finalUrl.replace(/(\/\d+[^\/]*|\?.*)?$/g, '') + '/' + link.pathname.match(/\d+/)[0], SEpreviewDoc: doc, }); else if (!isLinkPreviewable(link)) return (link.target = '_blank'); else if (!link.matches('.SEpreviewed')) downloadPreview(link.href); e.preventDefault(); } function downloadComments() { GM_xmlhttpRequest({ method: 'GET', url: new URL(data.finalUrl).origin + '/posts/' + comments.id.match(/\d+/)[0] + '/comments', onload: r => showComments(r.responseText), }); } function showComments(html) { let tbody = $(preview.frame.contentDocument, `#${comments.id} tbody`); let oldIds = new Set([...tbody.rows].map(e => e.id)); tbody.innerHTML = html; for (let tr of tbody.rows) if (!oldIds.has(tr.id)) tr.classList.add('new-comment-highlight'); } } function retainMainScrollPos(e) { let scrollPos = {x:scrollX, y:scrollY}; document.addEventListener('scroll', preventScroll); document.addEventListener('mouseover', releaseScrollLock); function preventScroll(e) { scrollTo(scrollPos.x, scrollPos.y); log('prevented main page scroll'); } function releaseScrollLock(e) { document.removeEventListener('mouseout', releaseScrollLock); document.removeEventListener('scroll', preventScroll); } } function getURLregexForMatchedSites() { return new RegExp('https?://(\\w*\\.)*(' + GM_info.script.matches.map(m => m.match(/^.*?\/\/\W*(\w.*?)\//)[1].replace(/\./g, '\\.') ).join('|') + ')/(questions|q|a)/\\d+'); } function getPageBaseUrls(url) { let base = httpsUrl((url.match(rxPreviewable) || [])[0]); return base ? { base, short: base.replace('/questions/', '/q/'), } : {}; } function isLinkPreviewable(link) { const inPreview = link.ownerDocument != document; if (!rxPreviewable.test(link.href) || link.matches('.short-link')) return false; const pageUrls = inPreview ? getPageBaseUrls(preview.link.href) : thisPageUrls; const url = httpsUrl(link.href); return !url.startsWith(pageUrls.base) && !url.startsWith(pageUrls.short); } function onFrameReady(frame, callback) { if (frame.contentDocument.readyState == 'complete') return callback.call(frame); else frame.onload = callback; } function httpsUrl(url) { return (url || '').replace(/^http:/, 'https:'); } function $(node__optional, selector) { return (node__optional || document).querySelector(selector || node__optional); } function $$(node__optional, selector) { return (node__optional || document).querySelectorAll(selector || node__optional); } function $text(node__optional, selector) { let e = $(node__optional, selector); return e ? e.textContent.trim() : ''; } function $$remove(node__optional, selector) { (node__optional || document).querySelectorAll(selector || node__optional) .forEach(e => e.remove()); } function log(...args) { console.log(GM_info.script.name, ...args); } function error(...args) { console.error(GM_info.script.name, ...args); } function initPolyfills() { NodeList.prototype.forEach = NodeList.prototype.forEach || Array.prototype.forEach; NodeList.prototype.map = NodeList.prototype.map || Array.prototype.map; } function initStyles() { GM_addStyle(` #SEpreview { all: unset; box-sizing: content-box; width: 720px; /* 660px + 30px + 30px */ height: 33%; min-height: 200px; position: fixed; opacity: 0; transition: opacity .5s cubic-bezier(.88,.02,.92,.66); right: 0; bottom: 0; padding: 0; margin: 0; background: white; box-shadow: 0 0 100px rgba(0,0,0,0.5); z-index: 999999; border: 8px solid rgb(${COLORS.question.backRGB}); } #SEpreview.SEpreviewIsAnswer { border-color: rgb(${COLORS.answer.backRGB}); } `); preview.stylesOverride = `<style> body, html { min-width: unset!important; box-shadow: none!important; } html, body { background: unset!important;; } body { display: flex; flex-direction: column; height: 100vh; } #SEpreviewTitle { all: unset; display: block; padding: 20px 30px; font-weight: bold; font-size: 20px; line-height: 1.3; background-color: rgba(${COLORS.question.backRGB}, 0.37); color: ${COLORS.question.foreRGB}; cursor: pointer; } #SEpreviewTitle:hover { text-decoration: underline; } #SEpreviewBody { padding: 30px!important; overflow: auto; flex-grow: 2; } #SEpreviewBody .post-menu { display: none!important; } #SEpreviewBody .question-status { margin: -25px -30px -30px; padding-left: 30px; } #SEpreviewBody .question-status h2 { font-weight: normal; } #SEpreviewBody::-webkit-scrollbar { background-color: rgba(${COLORS.question.backRGB}, 0.1); } #SEpreviewBody::-webkit-scrollbar-thumb { background-color: rgba(${COLORS.question.backRGB}, 0.2); } #SEpreviewBody::-webkit-scrollbar-thumb:hover { background-color: rgba(${COLORS.question.backRGB}, 0.3); } #SEpreviewBody::-webkit-scrollbar-thumb:active { background-color: rgba(${COLORS.question.backRGB}, 0.75); } body.SEpreviewIsAnswer #SEpreviewTitle { background-color: rgba(${COLORS.answer.backRGB}, 0.37); color: ${COLORS.answer.foreRGB}; } body.SEpreviewIsAnswer #SEpreviewBody::-webkit-scrollbar { background-color: rgba(${COLORS.answer.backRGB}, 0.1); } body.SEpreviewIsAnswer #SEpreviewBody::-webkit-scrollbar-thumb { background-color: rgba(${COLORS.answer.backRGB}, 0.2); } body.SEpreviewIsAnswer #SEpreviewBody::-webkit-scrollbar-thumb:hover { background-color: rgba(${COLORS.answer.backRGB}, 0.3); } body.SEpreviewIsAnswer #SEpreviewBody::-webkit-scrollbar-thumb:active { background-color: rgba(${COLORS.answer.backRGB}, 0.75); } #SEpreviewAnswers { all: unset; display: block; padding: 10px 30px; font-weight: bold; font-size: 20px; line-height: 1.3; border-top: 4px solid rgba(${COLORS.answer.backRGB}, 0.37); background-color: rgba(${COLORS.answer.backRGB}, 0.37); color: ${COLORS.answer.foreRGB}; word-break: break-word; } #SEpreviewAnswers a { color: ${COLORS.answer.foreRGB}; padding: .25ex .75ex; text-decoration: none; } #SEpreviewAnswers a:hover:not(.SEpreviewed) { text-decoration: underline; } #SEpreviewAnswers a.SEpreviewed { background-color: ${COLORS.answer.foreRGB}; color: ${COLORS.answer.foreInv}; } .comments .new-comment-highlight { -webkit-animation: highlight 9s cubic-bezier(0,.8,.37,.88); -moz-animation: highlight 9s cubic-bezier(0,.8,.37,.88); animation: highlight 9s cubic-bezier(0,.8,.37,.88); } @-webkit-keyframes highlight { from {background-color: #ffcf78} to {background-color: none} } </style>`; }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址