Copy to cliboard

It’s hard to debug without looking at your code. Would you be able to share your plugin with us? Please feel free to send me the file at kwak@adobe.com

With everything simplified, this should work:

const clipboard = require("clipboard");

function test() {
    clipboard.copyText("hello world");
}

module.exports = {
    commands: {
        test
    }
};

When plugin is run, “hello world” text will be copied to the clipboard

1 Like

@stevekwak, could you please try this one:

function helloHandlerFunction(selection) {
    const clipboard = require("clipboard");
    
    if (dialog == null) {
        //  create the dialog
        dialog = document.createElement("dialog");

        //  create the form element
        //  the form element has default styling and spacing
        let form = document.createElement("form");
        dialog.appendChild(form);
        //  don't forget to set your desired width
        form.style.width = 200;

        //  create a footer to hold your form submit and cancel buttons
        let footer = document.createElement("footer");
        form.appendChild(footer);

        let copyButton = document.createElement("button");
        copyButton.uxpVariant = "cta";
        copyButton.textContent = "Copy";
        copyButton.onclick = (e) => { clipboard.copyText("hello world"); };
        footer.appendChild(copyButton);
    }
    
    document.body.appendChild(dialog).showModal();
}

You need to return that because .showModal() returns a promise:

return document.body.appendChild(dialog).showModal();
1 Like

Thanks, that works for dialogs. What should be returned in case of panels?

function showCopyPanel() {
    const clipboard = require("clipboard");

    panel = document.createElement("panel");

    let form = document.createElement("form");
    panel.appendChild(form);
    form.style.width = 200;
    let footer = document.createElement("footer");
    form.appendChild(footer);

    let copyButton = document.createElement("button");
    copyButton.uxpVariant = "cta";
    copyButton.textContent = "Copy";
    copyButton.onclick = (e) => { clipboard.copyText("hello world"); };
    footer.appendChild(copyButton);

    return document.body.appendChild(panel);
}

and show event just call it:

show(event) {
  return showCopyPanel();
}

You need to attach the created panel to the event.node object. Take a look at this example and let me know if you have any questions.

Did I get it right, you mean something like following:

function showCopyPanel(container) {
    const clipboard = require("clipboard");
    panel = document.createElement("panel");
    let form = document.createElement("form");
    panel.appendChild(form);
    form.style.width = 200;
    let footer = document.createElement("footer");
    form.appendChild(footer);

    let copyButton = document.createElement("button");
    copyButton.uxpVariant = "cta";
    copyButton.textContent = "Copy";
    copyButton.onclick = (e) => { clipboard.copyText("hello world"); };
    footer.appendChild(copyButton);

    return container.appendChild(panel);
}

and show event

show(event) {
  return showCopyPanel(event.node);
}

The result is exactly the same :frowning: If you don’t mind could you please share code snippet with tested working example. Maybe it’s platform issue? I’m testing it on windows 10.

Wrap the above like so:

copyButton.onclick = (e) => {
    require("application").editDocument( () => clipboard.copyText("hello world"); );
}

Not sure why copy text requires document editing but it works! Thank you very much!

I’m getting the same error attempting to copy from a dialog:

<dialog>
  <button onclick="require('clipboard').copyText('hello world')">copy</button>
</dialog>

Throws with the following message:

Plugin <id> is not permitted to make changes from the background. Return a Promise to continue execution asynchronously.

and

<dialog>
  <button onclick="require('application').editDocument(() => require('clipboard').copyText('hello world'))">copy</button>
</dialog>

Throws:

Panel plugin edits must be initiated from a supported UI event.

Just to clarify, are using the code exactly like you shared? I checked the code you shared using innerHTML and it doesn’t work, it throws Plugin Error: Refusing to load event handler tag as executable code. The node was injected using innerHTML.

I’m asking because edit operation is very sensitive, e.g. I can’t use it because it doesn’t work after await: What is Panel plugin edits must be initiated from a supported UI event

What happens when you add an id and an uxpEditLabel?

<dialog>
   <button id="myButton" uxpEditLabel="Copy Text" onclick="require('application').editDocument(() => require('clipboard').copyText('hello world'))">copy</button>
</dialog>

You also have a typo in your onclick event:

 onclick="require('application" <-- non matching double quotes

@gdreyv @Velara apologies for the lack of clarity and the typo in my message above. Below are two repros you can try in your plugin. @Velara adding uxpEditLabel has no effect on either.

