Last active
June 12, 2023 09:06
-
-
Save Hashbrown777/4a093dcadac43d93b188526ab838aa7a to your computer and use it in GitHub Desktop.
Clones the selected element in the console into a new window, copying all styles and re-inserting any pseudo elements.
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
(function ({element, asBody, embedUrls, convertPseudo}) { | |
let copy = open().document; | |
copy.open(); | |
copy.write('<!doctype html>\n<html><head></head><body></body></html>'); | |
copy.close(); | |
if (asBody) { | |
for (const name of element.getAttributeNames()) | |
copy.body.setAttribute(name, element.getAttribute(name)); | |
for (const node of element.children) | |
copy.body.appendChild(copy.importNode(node, true)); | |
copy = [[copy.body, element]]; | |
} | |
else { | |
copy = [[ | |
copy.body.appendChild(copy.importNode(element, true)), | |
element | |
]]; | |
if (asBody == null) { | |
copy.push([ | |
copy[0][0].parentElement, | |
{computedStyleMap:document.body.computedStyleMap.bind(document.body)} | |
]); | |
} | |
} | |
let current; | |
const toHyphenate = /^(moz|webkit|ms|o)(?=[A-Z])|[A-Z]/g; | |
const doHyphenate = (what) => ('-' + what.toLowerCase()); | |
function pseudo(element, which) { | |
if (!(element instanceof Element)) | |
return; | |
element = getComputedStyle(element, ':' + which); | |
if (element.content == 'none') | |
return; | |
const computedStyleMap = new Map(); | |
for (let index = 0; index < element.length; ++index) { | |
computedStyleMap.set( | |
element[index].replace(toHyphenate, doHyphenate), | |
element[element[index]] | |
); | |
} | |
element = current.ownerDocument.createElement('span'); | |
element.className = which; | |
copy.push([element, {computedStyleMap:() => (computedStyleMap)}]); | |
current[(which == 'after') ? 'append' : 'prepend'](element); | |
} | |
const cssUrl = /(?<=^|[^a-zA-Z0-9_-])url\(\s*['"]?((?<!['"])[^\s)'"]+|(?<=')(?:[^'\\]|\\.)+(?=')|(?<=")(?:[^"\\]|\\.)+(?="))['"]?\s*\)/; | |
const urls = {length:0}; | |
const getUrl = (fallback, url) => { | |
if (!urls[url]) { | |
const key = `--fetched${++urls.length}`; | |
urls[url] = `var(${key})`; | |
(async (css) => { | |
const file = new FileReader(); | |
file.onloadend = () => { | |
css.setProperty(key, `url('${file.result}')`); | |
}; | |
file.onerror = () => { | |
css.setProperty(key, fallback); | |
}; | |
try { | |
file.readAsDataURL(await (await fetch(url)).blob()); | |
} | |
catch (e) { | |
file.onerror(); | |
} | |
})(current.ownerDocument.documentElement.style); | |
} | |
return urls[url]; | |
}; | |
while (copy.length) { | |
[current, element] = copy.pop(); | |
for ( | |
let next = [current.lastElementChild, element.lastElementChild]; | |
next[1]; | |
next = [next[0].previousElementSibling, next[1].previousElementSibling] | |
) | |
copy.push(next); | |
pseudo(element, 'after'); | |
pseudo(element, 'before'); | |
element = element.computedStyleMap(); | |
current.removeAttribute('style'); | |
const style = current.computedStyleMap(); | |
const output = []; | |
for (let [name, value] of element.entries()) { | |
value = value.toString(); | |
if (style.getAll(name).toString() != value) { | |
output.push(`${name}:${value.replace(cssUrl, getUrl)};`); | |
} | |
} | |
if (!output.length) | |
continue; | |
if ( | |
convertPseudo || | |
element.get('content').toString() == 'normal' | |
) { | |
current.setAttribute('style', output.join('')); | |
continue; | |
} | |
if (!current.parentElement.hasAttribute('data-pseudo')) { | |
current.parentElement.setAttribute( | |
'data-pseudo', | |
current.ownerDocument.head.children.length | |
); | |
} | |
current | |
.ownerDocument | |
.head | |
.appendChild(current.ownerDocument.createElement('style')) | |
.textContent | |
= `[data-pseudo="${ | |
current.parentElement.getAttribute('data-pseudo') | |
}"]:${current.className} { | |
${output.join('\n')} | |
content:${element.get('content')}; | |
}`; | |
current.remove(); | |
} | |
})({ | |
element : $0, | |
embedUrls : true, | |
//set to null if you want to copy the body styles | |
asBody : false, | |
//You can elect to switch this to true to effectively convert pseudo elements to real ones. | |
//However you'll need to parse `element.get('content')` and insert that into `current.textContent` which is non-trivial, | |
//since it's not just a matter of stripping quotes or parsing as json. | |
//For example: | |
// - in css encapsulating characters can be single quotes and unicode escape sequences are '\1234' not '\u1234' | |
// - the string could be an evaluated join or parenthesised à la `( "hello" "there" )` | |
// - they can even use css variables or references like 'attr()' | |
convertPseudo : false | |
}) |
fix for printing pages with youtube embeds (issue)
//run in each youtube iframe
for (let div of document.querySelectorAll('body,.html5-video-player,.ytp-cued-thumbnail-overlay-image')) { div.style.background = 'transparent'; }
//actually even gradients dont print, so add this to the one above
//for (let div of document.querySelectorAll('.ytp-gradient-top')) { div.style.background = 'linear-gradient(180deg, rgba(0,0,0,0.6) 0%, rgba(255,255,255,0) 75%)'; }
//run in top
for (let frame of document.querySelectorAll('iframe.YOUTUBE-iframe-video')) { frame.style.background = `url('${frame.getAttribute('data-thumbnail-src')}')`; frame.style.backgroundSize = 'cover'; frame.style.backgroundPosition = 'center'; }
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Tangentially: Delete print media rules from css