Recently I encountered a seemingly easy problem that I can’t solve:
I have some really long pages in xwiki that I want restructure. My question is how do you move parts, say a paragraph or chapter with pictures to another/new page?
Just cut and paste will break the image links to attached images.
OK I tough your use case was actually the opposite: have some content in a different page with its attachments and include that document.
Indeed if you copy an image from the WYSIWYG, paste it in the WYSIWYG in another page you should definitely not end up with a broken image in the end. I also think this is a bug to fix (either by changing the reference of the image or by copying the attachment to).
So everything we need is already there in data-cke-saved-src and src but there is also data-reference. Is my assumption correct that “on save” or “on preview” (or similiar action) the src-attribute of the image gets overwritten by the values of data-reference? Where does this happen (which code file)? Is there any way to test if there is an attachment on the page that data-reference refers to else “do something” like update reference or copy/move attachment?
The src attribute doesn’t get overwritten. You can check by inspecting the HTTP request made when you Save & Continue (using the Network tab from the browser’s developer tools). Both the src attribute and the reference are sent to the server, where indeed, the XWiki Rendering gives precedence to the reference. The reason is because the HTML you edit with the CKEditor is saved as wiki syntax and the Rendering needs to be able to “recompute” the wiki syntax (or preserve it in case you edit the page without touching the image).
I don’t think this is the right approach, because it’s perfectly valid to “insert” an image that doesn’t exist. Maybe you’re going to attach the image later, maybe it was removed by a different user at the same time you were editing, etc.
A better approach might be to “filter” the HTML content that is copied (before it is copied) so that the data-reference attribute is removed, which would make the Rendering rely on the src attribute when you paste and save.
My ultimate goal would be to show a dialog containing all the pasted images allowing you to chose which action should be done image by image.
The actions would be something like:
Update reference
Copy image
Do nothing
I’m not sure if this a good idea, but I’d really love to see more support for content restructuring.
I will try to implement this but I guess it will be a looong way to go for me, because I’m really only scratching the surface with xwiki development.
For now I will try to filter the pasted html as you suggested. Many thanks for your help.
Unfortunately there is no “copy” or “beforeCopy” event in CK editor. I tried my luck with the paste event but whatever changes I try to apply to the evt.data.dataValue the xwiki plugin is still able to retrieve the original value of data-reference and therefore “breaking” the reference to the image.
Another option, that is not bullet proof though, is to write an event listener on the server side that:
catches the document save event
looks for Image blocks in the document content XDOM
** if the Image block point to an attachment that doesn’t exist and there is an attachment with the same name on the document being saved then update the attachment reference on the Image block
I will consider that, if all else fails.
Currently I still don’t want to give up on the client side solution.
From my understanding of the documentation of the paste event it is meant to be used for processing the data. So if I throw away attributes in the listener they should no longer be available OR considered. Correct?
What I observe is that even if I process data in this event xwiki seems to be able to work on the original pasted data or at least retrieve the data.
If I add breakpoints to my paste handler and the toDataFormat function I see that my paste handler gets called first. The call stack at first glance does not show any signs of xwiki code being called. Still in this line var a = CKEDITOR.tools.escapeComment(b.attributes["data-reference"])the attribute data-reference has a value although I remove it in the paste handler.
This holds true regardless if the priority of the pastehandler is 2, 10 (default AFAIK) or 100.
From my understanding there are two possibilities:
I’m doing something badly wrong (please find my current code below).
The paste event handler does not work as expected in xwiki and it’s usage should be discouraged. I could not find anything in the documentation about this, so it may need to be updated.
I guess I found the problem: It’s just not enough to delete the data-reference attribute, you also have to delete the data-cke-widget-data. I’m not that happy with the manipulation of the src attribute but currently it is the best I can think of. @mflorea: What do you think is this reasonable approach or did I forget any edge cases?
What’s the value of data-cke-widget-data? I don’t recall. In any case, it looks like this attribute is set by CKEditor not by XWiki. You need to see if its value is required in some other place.
Actually I’d argue that this data should be cleaned if it is from an external source which we can determine via evt.data.dataTransfer.getTransferType(), see here. Let’s assume you have an application where you copy data from that also uses ckeditor in a different configuration, then you should not “trust” and reuse the data from them.
I updated the script once more to include the test for the DataTransferType and to update links also:
require(['deferred!ckeditor'], function(ckeditorPromise) {
ckeditorPromise.done(function(ckeditor) {
ckeditor.on('instanceCreated', function(event) {
event.editor.on('paste', function(evt){
//Update references only if they are pasted from an external source
console.log("DataTransferType: " + evt.data.dataTransfer.getTransferType())
if(evt.data.dataTransfer.getTransferType()!==CKEDITOR.DATA_TRANSFER_EXTERNAL){
return;
}
var fragment = CKEDITOR.htmlParser.fragment.fromHtml(evt.data.dataValue);
count=0;
fragment.forEach(function(node){
if(node.name!=null && (node.name=="img" || node.name=="a")){
if(node.attributes["data-reference"] || node.attributes["data-cke-widget-data"]){
delete node.attributes["data-reference"];
delete node.attributes["data-cke-widget-data"];
src=node.attributes["src"]
node.attributes["src"]=window.location.origin + src
count++;
}
}
}, CKEDITOR.NODE_ELEMENT, true);
if(count>0){
if(confirm("Update image references?")){
var writer = new CKEDITOR.htmlParser.basicWriter();
fragment.writeHtml( writer );
var writtenHTML=writer.getHtml();
console.log(writtenHTML);
evt.data.dataValue = writtenHTML;
}
}
});
});
});
});
This works quite well for my workflow. @mflorea, @vmassol: Do you think that this may be a (part of a) solution to fix Loading...? Are you interested in having a working workflow of pasting editor to editor?
If so, I will investigate this further and try to make this solution more generic and include other things like the figure macro. But I may need some help from you here.
If not I will leave it as it is (for now).
I made another small update, fixing the call of getTransferType:
require(['deferred!ckeditor'], function(ckeditorPromise) {
ckeditorPromise.done(function(ckeditor) {
ckeditor.on('instanceCreated', function(event) {
event.editor.on('paste', function(evt){
//Update references only if they are pasted from an external source
console.log("DataTransferType: " + evt.data.dataTransfer.getTransferType(event.editor))
if(evt.data.dataTransfer.getTransferType(event.editor)!==CKEDITOR.DATA_TRANSFER_EXTERNAL){
return;
}
var fragment = CKEDITOR.htmlParser.fragment.fromHtml(evt.data.dataValue);
count=0;
fragment.forEach(function(node){
if(node.name!=null && (node.name=="img" || node.name=="a")){
if(node.attributes["data-reference"] || node.attributes["data-cke-widget-data"]){
delete node.attributes["data-reference"];
delete node.attributes["data-cke-widget-data"];
src=node.attributes["src"]
node.attributes["src"]=window.location.origin + src
count++;
}
}
}, CKEDITOR.NODE_ELEMENT, true);
if(count>0){
if(confirm("Update image references?")){
var writer = new CKEDITOR.htmlParser.basicWriter();
fragment.writeHtml( writer );
var writtenHTML=writer.getHtml();
console.log(writtenHTML);
evt.data.dataValue = writtenHTML;
}
}
});
});
});
});