您需要先安装一个扩展,例如 篡改猴、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.2
- // @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://greasyfork.org/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 = 200;
- 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',
- },
- closed: {
- backRGB: '255, 206, 93',
- fore: 'rgb(204, 143, 0)',
- 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('SEpreview-hidden', '*');
- 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 isClosed = $('.question-originals-of-duplicate, .close-as-off-topic-status-list, .close-status-suffix', 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('SEpreview-type',
- isDeleted ? 'deleted' : isQuestion ? (isClosed ? 'closed' : '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 SEpreviewStyles = $replaceOrCreate({
- id: 'SEpreviewStyles',
- tag: 'style', parent: pvDoc.head, className: 'SEpreview-reuse',
- 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: 'SEpreview-reuse',
- innerHTML: e.innerHTML,
- } : {
- id: e.href.replace(/\W+/g, ''),
- tag: 'link', before: SEpreviewStyles, className: 'SEpreview-reuse',
- href: e.href, rel: 'stylesheet',
- })
- );
- return onStyleSheetsReady($$('link[rel="stylesheet"]', pvDoc));
- }
- function render() {
- pvDoc.body.setAttribute('SEpreview-type', preview.frame.getAttribute('SEpreview-type'));
- $replaceOrCreate([{
- // title
- id: 'SEpreview-title', tag: 'a',
- parent: pvDoc.body, className: 'SEpreviewable',
- href: finalUrlOfQuestion,
- textContent: title,
- }, {
- // vote count, date, views#
- id: 'SEpreview-meta',
- 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: 'SEpreview-body',
- 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: 'SEpreview-answers',
- parent: pvDoc.body,
- innerHTML: 'Answers: ' + answers.map(renderShelfAnswer).join(''),
- });
- } else
- $$remove('#SEpreview-answers', pvDoc);
- // cleanup leftovers from previously displayed post and foreign elements not injected by us
- $$('style, link, body script, html > *:not(head):not(body)', pvDoc).forEach(e => {
- if (e.classList.contains('SEpreview-reuse'))
- e.classList.remove('SEpreview-reuse');
- 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}"
- SEpreview-fullUrl="${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 == 'SEpreview-hidden') {
- pvWin.onmessage = null;
- pvDoc.onmouseover = null;
- pvDoc.onclick = null;
- }
- };
- $('#SEpreview-body', 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('#SEpreview-answers a, a#SEpreview-title'))
- showPreview({
- finalUrl: link.getAttribute('SEpreview-fullUrl') || link.href,
- doc
- });
- else
- downloadPreview(link.getAttribute('SEpreview-fullUrl') || 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-width: 8px;
- border-style: solid;
- }
- `
- + Object.keys(COLORS).map(s => `
- #SEpreview[SEpreview-type="${s}"] {
- border-color: rgb(${COLORS[s].backRGB});
- }
- `).join('')
- );
- 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(#SEpreview-title) {
- text-decoration: underline !important;
- }
- #SEpreview-title {
- all: unset;
- display: block;
- padding: 20px 30px;
- font-weight: bold;
- font-size: 18px;
- line-height: 1.2;
- cursor: pointer;
- }
- #SEpreview-title:hover {
- text-decoration: underline;
- }
- #SEpreview-meta {
- position: absolute;
- top: .5ex;
- left: 30px;
- opacity: 0.5;
- }
- #SEpreview-title:hover + #SEpreview-meta {
- opacity: 1.0;
- }
- #SEpreview-body {
- padding: 30px!important;
- overflow: auto;
- flex-grow: 2;
- }
- #SEpreview-body .post-menu {
- display: none!important;
- }
- #SEpreview-body > .question-status {
- margin: -10px -30px -30px;
- padding-left: 30px;
- }
- #SEpreview-body > .question-status h2 {
- font-weight: normal;
- }
- #SEpreview-body > a + .question-status {
- margin-top: 20px;
- }
- #SEpreview-answers {
- 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;
- }
- #SEpreview-answers a {
- color: ${COLORS.answer.fore};
- padding: .25ex .75ex;
- text-decoration: none;
- }
- #SEpreview-answers a.deleted-answer {
- color: ${COLORS.deleted.fore};
- background: transparent;
- }
- #SEpreview-answers a:hover:not(.SEpreviewed) {
- text-decoration: underline;
- }
- #SEpreview-answers a.SEpreviewed {
- background-color: ${COLORS.answer.fore};
- color: ${COLORS.answer.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}
- }
- `
- + Object.keys(COLORS).map(s => `
- body[SEpreview-type="${s}"] #SEpreview-title {
- background-color: rgba(${COLORS[s].backRGB}, 0.37);
- color: ${COLORS[s].fore};
- }
- body[SEpreview-type="${s}"] #SEpreview-body::-webkit-scrollbar {
- background-color: rgba(${COLORS[s].backRGB}, 0.1); }
- body[SEpreview-type="${s}"] #SEpreview-body::-webkit-scrollbar-thumb {
- background-color: rgba(${COLORS[s].backRGB}, 0.2); }
- body[SEpreview-type="${s}"] #SEpreview-body::-webkit-scrollbar-thumb:hover {
- background-color: rgba(${COLORS[s].backRGB}, 0.3); }
- body[SEpreview-type="${s}"] #SEpreview-body::-webkit-scrollbar-thumb:active {
- background-color: rgba(${COLORS[s].backRGB}, 0.75); }
- `).join('')
- + ['deleted', 'closed'].map(s => `
- body[SEpreview-type="${s}"] #SEpreview-answers {
- border-top-color: rgba(${COLORS[s].backRGB}, 0.37);
- background-color: rgba(${COLORS[s].backRGB}, 0.37);
- color: ${COLORS[s].fore};
- }
- body[SEpreview-type="${s}"] #SEpreview-answers a.SEpreviewed {
- background-color: ${COLORS[s].fore};
- color: ${COLORS[s].foreInv};
- }
- `).join('')
- }