Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot Paste Into Block Content in Mozilla #6715

Open
piet-maier opened this issue May 19, 2024 · 6 comments · May be fixed by #6855
Open

Cannot Paste Into Block Content in Mozilla #6715

piet-maier opened this issue May 19, 2024 · 6 comments · May be fixed by #6855

Comments

@piet-maier
Copy link

piet-maier commented May 19, 2024

Attempting to paste any text into a block content field does not work. Nothing happens, and the console displays the following message: DataTransfer with kind="string" is currently not supported

Pasting anywhere else, for example into a simple string field, still works as expected.

This has been tested on both Windows and MacOS, with npm and bun, both locally and deployed to sanity.io and using Firefox on both systems. Therefore I would assume that it is a bug within the studio, maybe in combination with Firefox.

  • Node 22.2.0
  • npm 10.8.0
  • bun 1.1.8
@sanity/cli (global)          3.42.1 (up to date)
@sanity/eslint-config-studio   4.0.0 (up to date)
@sanity/vision                3.42.1 (up to date)
sanity                        3.42.1 (up to date)
@ulumills
Copy link

Same issue here—it appears to be a Firefox-specific compatibility issue. When I switch to Chrome I can paste as normal.

Looking in the debugger, I see this in a comment above the error message:

We previously had support for reading datatransfer of strings here but decided to remove it since we don't handle it in higher up in the stack yet. If one day we want to support data transfer from a string value (e.g. copy+paste from a field to another), an earlier version of this file includes an implementation that uses DataTransferItem.getAsString to read the string value into a File

@linear linear bot changed the title Cannot Paste Into Block Content Cannot Paste Into Block Content in Mozilla May 24, 2024
@devmatteini
Copy link

Same issue here, we are using sanity 3.43.0 with Node 18.

It works fine with Chrome and Safari.

@mariusGundersen
Copy link

Weirdly this warning is logged in Chrome as well, but there content is pasted. From the source it seems like this is all called from here, but the majority of this function deals with pasted files, which are ignored. It's the last line, return onPaste?.(input), which is what is really doing the pasting work.

@mariusGundersen
Copy link

Turns out the onPaste is undefined, so the call isn't being done.

But I found the problem, it is here. When debugging this function it turns out that event.clipboardData has content in this function, but not inside the .then((result) => {....}) callback, so it seems like event.clipboardData gets wiped after waiting for the promise to resolve.

Testing a bit and it seems like both Chrome and Firefox clear the clipboardData after awaiting a short timeout, but Chrome does not clear it when the promise resolves right await:

bucket.addEventListener('paste', async e => {
  bucket.textContent = 'Pasted!\n';
  new Promise(r => r()).then(r => {
    writeln(`Promised ${e.clipboardData.items.length}`); // this outputs 0 in Firefox but 2 in Chrome
  })
  new Promise(r => setTimeout(r, 1)).then(() => {
    writeln(`Timeout ${e.clipboardData.items.length}`);    // this outputs 0 in both Firefox and Chrome
  })
}, false);

You can try it out in both Chrome and Firefox here: https://codepen.io/mariusgundersen/full/dyEvbdj

@braincomb
Copy link

Can confirm same issue with v3.44.0 on Firefox.

@mariusGundersen
Copy link

I've been trying to fix this. The problem is that Editable handlePaste awaits the onPaste prop callback, which doesn't work in Firefox. It also doesn't work in Chrome if the promise actually has any sort of delay. Probably it doesn't, which is why it works in Chrome but not in Firefox. This onPaste prop callback is the PortableTextInput handlePaste function, which looks like this:

    (input) => {
      const {event} = input
      extractPastedFiles(event.clipboardData)
        .then((files) => {
          return files.length > 0 ? files : []
        })
        .then((files) => {
          handleFiles(files)
        })
      return onPaste?.(input)
    }

So the promise chain here isn't awaited, which means it runs in parallel with the promise chain in the other function, which only depends on onPaste in the above code snippet. As noted in a previous comment, this onPaste is undefined, so there is nothing to await. This is actually checked for in Editable and it used to work, because the onPaste prop used to be just forwarded, before this commit.

This can be fixed if the code in Editable checks if the onPaste response is a promise or if it is undefined. It also needs to be wrapped with try/catch in order to behave the same, but that shouldn't be too difficult to do. It would be simpler to make the entire function async, but I'm not sure if that is OK with how it transpiles, so probably best to manually handle the promise chain and then duplicate the error handling. So to summarize, the method should look like this:

const handlePaste = useCallback(
    (event: ClipboardEvent<HTMLDivElement>): Promise<void> | void => {
      event.preventDefault()
      if (!slateEditor.selection) {
        return
      }
      if (!onPaste) {
        debug('Pasting normally')
        slateEditor.insertData(event.clipboardData)
        return
      }

      try{
        const value = PortableTextEditor.getValue(portableTextEditor)
        const ptRange = toPortableTextRange(value, slateEditor.selection, schemaTypes)
        const path = ptRange?.focus.path || []
        const result = onPaste({
            event,
            value,
            path,
            schemaTypes,
          })
          change$.next({type: 'loading', isLoading: true})
          if(!result){
            debug('No result from custom paste handler, pasting normally')
            slateEditor.insertData(event.clipboardData)
            return
          } else {
            Promise.resolve(result)
            .then((result) => {
              debug('Custom paste function from client resolved', result)
              change$.next({type: 'loading', isLoading: true})
              if (!result || !result.insert) {
                debug('No result from custom paste handler, pasting normally')
                slateEditor.insertData(event.clipboardData)
                return
              }
              if (result && result.insert) {
                slateEditor.insertFragment(
                  toSlateValue(result.insert as PortableTextBlock[], {schemaTypes}),
                )
                change$.next({type: 'loading', isLoading: false})
                return
              }
              console.warn('Your onPaste function returned something unexpected:', result)
            })
            .catch((error) => {
              change$.next({type: 'loading', isLoading: false})
              console.error(error) // eslint-disable-line no-console
            })
          }
      }catch(error){
        change$.next({type: 'loading', isLoading: false})
        console.error(error) // eslint-disable-line no-console
      }
    },
    [change$, onPaste, portableTextEditor, schemaTypes, slateEditor],
  )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants