Clipboard updates (#2254)
* Rename * Rename vars * Refactor paste target * Prevent most CSS url() properties from loading * Add helper function to clear rich function * Add useRichText argument * Update condition for using readText * Fix indent * Update CSS
This commit is contained in:
parent
a370b46fae
commit
9ef7f9d383
@ -11,6 +11,7 @@
|
|||||||
<link rel="icon" type="image/png" href="/images/icon48.png" sizes="48x48">
|
<link rel="icon" type="image/png" href="/images/icon48.png" sizes="48x48">
|
||||||
<link rel="icon" type="image/png" href="/images/icon64.png" sizes="64x64">
|
<link rel="icon" type="image/png" href="/images/icon64.png" sizes="64x64">
|
||||||
<link rel="icon" type="image/png" href="/images/icon128.png" sizes="128x128">
|
<link rel="icon" type="image/png" href="/images/icon128.png" sizes="128x128">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/background.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -61,7 +62,7 @@
|
|||||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1603985
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1603985
|
||||||
-->
|
-->
|
||||||
<!-- [html-validate-disable close-order] -->
|
<!-- [html-validate-disable close-order] -->
|
||||||
<div id="clipboard-image-paste-target" contenteditable="true">
|
<div id="clipboard-rich-content-paste-target" contenteditable="true">
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
29
ext/css/background.css
Normal file
29
ext/css/background.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Yomichan Authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* stylelint-disable declaration-no-important */
|
||||||
|
#clipboard-rich-content-paste-target * {
|
||||||
|
background-image: none !important;
|
||||||
|
list-style-image: none !important;
|
||||||
|
content: none !important;
|
||||||
|
cursor: auto !important;
|
||||||
|
border-image-source: none !important;
|
||||||
|
offset-path: none !important;
|
||||||
|
-webkit-mask-image: none !important;
|
||||||
|
mask-image: none !important;
|
||||||
|
}
|
||||||
|
/* stylelint-enable declaration-no-important */
|
@ -53,7 +53,7 @@ class Backend {
|
|||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
document: (typeof document === 'object' && document !== null ? document : null),
|
document: (typeof document === 'object' && document !== null ? document : null),
|
||||||
pasteTargetSelector: '#clipboard-paste-target',
|
pasteTargetSelector: '#clipboard-paste-target',
|
||||||
imagePasteTargetSelector: '#clipboard-image-paste-target'
|
richContentPasteTargetSelector: '#clipboard-rich-content-paste-target'
|
||||||
});
|
});
|
||||||
this._clipboardMonitor = new ClipboardMonitor({
|
this._clipboardMonitor = new ClipboardMonitor({
|
||||||
japaneseUtil: this._japaneseUtil,
|
japaneseUtil: this._japaneseUtil,
|
||||||
@ -596,7 +596,7 @@ class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _onApiClipboardGet() {
|
async _onApiClipboardGet() {
|
||||||
return this._clipboardReader.getText();
|
return this._clipboardReader.getText(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onApiGetDisplayTemplatesHtml() {
|
async _onApiGetDisplayTemplatesHtml() {
|
||||||
@ -1773,7 +1773,7 @@ class Backend {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (clipboardDetails !== null && clipboardDetails.text) {
|
if (clipboardDetails !== null && clipboardDetails.text) {
|
||||||
clipboardText = await this._clipboardReader.getText();
|
clipboardText = await this._clipboardReader.getText(false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errors.push(serializeError(e));
|
errors.push(serializeError(e));
|
||||||
|
@ -39,7 +39,7 @@ class ClipboardMonitor extends EventDispatcher {
|
|||||||
|
|
||||||
let text = null;
|
let text = null;
|
||||||
try {
|
try {
|
||||||
text = await this._clipboardReader.getText();
|
text = await this._clipboardReader.getText(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
|
@ -28,15 +28,15 @@ class ClipboardReader {
|
|||||||
* @param {object} details Details about how to set up the instance.
|
* @param {object} details Details about how to set up the instance.
|
||||||
* @param {?Document} details.document The Document object to be used, or null for no support.
|
* @param {?Document} details.document The Document object to be used, or null for no support.
|
||||||
* @param {?string} details.pasteTargetSelector The selector for the paste target element.
|
* @param {?string} details.pasteTargetSelector The selector for the paste target element.
|
||||||
* @param {?string} details.imagePasteTargetSelector The selector for the image paste target element.
|
* @param {?string} details.richContentPasteTargetSelector The selector for the rich content paste target element.
|
||||||
*/
|
*/
|
||||||
constructor({document=null, pasteTargetSelector=null, imagePasteTargetSelector=null}) {
|
constructor({document=null, pasteTargetSelector=null, richContentPasteTargetSelector=null}) {
|
||||||
this._document = document;
|
this._document = document;
|
||||||
this._browser = null;
|
this._browser = null;
|
||||||
this._pasteTarget = null;
|
this._pasteTarget = null;
|
||||||
this._pasteTargetSelector = pasteTargetSelector;
|
this._pasteTargetSelector = pasteTargetSelector;
|
||||||
this._imagePasteTarget = null;
|
this._richContentPasteTarget = null;
|
||||||
this._imagePasteTargetSelector = imagePasteTargetSelector;
|
this._richContentPasteTargetSelector = richContentPasteTargetSelector;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,13 +56,14 @@ class ClipboardReader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the text in the clipboard.
|
* Gets the text in the clipboard.
|
||||||
|
* @param {boolean} useRichText Whether or not to use rich text for pasting, when possible.
|
||||||
* @returns {string} A string containing the clipboard text.
|
* @returns {string} A string containing the clipboard text.
|
||||||
* @throws {Error} Error if not supported.
|
* @throws {Error} Error if not supported.
|
||||||
*/
|
*/
|
||||||
async getText() {
|
async getText(useRichText) {
|
||||||
/*
|
/*
|
||||||
Notes:
|
Notes:
|
||||||
document.execCommand('paste') doesn't work on Firefox.
|
document.execCommand('paste') sometimes doesn't work on Firefox.
|
||||||
See: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985
|
See: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985
|
||||||
Therefore, navigator.clipboard.readText() is used on Firefox.
|
Therefore, navigator.clipboard.readText() is used on Firefox.
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ class ClipboardReader {
|
|||||||
being an extension with clipboard permissions. It effectively asks for the
|
being an extension with clipboard permissions. It effectively asks for the
|
||||||
non-extension permission for clipboard access.
|
non-extension permission for clipboard access.
|
||||||
*/
|
*/
|
||||||
if (this._isFirefox()) {
|
if (this._isFirefox() && !useRichText) {
|
||||||
try {
|
try {
|
||||||
return await navigator.clipboard.readText();
|
return await navigator.clipboard.readText();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -86,21 +87,22 @@ class ClipboardReader {
|
|||||||
throw new Error('Clipboard reading not supported in this context');
|
throw new Error('Clipboard reading not supported in this context');
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = this._pasteTarget;
|
if (useRichText) {
|
||||||
if (target === null) {
|
const target = this._getRichContentPasteTarget();
|
||||||
target = document.querySelector(this._pasteTargetSelector);
|
target.focus();
|
||||||
if (target === null) {
|
document.execCommand('paste');
|
||||||
throw new Error('Clipboard paste target does not exist');
|
const result = target.textContent;
|
||||||
}
|
this._clearRichContent(target);
|
||||||
this._pasteTarget = target;
|
return result;
|
||||||
|
} else {
|
||||||
|
const target = this._getPasteTarget();
|
||||||
|
target.value = '';
|
||||||
|
target.focus();
|
||||||
|
document.execCommand('paste');
|
||||||
|
const result = target.value;
|
||||||
|
target.value = '';
|
||||||
|
return (typeof result === 'string' ? result : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
target.value = '';
|
|
||||||
target.focus();
|
|
||||||
document.execCommand('paste');
|
|
||||||
const result = target.value;
|
|
||||||
target.value = '';
|
|
||||||
return (typeof result === 'string' ? result : '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -143,23 +145,12 @@ class ClipboardReader {
|
|||||||
throw new Error('Clipboard reading not supported in this context');
|
throw new Error('Clipboard reading not supported in this context');
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = this._imagePasteTarget;
|
const target = this._getRichContentPasteTarget();
|
||||||
if (target === null) {
|
|
||||||
target = document.querySelector(this._imagePasteTargetSelector);
|
|
||||||
if (target === null) {
|
|
||||||
throw new Error('Clipboard paste target does not exist');
|
|
||||||
}
|
|
||||||
this._imagePasteTarget = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
target.focus();
|
target.focus();
|
||||||
document.execCommand('paste');
|
document.execCommand('paste');
|
||||||
const image = target.querySelector('img[src^="data:"]');
|
const image = target.querySelector('img[src^="data:"]');
|
||||||
const result = (image !== null ? image.getAttribute('src') : null);
|
const result = (image !== null ? image.getAttribute('src') : null);
|
||||||
for (const image2 of target.querySelectorAll('img')) {
|
this._clearRichContent(target);
|
||||||
image2.removeAttribute('src');
|
|
||||||
}
|
|
||||||
target.textContent = '';
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,4 +168,28 @@ class ClipboardReader {
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getPasteTarget() {
|
||||||
|
if (this._pasteTarget === null) { this._pasteTarget = this._findPasteTarget(this._pasteTargetSelector); }
|
||||||
|
return this._pasteTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getRichContentPasteTarget() {
|
||||||
|
if (this._richContentPasteTarget === null) { this._richContentPasteTarget = this._findPasteTarget(this._richContentPasteTargetSelector); }
|
||||||
|
return this._richContentPasteTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
_findPasteTarget(selector) {
|
||||||
|
const target = this._document.querySelector(selector);
|
||||||
|
if (target === null) { throw new Error('Clipboard paste target does not exist'); }
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearRichContent(element) {
|
||||||
|
for (const image of element.querySelectorAll('img')) {
|
||||||
|
image.removeAttribute('src');
|
||||||
|
image.removeAttribute('srcset');
|
||||||
|
}
|
||||||
|
element.textContent = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user