ChatGPT解决这个技术问题 Extra ChatGPT

How does Trello access the user's clipboard?

When you hover over a card in Trello and press Ctrl+C, the URL of this card is copied to the clipboard. How do they do this?

As far as I can tell, there is no Flash movie involved. I've got Flashblock installed, and the Firefox network tab shows no Flash movie loaded. (That's the usual method, for example, by ZeroClipboard.)

How do they achieve this magic?

(Right at this moment I think I had an epiphany: You cannot select text on the page, so I assume they have an invisible element, where they create a text selection via JavaScript code, and Ctrl+C triggers the browser's default behaviour, copying that invisible node's text value.)

If you look at the live DOM, there's a div with the class "clipboard-container". When you hold down the ctrl key, it gets filled with a textarea (and is removed when you lift off the ctrl key). I would assume your epiphany is correct. I'm just not exactly sure where they are storing the URL per card
@Ian, yes, I can confirm, that's exactly how it worked. Thanks for digging it up! (I don't bother with where the URL is stored. I was interested in the clipboard-without-flash technology.)
I looked up Daniel's profile, and it seems, he's a Trello developer. (I wondered, where he got the Coffeescript source from.) So he has an unjust advantage ;-) Thanks anyway!
I don't intend to detract from the resourcefulness of this technique, it's quite clever; but I can't help but think this is, at best, poorly publicized/documented, and at worst, a pretty jarring user experience. Granted, it's not invasively jarring (as I can't recall a time in which I accidentally copied the card URL), but as a long-time Trello user I had absolutely no idea this existed.
@MichaelWales This feature was added 5 days ago; we're still testing it out, and if it seems to be working it'll be documented as a keyboard shortcut.

P
Peter Mortensen

Disclosure: I wrote the code that Trello uses; the code below is the actual source code Trello uses to accomplish the clipboard trick.

We don't actually "access the user's clipboard", instead we help the user out a bit by selecting something useful when they press Ctrl+C.

Sounds like you've figured it out; we take advantage of the fact that when you want to hit Ctrl+C, you have to hit the Ctrl key first. When the Ctrl key is pressed, we pop in a textarea that contains the text we want to end up on the clipboard, and select all the text in it, so the selection is all set when the C key is hit. (Then we hide the textarea when the Ctrl key comes up.)

Specifically, Trello does this:

TrelloClipboard = new class
  constructor: ->
    @value = ""

    $(document).keydown (e) =>
      # Only do this if there's something to be put on the clipboard, and it
      # looks like they're starting a copy shortcut
      if !@value || !(e.ctrlKey || e.metaKey)
        return

      if $(e.target).is("input:visible,textarea:visible")
        return

      # Abort if it looks like they've selected some text (maybe they're trying
      # to copy out a bit of the description or something)
      if window.getSelection?()?.toString()
        return

      if document.selection?.createRange().text
        return

      _.defer =>
        $clipboardContainer = $("#clipboard-container")
        $clipboardContainer.empty().show()
        $("<textarea id='clipboard'></textarea>")
        .val(@value)
        .appendTo($clipboardContainer)
        .focus()
        .select()

    $(document).keyup (e) ->
      if $(e.target).is("#clipboard")
        $("#clipboard-container").empty().hide()

  set: (@value) ->

In the DOM we've got:

<div id="clipboard-container"><textarea id="clipboard"></textarea></div>

CSS for the clipboard stuff:

#clipboard-container {
  position: fixed;
  left: 0px;
  top: 0px;
  width: 0px;
  height: 0px;
  z-index: 100;
  display: none;
  opacity: 0;
}
#clipboard {
  width: 1px;
  height: 1px;
  padding: 0px;
}

... and the CSS makes it so you can't actually see the textarea when it pops in ... but it's "visible" enough to copy from.

When you hover over a card, it calls

TrelloClipboard.set(cardUrl)

... so then the clipboard helper knows what to select when the Ctrl key is pressed.


Awesome! But how do you have Mac OS - do you "listen" to the Command key there?
It's worth noting that a similar method works just as well for capturing pasted content
This sounds like it'd be bad for keyboard users - anytime you try to copy (or ctrl+click to open in another window, or Ctrl+F to search, and so on), your focus is moved somewhere unrelated.
+1. Lots of neat stuff going on in this answer. I like that you actually shared the source code. But what I thought was clever was the actual explanation of the process used to provide the ctrl+c functionality. In my opinion it was very smart to take advantage of the fact that ctrl and c can not be pressed at the exact same time by starting to prepare for the c when ctrl is pushed. I really liked that approach.
Feel free to use js2coffee.org to translate the original into js if so inclined.
P
Peter Mortensen

I actually built a Chrome extension that does exactly this, and for all web pages. The source code is on GitHub.

I find three bugs with Trello's approach, which I know because I've faced them myself :)

The copy doesn't work in these scenarios:

If you already have Ctrl pressed and then hover a link and hit C, the copy doesn't work. If your cursor is in some other text field in the page, the copy doesn't work. If your cursor is in the address bar, the copy doesn't work.

I solved #1 by always having a hidden span, rather than creating one when user hits Ctrl/Cmd.

I solved #2 by temporarily clearing the zero-length selection, saving the caret position, doing the copy and restoring the caret position.

