Как Trello получает доступ к буферу обмена пользователя?

936

Когда вы наводите курсор на карточку в Trello и нажимаете Ctrl+ C, URL этой карточки копируется в буфер обмена. Как они это делают?

Насколько я могу судить, Flash-фильм не задействован. У меня установлен Flashblock , и на вкладке сети Firefox не отображается загруженный Flash-фильм. (Это обычный метод, например, ZeroClipboard.)

Как они достигают этой магии?

(Прямо сейчас я думаю, что у меня было прозрение: вы не можете выделить текст на странице, поэтому я предполагаю, что у них есть невидимый элемент, где они создают выделение текста с помощью кода JavaScript, и Ctrl+ Cвызывает поведение браузера по умолчанию, копируя это невидимое текстовое значение узла.)

Boldewyn
источник
22
Если вы посмотрите на живой DOM, есть div с классом «clipboard-container». Когда вы удерживаете клавишу Ctrl, она заполняется текстовой областью (и удаляется, когда вы снимаете клавишу Ctrl). Я бы предположил, что ваше прозрение правильно. Я просто не совсем уверен, где они хранят URL для каждой карты
Ян
@ Ян, да, я могу подтвердить, это именно так и работает. Спасибо, что выкопали это! (Меня не интересует, где хранится URL-адрес. Меня интересовала технология буфера обмена без флеш-памяти.)
Болдевин
2
Я посмотрел профиль Даниэля, и, похоже, он разработчик Trello. (Интересно, откуда он взял источник Coffeescript?) Так что у него несправедливое преимущество ;-) В любом случае, спасибо!
Болдевин
1
Я не собираюсь умалять изобретательность этой техники, она довольно умна; но я не могу не думать, что в лучшем случае это плохо публикуется / документируется, а в худшем - довольно неприятный пользовательский опыт. Конечно, это не агрессивно сотрясает (поскольку я не могу вспомнить время, когда я случайно скопировал URL карты), но, как давний пользователь Trello, я понятия не имел, что это существовало.
Майкл Уэльс,
3
@MichaelWales Эта функция была добавлена ​​5 дней назад; мы все еще тестируем его, и если он будет работать, это будет задокументировано как сочетание клавиш.
Даниэль ЛеЧеминант

Ответы:

1547

Раскрытие: я написал код, который использует Trello ; приведенный ниже код является фактическим исходным кодом, который Trello использует для выполнения трюка с буфером обмена.


На самом деле мы не «получаем доступ к буферу обмена пользователя», вместо этого мы немного помогаем пользователю, выбирая что-то полезное, когда он нажимает Ctrl+ C.

Похоже, вы поняли это; мы используем тот факт, что когда вы хотите нажать Ctrl+ C, вы должны Ctrlсначала нажать клавишу. Когда Ctrlклавиша нажата, мы открываем текстовую область, которая содержит текст, который мы хотим поместить в буфер обмена, и выделяем весь текст в нем, поэтому выбор полностью устанавливается при Cнажатии клавиши. (Затем мы скрываем текстовую область, когда Ctrlключ появляется)

В частности, Trello делает это:

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

В DOM у нас есть

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

CSS для буфера обмена:

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

... и CSS делает это таким образом, что вы не можете видеть текстовую область, когда она появляется ... но она достаточно "видима" для копирования.

Когда вы наводите курсор на карту, она вызывает

TrelloClipboard.set(cardUrl)

... так что помощник буфера обмена знает, что выбрать при Ctrlнажатии клавиши.

Даниэль ЛеЧеминант
источник
3
Потрясающие! Но как у вас Mac OS - вы там «слушаете» клавишу Command?
Суман
28
Стоит отметить, что подобный метод работает так же хорошо для захвата вставленного контента
Майкл Робинсон
17
Это звучит так, как будто это будет плохо для пользователей клавиатуры - всякий раз, когда вы пытаетесь скопировать (или ctrl + щелчок, чтобы открыть в другом окне, или Ctrl + F, чтобы искать, и т. Д.), Ваш фокус перемещается куда-то не связанное.
Адам
2
+1. В этом ответе много интересного. Мне нравится, что вы на самом деле поделились исходным кодом. Но то, что я думал, было умным, было фактическим объяснением процесса, используемого для обеспечения функциональности ctrl + c. По моему мнению, было бы очень разумно воспользоваться тем фактом, что ctrl и c не могут быть нажаты в одно и то же время, начав готовиться к c при нажатии ctrl. Мне очень понравился такой подход.
Трэвис Дж
8
Не стесняйтесь использовать js2coffee.org, чтобы перевести оригинал в js, если вы этого хотите .
Александр Курилин
79

