Skip to content

Instantly share code, notes, and snippets.

@KevinBatdorf
Last active May 2, 2024 11:36
Show Gist options
  • Star 60 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save KevinBatdorf/8bd5f808fff6a59e100dfa08a7431822 to your computer and use it in GitHub Desktop.
Save KevinBatdorf/8bd5f808fff6a59e100dfa08a7431822 to your computer and use it in GitHub Desktop.
Auto copy the Alpine code from Tailwind UI's copy button
// ==UserScript==
// @name Add AlpineJs to Tailwind UI
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Add Alpine JS code to Tailwind Ui copy/paste
// @author https://gist.github.com/KevinBatdorf/8bd5f808fff6a59e100dfa08a7431822
// @match https://tailwindui.com/components/*
// @grant none
// ==/UserScript==
// Requires Tampermonkey
// Chrome - https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en
// FF - https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/
// Find me on Twitter: https://twitter.com/kevinbatdorf
// Get the version that downloads the code into a file here:
// https://gist.github.com/KevinBatdorf/322c68f532af6a4cc6773f906da0684d
// If it seems to not be working, check the classes here are matching
const code = Array.from(document.querySelectorAll('button')).filter(
b => b.classList.value === 'group relative ml-2 hidden h-9 w-9 items-center justify-center sm:flex'
);
code.forEach(node => {
node.addEventListener('click', function(event) {
event.preventDefault();
// This is the iFrame the component is in
const iFrame = event.target.closest('[id^=component-]').querySelector('[name][id^=frame]');
const contentArea = iFrame.contentWindow.document.querySelector('body > div');
const markup = contentArea.innerHTML;
// This will attempt to extract the function used in the x-data, like x-data="Component.popover"
const scripts = Array.from(contentArea.querySelectorAll('[x-data]'))
.filter((n) => /\(.*\)/.test(n.getAttribute('x-data').toString()))
.map((n) => {
const fnNameSplit = n
.getAttribute('x-data')
.replace('window.', '')
.replace(/\(.*\)/, '')
.split('.');
// We need to get the string representation of the method
const method = new Function(`return ${iFrame.contentWindow[fnNameSplit[0]][fnNameSplit[1]]};`);
// This adds checks to create the global object then register it.
return `
window.${fnNameSplit[0]} = window.${fnNameSplit[0]} ?? {}
window.${fnNameSplit[0]}['${fnNameSplit[1]}'] = ${method}()
`
})
.join(';')
.toString();
const textarea = document.createElement('textarea');
textarea.textContent = scripts ? `<script>${scripts}</script>` + markup : markup;
document.body.appendChild(textarea);
setTimeout(() => {
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}, 50);
})
})
@nford
Copy link

nford commented Jan 28, 2022

Thanks for your reply @KevinBatdorf, both fnNameSplit and iFrame are undefined. To clarify, this is using PageExtender 1.2.1 on Safari 15.3. Someone earlier in the thread had confirmed it worked with PageExtender but I understand it's not officially supported. I just tried it in Chrome with TamperMonkey and it works fine.

@KevinBatdorf
Copy link
Author

Any idea how to get iFrame defined there? We could update this line if the former is undefined.

const iFrame = event.target.closest('[id^=component-]').querySelector('[x-ref=iframe]') || theWayToGetTheFrameInPageExtender

@dyanakiev
Copy link

dyanakiev commented Feb 28, 2022

CleanShot 2022-02-28 at 23 14 52
Is it happening only to me or copying the first Home screen doesn't grab the proper HTML? I'm using chrome, all other components are copying with alpine-js

@KevinBatdorf
Copy link
Author

Hey @dyanakiev - it looks like those have just too much text in them so it fails to copy. I'm not really sure what we can do for that. Any ideas?

@magarrent
Copy link

@KevinBatdorf this is amazing mate!

Did you try it with combo boxes? I don't know what but it's not working I think:

https://tailwindui.com/components/application-ui/forms/comboboxes

@KevinBatdorf
Copy link
Author

Hey @magarrent It doesn't appear that the demos there are using Alpine, unfortunately.

@ovp87
Copy link

ovp87 commented May 18, 2022

Sadly doesn't seem to work on the later ecommerce templates

@KevinBatdorf
Copy link
Author

@ovp87 which one specifically? I know that some are too long and the clipboard doesn't seem to handle them. I could try a different approach but can you link to one not working?

@KevinBatdorf
Copy link
Author

@ovp87 Not sure I can get it to work. It seems to be hitting a limit. Looking at this screenshot below, if I manually cmd+c without the </a> it copies, but with it it doesn't. I'm assuming this is a length issue but I'm really not sure.

Screen Shot 2022-05-20 at 11 46 07 AM

The problem is it also doesn't trigger an error, and worse, the navigator.clipboard actually has the content in it (if I use the Clipboard API), so I can't compare whether the copy was a success.

How about a new script that always downloads a file with the when you press copy? Something like component-being-copied.html. Would that be useful you think? I don't want to write it if no one wants it :)

