Jump to content
Can

Better CKEditor image insertion (at least for me)

Recommended Posts

Hola amigos :)
 
I wrote a little DOMDocument magic (long time ago) which would automatically
link images
add classes for Magnific Popup
set alt tag
wrap the whole thing in a figure
insert figcaption if image description available
and wrap multiple figures in a container
 
think that's it.. :D So for our articles we only needed to open pwimage popup in cke, choose desired image, set the size and alignment
To caption images we use description input from image field
 
It was a nice starting point, and at some point I changed DOMDocument to SmartDOMDocument wrapped the code into TextformatterBlogImages.module..
 
But I had ideas to simplify the whole process..and due to a problem with the old implementation which broke design (just realised it a week ago, or that it's linked to my Textformatter) I finally took the time and rewrote the whole thing. Problem was that figure is not a valid child of <p> so at least in chrome I ended up with orphaned text and an empty paragraph so the text style differed from the rest..
 
I'm posting this as a boilerplate/resource/idea, it's really basic..everything is hardcoded and it's not flexible right now..
So there is the Textformatter module and a hook which is placed in my /site/ready.php right now but might be moved somewhere else..
Maybe it can become a proper module..Maybe I find time to do so..but if someone is interested everyone is welcome to grab the code and extend/modify for their own needs  ;)
 
Here's a little animated gif screencast (it's shrinked so the quality is not very beautiful..)
 
AutoImage.gif
 
So what it does right now:
TextformatterBlogImages.module:
first it replaces double line breaks like <br><br> or <br /><br /> or any other combination with </p><p> (I had such occurences)
parses the article for <p> tags using simple_html_dom
finds all containing images
it extracts the image name stripping image variations
gets the corresponding pageimage from images field
the image itself will be replaced by markup used with LazySizes JS plugin (https://github.com/aFarkas/lazysizes)
Example:
<img src="fullImageUrl" srcset="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" data-srcset="smallUrl smallWidth, medium mediumWidth, full fullWidth" data-sizes="auto" width="fullImageWidth">
data-sizes="auto" works for now but I might replace it by normal sizes markup, manually deciding how images should be chosen
width="fullWidth" I read that images with width attribute render faster but I'm not sure if this is true even if the image is downsized by css?
then the new image will be linked to full size image link with markup for js modal (wrote my own, but not ready yet)
 
if image class contains "align_left" or "align_right" it will wrap it in span moving classes from img to span
if pageimage got a description it'll insert the descr below the img within span in another span
the old image will just be replaced by the new span image so it will be in the same place
 
it image is not aligned (or align_center) it wraps the image in figure moving img classes to figure and description will be wrapped in figcaption
and the figure will be stuffed in an own container before the paragraph (original image within p will be removed)
The figure gets a class landscape or portrait according to the aspect ratio
 
Uh, I added another hook property called Pageimage::cdn so the images will be served directly from assets subdomain (using domain sharding option from AIOM but saving the extra redirect)
 
Then there is another "round" reparsing the article for newly created figure containers adding additional class containing the number of children and another class indicating how many of the images are landscape/portrait for later styling like 2lx1p for 2 landscape and 1 portrait
 
We add rows of 1 to 4 images, not more so my stylesheet contains rules for all possible cases like 1 landscape 2 portraits, 3 landscapes 1 portrait and so on to resize the images so alls images in a row will have the same height and the row will be always as wide as the main container as seen in the gif above
 
Because the textformatter is doing everything for us I added a little clickable area to each image in the image field and a little javascript which inserts an image into body cke field on click at caret position
Only thing to do is manually break the line after 4 images (or less)
(NOTE: This javascript code inserting the clickable area requires at least PW 3.0.17 with new image field, as mentioned by Robin S below)
 
For inline images I have to modify, I guess, the javascript a little, or maybe I can add another script which adds 3 links to inserted images, which add alignment classes to the image

Here you'll find the files (and code snippes)

https://gist.github.com/CanRau/662a559a07d6d7c492159d1cd497944f

Any questions, ideas, improvements, concerns and other comments are highly appreciated  :)

PS: I'm not using it in production yet, just finished the version, I think I need to review all articles first to ensure proper migration, and maybe I add the inline handling to the javascript as mentioned before

PPS:  If you're interested in using this you should at least cache the textformatter output using WireCache, or TemplateCache or you can of course use ProCache, but I wouldn't recommend using it without any caching mechanism as it would re-do all the work every time someone requests the page..

Un Saludo

Can

Edited by Can
  • Like 7

Share this post


Link to post
Share on other sites

AutoImage-ContextMenu.gif

Fixed (at least half of the "problem") the inline image issue by extending CKEditor's contextmenu.

So I added 3 menu items "alignLeft", "alignRight", "removeAlignment"

First step, and it's working good.

As a second step I wanted to add some preset sizes to the contextmenu like "small", "medium", "large" which would set the img size attribute

But it's not yet working, no errors but no changes..

