For my application I need a kind of internal clipboard with history.
I can't use the clipboard api (as far as I can see) as this would need permission from the user which is not an option.
I wan't to preserve formatting like bold, italics and strikethrough.
I was thinking about getting the content from window.getSelection(), but there is no way of easily cloning all the html that is in the selection.
The contents would need to go into another container element to be shown somewhere in the app.
Any ideas of how to achieve this are highly appreciated.
Best
Matthias
EDIT: I'm already interrupting the copy event and replace it with custom function. What I need to do is start at the anchorNode, cut some possible offset and go forward to the focusNode (also with offset). Also all unknown/unwanted tags (span, h1, div etc need to be removed but the text content shall stay). I hoped that someone has already done this or a similar task so I can save some time :/
The copy event could help you, as it doesn't require any permission to work.
var clipHistory = [];
document.addEventListener('copy', (event) => {
const selection = document.getSelection();
clipHistory.push(selection.toString());
event.preventDefault(); // optional, it prevents to modify the user's clipboard
});
// other functions can access clipHistory
A nice thing is that the copy event is very well supported.
Unlcukly this solution remove the formatting, but on way to keep it is to use a <div contenteditable id="parse-div"></div> and fire the parse event manually.
I found an intersting class: the Range one, but it doesn't allow to set a start and an end offset of non #text elements, as far I can undertand it.
Related
How does one insert, or better, replace the current selection with some content and then select it?
Here's my text: Hello nice world!
As you can see nice is selected by the user. Now he clicks a button and this code is run:
editor.execCommand('mceReplaceContent', 'nasty');
This works just fine, the result is: Hello nasty world, but nothing is selected.
How do I make it automatically select nasty in the result content?
This seems like a very natural thing for one to want to do, but can't seem to find a straight-forward solution. I need this to work in mostly two cases 1) I am wrapping the selected text in a f.e. span element or 2) I am removing the wrapping span element.
I know there are better ways of dealing with nodes, but I'm more concerned about the pure text scenario right now.
Thanks in advance!
P.S. I am using TinyMCE 3 not 4.
I found this in the docs (API 3.x)
// Sets some contents to the current selection in the editor
tinyMCE.activeEditor.selection.setContent('Some contents');
// Selects the first paragraph found
tinyMCE.activeEditor.selection.select(tinyMCE.activeEditor.dom.select('p')[0]);
The setContent function does practically the same as execCommand('mceReplaceContent'). I did not found something like the easy DOM properties selectionStart & selectionEnd.
I have successfully managed to set up an on paste event to capture the HTML pasted into the text area as it is pasted.
I need to automatically apply the removeFormat command to that HTML before or at the time it is pasted into the text area, so that I can strip it of classes, various tags, and other attributes. Could somebody point me in the right direction to apply the removeFormat command correctly?
Here's my code so far:
$(function(){
$('textarea').ckeditor(
function( textarea ){
var editor = this;
editor.on('paste', function( e ) {
//alert(e.data.html); // This shows the HTML
editor.execCommand( 'removeFormat', e.data.html ); // Doesn't seem to do anything, HTML is pasted with the attributes intact
});
}
)
});
Thanks!
P.S. Force plain text option is not viable as there are some HTML elements I wish to keep (p,table and others).
You can use
config.forcePasteAsPlainText = true;
cf http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.config.html
You need to select the content before you can apply removeFormat to it.
You could try grabbing the range ( even if it's just the cursor sitting at the insertion point ) and saving a bookmark before you paste.
After you paste, use the bookmark to select that range again.
That should select everything that you pasted between the start and end of the range.
Then you can use removeFormat:
editor.execCommand( 'removeFormat', editor.selection );
Here are the links to the range and selection API pages:
http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dom.range.html
http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dom.selection.html
I've found it easier to work with ranges, the createBookmark method is good because it sets markers and you can grab the correct start and end points even if the DOM changes ( as it will when you paste in the new content ). You can use moveToBookmark() after the paste to select the range.
http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dom.range.html#createBookmark
Because the documentation is sparse, I've found it helpful to search the source code for places where the methods are called. Looking at how they're used gives me a better idea of what kind of object I need to apply the methods to.
Be Well,
Joe
Starting from CKEditor 4.1 there is no need to do custom coding to define the list of elements that should be kept when pasting data into CKEditor, Advanced Content Filter should do the trick.
Either leave ACF enabled with the default configuration - CKEditor will accept all tags that can be created with it, or define your own set of rules with more or less strict set of allowed tags/attributes/styles. See documentation
Oddly specific question, but I have a solution already to paste plain text in a <span contentEditable="true"> by using a hidden textarea, which seems to work really well, except that it breaks the browser's undo feature. Right off the bat I'm not worried about a cross-browser solution; I only care about Chrome. My approach looks roughly like this:
$('.editable').live('paste', function()
{
var $this = $(this);
//more code here to remember caret position, etc
$('#clipboard').val('').focus(); //put the focus in the hidden textarea so that, when the paste actually occurs, it's auto-sanitized by the textarea
setTimeout(function() //then this will be executed immediately after the paste actually occurs
{
$this.focus();
document.execCommand('insertHTML', true, $('#clipboard').val());
});
});
So this works -- I can paste anything and it's reduced to plain text before going into the my contentEditable field -- but if I try to undo after pasting:
First undo undoes the paste.
Second undo tries to undo the changes to #clipboard, moving the focus away from my contentEditable.
I've tried everything I can think of to make the browser not try to undo the changes to #clipboard -- toggling display:none when it's not actively in use, toggling readonly and disabled state, destroying it at the end of and recreating it at the beginning of the event above, various other hacks -- but nothing seems to work.
Is this a terrible approach to sanitization? This is the first thing I've managed to really get working -- trying to clean up the markup after the paste occurs didn't work, as there are some things (entire HTML documents) which, when pasted, crash the browser, which I'd like to avoid.
Is there any way to make the #clipboard not undoable, or any other suggestions of how to get this working?
Edit
I managed to improve things a little bit by adding the line
$('#clipboard').val('');
Right after the execCommand line. This seems to neutralize undo completely: the caret no longer leaves the contentEditable field, but nothing gets undone at all. A bit of an improvement, but I'm still searching for a proper solution.
CodeMirror 1 does this by stripping away formatting after text is pasted. CodeMirror 2 does this by actually having an invisible textarea handle everything, and render the text and cursor manually.
CodeMirror's website describes how it works in more detail: http://codemirror.net/internals.html
Beyond that, there's always the CodeMirror source code. You can decide for yourself whether CodeMirror 1 or CodeMirror 2's approach is more suitable for your purposes. :)
Do you try that?
setTimeout(function() //then this will be executed immediately after the paste actually occurs
{
$this.focus();
document.execCommand('insertHTML', true, $('#clipboard').val());
var t = document.body.innerHTML;
document.execCommand("undo");
document.body.innerHTML = t;
});
I think it can help.
But I think you must use event object. Unfortunately there may be a problem cuz security reasons.
In onpaste:
Store the current selection.
var sel = window.getSelection();
var rangeĀ = selObj.getRangeAt(0).cloneRange;
// Store the range object somewhere.
Modify the selection object to point to your hidden textarea.
Set a timeout with a delay of 0 (occurs immediately after paste).
In the timeout function, grab the data from the hidden textarea, then:
var sel = window.getSelection();
sel.removeAllRanges();
var range = // restore the range object from before.
sel.addRange(range);
document.execCommand("insertHTML", false, /* contents of your textarea here */);
Now if you wanted to do this for actual HTML content, you'd be in a world of hurt....
Insert a <pre contenteditable="true">...</pre>. As I recall that's exactly what I understand you want. (Unfortunately I'm not yet allowed to join everyone in the comments, but I suppose this is an attempt to answer anyway.)
Ive got a method which will copy a section of html to the clipboard to allow pasting elsewhere.
It is usually a table of which will be the main content so it will be pasted into excel and keep its formatting, which is lovely.
What I want to do is remove certain elements from this section.
The main ones are checkboxes and textboxes - which cause excel to go really screwy, and for some reason you cannot delete them from excel - you just have to start a new sheet.
This is the method I am using to copy:
$('#CopyClipboard').click(function () {
var contentDiv = document.getElementById('copyablecontent');
var holdtext = document.getElementById('holdtext');
holdtext.innerText = contentDiv.innerHTML;
Copied = holdtext.createTextRange();
Copied.execCommand('Copy');
alert('Data copied to clipboard!');
});
(excuse the horrible mix of jquery and javascript).
So I have my 'contentDiv' variable, I want to parse that and remove all inputs, and possibly other elements too (I could give them all a css class 'doNotCopy' or something).
How can I do this?
you can use:
contentDiv.innerHTML.replace(/<input[^>]*>/g,"")
check the replace method here:
http://www.w3schools.com/jsref/jsref_replace.asp
you may have to adjust the regex for your needs
Since Flash 10 was introduced, many of the popular 'copy to clipboard' scripts out there have stopped working due to new security restrictions. There is a Flash-only solution here:
http://cfruss.blogspot.com/2009/01/copy-to-clipboard-swf-button-cross.html
...though I'm looking for the ability to trigger the copy function via JS, rather than relying on the user to click on a Flash object to trigger.
For an example of what we currently employ, see:
http://snipt.net/public
Any of the 'copy' links use jQuery's copy plugin here:
http://plugins.jquery.com/project/copy
UPDATE: OK, so I tried ZeroClipboard. At first glance, it looked great. However, the amount of redundant code needed to enable multiple clipboard bindings is unacceptable. In some cases, there will be 40+ instances of text that each have their own 'copy' link. Still looking for a better solution...
That's terrible news, I hadn't even noticed. I use the Flash trick extensively too. As far as I know that was the only way to get copy to work without having to install some other plugin (besides the ubiquitous Flash) due to browser security concerns.
Update: After much panic, and a few google searches, I stumbled on http://code.google.com/p/zeroclipboard/ which provides a Flash 10 compatible trick to get the copy to work once again. Now to go around updating websites...
This solution only works for keystrokes that would invoke the desired operation. It works by moving the user's cursor into a textarea element before the user finishes the relevant keystroke. It only works for text input. I've got this working in firefox and chrome. IE can use the clipboardData object (which is preferable to this hack).
In your html somewhere you should create a textarea element with arbitrarily large rows and cols attributes. The 'clipboard-textarea' element will be the holding area for the pasted and copied data. I hide the element using some style attributes.
The script:
var desiredClipboardContents = 'It works';
function onCopyKeyPressed() {
// The trick here is to populate the textarea with
// the text you want copied before the user releases
// the copy keystroke.
var textarea = document.getElementById('clipboard-textarea');
textarea.value = desiredClipboardContents;
textarea.focus();
textarea.select();
}
function onPasteKeyPressed() {
var textarea = document.getElementById('clipboard-textarea');
textarea.value = '';
textarea.focus();
// The trick here is to delay slurping the content
// that arrives in the textarea element until after
// the paste keystroke is completed. The 750 ms timeout
// provides the necessary delay.
setTimeout("finishedPasting", 750);
}
function finishedPasting() {
var textarea = document.getElementById('clipboard-textarea');
alert("Received from clipboard-paste: " + textarea.value);
}