PageRenderTime 107ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/plugin/specialchars/summernote-ext-specialchars.js

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