您需要先安装一个扩展,例如 篡改猴、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.2.0 // @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 // @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.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 CACHE_DURATION = 1 * 60 * 1000; // 1 minute for the recently active posts, scales up logarithmically const COLORS = { question: { backRGB: '80, 133, 195', fore: '#265184', }, answer: { backRGB: '112, 195, 80', fore: '#3f7722', foreInv: 'white', }, deleted: { backRGB: '181, 103, 103', fore: 'rgb(181, 103, 103)', foreInv: 'white', }, }; let xhr; let preview = { frame: null, link: null, hover: {x:0, y:0}, timer: 0, cacheCSS: {}, stylesOverride: '', }; const rxPreviewable = getURLregexForMatchedSites(); const thisPageUrls = getPageBaseUrls(location.href); initStyles(); initPolyfills(); setMutationHandler('a', onLinkAdded, {processExisting: true}); setTimeout(cleanupCache, 10000); /**************************************************************/ function onLinkAdded(links) { for (let i = 0, link; (link = links[i++]); ) { if (isLinkPreviewable(link)) { link.removeAttribute('title'); $on('mouseover', link, onLinkHovered); } } } function onLinkHovered(e) { if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) return; preview.link = this; $on('mousemove', this, onLinkMouseMove); $on('mouseout', this, abortPreview); $on('mousedown', this, 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; $off('mousemove', link, 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); preview.frame.contentWindow.postMessage('SEpreviewHidden', '*'); fadeOut(preview.frame); } }, PREVIEW_DELAY * 3, this); if (xhr) xhr.abort(); } function releaseLinkListeners(link) { $off('mousemove', link, onLinkMouseMove); $off('mouseout', link, abortPreview); $off('mousedown', link, abortPreview); clearTimeout(preview.timer); } function fadeOut(element, transition) { if (transition) { element.style.transition = typeof transition == 'number' ? `opacity ${transition}s ease-in-out` : transition; return setTimeout(fadeOut, 0, element); } element.style.opacity = 0; $on('transitionend', element, function remove() { $off('transitionend', element, remove); if (+element.style.opacity === 0) element.style.display = 'none'; }); } function downloadPreview(url) { let cached = readCache(url); if (cached) showPreview(cached); else { xhr = GM_xmlhttpRequest({ method: 'GET', url: httpsUrl(url), onload: r => { let html = r.responseText; let lastActivity = showPreview({finalUrl: r.finalUrl, html}); let inactiveDays = Math.max(0, (Date.now() - lastActivity) / (24 * 3600 * 1000)); let cacheDuration = CACHE_DURATION * Math.pow(Math.log(inactiveDays + 1) + 1, 2); writeCache({url, finalUrl: r.finalUrl, html, cacheDuration}); }, }); } } function showPreview({finalUrl, html, doc}) { doc = doc || new DOMParser().parseFromString(html, 'text/html'); if (!doc || !doc.head) { error('no HEAD in the document received for', finalUrl); return; } if (!$('base', doc)) doc.head.insertAdjacentHTML('afterbegin', `<base href="${finalUrl}">`); const answerIdMatch = finalUrl.match(/questions\/\d+\/[^\/]+\/(\d+)/); const isQuestion = !answerIdMatch; const postId = answerIdMatch ? '#answer-' + answerIdMatch[1] : '#question'; const post = $(postId + ' .post-text', doc); if (!post) return error('No parsable post found', doc); const isDeleted = post.closest('.deleted-answer'); const title = $('meta[property="og:title"]', doc).content; const status = isQuestion && !$('.question-status', post) && $('.question-status', doc); const comments = $(`${postId} .comments`, doc); const commentsHidden = +$('tbody', comments).dataset.remainingCommentsCount; const commentsShowLink = commentsHidden && $(`${postId} .js-show-link.comments-link`, doc); const finalUrlOfQuestion = getCacheableUrl(finalUrl); const lastActivity = +doc.body.getAttribute('SEpreview-lastActivity') || tryCatch(() => new Date($('.lastactivity-link', doc).title).getTime()) || Date.now(); if (lastActivity) doc.body.setAttribute('SEpreview-lastActivity', lastActivity); $$remove('script', doc); // underline previewable links for (let link of $$('a:not(.SEpreviewable)', doc)) { if (rxPreviewable.test(link.href)) { link.removeAttribute('title'); link.classList.add('SEpreviewable'); } } if (!preview.frame) { preview.frame = document.createElement('iframe'); preview.frame.id = 'SEpreview'; document.body.appendChild(preview.frame); } let pvDoc, pvWin; preview.frame.setAttribute('SEpreviewType', isDeleted ? 'deleted' : isQuestion ? 'question' : 'answer'); onFrameReady(preview.frame).then( () => { pvDoc = preview.frame.contentDocument; pvWin = preview.frame.contentWindow; initPolyfills(pvWin); }) .then(addStyles) .then(render) .then(show); return lastActivity; function addStyles() { const pvDoc = preview.frame.contentDocument; const SEpreviewStyles = $replaceOrCreate({ id: 'SEpreviewStyles', tag: 'style', parent: pvDoc.head, className: 'SEpreviewReuse', innerHTML: preview.stylesOverride, }); $replaceOrCreate($$('style, link[rel="stylesheet"]', doc).map(e => e.localName == 'style' ? { id: 'SEpreview' + e.innerHTML.replace(/\W+/g, '').length, tag: 'style', before: SEpreviewStyles, className: 'SEpreviewReuse', innerHTML: e.innerHTML, } : { id: e.href.replace(/\W+/g, ''), tag: 'link', before: SEpreviewStyles, className: 'SEpreviewReuse', href: e.href, rel: 'stylesheet', }) ); return onStyleSheetsReady($$('link[rel="stylesheet"]', pvDoc)); } function render() { pvDoc.body.setAttribute('SEpreviewType', preview.frame.getAttribute('SEpreviewType')); $replaceOrCreate([{ // title id: 'SEpreviewTitle', tag: 'a', parent: pvDoc.body, className: 'SEpreviewable', href: finalUrlOfQuestion, textContent: title, }, { // vote count, date, views# id: 'SEpreviewMeta', parent: pvDoc.body, innerHTML: [ renderVotes(post.closest('table')).replace(/(\S+) (\S+)/, '<b>$1</b> $2, '), isQuestion ? $$('#qinfo tr', doc) .map(row => $$('.label-key', row).map($text).join(' ')) .join(', ').replace(/^((.+?) (.+?), .+?), .+? \3$/, '$1') : [...$$('.user-action-time', post.closest('.answer'))] .reverse().map($text).join(', ') ].join('') }, { // content wrapper id: 'SEpreviewBody', parent: pvDoc.body, className: isDeleted ? 'deleted-answer' : '', children: [post.parentElement, comments, commentsShowLink, status], }]); renderCode(); // render bottom shelf const answers = $$('.answer', doc); if (answers.length > (isQuestion ? 0 : 1)) { $replaceOrCreate({ id: 'SEpreviewAnswers', parent: pvDoc.body, innerHTML: 'Answers: ' + answers.map(renderShelfAnswer).join(''), }); } else $$remove('#SEpreviewAnswers', pvDoc); // cleanup leftovers from previously displayed post $$('head style, head link, body script', pvDoc).forEach(e => { if (e.classList.contains('SEpreviewReuse')) e.classList.remove('SEpreviewReuse'); else e.remove(); }); } function renderCode() { const codeBlocks = $$('pre code', pvDoc); if (codeBlocks.length) { codeBlocks.forEach(e => e.parentElement.classList.add('prettyprint')); if (!pvWin.StackExchange) { pvWin.StackExchange = {}; let script = $scriptIn(pvDoc.head); script.text = 'StackExchange = {}'; script = $scriptIn(pvDoc.head); script.src = 'https://cdn.sstatic.net/Js/prettify-full.en.js'; script.setAttribute('onload', 'prettyPrint()'); } else $scriptIn(pvDoc.body).text = 'prettyPrint()'; } } function renderShelfAnswer(e, index) { const shortUrl = $('.short-link', e).href.replace(/(\d+)\/\d+/, '$1'); const extraClasses = (e.matches(postId) ? ' SEpreviewed' : '') + (e.matches('.deleted-answer') ? ' deleted-answer' : ''); const author = $('.post-signature:last-child', e); return `<a href="${shortUrl}" SEpreviewFullUrl="${finalUrlOfQuestion + '/' + shortUrl.match(/\/(\d+)/)[1]}" title="${$text('.user-details a', author) + ' (rep '+$text('.reputation-score', author) + ')\n' + $text('.user-action-time', author) + renderVotes(author)}" class="SEpreviewable${extraClasses}" >${index + 1}</a>`; } function renderVotes(post) { return $text('.vote-count-post', post) .replace(/-?\d+/, s => s == '0' ? '' : '\n' + s + ' vote' + (+s > 1 ? 's' : '')); } function show() { pvDoc.onmouseover = retainMainScrollPos; pvDoc.onclick = interceptLinks; pvWin.onmessage = e => { if (e.data == 'SEpreviewHidden') { pvWin.onmessage = null; pvDoc.onmouseover = null; pvDoc.onclick = null; } }; $('#SEpreviewBody', pvDoc).scrollTop = 0; preview.frame.style.opacity = 1; preview.frame.style.display = ''; } function retainMainScrollPos(e) { let scrollPos = {x:scrollX, y:scrollY}; $on('scroll', preventScroll); $on('mouseover', releaseScrollLock); function preventScroll(e) { scrollTo(scrollPos.x, scrollPos.y); } function releaseScrollLock(e) { $off('mouseout', releaseScrollLock); $off('scroll', preventScroll); } } function interceptLinks(e) { const link = e.target.closest('a'); if (!link) return; if (link.matches('.js-show-link.comments-link')) { fadeOut(link, 0.5); loadComments(); } else if (e.button || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || !link.matches('.SEpreviewable')) return (link.target = '_blank'); else if (link.matches('#SEpreviewAnswers a, a#SEpreviewTitle')) showPreview({ finalUrl: link.getAttribute('SEpreviewFullUrl') || link.href, doc }); else downloadPreview(link.getAttribute('SEpreviewFullUrl') || link.href); e.preventDefault(); } function loadComments() { GM_xmlhttpRequest({ method: 'GET', url: new URL(finalUrl).origin + '/posts/' + comments.id.match(/\d+/)[0] + '/comments', onload: r => { let tbody = $(`#${comments.id} tbody`, pvDoc); let oldIds = new Set([...tbody.rows].map(e => e.id)); tbody.innerHTML = r.responseText; for (let tr of tbody.rows) if (!oldIds.has(tr.id)) tr.classList.add('new-comment-highlight'); }, }); } } function getCacheableUrl(url) { // strips querys and hashes and anything after the main part https://site/questions/####/title/ return url .replace(/(\/q(?:uestions)?\/\d+\/[^\/]+).*/, '$1') .replace(/(\/a(?:nswers)?\/\d+).*/, '$1') .replace(/[?#].*$/, ''); } function readCache(url) { keyUrl = getCacheableUrl(url); const meta = (localStorage[keyUrl] || '').split('\t'); const expired = +meta[0] < Date.now(); const finalUrl = meta[1] || url; const keyFinalUrl = meta[1] ? getCacheableUrl(finalUrl) : keyUrl; return !expired && { finalUrl, html: LZString.decompressFromUTF16(localStorage[keyFinalUrl + '\thtml']), }; } function writeCache({url, finalUrl, html, cacheDuration = CACHE_DURATION, cleanupRetry}) { // keyUrl=expires // redirected keyUrl=expires+finalUrl, and an additional entry keyFinalUrl=expires is created // keyFinalUrl\thtml=html cacheDuration = Math.max(CACHE_DURATION, Math.min(0xDEADBEEF, Math.floor(cacheDuration))); finalUrl = finalUrl.replace(/[?#].*/, ''); const keyUrl = getCacheableUrl(url); const keyFinalUrl = getCacheableUrl(finalUrl); const expires = Date.now() + cacheDuration; if (!tryCatch(() => localStorage[keyFinalUrl + '\thtml'] = LZString.compressToUTF16(html))) { if (cleanupRetry) return error('localStorage write error'); cleanupCache({aggressive: true}); setIimeout(writeCache, 0, {url, finalUrl, html, cacheDuration, cleanupRetry: true}); } localStorage[keyFinalUrl] = expires; if (keyUrl != keyFinalUrl) localStorage[keyUrl] = expires + '\t' + finalUrl; setTimeout(() => { [keyUrl, keyFinalUrl, keyFinalUrl + '\thtml'].forEach(e => localStorage.removeItem(e)); }, cacheDuration + 1000); } function cleanupCache({aggressive = false} = {}) { Object.keys(localStorage).forEach(k => { if (k.match(/^https?:\/\/[^\t]+$/)) { let meta = (localStorage[k] || '').split('\t'); if (+meta[0] > Date.now() && !aggressive) return; if (meta[1]) localStorage.removeItem(meta[1]); localStorage.removeItem(`${meta[1] || k}\thtml`); localStorage.removeItem(k); } }); } function onFrameReady(frame) { if (frame.contentDocument.readyState == 'complete') return Promise.resolve(); else return new Promise(resolve => { $on('load', frame, function onLoad() { $off('load', frame, onLoad); resolve(); }); }); } function onStyleSheetsReady(linkElements) { return new Promise(function retry(resolve) { if (linkElements.every(e => e.sheet && e.sheet.href == e.href)) resolve(); else setTimeout(retry, 0, resolve); }); } 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 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 getPageBaseUrls(url) { const base = httpsUrl((url.match(rxPreviewable) || [])[0]); return base ? { base, short: base.replace('/questions/', '/q/'), } : {}; } function httpsUrl(url) { return (url || '').replace(/^http:/, 'https:'); } function $(selector, node = document) { return node.querySelector(selector); } function $$(selector, node = document) { return node.querySelectorAll(selector); } function $text(selector, node = document) { const e = typeof selector == 'string' ? node.querySelector(selector) : selector; return e ? e.textContent.trim() : ''; } function $$remove(selector, node = document) { node.querySelectorAll(selector).forEach(e => e.remove()); } function $appendChildren(newParent, elements) { const doc = newParent.ownerDocument; for (let e of elements) if (e) newParent.appendChild(e.ownerDocument == doc ? e : doc.importNode(e, true)); } function $replaceOrCreate(options) { if (options.length && typeof options[0] == 'object') return [].map.call(options, $replaceOrCreate); const doc = (options.parent || options.before).ownerDocument; const el = doc.getElementById(options.id) || doc.createElement(options.tag || 'div'); for (let key of Object.keys(options)) { switch (key) { case 'tag': case 'parent': case 'before': break; case 'children': if (el.children.length) el.innerHTML = ''; $appendChildren(el, options[key]); break; default: const value = options[key]; if (key in el && el[key] != value) el[key] = value; } } if (!el.parentElement) (options.parent || options.before.parentElement).insertBefore(el, options.before); return el; } function $scriptIn(element) { return element.appendChild(element.ownerDocument.createElement('script')); } function $on(eventName, ...args) { // eventName, selector, node, callback, options // eventName, selector, callback, options // eventName, node, callback, options // eventName, callback, options const selector = typeof args[0] == 'string' ? args[0] : null; const node = args[0] instanceof Node ? args[0] : args[1] instanceof Node ? args[1] : document; const callback = args[typeof args[0] == 'function' ? 0 : typeof args[1] == 'function' ? 1 : 2]; const options = args[args.length - 1] != callback ? args[args.length - 1] : undefined; const method = this == 'removeEventListener' ? this : 'addEventListener'; (selector ? node.querySelector(selector) : node)[method](eventName, callback, options); } function $off(eventName, ...args) { $on.apply('removeEventListener', arguments); } function log(...args) { console.log(GM_info.script.name, ...args); } function error(...args) { console.error(GM_info.script.name, ...args); } function tryCatch(fn) { try { return fn() } catch(e) {} } function initPolyfills(context = window) { for (let method of ['forEach', 'filter', 'map', 'every', context.Symbol.iterator]) if (!context.NodeList.prototype[method]) context.NodeList.prototype[method] = context.Array.prototype[method]; } 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[SEpreviewType="answer"] { border-color: rgb(${COLORS.answer.backRGB}); } #SEpreview[SEpreviewType="deleted"] { border-color: rgba(${COLORS.deleted.backRGB}, 0.65); } `); preview.stylesOverride = ` body, html { min-width: unset!important; box-shadow: none!important; padding: 0!important; margin: 0!important; } html, body { background: unset!important;; } body { display: flex; flex-direction: column; height: 100vh; } a.SEpreviewable:not(#SEpreviewTitle) { text-decoration: underline !important; } #SEpreviewTitle { all: unset; display: block; padding: 20px 30px; font-weight: bold; font-size: 18px; line-height: 1.2; background-color: rgba(${COLORS.question.backRGB}, 0.37); color: ${COLORS.question.fore}; cursor: pointer; } #SEpreviewTitle:hover { text-decoration: underline; } #SEpreviewMeta { position: absolute; top: .5ex; left: 30px; opacity: 0.5; } #SEpreviewTitle:hover + #SEpreviewMeta { opacity: 1.0; } #SEpreviewBody { padding: 30px!important; overflow: auto; flex-grow: 2; } #SEpreviewBody .post-menu { display: none!important; } #SEpreviewBody > .question-status { margin: -10px -30px -30px; padding-left: 30px; } #SEpreviewBody > .question-status h2 { font-weight: normal; } #SEpreviewBody > a + .question-status { margin-top: 20px; } #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); } /* answer */ body[SEpreviewType="answer"] #SEpreviewTitle { background-color: rgba(${COLORS.answer.backRGB}, 0.37); color: ${COLORS.answer.fore}; } body[SEpreviewType="answer"] #SEpreviewBody::-webkit-scrollbar { background-color: rgba(${COLORS.answer.backRGB}, 0.1); } body[SEpreviewType="answer"] #SEpreviewBody::-webkit-scrollbar-thumb { background-color: rgba(${COLORS.answer.backRGB}, 0.2); } body[SEpreviewType="answer"] #SEpreviewBody::-webkit-scrollbar-thumb:hover { background-color: rgba(${COLORS.answer.backRGB}, 0.3); } body[SEpreviewType="answer"] #SEpreviewBody::-webkit-scrollbar-thumb:active { background-color: rgba(${COLORS.answer.backRGB}, 0.75); } /* deleted */ body[SEpreviewType="deleted"] #SEpreviewTitle { background-color: rgba(${COLORS.deleted.backRGB}, 0.37); color: ${COLORS.deleted.fore}; } body[SEpreviewType="deleted"] #SEpreviewBody::-webkit-scrollbar { background-color: rgba(${COLORS.deleted.backRGB}, 0.1); } body[SEpreviewType="deleted"] #SEpreviewBody::-webkit-scrollbar-thumb { background-color: rgba(${COLORS.deleted.backRGB}, 0.2); } body[SEpreviewType="deleted"] #SEpreviewBody::-webkit-scrollbar-thumb:hover { background-color: rgba(${COLORS.deleted.backRGB}, 0.3); } body[SEpreviewType="deleted"] #SEpreviewBody::-webkit-scrollbar-thumb:active { background-color: rgba(${COLORS.deleted.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.fore}; word-break: break-word; } #SEpreviewAnswers a { color: ${COLORS.answer.fore}; padding: .25ex .75ex; text-decoration: none; } #SEpreviewAnswers a.deleted-answer { color: ${COLORS.deleted.fore}; background: transparent; } #SEpreviewAnswers a:hover:not(.SEpreviewed) { text-decoration: underline; } #SEpreviewAnswers a.SEpreviewed { background-color: ${COLORS.answer.fore}; color: ${COLORS.answer.foreInv}; } /* deleted */ body[SEpreviewType="deleted"] #SEpreviewAnswers { border-top-color: rgba(${COLORS.deleted.backRGB}, 0.37); background-color: rgba(${COLORS.deleted.backRGB}, 0.37); color: ${COLORS.deleted.fore}; } body[SEpreviewType="deleted"] #SEpreviewAnswers a.SEpreviewed { background-color: ${COLORS.deleted.fore}; color: ${COLORS.deleted.foreInv}; } /********/ .delete-tag, .comment-actions td:last-child { display: none; } .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} } `; }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址