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:
toasted-nutbread 2022-10-15 22:43:12 -04:00 committed by GitHub
parent a370b46fae
commit 9ef7f9d383
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 39 deletions

View File

@ -11,6 +11,7 @@
<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/icon128.png" sizes="128x128">
<link rel="stylesheet" type="text/css" href="/css/background.css">
</head>
<body>
@ -61,7 +62,7 @@
https://bugzilla.mozilla.org/show_bug.cgi?id=1603985
-->
<!-- [html-validate-disable close-order] -->
<div id="clipboard-image-paste-target" contenteditable="true">
<div id="clipboard-rich-content-paste-target" contenteditable="true">
</body>
</html>

29
ext/css/background.css Normal file
View 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 */

View File

@ -53,7 +53,7 @@ class Backend {
// eslint-disable-next-line no-undef
document: (typeof document === 'object' && document !== null ? document : null),
pasteTargetSelector: '#clipboard-paste-target',
imagePasteTargetSelector: '#clipboard-image-paste-target'
richContentPasteTargetSelector: '#clipboard-rich-content-paste-target'
});
this._clipboardMonitor = new ClipboardMonitor({
japaneseUtil: this._japaneseUtil,
@ -596,7 +596,7 @@ class Backend {
}
async _onApiClipboardGet() {
return this._clipboardReader.getText();
return this._clipboardReader.getText(false);
}
async _onApiGetDisplayTemplatesHtml() {
@ -1773,7 +1773,7 @@ class Backend {
try {
if (clipboardDetails !== null && clipboardDetails.text) {
clipboardText = await this._clipboardReader.getText();
clipboardText = await this._clipboardReader.getText(false);
}
} catch (e) {
errors.push(serializeError(e));

View File

@ -39,7 +39,7 @@ class ClipboardMonitor extends EventDispatcher {
let text = null;
try {
text = await this._clipboardReader.getText();
text = await this._clipboardReader.getText(false);
} catch (e) {
// NOP
}

View File

@ -28,15 +28,15 @@ class ClipboardReader {
* @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 {?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._browser = null;
this._pasteTarget = null;
this._pasteTargetSelector = pasteTargetSelector;
this._imagePasteTarget = null;
this._imagePasteTargetSelector = imagePasteTargetSelector;
this._richContentPasteTarget = null;
this._richContentPasteTargetSelector = richContentPasteTargetSelector;
}
/**
@ -56,13 +56,14 @@ class ClipboardReader {
/**
* 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.
* @throws {Error} Error if not supported.
*/
async getText() {
async getText(useRichText) {
/*
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
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
non-extension permission for clipboard access.
*/
if (this._isFirefox()) {
if (this._isFirefox() && !useRichText) {
try {
return await navigator.clipboard.readText();
} catch (e) {
@ -86,21 +87,22 @@ class ClipboardReader {
throw new Error('Clipboard reading not supported in this context');
}
let target = this._pasteTarget;
if (target === null) {
target = document.querySelector(this._pasteTargetSelector);
if (target === null) {
throw new Error('Clipboard paste target does not exist');
}
this._pasteTarget = target;
if (useRichText) {
const target = this._getRichContentPasteTarget();
target.focus();
document.execCommand('paste');
const result = target.textContent;
this._clearRichContent(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');
}
let target = this._imagePasteTarget;
if (target === null) {
target = document.querySelector(this._imagePasteTargetSelector);
if (target === null) {
throw new Error('Clipboard paste target does not exist');
}
this._imagePasteTarget = target;
}
const target = this._getRichContentPasteTarget();
target.focus();
document.execCommand('paste');
const image = target.querySelector('img[src^="data:"]');
const result = (image !== null ? image.getAttribute('src') : null);
for (const image2 of target.querySelectorAll('img')) {
image2.removeAttribute('src');
}
target.textContent = '';
this._clearRichContent(target);
return result;
}
@ -177,4 +168,28 @@ class ClipboardReader {
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 = '';
}
}