Greasy Fork

Greasy Fork is available in English.

dA_FilterNotifications

filter notifications by type

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         dA_FilterNotifications
// @namespace    http://tampermonkey.net/
// @version      0.9
// @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-nocheck
/**
	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}
#dA_FN_settings{width:30vw;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background-color:#edffea;display:grid;grid-template-columns: 80% auto;grid-template-rows: 3em repeat(5,2em) 7em 2em;	grid-gap: 10px;padding: 5px;border-radius: 10px;border: 1px ridge;box-shadow: 2px 2px 10px #777;align-items: center;user-select:none;}
#dA_FN_customCSS {grid-column: 1 /span 2;resize:none;height:100%;}
#dA_FN_settings .dA_FN_setBtn{grid-column: 1/span 2; text-align: center;}
#dA_FN_OKBtn {width: 100px;cursor: pointer;}
#dA_FN_settings input[type=checkbox]{cursor: pointer;}
#dA_FN_settings label{cursor: pointer;width:100%;}
#dA_FN_settings h3{grid-column:1 /span 2;font-weight: bold;text-align: center;font-size: larger;}
#dA_FN_cancelBtn{cursor: pointer;position:absolute;top:2px;right:2px;background-color:transparent;border:none;font-size:larger;}
#dA_FN_cancelBtn:hover{color: #ccc;}
#dA_FN_settingBtn{position: absolute;top: 2px;right: 2px;width: 20px;height: 20px;cursor: pointer;}
`);

	//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 ImgGear = '<svg  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.444057 20.232336" > <g transform="translate(-15.480352,-5.6695418)">  <g transform="matrix(0.26458333,0,0,0.26458333,25.702381,15.78571)"  style="fill:#000000">  <path  style="fill:#000000;stroke:#000000;stroke-width:1"  d="m 28.46196,-3.25861 4.23919,-0.48535 0.51123,0.00182 4.92206,1.5536 v 4.37708 l -4.92206,1.5536 -0.51123,0.00182 -4.23919,-0.48535 -1.40476,6.15466 4.02996,1.40204 0.45982,0.22345 3.76053,3.53535 -1.89914,3.94361 -5.1087,-0.73586 -0.4614,-0.22017 -3.60879,-2.2766 -3.93605,4.93565 3.02255,3.01173 0.31732,0.40083 1.8542,4.81687 -3.42214,2.72907 -4.2835,-2.87957 -0.32017,-0.39856 -2.26364,-3.61694 -5.68776,2.73908 1.41649,4.0249 0.11198,0.49883 -0.41938,5.14435 -4.26734,0.97399 -2.6099,-4.45294 -0.11554,-0.49801 -0.47013,-4.2409 h -6.31294 l -0.47013,4.2409 -0.11554,0.49801 -2.6099,4.45294 -4.26734,-0.97399 -0.41938,-5.14435 0.11198,-0.49883 1.41649,-4.0249 -5.68776,-2.73908 -2.26364,3.61694 -0.32017,0.39856 -4.2835,2.87957 -3.42214,-2.72907 1.8542,-4.81687 0.31732,-0.40083 3.02255,-3.01173 -3.93605,-4.93565 -3.60879,2.2766 -0.4614,0.22017 -5.1087,0.73586 -1.89914,-3.94361 3.76053,-3.53535 0.45982,-0.22345 4.02996,-1.40204 -1.40476,-6.15466 -4.23919,0.48535 -0.51123,-0.00182 -4.92206,-1.5536 v -4.37708 l 4.92206,-1.5536 0.51123,-0.00182 4.23919,0.48535 1.40476,-6.15466 -4.02996,-1.40204 -0.45982,-0.22345 -3.76053,-3.53535 1.89914,-3.94361 5.1087,0.73586 0.4614,0.22017 3.60879,2.2766 3.93605,-4.93565 -3.02255,-3.01173 -0.31732,-0.40083 -1.8542,-4.81687 3.42214,-2.72907 4.2835,2.87957 0.32017,0.39856 2.26364,3.61694 5.68776,-2.73908 -1.41649,-4.0249 -0.11198,-0.49883 0.41938,-5.14435 4.26734,-0.97399 2.6099,4.45294 0.11554,0.49801 0.47013,4.2409 h 6.31294 l 0.47013,-4.2409 0.11554,-0.49801 2.6099,-4.45294 4.26734,0.97399 0.41938,5.14435 -0.11198,0.49883 -1.41649,4.0249 5.68776,2.73908 2.26364,-3.61694 0.32017,-0.39856 4.2835,-2.87957 3.42214,2.72907 -1.8542,4.81687 -0.31732,0.40083 -3.02255,3.01173 3.93605,4.93565 3.60879,-2.2766 0.4614,-0.22017 5.1087,-0.73586 1.89914,3.94361 -3.76053,3.53535 -0.45982,0.22345 -4.02996,1.40204 z"  />  <circle  style="fill:#ffffff;stroke:#000000;stroke-width:1"  cx="0"  cy="0"  r="15" />  </g>  </g> </svg>';


	const SettingFormTmpl = `<h3>dA_FilterNotification Settings</h3>
		<label for="dA_FN_FilterSide">Show Sidebar</label><input type="checkbox" id="dA_FN_FilterSide"/>
		<label for="dA_FN_DragSelect">Drag to Select</label><input type="checkbox" id="dA_FN_DragSelect"/>
		<label for="dA_FN_UpdateCSS">Clean Up CSS</label><input type="checkbox" id="dA_FN_UpdateCSS"/>
		<label for="dA_FN_RemSiteNotes">Remove Site Messages</label><input type="checkbox" id="dA_FN_RemSiteNotes"/>
		<label for="dA_FN_useCustomCSS">Custom CSS</label><input type="checkbox" id="dA_FN_useCustomCSS"/>
		<textarea id="dA_FN_customCSS"></textarea>
		<div class="dA_FN_setBtn"><button id="dA_FN_OKBtn">OK</button></div>
		<button id='dA_FN_cancelBtn'>X</button>
	`; //#dA_FN_settings
	let settingForm;
	let settings = { filterSide: true, dragSelect: true, updateCss: true, useCustomCss: false, customCss: "",removeSiteNotes:true };

	const CSSInsertTMPL = `
	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;}
	`

	const Fm = { show: 0, hide: 1, only: 2 }; //filter mode
	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 cssIntval = null;

	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() {
			if (settings.updateCss || settings.useCustomCss) {
					let sty = document.getElementById("dA_FN_improveCSS");
					if (sty == null) {
							let sty = document.createElement("style");
							sty.id = 'dA_FN_improveCSS';
							if (settings.useCustomCss)
									sty.innerHTML = settings.customCss;
							else
									sty.innerHTML = CSSInsertTMPL;

							document.head.appendChild(sty);
					}
			}

			if (!settings.updateCss) return;
			clearInterval(cssIntval);
			cssIntval = setInterval(() => {
					let it = document.evaluate("//h2[contains(., 'All Notifications')]/parent::div/parent::div/preceding-sibling::*", document).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";
							let readAllBut=document.evaluate("//button[contains(.,'Mark All as Read')]",document).iterateNext();
							if(readAllBut!=null){
									it.parentNode.parentNode.before(readAllBut);
									readAllBut.style="margin:0;position:absolute;top:0;left:50%;transform:translateX(-50%);";
							}
					}

					it = document.querySelectorAll("div[data-nc]"); //no wasted space on large screens (old limit 1024px)
					it.forEach(el=>{el.parentNode.style.maxWidth = "unset";});

					if (settings.removeSiteNotes && cont != null){
							let adel=cont.querySelector("section");
							if(adel!=null)
									adel.style.display = "none"; //hide advertisement on top of left bar
					}
			}, 1000);
	}

	/**
	 * 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 (hidespan) {
					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
			}
	}

	/** Helper function checks overlap between 2 rectangles
	//  * requires top, height, left and width
	//  * @param {DOMRect} a first rectangle 
	//  * @param {DOMRect} b second rectangle
	//  * @param {Number} ov ignored overlap margin
	//  * @returns boolean wether both rectangles have an overlap
	//  */
	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() {
			if (!settings.dragSelect) return;
			//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 (ev.target.closest("#dA_FN_settings") != 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 closeSettingsForm() {
			settings.filterSide = document.getElementById("dA_FN_FilterSide").checked;
			settings.dragSelect = document.getElementById("dA_FN_DragSelect").checked;
			settings.updateCss = document.getElementById("dA_FN_UpdateCSS").checked;
			settings.useCustomCss = document.getElementById("dA_FN_useCustomCSS").checked;
			settings.customCss = document.getElementById("dA_FN_customCSS").value;
			settings.removeSiteNotes=document.getElementById("dA_FN_RemSiteNotes").checked;
			saveSettings();
			settingForm.style.display = "none";
			location.reload();
	}

	function showSettingsForm() {
			if (document.getElementById("dA_FN_settings") == null) {
					settingForm = document.createElement("div");
					settingForm.id = "dA_FN_settings";
					settingForm.innerHTML = SettingFormTmpl;
					document.body.append(settingForm);
					document.getElementById("dA_FN_OKBtn").addEventListener("click", closeSettingsForm, false);
					document.getElementById("dA_FN_cancelBtn").addEventListener("click", () => {
							settingForm.style.display = "none";
					}, false);
			}

			if (settings.customCss == "") settings.customCss = CSSInsertTMPL.replace(/^\t*(.*?)\n/gim, "$1\n");
			document.getElementById("dA_FN_FilterSide").checked = settings.filterSide ? "checked" : "";
			document.getElementById("dA_FN_DragSelect").checked = settings.dragSelect ? "checked" : "";
			document.getElementById("dA_FN_UpdateCSS").checked = settings.updateCss ? "checked" : "";
			document.getElementById("dA_FN_useCustomCSS").checked = settings.useCustomCss ? "checked" : "";
			document.getElementById("dA_FN_customCSS").value = settings.customCss;
			document.getElementById("dA_FN_RemSiteNotes").checked = settings.removeSiteNotes ? "checked" : "";

			settingForm.style.display = "";
	}

	//** save all Settings as json strings */
	function saveSettings() {
			GM.setValue("filter", JSON.stringify(filter));
			GM.setValue("hidden", JSON.stringify(Array.from(hideBuckets)));
			GM.setValue("settings", JSON.stringify(settings));
	}

	/** Load user settings and initializes variables	 */
	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);
					return GM.getValue("settings", null)
			}).then(ret => {
					let pr = JSON.parse(ret);
					if (pr != null) settings = pr;
			});
	}

	/**
	 * inserts GUI for filter bar on the right side. 
	 * if deactivated, empty div is created to mark page as evaluated
	 */
	function insertSideBarFilter() {
			if (!settings.filterSide) {
					let filEl = document.createElement("div");
					filEl.id = "dA_FN_bar";
					filEl.style.display = "none";
					document.body.append(filEl);
					return;
			}

			//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);


			cont.insertBefore(bar, cont.querySelector(`.${itemClass}`)); //insert container
			bar.nextSibling.style.top = "85px";
	}

	/** 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, document).iterateNext();
			if (xEl == null) return; //no place to add filter
			itemClass = xEl.className;
			cont = document.querySelector(`.${itemClass}`).parentElement;

			let setBtn = document.createElement("div");
			setBtn.id = "dA_FN_settingBtn";
			setBtn.innerHTML = ImgGear;
			setBtn.addEventListener("click", showSettingsForm, false);
			cont.closest("section").append(setBtn);

			insertSideBarFilter();
			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);
})();