Reputation: 6753
To reproduce:
here:
)window.onload = function() {
const info = document.querySelector('.info'),
pinfo = document.querySelector('.paste-info'),
target = document.querySelector('.target');
setInterval(() => {
const sel = ".source *, .target *"
info.innerHTML = '';
for (const elm of [...document.querySelectorAll(sel)]) {
info.innerHTML += "TAG: " + elm.tagName + "; TEXT: " + elm.innerText + "; FONTSIZE: " + window.getComputedStyle(elm)['font-size'] + "<br>";
}
}, 1000);
target.addEventListener('paste', function(e) {
pinfo.innerHTML += "PASTE HTML: <pre>" + e.clipboardData.getData('text/html').replaceAll('<', '<').replaceAll('>', '>') + '</pre><br>';
});
};
div[contenteditable] {
border: 1px solid black;
}
<div class="source" contenteditable=true>Source text: <b>foobar</b></div>
<div style="font-size: 14px">
<div contenteditable=true class="target">Destination, <h1>paste here:</h1></div>
</div>
<div class="info"></div>
<div class="paste-info"></div>
You will notice that:
<b>foobar</b>
(see content after PASTE HTML:
), but...style="font-size: 14px;"
set on the b
element (The 14px size comes from the parent of the contenteditable).I expect the pasted HTML to not have any font sizes set on it, because they were not specified in the source clipboard data.
Question: How to force Chrome to not put any font sizes on the pasted HTML, when there is no font-size present on the source HTML?
I tried one workaround: to set font-size: unset/revert
on the source, but it causes font-size: unset
to also be present in the pasted HTML. I prefer to not have any font-size to be present in the pasted HTML.
The context of this code is a Chrome extension, and I control the text/html data that is pasted into the target. I can attach a paste event listeners on the target contenteditable, but I cannot alter the HTML/styles of contents after it has been pasted.
Upvotes: 4
Views: 633
Reputation: 136707
You can force the "normal" pasting of the HTML markup by using the Selection API.
The steps are
Range
object representing the current selection.Range
object to parse the pasted HTML markup into a DocumentFragment
object thanks to the createContextualFragment()
method.Range#deleteContents()
).DocumentFragment
object we created at step 2, where the cursor is.Range
object so that the cursor goes to the end of the newly pasted content.Doing all these steps manually will prevent the browser's "smart" handling of the rich-text content; only what's in the clipboard will be parsed.
window.onload = function() {
const info = document.querySelector('.info'),
pinfo = document.querySelector('.paste-info'),
target = document.querySelector('.target');
setInterval(() => {
const sel = ".source *, .target *"
info.innerHTML = '';
for (const elm of [...document.querySelectorAll(sel)]) {
info.innerHTML += "TAG: " + elm.tagName + "; TEXT: " + elm.innerText + "; FONTSIZE: " + window.getComputedStyle(elm)['font-size'] + "<br>";
}
}, 1000);
target.addEventListener('paste', function(e) {
pinfo.innerHTML += "PASTE HTML: <pre>" + e.clipboardData.getData('text/html').replaceAll('<', '<').replaceAll('>', '>') + '</pre><br>';
e.preventDefault();
const markup = e.clipboardData.getData("text/html") ||
e.clipboardData.getData("text/plain");
const sel = getSelection();
const range = sel.getRangeAt(0);
const frag = range.createContextualFragment(markup);
range.deleteContents();
range.insertNode(frag);
range.collapse();
});
};
div[contenteditable] {
border: 1px solid black;
}
<div class="source" contenteditable=true>Source text: <b>foobar</b></div>
<div style="font-size: 14px">
<div contenteditable=true class="target">Destination, <h1>paste here:</h1></div>
</div>
<div class="info"></div>
<div class="paste-info"></div>
One big drawback to this method though: This will not make an entry in the edit history. This means that after your users did paste any content there, they'll be unable to undo that action.
Upvotes: 2
Reputation: 5281
(here as anwer because more space than comment, will remove when issue resolved/closed)
Given the system wide behavior of iOS and Windows when copy/pasting text, Chrome seems to provide the generally expected result.
Expected when selecting some text/element and [Ctrl-C] copy:
This is exactly what Chrome does, making Firefox the odd one out.
The above obviously doesn't not resolve your issue, but following the above you should be preventing FF from stripping the markup. This will add to your issue, because what did FF do with the original markup??
UPDATE
I think I figured it out: 'foobar' has no font settings, therefore it inherit
it's size from nearest parent => 14px
. This info is inside the copied 'package' (clipboardData
). Firefox strips it by default on [Ctrl-V], where Chrome does not.
As h1
stretches to 100%
(filling destination) in FF 'foobar' is 2em
while in Chrome it is 14px
on [Ctrl-V]. [Ctrl-V] basically should not strip, where [Ctrl-Shift-V] should. Hense my comment that Chrome does it correctly as the font-size
after [Ctrl-Shift-V] is indeed 2em
because of the h1
stretch.
While redundant in FF, you could strip all info from the clipboardData
inside the paste event, or rely on the [Ctrl-V]/[Ctrl-Shift-V] mechanism.
UPDATE 2
There is more going on. I created a codepen for testing.
Pasting in succession right after 'paste here:' and 'Destination, ' with both [Ctrl-V] and [Ctrl-Shift-V] randomly shows that info gets stripped when [Ctrl-Shift-V] is used first a few times. After using [Ctrl-V] and then [Ctrl-Shift-V] again, the stripping take no longer place (the text stays bold in either case).
Leads me to think that next to the different [Ctrl-V]/[Ctrl-Shift-V] handling of both browsers, the internal text editor has a few flaws I cannot get my head around...
Upvotes: 0
Reputation: 5335
Maybe its possible to intercept the pasting event, and alter the contents of the paste to force it to paste as plain text using js.
I've added an id of id="editor"
on the contenteditable
div. and added the following js code:
const editorEle = document.getElementById('editor');
// Handle the paste event
editorEle.addEventListener('paste', function (e) {
// Prevent the default action
e.preventDefault();
// Get the copied text from the clipboard
const text = e.clipboardData
? (e.originalEvent || e).clipboardData.getData('text/plain')
: // For IE
window.clipboardData
? window.clipboardData.getData('Text')
: '';
if (document.queryCommandSupported('insertText')) {
document.execCommand('insertText', false, text);
} else {
// Insert text at the current position of caret
const range = document.getSelection().getRangeAt(0);
range.deleteContents();
const textNode = document.createTextNode(text);
range.insertNode(textNode);
range.selectNodeContents(textNode);
range.collapse(false);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
});
here's a snippet of it running. let me know if this solved your issue:
const editorEle = document.getElementById('editor');
// Handle the `paste` event
editorEle.addEventListener('paste', function (e) {
// Prevent the default action
e.preventDefault();
// Get the copied text from the clipboard
const text = e.clipboardData
? (e.originalEvent || e).clipboardData.getData('text/plain')
: // For IE
window.clipboardData
? window.clipboardData.getData('Text')
: '';
if (document.queryCommandSupported('insertText')) {
document.execCommand('insertText', false, text);
} else {
// Insert text at the current position of caret
const range = document.getSelection().getRangeAt(0);
range.deleteContents();
const textNode = document.createTextNode(text);
range.insertNode(textNode);
range.selectNodeContents(textNode);
range.collapse(false);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
});
window.onload = function() {
const info = document.querySelector('.info'),
pinfo = document.querySelector('.paste-info'),
target = document.querySelector('.target');
setInterval(() => {
const sel = ".source *, .target *"
info.innerHTML = '';
for (const elm of [...document.querySelectorAll(sel)]) {
info.innerHTML += "TAG: " + elm.tagName + "; TEXT: " + elm.innerText + "; FONTSIZE: " + window.getComputedStyle(elm)['font-size'] + "<br>";
}
}, 1000);
target.addEventListener('paste', function(e) {
pinfo.innerHTML += "PASTE HTML: <pre>" + e.clipboardData.getData('text/html').replaceAll('<', '<').replaceAll('>', '>') + '</pre><br>';
});
};
div[contenteditable] {
border: 1px solid black;
}
<div class="source" contenteditable=true>Source text: <b>foobar</b></div>
<div style="font-size: 14px">
<div contenteditable=true id="editor" class="target">Destination, <h1>paste here:</h1></div>
</div>
<div class="info"></div>
<div class="paste-info"></div>
Upvotes: 0