/*!
* froala_editor v2.3.3 (https://www.froala.com/wysiwyg-editor) * License https://froala.com/wysiwyg-editor/terms/ * Copyright 2014-2016 Froala Labs */
(function (factory) {
if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); } else if (typeof module === 'object' && module.exports) { // Node/CommonJS module.exports = function( root, jQuery ) { if ( jQuery === undefined ) { // require('jQuery') returns a factory that requires window to // build a jQuery instance, we normalize how we use modules // that require this pattern but the window provided is a noop // if it's defined (how jquery works) if ( typeof window !== 'undefined' ) { jQuery = require('jquery'); } else { jQuery = require('jquery')(root); } } factory(jQuery); return jQuery; }; } else { // Browser globals factory(jQuery); }
}(function ($) {
'use strict'; // Extend defaults. $.extend($.FE.DEFAULTS, { imageManagerLoadURL: 'https://i.froala.com/load-files', imageManagerLoadMethod: 'get', imageManagerLoadParams: {}, imageManagerPreloader: '', imageManagerDeleteURL: '', imageManagerDeleteMethod: 'post', imageManagerDeleteParams: {}, imageManagerPageSize: 12, imageManagerScrollOffset: 20, imageManagerToggleTags: true }); $.FE.PLUGINS.imageManager = function (editor) { var $modal; var $preloader; var $media_files; var $scroller; var $image_tags; var $modal_title; var $overlay; var images; var page; var image_count; var loaded_images; var column_number; // Load errors. var BAD_LINK = 10; var ERROR_DURING_LOAD = 11; var MISSING_LOAD_URL_OPTION = 12; var LOAD_BAD_RESPONSE = 13; var MISSING_IMG_THUMB = 14; var MISSING_IMG_URL = 15; // Delete errors var ERROR_DURING_DELETE = 21; var MISSING_DELETE_URL_OPTION = 22; // Error Messages var error_messages = {}; error_messages[BAD_LINK] = 'Image cannot be loaded from the passed link.'; error_messages[ERROR_DURING_LOAD] = 'Error during load images request.'; error_messages[MISSING_LOAD_URL_OPTION] = 'Missing imageManagerLoadURL option.'; error_messages[LOAD_BAD_RESPONSE] = 'Parsing load response failed.'; error_messages[MISSING_IMG_THUMB] = 'Missing image thumb.'; error_messages[MISSING_IMG_URL] = 'Missing image URL.'; error_messages[ERROR_DURING_DELETE] = 'Error during delete image request.'; error_messages[MISSING_DELETE_URL_OPTION] = 'Missing imageManagerDeleteURL option.'; var $current_image; /* * Show the media manager. */ function show () { // Build the media manager. if (!$modal) _build(); $modal.data('instance', editor); // Show modal. $modal.show(); $overlay.show(); $current_image = editor.image.get(); if (!$preloader) { _delayedInit(); } // Load images. _loadImages(); // Prevent scrolling in page. editor.$doc.find('body').addClass('prevent-scroll'); // Mobile device if (editor.helpers.isMobile()) { editor.$doc.find('body').addClass('fr-mobile'); } } /* * Hide the media manager. */ function hide () { var inst = $modal.data('instance') || editor; inst.events.enableBlur(); $modal.hide(); $overlay.hide(); inst.$doc.find('body').removeClass('prevent-scroll fr-mobile'); } /* * Get the number of columns based on window width. */ function _columnNumber () { var window_width = $(window).outerWidth(); // Screen XS. if (window_width < 768) { return 2; } // Screen SM and MD. else if (window_width < 1200) { return 3; } // Screen LG. else { return 4; } } /* * Add the correct number of columns. */ function _buildColumns () { $media_files.empty(); for (var i = 0; i < column_number; i++) { $media_files.append('<div class="fr-list-column"></div>'); } } /* * The media manager modal HTML. */ function _modalHTML () { var cls = ''; if (editor.opts.theme) { cls = ' ' + editor.opts.theme + '-theme'; } // Modal wrapper. var html = '<div class="fr-modal' + cls + '"><div class="fr-modal-wrapper">'; // Modal title. html += '<div class="fr-modal-title"><div class="fr-modal-title-line"><i class="fa fa-bars fr-modal-more fr-not-available" id="fr-modal-more-' + editor.sid + '" title="' + editor.language.translate('Tags') + '"></i><h4 data-text="true">' + editor.language.translate('Manage Images') + '</h4><i title="' + editor.language.translate('Cancel') + '" class="fa fa-times fr-modal-close" id="fr-modal-close"></i></div>'; // Tags html += '<div class="fr-modal-tags" id="fr-modal-tags"></div>'; html += '</div>'; // Preloader. html += '<img class="fr-preloader" id="fr-preloader" alt="' + editor.language.translate('Loading') + '.." src="' + editor.opts.imageManagerPreloader + '" style="display: none;">'; // Modal scroller. html += '<div class="fr-scroller" id="fr-scroller"><div class="fr-image-list" id="fr-image-list"></div></div>'; html += '</div></div>'; return $(html); } /* * Build the image manager. */ function _build () { // Build modal. if (!editor.shared.$modal) { editor.shared.$modal = _modalHTML(); $modal = editor.shared.$modal; // Desktop or mobile device. if (!editor.helpers.isMobile()) { $modal.addClass('fr-desktop'); } // Append modal to body. $modal.appendTo('body'); editor.shared.$overlay = $('<div class="fr-overlay">').appendTo('body'); $overlay = editor.shared.$overlay; if (editor.opts.theme) { $overlay.addClass(editor.opts.theme + '-theme'); } // Finished building the media manager. hide(); } else { $modal = editor.shared.$modal; $overlay = editor.shared.$overlay; } // Editor destroy. editor.events.on('shared.destroy', function () { $modal.removeData().remove(); $overlay.removeData().remove(); }, true); } /* * Load images from server. */ function _loadImages () { $preloader.show(); $media_files.find('.fr-list-column').empty(); // If the images load URL is set. if (editor.opts.imageManagerLoadURL) { // Make GET request to get the images. $.ajax({ url: editor.opts.imageManagerLoadURL, method: editor.opts.imageManagerLoadMethod, data: editor.opts.imageManagerLoadParams, dataType: 'json', crossDomain: editor.opts.requestWithCORS, xhrFields: { withCredentials: editor.opts.requestWithCORS }, headers: editor.opts.requestHeaders }) // On success start processing the response. .done(function (data, status, xhr) { editor.events.trigger('imageManager.imagesLoaded', [data]); _processLoadedImages(data, xhr.response); $preloader.hide(); }) // On fail throw error during request. .fail(function () { var xhr = this.xhr(); _throwError(ERROR_DURING_LOAD, xhr.response || xhr.responseText); }); } // Throw missing imageManagerLoadURL option error. else { _throwError(MISSING_LOAD_URL_OPTION); } } /* * Process loaded images. */ function _processLoadedImages (imgs, response) { try { $media_files.find('.fr-list-column').empty(); page = 0; image_count = 0; loaded_images = 0; images = imgs; // Load files. _infiniteScroll(); } // Throw error while parsing the response. catch (ex) { _throwError(LOAD_BAD_RESPONSE, response); } } /* * Load more images if necessary. */ function _infiniteScroll () { // If there aren't enough images in the modal or if the user scrolls down. if (image_count < images.length && ($media_files.outerHeight() <= $scroller.outerHeight() + editor.opts.imageManagerScrollOffset || $scroller.scrollTop() + editor.opts.imageManagerScrollOffset > $media_files.outerHeight() - $scroller.outerHeight())) { // Increase page number. page++; // Load each image on this page. for (var i = editor.opts.imageManagerPageSize * (page - 1); i < Math.min(images.length, editor.opts.imageManagerPageSize * page); i++) { _loadImage(images[i]); } } } /* * Load file. */ function _loadImage (image) { var img = new Image(); var $img_container = $('<div class="fr-image-container fr-empty fr-image-' + (loaded_images++) + '" data-loading="' + editor.language.translate('Loading') + '.." data-deleting="' + editor.language.translate('Deleting') + '..">'); // After adding image empty container modal might change its height. _resizeModal(false); // Image has been loaded. img.onload = function () { // Update image container height. $img_container.height(Math.floor($img_container.width() / img.width * img.height)); // Create image HTML. var $img = $('<img/>'); // Use image thumb in image manager. if (image.thumb) { // Set image src attribute/ $img.attr('src', image.thumb); } // Image does not have thumb. else { // Throw missing image thumb error. _throwError(MISSING_IMG_THUMB, image); // Set image URL as src attribute. if (image.url) { $img.attr('src', image.url); } // Missing image URL. else { // Throw missing image url error. _throwError(MISSING_IMG_URL, image); // Don't go further if image does not have a src attribute. return false; } } // Save image URL. if (image.url) $img.attr('data-url', image.url); // Image tags. if (image.tag) { // Show tags only if there are any. $modal_title.find('.fr-modal-more.fr-not-available').removeClass('fr-not-available'); $modal_title.find('.fr-modal-tags').show(); // Image has more than one tag. if (image.tag.indexOf(',') >= 0) { // Add tags to the image manager tag list. var tags = image.tag.split(','); for (var i = 0; i < tags.length; i++) { // Remove trailing spaces. tags[i] = tags[i].trim(); // Add tag. if ($image_tags.find('a[title="' + tags[i] + '"]').length === 0) { $image_tags.append('<a role="button" title="' + tags[i] + '">' + tags[i] + '</a>'); } } // Set img tag attribute. $img.attr('data-tag', tags.join()); } // Image has only one tag. else { // Add tag to the tag list. if ($image_tags.find('a[title="' + image.tag.trim() + '"]').length === 0) { $image_tags.append('<a role="button" title="' + image.tag.trim() + '">' + image.tag.trim() + '</a>'); } // Set img tag attribute. $img.attr('data-tag', image.tag.trim()); } } // Set image additional data. for (var key in image) { if (image.hasOwnProperty(key)) { if (key != 'thumb' && key != 'url' && key != 'tag') { $img.attr('data-' + key, image[key]); } } } // Add image and insert and delete buttons to the image container. $img_container .append($img) .append($(editor.icon.create('imageManagerDelete')).addClass('fr-delete-img').attr('title', editor.language.translate('Delete'))) .append($(editor.icon.create('imageManagerInsert')).addClass('fr-insert-img').attr('title', editor.language.translate('Insert'))) // Show image only if it has selected tags. $image_tags.find('.fr-selected-tag').each (function (index, tag) { if (!_imageHasTag($img, tag.text)) { $img_container.hide(); } }); // After an image is loaded the modal may need to be resized. $img.on('load', function () { // Image container is no longer empty. $img_container.removeClass('fr-empty'); $img_container.height('auto'); // Increase image counter. image_count++; // A loded image may break the images order. Reorder them starting with this image. var imgs = _getImages(parseInt($img.parent().attr('class').match(/fr-image-(\d+)/)[1], 10) + 1); // Reorder images. _reorderImages(imgs); // Image modal may need resizing. _resizeModal(false); // If this was the last image on page then we might need to load more. if (image_count % editor.opts.imageManagerPageSize === 0) { _infiniteScroll(); } }); // Trigger imageLoaded event. editor.events.trigger('imageManager.imageLoaded', [$img]); }; // Error while loading the image. img.onerror = function () { image_count++; $img_container.remove(); // Removing an image container may break image order. var imgs = _getImages(parseInt($img_container.attr('class').match(/fr-image-(\d+)/)[1], 10) + 1); // Reorder images. _reorderImages(imgs); _throwError(BAD_LINK, image); // If this was the last image on page then we might need to load more. if (image_count % editor.opts.imageManagerPageSize === 0) { _infiniteScroll(); } }; // Set the image object's src. img.src = image.url; // Add loaded or empty image to the media manager image list on the shortest column. _shortestColumn().append($img_container); } /* * Get the shortest image column. */ function _shortestColumn () { var $col; var min_height; $media_files.find('.fr-list-column').each (function (index, col) { var $column = $(col); // Assume that the first column is the shortest. if (index === 0) { min_height = $column.outerHeight(); $col = $column; } // Check if another column is shorter. else { if ($column.outerHeight() < min_height) { min_height = $column.outerHeight(); $col = $column; } } }); return $col; } /* * Get all images from the image manager. */ function _getImages (from) { if (from === undefined) from = 0; var get_images = []; for (var i = loaded_images - 1; i >= from; i--) { var $image = $media_files.find('.fr-image-' + i); if ($image.length) { get_images.push($image); // Add images here before deleting them so the on load callback is triggered. $('<div id="fr-image-hidden-container">').append($image); $media_files.find('.fr-image-' + i).remove(); } } return get_images; } /* * Add images back into the image manager. */ function _reorderImages (imgs) { for (var i = imgs.length - 1; i >= 0; i--) { _shortestColumn().append(imgs[i]); } } /* * Resize the media manager modal and scroller if height changes. */ function _resizeModal (infinite_scroll) { if (infinite_scroll === undefined) infinite_scroll = true; if (!$modal.is(':visible')) return true; // If width changes, the number of columns may change. var cols = _columnNumber(); if (cols != column_number) { column_number = cols; // Get all images. var imgs = _getImages(); // Remove current columns and add new ones. _buildColumns(); // Reorder images. _reorderImages(imgs); } var height = editor.$win.height(); // The wrapper and scroller objects. var $wrapper = $modal.find('.fr-modal-wrapper'); // Wrapper's top and bottom margins. var wrapper_margins = parseFloat($wrapper.css('margin-top')) + parseFloat($wrapper.css('margin-bottom')); var wrapper_padding = parseFloat($wrapper.css('padding-top')) + parseFloat($wrapper.css('padding-bottom')); var wrapper_border_top = parseFloat($wrapper.css('border-top-width')); var h4_height = $wrapper.find('h4').outerHeight(); // Change height. $scroller.height(Math.min($media_files.outerHeight(), height - wrapper_margins - wrapper_padding - h4_height - wrapper_border_top)); // Load more photos when window is resized if necessary. if (infinite_scroll) { _infiniteScroll(); } } function _getImageAttrs ($img) { var img_attributes = {}; var img_data = $img.data(); for (var key in img_data) { if (img_data.hasOwnProperty(key)) { if (key != 'url' && key != 'tag') { img_attributes[key] = img_data[key]; } } } return img_attributes; } /* * Insert image into the editor. */ function _insertImage (e) { // Image to insert. var $img = $(e.currentTarget).siblings('img'); var inst = $modal.data('instance') || editor; hide(); inst.image.showProgressBar(); if (!$current_image) { // Make sure we have focus. inst.events.focus(true); inst.selection.restore(); var rect = inst.position.getBoundingRect(); var left = rect.left + rect.width / 2; var top = rect.top + rect.height; // Show the image insert popup. inst.popups.setContainer('image.insert', inst.$box || $('body')); inst.popups.show('image.insert', left, top); } else { $current_image.trigger('click'); } inst.image.insert($img.data('url'), false, _getImageAttrs($img), $current_image); } /* * Delete image. */ function _deleteImage (e) { // Image to delete. var $img = $(e.currentTarget).siblings('img'); // Confirmation message. var message = editor.language.translate('Are you sure? Image will be deleted.'); // Ask for confirmation. if (confirm(message)) { // If the images delete URL is set. if (editor.opts.imageManagerDeleteURL) { // Before delete image event. if (editor.events.trigger('imageManager.beforeDeleteImage', [$img]) !== false) { $img.parent().addClass('fr-image-deleting'); // Make request to delete image from server. $.ajax({ method: editor.opts.imageManagerDeleteMethod, url: editor.opts.imageManagerDeleteURL, data: $.extend($.extend({ src: $img.attr('src') }, _getImageAttrs($img)), editor.opts.imageManagerDeleteParams), crossDomain: editor.opts.requestWithCORS, xhrFields: { withCredentials: editor.opts.requestWithCORS }, headers: editor.opts.requestHeaders }) // On success remove the image from the image manager. .done(function (data) { editor.events.trigger('imageManager.imageDeleted', [data]); // A deleted image may break the images order. Reorder them starting with this image. var imgs = _getImages(parseInt($img.parent().attr('class').match(/fr-image-(\d+)/)[1], 10) + 1); // Remove the image. $img.parent().remove(); // Reorder images. _reorderImages(imgs); // Modal needs resizing. _resizeModal(true); }) // On fail throw error during request. .fail(function () { var xhr = this.xhr(); _throwError(ERROR_DURING_DELETE, xhr.response || xhr.responseText); }); } } // Throw missing imageManagerDeleteURL option error. else { _throwError(MISSING_DELETE_URL_OPTION); } } } /* * Throw image manager errors. */ function _throwError (code, response) { // Load images error. if (10 <= code && code < 20) { // Hide preloader. $preloader.hide(); } // Delete image error. else if (20 <= code && code < 30) { // Remove deleting overlay. $('.fr-image-deleting').removeClass('fr-image-deleting'); } // Trigger error event. editor.events.trigger('imageManager.error', [{ code: code, message: error_messages[code] }, response]); } /* * Toogle (show or hide) image tags. */ function _toggleTags () { var title_height = $modal_title.find('.fr-modal-title-line').outerHeight(); var tags_height = $image_tags.outerHeight(); // Use .fr-show-tags. $modal_title.toggleClass('.fr-show-tags'); if ($modal_title.hasClass('.fr-show-tags')) { // Show tags by changing height to have transition. $modal_title.css('height', title_height + tags_height); $image_tags.find('a').css('opacity', 1); } else { // Hide tags by changing height to have transition. $modal_title.css('height', title_height); $image_tags.find('a').css('opacity', 0); } } /* * Show only images with selected tags. */ function _showImagesByTags() { // Get all selected tags. var $tags = $image_tags.find('.fr-selected-tag'); // Show only images with selected tags. if ($tags.length > 0) { // Hide all images. $media_files.find('img').parent().show(); // Show only images with tag. $tags.each (function (index, tag) { $media_files.find('img').each (function (index, img) { var $img = $(img); if (!_imageHasTag($img, tag.text)) { $img.parent().hide(); } }); }); } // There are no more tags selected. Show all images. else { $media_files.find('img').parent().show(); } // Rearrange images. var imgs = _getImages(); // Reorder images. _reorderImages(imgs); // Load more images if necessary. _infiniteScroll(); } /* * Select an image tag from the list. */ function _selectTag (e) { e.preventDefault(); // Toggle current tags class. var $tag = $(e.currentTarget); $tag.toggleClass('fr-selected-tag'); // Toggle selected tags. if (editor.opts.imageManagerToggleTags) $tag.siblings('a').removeClass('fr-selected-tag'); // Change displayed images. _showImagesByTags(); } /* * Method to check if an image has a specific tag. */ function _imageHasTag ($image, tag) { var tags = $image.attr('data-tag').split(','); for (var i = 0; i < tags.length; i++) { if (tags[i] == tag) { return true; } } return false; } function _delayedInit() { $preloader = $modal.find('#fr-preloader'); $media_files = $modal.find('#fr-image-list'); $scroller = $modal.find('#fr-scroller'); $image_tags = $modal.find('#fr-modal-tags'); $modal_title = $image_tags.parent(); // Columns. column_number = _columnNumber(); _buildColumns(); // Set height for title (we need this for show tags transition). var title_height = $modal_title.find('.fr-modal-title-line').outerHeight(); $modal_title.css('height', title_height); $scroller.css('margin-top', title_height); // Close button. editor.events.bindClick($modal, 'i#fr-modal-close', hide); // Resize media manager modal on window resize. editor.events.$on($(editor.o_win), 'resize', function () { // Window resize with image manager opened. if (images) { _resizeModal(true); } // iOS window resize is triggered when modal first opens (no images loaded). else { _resizeModal(false); } }); // Delete and insert buttons for mobile. if (editor.helpers.isMobile()) { // Show image buttons on mobile. editor.events.bindClick($media_files, 'div.fr-image-container', function (e) { $modal.find('.fr-mobile-selected').removeClass('fr-mobile-selected'); $(e.currentTarget).addClass('fr-mobile-selected'); }); // Hide image buttons if we click outside it. $modal.on(editor._mousedown, function () { $modal.find('.fr-mobile-selected').removeClass('fr-mobile-selected'); }); } // Insert image. editor.events.bindClick($media_files, '.fr-insert-img', _insertImage); // Delete image. editor.events.bindClick($media_files, '.fr-delete-img', _deleteImage); // Make sure we don't trigger blur. $modal.on(editor._mousedown + ' ' + editor._mouseup, function (e) { e.stopPropagation(); }); // Mouse down on anything. $modal.on(editor._mousedown, '*', function () { editor.events.disableBlur(); }); // Infinite scroll $scroller.on('scroll', _infiniteScroll); // Click on image tags button. editor.events.bindClick($modal, 'i#fr-modal-more-' + editor.sid, _toggleTags); // Select an image tag. editor.events.bindClick($image_tags, 'a', _selectTag); } /* * Init media manager. */ function _init () { if (!editor.$wp && editor.$el.get(0).tagName != 'IMG') return false; } return { require: ['image'], _init: _init, show: show, hide: hide } }; if (!$.FE.PLUGINS.image) { throw new Error('Image manager plugin requires image plugin.'); } $.FE.DEFAULTS.imageInsertButtons.push('imageManager'); $.FE.RegisterCommand('imageManager', { title: 'Browse', undo: false, focus: false, callback: function () { this.imageManager.show(); }, plugin: 'imageManager' }) // Add the font size icon. $.FE.DefineIcon('imageManager', { NAME: 'folder' }); // Add the font size icon. $.FE.DefineIcon('imageManagerInsert', { NAME: 'plus' }); // Add the font size icon. $.FE.DefineIcon('imageManagerDelete', { NAME: 'trash' });
}));