您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
filter notifications by type
当前为
// ==UserScript== // @name dA_FilterNotifications // @namespace http://tampermonkey.net/ // @version 0.5 // @description filter notifications by type // @author Dediggefedde // @match https://www.deviantart.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=deviantart.com // @grant GM.addStyle // @grant GM.getValue // @grant GM.setValue // ==/UserScript== //@ts-check /** Adds a bar on deviantart /notification page and sidebar The bar provides buttons to filter and search within displayed elements TODO: trigger fill if filter reduces elements */ (function() { "use strict"; GM.addStyle(` #dA_FN_bar{display:flex;position: sticky;top: 0;z-index: 1;background-color: var(--g-bg-primary);} #dA_FN_bar>div{cursor:pointer;user-select:none} #dA_FN_bar>div:hover{filter: brightness(120%) saturate(150%) invert(20%);} #dA_FN_filterText{height: 1.2em;} .da_FN_selected{border:1px solid red;} .dA_FN_hidden{display:none;} .dA_FN_markHidden .dA_FN_hidden{display:block;} .dA_FN_markHidden .dA_FN_hidden button{ background-image:repeating-linear-gradient(45deg, #ffffff3b 0%, white 2%, #fdfdfd3b 2%,#c1c1c100 4%, white 4%);} .dA_FN_filtered{display:none;} #dA_FN_HideSel[active='2'] ellipse { fill: lightgray;}/*2: hiding=grey*/ #dA_FN_HideSel[active='2'] ellipse[role='iris'] { fill: lightgray;stroke:lightgray}/*2: hiding=grey*/ #dA_FN_HideSel[active='0'] ellipse[role='iris'] { fill: lightgray;stroke:red} /*0: hide selected=red*/ .da_fN_notSelect{user-select:none!important} `); //const itemClasses = ["_375AY", "_2TKoM", "_3PCaz", "_2VY4V"]; //possible classes for notification list item div const itemClassXPath = "//section//div[@data-bucket]/parent::div"; let itemClass = "_375AY"; const filterInterval = 200; //ms let filterTimer; /**@type {HTMLElement} */ let cont = null; //container of items let selRec = { left: 0, top: 0, width: 0, height: 0 }; //div element of selection rectangle let selAnc = { x: 0, y: 0 }; //initial point where you started selecting let dragSel = false; //selection rectangle visible //button images const commBtnSvg = '<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="evenodd" d="M20 3H6.414a1 1 0 00-.707.293L3.293 5.707A1 1 0 003 6.414V16a1 1 0 001 1h3v3a1 1 0 001 1h1.5a1 1 0 00.8-.4L13 17h4.586a1 1 0 00.707-.293l2.414-2.414a1 1 0 00.293-.707V4a1 1 0 00-1-1z"></path></svg>'; const llamaBtnSvg = '<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><path d="M17 5v7h1v3h-1v6h-3v-3h-1v3H9v-1H8v-3H7v-2H6v-2h1v-1h1v-1h4V5h-1V4h1V3h2v1h1V3h2v1h1v1h-1z"></path></g></svg>'; const devBtnSvg = '<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18 3a1 1 0 011 1v15.674a1 1 0 01-1.275.962L12 19l-5.725 1.636A1 1 0 015 19.674V4a1 1 0 011-1h12zm-3 3h-1.763l-.175.183-.832 1.635-.262.182H9v2.497h1.632l.145.181L9 14.182V16h1.763l.176-.183.831-1.635.262-.182H15v-2.497h-1.632l-.145-.183L15 7.818V6z" fill-rule="evenodd"></path></svg>'; const hideBtn = '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 250" stroke-width="20"> <defs> <radialGradient id="c1" cx="0.5" cy="0.5" r="0.5"> <stop offset="0" stop-color="#ffffff" /> <stop offset=".5" stop-color="hsl(40, 60%, 60%)" /> <stop offset="1" stop-color="#3dff3d" /> </radialGradient> </defs> <ellipse role="sclera" cx="250" cy="125" rx="200" ry="100" fill="white"/> <ellipse role="iris" cx="250" cy="125" rx="95" ry="95" stroke="black" fill="url(#c1)"/> <ellipse role="pupil" cx="250" cy="125" rx="50" ry="50" stroke="none" fill="black"/> <ellipse role="light" cx="200" cy="80" rx="50" ry="50" stroke="none" fill="#fffffaee"/> <ellipse role="outline" cx="250" cy="125" rx="200" ry="100" stroke="black" fill="none"/> </svg>' const fm = { show: 0, hide: 1, only: 2 }; //filter mode //"011-1h16zm-1 2H5v14h14V5zm-4" part of SVG in Journal icon //"01.68-.182l.862.505c.2" part of SVG in Shout icon const ftext = { //text used as regexp for each filter yourComment: ".*Your comment.*", yourLlama: ".*(Your (Llama badges|transactions|mentions|watchers))|(011-1h16zm-1 2H5v14h14V5zm-4)|(01.68-.182l.862.505c.2).*", yourDevs: "<img", text: ".*##ftext##.*" }; let filter = { //applied filter settings. text regexp /i. yourComment: fm.show, yourLlama: fm.show, yourDevs: fm.show, text: "", version: 0.1 }; let showHidden = false; //disable custom hidden buckets let hideBuckets = new Set(); //list of buckets to hide from view let shiftPressed = false; //shift pressed to prevent selection function improveLayout() { let sty = document.getElementById("dA_FN_improveCSS"); if (sty == null) { let sty = document.createElement("style"); sty.id = 'dA_FN_improveCSS'; sty.innerHTML = ` div[data-nc] * {visibility: visible;} /* buttons do not vanish*/ div[data-nc] button[data-hook="user_watch_button"] {display: none;} /*removes watch/unwatch buttons*/ [aria-label="Remove"] svg:hover {fill: red !important;} /*unify remove buttons hover color*/ div[data-nc*="favecollect"] > section > div > div:nth-child(2) {display: none;} /*remove activity gallery for new favourites*/ div[data-nc*="new_watcher"] > section > div > div:nth-child(2) {display: none;} /*remove activity gallery for watchers*/ div[data-nc*="badge_given"] > section > div > div:nth-child(2) {display: none;} /*remove activity gallery for Llamas*/ [role="presentation"] {display: none;} /*remove background image from deviation fullview*/ a[data-hook="deviation_link"] img {object-fit: contain;} /*make thumbnails contain full image*/ input[type="checkbox"] + div svg { fill: #0000;} input[type="checkbox"]:checked + div svg { fill: #000F;} ` document.head.appendChild(sty); } let it = document.evaluate("//h2[contains(., 'All Notifications')]/parent::div/parent::div/preceding-sibling::*").iterateNext(); //banner behind "all notification" if (it != null) { it.style.filter = "opacity(0.3) blur(1px)"; //option 1: less dominant title pic it.parentNode.parentNode.style.display = "none"; // option 2: no title pic it.parentNode.parentNode.nextSibling.style.marginTop = "20px"; } //it=document.querySelectorAll("div[data-nc] div[role]"); //option 1: vanishing buttons now have .dA_fN_notiSideBut //[...it].forEach(el=>{ // el.classList.add("dA_fN_notiSideBut"); // el.nextSibling.classList.add("dA_fN_notiSideBut"); // el.nextSibling.nextSibling.classList.add("dA_fN_notiSideBut"); //}); it = document.querySelector("div[data-nc]"); //no wasted space on large screens (old limit 1024px) if (it != null) it.parentNode.style.maxWidth = "unset"; cont.querySelector("section").style.display = "none"; // document.evaluate(`//div[@class='${itemClass}']//parent::div//section`).iterateNext().style.display="none"; // remove tutorial ads } /** * btn object, mode fm-mode, texts array of text [3] * @param {string} btnId DOM id of button to change title and color * @param {number} mode new mode of filter related to button * @param {Array<string>} texts title text array [show,hide,only] */ // function updateButton(btnId, mode, texts) { /** @type {HTMLElement} */ let btn = document.querySelector(btnId); if (btn == null) return; switch (mode) { case fm.hide: btn.style.fill = "grey"; break; case fm.only: btn.style.fill = "red"; break; case fm.show: btn.style.fill = ""; break; } btn.title = texts[mode]; } /** adapts GUI to settings */ function updateGUI() { updateButton("#dA_FN_hideYourCom", filter.yourComment, [ "Your comments are shown", "Your comments are hidden", "Only your comments are shown" ]); updateButton("#dA_FN_hideYourLlama", filter.yourLlama, [ "Your Correspondence is shown", "Your Correspondence are hidden", "Only your Correspondence are shown" ]); updateButton("#dA_FN_hideYourDevs", filter.yourDevs, [ "Your Deviations are shown", "Your Deviations are hidden", "Only your Deviations are shown" ]); /**@type {HTMLDivElement} */ let hidespan = document.querySelector("#dA_FN_HideSel"); if (document.getElementsByClassName("da_FN_selected").length == 0) { if (showHidden) { hidespan.title = "Hide Hidden"; hidespan.setAttribute("active", "1"); cont.classList.add("dA_FN_markHidden"); } else { hidespan.title = "Show Hidden"; hidespan.setAttribute("active", "2"); cont.classList.remove("dA_FN_markHidden"); } } else { if (showHidden) { hidespan.title = "Unhide"; } else { hidespan.title = "Hide"; } hidespan.setAttribute("active", "0"); } } /** * Event Handler Filter Button Click * Iterates show-hide-only-show * updates GUI, applies filter * @param {*} ev Mouse Click Event * @param {*} filt related filter in fm */ function btnIterateStateClick(ev, filt) { filter[filt] = (filter[filt] + 1) % 3; //iterate: show-hide-only-show if (filter[filt] == fm.only) { filter.yourComment = fm.show; filter.yourDevs = fm.show; filter.yourLlama = fm.show; filter[filt] = fm.only; } updateGUI(); filterDOMList(); //apply filter saveSettings(); } /** @type {Array<HTMLElement>} */ let listEls = []; /** refreshes internal list of DOM elements */ function grabList() { listEls = Array.from(document.querySelectorAll(`.${itemClass}`)); } /** * Apply filter to DOM elements in listEls * Text filter uses regexp. * TODO: does not trigger "load more" when list gets smaller than sidebar * */ function filterDOMList() { /** @type {Array<HTMLElement>} */ let elsA = []; //to be filled with elements to hide if (listEls.length == 0) return; listEls.forEach(el => { el.classList.remove("dA_FN_filtered"); el.classList.remove("dA_FN_hidden"); }); //iterate through filters, Or-Connect list of elements to hide, fill elsA (iterateNext fails if DOM is changed here) Object.entries(filter).forEach(fel => { if (fel[0] == "text" && fel[1] != "") { let reg = new RegExp(fel[1], "i"); elsA = elsA.concat( listEls.filter(lisEl => { return !reg.test(lisEl.innerHTML); }) ); } else { let reg = new RegExp(ftext[fel[0]]); elsA = elsA.concat( listEls.filter(lisEl => { let regtst = reg.test(lisEl.innerHTML); return (fel[1] == fm.only && !regtst) || (fel[1] == fm.hide && regtst); }) ); } }); //hide elements elsA.forEach((el) => { el.classList.add("dA_FN_filtered"); }); //hide custom hidden elements hideBuckets.forEach(el => { /**@type{HTMLElement} */ let bck = document.querySelector(`[data-bucket='${el}']`); if (bck == null) return; bck = bck.closest(`.${itemClass}`); if (bck != null) bck.classList.add("dA_FN_hidden"); }); //load more if end of list is visible var lastBnds = listEls[listEls.length - 1].getBoundingClientRect(); if (lastBnds.bottom < cont.clientHeight) { console.log("load more please!") //all shown, load of more required. //no idea how to trigger } } function isCollide(a, b, ov) { return !( ((a.top + a.height + ov) < (b.top)) || (a.top > (b.top + b.height + ov)) || ((a.left + a.width + ov) < b.left) || (a.left > (b.left + b.width + ov)) ); } /** attachs handlers for selection rectangle and selecting itemClass */ function applyDragSelectHandler() { //the rectangle selRec = document.createElement("div"); selRec.id = "dA_fM_select"; selRec.setAttribute("style", "display:none;position:absolute;z-index:1;top:0px;bottom:0px;width:0px;height:0px;background:#a008;"); document.body.append(selRec); let cntCl = `.${cont.className.replace(/\s+/gi,".")}`; //starting rectangle document.body.addEventListener("mousedown", (ev) => { //if (ev.target.closest(cntCl) == null) return; if (shiftPressed) return; //no selection when shift is pressed (text-select) if (ev.target.closest("#dA_FN_bar") != null) return; dragSel = true; selAnc.x = ev.clientX; selAnc.y = ev.clientY; selRec.style.left = selAnc.x + "px"; selRec.style.top = selAnc.y + "px"; updateSelection(ev); document.getElementById("root").classList.add("da_fN_notSelect"); }, false); //updating rectangle document.body.addEventListener("mousemove", (ev) => { ev.stopPropagation(); if (!dragSel) return; selRec.style.display = "block"; if (ev.clientX < selAnc.x) selRec.style.left = ev.clientX + "px"; selRec.style.width = Math.abs(ev.clientX - selAnc.x) + "px"; if (ev.clientY < selAnc.y) selRec.style.top = ev.clientY + "px"; selRec.style.height = Math.abs(ev.clientY - selAnc.y) + "px"; }, false); //stopping rectangle document.body.addEventListener("mouseup", (ev) => { dragSel = false; selRec.style.display = "none"; document.getElementById("root").classList.remove("da_fN_notSelect"); }, false); //updating selection document.body.addEventListener("mouseenter", updateSelection, true); document.body.addEventListener("mouseleave", updateSelection, true); document.body.addEventListener("keydown", (ev) => { shiftPressed = ev.shiftKey }, false); document.body.addEventListener("keyup", (ev) => { shiftPressed = ev.shiftKey }, false); } /** * checks collision with selection rectangle and updates selection list of .itemClass * @param {Event} ev */ function updateSelection(ev) { ev.stopPropagation(); if (!dragSel) return; let listels = Array.from(document.querySelectorAll(`.${itemClass}`)); let bndRec = selRec.getBoundingClientRect(); // if (bndRec.width == 0 && bndRec.height == 0) return; let elRec; listels.forEach(el => { elRec = el.getBoundingClientRect(); if (elRec.height == 0) return; if (isCollide(bndRec, elRec, 0)) { el.classList.add("da_FN_selected"); } else { el.classList.remove("da_FN_selected"); } }); listels = Array.from(document.querySelectorAll(`[data-nc]`)); listels.forEach(el => { elRec = el.getBoundingClientRect(); if (elRec.height == 0) return; if (isCollide(bndRec, elRec, 0)) { if (!el.classList.contains("da_FN_selected")) el.querySelector("input[type=checkbox]").parentNode.click(); el.classList.add("da_FN_selected"); } else { if (el.classList.contains("da_FN_selected")) el.querySelector("input[type=checkbox]").parentNode.click(); el.classList.remove("da_FN_selected"); } }); updateGUI(); } function saveSettings() { GM.setValue("filter", JSON.stringify(filter)); GM.setValue("hidden", JSON.stringify(Array.from(hideBuckets))); } function loadSettings() { GM.getValue("filter", null).then(ret => { let pr = JSON.parse(ret); if (pr != null && pr.version == "0.1") filter = pr; return GM.getValue("hidden", null); }).then(ret => { let pr = JSON.parse(ret); if (pr != null) hideBuckets = new Set(pr); }); } /** called periodically to insert GUI if not present. Site uses javascript navigation. */ function starter() { if (document.querySelector("#dA_FN_bar") != null) return; //already present let xEl = document.evaluate(itemClassXPath).iterateNext(); if (xEl == null) return; //no place to add filter itemClass = xEl.className; cont = document.querySelector(`.${itemClass}`).parentElement; //add elements. let bar = document.createElement("div"); //container bar bar.id = "dA_FN_bar"; let btnYourCom = document.createElement("div"); //button comments btnYourCom.id = "dA_FN_hideYourCom"; btnYourCom.innerHTML = commBtnSvg; btnYourCom.addEventListener("click", (ev) => { btnIterateStateClick(ev, "yourComment"); }, false); bar.appendChild(btnYourCom); let btnYourLlama = document.createElement("div"); //button correspondence btnYourLlama.id = "dA_FN_hideYourLlama"; btnYourLlama.innerHTML = llamaBtnSvg; btnYourLlama.addEventListener("click", (ev) => { btnIterateStateClick(ev, "yourLlama"); }, false); bar.appendChild(btnYourLlama); let btnYourDevs = document.createElement("div"); //button Deviations btnYourDevs.id = "dA_FN_hideYourDevs"; btnYourDevs.innerHTML = devBtnSvg; btnYourDevs.addEventListener("click", (ev) => { btnIterateStateClick(ev, "yourDevs"); }, false); bar.appendChild(btnYourDevs); let spanHide = document.createElement("div"); spanHide.id = "dA_FN_HideSel"; spanHide.innerHTML = hideBtn; spanHide.addEventListener("click", (ev) => { let selected = Array.from(document.getElementsByClassName("da_FN_selected")); if (selected.length > 0) { //hide mode selected.forEach(el => { let bck = el.querySelector("[data-bucket]"); if (bck == null) return; let bckID = bck.getAttribute("data-bucket"); if (showHidden) { hideBuckets.delete(bckID); } else { hideBuckets.add(bckID); } el.classList.remove("da_FN_selected"); }); } else { //show hidden mode showHidden = !showHidden; } clearTimeout(filterTimer); filterTimer = setTimeout(function() { filterDOMList(); }, filterInterval); saveSettings(); updateGUI(); }, false); bar.appendChild(spanHide); let editFilterTex = document.createElement("input"); //search Input editFilterTex.id = "dA_FN_filterText"; editFilterTex.type = "text"; editFilterTex.addEventListener("input", (ev) => { //throttle filterTimer to filterInterval in ms, apply filter filter.text = ev.target.value; clearTimeout(filterTimer); filterTimer = setTimeout(function() { filterDOMList(); }, filterInterval); saveSettings(); }, false); bar.appendChild(editFilterTex); xEl.parentNode.insertBefore(bar, xEl); //insert container bar.nextSibling.style.top = "85px"; grabList(); //prepare DOM list updateGUI(); //display filter setting filterDOMList(); improveLayout(); //scroll refreshes grablist and needs filter reapplied cont.addEventListener("scroll", function() { //throttle filterTimer to filterInterval in ms, apply filter if (document.getElementsByClassName(itemClass).length != listEls.length) grabList(); clearTimeout(filterTimer); filterTimer = setTimeout(function() { filterDOMList(); }, filterInterval); }, false); applyDragSelectHandler(); } //start script, //insert new elements periodically if not present. loadSettings(); setInterval(starter, 1000); })();