-
-
Save sp00n/e8b91d2f47c471bc0627f7b31d659291 to your computer and use it in GitHub Desktop.
// ==UserScript== | |
// @name Restored Pagination for Google | |
// @namespace google-pagination.sp00n.net | |
// @match *://*/search* | |
// @grant none | |
// @version 0.2 | |
// @author sp00n | |
// @description This script restores the paged navigation for the Google Search. It also requires that the "Continuous scrolling" in the Google Search settings is turned off (which infuriatingly does not restore the old Pagination!) | |
// @downloadURL https://gist.github.com/sp00n/e8b91d2f47c471bc0627f7b31d659291 | |
// ==/UserScript== | |
// Limit the script execution to only Google pages | |
// @match only allows wildcards for the TLD in ViolentMonkey, not in TamperMonkey or other alternatives | |
// @includeGlobs also doesn't work in all addons | |
// These are all the TLDs that Google runs on | |
// Taken from https://www.google.com/supported_domains | |
const supportedTLDs = [ | |
"com", "ad", "ae", "com.af", "com.ag", "al", "am", "co.ao", "com.ar", "as", "at", "com.au", "az", "ba", "com.bd", "be", "bf", "bg", "com.bh", "bi", "bj", | |
"com.bn", "com.bo", "com.br", "bs", "bt", "co.bw", "by", "com.bz", "ca", "cd", "cf", "cg", "ch", "ci", "co.ck", "cl", "cm", "cn", "com.co", "co.cr", | |
"com.cu", "cv", "com.cy", "cz", "de", "dj", "dk", "dm", "com.do", "dz", "com.ec", "ee", "com.eg", "es", "com.et", "fi", "com.fj", "fm", "fr", "ga", "ge", | |
"gg", "com.gh", "com.gi", "gl", "gm", "gr", "com.gt", "gy", "com.hk", "hn", "hr", "ht", "hu", "co.id", "ie", "co.il", "im", "co.in", "iq", "is", "it", | |
"je", "com.jm", "jo", "co.jp", "co.ke", "com.kh", "ki", "kg", "co.kr", "com.kw", "kz", "la", "com.lb", "li", "lk", "co.ls", "lt", "lu", "lv", "com.ly", | |
"co.ma", "md", "me", "mg", "mk", "ml", "com.mm", "mn", "com.mt", "mu", "mv", "mw", "com.mx", "com.my", "co.mz", "com.na", "com.ng", "com.ni", "ne", "nl", | |
"no", "com.np", "nr", "nu", "co.nz", "com.om", "com.pa", "com.pe", "com.pg", "com.ph", "com.pk", "pl", "pn", "com.pr", "ps", "pt", "com.py", "com.qa", "ro", | |
"ru", "rw", "com.sa", "com.sb", "sc", "se", "com.sg", "sh", "si", "sk", "com.sl", "sn", "so", "sm", "sr", "st", "com.sv", "td", "tg", "co.th", "com.tj", | |
"tl", "tm", "tn", "to", "com.tr", "tt", "com.tw", "co.tz", "com.ua", "co.ug", "co.uk", "com.uy", "co.uz", "com.vc", "co.ve", "co.vi", "com.vn", "vu", "ws", | |
"rs", "co.za", "co.zm", "co.zw", "cat" | |
]; | |
const tldString = supportedTLDs.join("|").replaceAll(".", String.raw`\.`); // Uses String.raw to be able to insert a backslash before the dot. Is there an easier way? | |
const googleRegEx = new RegExp(`^http(s)?://(www\.)?google\.(${tldString})/search\?.*`, "i"); | |
// No Google, no continue! | |
if ( !googleRegEx.test(location.href) ) { | |
return; | |
} | |
// Google uses a bunch of parameters, but we only need a couple | |
// https://www.google.com/search?hl=<LANG>&q=<QUERY>&tbm=isch&sa=X&ved=<UNIQUE_ID>&biw=<WIDTH>&bih=<HEIGHT>&dpr=<DPI_SCALING_INT_OR_DECIMAL> | |
// Date Filters | |
// Last Hour https://www.google.com/search?q=abc&tbs=qdr:h | |
// Last Month https://www.google.com/search?q=abc&tbs=qdr:m | |
// Last Year https://www.google.com/search?q=abc&tbs=qdr:y | |
// https://www.google.com/search?q=abc&tbs=cdr%3A1%2Ccd_min%3A01.01.2020%2Ccd_max%3A02.02.2022&tbm= | |
// Language | |
// German https://www.google.com/search?q=abc&tbs=lr:lang_1de&lr=lang_de | |
// Image Search | |
// https://www.google.com/search?q=abc&tbm=isch | |
let params = new URL(document.location).searchParams; | |
let searchQuery = params.get("q"); | |
let currentStartEntries = parseInt(params.get("start")) || 0; | |
let previousStartEntries = Math.max(0, currentStartEntries - 10); | |
let nextStartEntries = currentStartEntries + 10; // Can we get the maximum amount of search results? | |
let currentPage = Math.floor(currentStartEntries / 10) + 1; // Page 1 has start=0, Page 2 start=10, etc | |
let firstPageToDisplay = 1; | |
let lastPageToDisplay = 10; | |
// Very weak detection for the dark mode, but here we go | |
let isDarkMode = (getComputedStyle(document.body).backgroundColor != "rgb(255, 255, 255)"); | |
// Abort if we're on the image search | |
if ( params.has("tbm") && params.get("tbm") == "isch" ) { | |
return; | |
} | |
// Build the fixed query string | |
let fixedQueryParams = new URLSearchParams(); | |
fixedQueryParams.set("q", searchQuery); | |
if ( params.has("filter") ) { | |
fixedQueryParams.set("filter", params.get("filter")); | |
} | |
if ( params.has("tbs") ) { | |
fixedQueryParams.set("tbs", params.get("tbs")); | |
} | |
if ( params.has("lr") ) { | |
fixedQueryParams.set("lr", params.get("lr")); | |
} | |
let fixedQueryString = fixedQueryParams.toString(); | |
// Determine which pages to display | |
// Page 1-6: display 1-10 | |
// Page 7: 2-11 | |
// Page 8: 3-12 | |
// etc | |
// 5 to the left visible, 4 to the right | |
if ( currentPage >= 7 ) { | |
firstPageToDisplay = currentPage - 5; | |
lastPageToDisplay = currentPage + 4; | |
} | |
// The entries HTML string | |
let entriesHTMLString = ""; | |
let entryBgPosTop = ( isDarkMode ) ? -112 : 0; | |
// Generate the entries | |
for ( let entryPage = firstPageToDisplay; entryPage <= lastPageToDisplay; entryPage++ ) { | |
let entryStartEntries = (entryPage - 1) * 10; | |
let entryBgPosLeft = ( entryPage == 1 ) ? -53 : -74; | |
let entryTemplate = ""; | |
// The template for an entry in the navigation | |
if ( entryPage == currentPage ) { | |
entryTemplate = ` | |
<td class="YyVfkd"> | |
<span class="SJajHc" style="background:url(/images/nav_logo321_hr.webp) no-repeat;background-position:${entryBgPosLeft}px ${entryBgPosTop}px;background-size:167px;width:20px"></span> | |
${entryPage} | |
</td> | |
`; | |
} | |
else { | |
entryTemplate = ` | |
<td> | |
<a aria-label="Page ${entryPage}" class="fl" href="/search?${fixedQueryString}&start=${entryStartEntries}"> | |
<span class="SJajHc NVbCr" style="background:url(/images/nav_logo321_hr.webp) no-repeat;background-position:${entryBgPosLeft}px ${entryBgPosTop}px;background-size:167px;width:20px"></span> | |
${entryPage} | |
</a> | |
</td> | |
`; | |
} | |
entriesHTMLString += entryTemplate; | |
} | |
// The navigation table | |
let navigationHTMLString = ` | |
<table class="AaVjTc" style="border-collapse:collapse;text-align:left" role="presentation" id="restored-pagination"> | |
<tbody> | |
<tr jsname="TeSSVd" valign="top"> | |
<td aria-level="3" class="d6cvqb BBwThe" role="heading"> | |
<a href="/search?${fixedQueryString}&start=${previousStartEntries}" id="pnprev"> | |
<span class="SJajHc NVbCr" style="background:url(/images/nav_logo321_hr.webp) no-repeat;background-position:0 ${entryBgPosTop}px;background-size:167px;width:53px;float:right"></span> | |
<span style="display:block;margin-right:35px;clear:right">Previous</span> | |
</a> | |
</td> | |
` | |
+ entriesHTMLString + | |
` | |
<td aria-level="3" class="d6cvqb BBwThe" role="heading"> | |
<a href="/search?${fixedQueryString}&start=${nextStartEntries}" id="pnnext" style="text-align:left"> | |
<span class="SJajHc NVbCr" style="background:url(/images/nav_logo321_hr.webp) no-repeat;background-position:-96px ${entryBgPosTop}px;background-size:167px;width:71px"></span> | |
<span style="display:block;margin-left:53px">Next</span> | |
</a> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
`; | |
// Insert the table | |
// Get the old navigation container at the bottom, which is currently empty, but this may change or be removed in the future | |
const navigationContainer = [...document.querySelectorAll("div[role='navigation']:not([class])")].pop(); | |
navigationContainer.innerHTML = navigationHTMLString; | |
// And some additional styles | |
let styles = ` | |
<style type="text/css"> | |
#restored-pagination.AaVjTc { | |
margin: 30px auto 30px; | |
} | |
#restored-pagination.AaVjTc td { | |
padding: 0; | |
text-align: center; | |
} | |
#restored-pagination.AaVjTc a:link { | |
color: ${isDarkMode ? "#8ab4f8" : "#4285f4"}; | |
font-weight: normal; | |
} | |
#restored-pagination .SJajHc { | |
background: url(/images/nav_logo321_hr.webp) no-repeat; | |
background-size: 167px; | |
overflow: hidden; | |
background-position: 0 0; | |
height: 40px; | |
display: block; | |
} | |
#restored-pagination .NVbCr { | |
cursor: pointer; | |
} | |
#restored-pagination .YyVfkd { | |
color: ${isDarkMode ? "#bdc1c6" : "#202124"}; | |
font-weight: normal; | |
} | |
</style> | |
`; | |
navigationContainer.innerHTML += styles; |
match has to contain TLD and asterisks after search
to work properly with tampermonkey
should be \\ @match https://www.google.com/search*
Can you update from my fork? I've added an exclusion to prevent it from displaying in image search (it didn't work there anyway)
I didn't want to add a specific TLD so that it would work for all Google pages (like .com, .ca, .de, .fr, etc).
It works with Violentmonkey, but that's apparently the only implementation of the userscript addons that can do this, all other limit the wildcard usage.
I've updated the code, which now does the check in the code itself. Which means that initially it is loaded for all pages that contain a "/search" in their URL, but it is what it is. I didn't want to limit it only a couple of domains (there seems to be a limit for how many @match
es you can add).
It now should only run on those pages that are owned by Google and abort otherwise (and also abort on the Image Search page).
I also had to update the code slightly, as Google made some changes, at least for me, so that the navigation wasn't showing at the correct position anymore.
Thank you so much, really thank you!
Hello! Thank you very much for this script! Unfortunately, there is a problem when using this script in combination with Google's advanced search. When calling up the page 2 of the search results, the search string is changing to "null". Therefore, this script doesn't work properly with the advanced search of Google. Maybe, you can fix it. Would be great in any case!
This script works fine in 2024-4-6.
Thank you Sir.
This script restores the paged navigation for the Google Search.
It also requires that the "Continuous scrolling" in the Google Search settings has been turned off. Unfortunatly this setting doesn't restore the old behavior, which is why this script exists.