¿Cómo accede Trello al portapapeles del usuario?

936

Cuando pasa el cursor sobre una tarjeta en Trello y presiona Ctrl+ C, la URL de esta tarjeta se copia en el portapapeles. ¿Cómo lo hacen?

Por lo que puedo decir, no hay una película Flash involucrada. Tengo instalado Flashblock , y la pestaña de red de Firefox no muestra ninguna película Flash cargada. (Ese es el método habitual, por ejemplo, con ZeroClipboard).

¿Cómo logran esta magia?

(Justo en este momento creo que tuve una epifanía: no puede seleccionar texto en la página, por lo que supongo que tienen un elemento invisible, donde crean una selección de texto a través del código JavaScript y Ctrl+ Cactiva el comportamiento predeterminado del navegador, copiando ese invisible valor de texto del nodo).

Boldewyn
fuente
22
Si observa el DOM en vivo, hay un div con la clase "portapapeles". Cuando mantiene presionada la tecla Ctrl, se llena con un área de texto (y se elimina cuando levanta la tecla Ctrl). Supongo que su epifanía es correcta. No estoy exactamente seguro de dónde están almacenando la URL por tarjeta
Ian
@ Ian, sí, puedo confirmar, así es exactamente como funcionó. ¡Gracias por desenterrarlo! (No me molesto en dónde se almacena la URL. Estaba interesado en la tecnología de portapapeles sin flash.)
Boldewyn
2
Busqué el perfil de Daniel, y parece que es un desarrollador de Trello. (Me preguntaba de dónde sacó la fuente de Coffeescript). Así que tiene una ventaja injusta ;-) ¡Gracias de todos modos!
Boldewyn
1
No tengo la intención de restarle valor a esta técnica, es bastante inteligente; pero no puedo evitar pensar que esta es, en el mejor de los casos, mal publicitada / documentada, y en el peor, una experiencia de usuario bastante discordante. Por supuesto, no es una agitación invasiva (ya que no recuerdo un momento en el que copié accidentalmente la URL de la tarjeta), pero como usuario de Trello desde hace mucho tiempo, no tenía ni idea de que esto existiera.
Michael Wales
3
@MichaelWales Esta característica se agregó hace 5 días; todavía lo estamos probando y, si parece que funciona, se documentará como un método abreviado de teclado.
Daniel LeCheminant

Respuestas:

1547

Divulgación: escribí el código que usa Trello ; El siguiente código es el código fuente real que Trello usa para lograr el truco del portapapeles.


En realidad, no "accedemos al portapapeles del usuario", sino que ayudamos al usuario un poco seleccionando algo útil cuando presionan Ctrl+ C.

Parece que lo has descubierto; aprovechamos el hecho de que cuando quieres presionar Ctrl+ C, Ctrlprimero debes presionar la tecla. Cuando Ctrlse presiona la tecla, introducimos un área de texto que contiene el texto que queremos terminar en el portapapeles y seleccionamos todo el texto que contiene, de modo que la selección se establece cuando se Cpresiona la tecla. (Luego ocultamos el área de texto cuando Ctrlaparece la clave)

Específicamente, Trello hace esto:

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) ->

En el DOM tenemos

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

CSS para el material del portapapeles:

#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;
}

... y el CSS hace que no se pueda ver el área de texto cuando aparece ... pero es lo suficientemente "visible" para copiar.

Cuando pasas el mouse sobre una tarjeta, llama

TrelloClipboard.set(cardUrl)

... entonces el ayudante del portapapeles sabe qué seleccionar cuando Ctrlse presiona la tecla.

Daniel LeCheminant
fuente
3
¡Increíble! Pero, ¿cómo tienes Mac OS? ¿Escuchas la tecla Command allí?
Suman
28
Vale la pena señalar que un método similar funciona igual de bien para capturar contenido pegado
Michael Robinson
17
Parece que sería malo para los usuarios de teclado: cada vez que intente copiar (o ctrl + clic para abrir en otra ventana, o Ctrl + F para buscar, y así sucesivamente), su enfoque se mueve a otro lugar no relacionado.
Adam A
2
+1. En esta respuesta hay muchas cosas interesantes. Me gusta que hayas compartido el código fuente. Pero lo que pensé que era inteligente fue la explicación real del proceso utilizado para proporcionar la funcionalidad ctrl + c. En mi opinión, fue muy inteligente aprovechar el hecho de que ctrl y c no pueden presionarse al mismo tiempo al comenzar a prepararse para c cuando se presiona ctrl. Realmente me gustó ese enfoque.
Travis J
8
Siéntase libre de usar js2coffee.org para traducir el original a js.
Alexandr Kurilin
79

