W jaki sposób Trello uzyskuje dostęp do schowka użytkownika?

936

Po najechaniu myszką na kartę w Trello i naciśnięciu Ctrl+ Cadres URL tej karty jest kopiowany do schowka. Jak oni to robią?

O ile mi wiadomo, nie ma w tym filmu Flash. Mam zainstalowany Flashblock , a karta sieciowa Firefox pokazuje brak załadowanego filmu Flash. (Jest to zwykle stosowana metoda, na przykład przez ZeroClipboard.)

Jak osiągają tę magię?

(W tej chwili myślę, że miałem objawienie: Nie możesz zaznaczyć tekstu na stronie, więc zakładam, że mają niewidoczny element, w którym tworzą zaznaczenie tekstu za pomocą kodu JavaScript i Ctrl+ Cwyzwalają domyślne zachowanie przeglądarki, kopiując to niewidoczne wartość tekstowa węzła).

Boldewyn
źródło
22
Jeśli spojrzysz na rzeczywistą DOM, istnieje div z klasą „schowek-kontener”. Gdy przytrzymasz klawisz Ctrl, zostanie on wypełniony obszarem tekstowym (i zostanie usunięty po podniesieniu klawisza Ctrl). Zakładam, że twoje objawienie jest prawidłowe. Po prostu nie jestem do końca pewien, gdzie przechowują URL na kartę
Ian
@Ian, tak, mogę potwierdzić, dokładnie tak to działało. Dzięki za wykopanie go! (Nie przejmuję się tym, gdzie jest przechowywany adres URL. Byłem zainteresowany technologią schowka bez flasha).
Boldewyn
2
Sprawdziłem profil Daniela i wygląda na to, że jest programistą Trello. (Zastanawiałem się, skąd wziął źródło Coffeescript.) Ma więc niesprawiedliwą przewagę ;-) W każdym razie dzięki!
Boldewyn
1
Nie zamierzam umniejszać zaradności tej techniki, jest całkiem sprytna; ale nic nie mogę poradzić, ale myślę, że jest to w najlepszym razie źle nagłośnione / udokumentowane, aw najgorszym przypadku dość denerwujące doświadczenie użytkownika. To prawda, że ​​nie jest to inwazyjne wkurzanie (ponieważ nie pamiętam czasu, w którym przypadkowo skopiowałem adres URL karty), ale jako długoletni użytkownik Trello nie miałem absolutnie pojęcia, że ​​to istnieje.
Michael Wales
3
@MichaelWales Ta funkcja została dodana 5 dni temu; wciąż go testujemy, a jeśli wydaje się, że działa, zostanie to udokumentowane jako skrót klawiaturowy.
Daniel LeCheminant,

Odpowiedzi:

1546

Ujawnienie: Napisałem kod, którego używa Trello ; poniższy kod to rzeczywisty kod źródłowy, którego Trello używa do wykonania sztuczki ze schowka.


W rzeczywistości nie „uzyskujemy dostępu do schowka użytkownika”, zamiast tego pomagamy użytkownikowi, wybierając coś przydatnego po naciśnięciu Ctrl+ C.

Wygląda na to, że to rozgryzłeś; wykorzystujemy fakt, że jeśli chcesz nacisnąć Ctrl+ C, najpierw musisz nacisnąć Ctrlklawisz. Po Ctrlnaciśnięciu klawisza wchodzimy do obszaru tekstowego, który zawiera tekst, który chcemy skończyć w schowku, i zaznaczamy w nim cały tekst, więc wybór jest ustawiany po Cnaciśnięciu klawisza. (Następnie ukrywamy obszar tekstowy, gdy Ctrlpojawi się klucz)

W szczególności Trello robi to:

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

W DOM mamy

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

CSS dla schowka:

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

... a CSS sprawia, że ​​nie można zobaczyć obszaru tekstowego, kiedy się pojawia ... ale jest "wystarczająco widoczny", aby skopiować.

Gdy najedziesz kursorem na kartę, zadzwoni

TrelloClipboard.set(cardUrl)

... więc pomocnik schowka wie, co wybrać po Ctrlnaciśnięciu klawisza.

Daniel LeCheminant
źródło
3
Niesamowite! Ale skąd masz system Mac OS - czy „słuchasz” tam klawisza Command?
Suman
28
Warto zauważyć, że podobna metoda działa równie dobrze do przechwytywania wklejonych treści
Michael Robinson
17
Brzmi to źle dla użytkowników klawiatury - za każdym razem, gdy spróbujesz skopiować (lub Ctrl + kliknięcie, aby otworzyć w innym oknie lub Ctrl + F, aby wyszukać itd.), Fokus zostanie przeniesiony gdzieś niezwiązany.
Adam A
2
+1. W tej odpowiedzi dzieje się wiele fajnych rzeczy. Podoba mi się, że faktycznie podzieliłeś się kodem źródłowym. Ale to, co uważałem za sprytne, to faktyczne wyjaśnienie procesu zastosowanego w celu zapewnienia funkcjonalności ctrl + c. Moim zdaniem sprytnie było skorzystać z faktu, że nie można wcisnąć ctrl i c dokładnie w tym samym czasie, zaczynając przygotowywać się do c po naciśnięciu ctrl. Naprawdę podobało mi się to podejście.
Travis J
8
Jeśli chcesz, możesz użyć js2coffee.org do przetłumaczenia oryginału na js.
Alexandr Kurilin
79

