Adding new search filtering experience

For search engines, non-indie wiki results are now disabled, and a button to the indie wiki is inserted above it. The option to completely hide non-indie results is still available as an option.
pull/271/head
Kevin Payravi 2023-10-16 01:47:02 -04:00
parent 67dab25f83
commit 7c091198ca
8 changed files with 351 additions and 226 deletions

View File

@ -68,13 +68,11 @@ function insertCSS() {
color: white !important;
mix-blend-mode: difference !important;
}
.iwb-notice a {
text-decoration: underline !important;
color: white !important;
mix-blend-mode: difference !important;
}
.iwb-notice button {
cursor: pointer !important;
display: inline-block !important;
@ -88,17 +86,64 @@ function insertCSS() {
mix-blend-mode: difference !important;
text-align: left !important;
}
.iwb-hide {
display: none !important;
}
.iwb-show {
display: block !important;
}
.iwb-notice button:hover {
outline: 1px solid !important;
}
.iwb-new-link {
display: inline-block;
font-size: 12px !important;
text-decoration: none;
padding-left: 5px;
position: relative;
}
.iwb-new-link:hover {
text-decoration: none;
z-index: 9999999;
}
.iwb-new-link button {
cursor: pointer;
color: white;
background: #005799;
border: 0px solid #fff;
border-radius: 10px;
padding: 5px 10px;
margin: .5em 0 .2em 0;
font-size: 1.2em;
width: fit-content;
}
.iwb-new-link button:hover {
background: #00467a;
}
.iwb-new-link button * {
vertical-align: middle;
}
.iwb-new-link div:first-of-type {
display: inline-block;
background: white;
border-radius: 16px;
margin-right: 10px;
width: fit-content;
height: fit-content;
line-height: 12px;
padding: 5px;
}
.iwb-new-link img {
width: 12px;
}
.iwb-disavow > *:not(.iwb-new-link) {
opacity: 60%;
pointer-events: none;
cursor: default;
}
.iwb-disavow > a:not(.iwb-new-link), .iwb-disavow > *:not(.iwb-new-link) a, .iwb-disavow > *:not(.iwb-new-link) a * {
text-decoration: line-through !important;
}
`
style = document.createElement('style');
style.textContent = styleString;
@ -116,82 +161,58 @@ function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function filterSearchResults(searchResults, searchEngine, storage) {
getData().then(sites => {
function redirectSearchResults(searchResultContainer, site, link) {
let countFiltered = 0;
for (let searchResult of searchResults) {
try {
let searchResultLink = '';
if (searchEngine === 'bing') {
searchResultLink = searchResult.innerHTML.replaceAll('<strong>', '').replaceAll('</strong>', '');
} else {
searchResultLink = searchResult.closest('a[href]').href;
}
let link = String(decodeURIComponent(searchResultLink));
if (searchEngine ==='google') {
// Break if image result:
if (link.includes('imgurl=')) {
// Build new URL:
let article = link.split(site['origin_base_url'] + site['origin_content_path'])[1]?.split('#')[0].split('?')[0].split('&')[0];
let newURL = '';
if (article) {
let searchParams = '';
switch (site['destination_platform']) {
case 'mediawiki':
searchParams = 'Special:Search/' + site['destination_content_prefix'] + article;
break;
case 'doku':
searchParams = 'start?do=search&q=' + article;
break;
}
newURL = 'https://' + site["destination_base_url"] + site["destination_content_path"] + searchParams;
} else {
newURL = 'https://' + site["destination_base_url"];
}
// Check if site is in our list of wikis:
let matchingSites = sites.filter(el => {
if (link.substring(8).includes('/')) {
// If the URL has a path, check if an exact match with base URL or base URL + content path
// This is done to ensure we capture non-English Fandom wikis correctly
return (link === 'https://' + el.origin_base_url) || (link.includes('https://' + el.origin_base_url + el.origin_content_path));
if (searchResultContainer && !searchResultContainer.classList.contains('iwb-detected')) {
searchResultContainer.classList.add('iwb-detected');
var indieResultLink = document.createElement('a');
indieResultLink.href = newURL;
indieResultLink.classList.add('iwb-new-link');
var indieResultButton = document.createElement('button');
var indieResultFaviconContainer = document.createElement('div');
var indieResultFavicon = document.createElement('img');
indieResultFavicon.alt = '';
indieResultFavicon.width = '12';
indieResultFavicon.height = '12';
indieResultFavicon.src = chrome.runtime.getURL('favicons/' + site.lang.toLowerCase() + '/' + site.destination_icon);
indieResultFaviconContainer.append(indieResultFavicon);
var indieResultText = document.createElement('span');
if (article) {
indieResultText.innerText = 'Look up "' + decodeURIComponent(decodeURIComponent(article.replaceAll('_', ' '))) + '" on ' + site.destination;
} else {
// If URL does not have a path, just check base URL
return link.includes('https://' + el.origin_base_url);
indieResultText.innerText = 'Visit ' + site.destination + ' instead';
}
});
if (matchingSites.length > 0) {
// Select match with longest base URL
let closestMatch = "";
matchingSites.forEach(site => {
if (site.origin_base_url.length > closestMatch.length) {
closestMatch = site.origin_base_url;
}
});
let site = matchingSites.find(site => site.origin_base_url === closestMatch);
if (site) {
// Get user's settings for the wiki
let settings = storage.siteSettings || {};
let id = site['id'];
let searchFilterSetting = '';
if (settings.hasOwnProperty(id) && settings[id].searchFilter) {
searchFilterSetting = settings[id].searchFilter;
} else if (storage.defaultSearchFilterSettings && storage.defaultSearchFilterSettings[site.language]) {
searchFilterSetting = storage.defaultSearchFilterSettings[site.language];
} else {
searchFilterSetting = 'true';
}
if (searchFilterSetting === 'true') {
// Output stylesheet if not already done
if (filteredWikis.length === 0) {
// Wait for head to be available
const headElement = document.querySelector('head');
if (headElement) {
insertCSS();
} else {
const docObserver = new MutationObserver(function (mutations, mutationInstance) {
const headElement = document.querySelector('head');
if (headElement) {
insertCSS();
mutationInstance.disconnect();
}
});
docObserver.observe(document, {
childList: true,
subtree: true
});
indieResultButton.append(indieResultFaviconContainer);
indieResultButton.append(indieResultText);
indieResultLink.appendChild(indieResultButton);
searchResultContainer.prepend(indieResultLink);
searchResultContainer.classList.add('iwb-disavow');
countFiltered++;
}
return countFiltered;
}
function hideSearchResults(searchResultContainer, searchEngine, site) {
let countFiltered = 0;
// Insert search result removal notice
if (!filteredWikis.includes(site.lang + ' ' + site.origin_group)) {
filteredWikis.push(site.lang + ' ' + site.origin_group);
@ -310,54 +331,6 @@ function filterSearchResults(searchResults, searchEngine, storage) {
}
}
let cssQuery = '';
let searchResultContainer = null;
switch (searchEngine) {
case 'google':
if (searchResult.closest('div[data-hveid]')) {
cssQuery = 'div[data-hveid]';
searchResultContainer = searchResult.closest(cssQuery).parentElement;
}
break;
case 'bing':
if (searchResult.closest('li.b_algo')) {
cssQuery = 'li.b_algo';
searchResultContainer = searchResult.closest(cssQuery);
}
break;
case 'duckduckgo':
if (searchResult.closest('li[data-layout], div.web-result')) {
cssQuery = 'li[data-layout], div.web-result';
searchResultContainer = searchResult.closest(cssQuery);
}
break;
case 'brave':
if (searchResult.closest('div.snippet')) {
cssQuery = 'div.snippet';
searchResultContainer = searchResult.closest(cssQuery);
}
break;
case 'ecosia':
if (searchResult.closest('div.mainline__result-wrapper')) {
cssQuery = 'div.mainline__result-wrapper';
searchResultContainer = searchResult.closest(cssQuery);
}
break;
case 'startpage':
if (searchResult.closest('div.w-gl__result')) {
cssQuery = 'div.w-gl__result';
searchResultContainer = searchResult.closest(cssQuery);
}
break;
case 'yahoo':
if (searchResult.closest('#web > ol > li, section.algo')) {
cssQuery = '#web > ol > li, section.algo';
searchResultContainer = searchResult.closest(cssQuery);
}
break;
default:
}
if (!Array.from(searchResultContainer.classList).includes('iwb-hide')) {
let elementId = stringToId(site.lang + '-' + site.origin_group);
searchResultContainer.classList.add('iwb-search-result-' + elementId);
@ -367,6 +340,119 @@ function filterSearchResults(searchResults, searchEngine, storage) {
searchResultContainer.classList.add('iwb-show');
}
}
return countFiltered;
}
function filterSearchResults(searchResults, searchEngine, storage) {
getData().then(sites => {
let countFiltered = 0;
for (let searchResult of searchResults) {
try {
let searchResultLink = '';
if (searchEngine === 'bing') {
searchResultLink = searchResult.innerHTML.replaceAll('<strong>', '').replaceAll('</strong>', '');
} else {
searchResultLink = searchResult.closest('a[href]').href;
}
let link = String(decodeURIComponent(searchResultLink));
if (searchEngine === 'google') {
// Break if image result:
if (link.includes('imgurl=')) {
break;
}
}
// Check if site is in our list of wikis:
let matchingSites = sites.filter(el => {
if (link.substring(8).includes('/')) {
// If the URL has a path, check if an exact match with base URL or base URL + content path
// This is done to ensure we capture non-English Fandom wikis correctly
return (link === 'https://' + el.origin_base_url) || (link.includes('https://' + el.origin_base_url + el.origin_content_path));
} else {
// If URL does not have a path, just check base URL
return link.includes('https://' + el.origin_base_url);
}
});
if (matchingSites.length > 0) {
// Select match with longest base URL
let closestMatch = "";
matchingSites.forEach(site => {
if (site.origin_base_url.length > closestMatch.length) {
closestMatch = site.origin_base_url;
}
});
let site = matchingSites.find(site => site.origin_base_url === closestMatch);
if (site) {
// Get user's settings for the wiki
let settings = storage.siteSettings || {};
let id = site['id'];
let searchFilterSetting = '';
if (settings.hasOwnProperty(id) && settings[id].searchFilter) {
searchFilterSetting = settings[id].searchFilter;
} else if (storage.defaultSearchFilterSettings && storage.defaultSearchFilterSettings[site.language]) {
searchFilterSetting = storage.defaultSearchFilterSettings[site.language];
} else {
searchFilterSetting = 'true';
}
if (searchFilterSetting === 'true') {
// Output stylesheet if not already done
if (filteredWikis.length === 0) {
// Wait for head to be available
const headElement = document.querySelector('head');
if (headElement) {
insertCSS();
} else {
const docObserver = new MutationObserver(function (mutations, mutationInstance) {
const headElement = document.querySelector('head');
if (headElement) {
insertCSS();
mutationInstance.disconnect();
}
});
docObserver.observe(document, {
childList: true,
subtree: true
});
}
}
let cssQuery = '';
let searchResultContainer = null;
switch (searchEngine) {
case 'google':
searchResultContainer = searchResult.closest('div[data-hveid]');
break;
case 'bing':
searchResultContainer = searchResult.closest('li.b_algo');
break;
case 'duckduckgo':
searchResultContainer = searchResult.closest('li[data-layout], div.web-result');
break;
case 'brave':
searchResultContainer = searchResult.closest('div.snippet');
break;
case 'ecosia':
searchResultContainer = searchResult.closest('div.mainline__result-wrapper article div.result__body');
break;
case 'startpage':
searchResultContainer = searchResult.closest('div.w-gl__result');
break;
case 'yahoo':
searchResultContainer = searchResult.closest('#web > ol > li div.itm .exp, #web > ol > li div.algo, #web > ol > li, section.algo');
break;
default:
}
if (searchResultContainer) {
if (storage.searchSetting === 'hide') {
countFiltered += hideSearchResults(searchResultContainer, searchEngine, site);
} else {
countFiltered += redirectSearchResults(searchResultContainer, site, link);
}
}
}
}
}
@ -391,7 +477,7 @@ function main(mutations = null, observer = null) {
// Check if extension is on:
if ((storage.power ?? 'on') === 'on') {
// Determine which search engine we're on
if ((storage.searchFilter ?? 'on') === 'on') {
if ((storage.searchSetting ?? 'replace') !== 'nothing') {
if (currentURL.hostname.includes('www.google.')) {
// Function to filter search results in Google
function filterGoogle() {
@ -447,7 +533,6 @@ function main(mutations = null, observer = null) {
// Function to filter search results in Brave
function filterBrave() {
let searchResults = Array.from(document.querySelectorAll('div.snippet[data-type="web"] a')).filter(el => el.innerHTML.includes('fandom.com') || el.innerHTML.includes('fextralife.com'));
console.log(searchResults);
filterSearchResults(searchResults, 'brave', storage);
}

View File

@ -99,7 +99,8 @@
Indie Wiki Buddy provides a suite of features to help you discover independent wikis and improve your general wiki reading experience.
<br /><br />
When you visit a wiki on a large, corporate-run wiki host, this extension can notify or automatically redirect you to quality independent wikis when they're available.
Search results in Google, Bing, DuckDuckGo, Yahoo, Brave Search, Ecosia, and Startpage can also be filtered, replacing non-independent wikis with text inviting you to visit the independent counterpart.
Search results in Google, Bing, DuckDuckGo, Yahoo, Brave Search, Ecosia, and Startpage can also be filtered,
replacing non-independent wikis with links to independent counterpart (or hiding them completely).
<br /><br />
We currently redirect from Fandom and Fextralife wikis to independent counterparts.
<br /><br />
@ -119,6 +120,33 @@
This allows you to enable/disable all Indie Wiki Buddy functionality at the click of a button.
</li>
<br />
<li>
<b>🔎 Search engine options</b>
<br />
Indie Wiki Buddy provides two ways to filter results from
Google, Bing, DuckDuckGo, Yahoo, Brave Search, Ecosia, and Startpage.
<br />
<br />
When "disable non-indie wikis & add link to indie counterparts" is selected,
tracked Fandom and Fextralife wikis that appear in search engine results
will be disabled, and a button will be inserted above that attempts to
link you to the same article on the respective indie wiki.
<br />
<br />
When "hide non-indie wikis that have indie counterparts" is selected,
the results are completely hidden. A notice will be inserted at the top of the search page
letting you know that results were hidden, and you will have the option to temporarily
re-reveal the results or disable search engine filtering for that particular wiki.
<br />
<br />
If you don't want a particular Fandom or Fextralife wiki to be filtered from search results,
you can disable search engine filtering per-wiki on the full settings page.
<br />
<br />
Note that image results are not filtered;
we don't want users to miss out on images they might not find elsewhere!
</li>
<br />
<li>
<b>🔔 Desktop notifications when redirected to an indie wiki or BreezeWiki</b>
<br />
@ -131,24 +159,6 @@
settings to ensure that your browser is allowed to send notifications.
</li>
<br />
<li>
<b>🔎 Filter non-indie wikis in Google, Bing, DuckDuckGo, & other search engines</b>
<br />
When this option is enabled, non-indie wikis will be filtered from search engine results
when an independent wiki is available. This is to help prioritize independent wikis in your results.
This is on by default.
<br />
<br />
Currently, Google, Bing, DuckDuckGo, Yahoo, Brave Search, Ecosia, and Startpage are supported.
Note that image results are not filtered;
we don't want users to miss out on images they might not find elsewhere!
<br />
<br />
When a search result is filtered, it will be replaced with:
<br />
"<i>A search result from ABC Wiki has been removed by Indie Wiki Buddy. Look for results from XYZ instead!</i>"
</li>
<br />
<li>
<b>🌬️ Use BreezeWiki on Fandom</b>
<br />

View File

@ -40,6 +40,7 @@
}
},
"web_accessible_resources": [
"favicons/*",
"data/sitesDE.json",
"data/sitesEN.json",
"data/sitesES.json",

View File

@ -28,6 +28,7 @@
"web_accessible_resources": [
{
"resources": [
"favicons/*",
"data/sitesDE.json",
"data/sitesEN.json",
"data/sitesES.json",

View File

@ -139,7 +139,7 @@
width: 100%;
}
.options .settingToggleContainer {
padding-bottom: .8em;
padding-bottom: 1em;
}
.options .settingToggleContainer:last-child {
padding-bottom: 0;
@ -181,18 +181,30 @@
<div id="content">
<div class="options">
<div class="settingToggleContainer">
<div id="notifications" class="settingToggle">
<input id="notificationsCheckbox" type="checkbox" />
<label for="notificationsCheckbox">
<span id="notificationsText">🔔 Desktop notifications when redirected to indie wiki or BreezeWiki</span>
<div id="searchFilteringReplace" class="settingToggle">
<label for="searchFilteringReplaceRadio">
<input id="searchFilteringReplaceRadio" type="radio" name="searchSetting" value="replace" />
<span id="searchFilteringReplaceText">🔎 In search engines, disable non-indie wikis & add link to indie counterparts</span>
</label>
</div>
<div id="searchFilteringHide" class="settingToggle">
<label for="searchFilteringHideRadio">
<input id="searchFilteringHideRadio" type="radio" name="searchSetting" value="hide" />
<span id="searchFilteringHideText">🔎 In search engines, hide non-indie wikis that have indie counterparts</span>
</label>
</div>
<div id="searchFilteringNothing" class="settingToggle">
<label for="searchFilteringNothingRadio">
<input id="searchFilteringNothingRadio" type="radio" name="searchSetting" value="nothing" />
<span id="searchFilteringNothingText">🔎 Do nothing on search engines</span>
</label>
</div>
</div>
<div class="settingToggleContainer">
<div id="searchFilter" class="settingToggle">
<input id="searchFilterCheckbox" type="checkbox" />
<label for="searchFilterCheckbox">
<span id="searchFilterText">🔎 Filter non-indie wikis in Google, Bing, DuckDuckGo, & other search engines</span>
<div id="notifications" class="settingToggle">
<input id="notificationsCheckbox" type="checkbox" />
<label for="notificationsCheckbox">
<span id="notificationsText">🔔 Desktop notifications when redirected to an indie wiki or BreezeWiki</span>
</label>
</div>
</div>

View File

@ -145,14 +145,16 @@ function setNotifications(setting, storeSetting = true) {
}
// Set search filter setting
function setSearchFilter(setting, storeSetting = true) {
function setSearchSetting(setting, storeSetting = true) {
if (storeSetting) {
chrome.storage.sync.set({ 'searchFilter': setting });
chrome.storage.sync.set({ 'searchSetting': setting });
}
if (setting === 'on') {
document.getElementById('searchFilterCheckbox').checked = true;
if (setting === 'hide') {
document.getElementById('searchFilteringHideRadio').checked = true;
} else if (setting === 'nothing') {
document.getElementById('searchFilteringNothingRadio').checked = true;
} else {
document.getElementById('searchFilterCheckbox').checked = false;
document.getElementById('searchFilteringReplaceRadio').checked = true;
}
}
@ -242,8 +244,8 @@ document.addEventListener('DOMContentLoaded', function () {
chrome.storage.sync.get({ 'notifications': 'on' }, function (item) {
setNotifications(item.notifications, false);
});
chrome.storage.sync.get({ 'searchFilter': 'on' }, function (item) {
setSearchFilter(item.searchFilter, false);
chrome.storage.sync.get({ 'searchSetting': 'replace' }, function (item) {
setSearchSetting(item.searchSetting, false);
});
chrome.storage.sync.get({ 'breezewiki': 'off' }, function (item) {
setBreezeWiki(item.breezewiki, false);
@ -273,14 +275,14 @@ document.addEventListener('DOMContentLoaded', function () {
}
});
});
document.getElementById('searchFilterCheckbox').addEventListener('change', function () {
chrome.storage.sync.get({ 'searchFilter': 'on' }, function (item) {
if (item.searchFilter === 'on') {
setSearchFilter('off');
} else {
setSearchFilter('on');
}
document.getElementById('searchFilteringReplaceRadio').addEventListener('change', function () {
setSearchSetting('replace');
});
document.getElementById('searchFilteringHideRadio').addEventListener('change', function () {
setSearchSetting('hide');
});
document.getElementById('searchFilteringNothingRadio').addEventListener('change', function () {
setSearchSetting('nothing');
});
document.getElementById('breezewikiCheckbox').addEventListener('change', function () {
chrome.storage.sync.get({ 'breezewiki': 'off' }, function (item) {

View File

@ -131,7 +131,7 @@
width: fit-content;
}
.options .settingToggleContainer {
padding-bottom: .8em;
padding-bottom: 1em;
}
.options .settingToggleContainer:last-child {
padding-bottom: 0;
@ -361,18 +361,30 @@
</div>
</div>
<div class="settingToggleContainer">
<div id="notifications" class="settingToggle">
<label>
<input id="notificationsCheckbox" type="checkbox" />
<span id="notificationsText">🔔 Desktop notifications when redirected to an indie wiki or BreezeWiki</span>
<div id="searchFilteringReplace" class="settingToggle">
<label for="searchFilteringReplaceRadio">
<input id="searchFilteringReplaceRadio" type="radio" name="searchSetting" value="replace" />
<span id="searchFilteringReplaceText">🔎 In search engines, disable non-indie wikis & add link to indie counterparts</span>
</label>
</div>
<div id="searchFilteringHide" class="settingToggle">
<label for="searchFilteringHideRadio">
<input id="searchFilteringHideRadio" type="radio" name="searchSetting" value="hide" />
<span id="searchFilteringHideText">🔎 In search engines, hide non-indie wikis that have indie counterparts</span>
</label>
</div>
<div id="searchFilteringNothing" class="settingToggle">
<label for="searchFilteringNothingRadio">
<input id="searchFilteringNothingRadio" type="radio" name="searchSetting" value="nothing" />
<span id="searchFilteringNothingText">🔎 Do nothing on search engines</span>
</label>
</div>
</div>
<div class="settingToggleContainer">
<div id="searchFilter" class="settingToggle">
<div id="notifications" class="settingToggle">
<label>
<input id="searchFilterCheckbox" type="checkbox" />
<span id="searchFilterText">🔎 Filter non-indie wikis in Google, Bing, DuckDuckGo, & other search engines</span>
<input id="notificationsCheckbox" type="checkbox" />
<span id="notificationsText">🔔 Desktop notifications when redirected to an indie wiki or BreezeWiki</span>
</label>
</div>
</div>

View File

@ -439,14 +439,16 @@ function setNotifications(setting, storeSetting = true) {
}
// Set search filter setting
function setSearchFilter(setting, storeSetting = true) {
function setSearchSetting(setting, storeSetting = true) {
if (storeSetting) {
chrome.storage.sync.set({ 'searchFilter': setting });
chrome.storage.sync.set({ 'searchSetting': setting });
}
if (setting === 'on') {
document.getElementById('searchFilterCheckbox').checked = true;
if (setting === 'hide') {
document.getElementById('searchFilteringHideRadio').checked = true;
} else if (setting === 'nothing') {
document.getElementById('searchFilteringNothingRadio').checked = true;
} else {
document.getElementById('searchFilterCheckbox').checked = false;
document.getElementById('searchFilteringReplaceRadio').checked = true;
}
}
@ -578,8 +580,8 @@ document.addEventListener('DOMContentLoaded', function () {
chrome.storage.sync.get({ 'notifications': 'on' }, function (item) {
setNotifications(item.notifications, false);
});
chrome.storage.sync.get({ 'searchFilter': 'on' }, function (item) {
setSearchFilter(item.searchFilter, false);
chrome.storage.sync.get({ 'searchSetting': 'replace' }, function (item) {
setSearchSetting(item.searchSetting, false);
});
chrome.storage.sync.get({ 'breezewiki': 'off' }, function (item) {
setBreezeWiki(item.breezewiki, false);
@ -604,14 +606,14 @@ document.addEventListener('DOMContentLoaded', function () {
}
});
});
document.getElementById('searchFilterCheckbox').addEventListener('change', function () {
chrome.storage.sync.get({ 'searchFilter': 'on' }, function (item) {
if (item.searchFilter === 'on') {
setSearchFilter('off');
} else {
setSearchFilter('on');
}
document.getElementById('searchFilteringReplaceRadio').addEventListener('change', function () {
setSearchSetting('replace');
});
document.getElementById('searchFilteringHideRadio').addEventListener('change', function () {
setSearchSetting('hide');
});
document.getElementById('searchFilteringNothingRadio').addEventListener('change', function () {
setSearchSetting('nothing');
});
document.getElementById('breezewikiCheckbox').addEventListener('change', function () {
chrome.storage.sync.get({ 'breezewiki': 'off' }, function (item) {