plugin.js 18 KB

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