Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(GitHub Files Filter) Feature suggestion: invert select #97

Closed
darkred opened this issue Aug 3, 2019 · 17 comments
Closed

(GitHub Files Filter) Feature suggestion: invert select #97

darkred opened this issue Aug 3, 2019 · 17 comments

Comments

@darkred
Copy link
Collaborator

darkred commented Aug 3, 2019

Greetings @Mottie 🙂

Currently, 'GitHub Files Filter', only hides the files matching the activated filters.

My suggestion is to also be able to invert select, i.e. only show the matching files, e.g. with Ctrl+click.
For example, in your repo, when Ctrl+clicking MD:
2019-08-03_132328
to only display .md files, i.e. to strikethrough the rest filters.
And, if you keep Ctrl+clicking, e.g. also Ctrl+click «dot files»
to only display .md and dot files .

I hope you like my suggestion !

@Mottie
Copy link
Owner

Mottie commented Aug 4, 2019

Hi @darkred!

I'll see what I can do... what happens when someone Ctrl+clicks on the «ALL» button?

@darkred
Copy link
Collaborator Author

darkred commented Aug 4, 2019

what happens when someone Ctrl+clicks on the «ALL» button?

Currently, clicking on the «ALL» button disables all filters, i.e. displays all files.
IMHO Ctrl+clicking should have the exact same behavior, i.e. show all files -- hiding all files serves no purpose.

(please, see below)


Upon further consideration, I think the best way to implement would be not with Ctrl+click,
but to add an extra toggle button instead, entitled "Show/Hide" (its two statuses), that, when enabled, would make the filters work inverted (display instead of hide).

@darkred darkred changed the title (GitHub Files Filter) Feature request: invert select with Ctrl+click (GitHub Files Filter) Feature request: invert select Aug 4, 2019
@darkred darkred changed the title (GitHub Files Filter) Feature request: invert select (GitHub Files Filter) Feature suggestion: invert select Aug 4, 2019
@darkred
Copy link
Collaborator Author

darkred commented Aug 4, 2019

And, another suggestion, please, regarding the «ALL» button:
when you press it, all the filters to become disabled, i.e. even after reloading the page (F5).
Currently, their status remains until you manually disable each one, and that causes confusion for me.

@Mottie
Copy link
Owner

Mottie commented Aug 6, 2019

Hmm, now that I read all that again, I'm confused 🤣

So what if I add a new button named "toggle"? Clicking it will invert the filters. So if you only want to see the files ending with "js", you click on that button (which hides the js files), then hit the invert and then only the "js" files are visible.

Then I can store the selected filters in local storage so the next time you return to any PR files, only those filters will be enabled... hmm, I guess it'd have to be stored by PR number because not every PR will have the same file extensions being modified.

And I'm still not sure what you are describing that needs changing for the «ALL» button.

@darkred
Copy link
Collaborator Author

darkred commented Aug 6, 2019

First, of all, sorry for the confusion and for not being consistent to my suggestion description.. 😟
Hoping to clarify everything with my reply below...



So what if I add a new button named "toggle"? Clicking it will invert the filters. So if you only want to see the files ending with "js", you click on that button (which hides the js files), then hit the invert and then only the "js" files are visible.

Yes, exactly.


Then I can store the selected filters in local storage so the next time you return to any PR files, only those filters will be enabled... hmm, I guess it'd have to be stored by PR number because not every PR will have the same file extensions being modified.

Yes, I think that's the best approach.


And I'm still not sure what you are describing that needs changing for the «ALL» button.

Currently, when you enable a filter, e.g. "JS", and so .js files are hidden, the status of that "JS" filter (=enabled) remains after you reload the page (F5).
But, if you press "ALL", then the "JS" filter becomes only temporarily disabled, i.e. the "JS" filter becomes enabled again, after you reload the page.
My suggestion is when you press "ALL", the 'all-filters-are-now-disabled' status to remain after page reload.



