GetmeUK / ContentTools

A JS library for building WYSIWYG editors for HTML content.
http://getcontenttools.com
MIT License
3.95k stars 393 forks source link

Duplicate a table (or other elements) #535

Closed cyclaero closed 4 years ago

cyclaero commented 5 years ago

I did not find any facility for simply duplicating a table, paragraphs, images, etc. While duplication of simple elements can be overcome with copying/pasting, duplication of a table becomes really ugly because it is not possible to select and copy the whole table at once.

I saw, that it is possible to drag&drop a whole table to another position, though. I guess, technically this is achieved, by copying the table element to the new position and deleting it at the old one. So what happens, if I could somehow prevent the deleting step? Won't I end up with the desired duplicate, would I?

On a Mac we can use option dragging for duplication of any kind of content in any kind of applications. So for many of us dragging&dropping with a modifier key is a common procedure.

I am working with the latest content-tool.js file in the build directory. Please, may I ask you for pointing me to a section in this code where I can check whether the command key is down before deleting dragged content at the original place?

anthonyjb commented 5 years ago

Hey @cyclaero,

That's a really good idea, I'd never thought of it before but it makes a lot of sense to allow a modifier key to be depressed during a drag to duplicate :+1:

As a short term work around you can C&P HTML btw, so for example it should be possible to save your changes, select the HTML you want to duplicate, edit the page again and then paste the HTML in. This should work for tables, lists, paragraphs and any mix (though not images :/)

As to where to look to implement a solution, the drag/drop code is in the ContentEdit library not the ContentTools one: https://github.com/GetmeUK/ContentEdit/blob/master/src/scripts/bases.coffee#L957

There are a couple of class methods used to drop elements in different places, _dropVert and _dropBoth, if you wanted to modify this behaviour you'd need to change these functions to effectively not remove the element being dragged (e.g get rid of the detach(...) line) and then you need to duplicate the element ahead of dropping it to place. There's no specific copy method for nodes but you could achieve this by creating a div element (don't add it to the DOM) setting it's innerHTML value using the element's html method and then using the elements fromDOMElement class method to generate the copy from the div.children[0] - a bit long winded but it should work reliably.

Unfortunately the way ContentEdit and ContentTools are linked is to be be frank rubbish, I wrote the library when my knowledge of npm and packing tools wasn't great and so basically you either need to modify ContentEdit and then create a new version of ContentTools with that version of ContentEdit as part of the project or monkey patch the class methods I mentioned in your version of ContentTools, e.g do ContentEdit.Element._dropVert = function () { ... }. This is a fault I'll be correcting the next build around as well as moving to es6 but for now I can only apologise.

cyclaero commented 5 years ago

Thank you very much Anthony for your quick and helpful reply.

I have to apologise, that I use and to a certain extend modify your great tools for my needs outside of the build environment of your project. Unfortunately CoffeeScript/Bower/NPM does not tell anything to me. For the time being I use sed in my make files for injecting some minor changes into the readily built content-tools.min.js which cannot be overwritten by my code.

Anyway, in the file content-tools.js in the build directory of the ContentTools master, I found the two respective detach() lines, and I added in front of them if (!window.event.altKey) and this part works already. When I hold down the option (alt) key, detach(...) is not called, and vice versa. Now I have to figure out the part of duplicating the element by the way of an intermediary div element.

cyclaero commented 5 years ago

I got it working now.

However, this ContentEdit.TagNames.get().match(element._domElement.tagName) puzzles me. This was the only way (among dozens which I tested) of obtaining a working fromDOMElement()method. There might be a better way.

if (!window.event.altKey)
   element.parent().detach(element);
else
{
   var csscls, clone = element._domElement.cloneNode(true);
   if (csscls = clone.className.replace(/ *ce-element($| |--[^ ]*)/g, ""))
      clone.className = csscls;
   else
      clone.removeAttribute('class');
   element = ContentEdit.TagNames.get().match(element._domElement.tagName).fromDOMElement(clone);
}

Instead of getting the clone of the DOM element with cloneNode(true), I tested also your suggestion using an intermediate div, and both methods gave the same result. I stayed with cloneNode() because it seemed more canonical to me. In both cases, I needed to remove the ce-element class names, and I did it the JS way.

Since I am not the most proficient JS programmer and am neither that familiar with all the very facilities of the ContentTools and ContentEdit libraries, I would be really glad to receive suggestion and advices, on how to achieve the same result in a more perfect manner.

anthonyjb commented 5 years ago

Hey

So calling html should not return an element with the ce-element class name in, the html method should return the HTML as if it were to be saved into the page (and therefore should be content edit special classes free). It's basically serialising the content. My approach would have been approx.

const wrapper = document.createElement('div')
wrapper.innerHTML = element.html()
const clone = element.constructor.fromDOMElement(wrapper.children[0])

I'm not replying from a machine with a ContentTools demo set up on but I think the above should work to clone the element - please shout if you can't get it working though and I'll resolve.

cyclaero commented 5 years ago

Hello!

Yes, this works very well, and it is much cleaner, than my code. I did try everything but putting constructor. before the class method.

I won't mind to see this in the ContentEditor/-Tools master. Do you require a pull request for this? Or would it be possible for you to work-in the following into the respective CoffeeScript?

if (!window.event.altKey)
  element.parent().detach(element);
else
{
  const clipdiv = document.createElement('div');
  clipdiv.innerHTML = element.html();
  element = element.constructor.fromDOMElement(clipdiv.children[0]);
}

Thank you again for the great tools and your great support.

anthonyjb commented 4 years ago

@cyclaero this change will be in the release I push this weekend.

cyclaero commented 4 years ago

Thank you very much. I updated my sites already to the latest ContentTools, and everything works very well.