SE Preview on hover

Shows preview of the linked questions/answers on hover

目前為 2017-02-15 提交的版本,檢視 最新版本

// ==UserScript==
// @name           SE Preview on hover
// @description    Shows preview of the linked questions/answers on hover
// @version        0.1.8
// @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');
			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);
			preview.frame.contentWindow.postMessage('SEpreviewHidden', '*');
			fadeOut(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 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;
	element.addEventListener('transitionend', function remove() {
		element.removeEventListener('transitionend', 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 (!$(doc, 'base'))
		doc.head.insertAdjacentHTML('afterbegin', `<base href="${finalUrl}">`);

	const answerIdMatch = finalUrl.match(/questions\/.+?\/(\d+)/);
	const isQuestion = !answerIdMatch;
	const postId = answerIdMatch ? '#answer-' + answerIdMatch[1] : '#question';
	const post = $(doc, postId + ' .post-text');
	if (!post)
		return error('No parsable post found', doc);
	const isDeleted = post.closest('.deleted-answer');
	const title = $(doc, 'meta[property="og:title"]').content;
	const status = isQuestion && !$(post, '.question-status') && $(doc, '.question-status');
	const comments = $(doc, `${postId} .comments`);
	const commentsHidden = +$(comments, 'tbody').dataset.remainingCommentsCount;
	const commentsShowLink = commentsHidden && $(doc, `${postId} .js-show-link.comments-link`);

	const lastActivity = +doc.body.getAttribute('SEpreview-lastActivity')
		|| tryCatch(() => new Date($(doc, '.lastactivity-link').title).getTime())
		|| Date.now();
	if (lastActivity)
		doc.body.setAttribute('SEpreview-lastActivity', lastActivity);

	$$remove(doc, 'script');

	// underline previewable links
	for (let link of $$(doc, 'a:not(.SEpreviewable)')) {
		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);
	}

	preview.frame.setAttribute('SEpreviewType', isDeleted ? 'deleted' : isQuestion ? 'question' : 'answer');
	onFrameReady(preview.frame, addStyles);
	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($$(doc, 'style, link[rel="stylesheet"]').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',
			})
		);

		onStyleSheetsReady([...$$(pvDoc, 'link[rel="stylesheet"]')], render);
	}

	function render() {
		const finalUrlOfQuestion = getCacheableUrl(finalUrl);
		const pvDoc = preview.frame.contentDocument;
		pvDoc.body.setAttribute('SEpreviewType', preview.frame.getAttribute('SEpreviewType'));

		$replaceOrCreate([{
			id: 'SEpreviewTitle',
			tag: 'a', parent: pvDoc.body, className: 'SEpreviewable',
            href: isQuestion ? finalUrl : finalUrlOfQuestion,
			textContent: title,
		}, {
			id: 'SEpreviewBody',
			tag: 'div', parent: pvDoc.body, className: isDeleted ? 'deleted-answer' : '',
			children: [post.parentElement, comments, commentsShowLink, status],
		}]);

		const codeBlocks = $$(pvDoc, 'pre code');
		if (codeBlocks.length) {
			codeBlocks.forEach(e => e.parentElement.classList.add('prettyprint'));
			if (!preview.frame.contentWindow.StackExchange) {
				preview.frame.contentWindow.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()';
		}

		const answers = $$(doc, '.answer');
		if (answers.length > (isQuestion ? 0 : 1)) {
			$replaceOrCreate({
				id: 'SEpreviewAnswers',
				tag: 'div', parent: pvDoc.body,
				innerHTML: 'Answers:&nbsp;' + answers.map((e, index) => {
					const shortUrl = $(e, '.short-link').href.replace(/(\d+)\/\d+/, '$1');
					const extraClasses = (e.matches(postId) ? ' SEpreviewed' : '') +
					      (e.matches('.deleted-answer') ? ' deleted-answer' : '');
					const author = $(e, '.post-signature:last-child');
					return `<a href="${shortUrl}"
					    SEpreviewFullUrl="${finalUrlOfQuestion + '/' + shortUrl.match(/\/(\d+)/)[1]}"
						title="${$text(author, '.user-details a') +
						' (rep '+$text(author, '.reputation-score') + ')\n' +
					             $text(author, '.user-action-time') +
					             $text(author, '.vote-count-post').replace(/-?\d+/, s =>
					                  s == '0' ? '' : '\n' + s + ' vote' + (+s > 1 ? 's' : ''))}"
						class="SEpreviewable${extraClasses}"
					>${index + 1}</a>`;
				}).join(''),
			});
		} else
			$$remove(pvDoc, '#SEpreviewAnswers');

		[...$$(pvDoc.head, 'style, link'), ...$$(pvDoc.body, 'script')].forEach(e => {
			if (e.classList.contains('SEpreviewReuse'))
				e.classList.remove('SEpreviewReuse');
			else
				e.remove();
		});

		pvDoc.onmouseover = retainMainScrollPos;
		pvDoc.onclick = interceptLinks;
		preview.frame.contentWindow.onmessage = e => {
			if (e.data == 'SEpreviewHidden') {
				preview.frame.contentWindow.onmessage = null;
				pvDoc.onmouseover = null;
				pvDoc.onclick = null;
			}
		};

		$(pvDoc, '#SEpreviewBody').scrollTop = 0;
		preview.frame.style.opacity = 1;
		preview.frame.style.display = '';
	}

	function interceptLinks(e) {
		const link = e.target.closest('a');
		if (!link)
			return;
		if (link.matches('.js-show-link.comments-link')) {
			fadeOut(link, 0.5);
			downloadComments();
		}
		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 downloadComments() {
		GM_xmlhttpRequest({
			method: 'GET',
			url: new URL(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 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, callback, ...args) {
	if (frame.contentDocument.readyState == 'complete')
		return callback.call(frame, ...args);
	else
		frame.addEventListener('load', function onLoad() {
			frame.removeEventListener('load', onLoad);
			callback.call(frame, ...args);
		});
}

function onStyleSheetsReady(linkElements, callback, ...args) {
	if (linkElements.every(e => e.sheet && e.sheet.href == e.href))
		return callback(...args);
	else
		setTimeout(onStyleSheetsReady, 0, linkElements, callback, ...args);
}

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 $(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) {
	const 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 $appendTo(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);
	for (let key of Object.keys(options)) {
		switch (key) {
			case 'tag':
			case 'parent':
			case 'before':
				break;
			case 'children':
				if (el.children.length)
					el.innerHTML = '';
				$appendTo(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 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() {
	for (let method of ['forEach', 'filter', 'map', Symbol.iterator])
		if (!NodeList.prototype[method])
			NodeList.prototype[method] = 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 {
			text-decoration: underline !important;
		}
		#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.fore};
			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: -10px -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);
		}
/* 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};
		}
/********/
		.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或关注我们的公众号极客氢云获取最新地址