Я на самом деле создал расширение Chrome, которое делает именно это, и для всех веб-страниц. Исходный код находится на GitHub .

Я нашел три ошибки с подходом Trello, которые я знаю, потому что я столкнулся с ними сам :)

Копия не работает в этих сценариях:

  1. Если вы уже Ctrlнажали, а затем наведите ссылку и нажали C, копия не работает.
  2. Если курсор находится в каком-либо другом текстовом поле на странице, копия не работает.
  3. Если ваш курсор находится в адресной строке, копия не работает.

Я решил # 1, всегда имея скрытый диапазон, а не создавая его, когда пользователь нажимает Ctrl/ Cmd.

Я решил # 2, временно очистив выбор нулевой длины, сохранив позицию каретки, сделав копию и восстановив позицию каретки.

Я еще не нашел исправления для # 3 :) (Для информации, проверьте открытую проблему в моем проекте GitHub).

Дхрув Вемула
источник
10
Таким образом, вы сделали это так же, как Trello. Сладко, когда такие вещи сходятся
Томас Ахл
@ThomasAhle, что ты имеешь в виду?
Pacerier
7
@Pacerier, я предполагаю, что Томас намекал на Конвергентную Эволюцию - «... независимая эволюция сходных черт у видов разных линий»
yoniLavi
Святая корова, вы можете открыть новый чат на эту тему
carkod
20

С помощью кода raincoat ( ссылка на GitHub ) мне удалось получить работающую версию с доступом к буферу обмена с простым 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");

Единственная проблема в том, что эта версия работает только с Chrome. Платформа Trello поддерживает все браузеры. Чего мне не хватает?

Согласился благодаря Вадиму Иванову.

Смотрите рабочий пример: http://jsfiddle.net/AGEf7/

Феликс
источник
@ don41382 он не работает должным образом в Safari (по крайней мере, версия Mac). Под правильным я имею в виду, что он копирует, но вы должны дважды нажать cmd + C.
Вадим Иванов
@ ВадимИванов Правда! Кто-нибудь знает почему?
Феликс
1
@ don41382 Я не знаю точно, почему, но я нашел решение. У вас есть небольшая ошибка, onKeyDown первый оператор должен быть if (! (E.ctrlKey || e.metaKey)) {return; } Это означает, что нам нужно подготовить текстовую область для копирования на нажатой метакей (именно так парни из trello сделали трюк). Это код с trello.com gist.github.com/fustic/10870311
Вадим Иванов
@VadimIvanov Спасибо. Я исправлю это выше.
Феликс
1
Он не работал в FF 33.1, потому что он el.innerTextбыл неопределенным, поэтому я изменил последнюю строку clipboard()функции clip.setValue(el.innerText || el.textContent);на более кросс-браузерную совместимость. ссылка: jsfiddle.net/AGEf7/31
RevanProdigalKnight
7

Код Даниеля ЛеЧемананта не работал для меня после преобразования его из CoffeeScript в JavaScript ( js2coffee ). Это продолжало бомбить на _.defer()линии.

Я предположил, что это как-то связано с jQuery deferreds, поэтому я изменил его на $.Deferred()и теперь оно работает. Я протестировал его в Internet Explorer 11, Firefox 35 и Chrome 39 с jQuery 2.1.1. Использование такое же, как описано в посте Даниила.

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;

})());
TugboatCaptain
источник
5

Нечто похожее можно увидеть на http://goo.gl, когда вы сокращаете URL.

Существует элемент ввода только для чтения, который фокусируется программно, с нажатием всплывающей подсказки CTRL-Cдля копирования.

Когда вы нажимаете на этот ярлык, входной контент эффективно попадает в буфер обмена. Действительно мило :)

Борис Брдарич
источник