Something last, please:
it would be great if afterwards, you also added this new "Toggle" button
and strikethrough on applied hiding filter names (from my PR here: #68 )
to your other script, "GitHub Diff Files Filter", too.

@darkred
Copy link
Collaborator Author

darkred commented Aug 10, 2019

One more thing, please:
I believe that the default action of the script should be "show matching"(=filter),
not "hide matching"(=filter out).

I understand that you've written the script in order to be able to hide the non-important filetypes from the listings, but I find that it's more suitable to the script's title, 'GitHub Files Filter', if it had:

  • normal action: "show matching"
  • INVERT action: "hide matching"

@darkred
Copy link
Collaborator Author

darkred commented Aug 15, 2019

Here is a summary of my suggestions:

  • add a new button named TOGGLE :
    • clicking it will invert the filters
    • normal action: "show matching" / INVERT action: "hide matching"
    • (regarding the ALL button) the 'all-filters-are-now-disabled' status to remain after page reload
    • maintain the strikethrough on applied hiding filter names from my PR github-files-filter : strikethrough applied filter names #68

  • apply the above to your other script, "GitHub Diff Files Filter".

@Mottie
Copy link
Owner

Mottie commented Aug 15, 2019

Yes, thanks! Sorry, I've been busy job hunting. Hopefully, everything will be settled soon and I'll have some free time to take care of this.

I was actually thinking of replacing the ALL button with the TOGGLE action.

@darkred
Copy link
Collaborator Author

darkred commented Aug 15, 2019

No problem! I wish you every success ! 👍

I was actually thinking of replacing the ALL button with the TOGGLE action.

Great idea!

@Mottie
Copy link
Owner

Mottie commented Sep 2, 2019

Busy weekend. I didn't forget about you, I just didn't get around to working on this script. It's high on my list now 😸

@Mottie
Copy link
Owner

Mottie commented Sep 5, 2019

Ok try out this script; if it works like you want, I'll go ahead and commit it:

Files-filter
// ==UserScript==
// @name        GitHub Files Filter
// @version     1.1.8
// @description A userscript that adds filters that toggle the view of repo files by extension
// @license     MIT
// @author      Rob Garrison
// @namespace   https:/Mottie
// @include     https:/*
// @run-at      document-idle
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_addStyle
// @require     https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
// @icon        https://github.githubassets.com/pinned-octocat.svg
// @updateURL   https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-files-filter.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-files-filter.user.js
// ==/UserScript==
(() => {
	"use strict";

	// Emphasize selected buttons, disable hover when all selected and remove
	// animation delay; See #46
	GM_addStyle(`
		.gff-filter .btn.selected { font-variant: small-caps; }
		.gff-filter .btn:not(.selected):not(:first-child) {
			text-decoration: line-through;
		}
		.gff-filter .gff-all:not(.selected):focus,
		.gff-filter .gff-all:not(.selected) ~ .btn:focus,
		.gff-filter .gff-all:not(.selected) ~ .btn.selected:focus,
		.gff-filter .gff-all:not(.selected):hover,
		.gff-filter .gff-all:not(.selected) ~ .btn:hover,
		.gff-filter .gff-all:not(.selected) ~ .btn.selected:hover {
			border-color: #777 !important;
		}
		.gff-filter .btn:before, .gff-filter .btn:after {
			animation-delay: unset !important;
			filter: invert(10%);
		}
	`);

	let settings,
		inverted = [],
		list = {};
	const types = {
			// including ":" in key since it isn't allowed in a file name
			":all": {
				// return false to prevent adding files under this type
				is: () => false,
				text: "\u00ABtoggle\u00BB"
			},
			":noExt": {
				is: name => !/\./.test(name),
				text: "\u00ABno-ext\u00BB"
			},
			":dot": {
				// this will include ".travis.yml"... should we add to "yml" instead?
				is: name => /^\./.test(name),
				text: "\u00ABdot-files\u00BB"
			},
			":min": {
				is: name => /\.min\./.test(name),
				text: "\u00ABmin\u00BB"
			}
		},
		// TODO: add toggle for submodule and dot-folders
		folderIconClasses = [
			".octicon-file-directory",
			".octicon-file-symlink-directory",
			".octicon-file-submodule"
		].join(",");

	// default to all file types visible; remember settings between sessions
	list[":all"] = true; // list gets cleared in buildList function
	settings = GM_getValue("gff-filter-settings", list);

	function updateFilter(event) {
		event.preventDefault();
		event.stopPropagation();
		const el = event.target;
		toggleBlocks(
			el.getAttribute("data-ext"),
			el.classList.contains("selected") ? "hide" : "show",
			event.ctrlKey
		);
	}

	function updateSettings(name, mode) {
		settings[name] = mode === "show";
		GM_setValue("gff-filter-settings", settings);
	}

	function toggleImagePreview(ext, mode) {
		if ($(".ghip-image-previews")) {
			let selector = "a",
				hasType = types[ext];
			if (!hasType) {
				selector += `[href$="${ext}"]`;
			}
			$$(`.ghip-image-previews ${selector}`).forEach(el => {
				if (!$(".ghip-folder, .ghip-up-tree", el)) {
					if (hasType && ext !== ":all") {
						// image preview includes the filename
						let elm = $(".ghip-file-name", el);
						if (elm && !hasType.is(elm.textContent)) {
							return;
						}
					}
					el.style.display = mode === "show" ? "" : "none";
				}
			});
		}
	}

	function toggleRow(el, mode) {
		const row = el.closest("tr.js-navigation-item");
		// don't toggle folders
		if (row && !$(folderIconClasses, row)) {
			let state;
			if (mode) {
				state = mode === "show" ? "" : "none";
			} else {
				// toggle
				state = row.style.display === "none" ? "" : "none";
			}
			row.style.display = state;
		}
	}

	function toggleAll() {
		const files = $(".file-wrap");
		// Toggle all blocks
		$$("td.content .js-navigation-open", files).forEach(el => {
			toggleRow(el);
		});
		// update filter buttons
		$$(".gff-filter .btn", files).forEach(el => {
			el.classList.toggle("selected");
		});
	}

	function toggleFilter(filter, mode) {
		const files = $(".file-wrap"),
			elm = $(`.gff-filter .btn[data-ext="${filter}"]`, files);
		/* list[filter] contains an array of file names */
		list[filter].forEach(name => {
			const el = $(`a[title="${name}"]`, files);
			if (el) {
				toggleRow(el, mode);
			}
		});
		if (elm) {
			elm.classList.toggle("selected", mode === "show");
		}
		updateSettings(filter, mode);
	}

	function toggleBlocks(filter, mode) {
		if (filter === ":all") {
			toggleAll();
		} else if (list[filter]) {
			toggleFilter(filter, mode);
		}
		// update view for github-image-preview.user.js
		toggleImagePreview(filter, mode);
	}

	function addExt(ext, txt) {
		if (ext) {
			if (!list[ext]) {
				list[ext] = [];
			}
			list[ext].push(txt);
		}
	}

	function buildList() {
		list = {};
		Object.keys(types).forEach(item => {
			if (item !== ":all") {
				list[item] = [];
			}
		});
		// get all files
		$$("table.files tr.js-navigation-item").forEach(file => {
			if ($("td.icon .octicon-file", file)) {
				let ext, parts, sub,
					link = $("td.content .js-navigation-open", file),
					txt = (link.title || link.textContent || "").trim(),
					name = txt.split("/").slice(-1)[0];
				// test extension types; fallback to regex extraction
				ext = Object.keys(types).find(item => {
					return types[item].is(name);
				}) || /[^./\\]*$/.exec(name)[0];
				parts = name.split(".");
				if (!ext.startsWith(":") && parts.length > 2 && parts[0] !== "") {
					sub = parts.slice(0, -1).join(".");
					// Prevent version numbers & "vs. " from adding a filter button
					// See https:/tpn/pdfs
					if (!/[()]/.test(sub) && !/[\b\w]\.[\b\d]/.test(sub)) {
						addExt(ext, txt);
						ext = parts.slice(-2).join(".");
					}
				}
				addExt(ext, txt);
			}
		});
	}

	function sortList() {
		return Object.keys(list).sort((a, b) => {
			// move ":" filters to the beginning, then sort the rest of the
			// extensions; test on https:/rbsec/sslscan, where
			// the ".1" extension *was* appearing between ":" filters
			if (a[0] === ":") {
				return -1;
			}
			if (b[0] === ":") {
				return 1;
			}
			return a > b;
		});
	}

	function makeFilter() {
		let filters = 0;
		// get length, but don't count empty arrays
		Object.keys(list).forEach(ext => {
			filters += list[ext].length > 0 ? 1 : 0;
		});
		// Don't bother if only one extension is found
		const files = $(".file-wrap");
		if (files && filters > 1) {
			filters = $(".gff-filter-wrapper");
			if (!filters) {
				filters = document.createElement("div");
				// "commitinfo" allows GitHub-Dark styling
				filters.className = "gff-filter-wrapper commitinfo";
				filters.style = "padding:3px 5px 2px;border-bottom:1px solid #eaecef";
				files.insertBefore(filters, files.firstChild);
			}
			fixWidth();
			buildHTML();
			applyInitSettings();
		}
	}

	function buildButton(name, label, ext, text) {
		return `<button type="button" ` +
			`class="btn btn-sm selected BtnGroup-item tooltipped tooltipped-n` +
			(name ? name : "") + ` gff-btn" ` +
			`data-ext="${ext}" aria-label="${label}">${text}</button>`;
	}

	function buildHTML() {
		let len,
			html = `<div class="BtnGroup gff-filter">` +
				// add a filter "all" button to the beginning
				buildButton(" gff-all", "Toggle all files", ":all", types[":all"].text);
		sortList().forEach(ext => {
			len = list[ext].length;
			if (len) {
				html += buildButton("", len, ext, types[ext] && types[ext].text || ext);
			}
		});
		// prepend filter buttons
		$(".gff-filter-wrapper").innerHTML = html + "</div>";
	}

	function getWidth(el) {
		return parseFloat(window.getComputedStyle(el).width);
	}

	// lock-in the table cell widths, or the navigation up link jumps when you
	// hide all files... using percentages in case someone is using GitHub wide
	function fixWidth() {
		let group, width,
			html = "",
			table = $("table.files"),
			tableWidth = getWidth(table),
			cells = $$("tbody:last-child tr:last-child td", table);
		if (table && cells.length > 1 && !$("colgroup", table)) {
			group = document.createElement("colgroup");
			table.insertBefore(group, table.childNodes[0]);
			cells.forEach(el => {
				// keep two decimal point accuracy
				width = parseInt(getWidth(el) / tableWidth * 1e4, 10) / 100;
				html += `<col style="width:${width}%">`;
			});
			group.innerHTML = html;
		}
	}

	function applyInitSettings() {
		Object.keys(list).forEach(name => {
			if (settings[name] === false) {
				toggleBlocks(name, "hide");
			}
		});
	}

	function init() {
		if ($("table.files")) {
			buildList();
			makeFilter();
		}
	}

	function $(str, el) {
		return (el || document).querySelector(str);
	}

	function $$(str, el) {
		return Array.from((el || document).querySelectorAll(str));
	}

	document.addEventListener("click", e => {
		if (e.target.classList.contains("gff-btn")) {
			updateFilter(e);
		}
	});

	document.addEventListener("ghmo:container", () => {
		// init after a short delay to allow rendering of file list
		setTimeout(() => {
			init();
		}, 200);
	});
	init();

})();

@darkred
Copy link
Collaborator Author

darkred commented Sep 6, 2019

Thanks a lot for your effort! 👍


Unfortunately though, the new version doesn't do anything: no filter buttons group, no filtering, nothing.
And there's no error in browser's Console.

I've tried both latest Tampermonkey versions (TM 4.8.41 stable and TM 4.9.5971 beta)
in a fresh Chrome 76/Firefox 69 profile with only the script installed, in e.g.
https:/Mottie/GitHub-userscripts and
https:/darkred/Userscripts

@Mottie
Copy link
Owner

Mottie commented Sep 8, 2019

Hmm, you mentioned this before:

normal action: "show matching" / INVERT action: "hide matching"

To me that is essentially what the toggle button does now; it inverts the selected state of all the buttons instead of the button action (matching). The results are the same - unless I'm utterly over-thinking this whole thing.

@darkred
Copy link
Collaborator Author

darkred commented Sep 11, 2019

Sorry for changing my mind once more (I've deleted my last comment),
but I've been thinking over my last suggestion a lot, and I end up that it would be counterintuitive,
when you initially single/plain click on a filter, the other filters to become disabled.

So, my final suggestion is that, the new «Toggle» button is useful as it is, so keep it (changing its name to «Invert», its tooltip to Invert filters status and moving it to the end),
and that, it would be convenient if you also added a keyboard shortcut, e.g. Ctrl+click,
so that, what ever might be the filters current status,
as you Ctrl+click on a filter, the others to become disabled,
and as you keep on Ctrl+clicking filters to show more matching filetypes
.

@darkred
Copy link
Collaborator Author

darkred commented Sep 15, 2019

If I may facilitate you a bit, here are the few changes for my last suggestion that I managed to make:

  • (change button name) change line 50 to:
buildButton(" gff-all", "Invert filters status", ":all", types[":all"].text);
  • (detect Ctrl+Click) lines lines 317-321 to:
document.addEventListener("click", e => {
  if (e.target.classList.contains("gff-btn")) {
    if (!event.ctrlKey) {
      // plain click
      updateFilter(e);
    } else {
      // Ctrl+click
      showOnlyFilter(e); // New function
    }
  }
});

Now, what's left to be done (sorry I can't contribute more) :

  • The Invert to be the last button.
    (Note: if I change line 266 into the following, then the buttons don't work when clicked: )
    html = buildButton("", len, ext, types[ext] && types[ext].text || ext) + html;

  • Make a new function showOnlyFilter(e) that:

    whatever might be the filters current status,
    as you Ctrl+click on a filter, the others to become disabled,
    and as you keep on Ctrl+clicking filters to show more matching filetypes.

@Mottie
Copy link
Owner

Mottie commented Sep 23, 2019

I just updated the files filter userscript.

I didn't:

  • Move the invert button to the right. It'll look weird when there are a bunch of filter buttons that wrap around to the next line. I like it on the left - and I changed the icon.
  • Add a method to allow you to continue to use Ctrl + click. I mean, when you use it, it enables the selected filter and disables all the others. Continuing to use Ctrl + click will continue this behavior. To enable more file types, use a plain click.

@darkred
Copy link
Collaborator Author

darkred commented Sep 23, 2019

Thank you so much!! It's great! 👍

@darkred darkred closed this as completed Sep 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants