At the Google Images preview pan the script adds a button that opens an image in a new tab
// ==UserScript== // @name Google Images View Button // @description At the Google Images preview pan the script adds a button that opens an image in a new tab // @author Konf // @namespace https://greasyfork.org/users/424058 // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com // @version 2.0.0 // @match *://*.google.ad/search* // @match *://*.google.ae/search* // @match *://*.google.al/search* // @match *://*.google.am/search* // @match *://*.google.as/search* // @match *://*.google.at/search* // @match *://*.google.az/search* // @match *://*.google.ba/search* // @match *://*.google.be/search* // @match *://*.google.bf/search* // @match *://*.google.bg/search* // @match *://*.google.bi/search* // @match *://*.google.bj/search* // @match *://*.google.bs/search* // @match *://*.google.bt/search* // @match *://*.google.by/search* // @match *://*.google.ca/search* // @match *://*.google.cat/search* // @match *://*.google.cd/search* // @match *://*.google.cf/search* // @match *://*.google.cg/search* // @match *://*.google.ch/search* // @match *://*.google.ci/search* // @match *://*.google.cl/search* // @match *://*.google.cm/search* // @match *://*.google.cn/search* // @match *://*.google.co.ao/search* // @match *://*.google.co.bw/search* // @match *://*.google.co.ck/search* // @match *://*.google.co.cr/search* // @match *://*.google.co.id/search* // @match *://*.google.co.il/search* // @match *://*.google.co.in/search* // @match *://*.google.co.jp/search* // @match *://*.google.co.ke/search* // @match *://*.google.co.kr/search* // @match *://*.google.co.ls/search* // @match *://*.google.co.ma/search* // @match *://*.google.co.mz/search* // @match *://*.google.co.nz/search* // @match *://*.google.co.th/search* // @match *://*.google.co.tz/search* // @match *://*.google.co.ug/search* // @match *://*.google.co.uk/search* // @match *://*.google.co.uz/search* // @match *://*.google.co.ve/search* // @match *://*.google.co.vi/search* // @match *://*.google.co.za/search* // @match *://*.google.co.zm/search* // @match *://*.google.co.zw/search* // @match *://*.google.com/search* // @match *://*.google.com.af/search* // @match *://*.google.com.ag/search* // @match *://*.google.com.ai/search* // @match *://*.google.com.ar/search* // @match *://*.google.com.au/search* // @match *://*.google.com.bd/search* // @match *://*.google.com.bh/search* // @match *://*.google.com.bn/search* // @match *://*.google.com.bo/search* // @match *://*.google.com.br/search* // @match *://*.google.com.bz/search* // @match *://*.google.com.co/search* // @match *://*.google.com.cu/search* // @match *://*.google.com.cy/search* // @match *://*.google.com.do/search* // @match *://*.google.com.ec/search* // @match *://*.google.com.eg/search* // @match *://*.google.com.et/search* // @match *://*.google.com.fj/search* // @match *://*.google.com.gh/search* // @match *://*.google.com.gi/search* // @match *://*.google.com.gt/search* // @match *://*.google.com.hk/search* // @match *://*.google.com.jm/search* // @match *://*.google.com.kh/search* // @match *://*.google.com.kw/search* // @match *://*.google.com.lb/search* // @match *://*.google.com.ly/search* // @match *://*.google.com.mm/search* // @match *://*.google.com.mt/search* // @match *://*.google.com.mx/search* // @match *://*.google.com.my/search* // @match *://*.google.com.na/search* // @match *://*.google.com.ng/search* // @match *://*.google.com.ni/search* // @match *://*.google.com.np/search* // @match *://*.google.com.om/search* // @match *://*.google.com.pa/search* // @match *://*.google.com.pe/search* // @match *://*.google.com.pg/search* // @match *://*.google.com.ph/search* // @match *://*.google.com.pk/search* // @match *://*.google.com.pr/search* // @match *://*.google.com.py/search* // @match *://*.google.com.qa/search* // @match *://*.google.com.sa/search* // @match *://*.google.com.sb/search* // @match *://*.google.com.sg/search* // @match *://*.google.com.sl/search* // @match *://*.google.com.sv/search* // @match *://*.google.com.tj/search* // @match *://*.google.com.tr/search* // @match *://*.google.com.tw/search* // @match *://*.google.com.ua/search* // @match *://*.google.com.uy/search* // @match *://*.google.com.vc/search* // @match *://*.google.com.vn/search* // @match *://*.google.cv/search* // @match *://*.google.cz/search* // @match *://*.google.de/search* // @match *://*.google.dj/search* // @match *://*.google.dk/search* // @match *://*.google.dm/search* // @match *://*.google.dz/search* // @match *://*.google.ee/search* // @match *://*.google.es/search* // @match *://*.google.fi/search* // @match *://*.google.fm/search* // @match *://*.google.fr/search* // @match *://*.google.ga/search* // @match *://*.google.ge/search* // @match *://*.google.gg/search* // @match *://*.google.gl/search* // @match *://*.google.gm/search* // @match *://*.google.gr/search* // @match *://*.google.gy/search* // @match *://*.google.hk/search* // @match *://*.google.hn/search* // @match *://*.google.hr/search* // @match *://*.google.ht/search* // @match *://*.google.hu/search* // @match *://*.google.ie/search* // @match *://*.google.im/search* // @match *://*.google.iq/search* // @match *://*.google.is/search* // @match *://*.google.it/search* // @match *://*.google.je/search* // @match *://*.google.jo/search* // @match *://*.google.jp/search* // @match *://*.google.kg/search* // @match *://*.google.ki/search* // @match *://*.google.kz/search* // @match *://*.google.la/search* // @match *://*.google.li/search* // @match *://*.google.lk/search* // @match *://*.google.lt/search* // @match *://*.google.lu/search* // @match *://*.google.lv/search* // @match *://*.google.md/search* // @match *://*.google.me/search* // @match *://*.google.mg/search* // @match *://*.google.mk/search* // @match *://*.google.ml/search* // @match *://*.google.mn/search* // @match *://*.google.ms/search* // @match *://*.google.mu/search* // @match *://*.google.mv/search* // @match *://*.google.mw/search* // @match *://*.google.ne/search* // @match *://*.google.nl/search* // @match *://*.google.no/search* // @match *://*.google.nr/search* // @match *://*.google.nu/search* // @match *://*.google.pl/search* // @match *://*.google.pn/search* // @match *://*.google.ps/search* // @match *://*.google.pt/search* // @match *://*.google.ro/search* // @match *://*.google.rs/search* // @match *://*.google.ru/search* // @match *://*.google.rw/search* // @match *://*.google.sc/search* // @match *://*.google.se/search* // @match *://*.google.sh/search* // @match *://*.google.si/search* // @match *://*.google.sk/search* // @match *://*.google.sm/search* // @match *://*.google.sn/search* // @match *://*.google.so/search* // @match *://*.google.sr/search* // @match *://*.google.st/search* // @match *://*.google.td/search* // @match *://*.google.tg/search* // @match *://*.google.tl/search* // @match *://*.google.tm/search* // @match *://*.google.tn/search* // @match *://*.google.to/search* // @match *://*.google.tt/search* // @match *://*.google.vg/search* // @match *://*.google.vu/search* // @match *://*.google.ws/search* // @compatible Chrome // @compatible Opera // @compatible Firefox // @run-at document-body // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @noframes // ==/UserScript== /** * Hi! Don't change (or even resave) anything here because * doing so in Tampermonkey will turn off the script updates. * Not sure about other script managers. * This can be restored in settings, but it might be hard to find, * so it's better to reinstall the script if you're not sure. */ /* jshint esversion: 11 */ (function() { 'use strict'; // https://icons8.com/icon/43740/linking // https://img.icons8.com/small/96/ffffff/external-link-squared.png const viewBtnIconBase64 = [ 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4A', 'AAACXBIWXMAAAsTAAALEwEAmpwYAAAC9klEQVR4nO2dy04UQRSGawUmEFdGQRPjWnGJ+', 'ARKiM8hlydxA2pivLyFlxF9EtGFibwAhkvEAOYznTkmxEz1MNBTp7rr/9bddbr+r6d6Z', 'mBOhSCEEEIIIYQQQgghxDkAbgBrwCawBRzQfg5sLtWcVqs5ZndzAFeA58Ax3ecYeAPMh', 'hwAloBdyuMnsOgd/gpwQrmcAMte4T8sPPx//AEepQ5/ttBlp245upZSwOvopcAvYB2YB', '6ZCywGmgHvABnBYM++XKd9qxt7t/ABuh44CzAHbkbkfAddTXMRazZ3f2fD/kxB7JayEc', 'WMfSAaxHgoBeBrJoJei+LdI8flQCPSfCYPYSlF8P1K89Q/cswJMRzLYC+MmUhivuuMmt', 'xwkwJCARIQIox7fGF6FcSK3HCTAkIBEhAijHt8YboUbApgA3ktAC8KvqBlrICkm4VPYI', 'XwJcA5fAhoAmAQ+Dsk59gWjlqAEd/5n4JIE+IT/qQrfjtcroOHw3501fDtHArzCt/Mkw', 'Ct8O1cCvMK38yXAK3wbQwK8wrdxJMArfBtLArzCt/EkwCt8G1MCvMK3cSXAK3wbWwK8w', 'rfxJeCC4W+eN3yrIQE14b8dZ/hWRwK8wrdaEuAVvtWTAK/wraYEjBj+ZHBkVGGtKQy8G', 'hJ+9WfGiabqXeA6OyvgJvA91zu/8wJqJGQTfucFDJCQVfhFCKgAbtkzwX3NL1JAzkiAM', 'xLgjAQ4IwHOSIAzEuCMBDgjAc5IgDMS4IwEFCxgL1J7OhQCcDmSwW6K4l8jxUtqWbbg2', 'bIs9pvbjVAIwLNIBh8821ZWrRzvhI4D3PVuW1nXuLVqajoXuh3+dk3j1jQt7Yf858Kh9', 'dVc6MKDmX6HxPu27PyumfeLlBc1Yw2rRZ8d4GoyASbhQSE7ZgyjauG/lDT8UxKWC99D4', 'AR47BL+KQmLhS5HO9UmFiGjTXyeDHlIdYUj28RnJuRG1T/f9pXpAV9q+ky3iX2bS8/ml', 'sfuSUIIIYQQQgghhBAitI2/ZYk4Uk/wyKQAAAAASUVORK5CYII=' ].join(''); let ignoreThumbnails = GM_getValue('ignoreThumbnails', true); let menuId = null; (function updateMenu() { if (menuId) GM_unregisterMenuCommand(menuId); menuId = GM_registerMenuCommand(`Ignore thumbnails: ${ignoreThumbnails}`, () => { ignoreThumbnails = !ignoreThumbnails; GM_setValue('ignoreThumbnails', ignoreThumbnails); updateMenu(); }); }()); // Skip if it is not an image search section if ((new URLSearchParams(window.location.search)).get('udm') !== '2') return; waitForElement('div[data-viewer-id] a > img', { existing: true, }, (image) => { // Recursion skip if (image.matches('.GIVB-icon')) return; // Remove existing view button, if present image.parentElement.querySelector('a.GIVB-btn')?.remove(); // Might be not reliable enough, but it's the best I've found const imageIsThumbnail = [ ...image.parentElement.children ].filter(n => n.nodeName === 'IMG').length === 1; if (imageIsThumbnail && ignoreThumbnails) return; const viewBtn = document.createElement('a'); const viewBtnIcon = document.createElement('img'); viewBtn.addEventListener('click', (ev) => { ev.preventDefault(); window.open(viewBtn.href, '_blank'); }); viewBtn.href = image.src; viewBtn.title = 'Open in a new tab'; viewBtn.className = 'GIVB-btn'; viewBtnIcon.className = 'GIVB-icon'; viewBtnIcon.draggable = false; viewBtnIcon.src = viewBtnIconBase64; viewBtn.append(viewBtnIcon); image.parentElement.append(viewBtn); }); GM_addStyle([` .GIVB-btn { position: absolute; top: 16px; right: 16px; height: 36px; width: 36px; background-color: #0009; border-radius: 50%; } .GIVB-btn:hover { background-color: #000c; } .GIVB-icon { position: absolute; top: 6px; right: 6px; height: 24px; width: 24px; } `][0]); // utils > ----------------------------------------------------------------------- function waitForElement(query, { callbackOnTimeout = false, existing = false, onceOnly = false, rootElement = document.documentElement, timeout, // "attributes" prop is not supported observerOptions = { childList: true, subtree: true, }, }, callback) { if (!query) throw new Error('Query is needed'); if (!callback) throw new Error('Callback is needed'); observerOptions = Object.assign({}, observerOptions); const handledElements = new WeakSet(); const existingElements = rootElement.querySelectorAll(query); let timeoutId = null; if (existingElements.length) { // Mark all as handled for a proper work when `existing` is false // to ignore them later on for (const node of existingElements) { handledElements.add(node); } if (existing) { if (onceOnly) { try { callback(existingElements[0]); } catch (e) { console.error(e); } return; } else { for (const node of existingElements) { try { callback(node); } catch (e) { console.error(e); } } } } } const observer = new MutationObserver((mutations, observer) => { for (const node of rootElement.querySelectorAll(query)) { if (handledElements.has(node)) continue; handledElements.add(node); try { callback(node); } catch (e) { console.error(e); } if (onceOnly) { observer.disconnect(); if (timeoutId) clearTimeout(timeoutId); return; } } }); observer.observe(rootElement, { attributes: false, childList: observerOptions.childList || false, subtree: observerOptions.subtree || false, }); if (timeout !== undefined) { timeoutId = setTimeout(() => { observer.disconnect(); if (callbackOnTimeout) { try { callback(null); } catch (e) { console.error(e); } } }, timeout); } return observer; } // < utils ----------------------------------------------------------------------- }());