plugin.js 20 KB

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