@ovp87
Copy link

ovp87 commented May 20, 2022

@ovp87 Not sure I can get it to work. It seems to be hitting a limit. Looking at this screenshot below, if I manually cmd+c without the </a> it copies, but with it it doesn't. I'm assuming this is a length issue but I'm really not sure.

Screen Shot 2022-05-20 at 11 46 07 AM

The problem is it also doesn't trigger an error, and worse, the navigator.clipboard actually has the content in it (if I use the Clipboard API), so I can't compare whether the copy was a success.

How about a new script that always downloads a file with the when you press copy? Something like component-being-copied.html. Would that be useful you think? I don't want to write it if no one wants it :)

Thats weird 🤔 your idea of a download could be a nice workaround, still better than doing the job manually 👍

Shame Adam decided to remove alpine in the examples.

@KevinBatdorf
Copy link
Author

Ok I created version that will put the content into a file like with-image-grid.html and download it.

Give it a try: https://gist.github.com/KevinBatdorf/322c68f532af6a4cc6773f906da0684d

I only lightly tested it, but it works with the component you shared. Let me know if any bugs.

@bureauvk
Copy link

bureauvk commented Aug 2, 2022

I think it stopped working after the Templates update of TailwindUI. Anyone else experiencing issues?

@kirantpatil
Copy link

Namaste to all.

@calebporzio is working on headless alpine UI and later he is planning to support Tailwind UI.

Please join him and contribute.

@KevinBatdorf
Copy link
Author

KevinBatdorf commented Aug 5, 2022

@bureauvk Link to the component and I'll check it out. If the html is too much the copy will fail. Try the version above where it saves to a file.

@kirantpatil I don't see anything about where to "join him and contribute" - Do you have more information on that?

@bureauvk
Copy link

bureauvk commented Aug 5, 2022

@kirantpatil I dont think you can contribute to that, they will be selling it.

@KevinBatdorf every component fails on my end. Same with the script that downloads the code.
The script is running, but there are no nodes found.
We are using includes('clipboard') in the script, I think something changed on their end with the clipboard.
I added the EventListener some other way, but I cant get the contentArea.

@bureauvk
Copy link

bureauvk commented Aug 5, 2022

Also the iFrame doesn't have the x-ref value anymore.

@KevinBatdorf
Copy link
Author

Looks like they stopped using Alpine on their site. They also have duplicated id attributes on the iFrame and it's wrapper.

Anyway, I updated it now (this gist and the file variety) to find the iFrame a bit differently. it's more fickle now since there isn't a clean way to identify the clipboard buttons anymore, but it works now by just matching the class list.

Let me know if it breaks again.

@MartijnHarmenzon
Copy link

MartijnHarmenzon commented Oct 15, 2022

Hi all. Trying to get this to work. I added the code in Tampermonkey. Now when I hit the copy button in Tailwind UI should it copy the html and include the Alpine code? Cause it doesn't. Not sure if Tampermonkey run the script.. I'm trying on Navbars.

image

@KevinBatdorf
Copy link
Author

I just tried it out on the first nav item and it seems to work still. Make sure it's active on the site. You should see a red 1 in a square

Screen Shot 2022-10-15 at 11 53 23 AM

@MartijnHarmenzon
Copy link

MartijnHarmenzon commented Oct 17, 2022

I just tried it out on the first nav item and it seems to work still. Make sure it's active on the site. You should see a red 1 in a square

Screen Shot 2022-10-15 at 11 53 23 AM

Thanks @KevinBatdorf. It is working now. You are correct. Tampermonkey was not active. My bad.

@gregnewman
Copy link

This doesn't seem to be working anymore. Trying to use it on command palettes. I tried in both safari and chrome. Chrome only copies a nearly empty container. Safari copies exactly what is in the code preview.

@KevinBatdorf
Copy link
Author

Does it work on other components? It won't copy if the content is larger than the clipboard can hold. You could try the gist I mention in the comment on line 18

https://gist.github.com/KevinBatdorf/322c68f532af6a4cc6773f906da0684d

@gregnewman
Copy link

@KevinBatdorf no it doesn't work on other components. Just grabs the empty div with id of app. Tried the gist you linked to also and does the same thing.

@gregnewman
Copy link

Just got it to work on a modal component. It seems spotty in what it can extract.

@KevinBatdorf
Copy link
Author

Can you share a component it's not working on? I tried it on the hero sections and a few others and it's working

Screen Shot 2023-02-01 at 10 38 11 AM

@KevinBatdorf
Copy link
Author

Just got it to work on a modal component. It seems spotty in what it can extract.

Saw this after I commented. I can try to see why some aren't working. It could be they changed something.

@ovp87
Copy link

ovp87 commented Feb 6, 2023

@KevinBatdorf Kudos for still maintaining this 👍

@aitoehigie
Copy link

It no longer seems to work?

@KevinBatdorf
Copy link
Author

@aitoehigie I just tried it on a dropdown and it worked. Which componen isn't working?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment