plugin.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. /**
  2. * Copyright (c) Tiny Technologies, Inc. All rights reserved.
  3. * Licensed under the LGPL or a commercial license.
  4. * For LGPL see License.txt in the project root for license information.
  5. * For commercial licenses see https://www.tiny.cloud/
  6. *
  7. * Version: 5.5.1 (2020-10-01)
  8. */
  9. (function () {
  10. 'use strict';
  11. var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
  12. var DEFAULT_ID = 'tinymce.plugins.emoticons';
  13. var getEmoticonDatabaseUrl = function (editor, pluginUrl) {
  14. return editor.getParam('emoticons_database_url', pluginUrl + '/js/emojis' + editor.suffix + '.js');
  15. };
  16. var getEmoticonDatabaseId = function (editor) {
  17. return editor.getParam('emoticons_database_id', DEFAULT_ID, 'string');
  18. };
  19. var getAppendedEmoticons = function (editor) {
  20. return editor.getParam('emoticons_append', {}, 'object');
  21. };
  22. var __assign = function () {
  23. __assign = Object.assign || function __assign(t) {
  24. for (var s, i = 1, n = arguments.length; i < n; i++) {
  25. s = arguments[i];
  26. for (var p in s)
  27. if (Object.prototype.hasOwnProperty.call(s, p))
  28. t[p] = s[p];
  29. }
  30. return t;
  31. };
  32. return __assign.apply(this, arguments);
  33. };
  34. var Cell = function (initial) {
  35. var value = initial;
  36. var get = function () {
  37. return value;
  38. };
  39. var set = function (v) {
  40. value = v;
  41. };
  42. return {
  43. get: get,
  44. set: set
  45. };
  46. };
  47. var hasOwnProperty = Object.prototype.hasOwnProperty;
  48. var shallow = function (old, nu) {
  49. return nu;
  50. };
  51. var baseMerge = function (merger) {
  52. return function () {
  53. var objects = new Array(arguments.length);
  54. for (var i = 0; i < objects.length; i++) {
  55. objects[i] = arguments[i];
  56. }
  57. if (objects.length === 0) {
  58. throw new Error('Can\'t merge zero objects');
  59. }
  60. var ret = {};
  61. for (var j = 0; j < objects.length; j++) {
  62. var curObject = objects[j];
  63. for (var key in curObject) {
  64. if (hasOwnProperty.call(curObject, key)) {
  65. ret[key] = merger(ret[key], curObject[key]);
  66. }
  67. }
  68. }
  69. return ret;
  70. };
  71. };
  72. var merge = baseMerge(shallow);
  73. var noop = function () {
  74. };
  75. var constant = function (value) {
  76. return function () {
  77. return value;
  78. };
  79. };
  80. var never = constant(false);
  81. var always = constant(true);
  82. var none = function () {
  83. return NONE;
  84. };
  85. var NONE = function () {
  86. var eq = function (o) {
  87. return o.isNone();
  88. };
  89. var call = function (thunk) {
  90. return thunk();
  91. };
  92. var id = function (n) {
  93. return n;
  94. };
  95. var me = {
  96. fold: function (n, _s) {
  97. return n();
  98. },
  99. is: never,
  100. isSome: never,
  101. isNone: always,
  102. getOr: id,
  103. getOrThunk: call,
  104. getOrDie: function (msg) {
  105. throw new Error(msg || 'error: getOrDie called on none.');
  106. },
  107. getOrNull: constant(null),
  108. getOrUndefined: constant(undefined),
  109. or: id,
  110. orThunk: call,
  111. map: none,
  112. each: noop,
  113. bind: none,
  114. exists: never,
  115. forall: always,
  116. filter: none,
  117. equals: eq,
  118. equals_: eq,
  119. toArray: function () {
  120. return [];
  121. },
  122. toString: constant('none()')
  123. };
  124. return me;
  125. }();
  126. var some = function (a) {
  127. var constant_a = constant(a);
  128. var self = function () {
  129. return me;
  130. };
  131. var bind = function (f) {
  132. return f(a);
  133. };
  134. var me = {
  135. fold: function (n, s) {
  136. return s(a);
  137. },
  138. is: function (v) {
  139. return a === v;
  140. },
  141. isSome: always,
  142. isNone: never,
  143. getOr: constant_a,
  144. getOrThunk: constant_a,
  145. getOrDie: constant_a,
  146. getOrNull: constant_a,
  147. getOrUndefined: constant_a,
  148. or: self,
  149. orThunk: self,
  150. map: function (f) {
  151. return some(f(a));
  152. },
  153. each: function (f) {
  154. f(a);
  155. },
  156. bind: bind,
  157. exists: bind,
  158. forall: bind,
  159. filter: function (f) {
  160. return f(a) ? me : NONE;
  161. },
  162. toArray: function () {
  163. return [a];
  164. },
  165. toString: function () {
  166. return 'some(' + a + ')';
  167. },
  168. equals: function (o) {
  169. return o.is(a);
  170. },
  171. equals_: function (o, elementEq) {
  172. return o.fold(never, function (b) {
  173. return elementEq(a, b);
  174. });
  175. }
  176. };
  177. return me;
  178. };
  179. var from = function (value) {
  180. return value === null || value === undefined ? NONE : some(value);
  181. };
  182. var Optional = {
  183. some: some,
  184. none: none,
  185. from: from
  186. };
  187. var keys = Object.keys;
  188. var hasOwnProperty$1 = Object.hasOwnProperty;
  189. var each = function (obj, f) {
  190. var props = keys(obj);
  191. for (var k = 0, len = props.length; k < len; k++) {
  192. var i = props[k];
  193. var x = obj[i];
  194. f(x, i);
  195. }
  196. };
  197. var map = function (obj, f) {
  198. return tupleMap(obj, function (x, i) {
  199. return {
  200. k: i,
  201. v: f(x, i)
  202. };
  203. });
  204. };
  205. var tupleMap = function (obj, f) {
  206. var r = {};
  207. each(obj, function (x, i) {
  208. var tuple = f(x, i);
  209. r[tuple.k] = tuple.v;
  210. });
  211. return r;
  212. };
  213. var has = function (obj, key) {
  214. return hasOwnProperty$1.call(obj, key);
  215. };
  216. var global$1 = tinymce.util.Tools.resolve('tinymce.Resource');
  217. var global$2 = tinymce.util.Tools.resolve('tinymce.util.Delay');
  218. var global$3 = tinymce.util.Tools.resolve('tinymce.util.Promise');
  219. var ALL_CATEGORY = 'All';
  220. var categoryNameMap = {
  221. symbols: 'Symbols',
  222. people: 'People',
  223. animals_and_nature: 'Animals and Nature',
  224. food_and_drink: 'Food and Drink',
  225. activity: 'Activity',
  226. travel_and_places: 'Travel and Places',
  227. objects: 'Objects',
  228. flags: 'Flags',
  229. user: 'User Defined'
  230. };
  231. var translateCategory = function (categories, name) {
  232. return has(categories, name) ? categories[name] : name;
  233. };
  234. var getUserDefinedEmoticons = function (editor) {
  235. var userDefinedEmoticons = getAppendedEmoticons(editor);
  236. return map(userDefinedEmoticons, function (value) {
  237. return __assign({
  238. keywords: [],
  239. category: 'user'
  240. }, value);
  241. });
  242. };
  243. var initDatabase = function (editor, databaseUrl, databaseId) {
  244. var categories = Cell(Optional.none());
  245. var all = Cell(Optional.none());
  246. var processEmojis = function (emojis) {
  247. var cats = {};
  248. var everything = [];
  249. each(emojis, function (lib, title) {
  250. var entry = {
  251. title: title,
  252. keywords: lib.keywords,
  253. char: lib.char,
  254. category: translateCategory(categoryNameMap, lib.category)
  255. };
  256. var current = cats[entry.category] !== undefined ? cats[entry.category] : [];
  257. cats[entry.category] = current.concat([entry]);
  258. everything.push(entry);
  259. });
  260. categories.set(Optional.some(cats));
  261. all.set(Optional.some(everything));
  262. };
  263. editor.on('init', function () {
  264. global$1.load(databaseId, databaseUrl).then(function (emojis) {
  265. var userEmojis = getUserDefinedEmoticons(editor);
  266. processEmojis(merge(emojis, userEmojis));
  267. }, function (err) {
  268. console.log('Failed to load emoticons: ' + err);
  269. categories.set(Optional.some({}));
  270. all.set(Optional.some([]));
  271. });
  272. });
  273. var listCategory = function (category) {
  274. if (category === ALL_CATEGORY) {
  275. return listAll();
  276. }
  277. return categories.get().bind(function (cats) {
  278. return Optional.from(cats[category]);
  279. }).getOr([]);
  280. };
  281. var listAll = function () {
  282. return all.get().getOr([]);
  283. };
  284. var listCategories = function () {
  285. return [ALL_CATEGORY].concat(keys(categories.get().getOr({})));
  286. };
  287. var waitForLoad = function () {
  288. if (hasLoaded()) {
  289. return global$3.resolve(true);
  290. } else {
  291. return new global$3(function (resolve, reject) {
  292. var numRetries = 15;
  293. var interval = global$2.setInterval(function () {
  294. if (hasLoaded()) {
  295. global$2.clearInterval(interval);
  296. resolve(true);
  297. } else {
  298. numRetries--;
  299. if (numRetries < 0) {
  300. console.log('Could not load emojis from url: ' + databaseUrl);
  301. global$2.clearInterval(interval);
  302. reject(false);
  303. }
  304. }
  305. }, 100);
  306. });
  307. }
  308. };
  309. var hasLoaded = function () {
  310. return categories.get().isSome() && all.get().isSome();
  311. };
  312. return {
  313. listCategories: listCategories,
  314. hasLoaded: hasLoaded,
  315. waitForLoad: waitForLoad,
  316. listAll: listAll,
  317. listCategory: listCategory
  318. };
  319. };
  320. var exists = function (xs, pred) {
  321. for (var i = 0, len = xs.length; i < len; i++) {
  322. var x = xs[i];
  323. if (pred(x, i)) {
  324. return true;
  325. }
  326. }
  327. return false;
  328. };
  329. var map$1 = function (xs, f) {
  330. var len = xs.length;
  331. var r = new Array(len);
  332. for (var i = 0; i < len; i++) {
  333. var x = xs[i];
  334. r[i] = f(x, i);
  335. }
  336. return r;
  337. };
  338. var contains = function (str, substr) {
  339. return str.indexOf(substr) !== -1;
  340. };
  341. var emojiMatches = function (emoji, lowerCasePattern) {
  342. return contains(emoji.title.toLowerCase(), lowerCasePattern) || exists(emoji.keywords, function (k) {
  343. return contains(k.toLowerCase(), lowerCasePattern);
  344. });
  345. };
  346. var emojisFrom = function (list, pattern, maxResults) {
  347. var matches = [];
  348. var lowerCasePattern = pattern.toLowerCase();
  349. var reachedLimit = maxResults.fold(function () {
  350. return never;
  351. }, function (max) {
  352. return function (size) {
  353. return size >= max;
  354. };
  355. });
  356. for (var i = 0; i < list.length; i++) {
  357. if (pattern.length === 0 || emojiMatches(list[i], lowerCasePattern)) {
  358. matches.push({
  359. value: list[i].char,
  360. text: list[i].title,
  361. icon: list[i].char
  362. });
  363. if (reachedLimit(matches.length)) {
  364. break;
  365. }
  366. }
  367. }
  368. return matches;
  369. };
  370. var init = function (editor, database) {
  371. editor.ui.registry.addAutocompleter('emoticons', {
  372. ch: ':',
  373. columns: 'auto',
  374. minChars: 2,
  375. fetch: function (pattern, maxResults) {
  376. return database.waitForLoad().then(function () {
  377. var candidates = database.listAll();
  378. return emojisFrom(candidates, pattern, Optional.some(maxResults));
  379. });
  380. },
  381. onAction: function (autocompleteApi, rng, value) {
  382. editor.selection.setRng(rng);
  383. editor.insertContent(value);
  384. autocompleteApi.hide();
  385. }
  386. });
  387. };
  388. var last = function (fn, rate) {
  389. var timer = null;
  390. var cancel = function () {
  391. if (timer !== null) {
  392. clearTimeout(timer);
  393. timer = null;
  394. }
  395. };
  396. var throttle = function () {
  397. var args = [];
  398. for (var _i = 0; _i < arguments.length; _i++) {
  399. args[_i] = arguments[_i];
  400. }
  401. if (timer !== null) {
  402. clearTimeout(timer);
  403. }
  404. timer = setTimeout(function () {
  405. fn.apply(null, args);
  406. timer = null;
  407. }, rate);
  408. };
  409. return {
  410. cancel: cancel,
  411. throttle: throttle
  412. };
  413. };
  414. var insertEmoticon = function (editor, ch) {
  415. editor.insertContent(ch);
  416. };
  417. var patternName = 'pattern';
  418. var open = function (editor, database) {
  419. var initialState = {
  420. pattern: '',
  421. results: emojisFrom(database.listAll(), '', Optional.some(300))
  422. };
  423. var currentTab = Cell(ALL_CATEGORY);
  424. var scan = function (dialogApi) {
  425. var dialogData = dialogApi.getData();
  426. var category = currentTab.get();
  427. var candidates = database.listCategory(category);
  428. var results = emojisFrom(candidates, dialogData[patternName], category === ALL_CATEGORY ? Optional.some(300) : Optional.none());
  429. dialogApi.setData({ results: results });
  430. };
  431. var updateFilter = last(function (dialogApi) {
  432. scan(dialogApi);
  433. }, 200);
  434. var searchField = {
  435. label: 'Search',
  436. type: 'input',
  437. name: patternName
  438. };
  439. var resultsField = {
  440. type: 'collection',
  441. name: 'results'
  442. };
  443. var getInitialState = function () {
  444. var body = {
  445. type: 'tabpanel',
  446. tabs: map$1(database.listCategories(), function (cat) {
  447. return {
  448. title: cat,
  449. name: cat,
  450. items: [
  451. searchField,
  452. resultsField
  453. ]
  454. };
  455. })
  456. };
  457. return {
  458. title: 'Emoticons',
  459. size: 'normal',
  460. body: body,
  461. initialData: initialState,
  462. onTabChange: function (dialogApi, details) {
  463. currentTab.set(details.newTabName);
  464. updateFilter.throttle(dialogApi);
  465. },
  466. onChange: updateFilter.throttle,
  467. onAction: function (dialogApi, actionData) {
  468. if (actionData.name === 'results') {
  469. insertEmoticon(editor, actionData.value);
  470. dialogApi.close();
  471. }
  472. },
  473. buttons: [{
  474. type: 'cancel',
  475. text: 'Close',
  476. primary: true
  477. }]
  478. };
  479. };
  480. var dialogApi = editor.windowManager.open(getInitialState());
  481. dialogApi.focus(patternName);
  482. if (!database.hasLoaded()) {
  483. dialogApi.block('Loading emoticons...');
  484. database.waitForLoad().then(function () {
  485. dialogApi.redial(getInitialState());
  486. updateFilter.throttle(dialogApi);
  487. dialogApi.focus(patternName);
  488. dialogApi.unblock();
  489. }).catch(function (_err) {
  490. dialogApi.redial({
  491. title: 'Emoticons',
  492. body: {
  493. type: 'panel',
  494. items: [{
  495. type: 'alertbanner',
  496. level: 'error',
  497. icon: 'warning',
  498. text: '<p>Could not load emoticons</p>'
  499. }]
  500. },
  501. buttons: [{
  502. type: 'cancel',
  503. text: 'Close',
  504. primary: true
  505. }],
  506. initialData: {
  507. pattern: '',
  508. results: []
  509. }
  510. });
  511. dialogApi.focus(patternName);
  512. dialogApi.unblock();
  513. });
  514. }
  515. };
  516. var register = function (editor, database) {
  517. var onAction = function () {
  518. return open(editor, database);
  519. };
  520. editor.ui.registry.addButton('emoticons', {
  521. tooltip: 'Emoticons',
  522. icon: 'emoji',
  523. onAction: onAction
  524. });
  525. editor.ui.registry.addMenuItem('emoticons', {
  526. text: 'Emoticons...',
  527. icon: 'emoji',
  528. onAction: onAction
  529. });
  530. };
  531. function Plugin () {
  532. global.add('emoticons', function (editor, pluginUrl) {
  533. var databaseUrl = getEmoticonDatabaseUrl(editor, pluginUrl);
  534. var databaseId = getEmoticonDatabaseId(editor);
  535. var database = initDatabase(editor, databaseUrl, databaseId);
  536. register(editor, database);
  537. init(editor, database);
  538. });
  539. }
  540. Plugin();
  541. }());