var editor = CKEDITOR.instances.Inputfield_body;
editor.on( 'instanceReady', function(e) {
    if ( editor.contextMenu ) {
        editor.addCommand('alignLeft', { exec: function (editor) {
            var element = editor.getSelection().getStartElement();
            element.removeClass('align_right');
            element.addClass('align_left');
        } });
        editor.addCommand('alignRight', { exec: function (editor) {
            var element = editor.getSelection().getStartElement();
            element.removeClass('align_left');
            element.addClass('align_right');
        } });
        editor.addCommand('removeAlignment', { exec: function (editor) {
            var element = editor.getSelection().getStartElement();
            element.removeClass('align_left');
            element.removeClass('align_right');
        } });
        editor.addCommand('small', { exec: function (editor) {
            var element = editor.getSelection().getStartElement().$;
            console.log(element);
            element.attributes.width = 100;
        } });

        editor.addMenuGroup( 'HappyImages' );
        editor.addMenuItems({
           alignLeft : {
              label : 'Rechts umfließen',
              command : 'alignLeft',
              group : 'HappyImages',
              order : 1
            },
           alignRight : {
              label : 'Links umfließen',
              command : 'alignRight',
              group : 'HappyImages',
              order : 2
            },
           removeAlignment : {
              label : 'Nicht umfließen',
              command : 'removeAlignment',
              group : 'HappyImages',
              order : 3
            },
           small : {
              label : 'klein',
              command : 'small',
              group : 'HappyImages',
              order : 4
            }
        });
    }
    editor.contextMenu.addListener( function( element ) {
        if ( element.getAscendant( 'img', true ) ) {
            return { alignLeft: CKEDITOR.TRISTATE_OFF, alignRight: CKEDITOR.TRISTATE_OFF, removeAlignment: CKEDITOR.TRISTATE_OFF, small: CKEDITOR.TRISTATE_OFF };
        }
    });
});

Maybe someone knows why the small command is not working?

Other ideas, improvements and any feedback is always welcome ;)

Edited by Can
  • Like 2

Share this post


Link to post
Share on other sites

That JS for inserting images into the CKEditor field is really cool!  :) 

For setting the width with the "small" command, try:

element.setAttribute('width', 100);

For anyone trying out Can's code, note that the JS that adds the HappyImageSelector is for PW3.x image field markup. Not hard to adapt to PW2.x though.

  • Like 2

Share this post


Link to post
Share on other sites

Awesome, thank you Robin! It just works! :D

And good note about PW3, I'll add it above and in the gist, too!

JS in updated gist contains now two presets "small" (200), "medium" (350) and menu item to remove with entirely

I think I'll add a default width to inserted images in the next update..

AutoImage-ContextMenu-resizing.gif

Edited by Can
  • Like 1

Share this post


Link to post
Share on other sites

Hello Can,

I have tried to install your module in the latest PW version, but unfortunately I always see the code of the module in my backend.

Screenshot_2.jpg

Maybe I have done something wrong:

I have added the content of the ready.php file to my ready.php file. I have uploaded the Blogimage.module without the ready.php and the CSS file to the module directory.

Have you tested it with the latest PW version.

EDIT: OK I have found out that the opening php tag was missing.

Share this post


Link to post
Share on other sites

Oh, I'm sorry about the missing opening tags, added them to the gist

The ready.php part was more thought to be added to an probably existing file rather then being a file, that's why I wrote "it's placed in my ready.php" and it's more like a boilerplate and not a proper module, so the post was mostly to see if people like it ;-)

I'm currently using it with PW 3.0.39 and everything works fine

what errors are you getting? maybe we can figure out the cause..

Hopefully I'll find some time to stuff all of it in a module or similar and make a proper repository and readme soon..
The cke additions are already in a custom cke plugin because the above code wouldn't work with TextareaLanguage, the plugin takes care of applying the contextmenu extensions to all cke instances of every language and handles insertion properly..

and actually I need to ensure it's working with single language cke, because right now the js selector are based on the language ids/classes

 

okay I've just initiated a local repo, but I need to test things further and play around with a single language pw install to have it support both because selectors seem to differ and I still need to figure how to make it as easy to install as possible which dictates the structure and readme of the repo ;-)

  • Like 1

Share this post


Link to post
Share on other sites

Hello Can,

I am using TextareaLanguage field as my body field. Therefore I got fe an error message at "$value->getLanguageValue($user->language)" - this could not be used with a string. Anyway, I have written my own image markup manipulation module using simplehtmldom. I am using UIKit as my framework and I have to include the UIKIT CSS classes and markup for images inserted with CKEditor. I have used your module as starting point for mine.

Best regards

  • Like 1

Share this post


Link to post
Share on other sites

hmm not sure about the error right now but I'll look into it. I'm fine tuning the cke plugin at the moment and planning to make as much customizable as possible so class names will be changeable, too.

Great that you could use the module as a starting point :-)

  • Like 1

Share this post


Link to post
Share on other sites

Hi Can,

i'm new to processwire... but i think what you've done is really interesting... will make things a lot easier for me... maybe i dont get it... but i need some installation instructions/steps to apply this.... running the latest processwire 3.0.39.

  • Like 1

Share this post


Link to post
Share on other sites

Hi @Rajiv I'm glad you like it :)

I'm refactoring the code to have it more customizable and pluggable, so installation should be easier, too.
Don't know when I can post the new version, though. Hopefully within the next days, as I need it anyway for a new project, too ;-) 

  • Like 2

Share this post


Link to post
Share on other sites

Hello @Can

this line of code in your module code leads to an error message

$value->getLanguageValue($user->language)

Call to a member function getLanguageValue() on string

Best regards

Share this post


Link to post
Share on other sites

haven't tested it but you should be able to replace this

$value->getLanguageValue($user->language)

with this

$value

 

Share this post


Link to post
Share on other sites
On 12/3/2016 at 12:08 PM, Can said:

I'm refactoring the code to have it more customizable and pluggable, so installation should be easier, too.
Don't know when I can post the new version, though. Hopefully within the next days, as I need it anyway for a new project, too 😉

Hello Can,

I know the whole thing is a little older... but did you get any further back then? Have you been able to develop the whole thing into a module?
Many thanks and many greetings to Peru

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...