text: /**
   * tinymce_mce_popup.js
   *
   * Released under LGPL License.
   * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
   *
   * License: http://www.tinymce.com/license
   * Contributing: http://www.tinymce.com/contributing
   */
  
  var tinymce, tinyMCE;
  
  /**
   * TinyMCE popup/dialog helper class. This gives you easy access to the
   * parent editor instance and a bunch of other things. It's higly recommended
   * that you load this script into your dialogs.
   *
   * @static
   * @class tinyMCEPopup
   */
  var tinyMCEPopup = {
    /**
     * Initializes the popup this will be called automatically.
     *
     * @method init
     */
    init: function () {
      var self = this, parentWin, settings, uiWindow;
  
      // Find window & API
      parentWin = self.getWin();
      tinymce = tinyMCE = parentWin.tinymce;
      self.editor = tinymce.EditorManager.activeEditor;
      self.params = self.editor.windowManager.getParams();
  
      uiWindow = self.editor.windowManager.windows[self.editor.windowManager.windows.length - 1];
      self.features = uiWindow.features;
      self.uiWindow = uiWindow;
  
      settings = self.editor.settings;
  
      // Setup popup CSS path(s)
      if (settings.popup_css !== false) {
        if (settings.popup_css) {
          settings.popup_css = self.editor.documentBaseURI.toAbsolute(settings.popup_css);
        } else {
          settings.popup_css = self.editor.baseURI.toAbsolute("plugins/compat3x/css/dialog.css");
        }
      }
  
      if (settings.popup_css_add) {
        settings.popup_css += ',' + self.editor.documentBaseURI.toAbsolute(settings.popup_css_add);
      }
  
      // Setup local DOM
      self.dom = self.editor.windowManager.createInstance('tinymce.dom.DOMUtils', document, {
        ownEvents: true,
        proxy: tinyMCEPopup._eventProxy
      });
  
      self.dom.bind(window, 'ready', self._onDOMLoaded, self);
  
      // Enables you to skip loading the default css
      if (self.features.popup_css !== false) {
        self.dom.loadCSS(self.features.popup_css || self.editor.settings.popup_css);
      }
  
      // Setup on init listeners
      self.listeners = [];
  
      /**
       * Fires when the popup is initialized.
       *
       * @event onInit
       * @param {tinymce.Editor} editor Editor instance.
       * @example
       * // Alerts the selected contents when the dialog is loaded
       * tinyMCEPopup.onInit.add(function(ed) {
       *     alert(ed.selection.getContent());
       * });
       *
       * // Executes the init method on page load in some object using the SomeObject scope
       * tinyMCEPopup.onInit.add(SomeObject.init, SomeObject);
       */
      self.onInit = {
        add: function (func, scope) {
          self.listeners.push({ func: func, scope: scope });
        }
      };
  
      self.isWindow = !self.getWindowArg('mce_inline');
      self.id = self.getWindowArg('mce_window_id');
    },
  
    /**
     * Returns the reference to the parent window that opened the dialog.
     *
     * @method getWin
     * @return {Window} Reference to the parent window that opened the dialog.
     */
    getWin: function () {
      // Added frameElement check to fix bug: #2817583
      return (!window.frameElement && window.dialogArguments) || opener || parent || top;
    },
  
    /**
     * Returns a window argument/parameter by name.
     *
     * @method getWindowArg
     * @param {String} name Name of the window argument to retrieve.
     * @param {String} defaultValue Optional default value to return.
     * @return {String} Argument value or default value if it wasn't found.
     */
    getWindowArg: function (name, defaultValue) {
      var value = this.params[name];
  
      return tinymce.is(value) ? value : defaultValue;
    },
  
    /**
     * Returns a editor parameter/config option value.
     *
     * @method getParam
     * @param {String} name Name of the editor config option to retrieve.
     * @param {String} defaultValue Optional default value to return.
     * @return {String} Parameter value or default value if it wasn't found.
     */
    getParam: function (name, defaultValue) {
      return this.editor.getParam(name, defaultValue);
    },
  
    /**
     * Returns a language item by key.
     *
     * @method getLang
     * @param {String} name Language item like mydialog.something.
     * @param {String} defaultValue Optional default value to return.
     * @return {String} Language value for the item like "my string" or the default value if it wasn't found.
     */
    getLang: function (name, defaultValue) {
      return this.editor.getLang(name, defaultValue);
    },
  
    /**
     * Executed a command on editor that opened the dialog/popup.
     *
     * @method execCommand
     * @param {String} cmd Command to execute.
     * @param {Boolean} ui Optional boolean value if the UI for the command should be presented or not.
     * @param {Object} val Optional value to pass with the comman like an URL.
     * @param {Object} a Optional arguments object.
     */
    execCommand: function (cmd, ui, val, args) {
      args = args || {};
      args.skip_focus = 1;
  
      this.restoreSelection();
      return this.editor.execCommand(cmd, ui, val, args);
    },
  
    /**
     * Resizes the dialog to the inner size of the window. This is needed since various browsers
     * have different border sizes on windows.
     *
     * @method resizeToInnerSize
     */
    resizeToInnerSize: function () {
      /*var self = this;
  
      // Detach it to workaround a Chrome specific bug
      // https://sourceforge.net/tracker/?func=detail&atid=635682&aid=2926339&group_id=103281
      setTimeout(function() {
        var vp = self.dom.getViewPort(window);
  
        self.editor.windowManager.resizeBy(
          self.getWindowArg('mce_width') - vp.w,
          self.getWindowArg('mce_height') - vp.h,
          self.id || window
        );
      }, 10);*/
    },
  
    /**
     * Will executed the specified string when the page has been loaded. This function
     * was added for compatibility with the 2.x branch.
     *
     * @method executeOnLoad
     * @param {String} evil String to evalutate on init.
     */
    executeOnLoad: function (evil) {
      this.onInit.add(function () {
        eval(evil);
      });
    },
  
    /**
     * Stores the current editor selection for later restoration. This can be useful since some browsers
     * looses it's selection if a control element is selected/focused inside the dialogs.
     *
     * @method storeSelection
     */
    storeSelection: function () {
      this.editor.windowManager.bookmark = tinyMCEPopup.editor.selection.getBookmark(1);
    },
  
    /**
     * Restores any stored selection. This can be useful since some browsers
     * looses it's selection if a control element is selected/focused inside the dialogs.
     *
     * @method restoreSelection
     */
    restoreSelection: function () {
      var self = tinyMCEPopup;
  
      if (!self.isWindow && tinymce.isIE) {
        self.editor.selection.moveToBookmark(self.editor.windowManager.bookmark);
      }
    },
  
    /**
     * Loads a specific dialog language pack. If you pass in plugin_url as a argument
     * when you open the window it will load the <plugin url>/langs/<code>_dlg.js lang pack file.
     *
     * @method requireLangPack
     */
    requireLangPack: function () {
      var self = this, url = self.getWindowArg('plugin_url') || self.getWindowArg('theme_url'), settings = self.editor.settings, lang;
  
      if (settings.language !== false) {
        lang = settings.language || "en";
      }
  
      if (url && lang && self.features.translate_i18n !== false && settings.language_load !== false) {
        url += '/langs/' + lang + '_dlg.js';
  
        if (!tinymce.ScriptLoader.isDone(url)) {
          document.write('<script type="text/javascript" src="' + url + '"></script>');
          tinymce.ScriptLoader.markDone(url);
        }
      }
    },
  
    /**
     * Executes a color picker on the specified element id. When the user
     * then selects a color it will be set as the value of the specified element.
     *
     * @method pickColor
     * @param {DOMEvent} e DOM event object.
     * @param {string} element_id Element id to be filled with the color value from the picker.
     */
    pickColor: function (e, element_id) {
      var el = document.getElementById(element_id), colorPickerCallback = this.editor.settings.color_picker_callback;
      if (colorPickerCallback) {
        colorPickerCallback.call(
          this.editor,
          function (value) {
            el.value = value;
            try {
              el.onchange();
            } catch (ex) {
              // Try fire event, ignore errors
            }
          },
          el.value
        );
      }
    },
  
    /**
     * Opens a filebrowser/imagebrowser this will set the output value from
     * the browser as a value on the specified element.
     *
     * @method openBrowser
     * @param {string} element_id Id of the element to set value in.
     * @param {string} type Type of browser to open image/file/flash.
     * @param {string} option Option name to get the file_broswer_callback function name from.
     */
    openBrowser: function (element_id, type) {
      tinyMCEPopup.restoreSelection();
      this.editor.execCallback('file_browser_callback', element_id, document.getElementById(element_id).value, type, window);
    },
  
    /**
     * Creates a confirm dialog. Please don't use the blocking behavior of this
     * native version use the callback method instead then it can be extended.
     *
     * @method confirm
     * @param {String} t Title for the new confirm dialog.
     * @param {function} cb Callback function to be executed after the user has selected ok or cancel.
     * @param {Object} s Optional scope to execute the callback in.
     */
    confirm: function (t, cb, s) {
      this.editor.windowManager.confirm(t, cb, s, window);
    },
  
    /**
     * Creates a alert dialog. Please don't use the blocking behavior of this
     * native version use the callback method instead then it can be extended.
     *
     * @method alert
     * @param {String} tx Title for the new alert dialog.
     * @param {function} cb Callback function to be executed after the user has selected ok.
     * @param {Object} s Optional scope to execute the callback in.
     */
    alert: function (tx, cb, s) {
      this.editor.windowManager.alert(tx, cb, s, window);
    },
  
    /**
     * Closes the current window.
     *
     * @method close
     */
    close: function () {
      var t = this;
  
      // To avoid domain relaxing issue in Opera
      function close() {
        t.editor.windowManager.close(window);
        tinymce = tinyMCE = t.editor = t.params = t.dom = t.dom.doc = null; // Cleanup
      }
  
      if (tinymce.isOpera) {
        t.getWin().setTimeout(close, 0);
      } else {
        close();
      }
    },
  
    // Internal functions
  
    _restoreSelection: function () {
      var e = window.event.srcElement;
  
      if (e.nodeName == 'INPUT' && (e.type == 'submit' || e.type == 'button')) {
        tinyMCEPopup.restoreSelection();
      }
    },
  
    /* _restoreSelection : function() {
        var e = window.event.srcElement;
  
        // If user focus a non text input or textarea
        if ((e.nodeName != 'INPUT' && e.nodeName != 'TEXTAREA') || e.type != 'text')
          tinyMCEPopup.restoreSelection();
      },*/
  
    _onDOMLoaded: function () {
      var t = tinyMCEPopup, ti = document.title, h, nv;
  
      // Translate page
      if (t.features.translate_i18n !== false) {
        var map = {
          "update": "Ok",
          "insert": "Ok",
          "cancel": "Cancel",
          "not_set": "--",
          "class_name": "Class name",
          "browse": "Browse"
        };
  
        var langCode = (tinymce.settings ? tinymce.settings : t.editor.settings).language || 'en';
        for (var key in map) {
          tinymce.i18n.data[langCode + "." + key] = tinymce.i18n.translate(map[key]);
        }
  
        h = document.body.innerHTML;
  
        // Replace a=x with a="x" in IE
        if (tinymce.isIE) {
          h = h.replace(/ (value|title|alt)=([^"][^\s>]+)/gi, ' $1="$2"');
        }
  
        document.dir = t.editor.getParam('directionality', '');
  
        if ((nv = t.editor.translate(h)) && nv != h) {
          document.body.innerHTML = nv;
        }
  
        if ((nv = t.editor.translate(ti)) && nv != ti) {
          document.title = ti = nv;
        }
      }
  
      if (!t.editor.getParam('browser_preferred_colors', false) || !t.isWindow) {
        t.dom.addClass(document.body, 'forceColors');
      }
  
      document.body.style.display = '';
  
      // Restore selection in IE when focus is placed on a non textarea or input element of the type text
      if (tinymce.Env.ie) {
        if (tinymce.Env.ie < 11) {
          document.attachEvent('onmouseup', tinyMCEPopup._restoreSelection);
  
          // Add base target element for it since it would fail with modal dialogs
          t.dom.add(t.dom.select('head')[0], 'base', { target: '_self' });
        } else {
          document.addEventListener('mouseup', tinyMCEPopup._restoreSelection, false);
        }
      }
  
      t.restoreSelection();
      t.resizeToInnerSize();
  
      // Set inline title
      if (!t.isWindow) {
        t.editor.windowManager.setTitle(window, ti);
      } else {
        window.focus();
      }
  
      if (!tinymce.isIE && !t.isWindow) {
        t.dom.bind(document, 'focus', function () {
          t.editor.windowManager.focus(t.id);
        });
      }
  
      // Patch for accessibility
      tinymce.each(t.dom.select('select'), function (e) {
        e.onkeydown = tinyMCEPopup._accessHandler;
      });
  
      // Call onInit
      // Init must be called before focus so the selection won't get lost by the focus call
      tinymce.each(t.listeners, function (o) {
        o.func.call(o.scope, t.editor);
      });
  
      // Move focus to window
      if (t.getWindowArg('mce_auto_focus', true)) {
        window.focus();
  
        // Focus element with mceFocus class
        tinymce.each(document.forms, function (f) {
          tinymce.each(f.elements, function (e) {
            if (t.dom.hasClass(e, 'mceFocus') && !e.disabled) {
              e.focus();
              return false; // Break loop
            }
          });
        });
      }
  
      document.onkeyup = tinyMCEPopup._closeWinKeyHandler;
  
      if ('textContent' in document) {
        t.uiWindow.getEl('head').firstChild.textContent = document.title;
      } else {
        t.uiWindow.getEl('head').firstChild.innerText = document.title;
      }
    },
  
    _accessHandler: function (e) {
      e = e || window.event;
  
      if (e.keyCode == 13 || e.keyCode == 32) {
        var elm = e.target || e.srcElement;
  
        if (elm.onchange) {
          elm.onchange();
        }
  
        return tinymce.dom.Event.cancel(e);
      }
    },
  
    _closeWinKeyHandler: function (e) {
      e = e || window.event;
  
      if (e.keyCode == 27) {
        tinyMCEPopup.close();
      }
    },
  
    _eventProxy: function (id) {
      return function (evt) {
        tinyMCEPopup.dom.events.callNativeHandler(id, evt);
      };
    }
  };
  
  tinyMCEPopup.init();
  
  tinymce.util.Dispatcher = function (scope) {
    this.scope = scope || this;
    this.listeners = [];
  
    this.add = function (callback, scope) {
      this.listeners.push({ cb: callback, scope: scope || this.scope });
  
      return callback;
    };
  
    this.addToTop = function (callback, scope) {
      var self = this, listener = { cb: callback, scope: scope || self.scope };
  
      // Create new listeners if addToTop is executed in a dispatch loop
      if (self.inDispatch) {
        self.listeners = [listener].concat(self.listeners);
      } else {
        self.listeners.unshift(listener);
      }
  
      return callback;
    };
  
    this.remove = function (callback) {
      var listeners = this.listeners, output = null;
  
      tinymce.each(listeners, function (listener, i) {
        if (callback == listener.cb) {
          output = listener;
          listeners.splice(i, 1);
          return false;
        }
      });
  
      return output;
    };
  
    this.dispatch = function () {
      var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
  
      self.inDispatch = true;
  
      // Needs to be a real loop since the listener count might change while looping
      // And this is also more efficient
      for (i = 0; i < listeners.length; i++) {
        listener = listeners[i];
        returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
  
        if (returnValue === false) {
          break;
        }
      }
  
      self.inDispatch = false;
  
      return returnValue;
    };
  };
  