Как Trello открывает пользовательский буфер обмена?

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

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

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

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

Ответ 1

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


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

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

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

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

    $(document).keydown (e) =>
      # Only do this if there something to be put on the clipboard, and it
      # looks like they're starting a copy shortcut
      if [email protected] || !(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.

Ответ 2

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

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

В этих сценариях копия не работает:

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

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

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

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

Ответ 3

С помощью 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/

Ответ 4

Код Daniel LeCheminant не работал у меня после преобразования его с CoffeeScript на JavaScript (js2coffee). Он продолжал бомбардировать линию _.defer().

Я предположил, что это связано с отсрочкой jQuery, поэтому я изменил ее на $.Deferred(), и теперь она работает. Я тестировал его в Internet Explorer 11, Firefox 35 и Chrome 39 с помощью jQuery 2.1.1. Использование такое же, как описано в сообщении 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;

})());

Ответ 5

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

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