/**
 * @module MEL_ELASTIC_UI
 */

$(document).ready(() => {
  const CONST_CLASS_THEME_BADGE_SELECTED = 'green-badge';
  //const CONST_THEME_CLASSE_CAN_RESIZE = 'mel-resize-ok';
  const CONST_SELECTOR_THEME_BADGE_SELECTED = `${CONST_JQUERY_SELECTOR_CLASS}${CONST_CLASS_THEME_BADGE_SELECTED}`;
  const CONST_THEME_PANEL_ID = 'theme-panel';
  const CONST_THEME_PANEL_CONTENTS_CLASS = 'contents';
  const CONST_SELECTOR_THEME_PANEL_CONTENT_DEFAULT = `${CONST_JQUERY_SELECTOR_ID}${CONST_THEME_PANEL_ID} ${CONST_JQUERY_SELECTOR_CLASS}${CONST_THEME_PANEL_CONTENTS_CLASS}`;
  const CONST_SELECTOR_THEME_PANEL_TAB_DIV = `${CONST_JQUERY_SELECTOR_ID}${CONST_THEME_PANEL_ID} ${CONST_JQUERY_SELECTOR_CLASS}title`;
  const CONST_THEME_BUTTON_ID = 'theme-switch-button';
  const CONST_THEME_ICON_PREFIX = 'icon-theme-';
  const CONST_CLASS_MEL_RESIZE_OK = 'mel-resize-ok';
  //const CONST_FORMAT_THEME_TITLE = '%1?/( -?(%1?&&%2?||!%1?)? )/%2?';
  const CONST_TAB_THEME_ID = 'theme-pannel-tab-theme';
  const CONST_TAB_PICTURE_ID = 'theme-pannel-tab-pictures';
  const CONST_TABS_LIST = [
    {
      display: 'Thèmes',
      id: CONST_TAB_THEME_ID,
      already: true,
    },
    {
      display: 'Images',
      id: CONST_TAB_PICTURE_ID,
      already: false,
    },
  ];

  try {
    Enumerable.from([]);
  } catch (error) {
    window.Enumerable = null;
  }

  /**
   * Classe qui gère une règle css. Elle pourra être ajouter ou supprimer.
   * @class
   * @classdesc Représente une règle CSS et est utilisé par `Mel_CSS_Style_Sheet`
   * @example new Mel_CSS_Rule('p{color:red;}')
   * @see {@link Mel_CSS_Style_Sheet}
   * @package
   */
  class Mel_CSS_Rule {
    /**
     *
     * @param {!string} rule Règle css style `p{color:red;}`
     */
    constructor(rule) {
      /**
       * Règle css
       * @type {!string}
       */
      this.rule = rule;
      /**
       * Lien de la règle dans le stylesheet associé
       * @type {?CSSStyleRule}
       */
      this.styleRule = null;
    }

    /**
     * Récupère l'id de la règle dans la stylesheet
     * @returns {number}
     */
    id() {
      return Enumerable.from(Mel_CSS_Rule.component().cssRules)
        .select((x, i) => [x, i])
        .where((x) => x[0] === this.styleRule)
        .firstOrDefault()[1];
    }

    /**
     * Intègre le style à la page web
     * @returns {Mel_CSS_Rule} Chaînage
     */
    set() {
      let id = Mel_CSS_Rule.component().insertRule(
        this.toString(),
        Mel_CSS_Rule.lastIndex(),
      );
      this.styleRule = Mel_CSS_Rule.component().cssRules[id];
      return this;
    }

    /**
     * Supprime le style de la page web
     * @returns {Mel_CSS_Rule} Chaînage
     */
    delete() {
      let id = this.id();

      if (id === 0 || !!id) Mel_CSS_Rule.component().deleteRule(id);

      return this;
    }

    /**
     * Retourne la règle css sous forme de string
     * @returns {string}
     */
    toString() {
      return this.rule;
    }

    /**
     * Récupère la feuille de style lié à cette classe.
     *
     * Si elle n'éxiste pas, elle sera créée.
     * @static
     * @returns {!CSSStyleSheet}
     */
    static component() {
      if (!Mel_CSS_Rule.component._instance) {
        let style = document.createElement('style');
        document.head.appendChild(style);
        Mel_CSS_Rule.component._instance = style.sheet;
      }
      return Mel_CSS_Rule.component._instance;
    }

    /**
     * Récupère le prochain index de la feuille de style
     * @returns {number}
     * @static
     */
    static lastIndex() {
      return Mel_CSS_Rule.component().cssRules.length;
    }

    /**
     * Génère une règle css à partir d'un tableau de règles
     * @param {Mel_CSS_Rule[]} rules
     * @returns {Mel_CSS_Rule}
     * @static
     */
    static from_array(rules = []) {
      return new Mel_CSS_Rule(rules.map((x) => x.toString()).join(''));
    }
  }

  /**
   * Classe qui gère une règle css.
   * Gère un sélécteur ainsi qu'une liste de modificateur css.
   * @class
   * @classdesc Représentation d'une règle css avec un sélecteur et c'est différentes propriétés css.
   * @extends Mel_CSS_Rule
   * @package
   * @example new Mel_CSS_Advanced_Rule('p', 'color:red', 'background-color:blue');
   */
  class Mel_CSS_Advanced_Rule extends Mel_CSS_Rule {
    /**
     *
     * @param {string} selector Selector html (exemple: p.maclass)
     * @param  {...string} rules Liste de règles html (exemple : color:red) sans les ';'
     */
    constructor(selector, ...rules) {
      super(rules);
      /**
       * Liste des propriétés css
       * @type {string[]}
       */
      // eslint-disable-next-line no-self-assign
      this.rule = this.rule;
      /**
       * Sélécteur
       * @type {string}
       */
      this.selector = selector;
    }

    /**
     * Intègre le style à la page web
     * @returns {Mel_CSS_Advanced_Rule} Chaînage
     * @override
     */
    set() {
      let id = Mel_CSS_Rule.component().insertRule(
        this.toString(),
        Mel_CSS_Rule.lastIndex(),
      );
      this.styleRule = Mel_CSS_Rule.component().cssRules[id];
      return this;
    }

    /**
     * Ajoute une propriété à la règle
     * @param {string} rule Règle à ajouter (sans les `;`)
     * @param {!boolean} force_set Si vrai, met à jour la règle dans la page web
     * @returns Chaînage
     */
    add(rule, force_set = true) {
      this.rule.push(rule);
      return this._update_rule(force_set);
    }

    /**
     * Supprime la règle.
     * @private
     * @param {!boolean} force_set Si vrai, ajoute la règle dans la page web
     * @returns {Mel_CSS_Advanced_Rule} Chaînage
     */
    _update_rule(force_set = true) {
      let id = this.id();
      if (id === 0 || !!id) {
        this.delete();

        if (force_set) this.set();
      }

      return this;
    }

    /**
     * Supprime une propriétée
     * @param {!number} index Index de la propriété à supprimer
     * @param {!boolean} force_set Si vrai, met à jour la règle dans la page web
     * @returns Chaînage
     */
    remove(index, force_set = true) {
      this.rule.splice(index, 1);
      return this._update_rule(force_set);
    }

    /**
     * Met à  jour une règle
     * @param {number} index Index de la règle à  modifier
     * @param {string} new_rule Nouvelle règle
     * @param {boolean} force_set Si vrai, met à  jour la règle dans la page web
     * @returns Chaînage
     */
    update(index, new_rule, force_set = true) {
      this.rule[index] = new_rule;
      return this._update_rule(force_set);
    }

    /**
     * Récupère les règles
     * @returns {string} Règles
     */
    rules() {
      return this.rule;
    }

    /**
     * Retourne la règle css sous forme de string
     * @returns {string}
     * @override
     */
    toString() {
      return `${this.selector}{${this.rule.join(';\r\n')}}`;
    }
  }

  /**
   * Classe qui représente une liste de règle css
   * @extends Mel_CSS_Rule
   * @class
   * @classdesc Représente une liste de règle css
   * @package
   */
  class Mel_CSS_Array_Rule extends Mel_CSS_Rule {
    constructor(rules) {
      super(rules);

      /**
       * Liste des ids des règles par rapport à leurs stylesheet
       * @type {number[]}
       */
      this.ids = [];
      /**
       * Liste des règles css
       * @type {CSSStyleRule[]}
       */
      this.styleRule = {};
    }

    /**
     * Ajoute les règles à la liste de règles
     * @override
     * @returns {Mel_CSS_Array_Rule} Chaînage
     */
    set() {
      for (let index = 0, len = this.rule.length, id; index < len; ++index) {
        const element = this.rule[index];
        id = Mel_CSS_Rule.component().insertRule(
          element.toString(),
          Mel_CSS_Rule.lastIndex(),
        );
        this.ids.push(id);
        this.styleRule[id] = Mel_CSS_Rule.component().cssRules[id];
      }
      return this;
    }

    /**
     * Récupère l'id de la règle dans la stylesheet à partir de son id dans ce tableau
     * @param {!number} id
     * @returns {number}
     */
    rule_id(id) {
      return Enumerable.from(Mel_CSS_Rule.component().cssRules)
        .select((x, i) => [x, i])
        .where((x) => x[0] === this.styleRule[id])
        .firstOrDefault()[1];
    }

    /**
     * Supprime la règle
     * @overload
     * @returns {Mel_CSS_Array_Rule} Chaînage
     */
    delete() {
      for (const iterator of this.ids) {
        Mel_CSS_Rule.component().deleteRule(this.rule_id(iterator));
      }
      this.ids.length = 0;
      this.styleRule = null;
      return this;
    }

    /**
     * Convertit en string
     * @returns {string}
     * @override
     */
    toString() {
      return this.rule.map((x) => x.toString()).join(';\r\n');
    }
  }

  /**
   * Gère les différentes règles d'une feuille de style.
   * @class
   * @classdesc Représente une feuille de style et gère les règles qui se trouvent à l'intérieur.
   * @package
   */
  class Mel_CSS_Style_Sheet {
    constructor() {
      /**
       * Dictionnaire de règle css
       * @type {Object.<string, Mel_CSS_Rule>}
       */
      this.css = {};
    }

    /**
     * Ajoute une règle à  la liste de règles
     * @private
     * @param {string} key Clé qui permettra de récupérer la règle ajouté
     * @param {Mel_CSS_Rule} rule Règle qui sera ajouté
     * @returns {Mel_CSS_Style_Sheet} Chaînage
     */
    _add(key, rule) {
      if (!this.ruleExist(key)) this.css[key] = rule.set();
      else {
        throw Mel_CSS_Style_Sheet.exception(
          `###[Mel_CSS_Style_Sheet] ${key} existe déjà  !`,
        );
      }
      return this;
    }

    /**
     * Ajoute une règle à la liste
     * @param {!string} key Clé qui permettra de récupérer la règle ajouté
     * @param {!string} rule Règle qui sera ajouté (ex : p{color:red;})
     * @returns {Mel_CSS_Style_Sheet} Chaînage
     */
    add(key, rule) {
      return this._add(key, new Mel_CSS_Rule(rule));
    }

    /**
     * Ajoute une règle à la liste
     *
     * Cette fonction permet d'ajouter une règle avec un sélécteur et une liste de propriétés.
     * @param {string} key  Clé qui permettra de récupérer la règle ajouté
     * @param {string} selector Sélécteur html
     * @param  {...string} rules Liste de propriétés (ex : 'color:red', 'background-color:blue')
     * @returns {Mel_CSS_Style_Sheet} Chaînage
     */
    addAdvanced(key, selector, ...rules) {
      return this._add(key, new Mel_CSS_Advanced_Rule(selector, ...rules));
    }

    /**
     * @typedef RuleKeyValuePair
     * @property {string} key
     * @property {string} rule
     */

    /**
     * Ajoute plusieurs règles
     * @param  {...RuleKeyValuePair} rules Règles à ajouter
     * @returns {Mel_CSS_Style_Sheet} Chaînage
     * @see {@link RuleKeyValuePair}
     */
    addRules(...rules) {
      for (const key in rules) {
        if (Object.hasOwnProperty.call(rules, key)) {
          const element = rules[key];
          this.add(element.key, element.rule);
        }
      }

      return this;
    }

    /**
     * Supprime une règle
     * @param {!string} key
     * @returns {Mel_CSS_Style_Sheet} Chaînage
     */
    remove(key) {
      if (this.ruleExist(key)) {
        this.css[key].delete();
        delete this.css[key];
      }

      return this;
    }

    /**
     * Supprime plusieurs règles
     * @param  {...string} keys
     * @returns {Mel_CSS_Style_Sheet} Chaînage
     */
    removeRules(...keys) {
      for (const key in keys) {
        if (Object.hasOwnProperty.call(keys, key)) {
          const element = keys[key];
          this.remove(element);
        }
      }

      return this;
    }

    /**
     * Vérifie si une règle éxiste
     * @param {string} key
     * @returns {boolean}
     */
    ruleExist(key) {
      return !!this.css[key];
    }

    /**
     * Récupère les clés de la classe
     * @returns {string[]}
     */
    getKeys() {
      return (
        Enumerable.from(this.css)
          .select((x) => x.key)
          .toArray() || []
      );
    }

    /**
     * Récupère la feuille de style
     * @returns {string}
     */
    getStyleSheet() {
      return (
        Enumerable.from(this.css)
          .select((x) => x.value)
          .toArray()
          .join('\r\n') || ''
      );
    }

    /**
     * Vide la feuille de style
     * @returns {Mel_CSS_Style_Sheet} Chaînage
     */
    reset() {
      for (const key in this.css) {
        if (Object.hasOwnProperty.call(this.css, key)) {
          const element = this.css[key];
          element.delete();
        }
      }

      this.css = {};
      return this;
    }

    static exception(message, ...datas) {
      return {
        message,
        datas,
      };
    }
  }

  /**
   * @class
   * @classdesc Gère la sauvegarde de données dans le stockage local pour ce fichier js
   * @static
   * @package
   * @hideconstructor
   */
  class Mel_Elastic_Storage {
    /**
     * Sauvegarde dans le stockage local
     * @param {!string} key Clé qui permettra de retrouver la données plus tard
     * @param {*} item Item à sauvegarder, si ce n'est pas un `string` il sera serialiser.
     */
    static save(key, item) {
      localStorage.setItem(
        `${Mel_Elastic_Storage.KEY}_${key}`,
        JSON.stringify(item),
      );
    }

    /**
     * Charge une donnée sauvegardé dans le stockage local
     * @param {!string} key Clé qui permet de retrouver la données
     * @param {?any} _default Si la données n'éxiste pas, cette valeur sera retournée.
     * @returns {?any}
     */
    static load(key, _default = null) {
      return (
        JSON.parse(localStorage.getItem(`${Mel_Elastic_Storage.KEY}_${key}`)) ??
        _default
      );
    }

    /**
     * Supprime une donnée dans le stockage local
     * @param {!string} key Clé qui permet de retrouver la données
     */
    static remove(key) {
      localStorage.removeItem(`${Mel_Elastic_Storage.KEY}_${key}`);
    }
  }

  /**
   * Texte qui sera ajouté devant chaque clé dans le stockage local de la classe `Mel_Elastic_Storage`
   * @static
   * @readonly
   * @type {string}
   */
  Mel_Elastic_Storage.KEY = 'MEL_ELASTIC';

  /**
   * Classe qui sert à gérer les différentes interfaces
   * @class
   * @classdesc Gère le visuel du bnum. Donne aussi des fonctions utiles lié au visuel du Bnum.
   * @package
   */
  class Mel_Elastic {
    constructor() {
      this.init().setup().update();

      $('#theme-panel #theme-pannel-tab-pictures').on('click', () => {
        this._resize_themes();
      });

      $('.tab-meltheme.mel-tab.mel-tabheader').click(() => {
        this._resize_themes();
      });
    }

    ////////////************* Inits and setups functions *************///////////

    /**
     * Initialise les différentes variables et constantes de la classe.
     * @returns {Mel_Elastic} Chaînage
     * @package
     */
    init() {
      this.set_font_size();
      const ID_THEME_CONTENT = 0;
      const ID_PICTURES_CONTENT = 1;
      /**
       * Type d'écran
       * @type {string}
       */
      this.screen_type = null;
      /**
       * Si on cache les menu ou non
       * @type {boolean}
       * @package
       */
      this._hide_main_menu =
        rcmail.env.mel_metapage_is_from_iframe || rcmail.env.extwin;
      /**
       * Feuille du style css
       * @type {Mel_CSS_Style_Sheet}
       */
      this.css_rules = new Mel_CSS_Style_Sheet();
      const tabs = this.init_theme_tabs({});
      return this.init_const()
        .init_theme($(`#theme-panel .${tabs[ID_THEME_CONTENT].id}`))
        .init_theme_pictures({
          picturePannel: `#theme-panel .${tabs[ID_PICTURES_CONTENT].id}`,
        });
    }

    /**
     * Initialise les différentes constantes de la classe.
     * @returns {Mel_Elastic} Chaînage
     */
    init_const() {
      /**
       * @type {string}
       * @constant
       * @default '¤¤¤'
       */
      this.JSON_CHAR_REPLACE;
      /**
       * @type {string}
       * @constant
       * @default '<value/>'
       */
      this.SELECT_VALUE_REPLACE;
      /**
       * Si l'url contient extwin
       * @type {boolean}
       * @constant
       */
      this.IS_EXTERNE;
      /**
       * Si l'url contient _is_from
       * @type {boolean}
       * @constant
       */
      this.FROM_INFOS;
      /**
       * @type {number}
       * @constant
       * @package
       * @default 8
       */
      this._integer;

      /**
       * @typedef Keys
       * @property {35} end
       * @property {36} home
       * @property {37} left
       * @property {38} up
       * @property {39} right
       * @property {40} down
       * @property {41} delete
       * @package
       */

      /**
       * @type {Keys}
       * @readonly
       * @package
       * @see {@link Keys}
       */
      this.keys;

      Object.defineProperty(this, 'JSON_CHAR_REPLACE', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: '¤¤¤',
      });

      Object.defineProperty(this, 'SELECT_VALUE_REPLACE', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: '<value/>',
      });

      Object.defineProperty(this, '_integer', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: 8,
      });

      Object.defineProperty(this, 'IS_EXTERNE', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: window.location.href.includes('_extwin'),
      });

      Object.defineProperty(this, 'FROM_INFOS', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: {
          key: '_is_from',
          value: 'iframe',
        },
      });

      Object.defineProperty(this, 'keys', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: {
          end: 35,
          home: 36,
          left: 37,
          up: 38,
          right: 39,
          down: 40,
          delete: 46,
        },
      });

      return this;
    }

    onthemeclick($parent) {
      let $green = $parent.find(CONST_SELECTOR_THEME_BADGE_SELECTED);

      if ($green.length === 0) {
        new mel_html2(CONST_HTML_DIV, {
          attribs: {
            class: CONST_CLASS_THEME_BADGE_SELECTED,
            title: $parent.children().attr('title'),
          },
          contents: [
            new mel_html(CONST_HTML_SPAN, {
              class: `${CONST_ICON_CHECK} ${CONST_CLASS_ABSOLUTE_CENTER}`,
            }),
          ],
        }).create($parent.css(CONST_CSS_POSITION, CONST_CSS_POSITION_RELATIVE));
      } else $green.css(CONST_CSS_DISPLAY, EMPTY_STRING);
    }

    format_text(format, ...values) {
      for (
        let index = 0,
          len = values.length,
          trueIndex = index + 1,
          exist,
          formatted = {
            index: EMPTY_STRING,
            conditional_index: EMPTY_STRING,
            conditional_item: EMPTY_STRING,
          };
        index < len;
        ++index, ++trueIndex
      ) {
        const element = values[index];
        formatted.index = `%${trueIndex}`;
        formatted.conditional_index = `${formatted.index}?`;
        formatted.conditional_item = `(${formatted.conditional_index})`;
        exist = element === 0 || !!element;

        if (format.includes(formatted.conditional_item)) {
          for (let iterator of this._find_conditionnal_text(
            format,
            formatted.conditional_item,
          )) {
            format = format.replace(
              iterator.get(),
              exist ? iterator.getFormated() : EMPTY_STRING,
            );
          }
        } else if (format.includes(formatted.conditional_index)) {
          format = format.replaceAll(
            formatted.conditional_index,
            exist ? element : EMPTY_STRING,
          );
        } else format = format.replaceAll(formatted.index, element);
      }

      return format;
    }

    *_find_conditionnal_text(format, conditionnal) {
      let splited = format.split(conditionnal);
      let text = [];
      for (let index = 0, len = splited.length; index < len; ++index) {
        const element = splited[index];
        if (element.includes('/(')) text.push(element.split('/(')[1]);
        else if (element.includes(')/')) {
          text.push(element.split(')/')[0]);

          yield {
            format: conditionnal,
            array: text,
            joined: EMPTY_STRING,
            get() {
              if (EMPTY_STRING === this.joined)
                this.joined = this.array.join(this.format);
              return this.joined;
            },
            getFormated() {
              return this.get()
                .replace('/(', EMPTY_STRING)
                .replace('/)', EMPTY_STRING)
                .replace(this.format, EMPTY_STRING);
            },
          };

          text.length = 0;
        }
      }

      splited = ((text = null), null);
    }

    /**
     * TODO FINIR
     * @param {*} format
     * @param {*} values
     */
    _format_replace_conditions(format, values) {
      const regex = /\?\(([^)]+)\)\?/gm;

      if (regex.test(format)) {
        let matches = format.match(regex);

        for (
          let index = 0,
            len = matches.length,
            element,
            break_first_loop = false;
          index < len;
          ++index
        ) {
          element = matches[index]
            .replace('?(', EMPTY_STRING)
            .replace(')?', '');
          for (let j = 0, values_len = values.length; j < values_len; ++j) {
            const value = values[j];
            const index_condition = `%${j + 1}?`;
          }

          if (break_first_loop) break;
        }
      }
    }

    /**
     * Initialise la liste des thèles
     * @param {$} $panel
     * @param {boolean} $force
     * @param {Function} callback_click
     * @param {Function[]} callback_add
     * @returns Chaîne
     */
    init_theme(
      $panel = $(CONST_SELECTOR_THEME_PANEL_CONTENT_DEFAULT),
      $force = false,
      callback_click = null,
      callback_add = null,
    ) {
      //CONSTANTES
      const THEME_BUTTON_CLASS_ACTIVE = 'activated';
      const THEME_BUTTON_ICON_INACTIVE = 'palette';
      const THEME_BUTTON_ICON_ACTIVE = 'arrow_back';
      const STYLE_MAIN_DIV_THEME = 'margin:0;padding:0 5px;';
      const STYLE_THEME_BUTTON = 'margin:0;margin-bottom:5px;';
      const STYLE_THEME_BUTTON_PARENT = 'padding:0 5px;';
      const THEME_CLASS_COL_NUMBER = 6;
      const THEME_MAIN_CLASS_COL = `${CONST_CLASS_COL}-${THEME_CLASS_COL_NUMBER}`;
      const SELECTOR_NOTIFICATION_PANEL = `${CONST_JQUERY_SELECTOR_ID}notifications-panel`;
      const SELECTOR_THEME_PANEL = `${CONST_JQUERY_SELECTOR_ID}${CONST_THEME_PANEL_ID}`;

      const DEFAULT_THEME =
        rcmail.env.mel_themes[CONST_THEME_DEFAULT_ID]?.default_theme ??
        CONST_THEME_DEFAULT_ID;

      /**
       * Thèmes additionnels, générer par le javascript
       * @type {Function[]}
       */
      let additionnalThemes = callback_add || [];
      /**
       * Div html qui contient la liste des thèmes
       * @type {mel_html2}
       */
      let html = new mel_html2(CONST_HTML_DIV, {
        attribs: { class: CONST_CLASS_ROW, style: STYLE_MAIN_DIV_THEME },
      });
      this.theme = rcmail.env.current_theme || DEFAULT_THEME;

      if (
        rcmail.env.current_theme === CONST_THEME_DEFAULT_ID &&
        DEFAULT_THEME !== CONST_THEME_DEFAULT_ID
      )
        rcmail.env.current_theme = DEFAULT_THEME;

      //Si il y a un thème courant, on l'applique à la page
      if (rcmail.env.current_theme) {
        let $html = mel_html.select_html();

        if (!rcmail.env.mel_themes[rcmail.env.current_theme]) {
          rcmail.env.current_theme = DEFAULT_THEME;
          this.theme = rcmail.env.current_theme;
        }

        let current = rcmail.env.mel_themes[rcmail.env.current_theme];

        do {
          //Gestion du multi-thème
          $html.addClass(current.class);
          current = rcmail.env.mel_themes[current.parent];
        } while (current);
      }

      //Si il y a des thèmes ou que l'on souhaite forcer une génération de thème
      if (!!rcmail.env.mel_themes || $force) {
        const themes = rcmail.env.mel_themes;
        rcmail.env.mel_themes = null; //Ne plus le rendre disponible via rcmail.env.mel_themes pour éviter les doublons
        this.themes = themes || this.themes;

        if ($panel.length !== 0) {
          /**
           * Thème traité
           * @type {mel_html}
           */
          let html_theme;
          /**
           * Thème selectionné ou non
           * @type {boolean}
           */
          let is_selected;
          let html_main_div;
          for (const iterator of Enumerable.from(this.themes)
            .concat(additionnalThemes)
            .where((x) => x.value.showed)
            .orderBy((x) => x.value.order ?? Infinity)) {
            if (
              iterator.value.id === CONST_THEME_DEFAULT_ID &&
              CONST_THEME_DEFAULT_ID !== DEFAULT_THEME
            )
              continue;

            is_selected = iterator.value.id === this.theme;
            //Création du "bouton" du thème
            html_theme = new mel_html(CONST_HTML_DIV, {
              class: `${CONST_THEME_ICON_PREFIX}${iterator.value.id} ${CONST_CLASS_SELECTABLE} ${CONST_CLASS_SELECTABLE_PICTURE_ONLY} ${CONST_CLASS_SELECTABLE_PICTURE_ONLY_WITH_BORDER} ${CONST_CLASS_MEL_RESIZE_OK} ${is_selected ? CONST_CLASS_SELECTED : EMPTY_STRING}`,
              style: `${CONST_CSS_BACKGROUND}${CONST_CSS_ASSIGN}${CONST_CSS_BACKGROUND_URL}(${iterator.value.icon})${CONST_CSS_SEPARATOR}${STYLE_THEME_BUTTON}`,
              title: `${iterator.value.displayed}${iterator.value.desc ? ` - ${iterator.value.desc}` : EMPTY_STRING}`,
              role: CONST_ATTRIB_ROLE_BUTTON,
              tabindex: 0,
              'data-name': iterator.value.id,
              'aria-pressed': iterator.value.id === this.theme,
            });

            html_theme.onkeydown.push((e) => {
              switch (e.originalEvent.code) {
                case 'Enter':
                case ' ':
                  $(e.currentTarget).click();
                  break;

                default:
                  break;
              }
            });

            //Action à faire au clique
            html_theme.onclick.push((e) => {
              let dosomething = callback_click ? callback_click(e) : true;
              if (!dosomething) return;

              e = $(e.currentTarget);
              $panel
                .find(CONST_SELECTOR_THEME_BADGE_SELECTED)
                .css(CONST_CSS_DISPLAY, CONST_CSS_NONE);
              $panel
                .find(`${CONST_JQUERY_SELECTOR_CLASS}${CONST_CLASS_SELECTABLE}`)
                .removeClass(CONST_CLASS_SELECTED)
                .attr(CONST_ATTRIB_ARIA_PRESSED, false);
              e.addClass(CONST_CLASS_SELECTED).attr(
                CONST_ATTRIB_ARIA_PRESSED,
                true,
              );

              this.onthemeclick(e.parent());

              this.update_theme(e.data('name'));
              this._update_theme_color();
            });
            html_main_div = new mel_html2(CONST_HTML_DIV, {
              attribs: {
                class: THEME_MAIN_CLASS_COL,
                style: STYLE_THEME_BUTTON_PARENT,
              },
              contents: [html_theme],
            });
            html_main_div.css('position', 'flex');

            if (iterator.value.saison) {
              html_main_div.addContent(
                new mel_html(
                  CONST_HTML_SPAN,
                  { class: 'theme-saison-date' },
                  `${iterator.value.saison.start} - ${iterator.value.saison.end}`,
                ),
              );
            }

            //Ajouter à la DIV des thèmes, le bouton de thème dans une div "col-6"
            html.addContent(html_main_div);
          }

          //Après la génération de la liste des thèmes, appliquer les effets visuels sur le thème selectionné.
          html.aftergenerate.push((generated) => {
            if ($panel.length !== 0)
              this.onthemeclick(
                generated
                  .find(`${CONST_JQUERY_SELECTOR_CLASS}${CONST_CLASS_SELECTED}`)
                  .parent(),
              );
          });
        }
      }

      let $theme_button = $(
        `${CONST_JQUERY_SELECTOR_ID}${CONST_THEME_BUTTON_ID}`,
      );

      if (top === window) {
        //Si on est en Top & que le bouton de thème existe
        if (html.count() > 1) {
          //Si il y + d'un thème, on active les propriétés du bouton
          if (!this.theme_button_activated) {
            this.theme_button_activated = true;
            $theme_button.click(($e) => {
              $e = $($e.currentTarget);

              if (!$e.hasClass(THEME_BUTTON_CLASS_ACTIVE)) {
                $e.addClass(THEME_BUTTON_CLASS_ACTIVE).html(
                  THEME_BUTTON_ICON_ACTIVE,
                );
                $e.attr({
                  title: rcmail.gettext(
                    'back_to_notifications',
                    'mel_metapage',
                  ),
                });
                $(SELECTOR_NOTIFICATION_PANEL).css(
                  CONST_CSS_DISPLAY,
                  CONST_CSS_NONE,
                );
                $(SELECTOR_THEME_PANEL).css(CONST_CSS_DISPLAY, EMPTY_STRING);
                this._resize_themes();
              } else {
                $e.removeClass(THEME_BUTTON_CLASS_ACTIVE).html(
                  THEME_BUTTON_ICON_INACTIVE,
                );
                $e.attr({
                  title: rcmail.gettext('change_theme', 'mel_metapage'),
                });
                $(SELECTOR_NOTIFICATION_PANEL).css(
                  CONST_CSS_DISPLAY,
                  EMPTY_STRING,
                );
                $(SELECTOR_THEME_PANEL).css(CONST_CSS_DISPLAY, CONST_CSS_NONE);
              }
            });
          }

          if ($panel.length !== 0) html.create($panel);
        } else {
          //Sinon, on le désactive
          $theme_button
            .addClass(CONST_ATTRIB_DISABLED)
            .attr(CONST_ATTRIB_DISABLED, CONST_ATTRIB_DISABLED);
        }
      } else if (
        rcmail.env.task === 'settings' &&
        parent.MEL_ELASTIC_UI.color_mode() !== this.color_mode()
      ) {
        if (parent.MEL_ELASTIC_UI.color_mode() === 'dark') {
          if (this.themes[this.theme]?.custom_dark_mode)
            $('html').addClass('dark-mode-custom');
          else $('html').addClass('dark-mode');
        } else
          $('html').removeClass('dark-mode-custom').removeClass('dark-mode');
      }

      if ($('#rcmfd-toggle-anims').length > 0) {
        //Affichage du bouton de thème
        if (!this.themes[this.theme].animation_class) {
          $('#rcmfd-toggle-anims')
            .addClass('disabled')
            .attr('disabled', 'disabled')
            .parent()
            .parent()
            .css({ opacity: 0, position: 'absolute' });
        } else {
          $('#rcmfd-toggle-anims')
            .removeClass('disabled')
            .removeAttr('disabled')
            .parent()
            .parent()
            .css({ opacity: '', position: '' });
        }

        let current = this.themes[this.theme];

        if (current.animation_class) {
          if (
            rcmail.env.animation_enabled ??
            current.animation_enabled_by_default
          ) {
            $('body').addClass(current.animation_class);
            $('#rcmfd-toggle-anims')[0].checked = false;
          } else $('#rcmfd-toggle-anims')[0].checked = true;
        }
      }

      return this._update_theme_color();
    }

    init_theme_tabs({
      tabs_div_selector = CONST_SELECTOR_THEME_PANEL_TAB_DIV,
      $themePannel = $(CONST_SELECTOR_THEME_PANEL_CONTENT_DEFAULT),
      tabs = CONST_TABS_LIST,
    }) {
      const CLASS_TAB = 'mel-tab';
      const NAMESPACE = 'tab-meltheme';
      const CLASS_TAB_HEADER = 'mel-tabheader';
      const CLASS_TAB_CONTENT = 'mel-tab-content';
      const TAB_ACTIVE = 'active';
      const TAB_LAST = 'last';
      const CLASS_CONTENTS = 'themescontents';

      let themeIndex = null;
      let $tabs = $(tabs_div_selector).html(EMPTY_STRING);

      if ($tabs.length === 0) return tabs;

      for (let index = 0, len = tabs.length; index < len; ++index) {
        //On génère les onglets
        const element = tabs[index];
        new mel_html(
          CONST_HTML_DIV,
          {
            id: element.id,
            class: `${NAMESPACE} ${CLASS_TAB} ${CLASS_TAB_HEADER} ${index === 0 ? TAB_ACTIVE : len - 1 === index ? TAB_LAST : EMPTY_STRING}`,
          },
          element.display,
        ).create($tabs);

        if (CONST_TAB_THEME_ID === element.id) themeIndex = index;
      }

      let $pannelParent = $themePannel.parent();
      let $contents = new mel_html2(CONST_HTML_DIV, {
        attribs: {
          class: CLASS_CONTENTS,
          tabindex: 0,
        },
      });

      //Passer les thèmes dans le panel des thèmes
      if (themeIndex !== null) {
        let $divThemes = new mel_html(CONST_HTML_DIV, {
          class: `${tabs[themeIndex].id} ${NAMESPACE} ${CLASS_TAB_CONTENT}`,
        });
        $contents.addContent($divThemes);
        $divThemes.aftergenerate.push(($generated) => {
          $themePannel.appendTo($generated);
        });
      }

      //Ajout des onglets et apnneaux supplémentaires
      for (let index = 0, len = tabs.length; index < len; ++index) {
        const element = tabs[index];
        if (!element.already) {
          $contents.addContent(
            new mel_html(CONST_HTML_DIV, {
              class: `${element.id} ${NAMESPACE} ${CLASS_TAB_CONTENT}`,
              style: `${CONST_CSS_DISPLAY}${CONST_CSS_ASSIGN}${CONST_CSS_NONE}${CONST_CSS_SEPARATOR}`,
            }),
          );
        }
      }

      $contents.create($pannelParent);

      return tabs;
    }

    init_theme_pictures({
      picturePannel = '#theme-panel .theme-pannel-tab-pictures',
      picturesToIgnore = [],
      picturesToAdd = [],
    }) {
      const CLASS_PARENT_DIV = `${CONST_CLASS_COL}-4`;
      const CLASS_PARENT_HOVER = 'hovered';
      const CUSTOM_THEME_CLASSES =
        'input-top-selectable mel-resize-ok half-resize';
      const CUSTOM_THEME_PARENT_DIV_CLASS = 'div-custom-picture px-1';
      const CUSTOM_THEME_CLASSES_PIC = 'half-resize';
      const INPUT_CLASS = 'hidden';
      const INPUT_ACCEPT = ['.png', '.jpg', '.svg', '.gif'].join(',');
      const THEME_ATTRIB_DATA_USER_PREF_ID = 'prefid';
      const THEME_ATTRIB_DATA_PATH = 'picpath';
      const THEME_ATTRIB_DATA_ID = 'picid';
      const THEME_ATTRIB_DATA_IS_CUSTOM = 'iscustom';
      const THEME_DEFAULT_CLASS = 'barup-background-color';
      const THEME_CLASSES = 'mel-selectable picture mel-resize-ok';
      const RULE_KEY = 'barup-background';
      const DATA_PIC_ID = `${CONST_ATTRIB_DATA}-${THEME_ATTRIB_DATA_ID}`;
      const DATA_PIC_PATH = `${CONST_ATTRIB_DATA}-${THEME_ATTRIB_DATA_PATH}`;
      const DATA_IS_CUSTOM = `${CONST_ATTRIB_DATA}-${THEME_ATTRIB_DATA_IS_CUSTOM}`;
      const CONST_SELECTOR_SELECTED = `${CONST_JQUERY_SELECTOR_CLASS}${CONST_CLASS_SELECTED}`;
      const STYLE = 'padding:0 5px;';
      let $pannel = $(picturePannel);

      if ($pannel.length === 0) return this;
      const pictures = rcmail.env.mel_themes_pictures;
      let $item;
      /**
       * Contient la liste des images
       * @type {mel_html2}
       */
      let $mainrow = new mel_html2(CONST_HTML_DIV, {
        attribs: {
          class: CONST_CLASS_ROW,
          style: STYLE,
        },
      });
      this.theme_selected_picture = rcmail.env.theme_selected_picture || null;

      for (const iterator of Enumerable.from(pictures)
        .where((x) => !picturesToIgnore.includes(x.key))
        .concat(picturesToAdd)
        .orderBy((x) =>
          x.value.isFirst === true
            ? Number.NEGATIVE_INFINITY
            : x.value.customOrder || Number.POSITIVE_INFINITY,
        )) {
        //Div de position
        $item = new mel_html2(CONST_HTML_DIV, {
          attribs: {
            class: CLASS_PARENT_DIV,
            style: STYLE,
          },
        }).appendTo($mainrow);
        //Vrai DIV
        $item = new mel_html2(CONST_HTML_DIV, {}).appendTo($item);

        //Si c'est on peux choisir une image custom
        if (iterator.value.userprefid) {
          /**
           * Input caché
           * @type {mel_input}
           */
          var $input = new mel_input({
            class: INPUT_CLASS,
            type: CONST_ATTRIB_TYPE_FILE,
            accept: INPUT_ACCEPT,
            'data-prefid': iterator.value.userprefid,
          });

          //MAJ de l'image
          $input.onchange.push((ev) => {
            ev = $(ev.currentTarget);
            const prefid = ev.data(THEME_ATTRIB_DATA_USER_PREF_ID);
            const file = ev[0].files[0];
            var reader = new FileReader();
            reader.onload = (e) => {
              const picture = e.target.result;
              const size =
                mel_metapage.Functions.calculateObjectSizeInMo(picture);

              if (size < 2) {
                this.update_custom_picture(picture, prefid);
                let $selectable = ev
                  .parent()
                  .parent()
                  .find(
                    `${CONST_JQUERY_SELECTOR_CLASS}${CONST_CLASS_SELECTABLE}`,
                  )
                  .removeClass(THEME_DEFAULT_CLASS)
                  .css(
                    CONST_CSS_BACKGROUND_IMAGE,
                    `${CONST_CSS_BACKGROUND_URL}(${picture})`,
                  )
                  .css(
                    CONST_CSS_BACKGROUND_SIZE,
                    CONST_CSS_BACKGROUND_SIZE_COVER,
                  )
                  .data(THEME_ATTRIB_DATA_PATH, picture)
                  .data(THEME_ATTRIB_DATA_IS_CUSTOM, true);

                if (
                  $selectable.data(THEME_ATTRIB_DATA_ID) ===
                  this.get_theme_picture()
                ) {
                  this.css_rules.remove(RULE_KEY);
                  this._add_background(picture, true);
                }
              } else {
                rcmail.display_message(
                  'Votre image est trop lourde !',
                  'error',
                );
              }
            };
            reader.readAsDataURL(file);
          });
          //Bouton qui sert à cliquer sur l'input
          var $icon = new mel_html(
            'span',
            { class: 'material-symbols-outlined' },
            'upload',
          );
          var $button = new mel_button({}, $icon.toString());
          $button.onmouseover.push((e) => {
            $(e.currentTarget).parent().addClass(CLASS_PARENT_HOVER);
          });
          $button.onmouseout.push((e) => {
            $(e.currentTarget).parent().removeClass(CLASS_PARENT_HOVER);
          });
          $button.onclick.push((e) => {
            $(e.currentTarget).parent().find(CONST_HTML_INPUT).click();
          });

          if (iterator.value.title)
            $button.setAttr('title', iterator.value.title);

          //Mise en forme
          $item.addContent(
            new mel_html2(CONST_HTML_DIV, {
              attribs: {
                class: CUSTOM_THEME_CLASSES,
              },
              contents: [$input, $button],
            }),
          );
          $item.css(CONST_CSS_DISPLAY, CONST_CSS_DISPLAY_FLEX).attribs[
            CONST_ATTRIB_CLASS
          ] =
            ($item.attribs[CONST_ATTRIB_CLASS] || EMPTY_STRING) +
            ` ${CUSTOM_THEME_PARENT_DIV_CLASS}`;
          $item = new mel_html2(CONST_HTML_DIV, {
            attribs: {
              style: `${CONST_CSS_DISPLAY}${CONST_CSS_ASSIGN}${CONST_CSS_DISPLAY_FLEX}`,
            },
          })
            .addClass(CUSTOM_THEME_CLASSES_PIC)
            .appendTo($item);
        } else if (iterator.value.title) {
          $item.setAttr('title', iterator.value.title);
        }

        $item.setAttr('tabindex', 0);
        $item.addClass('mel-focus');
        $item.onkeydown.push((e) => {
          switch (e.originalEvent.code) {
            case 'Enter':
            case ' ':
              $(e.currentTarget).click();
              break;

            default:
              break;
          }
        });

        //Ajout des classes et du fonctionnement des boutons
        $item.addClass(THEME_CLASSES);
        $item.attribs[DATA_PIC_ID] = iterator.key;

        const isCustom =
          !!iterator.value.userprefid && !!iterator.value.background;
        if (iterator.value.isThemeColor === true) {
          $item.addClass(THEME_DEFAULT_CLASS);
        } else {
          $item.attribs[DATA_PIC_PATH] = iterator.value.background;
          $item
            .css(
              CONST_CSS_BACKGROUND_IMAGE,
              `${CONST_CSS_BACKGROUND_URL}(${isCustom ? iterator.value.background : iterator.value.view})`,
            )
            .css(CONST_CSS_BACKGROUND_SIZE, CONST_CSS_BACKGROUND_SIZE_COVER)
            .css(
              CONST_CSS_BACKGROUND_POSITION,
              CONST_CSS_BACKGROUND_POSITION_CENTER,
            );
          if (isCustom) $item.attribs[DATA_IS_CUSTOM] = true;
        }

        $item.onclick.push((e) => {
          e = $(e.currentTarget);
          $pannel
            .find(CONST_SELECTOR_THEME_BADGE_SELECTED)
            .css(CONST_CSS_DISPLAY, CONST_CSS_NONE);
          $pannel
            .find(CONST_SELECTOR_SELECTED)
            .removeClass(CONST_CLASS_SELECTED);
          e.addClass(CONST_CLASS_SELECTED);
          const data = e.data(THEME_ATTRIB_DATA_PATH);
          const isCustom = e.data(THEME_ATTRIB_DATA_IS_CUSTOM);

          try {
            this.css_rules.remove(RULE_KEY);
          } catch (error) {}

          if (data) {
            this._add_background(data, isCustom);
            $(CONST_HTML_HTML).addClass(CONST_CLASS_HTML_HAS_PICTURE);
          } else {
            $(CONST_HTML_HTML).removeClass(CONST_CLASS_HTML_HAS_PICTURE);
          }

          this.onthemeclick(e.parent());

          this.set_theme_picture(e.data(THEME_ATTRIB_DATA_ID));
        });

        //Effectuer les actions de séléction si il s'agit du thèmes en cours
        if (
          this.theme_selected_picture === null ||
          iterator.key === this.theme_selected_picture
        ) {
          $item.addClass(CONST_CLASS_SELECTED);
          $item.parent.aftergenerate.push(($generated) => {
            this.onthemeclick($generated);
          });

          if (
            this.theme_selected_picture === null ||
            !iterator.value.background
          ) {
            this.theme_selected_picture = iterator.key;
            this.css_rules.remove(RULE_KEY);
          } else {
            this._add_background(iterator.value.background, isCustom);
            $(CONST_HTML_HTML).addClass(CONST_CLASS_HTML_HAS_PICTURE);
          }
        }
      } //FIN FOR

      $mainrow.create($pannel);

      return this;
    }

    _resize_themes() {
      const size =
        $('#theme-panel .container').height() +
        $('#groupoptions-user .container.menu').height() +
        60 +
        30;
      $('#theme-panel .themescontents').css(
        'max-height',
        `${window.innerHeight - size}px`,
      );
    }

    _add_background(path, isRule) {
      this.css_rules.addAdvanced(
        'barup-background',
        '.barup',
        `background-image:url(${isRule ? path : (window.location.origin + window.location.pathname + path.replace('./', '/')).replaceAll('://', '¤').replaceAll('//', '/').replaceAll('¤', '://')})!important`,
        'background-size: cover !important',
        'background-position: center !important',
      );
      return this;
    }

    async update_custom_picture(datas, pref) {
      await mel_metapage.Functions.post(
        mel_metapage.Functions.url(
          'mel_metapage',
          'plugin.update_custom_picture',
        ),
        {
          _datas: datas,
          _prefid: pref,
        },
        (datas) => {
          const VALIDATION = 'ok';
          if (VALIDATION === datas) {
            rcmail.display_message(
              'Image chargée avec succès !',
              'confirmation',
            );
          }
        },
      );
    }

    async set_theme_picture(id) {
      this.theme_selected_picture = id || null;

      if (this.theme_selected_picture !== null) {
        await mel_metapage.Functions.post(
          mel_metapage.Functions.url(
            'mel_metapage',
            'plugin.update_theme_picture',
          ),
          {
            _id: id,
          },
          (datas) => {
            const VALIDATION = 'ok';
            if (VALIDATION === datas) {
              rcmail.display_message(
                'Image changée avec succès !',
                'confirmation',
              );
            }
          },
        );
      }
    }

    get_theme_picture() {
      return this.theme_selected_picture;
    }

    /**
     * Différentes actions à  faire après l'initialisation.
     * @returns {Mel_Elastic} Chaînage
     */
    setup() {
      if (rcmail === undefined) return this;

      return this.setup_html()
        .setup_nav()
        .setup_tasks()
        .setup_search()
        .setup_other_apps()
        .setup_calendar()
        .setup_settings();
    }

    /**
     * Met en place l'apparence et le fonctionnel de la barre de navigation principale
     * @returns {Mel_Elastic} Chaînage
     */
    setup_nav() {
      if (parent === window) {
        //La sidebar étant en position absolue, on décale certaines divs pour que l'affichage soit correct.
        const width = '60px';

        if (!this.IS_EXTERNE && $('#layout-sidebar').length > 0)
          $('#layout-sidebar').css('margin-left', width);
        else if (!this.IS_EXTERNE && $('#layout-content').length > 0)
          $('#layout-content').css('margin-left', width);
      }

      let $taskmenu = $('#taskmenu');
      if ($taskmenu.length > 0) {
        if (this._hide_main_menu) {
          $('#layout-menu').remove();
        } else {
          //On met dans l'ordre les différents boutons de la barre de navigation principale
          let array = [];
          $taskmenu.find('a').each((i, e) => {
            e = $(e);

            if (e.parent().hasClass('special-buttons')) return;

            // if (e.hasClass('settings')) {
            //     e.css('display', 'none');
            //     return;
            // }

            const order = e.css('order');
            const tmp = e.removeAttr('title')[0].outerHTML;
            e.remove();
            e = null;
            array.push({
              order: order,
              item: $(tmp).keypress((event) => {
                if (event.originalEvent.keyCode === 32)
                  $(event.currentTarget).click();
              }),
            });
          });

          $taskmenu.append('<ul class="list-unstyled"></ul>');

          let li;
          Enumerable.from(array)
            .orderBy((x) => parseInt(x.order))
            .forEach((e) => {
              li = $(
                `<li style="display:block" data-order="${e.order}" class="button-${this.get_nav_button_main_class(e.item[0])}"></li>`,
              );
              e.item.attr('data-order', e.order);
              e = e.item;
              if (
                e.css('display') === 'none' ||
                e.hasClass('hidden') ||
                e.hasClass('compose')
              )
                li.css('display', 'none');

              e.appendTo(li);
              li.appendTo($('#taskmenu ul'));
            });

          li = null;

          let $taskMenu = $('#taskmenu .menu-last-frame').attr(
            'tabIndex',
            '-1',
          );

          if (rcmail.env.menu_last_frame_enabled !== true)
            $taskMenu.parent().css(CONST_CSS_DISPLAY, CONST_CSS_NONE);

          //On supprime le stockage si on y a pas accès.
          if (!mel_metapage.Functions.stockage.is_stockage_active())
            $('#taskmenu .stockage').parent().remove();
        }
      }

      return this.update_main_nav_meca();
    }

    update_main_nav_meca() {
      let $menu = $('#layout-menu');
      if ($menu.length > 0) {
        if (!rcmail.env.main_nav_can_deploy) {
          $menu
            .addClass('main-nav-cannot-deploy')
            .find('a')
            .each((index, element) => {
              element = $(element);
              element.attr('title', element.find('span').first().text());
            });
        } else {
          $menu
            .removeClass('main-nav-cannot-deploy')
            .find('a')
            .each((index, element) => {
              element = $(element);
              element.removeAttr('title');
            });
        }
      }

      $menu = $('#otherapps');
      if ($menu.length > 0) {
        if (!rcmail.env.main_nav_can_deploy) {
          $menu.find('a').each((index, element) => {
            element = $(element);
            element.attr('title', element.find('span').first().text());
          });
        } else {
          $menu.find('a').each((index, element) => {
            element = $(element);
            element.removeAttr('title');
          });
        }
      }

      $menu = null;

      return this;
    }

    /**
     * Met en place les actions pour les tâches qui en ont besoins.
     * @returns {Mel_Elastic} Chaînage
     */
    setup_tasks() {
      try {
        //Gérer le texte du bouton de login.
        if (rcmail.env.task == 'login' || rcmail.env.task == 'logout')
          $('#rcmloginsubmit').val('Se connecter').html('Se connecter');

        //Revenir à la liste des mails sans rafraîchir la page.
        if (
          rcmail.env.task === 'mail' &&
          rcmail.env.action === 'show' &&
          !this.IS_EXTERNE
        ) {
          $(
            '<li role="menuitem"><a class="icon-mel-close" href="#back" title="Revenir aux mails"><span style="font-family:Roboto,sans-serif" class="inner">Retour</span></a></li>',
          )
            .on('click', () => {
              window.location.href = this.url('mail');
            })
            .prependTo($('#toolbar-menu'));
        }
      } catch (error) {}

      try {
        //Gérer le changement de mot de passe dans le login.
        $('#login-form p.formbuttons a').click(() => {
          event.preventDefault();
          window.location.href = window.location.href.replaceAll(
            '/changepassword/index.php',
            '',
          );
        });
      } catch (error) {
        console.error(error);
      }

      return this.setup_mails().setup_adressbook();
    }

    /**
     * Met en place les mails.
     * @returns {Mel_Elastic} Chaînage
     */
    setup_mails() {
      if (rcmail.env.task === 'mail' && $('#mailsearchform').length > 0) {
        $('#mailsearchform')
          .parent()
          .parent()
          .find('.unread')
          .on('click', (e) => {
            if (!$(e.target).hasClass('selected'))
              $(e.target).attr('title', 'Afficher tout les courriels');
            else $(e.target).attr('title', rcmail.gettext('showunread'));
          });
      }

      if (rcmail.env.task === 'mail') {
        $('.header-content a.headers').remove();

        $('.task-mail #quotadisplay')
          .prepend(`<span id='stockage-space-text'>Espace de stockage</span><p style="flex-basis: 100%;
                height: 0;
                margin: 0;"></p>`);

        //Gérer la prévisu des mails.
        rcmail.show_contentframe_parent = rcmail.show_contentframe;
        rcmail.show_contentframe = function (show) {
          //On ne fait rien si en mode small ou phone
          if (
            show &&
            ($('html').hasClass('layout-small') ||
              $('html').hasClass('layout-phone'))
          ) {
            rcmail.show_contentframe_parent(show);
            $('#layout-content')
              .css('display', '')
              .removeClass('layout-hidden');
            return;
          }

          if (rcmail.env.is_from_scroll === true)
            delete rcmail.env.is_from_scroll;
          else if ($('#layout-list').hasClass('initial') && show) {
            rcmail.triggerEvent('ui.open-mail-visu');
            //Mise en place du système
            $('#layout-content')
              .css('display', '')
              .removeClass('hidden layout-hidden');
            $('#layout-list').removeClass('initial');

            //On réduit la recherche au besoin
            $('#mailsearchlist')
              .addClass('hoverable')
              .on('mouseover focusin', () => {
                if (
                  $('#mailsearchlist').hasClass('hoverable') &&
                  !$('#layout-list').hasClass('full')
                )
                  $('#mailsearchlist').removeClass('hoverable');
              })
              .on('mouseleave focusout', () => {
                if (document.activeElement === $('#mailsearchform')[0]) return;

                if (
                  !$('#mailsearchlist').hasClass('hoverable') &&
                  !$('#layout-list').hasClass('full')
                )
                  $('#mailsearchlist').addClass('hoverable');
              })
              .find('#mailsearchform')
              .on('focusout', (e) => {
                if (e.relatedTarget === $('#mailsearchlist .reset')[0]) return;

                if (
                  !$('#mailsearchlist').hasClass('hoverable') &&
                  !$('#layout-list').hasClass('full')
                )
                  $('#mailsearchlist').addClass('hoverable');
              });

            let $back = `<li role="menuitem" class="parent-close-visu">
                            <a  onclick="return rcmail.command('close-mail-visu','',this,event)"  class="close-visu"  role="button" href="#" ><span class="inner">Fermer</span></a>
                        </li>`;

            $('#layout-content ul#toolbar-menu').prepend($back);

            $('#layout-content .header .move.simplified').css(
              'display',
              'none',
            );

            //Fermer la prévisu
            rcmail.register_command(
              'close-mail-visu',
              (args) => {
                const { ignore_select } = args;

                if (!ignore_select)
                  $('#messagelist-content .selected')
                    .removeClass('selected')
                    .removeClass('focused')
                    .removeAttr('aria-selected')
                    .find('.selection input')
                    .click();

                $('#layout-content')
                  .css('display', 'none')
                  .addClass('hidden layout-hidden');
                $('#layout-list').addClass('full');

                $('#mailsearchlist').removeClass('hoverable');

                rcmail.triggerEvent('ui.close-mail-visu');
              },
              true,
            );
          } else if ($('#layout-list').hasClass('full') && show) {
            //Afficher ou fermer
            $('#layout-content')
              .css('display', '')
              .removeClass('hidden')
              .removeClass('layout-hidden');
            $('#layout-list').removeClass('full');

            $('#mailsearchlist').addClass('hoverable');

            rcmail.triggerEvent('ui.open-mail-visu');
          }

          rcmail.show_contentframe_parent(show);

          let hidden = $(
            '#layout-content .header #toolbar-menu .hidden-item-mt a',
          );
          if (hidden.length > 0) {
            hidden.each(async (i, e) => {
              let a = $(`#message-menu #${e.id}`);
              if (a.hasClass('disabled') && !$(e).hasClass('disabled'))
                a.removeClass('disabled');
              else if (!a.hasClass('disabled') && $(e).hasClass('disabled'))
                a.addClass('disabled');
            });
          }
        }; /////////

        if (rcmail.env.action === 'compose') {
          //Ajouter "Envoyer" en haut.
          $('.btn.btn-primary.send').remove();
          $('#toolbar-menu').prepend(`
                        <li role="menuitem">
                            <a class="send" href=# onclick="return rcmail.command('send','',this,event)"><span class="inner">Envoyer</span></a>
                        </li>
                    `);
        } else if (rcmail.env.action === '' || rcmail.env.action === 'index') {
          rcmail.addEventListener('init', () => {
            let i = 7;
            let $aria = null;

            for (i; ($aria = $(`li[aria-level="${i}"] a`)).length > 0; ++i) {
              $aria.css('padding-left', `${i * 1.5 - 2}em`);
            }
          });

          //Gestion de l'apparence et de l'affichage des mails.
          // add roundcube events
          rcmail.addEventListener('insertrow', function (event) {
            var rowobj = $(event.row.obj);
            rowobj.find('.selection input').on('change', () => {
              let hidden = $(
                '#layout-content .header #toolbar-menu .hidden-item-mt a',
              );
              if (hidden.length > 0) {
                hidden.each(async (i, e) => {
                  let a = $(`#message-menu #${e.id}`);
                  if (a.hasClass('disabled') && !$(e).hasClass('disabled'))
                    a.removeClass('disabled');
                  else if (!a.hasClass('disabled') && $(e).hasClass('disabled'))
                    a.addClass('disabled');
                });
              }
            });
          });

          $('#mailsearchlist .searchbar .reset').click(() => {
            $('#mailsearchlist .searchbar .flag').removeClass('selected');
          });
          $('#mailsearchlist .searchbar .unread')
            .click(() => {
              $('#mailsearchlist .searchbar .flag').removeClass('selected');
            })
            .before(
              $(
                '<a class="button flag" href="#" role="button" title="Afficher les courriels suivis"></a>',
              ).click((e) => {
                const item = $(e.target);

                if (item.hasClass('selected')) item.removeClass('selected');
                else item.addClass('selected');

                $(rcmail.gui_objects.search_filter).val(
                  !item.is('.selected') ? 'ALL' : 'FLAGGED',
                );
                rcmail.command('search');
              }),
            );

          $('#toolbar-list-menu .compose')
            .parent()
            .prependTo($('#toolbar-list-menu .compose').parent().parent());

          //Ajout de "plus"
          $('#toolbar-list-menu').append(
            $(`
                        <li id="limelmailplusmenu" class="marked" style="display:none" role="menuitem">
                        
                        </li>
                    `).append($('#melplusmails').css('display', '')),
          );

          let test = new ResizeObserver(() => {
            let value = 0;
            {
              let iterator;
              for (iterator of $('#toolbar-list-menu li')) {
                iterator = $(iterator);
                if (!iterator.hasClass('marked')) {
                  value += iterator.width();
                }
              }
              iterator = null;
              const additional_width = $(
                '.header .toolbar-button.refresh',
              ).width();
              value += additional_width + additional_width / 2.0;
            }
            const max =
              $('#layout-list').width() - $('#mail-search-border').width(); //mailConfig === null || mailConfig["mel-icon-size"] === rcmail.gettext("normal", "mel_metapage") ? 370 : 347; //370;

            if (value > max) {
              $('#toolbar-list-menu li')
                .css('display', 'none')
                .find('.compose')
                .parent()
                .css('display', '');
              $('#limelmailplusmenu').css('display', '');
            } else {
              $('#toolbar-list-menu li').css('display', '');
              $('#limelmailplusmenu').css('display', 'none');
            }

            if (
              !$('html').hasClass('touch') &&
              $('#toolbar-list-menu').hasClass('hidden')
            ) {
              $('#toolbar-list-menu')
                .removeClass('hidden')
                .removeAttr('aria-hidden');
            }

            if (
              rcmail.env.search_initialized !== true &&
              window.innerWidth < 410 &&
              window.innerWidth > 0
            ) {
              console.log(
                'hoverable observer',
                rcmail.env.search_initialized,
                window.innerWidth,
              );
              rcmail.env.search_initialized = true;
              $('#mailsearchlist')
                .addClass('hoverable')
                .click((e) => {
                  //console.log("e", $("#mailsearchlist").hasClass("stopclick"));
                  if ($('#mailsearchlist').hasClass('stopclick')) {
                    $('#mailsearchlist').removeClass('stopclick');

                    if (!$('#mailsearchlist').hasClass('hoverable')) {
                      $('#mailsearchlist').addClass('hoverable');
                      return;
                    }
                  }

                  if (window.innerWidth < 410) {
                    $('#mailsearchlist').removeClass('hoverable');
                    $('#mailsearchlist input').focus();
                  }
                })
                .find('input')
                .on('focusout', (e) => {
                  if (window.innerWidth < 410) {
                    console.log('hoverable observer', e, window.innerWidth);
                    let parent =
                      e.relatedTarget === null ? null : $(e.relatedTarget); //e.originalEvent === null || e.originalEvent.explicitOriginalTarget === null ? null : $(e.originalEvent.explicitOriginalTarget);
                    while (
                      parent !== null &&
                      parent.attr('id') != 'mailsearchlist' &&
                      parent[0].nodeName != 'BODY' &&
                      !parent.hasClass('icon-mel-search')
                    ) {
                      parent = parent.parent();
                    }

                    if (
                      parent === null ||
                      parent.hasClass('icon-mel-search') ||
                      parent[0].nodeName === 'BODY'
                    ) {
                      if (!!parent && parent.hasClass('icon-mel-search'))
                        $('#mailsearchlist').addClass('stopclick');
                      else {
                        $('#mailsearchlist').addClass('hoverable');
                      }
                      document.activeElement.blur();
                    }
                  }
                });
            }
          });
          test.observe($('#layout-list')[0]);

          this.update_mail_css({});

          //message_extwin @Rotomeca
          const alias_mel_rcmail_show_message = rcmail.show_message;
          rcmail.show_message = function (id, safe, preview) {
            const openInPopUp =
              !preview && !this.env.message_extwin && !this.env.extwin;

            if (openInPopUp) {
              let url = this.params_from_uid(id, {
                _caps: this.browser_capabilities(),
              });

              if (safe) url._safe = 1;
              url._extwin = 1;
              url = this.url('show', url);
              new Windows_Like_PopUp(top.$('body'), {
                title: "Ouverture d'un mail...",
                content: `${MEL_ELASTIC_UI.create_loader('rotomecamelloader', true)[0].outerHTML}<iframe title="Ouverture d'un mail" src="${url + '&_is_from=iframe'}" style="width:100%;height:calc(100%);display:none;"/>`,
                afterCreatingContent($html, box, popup) {
                  box.content.find('iframe').on('load', () => {
                    let $iframe = box.content.find('iframe');
                    const title = $iframe[0].contentDocument.title;
                    const delete_action = function delete_action() {
                      try {
                        rcmail.command('checkmail');
                      } catch (error) {}
                      box.close.click();
                    };

                    box.title.find('h3').html(title);
                    box.content.find('#rotomecamelloader').remove();
                    $iframe.css('display', '');
                    $iframe[0].contentWindow.Windows_Like_PopUp =
                      Windows_Like_PopUp;
                    $iframe[0].contentWindow.rcmail.env.is_in_popup_mail = true;
                    $iframe[0].contentWindow.rcmail.addEventListener(
                      'message.deleted',
                      async (event) => {
                        delete_action();
                      },
                    );
                    $iframe[0].contentWindow.rcmail.addEventListener(
                      'message.moved',
                      async (event) => {
                        delete_action();
                      },
                    );
                    $iframe[0].contentWindow.rcmail.addEventListener(
                      'message.junk',
                      async (event) => {
                        delete_action();
                      },
                    );

                    if (popup._minified_header) {
                      popup._minified_header
                        .find('h3')
                        .html(
                          title.length > 20
                            ? title.slice(0, 20) + '...'
                            : title,
                        );
                    }
                  });

                  // let spinner = box.content.find(".spinner-border").css("width", '30%');
                  // let spinner_size = Math.round(spinner.width());
                  // spinner.css("width", `${spinner_size}px`)
                  // .css('height', `${spinner_size}px`).css('margin', '15px');
                },
                width: 'calc(100% - 60px)',
                height: 'calc(100% - 60px)',
                context: top,
                fullscreen: true,
              });
            } else alias_mel_rcmail_show_message.call(this, id, safe, preview);
          };
        } else if (
          rcmail.env.action === 'preview' ||
          rcmail.env.action === 'show'
        ) {
          $('#message-header .headers-table td').each((i, e) => {
            switch ($(e).html()) {
              case 'De':
                $(e).parent().addClass('mel-header-from');
                break;
              case 'Date':
                $(e).parent().addClass('mel-header-date');
                break;

              default:
                break;
            }
          });

          $('#mel-message-details').click(() => {
            const plus = 'icon-mel-plus';
            const minus = 'icon-mel-minus';
            let querry = $('#mel-message-details .mel-d-icon');

            if (querry.hasClass(minus)) {
              querry.removeClass(minus).addClass(plus);
              $('.message-partheaders').hide();
            } else {
              querry.removeClass(plus).addClass(minus);
              $('.message-partheaders').show();
            }
          });
        }

        const alias_mel_rcmail_permanently_remove_messages =
          rcmail.permanently_remove_messages;
        rcmail.permanently_remove_messages = function () {
          alias_mel_rcmail_permanently_remove_messages.call(this);
          rcmail.triggerEvent('message.deleted');
        };

        const alias_mel_rcmail_move_messages = rcmail.move_messages;
        rcmail.move_messages = function (mbox, event, uids) {
          alias_mel_rcmail_move_messages.call(this, mbox, event, uids);
          rcmail.triggerEvent('message.moved', { mbox, uids, e: event });
        };

        rcmail.addEventListener('responsebeforeplugin.markasjunk.junk', () => {
          rcmail.triggerEvent('message.junk', { junk: true });
        });

        rcmail.addEventListener(
          'responsebeforeplugin.markasjunk.not_junk',
          () => {
            rcmail.triggerEvent('message.junk', { junk: false });
          },
        );
      }

      return this.setup_mails_classes().setup_compose();
    }

    /**
     * Met en place les actions relative aux mails
     * @returns
     */
    setup_mails_classes() {
      if (rcmail.env.task === 'mail') {
        let action = ($querry, _class) => {
          if (!$querry.attr('id').includes('clone')) {
            $querry.addClass('mel').parent().addClass(`mel-li-${_class}`);
          }
        };

        let testing = (e, _class) => {
          const array = Enumerable.from(e.classList).toArray() || [];
          const count = array.length;
          if (count === 1) action($(e), _class);
          else if (
            count === 2 &&
            array.includes(_class) &&
            (array.includes('disabled') || array.includes('active'))
          ) {
            action($(e), _class);
          }
        };

        //Répondre
        $('#toolbar-menu .reply').each((i, e) => {
          testing(e, 'reply');
        });

        //transfert
        $('#toolbar-menu .forward').each((i, e) => {
          testing(e, 'forward');
        });
      }

      return this;
    }

    /**
     * Met en place la rédaction d'un mail.
     * @returns {Mel_Elastic} Chaînage
     */
    setup_compose() {
      //Si on se trouve au bon endroit.
      if (rcmail.env.task === 'mail' && rcmail.env.action === 'compose') {
        const key = 'bureau_num_last_fields';
        //Sauvegarder le champs
        $('a.input-group-text.icon.add').click(() => {
          $('#headers-menu ul.menu a.recipient.active').each((i, e) => {
            e = $(e);
            e.click(() => {
              let storage = Mel_Elastic_Storage.load(key, []);

              const field = e.data('target');
              if (!storage.includes(field)) {
                storage.push(field);
                Mel_Elastic_Storage.save(key, storage);
              }
            });
          });
        });

        //Supprimer le champ
        $('a.input-group-text.icon.delete').each((i, e) => {
          e = $(e);
          e.click(() => {
            let $input = e.parent().parent().find('textarea');

            if ($input.length === 0) $input = e.parent().parent().find('input');

            const field = $input.attr('id').replace('_', '');
            let storage = Mel_Elastic_Storage.load(key, []);

            if (storage.includes(field)) {
              storage =
                Enumerable.from(storage)
                  .where((x) => x !== field)
                  .toArray() || [];

              if (storage.length === 0) Mel_Elastic_Storage.remove(key);
              else Mel_Elastic_Storage.save(key, storage);
            }
          });
        });

        const storage = Mel_Elastic_Storage.load(key, []);

        //Afficher les champs
        if (storage !== null && storage.length > 0) {
          for (let index = 0; index < storage.length; ++index) {
            const element = storage[index];
            $(`#compose_${element}`).removeClass('hidden');
          }
        }

        //Suppression du "commenter"
        $('#mel-comment-mail').css('display', 'none');

        //Gestion des options
        if (
          rcmail.env.compose_option !== undefined &&
          rcmail.env.compose_option !== null &&
          rcmail.env.compose_option !== ''
        ) {
          switch (rcmail.env.compose_option) {
            case 'empty':
              $('#compose-subject').val('');
              try {
                $('#compose-subject').change();
              } catch (error) {}

              // rcmail.addEventListener('editor-load', () => {
              //     if (rcmail.env.editor_emptied !== true)
              //     {
              //         rcmail.editor.set_content("");
              //         rcmail.env.editor_emptied = true;
              //     }
              // });

              break;

            default:
              break;
          }
        }

        if (top !== window) {
          $('#toolbar-menu a.send').removeAttr('href');
        }

        if (window === top && rcmail.env.extwin !== 1) {
          let $tmp_quit = $(
            '<a style="margin-right:15px" class="back" href="#" >Messages</a>',
          ).click(() => {
            rcmail.env.action = 'index';
            rcmail.action = 'index';
            rcmail.compose_skip_unsavedcheck = true;
            const _$ = top.$;

            _$('.task-mail.action-compose #taskmenu li a')
              .removeClass('disabled')
              .removeAttr('disabled');
            _$('.barup button').removeClass('disabled').removeAttr('disabled');
            _$('#barup-search-input')
              .removeClass('disabled')
              .removeAttr('disabled');
            _$('#user-up-popup').css('pointer-events', 'all');
            _$('.tiny-rocket-chat')
              .removeClass('disabled')
              .removeAttr('disabled');
            _$('.task-mail.action-compose #taskmenu li a.menu-last-frame')
              .addClass('disabled')
              .attr('disabled', 'disabled');

            if (_$('iframe.mail-frame').length > 0)
              mel_metapage.Functions.change_frame('mail', true, true);
            else if (_$('.mail-frame').length > 0) {
              _$('.mail-frame').remove();
              mel_metapage.Functions.change_frame('mail', true, true);
            } else mel_metapage.Functions.change_frame('mail', true, true);

            _$('body').removeClass('action-compose').addClass('action-none');
          });

          $('ul#toolbar-menu').prepend(
            $(`<li role="menuitem">
                    
                    </li>`).append($tmp_quit),
          );

          $('.task-mail.action-compose #taskmenu li a');
          z.addClass('disabled').attr('disabled', 'disabled');
          $('.barup button').addClass('disabled').attr('disabled', 'disabled');
          $('#barup-search-input')
            .addClass('disabled')
            .attr('disabled', 'disabled');
          $('#user-up-popup').css('pointer-events', 'none');
          const interval = setInterval(() => {
            if (!$('.tiny-rocket-chat').hasClass('disabled')) {
              $('.tiny-rocket-chat')
                .addClass('disabled')
                .attr('disabled', 'disabled');
            } else clearInterval(interval);
          }, 100);

          $('#layout-content').css('margin-left', '60px');
        }
      }

      rcmail.addEventListener('fileappended', (file) => {
        //console.log("file", file);
        if (file.attachment.html.includes('class="delete"')) {
          $('a.delete').html('');
        }
      });

      const alias_mel_rcmail_open_compose_step = rcmail.open_compose_step;
      rcmail.open_compose_step = function (p) {
        if (parent !== window) return parent.rcmail.open_compose_step(p);

        if (window.create_popUp !== undefined) create_popUp.close();

        if (
          (rcmail.env.compose_extwin && !rcmail.env.extwin) ||
          rcmail.env.extwin
        ) {
          if (rcmail.env.extwin && rcmail.env.is_in_popup_mail === true) {
            var last_ext_win = rcmail.env.extwin;
            rcmail.env.extwin = 0;

            if (!rcmail.env.compose_extwin) {
              rcmail.open_compose_step(p);
              rcmail.env.extwin = last_ext_win;
              return;
            }
          }

          alias_mel_rcmail_open_compose_step.call(this, p);

          if (last_ext_win !== undefined) rcmail.env.extwin = last_ext_win;
        } else {
          p['_extwin'] = 1;
          const url = rcmail.url('mail/compose', p);
          let config = {
            title: 'Rédaction',
            content: `${MEL_ELASTIC_UI.create_loader('rotomecamelloader', true)[0].outerHTML}<iframe class="sr-only" title="Rédaction d'un mail" src="${url + '&_is_from=iframe'}" style="width:100%;height:calc(100%);"/>`,
            onclose(popup) {
              if (popup.box.close.data('force') == '1') return;
              if (
                popup.waiting_save !== true &&
                confirm('Voulez-vous sauvegarder le message comme brouillon ?')
              ) {
                popup.waiting_save = true;
                popup.box.close
                  .addClass('disabled')
                  .attr('disabled', 'disabled');
                popup.box.minifier
                  .addClass('disabled')
                  .attr('disabled', 'disabled');
                popup.box.title
                  .find('h3')
                  .html(
                    popup.box.title
                      .find('h3')
                      .html()
                      .replace('Rédaction : ', 'Sauvegarde de : '),
                  );

                if (
                  popup.box.minifier
                    .find('span')
                    .hasClass(popup.settings.icon_minify)
                )
                  popup.minify();

                let frame_context =
                  popup.box.content.find('iframe')[0].contentWindow;

                const alias_mel_rcmail_compose_field_hash =
                  frame_context.rcmail.compose_field_hash;
                frame_context.rcmail.compose_field_hash = function (save) {
                  alias_mel_rcmail_compose_field_hash.call(this, save);
                  popup.close();
                };

                frame_context.rcmail.command('savedraft');

                return 'break';
              }
            },
            afterCreatingContent($html, box, popup) {
              box.close.data('force', '1');
              box.content.find('iframe').on('load', () => {
                box.close.data('force', '');
                let frame_context = box.content.find('iframe')[0].contentWindow;

                frame_context.popup_action = (callback) => {
                  callback($html, box);
                };

                frame_context
                  .$('#layout-sidebar .scroller')
                  .css('max-height', '100%');

                frame_context
                  .$('#compose-subject')
                  .on('input', (e) => {
                    box.title
                      .find('h3')
                      .html('Rédaction : ' + $(e.currentTarget).val());
                  })
                  .on('change', (e) => {
                    box.title
                      .find('h3')
                      .html('Rédaction : ' + $(e.currentTarget).val());
                  });

                box.content.find('#rotomecamelloader').css('display', 'none');
                box.content.find('iframe').removeClass('sr-only');
                const obj = frame_context.$('#compose-subject').val();

                if ((obj || '') !== '')
                  box.title.find('h3').html('Rédaction : ' + obj);

                frame_context.rcmail.addEventListener(
                  'message_submited',
                  async (args) => {
                    if (args.draft !== true) {
                      box.get.find('iframe').css('display', 'none');
                      box.close
                        .addClass('disabled')
                        .attr('disabled', 'disabled');
                      box.title
                        .find('h3')
                        .html(
                          box.title
                            .find('h3')
                            .html()
                            .replace('Rédaction : ', 'Envoi de : '),
                        );
                      box.content.find('#rotomecamelloader').css('display', '');
                    }
                  },
                );

                frame_context.rcmail.addEventListener(
                  'message_sent',
                  async (args) => {
                    rcmail.display_message(args.msg, args.type);
                    box.close.data('force', '1');
                    box.close.removeClass('disabled').removeAttr('disabled');
                    const interval = setInterval(() => {
                      if (rcmail.busy === false) {
                        let frame = top.$('iframe.mail-frame');
                        if (frame.length > 0) {
                          frame[0].contentWindow.rcmail.command(
                            'checkmail',
                            '',
                          );
                        }
                        frame = null;

                        rcmail.command('checkmail', '');
                        clearTimeout(interval);
                      }
                    }, 100);
                    box.close.click();
                  },
                );

                frame_context.rcmail.addEventListener(
                  'plugin.message_send_error',
                  ($args) => {
                    console.error('###[plugin.message_send_error]', $args);
                    rcmail.display_message(
                      "Impossible d'envoyer le mail !",
                      'error',
                    );
                    if (
                      popup.box.minifier
                        .find('span')
                        .hasClass(popup.settings.icon_expend)
                    )
                      popup.expand();

                    box.close.removeClass('disabled').removeAttr('disabled');
                    box.get.find('iframe').css('display', '');
                    box.title
                      .find('h3')
                      .html(
                        box.title
                          .find('h3')
                          .html()
                          .replace('Envoi de : ', 'Rédaction : '),
                      );
                    box.content
                      .find('#rotomecamelloader')
                      .css('display', 'none');

                    if (frame_context.$('#mail-send-loader').length > 0) {
                      frame_context.$('#mail-send-loader').remove();
                      frame_context
                        .$('#layout-sidebar, #layout-content')
                        .css('display', '');
                      setTimeout(() => {
                        if (frame_context.$('#mail-send-loader').length > 0) {
                          frame_context.$('#mail-send-loader').remove();
                        }
                      }, 10);
                    }
                  },
                );

                frame_context.$('#toolbar-menu a.send').removeAttr('href');

                if (frame_context.rcmail.env.is_model)
                  box.close.data('force', '1');
              });

              // let spinner = box.content.find(".spinner-border").css("width", '30%');

              // let spinner_size = Math.round(box.content.find(".spinner-border").width());

              // spinner.css("width", `${spinner_size}px`)
              // .css('height', `${spinner_size}px`).css('margin', '15px');
            },
            width: 'calc(100% - 60px)',
            height: 'calc(100% - 60px)',
            context: top,
            fullscreen: true,
          };

          const popup_class =
            window.Windows_Like_PopUp || top.Windows_Like_PopUp;
          return new popup_class(top.$('body'), config);
        }
      };

      const alias_mel_rcmail_submit_messageform = rcmail.submit_messageform;
      rcmail.submit_messageform = function (draft, saveonly) {
        alias_mel_rcmail_submit_messageform.call(this, draft, saveonly);

        rcmail.triggerEvent('message_submited', {
          draft,
          saveonly,
        });
      };

      const alias_mel_rcmail_sent_successfully = rcmail.sent_successfully;
      rcmail.sent_successfully = function (type, msg, folders, save_error) {
        alias_mel_rcmail_sent_successfully.call(
          this,
          type,
          msg,
          folders,
          save_error,
        );

        rcmail.triggerEvent('message_sent', {
          type,
          msg,
          folders,
          save_error,
        });
      };

      return this;
    }

    /**
     * Met en place certaines actions pour les contacts
     * @returns {Mel_Elastic} Chaînage
     */
    setup_adressbook() {
      if (
        rcmail.env.task === 'addressbook' &&
        rcmail.env.action === 'show' &&
        window != parent &&
        rcmail.env.accept_back === true
      ) {
        let $tmp = $(
          '<button type="button" class="btn btn-secondary mel-button create mel-before-remover">Retour <span class="plus icon-mel-undo "></span></button>',
        ).on('click', () => {
          return top.rcmail.command('mel.metapage.contacts.back');
          // let $args = {
          //     _source:rcmail.env.annuaire_source
          // };

          // parent.postMessage({
          //     exec:"searchToAddressbook",
          //     _integrated:true,
          //     child:false
          // }, '*');

          // rcmail.set_busy(true, "loading");
          // window.location.href = this.url("addressbook", "plugin.annuaire", $args);
        });
        $('#contacthead').append($tmp);
      }

      return this;
    }

    /**
     * Met en place la recherche
     * @returns
     */
    setup_search() {
      let $sbox = $('#quicksearchbox');

      if ($sbox.length === 0) $sbox = $('#searchform');

      if ($sbox.length > 0)
        $sbox.on('change', () => {
          $sbox.parent().submit();
        });

      return this;
    }

    is_hidden(e) {
      e = $(e);

      return (
        e.css('display') === 'none' || e.parent().css('display') === 'none'
      );
    }

    setup_settings() {
      if (rcmail.env.task === 'settings') {
        let $list = $('#settings-menu');

        if ($list.length) {
          $(document).ready(() => {
            let switch_a = false;
            $list = $('ul#settings-menu li a');
            for (const a of $list) {
              if (switch_a) $(a).attr('tabindex', -1);
              else switch_a = true;
            }

            $list.on('keydown', (e) => {
              let it = 1;
              let $all_a = $('ul#settings-menu li a');
              const index = $('ul#settings-menu li a').index(e.currentTarget);
              switch (e.originalEvent.code) {
                case 'ArrowDown':
                  while (this.is_hidden($all_a[index + it])) {
                    ++it;
                  }

                  if (index + it < $all_a.length) {
                    $($all_a[index + it])
                      .attr('tabindex', 0)
                      .focus();

                    $all_a = true;
                  }
                  break;

                case 'ArrowUp':
                  while (this.is_hidden($all_a[index - it])) {
                    ++it;
                  }
                  if (index - it >= 0) {
                    $($all_a[index - it])
                      .attr('tabindex', 0)
                      .focus();

                    $all_a = true;
                  }

                default:
                  break;
              }

              if ($all_a === true) $(e.currentTarget).attr('tabindex', -1);
            });
          });
        }
      }

      return this;
    }

    /**
     * Met en place la barre de navigation pour les autres applis.
     * @returns {Mel_Elastic} Chaînage
     */
    setup_other_apps(ignoreStart = false) {
      if (!ignoreStart) {
        const CLASS_FOR_LI_PREFIX = 'li-';
        //Enrobe les "a" par des "li".
        let tmp;
        for (let e of $('#otherapps a')) {
          tmp = new mel_html('li', {
            style: 'width:100%',
            class: Enumerable.from(e.classList)
              .where((x) => x.includes(CLASS_FOR_LI_PREFIX))
              .select((x) => x.replace(CLASS_FOR_LI_PREFIX, EMPTY_STRING))
              .toArray(),
          });
          $(e)
            .addClass('mel-focus')
            .appendTo(tmp.create($(e).parent()));
        }
      }

      //Gestion de la barre.
      $('#otherapps')
        .find('a')
        .on('focusout', (e) => {
          $('#menu-overlay').remove();
          let $parent = $(e.relatedTarget);

          if ($parent.length > 0) {
            while (
              !$parent.hasClass('otherapps') &&
              $parent.length > 0 &&
              $parent[0].nodeName !== 'BODY'
            ) {
              $parent = $parent.parent();
            }
          }

          if (!$parent.hasClass('otherapps')) {
            if (
              !$(e.relatedTarget).hasClass('more-options') &&
              $('#otherapps').css('display') !== 'none'
            ) {
              $('a.more-options').click();
              if ($('html').hasClass('touch')) {
                $('#touchmelmenu').click();
              }
            }
          }
        });

      return this;
    }

    /**
     * Met en place diverses choses qui concerne les choses invisibles.
     * @returns {Mel_Elastic} Chaînage
     */
    setup_html() {
      const ff_check_parent = true;
      try {
        $('meta[name=viewport]').attr(
          'content',
          $('meta[name=viewport]')
            .attr('content')
            .replace(', maximum-scale=1.0', ''),
        );
      } catch (error) {}

      if (
        (ff_check_parent ? window === parent : true) &&
        typeof InstallTrigger !== 'undefined'
      ) {
        $('html').addClass('navigator-firefox');
      }

      return this;
    }

    /**
     * Met en place le calendrier
     * @returns
     */
    setup_calendar() {
      if (rcmail.env.task === 'calendar') {
        let $waiting_events = $('#layout-sidebar .waiting-events-number');

        if ($waiting_events.length > 0) {
          $waiting_events
            .on('mouseenter', () => {
              const id = 'waiting-events-list';
              const id_child = 'waiting-events-list-items';
              const id_selector = `#${id}`;
              if ($(id_selector).length > 0) {
                return;
              }

              const events = rcube_calendar.get_number_waiting_events(false);

              if (!!events && events.length > 0) {
                let $we_list = $(
                  `<div id="${id}"><div id="${id_child}"></div></div>`,
                );

                let $wel_items = $we_list.find(`#${id_child}`);

                const format = 'DD/MM/YYYY HH:mm';
                const size = events.length;
                for (let index = 0; index < size; ++index) {
                  const element = events[index];
                  $wel_items.append(
                    $(`
                                <button title="Evènement ${element.title}, cliquez pour afficher l'évènement" class="waiting-event-button mel-button">
                                    <span class="waiting-event-title">${element.title}</span>
                                    <span class="waiting-event-horodate">${moment(element.start).format(format)} - ${moment(element.end).format(format)}</span>
                                </button>
                                `).click(() => {
                      top.SearchResultCalendar.CreateOrOpen(
                        JSON.stringify(element),
                      );
                    }),
                  );
                }

                $waiting_events.append($we_list);
              }
            })
            .on('mouseleave', (e) => {
              const id = 'waiting-events-list';
              const id_selector = `#${id}`;

              if ($(id_selector).length > 0) $(id_selector).remove();
            });
        }
      }

      return this;
    }

    update_mail_css({ key = null, value = null }) {
      if (
        !!key &&
        !!value &&
        !!value[key] &&
        rcmail.env.mel_metapage_mail_configs[key] !== value[key]
      ) {
        rcmail.env.mel_metapage_mail_configs[key] = value[key];
      }

      if (rcmail.env.task !== 'mail') return;

      const mailConfig = rcmail.env.mel_metapage_mail_configs;

      if (mailConfig !== null) {
        let _css = [];

        const mel_icon_size = 'mel-icon-size';
        if (this.css_rules.ruleExist(mel_icon_size))
          this.css_rules.remove(mel_icon_size);

        //Taille des icônes
        if (
          mailConfig[mel_icon_size] !== rcmail.gettext('normal', 'mel_metapage')
        ) {
          this.css_rules.addAdvanced(
            mel_icon_size,
            '#toolbar-menu li a,#messagelist-header a.refresh,#toolbar-list-menu li a',
            'font-size: 0.9rem;',
          );
        }

        const mel_folder_space = 'mel-folder-space';
        if (this.css_rules.ruleExist(mel_folder_space))
          this.css_rules.remove(mel_folder_space);

        //Espacement des dossiers
        if (
          mailConfig[mel_folder_space] ===
          rcmail.gettext('larger', 'mel_metapage')
        )
          this.css_rules.addAdvanced(
            mel_folder_space,
            '#folderlist-content li',
            '--settings-mail-folder-margin-top: 30px;',
            '--settings-mail-subfolder-margin-top: 5px;',
            ' --settings-mail-opened-folder-margin-top: 30px;',
          );
        else if (
          mailConfig[mel_folder_space] ===
          rcmail.gettext('smaller', 'mel_metapage')
        ) {
          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#folderlist-content li',
              '--settings-mail-folder-margin-top: 0px;',
              '--settings-mail-subfolder-margin-top: -5px;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#folderlist-content .unified li',
              '--settings-mail-folder-margin-top: 0px;',
              '--settings-mail-subfolder-margin-top: -5px;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#folderlist-content li.mailbox.boite[aria-expanded="true"]',
              '--settings-mail-opened-folder-margin-bottom: 20px;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#folderlist-content .unified li.mailbox.boite[aria-expanded="true"]',
              '--settings-mail-opened-folder-margin-top: 5px!important;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#folderlist-content .unified li.mailbox[aria-expanded="true"]',
              '--settings-mail-folder-margin-bottom: 20px!important;',
              '--settings-mail-folder-margin-top: 5px!important;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#folderlist-content ul#mailboxlist > li > ul li[aria-level="2"]:first-of-type',
              'border: none;',
              'margin-top: 0;',
              'padding-top: 0;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#folderlist-content ul#mailboxlist > li > ul li[aria-level="2"]:first-of-type div.treetoggle,#folderlist-content ul#mailboxlist > li > ul li[aria-level="2"]:first-of-type .unreadcount',
              'top:0;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#mailboxlist li ul:first-of-type',
              'padding-left: 1.5em;',
            ),
          );

          const rule = new Mel_CSS_Array_Rule(_css);

          this.css_rules._add(mel_folder_space, rule);
          _css = [];
        }

        const mel_message_space = 'mel-message-space';
        if (this.css_rules.ruleExist(mel_message_space))
          this.css_rules.remove(mel_message_space);

        //Espacement des messages
        if (
          mailConfig[mel_message_space] ===
          rcmail.gettext('larger', 'mel_metapage')
        )
          this.css_rules.addAdvanced(
            mel_message_space,
            '#messagelist tr.message td',
            'padding-top: 1rem;',
            'padding-bottom: 1rem;',
          );
        else if (
          mailConfig[mel_message_space] ===
          rcmail.gettext('smaller', 'mel_metapage')
        ) {
          _css.push(
            new Mel_CSS_Advanced_Rule(
              '#messagelist tr.message td',
              'padding-top: 0;',
              'padding-bottom:0;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              'table.messagelist td.subject span.subject',
              'margin-top: -10px;',
            ),
          );

          _css.push(
            new Mel_CSS_Advanced_Rule(
              'table.messagelist tr.message td.flags span.attachment',
              'margin-top: -7px;',
              'pointer-events: none',
            ),
          );

          const rule = new Mel_CSS_Array_Rule(_css);

          this.css_rules._add(mel_message_space, rule);

          _css = [];
        }

        // var style=document.createElement('style');
        // style.type='text/css';

        // if(style.styleSheet){
        //     style.styleSheet.cssText = _css;
        // }else{
        //     style.appendChild(document.createTextNode(_css));
        // }
        // document.getElementsByTagName('head')[0].appendChild(style);
      }
    }

    async update_mail_css_async({ key = null, value = null }) {
      return this.update_mail_css({ key, value });
    }

    set_font_size() {
      const size = `${rcmail.env['font-size']}-text`;
      const other_size = `${size === 'sm' ? 'lg' : 'sm'}-text`;
      let $html = $('html');

      if (!$html.hasClass(size)) $html.removeClass(other_size).addClass(size);

      return this;
    }

    /**
     * Renvoie vrai si les barre de défilements sont en mode automatique
     * @returns
     */
    isScollBarAuto() {
      if (!this.auto_text)
        this.auto_text = rcmail.gettext('auto', 'mel_metapage');

      return (
        rcmail.env.mel_metapage_mail_configs['mel-scrollbar-size'] ===
        this.auto_text
      );
    }

    updateScollBarMode() {
      const css_key = 'scroll_bar_size';
      if (this.css_rules.ruleExist(css_key)) {
        this.css_rules.remove(css_key);
      }

      if (this.isScollBarAuto()) {
        //Le mode automatique n'existe plus
        rcmail.env.mel_metapage_mail_configs['mel-scrollbar-size'] =
          rcmail.gettext('default', 'mel_metapage');
      }

      if (this.isScollBarAuto() && !this.updateScollBarMode.initialized) {
        let ele;
        let distance;
        let last_target = null;
        document.addEventListener('mousemove', function (e) {
          if (UI.is_touch() === false && MEL_ELASTIC_UI.isScollBarAuto()) {
            ele = e.target;

            if (last_target) {
              if (ele !== last_target) {
                $('.more-width').removeClass('more-width');
                last_target = ele;
              }
            } else last_target = ele;

            distance = ele.offsetLeft + ele.offsetWidth - e.pageX;
            distance < 15 && distance > -15
              ? ele.classList.add('more-width')
              : ele.classList.remove('more-width');
          }
        });

        this.updateScollBarMode.initialized = true;
      } else if (
        rcmail.env.mel_metapage_mail_configs['mel-scrollbar-size'] ===
        rcmail.gettext('normal', 'mel_metapage')
      ) {
        this.css_rules.addAdvanced(
          css_key,
          ':root',
          '--scrollbar-width-moz:var(--scrollbar-width-moz-normal)!important',
          '--scrollbar-width-webkit:var(--scrollbar-width-webkit-normal)!important',
        );
      } else if (
        rcmail.env.mel_metapage_mail_configs['mel-scrollbar-size'] ===
        rcmail.gettext('large', 'mel_metapage')
      ) {
        this.css_rules.addAdvanced(
          css_key,
          ':root',
          '--scrollbar-width-webkit:var(--scrollbar-width-webkit-large)!important',
          '--scrollbar-width-moz:var(--scrollbar-width-moz-large)!important',
        );
      }

      return this;
    }

    /**
     * Met à jours la page.
     * @returns {Mel_Elastic} Chaînage
     */
    update() {
      return this.update_tabs()
        .update_pagination()
        .updateScollBarMode()
        .redStars();
    }

    /**
     * Met à  jours le système d'onglet
     * @returns {Mel_Elastic} Chaînage
     */
    update_tabs() {
      let querry = $('.mel-tabheader');

      if (querry.length > 0) {
        querry.unbind('click');
        querry.on('click', (e) => {
          //console.log("MEL_ELASTIC", this, e);
          this.switchTab(e.currentTarget);
        });
        querry.each((i, e) => {
          let parent = $(e).parent();

          if (!parent.hasClass('mel-ui-tab-system')) {
            this.gestionTabs(parent);

            for (let index = 0; index < e.classList.length; ++index) {
              const element = e.classList[index];

              if (element.includes('tab-')) {
                $(`.${element}.mel-tabheader`).each((index, element) => {
                  if (index !== 0) $(element).attr('tabindex', -1);
                });
                break;
              }
            }
          }
        });
      }

      querry = $('.select-button-mel');

      if (querry.length > 0) {
        querry.unbind('click');
        querry.on('click', (e) => {
          this.generateSelect(e.currentTarget);
        });
      }

      return this;
    }

    /**
     * Met à  jours le système de pagination.
     * @returns {Mel_Elastic} Chaînage
     */
    update_pagination() {
      let querry = $('.pagination');

      if (querry.length > 0) {
        querry.each((i, e) => {
          e = $(e);
          this.set_pagination(
            e,
            e.data('count'),
            e.data('current') === undefined ? null : e.data('current'),
          );
          return this;
        });
      }

      return this;
    }

    ////////////************* Main functions *************///////////

    /**
     * Gère les étoiles rouges.
     * @returns {Mel_Elastic} Chaînage
     */
    redStars() {
      let querry = $('.red-star-after');

      if (querry.length > 0) {
        $('.red-star-after').each((i, e) => {
          e = $(e);

          if (!e.hasClass('mel-after-remover'))
            e.append(
              '<star class="red-star mel-before-remover">*</star>',
            ).addClass('mel-after-remover');
        });
      }

      querry = $('.red-star');

      if (querry.length > 0) {
        querry.each((i, e) => {
          e = $(e);

          if (!e.hasClass('mel-before-remover'))
            e.prepend('<star class="red-star mel-before-remover">*</star>')
              .removeClass('red-star')
              .addClass('red-star-removed');
        });
      }

      return this;
    }

    /**
     * Switch de thème (sombre/light)
     * @returns {Mel_Elastic} Chaînage
     */
    switch_color() {
      let $html = $('html');
      if ($html.hasClass('dark-mode-custom'))
        $html.removeClass('dark-mode-custom');

      $html = null;

      $('iframe.mm-frame').each((i, e) => {
        if (!e.classList.contains('discussion-frame')) {
          $html = e.contentWindow.$('html');

          if ($html.hasClass('dark-mode-custom'))
            $html.removeClass('dark-mode-custom');
        }
      });

      rcmail.triggerEvent('switch_color_theme');

      return this._update_theme_color();
    }

    /**
     * Retourne le thème en cours
     * @returns {string} dark/light
     */
    color_mode() {
      return $('html').hasClass('dark-mode') ||
        $('html').hasClass('dark-mode-custom')
        ? 'dark'
        : 'light';
    }

    _update_theme_color() {
      let $html = $('html');

      if (this.color_mode() === 'dark') {
        if (this.themes[this.theme]?.custom_dark_mode) {
          if ($html.hasClass('dark-mode')) $html.removeClass('dark-mode');
          if (!$html.hasClass('dark-mode-custom'))
            $html.addClass('dark-mode-custom');
        } else {
          if (!$html.hasClass('dark-mode')) $html.addClass('dark-mode');
          if ($html.hasClass('dark-mode-custom'))
            $html.removeClass('dark-mode-custom');
        }
      } else if ($html.hasClass('dark-mode-custom'))
        $html.removeClass('dark-mode-custom');

      if (
        this.color_mode() === 'dark' &&
        !this.themes[this.theme]?.custom_dark_mode
      ) {
        let current = this.themes[this.get_current_theme()];
        do {
          $html.removeClass(current.class);
          current = this.themes[current.parent];
        } while (current);
      } else if (this.get_current_theme() !== 'default') {
        let current = this.themes[this.get_current_theme()];
        do {
          $html.addClass(current.class);
          current = this.themes[current.parent];
        } while (current);
      }

      $('iframe.mm-frame').each((i, e) => {
        if (
          !e.classList.contains('discussion-frame') &&
          !!e.contentWindow.MEL_ELASTIC_UI
        ) {
          e.contentWindow.MEL_ELASTIC_UI._update_theme_color();
        }
      });

      return this;
    }

    ////////////************* Other functions *************///////////
    /**
     * Récupère la classe principale d'un bouton de la barre de navigation
     * @param {DomElement} button - Ne doit pas être du JQUERY
     * @returns {string} - Classe principale ou "no-class-found" si aucune classe principale trouvé.
     */
    get_nav_button_main_class(button) {
      const list = button.classList;
      for (const key in list) {
        if (Object.hasOwnProperty.call(list, key)) {
          const element = list[key];
          switch (element) {
            case 'mel-focus':
            case 'selected':
            case 'order1':
            case 'mel':
            case 'disabled':
            case 'hidden':
              continue;

            default:
              if (element.includes('icofont')) continue;
              if (element.includes('icon-mel-')) continue;
              if (element.includes('button')) continue;

              return element;
          }
        }
      }

      return 'no-class-found';
    }

    /**
     * Génère une couleur au hasard.
     * @returns {string} Couleur héxadécimale
     */
    getRandomColor() {
      var letters = '0123456789ABCDEF';
      var color = '#';
      for (var i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
      }
      return color;
    }

    /**
     * Récupère la différence de 2 réctangles.
     * @param {DOMRect} rect1 Rectangle à soustraire
     * @param {DOMRect} rect2 Rectangle de soustraction
     * @returns {{top:number, left:number, right:number, bottom:number, rect1:DOMRect, rect2:DOMRect}} Résultats
     */
    getRect(rect1, rect2) {
      return {
        top: rect1.top - rect2.top,
        left: rect1.left - rect2.left,
        right: rect1.right - rect2.right,
        bottom: rect1.bottom - rect2.bottom,
        rect1,
        rect2,
      };
    }

    /**
     * Récupère une url correcte
     * @param {string} task Tâche que l'on souhaite
     * @param {string} action Action que l'on souhaite
     * @param {JSON} args Arguments supplémentaires
     * @returns {string} Url fonctionnel
     */
    url(task, action = '', args = null) {
      if (window.mel_metapage !== undefined)
        return mel_metapage.Functions.url(task, action, args);
      else {
        let tmp = '';

        if (action !== '') tmp += '&_action=' + action;

        if (
          window.location.href.includes(this.FROM_INFOS.key) &&
          window.location.href.includes(this.FROM_INFOS.value)
        ) {
          if (args === null || args === undefined) args = {};

          args[this.FROM_INFOS.key] = this.FROM_INFOS.value;
        }

        for (const key in args) {
          if (Object.hasOwnProperty.call(args, key)) {
            const element = args[key];
            tmp += '&' + key + '=' + element;
          }
        }
        return rcmail.get_task_url(
          task + tmp,
          window.location.origin + window.location.pathname,
        );
      }
    }

    /**
     * Récupère une recherche de contact avec autocompletion.
     * @param {string} id Id de l'input
     * @returns {string} html
     */
    get_input_mail_search(
      id = '',
      { inputTabIndex = 1, contactTabIndex = 1 } = {},
    ) {
      let html = 'Participants<span class=red-star></span>';
      html += '<div class="input-group">';
      html +=
        '<textarea name="_to_workspace" spellcheck="false" id="to-workspace" tabindex="-1" data-recipient-input="true" style="position: absolute; opacity: 0; left: -5000px; width: 10px;" autocomplete="off" aria-autocomplete="list" aria-expanded="false" role="combobox"></textarea>';
      html +=
        '<ul id="wspf" class="form-control recipient-input ac-input rounded-left">';
      html +=
        '<li class="input"><input id="' +
        id +
        `" onchange="m_mp_autocoplete(this)" oninput="m_mp_autocoplete(this)" type="text" tabindex="${inputTabIndex}" autocomplete="off" aria-autocomplete="list" aria-expanded="false" role="combobox"></li></ul>`;
      html += '<span class="input-group-append">';
      html += `<a href="#add-contact" onclick="m_mp_openTo(this, '${id}')" class="input-group-text icon add recipient" title="Ajouter un contact" tabindex="${contactTabIndex}"><span class="inner">Ajouter un contact</span></a>`;
      html += '			</span>';
      html += '			</div>';
      return html;
    }

    ////////////************* Select box functions *************///////////
    /**
     * Créer une selectbox stylisé via un élément
     * @param {JQUERY} event Jquery élément
     * @returns {Mel_Elastic} Chaînage
     */
    generateSelect(event) {
      event = $(event);
      const rc =
        typeof event.data('rcmail') === 'string'
          ? event.data('rcmail') === 'true'
          : event.data('rcmail');

      if (rc) {
        if (rcmail.is_busy) return;
      }

      //console.log("generateSelect", event.data("options"), typeof event.data("options"), event);
      const options =
        typeof event.data('options') === 'string'
          ? JSON.parse(
              event.data('options').includes('¤¤¤')
                ? event.data('options').replaceAll('¤¤¤', '"')
                : event.data('options'),
            )
          : event.data('options');
      const options_class =
        typeof event.data('options_class') === 'string'
          ? JSON.parse(
              event.data('options_class').includes('¤¤¤')
                ? event.data('options_class').replaceAll('¤¤¤', '"')
                : event.data('options_class'),
            )
          : event.data('options_class');
      const options_title =
        typeof event.data('option-title') === 'string'
          ? JSON.parse(
              event.data('option-title').includes('¤¤¤')
                ? event.data('option-title').replaceAll('¤¤¤', '"')
                : event.data('option-title'),
            )
          : event.data('option-title');
      const options_current_title =
        typeof event.data('option-title-current') === 'string'
          ? JSON.parse(
              event.data('option-title-current').includes('¤¤¤')
                ? event.data('option-title-current').replaceAll('¤¤¤', '"')
                : event.data('option-title-current'),
            )
          : event.data('option-title-current');
      const update = event.data('event');
      const is_icon =
        typeof event.data('is_icon') === 'string'
          ? event.data('is_icon') === 'true'
          : event.data('is_icon');
      const value = event.data('value');
      const onchange = event.data('onchange'); // = typeof event.data("on") === "string" ? JSON.parse(event.data("on").includes("¤¤¤") ? event.data("on").replaceAll('¤¤¤', '"') : event.data("on")) : event.data("on");
      //Create selectbox
      if (event.parent().css('position') !== 'relative')
        event.parent().css('position', 'relative');

      let html = '<div class="btn-group-vertical">';

      for (const key in options) {
        if (Object.hasOwnProperty.call(options, key)) {
          const element = options[key];
          const current_option_class =
            options_class !== null &&
            options_class !== undefined &&
            options_class[key] !== undefined
              ? options_class[key]
              : '';
          const current_option_title =
            options_title !== null &&
            options_title !== undefined &&
            options_title[key] !== undefined
              ? options_title[key]
              : '';
          const new_option_title =
            options_current_title !== null &&
            options_current_title !== undefined &&
            options_current_title[key] !== undefined
              ? options_current_title[key]
              : '';
          html +=
            '<button title="' +
            current_option_title +
            '" onclick="MEL_ELASTIC_UI.updateSelectValue(`' +
            key +
            '`, `' +
            new_option_title +
            '`)" class="' +
            current_option_class +
            ' mel-selected-content-button btn btn-primary ' +
            (value === key ? 'active' : '') +
            '">' +
            (is_icon ? '<span class=' + element + '></span>' : element) +
            '</button>';
        }
      }

      html += '</div>';
      const rect = this.getRect(
        event[0].getBoundingClientRect(),
        event.parent()[0].getBoundingClientRect(),
      );
      html = $(html)
        .css('position', 'absolute')
        .css('top', rect.top + rect.rect1.height)
        .css('left', rect.left)
        .css('z-index', 50)
        .addClass('mel-select-popup');
      event.parent().append(html);
      event.on('focusout', (e) => {
        if ($(e.relatedTarget).hasClass('mel-selected-content-button')) {
          $(e.relatedTarget).click();
          event.focus();
        } else $('.mel-select-popup').remove();
      });

      this.tmp_popup = {
        options: options,
        options_class: options_class,
        event: event,
        is_icon: is_icon,
        onchange: onchange,
      };

      return this;
    }

    /**
     * Met à jour la valeur du select
     * @param {string} value Nouvelle valeur
     * @param {string} newTitle Nouveau titre associé
     * @returns {Mel_Elastic} Chaînage
     */
    updateSelectValue(value, newTitle = '') {
      //console.log("generateSelect", value);
      if (this.tmp_popup !== undefined) {
        if (this.tmp_popup.event.data('value') === value) {
          $('.mel-select-popup').remove();
          delete this.tmp_popup;
          return;
        }

        this.tmp_popup.event
          .data('value', value)
          .attr('title', newTitle)
          .html(
            this.tmp_popup.is_icon
              ? '<span class=' + this.tmp_popup.options[value] + '></span>'
              : this.tmp_popup.options[value],
          );
        const options_class = this.tmp_popup.options_class;

        if (options_class !== null && options_class !== undefined) {
          const current_option_class =
            options_class[value] !== undefined ? options_class[value] : null;
          for (const key in options_class) {
            if (Object.hasOwnProperty.call(options_class, key)) {
              const element = options_class[key];
              this.tmp_popup.event.removeClass(element);
            }
          }
          if (current_option_class !== null)
            this.tmp_popup.event.addClass(current_option_class);
        }

        $('.mel-select-popup').remove();

        if (
          this.tmp_popup.onchange !== null &&
          this.tmp_popup.onchange !== undefined
        ) {
          if (this.tmp_popup.onchange.includes('<value/>'))
            this.tmp_popup.onchange = this.tmp_popup.onchange.replaceAll(
              '<value/>',
              value,
            );

          if (
            this.tmp_popup.onchange.includes(
              'MEL_ELASTIC_UI.SELECT_VALUE_REPLACE',
            )
          )
            this.tmp_popup.onchange = this.tmp_popup.onchange.replaceAll(
              'MEL_ELASTIC_UI.SELECT_VALUE_REPLACE',
              '`' + value + '`',
            );

          eval(this.tmp_popup.onchange);
        }

        delete this.tmp_popup;
      }

      return this;
    }

    /**
     * Modifie la valeur d'un select
     * @param {string} new_value Nouvelle valeur
     * @param {JQUERY} event Élément JQUERY
     * @returns {Mel_Elastic} Chaînage
     */
    setValue(new_value, event) {
      const options =
        typeof event.data('options') === 'string'
          ? JSON.parse(
              event.data('options').includes('¤¤¤')
                ? event.data('options').replaceAll('¤¤¤', '"')
                : event.data('options'),
            )
          : event.data('options');
      const options_class =
        typeof event.data('options_class') === 'string'
          ? JSON.parse(
              event.data('options_class').includes('¤¤¤')
                ? event.data('options_class').replaceAll('¤¤¤', '"')
                : event.data('options_class'),
            )
          : event.data('options_class');
      const update = event.data('event');
      const is_icon =
        typeof event.data('is_icon') === 'string'
          ? event.data('is_icon') === 'true'
          : event.data('is_icon');
      const value = event.data('value');
      const onchange = event.data('onchange');

      this.tmp_popup = {
        options: options,
        options_class: options_class,
        event: event,
        is_icon: is_icon,
        onchange: onchange,
      };
      this.updateSelectValue(new_value);

      return this;
    }

    ////////////************* Tab functions *************///////////
    /**
     * Change d'onglet
     * @param {DOMElement} event
     * @returns {Mel_Elastic} Chaînage
     */
    switchTab(event) {
      //get id
      const id = event.id;
      //get namespace (tab-)
      let namespace = null;

      $(event).each((i, e) => {
        for (let index = 0; index < e.classList.length; ++index) {
          const element = e.classList[index];
          if (element.includes('tab-')) {
            namespace = element;
            break;
          }
        }
      });

      if (namespace === null) return this;

      //Désactivation des autres tabs et objets
      $('.' + namespace + '.mel-tab')
        .removeClass('active')
        .attr('aria-selected', false)
        .attr('tabindex', -1);
      $('.' + namespace + '.mel-tab-content').css('display', 'none');

      //Activation de la tab
      $(event)
        .addClass('active')
        .attr('aria-selected', true)
        .attr('tabindex', 0);

      //activation des objets lié Ã  la tab
      $('.' + id + '.' + namespace).css('display', '');
      const onclick = $(event).data('onclick');

      if (onclick !== null && onclick !== undefined && onclick !== '') {
        new Promise((a, b) => {
          try {
            eval(onclick);
          } catch (error) {
            console.error(error);
          }

          if ($(event).data('delete-after-click') === true)
            $(event).data('onclick', '');
        });
      }

      return this;
    }

    /**
     * Gère les différents onglets
     * @param {JQUERY | DOMElement} $item Si null, gère la page entière
     * @returns {Mel_Elastic} Chaînage
     */
    gestionTabs($item = null) {
      if ($item === null) {
        const items = document.querySelectorAll('[role="tablist"]');
        for (let index = 0; index < items.length; ++index) {
          const element = items[index];
          this.gestionTabs(element);
        }
      } else {
        const determineDelay = function () {
          var hasDelay = tablist.hasAttribute('data-delay');
          var delay = 0;

          if (hasDelay) {
            var delayValue = tablist.getAttribute('data-delay');
            if (delayValue) {
              delay = delayValue;
            } else {
              // If no value is specified, default to 300ms
              delay = 300;
            }
          }

          return delay;
        };

        let item = $($item);

        if (item.hasClass('mel-ui-tab-system')) return this;
        else item.addClass('mel-ui-tab-system');

        let tabs = item.find('button');

        if (tabs.length === 0) {
          tabs = item.children();
          tabs.attr('tabindex', 0);
        }

        tabs.keydown((event) => {
          const key = event.keyCode;

          let direction = 0;
          switch (key) {
            case this.keys.left:
              direction = -1;
              break;
            case this.keys.right:
              direction = 1;
              break;

            case this.keys.home:
              $(tabs[0]).focus().click();
              break;
            case this.keys.end:
              $(tabs[tabs.length - 1])
                .focus()
                .click();
              break;

            default:
              break;
          }

          if (direction !== 0) {
            for (let index = 0; index < tabs.length; ++index) {
              const element = $(tabs[index]);

              if (element.hasClass('selected') || element.hasClass('active')) {
                let id;
                if (index + direction < 0) id = tabs.length - 1;
                else if (index + direction >= tabs.length) id = 0;
                else id = index + direction;

                $(tabs[id]).focus().click();

                break;
              }
            }
          }
        });
      }

      return this;
    }

    create_loader(id, absoluteCentered = true, generate = true) {
      let loading = new mel_html2(CONST_HTML_DIV, {
        attribs: {
          class: 'loader-base',
        },
        contents: [new mel_html(CONST_HTML_DIV, { class: 'loader-looping' })],
      });

      if (absoluteCentered) {
        loading = new mel_html2(CONST_HTML_DIV, {
          attribs: { class: CONST_CLASS_ABSOLUTE_CENTER },
          contents: [loading],
        });
      }

      loading = new mel_html2(CONST_HTML_DIV, {
        attribs: { id },
        contents: [loading],
      });

      return generate ? loading.generate() : loading;
    }

    ////////////************* Pagination functions *************///////////
    /**
     * Créer un html pour le nombre de la pagination
     * @param {number} number Nombre
     * @param {boolean} isClickable Si le nombre est clickable
     * @param {boolean} active Si le nombre est actif
     * @returns {string} HTML
     */
    create_number(number, isClickable = true, active = false) {
      return (
        '<span class="pagination-number pagination-number-' +
        number.toString().replaceAll('.', 'a') +
        (active ? ' active ' : '') +
        (isClickable ? '' : 'disabled') +
        '" onclick="MEL_ELASTIC_UI.pagination_page(this, ' +
        number +
        ')">' +
        number +
        '</span>'
      );
    }

    /**
     * Créer une barre de pagination
     * @param {DomElement} e Element qui contiendra la pagination
     * @param {number} count Nombre d'éléments
     * @param {number} current Elément courant
     * @returns {Mel_Elastic} Chaînage
     */
    set_pagination(e, count, current = null) {
      const _integer = this._integer;
      //debugger;
      //console.log("count", count);
      count = Math.ceil(count / 7.0);
      e.html(
        '<button class="pagination_prev pagination-button" onclick="MEL_ELASTIC_UI.pagination_prev(this)">Précédent</button>',
      );
      e.append('<div class=pagination-elements></div>');
      let pagination_elements = e.find('.pagination-elements');
      for (let index = (current ?? 1) - 1; index < count; ++index) {
        pagination_elements.append(
          this.create_number(index + 1, true, index === 0),
        );
      }

      e.append(
        '<button class="pagination_next pagination-button" onclick="MEL_ELASTIC_UI.pagination_next(this)">Suivant</button>',
      );
      //console.log("current", current);
      if (current !== null)
        this.pagination_page(
          $('.pagination-number-' + current)[0],
          current,
          false,
        );

      return this;
    }

    /**
     * Change de page spécifiquement
     * @param {JQUERY} e Elément cliqué
     * @param {number} number Page
     * @param {boolean} doAction Si on effectue l'action ou non.
     * @returns {Mel_Elastic} Chaînage
     */
    pagination_page(e, number, doAction = true) {
      const _integer = this._integer;
      e = $(e).parent();
      const count = Math.ceil(e.parent().data('count') / 7.0);
      let html = '';

      if (count > _integer) {
        let before = false;
        let after = false;

        for (let index = 0; index < count; ++index) {
          if (index === 0 || index === count - 1) {
            html += this.create_number(index + 1, true, index + 1 === number);
          } else if (index >= number - _integer && index <= number + _integer) {
            html += this.create_number(index + 1, true, index + 1 === number);
          } else {
            if (!before && index < number - _integer) {
              html += this.create_number('...', false);
              before = true;
            } else if (!after && index > number + _integer) {
              html += this.create_number('...', false);
              after = true;
            }
          }
        }

        // for (let index = 0; index < count; ++index) {
        //   //affichage du premier
        //   if (index === 0)
        //     html += this.create_number(index + 1, true, index + 1 === number);
        //   //affichage du dernier
        //   else if (index === count - 1)
        //     html += this.create_number(count, true, number === count);
        //   //si loin premier et loin dernier
        //   else if (index > _integer && index > count - _integer) {
        //     if (index < number - _integer / 2.0) {
        //       // || index > number + _integer / 2.0)
        //       if (!before) {
        //         html += this.create_number('...', false);
        //         before = true;
        //       }
        //     } else if (index > number + _integer / 2.0) {
        //       if (!after) {
        //         html += this.create_number('...', false);
        //         after = true;
        //       }
        //     } else
        //       html += this.create_number(index + 1, true, index + 1 === number);
        //   }
        //   //si proche premier
        //   else if (index < _integer) {
        //     if (index === _integer) {
        //       html += this.create_number('...', false);
        //       html += this.create_number(count);
        //       break;
        //     } else
        //       html += this.create_number(index + 1, true, index + 1 === number);
        //   }
        //   //si proche dernier
        //   else {
        //     if (index > count - _integer)
        //       html += this.create_number(index + 1, true, index + 1 === number);
        //     else {
        //       if (!before) {
        //         html += this.create_number('...', false);
        //         before = true;
        //       }
        //     }
        //   }
        // }
      } else {
        for (let index = 0; index < count; ++index) {
          //console.log("test", index+1 === number);
          html += this.create_number(index + 1, true, index + 1 === number);
        }
      }
      e.html(html);
      e.parent().data('current', number);

      if (doAction) eval(e.parent().data('page').replaceAll('¤page¤', number));

      return this;
    }

    /**
     * On affiche la page suivante
     * @param {DOMElement} e Element cliqué
     * @returns {Mel_Elastic} Chaînage
     */
    pagination_next(e) {
      const count = $(e).parent().data('count');
      let current = $(e).parent().data('current');
      current = current === null || current === undefined ? 2 : current + 1;

      if (count + 1 != current)
        this.pagination_page($('.pagination-number-' + current)[0], current);

      return this;
    }

    /**
     * On affiche la page précédente
     * @param {DOMElement} e Element cliqué
     * @returns {Mel_Elastic} Chaînage
     */
    pagination_prev(e) {
      const current = $(e).parent().data('current');

      if (current !== undefined || current !== 1)
        this.pagination_page(
          $('.pagination-number-' + (current - 1))[0],
          current - 1,
        );

      return this;
    }

    initResponsive(key = null, changeReturn = false) {
      const each = ($tn, tn, t, d, ptl, namespace) => {
        const $tabName = $tn; //$(e).find('.rTabName');
        const tabName = tn; //$(e).data('tab-name');
        const tab = t; //$(e).data('selector-tab');
        const _default = d; //$(e).data('is-default-tab');
        const parentTabList = ptl; //$(e).data('parent-tabs');

        const item = {
          $tabName: $tabName.length === 0 ? tabName : $tabName,
          $tab: $(tab),
          default: _default,
          $parentTabList: $(parentTabList),
          isTabNameString() {
            return typeof this.$tabName === 'string';
          },
        };

        return this.initTabResponsive(namespace, item);
      };

      const tab_class = 'mel-responsive-tab';
      const tab_header_class = 'mel-responsive-tabheader';
      const tab_content = 'mel-responsive-tab-content';

      if (!key) {
        let namespace;
        switch (rcmail.env.task) {
          case 'bureau':
            namespace = 'namespace-reponsive-tab-desk';
            break;
          case 'news':
            namespace = 'namespace-reponsive-tab-news';
            break;

          default:
            break;
        }

        if ($(`.${namespace}`).length === 0) {
          $('.module_parent').each((i, e) => {
            e = $(e);
            each(e.find('h2').first(), '', e, i === 0, '#contents', namespace);
          });
          $('#contents')
            .find(`.${tab_class}.${tab_header_class}`)
            .last()
            .addClass('last');
        }
      } else {
        let namespace = null;

        switch (key) {
          case 'shortcut':
            namespace = 'namespace-reponsive-tab-' + key;
            break;

          default:
            break;
        }

        let $querry = $(`.${tab_content}${namespace ? `.${namespace}` : ''}`);

        if ($querry.length === 0) $querry = $(`.${tab_content}.no-namespace`);

        const size = $querry.length - 1;

        $(`.${tab_content}${namespace ? `.${namespace}` : ''}`).each((i, e) => {
          let $tab = each(
            $(e).find('.responsive-tab-name'),
            $(e).data('tab-name'),
            $(e).data('selector-tab'),
            $(e).data('is-default-tab'),
            $(e).data('parent-tabs'),
            namespace || $(e).parent().data('namespace') || 'no-namespace',
          );

          if (!!$tab && $tab.length > 0 && i === size) $tab.addClass('last');
        });
      }

      rcmail.addEventListener('skin-resize', (datas) => {
        const small = 'small';
        const phone = 'phone';
        //const header_to_hide = 'mel-header-to-hidden';
        const hide_button = 'responsive-hide-button';
        const tab_list = 'responsiveTabList';
        const selected_item_class = 'selected';
        // const tab_content = 'mel-responsive-tab-content';
        datas = datas.mode;

        if (datas === small) datas = phone;

        if (this.screen_type !== (datas || this.screen_type)) {
          if (
            this.screen_type !== phone &&
            this.screen_type !== small &&
            (datas === phone || datas === small)
          ) {
            //On passe en mode phone
            $(`.${tab_list}`)
              .css('display', '')
              .find(`.${selected_item_class}`)
              .click();

            if (rcmail.env.task === 'bureau') {
              $('#welcome-text')
                .find('.col-3')
                .css('display', 'none')
                .parent()
                .find('.col-6')
                .first()
                .removeClass('col-6')
                .addClass('col-12')
                .children()
                .css('font-size', '30px')
                .css('margin-top', 0);
            }
          } else {
            $(`.${tab_list}`)
              .css('display', 'none')
              .find(`.${hide_button}`)
              .click();

            if (rcmail.env.task === 'bureau') {
              $('#welcome-text')
                .find('.col-3')
                .css('display', '')
                .parent()
                .find('.col-12')
                .first()
                .removeClass('col-12')
                .addClass('col-6')
                .children()
                .css('font-size', '40px')
                .css('margin-top', '55px');
            }
          }

          this.screen_type = datas || this.screen_type;
        }
      });

      if (!changeReturn) return this;
      else {
        const screen_type = this.screen_type;
        const update_screen_type = (x) => {
          this.screen_type = x;
        };
        return {
          update() {
            const tab_list = 'responsiveTabList';
            const selected_item_class = 'selected';
            const last = screen_type;
            update_screen_type(null);
            rcmail.triggerEvent('skin-resize', { mode: last });

            if (last === 'small' || last === 'phone')
              $(`.${tab_list}`)
                .css('display', '')
                .find(`.${selected_item_class}`)
                .click();
          },
        };
      }
    }

    initTabResponsive(
      namespace,
      item = {
        isTabNameString: () => {
          return true;
        },
        $parentTabList: $(),
        $tabName: $(),
        $tab: $(),
        default: false,
      },
    ) {
      const header_to_hide = 'mel-header-to-hidden';
      const hide_button = 'responsive-hide-button';
      const tab_list = 'responsiveTabList';
      const selected_item_class = 'selected';
      const tab_class = 'mel-responsive-tab';
      const tab_header_class = 'mel-responsive-tabheader';
      const tab_content = 'mel-responsive-tab-content';

      let tabName = '';
      let $tab = null;

      //Gestion de l'onglet
      if (!item.isTabNameString() && item.$tab.find(item.$tabName).length > 0) {
        //Si est à  l'intérieur du corps
        tabName = item.$tabName[0].innerText;
        item.$tabName.addClass(header_to_hide);
      } else tabName = item.$tabName; //Si c'est juste le nom

      //Id généré depuis son nom
      const tab_id =
        'responsive-generated-' +
        tabName.replace(/[^0-9a-z]/gi, '').toLowerCase();

      let $tabList = item.$parentTabList.find(`.${tab_list}.${namespace}`);

      //Création de la liste d'onglet si elle n'éxiste pas
      if ($tabList.length === 0) {
        $tabList = item.$parentTabList
          .prepend(
            `<div class='row ${tab_list} ${namespace}' data-namespace="${namespace}" style="flex-wrap: nowrap;"></div>`,
          )
          .find(`.${tab_list}.${namespace}`);
      }

      //Ajout du bouton si il n'éxiste pas
      $tab = $tabList.find(`#${tab_id}`);
      if ($tab.length === 0) {
        $tab = $tabList
          .append(
            $(`
                    <button id="${tab_id}" class="${item.default ? selected_item_class : ''} ${tab_class} ${tab_header_class} ${namespace}">${tabName}</button>
                `).click(() => {
              $(`.${header_to_hide}`).css('display', 'none');
              $(`.${tab_content}.${namespace}`).css('display', 'none');
              $(
                `.${tab_class}.${tab_header_class}.${selected_item_class}.${namespace}`,
              ).removeClass(selected_item_class);
              item.$tab.css('display', '');
              $(`#${tab_id}`).addClass(selected_item_class);
            }),
          )
          .find(`#${tab_id}`);
      }

      //Création du bouton de "désaffichage"
      if ($tabList.find(`.${hide_button}`).length === 0) {
        $tabList.append(
          `<button class="${hide_button} ${namespace} hidden"></button>`,
        );
      }

      $tabList.find(`.${hide_button}.${namespace}`).click(() => {
        item.$tab.css('display', '');

        //if (!item.isTabNameString() && item.$tab.find(item.$tabName).length > 0) item.$tabName.css('display', '');
        $(`.${header_to_hide}`).css('display', '');
      });

      item.$tab.addClass(tab_content).addClass(namespace);

      return $tab;
    }

    get_current_theme() {
      return this.theme || 'default';
    }

    get_current_theme_picture() {
      return this.get_themes()[this.get_current_theme()];
    }

    update_theme(theme, post = true) {
      theme = theme || 'default';
      if (theme !== this.theme) {
        const newThemeDatas = this.themes[theme];
        const oldThemeDatas = this.themes[this.theme];
        let $html = $('html');

        if (this.theme !== 'default') {
          let current = oldThemeDatas;

          do {
            $html.removeClass(current.class);
            current = this.themes[current.parent];
          } while (current);
        }
        if (theme !== 'default') {
          let current = newThemeDatas;

          do {
            $html.addClass(current.class);
            current = this.themes[current.parent];
          } while (current);
        }

        this.theme = theme;

        //Désactiver les autres animations
        for (const key in this.themes) {
          if (Object.hasOwnProperty.call(this.themes, key)) {
            const element = this.themes[key];

            if (element.animation_class)
              $('body').removeClass(element.animation_class);
          }
        }

        if ($('#rcmfd-toggle-anims').length > 0) {
          //Afficher ou non un bouton pour activer les animations
          if (!this.themes[this.theme].animation_class) {
            $('#rcmfd-toggle-anims')
              .addClass('disabled')
              .attr('disabled', 'disabled')
              .parent()
              .parent()
              .css({ opacity: 0, position: 'absolute' });
          } else {
            $('#rcmfd-toggle-anims')
              .removeClass('disabled')
              .removeAttr('disabled')
              .parent()
              .parent()
              .css({ opacity: '', position: '' });
          }

          //Activer ou non les animations du thème
          let current = this.themes[this.theme];

          if (current.animation_class) {
            if (
              rcmail.env.animation_enabled ??
              current.animation_enabled_by_default
            ) {
              $('body').addClass(current.animation_class);
              $('#rcmfd-toggle-anims')[0].checked = false;
            } else $('#rcmfd-toggle-anims')[0].checked = true;
          }
        }
      }

      $('iframe.mm-frame').each((i, e) => {
        try {
          if (!$(e).hasClass('discussion-frame'))
            e.contentWindow.MEL_ELASTIC_UI.update_theme(theme, false);
        } catch (error) {}
      });

      if (rcmail.env.task === 'settings') {
        try {
          let $settings_frames = $('iframe');
          for (const iterator of $settings_frames) {
            iterator.contentWindow.MEL_ELASTIC_UI.update_theme(theme, false);
          }
        } catch (error) {}
      }

      if (post) {
        mel_metapage.Functions.post(
          mel_metapage.Functions.url('mel_metapage', 'plugin.update_theme'),
          { _t: theme },
          (f) => {},
        );
      }

      rcmail.triggerEvent('theme.changed', theme);

      return this;
    }

    get_themes() {
      return this.themes || [];
    }
  }

  try {
    /**
     * Contient les fonctions de la classe Mel_Elastic
     */
    window.MEL_ELASTIC_UI = new Mel_Elastic();
    window.mel_deploy_undeploy = (e, selector) => {
      e = $(e);
      if (e.css('display') !== 'none') {
        e.css('display', 'none');
        $(selector).css('display', '');
      } else {
        e.css('display', '');
        $(selector).css('display', 'none');
      }
    };
  } catch (error) {
    console.error('### [BNUM]MEL_ELASTIC_UI : ', error);
  }
});