summernote-ext-specialchars.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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. var $editor = context.layoutInfo.editor;
  19. var options = context.options;
  20. var lang = options.langInfo;
  21. var KEY = {
  22. UP: 38,
  23. DOWN: 40,
  24. LEFT: 37,
  25. RIGHT: 39,
  26. ENTER: 13,
  27. };
  28. var COLUMN_LENGTH = 15;
  29. var COLUMN_WIDTH = 35;
  30. var currentColumn = 0;
  31. var currentRow = 0;
  32. var totalColumn = 0;
  33. var totalRow = 0;
  34. // special characters data set
  35. var specialCharDataSet = [
  36. '"', '&', '<', '>', '¡', '¢',
  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. context.memo('button.specialchars', function() {
  63. return ui.button({
  64. contents: '<i class="fa fa-font fa-flip-vertical">',
  65. tooltip: lang.specialChar.specialChar,
  66. click: function() {
  67. self.show();
  68. },
  69. }).render();
  70. });
  71. /**
  72. * Make Special Characters Table
  73. *
  74. * @member plugin.specialChar
  75. * @private
  76. * @return {jQuery}
  77. */
  78. this.makeSpecialCharSetTable = function() {
  79. var $table = $('<table/>');
  80. $.each(specialCharDataSet, function(idx, text) {
  81. var $td = $('<td/>').addClass('note-specialchar-node');
  82. var $tr = (idx % COLUMN_LENGTH === 0) ? $('<tr/>') : $table.find('tr').last();
  83. var $button = ui.button({
  84. callback: function($node) {
  85. $node.html(text);
  86. $node.attr('title', text);
  87. $node.attr('data-value', encodeURIComponent(text));
  88. $node.css({
  89. width: COLUMN_WIDTH,
  90. 'margin-right': '2px',
  91. 'margin-bottom': '2px',
  92. });
  93. },
  94. }).render();
  95. $td.append($button);
  96. $tr.append($td);
  97. if (idx % COLUMN_LENGTH === 0) {
  98. $table.append($tr);
  99. }
  100. });
  101. totalRow = $table.find('tr').length;
  102. totalColumn = COLUMN_LENGTH;
  103. return $table;
  104. };
  105. this.initialize = function() {
  106. var $container = options.dialogsInBody ? $(document.body) : $editor;
  107. var body = '<div class="form-group row-fluid">' + this.makeSpecialCharSetTable()[0].outerHTML + '</div>';
  108. this.$dialog = ui.dialog({
  109. title: lang.specialChar.select,
  110. body: body,
  111. }).render().appendTo($container);
  112. };
  113. this.show = function() {
  114. var text = context.invoke('editor.getSelectedText');
  115. context.invoke('editor.saveRange');
  116. this.showSpecialCharDialog(text).then(function(selectChar) {
  117. context.invoke('editor.restoreRange');
  118. // build node
  119. var $node = $('<span></span>').html(selectChar)[0];
  120. if ($node) {
  121. // insert video node
  122. context.invoke('editor.insertNode', $node);
  123. }
  124. }).fail(function() {
  125. context.invoke('editor.restoreRange');
  126. });
  127. };
  128. /**
  129. * show image dialog
  130. *
  131. * @param {jQuery} $dialog
  132. * @return {Promise}
  133. */
  134. this.showSpecialCharDialog = function(text) {
  135. return $.Deferred(function(deferred) {
  136. var $specialCharDialog = self.$dialog;
  137. var $specialCharNode = $specialCharDialog.find('.note-specialchar-node');
  138. var $selectedNode = null;
  139. var ARROW_KEYS = [KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT];
  140. var ENTER_KEY = KEY.ENTER;
  141. function addActiveClass($target) {
  142. if (!$target) {
  143. return;
  144. }
  145. $target.find('button').addClass('active');
  146. $selectedNode = $target;
  147. }
  148. function removeActiveClass($target) {
  149. $target.find('button').removeClass('active');
  150. $selectedNode = null;
  151. }
  152. // find next node
  153. function findNextNode(row, column) {
  154. var findNode = null;
  155. $.each($specialCharNode, function(idx, $node) {
  156. var findRow = Math.ceil((idx + 1) / COLUMN_LENGTH);
  157. var findColumn = ((idx + 1) % COLUMN_LENGTH === 0) ? COLUMN_LENGTH : (idx + 1) % COLUMN_LENGTH;
  158. if (findRow === row && findColumn === column) {
  159. findNode = $node;
  160. return false;
  161. }
  162. });
  163. return $(findNode);
  164. }
  165. function arrowKeyHandler(keyCode) {
  166. // left, right, up, down key
  167. var $nextNode;
  168. var lastRowColumnLength = $specialCharNode.length % totalColumn;
  169. if (KEY.LEFT === keyCode) {
  170. if (currentColumn > 1) {
  171. currentColumn = currentColumn - 1;
  172. } else if (currentRow === 1 && currentColumn === 1) {
  173. currentColumn = lastRowColumnLength;
  174. currentRow = totalRow;
  175. } else {
  176. currentColumn = totalColumn;
  177. currentRow = currentRow - 1;
  178. }
  179. } else if (KEY.RIGHT === keyCode) {
  180. if (currentRow === totalRow && lastRowColumnLength === currentColumn) {
  181. currentColumn = 1;
  182. currentRow = 1;
  183. } else if (currentColumn < totalColumn) {
  184. currentColumn = currentColumn + 1;
  185. } else {
  186. currentColumn = 1;
  187. currentRow = currentRow + 1;
  188. }
  189. } else if (KEY.UP === keyCode) {
  190. if (currentRow === 1 && lastRowColumnLength < currentColumn) {
  191. currentRow = totalRow - 1;
  192. } else {
  193. currentRow = currentRow - 1;
  194. }
  195. } else if (KEY.DOWN === keyCode) {
  196. currentRow = currentRow + 1;
  197. }
  198. if (currentRow === totalRow && currentColumn > lastRowColumnLength) {
  199. currentRow = 1;
  200. } else if (currentRow > totalRow) {
  201. currentRow = 1;
  202. } else if (currentRow < 1) {
  203. currentRow = totalRow;
  204. }
  205. $nextNode = findNextNode(currentRow, currentColumn);
  206. if ($nextNode) {
  207. removeActiveClass($selectedNode);
  208. addActiveClass($nextNode);
  209. }
  210. }
  211. function enterKeyHandler() {
  212. if (!$selectedNode) {
  213. return;
  214. }
  215. deferred.resolve(decodeURIComponent($selectedNode.find('button').attr('data-value')));
  216. $specialCharDialog.modal('hide');
  217. }
  218. function keyDownEventHandler(event) {
  219. event.preventDefault();
  220. var keyCode = event.keyCode;
  221. if (keyCode === undefined || keyCode === null) {
  222. return;
  223. }
  224. // check arrowKeys match
  225. if (ARROW_KEYS.indexOf(keyCode) > -1) {
  226. if ($selectedNode === null) {
  227. addActiveClass($specialCharNode.eq(0));
  228. currentColumn = 1;
  229. currentRow = 1;
  230. return;
  231. }
  232. arrowKeyHandler(keyCode);
  233. } else if (keyCode === ENTER_KEY) {
  234. enterKeyHandler();
  235. }
  236. return false;
  237. }
  238. // remove class
  239. removeActiveClass($specialCharNode);
  240. // find selected node
  241. if (text) {
  242. for (var i = 0; i < $specialCharNode.length; i++) {
  243. var $checkNode = $($specialCharNode[i]);
  244. if ($checkNode.text() === text) {
  245. addActiveClass($checkNode);
  246. currentRow = Math.ceil((i + 1) / COLUMN_LENGTH);
  247. currentColumn = (i + 1) % COLUMN_LENGTH;
  248. }
  249. }
  250. }
  251. ui.onDialogShown(self.$dialog, function() {
  252. $(document).on('keydown', keyDownEventHandler);
  253. self.$dialog.find('button').tooltip();
  254. $specialCharNode.on('click', function(event) {
  255. event.preventDefault();
  256. deferred.resolve(decodeURIComponent($(event.currentTarget).find('button').attr('data-value')));
  257. ui.hideDialog(self.$dialog);
  258. });
  259. });
  260. ui.onDialogHidden(self.$dialog, function() {
  261. $specialCharNode.off('click');
  262. self.$dialog.find('button').tooltip('destroy');
  263. $(document).off('keydown', keyDownEventHandler);
  264. if (deferred.state() === 'pending') {
  265. deferred.reject();
  266. }
  267. });
  268. ui.showDialog(self.$dialog);
  269. });
  270. };
  271. },
  272. });
  273. }));