├── .gitattributes ├── summernote-image-list.min.css ├── summernote-image-list.css ├── LICENSE ├── README.md ├── summernote-image-list.min.js └── summernote-image-list.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /summernote-image-list.min.css: -------------------------------------------------------------------------------- 1 | .image-list-item{display:inline-block;position:relative;width:100%;height:120px;margin-bottom:50px;border:2px solid #ddd;border-radius:5px}.image-list-item:hover{cursor:pointer;border:2px solid #337ab7}.image-list-item img{position:absolute;top:50%;left:50%;width:auto;height:auto;max-width:90%;max-height:90%;transform:translate(-50%,-50%)}.image-list-item p{margin-top:120px;text-align:center;font-size:90%}.image-list-item:hover p{color:#337ab7} -------------------------------------------------------------------------------- /summernote-image-list.css: -------------------------------------------------------------------------------- 1 | .image-list-item { 2 | display: inline-block; 3 | position: relative; 4 | width: 100%; 5 | height: 120px; 6 | margin-bottom: 50px; 7 | border: 2px solid #ddd; 8 | border-radius: 5px; 9 | } 10 | 11 | .image-list-item:hover { 12 | cursor: pointer; 13 | border: 2px solid #337ab7; 14 | } 15 | 16 | .image-list-item img { 17 | position: absolute; 18 | top: 50%; 19 | left: 50%; 20 | width: auto; 21 | height: auto; 22 | max-width: 90%; 23 | max-height: 90%; 24 | transform: translate(-50%, -50%); 25 | } 26 | 27 | .image-list-item p { 28 | margin-top: 120px; 29 | text-align: center; 30 | font-size: 90%; 31 | } 32 | 33 | .image-list-item:hover p { 34 | color: #337ab7; 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mohd Hafizuddin Bin M Marzuki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # summernote-image-list 2 | 3 | A plugin for [Summernote](https://github.com/summernote/summernote/) WYSIWYG editor. 4 | 5 | Add a button that shows a dialog box which displays a list of images. 6 | The list of images is generated using data retrieved from the provided endpoint. 7 | The data must be in `JSON` format. 8 | Selecting an image in the list will insert it into the editor. 9 | 10 | ## Dependencies 11 | - [Bootstrap](http://getbootstrap.com/): `HTML` markup in the code depends on Bootstrap 3's styling. 12 | - [Font Awesome](http://fontawesome.io/): Use some icons for button and spinner 13 | 14 | 15 | ## Installation 16 | 17 | ### 1. Include `CSS` & `JS` files 18 | 19 | Include the following code after Summernote code: 20 | ```html 21 | 22 | ``` 23 | 24 | and 25 | 26 | ```html 27 | 28 | ``` 29 | 30 | ### 2. Customize Summernote options 31 | 32 | Basic customization of the options: 33 | 34 | ```javascript 35 | $(document).ready(function() { 36 | var summernoteOptions = { 37 | toolbar: [ 38 | ["style", ["bold", "italic", "underline", "clear"]], 39 | ["fontname", ["fontname"]], 40 | ["fontsize", ["fontsize"]], 41 | ["color", ["color"]], 42 | ["para", ["ul", "ol", "paragraph"]], 43 | ["height", ["height"]], 44 | ["insert", ["link", "picture", "imageList", "video", "hr"]], 45 | ["help", ["help"]] 46 | ], 47 | dialogsInBody: true, 48 | imageList: { 49 | endpoint: "image-list.php", 50 | fullUrlPrefix: "images/", 51 | thumbUrlPrefix: "images/thumb/" 52 | } 53 | }; 54 | }); 55 | ``` 56 | 57 | ### 3. Prepare the endpoint 58 | 59 | Basic example for the endpoint, in `PHP`: 60 | 61 | ```php 62 | 75 | ``` 76 | 77 | ## License 78 | 79 | This plugin may be freely distributed and is licensed under the MIT license. -------------------------------------------------------------------------------- /summernote-image-list.min.js: -------------------------------------------------------------------------------- 1 | (function(factory){if(typeof define==="function"&&define.amd){define(['jquery'],factory)}else if(typeof module==="object"&&module.exports){module.exports=factory(require('jquery'))}else{factory(window.jQuery)}}(function($){$.extend($.summernote.plugins,{imageList:function(context){var self=this;var ui=$.summernote.ui;var editor=context.layoutInfo.editor;var options=context.options;var isIncludedInToolbar=!1;for(var idx in options.toolbar){var buttons=options.toolbar[idx][1];if($.inArray("imageList",buttons)>-1){isIncludedInToolbar=!0;break}} 2 | if(!isIncludedInToolbar)return;var defaultImageListOptions={title:"Image List",tooltip:"Image List",buttonHtml:'',spinnerHtml:'',endpoint:"",fullUrlPrefix:"",thumbUrlPrefix:""};var imageListOptions=typeof options.imageList==="undefined"?{}:options.imageList;for(var propertyName in defaultImageListOptions){if(imageListOptions.hasOwnProperty(propertyName)===!1){imageListOptions[propertyName]=defaultImageListOptions[propertyName]}} 3 | context.memo("button.imageList",function(){var button=ui.button({contents:imageListOptions.buttonHtml,tooltip:imageListOptions.tooltip,click:function(event){self.show()}});return button.render()});this.createDialog=function(container){var dialogOption={title:imageListOptions.title,body:['
'].join(""),footer:[''].join(""),closeOnEscape:!0};self.$dialog=ui.dialog(dialogOption).render().appendTo(container);self.$dialog.find(".modal-dialog").addClass("modal-lg")};this.showDialog=function(){return $.Deferred(function(deferred){ui.onDialogShown(self.$dialog,function(){context.triggerEvent("dialog.shown");self.$dialog.find(".image-list-content").html(imageListOptions.spinnerHtml);$.get(imageListOptions.endpoint,null,null,"json").done(function(data){var content=[];var fullUrlPrefix=imageListOptions.fullUrlPrefix;var thumbUrlPrefix=imageListOptions.thumbUrlPrefix;for(var i=0;i','
','','

'+data[i]+'

','
',''].join(""));if((i+1)>0&&(i+1)%2===0)content.push('
');if((i+1)>0&&(i+1)%3===0)content.push('
');if((i+1)>0&&(i+1)%4===0)content.push('
')} 4 | self.$dialog.find(".image-list-content").html('
'+content.join("")+'
');self.$dialog.find(".image-list-item").click(function(event){deferred.resolve({filename:$(this).children("img").data("filename"),fullUrl:$(this).children("img").data("full-url")})})});self.$dialog.find(".image-list-btn-close").click(function(event){ui.hideDialog(self.$dialog);self.$dialog.remove()})});ui.onDialogHidden(self.$dialog,function(){if(deferred.state()==="pending"){deferred.reject()}});ui.showDialog(self.$dialog)})};this.insertImage=function(filename,fullUrl){fullUrl=fullUrl.replace("https:","").replace("http:","");context.invoke("editor.insertNode",$('')[0])};this.show=function(){if(!editor.hasClass("fullscreen")){$("html, body").css("overflow","")} 5 | context.invoke("editor.saveRange");self.showDialog().then(function(data){context.invoke("editor.restoreRange");self.insertImage(data.filename,data.fullUrl);ui.hideDialog(self.$dialog)}).fail(function(){context.invoke("editor.restoreRange")})};this.initialize=function(){var container=options.dialogsInBody?$("body"):editor;self.createDialog(container)};this.destroy=function(){ui.hideDialog(self.$dialog);self.$dialog.remove()}}})})) -------------------------------------------------------------------------------- /summernote-image-list.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | /* Global define */ 3 | if (typeof define === "function" && define.amd) { 4 | // AMD. Register as an anonymous module. 5 | define(['jquery'], factory); 6 | } else if (typeof module === "object" && module.exports) { 7 | // Node/CommonJS 8 | module.exports = factory(require('jquery')); 9 | } else { 10 | // Browser globals 11 | factory(window.jQuery); 12 | } 13 | }(function($) { 14 | $.extend($.summernote.plugins, { 15 | imageList: function(context) { 16 | var self = this; 17 | var ui = $.summernote.ui; 18 | var editor = context.layoutInfo.editor; 19 | 20 | var options = context.options; 21 | 22 | // Return early if not included in the toolbar 23 | var isIncludedInToolbar = false; 24 | 25 | for (var idx in options.toolbar) { 26 | var buttons = options.toolbar[idx][1]; 27 | 28 | if ($.inArray("imageList", buttons) > -1) { 29 | isIncludedInToolbar = true; 30 | break; 31 | } 32 | } 33 | 34 | if (!isIncludedInToolbar) return; 35 | 36 | // Default options 37 | var defaultImageListOptions = { 38 | title: "Image List", 39 | tooltip: "Image List", 40 | buttonHtml: '', 41 | spinnerHtml: '', 42 | endpoint: "", 43 | fullUrlPrefix: "", 44 | thumbUrlPrefix: "" 45 | }; 46 | 47 | // Provided options 48 | var imageListOptions = typeof options.imageList === "undefined" ? {} : options.imageList; 49 | 50 | // Assign default values if not provided 51 | for (var propertyName in defaultImageListOptions) { 52 | if (imageListOptions.hasOwnProperty(propertyName) === false) { 53 | imageListOptions[propertyName] = defaultImageListOptions[propertyName]; 54 | } 55 | } 56 | 57 | // Add the button 58 | context.memo("button.imageList", function() { 59 | var button = ui.button({ 60 | contents: imageListOptions.buttonHtml, 61 | tooltip: imageListOptions.tooltip, 62 | click: function(event) { 63 | self.show(); 64 | } 65 | }); 66 | 67 | // Create jQuery object from button instance. 68 | return button.render(); 69 | }); 70 | 71 | this.createDialog = function(container) { 72 | var dialogOption = { 73 | title: imageListOptions.title, 74 | body: [ 75 | '
' 76 | ].join(""), 77 | footer: [ 78 | '' 79 | ].join(""), 80 | closeOnEscape: true 81 | }; 82 | 83 | self.$dialog = ui.dialog(dialogOption).render().appendTo(container); 84 | 85 | self.$dialog.find(".modal-dialog").addClass("modal-lg"); 86 | }; 87 | 88 | this.showDialog = function() { 89 | return $.Deferred(function(deferred) { 90 | ui.onDialogShown(self.$dialog, function() { 91 | context.triggerEvent("dialog.shown"); 92 | 93 | // Show the spinner 94 | self.$dialog.find(".image-list-content").html(imageListOptions.spinnerHtml); 95 | 96 | // Retrieve the data from the endpoint and render the list 97 | $.get( 98 | imageListOptions.endpoint, 99 | null, 100 | null, 101 | "json" 102 | ).done(function(data) { 103 | var content = []; 104 | var fullUrlPrefix = imageListOptions.fullUrlPrefix; 105 | var thumbUrlPrefix = imageListOptions.thumbUrlPrefix; 106 | 107 | for (var i = 0; i < data.length; i++) { 108 | content.push([ 109 | '
', 110 | '
', 111 | '', 112 | '

' + data[i] + '

', 113 | '
', 114 | '
' 115 | ].join("")); 116 | 117 | if ((i + 1) > 0 && (i + 1) % 2 === 0) content.push('
'); 118 | if ((i + 1) > 0 && (i + 1) % 3 === 0) content.push('
'); 119 | if ((i + 1) > 0 && (i + 1) % 4 === 0) content.push('
'); 120 | } 121 | 122 | self.$dialog.find(".image-list-content").html('
' + content.join("") + '
'); 123 | 124 | self.$dialog.find(".image-list-item").click(function(event) { 125 | deferred.resolve({ 126 | filename: $(this).children("img").data("filename"), 127 | fullUrl: $(this).children("img").data("full-url") 128 | }); 129 | }); 130 | }); 131 | 132 | self.$dialog.find(".image-list-btn-close").click(function(event) { 133 | ui.hideDialog(self.$dialog); 134 | self.$dialog.remove(); 135 | }); 136 | }); 137 | 138 | ui.onDialogHidden(self.$dialog, function() { 139 | if (deferred.state() === "pending") { 140 | deferred.reject(); 141 | } 142 | }); 143 | 144 | ui.showDialog(self.$dialog); 145 | }); 146 | }; 147 | 148 | // Insert selected image into the editor 149 | this.insertImage = function(filename, fullUrl) { 150 | fullUrl = fullUrl.replace("https:", "").replace("http:", ""); 151 | context.invoke("editor.insertNode", $('')[0]); 152 | }; 153 | 154 | // 155 | this.show = function() { 156 | if (!editor.hasClass("fullscreen")) { 157 | $("html, body").css("overflow", ""); 158 | } 159 | 160 | context.invoke("editor.saveRange"); 161 | 162 | self.showDialog() 163 | .then(function(data) { 164 | context.invoke("editor.restoreRange"); 165 | self.insertImage(data.filename, data.fullUrl); 166 | ui.hideDialog(self.$dialog); 167 | }).fail(function() { 168 | context.invoke("editor.restoreRange"); 169 | }); 170 | }; 171 | 172 | this.initialize = function() { 173 | var container = options.dialogsInBody ? $("body") : editor; 174 | self.createDialog(container); 175 | }; 176 | 177 | this.destroy = function() { 178 | ui.hideDialog(self.$dialog); 179 | self.$dialog.remove(); 180 | }; 181 | } 182 | }); 183 | })); --------------------------------------------------------------------------------