You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

666 lines
13 KiB

7 years ago
  1. /*
  2. * Emoji plugin for summernote [https://github.com/summernote/summernote]
  3. * Canonical - https://github.com/JustinEldracher/summernote-plugins
  4. */
  5. odoo.define('muk_web_utils.summernote_ext_specialchars', function (require) {
  6. 'use strict';
  7. //template
  8. var tmpl = $.summernote.renderer.getTemplate();
  9. // core functions: range, dom
  10. var range = $.summernote.core.range;
  11. var dom = $.summernote.core.dom;
  12. var KEY = {
  13. UP: 38,
  14. DOWN: 40,
  15. LEFT: 37,
  16. RIGHT: 39,
  17. ENTER: 13
  18. };
  19. var COLUMN_LENGTH = 15;
  20. var COLUMN_WIDTH = 35;
  21. var currentColumn, currentRow, totalColumn, totalRow = 0;
  22. // special characters data set
  23. var specialCharDataSet = [
  24. "!",
  25. """,
  26. "#",
  27. "$",
  28. "%",
  29. "&",
  30. "'",
  31. "(",
  32. ")",
  33. "*",
  34. "+",
  35. ",",
  36. "-",
  37. ".",
  38. "/",
  39. "0",
  40. "1",
  41. "2",
  42. "3",
  43. "4",
  44. "5",
  45. "6",
  46. "7",
  47. "8",
  48. "9",
  49. ":",
  50. "&#59;",
  51. "<",
  52. "=",
  53. ">",
  54. "?",
  55. "@",
  56. "A",
  57. "B",
  58. "C",
  59. "D",
  60. "E",
  61. "F",
  62. "G",
  63. "H",
  64. "I",
  65. "J",
  66. "K",
  67. "L",
  68. "M",
  69. "N",
  70. "O",
  71. "P",
  72. "Q",
  73. "R",
  74. "S",
  75. "T",
  76. "U",
  77. "V",
  78. "W",
  79. "X",
  80. "Y",
  81. "Z",
  82. "[",
  83. "\",
  84. "]",
  85. "^",
  86. "_",
  87. "`",
  88. "a",
  89. "b",
  90. "c",
  91. "d",
  92. "e",
  93. "f",
  94. "g",
  95. "h",
  96. "i",
  97. "j",
  98. "k",
  99. "l",
  100. "m",
  101. "n",
  102. "o",
  103. "p",
  104. "q",
  105. "r",
  106. "s",
  107. "t",
  108. "u",
  109. "v",
  110. "w",
  111. "x",
  112. "y",
  113. "z",
  114. "{",
  115. "|",
  116. "}",
  117. "~",
  118. "À",
  119. "Á",
  120. "Â",
  121. "Ã",
  122. "Ä",
  123. "Å",
  124. "Æ",
  125. "Ç",
  126. "È",
  127. "É",
  128. "Ê",
  129. "Ë",
  130. "Ì",
  131. "Í",
  132. "Î",
  133. "Ï",
  134. "Ð",
  135. "Ñ",
  136. "Ò",
  137. "Ó",
  138. "Ô",
  139. "Õ",
  140. "Ö",
  141. "Ø",
  142. "Ù",
  143. "Ú",
  144. "Û",
  145. "Ü",
  146. "Ý",
  147. "Þ",
  148. "ß",
  149. "à",
  150. "á",
  151. "â",
  152. "ã",
  153. "ä",
  154. "å",
  155. "æ",
  156. "ç",
  157. "è",
  158. "é",
  159. "ê",
  160. "ë",
  161. "ì",
  162. "í",
  163. "î",
  164. "ï",
  165. "ð",
  166. "ñ",
  167. "ò",
  168. "ó",
  169. "ô",
  170. "õ",
  171. "ö",
  172. "ø",
  173. "ù",
  174. "ú",
  175. "û",
  176. "ü",
  177. "ý",
  178. "þ",
  179. "ÿ",
  180. "¡",
  181. "¢",
  182. "£",
  183. "¤",
  184. "¥",
  185. "¦",
  186. "§",
  187. "¨",
  188. "©",
  189. "ª",
  190. "«",
  191. "¬",
  192. "®",
  193. "¯",
  194. "°",
  195. "±",
  196. "²",
  197. "³",
  198. "´",
  199. "µ",
  200. "¶",
  201. "¸",
  202. "¹",
  203. "º",
  204. "»",
  205. "¼",
  206. "½",
  207. "¾",
  208. "¿",
  209. "×",
  210. "÷",
  211. "∀",
  212. "∂",
  213. "∃",
  214. "∅",
  215. "∇",
  216. "∈",
  217. "∉",
  218. "∋",
  219. "∏",
  220. "∑",
  221. "−",
  222. "∗",
  223. "√",
  224. "∝",
  225. "∞",
  226. "∠",
  227. "∧",
  228. "∨",
  229. "∩",
  230. "∪",
  231. "∫",
  232. "∴",
  233. "∼",
  234. "≅",
  235. "≈",
  236. "≠",
  237. "≡",
  238. "≤",
  239. "≥",
  240. "⊂",
  241. "⊃",
  242. "⊄",
  243. "⊆",
  244. "⊇",
  245. "⊕",
  246. "⊗",
  247. "⊥",
  248. "⋅",
  249. "Α",
  250. "Β",
  251. "Γ",
  252. "Δ",
  253. "Ε",
  254. "Ζ",
  255. "Η",
  256. "Θ",
  257. "Ι",
  258. "Κ",
  259. "Λ",
  260. "Μ",
  261. "Ν",
  262. "Ξ",
  263. "Ο",
  264. "Π",
  265. "Ρ",
  266. "Σ",
  267. "Τ",
  268. "Υ",
  269. "Φ",
  270. "Χ",
  271. "Ψ",
  272. "Ω",
  273. "α",
  274. "β",
  275. "γ",
  276. "δ",
  277. "ε",
  278. "ζ",
  279. "η",
  280. "θ",
  281. "ι",
  282. "κ",
  283. "λ",
  284. "μ",
  285. "ν",
  286. "ξ",
  287. "ο",
  288. "π",
  289. "ρ",
  290. "ς",
  291. "σ",
  292. "τ",
  293. "υ",
  294. "φ",
  295. "χ",
  296. "ψ",
  297. "ω",
  298. "ϑ",
  299. "ϒ",
  300. "ϖ",
  301. "Œ",
  302. "œ",
  303. "Š",
  304. "š",
  305. "Ÿ",
  306. "ƒ",
  307. "ˆ",
  308. "˜",
  309. " ",
  310. " ",
  311. " ",
  312. "‌",
  313. "‍",
  314. "‎",
  315. "‏",
  316. "–",
  317. "—",
  318. "‘",
  319. "’",
  320. "‚",
  321. "“",
  322. "”",
  323. "„",
  324. "†",
  325. "‡",
  326. "•",
  327. "…",
  328. "‰",
  329. "′",
  330. "″",
  331. "‹",
  332. "›",
  333. "‾",
  334. "€",
  335. "™",
  336. "←",
  337. "↑",
  338. "→",
  339. "↓",
  340. "↔",
  341. "↵",
  342. "⌈",
  343. "⌉",
  344. "⌊",
  345. "⌋",
  346. "◊",
  347. "♠",
  348. "♣",
  349. "♥",
  350. "♦",
  351. ];
  352. /**
  353. * @member plugin.specialChar
  354. * @private
  355. * @param {jQuery} $editable
  356. * @return {String}
  357. */
  358. var getTextOnRange = function ($editable) {
  359. $editable.focus();
  360. var rng = range.create();
  361. // if range on anchor, expand range with anchor
  362. if (rng.isOnAnchor()) {
  363. var anchor = dom.ancestor(rng.sc, dom.isAnchor);
  364. rng = range.createFromNode(anchor);
  365. }
  366. return rng.toString();
  367. };
  368. /**
  369. * Make Special Characters Table
  370. *
  371. * @member plugin.specialChar
  372. * @private
  373. * @return {jQuery}
  374. */
  375. var makeSpecialCharSetTable = function () {
  376. var $table = $("<div/>").attr("id", "specialCharTable");
  377. $.each(specialCharDataSet, function (idx, text) {
  378. var $block = $("<span/>").attr("style", "border:1px solid black;display:inline-block;height:50px;width:35px;text-align:center;font-size:14pt;color:black;padding-top:10px;cursor:pointer;")
  379. .addClass("note-specialchar-node char-" + idx).attr("title", text).attr("id", "char-" + idx);
  380. $block.append(text);
  381. $table.append($block);
  382. });
  383. return $table;
  384. };
  385. /**
  386. * Show Special Characters and set event handlers on dialog controls.
  387. *
  388. * @member plugin.specialChar
  389. * @private
  390. * @param {jQuery} $dialog
  391. * @param {jQuery} $dialog
  392. * @param {Object} text
  393. * @return {Promise}
  394. */
  395. var showSpecialCharDialog = function ($editable, $dialog, text) {
  396. return $.Deferred(function (deferred) {
  397. var $specialCharDialog = $dialog.find('.note-specialchar-dialog');
  398. var $specialCharNode = $specialCharDialog.find('.note-specialchar-node');
  399. var $selectedNode = null;
  400. var ARROW_KEYS = [KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT];
  401. var ENTER_KEY = KEY.ENTER;
  402. var pos = 0;
  403. var end = specialCharDataSet.length;
  404. function addActiveClass($target) {
  405. if (!$target) {
  406. return;
  407. }
  408. $target.find('span').addClass('active');
  409. $selectedNode = $target;
  410. }
  411. function removeActiveClass($target) {
  412. $target.find('span').removeClass('active');
  413. $selectedNode = null;
  414. }
  415. // find next node
  416. function findNextNode(row, column) {
  417. var findNode = null;
  418. $.each($specialCharNode, function (idx, $node) {
  419. var findRow = Math.ceil((idx + 1) / COLUMN_LENGTH);
  420. var findColumn = ((idx + 1) % COLUMN_LENGTH === 0) ? COLUMN_LENGTH : (idx + 1) % COLUMN_LENGTH;
  421. if (findRow === row && findColumn === column) {
  422. findNode = $node;
  423. return false;
  424. }
  425. });
  426. return $(findNode);
  427. }
  428. function arrowKeyHandler(keyCode) {
  429. // left, right, up, down key
  430. var w = $("#specialCharTable").css("width") + "";
  431. w = w.substr(0, w.length - 2);
  432. var cols = Math.floor(w / 35);
  433. pos = parseInt(pos);
  434. if (KEY.LEFT === keyCode) {
  435. if (pos > 0) {
  436. pos--;
  437. clear();
  438. $(".char-" + pos).css("border", "1px solid blue").css("background-color", "aliceblue");
  439. $selectedNode = $(".char-" + pos);
  440. }
  441. } else if (KEY.RIGHT === keyCode) {
  442. if (pos < end - 1) {
  443. pos++;
  444. clear();
  445. $(".char-" + pos).css("border", "1px solid blue").css("background-color", "aliceblue");
  446. $selectedNode = $(".char-" + pos);
  447. }
  448. } else if (KEY.UP === keyCode) {
  449. if (pos - cols >= 0) {
  450. clear();
  451. pos = pos - cols;
  452. $(".char-" + pos).css("border", "1px solid blue").css("background-color", "aliceblue");
  453. $selectedNode = $(".char-" + pos);
  454. }
  455. } else if (KEY.DOWN === keyCode) {
  456. if (pos + cols <= end) {
  457. clear();
  458. pos = pos + cols;
  459. $(".char-" + pos).css("border", "1px solid blue").css("background-color", "aliceblue");
  460. $selectedNode = $(".char-" + pos);
  461. }
  462. }
  463. }
  464. function enterKeyHandler() {
  465. if (!$selectedNode) {
  466. return;
  467. }
  468. pos = 0;
  469. deferred.resolve(decodeURIComponent($selectedNode.attr("title")));
  470. $specialCharDialog.modal('hide');
  471. }
  472. function keyDownEventHandler(event) {
  473. event.preventDefault();
  474. var keyCode = event.keyCode;
  475. if (keyCode === undefined || keyCode === null) {
  476. return;
  477. }
  478. // check arrowKeys match
  479. if (ARROW_KEYS.indexOf(keyCode) > -1) {
  480. arrowKeyHandler(keyCode);
  481. } else if (keyCode === ENTER_KEY) {
  482. enterKeyHandler();
  483. }
  484. return false;
  485. }
  486. // remove class
  487. removeActiveClass($specialCharNode);
  488. // find selected node
  489. if (text) {
  490. for (var i = 0; i < $specialCharNode.length; i++) {
  491. var $checkNode = $($specialCharNode[i]);
  492. if ($checkNode.text() === text) {
  493. addActiveClass($checkNode);
  494. currentRow = Math.ceil((i + 1) / COLUMN_LENGTH);
  495. currentColumn = (i + 1) % COLUMN_LENGTH;
  496. }
  497. }
  498. }
  499. $specialCharDialog.one('shown.bs.modal', function () {
  500. $(document).on('keydown', keyDownEventHandler);
  501. $specialCharNode.on('click', function (event) {
  502. event.preventDefault();
  503. pos = 0;
  504. deferred.resolve(decodeURIComponent(event.currentTarget.title));
  505. $specialCharDialog.modal('hide');
  506. });
  507. $specialCharNode.mouseenter(function() {
  508. clear();
  509. $(this).css("border", "1px solid blue").css("background-color", "aliceblue");
  510. $selectedNode = $(this);
  511. var thisid = $(this).attr("id") + "";
  512. pos = thisid.substr(5);
  513. });
  514. $specialCharNode.mouseleave(function() {
  515. clear();
  516. });
  517. }).one('hidden.bs.modal', function () {
  518. $specialCharNode.off('click');
  519. $(document).off('keydown', keyDownEventHandler);
  520. if (deferred.state() === 'pending') {
  521. deferred.reject();
  522. }
  523. }).modal('show');
  524. // tooltip
  525. /*$dialog.find('span').tooltip({
  526. container: $specialCharDialog.find('.form-group'),
  527. trigger: 'hover',
  528. placement: 'top'
  529. });*/
  530. // $editable blur
  531. $editable.blur();
  532. function clear() {
  533. $specialCharNode.css("border", "1px solid black").css("background-color", "white");
  534. $selectedNode = null;
  535. }
  536. });
  537. };
  538. /**
  539. * @class plugin.specialChar
  540. *
  541. * Special Characters Plugin
  542. *
  543. * ### load script
  544. *
  545. * ```
  546. * < script src="plugin/summernote-ext-specialchar.js"></script >
  547. * ```
  548. *
  549. * ### use a plugin in toolbar
  550. * ```
  551. * $("#editor").summernote({
  552. * ...
  553. * toolbar : [
  554. * ['group', [ 'specialChar' ]]
  555. * ]
  556. * ...
  557. * });
  558. * ```
  559. */
  560. $.summernote.addPlugin({
  561. /** @property {String} name name of plugin */
  562. name: 'specialChar',
  563. /**
  564. * @property {Object} buttons
  565. * @property {function(object): string} buttons.specialChar
  566. */
  567. buttons: {
  568. specialChar: function (lang, options) {
  569. return tmpl.iconButton(options.iconPrefix + 'circle-o ' + options.iconPrefix, {
  570. event: 'showSpecialCharDialog',
  571. title: lang.specialChar.specialChar,
  572. hide: true
  573. });
  574. }
  575. },
  576. /**
  577. * @property {Object} dialogs
  578. * @property {function(object, object): string} dialogs.specialChar
  579. */
  580. dialogs: {
  581. specialChar: function (lang) {
  582. var body = '<div class="form-group row-fluid">' +
  583. makeSpecialCharSetTable()[0].outerHTML +
  584. '</div>';
  585. return tmpl.dialog('note-specialchar-dialog', lang.specialChar.select, body);
  586. }
  587. },
  588. /**
  589. * @property {Object} events
  590. * @property {Function} events.showSpecialCharDialog
  591. */
  592. events: {
  593. showSpecialCharDialog: function (event, editor, layoutInfo) {
  594. var $dialog = layoutInfo.dialog(),
  595. $editable = layoutInfo.editable(),
  596. currentSpecialChar = getTextOnRange($editable);
  597. // save current range
  598. editor.saveRange($editable);
  599. showSpecialCharDialog($editable, $dialog, currentSpecialChar).then(function (selectChar) {
  600. // when ok button clicked
  601. // restore range
  602. editor.restoreRange($editable);
  603. // build node
  604. var $node = $('<span></span>').html(selectChar)[0];
  605. //var $node = $(selectChar)[0];
  606. if ($node) {
  607. // insert character node
  608. editor.insertNode($editable, $node);
  609. }
  610. }).fail(function () {
  611. // when cancel button clicked
  612. editor.restoreRange($editable);
  613. });
  614. }
  615. },
  616. // define language
  617. langs: {
  618. 'en-US': {
  619. specialChar: {
  620. specialChar: 'Special Characters',
  621. select: 'Select Special characters'
  622. }
  623. },
  624. 'ko-KR': {
  625. specialChar: {
  626. specialChar: '특수문자',
  627. select: '특수문자를 선택하세요'
  628. }
  629. }
  630. }
  631. });
  632. });