summernote-ext-specialchars.js (10765B)
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 'specialchars': function (context) { 16 var self = this; 17 var ui = $.summernote.ui; 18 19 var $editor = context.layoutInfo.editor; 20 var options = context.options; 21 var lang = options.langInfo; 22 23 var KEY = { 24 UP: 38, 25 DOWN: 40, 26 LEFT: 37, 27 RIGHT: 39, 28 ENTER: 13 29 }; 30 var COLUMN_LENGTH = 15; 31 var COLUMN_WIDTH = 35; 32 33 var currentColumn, currentRow, totalColumn, totalRow = 0; 34 35 // special characters data set 36 var specialCharDataSet = [ 37 '"', '&', '<', '>', '¡', '¢', 38 '£', '¤', '¥', '¦', '§', 39 '¨', '©', 'ª', '«', '¬', 40 '®', '¯', '°', '±', '²', 41 '³', '´', 'µ', '¶', '·', 42 '¸', '¹', 'º', '»', '¼', 43 '½', '¾', '¿', '×', '÷', 44 'ƒ', 'ˆ', '˜', '–', '—', 45 '‘', '’', '‚', '“', '”', 46 '„', '†', '‡', '•', '…', 47 '‰', '′', '″', '‹', '›', 48 '‾', '⁄', '€', 'ℑ', '℘', 49 'ℜ', '™', 'ℵ', '←', '↑', 50 '→', '↓', '↔', '↵', '⇐', 51 '⇑', '⇒', '⇓', '⇔', '∀', 52 '∂', '∃', '∅', '∇', '∈', 53 '∉', '∋', '∏', '∑', '−', 54 '∗', '√', '∝', '∞', '∠', 55 '∧', '∨', '∩', '∪', '∫', 56 '∴', '∼', '≅', '≈', '≠', 57 '≡', '≤', '≥', '⊂', '⊃', 58 '⊄', '⊆', '⊇', '⊕', '⊗', 59 '⊥', '⋅', '⌈', '⌉', '⌊', 60 '⌋', '◊', '♠', '♣', '♥', 61 '♦' 62 ]; 63 64 context.memo('button.specialCharacter', function () { 65 return ui.button({ 66 contents: '<i class="fa fa-font fa-flip-vertical">', 67 tooltip: lang.specialChar.specialChar, 68 click: function () { 69 self.show(); 70 } 71 }).render(); 72 }); 73 74 /** 75 * Make Special Characters Table 76 * 77 * @member plugin.specialChar 78 * @private 79 * @return {jQuery} 80 */ 81 this.makeSpecialCharSetTable = function () { 82 var $table = $('<table/>'); 83 $.each(specialCharDataSet, function (idx, text) { 84 var $td = $('<td/>').addClass('note-specialchar-node'); 85 var $tr = (idx % COLUMN_LENGTH === 0) ? $('<tr/>') : $table.find('tr').last(); 86 87 var $button = ui.button({ 88 callback: function ($node) { 89 $node.html(text); 90 $node.attr('title', text); 91 $node.attr('data-value', encodeURIComponent(text)); 92 $node.css({ 93 width: COLUMN_WIDTH, 94 'margin-right': '2px', 95 'margin-bottom': '2px' 96 }); 97 } 98 }).render(); 99 100 $td.append($button); 101 102 $tr.append($td); 103 if (idx % COLUMN_LENGTH === 0) { 104 $table.append($tr); 105 } 106 }); 107 108 totalRow = $table.find('tr').length; 109 totalColumn = COLUMN_LENGTH; 110 111 return $table; 112 }; 113 114 this.initialize = function () { 115 var $container = options.dialogsInBody ? $(document.body) : $editor; 116 117 var body = '<div class="form-group row-fluid">' + this.makeSpecialCharSetTable()[0].outerHTML + '</div>'; 118 119 this.$dialog = ui.dialog({ 120 title: lang.specialChar.select, 121 body: body 122 }).render().appendTo($container); 123 }; 124 125 this.show = function () { 126 var text = context.invoke('editor.getSelectedText'); 127 context.invoke('editor.saveRange'); 128 this.showSpecialCharDialog(text).then(function (selectChar) { 129 context.invoke('editor.restoreRange'); 130 131 // build node 132 var $node = $('<span></span>').html(selectChar)[0]; 133 134 if ($node) { 135 // insert video node 136 context.invoke('editor.insertNode', $node); 137 } 138 }).fail(function () { 139 context.invoke('editor.restoreRange'); 140 }); 141 }; 142 143 /** 144 * show image dialog 145 * 146 * @param {jQuery} $dialog 147 * @return {Promise} 148 */ 149 this.showSpecialCharDialog = function (text) { 150 return $.Deferred(function (deferred) { 151 var $specialCharDialog = self.$dialog; 152 var $specialCharNode = $specialCharDialog.find('.note-specialchar-node'); 153 var $selectedNode = null; 154 var ARROW_KEYS = [KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT]; 155 var ENTER_KEY = KEY.ENTER; 156 157 function addActiveClass($target) { 158 if (!$target) { 159 return; 160 } 161 $target.find('button').addClass('active'); 162 $selectedNode = $target; 163 } 164 165 function removeActiveClass($target) { 166 $target.find('button').removeClass('active'); 167 $selectedNode = null; 168 } 169 170 // find next node 171 function findNextNode(row, column) { 172 var findNode = null; 173 $.each($specialCharNode, function (idx, $node) { 174 var findRow = Math.ceil((idx + 1) / COLUMN_LENGTH); 175 var findColumn = ((idx + 1) % COLUMN_LENGTH === 0) ? COLUMN_LENGTH : (idx + 1) % COLUMN_LENGTH; 176 if (findRow === row && findColumn === column) { 177 findNode = $node; 178 return false; 179 } 180 }); 181 return $(findNode); 182 } 183 184 function arrowKeyHandler(keyCode) { 185 // left, right, up, down key 186 var $nextNode; 187 var lastRowColumnLength = $specialCharNode.length % totalColumn; 188 189 if (KEY.LEFT === keyCode) { 190 191 if (currentColumn > 1) { 192 currentColumn = currentColumn - 1; 193 } else if (currentRow === 1 && currentColumn === 1) { 194 currentColumn = lastRowColumnLength; 195 currentRow = totalRow; 196 } else { 197 currentColumn = totalColumn; 198 currentRow = currentRow - 1; 199 } 200 201 } else if (KEY.RIGHT === keyCode) { 202 203 if (currentRow === totalRow && lastRowColumnLength === currentColumn) { 204 currentColumn = 1; 205 currentRow = 1; 206 } else if (currentColumn < totalColumn) { 207 currentColumn = currentColumn + 1; 208 } else { 209 currentColumn = 1; 210 currentRow = currentRow + 1; 211 } 212 213 } else if (KEY.UP === keyCode) { 214 if (currentRow === 1 && lastRowColumnLength < currentColumn) { 215 currentRow = totalRow - 1; 216 } else { 217 currentRow = currentRow - 1; 218 } 219 } else if (KEY.DOWN === keyCode) { 220 currentRow = currentRow + 1; 221 } 222 223 if (currentRow === totalRow && currentColumn > lastRowColumnLength) { 224 currentRow = 1; 225 } else if (currentRow > totalRow) { 226 currentRow = 1; 227 } else if (currentRow < 1) { 228 currentRow = totalRow; 229 } 230 231 $nextNode = findNextNode(currentRow, currentColumn); 232 233 if ($nextNode) { 234 removeActiveClass($selectedNode); 235 addActiveClass($nextNode); 236 } 237 } 238 239 function enterKeyHandler() { 240 if (!$selectedNode) { 241 return; 242 } 243 244 deferred.resolve(decodeURIComponent($selectedNode.find('button').attr('data-value'))); 245 $specialCharDialog.modal('hide'); 246 } 247 248 function keyDownEventHandler(event) { 249 event.preventDefault(); 250 var keyCode = event.keyCode; 251 if (keyCode === undefined || keyCode === null) { 252 return; 253 } 254 // check arrowKeys match 255 if (ARROW_KEYS.indexOf(keyCode) > -1) { 256 if ($selectedNode === null) { 257 addActiveClass($specialCharNode.eq(0)); 258 currentColumn = 1; 259 currentRow = 1; 260 return; 261 } 262 arrowKeyHandler(keyCode); 263 } else if (keyCode === ENTER_KEY) { 264 enterKeyHandler(); 265 } 266 return false; 267 } 268 269 // remove class 270 removeActiveClass($specialCharNode); 271 272 // find selected node 273 if (text) { 274 for (var i = 0; i < $specialCharNode.length; i++) { 275 var $checkNode = $($specialCharNode[i]); 276 if ($checkNode.text() === text) { 277 addActiveClass($checkNode); 278 currentRow = Math.ceil((i + 1) / COLUMN_LENGTH); 279 currentColumn = (i + 1) % COLUMN_LENGTH; 280 } 281 } 282 } 283 284 ui.onDialogShown(self.$dialog, function () { 285 286 $(document).on('keydown', keyDownEventHandler); 287 288 self.$dialog.find('button').tooltip(); 289 290 $specialCharNode.on('click', function (event) { 291 event.preventDefault(); 292 deferred.resolve(decodeURIComponent($(event.currentTarget).find('button').attr('data-value'))); 293 ui.hideDialog(self.$dialog); 294 }); 295 296 }); 297 298 ui.onDialogHidden(self.$dialog, function () { 299 $specialCharNode.off('click'); 300 301 self.$dialog.find('button').tooltip('destroy'); 302 303 $(document).off('keydown', keyDownEventHandler); 304 305 if (deferred.state() === 'pending') { 306 deferred.reject(); 307 } 308 }); 309 310 ui.showDialog(self.$dialog); 311 }); 312 }; 313 } 314 }); 315 }));