Inside a Wordpress theme I am developing, i've a TinyMCEPopup to add shortcode to the editor, some shortcode requires images. Can i add an "Add media" button which opens the Wordpress media uploader and allow the user to select or upload an image even if i'm inside a TinyMCEPopup?
Don't know if it will help, but I had the same issue and solved it like this.
In functions.php add
add_action( 'after_setup_theme', 'mytheme_theme_setup' );
if ( ! function_exists( 'mytheme_theme_setup' ) ) {
function mytheme_theme_setup() {
add_action( 'init', 'mytheme_buttons' );
}
}
/********* TinyMCE Buttons ***********/
if ( ! function_exists( 'mytheme_buttons' ) ) {
function mytheme_buttons() {
if ( ! current_user_can( 'edit_posts' ) && ! current_user_can( 'edit_pages' ) ) {
return;
}
if ( get_user_option( 'rich_editing' ) !== 'true' ) {
return;
}
add_filter( 'mce_external_plugins', 'mytheme_add_buttons' );
add_filter( 'mce_buttons', 'mytheme_register_buttons' );
}
}
if ( ! function_exists( 'mytheme_add_buttons' ) ) {
function mytheme_add_buttons( $plugin_array ) {
$plugin_array['mybutton'] = get_template_directory_uri().'/js/tinymce_buttons.js';
return $plugin_array;
}
}
if ( ! function_exists( 'mytheme_register_buttons' ) ) {
function mytheme_register_buttons( $buttons ) {
array_push( $buttons, 'mybutton' );
return $buttons;
}
}
This will initialize the button you need. Now in the tinymce_buttons.js you'll add something like
(function() {
tinymce.PluginManager.add('mybutton', function( editor, url ) {
editor.addButton( 'mybutton', {
text: 'My button for media upload',
icon: false,
onclick: function() {
editor.windowManager.open( {
title: 'Insert Media',
body: [
{
type: 'textbox',
name: 'img',
label: 'Image',
value: '',
classes: 'my_input_image',
},
{
type: 'button',
name: 'my_upload_button',
label: '',
text: 'Upload image',
classes: 'my_upload_button',
},
],
onsubmit: function( e ) {
editor.insertContent( '[shortcode-name img="' + e.data.img + '"]');
}
});
},
});
});
})();
jQuery(document).ready(function($){
$(document).on('click', '.mce-my_upload_button', upload_image_tinymce);
function upload_image_tinymce(e) {
e.preventDefault();
var $input_field = $('.mce-my_input_image');
var custom_uploader = wp.media.frames.file_frame = wp.media({
title: 'Add Image',
button: {
text: 'Add Image'
},
multiple: false
});
custom_uploader.on('select', function() {
var attachment = custom_uploader.state().get('selection').first().toJSON();
$input_field.val(attachment.url);
});
custom_uploader.open();
}
});
First you add the button functionality. The list of options to put in popup is given here. It's not 100% complete, but better than the official documentation which is crap.
The first part is the button initialization. This gives you a 'My button for media upload' button, and on click you should get a modal with input field and a button.
On button click the media upload will open and you can select your image. On select the url will be in the input field, and you'll get it in your shortcode.
Hope it helps :)
There was another question like this (Open/Access WP Media library from tinymce plugin popup window) , so I'm pasting my answer here since this is similar:
Hi - I had the same problem just now and found the solution so I'm sharing it here. I hope it's not too late.
First to be able to use WP Add Media button you would have to enqueue the needed script. This is easy, just call the wp_enqueue_media() function like so:
add_action('admin_enqueue_scripts', 'enqueue_scripts_styles_admin');
function enqueue_scripts_styles_admin(){
wp_enqueue_media();
}
This call ensures you have the needed libraries to use the WP Media button.
Of course you should also have the HTML elements to hold the uploaded/selected media file URL, something like this:
<input type="text" class="selected_image" />
<input type="button" class="upload_image_button" value="Upload Image">
The first text field will hold the URL of the media file while the second is a button to open the media popup window itself.
Then in your jscript, you'd have something like this:
var custom_uploader;
$('.upload_image_button').click(function(e) {
e.preventDefault();
var $upload_button = $(this);
//Extend the wp.media object
custom_uploader = wp.media.frames.file_frame = wp.media({
title: 'Choose Image',
button: {
text: 'Choose Image'
},
multiple: false
});
//When a file is selected, grab the URL and set it as the text field's value
custom_uploader.on('select', function() {
var attachment = custom_uploader.state().get('selection').first().toJSON();
$upload_button.siblings('input[type="text"]').val(attachment.url);
});
//Open the uploader dialog
custom_uploader.open();
});
Now I'm not going to explain every line because it's not that hard to understand. The most important part is the one that uses the wp object to make all these to work.
The tricky part is making all these work on a TinyMCE popup(which is the problem I faced). I've searched hi and lo for the solution and here's what worked for me. But before that, I'll talk about what problem I encountered first. When I first tried to implement this, I encountered the "WP is undefined" problem on the popup itself. To solve this, you just have to pass the WP object to the script like so:
(function() {
tinymce.create('tinymce.plugins.someplugin', {
init : function(ed, url) {
// Register commands
ed.addCommand('mcebutton', function() {
ed.windowManager.open(
{
file : url + '/editor_button.php', // file that contains HTML for our modal window
width : 800 + parseInt(ed.getLang('button.delta_width', 0)), // size of our window
height : 600 + parseInt(ed.getLang('button.delta_height', 0)), // size of our window
inline : 1
},
{
plugin_url : url,
wp: wp
}
);
});
// Register buttons
ed.addButton('someplugin_button', {title : 'Insert Seomthing', cmd : 'mcebutton', image: url + '/images/some_button.gif' });
}
});
// Register plugin
// first parameter is the button ID and must match ID elsewhere
// second parameter must match the first parameter of the tinymce.create() function above
tinymce.PluginManager.add('someplugin_button', tinymce.plugins.someplugin);
})();
What we're interested in is this line => "wp: wp" . This line ensures that we are passing the wp object to the popup window (an iframe really...) that is to be opened when we click the tinymce button. You can actually pass anything to the popup window via this object (the 2nd parameter of the ed.windowManager.open method)!
Last but not the least you'd have to reference that passed wp object on your javascript like so:
var args = top.tinymce.activeEditor.windowManager.getParams();
var wp = args.wp;
Make sure you do that before calling/using the WP object.
That's all you have to do to make this work. It worked for me, I hope it works for you :)
Related
I created a custom link button and want it appear highlighted/selected when you select/click on the link in the text editor, just like clicking on bold text shows the Bold icon as selected. In TinyMCE 4 you could simply use "stateSelector" to have it highlight when that kind of DOM element was selected, like this:
editor.ui.registry.addButton('SpecialLink', {
icon: 'link',
onAction: makeSpecialLink(),
**stateSelector: 'a[href]'**
});
I can't find anything about what stateSelector was replaced with in TinyMCE 5 and so far all I've been able to do is recreate some of that functionality inside tinymce.init:
init_instance_callback: function(editor) {
editor.on("SelectionChange", function(e){
let elem = editor.selection.getNode();
if( $(elem).is("a") )
console.log("Highlight the Special Link button");
else
console.log("Deselect the Special Link button");
})
}
I can reference myMCE.plugins.SpecialLink, but I can't call setActive(true) on it.
Any help would be greatly appreciated!
You can use addToggleButton rather than addButton, then call setActive.
Here's a snippet of my code.
editor.ui.registry.addToggleButton('my-action', {
icon: null,
text: 'My action',
onAction: function onAction() {
// ...do stuff
},
onSetup: function(api) {
function nodeChangeHandler(){
const selectedNode = editor.selection.getNode();
return api.setActive(selectedNode.id === constants.id);
}
editor.on('NodeChange', nodeChangeHandler);
}
});
}
https://www.tiny.cloud/docs/ui-components/typesoftoolbarbuttons/#togglebutton
#Muki's answer is similar to what I did, but I referenced the core code for anchor button from tinymce's git repo here https://github.com/tinymce/tinymce/blob/develop/modules/tinymce/src/plugins/anchor/main/ts/ui/Buttons.ts
I changed editor.ui.registry.addButton to editor.ui.registry.addToggleButton and added onSetup: (buttonApi) => editor.selection.selectorChangedWithUnbind('a:not([href])', buttonApi.setActive).unbind instead of stateSelector after onAction.
Something like below:
editor.ui.registry.addToggleButton('SpecialLink', {
icon: 'link',
onAction: makeSpecialLink(),
onSetup: (buttonApi) => editor.selection.selectorChangedWithUnbind('a:not([href])', buttonApi.setActive).unbind
});
I'm playing around with CKEditor4 and I want to add a custom class to all images that I inserted. Unfortunately it seems as the dataFilter rule which works for adding classes on inserted <p> does not work on <img>-images. Image plugin I use is enhancedImage ("image2") and I do not upload the image, I pick it from a fileBrowser instance.
My code:
$(function(){
var editor = CKEDITOR.replace( 'article-ckeditor', {
customConfig: '/vendor/ckeditor/ckeditor.config.js',
filebrowserImageBrowseUrl: '/laravel-filemanager?type=Images',
filebrowserImageUploadUrl: '',
filebrowserBrowseUrl: '',
filebrowserUploadUrl: '',
on: {
instanceReady: function (ev) {
ev.editor.dataProcessor.dataFilter.addRules( {
elements : {
img: function( el ) {
el.attributes['class'] = 'img-fluid';
}
}
});
}
}
});
});
For me it seems as the event is just not fired. But I cannot find anything about it in the documentation...
Thanks for your help!
If you just want to add a custom class for inserted images, you could easily listen to dialogHide event:
editor.on( 'dialogHide', function( evt ) {
var widget = evt.data.widget;
if ( widget && widget.name === 'image' && widget.element ) {
widget.element.addClass( 'img-fluid' );
}
} );
I'm using CKEditor version 3.6
I want to automatically add class="newsleft" to any image tag added through the WYSIWYG.
I've seen a few posts that mention dataProcessor but have no idea which file this should be added or how to do it.
Can someone tell me where I would place the following code?
editor.dataProcessor.htmlFilter.addRules(
{
elements:
{
img: function( element )
{
if ( !element.attributes.alt )
element.attributes.alt = 'An image';
}
}
} );
Basically put it in instanceReady listener and it will be fine (3.x and 4.x) (fiddle):
CKEDITOR.replace( 'editor', {
plugins: 'wysiwygarea,toolbar,sourcearea,image,basicstyles',
on: {
instanceReady: function() {
this.dataProcessor.htmlFilter.addRules( {
elements: {
img: function( el ) {
// Add an attribute.
if ( !el.attributes.alt )
el.attributes.alt = 'An image';
// Add some class.
el.addClass( 'newsleft' );
}
}
} );
}
}
} );
CKEDITOR.htmlParser.element.addClass is available since CKEditor 4.4. You can use this.attributes[ 'class' ] prior to that version.
Here's another approach:
CKEDITOR.on( 'instanceReady', function( evt ) {
evt.editor.dataProcessor.htmlFilter.addRules( {
elements: {
img: function(el) {
el.addClass('img-responsive');
}
}
});
});
this worked for me in 3.6
add the following code to config.js
CKEDITOR.on('dialogDefinition', function (ev) {
// Take the dialog name and its definition from the event data.
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
// Check if the definition is image dialog window
if (dialogName == 'image') {
// Get a reference to the "Advanced" tab.
var advanced = dialogDefinition.getContents('advanced');
// Set the default value CSS class
var styles = advanced.get('txtGenClass');
styles['default'] = 'newsleft';
}
});
Because of this topic is being found in Google very high, I might help people by answering the question: how to add a default class when inserting an image in CKeditor. This answer is for CKeditor 4.5.1. as this is the latest version right now.
Look up image.js in /ckeditor/plugins/image/dialogs/image.js
Search for d.lang.common.cssClass
You will find: d.lang.common.cssClass,"default":""
Edit it with your class name(s) such as: d.lang.common.cssClass,"default":"img-responsive"
I've tried this and it works!
in Version: 4.5.3
Look up image.js in /ckeditor/plugins/image/dialogs/image.js
Search for editor.lang.common.cssClass
You will find: editor.lang.common.cssClass,"default":""
Edit it with your class name(s) such as: editor.lang.common.cssClass,"default":"your-class-name"
I'm having trouble adding classes to selected image in ckeditor. What I came up with is this http://pokit.org/get/img/8d89802e1d6f6371f5bc326898d8b414.jpg.
I added 2 buttons for selecting whether whether a picture is in portrait or landscape mode. You can select either of them or none, and add costum height/width.
Here is my code:
CKEDITOR.replace('maindesc', {
"extraPlugins": "imgbrowse",
"filebrowserImageBrowseUrl": "/ckeditor/plugins/imgbrowse",
on: {
instanceReady: function() {
this.dataProcessor.htmlFilter.addRules( {
elements: {
img: function( el ) {
// Add an attribute.
if ( !el.attributes.alt ) {
el.attributes.alt = 'Img';
el.addClass('ckeditorImg');
if (Landscape == 1) {
el.addClass('ckLandscape');
el.attributes['style'] = '';
}
else if (Portrait == 1) {
el.addClass('ckPortrait');
el.attributes['style'] = '';
}
}
}
}
} );
}
}
});
So as far as I understand this goes through all, so I wrote that if the image has no alt attribute to add one and add the classes I want. Unfortunately this approach doesn't allow me to change the class on selected image when a user wants to change it, but instead he has to delete the image, select it again and then choose class.
My question is whether there is a way to get to currently selected image instead of going through all <img> tags in ckeditor and change its class.
Here is an example for how to add a new button to ckeditor that is enabled/disables based on the element that you currently select and add a class to that specific element (in this example it's for images, however you can use it in any way you want).
// Set the callback function
var setLandscapeClass = {
exec: function(editor) {
editor.getSelection().getStartElement().addClass('ckLandscape')
}
}
//Create the plugin
CKEDITOR.plugins.add('setLandscapeClass', {
init: function(editor) {
editor.addCommand('setLandscapeClass', setLandscapeClass);
editor.ui.addButton("setLandscapeClass", {
label: 'Set Landscape Class',
icon: '',
command: 'setLandscapeClass'
});
}
});
// Create the instance and add the plugin
CKEDITOR.replace( 'editor1', {
extraPlugins: 'setLandscapeClass',
allowedContent: true
});
// enable/disable the button based on the selection of the text in the editor
CKEDITOR.instances.editor1.on( 'selectionChange', function( evt ) {
var landscapeButton = this.getCommand( 'setLandscapeClass' );
if ( evt.data.path.lastElement.is( 'img' ) ) {
landscapeButton.enable();
} else {
landscapeButton.disable();
}
});
You can see a working demo here:
https://jsfiddle.net/7nm9q1qv/
I only created 1 button, and there is no icon there. I think you can use that code to create also the second button (for portrait class).
Update - add item to the context menu
In order to add a new item to the context-menu you should add this code:
// Add the context-menu
if (editor.addMenuItem) {
editor.addMenuGroup('testgroup');
editor.addMenuItem('setLandscapeItem', {
label: 'Set landscape class',
command: 'setLandscapeClass',
group: 'testgroup'
});
}
// On contextmenu - set the item as "visible" by the menu
if (editor.contextMenu) {
editor.contextMenu.addListener(function(element, selection) {
if (element.hasClass('ckLandscape') === false) {
return { setLandscapeItem: CKEDITOR.TRISTATE_ON };
}
});
}
Inside the init function of the plugin you add.
You can see that I added this line:
if (element.hasClass('ckLandscape') === false) {
(Which you can remove) only to give you an example of how to show the item in the menu only if the ckLandscape class doesn't exists for this image.
The updated version of the jsfiddle is here:
https://jsfiddle.net/7nm9q1qv/1/
In my WordPress' projects, I'm using the following code again and again for many of my fields where I'm using a button to initiate the WordPress media uploader and on selection of the file I'm sending its path/url to a text field.
var project_field_image_uploader;
$('#button-input').click( function(e) {
e.preventDefault();
//if the uploader object has already been created, reopen the dialog
if( project_field_image_uploader ) {
project_field_image_uploader.open();
return;
}
//extend the wp.media object
project_field_image_uploader = wp.media.frames.file_frame = wp.media( {
title:"Choose an image",
button:{
text: "Insert"
},
multiple: false
} );
//when a file is selected, grab the URL and set it as the text field's value
project_field_image_uploader.on( 'select', function() {
attachment = project_field_image_uploader.state().get('selection').first().toJSON();
$('#text-field').val(attachment.url);
});
//Open the uploader dialog
project_field_image_uploader.open();
});
For each of the field I need to edit the following things:
First variable - project_field_image_uploader (not necessarily it should be meaningful, it is only for creating different instances, so in a reusable way, it can be anything, but not conflicting)
Button's ID - $('#button-input')
Text field's ID - $('#text-field')
Media Library Modal's head - title:"Choose an image",
Media Library's Media Insertion button's text - text: "Insert"
Is there a way I can make this code reusable, so that I can be with DRY ideology? A jQuery function may do the job for me, but I cannot sort things out, how can I sort this thing.
<script>
$(function(){
$('#button-input').click(function(e){
var text_field = $('#text-field');
....................
var mytext = 'my text';
myfunc(e,project_field_image_uploader,text_field,mytitle,mytext);
});
//reuse with any other button click with different parameters
});
function myfunc(e,project_field_image_uploader,text_field,mytitle,mytext){
e.preventDefault();
//if the uploader object has already been created, reopen the dialog
if( project_field_image_uploader ) {
project_field_image_uploader.open();
return;
}
//extend the wp.media object
project_field_image_uploader = wp.media.frames.file_frame = wp.media( {
title:mytitle,
button:{
text: mytext
},
multiple: false
} );
//when a file is selected, grab the URL and set it as the text field's value
project_field_image_uploader.on( 'select', function() {
attachment = project_field_image_uploader.state().get('selection').first().toJSON();
text_field.val(attachment.url);
});
//Open the uploader dialog
project_field_image_uploader.open();
}
</script>
Thanks to #alamnaryab for his answer that directed me to the right way (+1 for that). But passing a variable as a function parameter was problematic. It produces an error:
project_field_image_uploader is not defined
I figured out things that, a variable need not to pass as a function parameter to be unique, because a variable inside a function is a local variable. So I simply called the variable inside the function and reused the function multiple times. I'm here posting the working example code.
And declaring multiple variables, I used comma with a single var declaration. There's no need to repeat things. Thanks again to Mr. Alam Naryab.
<script>
$(function(){
$('#button-input').click(function(e){
var text_field = $('#text-field'),
media_lib_head = 'Choose an image',
btn_text = 'Insert';
//using the function where necessary
project_reusable_repeating_func( e, text_field, media_lib_head, btn_text );
});
});
/**
* Reusable function
* #author alamnaryab
* #link http://stackoverflow.com/a/32035149/1743124
*/
function project_reusable_repeating_func( e, text_field, media_lib_head, btn_text ){
//a variable that need not to be unique, because it's local
var project_field_image_uploader;
e.preventDefault();
//if the uploader object has already been created, reopen the dialog
if( project_field_image_uploader ) {
project_field_image_uploader.open();
return;
}
//extend the wp.media object
project_field_image_uploader = wp.media.frames.file_frame = wp.media( {
title: media_lib_head,
button:{
text: btn_text
},
multiple: false
} );
//when a file is selected, grab the URL and set it as the text field's value
project_field_image_uploader.on( 'select', function() {
attachment = project_field_image_uploader.state().get('selection').first().toJSON();
text_field.val(attachment.url);
});
//Open the uploader dialog
project_field_image_uploader.open();
}
</script>