plugin.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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.1.0 (2019-10-17)
  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 hasOwnProperty = Object.prototype.hasOwnProperty;
  267. var shallow = function (old, nu) {
  268. return nu;
  269. };
  270. var baseMerge = function (merger) {
  271. return function () {
  272. var objects = new Array(arguments.length);
  273. for (var i = 0; i < objects.length; i++) {
  274. objects[i] = arguments[i];
  275. }
  276. if (objects.length === 0) {
  277. throw new Error('Can\'t merge zero objects');
  278. }
  279. var ret = {};
  280. for (var j = 0; j < objects.length; j++) {
  281. var curObject = objects[j];
  282. for (var key in curObject) {
  283. if (hasOwnProperty.call(curObject, key)) {
  284. ret[key] = merger(ret[key], curObject[key]);
  285. }
  286. }
  287. }
  288. return ret;
  289. };
  290. };
  291. var merge = baseMerge(shallow);
  292. var keys = Object.keys;
  293. var hasOwnProperty$1 = Object.hasOwnProperty;
  294. var each = function (obj, f) {
  295. var props = keys(obj);
  296. for (var k = 0, len = props.length; k < len; k++) {
  297. var i = props[k];
  298. var x = obj[i];
  299. f(x, i);
  300. }
  301. };
  302. var map$1 = function (obj, f) {
  303. return tupleMap(obj, function (x, i) {
  304. return {
  305. k: i,
  306. v: f(x, i)
  307. };
  308. });
  309. };
  310. var tupleMap = function (obj, f) {
  311. var r = {};
  312. each(obj, function (x, i) {
  313. var tuple = f(x, i);
  314. r[tuple.k] = tuple.v;
  315. });
  316. return r;
  317. };
  318. var has = function (obj, key) {
  319. return hasOwnProperty$1.call(obj, key);
  320. };
  321. var global$1 = tinymce.util.Tools.resolve('tinymce.Resource');
  322. var global$2 = tinymce.util.Tools.resolve('tinymce.util.Delay');
  323. var global$3 = tinymce.util.Tools.resolve('tinymce.util.Promise');
  324. var DEFAULT_ID = 'tinymce.plugins.emoticons';
  325. var getEmoticonDatabaseUrl = function (editor, pluginUrl) {
  326. return editor.getParam('emoticons_database_url', pluginUrl + '/js/emojis' + editor.suffix + '.js');
  327. };
  328. var getEmoticonDatabaseId = function (editor) {
  329. return editor.getParam('emoticons_database_id', DEFAULT_ID, 'string');
  330. };
  331. var getAppendedEmoticons = function (editor) {
  332. return editor.getParam('emoticons_append', {}, 'object');
  333. };
  334. var Settings = {
  335. getEmoticonDatabaseUrl: getEmoticonDatabaseUrl,
  336. getEmoticonDatabaseId: getEmoticonDatabaseId,
  337. getAppendedEmoticons: getAppendedEmoticons
  338. };
  339. var ALL_CATEGORY = 'All';
  340. var categoryNameMap = {
  341. symbols: 'Symbols',
  342. people: 'People',
  343. animals_and_nature: 'Animals and Nature',
  344. food_and_drink: 'Food and Drink',
  345. activity: 'Activity',
  346. travel_and_places: 'Travel and Places',
  347. objects: 'Objects',
  348. flags: 'Flags',
  349. user: 'User Defined'
  350. };
  351. var translateCategory = function (categories, name) {
  352. return has(categories, name) ? categories[name] : name;
  353. };
  354. var getUserDefinedEmoticons = function (editor) {
  355. var userDefinedEmoticons = Settings.getAppendedEmoticons(editor);
  356. return map$1(userDefinedEmoticons, function (value) {
  357. return merge({
  358. keywords: [],
  359. category: 'user'
  360. }, value);
  361. });
  362. };
  363. var initDatabase = function (editor, databaseUrl, databaseId) {
  364. var categories = Cell(Option.none());
  365. var all = Cell(Option.none());
  366. var processEmojis = function (emojis) {
  367. var cats = {};
  368. var everything = [];
  369. each(emojis, function (lib, title) {
  370. var entry = {
  371. title: title,
  372. keywords: lib.keywords,
  373. char: lib.char,
  374. category: translateCategory(categoryNameMap, lib.category)
  375. };
  376. var current = cats[entry.category] !== undefined ? cats[entry.category] : [];
  377. cats[entry.category] = current.concat([entry]);
  378. everything.push(entry);
  379. });
  380. categories.set(Option.some(cats));
  381. all.set(Option.some(everything));
  382. };
  383. editor.on('init', function () {
  384. global$1.load(databaseId, databaseUrl).then(function (emojis) {
  385. var userEmojis = getUserDefinedEmoticons(editor);
  386. processEmojis(merge(emojis, userEmojis));
  387. }, function (err) {
  388. domGlobals.console.log('Failed to load emoticons: ' + err);
  389. categories.set(Option.some({}));
  390. all.set(Option.some([]));
  391. });
  392. });
  393. var listCategory = function (category) {
  394. if (category === ALL_CATEGORY) {
  395. return listAll();
  396. }
  397. return categories.get().bind(function (cats) {
  398. return Option.from(cats[category]);
  399. }).getOr([]);
  400. };
  401. var listAll = function () {
  402. return all.get().getOr([]);
  403. };
  404. var listCategories = function () {
  405. return [ALL_CATEGORY].concat(keys(categories.get().getOr({})));
  406. };
  407. var waitForLoad = function () {
  408. if (hasLoaded()) {
  409. return global$3.resolve(true);
  410. } else {
  411. return new global$3(function (resolve, reject) {
  412. var numRetries = 15;
  413. var interval = global$2.setInterval(function () {
  414. if (hasLoaded()) {
  415. global$2.clearInterval(interval);
  416. resolve(true);
  417. } else {
  418. numRetries--;
  419. if (numRetries < 0) {
  420. domGlobals.console.log('Could not load emojis from url: ' + databaseUrl);
  421. global$2.clearInterval(interval);
  422. reject(false);
  423. }
  424. }
  425. }, 100);
  426. });
  427. }
  428. };
  429. var hasLoaded = function () {
  430. return categories.get().isSome() && all.get().isSome();
  431. };
  432. return {
  433. listCategories: listCategories,
  434. hasLoaded: hasLoaded,
  435. waitForLoad: waitForLoad,
  436. listAll: listAll,
  437. listCategory: listCategory
  438. };
  439. };
  440. var patternName = 'pattern';
  441. var open = function (editor, database) {
  442. var initialState = {
  443. pattern: '',
  444. results: emojisFrom(database.listAll(), '', Option.some(300))
  445. };
  446. var currentTab = Cell(ALL_CATEGORY);
  447. var scan = function (dialogApi) {
  448. var dialogData = dialogApi.getData();
  449. var category = currentTab.get();
  450. var candidates = database.listCategory(category);
  451. var results = emojisFrom(candidates, dialogData[patternName], category === ALL_CATEGORY ? Option.some(300) : Option.none());
  452. dialogApi.setData({ results: results });
  453. };
  454. var updateFilter = last(function (dialogApi) {
  455. scan(dialogApi);
  456. }, 200);
  457. var searchField = {
  458. label: 'Search',
  459. type: 'input',
  460. name: patternName
  461. };
  462. var resultsField = {
  463. type: 'collection',
  464. name: 'results'
  465. };
  466. var getInitialState = function () {
  467. var body = {
  468. type: 'tabpanel',
  469. tabs: map(database.listCategories(), function (cat) {
  470. return {
  471. title: cat,
  472. name: cat,
  473. items: [
  474. searchField,
  475. resultsField
  476. ]
  477. };
  478. })
  479. };
  480. return {
  481. title: 'Emoticons',
  482. size: 'normal',
  483. body: body,
  484. initialData: initialState,
  485. onTabChange: function (dialogApi, details) {
  486. currentTab.set(details.newTabName);
  487. updateFilter.throttle(dialogApi);
  488. },
  489. onChange: updateFilter.throttle,
  490. onAction: function (dialogApi, actionData) {
  491. if (actionData.name === 'results') {
  492. insertEmoticon(editor, actionData.value);
  493. dialogApi.close();
  494. }
  495. },
  496. buttons: [{
  497. type: 'cancel',
  498. text: 'Close',
  499. primary: true
  500. }]
  501. };
  502. };
  503. var dialogApi = editor.windowManager.open(getInitialState());
  504. dialogApi.focus(patternName);
  505. if (!database.hasLoaded()) {
  506. dialogApi.block('Loading emoticons...');
  507. database.waitForLoad().then(function () {
  508. dialogApi.redial(getInitialState());
  509. updateFilter.throttle(dialogApi);
  510. dialogApi.focus(patternName);
  511. dialogApi.unblock();
  512. }).catch(function (err) {
  513. dialogApi.redial({
  514. title: 'Emoticons',
  515. body: {
  516. type: 'panel',
  517. items: [{
  518. type: 'alertbanner',
  519. level: 'error',
  520. icon: 'warning',
  521. text: '<p>Could not load emoticons</p>'
  522. }]
  523. },
  524. buttons: [{
  525. type: 'cancel',
  526. text: 'Close',
  527. primary: true
  528. }],
  529. initialData: {
  530. pattern: '',
  531. results: []
  532. }
  533. });
  534. dialogApi.focus(patternName);
  535. dialogApi.unblock();
  536. });
  537. }
  538. };
  539. var Dialog = { open: open };
  540. var register = function (editor, database) {
  541. var onAction = function () {
  542. return Dialog.open(editor, database);
  543. };
  544. editor.ui.registry.addButton('emoticons', {
  545. tooltip: 'Emoticons',
  546. icon: 'emoji',
  547. onAction: onAction
  548. });
  549. editor.ui.registry.addMenuItem('emoticons', {
  550. text: 'Emoticons...',
  551. icon: 'emoji',
  552. onAction: onAction
  553. });
  554. };
  555. var Buttons = { register: register };
  556. function Plugin () {
  557. global.add('emoticons', function (editor, pluginUrl) {
  558. var databaseUrl = Settings.getEmoticonDatabaseUrl(editor, pluginUrl);
  559. var databaseId = Settings.getEmoticonDatabaseId(editor);
  560. var database = initDatabase(editor, databaseUrl, databaseId);
  561. Buttons.register(editor, database);
  562. init(editor, database);
  563. });
  564. }
  565. Plugin();
  566. }(window));