Создаю расширение для chrome
.
Столкнулся с проблемой копирования текста на js.
Сам код копирования простой.
Суть проблемы в том что код не копируется в буфер обмена.
Кто то может помочь решить проблему?
Вот сам код.
function copyToClipboard() {
/* Get the text field */
var copyText = document.getElementById("clipboard-text");
/* Select the text field */
copyText.select();
copyText.setSelectionRange(0, 99999); /*For mobile devices*/
/* Copy the text inside the text field */
document.execCommand("copy");
/* Alert the copied text */
alert("Copied the text: " + copyText.value);
}
document.getElementById('clipboard-copy').addEventListener("click", copyToClipboard);
После клика на кнопку Copy
срабатывает событие но текст все ровно не копируется в буфер обмена.
Странно, у меня работает как в браузере так и в расширении без всяких дополнительных разрешений - причем как execCommand
, так и clipboard.writeText
Версия Chrome 77.0.3865.120 (Официальная сборка), (64 бит)
В том числе, код работает и здесь
// lib.js
function isInt(v) {
return typeof v === 'number' && isFinite(v) && Math.floor(v) === v
}
function randomMinMax(min, max) {
return Math.floor(Math.random() * (++max - min)) + min;
}
function isProperty(prop, obj, type) {
return typeof obj === 'object' && prop in obj && (type ? typeof obj[prop] === type : true)
}
// charGenerator.js
function charGenerator(...arrs) {
let arr = []
const testpush = (s) => {
if (arr.indexOf(s) === -1) {
arr.push(s)
}
}
const set = (s, e) => {
if (!isInt(s) || !isInt(e)) {
return
}
for (let i = s; i <= e; ++i) {
testpush(String.fromCharCode(i))
}
}
let s
for (let i = 0; i < arrs.length; ++i) {
s = arrs[i]
if (typeof s !== 'string' || !s.length) {
continue
}
testpush(arrs[i][0])
if (s.length > 1) {
set(s.charCodeAt(0) + 1, s.charCodeAt(1))
}
}
return arr
}
// Log.js
class Log {
constructor(elout) {
this.out = elout
}
log(mess, cl = 'log') {
let l = document.createElement('code')
l.className = cl
l.textContent = mess
this.out.appendChild(l)
}
}
// InputRandomText.js
const numberChar = 8
const abc = charGenerator('09', 'AZ', 'az')
class InputRandomText {
constructor(random, input) {
this.input = input
this.buttonExec = random
this.buttonExec.addEventListener('click', () => {
this.setInput()
})
}
static generate(length = numberChar) {
let l = length > abc.length ? abc.length : length
let arr = abc.slice()
let arrTo = []
let i
while (l--) {
i = randomMinMax(0, arr.length)
arrTo.push(arr.splice(i, 1)[0])
}
return arrTo.join('')
}
setInput() {
this.input.value = InputRandomText.generate(randomMinMax(5, numberChar))
}
}
// CopyToClipboard.js
class CopyToClipboard extends Log {
constructor(input, exec, log, methodExecCommand) {
super(log)
this.input = input
this.buttonExec = exec
this.methodExecCommand = methodExecCommand
this.write = null
// test
if (this.test()) {
this.buttonExec.addEventListener('click', () => {
this.copy()
})
}
else {
this.log('Возможно браузер не поддерживает копирование в буфер обмена', 'error')
}
}
copy() {
if (/^\s+|\s+$/g.test(this.input.value)) {
this.input.value = this.input.value.replace(/^\s+|\s+$/g, '')
}
if (this.input.value.length) {
this.input.select()
try {
this.write()
} catch (e) {
this.log(e.message, 'error')
return
}
this.log(`Текст value.length: ${this.input.value.length} скопирован в буфер обмена`)
return
}
this.log('Отсутствует текст в поле ввода', 'warn')
}
testExecCommand() {
if (isProperty('queryCommandSupported', document, 'function') && document.queryCommandSupported('copy') && isProperty('execCommand', document, 'function')) {
this.write = () => {
document.execCommand('copy')
}
this.log('Копируем через document.execCommand')
return true
}
return false
}
testClipboard() {
if (isProperty('clipboard', navigator) && isProperty('writeText', navigator.clipboard, 'function')) {
this.write = () => {
navigator.clipboard.writeText(this.input.value)
}
this.log('Копируем через navigator.clipboard')
return true
}
return false
}
test() {
// для теста меняем порядок проверки
// clipboard.html
// clipboard.html?execCommand -> methodExecCommand
if (this.methodExecCommand && this.testExecCommand()) {
return true
}
if (this.testClipboard() || this.testExecCommand()) {
return true
}
return false
}
}
// clipboard.js
document.addEventListener('DOMContentLoaded', () => {
new CopyToClipboard(
document.body.querySelector('[data-input]'),
document.body.querySelector('[data-exec]'),
document.body.querySelector('[data-log]'),
/execCommand/i.test(document.location.search)
)
new InputRandomText(
document.body.querySelector('[data-random]'),
document.body.querySelector('[data-input]')
)
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="shortcut icon" href="./icons/chemistry.png" type="image/png">
<title>clipboard</title>
<script src="./clipboard.js" type="module"></script>
<style>
body {
font-family: 'Courier New', Courier, monospace;
}
[data-log] {
margin-top: 1em;
width: 500px;
min-height: 100px;
padding: 1em;
background-color: #ececec;
border: 1px solid #9E9E9E;
}
[data-log]>code {
display: block;
margin-top: 1em;
border-left: 3px solid green;
padding-left: 1em;
font-size: 1.5em;
}
[data-log]>code.error {
border-left-color: red;
}
[data-log]>code.warn {
border-left-color: #ff9800;
}
</style>
</head>
<body>
<h1>Test Clipboard</h1>
<!-- Только для расширения и браузера
<div>Для теста меняем порядок проверки и использования доступных API</div>
<ul>
<li><a href="./clipboard.html?clipboard">clipboard.html?clipboard</a></li>
<li><a href="./clipboard.html?execCommand">clipboard.html?execCommand</a></li>
</ul>
-->
<button data-random>Для ленивых</button>
<input data-input type="text" placeholder="input text">
<button data-exec>Copy</button>
<br><br>
<input type="text" placeholder="проверим ctrl+p"> Сюда просто вставляем что скопировано горячими клавишами [ctrl+p]
<div data-log></div>
</body>
</html>
Копия манифеста
{
"manifest_version": 2,
"name": "TestClipboard",
"description": "TestClipboard",
"version": "1.0.0",
"author": "Alexander Lonberg",
"icons": {
"32": "icons/chemistry.png"
},
"browser_action": {
"default_icon": {
"32": "icons/chemistry.png"
},
"default_popup": "clipboard.html",
"default_title": "TestClipboard"
},
"permissions": []
}
Можете попробовать подключить эту папку как расширение или запустить в браузере
https://yadi.sk/d/055Y4jgQCTuJWQ
UPD
опечатка ctrl+p
конечно ctrl+v
Убедитесь что у вас запрошено разрешение на "clipboardWrite"
в разрешениях:
manifest.json:
{
...
"permissions": [
...
"clipboardWrite"
]
...
}
При беглом взгляде код выглядет корректно. В своем расширении я применяю достаточно схожий сниппет:
const tempEl = document.createElement('textarea');
document.body.appendChild(tempEl);
tempEl.value = textToCopy;
tempEl.select();
const copied = document.execCommand('copy');
document.body.removeChild(tempEl);
Предполагается использование нового Clipboard API.
С помощью него, копирование в буфер выполняется методом navigator.clipboard.writeText()
, который принимает аргументом строковое значение - тот текст, который нужно скопировать.
В качестве результата возвращается Promise
- то есть, выполнение асинхронное... но с другой стороны, можно удобно ловить ошибки. Да и async
+await
никто не отменял ;)
При вызове метода из popup'а в расширении Chrome, не требуется каких-либо дополнительных разрешений.
В расширениях Firefox они так же необязательны, но есть условие что вызов метода должен выполняться в коллбэке обработчика события, инициированного пользователем (например, в обработчике клика).
Я проверял в расширении в режиме разработчика Chrome v77, все работает исправно.
Пример, повторяющий тестовое расширение:
/* popup.js */
document.addEventListener('DOMContentLoaded', () => {
const inputEl = document.querySelector('input');
document.querySelector('button').addEventListener('click', () => {
navigator.clipboard.writeText(inputEl.value)
.then(() => { setStatus('Скопировано!'); })
.catch(setStatus);
});
});
function setStatus(data) { // эта функция только для показа результата
const statusEl = document.querySelector('p');
if (data instanceof Error) {
statusEl.textContent = data.message;
statusEl.style.color = '#e00';
} else {
statusEl.textContent = data;
statusEl.style.color = '#0c0';
setTimeout(() => setStatus(''), 2e3);
}
}
/* popup.html > html > head > style */
body { font-family: sans-serif; }
body > * { font-size: 1rem; }
div, p { margin: 1rem 0 0 0; padding: 0.5rem 0 0 0; border-top: 1px solid #eee; }
textarea { display: block; width: 90%; }
<!-- popup.html > html > body -->
<input value="Lorem ipsum">
<button>Скопировать в буфер обмена</button>
<div>Сюда можно вставить текст (для проверки):</div>
<textarea></textarea>
<p></p>
Манифест тестового расширения:
{
"name": "Test extension",
"description": "Test extension",
"version": "0.0.1",
"manifest_version": 2,
"browser_action": {
"default_popup": "popup.html"
}
}
При использовании на веб-странице (то есть, не в расширении), нужно учесть три момента:
по-видимому, Clipboard API работает только если страница загружена через HTTPS
некоторые браузеры могут требовать запрос разрешений через Permissions API
Clipboard API экспериментальный, а значит он не поддерживается старыми/отстающими браузерами, и (на данный момент) поддержка отсутствует в Android WebView (а в Chrome для Android - она есть)
Страница на MDN: https://wiki.developer.mozilla.org/en-US/docs/Web/API/Clipboard
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Использую плагин bootstrap datepicker для выбора датКак можно прописать ограничение, чтобы можно было выбрать даты в диапазоне от 18 до 100 лет?
Уже был подобный вопрос (Datepicker), сделал все, как там написано, подключил все библиотеки
Как проверить записанны ли куки в браузере, в данном коде я сначала записал куки в браузер, далее мне нужно вывести сообщение что куки записаны,...