summernote-ext-specialchars.js 10 KB

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