I haven't found a fix for #3 yet :) (For information, check the open issue in my GitHub project).


So you actually did this the same way as Trello. Sweet when such things converge
@ThomasAhle, What do you mean?
@Pacerier, I assume that Thomas alluded to Convergent Evolution - "...independent evolution of similar features in species of different lineages"
holy cow you could open a new chat about this topic
P
Peter Mortensen

With the help of raincoat's code on GitHub, I managed to get a running version accessing the clipboard with plain JavaScript.

function TrelloClipboard() {
    var me = this;

    var utils = {
        nodeName: function (node, name) {
            return !!(node.nodeName.toLowerCase() === name)
        }
    }
    var textareaId = 'simulate-trello-clipboard',
        containerId = textareaId + '-container',
        container, textarea

    var createTextarea = function () {
        container = document.querySelector('#' + containerId)
        if (!container) {
            container = document.createElement('div')
            container.id = containerId
            container.setAttribute('style', [, 'position: fixed;', 'left: 0px;', 'top: 0px;', 'width: 0px;', 'height: 0px;', 'z-index: 100;', 'opacity: 0;'].join(''))
            document.body.appendChild(container)
        }
        container.style.display = 'block'
        textarea = document.createElement('textarea')
        textarea.setAttribute('style', [, 'width: 1px;', 'height: 1px;', 'padding: 0px;'].join(''))
        textarea.id = textareaId
        container.innerHTML = ''
        container.appendChild(textarea)

        textarea.appendChild(document.createTextNode(me.value))
        textarea.focus()
        textarea.select()
    }

    var keyDownMonitor = function (e) {
        var code = e.keyCode || e.which;
        if (!(e.ctrlKey || e.metaKey)) {
            return
        }
        var target = e.target
        if (utils.nodeName(target, 'textarea') || utils.nodeName(target, 'input')) {
            return
        }
        if (window.getSelection && window.getSelection() && window.getSelection().toString()) {
            return
        }
        if (document.selection && document.selection.createRange().text) {
            return
        }
        setTimeout(createTextarea, 0)
    }

    var keyUpMonitor = function (e) {
        var code = e.keyCode || e.which;
        if (e.target.id !== textareaId || code !== 67) {
            return
        }
        container.style.display = 'none'
    }

    document.addEventListener('keydown', keyDownMonitor)
    document.addEventListener('keyup', keyUpMonitor)
}

TrelloClipboard.prototype.setValue = function (value) {
    this.value = value;
}

var clip = new TrelloClipboard();
clip.setValue("test");

See a working example: http://jsfiddle.net/AGEf7/


@don41382 it doesn't properly work on Safari (at least Mac version). Under proper I mean it does copy, but you have to push cmd+C twice.
@VadimIvanov True! Does somebody knows why?
@don41382 I don't know exactly why, but I found a solution. You have a minor bug, onKeyDown the first statement should be if (!(e.ctrlKey || e.metaKey)) { return; } It means that we need to prepare textarea for copy on metaKey pressed (this is how guys from trello made a trick). This is a code from trello.com gist.github.com/fustic/10870311
@VadimIvanov Thanks. I'll fix it above.
It wasn't working in FF 33.1 because el.innerText was undefined, so I changed the last line of the clipboard() function to clip.setValue(el.innerText || el.textContent); for more cross-browser compatibility. link: jsfiddle.net/AGEf7/31
P
Peter Mortensen

Daniel LeCheminant's code didn't work for me after converting it from CoffeeScript to JavaScript (js2coffee). It kept bombing out on the _.defer() line.

I assumed this was something to do with jQuery deferreds, so I changed it to $.Deferred() and it's working now. I tested it in Internet Explorer 11, Firefox 35, and Chrome 39 with jQuery 2.1.1. The usage is the same as described in Daniel's post.

var TrelloClipboard;

TrelloClipboard = new ((function () {
    function _Class() {
        this.value = "";
        $(document).keydown((function (_this) {
            return function (e) {
                var _ref, _ref1;
                if (!_this.value || !(e.ctrlKey || e.metaKey)) {
                    return;
                }
                if ($(e.target).is("input:visible,textarea:visible")) {
                    return;
                }
                if (typeof window.getSelection === "function" ? (_ref = window.getSelection()) != null ? _ref.toString() : void 0 : void 0) {
                    return;
                }
                if ((_ref1 = document.selection) != null ? _ref1.createRange().text : void 0) {
                    return;
                }
                return $.Deferred(function () {
                    var $clipboardContainer;
                    $clipboardContainer = $("#clipboard-container");
                    $clipboardContainer.empty().show();
                    return $("<textarea id='clipboard'></textarea>").val(_this.value).appendTo($clipboardContainer).focus().select();
                });
            };
        })(this));

        $(document).keyup(function (e) {
            if ($(e.target).is("#clipboard")) {
                return $("#clipboard-container").empty().hide();
            }
        });
    }

    _Class.prototype.set = function (value) {
        this.value = value;
    };

    return _Class;

})());

A
Arsen Khachaturyan

Something very similar can be seen on http://goo.gl when you shorten the URL.

There is a readonly input element that gets programmatically focused, with tooltip press CTRL-C to copy.

When you hit that shortcut, the input content effectively gets into the clipboard. Really nice :)