您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Save hashes of displayed comments locally and mark new ones when displayed for the first time
当前为
// ==UserScript== // @name HN: style unread content // @description Save hashes of displayed comments locally and mark new ones when displayed for the first time // @namespace myfonj // @match https://news.ycombinator.com/* // @grant none // @version 1.0.3 // @author myfonj // ==/UserScript== // https://gf.qytechs.cn/en/scripts/423969/versions/new // kinda sorta configuration const SELECTOR = '.commtext, .storylink'; const VIEWPORT_EXPOSITION_DURATION_UNTIL_READ = 900; const CSS_CLASSES = { unread: 'new', read: 'read', old: 'old' }; // Styling. Very lame for now. const CSS_STR = ` .${CSS_CLASSES.unread} { border-right: 2px solid #3F36; display: block; padding-right: 1em; } .${CSS_CLASSES.read} { border-right: 2px solid #0F03; display: block; padding-right: 1em; } .${CSS_CLASSES.old} { /* nothing */ } `; // base64 'SHA-1' digest hash = 28 characters; most probably ending with '=' always (?) // could be used for splitting pergaps, but not used for now. const HASH_DIGEST_ALGO = 'SHA-1'; // actual code, yo document.head.appendChild(document.createElement('style')).textContent = CSS_STR; // TODO mutation observer for client-side rendered pages (not HNs case) const ELS_TO_WATCH = document.querySelectorAll(SELECTOR); const MAP_EL_HASH = new WeakMap(); const MAP_EL_TIMEOUT = new WeakMap(); const LS_KEY = 'displayed_hashes_' + HASH_DIGEST_ALGO; const SEEN_HASH_LIST = new Set((localStorage.getItem(LS_KEY) || '').split(',')); const VIEWPORT_ENTRY_CHECKER = (entry) => { const TGT = entry.target; if (entry.isIntersecting) { // entered viewport if (MAP_EL_TIMEOUT.get(TGT)) { // already measuring - quick re-entry return } // measure time in viewport MAP_EL_TIMEOUT.set( TGT, window.setTimeout(function () { if (MAP_EL_TIMEOUT.get(TGT)) { // HA! STILL in viewport! // mark as read TGT.classList.remove(CSS_CLASSES.unread); TGT.classList.add(CSS_CLASSES.read); SEEN_HASH_LIST.add(MAP_EL_HASH.get(TGT)) MAP_EL_TIMEOUT.delete(TGT); // not interested in this element anymore VIEWPORT_OBSERVER.unobserve(TGT); // TODO move the persistence to unload and/or blur for fewer LS writes localStorage.setItem( LS_KEY, Array.from(SEEN_HASH_LIST).join(',') ); } }, VIEWPORT_EXPOSITION_DURATION_UNTIL_READ) ); } else { // left viewport MAP_EL_TIMEOUT.delete(TGT); } } const VIEWPORT_OBSERVER = new IntersectionObserver( (entries, observer) => { entries.forEach(_ => VIEWPORT_ENTRY_CHECKER(_)); }, { root: null, rootMargin: "-9%", // TODO use "lines" height here? threshold: 0 } ); ELS_TO_WATCH.forEach(async el => { const hash = await makeHash(el.textContent); if (SEEN_HASH_LIST.has(hash)) { el.classList.add(CSS_CLASSES.old); return; } el.classList.add(CSS_CLASSES.unread); MAP_EL_HASH.set(el, hash); VIEWPORT_OBSERVER.observe(el); }); async function makeHash (input) { return btoa( String.fromCharCode.apply( null, new Uint8Array( await crypto.subtle.digest( HASH_DIGEST_ALGO, (new TextEncoder()).encode(input) ) ) ) ); };
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址