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.3.0 (2020-05-21)
  8. */
  9. (function (domGlobals) {
  10. 'use strict';
  11. var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
  12. var noop = function () {
  13. };
  14. var constant = function (value) {
  15. return function () {
  16. return value;
  17. };
  18. };
  19. var never = constant(false);
  20. var always = constant(true);
  21. var none = function () {
  22. return NONE;
  23. };
  24. var NONE = function () {
  25. var eq = function (o) {
  26. return o.isNone();
  27. };
  28. var call = function (thunk) {
  29. return thunk();
  30. };
  31. var id = function (n) {
  32. return n;
  33. };
  34. var me = {
  35. fold: function (n, _s) {
  36. return n();
  37. },
  38. is: never,
  39. isSome: never,
  40. isNone: always,
  41. getOr: id,
  42. getOrThunk: call,
  43. getOrDie: function (msg) {
  44. throw new Error(msg || 'error: getOrDie called on none.');
  45. },
  46. getOrNull: constant(null),
  47. getOrUndefined: constant(undefined),
  48. or: id,
  49. orThunk: call,
  50. map: none,
  51. each: noop,
  52. bind: none,
  53. exists: never,
  54. forall: always,
  55. filter: none,
  56. equals: eq,
  57. equals_: eq,
  58. toArray: function () {
  59. return [];
  60. },
  61. toString: constant('none()')
  62. };
  63. return me;
  64. }();
  65. var some = function (a) {
  66. var constant_a = constant(a);
  67. var self = function () {
  68. return me;
  69. };
  70. var bind = function (f) {
  71. return f(a);
  72. };
  73. var me = {
  74. fold: function (n, s) {
  75. return s(a);
  76. },
  77. is: function (v) {
  78. return a === v;
  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. equals: function (o) {
  108. return o.is(a);
  109. },
  110. equals_: function (o, elementEq) {
  111. return o.fold(never, function (b) {
  112. return elementEq(a, b);
  113. });
  114. }
  115. };
  116. return me;
  117. };
  118. var from = function (value) {
  119. return value === null || value === undefined ? NONE : some(value);
  120. };
  121. var Option = {
  122. some: some,
  123. none: none,
  124. from: from
  125. };
  126. var exists = function (xs, pred) {
  127. for (var i = 0, len = xs.length; i < len; i++) {
  128. var x = xs[i];
  129. if (pred(x, i)) {
  130. return true;
  131. }
  132. }
  133. return false;
  134. };
  135. var map = function (xs, f) {
  136. var len = xs.length;
  137. var r = new Array(len);
  138. for (var i = 0; i < len; i++) {
  139. var x = xs[i];
  140. r[i] = f(x, i);
  141. }
  142. return r;
  143. };
  144. var contains = function (str, substr) {
  145. return str.indexOf(substr) !== -1;
  146. };
  147. var emojiMatches = function (emoji, lowerCasePattern) {
  148. return contains(emoji.title.toLowerCase(), lowerCasePattern) || exists(emoji.keywords, function (k) {
  149. return contains(k.toLowerCase(), lowerCasePattern);
  150. });
  151. };
  152. var emojisFrom = function (list, pattern, maxResults) {
  153. var matches = [];
  154. var lowerCasePattern = pattern.toLowerCase();
  155. var reachedLimit = maxResults.fold(function () {
  156. return never;
  157. }, function (max) {
  158. return function (size) {
  159. return size >= max;
  160. };
  161. });
  162. for (var i = 0; i < list.length; i++) {
  163. if (pattern.length === 0 || emojiMatches(list[i], lowerCasePattern)) {
  164. matches.push({
  165. value: list[i].char,
  166. text: list[i].title,
  167. icon: list[i].char
  168. });
  169. if (reachedLimit(matches.length)) {
  170. break;
  171. }
  172. }
  173. }
  174. return matches;
  175. };
  176. var init = function (editor, database) {
  177. editor.ui.registry.addAutocompleter('emoticons', {
  178. ch: ':',
  179. columns: 'auto',
  180. minChars: 2,
  181. fetch: function (pattern, maxResults) {
  182. return database.waitForLoad().then(function () {
  183. var candidates = database.listAll();
  184. return emojisFrom(candidates, pattern, Option.some(maxResults));
  185. });
  186. },
  187. onAction: function (autocompleteApi, rng, value) {
  188. editor.selection.setRng(rng);
  189. editor.insertContent(value);
  190. autocompleteApi.hide();
  191. }
  192. });
  193. };
  194. var Cell = function (initial) {
  195. var value = initial;
  196. var get = function () {
  197. return value;
  198. };
  199. var set = function (v) {
  200. value = v;
  201. };
  202. return {
  203. get: get,
  204. set: set
  205. };
  206. };
  207. var last = function (fn, rate) {
  208. var timer = null;
  209. var cancel = function () {
  210. if (timer !== null) {
  211. domGlobals.clearTimeout(timer);
  212. timer = null;
  213. }
  214. };
  215. var throttle = function () {
  216. var args = [];
  217. for (var _i = 0; _i < arguments.length; _i++) {
  218. args[_i] = arguments[_i];
  219. }
  220. if (timer !== null) {
  221. domGlobals.clearTimeout(timer);
  222. }
  223. timer = domGlobals.setTimeout(function () {
  224. fn.apply(null, args);
  225. timer = null;
  226. }, rate);
  227. };
  228. return {
  229. cancel: cancel,
  230. throttle: throttle
  231. };
  232. };
  233. var insertEmoticon = function (editor, ch) {
  234. editor.insertContent(ch);
  235. };
  236. var __assign = function () {
  237. __assign = Object.assign || function __assign(t) {
  238. for (var s, i = 1, n = arguments.length; i < n; i++) {
  239. s = arguments[i];
  240. for (var p in s)
  241. if (Object.prototype.hasOwnProperty.call(s, p))
  242. t[p] = s[p];
  243. }
  244. return t;
  245. };
  246. return __assign.apply(this, arguments);
  247. };
  248. var hasOwnProperty = Object.prototype.hasOwnProperty;
  249. var shallow = function (old, nu) {
  250. return nu;
  251. };
  252. var baseMerge = function (merger) {
  253. return function () {
  254. var objects = new Array(arguments.length);
  255. for (var i = 0; i < objects.length; i++) {
  256. objects[i] = arguments[i];
  257. }
  258. if (objects.length === 0) {
  259. throw new Error('Can\'t merge zero objects');
  260. }
  261. var ret = {};
  262. for (var j = 0; j < objects.length; j++) {
  263. var curObject = objects[j];
  264. for (var key in curObject) {
  265. if (hasOwnProperty.call(curObject, key)) {
  266. ret[key] = merger(ret[key], curObject[key]);
  267. }
  268. }
  269. }
  270. return ret;
  271. };
  272. };
  273. var merge = baseMerge(shallow);
  274. var keys = Object.keys;
  275. var hasOwnProperty$1 = Object.hasOwnProperty;
  276. var each = function (obj, f) {
  277. var props = keys(obj);
  278. for (var k = 0, len = props.length; k < len; k++) {
  279. var i = props[k];
  280. var x = obj[i];
  281. f(x, i);
  282. }
  283. };
  284. var map$1 = function (obj, f) {
  285. return tupleMap(obj, function (x, i) {
  286. return {
  287. k: i,
  288. v: f(x, i)
  289. };
  290. });
  291. };
  292. var tupleMap = function (obj, f) {
  293. var r = {};
  294. each(obj, function (x, i) {
  295. var tuple = f(x, i);
  296. r[tuple.k] = tuple.v;
  297. });
  298. return r;
  299. };
  300. var has = function (obj, key) {
  301. return hasOwnProperty$1.call(obj, key);
  302. };
  303. var global$1 = tinymce.util.Tools.resolve('tinymce.Resource');
  304. var global$2 = tinymce.util.Tools.resolve('tinymce.util.Delay');
  305. var global$3 = tinymce.util.Tools.resolve('tinymce.util.Promise');
  306. var DEFAULT_ID = 'tinymce.plugins.emoticons';
  307. var getEmoticonDatabaseUrl = function (editor, pluginUrl) {
  308. return editor.getParam('emoticons_database_url', pluginUrl + '/js/emojis' + editor.suffix + '.js');
  309. };
  310. var getEmoticonDatabaseId = function (editor) {
  311. return editor.getParam('emoticons_database_id', DEFAULT_ID, 'string');
  312. };
  313. var getAppendedEmoticons = function (editor) {
  314. return editor.getParam('emoticons_append', {}, 'object');
  315. };
  316. var ALL_CATEGORY = 'All';
  317. var categoryNameMap = {
  318. symbols: 'Symbols',
  319. people: 'People',
  320. animals_and_nature: 'Animals and Nature',
  321. food_and_drink: 'Food and Drink',
  322. activity: 'Activity',
  323. travel_and_places: 'Travel and Places',
  324. objects: 'Objects',
  325. flags: 'Flags',
  326. user: 'User Defined'
  327. };
  328. var translateCategory = function (categories, name) {
  329. return has(categories, name) ? categories[name] : name;
  330. };
  331. var getUserDefinedEmoticons = function (editor) {
  332. var userDefinedEmoticons = getAppendedEmoticons(editor);
  333. return map$1(userDefinedEmoticons, function (value) {
  334. return __assign({
  335. keywords: [],
  336. category: 'user'
  337. }, value);
  338. });
  339. };
  340. var initDatabase = function (editor, databaseUrl, databaseId) {
  341. var categories = Cell(Option.none());
  342. var all = Cell(Option.none());
  343. var processEmojis = function (emojis) {
  344. var cats = {};
  345. var everything = [];
  346. each(emojis, function (lib, title) {
  347. var entry = {
  348. title: title,
  349. keywords: lib.keywords,
  350. char: lib.char,
  351. category: translateCategory(categoryNameMap, lib.category)
  352. };
  353. var current = cats[entry.category] !== undefined ? cats[entry.category] : [];
  354. cats[entry.category] = current.concat([entry]);
  355. everything.push(entry);
  356. });
  357. categories.set(Option.some(cats));
  358. all.set(Option.some(everything));
  359. };
  360. editor.on('init', function () {
  361. global$1.load(databaseId, databaseUrl).then(function (emojis) {
  362. var userEmojis = getUserDefinedEmoticons(editor);
  363. processEmojis(merge(emojis, userEmojis));
  364. }, function (err) {
  365. domGlobals.console.log('Failed to load emoticons: ' + err);
  366. categories.set(Option.some({}));
  367. all.set(Option.some([]));
  368. });
  369. });
  370. var listCategory = function (category) {
  371. if (category === ALL_CATEGORY) {
  372. return listAll();
  373. }
  374. return categories.get().bind(function (cats) {
  375. return Option.from(cats[category]);
  376. }).getOr([]);
  377. };
  378. var listAll = function () {
  379. return all.get().getOr([]);
  380. };
  381. var listCategories = function () {
  382. return [ALL_CATEGORY].concat(keys(categories.get().getOr({})));
  383. };
  384. var waitForLoad = function () {
  385. if (hasLoaded()) {
  386. return global$3.resolve(true);
  387. } else {
  388. return new global$3(function (resolve, reject) {
  389. var numRetries = 15;
  390. var interval = global$2.setInterval(function () {
  391. if (hasLoaded()) {
  392. global$2.clearInterval(interval);
  393. resolve(true);
  394. } else {
  395. numRetries--;
  396. if (numRetries < 0) {
  397. domGlobals.console.log('Could not load emojis from url: ' + databaseUrl);
  398. global$2.clearInterval(interval);
  399. reject(false);
  400. }
  401. }
  402. }, 100);
  403. });
  404. }
  405. };
  406. var hasLoaded = function () {
  407. return categories.get().isSome() && all.get().isSome();
  408. };
  409. return {
  410. listCategories: listCategories,
  411. hasLoaded: hasLoaded,
  412. waitForLoad: waitForLoad,
  413. listAll: listAll,
  414. listCategory: listCategory
  415. };
  416. };
  417. var patternName = 'pattern';
  418. var open = function (editor, database) {
  419. var initialState = {
  420. pattern: '',
  421. results: emojisFrom(database.listAll(), '', Option.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 ? Option.some(300) : Option.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(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. }(window));