document.body.innerHTML = '<div id="container"></div>';
document.getElementById('container').innerHTML = `
  <dialog id="dialog">
    <button id="button">copy</button>
  </dialog>
`;

document.getElementById('dialog').showModal();
document.getElementById('button').addEventListener('click', () => {
  require('clipboard').copyText('hello world');
});

Throws:

Plugin Error: Plugin <my_plugin_id> is not permitted to make changes from the background. Return a Promise to continue execution asynchronously.
    at convertPluginErrorToString (plugins/PluginErrorUtil.js:1:198)
    at internalFormatPluginError (plugins/PluginErrorUtil.js:1:1073)
    at internalReportPluginError (plugins/PluginErrorUtil.js:1:1180)
    at Object.reportPluginError (plugins/PluginErrorUtil.js:1:1612)
    at Object.checkAllowedToEdit (plugins/ScenegraphGuard.js:1:1572)
    at Object.<anonymous> (plugins/ClipboardWrapper.js:1:194)
    at b.document.getElementById.addEventListener
   ...

and

document.body.innerHTML = '<div id="container"></div>';
document.getElementById('container').innerHTML = `
  <dialog id="dialog">
    <button id="button">copy</button>
  </dialog>
`;

document.getElementById('dialog').showModal();
document.getElementById('button').addEventListener('click', () => {
  require('application').editDocument(() =>
    require('clipboard').copyText('hello world')
  );
});

Throws:

Plugin Error: Panel plugin edits must be initiated from a supported UI event
    at Object.checkAllowedPanelToEdit (plugins/ScenegraphGuard.js:1:2127)
    at Object.<anonymous> (plugins/ApplicationWrapper.js:1:4137)
    at b.document.getElementById.addEventListener
   ...

Sorry, I didn’t try to reproduce your error but check posts above Copy to cliboard - showModal really is not a void function. It returns Promise which you must return to the parent function in order to use something like editDocument. I guess it must be warned by the app but not it’s tricky. @stevekwak, even official examples have this problem: https://github.com/AdobeXD/plugin-samples/blob/master/ui-hello/main.js
For me it solved the error for dialog.

Is it in a dialog window or a plugin panel? A few suggestions:

Try putting the button inside of a form:

document.getElementById('container').innerHTML = `
  <dialog id="dialog">
    <form method="dialog"><button id="button">copy</button></form>
  </dialog>
`;

Or try using an input:

document.getElementById('container').innerHTML = `
  <dialog id="dialog">
    <form method="dialog"><input type="button" id="button">copy</button></form>
  </dialog>
`;

It’s possible that if you have any async calls before you get the copyText() method it not be initiated within the click event.

I would also wrap the call in a try catch block and call a known function (not anonymous) and add the event listener before calling showModal() and return a value from showModal().

@Velara thanks for replying.

I run the example code from a ui-entry-point with the following wrapper: export const commands = { main() { ... } }; That’s the only code in the repro plugin. Putting it in a <form> has no effect. <input type="button" /> is unsupported in XD. No async calls are happening. My example code above is the only code in the test plugin (not counting the wrapper).

Change this:

document.getElementById('dialog').showModal(); document.getElementById('button').addEventListener('click', () => { require('application').editDocument(() => require('clipboard').copyText('hello world') ); });

To this:

function copyToClipboard() {
    var clipboard = require("clipboard");
    clipboard.copyText('hello world');
}

document.getElementById('button').addEventListener('click', copyToClipboard);
var waiting = document.getElementById('dialog').showModal();

Thanks, tried it. Naming the function has no effect @Velara

What is var waiting =? Such code doesn’t make any sense. Promise should be awaited by caller. So I suppose it must be return dialog.showModal(); like it was suggested in the very beginning of the topic.

@nemtsov
I’ve read on here there might be issues with using innerHTML. You might want to try the append methods. Can you get any of the basic examples working here? Get those working first and then step by step add your own coding style back into the working example. The UXP environment is not the browser.

Also, it wasn’t clear if you were putting an await keyword in front of your showModal() call. If you don’t put an await in front of your call some events will occur out of the expected order. Assign your event listener before hand.

But if you use await then the next line won’t run until the dialog is closed.

@gdreyv
In the past I have had issues with async and await. Something like the following seemed to fix a lot of issues and in one function I had more than one dialog so I gave them each different names:

if (noArtboardSelected) {
  let dialog = await alertDialog.showModal();
  console.log("type of ", typeof dialog); // string
  return dialog;
}
else {
  let dialog = await showMainDialog();
  return dialog;
}