API буфера обмена и несовместимое поведение браузера между операционными системами - PullRequest
0 голосов
/ 14 мая 2019

Я пытаюсь устранить проблему, из-за которой пользователи Windows нашего приложения React не могут копировать и вставлять содержимое, которое не могут пользователи Windows.

У нас есть содержимое, отображаемое в реагирующем модальном окне, с кнопкой, которая при нажатии выделяет область для копирования и копирует ее в буфер обмена для вставки.

Мы запускаем следующее в событии onCopy, инициируемом событием щелчка на кнопке:

const range = document.createRange();
range.selectNode(this.getCiteTextNode()); // function to get the node
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);

const listener = document.addEventListener("copy", e => {
  const outerHTML = this.getCitationTextWithoutStyle(); //function strips styling attributes
  e.clipboardData.setData("text/html", outerHTML);
  e.preventDefault();
});

document.execCommand("copy");
document.removeEventListener("copy", listener);

Что я заметил, так это то, что хотя браузеры в MacOS (Safari, Firefox, Chrome) позволяют выполнять эту операцию копирования / вставки, в Windows, Edge, FF и Chrome происходит сбой без вывода сообщений (в буфере буфера обмена ничего нет), хотя программный доступ был предоставлен в этих браузерах. Более того, кажется, что ЛЮБОЙ запрос на копирование (меню, мышь или клавиатура) завершается неудачно; Я могу вручную выделить другую область модальной области, выбрать копию и попытаться вставить другой документ, и он просто не копирует - ничто не попадает в буфер.

Это, однако, работает в IE11 - с подсказкой - но доступ к буферу обмена работает нормально, когда инициируется событие нажатия кнопки.

Дальнейшие эксперименты показали, что это работает в браузерах Windows, когда я изменяю preventDefault на stopPropagation. После того, как я это сделаю, контент правильно попадет в буфер обмена.

Кто-нибудь когда-нибудь сталкивался с этим? Как предотвращение всплытия событий приводит к тому, что это «работает» только в браузерах Windows?

PS - в курсе черновика статуса execCommand.

Спасибо

1 Ответ

0 голосов
/ 15 мая 2019

Преамбула : Этот ответ пытается объяснить, что делает код в OP, с чем они могут столкнулись, и как они должны обращатьсяэто, однако, немного ослаблено тем фактом, что и Windows, и macOs ведут себя для меня одинаково ...


Если мы пошагово сделаем ваш код, это будет

  • Выберите узел в DOM.
  • Выполните команду copy
    [внутренняя логика браузера]
    => Запустите copy событие
  • Обработка события copy
  • Установка данных DataTransfer события.
    => Установить outerHTML как text/html в буфере обмена
  • Запретить действие по умолчанию copy event.

Если не предотвращено, то должно быть " действие по умолчанию ",

=> Получить разметку активного выделения как text/HTMLв буфер обмена.
=> Возьмите toString() выделения как text/plain в буфере обмена.

Возможно, вы уже видите, чего не хватает в вашем коде:

Вы не установили text/plain значение вашего события копирования .

Тольконемногие приложения будут пытаться получить данные text/html из буфера обмена, большинство просто будет искать значение text/plain.

Так что вам нужно также установить его.

Вот небольшая игровая площадка *, на которой вы можете видеть, что даже на веб-странице только некоторые места позволяют вставлять как text/html, тогда как другие будут использовать значение text/plain.

btn.onclick = e => {
  const range = document.createRange();
  range.selectNode(copyme); // function to get the node
  const selection = getSelection();
  selection.removeAllRanges();
  selection.addRange(range);

  const listener = e => {
    // make it green in the clipboard
    const outerHTML = copyme.outerHTML.replace('red', 'green');
    // add some text to show we have the control
    const plaintext = selection.toString() + ' plaintext';

    const dT = e.clipboardData;
    dT.setData("text/html", outerHTML);
    // IIRC IE does only support `"text"` MIME type
    dT.setData("text", plaintext);
    e.preventDefault();
  };

  document.addEventListener("copy", listener);
  document.execCommand("copy");
  document.removeEventListener("copy", listener);
};
[contenteditable] {border: 1px solid;}
<button id="btn">copy</button>

<span id="copyme" style="color:red">Hello</span>
<br>

<!-- contenteditable elements will grab the text/html data -->
<div contenteditable>paste HTML here<br> </div>

<!-- <textarea> and <input> will only grab the text/plain -->
<textarea>paste plain here
</textarea>

* Я исправил несколько несоответствующих опечаток из вашего исходного кода, например, неверное удаление события, указанное в комментариях к вашему вопросу.


Также обратите внимание, что не stopPropagation творил магию, а просто тот факт, что вы не вызывали preventDefault.
Делая так, поведение по умолчаниюкопирования разметки узла, возвращенного this.getCiteTextNode() как text/html, и его textContent как text/plain переопределяет ваши собственные настройки.

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

btn.onclick = e => {
  const range = document.createRange();
  range.selectNode(copyme); // function to get the node
  const selection = getSelection();
  selection.removeAllRanges();
  selection.addRange(range);

  const listener = e => {
    // make it green in the clipboard
    const outerHTML = copyme.outerHTML.replace('red', 'green');
    // add some text to show we have the control
    const plaintext = selection.toString() + ' plaintext';

    const dT = e.clipboardData;
    dT.setData("text/html", outerHTML);
    // IIRC IE does only support `"text"` MIME type
    dT.setData("text", plaintext);
	  if(prev.checked) {
      e.preventDefault();
    }
  };

  document.addEventListener("copy", listener);
  document.execCommand("copy");
  document.removeEventListener("copy", listener);
};
[contenteditable] {border: 1px solid;}
<label>prevent default behavior<input type="checkbox" id="prev" checked></label><br>
<button id="btn">copy</button>

<span id="copyme" style="color:red">Hello</span>
<br>

<!-- contenteditable elements will grab the text/html data -->
<div contenteditable>paste HTML here<br> </div>

<!-- <textarea> and <input> will only grab the text/plain -->
<textarea>paste plain here
</textarea>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...