Realmente construí una extensión de Chrome que hace exactamente esto, y para todas las páginas web. El código fuente está en GitHub .

Encuentro tres errores con el enfoque de Trello, que sé porque los he enfrentado yo mismo :)

La copia no funciona en estos escenarios:

  1. Si ya ha Ctrlpresionado y luego coloca el cursor sobre un enlace y presiona C, la copia no funciona.
  2. Si su cursor está en algún otro campo de texto en la página, la copia no funciona.
  3. Si su cursor está en la barra de direcciones, la copia no funciona.

Resolví el # 1 al tener siempre un lapso oculto, en lugar de crear uno cuando el usuario golpea Ctrl/ Cmd.

Resolví el n. ° 2 borrando temporalmente la selección de longitud cero, guardando la posición de intercalación, haciendo la copia y restaurando la posición de intercalación.

Todavía no he encontrado una solución para el # 3 :) (Para obtener información, consulte el problema abierto en mi proyecto GitHub).

Dhruv Vemula
fuente
10
Entonces realmente hiciste esto de la misma manera que Trello. Dulce cuando tales cosas convergen
Thomas Ahle
@ThomasAhle, ¿qué quieres decir?
Pacerier
77
@Pacerier, supongo que Thomas aludió a la evolución convergente - "... evolución independiente de características similares en especies de diferentes linajes"
yoniLavi
vaca sagrada, podría abrir una nueva conversación sobre este tema
carkod
20

Con la ayuda del código del impermeable ( enlace a GitHub ), logré obtener una versión en ejecución accediendo al portapapeles con JavaScript simple.

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");

El único problema es que esta versión solo funciona con Chrome. La plataforma Trello es compatible con todos los navegadores. ¿Qué me estoy perdiendo?

Sovled gracias a VadimIvanov.

Vea un ejemplo de trabajo: http://jsfiddle.net/AGEf7/

Felix
fuente
@ don41382 no funciona correctamente en Safari (al menos la versión para Mac). Bajo apropiado quiero decir que copia, pero hay que presionar cmd + C dos veces.
Vadim Ivanov
@VadimIvanov ¡Cierto! ¿Alguien sabe por qué?
Felix
1
@ don41382 No sé exactamente por qué, pero encontré una solución. Tiene un error menor, onKeyDown la primera instrucción debería ser if (! (E.ctrlKey || e.metaKey)) {return; } Significa que necesitamos preparar textarea para copiar en metaKey presionada (así es como los chicos de trello hicieron un truco). Este es un código de trello.com gist.github.com/fustic/10870311
Vadim Ivanov
@VadimIvanov Gracias. Lo arreglaré arriba.
Felix
1
No funcionaba en FF 33.1 porque el.innerTextno estaba definido, así que cambié la última línea de la clipboard()función para clip.setValue(el.innerText || el.textContent);obtener una mayor compatibilidad entre navegadores. enlace: jsfiddle.net/AGEf7/31
RevanProdigalKnight
7

El código de Daniel LeCheminant no funcionó para mí después de convertirlo de CoffeeScript a JavaScript ( js2coffee ). Siguió bombardeando en la _.defer()línea.

Asumí que esto tenía algo que ver con los aplazamientos de jQuery, así que lo cambié $.Deferred()y está funcionando ahora. Lo probé en Internet Explorer 11, Firefox 35 y Chrome 39 con jQuery 2.1.1. El uso es el mismo que se describe en la publicación de Daniel.

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;

})());
RemolcadorCapitán
fuente
5

Algo muy similar se puede ver en http://goo.gl cuando acorta la URL.

Hay un elemento de entrada de solo lectura que se enfoca programáticamente, con información sobre herramientas CTRL-Cpara copiar.

Cuando tocas ese atajo, el contenido de entrada entra efectivamente en el portapapeles. Muy agradable :)

Boris Brdarić
fuente