您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhanced markdown formatting for pollinations.ai with better readability, and smoother viewing
// ==UserScript== // @name Pollinations.ai Enhancer // @namespace https://greasyfork.org/en/users/1462897-fisventurous // @version 1.9.4 // @description Enhanced markdown formatting for pollinations.ai with better readability, and smoother viewing // @author fisventurous // @match *://*.pollinations.ai/* // @connect * // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-start // @license MIT // ==/UserScript== (function () { "use strict"; const THEME_KEY = "pollinations_enhancer_theme"; const FONT_SIZE_KEY = "pollinations_enhancer_fontsize"; let observer = null; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } function init() { const hostname = window.location.hostname; if (!hostname.startsWith('text.pollinations.ai') && !hostname.startsWith('image.pollinations.ai')) { console.log("Pollinations Enhancer: Skipping execution on unsupported domain:", hostname); if (document.readyState === "loading") { document.removeEventListener("DOMContentLoaded", init); } return; } addStyles(); applyTheme(); applyFontSize(); const pageType = detectPageType(); setupLinkPreviews(); if (pageType.isText) enhanceTextPage(); else if (pageType.isImage) enhanceImagePage(); else if (pageType.isAudio) enhanceAudioPage(); else createCommonButtons(extractUrlParameters(), "unknown"); if (pageType.isText) updateThemeToggleButton( document.body.classList.contains("theme-dark"), ); startObserver(pageType); } function detectPageType() { const url = window.location.href.toLowerCase(); const urlParams = new URLSearchParams(window.location.search); let isImage = false; let isAudio = false; let isText = false; if ( url.includes("image.pollinations.ai") || url.includes("/image/") ) { isImage = true; } else if ( url.includes("audio") || url.match(/\.(mp3|wav|ogg|m4a)(\?|$)/i) ) { isAudio = true; } else if (url.includes("text.pollinations.ai")) { isText = true; } if (!isImage && !isAudio && !isText) { const model = urlParams.get("model") || ""; const hasVoice = urlParams.has("voice"); if (model.includes("audio") || hasVoice) { isAudio = true; } else if (model.includes("image")) { isImage = true; } } if (!isImage && !isAudio && !isText) { if (document.querySelector('img:not([width="16"][height="16"])')) { isImage = true; } else if ( document.querySelector('audio, video, [type*="audio"]') ) { isAudio = true; } else { isText = true; } } return { isText, isImage, isAudio }; } function startObserver(pageType) { if (observer) observer.disconnect(); observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if ( mutation.type === "childList" && mutation.addedNodes.length ) { if ( pageType.isImage && !document.getElementById("save-image-btn") ) { const newImages = Array.from(mutation.addedNodes).filter( (node) => node.tagName === "IMG" || (node.querySelectorAll && node.querySelectorAll("img").length), ); if (newImages.length) { setTimeout(enhanceImagePage, 100); } } if ( pageType.isAudio && !document.getElementById("save-audio-btn") ) { const newAudio = Array.from(mutation.addedNodes).filter( (node) => node.tagName === "AUDIO" || node.tagName === "VIDEO" || (node.querySelectorAll && (node.querySelectorAll("audio").length || node.querySelectorAll("video") .length || node.querySelectorAll( 'source[type*="audio"]', ).length)), ); if (newAudio.length) { setTimeout(enhanceAudioPage, 100); } } } } }); observer.observe(document.body, { childList: true, subtree: true, }); } function applyTheme() { const savedTheme = GM_getValue(THEME_KEY, "theme-dark"); document.body.classList.remove("theme-dark", "theme-light"); document.body.classList.add(savedTheme); } function toggleTheme() { const isDark = document.body.classList.contains("theme-dark"); const newTheme = isDark ? "theme-light" : "theme-dark"; document.body.classList.remove("theme-dark", "theme-light"); document.body.classList.add(newTheme); GM_setValue(THEME_KEY, newTheme); updateThemeToggleButton(newTheme === "theme-dark"); } function updateThemeToggleButton(isDark) { const btn = document.getElementById("theme-toggle-btn"); if (btn) { btn.innerHTML = isDark ? '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"></path></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>'; } } function setupLinkPreviews() { const preview = document.createElement("div"); preview.className = "preview"; document.body.appendChild(preview); let showTimeout = null, hideTimeout = null, currentLink = null; document.body.addEventListener("mouseover", (e) => { const link = e.target.closest("a"); if (link && link.href) { clearTimeout(hideTimeout); if (currentLink !== link) { currentLink = link; clearTimeout(showTimeout); showTimeout = setTimeout( () => showPreview(link, preview), 200, ); } } }); document.body.addEventListener("mouseout", (e) => { if (e.target.closest("a")) { clearTimeout(showTimeout); hideTimeout = setTimeout(() => { preview.style.opacity = "0"; setTimeout(() => { preview.style.display = "none"; currentLink = null; }, 150); }, 200); } }); preview.addEventListener("mouseenter", () => clearTimeout(hideTimeout)); preview.addEventListener("mouseleave", () => { hideTimeout = setTimeout(() => { preview.style.opacity = "0"; setTimeout(() => { preview.style.display = "none"; currentLink = null; }, 150); }, 200); }); } function showPreview(link, preview) { try { const url = link.href; const rect = link.getBoundingClientRect(); let sourceName = "Source", faviconUrl = ""; try { const urlObj = new URL(url); sourceName = urlObj.hostname.replace(/^www\./, ""); faviconUrl = `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=64`; } catch (err) {} let contentHTML = link.textContent?.trim() || url; if (link.closest("pre, code")) contentHTML = `<code class="inline-code">${contentHTML}</code>`; preview.innerHTML = `<div class="preview-header"><div class="preview-icon">${faviconUrl ? `<img src="${faviconUrl}" alt="" onerror="this.style.display='none'; this.parentElement.textContent='${sourceName.charAt(0).toUpperCase()}';" />` : sourceName.charAt(0).toUpperCase()}</div><div>${sourceName}</div></div><div class="preview-content">${contentHTML}</div><div class="preview-url">${url}</div>`; preview.style.opacity = 0; preview.style.display = "block"; requestAnimationFrame(() => { const previewRect = preview.getBoundingClientRect(); const winWidth = window.innerWidth, winHeight = window.innerHeight; let top = rect.bottom + window.scrollY + 5, left = rect.left + window.scrollX; if (left + previewRect.width > winWidth - 10) left = winWidth - previewRect.width - 10; if (left < 10) left = 10; if (top + previewRect.height > winHeight + window.scrollY - 10) top = rect.top + window.scrollY - previewRect.height - 5; if (top < window.scrollY + 10) top = window.scrollY + 10; preview.style.top = `${top}px`; preview.style.left = `${left}px`; preview.classList.add("active"); preview.style.opacity = 1; }); } catch (e) { console.error("Pollinations Enhancer: Error showing preview", e); preview.classList.remove("active"); } } function enhanceTextPage() { document.body.classList.add("text-enhanced"); const params = extractUrlParameters(); let contentContainer = null; let originalContent = ""; try { contentContainer = document.querySelector( "main:not(:empty), article:not(:empty), .content:not(:empty), #content:not(:empty), .main-content:not(:empty), .post-content:not(:empty)", ); if ( !contentContainer && document.body.children.length === 1 && document.body.firstElementChild?.tagName === "PRE" ) { contentContainer = document.body.firstElementChild; } if ( !contentContainer && (document.body.innerText || document.body.textContent || "") .trim().length > 50 ) { contentContainer = document.createElement("div"); contentContainer.className = "content-container-generated"; while ( document.body.firstChild && (!document.body.firstChild.matches || !document.body.firstChild.matches( "#pollinations-enhancer-buttons, .preview, script, style, #metadata-box", )) ) { contentContainer.appendChild(document.body.firstChild); } document.body.appendChild(contentContainer); } if (contentContainer) { contentContainer.classList.add("content-container"); originalContent = contentContainer.innerText || contentContainer.textContent || ""; if (params.json === "true") { const jsonText = originalContent; contentContainer.innerHTML = ""; const pre = document.createElement("pre"); pre.className = "code-block-container"; const header = document.createElement("div"); header.className = "code-header"; const langSpan = document.createElement("span"); langSpan.className = "code-language"; langSpan.textContent = "json"; const copyBtn = document.createElement("button"); copyBtn.className = "code-copy-btn"; copyBtn.title = "Copy code"; copyBtn.textContent = "Copy"; copyBtn.addEventListener("click", () => { navigator.clipboard .writeText(jsonText) .then(() => { const originalText = copyBtn.textContent; copyBtn.textContent = "Copied!"; setTimeout(() => { copyBtn.textContent = originalText; }, 1500); }) .catch((err) => { console.error("Failed to copy JSON:", err); }); }); header.appendChild(langSpan); header.appendChild(copyBtn); const code = document.createElement("code"); code.className = "language-json"; code.textContent = jsonText; pre.appendChild(header); pre.appendChild(code); contentContainer.appendChild(pre); } else { if (contentContainer.tagName !== "PRE") { processMarkdown(contentContainer); } else { const preContent = contentContainer.textContent || ""; contentContainer.innerHTML = ""; const preWrapper = document.createElement("pre"); preWrapper.className = "code-block-container"; const header = document.createElement("div"); header.className = "code-header"; const langSpan = document.createElement("span"); langSpan.className = "code-language"; langSpan.textContent = "text"; const copyBtn = document.createElement("button"); copyBtn.className = "code-copy-btn"; copyBtn.title = "Copy code"; copyBtn.textContent = "Copy"; copyBtn.addEventListener("click", () => { navigator.clipboard .writeText(preContent) .then(() => { const originalText = copyBtn.textContent; copyBtn.textContent = "Copied!"; setTimeout(() => { copyBtn.textContent = originalText; }, 1500); }) .catch((err) => { console.error( "Failed to copy preformatted text:", err, ); }); }); header.appendChild(langSpan); header.appendChild(copyBtn); const code = document.createElement("code"); code.className = "language-text"; code.textContent = preContent; preWrapper.appendChild(header); preWrapper.appendChild(code); contentContainer.appendChild(preWrapper); } } } else { console.warn( "Pollinations Enhancer: Could not find suitable content container.", ); } } catch (error) { console.error( "Pollinations Enhancer: Error enhancing text page:", error, ); } createCommonButtons(params, "text", contentContainer, originalContent); } function processMarkdown(container) { if (!container) return; container.innerHTML = container.innerHTML .replace(/<span class="[^"]*">/g, "") .replace(/<\/span>/g, ""); const codeBlocks = []; let codeBlockCount = 0; container.innerHTML = container.innerHTML.replace( /```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => { const placeholder = `<!--CODEBLOCK_${codeBlockCount}-->`; codeBlocks.push({ placeholder, language: lang || "text", code: code.replace(/`/g, "`"), }); codeBlockCount++; return placeholder; }, ); const originalHtml = container.innerHTML; const content = container.textContent; const blockquoteHtml = processBlockquotes(content); if (blockquoteHtml !== content) { const processedWithCodeBlocksPreserved = restoreCodeBlocks( blockquoteHtml, originalHtml, ); container.innerHTML = processedWithCodeBlocksPreserved; } container.innerHTML = container.innerHTML.replace( /^(#{1,6})\s+(.+)$/gm, function (match, hashes, content) { const level = hashes.length; return `<h${level}>${content}</h${level}>`; }, ); container.innerHTML = container.innerHTML.replace( /^---+$/gm, '<hr class="markdown-hr">', ); container.innerHTML = processLists(container.innerHTML); container.innerHTML = container.innerHTML .replace(/\*\*(.*?)\*\*|__(.*?)__/g, "<strong>$1$2</strong>") .replace(/\*(.*?)\*|_(.*?)_/g, "<em>$1$2</em>") .replace(/~~(.*?)~~/g, "<del>$1</del>") .replace( /\[([^\]]+?)\]\((https?:\/\/[^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>', ) .replace(/`([^`]+?)`/g, '<code class="inline-code">$1</code>'); for (let i = 0; i < codeBlocks.length; i++) { const block = codeBlocks[i]; const html = `<pre class="code-block-container"><div class="code-header"><span class="code-language">${block.language}</span><button class="code-copy-btn" title="Copy code">Copy</button></div><code class="language-${block.language}">${block.code}</code></pre>`; container.innerHTML = container.innerHTML.replace( block.placeholder, html, ); } const copyButtons = container.querySelectorAll(".code-copy-btn"); copyButtons.forEach((btn) => { btn.addEventListener("click", () => { const codeBlock = btn.parentElement.nextElementSibling; const textToCopy = codeBlock.textContent; navigator.clipboard .writeText(textToCopy) .then(() => { const originalText = btn.textContent; btn.textContent = "Copied!"; setTimeout(() => { btn.textContent = originalText; }, 1500); }) .catch((err) => { console.error("Failed to copy code:", err); }); }); }); } function processBlockquotes(text) { const lines = text.split("\n"); let result = []; let inMultiBlockquote = false; let blockquoteContent = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmedLine = line.trim(); if (trimmedLine.startsWith(">>>")) { if (inMultiBlockquote) { result.push( `<blockquote class="blockquote-multi">${blockquoteContent.join(" ")}</blockquote>`, ); } inMultiBlockquote = true; const initial = trimmedLine.substring(3).trim(); blockquoteContent = initial ? [initial] : []; continue; } if (inMultiBlockquote) { if (trimmedLine === "" || trimmedLine.startsWith(">")) { result.push( `<blockquote class="blockquote-multi">${blockquoteContent.join(" ")}</blockquote>`, ); inMultiBlockquote = false; blockquoteContent = []; if (trimmedLine === "") { result.push(""); continue; } } else { blockquoteContent.push(trimmedLine); continue; } } if (trimmedLine === ">") { result.push(""); continue; } if (trimmedLine.startsWith("> ")) { const content = trimmedLine.substring(2); if (content) { result.push( `<blockquote class="blockquote-single">${content}</blockquote>`, ); } else { result.push(""); } continue; } result.push(line); } if (inMultiBlockquote) { result.push( `<blockquote class="blockquote-multi">${blockquoteContent.join(" ")}</blockquote>`, ); } return result.join("\n"); } function restoreCodeBlocks(blockquoteHtml, originalHtml) { const placeholders = []; const regex = /<!--CODEBLOCK_\d+-->/g; let match; while ((match = regex.exec(originalHtml)) !== null) { placeholders.push(match[0]); } if (placeholders.length === 0) { return blockquoteHtml; } const blockquoteLines = blockquoteHtml.split("\n"); const originalLines = originalHtml.split("\n"); let placeholderIndex = 0; for (let i = 0; i < originalLines.length; i++) { if ( originalLines[i].includes("<!--CODEBLOCK_") && placeholderIndex < placeholders.length ) { const placeholder = placeholders[placeholderIndex]; const insertPosition = Math.min(i, blockquoteLines.length); blockquoteLines.splice(insertPosition, 0, placeholder); placeholderIndex++; } } return blockquoteLines.join("\n"); } function processLists(text) { const lines = text.split("\n"); let result = []; let lastWasNumbered = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const numberedMatch = line.match(/^(\d+)\.\s+(.*)/); const dashMatch = line.match(/^[-*]\s+(.*)/); if (numberedMatch) { const num = numberedMatch[1]; const content = numberedMatch[2]; result.push( `<div class="numbered-item">${num}. ${content}</div>`, ); lastWasNumbered = true; } else if (dashMatch && lastWasNumbered) { const content = dashMatch[1]; result.push( `<div class="nested-bullet-item">${content}</div>`, ); } else if (dashMatch) { const content = dashMatch[1]; result.push(`<div class="bullet-item">${content}</div>`); } else if (line.trim() === "") { result.push(line); lastWasNumbered = false; } else { result.push(line); lastWasNumbered = false; } } return result.join("\n"); } function enhanceImagePage() { let mainImage = null; removeExistingButtons(); try { const directImages = document.querySelectorAll("body > img"); if (directImages.length > 0) { let largestArea = 0; directImages.forEach((img) => { if (img.complete) { const area = img.naturalWidth * img.naturalHeight; if (area > largestArea) { largestArea = area; mainImage = img; } } else { img.addEventListener("load", () => { setTimeout(enhanceImagePage, 100); }); } }); } if (!mainImage) { const allImages = document.querySelectorAll("img"); let largestArea = 0; allImages.forEach((img) => { if (img.width < 30 || img.height < 30) return; const area = img.naturalWidth * img.naturalHeight || img.width * img.height; if (area > largestArea) { largestArea = area; mainImage = img; } }); } } catch (error) { console.error( "Pollinations Enhancer: Error finding image element:", error, ); } const params = extractUrlParameters(); if (mainImage) { params.width = mainImage.naturalWidth || mainImage.width || 0; params.height = mainImage.naturalHeight || mainImage.height || 0; createCommonButtons(params, "image", mainImage); } else { setTimeout(enhanceImagePage, 500); } } function enhanceAudioPage() { removeExistingButtons(); const params = extractUrlParameters(); let audioSrc = findAudioSource(); if (!audioSrc) { audioSrc = window.location.href; } createCommonButtons(params, "audio", null, "", audioSrc); } function removeExistingButtons() { const existingContainer = document.getElementById( "pollinations-enhancer-buttons", ); if (existingContainer) existingContainer.remove(); const existingMetadata = document.getElementById("metadata-box"); if (existingMetadata) existingMetadata.remove(); } function findAudioSource() { const audioExtRegex = /\.(mp3|wav|ogg|m4a|flac|aac)(\?|$)/i; const audioElements = document.querySelectorAll("audio, video"); for (const el of audioElements) { if (el.src && el.src.length > 10) { return el.src; } for (const source of el.querySelectorAll("source")) { if (source.src && source.src.length > 10) { return source.src; } } } for (const link of document.querySelectorAll("a[href]")) { try { const url = new URL( link.getAttribute("href"), window.location.href, ); if ( audioExtRegex.test(url.pathname) || url.pathname.includes("/audio/") ) { return url.href; } } catch (e) {} } for (const el of document.querySelectorAll( '[type*="audio"], [src*=".mp3"], [src*="/audio/"]', )) { const src = el.getAttribute("src") || el.getAttribute("data-src"); if (src && src.length > 10) { return new URL(src, window.location.href).href; } } const metaOgAudio = document.querySelector( 'meta[property="og:audio"], meta[property="og:audio:url"]', ); if (metaOgAudio && metaOgAudio.content) { return metaOgAudio.content; } const htmlContent = document.documentElement.outerHTML; const audioUrlMatches = htmlContent.match( /https?:\/\/[^"'\s]+\.(mp3|wav|ogg|m4a)(\?[^"'\s]*)?/gi, ); if (audioUrlMatches && audioUrlMatches.length) { return audioUrlMatches[0]; } return null; } function createCommonButtons( params, type, targetElement = null, originalContent = "", resourceUrl = "", ) { try { removeExistingButtons(); const buttonContainer = document.createElement("div"); buttonContainer.id = "pollinations-enhancer-buttons"; buttonContainer.style.cssText = "position: fixed; top: 10px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 8px;"; if (type === "text") { const themeToggleBtn = createButton( "theme-toggle-btn", "Toggle theme (Light/Dark)", "", toggleTheme, ); buttonContainer.appendChild(themeToggleBtn); } if (type === "text" && targetElement) { const copyContentBtn = createButton( "copy-content-btn", "Copy text content", '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>', () => { navigator.clipboard .writeText( originalContent || (targetElement.innerText || targetElement.textContent || ""), ) .then( () => flashButton(copyContentBtn, true), () => flashButton(copyContentBtn, false), ); }, ); buttonContainer.appendChild(copyContentBtn); const fontSizeContainer = document.createElement("div"); fontSizeContainer.className = "font-size-controls"; fontSizeContainer.style.cssText = "position: relative; right: 41px; display: flex; gap: 5px;"; const increaseFontBtn = createButton( "increase-font-btn", "Increase text size", '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>', increaseFontSize, ); const decreaseFontBtn = createButton( "decrease-font-btn", "Decrease text size", '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line></svg>', decreaseFontSize, ); fontSizeContainer.appendChild(increaseFontBtn); fontSizeContainer.appendChild(decreaseFontBtn); buttonContainer.appendChild(fontSizeContainer); } if (type === "image" && targetElement?.src) { const saveImageBtn = createButton( "save-image-btn", "Save image (Stripped EXIF)", '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>', () => downloadResource( targetElement.src, `pollinations-image-${Date.now()}.jpg`, saveImageBtn, "image", ), ); buttonContainer.appendChild(saveImageBtn); } if (type === "audio" && resourceUrl) { const saveAudioBtn = createButton( "save-audio-btn", "Save audio", '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>', () => downloadResource( resourceUrl, `pollinations-audio-${Date.now()}.mp3`, saveAudioBtn, "audio", ), ); buttonContainer.appendChild(saveAudioBtn); } const hasMetadata = params.model || params.prompt || params.seed || params.voice || params.width || params.system || params.private || params.nologo; if (hasMetadata) { const metadataBtn = createButton( "metadata-btn", "View metadata", '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>', () => { const box = document.getElementById("metadata-box"); if (box) { box.classList.toggle("visible"); if (box.classList.contains("visible")) box.classList.remove("collapsed"); updateMetadataToggleIcon(box); } }, ); buttonContainer.appendChild(metadataBtn); createMetadataBox(params, type); } if (buttonContainer.hasChildNodes()) document.body.appendChild(buttonContainer); } catch (error) { console.error( "Pollinations Enhancer: Error creating common buttons:", error, ); } } function increaseFontSize() { const currentScale = parseFloat(GM_getValue(FONT_SIZE_KEY, "1")) || 1; const newScale = Math.min(currentScale + 0.1, 2.0).toFixed(1); GM_setValue(FONT_SIZE_KEY, newScale); applyFontSize(); } function decreaseFontSize() { const currentScale = parseFloat(GM_getValue(FONT_SIZE_KEY, "1")) || 1; const newScale = Math.max(currentScale - 0.1, 0.7).toFixed(1); GM_setValue(FONT_SIZE_KEY, newScale); applyFontSize(); } function applyFontSize() { const scale = GM_getValue(FONT_SIZE_KEY, "1") || "1"; const existingStyle = document.getElementById("font-size-style"); if (existingStyle) existingStyle.remove(); const styleEl = document.createElement("style"); styleEl.id = "font-size-style"; styleEl.textContent = ` .content-container { font-size: calc(17px * ${scale}) !important; line-height: 1.5 !important; transform: scale(1) !important; } `; document.head.appendChild(styleEl); } function createButton(id, title, innerHTML, onClick) { const btn = document.createElement("div"); btn.id = id; btn.className = "p-btn"; btn.title = title; btn.innerHTML = innerHTML; btn.addEventListener("click", onClick); btn.style.position = "relative"; btn.style.margin = "0"; return btn; } function flashButton(button, success) { if (!button) return; const originalColor = button.style.backgroundColor; button.style.backgroundColor = success ? "var(--success-color)" : "var(--error-color)"; button.style.transform = "scale(1.1)"; setTimeout(() => { if (button) { button.style.backgroundColor = originalColor; button.style.transform = "scale(1)"; } }, 1000); } function downloadResource( url, filename, buttonToFlash, resourceType = "unknown", ) { GM_xmlhttpRequest({ method: "GET", url: url, responseType: "blob", onload: function (response) { if (response.status === 200 && response.response) { const originalBlob = response.response; if ( resourceType === "image" && originalBlob.type.startsWith("image/") ) { const img = document.createElement("img"); const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); const objectURL = URL.createObjectURL(originalBlob); img.onload = () => { try { canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; ctx.drawImage(img, 0, 0); let mimeType = originalBlob.type; if ( ![ "image/jpeg", "image/png", "image/webp", ].includes(mimeType) ) { mimeType = "image/jpeg"; filename = filename.replace( /\.[^.]+$/, ".jpg", ); } canvas.toBlob( (processedBlob) => { if (processedBlob) { triggerDownload( processedBlob, filename, buttonToFlash, true, ); } else { triggerDownload( originalBlob, filename, buttonToFlash, false, ); } URL.revokeObjectURL(objectURL); }, mimeType, 0.92, ); } catch (e) { URL.revokeObjectURL(objectURL); triggerDownload( originalBlob, filename, buttonToFlash, false, ); } }; img.onerror = () => { URL.revokeObjectURL(objectURL); triggerDownload( originalBlob, filename, buttonToFlash, false, ); }; img.src = objectURL; } else { triggerDownload( originalBlob, filename, buttonToFlash, true, ); } } else { if (buttonToFlash) flashButton(buttonToFlash, false); tryDirectDownload(url, filename, buttonToFlash); } }, onerror: function (response) { if (buttonToFlash) flashButton(buttonToFlash, false); tryDirectDownload(url, filename, buttonToFlash); }, }); } function triggerDownload(blob, filename, buttonToFlash, successStatus) { try { const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href); if (buttonToFlash) flashButton(buttonToFlash, successStatus); } catch (e) { if (buttonToFlash) flashButton(buttonToFlash, false); } } function tryDirectDownload(url, filename, buttonToFlash) { try { const link = document.createElement("a"); link.href = url; link.download = filename; link.target = "_blank"; link.rel = "noopener noreferrer"; document.body.appendChild(link); link.click(); document.body.removeChild(link); } catch (err) { if (buttonToFlash) flashButton(buttonToFlash, false); } } function createMetadataBox(params, type) { const existingBox = document.getElementById("metadata-box"); if (existingBox) existingBox.remove(); const metadataBox = document.createElement("div"); metadataBox.id = "metadata-box"; metadataBox.className = "meta-box"; let contentHTML = ""; if (params.model) contentHTML += createMetaRow("Model", params.model); if (params.prompt) contentHTML += createMetaRow("Prompt", params.prompt, true); if (params.seed) contentHTML += createMetaRow("Seed", params.seed); if (params.private) contentHTML += createMetaRow("Private", params.private); if (params.json) contentHTML += createMetaRow("JSON Mode", params.json); if (type === "image") { if (params.width && params.height) contentHTML += createMetaRow( "Size", `${params.width} × ${params.height}`, ); if (params.guidance_scale) contentHTML += createMetaRow( "Guidance Scale", params.guidance_scale, ); if (params.negative_prompt) contentHTML += createMetaRow( "Negative Prompt", params.negative_prompt, true, ); if (params.strength) contentHTML += createMetaRow("Strength", params.strength); if (params.steps) contentHTML += createMetaRow("Steps", params.steps); if (params.nologo) contentHTML += createMetaRow("No Logo", params.nologo); } if (type === "audio") { if (params.voice) contentHTML += createMetaRow("Voice", params.voice); if (params.format) contentHTML += createMetaRow("Format", params.format); } if (type === "text") { if (params.system) contentHTML += createMetaRow("System", params.system, true); if (params.language) contentHTML += createMetaRow("Language", params.language); if (params.modalities) contentHTML += createMetaRow("Modalities", params.modalities); } if (!contentHTML.trim()) return; metadataBox.innerHTML = ` <div class="meta-header" title="Click to toggle details"> <span>${type.charAt(0).toUpperCase() + type.slice(1)} Parameters</span> <span class="toggle-icon">▼</span> </div> <div class="meta-content">${contentHTML}</div> `; document.body.appendChild(metadataBox); metadataBox.querySelector(".meta-header").addEventListener("click", () => { if (metadataBox.classList.contains("visible")) { metadataBox.classList.toggle("collapsed"); } else { metadataBox.classList.add("visible"); metadataBox.classList.remove("collapsed"); } updateMetadataToggleIcon(metadataBox); }); metadataBox .querySelectorAll(".copy-btn[data-copy-target]") .forEach((copyBtn) => { const targetLabel = copyBtn.getAttribute("data-copy-target"); const paramKey = targetLabel.toLowerCase().replace(" ", "_"); const textToCopy = params[paramKey]; if (textToCopy) { copyBtn.addEventListener("click", (e) => { e.stopPropagation(); navigator.clipboard .writeText(textToCopy) .then(() => { copyBtn.textContent = "✓"; setTimeout(() => { copyBtn.textContent = "📋"; }, 1200); }) .catch((err) => console.error( `Failed to copy ${targetLabel}:`, err, ), ); }); } else { copyBtn.style.display = "none"; } }); updateMetadataToggleIcon(metadataBox); } function updateMetadataToggleIcon(box) { const icon = box?.querySelector(".toggle-icon"); if (icon) icon.textContent = box.classList.contains("collapsed") ? "▼" : "▲"; } function createMetaRow(label, value, addCopyButton = false) { if (!value) return ""; const cleanValue = String(value) .replace(/</g, "<") .replace(/>/g, ">"); const copyBtnHTML = addCopyButton ? `<span class="copy-btn" title="Copy ${label}" data-copy-target="${label}">📋</span>` : ""; return `<div class="meta-row"><div class="meta-label">${label}:</div><div class="meta-value">${cleanValue}${copyBtnHTML}</div></div>`; } function sanitizePrompt(text) { if (!text) return ""; try { let decoded = text; for (let i = 0; i < 3; i++) { if (decoded.includes("%")) decoded = decodeURIComponent(decoded); else break; } return decoded.replace(/\+/g, " ").replace(/\uFFFD/gu, "").trim(); } catch (e) { return text.replace(/\+/g, " ").replace(/%20/g, " ").trim(); } } function extractUrlParameters() { const urlParams = new URLSearchParams(window.location.search); const path = window.location.pathname; let rawPrompt = ""; if (urlParams.has("prompt")) { rawPrompt = urlParams.get("prompt"); } else { const promptFromPath = decodeURIComponent(path) .split("/") .filter(Boolean) .pop(); if (promptFromPath) rawPrompt = promptFromPath; } return { prompt: sanitizePrompt(rawPrompt), model: urlParams.get("model") || "", seed: urlParams.get("seed") || "", voice: urlParams.get("voice") || "", width: urlParams.get("width") || "", height: urlParams.get("height") || "", system: sanitizePrompt(urlParams.get("system") || ""), private: urlParams.get("private") || "", format: urlParams.get("format") || "", modalities: urlParams.get("modalities") || "", guidance_scale: urlParams.get("guidance_scale") || "", negative_prompt: sanitizePrompt( urlParams.get("negative_prompt") || "", ), strength: urlParams.get("strength") || "", steps: urlParams.get("steps") || "", language: urlParams.get("language") || "", json: urlParams.get("json") || "", nologo: urlParams.get("nologo") || "", }; } function addStyles() { GM_addStyle(` :root { --text-color: #f0f0f0; --bg-color: #1a1a1a; --accent: #3498db; --accent-hover: #2980b9; --light-bg: #f2f2f2; --light-text: #333333; --light-content-bg: #ffffff; --dark-content-bg: #2d2d2d; --border-color-dark: #444; --border-color-light: #ddd; --button-bg: #333; --button-hover-bg: #444; --success-color: #27ae60; --error-color: #e74c3c; } body.theme-light { background-color: var(--light-bg) !important; color: var(--light-text) !important; } body.theme-dark { background-color: var(--bg-color) !important; color: var(--text-color) !important; } body { transition: background-color 0.3s, color 0.3s; } .p-btn { background-color: var(--accent); color: white; border: none; border-radius: 50%; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; margin: 5px; transition: all 0.2s; box-shadow: 0 2px 5px rgba(0,0,0,0.2); flex-shrink: 0; } .p-btn:hover { background-color: var(--accent-hover); transform: scale(1.05); } .p-btn svg { pointer-events: none; } .font-size-controls { display: flex; gap: 5px; justify-content: center; } .meta-box { position: fixed; bottom: 20px; right: 20px; background-color: rgba(45, 45, 45, 0.9); color: var(--text-color); border-radius: 8px; padding: 12px; max-width: 320px; width: auto; font-size: 13px; line-height: 1.5; z-index: 9998; transition: opacity 0.3s, transform 0.3s, height 0.3s ease-out; box-shadow: 0 4px 15px rgba(0,0,0,0.3); opacity: 0; transform: translateY(10px); pointer-events: none; overflow: hidden; backdrop-filter: blur(3px); } body.theme-light .meta-box { background-color: rgba(255, 255, 255, 0.9); color: var(--light-text); } .meta-box.visible { opacity: 1; transform: translateY(0); pointer-events: auto; } .meta-box.collapsed { height: 24px; padding-top: 5px; padding-bottom: 5px; min-height: 24px; } .meta-box.collapsed .meta-content { display: none; } .meta-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; cursor: pointer; user-select: none; font-weight: bold; } .meta-box.collapsed .meta-header { margin-bottom: 0; } .meta-header .toggle-icon { font-size: 16px; padding: 0 5px; } .meta-content { margin-top: 5px; } .meta-row { display: flex; margin-bottom: 5px; } .meta-label { font-weight: 600; width: 70px; color: #aaa; flex-shrink: 0; } body.theme-light .meta-label { color: #555; } .meta-value { flex: 1; word-break: break-word; } .copy-btn { display: inline-block; cursor: pointer; margin-left: 8px; font-size: 14px; opacity: 0.7; transition: opacity 0.2s; } .copy-btn:hover { opacity: 1; } .preview { position: absolute; display: none; max-width: 350px; background-color: #383838; color: #eee; border-radius: 6px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.4); padding: 10px 12px; font-size: 13px; z-index: 10001; pointer-events: none; transition: opacity 0.15s ease-in-out; opacity: 0; border: 1px solid rgba(255, 255, 255, 0.1); } body.theme-light .preview { background-color: white; color: #333; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); border: 1px solid #eee; } .preview.active { opacity: 1; pointer-events: auto; display: block; } .preview-header { display: flex; align-items: center; margin-bottom: 6px; font-weight: 600; color: #f5f5f5; } body.theme-light .preview-header { color: #444; } .preview-icon { width: 16px; height: 16px; margin-right: 7px; border-radius: 3px; background-color: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 10px; color: white; overflow: hidden; flex-shrink: 0; } .preview-icon img { width: 100%; height: 100%; object-fit: cover; } .preview-content { line-height: 1.4; max-height: 100px; overflow: hidden; text-overflow: ellipsis; } .preview-url { font-size: 11px; color: #aaa; margin-top: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } body.theme-light .preview-url { color: #777; } body.text-enhanced { font-family: Arial, sans-serif !important; font-size: 17px !important; line-height: 1.5 !important; } .content-container { max-width: 850px !important; margin: 20px auto !important; padding: 20px 30px !important; border-radius: 8px !important; } body.theme-dark .content-container { background-color: var(--dark-content-bg) !important; box-shadow: 0 3px 10px rgba(0,0,0,0.2); } body.theme-light .content-container { background-color: var(--light-content-bg) !important; box-shadow: 0 2px 10px rgba(0,0,0,0.08) !important; } .content-container h1, .content-container h2, .content-container h3, .content-container h4, .content-container h5, .content-container h6 { margin-top: 0.8em !important; margin-bottom: 0.3em !important; font-weight: 600; line-height: 1.1; } body.theme-dark .content-container h1, body.theme-dark .content-container h2, body.theme-dark .content-container h3 { color: #eee !important; } body.theme-light .content-container h1, body.theme-light .content-container h2, body.theme-light .content-container h3 { color: #222 !important; } .content-container h3 + .code-block-container { margin-top: 0.25em !important; } .content-container h3:has(+ .code-block-container) { margin-bottom: 0.25em !important; } .content-container h1 { font-size: 2.0em !important; border-bottom: 1px solid var(--border-color-dark); padding-bottom: 0.2em; } .content-container h2 { font-size: 1.6em !important; border-bottom: 1px solid var(--border-color-dark); padding-bottom: 0.2em;} .content-container h3 { font-size: 1.3em !important; } body.theme-light .content-container h1, body.theme-light .content-container h2 { border-bottom-color: var(--border-color-light); } .content-container a { color: var(--accent) !important; text-decoration: none !important; transition: color 0.2s; } .content-container a:hover { color: var(--accent-hover) !important; text-decoration: underline !important; } .content-container blockquote, .content-container .blockquote-single, .content-container .blockquote-multi { border-left: 4px solid var(--accent) !important; padding: 8px 15px !important; margin: 0.8em 0 !important; background-color: rgba(128,128,128,0.08); border-radius: 0 4px 4px 0; } body.theme-dark .content-container blockquote, body.theme-dark .content-container .blockquote-single, body.theme-dark .content-container .blockquote-multi { color: #ccc !important; background-color: rgba(255,255,255,0.05); } body.theme-light .content-container blockquote, body.theme-light .content-container .blockquote-single, body.theme-light .content-container .blockquote-multi { color: #555 !important; background-color: rgba(0,0,0,0.03); } .content-container ul, .content-container ol { list-style: disc; margin: 0; padding-left: 20px; } .content-container ul li, .content-container ol li { margin: 0; padding: 0; line-height: 0.05; } .content-container ul ul, .content-container ol ol, .content-container ul ol, .content-container ol ul { margin: 0.1em 0; } .code-block-container ul { list-style: none; margin: 0; padding: 0; } .code-block-container ul li { margin: 0; } .content-container code:not(pre code) { font-family: 'Consolas', 'Monaco', monospace !important; background-color: rgba(128, 128, 128, 0.15) !important; padding: 2px 5px !important; border-radius: 4px !important; font-size: 0.9em; } .content-container pre { background-color: #262626 !important; border: 1px solid var(--border-color-dark); border-radius: 6px !important; padding: 15px !important; overflow-x: auto !important; margin: 1em 0 !important; font-family: 'Consolas', 'Monaco', monospace !important; font-size: 0.9em; line-height: 1.45; color: #eee; } body.theme-light .content-container pre { background-color: #f8f8f8 !important; border-color: var(--border-color-light); color: #333 !important; } .content-container pre code { background-color: transparent !important; padding: 0 !important; border-radius: 0 !important; font-size: inherit; } .content-container strong, .content-container b { font-weight: 600 !important; } .content-container em, .content-container i { font-style: italic !important; } .code-block-container { position: relative; background-color: #1e1e1e !important; border: 1px solid var(--border-color-dark); border-radius: 6px !important; margin: 0.50em 0 !important; padding: 0 !important; overflow: hidden !important; } body.theme-light .code-block-container { background-color: #ffffff !important; border-color: var(--border-color-light); } .code-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: linear-gradient(90deg, rgba(30,30,30,1) 0%, rgba(45,45,45,1) 100%); border-bottom: 1px solid #444; font-family: 'Consolas', 'Monaco', monospace !important; font-size: 12px; } body.theme-light .code-header { background: linear-gradient(90deg, rgba(245,245,245,1) 0%, rgba(235,235,235,1) 100%); border-bottom: 1px solid #ddd; } .code-language { color: #aaa; text-transform: lowercase; font-weight: bold; } body.theme-light .code-language { color: #666; } .code-copy-btn { background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.3); color: #fff; font-size: 11px; padding: 4px 10px; border-radius: 4px; cursor: pointer; transition: all 0.2s; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; } body.theme-light .code-copy-btn { background: rgba(0,0,0,0.05); border: 1px solid rgba(0,0,0,0.2); color: #333; } .code-copy-btn:hover { background-color: rgba(255,255,255,0.2); transform: translateY(-1px); box-shadow: 0 2px 5px rgba(0,0,0,0.2); } body.theme-light .code-copy-btn:hover { background-color: rgba(0,0,0,0.1); color: black; } .code-block-container code { display: block; padding: 15px !important; overflow-x: auto !important; font-family: 'Consolas', 'Monaco', monospace !important; font-size: 0.9em; line-height: 1.45; color: #e9e9e9; } body.theme-light .code-block-container code { color: #2d2d2d !important; } .inline-code { font-family: 'Consolas', 'Monaco', monospace !important; background-color: rgba(128, 128, 128, 0.15) !important; padding: 2px 5px !important; border-radius: 4px !important; font-size: 0.9em; } .content-container .numbered-item { font-weight: bold; margin: 0 0 0.1em 0; line-height: 1.1; } .content-container .nested-bullet-item { margin: 0 0 0.1em 0; line-height: 1.1; padding-left: 2em; } .content-container .nested-bullet-item:before { content: "◦ "; margin-right: 3px; } .content-container .bullet-item { margin: 0 0 0.1em 0; line-height: 1.1; padding-left: 1em; } .content-container .bullet-item:before { content: "• "; margin-right: 3px; } .content-container hr.markdown-hr { border: 0; height: 1px; background: var(--border-color-dark); margin: 0.6em 0; } body.theme-light .content-container hr.markdown-hr { background: var(--border-color-light); } `); } })();