plugin.js 17 KB

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