Google Images View Button

At the Google Images preview pan the script adds a button that opens an image in a new tab

  1. // ==UserScript==
  2. // @name Google Images View Button
  3. // @description At the Google Images preview pan the script adds a button that opens an image in a new tab
  4. // @author Konf
  5. // @namespace https://gf.qytechs.cn/users/424058
  6. // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
  7. // @version 2.0.0
  8. // @match *://*.google.ad/search*
  9. // @match *://*.google.ae/search*
  10. // @match *://*.google.al/search*
  11. // @match *://*.google.am/search*
  12. // @match *://*.google.as/search*
  13. // @match *://*.google.at/search*
  14. // @match *://*.google.az/search*
  15. // @match *://*.google.ba/search*
  16. // @match *://*.google.be/search*
  17. // @match *://*.google.bf/search*
  18. // @match *://*.google.bg/search*
  19. // @match *://*.google.bi/search*
  20. // @match *://*.google.bj/search*
  21. // @match *://*.google.bs/search*
  22. // @match *://*.google.bt/search*
  23. // @match *://*.google.by/search*
  24. // @match *://*.google.ca/search*
  25. // @match *://*.google.cat/search*
  26. // @match *://*.google.cd/search*
  27. // @match *://*.google.cf/search*
  28. // @match *://*.google.cg/search*
  29. // @match *://*.google.ch/search*
  30. // @match *://*.google.ci/search*
  31. // @match *://*.google.cl/search*
  32. // @match *://*.google.cm/search*
  33. // @match *://*.google.cn/search*
  34. // @match *://*.google.co.ao/search*
  35. // @match *://*.google.co.bw/search*
  36. // @match *://*.google.co.ck/search*
  37. // @match *://*.google.co.cr/search*
  38. // @match *://*.google.co.id/search*
  39. // @match *://*.google.co.il/search*
  40. // @match *://*.google.co.in/search*
  41. // @match *://*.google.co.jp/search*
  42. // @match *://*.google.co.ke/search*
  43. // @match *://*.google.co.kr/search*
  44. // @match *://*.google.co.ls/search*
  45. // @match *://*.google.co.ma/search*
  46. // @match *://*.google.co.mz/search*
  47. // @match *://*.google.co.nz/search*
  48. // @match *://*.google.co.th/search*
  49. // @match *://*.google.co.tz/search*
  50. // @match *://*.google.co.ug/search*
  51. // @match *://*.google.co.uk/search*
  52. // @match *://*.google.co.uz/search*
  53. // @match *://*.google.co.ve/search*
  54. // @match *://*.google.co.vi/search*
  55. // @match *://*.google.co.za/search*
  56. // @match *://*.google.co.zm/search*
  57. // @match *://*.google.co.zw/search*
  58. // @match *://*.google.com/search*
  59. // @match *://*.google.com.af/search*
  60. // @match *://*.google.com.ag/search*
  61. // @match *://*.google.com.ai/search*
  62. // @match *://*.google.com.ar/search*
  63. // @match *://*.google.com.au/search*
  64. // @match *://*.google.com.bd/search*
  65. // @match *://*.google.com.bh/search*
  66. // @match *://*.google.com.bn/search*
  67. // @match *://*.google.com.bo/search*
  68. // @match *://*.google.com.br/search*
  69. // @match *://*.google.com.bz/search*
  70. // @match *://*.google.com.co/search*
  71. // @match *://*.google.com.cu/search*
  72. // @match *://*.google.com.cy/search*
  73. // @match *://*.google.com.do/search*
  74. // @match *://*.google.com.ec/search*
  75. // @match *://*.google.com.eg/search*
  76. // @match *://*.google.com.et/search*
  77. // @match *://*.google.com.fj/search*
  78. // @match *://*.google.com.gh/search*
  79. // @match *://*.google.com.gi/search*
  80. // @match *://*.google.com.gt/search*
  81. // @match *://*.google.com.hk/search*
  82. // @match *://*.google.com.jm/search*
  83. // @match *://*.google.com.kh/search*
  84. // @match *://*.google.com.kw/search*
  85. // @match *://*.google.com.lb/search*
  86. // @match *://*.google.com.ly/search*
  87. // @match *://*.google.com.mm/search*
  88. // @match *://*.google.com.mt/search*
  89. // @match *://*.google.com.mx/search*
  90. // @match *://*.google.com.my/search*
  91. // @match *://*.google.com.na/search*
  92. // @match *://*.google.com.ng/search*
  93. // @match *://*.google.com.ni/search*
  94. // @match *://*.google.com.np/search*
  95. // @match *://*.google.com.om/search*
  96. // @match *://*.google.com.pa/search*
  97. // @match *://*.google.com.pe/search*
  98. // @match *://*.google.com.pg/search*
  99. // @match *://*.google.com.ph/search*
  100. // @match *://*.google.com.pk/search*
  101. // @match *://*.google.com.pr/search*
  102. // @match *://*.google.com.py/search*
  103. // @match *://*.google.com.qa/search*
  104. // @match *://*.google.com.sa/search*
  105. // @match *://*.google.com.sb/search*
  106. // @match *://*.google.com.sg/search*
  107. // @match *://*.google.com.sl/search*
  108. // @match *://*.google.com.sv/search*
  109. // @match *://*.google.com.tj/search*
  110. // @match *://*.google.com.tr/search*
  111. // @match *://*.google.com.tw/search*
  112. // @match *://*.google.com.ua/search*
  113. // @match *://*.google.com.uy/search*
  114. // @match *://*.google.com.vc/search*
  115. // @match *://*.google.com.vn/search*
  116. // @match *://*.google.cv/search*
  117. // @match *://*.google.cz/search*
  118. // @match *://*.google.de/search*
  119. // @match *://*.google.dj/search*
  120. // @match *://*.google.dk/search*
  121. // @match *://*.google.dm/search*
  122. // @match *://*.google.dz/search*
  123. // @match *://*.google.ee/search*
  124. // @match *://*.google.es/search*
  125. // @match *://*.google.fi/search*
  126. // @match *://*.google.fm/search*
  127. // @match *://*.google.fr/search*
  128. // @match *://*.google.ga/search*
  129. // @match *://*.google.ge/search*
  130. // @match *://*.google.gg/search*
  131. // @match *://*.google.gl/search*
  132. // @match *://*.google.gm/search*
  133. // @match *://*.google.gr/search*
  134. // @match *://*.google.gy/search*
  135. // @match *://*.google.hk/search*
  136. // @match *://*.google.hn/search*
  137. // @match *://*.google.hr/search*
  138. // @match *://*.google.ht/search*
  139. // @match *://*.google.hu/search*
  140. // @match *://*.google.ie/search*
  141. // @match *://*.google.im/search*
  142. // @match *://*.google.iq/search*
  143. // @match *://*.google.is/search*
  144. // @match *://*.google.it/search*
  145. // @match *://*.google.je/search*
  146. // @match *://*.google.jo/search*
  147. // @match *://*.google.jp/search*
  148. // @match *://*.google.kg/search*
  149. // @match *://*.google.ki/search*
  150. // @match *://*.google.kz/search*
  151. // @match *://*.google.la/search*
  152. // @match *://*.google.li/search*
  153. // @match *://*.google.lk/search*
  154. // @match *://*.google.lt/search*
  155. // @match *://*.google.lu/search*
  156. // @match *://*.google.lv/search*
  157. // @match *://*.google.md/search*
  158. // @match *://*.google.me/search*
  159. // @match *://*.google.mg/search*
  160. // @match *://*.google.mk/search*
  161. // @match *://*.google.ml/search*
  162. // @match *://*.google.mn/search*
  163. // @match *://*.google.ms/search*
  164. // @match *://*.google.mu/search*
  165. // @match *://*.google.mv/search*
  166. // @match *://*.google.mw/search*
  167. // @match *://*.google.ne/search*
  168. // @match *://*.google.nl/search*
  169. // @match *://*.google.no/search*
  170. // @match *://*.google.nr/search*
  171. // @match *://*.google.nu/search*
  172. // @match *://*.google.pl/search*
  173. // @match *://*.google.pn/search*
  174. // @match *://*.google.ps/search*
  175. // @match *://*.google.pt/search*
  176. // @match *://*.google.ro/search*
  177. // @match *://*.google.rs/search*
  178. // @match *://*.google.ru/search*
  179. // @match *://*.google.rw/search*
  180. // @match *://*.google.sc/search*
  181. // @match *://*.google.se/search*
  182. // @match *://*.google.sh/search*
  183. // @match *://*.google.si/search*
  184. // @match *://*.google.sk/search*
  185. // @match *://*.google.sm/search*
  186. // @match *://*.google.sn/search*
  187. // @match *://*.google.so/search*
  188. // @match *://*.google.sr/search*
  189. // @match *://*.google.st/search*
  190. // @match *://*.google.td/search*
  191. // @match *://*.google.tg/search*
  192. // @match *://*.google.tl/search*
  193. // @match *://*.google.tm/search*
  194. // @match *://*.google.tn/search*
  195. // @match *://*.google.to/search*
  196. // @match *://*.google.tt/search*
  197. // @match *://*.google.vg/search*
  198. // @match *://*.google.vu/search*
  199. // @match *://*.google.ws/search*
  200. // @compatible Chrome
  201. // @compatible Opera
  202. // @compatible Firefox
  203. // @run-at document-body
  204. // @grant GM_addStyle
  205. // @grant GM_setValue
  206. // @grant GM_getValue
  207. // @grant GM_registerMenuCommand
  208. // @grant GM_unregisterMenuCommand
  209. // @noframes
  210. // ==/UserScript==
  211.  
  212. /**
  213. * Hi! Don't change (or even resave) anything here because
  214. * doing so in Tampermonkey will turn off the script updates.
  215. * Not sure about other script managers.
  216. * This can be restored in settings, but it might be hard to find,
  217. * so it's better to reinstall the script if you're not sure.
  218. */
  219.  
  220. /* jshint esversion: 11 */
  221.  
  222. (function() {
  223. 'use strict';
  224.  
  225. // https://icons8.com/icon/43740/linking
  226. // https://img.icons8.com/small/96/ffffff/external-link-squared.png
  227. const viewBtnIconBase64 = [
  228. 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4A',
  229. 'AAACXBIWXMAAAsTAAALEwEAmpwYAAAC9klEQVR4nO2dy04UQRSGawUmEFdGQRPjWnGJ+',
  230. 'ARKiM8hlydxA2pivLyFlxF9EtGFibwAhkvEAOYznTkmxEz1MNBTp7rr/9bddbr+r6d6Z',
  231. 'mBOhSCEEEIIIYQQQgghxDkAbgBrwCawBRzQfg5sLtWcVqs5ZndzAFeA58Ax3ecYeAPMh',
  232. 'hwAloBdyuMnsOgd/gpwQrmcAMte4T8sPPx//AEepQ5/ttBlp245upZSwOvopcAvYB2YB',
  233. '6ZCywGmgHvABnBYM++XKd9qxt7t/ABuh44CzAHbkbkfAddTXMRazZ3f2fD/kxB7JayEc',
  234. 'WMfSAaxHgoBeBrJoJei+LdI8flQCPSfCYPYSlF8P1K89Q/cswJMRzLYC+MmUhivuuMmt',
  235. 'xwkwJCARIQIox7fGF6FcSK3HCTAkIBEhAijHt8YboUbApgA3ktAC8KvqBlrICkm4VPYI',
  236. 'XwJcA5fAhoAmAQ+Dsk59gWjlqAEd/5n4JIE+IT/qQrfjtcroOHw3501fDtHArzCt/Mkw',
  237. 'Ct8O1cCvMK38yXAK3wbQwK8wrdxJMArfBtLArzCt/EkwCt8G1MCvMK3cSXAK3wbWwK8w',
  238. 'rfxJeCC4W+eN3yrIQE14b8dZ/hWRwK8wrdaEuAVvtWTAK/wraYEjBj+ZHBkVGGtKQy8G',
  239. 'hJ+9WfGiabqXeA6OyvgJvA91zu/8wJqJGQTfucFDJCQVfhFCKgAbtkzwX3NL1JAzkiAM',
  240. 'xLgjAQ4IwHOSIAzEuCMBDgjAc5IgDMS4IwEFCxgL1J7OhQCcDmSwW6K4l8jxUtqWbbg2',
  241. 'bIs9pvbjVAIwLNIBh8821ZWrRzvhI4D3PVuW1nXuLVqajoXuh3+dk3j1jQt7Yf858Kh9',
  242. 'dVc6MKDmX6HxPu27PyumfeLlBc1Yw2rRZ8d4GoyASbhQSE7ZgyjauG/lDT8UxKWC99D4',
  243. 'AR47BL+KQmLhS5HO9UmFiGjTXyeDHlIdYUj28RnJuRG1T/f9pXpAV9q+ky3iX2bS8/ml',
  244. 'sfuSUIIIYQQQgghhBAitI2/ZYk4Uk/wyKQAAAAASUVORK5CYII='
  245. ].join('');
  246.  
  247. let ignoreThumbnails = GM_getValue('ignoreThumbnails', true);
  248. let menuId = null;
  249.  
  250. (function updateMenu() {
  251. if (menuId) GM_unregisterMenuCommand(menuId);
  252.  
  253. menuId = GM_registerMenuCommand(`Ignore thumbnails: ${ignoreThumbnails}`, () => {
  254. ignoreThumbnails = !ignoreThumbnails;
  255.  
  256. GM_setValue('ignoreThumbnails', ignoreThumbnails);
  257. updateMenu();
  258. });
  259. }());
  260.  
  261. // Skip if it is not an image search section
  262. if ((new URLSearchParams(window.location.search)).get('udm') !== '2') return;
  263.  
  264. waitForElement('div[data-viewer-id] a > img', {
  265. existing: true,
  266. }, (image) => {
  267. // Recursion skip
  268. if (image.matches('.GIVB-icon')) return;
  269.  
  270. // Remove existing view button, if present
  271. image.parentElement.querySelector('a.GIVB-btn')?.remove();
  272.  
  273. // Might be not reliable enough, but it's the best I've found
  274. const imageIsThumbnail = [
  275. ...image.parentElement.children
  276. ].filter(n => n.nodeName === 'IMG').length === 1;
  277.  
  278. if (imageIsThumbnail && ignoreThumbnails) return;
  279.  
  280. const viewBtn = document.createElement('a');
  281. const viewBtnIcon = document.createElement('img');
  282.  
  283. viewBtn.addEventListener('click', (ev) => {
  284. ev.preventDefault();
  285. window.open(viewBtn.href, '_blank');
  286. });
  287.  
  288. viewBtn.href = image.src;
  289. viewBtn.title = 'Open in a new tab';
  290. viewBtn.className = 'GIVB-btn';
  291. viewBtnIcon.className = 'GIVB-icon';
  292. viewBtnIcon.draggable = false;
  293. viewBtnIcon.src = viewBtnIconBase64;
  294.  
  295. viewBtn.append(viewBtnIcon);
  296. image.parentElement.append(viewBtn);
  297. });
  298.  
  299. GM_addStyle([`
  300. .GIVB-btn {
  301. position: absolute;
  302. top: 16px;
  303. right: 16px;
  304. height: 36px;
  305. width: 36px;
  306. background-color: #0009;
  307. border-radius: 50%;
  308. }
  309.  
  310. .GIVB-btn:hover {
  311. background-color: #000c;
  312. }
  313.  
  314. .GIVB-icon {
  315. position: absolute;
  316. top: 6px;
  317. right: 6px;
  318. height: 24px;
  319. width: 24px;
  320. }
  321. `][0]);
  322.  
  323.  
  324. // utils > -----------------------------------------------------------------------
  325.  
  326. function waitForElement(query, {
  327. callbackOnTimeout = false,
  328. existing = false,
  329. onceOnly = false,
  330. rootElement = document.documentElement,
  331. timeout,
  332.  
  333. // "attributes" prop is not supported
  334. observerOptions = {
  335. childList: true,
  336. subtree: true,
  337. },
  338. }, callback) {
  339. if (!query) throw new Error('Query is needed');
  340. if (!callback) throw new Error('Callback is needed');
  341.  
  342. observerOptions = Object.assign({}, observerOptions);
  343.  
  344. const handledElements = new WeakSet();
  345. const existingElements = rootElement.querySelectorAll(query);
  346. let timeoutId = null;
  347.  
  348. if (existingElements.length) {
  349. // Mark all as handled for a proper work when `existing` is false
  350. // to ignore them later on
  351. for (const node of existingElements) {
  352. handledElements.add(node);
  353. }
  354.  
  355. if (existing) {
  356. if (onceOnly) {
  357. try {
  358. callback(existingElements[0]);
  359. } catch (e) {
  360. console.error(e);
  361. }
  362.  
  363. return;
  364. } else {
  365. for (const node of existingElements) {
  366. try {
  367. callback(node);
  368. } catch (e) {
  369. console.error(e);
  370. }
  371. }
  372. }
  373. }
  374. }
  375.  
  376. const observer = new MutationObserver((mutations, observer) => {
  377. for (const node of rootElement.querySelectorAll(query)) {
  378. if (handledElements.has(node)) continue;
  379.  
  380. handledElements.add(node);
  381.  
  382. try {
  383. callback(node);
  384. } catch (e) {
  385. console.error(e);
  386. }
  387.  
  388. if (onceOnly) {
  389. observer.disconnect();
  390.  
  391. if (timeoutId) clearTimeout(timeoutId);
  392.  
  393. return;
  394. }
  395. }
  396. });
  397.  
  398. observer.observe(rootElement, {
  399. attributes: false,
  400. childList: observerOptions.childList || false,
  401. subtree: observerOptions.subtree || false,
  402. });
  403.  
  404. if (timeout !== undefined) {
  405. timeoutId = setTimeout(() => {
  406. observer.disconnect();
  407.  
  408. if (callbackOnTimeout) {
  409. try {
  410. callback(null);
  411. } catch (e) {
  412. console.error(e);
  413. }
  414. }
  415. }, timeout);
  416. }
  417.  
  418. return observer;
  419. }
  420.  
  421. // < utils -----------------------------------------------------------------------
  422. }());

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址