Last active
February 4, 2025 16:11
-
-
Save danrichman/4ecd34247cca12bad1da7ed7a9c77bdf to your computer and use it in GitHub Desktop.
Copy Selected HTML/CSS Bookmarklet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| javascript:(function()%7Bconst highlight=document.createElement('div');highlight.style.cssText=%60%0A position: absolute;%0A background: rgba(78, 146, 249, 0.3);%0A border: 2px solid %234e92f9;%0A pointer-events: none;%0A z-index: 999999;%0A transition: all 50ms;%0A box-sizing: border-box;%0A %60;let selectedElement=null;function processCSSRule(rule,elements)%7Btry%7Bif(rule instanceof CSSStyleRule)%7Breturn processStyleRule(rule,elements)%7Dif(rule instanceof CSSMediaRule%7C%7Crule instanceof CSSSupportsRule)%7Breturn processConditionalRule(rule,elements)%7Dif(rule instanceof CSSFontFaceRule)%7Breturn processFontRule(rule)%7Dreturn ''%7Dcatch(error)%7Breturn ''%7D%7Dfunction processStyleRule(rule,elements)%7Bconst selector=rule.selectorText?.replace(/%5Cs*%5C%7B.*/,%27%27)%7C%7C%27%27;if(!selector%7C%7C!elements.some(el=>el.matches(selector)%7C%7Cel.closest(selector)))%7Breturn %27%27%7Dconst styles=Array.from(rule.style).filter(prop=>%7Bconst value=rule.style.getPropertyValue(prop).trim();return value&&!%5B%27none%27,%27normal%27,%27auto%27%5D.includes(value)%7D).map(prop=>%7Bconst value=rule.style.getPropertyValue(prop);const priority=rule.style.getPropertyPriority(prop);return %60$%7B prop %7D: $%7B value %7D$%7Bpriority?%27 !important%27:%27%27%7D%60%7D).join(%27; %27);return styles?%60$%7B selector %7D %7B $%7B styles %7D %7D%60:%27%27%7Dfunction processConditionalRule(rule,elements)%7Bconst condition=rule instanceof CSSMediaRule?%60@media $%7Brule.conditionText %7D%60:%60@supports $%7Brule.conditionText %7D%60;const nestedRules=Array.from(rule.cssRules).map(nestedRule=>processCSSRule(nestedRule,elements)).filter(Boolean).join(%27%5Cn%27);return nestedRules?%60$%7B condition %7D %7B%5Cn$%7B nestedRules %7D%5Cn%7D%60:%27%27%7Dfunction processFontRule(rule)%7Btry%7Bconst fontFamily=rule.style.getPropertyValue(%27font-family%27);return document.fonts.check(%6012px $%7B fontFamily %7D%60)?rule.cssText:%27%27%7Dcatch%7Breturn %27%27%7D%7Dfunction startSelection()%7Bdocument.body.style.cursor=%27crosshair%27;document.body.appendChild(highlight);const mouseMoveHandler=e=>%7Bif(e.target===highlight)%7Breturn%7DselectedElement=e.target;const rect=selectedElement.getBoundingClientRect();Object.assign(highlight.style,%7Bwidth:%60$%7Brect.width %7Dpx%60,height:%60$%7Brect.height %7Dpx%60,top:%60$%7Brect.top+window.scrollY %7Dpx%60,left:%60$%7Brect.left+window.scrollX %7Dpx%60%7D)%7D;const clickHandler=e=>%7Be.preventDefault();e.stopImmediatePropagation();document.body.style.cursor=%27%27;highlight.remove();const elements=%5BselectedElement,...selectedElement.querySelectorAll(%27*%27)%5D;const cssRules=Array.from(document.styleSheets).flatMap(sheet=>%7Btry%7Breturn Array.from(sheet.cssRules%7C%7C%5B%5D)%7Dcatch%7Breturn%5B%5D%7D%7D).map(rule=>processCSSRule(rule,elements)).filter(Boolean);elements.forEach(el=>%7Bif(el.style.cssText)%7Bconst inlineStyles=Array.from(el.style).filter(prop=>el.style.getPropertyValue(prop).trim()).map(prop=>%60$%7B prop %7D: $%7Bel.style.getPropertyValue(prop)%7D%60).join(%27; %27);if(inlineStyles)%7BcssRules.push(%60%5Bdata-inline-$%7Bel.tagName %7D%5D %7B $%7B inlineStyles %7D %7D%60)%7D%7D%7D);const clonedElement=selectedElement.cloneNode(true);clonedElement.querySelectorAll(%27*%27).forEach(el=>%7Bif(el.style.cssText)%7Bel.setAttribute(%60data-inline-$%7Bel.tagName %7D%60,%27%27)%7D%7D);const output=%60$%7BclonedElement.outerHTML %7D%5Cn<style>%5Cn$%7B%5B...new Set(cssRules)%5D.join(%27%5Cn%27)%7D%5Cn</style>%60;navigator.clipboard.writeText(output).then(()=>alert(%27Copied! ✨%27)).catch(()=>alert(%27Copy failed - check console%27));document.removeEventListener(%27mousemove%27,mouseMoveHandler);document.removeEventListener(%27click%27,clickHandler,true)%7D;document.addEventListener(%27mousemove%27,mouseMoveHandler);document.addEventListener(%27click%27,clickHandler,%7Bcapture:true,once:true%7D)%7DstartSelection()%7D)(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| javascript:(function() { | |
| // Visual highlight element configuration | |
| const highlight = document.createElement('div'); | |
| highlight.style.cssText = ` | |
| position: absolute; | |
| background: rgba(78, 146, 249, 0.3); | |
| border: 2px solid #4e92f9; | |
| pointer-events: none; | |
| z-index: 999999; | |
| transition: all 50ms; | |
| box-sizing: border-box; | |
| `; | |
| let selectedElement = null; | |
| // ======================== | |
| // CSS PROCESSING FUNCTIONS | |
| // ======================== | |
| /** | |
| * Processes CSS rules and returns applicable styles | |
| * @param {CSSRule} rule - CSS rule to process | |
| * @param {Array<Element>} elements - Elements to match against | |
| * @returns {string} Valid CSS text | |
| */ | |
| function processCSSRule(rule, elements) { | |
| try { | |
| // Handle standard style rules | |
| if (rule instanceof CSSStyleRule) { | |
| return processStyleRule(rule, elements); | |
| } | |
| // Handle nested conditional rules | |
| if (rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule) { | |
| return processConditionalRule(rule, elements); | |
| } | |
| // Handle font-face rules | |
| if (rule instanceof CSSFontFaceRule) { | |
| return processFontRule(rule); | |
| } | |
| return ''; | |
| } catch (error) { | |
| return ''; | |
| } | |
| } | |
| function processStyleRule(rule, elements) { | |
| const selector = rule.selectorText?.replace(/\s*\{.*/, '') || ''; | |
| if (!selector || !elements.some(el => el.matches(selector) || el.closest(selector))) return ''; | |
| // Filter valid style declarations | |
| const styles = Array.from(rule.style) | |
| .filter(prop => { | |
| const value = rule.style.getPropertyValue(prop).trim(); | |
| return value && !['none', 'normal', 'auto'].includes(value); | |
| }) | |
| .map(prop => { | |
| const value = rule.style.getPropertyValue(prop); | |
| const priority = rule.style.getPropertyPriority(prop); | |
| return `${prop}: ${value}${priority ? ' !important' : ''}`; | |
| }) | |
| .join('; '); | |
| return styles ? `${selector} { ${styles} }` : ''; | |
| } | |
| function processConditionalRule(rule, elements) { | |
| const condition = rule instanceof CSSMediaRule | |
| ? `@media ${rule.conditionText}` | |
| : `@supports ${rule.conditionText}`; | |
| const nestedRules = Array.from(rule.cssRules) | |
| .map(nestedRule => processCSSRule(nestedRule, elements)) | |
| .filter(Boolean) | |
| .join('\n'); | |
| return nestedRules ? `${condition} {\n${nestedRules}\n}` : ''; | |
| } | |
| function processFontRule(rule) { | |
| try { | |
| const fontFamily = rule.style.getPropertyValue('font-family'); | |
| return document.fonts.check(`12px ${fontFamily}`) ? rule.cssText : ''; | |
| } catch { | |
| return ''; | |
| } | |
| } | |
| // ===================== | |
| // CORE FUNCTIONALITY | |
| // ===================== | |
| function startSelection() { | |
| document.body.style.cursor = 'crosshair'; | |
| document.body.appendChild(highlight); | |
| // Highlight positioning handler | |
| const mouseMoveHandler = e => { | |
| if (e.target === highlight) return; | |
| selectedElement = e.target; | |
| const rect = selectedElement.getBoundingClientRect(); | |
| Object.assign(highlight.style, { | |
| width: `${rect.width}px`, | |
| height: `${rect.height}px`, | |
| top: `${rect.top + window.scrollY}px`, | |
| left: `${rect.left + window.scrollX}px` | |
| }); | |
| }; | |
| // Click handler (main logic) | |
| const clickHandler = e => { | |
| e.preventDefault(); | |
| e.stopImmediatePropagation(); | |
| // Cleanup UI | |
| document.body.style.cursor = ''; | |
| highlight.remove(); | |
| // Get target element and its children | |
| const elements = [selectedElement, ...selectedElement.querySelectorAll('*')]; | |
| // Process all CSS rules | |
| const cssRules = Array.from(document.styleSheets) | |
| .flatMap(sheet => { | |
| try { | |
| return Array.from(sheet.cssRules || []); | |
| } catch { | |
| return []; | |
| } | |
| }) | |
| .map(rule => processCSSRule(rule, elements)) | |
| .filter(Boolean); | |
| // Add inline styles | |
| elements.forEach(el => { | |
| if (el.style.cssText) { | |
| const inlineStyles = Array.from(el.style) | |
| .filter(prop => el.style.getPropertyValue(prop).trim()) | |
| .map(prop => `${prop}: ${el.style.getPropertyValue(prop)}`) | |
| .join('; '); | |
| if (inlineStyles) { | |
| cssRules.push(`[data-inline-${el.tagName}] { ${inlineStyles} }`); | |
| } | |
| } | |
| }); | |
| // Clone HTML structure | |
| const clonedElement = selectedElement.cloneNode(true); | |
| clonedElement.querySelectorAll('*').forEach(el => { | |
| if (el.style.cssText) { | |
| el.setAttribute(`data-inline-${el.tagName}`, ''); | |
| } | |
| }); | |
| // Prepare final output | |
| const output = `${clonedElement.outerHTML}\n<style>\n${[...new Set(cssRules)].join('\n')}\n</style>`; | |
| // Copy to clipboard | |
| navigator.clipboard.writeText(output) | |
| .then(() => alert('Copied! ✨')) | |
| .catch(() => alert('Copy failed - check console')); | |
| // Cleanup event listeners | |
| document.removeEventListener('mousemove', mouseMoveHandler); | |
| document.removeEventListener('click', clickHandler, true); | |
| }; | |
| // Event listeners | |
| document.addEventListener('mousemove', mouseMoveHandler); | |
| document.addEventListener('click', clickHandler, { capture: true, once: true }); | |
| } | |
| // Start the selection process | |
| startSelection(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment