/** * TinyMCE version 6.4.1 (2023-03-29) */ (function () { 'use strict'; var global$1 = tinymce.util.Tools.resolve('tinymce.PluginManager'); const eq = t => a => t === a; const isNull = eq(null); const isUndefined = eq(undefined); const isNullable = a => a === null || a === undefined; const isNonNullable = a => !isNullable(a); const noop = () => { }; const constant = value => { return () => { return value; }; }; const never = constant(false); class Optional { constructor(tag, value) { this.tag = tag; this.value = value; } static some(value) { return new Optional(true, value); } static none() { return Optional.singletonNone; } fold(onNone, onSome) { if (this.tag) { return onSome(this.value); } else { return onNone(); } } isSome() { return this.tag; } isNone() { return !this.tag; } map(mapper) { if (this.tag) { return Optional.some(mapper(this.value)); } else { return Optional.none(); } } bind(binder) { if (this.tag) { return binder(this.value); } else { return Optional.none(); } } exists(predicate) { return this.tag && predicate(this.value); } forall(predicate) { return !this.tag || predicate(this.value); } filter(predicate) { if (!this.tag || predicate(this.value)) { return this; } else { return Optional.none(); } } getOr(replacement) { return this.tag ? this.value : replacement; } or(replacement) { return this.tag ? this : replacement; } getOrThunk(thunk) { return this.tag ? this.value : thunk(); } orThunk(thunk) { return this.tag ? this : thunk(); } getOrDie(message) { if (!this.tag) { throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None'); } else { return this.value; } } static from(value) { return isNonNullable(value) ? Optional.some(value) : Optional.none(); } getOrNull() { return this.tag ? this.value : null; } getOrUndefined() { return this.value; } each(worker) { if (this.tag) { worker(this.value); } } toArray() { return this.tag ? [this.value] : []; } toString() { return this.tag ? `some(${ this.value })` : 'none()'; } } Optional.singletonNone = new Optional(false); const exists = (xs, pred) => { for (let i = 0, len = xs.length; i < len; i++) { const x = xs[i]; if (pred(x, i)) { return true; } } return false; }; const map$1 = (xs, f) => { const len = xs.length; const r = new Array(len); for (let i = 0; i < len; i++) { const x = xs[i]; r[i] = f(x, i); } return r; }; const each$1 = (xs, f) => { for (let i = 0, len = xs.length; i < len; i++) { const x = xs[i]; f(x, i); } }; const Cell = initial => { let value = initial; const get = () => { return value; }; const set = v => { value = v; }; return { get, set }; }; const last = (fn, rate) => { let timer = null; const cancel = () => { if (!isNull(timer)) { clearTimeout(timer); timer = null; } }; const throttle = (...args) => { cancel(); timer = setTimeout(() => { timer = null; fn.apply(null, args); }, rate); }; return { cancel, throttle }; }; const insertEmoticon = (editor, ch) => { editor.insertContent(ch); }; const keys = Object.keys; const hasOwnProperty = Object.hasOwnProperty; const each = (obj, f) => { const props = keys(obj); for (let k = 0, len = props.length; k < len; k++) { const i = props[k]; const x = obj[i]; f(x, i); } }; const map = (obj, f) => { return tupleMap(obj, (x, i) => ({ k: i, v: f(x, i) })); }; const tupleMap = (obj, f) => { const r = {}; each(obj, (x, i) => { const tuple = f(x, i); r[tuple.k] = tuple.v; }); return r; }; const has = (obj, key) => hasOwnProperty.call(obj, key); const shallow = (old, nu) => { return nu; }; const baseMerge = merger => { return (...objects) => { if (objects.length === 0) { throw new Error(`Can't merge zero objects`); } const ret = {}; for (let j = 0; j < objects.length; j++) { const curObject = objects[j]; for (const key in curObject) { if (has(curObject, key)) { ret[key] = merger(ret[key], curObject[key]); } } } return ret; }; }; const merge = baseMerge(shallow); const singleton = doRevoke => { const subject = Cell(Optional.none()); const revoke = () => subject.get().each(doRevoke); const clear = () => { revoke(); subject.set(Optional.none()); }; const isSet = () => subject.get().isSome(); const get = () => subject.get(); const set = s => { revoke(); subject.set(Optional.some(s)); }; return { clear, isSet, get, set }; }; const value = () => { const subject = singleton(noop); const on = f => subject.get().each(f); return { ...subject, on }; }; const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr; const contains = (str, substr, start = 0, end) => { const idx = str.indexOf(substr, start); if (idx !== -1) { return isUndefined(end) ? true : idx + substr.length <= end; } else { return false; } }; const startsWith = (str, prefix) => { return checkRange(str, prefix, 0); }; var global = tinymce.util.Tools.resolve('tinymce.Resource'); const DEFAULT_ID = 'tinymce.plugins.emoticons'; const option = name => editor => editor.options.get(name); const register$2 = (editor, pluginUrl) => { const registerOption = editor.options.register; registerOption('emoticons_database', { processor: 'string', default: 'emojis' }); registerOption('emoticons_database_url', { processor: 'string', default: `${ pluginUrl }/js/${ getEmojiDatabase(editor) }${ editor.suffix }.js` }); registerOption('emoticons_database_id', { processor: 'string', default: DEFAULT_ID }); registerOption('emoticons_append', { processor: 'object', default: {} }); registerOption('emoticons_images_url', { processor: 'string', default: 'https://twemoji.maxcdn.com/v/13.0.1/72x72/' }); }; const getEmojiDatabase = option('emoticons_database'); const getEmojiDatabaseUrl = option('emoticons_database_url'); const getEmojiDatabaseId = option('emoticons_database_id'); const getAppendedEmoji = option('emoticons_append'); const getEmojiImageUrl = option('emoticons_images_url'); const ALL_CATEGORY = 'All'; const categoryNameMap = { symbols: 'Symbols', people: 'People', animals_and_nature: 'Animals and Nature', food_and_drink: 'Food and Drink', activity: 'Activity', travel_and_places: 'Travel and Places', objects: 'Objects', flags: 'Flags', user: 'User Defined' }; const translateCategory = (categories, name) => has(categories, name) ? categories[name] : name; const getUserDefinedEmoji = editor => { const userDefinedEmoticons = getAppendedEmoji(editor); return map(userDefinedEmoticons, value => ({ keywords: [], category: 'user', ...value })); }; const initDatabase = (editor, databaseUrl, databaseId) => { const categories = value(); const all = value(); const emojiImagesUrl = getEmojiImageUrl(editor); const getEmoji = lib => { if (startsWith(lib.char, ' `src="${ emojiImagesUrl }${ url }"`); } else { return lib.char; } }; const processEmojis = emojis => { const cats = {}; const everything = []; each(emojis, (lib, title) => { const entry = { title, keywords: lib.keywords, char: getEmoji(lib), category: translateCategory(categoryNameMap, lib.category) }; const current = cats[entry.category] !== undefined ? cats[entry.category] : []; cats[entry.category] = current.concat([entry]); everything.push(entry); }); categories.set(cats); all.set(everything); }; editor.on('init', () => { global.load(databaseId, databaseUrl).then(emojis => { const userEmojis = getUserDefinedEmoji(editor); processEmojis(merge(emojis, userEmojis)); }, err => { console.log(`Failed to load emojis: ${ err }`); categories.set({}); all.set([]); }); }); const listCategory = category => { if (category === ALL_CATEGORY) { return listAll(); } return categories.get().bind(cats => Optional.from(cats[category])).getOr([]); }; const listAll = () => all.get().getOr([]); const listCategories = () => [ALL_CATEGORY].concat(keys(categories.get().getOr({}))); const waitForLoad = () => { if (hasLoaded()) { return Promise.resolve(true); } else { return new Promise((resolve, reject) => { let numRetries = 15; const interval = setInterval(() => { if (hasLoaded()) { clearInterval(interval); resolve(true); } else { numRetries--; if (numRetries < 0) { console.log('Could not load emojis from url: ' + databaseUrl); clearInterval(interval); reject(false); } } }, 100); }); } }; const hasLoaded = () => categories.isSet() && all.isSet(); return { listCategories, hasLoaded, waitForLoad, listAll, listCategory }; }; const emojiMatches = (emoji, lowerCasePattern) => contains(emoji.title.toLowerCase(), lowerCasePattern) || exists(emoji.keywords, k => contains(k.toLowerCase(), lowerCasePattern)); const emojisFrom = (list, pattern, maxResults) => { const matches = []; const lowerCasePattern = pattern.toLowerCase(); const reachedLimit = maxResults.fold(() => never, max => size => size >= max); for (let i = 0; i < list.length; i++) { if (pattern.length === 0 || emojiMatches(list[i], lowerCasePattern)) { matches.push({ value: list[i].char, text: list[i].title, icon: list[i].char }); if (reachedLimit(matches.length)) { break; } } } return matches; }; const patternName = 'pattern'; const open = (editor, database) => { const initialState = { pattern: '', results: emojisFrom(database.listAll(), '', Optional.some(300)) }; const currentTab = Cell(ALL_CATEGORY); const scan = dialogApi => { const dialogData = dialogApi.getData(); const category = currentTab.get(); const candidates = database.listCategory(category); const results = emojisFrom(candidates, dialogData[patternName], category === ALL_CATEGORY ? Optional.some(300) : Optional.none()); dialogApi.setData({ results }); }; const updateFilter = last(dialogApi => { scan(dialogApi); }, 200); const searchField = { label: 'Search', type: 'input', name: patternName }; const resultsField = { type: 'collection', name: 'results' }; const getInitialState = () => { const body = { type: 'tabpanel', tabs: map$1(database.listCategories(), cat => ({ title: cat, name: cat, items: [ searchField, resultsField ] })) }; return { title: 'Emojis', size: 'normal', body, initialData: initialState, onTabChange: (dialogApi, details) => { currentTab.set(details.newTabName); updateFilter.throttle(dialogApi); }, onChange: updateFilter.throttle, onAction: (dialogApi, actionData) => { if (actionData.name === 'results') { insertEmoticon(editor, actionData.value); dialogApi.close(); } }, buttons: [{ type: 'cancel', text: 'Close', primary: true }] }; }; const dialogApi = editor.windowManager.open(getInitialState()); dialogApi.focus(patternName); if (!database.hasLoaded()) { dialogApi.block('Loading emojis...'); database.waitForLoad().then(() => { dialogApi.redial(getInitialState()); updateFilter.throttle(dialogApi); dialogApi.focus(patternName); dialogApi.unblock(); }).catch(_err => { dialogApi.redial({ title: 'Emojis', body: { type: 'panel', items: [{ type: 'alertbanner', level: 'error', icon: 'warning', text: 'Could not load emojis' }] }, buttons: [{ type: 'cancel', text: 'Close', primary: true }], initialData: { pattern: '', results: [] } }); dialogApi.focus(patternName); dialogApi.unblock(); }); } }; const register$1 = (editor, database) => { editor.addCommand('mceEmoticons', () => open(editor, database)); }; const setup = editor => { editor.on('PreInit', () => { editor.parser.addAttributeFilter('data-emoticon', nodes => { each$1(nodes, node => { node.attr('data-mce-resize', 'false'); node.attr('data-mce-placeholder', '1'); }); }); }); }; const init = (editor, database) => { editor.ui.registry.addAutocompleter('emoticons', { trigger: ':', columns: 'auto', minChars: 2, fetch: (pattern, maxResults) => database.waitForLoad().then(() => { const candidates = database.listAll(); return emojisFrom(candidates, pattern, Optional.some(maxResults)); }), onAction: (autocompleteApi, rng, value) => { editor.selection.setRng(rng); editor.insertContent(value); autocompleteApi.hide(); } }); }; const register = editor => { const onAction = () => editor.execCommand('mceEmoticons'); editor.ui.registry.addButton('emoticons', { tooltip: 'Emojis', icon: 'emoji', onAction }); editor.ui.registry.addMenuItem('emoticons', { text: 'Emojis...', icon: 'emoji', onAction }); }; var Plugin = () => { global$1.add('emoticons', (editor, pluginUrl) => { register$2(editor, pluginUrl); const databaseUrl = getEmojiDatabaseUrl(editor); const databaseId = getEmojiDatabaseId(editor); const database = initDatabase(editor, databaseUrl, databaseId); register$1(editor, database); register(editor); init(editor, database); setup(editor); }); }; Plugin(); })();