您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Auto-preview Google Sheets image tooltips with zoom-to-fit, regardless of pixelation. Escape or click to close. Adjustable zoom %.
// ==UserScript== // @name Google Sheets Image Zoom // @namespace https://github.com/1LineAtaTime/TamperMonkey-Scripts // @version 2.0 // @description Auto-preview Google Sheets image tooltips with zoom-to-fit, regardless of pixelation. Escape or click to close. Adjustable zoom %. // @author 1LineAtaTime // @match https://docs.google.com/spreadsheets/* // @grant none // @license GPL-3.0 // ==/UserScript== (function () { 'use strict'; const DEBUG = false; //Adds messages to the console log for debugging purposes. const FILL_PERCENTAGE = 1.0; // Set to 1.0 for 100%, 0.95 for 95%, etc. const BUBBLE_WRAPPER_SELECTOR = '.waffle-multilink-tooltip'; const IMAGE_SELECTOR = '.link-bubble-drive-thumbnail-image'; const LINK_SELECTOR = '#docs-linkbubble-link-text'; const IMAGE_ID = 'gs_bubble_preview_img'; const ZINDEX = 999999; let currentHref = ''; let observer = null; let mainObserver = null; let observerInterval; function log(...args) { if (DEBUG) console.log('[GS‑Preview]', ...args); } function hideOverlay() { const img = document.getElementById(IMAGE_ID); if (img) { img.remove(); log('hide image'); } } function buildOverlay(imgSrc) { log('Request to show image:', imgSrc); hideOverlay(); const img = document.createElement('img'); img.id = IMAGE_ID; img.src = imgSrc; img.style.cssText = ` position: fixed; inset: 0; margin: auto; width: auto; height: auto; object-fit: contain; z-index: ${ZINDEX}; pointer-events: auto; background: transparent; cursor: zoom-out; box-shadow: 0 0 25px rgba(0,0,0,0.6); transition: opacity 0.2s ease; opacity: 0; `; img.addEventListener('click', hideOverlay); img.onload = () => { const vw = window.innerWidth; const vh = window.innerHeight; const iw = img.naturalWidth; const ih = img.naturalHeight; log('Original image size:', iw + 'x' + ih); log('Viewport:', vw + 'x' + vh); const aspectRatio = iw / ih; const maxW = vw * FILL_PERCENTAGE; const maxH = vh * FILL_PERCENTAGE; let finalW, finalH; if (maxW / maxH < aspectRatio) { finalW = maxW; finalH = finalW / aspectRatio; } else { finalH = maxH; finalW = finalH * aspectRatio; } log('Scaled image size:', Math.round(finalW) + 'x' + Math.round(finalH)); img.style.width = `${finalW}px`; img.style.height = `${finalH}px`; img.style.opacity = '1'; }; document.body.appendChild(img); } function getBubbleLinkHref() { const linkEl = document.querySelector(LINK_SELECTOR); return linkEl ? linkEl.href : null; } function getImageSrcFromBubble() { const imgEl = document.querySelector(IMAGE_SELECTOR); return imgEl ? imgEl.src : null; } function isBubbleVisible() { const wrapper = document.querySelector(BUBBLE_WRAPPER_SELECTOR); return wrapper && wrapper.offsetParent !== null && wrapper.style.display !== 'none'; } function observeBubbleWrapperWhenAvailable() { if (mainObserver) mainObserver.disconnect(); mainObserver = new MutationObserver(() => { const wrapper = document.querySelector(BUBBLE_WRAPPER_SELECTOR); if (wrapper) { log('Bubble wrapper found — starting inner observer'); startBubbleObserver(wrapper); mainObserver.disconnect(); // Only attach once } }); mainObserver.observe(document.body, { childList: true, subtree: true }); log('Waiting for bubble wrapper to appear...'); } function startBubbleObserver(wrapper) { if (observer) observer.disconnect(); observer = new MutationObserver(() => { if (!isBubbleVisible()) { hideOverlay(); currentHref = ''; return; } const newHref = getBubbleLinkHref(); if (!newHref || newHref === currentHref) return; const imgSrc = getImageSrcFromBubble(); if (imgSrc) { currentHref = newHref; buildOverlay(imgSrc); } }); observer.observe(wrapper, { attributes: true, childList: true, subtree: true }); log('Started observing for Sheets bubbles'); } function ensureObserverAlive() { if (!observer || observer.takeRecords().length === 0) { log('Observer heartbeat: refreshing...'); observeBubbleWrapperWhenAvailable(); } } document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideOverlay(); }); window.addEventListener('load', () => { log('Page loaded – watching for bubble wrapper dynamically'); observeBubbleWrapperWhenAvailable(); observerInterval = setInterval(ensureObserverAlive, 3000); }); })();