Zbudowałem rozszerzenie Chrome, które właśnie to robi, i dla wszystkich stron internetowych. Kod źródłowy znajduje się na GitHub .

Znalazłem trzy błędy w podejściu Trello, które znam, bo sam sobie z nimi poradziłem :)

Kopiowanie nie działa w tych scenariuszach:

  1. Jeśli już Ctrlnacisnąłeś, a następnie umieściłeś link i uderzyłeś C, kopia nie działa.
  2. Jeśli kursor znajduje się w innym polu tekstowym na stronie, kopia nie działa.
  3. Jeśli kursor znajduje się na pasku adresu, kopia nie działa.

Rozwiązałem # 1, zawsze mając ukryty zakres, zamiast tworzyć go, gdy użytkownik kliknie Ctrl/ Cmd.

Rozwiązałem # 2, tymczasowo usuwając zaznaczenie zerowej długości, zapisując pozycję karetki, wykonując kopię i przywracając pozycję karetki.

Nie znalazłem jeszcze poprawki dla nr 3 :) (Aby uzyskać informacje, sprawdź otwarty problem w moim projekcie GitHub).

Dhruv Vemula
źródło
10
Więc zrobiłeś to tak samo jak Trello. Słodko, gdy takie rzeczy się zbiegają
Thomas Ahle,
@ThomasAhle, co masz na myśli?
Pacerier
7
@Pacerier, zakładam, że Thomas nawiązał do Convergent Evolution - „... niezależna ewolucja podobnych cech u gatunków o różnych liniach”
yoniLavi,
święta krowa, możesz otworzyć nowy czat na ten temat
carkod
20

Za pomocą kodu płaszcza przeciwdeszczowego ( link do GitHub ) udało mi się uzyskać dostęp do działającej wersji dostępnej do schowka za pomocą zwykłego 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");

Jedynym problemem jest to, że ta wersja działa tylko z Chrome. Platforma Trello obsługuje wszystkie przeglądarki. Czego mi brakuje?

Wysłany dzięki VadimIvanov.

Zobacz działający przykład: http://jsfiddle.net/AGEf7/

Felix
źródło
@ don41382 nie działa poprawnie w przeglądarce Safari (przynajmniej w wersji dla komputerów Mac). Pod właściwym mam na myśli, że kopiuje, ale musisz nacisnąć cmd + C dwa razy.
Vadim Iwanow
@VadimIvanov Prawda! Czy ktoś wie dlaczego?
Felix
1
@ don41382 Nie wiem dokładnie dlaczego, ale znalazłem rozwiązanie. Masz drobny błąd, onKeyDown pierwszą instrukcją powinno być if (! (E.ctrlKey || e.metaKey)) {return; } Oznacza to, że musimy przygotować obszar tekstowy do kopiowania na naciśniętym metaKey (w ten sposób faceci z trello zrobili lewę). To jest kod z trello.com gist.github.com/fustic/10870311
Vadim Iwanow
@VadimIvanov Thanks. Naprawię to powyżej.
Felix
1
To nie działało w FF 33.1, ponieważ el.innerTextbyło niezdefiniowane, więc zmieniłem ostatni wiersz clipboard()funkcji na clip.setValue(el.innerText || el.textContent);dla większej kompatybilności z różnymi przeglądarkami. link: jsfiddle.net/AGEf7/31
RevanProdigalKnight
7

Kod Daniela LeCheminanta nie działał dla mnie po konwersji z CoffeeScript na JavaScript ( js2coffee ). Nadal bombardował _.defer()linię.

Założyłem, że ma to coś wspólnego z odroczeniem jQuery, więc zmieniłem go na $.Deferred()i działa teraz. Przetestowałem to w Internet Explorerze 11, Firefox 35 i Chrome 39 z jQuery 2.1.1. Użycie jest takie samo, jak opisano w poście Daniela.

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;

})());
Holownik Kapitan
źródło
5

Coś bardzo podobnego można zobaczyć na http://goo.gl po skróceniu adresu URL.

Istnieje element wejściowy tylko do odczytu, który jest programowo skoncentrowany, z naciśnięciem podpowiedzi CTRL-Cdo kopiowania.

Po naciśnięciu tego skrótu treść wejściowa skutecznie trafia do schowka. Bardzo miłe :)

Boris Brdarić
źródło