您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds download and copy buttons for Font Awesome Pro icons.
// ==UserScript== // @name Font Awesome Pro Downloader // @description Adds download and copy buttons for Font Awesome Pro icons. // @icon https://fontawesome.com/images/favicon/icon.svg // @version 1.6 // @author afkarxyz // @namespace https://github.com/afkarxyz/userscripts/ // @supportURL https://github.com/afkarxyz/userscripts/issues // @license MIT // @match https://fontawesome.com/* // @grant none // @run-at document-end // ==/UserScript== (function () { "use strict"; const CACHE_KEY_PREFIX = "FA_SVG_CACHE_"; const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; const DOWNLOAD_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="12" height="12"><path d="M378.1 198.6L249.5 341.4c-6.1 6.7-14.7 10.6-23.8 10.6l-3.5 0c-9.1 0-17.7-3.8-23.8-10.6L69.9 198.6c-3.8-4.2-5.9-9.8-5.9-15.5C64 170.4 74.4 160 87.1 160l72.9 0 0-128c0-17.7 14.3-32 32-32l64 0c17.7 0 32 14.3 32 32l0 128 72.9 0c12.8 0 23.1 10.4 23.1 23.1c0 5.7-2.1 11.2-5.9 15.5zM64 352l0 64c0 17.7 14.3 32 32 32l256 0c17.7 0 32-14.3 32-32l0-64c0-17.7 14.3-32 32-32s32 14.3 32 32l0 64c0 53-43 96-96 96L96 512c-53 0-96-43-96-96l0-64c0-17.7 14.3-32 32-32s32 14.3 32 32z"/></svg>`; const COPY_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="12" height="12"><path d="M192 0c-41.8 0-77.4 26.7-90.5 64L64 64C28.7 64 0 92.7 0 128L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64l-37.5 0C269.4 26.7 233.8 0 192 0zm0 64a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM72 272a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zm104-16l128 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-128 0c-8.8 0-16-7.2-16-16s7.2-16 16-16zM72 368a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zm88 0c0-8.8 7.2-16 16-16l128 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-128 0c-8.8 0-16-7.2-16-16z"/></svg>`; const SUCCESS_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="12" height="12"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>`; const processedIcons = new WeakSet(); function showSuccessAnimation(button) { const originalContent = button.innerHTML; const parser = new DOMParser(); const successSvg = parser.parseFromString(SUCCESS_ICON, "image/svg+xml"); button.innerHTML = ""; button.appendChild(successSvg.documentElement); setTimeout(() => { button.innerHTML = originalContent; }, 250); } function clearExpiredCache() { const currentTime = Date.now(); for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith(CACHE_KEY_PREFIX)) { try { const cachedItem = JSON.parse(localStorage.getItem(key)); if (currentTime - cachedItem.timestamp > CACHE_DURATION) { localStorage.removeItem(key); } } catch (error) { console.error("Error clearing cache:", error); } } } } function getCachedSVG(url) { clearExpiredCache(); const cacheKey = CACHE_KEY_PREFIX + btoa(url); const cachedItem = localStorage.getItem(cacheKey); if (cachedItem) { try { const parsedItem = JSON.parse(cachedItem); return parsedItem.content; } catch (error) { console.error("Error parsing cached SVG:", error); return null; } } return null; } function cacheSVG(url, svgContent) { const cacheKey = CACHE_KEY_PREFIX + btoa(url); const cacheItem = { content: svgContent, timestamp: Date.now(), }; try { localStorage.setItem(cacheKey, JSON.stringify(cacheItem)); } catch (error) { console.error("Error caching SVG:", error); } } async function fetchAndCacheSVG(url, iconStyle, iconName) { const cachedSVG = getCachedSVG(url); if (cachedSVG) return cachedSVG; try { const response = await fetch(url); if (!response.ok) { if (response.status === 403 || response.status === 404) { let fallbackUrl = null; if (iconStyle === "duotone-solid") { fallbackUrl = url.replace("duotone-solid", "duotone"); } else if (iconStyle === "sharp-thin") { fallbackUrl = url.replace("sharp-thin", "sharp-light"); } else if ( !["solid", "regular", "light", "thin", "brands"].includes(iconStyle) ) { fallbackUrl = url.replace(`/${iconStyle}/`, "/solid/"); } if (fallbackUrl) { console.log(`Retrying with fallback style: ${fallbackUrl}`); const retryResponse = await fetch(fallbackUrl); if (!retryResponse.ok) throw new Error("Network response was not ok."); let svgContent = await retryResponse.text(); svgContent = svgContent.replace(/<!--[\s\S]*?-->/g, ""); cacheSVG(url, svgContent); return svgContent; } } throw new Error("Network response was not ok."); } let svgContent = await response.text(); svgContent = svgContent.replace(/<!--[\s\S]*?-->/g, ""); cacheSVG(url, svgContent); return svgContent; } catch (error) { console.error("Error fetching SVG:", error); throw error; } } async function copySVG(url, button, iconStyle, iconName) { try { const svgContent = await fetchAndCacheSVG(url, iconStyle, iconName); await navigator.clipboard.writeText(svgContent); showSuccessAnimation(button); } catch (error) { const errorMessage = `Failed to copy SVG: ${error.message}`; console.error(errorMessage); alert(errorMessage); } } async function downloadSVG(url, filename, button, iconStyle, iconName) { try { const svgContent = await fetchAndCacheSVG(url, iconStyle, iconName); const blob = new Blob([svgContent], { type: "image/svg+xml" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = filename; a.style.display = "none"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); showSuccessAnimation(button); } catch (error) { console.error("Failed to download SVG:", error); alert("Failed to download SVG. Check the console for details."); } } function getSelectedVersion() { const selectElement = document.getElementById( "choose_aversionoffontawesome" ); if (selectElement?.value) return selectElement.value.trim(); const styleLink = document.querySelector( 'link[rel="stylesheet"][href*="fontawesome.com/releases"]' ); const versionMatch = styleLink ?.getAttribute("href") ?.match(/releases\/v([\d.]+)/); return versionMatch?.[1] || "7.0.0"; } function parseVersion(versionString) { const parts = versionString.split(".").map(Number); return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0, full: versionString, isV7OrHigher: parts[0] >= 7, isV6OrLower: parts[0] <= 6, }; } function getUrlPath(version) { const versionInfo = parseVersion(version); return versionInfo.isV7OrHigher ? "svgs-full" : "svgs"; } function createButton(icon, title, className) { const button = document.createElement("span"); button.className = `fa-icon-button ${className}`; button.title = title; const parser = new DOMParser(); const svgDoc = parser.parseFromString(icon, "image/svg+xml"); button.appendChild(svgDoc.documentElement); return button; } function getIconStyle(iconElement, version = null) { const classes = new Set(iconElement.classList); const versionInfo = version ? parseVersion(version) : parseVersion(getSelectedVersion()); if (classes.has("fa-brands")) return "brands"; if (versionInfo.isV7OrHigher) { const newStyleMap = { "fa-chisel": "chisel-regular", "fa-etch": "etch-solid", "fa-jelly": "jelly-regular", "fa-jelly-duo": "jelly-duo-regular", "fa-jelly-fill": "jelly-fill-regular", "fa-notdog": "notdog-solid", "fa-notdog-duo": "notdog-duo-solid", "fa-slab": "slab-regular", "fa-slab-press": "slab-press-regular", "fa-thumbprint": "thumbprint-light", "fa-whiteboard": "whiteboard-semibold", }; for (const [className, styleName] of Object.entries(newStyleMap)) { if (classes.has(className)) { return styleName; } } } const baseStyleMap = { "fa-solid": "solid", "fa-light": "light", "fa-thin": "thin", "fa-regular": "regular", }; let style = ""; if (classes.has("fa-duotone")) { style = "duotone"; for (const [className, styleName] of Object.entries(baseStyleMap)) { if (classes.has(className)) { return styleName === "solid" ? "duotone" : `duotone-${styleName}`; } } return "duotone"; } if (classes.has("fa-sharp")) { if (classes.has("fa-duotone")) { style = "sharp-duotone"; for (const [className, styleName] of Object.entries(baseStyleMap)) { if (classes.has(className)) { return `sharp-duotone-${styleName}`; } } return "sharp-duotone-solid"; } else { style = "sharp"; for (const [className, styleName] of Object.entries(baseStyleMap)) { if (classes.has(className)) { return `sharp-${styleName}`; } } return "sharp-solid"; } } for (const [className, styleName] of Object.entries(baseStyleMap)) { if (classes.has(className)) { return styleName; } } return "solid"; } function processIcon(icon) { if (processedIcons.has(icon)) return; const iconElement = icon.querySelector("i"); const iconNameElement = icon.querySelector(".icon-name"); if (!iconElement || !iconNameElement?.textContent) return; const iconName = iconNameElement.textContent.trim(); if (!iconElement || !iconName) { return; } const version = getSelectedVersion(); const iconStyle = getIconStyle(iconElement, version); const urlPath = getUrlPath(version); const url = `https://site-assets.fontawesome.com/releases/v${version}/${urlPath}/${iconStyle}/${iconName}.svg`; const filename = iconStyle === "brands" ? `${iconName}.svg` : `${iconName}_${iconStyle}.svg`; const container = document.createElement("div"); container.className = "fa-buttons-container"; const downloadButton = createButton( DOWNLOAD_ICON, "Download SVG", "fa-download-button" ); const copyButton = createButton(COPY_ICON, "Copy SVG", "fa-copy-button"); downloadButton.addEventListener("click", () => downloadSVG(url, filename, downloadButton, iconStyle, iconName) ); copyButton.addEventListener("click", () => copySVG(url, copyButton, iconStyle, iconName) ); container.appendChild(copyButton); container.appendChild(downloadButton); let tagContainer = icon.querySelector(".tag"); if (!tagContainer) { tagContainer = document.createElement("div"); tagContainer.className = "tag"; } tagContainer.innerHTML = ""; tagContainer.appendChild(container); const buttonElement = icon.querySelector("button"); if (buttonElement && !buttonElement.parentNode.querySelector(".tag")) { buttonElement.parentNode.insertBefore( tagContainer, buttonElement.nextSibling ); } processedIcons.add(icon); } function processAllIcons(icons) { Array.from(icons).forEach((icon) => { if (!processedIcons.has(icon)) { processIcon(icon); } }); } function setupMutationObserver() { const observer = new MutationObserver(() => { const icons = document.querySelectorAll("article.wrap-icon"); processAllIcons(icons); }); observer.observe(document.body, { childList: true, subtree: true, }); } const style = document.createElement("style"); style.textContent = ` .fa-buttons-container { display: inline-flex; gap: 3px; align-items: center; justify-content: center; min-width: 50px; position: absolute; top: 50%; transform: translateY(-50%); } .tag { display: flex; justify-content: center; position: relative; min-height: 28px; padding: 0 4px !important; margin: 0 !important; background: transparent !important; } .fa-icon-button { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 4px; background-color: var(--fa-yellow); color: var(--fa-navy); cursor: pointer; padding: 3px; } .fa-icon-button svg { width: 100%; height: 100%; } .fa-icon-button svg path { fill: var(--fa-navy); } `; document.head.appendChild(style); const initialIcons = document.querySelectorAll("article.wrap-icon"); processAllIcons(initialIcons); setupMutationObserver(); })();