;(function(factory) {
    "use strict";

    var _cmd_ = {};

    if (typeof exports === 'object') {
        if (typeof require === 'function'){
            var ecmd = require('./ecmd').ECMD;

            _cmd_ = factory;

            Object.keys(ecmd).forEach(function (cmd) {
                    _cmd_[cmd] = ecmd[cmd];
                });

            exports.CMD = _cmd_;
        } else {
            exports.CMD = factory;
        }
    } else {
        _cmd_ = factory;

        if (window && window.mcConst && window.mcConst._CMD_){
            Object.keys(_cmd_).forEach(function (cmd) {
                window.mcConst._CMD_[cmd] = _cmd_[cmd];
            });
        }

        window.getCMD = function getCMD(){
            if (window.hasOwnProperty('_ECMD_')){
                Object.keys(window._ECMD_).forEach(function (cmd) {
                    _cmd_[cmd] = window._ECMD_[cmd];
                });
            }

            return _cmd_;
        }
    }
})({
        Login        : 'login',
        Register     : 'register',
        Reminde      : 'reminde',
        OK           : 'OK',
        Ping         : 'ping',
        History      : 'history',
        NewFoto      : 'newfoto',
        Logs         : 'logs',
        StopLogs     : 'stopLogs',
        Blocked      : 'blocked',
        cLog         : 'cLog',
        AdsFiles     : 'adsFiles',
        checkSSL     : 'checkSSL',
        generateCert : 'generateCert',
        checkPush    : 'checkPush',
        getUrl       : 'getUrl',
        
        getUrlProcess: 'getUrlProcess',
        getUrlAbort  : 'getUrlAbort',
        getUrlError  : 'getUrlError',

        encode_qrcode: 'encode_qrcode',

        // === FTP == browser -> node ================

        bn_ftp_get_list : "bn0001", //
        bn_ftp_login    : "bn0002", //

        // === FTP == node -> browser ================

        nb_ftp_get_list : "nb0001", //
        nb_ftp_login    : "nb0002", //
        nb_ftp_error    : "nb0003", //

        // ===========================================

        sc_drop_connect: '9999',

        // = Client CMD ==============================
        cs_hello                                : '0001', // команда-приветствие, отправляется от клиента при подключении к серверу. С этой команды начинается любое подключение к серверу.
        cs_login                                : '0003', // подключение к серверу под заданной учётной записью. Логин может происходить по одному из вариантов: UIN, ник или адрес электронной почты.
        cs_register_new_user                    : '0004', // зарегистрировать на сервере нового пользователя.
        cs_domain_login                         : '0005', // подключение к серверу с доменной авторизацией в Active Directory.
        cs_restore_account                      : '0006', // восстановление своей учётной записи
        cs_work_time_upload_old_data            : '0007', // отправка на сервер данных о рабочем времени клиента, пока тот был в офлайн (нужна доработка)
        cs_restore_connect_to_channels          : '0008', // восстановить вход в открытые каналы после отключения от сервера (нужна доработка)
        cs_get_bbs                              : "0009", // получить полный текст доски объявлений
        cs_get_adv_new_data                     : "000A", // запрос новых данных на рекламу
        cs_special_ping                         : "000B", // пинг сервера, проверка связи
        cs_ping                                 : "000B", // пинг от клиента, проверка, есть ли соединение. Если длительное время от клиентов не было пинга или любого другого запроса - соединение считается "мёртвым" и автоматически удаляется с сервера.

        cs_ping2                                : "000C",

        cs_get_channels_list                    : "000D", // получить с сервера список созданных каналов
        cs_get_actions_list                     : "000E", // получить список ACTIONs, если они есть и к ним есть доступ
        cs_work_time_control_active_caption     : "000F", // отправка на сервер данных о рабочем времени, нужна доработка
        // deprecated cs_get_small_user_info                  : "0010", // получить краткую информацию о пользователе
        cs_private_request                      : "0011", // запрос на открытие привата с указанным пользователем
        cs_get_hello_msg                        : "0012", // запрос содержимого приветствия канала
        cs_pong                                 : "0013", // ответ на серверную команду sc_ping
        cs_get_contacts_list                    : "0014", // получить список личных персональных контактов.
        cs_get_common_contacts_list             : "0015", // получить список общих контактов (древовидный список контактов, общий для всех пользователей сервера).
        cs_put_msg2txt_channel                  : "0016", // отправить сообщение в текстовый канал
        cs_private_msg                          : "0017", // отправить приватное сообшение другому пользователю
        cs_join_txt_channel                     : "0018", // подключиться к существующему текстовому каналу
        cs_leave_txt_channel                    : "0019", // выйти из текстового канала
        cs_add_new_bbs                          : "001A", // отправить новое сообщение на доску объявлений

        cs_create_txt_channel                   : "001D", // создать новый текстовый канал.
        cs_get_uin_info                         : "001E", // получить полную информацию об указанном пользователе
        cs_get_uin_foto                         : "001F", // получить фотографию пользователя
        cs_get_remote_uin_current_time          : "0020", // получить текущее системное время удалённого пользователя
        cs_send_my_current_time                 : "0021", // отправить свою текущую дату и время указанному пользователю
        cs_get_user_computer_info               : "0022", // получить IP/MAC адрес и название компьютера удалённого клиента
        cs_get_active_window_caption            : "0023", // получить заголовок активного окна пользователя
        cs_active_window_caption                : "0024", // отправка заголовка активного окна
        cs_get_ignores_list                     : "0025", // получить список игноров для указанного пользователя
        cs_update_ignores_list                  : "0026", // обновить персональный список игноров (доработать разбор списка)
        cs_get_favorite_channels                : "0027", // получить список "любимых" каналов
        cs_add_favorite_channel                 : "0028", // добавить канал с указаненым идентификатором в список "любимых"
        cs_delete_favorite_channel              : "0029", // удалить текстовый канал из списка "любимых"
        cs_get_msgtemplates                     : "002A", // получить список персональных "быстрых" сообщений
        cs_update_msgtemplates                  : "002B", // сохранить на сервере в своём аккаунте список "быстрых" сообщений
        cs_set_account_password                 : "002C", // установить новый пароль для своей учётной записи

        cs_add_personal_contact_group           : "001B", // добавить новую группу в персональную панель контактов
        cs_delete_id_from_personal_contacts     : "001C", // удалить группу контактов из личного списка контактов
        cs_add_new_contact                      : "002D", // добавить пользователя в личный список контактов
        cs_rename_contacts_group                : "002F", // переименовать личную группу контактов
        cs_move_personal_contact                : "0030", // перенести персональный контакт из одной группы в другую

        cs_open_remote_computer_shares          : "0031", // окрыть удалённый компьютер по сети Проводником (показать открытые ресурсы (shares))
        cs_forcibly_join_to_text_channel        : "0032", // принудительно включить выбранного пользователя в указанный текстовый канал
        cs_change_txtch_topic                   : "0033", // изменить тему текстового канала
        cs_get_public_ftp_info                  : "0034", // получить информацию с сервера о публичном доступе к общему FTP серверу
        cs_get_channel_history_messages         : "0035", // получить историю сообщений в указанном текстовом канале
        cs_rename_text_channel                  : "0036", // переименовать существующий текстовый канал
        cs_ignore_get                           : "0037", // добавить в игнор пользователя и / или получить информацию об его игнорировании
        cs_add_ignore                           : "0038", // добавить юзера в игнор-список (либо обновить данные)
        cs_clear_text_channel                   : "0039", // очистить выбранный текстовый канал от всех сообщений у всех находящихся в нём пользователей
        cs_personal_message2txtch               : "003A", // персональное/важное сообщение лично другому пользователю в текстовый канал
        cs_block_mac_by_uin                     : "003B", // заблокировать MAC адрес пользователя по его UIN
        cs_block_ip_by_uin                      : "003C", // заблокировать IP адрес пользователя по его UIN
        cs_kill_user                            : "003D", // отключить пользователя от сервера
        cs_kick_from_txt_channel                : "003E", // выгнать пользователя из текстового канала
        cs_private_beep                         : "003F", // отправить пользователю звуковой сигнал
        cs_get_server_uptime                    : "0040", // получить аптайм сервера MyChat
        cs_get_server_version                   : "0041", // получить версию сервера
        cs_halt_user                            : "0042", // отключить пользователя от сервера и завершить его приложение
        cs_set_state                            : "0043", // установить свой сетевой статус (свободен, занят и т.п.)
        cs_typing_notify                        : "0044", // отправить уведомление пользователю о том, что мы ему что-то пишем в привате
        cs_get_user_state                       : "0045", // получить статус другого пользователя
        cs_set_uin_foto                         : "0046", // установить фотографию для своего профиля
        cs_quit                                 : "0047", // пользователь отключается от сервера и посылает ему уведомление об этом
        cs_files_offline_ok                     : "0048", // пользователь согласился принять отложенные для него на сервере файлы
        cs_files_offline_late                   : "0049", // мы не хотим принимать отложенные для нас файлы прямо сейчас, примем их позже
        cs_files_offline_delete                 : "004A", // удалить все файлы, которые были отправлены офлайн указанным пользователем
        cs_get_user_quick_info                  : "004B", // получить краткую информацию об указанном пользователе
        cs_raw_data                             : "004C", // отправка произвольного пакета данных для онлайн-клиента
        cs_raw_data_txt_channel                 : "004D", // отправка произвольного пакета данных в текстовый канал
        cs_action                               : "004E", // выполнение MyChat Action
        cs_stat                                 : '0058', // отправить на сервер статистику использования сервисов чата
        cs_fatal_error_info                     : '0054', // отправить на сервер данные о произошедшей фатальной ошибке, приведшей к краху клиентского приложения

        // === file transfer ===

        cs_files_request_transfer               : '004F', // отправить запрос на отправку файлов и/или папок другому пользователю
        cs_files_transfer_deny                  : '0050', // отказаться принимать файлы от другого пользователя
        cs_files_transfer_accept                : '0051', // согласиться принимать файлы от другого пользователя
        cs_files_transfer_abort                 : '0052', // прервать передачу файлов
        cs_files_internal_send_idx              : '0053', // уведомление о начале передачи очередного файла из списка файлов для отправки
        cs_files_transfer_request_abort         : '0055', // отправить пользователю отказ от принятия файлов
        cs_files_internal_sended_ok_idx         : '0056', // успешно передан файл с указанным индексом из общего списка передаваемых файлов в сеансе связи
        cs_files_transfer_progress              : '0061', // отправить информацию другому пользователю о прогрессе передаваемых файлов. Отправляется не чаще раза в секунду
        cs_files_success_recieved               : '0062', // уведомление отправителю файлов, что все файлы успешно приняты
        cs_files_request_timeout                : '0057', // команда, которая отправляется автоматически, по истечении периода времени, когда получатель ничего не ответил на
                                                          // запрос приёма файлов(например, его не было за компьютером). Передача файлов в этом случае должна быть отменена автоматически
        // ==========================

        cs_send_broadcast                       : '005A', // отправить оповещение
        cs_get_broadcast_presets_names          : '005B', // получить список персональных пресетов для рассылки оповещений
        cs_delete_broadcast_preset              : '005C', // удалить шаблон списка пользователей для оповещения
        cs_add_broadcast_preset                 : '005D', // добавить новый пресет для оповещений
        cs_broadcast_readed_notify              : '005E', // уведомить отправителя оповещения персональным сообщением, что его сообщение только что было прочитано получателем, набор объектов
        cs_broadcast_just_readed                : '005F', // отправить на сервер уведомление об обязательном прочтении полученных оповещений (или одного оповещения), набор объектов

        // ==========================

        cs_get_users_list                       : '0059', // получить с сервера список пользователей (ФИО, пол, аватар и т.п.) согласно запросу
        cs_set_uin_info                         : '0068', // обновить информацию о профиле пользователя
        cs_find_users                           : '0060', // поиск пользователей на сервере согласно заданным критериям. Есть два варианта объекта
        cs_ban_user                             : '0066', // наложить наказание на пользователя (бан). По истении строка наказания оно автоматически снимается сервером.
                                                          // Наказание может наложить только оператор канала
        cs_set_user_online_notify               : '0067', // установить статус онлайн-уведомления о присутствии другого пользователя в сети
        cs_get_private_info                     : '0078', // получить краткую информацию о пользователе, с которым открывается приватный разговор
        cs_send_messages_buffer                 : "0079", // отправка буфера сообщений на сервер перед разрывом соединения

        cs_media_call                           : '007A',
        cs_media_call_accept                    : '007B',
        cs_media_call_reject                    : '007C',
        cs_media_call_close                     : '0080',
        cs_media_call_error                     : '0081', //произошла ошибка при сеансе аудио или видео связи
        cs_media_call_busy                      : '008E',

        cs_web_get_user_foto_file               : '007D',
        cs_web_support_i_am_busy                : '0094', // если пользователю WEB-поддержки с сайта приходит сообщение не от оператора, с кем он в данный момент говорит, он отвечает этой командой

        cs_adm_list_users                       : "0086", // получить список пользователей с сервера по заданным критериям. Нужны административные права
        cs_adm_get_server_info                  : '0090',
        cs_adm_get_user_profile                 : "0091", // получение полной информации о профиле любого пользователя, административная команда
        cs_adm_list_roles                       : '008B', // получить список групп прав пользователей (роли), нужны административные права
        cs_adm_set_user_password                : '0097', // изменить пароль пользователя
        cs_adm_create_user                      : '0096', // создать нового пользователя, административная команда
        cs_adm_remove_user                      : '0095', // удалить пользователя, административная команда
        cs_adm_modify_user_profile              : '0098', // модификация профиля юзера
        cs_adm_clear_user_foto                  : '0099', // удаление фотографии указанного пользователя, админкоманда
        cs_adm_get_server_options               : '009A', // получить все настройки сервера, админкоманда
        cs_adm_get_server_net_interfaces        : '009C', // получить список реальных IP всех сетевых интерфейсов сервера MyChat, админкоманда
        cs_adm_get_user_info_by_preset          : '009D', // получить список любых полей из профиля пользователя, админкоманда
        cs_adm_set_server_options               : '009B', // установить настройки сервера, админкоманда
        cs_adm_remove_all_logs                  : '009E', // удалить все протоколы на сервере, админкоманда
        cs_adm_make_backup_server_db            : '009F', // создать резервную копию базы данных сервера, админкоманда

        cs_adm_set_user_foto                    : "00A0", // назначить фото для пользователя

        cs_adm_get_job_positions_list           : '0093', // получить список должностей компании, административная команда
        cs_adm_job_positions_del                : '00A4', // удалить должность по индексу
        cs_adm_job_positions_add                : '00A3', // добавить новую должность
        cs_adm_job_positions_set_sequence       : '00A2', // установить последовательность должностей компании, массив, админкоманда (последовательсть ID должностей, те, что первые - "старше" тех, кто дальше)
        cs_adm_job_positions_modify             : '00A5', // изменить должность

        cs_adm_role_add                         : '00A6', // добавить новую группу прав
        cs_adm_role_delete                      : '00A7', // удалить группу прав
        cs_adm_role_modify                      : '00A8', // изменить группу прав
        cs_adm_role_set_rights                  : '00A9', // установить новые правила для группы прав
        cs_adm_role_users_list                  : '00AA', // получить список пользователей, входящих в заданную группу прав
        cs_adm_role_apply_to_users              : '00AB', // назначить роль указанному списку пользователей

        cs_send_invite                          : '00AC', // todo: отправить приглашение для любого пользователя присоединиться к MyChat

        cs_adm_check_smtp                       : '00AD', // проверить работоспособность системы отправки почты

        cs_adm_enter_mychat_reg_data            : '00AE', // ввести регистрационные данные для MyChat Server
        cs_adm_enter_mychat_guest_reg_data      : '00AF', // ввести серийный номер для MyChat Guest

        cs_adm_get_websupport_groups            : '00B0', // получить список групп для веб-поддержки
        cs_adm_add_user_to_websupport_group     : '00B1', // перенести пользователя в заданную группу веб-поддержки
        cs_adm_delete_user_from_websupport      : '00B2', // удаление пользователя с группы веб-поддержки
        cs_adm_add_websupport_group             : '00B3', // добавить новую группу WEB-поддержки
        cs_adm_delete_websupport_group          : '00B4', // удалить группу веб-поддержки
        cs_adm_rename_websupport_group          : '00B5', // переименовать группу веб-поддержки
        cs_adm_get_websupport_group_users       : '00B6', // получить список пользователей указанной группы WEB-поддержки

        cs_adm_get_custom_options               : '00B7', // запросить кастомные настройки

        cs_adm_get_total_contacts_list          : '00B8', // запросить общий список контактов
        cs_adm_tcl_add_group                    : '00B9', // добавить новую группу в общий список контактов
        cs_adm_tcl_del_ids_list                 : '00BA', // удалить список групп и/или пользователей из общего списка контактов
        cs_adm_tcl_add_users_list_to_group      : '00E3', // добавить список пользователей в указанную группу
        cs_adm_tcl_move_users_and_groups        : '00E4', // переместить пользователей и/или группы в пределах общего списка контактов
        cs_adm_tcl_rename_group                 : '00E5', // переименовать группу в общем списке контактов

        cs_adm_import_users                     : '00BB', //

        cs_adm_return_server_to_zero_state      : '00BD', // вернуть базу данных MyChat Server в первоначальное состояние, все данные будут удалены. Админкоманда

        cs_adm_set_custom_options               : '00BC', // установить заданные настройки, количество полей может быть переменным
        cs_adm_get_licenses                     : '00BE', // получить информацию по лицензиям MyChat Server

        cs_adm_get_antiflood_filter_data        : '00BF', // получить информацию по антифлуд-фильтрам. Админкоманда
        cs_adm_set_antiflood_filter_data        : '00C0', // установить настройки серверных антифлуд-фильтров. Админкоманда
        cs_adm_modify_flood_except_users        : '00C1', // изменить список пользователей, которых не касаются настройки антифлуд-фильтров
        cs_adm_get_flood_except_users_list      : '00C2', // получить список пользователей, которых не касаются настройки антифлуд-фильтров

        cs_adm_get_allow_ip_list                : '00C3', // получить список IP адресов ("белый" список), которым разрешено подключаться к MyChat Server
        cs_adm_add_new_ip_to_allow_list         : '00C4', // добавить новый IP адрес, маску или диапазон в список разрешённых адресов
        cs_adm_delete_allow_ip                  : '00C5', // удалить IP адрес из списка разрешённых адресов для подключения
        cs_adm_clear_allow_ip_list              : '00C6', // полностью очистить список разрешённых IP адресов

        cs_adm_get_blocked_ip_list              : '00C7', // получить список заблокированных IP адресов (масок и диапазонов). Админкоманда
        cs_adm_add_ip_to_block_list             : '00C8', // добавить IP адрес в список заблокированных
        cs_adm_delete_blocked_ip                : '00C9', // удалить из списка заблокированных IP адрес/маску/диапазон
        cs_adm_clear_blocked_ip_list            : '00CA', // очистить список заблокированных IP адресов/масок/диапазонов

        cs_adm_get_blocked_mac_list             : '00CB', // получить список заблокированных MAC адресов
        cs_adm_delete_blocked_mac               : '00CC', // удалить заблокированный MAC адрес из общего списка
        cs_adm_add_mac_to_block_list            : '00CD', // добавить MAC адрес в список заблокированных
        cs_adm_clear_blocked_mac_list           : '00CE', // очистить список заблокированных MAC адресов

        cs_adm_get_deny_nicks_list              : '00CF', // получить список заблокированных ников в JSON-формате
        cs_adm_add_deny_nick                    : '00D0', // добавить новый ник в заблокированные
        cs_adm_delete_deny_nick                 : '00D1', // удалить ник из списка блокировок
        cs_adm_clear_deny_nicks_list            : '00D2', // полностью очистить список заблокированных ников

        cs_adm_get_deny_conf_list               : '00D3', // получить список заблокированных конференций в JSON-формате
        cs_adm_add_deny_conf                    : '00D4', // добавить новую конференцию в список заблокированных
        cs_adm_delete_deny_conf                 : '00D5', // удалить конференцию из блек-листа
        cs_adm_clear_deny_conf_list             : '00D6', // полностью очистить список заблокированных конференций

        cs_adm_get_bad_words_filters_data       : '00D7', // получить информацию по фильтрам плохих слов в чате
        cs_adm_set_bad_words_filter_data        : '00D8', // сохранить данные по фильтрам "плохих" слов
        cs_adm_get_bad_words_list               : '00D9', // получить полный список "плохих" слов. Админкоманда
        cs_adm_add_bad_word_to_filters          : '00DA', // добавить новое "плохое" слово в список фильтрации, админкоманда
        cs_adm_del_bad_word_from_filters        : '00DB', // удалить "плохое" слово и общего списка
        cs_adm_clear_bad_words_list             : '00DC', // полностью очистить список "плохих" слов, админкоманда
        cs_adm_load_badwords_except_confs       : '00DD', // получить список конференций, в которых отключен фильтр мата
        cs_adm_add_badwords_except_conf         : '00DE', // добавить новую конференцию в список исключений антимат-фильтра
        cs_adm_del_badwords_except_conf         : '00DF', // удалить конференцию из списка исключений антимат-фильтра
        cs_adm_clear_badwords_except_confs      : '00E0', // очистить список исключений конференций антимат-фильтров

        cs_adm_export_users_to_csv              : '00E1', // экспорт списка пользователей в CSV файл
        cs_adm_multiply_modify_users            : '00E2', // множественное изменение данных пользователей
        cs_get_online_users_states              : '00E6', // получить список UIN-ов и статусов всех пользователей, которые в данный момент есть онлайн
        cs_get_depts_list                       : '00E7', // получить список подразделений компании

        cs_adm_get_all_text_confs               : '00E8', // получить список всех текстовых конференций

        cs_adm_force_update_global_contacts     : '00E9', // принудительно обновить кеш общего списка контактов и разослать его всем онлайн-пользователям

        cs_adm_get_bans_list                    : '00EA', // получить список всех банов, которые в данный момент актуальны на сервере. Админкоманда
        cs_adm_del_bans                         : '00EB', // снять указанные баны по индексам, админкоманда
        cs_adm_get_moderators                   : '00EC', // получить список модераторов, админкоманда
        cs_adm_get_conf_names_by_uid_list       : '00ED', // получить список названий конференций по их UID
        cs_adm_delete_moderators                : '00EE', // удалить список модераторов
        cs_adm_add_moderator                    : '00EF', // добавить модератора конференции
        cs_adm_del_moderator_confs              : '00F0', //

        cs_get_broadcasts_history               : '00F1', //

        cs_get_random_uin                       : '00F3', // получить случайный UIN из списка людей онлайн
        cs_set_custom_ignore                    : '00F4', // добавить игнор для указанного пользователя
        cs_del_custom_ignore                    : '00F5', //  игнор для указанного пользователя

        cs_adm_get_auto_conf_list               : '00F6', // получить список автосоздаваемых конференций на сервере
        cs_adm_del_auto_conf                    : '00F7', // удаление автосоздаваемой конференции по идентификатору
        cs_adm_add_auto_conf                    : '00F8', // создать новый автосоздаваемый канал
        cs_adm_update_auto_conf                 : '00F9', // обновить данные об австосоздаваемой конференции
        cs_adm_add_group_to_auto_conf           : '00FA', // добавить группу для включения в автосоздаваемую конференцию
        cs_adm_del_group_from_auto_conf         : '00FB', // удалить группу пользователей из автосоздаваемой конференции

        cs_adm_get_bbs_msg_list                 : '00FC', // получить список актуальных сообщений на доске объявлений
        cs_adm_bbs_clear_actual                 : '00FD', // удалить все актуальные сообщения на доске объявлений
        cs_adm_bbs_get_msg                      : '00FE', // получить данные сообщение на доске объявлений по его идентификатору
        cs_adm_bbs_modify_msg                   : '00FF', // изменить сообщение на доске объявлений, админкоманда
        cs_adm_bbs_del_messages                 : '0100', // удалить актуальные сообщения на доске объявлений по их индексу
        cs_adm_add_bbs_message                  : '0101', // добавить новое сообщение на доску объявлений

        cs_adm_save_ad_options                  : '0102', // сохранить данные по настройке Active Directory интеграции
        cs_adm_get_ad_options                   : '0103', // получить настройки по Active Directory интеграции

        cs_adm_import_ad_users                  : '0104', // импортировать выбранных пользователей active directory

        cs_adm_get_update_packet_data           : '0105', // получить данные update-пакета для клиентов MyChat
        cs_adm_upload_update_packet             : '0106', // загрузить пакет обновления на сервер. Административная команда

        cs_adm_get_online_users_list            : '0107', // получить список онлайн-пользователей
        cs_adm_kill_users                       : '0108', // отключить от сервера указанный список пользователей (разорвать соединение). В ответ приходит sc_adm_command_complete с командой cs_adm_kill_users
        cs_adm_halt_users                       : '0109', // отправить списку пользователей команду завершения работы приложения с последующим отключением от сервера.
                                                          // В ответ приходит sc_adm_command_complete с командой cs_adm_halt_users
        cs_adm_get_ad_users_list                : '010A', // получить с сервера Active Directory список пользователей
        cs_sync_private_history_last            : '010B', // получить последние N сообщений из приватного разговора с указанным пользователей. Можно запросить максимум 100 сообщений
        cs_sync_private_history                 : '010C', // получить логи разговоров в привате (синхронизация)

        cs_adm_get_script                       : '010D', // получить скрипт по его ID
        cs_adm_get_script_event_states          : '010E', // получить список статусов доступных скриптов. Нужны админправа
        cs_adm_script_check_syntax              : '010F', // проверить синтаксис написанного серверного скрипта
        cs_adm_script_onoff                     : '0110', // включение выключение скрипта
        cs_adm_script_run                       : '0111', // запуск скрипта
        cs_adm_script_delete                    : '0112', // удалить скрипт
        cs_adm_script_save                      : '0113', // сохранить скрипт
        cs_adm_script_create                    : '0114', // создать скрипт

        cs_adm_restart_server_services          : '0115', // принудительный перезапуск встроенного WEB-сервера, админкоманда

        cs_adm_get_active_conf_list             : '011B', // получить список работающих конференций, админкоманда
        cs_adm_kill_txt_conf                    : '011C', // удалить текстовую конференцию с сервера (не автосоздаваемую). Админкоманда. В ответ приходит complete: cs_adm_kill_txt_conf либо ошибка
        cs_adm_get_users_in_conf                : '011D', // получить список пользователей в конференции
        cs_adm_kick_users_from_conf             : '011E', //выгнать из конференции указанных пользователей

        cs_info                                 : '077C', //

        cs_adm_remove_conversations             : '0183', // удалить любые протоколы разговоров на сервере и у клиентов за всё время
        cs_live_reconnect                       : '01AF', // лайв переподключение андроида

        // ==== KANBAN =========================================================

        cs_kanban_get_projects_list             : '0117', // получить список проектов kanban
        cs_kanban_get_stages_list               : '0118', // получить список этапов проекта
        cs_kanban_get_tasks_list                : '0119', // получить список задач kanban-доски
        cs_kanban_get_tags_list                 : '011A', // получить список тегов kanban-доски
        cs_kanban_add_project                   : '0120', // добавить новый проект на kanban-доску
        cs_kanban_add_stage                     : '0121', // добавить новый stage на kanban доску
        cs_kanban_delete_stage                  : '0122', // удалить этап указанного проекта с kanban-доски
        cs_kanban_modify_stage                  : '0123', // изменить этап проекта kanban-доски
        cs_kanban_add_task                      : '013B', // добавить новую задачу на kanban-доску
        cs_kanban_task_move                     : '013C', // перенос задачи в новый stage
        cs_kanban_task_remove                   : '013D', // удаление задачи
        cs_kanban_task_edit                     : '013E', // редактирование задачи
        cs_kanban_add_comment                   : '013F', // добавление нового комментария к таску на kanban-доске
        cs_kanban_get_comments                  : '0140', // получить список комментариев к задаче на kanban-доске
        cs_kanban_delete_comment                : '0141', // удалить комментарий к задаче с доски kanban
        cs_kanban_close_project                 : '0142', // закрыть проект kanban
        cs_kanban_reopen_project                : '0143', // открыть заново уже закрытый проект kanban
        cs_kanban_delete_project                : '0144', // удалить kanban проект
        cs_kanban_edit_project                  : '0145', // изменить kanban-проект
        cs_kanban_get_personal_options          : '0149', // получить персональный набор опций клиента по его UIN
        cs_kanban_save_personal_options         : '014A', // сохранить персональный набор настроек. JSON объект, любые поля

        cs_kanban_get_project_info              : '014C', // получить краткую информацию о проекте
        cs_kanban_get_task_info                 : '014D', // получить краткую информацию о задаче

        cs_kanban_get_last_performers           : '0153', // получить последних исполнителей задач для указанного проекта
        cs_kanban_get_project_tags_list         : '0168', // получить список тегов для конкретного проекта
        cs_kanban_get_dashboard                 : '0169', // получить краткую сводную информацию о проектах Kanban

        // ==============================================================

        cs_get_personal_options                 : '0149', // получить персональный набор опций клиента по его UIN
        cs_save_personal_options                : '014A', // сохранить персональный набор настроек. JSON объект, любые поля

        cs_browser_info                         : '0124', // информация о браузере пользователя, если это web-клиент

        cs_media_offer                          : '0125', // создание медиа-запроса к клиенту
        cs_media_answer                         : '0126', // создание медиа-ответа на запрос media offer
        cs_media_ice_candidate                  : '0127', // ответ STUN/TURN сервера для собеседника
        cs_media_change_settings                : '0128', // изменение настроек медиа-данных пользователя (аудио/видео)
        cs_media_ready                          : '0129', // NodeWebKit клиента успешно стартовал и работает
        cs_media_exit_from_mid                  : '012A', // пользователь вышел из media-канала

        cs_import_personal_contacts             : '012B', // импорт персональных контактов

        cs_adm_get_client_options_presets       : '012C', // получить список шаблонов настроек клиентов, админкоманда
        cs_adm_del_client_options_preset        : '012D', // удалить шаблон настроек клиента по его идентификатору, админкоманда
        cs_adm_create_client_options_preset     : '012E', // создать новый шаблон настроек для клиентов, админкоманда
        cs_adm_update_client_options_preset     : '012F', // изменить шаблон настроек, админкоманда
        cs_adm_apply_roles_to_options_presets   : '0131', // назначить роли групп прав пользователей для наборов настроек, массив

        cs_adm_add_new_user_group               : '0132', // создать новую группу пользователей
        cs_adm_get_user_groups                  : '0133', // получить список существующих групп пользователей
        cs_adm_get_group_users_list             : '0134', // получить список пользователей указанный группы
        cs_adm_add_users_to_group               : '0135', // добавить список пользователей в группу
        cs_adm_remove_users_from_group          : '0136', // удалить пользователей из группы
        cs_adm_rename_group_of_users            : '0137', // переименовать указанную группу пользователей
        cs_adm_delete_groups_list               : '0138', // удалить список групп пользователей

        cs_adm_set_integration_tool_options     : '0139', // установить настройки интеграции инструмента (phpbb... etc.)
        cs_adm_get_integration_tool_options     : '013A', // получить настройки интеграции инструмента (phpbb... etc.)

        cs_adm_get_check_domains_list           : '0146', // получить список доменов с адресами и портами, которые используются для проверки наличия пользователей в данных доменах
        cs_adm_add_check_domain                 : '0147', // добавить домен в список доменов для проверки возможности логинов доменных пользователей
        cs_adm_delete_check_domain              : '0148', // удалить домен из списка доменов для проверки подключающихся доменных пользователей

        cs_adm_get_allowed_and_blocked_plugins  : '014E', // получить списки разрешённых и запрещённых плагинов
        cs_adm_add_allowed_or_blocked_plugins   : '014F', // добавить названия плагинов в список запрещённых или разрешённых клиентских плагинов
        cs_adm_delete_allowed_or_blocked_plugin : '0150', // удалить плагин из списка разрешённых или запрещённых клиентских плагинов

        cs_adm_change_mychat_server             : '0151', // изменить параметры подключения клиентов к серверу MyChat
        cs_adm_get_script_info                  : '0152', // получить краткую информацию о скрипте, админкоманда

        // == FORUM ===========================================

        cs_forum_get_structure                  : '0156', // получить структуру форума (разделы и топики, в JSON)
        cs_forum_create_section                 : '0157', // добавить новый раздел в форум
        cs_forum_delete_section                 : '0158', // удаление раздела форума
        cs_forum_change_section                 : '0159', // перемещение/переименование раздела форума
        cs_forum_create_topic                   : '015A', // добавить новый топик в форум
        cs_forum_change_topic                   : '015B', // перемещение/переименование топика форума
        cs_forum_create_post                    : '015C', // добавить новый пост в форум

        cs_forum_delete_post                    : '015D', // удалить сообщение из форума
        cs_forum_delete_topic                   : '015E', // удалить тему из форума
        cs_forum_change_post                    : '015F', // перемещение/переименование сообщения форума
        cs_forum_get_topic_posts                : '0160', // получить список сообщений указанного топика форума
        cs_forum_get_section_info               : '0161', // получить информацию о секции (список топиков, краткая информация о них: LastChange, UIN, DisplayName, Sex, Avatar, DateTime, CountMessages)

        cs_get_book                             : '0164', // получить справочник с сервера
        cs_is_file_exists                       : '0165', // узнать, есть ли файл с таким хэш-кодом на сервере. Параметры Where и ID потом вернутся обратно пользователю в команде-ответке
        cs_get_image_thumbs                     : '0166', // получить уменьшенную копию изображения с сервера

        cs_create_token                         : '0154', // создать токен для быстрого доступа в какой-нибудь сервис чата
        cs_login_by_token                       : '0155', // логин в MyChat/kanban/forum/web по токену
        cs_get_removed_user_info                : '016F', // получить краткую информацию об удалённом пользователе

        cs_adm_get_integration_telegram_users   : '016B', // получить список пользователей для интеграции с Telegram
        cs_adm_integration_telegram_delete_user : '016C', // удалить Telegram пользователя из списка для интеграции
        cs_adm_integration_telegram_modify_user : '016D', // изменить параметры Telegram пользователя из списка для интеграции
        cs_adm_integration_telegram_clear_users_list: '016E', // очистить список пользователей для интеграции с Telegram

        cs_integration_icq_set_account          : '0170', // привязать ICQ аккаунт к своей учётной записи
        cs_integration_icq_send_message         : '0171', // отправить сообщение пользователю ICQ
        cs_adm_delete_conf_message              : '0172', // удалить сообщение из текстовой конференции

        cs_adm_get_history_info_private         : '0173', // админкоманда, получить информацию по сообщениям в привате за диапазон времени. В ответ приходит 8107 sc_adm_history_info_private
        cs_adm_get_history_private              : '0174', // админкоманда, получение истории приватов двух юзеров по диапазону индексов
        cs_adm_get_history_info_conf            : '0175', // админкоманда, получить информацию по сообщениям в конференции за диапазон времени. В ответ приходит 8109 sc_adm_history_info_conf
        cs_adm_get_history_conf                 : '0176', // админкоманда, получение истории в конференции по диапазону индексов
        cs_adm_get_history_private_user_pairs   : '0177', // админкоманда, получить все пары приватных разговоров за все время, ответ будет 810B sc_adm_history_private_user_pairs

        cs_adm_check_ports                      : '017A', // проверить, не заняты ли порты указанных сервисов, админкоманда. Если всё ок, будет complete, если нет - ошибка с первым "неудачным" портом

        cs_moder_delete_conf_message            : '017B', // удалить сообщение из текстовой конференции, нужны права для удаления сообщений в конференциях из раздела "Администрирование"

        cs_adm_reconnect_users2autoconf         : '017D', // добавить/выгнать пользователей автосоздаваемой конференции, которую только что меняли

        cs_adm_get_blocked_hwid_list            : '017E', // получить список заблокированных HardwareID, админкоманда
        cs_adm_delete_blocked_hwid              : '017F', // удалить заблокированный HardwareID адрес из общего списка
        cs_adm_clear_blocked_hwid_list          : '0180', // очистить список заблокированных HardwareID на сервере
        cs_adm_add_hwid_to_block_list           : '0181', // добавить HardwareID клиента в список блокировок
        cs_adm_get_system_logs                  : '0182', // получить системные логи за период времени, админкоманда

        cs_get_options_preset                   : '0130', // получить шаблон настроек по его идентификатору

        cs_adm_modify_check_domain              : '0185', // отредактировать настройки для указанного домена
        cs_adm_get_domains_list                 : '0186', // получить список доменов, с которыми может работать сервер MyChat
        
        cs_adm_forum_get_subs_groups            : '0187', // получить список групп пользователей, которые подписаны на конкретную тему либо секцию
        cs_adm_forum_add_subs_group             : '0188', // подписать группу пользователей на тему или секцию форума
        cs_adm_forum_delete_subs_group          : '0189', // удалить группу пользователей из подписки на секцию или тему форума
        cs_adm_forum_clear_subs                 : '018A', // удалить все подписки на тему или раздел форума (группы и пользователи)
        cs_adm_forum_add_subs_user              : '018B', // подписать пользователя на тему или секцию форума
        cs_adm_forum_delete_subs_user           : '018C', // удалить пользователей из подписки на секцию или тему форума
        cs_adm_forum_get_subs_users             : '018D', // получить список пользователей, которые подписаны на конкретную тему либо секцию

        cs_sync_conf_history                    : '0179', // получить логи разговоров в конференции (синхронизация). Пользователь должен находиться в указанной конференции
        cs_get_forum_subs                       : '018E', // получить список подписок на темы и разделы форума
        cs_forum_toggle_subs                    : '018F', // добавить или убрать подписку на тему либо раздел (секцию) форума
        cs_set_custom_options                   : '0190', // сохранить кастомные настройки клиента на сервере
        cs_get_custom_options                   : '0191', // получить кастомные настройки клиента на сервере

        cs_adm_integration_quiz_check_question  : '0193', // проверить существование вопроса в базе робота-викторины по тексту вопроса или по номеру
        cs_adm_integration_quiz_add_question    : '0194', // добавить новый вопрос в базу робота-викторины
        cs_adm_integration_quiz_del_question    : '0195', // удалить вопрос из базы робота-викторины по номеру
        cs_adm_integration_quiz_modify_question : '0196', // редактировать вопрос в базе робота-викторины по индексу
        cs_adm_integration_quiz_get_stat        : '0197', // получить статистику робота-викторины
        cs_adm_integration_quiz_clear_stat      : '0198', // очистить статистику робота-викторины
        cs_adm_enter_quiz_reg_data              : '0199', // ввести серийный номер для робота-викторины

        cs_users_live_search                    : '019A', // найти список пользователей на сервере
        cs_get_private_dialogs                  : '019B', // получить список своих приватных диалогов
        cs_del_private_dialog                   : '019C', // удалить диалог с указанным юзером из списка приватных диалогов
        cs_private_msg_read                     : '019D', // пользователь только что прочитал приватное сообщение из указанного диалога
        cs_private_msg_got                      : '019E', // пользователь только что получил приватное сообщение из указанного диалога
        cs_private_get_dialog_msg_states        : '019F', // получить информацию о полученных и прочитанных сообщениях в приватном диалоге с оппонентом

        cs_adm_delete_private_message           : '01A0', // удалить сообщение из приватного диалога

        cs_delete_private_message               : '01A1', // удалить сообщение из привата, нужны права для удаления произвольных сообщений в своих приватах
        cs_redirect_private_talk                : '01A2', // перенаправить приватный разговор от одного пользователя к другому

        cs_get_users_for_redirect_dialog        : '01A3', // получить список пользователей, на которых можно сделать перенаправление диалога
        cs_forward_messages                     : '01A4', // (7.4+) переслать сообщения из привата или конференции
        cs_private_multicast_msg                : '01A5', // (7.4+) отправить приватное сообщение нескольким пользователям
        cs_create_presenter                     : '01A6', // (7.4+) создать и сохранить presenter страницы для форума
        cs_adm_terminal_command                 : '01A7', // выполнить команду из консоли админки

        cs_create_media_conf                    : '01A8', // создать медиа-конференцию или вебинар

        cs_get_delayed_commands                 : '01A9', // получить список отложенных команд

        cs_adm_modify_allow_ip                  : '01AA', // изменить IP, маску или диапазон в фильтре разрешённых IP адресов
        cs_adm_modify_blocked_ip                : '01AB', // изменить IP, маску или диапазон в фильтре запрещённых IP адресов

        cs_kanban_task_take                     : '01AC', // взять задачу в работу

        cs_get_users_changeid_data              : '01AD', // получить краткую информацию о списке пользователей
        cs_get_user_avatars                     : '01AE', // получить аватары пользователей (прямоугольные картинки)

        cs_conf_got_message                     : '01B0', // юзер получил сообщение в конференции
        cs_conf_read_message                    : '01B1', // юзер прочитал сообщение в конференции

        cs_media_busy                           : '01B2', // передать сигнал "занято" для входящего звонка, сервер обрабатывает эту команду и генерирует приватное сообщение от имени звонящего с типом сообщения MSG_TYPE_MEDIA_CALL (12) и Type=mediastate_call_busy (10)

        cs_halt_cid                             : '01B4', // отключить CID пользователя от сервера и завершить его приложение
        cs_kill_cid                             : '01B5', // отключить CID пользователя от сервера

        cs_adm_asterisk_do_connect              : '01BB', // подключиться к серверу Asterisk
        cs_adm_asterisk_send_command            : '01BC', // выполнить команду Asterisk
        cs_adm_dismiss_an_employee              : '01BD', // уволить одного или несколько сотрудников, пользователь блокируется, удаляется из всех списков контактов и из групп пользователей

        cs_adm_integration_turbosms_check_balance: '01BF', // получить баланс состояния счёта в сервисе TurboSMS
        cs_adm_integration_turbosms_send_message : '01C0', // отправить SMS либо Viber-сообщение на указанный номер телефона

        cs_adm_modify_common_user_group         : '01C3', // изменить группу пользователей
        cs_adm_create_personal_user_group       : '01C4', // создать новую личную группу пользователей
        cs_adm_get_personal_user_groups         : '01C5', // получить список персональных групп указанного пользователя
        cs_adm_modify_personal_user_group       : '01CA', // изменить персональную группу пользователей
        cs_adm_add_users_to_private_group       : '01CB', // добавить пользователей в личную группу указанного пользователя
        cs_adm_remove_users_from_private_group  : '01CC', // удалить пользователей из личной группы пользователя
        cs_adm_get_personal_group_users_list    : '01CD', // получить список пользователей персональной группы

        cs_kanban_add_user_group_to_project     : '01C6', // дать доступ группе пользователей к канбан-проекту
        cs_kanban_add_access_user_groups_to_stage: '01C8', // дать доступ списку групп пользователей к этапу канбан-проекта, ответка -
        cs_create_personal_user_group           : '01CE', // создать новую личную группу пользователей
        cs_remove_personal_user_group           : '01CF', // удалить личные группы пользователя
        cs_add_users_to_personal_user_group     : '01D0', // добавить список пользователей в личную группу пользователя
        cs_get_personal_user_group_data         : '01D1', // получить данные персональной группы пользователей
        cs_remove_users_from_personal_user_group: '01D2', // удалить пользователей из личной группы пользователя
        cs_get_personal_user_groups             : '01D3', // получить список персональных групп пользователя
        cs_rename_personal_user_group           : '01D4', // переименовать персональную группу пользователей
        cs_get_public_user_groups               : '01D5', // получить список публичных групп пользователей
        cs_get_kanban_rights_group_list         : '01D6', // получить список групп прав с доступом к канбану
        cs_get_kanban_get_projects_groups       : '01D7', // получить список групп проекта для разграничения прав доступа
        cs_kanban_remove_user_groups_from_project: '01C7', // убрать доступ для групп пользователей в канбан-проекте
        cs_get_common_user_group_data           : '01D8', // получить данные общей группы пользователей

        cs_adm_get_all_user_group_membership_list: '01D9', // получить список общих и личных групп, куда входит указанный пользователь, ответ - sc_all_user_group_membership_list

        cs_geo_position                         : '01DA', // текущая геопозиция устройства пользователя ответ на запрос 815C sc_get_geo_position (8.12+)
        cs_get_user_gps_history                 : '01DB', // запрос истории перемещений пользователя за период времени
        cs_get_user_gps_position                : '01DC', // отправить запрос на текущие GPS координаты всем онлайн-приложениям указанного пользователя

        cs_adm_fire_users                       : '01DD', // уволить одного или нескольких сотрудников, учётные записи блокируются, пользователи удаляются из общего и личных списков контактов и из всех групп ответ на команду - ошибка или sc_users_fired

        cs_media_conf                           : '01DE', // работа с медиаконференциями, универсальная команда

        cs_adm_custom_menu_create               : '01DF', // создать custom-меню для клиентских приложений
        cs_adm_get_custom_menu_list             : '01E0', // получить список существующих custom-меню для клиентских приложений
        cs_adm_remove_custom_menu_list          : '01E1', // удалить список шаблонов кастомных меню по их индексам, если всё ок, то отправляется команда sc_adm_command_complete
                                                          // если есть ошибки - то 8001 sc_error с указанием номера, что не так
        cs_adm_modify_custom_menu               : '01E2', // изменить шаблон кастомного меню по индексу
        cs_adm_add_group_to_custom_menu         : '01E3', // добавить группу пользователей в custom-menu
        cs_remove_groups_from_custom_menu       : '01E4', // удалить группы пользователей из custom-menu, если всё ок, то ответка - sc_adm_command_complete
        cs_get_custom_menu                      : '01E5', // получить данные своего custom-menu, ответ, если меню есть - 8164 sc_custom_menu (8.14+)
                                                          // если меню нету, никакого ответа не будет, ошибки тоже не будет
        cs_remote_exec                          : '01E6', // удалённый запуск скрипта на сервере, скриптовое событие OnClientRemoteExec. Нужны права

        cs_media_conf_leave                     : '01E8', // пользователь выходит из медиаконференции
        cs_media_conf_join                      : '01E9', // пользователь подключается к уже созданной медиаконференции

        cs_adm_ad_get_user_photo_thumbs         : '01EB', //  получить миниатюры фотографий пользователей из домена
        cs_adm_ad_sync_now                      : '01ED', // административная команда для немедленного выполнения синхронизации с доменом

        cs_adm_check_user_pwd                   : '01EF', // проверка, правильный ли пароль у пользователя, ответ на запрос - sc_adm_check_user_pwd_state

        cs_media_conf_close                     : '01F0', // пользователь закрывает медиаконференцию, все автоматически выходят из неё
        cs_media_conf_broadcast                 : '01F2', // универсальная команда рассылки сообщения всем пользовтелям медиа конференции, кроме отправителя

        cs_get_fresh_users_data                 : '01F3', // получить свежую информацию по изменениям ChangeID пользователей сервера, всех, у кого этот показатель больше
                                                          // указанного числа
        cs_login_done                           : '01F4', // клиент сигнализирует, что он получил от сервера основную порцию данных для первичного подключения и готов принимать оставшиеся данные
        cs_get_conf_user_list                   : '01F5', // получить список пользователей указанной конференции. Пользователь должен в ней находиться
                                                          // команда отправляется, если на клиент-сайде нет списка юзеров конференции или не совпадает
                                                          // контрольная сумма "UserListCRC32" из команды sc_join_txt_channel. Ответка -
      
        cs_close_text_conf                      : '01F6', // закрыть текстовую конференцию. Для этого должны быть права на закрытие конференций либо юзер должен быть owner-ом указанной конфы.
                                                          // Нельзя закрыть автосоздаваемую конференцию, у которой owner Elisa (UIN 0). Если в данный момент в конфе происходит звонок - он будет прекращён.
                                                          // Необязательно находиться в конференции. Если конфа будет успешно закрыта, то она блокируется, убирается из онлайн-структур сервера,
                                                          // а все юзеры получают уведомление про выход из этой конференции

        cs_adm_change_text_conf_owner           : '01F7', // сменить владельца текстовой конференции. Если владелец uin=0, то функция вернёт ошибку.
        cs_adm_get_user_conf_list               : '01F8', // получить список конференций, куда входит указанный пользователь, ответ - sc_adm_user_conf_list

        cs_adm_upload_badword_list              : '01FA', // загрузить на сервер список "плохих" слов для фильтра, список дополнит сущетвующий и в ответ придёт команда sc_badword_list_uploaded
                                                          // с информацией, сколько слов добавлено, либо ошибка
        cs_try_to_change_private_message        : '01B8', // попытка отредактировать своё сообщение в привате.
                                                          // редактировать можно только свои последние сообщения в течение заданного периода времени
        cs_try_to_change_conf_message           : '01B9', // попытка отредактировать своё сообщение в конференции, отправленное только в течение указанного в настройках сервера периода времени


        // =========================================

        adm_ldap_get_user_list: 'ldap_get_user_list',
        adm_ldap_get_user_info: 'ldap_get_user_info',

        adm_ldap_user_list: 'ldap_user_list',
        adm_ldap_user_info: 'ldap_user_info',

        // Server CMD ==============================

        sc_hello                                : '8000', // ответ на соединение клиента (сигнатура версии которую присылает клиент, например "mc5.0")
        sc_error                                : "8001", // произошла какая-то ошибка
        sc_accept_connection                    : "8002", // сервер разрешает соединение
        sc_free_edition                         : "8003", // сообщение клиенту, что тот подключен к бесплатному серверу MyChat
        sc_get_all_rights                       : "8004", // список прав клиента на сервисы чата, а также список разрешённых и запрещённых плагинов на данном сервере
        sc_login                                : "8005", // fucking login...
        //sc_job_positions                        : "8006", // * список должностей компании. Нужна доработка
        sc_remote_set_options_file              : "8007", // удалённая отправка списка настроек клиенту (файл). Будет удалено, останется только команда 8022 sc_remote_set_options
        sc_get_channels_list                    : "8008", // список каналов, которые созданы на сервере
        sc_bbs_changed                          : "8009", // уведомление клиента о том, что была изменена доска объявлений
        sc_offline_private                      : "800A", // офлайн приватное сообщение для клиента
        sc_private                              : "800B", // обычное приватное сообщение для клиента
        // !!! deprecated sc_get_small_user_info                  : "800C", // получить краткую информацию о пользователе (доделать)
        sc_show_advert_block                    : "800D", // показать рекламный блок в клиенте
        sc_hide_advert_block                    : "800E", // спрятать рекламный блок
        sc_pong                                 : "800F", // отправка с сервера команды-реакции на cs_ping, проверка связи
        sc_actions_list                         : "8010", // пришёл список команд ACTIONs с сервера, нужна доработка
        sc_files_offline_exists                 : "8011", // есть отложенные файлы для клиента
        sc_grant_private_access                 : "8012", // разрешение общаться с пользователем в привате, краткая информация о собеседнике
        sc_join_txt_channel                     : "8013", // вход пользователя в текстовый канал
        sc_txt_channel_hello_msg                : "8014", // текст приветствия указанного текстового канала
        sc_txt_channel_give_grant               : "8015", // получение прав на текстовый канал
        sc_ulist_in_txt_ch                      : "8016", // список пользователей в текстовом канале
        // !!! deprecated sc_join_newbie_txt_channel              : "8017", // новый пользователь вошёл в текстовый канал
        // !!! deprecated sc_join_newbies_txt_channel             : "8073", // новые пользователи вошли в текстовый канал
        sc_online_notify                        : "8018", // уведомление о том, что пользователь, который нам интересен, появился в онлайн
        sc_broadcast                            : "8019", // текстовое оповещение
        sc_user_online                          : "801A", // указанный пользователь подключен к серверу
        sc_trial                                : "801B", // информация о том, что используемая версия сервера незарегистрирована, и сколько дней осталось до конца работы trial-версии
        sc_remote_set_options                   : "801C", // настроить клиент в соответствии с пришедшим набором настроек в JSON формате. Доделать описание
        sc_get_msgtemplates                     : "801D", // с сервера пришёл список шаблонов для "быстрых" сообщений. Нужна переделка
        sc_get_contacts_list                    : "801E", // список личных контактов клиента. Доделать
        sc_get_common_contacts_list             : "801F", // список общих контактов клиента. Доделать
        sc_ping                                 : "8020", // 8020 принудительный пинг с сервера, проверка связи при плохом соединении, нужно при получении ответить командой cs_pong
        sc_ignore                               : "8021", // отправленное сообщение было проигнорировано другим пользователем
        sc_user_state                           : "8022", // текущий статус пользователя
        sc_leave_txt_channel                    : "8023", // пользователь вышел из указанного канала, информационное уведомление
        sc_kill_txt_ch                          : "8024", // текстовый канал удалён с сервера
        sc_bbs_body                             : "8025", // содержимое доски объявлений, доработать
        sc_add_personal_contact_group           : "8026", // в персональную панель контактов была успешно добавлена новая группа
        sc_delete_personal_contact_group        : "8027", // удалить группу из личного списка контактов
        sc_txt_channel_created                  : "8028", // уведомление о том, что на сервере создан новый текстовый канал
        sc_get_uin_info                         : "8029", // получение полной информации о пользователе
        sc_get_remote_uin_current_time          : "802A", // получить текущее системное время удалённого пользователя (remote user)
        sc_user_current_time                    : "802B", // текущие системные дата и время указанного пользователя
        sc_user_foto                            : "802C", // информация о фотографии указанного пользователя
        sc_user_computer_info                   : "802D", // информация об IP/MAC адресе пользователя и сетевом имени его компьютера
        sc_get_active_window_caption            : "802E", // запрос сервера у клиента заголовока активного окна
        sc_user_active_window_caption           : "802F", // ответ сервера клиенту с заголовока активного окна
        sc_ignores_list                         : "8030", // список игноров для указанного пользователя // доработать разбор строки
        sc_favorite_channels_list               : "8031", // список "любимых" каналов пользователя
        sc_change_password                      : "8032", // для пользователя установлен новый пароль к его учётной записи. Команда приходит в ответ на попытку изменения собственного пароля, либо при изменении пароля пользователя принудительно, на сервере, администратором
        sc_add_new_contact                      : "8033", // добавить новый контакт в персональный список контактов
        sc_delete_contact                       : "8034", // удалён контакт из личного списка контактов
        sc_rename_contacts_group                : "8035", // переименована личная группа контактов
        sc_move_personal_contact                : "8036", // персональный контакт перенесён в новую группу
        sc_open_remote_computer_shares          : "8037", // открыть удалённый компьютер по сети Проводником (показать открытые ресурсы (shares))
        sc_txtch_topic_changed                  : "8038", // изменена тема текстового канала
        sc_public_ftp_info                      : "8039", // информация о публичном доступе к FTP серверу MyChat
        sc_channel_history_messages             : "803A", // получена история сообщений в текстовом канале за временной период
        sc_rename_text_channel                  : "803B", // текстовый канал переименован
        sc_user_ignore_info                     : "803C", // информация об игнорировании указанного пользователя
        sc_clear_text_channel                   : "803D", // очистить выбранный текстовый канал от всех сообщений
        sc_personal_message2txtch               : "803E", // персональное/важное сообщение лично другому пользователю в текстовый канал
        sc_kick_from_txt_channel                : "803F", // пользователя выгнали из текстового канала
        sc_private_beep                         : "8040", // отправить пользователю звуковой сигнал
        sc_server_uptime                        : "8041", // сколько времени работает сервер MyChat без перезагрузки
        sc_server_version                       : "8042", // информация о версии сервера
        sc_user_offline                         : "8043", // пользователь с указанным UIN о тключился от сервера
        sc_halt                                 : "8044", // немедленно отключиться от сервера и завершить работу приложения в принудительном порядке
        sc_typing_notify                        : "8045", // уведомление о том, что нам в привате в данный момент кто-то пишет ответ
        // !!! deprecated sc_update_user_data                     : "8046", // обновить информацию об указанном пользователей
        sc_change_server                        : "8047", // изменить параметры подключения к серверу MyChat
        sc_user_quick_info                      : "8048", // краткая информация о пользователе
        sc_raw_data                             : "8049", // отправка произвольного пакета данных для онлайн-клиента
        sc_raw_data_txt_channel                 : "804A", // отправка произвольного пакета данных в текстовый канал

        sc_files_offline_send_accept            : "804B", // подтверждение на отправку файлов в офлайн (на сервер) для указанного пользователя
        sc_files_request_transfer               : "804C", // запрос на приём файлов от указанного пользователя
        sc_files_transfer_deny                  : "804D", // пользователь отказался принимать файлы
        sc_files_transfer_accept                : "804E", // пользователь согласился принимать файлы
        sc_files_transfer_abort                 : "804F", // пользователь прервал передачу файлов
        sc_file_internal_send_idx               : "8050", // идёт передача очередного файла
        sc_files_transfer_request_abort         : "8051", // пользователь ответил отказом на запрос приёма файлов
        sc_file_internal_sended_ok_idx          : "8052", // очередной файл успешно передан
        sc_files_request_timeout                : "8053", // отказ от приёма файлов по тайм-ауту
        sc_files_transfer_progress              : '8059', // информация о прогрессе передаваемых файлов между двумя пользователями
        sc_files_success_recieved               : '805B', //

        // !!! deprecated sc_update_self_nick_avatar_sex          : '805A', // уведомление пользователю о том, что ему следует обновить свои ник, аватар и пол в интерфейсе программы
        
        sc_registered                           : "805E", // уведомление пользователя о том, что он успешно зарегистрирован на сервере
        sc_find_users_list                      : "8058", // список пользователей, в ответ на клиентский запрос поиска пользователей (cs_find_users). Набор объектов
        sc_users_list                           : "8055", // список пользователей, отправляемый на клиент. Набор объектов
        sc_serv_stat                            : '8054', // общая статистика сервера
        sc_private_info                         : '806F', // краткая информация о пользователе, полученная по шаблону

        sc_media_call                           : '8070',
        sc_media_call_accept                    : '8071',
        sc_media_call_reject                    : '8072',
        sc_media_call_close                     : '8075',
        sc_media_call_error                     : '8076',
        sc_media_call_busy                      : '8081',
        sc_media_incoming_call_missed           : '80D7', // пропущен входящий медиазвонок от указанного пользователя, вы в данный момент общались с другим человеком
        sc_media_offer                          : '80D2', // медиа-запрос к клиенту
        sc_media_answer                         : '80D3', // ответ на запрос media offer
        sc_media_ice_candidate                  : '80D4', // ответ на запрос ответ cs_media_ice_candidate
        sc_media_change_settings                : '80D5', // изменение настроек медиа-данных пользователя (аудио/видео)
        sc_media_ready                          : '80D6', // NodeWebKit клиента успешно стартовал и работает

        sc_web_user_foto_file                   : '8074',
        sc_web_support_i_am_busy                : '8087', // если пользователю WEB-поддержки с сайта приходит сообщение не от оператора, с кем он в данный момент говорит, он отвечает этой командой

        sc_adm_set_topic                        : "8079", // результат работы административной команды по установке темы конференции
        sc_adm_list_channels                    : "807A",
        sc_adm_list_users                       : "807B",
        sc_adm_get_server_info                  : "8083", // результат работы административной команды по получению полного списка созданных текстовых конференций. Набор объектов, UID канала
        sc_adm_get_user_profile                 : "8084", // получение полной информации о профиле любого пользователя, административная команда
        sc_adm_list_roles                       : '807F', // список ролей (групп права) пользователей с сервера, административная команда, массив
        sc_adm_user_created                     : '8088', // пользователь только что был создан
        sc_adm_get_job_positions_list           : '8086', // список должностей компании, массив объектов
        sc_adm_server_options                   : '8089', // настройки сервера, ответ на административную команду cs_adm_get_server_options
        sc_adm_server_net_interfaces            : '808A', // список реальных IP адресов сетевых интерфейсов сервера MyChat (IPv4 и IPv6)
        sc_adm_user_info_by_preset              : '808B', // список информационных полей указанного пользователя согласно запрошенному набору
        sc_adm_command_complete                 : '808C', // административная команда успешно выполнена

        sc_adm_job_positions_added              : '808E', // добавлена новая должность
        sc_adm_job_positions_modified           : '808F', // должность была изменена успешно

        sc_adm_role_added                       : '8090', // успешно создана новая группа прав
        sc_adm_role_modified                    : '8091', // группа прав была успешно изменена
        sc_adm_role_users_list                  : '8092', // список пользователей указанной группы прав

        sc_adm_smtp_checked                     : '8093', // SMTP проверка
        sc_invite_to_webchat_sended_ok          : '8094', // уведомление о том, что отправка приглашения для другого человека в MyChat прошла успешно
        sc_adm_websupport_groups                : '8095', // список групп для сотрудников WEB-поддержки сайта, массив
        sc_adm_websupport_group_added           : '8096', // добавлена новая группа WEB-поддержки
        sc_adm_websupport_group_users           : '8097', // список пользователей WEB-support группы
        sc_adm_websupport_group_renamed         : '8098', // группа веб-поддержки успешно переименована
        sc_adm_custom_options                   : '8099', // кастомные настройки, запрошенные с помощью команды cs_adm_get_custom_options
                                                          // Количество полей может быть переменным
        sc_adm_total_contacts_list              : '809A', // общий список контактов, массив объектов

        sc_adm_tcl_group_added                  : '809B', // группа общего списка контактов успешно создана
        sc_adm_tcl_users_added                  : '80A9', // список пользователей успешно занесён в группу общих контактов

        sc_adm_users_imported                   : '809C', // пользователи импортированы в сервер
        sc_adm_server_licenses                  : '809D', // информация о лицензиях MyChat Server

        sc_adm_antiflood_filter_data            : '809E', // информация по антифлуд-фильтрам. Админкоманда
        sc_adm_flood_except_users_list          : '809F', // список пользователей, которых не касаются серверные антифлуд-фильтры, массив. Если массив пустой - значит, таких пользователей на сервере нет

        sc_adm_allow_ip_list                    : '80A0', // список разрешённых IP адресов
        sc_adm_blocked_ip_list                  : '80A1', // список заблокированных IP адресов (масок и диапазонов), массив объектов
        sc_adm_blocked_mac_list                 : '80A2', // список заблокированных MAC адресов
        sc_adm_blocked_nicks_list               : '80A3', // список заблокированных ников, админкоманда. Массив текстовых строк
        sc_adm_blocked_confs_list               : '80A4', // список заблокированных названий конференций. Массив текстовых строк

        sc_adm_bad_words_filter_data            : '80A5', // данные по фильтрам "плохих" слов
        sc_adm_bad_words_list                   : '80A6', // полный список "плохих" слов, админкоманда. Массив текстовых строк
        sc_adm_bad_words_except_confs           : '80A7', // список конференций, в кот орых отключен антимат-фильтр. Массив текстовых строк
        sc_adm_export_users_to_csv_file         : '80A8', // список пользователей с указанными данными успешно экспортирован в CSV файл

        sc_online_users_states                  : '80AA', // список UIN-ов и статусов всех пользователей, которые в данный момент есть онлайн
        sc_dept_list                            : '80AB', // список подразделений компании, массив объектов
        sc_caches                               : '80AC', // контрольные суммы (CRC32) кешей различных данных, которые, возможно, будут нужны клиенту
        sc_adm_all_text_confs                   : '80AD', // список всех текстовых конференций, которые когда-либо создавались на сервере
        sc_just_connect_disconnect_users        : '80AE', // список пользователей, которые отключились или подключились за последнее время (раз в три секунды обычно)
        sc_adm_bans_list                        : '80AF', // список банов пользователей, массив объектов
        sc_adm_moderators_list                  : '80B0', // список модераторов чата, админкоманда, массив объектов
        sc_adm_conf_names_list_by_uid           : '80B1', // список текстовых конференций, которые когда-либо создавались на сервере.

        sc_broadcasts_list_history              : '80B2', // список оповещений, которые были отправлены пользователем, за период. Массив объектов
        sc_put_msg2txt_channel                  : '80B4', // получить сообщение в текстовый канал

        sc_adm_auto_conf_list                   : '80B5', // получить список автосоздаваемых каналов
        sc_adm_auto_conf_created                : '80B6', // успешно добавлена автосоздаваемая конференция. Админкоманда
        sc_txtch_update_modes                   : '80B7', // обновить режимы текстовой конференции
        sc_adm_bbs_msg_list                     : '80B9', // список сообщений на доске объявлений, админкоманда, массив объектов
        sc_adm_bbs_msg_data                     : '80BA', // подробные данные по сообщению на доске объявлений

        sc_adm_ad_users_imported                : '80BB', // список пользователей, которых импортировали из Active Directory
        sc_adm_ad_options                       : '80BC', // настройки по Active Directory интеграции

        sc_adm_update_packet_data               : '80BD', // данные по пакетам обновления для MyChat Client
        sc_adm_online_users_list                : '80BE', // список онлайн-пользователей, которые в данный момент подключены к MyChat Server. Массив объектов
        sc_adm_ad_users_list                    : '80BF', // список пользователей домена для импорта, массив объектов. Количество полей может быть переменным, в зависимости от того, указаны они для данного пользователя
                                      //                                      в домене, или нет. Заблокированные доменные пользователи специально не "вытаскиваются".

        sc_sync_private_history                 : '80C0', // данные для синхронизации истории сообщений в привате. Массив объектов
        sc_adm_script_data                      : '80C1', // запрошенный серверный скрипт со всеми данными
        sc_adm_all_script_states                : '80C2', // список скриптов со статусами, массив объектов
        sc_adm_script_checked                   : '80C3', // проверен синтаксис скрипта
        sc_adm_script_executed                  : '80C4', // скрипт выполнен по запросу, админкоманда
        sc_adm_script_created                   : '80C5', // новый серверный скрипт создан, админкоманда, ответ на 0114 cs_adm_script_create
        sc_adm_script_deleted                   : '80C6', // скрипт только что удалён успешно, пришёл список новых идентификаторов позиций для скриптов (если они есть, если нет - список пустой)
        sc_adm_script_saved                     : '80C7', // скрипт успешно сохранён, админкоманда
        sc_adm_script_log                       : '80C8', // добавить текстовую строчку в лог редактора скриптов

        sc_adm_active_conf_list                 : '80CC', // список работающих конференций, админкоманда, ответ на запрос 011B cs_adm_get_active_conf_list. Массив объектов
        sc_adm_conf_users_list                  : '80CD', // полный список пользователей конференции, админкоманда, ответ на запрос 011D cs_adm_get_users_in_conf. Массив объектов

        // ==== BROADCASTS ==========================================

        sc_broadcast_read_notify                : '8057', // уведомление о прочтении оповещения для пользователя
        sc_broadcast_sent_ok                    : '8100', // оповещение успешно отправлено

        // ==== KANBAN ================================================

        sc_kanban_projects_list                 : '80C9', // список проектов kanban доски, массив объектов, ответ на 0117 cs_kanban_get_projects_list
        sc_kanban_stages_list                   : '80CA', // список этапов kanban-доски, массив объектов
        sc_kanban_tags_list                     : '80CB', // список тегов kanban-доски, ответ на 011A cs_kanban_get_tags_list
        sc_kanban_tasks_list                    : '80CE', // список задач kanban доски. Массив объектов
        sc_kanban_project_created               : '80D0', // новый проект kanban-доски успешно создан
        sc_kanban_stage_created                 : '80D1', // новый этап kanban-доски успешно создан
        sc_kanban_task_created                  : '80E1', // новый проект kanban-доски успешно создан
        sc_kanban_task_comment_added            : '80E2', // новый комментарий к задаче успешно добавлен
        sc_kanban_task_comments_list            : '80E3', // список комментариев к задаче на kanban доске, массив объектов
        sc_kanban_personal_options_list         : '80E5', // список персональных настроек клиента, JSON объект
        sc_kanban_event                         : '80E6', // kanban events
        sc_kanban_project_info                  : '80E7', // информация о kanban-проекте, ответ на запрос 014C cs_kanban_get_project_info
        sc_kanban_task_info                     : '80E8', // информация о задаче на kanban-доске, ответ на запрос 014D cs_kanban_get_task_info (5.21+)
        sc_kanban_last_performers_list          : '80EB', // список последних исполнителей задач на kanban-доске, ответ на запрос 0153 cs_kanban_get_last_performers, массив объектов
        sc_kanban_project_tags_list             : '80FE', // список тегов конкретного проекта
        sc_kanban_dashboard                     : '80FF', // краткая сводная информация по текущим задачам и проектам, массив объектов

        // ====================================================

        sc_personal_options_list                : '80E5', // список персональных настроек клиента, JSON объект

        sc_apply_client_settings                : '80DB', // применить клиентские настройки

        sc_adm_client_options_presets_list      : '80D8', // список шаблонов клиентских настроек, админкоманда, ответ на запрос 122C cs_adm_get_client_options_presets. Массив объектов
        sc_adm_client_options_preset_created    : '80D9', // набор настроек клиента успешно создан, ответ на команду 122E cs_adm_create_client_options_preset
        sc_adm_client_options_preset_updated    : '80DA', // набор настроек успешно отредактирован

        sc_adm_new_user_group_created           : '80DD', // уведомление о том, что новая группа пользователей успешно создана
        sc_adm_users_groups_list                : '80DE', // список групп пользователей, масив объектов
        sc_adm_group_users_list                 : '80DF', // список пользователей в группе, массив объектов

        sc_adm_integration_tool_options         : '80E0', // настройки интеграции инструмента (phpbb... etc.)
        sc_adm_get_check_domains_list           : '80E4', // список доменов с адресами, пользователи которых могут подключаться к серверу, массив объектов

        sc_adm_allowed_and_blocked_plugins      : '80E9', // список разрешённых и запрещённых клиентских плагинов
        sc_adm_script_info                      : '80EA', // краткая информация о скрипте, ответ на 0152 cs_adm_get_script_info

        // ==== FORUM ================================================

        sc_forum_structure                        : '80EF', // структура форума MyChat, ответ на запрос 0156 cs_forum_get_structure
        sc_forum_section_or_topic_created         : '80F0', // раздел форума или тема успешно создан
        sc_forum_post_created                     : '80F2', // сообщение в форуме успешно создано
        sc_forum_section_or_topic_changed         : '80F3', // раздел или тема форума успешно изменены
        sc_forum_section_or_topic_or_post_deleted : '80F4', // раздел, тема форума или сообщение успешно удалены
        sc_forum_post_changed                     : '80F5', // пост форума изменён
        sc_forum_topic_posts                      : '80F6', // список сообщений темы форума. Массив объектов
        sc_forum_section_info                     : '80F7', // информация о секции (список топиков, краткая информация о них: LastChange, UIN, DisplayName, Sex, Avatar, CountMessages). Массив объектов

        // ==========================================================

        sc_command_complete                     : '80F1', // команда успешно выполнена
        sc_book_data                            : '80FB', // возвращает содержимое справочника в ответ на команду cs_get_book

        sc_file_exists                          : '80FC', // есть или нет файл с таким хэш-кодом на сервере
        sc_image_thumbs                         : '80FD', // уменьшенная копия изображения с сервера

        sc_token_created                        : '80ED', // создан токен для доступа к сервису чата

        sc_adm_backup_server_db_ok              : '8101', // резервная копия баз данных сервера успешно создана
        sc_hide_text_conf                       : '8102', // скрыть конференцию из списка доступных
        sc_adm_integration_telegram_users_list  : '8103', // список пользователей для интеграции с Telegram. Массив объектов
        sc_adm_integration_telegram_user_added  : '8104', // уведомление, только что добавлен новый мобильный юзер в таблицу Telegram-пользователей
        sc_removed_user_info                    : '8105', // краткая информация об удалённом пользователе
        sc_login_finish                         : '8106', // клиентское приложение успешно авторизовано на сервере и ему передана вся необходимая информация (права, join-ы в конференции и т.п.)

        sc_adm_history_info_private             : '8107', // админкоманда, информация по сообщениям в привате за диапазон времени, ответ на 0173 cs_adm_get_history_info_private
        sc_adm_history_private                  : '8108', // лог приватных сообщений двух пользователей по указанному диапазону индексов, админкоманда, ответ на 0174 cs_adm_get_history_private
        sc_adm_history_info_conf                : '8109', // админкоманда, информация по сообщениям в конференции за диапазон времени, ответ на 0175 cs_adm_get_history_info_conf
        sc_adm_history_conf                     : '810A', // лог приватных сообщений двух пользователей по указанному диапазону индексов, админкоманда, ответ на 0176 cs_adm_get_history_conf
        sc_adm_history_private_user_pairs       : '810B', // админкоманда, все пары приватных разговоров за все время, ответ на 0177 cs_adm_get_history_private_user_pairs

        sc_sync_conf_history                    : '810D', // данные для синхронизации истории сообщений в конференции
        sc_info                                 : '810E', //

        sc_adm_clear_console                    : '810F', // очистить консоль вывода данных в редакторе скриптов
        sc_adm_blocked_hwid_list                : '8110', // список заблокированных HardwareID адресов
        sc_adm_system_logs                      : '8111', // системные логи за период времени, ответ на 0182 cs_adm_get_system_logs

        sc_web_services_info                    : '8114', // информация о WEB-сервисах MyChat Server. Присылается, только если они перенастроены либо изменяются в данный момент
        sc_server_ports                         : '8115', // список сетевых портов MyChat сервер, отправляется клиенту, если меняются сетевые настройки MyChat Server

        sc_adm_domains_list                     : '8116', // список доменов, с которыми может работать сервер MyChat
        sc_adm_forum_subs_groups_list           : '8117', // список групп пользователей, которые подписаны на конкретную тему либо секцию
        sc_adm_forum_subs_users_list            : '8118', // список идентификаторов пользователей, которые подписаны на конкретную тему либо секцию

        sc_forum_subs                           : '8119', // список подписок на темы и разделы форума
        sc_custom_options                       : '811A', // кастомные настройки клиента, запрошенные с сервера командой cs_set_custom_options

        sc_adm_integration_quiz_check_question  : '811B', // проверка существования вопроса в базе робота-викторины
        sc_adm_integration_quiz_question_added  : '811C', // новый вопрос в базу робота-викторины добавлен успешно
        sc_adm_integration_quiz_stat            : '811D', // статистика робота-викторины

        sc_exec_func_result                     : '811E', // результат выполнения функции на сервере
        sc_users_live_search                    : '811F', // список пользователей на сервере по заданному шаблону, результат поиска команды 019A cs_users_live_search

        sc_private_dialogs                      : '8120', // список приватных диалогов пользователя, массив объектов, свежие - сначала
        sc_del_private_dialog                   : '8121', // удалить диалог с указанным юзером из списка приватных диалогов, уведомление об успешном удалении на сервере, рассылается всем пользователям-владельцам

        sc_private_read                         : '8122', // уведомление самому себе и всем инстансам себя (multilogin) о том, что указанное приватное сообщение прочитано
        sc_private_read_notify                  : '8123', // уведомление оппоненту, что его собеседник только что прочитал приватное сообщение с указанным индексом (и все приватные сообщения до него)
        sc_private_got                          : '8124', // уведомление самому себе и всем инстансам себя (multilogin) о том, что указанное приватное сообщение получено
        sc_private_got_notify                   : '8125', // уведомление оппоненту, что его собеседник только что получил приватное сообщение с указанным индексом (и все приватные сообщения до него)
        sc_private_dialog_msg_states            : '8126', // информация о полученных и прочитанных сообщениях в приватном диалоге с оппонентом

        sc_private_redirect_complete            : '8127', // диалог успешно передан, приват можно закрыть
        sc_users_list_for_redirect_dialog       : '8128', // список пользователей для редиректа
        sc_my_multicast_msg                     : '8129', // (7.4+) моё многоадресное сообщение, ответ от сервера

        sc_create_presenter                     : '812A', // ответ на создание презентера
        sc_adm_terminal_command_result          : '812B', // ответ на терминальную команду админки


        sc_remove_conversations                 : '8112', // полностью удалить указанные протоколы разговоров за всё время из локальной базы данных
        sc_tech_kick                            : '812C', // принудительно выйти из текстовой конференции, на сервер ничего не отправлять
        sc_common_options                       : '812D', // команда для обновления общих для всех пользователей сервера параметров,
                                                          // все переменные - необязатльные, применять настройку, только если поле реально есть внутри JSON

        sc_adm_allow_ip_added                   : '812E', // добавлен IP, маска или диапазон в фильтре разрешённых IP адресов
        sc_adm_blocked_ip_added                 : '812F', // добавлен IP, маска или диапазон в фильтре запрещённых IP адресов
        sc_adm_script_log_clear                 : '8130', // очистить лог редактора скриптов в админке

        sc_kanban_task_taken                    : '8131', // задача успешно взята в работу

        sc_users_changeid_data                  : '8132', // информация о списке пользователей, UIN, ChangeID + данные
        sc_users_avatars                        : '8133', // аватары пользователей (прямоугольные картинки), массив объектов

        sc_cids_killed                          : '8137', // только что отключен от сервера список CID-ов пользователей
        sc_cids_halted                          : '8138', // только что отключен от сервера + завершены приложения списка CID-ов пользователей

        sc_conf_read                            : '8139', // пользователь только что прочитал сообщение в конференции, команда рассылается  всем инстансам UIN при мультилогине
        sc_media_incoming_call_accepted_by_another_cid: '813A', // медиа-звонок принят мной на другом устройстве
        sc_close_broadcast_window               : '813B', // закрыть окно входящего оповещения, оповещения были прочитаны на другом устройстве

        sc_adm_asterisk_log                     : '8142', // свежий кусок лога работы с Asterisk
        sc_integrations                         : '8143', // список включенных интеграций, массив объектов

        sc_adm_integration_turbosms_balance     : '8147', // баланс состояния счёта в сервисе TurboSMS
        sc_adm_integration_turbosms_ping        : '8148', // ответ PING от сервиса TurboSMS
        sc_adm_integration_turbosms_send_message: '8149', // ответ на отправку тестового сообщения

        sc_media_call_start_ok                  : '814A', // начат медиа звонок с указанными параметрами

        sc_tech_message                         : '80EE', //

        sc_personal_users_groups_list           : '814C', // список личных групп пользователей, со списком юзеров внутри, массив объектов
        sc_adm_new_personal_user_group_created  : '814D', // уведомление о том, что новая персональная группа пользователей успешно создана
        sc_adm_personal_group_users_list        : '814E', // список пользователей в персональной группе юзера, массив объектов
        sc_new_personal_user_group_created      : '814F', //  уведомление о том, что новая персональная группа пользователей успешно создана
        sc_personal_user_group_data             : '8151', // личная группа пользователя, ID, название и список пользователей
        sc_users_from_personal_user_group_removed: '8152', // список пользователей из персональной группы успешно удалён
        sc_personal_users_groups_names_list     : '8153', // список личных групп пользователей, массив объектов
        sc_public_users_groups_list             : '8155', // список публичных групп пользователей, массив объектов
        sc_kanban_rights_group_list             : '8156', // список групп прав с доступом к канбану, массив объектов
        sc_kanban_user_group_added_to_project   : '8157', // группа успешно добавлена в проект
        sc_kanban_project_group_list            : '8158', // список групп доступа к канбан-проекту
        sc_kanban_removed_user_groups_from_project: '8159', // из канбан-проекта удалены указанные группы пользователей
        sc_common_user_group_data               : '815A', // общая группа пользователя, ID, название и список пользователей
        sc_all_user_group_membership_list       : '815B', // список общих и личных групп, куда входит указанный пользователь, ответ на запрос cs_adm_get_all_user_group_membership_list

        sc_get_geo_position                     : '815C', // получить географическую позицию устройства пользователя ответ - 01DA cs_geo_position (8.12+)
        sc_user_gps_history                     : '815D', // история перемещений пользователя за период, ответ на запрос 01DB cs_get_user_gps_history

        sc_users_fired                          : '815E', // пользователи успешно уволены, ответ на cs_adm_fire_users
        sc_remove_history                       : '815F', // удаление сообщений/файлов/логов/записей звонков и т.п. любого контента в MyChat Client за период или полностью
                                                          // команда может приходить по запросу cs_get_delayed_commands либо, если клиент онлайн, то в момент выполнения на сервере
        sc_adm_custom_menu_created              : '8162', // custom-меню для клиентских приложений создано успешно, ответка на 01DF cs_adm_custom_menu_create
        sc_adm_custom_menu_list                 : '8163', // полный список custom-меню для клиентских приложений, массив объектов, ответка на 01E0 cs_adm_get_custom_menu_list
        sc_custom_menu                          : '8164', // custom-menu пользователя ответ на 01E5 cs_get_custom_menu
        sc_set_remote_client_current_place_id   : '8145', // открыть в интерфейсе удалённого клиента приват с определённым человеком либо конференцию

        sc_media_conf                           : '8160', // работа с медиаконференциями, основные команды с большой буквы, дополнительные - с маленькой
        sc_media_conf_join                      : '8166', // подключиться к медиаконференции
        sc_media_conf_leave                     : '8167', // отключиться от медиаконференции

        sc_adm_ad_users_photos                  : '8168', // отмашка в админку, что с запросами фоток мы закончили, можно начинать спрашивать, либо информация о наличии фоток (контрольные суммы, логины)
                                                          // массив объектов
        sc_adm_ad_user_photo_thumb              : '8169', // ответ от сервера, миниатюра фотографии пользователя
        sc_remove_my_acc                        : '816B', //

        sc_conf_status_change                   : '816C', // изменён статус конференции

        sc_adm_check_user_pwd_state             : '816D', // ответ на запрос корректности пароля пользователя (cs_adm_check_user_pwd)
        sc_media_conf_user_list                 : '816E', // ответ на запрос списка пользователей медиа-конференции (cs_get_media_conf_user_list)
        sc_media_conf_user_joined               : '816F', // пользователь подключился к медиаконференции, команда отправляется всем онлайн-пользователям указанной конференции
        sc_media_conf_broadcast                 : '8170', // универсальная команда рассылки сообщения всем пользователям медиа конференции, кроме отправителя

        sc_conf_user_list                       : '8171', // список пользователей указанной конференции, ответ на cs_get_conf_user_list
        sc_remove_users                         : '8172', // с сервера удалены указанные пользователи
        sc_join_newbie_to_txt_channel           : '8173', // замена sc_join_newbies_txt_channel

        sc_all_users_changed                    : '80F8', // информирование всех подключенных клиентов о том, что все юзеры изменились. Как правило, поменялся DisplayName. Следует спросить у сервера
                                                          // новую информацию обо всех пользователях, которые интересны клиентам (открытые конференции и приваты).
        sc_important_notifiers                  : '8174', // важное уведомление с сервера, нужно сгенерировать локально
        sc_adm_user_conf_list                   : '8175', // список конференций, в которые входит пользователь, ответ на запрос cs_adm_get_user_conf_list, массив объектов

        sc_conf_removed                         : '8176', // текстовая конференция удалена, нужно удалить кеши и всю локальную историю
        sc_badword_list_uploaded                : '8177', // список "плохих" слов для фильтра успешно добавлен


        // ==== Server -> Node =================================================

        sn_config                               : '1',
        sn_start                                : '2',
        sn_stop                                 : '3',
        sn_quit                                 : '4',
        sn_invite                               : '5',
        sn_iApi                                 : '6',
        sn_ip_filter                            : '7',
        sn_iApi_key                             : '8',
        sn_https_error                          : '9', 
        sn_rest_request                         : '10',
        sn_icq_connect                          : '11',
        sn_icq_send_message                     : '12',
        // sn_get_backup_path                      : '13', // sn_path_info
        sn_info                                 : '14',
        sn_path_info                            : '15',
        sn_operators                            : '16',
        sn_support_options                      : '17',
        sn_update_options                       : '18',
        sn_check_token                          : '19',
        sn_run_command                          : '20',
        sn_check_group_invite                   : '21',
        
        sn_media_conf_create                    : '22',
        sn_media_conf                           : '23',
        sn_media_conf_join                      : '24',
        sn_media_conf_leave                     : '25',
        sn_user_conf_info                       : '26',
        sn_media_conf_close                     : '27', // принудительное удаление медиа-конференции

        sn_check_ssl_info                       : '28', // перевірка сертифіката

        // ==== Node -> Server =================================================

        ns_started                              : '1001',
        ns_stopped                              : '1002',
        ns_bye                                  : '1003',
        ns_check_invite                         : '1004',
        ns_invite_sent                          : '1005',
        ns_integrationAPI                       : '1006',
        ns_ip_filter                            : '1007',
        ns_iApi_key                             : '1008',
        ns_https_error                          : '1009',
        ns_rest_request                         : '1010',
        ns_icq_connected                        : '1011',
        ns_icq_contact_list                     : '1012',
        ns_icq_send_message                     : '1013',
        ns_get_by_groups                        : '1015',
        ns_get_by_uins                          : '1016',
        ns_support_options                      : '1017',
        ns_check_token                          : '1018',
        ns_remove_tokens                        : '1019',
        ns_private_message                      : '1020',
        ns_check_group_invite                   : '1021',

        ns_media_conf                           : '1022',
        ns_media_conf_created                   : '1023',
        ns_media_conf_error                     : '1024',
        
        ns_get_user_conf_info                   : '1025',
        ns_media_conf_closed                    : '1026',
        ns_media_conf_leave                     : '1027',

        ns_check_ssl_info                       : '1028',

        // == HELP PATH ==========================================================

        accordance : {
                "navServerInfo/ServerInfo"     : "/serverhelp/#{lang}/index.html?serverinfo.htm",
                "navServerInfo/OnlineUsers"    : "/serverhelp/#{lang}/index.html?infoonlineusers.htm",
                "navServerInfo/NssService"     : "/serverhelp/#{lang}/index.html?infoadditionalservices.htm",
                "navServerInfo/Forum"          : "/serverhelp/#{lang}/index.html?infomychattechforum.htm",
                "navServerInfo/UserProfile"    : "/serverhelp/#{lang}/index.html?userprofile.htm",
                "navServerInfo/Terminal"       : "/serverhelp/#{lang}/index.html?infomychatterminal.htm",
                "navUsers/UserManager"         : "/serverhelp/#{lang}/index.html?usermanagement.htm",
                "navUsers/UserProfile"         : "/serverhelp/#{lang}/index.html?userprofile.htm",
                "navUsers/WorkPositions"       : "/serverhelp/#{lang}/index.html?userdepartments.htm",
                "navUsers/Contacts"            : "/serverhelp/#{lang}/index.html?usercommoncontacslist.htm",
                "navUsers/UsersImports"        : "/serverhelp/#{lang}/index.html?userimportfromfile.htm",
                "navUsers/Operators"           : "/serverhelp/#{lang}/index.html?usermoderators.htm",
                "navUsers/Bans"                : "/serverhelp/#{lang}/index.html?userbanlist.htm",
                "navUsers/ActiveDirectory"     : "/serverhelp/#{lang}/index.html?userimportactivedirectory.htm",
                "navUsers/UserGroups"          : "/serverhelp/#{lang}/index.html?usersgroups.htm",
                "navUsers/AdDomains"           : "/serverhelp/#{lang}/index.html?", // нет, синхронизация
                "navConference/AutoChannels"   : "/serverhelp/#{lang}/index.html?conferenceauto.htm",
                "navConference/ActiveChannels" : "/serverhelp/#{lang}/index.html?conferencesonlinelist.htm",
                "navRights/GroupRights"        : "/serverhelp/#{lang}/index.html?grouprights.htm",
                "navRights/GroupUsers"         : "/serverhelp/#{lang}/index.html?grouprightsusers.htm",
                "navRights/PluginAccess"       : "/serverhelp/#{lang}/index.html?", // нет
                "navTools/WebSupport"          : "/serverhelp/#{lang}/index.html?websupport.htm",
                "navTools/MyChatGuest"         : "/serverhelp/#{lang}/index.html?mychatguest.htm",
                "navTools/History"             : "/serverhelp/#{lang}/index.html?logsviewer.htm",
                "navTools/BBS"                 : "/serverhelp/#{lang}/index.html?", // нет
                "navTools/Updates"             : "/serverhelp/#{lang}/index.html?mychatupdatesystem.htm", // +
                "navTools/MCScripts"           : "/serverhelp/#{lang}/index.html?scripts.htm",
                "navTools/Reboot"              : "/serverhelp/#{lang}/index.html?", // нет
                "navTools/PresetManager"       : "/serverhelp/#{lang}/index.html?optionspresetscreate.htm", // +
                "navTools/RemoteSettings"      : "/serverhelp/#{lang}/index.html?optionspresetsassign.htm", // +
                "navTools/Integrations"        : "/serverhelp/#{lang}/index.html?toolsintegrations.htm", // +
                "navTools/Relocation"          : "/serverhelp/#{lang}/index.html?", // нет
                "navTools/Bots"                : "/serverhelp/#{lang}/index.html?", // нет
                "navTools/TotalClear"          : "/serverhelp/#{lang}/index.html?", // нет
                "navTools/Testport"            : "/serverhelp/#{lang}/index.html?MyChatServerTestPortErrors.htm",
                "navFilters/FilterAntiflood"   : "/serverhelp/#{lang}/index.html?filtersantiflood.htm",
                "navFilters/FilterBadWords"    : "/serverhelp/#{lang}/index.html?filtersbadwords.htm",
                "navFilters/FilterIP"          : "/serverhelp/#{lang}/index.html?filtersip.htm",
                "navFilters/FilterMAC"         : "/serverhelp/#{lang}/index.html?filtersmac.htm",
                "navFilters/FilterNames"       : "/serverhelp/#{lang}/index.html?filtersnames.htm",
                "navFilters/FilterHardware"    : "/serverhelp/#{lang}/index.html?filtershardwareid.htm",
                "navSettings/SettingsNetwork"  : "/serverhelp/#{lang}/index.html?networkoptions.htm",
                "navSettings/SettingsInfo"     : "/serverhelp/#{lang}/index.html?serverinfooptions.htm",
                "navSettings/SettingsLogs"     : "/serverhelp/#{lang}/index.html?logoptions.htm",
                "navSettings/SettingsFTP"      : "/serverhelp/#{lang}/index.html?ftpoptions.htm",
                "navSettings/SettingsWEB"      : "/serverhelp/#{lang}/index.html?webservicesoptions.htm",
                "navSettings/SettingsBackup"   : "/serverhelp/#{lang}/index.html?backupoptions.htm",
                "navSettings/SettingsService"  : "/serverhelp/#{lang}/index.html?maintenanceoptions.htm",
                "navSettings/SettingsAPI"      : "/serverhelp/#{lang}/index.html?integrationapioptions.htm",
                "navSettings/SettingsSMTP"     : "/serverhelp/#{lang}/index.html?smtpoptions.htm",
                "navSettings/SettingsFolders"  : "/serverhelp/#{lang}/index.html?mychatserverfolders.htm",
                "navSettings/Others"           : "/serverhelp/#{lang}/index.html?additionaloptions.htm",
                "navLicense/LicenseMyChat"     : "/serverhelp/#{lang}/index.html?mychatserverlicenses.htm",
                "navLicense/LicenseGuest"      : "/serverhelp/#{lang}/index.html?mychatguestlicenses.htm",
                "navLicense/LicenseClear"      : "/serverhelp/#{lang}/index.html?mychatclearlicenses.htm", // +
                "navLicense/LicenseQuiz"       : "/serverhelp/#{lang}/index.html?mychatquizlicenses.htm",  // +
                "mychat_versions"              : "/serverhelp/#{lang}/index.html?versionshistory.htm",

        },

        // ==== Client Type ====================================================

        ClientType: {
            WIN32           : 'win32',
            FTP             : 'ftp',
            WEB_ADMIN       : 'admin',
            WEB_KANBAN      : 'kanban',
            WEB_CHAT        : 'web',
            WEB_SUPPORT     : 'websupport',
            COMMON          : 'common',
            INTEGRATION_API : 'integrationapi',
            WEB_FORUM       : 'forum',
            WIN32_NEXT      : 'win32next',
            MACOS           : 'macos',
            ANDROID         : 'android',
            LINUX           : 'linux',
            GUEST           : 'mcguest',
            IOS             : 'ios',
            UPDATES         : 'updates',

            ALL             : 'any',
            UNKNOWN         : 'unknown'
        },

        callType : {
            VOICE  : 0,
            VIDEO  : 1,
            SHARING: 2
        },

        PrivateTask: {
            DEFAULT                  : 0,
            SEND_FILES               : 1,
            SEND_FILES_FROM_EXPLORER : 2,
            VOICE_CALL               : 3,
            VIDEO_CALL               : 4,
            MY_SCREEN_SHARING        : 5
        },

        serverLogLevel: {
            NOTHING         : 0, // ничего не логгировать
            SERVER_ERRORS   : 10, // серверные ошибки
            NETWORK_ERRORS  : 20, // сетевые ошибки
            SERVER_INFO     : 30, // информационные сообщения о работе сервера

            CONNECTIONS     : 800, // подключения и отключения пользователей
            NETWORK         : 900, // входящий трафик
            VERBOSE         : 1000 // полный трафик от сервера к клиентам и обратно
        },

        serverLogEvents: {
            MYCHAT_TRAFFIC_IN  : 1,
            MYCHAT_TRAFFIC_OUT : 2,
            ALL_INPUT_TRAFFIC  : 3
        },

        userStates: {
            offline    : -1,
            online     : 0,
            away       : 1,
            dnd        : 2,
            webOnline  : 3
        },

        // ==== User Rights ====================================================

        RS: {
            QLogin: 1,                                            //"811":"Подключаться к MyChat серверу",
       /*+*/QBBSPost: 2,                                          //"786":"Создание новых сообщений на доске",
       /*+*/QBBSView: 3,                                          //"785":"Просмотр доски объявлений",
       /*+*/QTxtChCreate: 4,                                      //"797":"Создание текстовых каналов",
       /*+*/QTxtChSay: 5,                                         //"844":"Отправка сообщений в текстовые каналы",
       /*+*/QTxtChJoin: 6,                                        //"796":"Вход в существующие текстовые каналы",
       /*+*/QPrivateOpen: 7,                                      //"831":"Открытие новых приватов",
       /*+*/QPrivatesGetMessages: 8,                              //"832":"Получение приватных сообщений от других пользователей",
       /*+*/QUserDetailsChange: 9,                                //"837":"Изменение персонального профиля",
       /*+*/QFilesSend: 10,                                       //"841":"Разрешить отправлять файлы другим пользователям",
       /*+*/QFilesReceive: 11,                                    //"842":"Разрешить принимать файлы от других пользователей",

            QOperatorBan: 12,                                     //"821":"Право наказывать пользователей (ban)",
            QOperatorTurnOutFromChannel: 13,                      //"820":"Право изгнания пользователей из текстовых каналов (kick)",
            QKill: 14,                                            //
            QBlockIP: 15,                                         //
            QBlockMAC: 16,                                        //
            QOperatorTxtChTopicSet: 17,                           //"818":"Изменение темы текстовых каналов",

       /*+*/QFTPPublic: 18,                                       //"840":"Доступ к общей папке на файловом сервере",
       /*+*/QFTPPersonal: 19,                                     //"839":"Доступ к личной папке на файловом сервере",

            QRemoteAdm: 20,                                       //"783":"Удаленное администрирование сервера через консоль MyChat Client",
            QOperatorClearChannelText: 21,                        //"819":"Очистка текстовых каналов у всех пользователей",

       /*+*/QContactsListChange: 22,                              //"799":"Изменение персональной панели контактов",
       /*+*/QBroadcasts: 23,                                      //"823":"Отправка оповещения другим пользователям",

       /*-*/QAllowPersonalMsg: 24, //trash                               //"794":"Отправка персональных сообщений в каналах",
       /*-*/QAllowAlertMsg: 25,    //trash                               //"795":"Отправка важных персональных сообщений в каналах",
            QAllowSysInfoView: 26,                                //"815":"Просмотр сетевого имени компьютера и IP-адреса пользователя",

       /*+*/QSendImages: 27,                                      //"792":"Вставка изображений",

            QAllowIgnores: 28,                                    //"805":"Игнорирование других пользователей",

            QAllowInvites: 29,                                    //"810":"Отправка приглашений в каналы другим пользователям",

       /*+*/QImagesPrivates: 30,                                  //"830":"Вставка изображений",
       /*+*/QShowCommonContactsList: 31,                          //"800":"Показывать общий список контактов",
       /*+*/QHalt: 32,

            QTxtChRename: 33,
            QTxtChDelete: 34,
         // QForciblyInvites: 35,                                 // deprecated
            QBlockUIN: 36,
            QAllowPlugins: 37,                                    //"827":"Использование плагинов",

       /*+*/QAllowOptionsChange: 38,                              //"806":"Изменение настроек клиентского приложения",

       /*-*/QAllowMyFavoriteChannels: 39, //trash                        //"793":"Доступ к ''Моим любимым каналам''",

       /*+*/QAllowCloseProgram: 40,
       /*+*/QAllowContactsListsTotal: 41,                         // разрешить или запретить полностью доступ к панели контактов
       /*+*/QAllowAccountsManager: 42,                            // разрешить пользоваться менеджером учётных записей
            QWEBAllowViewLogsSelfPrivates: 43,                    // просмотр своих разговоров в приватах
            QWEBAllowViewLogsAllChannels: 44,                     // просмотр всех разговоров в каналах
            QWEBAllowViewLogsAllPrivates: 45,                     // просмотр всех разговоров в приватах
       /*+*/QAllowFindUsersTool: 46,                              // разрешить поиск пользователей

       /*-*/QAllowActions: 47,    //trash                                // разрешить вставку в текст Action-ов
            QAllowClientLogPrivates: 48,                          // разрешить или запретить клиенту писать логи приватов локально, у себя на компьютере
       /*-*/QMessengerStyle: 49,                                  // Messenger-style отображения майчат клиента

            QAllowOpenComputer: 50,                               // показывать или нет в меню опцию "Открыть компьютер" по щелчку на имени юзера
            QExceptionAllowReceivePrivates: 51,                   // Приём приватных сообщений от пользователей, которым запрещена отправка приватных сообщений
            QAllowViewRemoteUserMACAddress: 52,                   // разрешить смотреть MAC адрес удалённого юзера
       /*+*/QAllowViewRemoteUserActiveProcess: 53,                // разрешить смотреть активный процесс удалённого юзера
                
       /*-*/QWEBAllowViewFTPLog: 54,                              // Просмотр FTP протоколов

       /*+*/QWEBAllowViewSystemLog: 55,                           // Просмотр системных протоколов

       /*-*/QEnableTaskWorkersJobTimeControlSystem: 56,           // контроль рабочего времени
            QWEBSupportAllowAccess: 57,                           // доступ к WEB-интерфейсу веб-суппорта

       /*+*/QWEBAllowAdmin: 58,                                   // администрирование сервере через WEB

            QAllowProgramBlockTool: 59,                           // Блокирование работающей программы паролем
            QAllowNetworkStatusChange: 60,                        // Изменение сетевого статуса
            QEnableAllowedClientsPluginsList: 61,                 // Активировать список разрешённых клиентских плагинов
            QEnableBlockedClientsPluginsList: 62,                 // Активировать список запрещённых клиенских плагинов
            QEnablePluginsManagement: 63,                         // Разрешить управление плагинами на клиенте

            QAllowImagesInBroadcasts: 64,                         // вставка картинок в оповещениях

        /*-*/QEnableFontLayoutsInMessages: 65,                     // использование шрифтовой разметки в отправляемых сообщениях
            QAllowCreateHiddenChannels: 66,                       // - (это ещё не внесено в базу данных) разрешить создавать скрытые каналы
        /*-*/QEnableTransferFilesOnlyViaServer: 67,                // передавать файлы только через сервер
            QEnableImmunityFromBans: 68,                          // иммунитет от банов

       /*- ?? */QEnableAdvertBlock: 69,                               // показывать или нет рекламный блок
       /*+*/QBlockPrivatesExceptPersonalContacts: 70,             // Запретить приваты со всеми, кроме входящих в личный список контактов
       /*+*/QBlockPrivatesExceptCommonContacts: 71,               // Запретить приваты со всеми, кроме входящих в общий список контактов
       /*+*/QEnableImmunityFromIgnores: 72,                       // Иммунитет от игноров

            QIntegrationAPIEnableSendMsgFromAnyUser: 73,          // Разрешить отправку сообщений от имени этих пользователей

       /*+*/QEnableVoiceCalls: 74,                                // Голосовые звонки
       /*+*/QEnableVideoCalls: 75,                                // Видеозвонки
       /*+*/QAllowGetVoiceCalls: 76,                              // Разрешить получать голосовые звонки
       /*+*/QAllowGetVideoCalls: 77,                              // Разрешить получать видеозвонки
                
            QAllowAddNewPlugins: 78,                              // Добавление новых плагинов
            QAllowDeletePlugins: 79,                              // Удаление плагинов

       /*+*/QAllowChangeAccountPassword: 80,                      // Изменение пароля своей учётной записи
            QAllowAccessForeignersToPersonalFiles: 81,            // Разрешить доступ к личным файлам для других пользователей ("только чтение")
            QAllowPublishLinkToPersonalFiles: 82,                 // Разрешить публиковать ссылку на "Мои файлы на сервере"

            QFTPPublicWrite: 83,                                  // разрешить писать в общую папку
            QFTPPersonalWrite: 84,                                // разрешить писать в личную папку
            QAllowSendInvites: 85,                                // разрешить отправлять приглашения в MyChat другим людям
            QWEBAllowViewNodeJSLogs: 86,                          // разрешить просмотр логов NodeJS сервера
            QWEBAllowViewAuditLogs: 87,                           // разрешить просмотр логов аудита

       /*-*/QCommonAllowMainMenu: 144,                            // главное меню программы
       /*-*/QCommonAllowDialogs: 145,                             // панель диалогов

            QForumAccess: 146,                                    // доступ ко встроенному форуму
            QForumCreateSections: 147,                            // форум, "Можно создавать новые разделы"
            QForumChangeSections: 148,                            // форум, "Изменение разделов (удаление, переименование, перенос)"
            QForumCreateTopics: 149,                              // форум, "Можно создавать новые темы"
            QForumChangeTopics: 150,                              // форум, "Изменение тем (удаление, переименование, перенос)"
            QForumCreatePosts: 151,                               // форум, "Можно создавать новые сообщения"
            QForumChangePosts: 152,                               // форум, "Изменение сообщений (удаление, модификация, перенос)"
            QForumChangeSelfPosts: 153,                           // форум, "Изменение собственных сообщений (удаление, модификация, перенос)"
            QForumDeleteSections: 154,                            // форум, "Удаление секций"
            QForumDeleteTopics: 155,                              // форум, "Удаление тем"
            QForumDeletePosts: 156,                               // форум, "Удаление сообщений"
            QForumDeleteSelfPosts: 157,                           // форум, "Удаление собственных сообщений"

            QBroadcastsOnlyInMyGroups: 158,                       // оповещения, "Отправка оповещений только в пределах своих групп пользователей"
            QTxtChViewHidden : 159,                               // Конференции, "Просмотр скрытых конференций"
            QICQAccess: 160,                                      // "Интеграция с ICQ", "Доступ к ICQ"

         /*+*/QTxtConfSendFiles: 161,                               // "Конференции", "Вставка файлов"
         /*+*/QPrivSendMessages: 162,                               // "Приваты", "Отправка сообщений"
         /*+*/QPrivInsertFiles: 163,                                // "Приваты", "Вставка файлов"
                
            QAllowRemoveConfMessages: 164,                        // "Администрирование", "Удаление любых сообщений в конференциях"
            QAdmAccessToRemoveConfMessages: 165,                  // "админка", "Инструменты", "Просмотр протоколов", Удаление сообщений из протоколов конференций
            QAllowViewHistoryTool: 166,                           // Просмотр истории сообщений

       /*+*/QAllowViewRemoteUserProfile: 174,                     //  "Профиль", "Просмотр профилей других пользователей"
            QAllowFilterHardware: 175,
            QAllowSettingsFolders: 176,
            
            QAdmAccessLicensesWipe: 177,                          // админка, "Доступ к функциям управления сервером", "Лицензии", "Очистка данных"
            QAdmToolWipe: 178,                                    // админка, доступ к инструменту "Очистка данных"
            QEnableScreenSharing: 179,                            // "Приваты", Трансляция экрана
            QAllowGetScreenSharing: 180,                          // "Приваты", Разрешить получать трансляции экрана
            
            QForumChangeTopicsColor : 181,                        // "Форум", "Изменение цвета для тем"
            QForumSubsNotify        : 182,                        // "Форум", "Подписка/отписка на уведомления в секциях и темах"
            QForumInsertImages      : 183,                        // "Форум", "Вставка изображений в сообщениях"
            QForumInsertFiles       : 184,                        // "Форум", "Вставка файлов в сообщениях"
            QAdmAccessForumSubs     : 185,                        // "Форум", "Управление подписками"

            QAdmAccessLicensesQuiz  : 187,                        // админка, "Доступ к функциям управления сервером", "Лицензии", "MyChat викторина"
            QAdmAccessToRemovePrivMessages  : 188,                // "админка", "Инструменты", "Просмотр протоколов", Удаление приватных сообщений из протоколов"
            QCommonAllowScreenshotTool      : 189,                // Отправка снимка экрана в чат

       /*+*/QAllowRemovePrivateMessages     : 190,                // Удаление любых сообщений в приватах
       /*+*/QCommonAllowQuickMessages       : 191,                // "Быстрые" сообщения
       /*+*/QAllowRedirectDialogs           : 192,                // Перенаправление диалогов
                
            QMulticast : 193,                                     // "Приваты", "Многоадресные приватные сообщения"
            QAdmTerminal: 194,                                    // "MyChat Server", "Terminal"
            QForumPresenter : 195,                                // "Форум", "Создание ссылок для просмотра тем и сообщений"
            QKanbanFullAccess: 196,                               // полный доступ к Kanban доске
            QEnableWakeUpBeep: 197,                               // Разрешить "будить" пользователя
            QAllowRemoveLocalDBConfMessages: 198,                 // "Конференции", "Удаление любых сообщений в конференциях в своей локальной базе данных"
            QAllowRemoveLocalDBPrivateMessages: 199,              // "Приваты", "Удаление любых сообщений в приватах в своей локальной базе данных"
            QAllowClientLogConferences: 200,                      // "Протоколирование", "Хранить историю сообщений конференций в локальной базе данных"
            QAndroidSecurityMode: 201,                            // "Android", "Запрет скриншов экрана в диалогах и blur окна приложения при переключении между последними приложениями"
            QAndroidShowCallsOnMainScreen: 202,                   // "Отображать вкладку \"Звонки\" на главном экране",
            QPrivMakeAsteriskCalls: 203,                          // "Приваты", "Звонки через интеграцию с Asterisk"
            QPrivGetAsteriskCalls: 204,                           // "Приваты", "Разрешить получать звонки через интеграцию с Asterisk"
            QAllowViewConfMsgReadGotStatus: 205,                  // "Конференции", "Разрешить смотреть статусы доставки/прочтения сообщений"
            QPrivAllowHangUpCalls: 206,                           // "Приваты", "Разрешить завершать звонки"
            QPrivAutoAnswerIncomingCalls: 207,                    // "Приваты", "Автоматически отвечать на входящие звонки"
            QPrivAllowMuteSoundDuringCalls: 208,                  // "Приваты", "Разрешить выключать микрофон во время звонка"
            QPrivAllowDisableCameraDuringCalls: 209,              // "Приваты", "Разрешить выключать камеру во время звонка"
            QPrivAllowRecordMediaCalls: 210,                      // "Приваты", "Разрешить запись звонков"
            QAllowExitTextConfs: 211,                             // "Конференции", "Разрешить покидать текстовые конференции"
            QViewTextConfMembersList: 212,                        // "Конференции", "Показывать список членов конференции"
            QAllowMyChatGuest2Conf: 213,                          // "MyChat Guest", "Отправка приглашений MyChat Guest для входа в конференцию"
            QAllowBrowseTextConfList: 214,                        // "Конференции", "Разрешить просматривать список доступных конференций"

       /*+*/QKanbanAccess: 134,                                   // доступ к Kanban
            QKanbanAddTask: 186,                                  // "Kanban", "Добавление заданий"
            QKanbanProjectAdd: 215,                               // "Kanban", "Проекты", "Создание новых проектов"
            QKanbanProjectEdit: 216,                              // "Kanban", "Проекты", "Редактирование проектов"
            QKanbanProjectClose: 217,                             // "Kanban", "Проекты", "Закрытие проектов"
            QKanbanStageAdd: 218,                                 // "Kanban", "Этапы", "Создание новых этапов"
            QKanbanStageRemove: 219,                              // "Kanban", "Этапы", "Удаление этапов"
            QKanbanStageEdit: 220,                                // "Kanban", "Этапы", "Редактирование этапов"
            QKanbanTaskRemove: 221,                               // "Kanban", "Задачи", "Удаление задач"
            QKanbanTaskEdit: 222,                                 // "Kanban", "Задачи", "Редактирование задач"
            QKanbanTaskMoveBetweenStages: 223,                    // "Kanban", "Задачи", "Перенос задач между этапами"
            QKanbanTaskAccessOnlyPersonal: 224,                   // "Kanban", "Задачи", "Доступ только к своим заданиям"
            QKanbanCommentAdd: 225,                               // "Kanban", "Комментарии", "Добавление комментариев"
            QKanbanCommentRemove: 226,                            // "Kanban", "Комментарии", "Удаление комментариев"

            QUserGroupsCommonView: 227,                           // "Группы пользователей", "Общие группы",  "Доступ к общим группам"
            QUserGroupsPersonalView: 228,                         // "Группы пользователей", "Личные группы", "Доступ к личным группам"
            QUserGroupsPersonalCreate: 229,                       // "Группы пользователей", "Личные группы", "Создание личных групп"
            QUserGroupsPersonalRemove: 230,                       // "Группы пользователей", "Личные группы", "Удаление личных групп"
            QUserGroupsPersonalEdit: 231,                         // "Группы пользователей", "Личные группы", "Редактирование личных групп"
            QTxtConfInsertSourceCode: 232,                        // "Конференции", "Вставка форматированонго исходного кода"
            QPrivInsertSourceCode: 233,                           // "Приваты", "Вставка форматированонго исходного кода"
            QKanbanRemoveSelfComments: 234,                       // "Канбан", "Комментарий", "Удаление своих комментариев"
            QKanbanViewTaskHistory:235,                           // "Канбан", "Комментарий", "Просмотр истории изменения задачи"
            QAdmAccessPersonalGroups: 236,                        // админка, "Пользователи", "Личные группы пользователей"
            QAdmAccessSettingsPlugins: 237,                       // админка, "Настройки", "Плагины"
            QEnableGPSTracking: 238,                              // "Общие", "Отправка текущей геопозиции на сервер"
            QAllowViewUserGeoPosition: 239,                       // "Профиль", "Просмотр информации", "Геопозиция (GPS)"
            QAdmAccessOptionsGPS: 240,                            //
            QFireUsers: 241,                                      // "Администрирование", "Увольнение пользователей"
            QAllowPrintChatText: 242,                             // "Общие", "Печать истории чатов на принтере"
            QAllowSaveChatText: 243,                              // "Общие", "Сохранение истории чатов в файл"
            QAllowRemoteExecScripts: 244,                         // "Integration API", "Разрешить удалённое выполнение скриптов"
            QAllowCustomMenu: 245,                                // "Общие", "Пользовательское меню"
            QAdmAccessToCustomMenu: 246,                          // админка, "Доступ к функциям управления сервером", "Инструменты", "Пользовательские меню"
            QAdmAccessPortTestTool: 247,                          // админка, "Доступ к функциям управления сервером", "Инструменты", "Тест портов"
            QProfileAllowToRemove: 248,                           // "Профиль", "Разрешить удаление своей учётной записи"
            QAllowConnectWindows: 249,                            //
            QAllowConnectLinux: 250,                              //
            QAllowConnectmacOS: 251,                              //
            QAllowConnectWEB: 252,                                //
            QAllowConnectAndroid: 253,                            //
            QAllowConnectiOS: 254,                                //
            QAllowCreateVoiceConf: 255,                           // "Конференции", "Звонки", "Создание голосовых конференций"
            QAllowCreateVideoConf: 256,                           // "Конференции", "Звонки", "Создание видеоконференций"
            QAllowCreateWebinar: 257,                             // "Конференции", "Звонки", "Создание вебинаров"
            QAllowCloseMediaConf: 258,                            // "Конференции", "Звонки", "Закрытие медиа конференций"
            QAllowEnterMediaConf: 259,                            // "Конференции", "Звонки", "Вход в медиа конференции"
            QAllowLeaveMediaConf: 260,                            // "Конференции", "Звонки", "Выход из медиа конференций"
            QAllowRemoveConf: 261,                                // "Конференции", "Удаление конференций"
            QAllowCloseConf: 262,                                 // "Конференции", "Закрытие конференций"
            QAllowRemoveSelfConf: 263,                            // "Конференции", "Удаление своих конференций"
            QAllowCloseSelfConf: 264,                             // "Конференции", "Закрытие своих конференций"
            QAllowEditSelfPrivateMessages: 265,                   // "Приваты", "Редактирование своих сообщений"
            QAllowEditSelfConfMessages: 266,                      // "Конференции", "Редактирование своих сообщений"
        },

        // =====================================================================

        msgType: {
             OLD_SHIT            : 0,
             TEXT                : 1,
            IMAGE               : 2,
            BEEP                : 3,
            OLD_FILE            : 4,  // deprecated
            MARKDOWN            : 5,
            ACTION              : 6,
            LAN_LINK            : 7,
            ANY_LINK            : 8,
            PREVIEW_CUSTOM_LINK : 9,
            INTEGRATION_API     : 10,
            SOURCE_CODE         : 11,
            MEDIA_CALL          : 12,
            IGNORE              : 13,
            SERVICE             : 14,
            CONF_PERSONAL       : 15,
            CONF_ALERT          : 16,
            CONF_CHANGE_TOPIC   : 17,
            CONF_BAN            : 18,
            CONF_TURN_OUT       : 19,
            BROADCAST_NOTIFY    : 20,
            COMMENT             : 21, // simple text, not shown in WEB support (for homemedhelp)
             KANBAN_NOTIFY      : 22, // уведомления с канбан-доски
            FILE                : 23,
             LOCATION           : 24, // географическая позиция
             CONTACT            : 25, // vCard
            AUDIO               : 26, // аудиозапись
            VIDEO               : 27, // видеозапись
            ADM_DELETE_MESSAGE  : 28, // удаление любого сообщения по его индексу
             FORUM_NOTIFY       : 29, // уведомления с форума
            SHOW_RECEIVED_FILES : 'srf', //
            SHOW_UPLOADED_FILES : 'suf', //
            PRIVATE_REDIRECT    : 30, // перенаправление приватного сообщения от одного пользователя - другому

            FORWARD             : 31, // message forward, оригинальное сообщение внутри "Msg", как контейнер
            CLIPBOARD_EXCEL     : 32, // (7.5+), содержимое буфера обмена в текстовом виде таблицы MS Excel
            BARCODE             : 33, // qr или штрих-код
            FTP_PUBLIC          : 34, // 8.0+ общий FTP
            FTP_PERSONAL        : 35, // 8.0+ личный FTP
            CONF_LINK           : 36, // Ссылка на текстовую конференцию, JSON в теле сообщения (поле "Msg")

            CONF_JUST_CREATED   : 38, // Информация о созданной конференции.
            CONF_INVITED        : 40, // Одного или нескольких пользователей пригласили в конференцию.
            YOUTUBE_LINK        : 41, // 8.6+ YouTube-ссылка
            REPLY               : 42, // 8.6+ ответ на сообщение
            REPLY_QUOTE         : 43,
            IMAGE_NEW           : 44, // Изображение
            FILE_NEW            : 45, // файлы
            EXCEL_NEW           : 46, // (8.6+), содержимое буфера обмена в текстовом виде таблицы MS Excel
            FORMATTED_TEXT      : 47, // (8.6+) форматированный текст
            MYCHAT_GUEST        : 48, // (8.8+) приглашение MyChat Guest успешно отправлено
            TEXT_WITH_TAGS      : 50, // (8.10+) текст с тегами [MENTION]123[/MENTION]
            TEXT_MODIFY         : 51, // Редактирование любого текстового сообщения (MSG_TYPE_TEXT) по его индексу.
                                      // "Idx" : 1745, // идентификатор сообщения, которое нужно изменить
                                      // "MsgNew" : "Bla-bla-bla", // текст нового сообщения
                                      // "Modifier": 17 // UIN юзера, который изменил сообщение, необязательный параметр, если месадж редактировал отправитель

            MEDIA_CONF_START    : 52,
            MEDIA_CONF_CLOSE    : 53
        },
        
        msgClientType: {
            WIN32   : 0,
            WEB     : 1,
            MACOS   : 2,
            LINUX   : 3,
            ANDROID : 4
        },

        loginStyle: {
            STANDARD       : '0',
            AD             : '1',
            NO_PASS        : '2',
            AD_TRANSPARENT : '3'
        },

        msgMods: {
            NONE     : 0,
            MODIFIED : 1,
            DELETED  : 2
        },

        confType: {
            STANDARD : 1, // обычная конференция
            PASSWORD : 2, // закрытая паролем
            HIDDEN   : 3  // скрытая
        },

        Errors: {
            WrongPassword       : 14,
            UserNotExist        : 17,
            WrongServerVersion  : 81,
            ConnectionLimit     : 209,
            NetworkError        : 224,
            Disconnected        : 223,
            WebServerStopped    : 321,
            ServerBusy          : 312,

            '---' : '---'
        },
        
        nsInternalErrors: {
            TELEGRAM_SCRIPT_NOT_FOUND      : 1,
            TELEGRAM_SCRIPT_DISABLED       : 2,
            INVALID_SCRIPT_RESULT          : 3,
            SCRIPT_TIMED_OUT               : 4,
            SCRIPT_SYNTAX                  : 5,
            SCRIPT_RUNTIME                 : 6,
            SCRIPT_NO_ONREQUESTBYREST      : 7,
            SCRIPT_INVALID_ONREQUESTBYREST : 8
        },

        whereType: {
            PRIVATE    : 1,
            CONFERENCE : 2,
            FORUM      : 3,
            KANBAN     : 4,
            BBS        : 5,
            BROADCAST  : 6,
        },

        ntfType: {
            ANDROID_SETUP             : 100, // просьба установить Android-приложение
            COMMON                    : 101, // встроенное уведомление, генерируется локально
            LINUXMACWEB               : 102, // встроенное уведомление, linux/mac/web
            UTP                       : 107, // встроенное уведомление, платные услуги
            GAME                      : 108, // встроенное уведомление, робот-викторина и т.п. развлекательные штуки
            IOS_SETUP                 : 109, // просьба установить iOS-приложение
            NEED_SUBSCRIPTION_2MONTHS : 110, // осталось два месяца до конца действия подписки
            NEED_SUBSCRIPTION_1MONTH  : 111, // остался месяц до конца действия подписки
            NEED_SUBSCRIPTION_2WEEKS  : 112, // осталось две недели до конца действия подписки
            SUBSCRIPTION_EXPIRED      : 113, // подписка просрочена
            FREE_VER_EXPIRED          : 114, // срок использования бесплатной версии закончился
        },

        updateConsoleCMD : "--updateClient",
        normalStart      : "--start",

        _last_: '_last_' // просто, шоб було
        // ==========================================
});

    
var mcConst = {
    Title : "MyChat Server Control Panel",
    LMSG  : [],
    SYSLOG: [],
    _CMD_ : window.hasOwnProperty('getCMD') ? getCMD() : {},

    Lang : "ru",

    cardWidth: 300,

    kanbanActions : {
        created : 0,
        changed : 1,
        deleted : 2,
        open    : 99
    },

    accessType: {
        fullAccess: 0,
        customAccess: 1
    },

    stagesTypes: {
        typical: 0,
        done   : 1,
        archive: 2
    },

    methods: {
        OR: "or",
        AND: "and"
    },

    io_transport: ["polling"], // , "websocket"

    nodePathes: {
        images: "sendimages",
        files : "files"
    },

    pathAliases: {
        AliasAdmin    : "admin", // папка-псевдоним, путь к админке
        AliasAPI      : "API", // папка-псевдоним, путь к Integration API
        AliasChat     : "chat", // папка-псевдоним, путь к WEB-чату
        AliasFiles    : "files", // папка-псевдоним, путь к файлам, залитым на сервер
        AliasForum    : "forum", // папка-псевдоним, путь ко встроенному форуму
        AliasKanban   : "kanban", // папка-псевдоним, путь к канбан-доске

        LobbyEnable   : true, // включен доступ к списку сервисов MyChat
        ForumEnable   : true, // включить доступ ко встроенному форуму
        KanbanEnable  : true, // включить доступ к канбан-доске
        ChatEnable    : true, // включить доступ к WEB-чату
        AdminEnable   : true // включить доступ к WEB-админке
    },

    whereFiles: {
        private: 1,
        conf   : 2,
        forum  : 3,
        kanban : 4,
        bbs    : 5,
        broadc : 6
    },

    lockPrevHotKeys: true,

    lockInterface : 'lockInterface',

    maxAvailableDate: '01.01.3000.00.00.00',
    neverDate       : '01.01.1900.00.00.00',

    priority: {
        LOW      : 1,
        MEDIUM   : 2,
        HIGH     : 3,
        IMPORTANT: 4,
        URGENT   : 5,
        CRITICAL : 6
    },

    prefix: {
        file : "@{",
        image: "${"
    },

    storageFields: {
        lang    : "kbLang",
        Login   : "kbLogin",
        Pwd     : "kbPwd",
        ServPwd : "kbServPwd",
        Rm      : "kbRm"
    },

    userFields : 'REMOTECLIENTTYPE,REMOTEINTERFACESLIST,LASTACCESS,LASTSPEAKTIME,STATE,TAG,HARDWAREID,DOMAIN,' +
                 'DOMAINREGTIME,DOMAINCHANGETIME,VER,UTC,REPUTATION,NICK,DISPLAYNAME,EMAIL,GROUPRIGHTS,COMPNETNAME,' +
                 'IP,MAC,UIN,ACTIVE,REGISTERED,WORKDEPT,TEAMLEAD,AUTHTYPE,SECURED', //

    dataModels : {
        Login           : 'Login',
        Wrapper         : 'Wrapper',
        Toolbar         : 'Toolbar',
        KanbanBody      : 'KanbanBody',
        Task            : 'Task',
        TaskEdit        : 'TaskEdit',
        // NewProject      : 'NewProject',
        Comments        : 'Comments',
        Dashboard       : 'Dashboard',
        EditProject     : 'EditProject',

        Menu            : 'Menu'
    },

    imagesPath : {
        noFilter   : "source/images/nofilter20.png",
        // langPath   : 'source/languages/',
        loadImage  : 'source/images/loadimg.gif',
        loadingImg : 'source/images/loading.gif',
        all        : 'source/images/',
        flags      : 'source/images/flags/',
        noImage    : 'source/images/noimage.png',
        noFile     : 'source/images/nofile.gif'
    },

    imageSize: {
        small : {
            x: 120,
            y: 120,
            xy: '120x120'
        },
        medium: {
            x: 150,
            y: 150,
            xy: '150x150'
        },
        big   : {
            x: 200,
            y: 200,
            xy: '200x200'
        },
        "0" : {
            x: 120,
            y: 120,
            xy: '120x120'
        },
        "1": {
            x: 150,
            y: 150,
            xy: '150x150'
        },
        "2"   : {
            x: 200,
            y: 200,
            xy: '200x200'
        }
    },

    customLogin : false,

    lockWindowHeight : 65,

    ApplyPanelShowed : false,

    errMessageExpire : 5000,

    guestGroupId : 7,

    userFoto : {
        w : 135,
        h : 157
    },

    CRLF : '\r\n',
    CR   : '\r',
    LF   : '\n',

    MyRightsSet : [
      /*
        + {1}   QLogin, "811":"Подключаться к MyChat серверу",
        + {2}   QBBSPost, "786":"Создание новых сообщений на доске",
        + {3}   QBBSView, "785":"Просмотр доски объявлений",
        + {4}   QTxtChCreate, "797":"Создание текстовых каналов",
        + {5}   QTxtChSay, "844":"Отправка сообщений в текстовые каналы",
        + {6}   QTxtChJoin, "796":"Вход в существующие текстовые каналы",
        + {7}   QPrivateOpen, "831":"Открытие новых приватов",
        + {8}   QPrivatesGetMessages, "832":"Получение приватных сообщений от других пользователей",
        + {9}   QUserDetailsChange, "837":"Изменение персонального профиля",
        + {10}  QFilesSend, "841":"Разрешить отправлять файлы другим пользователям",
        + {11}  QFilesReceive, "842":"Разрешить принимать файлы от других пользователей",
        + {12}  QOperatorBan, "821":"Право наказывать пользователей (ban)",
        + {13}  QOperatorTurnOutFromChannel, "820":"Право изгнания пользователей из текстовых каналов (kick)",
        + {14}  QKill,
        + {15}  QBlockIP,
        + {16}  QBlockMAC,
        + {17}  QOperatorTxtChTopicSet, "818":"Изменение темы текстовых каналов",
        + {18}  QFTPPublic, "840":"Доступ к общей папке на файловом сервере",
        + {19}  QFTPPersonal, "839":"Доступ к личной папке на файловом сервере",
        + {20}  QRemoteAdm,
        + {21}  QOperatorClearChannelText, "819":"Очистка текстовых каналов у всех пользователей",
        + {22}  QContactsListChange, "799":"Изменение персональной панели контактов",
        + {23}  QBroadcasts, "823":"Отправка оповещения другим пользователям",
        + {24}  QAllowPersonalMsg, "794":"Отправка персональных сообщений в каналах",
        + {25}  QAllowAlertMsg, "795":"Отправка важных персональных сообщений в каналах",
        + {26}  QAllowSysInfoView, "815":"Просмотр сетевого имени компьютера и IP-адреса пользователя",
        + {27}  QSendImages, "792":"Вставка изображений",
        + {28}  QAllowIgnores, "805":"Игнорирование других пользователей",
        + {29}  QAllowInvites, "810":"Отправка приглашений в каналы другим пользователям",
        + {30}  QImagesPrivates, "830":"Вставка изображений",
        + {31}  QShowCommonContactsList, "800":"Показывать общий список контактов",
        + {32}  QHalt,
        + {33}  QTxtChRename,
        + {34}  QTxtChDelete,
        + {35}  QForciblyInvites,
        + {36}  QBlockUIN,
        + {37}  QAllowPlugins, "827":"Использование плагинов",
        + {38}  QAllowOptionsChange, "806":"Изменение настроек клиентского приложения",
        + {39}  QAllowMyFavoriteChannels, "793":"Доступ к ''Моим любимым каналам''",
        + {40}  QAllowCloseProgram,
        + {41}  QAllowContactsListsTotal,                         // разрешить или запретить полностью доступ к панели контактов
        + {42}  QAllowAccountsManager,                            // разрешить пользоваться менеджером учётных записей
        + {43}  QWEBAllowViewLogsSelfPrivates,                    // просмотр своих разговоров в приватах
        + {44}  QWEBAllowViewLogsAllChannels,                     // просмотр всех разговоров в каналах
        + {45}  QWEBAllowViewLogsAllPrivates,                     // просмотр всех разговоров в приватах
        + {46}  QAllowFindUsersTool,                              // разрешить поиск пользователей
        + {47}  QAllowActions,                                    // разрешить вставку в текст Action-ов
        + {48}  QAllowClientLogPrivatesAndChannels,               // разрешить или запретить клиенту писать логи локально, у себя на компьютере
        + {49}  QMessengerStyle,                                  // Messenger-style отображения майчат клиента
        + {50}  QAllowOpenComputer,                               // показывать или нет в меню опцию "Открыть компьютер" по щелчку на имени юзера
        + {51}  QExceptionAllowRecievePrivates,                   // Приём приватных сообщений от пользователей, которым запрещена отправка приватных сообщений
        + {52}  QAllowViewRemoteUserMACAddress,                   // разрешить смотреть MAC адрес удалённого юзера
        + {53}  QAllowViewRemoteUserActiveProcess,                // разрешить смотреть активный процесс удалённого юзера
        + {54}  QWEBAllowViewFTPLog,                              // Просмотр FTP протоколов
        + {55}  QWEBAllowViewSystemLog,                           // Просмотр системных протоколов
        + {56}  QEnableTaskWorkersJobTimeControlSystem,           // контроль рабочего времени
        + {57}  QWEBSupportAllowAccess,                           // доступ к WEB-интерфейсу веб-суппорта
        + {58}  QWEBAllowAdmin,                                   // администрирование сервере через WEB
        + {59}  QAllowProgramBlockTool,                           // Блокирование работающей программы паролем
        + {60}  QAllowNetworkStatusChange,                        // Изменение сетевого статуса
        + {61}  QEnableAllowedClientsPluginsList,                 // Активировать список разрешённых клиентских плагинов
        + {62}  QEnableBlockedCLientsPluginsList,                 // Активировать список запрещённых клиенских плагинов
        + {63}  QEnablePluginsManagement,                         // Разрешить управление плагинами на клиенте
        + {64}  QAllowImagesInBroadcasts,                         // вставка картинок в оповещениях
        + {65}  QEnableFontLayoutsInMessages,                     // использование шрифтовой разметки в отправляемых сообщениях
          {66}  QAllowCreateHiddenChannels,                       // - (это ещё не внесено в базу данных) разрешить создавать скрытые каналы
        + {67}  QEnableTransferFilesOnlyViaServer,                // передавать файлы только через сервер
        + {68}  QEnableImmunityFromBans,                          // иммунитет от банов
        + {69}  QEnableAdvertBlock                                // показывать или нет рекламный блок
        + {70}  QBlockPrivatesExceptPersonalContacts,             // Запретить приваты со всеми, кроме входящих в личный список контактов
        + {71}  QBlockPrivatesExceptCommonContacts,               // Запретить приваты со всеми, кроме входящих в общий список контактов
        + {72}  QEnableImmunityFromIgnores,                       // Иммунитет от игноров
        + {73}  QIntegrationAPIEnableSendMsgFromAnyUser,          // Разрешить отправку сообщений от имени этих пользователей
        + {74}  QEnableVoiceCalls,                                // Голосовые звонки
        + {75}  QEnableVideoCalls,                                // Видеозвонки
        + {76}  QAllowGetVoiceCalls,                              // Разрешить получать голосовые звонки
        + {77}  QAllowGetVideoCalls,                              // Разрешить получать видеозвонки
        + {78}  QAllowAddNewPlugins,                              // Добавление новых плагинов
        + {79}  QAllowDeletePlugins,                              // Удаление плагинов
        + {80}  QAllowChangeAccountPassword,                      // Изменение пароля своей учётной записи
        + {81}  QAllowAccessForeignersToPersonalFiles,            // Разрешить доступ к личным файлам для других пользователей ("только чтение")
        + {82}  QAllowPublishLinkToPersonalFiles,                 // Разрешить публиковать ссылку на "Мои файлы на сервере"
        + {83}  QFTPPublicWrite,                                  // разрешить писать в общую папку
        + {84}  QFTPPersonalWrite                                 // разрешить писать в личную папку
        + {85}  QAllowSendInvites                                 // разрешить отправлять приглашения в MyChat другим людям
        + {86}  QWEBAllowViewNodeJSLogs,                          // разрешить просмотр логов NodeJS сервера
        + {87}  QWEBAllowViewAuditLogs                            // разрешить просмотр логов аудита
      */
    ],

    LoginInfo : {
        login   : "",
        pwd     : "",
        servPwd : "",
        rm      : ""
    },

    Auth      : '',
    PWD       : '',
    servPass  : '',

    BrowserID : '',
    SessionID : '',

    LoggedIn  : false,
    nowLogin  : false,

    terminator       : '\u2022',
    newLine          : '\u000b',

    PingTimer        : null,
    PingInterval     : 1100,

    mcPara           : "L$",

    UserInfo   : {                       // информация о залогиненом юзере
        MaxUploadFileSize : 0,
        "UIN"             : 0,           // UIN пользователя, который залогинен в системе
        "Nick"            : "Anonymous", // ник пользователя, который сообщил ему сервер
        "Sex"             : 1,           // пол пользователя
        "Avatar"          : 0,           // номер аватара пользователя
        "LoginStyle"      : "login",     // тип логина: "login" - обычный логин, "domain" - доменная авторизация
        "AutoAwayTime"    : 15,          // время в минутах, после которого будет считаться, что пользователь неактивен
                                         // за компьютером, если он не нажимал ничего на клавиатуре и не шевелил мышкой
        "ServerSignature" : "",          // специальная сигнатура сервера MyChat, нужна для системы статистики
        "HWID"            : "",          // серийник сервера
        "Domain"          : "domain"     // если доменная авторизация, "LoginStyle" = "domain", то в этом параметре
                                         // передаётся ещё и имя домена, с которого проходит аторизация.
                                         // Если доменной авторизации нет - этот параметр не передаётся
    },

    ErrorText  : {},
    SrvErrorText: {},

    CanDisplayNotificator : false,
    NotificatorObject     : null,

    ExtendedMode           : true,
    CanChangToExtendedMode : true,
    WidthForExtendedMode   : 800,

    oneDay : 1000*60*60*24,

    navWidth : 270,

    isShowed : null,
    nextShow : '',

    scrResize : null,
    navResize : null,

    keyCodes : {
        // Alphabet
        a:65, b:66, c:67, d:68, e:69,
        f:70, g:71, h:72, i:73, j:74,
        k:75, l:76, m:77, n:78, o:79,
        p:80, q:81, r:82, s:83, t:84,
        u:85, v:86, w:87, x:88, y:89,
        z:90,

        // Numbers
        n0:48, n1:49, n2:50, n3:51, n4:52,
        n5:53, n6:54, n7:55, n8:56, n9:57,

        // Controls
        tab:  9, enter :13,  shift :16, backspace:8,
        ctrl:17, alt   :18,  esc   :27, space    :32,
        menu:93, pause :19,  cmd   :91, insert   :45,
        home:36, pageup:33,'delete':46, end      :35,
        pagedown:34,

        // F*
        f1:112, f2:113, f3:114, f4 :115, f5 :116, f6 :117,
        f7:118, f8:119, f9:120, f10:121, f11:122, f12:123,

        // numpad
        np0: 96, np1: 97, np2: 98, np3: 99, np4:100,
        np5:101, np6:102, np7:103, np8:104, np9:105,

        npslash:11,npstar:106,nphyphen:109,npplus:107,npdot:110,

        // Lock
        capslock:20, numlock:144, scrolllock:145,

        // Symbols
        equals: 61, hyphen   :109, coma  :188, dot:190,
        gravis:192, backslash:220, sbopen:219, sbclose:221,
        slash :191, semicolon: 59, apostrophe : 222,

        // Arrows
        aleft:37, aup:  38, aright:39, adown:40,

        PrintSymbols :
            [32, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65,
                66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76,
                77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
                88, 89, 90, 96, 97, 98, 99, 100,101,102,103,
                105,106,107,109,110,111,186,187,188,189,190,
                104,191,192,219,220,221,222],

        EditSymbols  : [8,  32, 46],

        Digits :
            [48, 49, 50, 51,  52, 53, 54, 55, 56, 57,
                96, 97, 98, 99, 100,101,102,103,104,105],

        ControlFunc	 :
            [ 33, 34, 35, 36, 37, 38, 39, 40,112,113,
                114,115,116,117,118,119,120,121,122,123],

        ControlsKeys : [36, 33, 35, 34, 37, 38, 39, 40],

        PrintAndEdit : function(){
            return mcConst.keyCodes.PrintSymbols.concat(mcConst.keyCodes.EditSymbols);
        }
    }
};
(function () {
    var /*
 * Rusha, a JavaScript implementation of the Secure Hash Algorithm, SHA-1,
 * as defined in FIPS PUB 180-1, tuned for high performance with large inputs.
 * (http://github.com/srijs/rusha)
 *
 * Inspired by Paul Johnstons implementation (http://pajhome.org.uk/crypt/md5).
 *
 * Copyright (c) 2013 Sam Rijs (http://awesam.de).
 * Released under the terms of the MIT license as follows:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
    util = {
        getDataType: function (data) {
            if (typeof data === 'string') {
                return 'string';
            }
            if (data instanceof Array) {
                return 'array';
            }
            if (typeof global !== 'undefined' && global.Buffer && global.Buffer.isBuffer(data)) {
                return 'buffer';
            }
            if (data instanceof ArrayBuffer) {
                return 'arraybuffer';
            }
            if (data.buffer instanceof ArrayBuffer) {
                return 'view';
            }
            if (data instanceof Blob) {
                return 'blob';
            }
            throw new Error('Unsupported data type.');
        }
    };
    function Rusha(chunkSize) {
        'use strict';
        var // Private object structure.
        self$2 = { fill: 0 };
        var // Calculate the length of buffer that the sha1 routine uses
        // including the padding.
        padlen = function (len) {
            for (len += 9; len % 64 > 0; len += 1);
            return len;
        };
        var padZeroes = function (bin, len) {
            var h8 = new Uint8Array(bin.buffer);
            var om = len % 4, align = len - om;
            switch (om) {
            case 0:
                h8[align + 3] = 0;
            case 1:
                h8[align + 2] = 0;
            case 2:
                h8[align + 1] = 0;
            case 3:
                h8[align + 0] = 0;
            }
            for (var i$2 = (len >> 2) + 1; i$2 < bin.length; i$2++)
                bin[i$2] = 0;
        };
        var padData = function (bin, chunkLen, msgLen) {
            bin[chunkLen >> 2] |= 128 << 24 - (chunkLen % 4 << 3);
            // To support msgLen >= 2 GiB, use a float division when computing the
            // high 32-bits of the big-endian message length in bits.
            bin[((chunkLen >> 2) + 2 & ~15) + 14] = msgLen / (1 << 29) | 0;
            bin[((chunkLen >> 2) + 2 & ~15) + 15] = msgLen << 3;
        };
        var // Convert a binary string and write it to the heap.
        // A binary string is expected to only contain char codes < 256.
        convStr = function (H8, H32, start, len, off) {
            var str = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm;
            switch (om) {
            case 0:
                H8[off] = str.charCodeAt(start + 3);
            case 1:
                H8[off + 1 - (om << 1) | 0] = str.charCodeAt(start + 2);
            case 2:
                H8[off + 2 - (om << 1) | 0] = str.charCodeAt(start + 1);
            case 3:
                H8[off + 3 - (om << 1) | 0] = str.charCodeAt(start);
            }
            if (len < lm + om) {
                return;
            }
            for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) {
                H32[off + i$2 >> 2] = str.charCodeAt(start + i$2) << 24 | str.charCodeAt(start + i$2 + 1) << 16 | str.charCodeAt(start + i$2 + 2) << 8 | str.charCodeAt(start + i$2 + 3);
            }
            switch (lm) {
            case 3:
                H8[off + j + 1 | 0] = str.charCodeAt(start + j + 2);
            case 2:
                H8[off + j + 2 | 0] = str.charCodeAt(start + j + 1);
            case 1:
                H8[off + j + 3 | 0] = str.charCodeAt(start + j);
            }
        };
        var // Convert a buffer or array and write it to the heap.
        // The buffer or array is expected to only contain elements < 256.
        convBuf = function (H8, H32, start, len, off) {
            var buf = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm;
            switch (om) {
            case 0:
                H8[off] = buf[start + 3];
            case 1:
                H8[off + 1 - (om << 1) | 0] = buf[start + 2];
            case 2:
                H8[off + 2 - (om << 1) | 0] = buf[start + 1];
            case 3:
                H8[off + 3 - (om << 1) | 0] = buf[start];
            }
            if (len < lm + om) {
                return;
            }
            for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) {
                H32[off + i$2 >> 2 | 0] = buf[start + i$2] << 24 | buf[start + i$2 + 1] << 16 | buf[start + i$2 + 2] << 8 | buf[start + i$2 + 3];
            }
            switch (lm) {
            case 3:
                H8[off + j + 1 | 0] = buf[start + j + 2];
            case 2:
                H8[off + j + 2 | 0] = buf[start + j + 1];
            case 1:
                H8[off + j + 3 | 0] = buf[start + j];
            }
        };
        var convBlob = function (H8, H32, start, len, off) {
            var blob = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm;
            var buf = new Uint8Array(reader.readAsArrayBuffer(blob.slice(start, start + len)));
            switch (om) {
            case 0:
                H8[off] = buf[3];
            case 1:
                H8[off + 1 - (om << 1) | 0] = buf[2];
            case 2:
                H8[off + 2 - (om << 1) | 0] = buf[1];
            case 3:
                H8[off + 3 - (om << 1) | 0] = buf[0];
            }
            if (len < lm + om) {
                return;
            }
            for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) {
                H32[off + i$2 >> 2 | 0] = buf[i$2] << 24 | buf[i$2 + 1] << 16 | buf[i$2 + 2] << 8 | buf[i$2 + 3];
            }
            switch (lm) {
            case 3:
                H8[off + j + 1 | 0] = buf[j + 2];
            case 2:
                H8[off + j + 2 | 0] = buf[j + 1];
            case 1:
                H8[off + j + 3 | 0] = buf[j];
            }
        };
        var convFn = function (data) {
            switch (util.getDataType(data)) {
            case 'string':
                return convStr.bind(data);
            case 'array':
                return convBuf.bind(data);
            case 'buffer':
                return convBuf.bind(data);
            case 'arraybuffer':
                return convBuf.bind(new Uint8Array(data));
            case 'view':
                return convBuf.bind(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
            case 'blob':
                return convBlob.bind(data);
            }
        };
        var slice = function (data, offset) {
            switch (util.getDataType(data)) {
            case 'string':
                return data.slice(offset);
            case 'array':
                return data.slice(offset);
            case 'buffer':
                return data.slice(offset);
            case 'arraybuffer':
                return data.slice(offset);
            case 'view':
                return data.buffer.slice(offset);
            }
        };
        var // Precompute 00 - ff strings
        precomputedHex = new Array(256);
        for (var i = 0; i < 256; i++) {
            precomputedHex[i] = (i < 16 ? '0' : '') + i.toString(16);
        }
        var // Convert an ArrayBuffer into its hexadecimal string representation.
        hex = function (arrayBuffer) {
            var binarray = new Uint8Array(arrayBuffer);
            var res = new Array(arrayBuffer.byteLength);
            for (var i$2 = 0; i$2 < res.length; i$2++) {
                res[i$2] = precomputedHex[binarray[i$2]];
            }
            return res.join('');
        };
        var ceilHeapSize = function (v) {
            // The asm.js spec says:
            // The heap object's byteLength must be either
            // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
            // Also, byteLengths smaller than 2^16 are deprecated.
            var p;
            if (// If v is smaller than 2^16, the smallest possible solution
                // is 2^16.
                v <= 65536)
                return 65536;
            if (// If v < 2^24, we round up to 2^n,
                // otherwise we round up to 2^24 * n.
                v < 16777216) {
                for (p = 1; p < v; p = p << 1);
            } else {
                for (p = 16777216; p < v; p += 16777216);
            }
            return p;
        };
        var // Initialize the internal data structures to a new capacity.
        init = function (size) {
            if (size % 64 > 0) {
                throw new Error('Chunk size must be a multiple of 128 bit');
            }
            self$2.offset = 0;
            self$2.maxChunkLen = size;
            self$2.padMaxChunkLen = padlen(size);
            // The size of the heap is the sum of:
            // 1. The padded input message size
            // 2. The extended space the algorithm needs (320 byte)
            // 3. The 160 bit state the algoritm uses
            self$2.heap = new ArrayBuffer(ceilHeapSize(self$2.padMaxChunkLen + 320 + 20));
            self$2.h32 = new Int32Array(self$2.heap);
            self$2.h8 = new Int8Array(self$2.heap);
            self$2.core = new Rusha._core({
                Int32Array: Int32Array,
                DataView: DataView
            }, {}, self$2.heap);
            self$2.buffer = null;
        };
        // Iinitializethe datastructures according
        // to a chunk siyze.
        init(chunkSize || 64 * 1024);
        var initState = function (heap, padMsgLen) {
            self$2.offset = 0;
            var io = new Int32Array(heap, padMsgLen + 320, 5);
            io[0] = 1732584193;
            io[1] = -271733879;
            io[2] = -1732584194;
            io[3] = 271733878;
            io[4] = -1009589776;
        };
        var padChunk = function (chunkLen, msgLen) {
            var padChunkLen = padlen(chunkLen);
            var view = new Int32Array(self$2.heap, 0, padChunkLen >> 2);
            padZeroes(view, chunkLen);
            padData(view, chunkLen, msgLen);
            return padChunkLen;
        };
        var // Write data to the heap.
        write = function (data, chunkOffset, chunkLen, off) {
            convFn(data)(self$2.h8, self$2.h32, chunkOffset, chunkLen, off || 0);
        };
        var // Initialize and call the RushaCore,
        // assuming an input buffer of length len * 4.
        coreCall = function (data, chunkOffset, chunkLen, msgLen, finalize) {
            var padChunkLen = chunkLen;
            write(data, chunkOffset, chunkLen);
            if (finalize) {
                padChunkLen = padChunk(chunkLen, msgLen);
            }
            self$2.core.hash(padChunkLen, self$2.padMaxChunkLen);
        };
        var getRawDigest = function (heap, padMaxChunkLen) {
            var io = new Int32Array(heap, padMaxChunkLen + 320, 5);
            var out = new Int32Array(5);
            var arr = new DataView(out.buffer);
            arr.setInt32(0, io[0], false);
            arr.setInt32(4, io[1], false);
            arr.setInt32(8, io[2], false);
            arr.setInt32(12, io[3], false);
            arr.setInt32(16, io[4], false);
            return out;
        };
        var // Calculate the hash digest as an array of 5 32bit integers.
        rawDigest = this.rawDigest = function (str) {
            var msgLen = str.byteLength || str.length || str.size || 0;
            initState(self$2.heap, self$2.padMaxChunkLen);
            var chunkOffset = 0, chunkLen = self$2.maxChunkLen;
            for (chunkOffset = 0; msgLen > chunkOffset + chunkLen; chunkOffset += chunkLen) {
                coreCall(str, chunkOffset, chunkLen, msgLen, false);
            }
            coreCall(str, chunkOffset, msgLen - chunkOffset, msgLen, true);
            return getRawDigest(self$2.heap, self$2.padMaxChunkLen);
        };
        // The digest and digestFrom* interface returns the hash digest
        // as a hex string.
        this.digest = this.digestFromString = this.digestFromBuffer = this.digestFromArrayBuffer = function (str) {
            return hex(rawDigest(str).buffer);
        };
        this.resetState = function () {
            initState(self$2.heap, self$2.padMaxChunkLen);
            return this;
        };
        this.append = function (chunk) {
            var chunkOffset = 0;
            var chunkLen = chunk.byteLength || chunk.length || chunk.size || 0;
            var turnOffset = self$2.offset % self$2.maxChunkLen;
            var inputLen;
            self$2.offset += chunkLen;
            while (chunkOffset < chunkLen) {
                inputLen = Math.min(chunkLen - chunkOffset, self$2.maxChunkLen - turnOffset);
                write(chunk, chunkOffset, inputLen, turnOffset);
                turnOffset += inputLen;
                chunkOffset += inputLen;
                if (turnOffset === self$2.maxChunkLen) {
                    self$2.core.hash(self$2.maxChunkLen, self$2.padMaxChunkLen);
                    turnOffset = 0;
                }
            }
            return this;
        };
        this.getState = function () {
            var turnOffset = self$2.offset % self$2.maxChunkLen;
            var heap;
            if (!turnOffset) {
                var io = new Int32Array(self$2.heap, self$2.padMaxChunkLen + 320, 5);
                heap = io.buffer.slice(io.byteOffset, io.byteOffset + io.byteLength);
            } else {
                heap = self$2.heap.slice(0);
            }
            return {
                offset: self$2.offset,
                heap: heap
            };
        };
        this.setState = function (state) {
            self$2.offset = state.offset;
            if (state.heap.byteLength === 20) {
                var io = new Int32Array(self$2.heap, self$2.padMaxChunkLen + 320, 5);
                io.set(new Int32Array(state.heap));
            } else {
                self$2.h32.set(new Int32Array(state.heap));
            }
            return this;
        };
        var rawEnd = this.rawEnd = function () {
            var msgLen = self$2.offset;
            var chunkLen = msgLen % self$2.maxChunkLen;
            var padChunkLen = padChunk(chunkLen, msgLen);
            self$2.core.hash(padChunkLen, self$2.padMaxChunkLen);
            var result = getRawDigest(self$2.heap, self$2.padMaxChunkLen);
            initState(self$2.heap, self$2.padMaxChunkLen);
            return result;
        };
        this.end = function () {
            return hex(rawEnd().buffer);
        };
    }
    ;
    // The low-level RushCore module provides the heart of Rusha,
    // a high-speed sha1 implementation working on an Int32Array heap.
    // At first glance, the implementation seems complicated, however
    // with the SHA1 spec at hand, it is obvious this almost a textbook
    // implementation that has a few functions hand-inlined and a few loops
    // hand-unrolled.
    Rusha._core = function RushaCore(stdlib, foreign, heap) {
        'use asm';
        var H = new stdlib.Int32Array(heap);
        function hash(k, x) {
            // k in bytes
            k = k | 0;
            x = x | 0;
            var i = 0, j = 0, y0 = 0, z0 = 0, y1 = 0, z1 = 0, y2 = 0, z2 = 0, y3 = 0, z3 = 0, y4 = 0, z4 = 0, t0 = 0, t1 = 0;
            y0 = H[x + 320 >> 2] | 0;
            y1 = H[x + 324 >> 2] | 0;
            y2 = H[x + 328 >> 2] | 0;
            y3 = H[x + 332 >> 2] | 0;
            y4 = H[x + 336 >> 2] | 0;
            for (i = 0; (i | 0) < (k | 0); i = i + 64 | 0) {
                z0 = y0;
                z1 = y1;
                z2 = y2;
                z3 = y3;
                z4 = y4;
                for (j = 0; (j | 0) < 64; j = j + 4 | 0) {
                    t1 = H[i + j >> 2] | 0;
                    t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0;
                    y4 = y3;
                    y3 = y2;
                    y2 = y1 << 30 | y1 >>> 2;
                    y1 = y0;
                    y0 = t0;
                    H[k + j >> 2] = t1;
                }
                for (j = k + 64 | 0; (j | 0) < (k + 80 | 0); j = j + 4 | 0) {
                    t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31;
                    t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0;
                    y4 = y3;
                    y3 = y2;
                    y2 = y1 << 30 | y1 >>> 2;
                    y1 = y0;
                    y0 = t0;
                    H[j >> 2] = t1;
                }
                for (j = k + 80 | 0; (j | 0) < (k + 160 | 0); j = j + 4 | 0) {
                    t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31;
                    t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) + 1859775393 | 0) | 0;
                    y4 = y3;
                    y3 = y2;
                    y2 = y1 << 30 | y1 >>> 2;
                    y1 = y0;
                    y0 = t0;
                    H[j >> 2] = t1;
                }
                for (j = k + 160 | 0; (j | 0) < (k + 240 | 0); j = j + 4 | 0) {
                    t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31;
                    t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | y1 & y3 | y2 & y3) | 0) + ((t1 + y4 | 0) - 1894007588 | 0) | 0;
                    y4 = y3;
                    y3 = y2;
                    y2 = y1 << 30 | y1 >>> 2;
                    y1 = y0;
                    y0 = t0;
                    H[j >> 2] = t1;
                }
                for (j = k + 240 | 0; (j | 0) < (k + 320 | 0); j = j + 4 | 0) {
                    t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31;
                    t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) - 899497514 | 0) | 0;
                    y4 = y3;
                    y3 = y2;
                    y2 = y1 << 30 | y1 >>> 2;
                    y1 = y0;
                    y0 = t0;
                    H[j >> 2] = t1;
                }
                y0 = y0 + z0 | 0;
                y1 = y1 + z1 | 0;
                y2 = y2 + z2 | 0;
                y3 = y3 + z3 | 0;
                y4 = y4 + z4 | 0;
            }
            H[x + 320 >> 2] = y0;
            H[x + 324 >> 2] = y1;
            H[x + 328 >> 2] = y2;
            H[x + 332 >> 2] = y3;
            H[x + 336 >> 2] = y4;
        }
        return { hash: hash };
    };

    if (// If we're running in a DOM context, export
        // the Rusha object to toplevel.
        typeof window !== 'undefined') {
        window.Rusha = Rusha;
    } else 
    if (// If we'e running in Node.JS, export a module.
        typeof module !== 'undefined') {
        module.exports = Rusha;
    } 

    if (// If we're running in a webworker, accept
        // messages containing a jobid and a buffer
        // or blob object, and return the hash result.
        typeof FileReaderSync !== 'undefined') {
        var reader = new FileReaderSync();
        var hashData = function hash(hasher, data, cb) {
            try {
                return cb(null, hasher.digest(data));
            } catch (e) {
                return cb(e);
            }
        };
        var hashFile = function hashArrayBuffer(hasher, readTotal, blockSize, file, cb) {
            var reader$2 = new self.FileReader();
            reader$2.onloadend = function onloadend() {
                var buffer = reader$2.result;
                readTotal += reader$2.result.byteLength;
                try {
                    hasher.append(buffer);
                } catch (e) {
                    cb(e);
                    return;
                }
                if (readTotal < file.size) {
                    hashFile(hasher, readTotal, blockSize, file, cb);
                } else {
                    cb(null, hasher.end());
                }
            };
            reader$2.readAsArrayBuffer(file.slice(readTotal, readTotal + blockSize));
        };
        self.onmessage = function onMessage(event) {
            var data = event.data.data, file = event.data.file, id = event.data.id;
            if (typeof id === 'undefined')
                return;
            if (!file && !data)
                return;
            var blockSize = event.data.blockSize || 4 * 1024 * 1024;
            var hasher = new Rusha(blockSize);
            hasher.resetState();
            var done = function done$2(err, hash) {
                if (!err) {
                    self.postMessage({
                        id: id,
                        hash: hash
                    });
                } else {
                    self.postMessage({
                        id: id,
                        error: err.name
                    });
                }
            };
            if (data)
                hashData(hasher, data, done);
            if (file)
                hashFile(hasher, 0, blockSize, file, done);
        };
    }
}());
/*
 * Purl (A JavaScript URL parser) v2.3.1
 * Developed and maintanined by Mark Perkins, mark@allmarkedup.com
 * Source repository: https://github.com/allmarkedup/jQuery-URL-Parser
 * Licensed under an MIT-style license. See https://github.com/allmarkedup/jQuery-URL-Parser/blob/master/LICENSE for details.
 */

;(function(factory) {
    if (typeof exports === 'object') {
        exports.purl = factory();
        return;
    }

    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {
        window.purl = factory();
    }
})(function() {

    var tag2attr = {
            a       : 'href',
            img     : 'src',
            form    : 'action',
            base    : 'href',
            script  : 'src',
            iframe  : 'src',
            link    : 'href',
            embed   : 'src',
            object  : 'data'
        },

        key = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'], // keys available to query

        aliases = { 'anchor' : 'fragment' }, // aliases for backwards compatability

        parser = {
            strict : /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,  //less intuitive, more accurate to the specs
            loose :  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
        },

        isint = /^[0-9]+$/;

    function parseUri( url, strictMode ) {
        var str = decodeURI( url ),
        res   = parser[ strictMode || false ? 'strict' : 'loose' ].exec( str ),
        uri = { attr : {}, param : {}, seg : {} },
        i   = 14;

        while ( i-- ) {
            uri.attr[ key[i] ] = res[i] || '';
        }

        // build query and fragment parameters
        uri.param['query'] = parseString(uri.attr['query']);
        uri.param['fragment'] = parseString(uri.attr['fragment']);

        // split path and fragement into segments
        uri.seg['path'] = uri.attr.path.replace(/^\/+|\/+$/g,'').split('/');
        uri.seg['fragment'] = uri.attr.fragment.replace(/^\/+|\/+$/g,'').split('/');

        // compile a 'base' domain attribute
        uri.attr['base'] = uri.attr.host ? (uri.attr.protocol ?  uri.attr.protocol+'://'+uri.attr.host : uri.attr.host) + (uri.attr.port ? ':'+uri.attr.port : '') : '';

        return uri;
    }

    function getAttrName( elm ) {
        var tn = elm.tagName;
        if ( typeof tn !== 'undefined' ) return tag2attr[tn.toLowerCase()];
        return tn;
    }

    function promote(parent, key) {
        if (parent[key].length === 0) return parent[key] = {};
        var t = {};
        for (var i in parent[key]) t[i] = parent[key][i];
        parent[key] = t;
        return t;
    }

    function parse(parts, parent, key, val) {
        var part = parts.shift();
        if (!part) {
            if (isArray(parent[key])) {
                parent[key].push(val);
            } else if ('object' == typeof parent[key]) {
                parent[key] = val;
            } else if ('undefined' == typeof parent[key]) {
                parent[key] = val;
            } else {
                parent[key] = [parent[key], val];
            }
        } else {
            var obj = parent[key] = parent[key] || [];
            if (']' == part) {
                if (isArray(obj)) {
                    if ('' !== val) obj.push(val);
                } else if ('object' == typeof obj) {
                    obj[keys(obj).length] = val;
                } else {
                    obj = parent[key] = [parent[key], val];
                }
            } else if (~part.indexOf(']')) {
                part = part.substr(0, part.length - 1);
                if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
                parse(parts, obj, part, val);
                // key
            } else {
                if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
                parse(parts, obj, part, val);
            }
        }
    }

    function merge(parent, key, val) {
        if (~key.indexOf(']')) {
            var parts = key.split('[');
            parse(parts, parent, 'base', val);
        } else {
            if (!isint.test(key) && isArray(parent.base)) {
                var t = {};
                for (var k in parent.base) t[k] = parent.base[k];
                parent.base = t;
            }
            if (key !== '') {
                set(parent.base, key, val);
            }
        }
        return parent;
    }

    function parseString(str) {
        return reduce(String(str).split(/&|;/), function(ret, pair) {
            try {
                pair = decodeURIComponent(pair.replace(/\+/g, ' '));
            } catch(e) {
                // ignore
            }
            var eql = pair.indexOf('='),
                brace = lastBraceInKey(pair),
                key = pair.substr(0, brace || eql),
                val = pair.substr(brace || eql, pair.length);

            val = val.substr(val.indexOf('=') + 1, val.length);

            if (key === '') {
                key = pair;
                val = '';
            }

            return merge(ret, key, val);
        }, { base: {} }).base;
    }

    function set(obj, key, val) {
        var v = obj[key];
        if (typeof v === 'undefined') {
            obj[key] = val;
        } else if (isArray(v)) {
            v.push(val);
        } else {
            obj[key] = [v, val];
        }
    }

    function lastBraceInKey(str) {
        var len = str.length,
            brace,
            c;
        for (var i = 0; i < len; ++i) {
            c = str[i];
            if (']' == c) brace = false;
            if ('[' == c) brace = true;
            if ('=' == c && !brace) return i;
        }
    }

    function reduce(obj, accumulator){
        var i = 0,
            l = obj.length >> 0,
            curr = arguments[2];
        while (i < l) {
            if (i in obj) curr = accumulator.call(undefined, curr, obj[i], i, obj);
            ++i;
        }
        return curr;
    }

    function isArray(vArg) {
        return Object.prototype.toString.call(vArg) === "[object Array]";
    }

    function keys(obj) {
        var key_array = [];
        for ( var prop in obj ) {
            if ( obj.hasOwnProperty(prop) ) key_array.push(prop);
        }
        return key_array;
    }

    function purl( url, strictMode ) {
        if ( arguments.length === 1 && url === true ) {
            strictMode = true;
            url = undefined;
        }
        strictMode = strictMode || false;
        url = url || window.location.toString();

        return {

            data : parseUri(url, strictMode),

            // get various attributes from the URI
            attr : function( attr ) {
                attr = aliases[attr] || attr;
                return typeof attr !== 'undefined' ? this.data.attr[attr] : this.data.attr;
            },

            // return query string parameters
            param : function( param ) {
                return typeof param !== 'undefined' ? this.data.param.query[param] : this.data.param.query;
            },

            // return fragment parameters
            fparam : function( param ) {
                return typeof param !== 'undefined' ? this.data.param.fragment[param] : this.data.param.fragment;
            },

            // return path segments
            segment : function( seg ) {
                if ( typeof seg === 'undefined' ) {
                    return this.data.seg.path;
                } else {
                    seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1; // negative segments count from the end
                    return this.data.seg.path[seg];
                }
            },

            // return fragment segments
            fsegment : function( seg ) {
                if ( typeof seg === 'undefined' ) {
                    return this.data.seg.fragment;
                } else {
                    seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1; // negative segments count from the end
                    return this.data.seg.fragment[seg];
                }
            }

        };

    }
    
    purl.jQuery = function($){
        if ($ != null) {
            $.fn.url = function( strictMode ) {
                var url = '';
                if ( this.length ) {
                    url = $(this).attr( getAttrName(this[0]) ) || '';
                }
                return purl( url, strictMode );
            };

            $.url = purl;
        }
    };

    //purl.jQuery(window.jQuery);

    return purl;

});


function mcConnect () {
    "use strict";

    var CLIENT_TO_SERVER = 'client-to-server-cmd';
    var SERVER_TO_CLIENT = 'server-to-client-cmd';

    var CallBackFunctionPull = {};
    var sendStack = [];
    var self      = this;
    var _config   = {
        idError              : 0,
        AjaxInProgress       : false,
        _Timeout             : 20000,
        CRLF                 : "\r\n",
        CR                   : "\r",
        LF                   : "\n"
    };

    self.NoCMD          = false;
    self.CallBackNoCMD  = null;
    self.LeaveCallBacks = false;

    function check(idx, CMD) {
        CallBackFunctionPull[CMD].splice(idx, 1);

        if (CallBackFunctionPull[CMD].length === 0) {
            delete CallBackFunctionPull[CMD];
        }

        return CallBackFunctionPull.hasOwnProperty(CMD) ? CallBackFunctionPull[CMD].length > 0 : false;
    }
    
    function isObject(obj) {
        return Object.prototype.toString.call( obj ) === '[object Object]';
    }

    function requestError(XMLHttpRequest) {
        CallBackFunctionPull = {};
        sendStack = [];

        if (XMLHttpRequest >= 1 || XMLHttpRequest === undefined){
            if (self.OnError) {
                //self.OnError((XMLHttpRequest == '' || !XMLHttpRequest)? "error" : XMLHttpRequest);
                self.OnError("error");
            }

            if (mcConst.socketIO){
                mcConst.socketIO.disconnect();

                // throw new Error("Network error");
            }
        }
    }

    function __SendData(_data){
        mcConst.socketIO.emit(CLIENT_TO_SERVER, _data);
    }

    function RequestSuccess(msg){
        var CR_IDX = msg.length ? msg.indexOf(_config.CR) : -1;

        // console.log(msg);

        if (CR_IDX !== -1) {
            var cmd  = msg.slice(0, CR_IDX);
            var data = msg.slice(CR_IDX + 1, msg.length + 1);

            if (self.NoCMD) {
                self.CallBackNoCMD.apply(msg);
            } else {
                self.RunCallBackFunctionByCMD(cmd, data);
            }
        } else {
            var cmd  = "ping";

            if (self.NoCMD) {
                self.CallBackNoCMD.apply(msg);
            } else {
                self.RunCallBackFunctionByCMD(cmd, msg);
            }
        }
    }

    /**
     * @return {number}
     */
    self.SendDataToServer = function(_data){
        var sendingData = '';
        var prepareData = {};

        if ((!_data) || (_data.length === 0)) {
            if (console && console.warn) {
                console.warn('SendDataToServer: _data is empty or undefined');
            }

            return 1;
        } else {
            switch (typeof _data){
                case "string":
                    sendingData = _data;
                break;

                case "object":
                     prepareData = mcService.Marge({
                        CMD        : '',
                        Data       : '',
                        OnReceive  : null
                    }, _data || {});

                    if (prepareData.Data === ''){
                        console.error('SendDataToServer: "Data" is undefined');

                        return 2;
                    }

                    if (isObject(prepareData.Data)){
                        prepareData.Data = JSON.stringify(prepareData.Data);
                    }

                    sendingData = prepareData.CMD + _config.CR + prepareData.Data;

                    if (prepareData.CMD !== '' && prepareData.OnReceive){
                        self.SetCallBackFunctionByCMD(prepareData.CMD, prepareData.OnReceive);
                    }
                break;
            }

            __SendData(sendingData, prepareData.immediately);
        }
    };

    //=====================================================================
    self.OnError        = undefined;

    //=====================================================================
    self.SetCallBackFunctionByCMD = function(CMD, callback){
        if (callback){
            if (!CallBackFunctionPull.hasOwnProperty(CMD)) {
                CallBackFunctionPull[CMD] = [];
            }

            CallBackFunctionPull[CMD].push(callback);
        }
    };

    //=====================================================================
    self.ClearAllCallBackFunctionByCMD = function(){
        for (var i in CallBackFunctionPull){
            if (CallBackFunctionPull.hasOwnProperty(i)) {
                delete CallBackFunctionPull[i];
            }
        }
    };

    //=====================================================================
    self.RunCallBackFunctionByCMD = function(CMD, MSG){
        var next = CallBackFunctionPull.hasOwnProperty(CMD) ? CallBackFunctionPull[CMD].length > 0 : false;
        var idx  = 0;

        if (!next && CMD != 'OK') {
            console.warn("CMD: \"" + CMD + "\" not have request func");

            requestError();
        }

        while (next){
            if (CallBackFunctionPull[CMD] && CallBackFunctionPull[CMD][idx]){
                if (isObject(CallBackFunctionPull[CMD][idx])) {
                    CallBackFunctionPull[CMD][idx].cb.apply(MSG);

                    if (CallBackFunctionPull[CMD][idx].one){
                        next = check(idx, CMD);

                        continue;
                    }
                } else {
                    CallBackFunctionPull[CMD][idx].apply(MSG);
                }

                if (self.LeaveCallBacks){
                    idx++;
                } else {
                    next = check(idx, CMD);
                }
            } else {
                next = false;
            }
        }
    };

    mcConst.initWS = function (socketIO) {
        socketIO.on(SERVER_TO_CLIENT, RequestSuccess);
        socketIO.on('error',          requestError);
        socketIO.on('connect_failed', requestError);
        socketIO.on('disconnected',   requestError);
        socketIO.on('reconnecting',   requestError);
    }
}

/**
 * Created by Gifer on 10.07.2015.
 */

function mcFileUploading($rootScope){
    "use strict";

    var uploader           = null;
    var errorCallback      = null;
    var successCallback    = null;
    var uploaderOptions    = {};
    var inputFileName      = "__fileSender__";
    var abortUploadingID   = null;
    var abortPrepUploading = null;
    var abortUploadingCB   = null;
    var abortNow           = false;
    var whereWeWork        = "";

    function parseFile(file, callback) {
        var fileSize  = file.size;
        var chunkSize = 512 * 1024; // bytes
        var offset    = 0;
        var chunkReaderBlock = null;
        var onePercent     = file.size / 100;
        var currentPercent = 0;

        var readEventHandler = function (evt) {
            if (!abortNow){
                if (evt.target.error == null) {
                    offset += evt.target.result.byteLength;
                    currentPercent = (offset / onePercent).toFixed();

                    callback(null, evt.target.result, currentPercent); // callback for handling read chunk
                } else {
                    callback(evt.target.error);

                    return;
                }

                if (offset >= fileSize) {
                    callback(null, null, 100);

                    return;
                }

                chunkReaderBlock(fileReader, offset, chunkSize, file);
            }
        };

        chunkReaderBlock = function (_reader, _offset, length, _file) {
            var reader = _reader || new FileReader();
            var blob   = _file.slice(_offset, length + _offset);

            if (!_reader) {
                if (uploaderOptions.onBeforeFileAdd) {
                    uploaderOptions.onBeforeFileAdd(file);
                }

                if (!abortPrepUploading){
                    abortPrepUploading = function () {
                        reader.abort();
                        reader.onload = null;

                        abortPrepUploading = null;
                    }
                }

                reader.onload = readEventHandler;
            }

            reader.readAsArrayBuffer(blob);

            return reader;
        };

        var fileReader = chunkReaderBlock(null, offset, chunkSize, file);
    }

    function _startUpload (info, file) {
        if (!info.Present) { // file not exist - uploading
            uploader.define("upload", mcService.getLocalHostPath($rootScope.isWebClient) + "/uploading/?sid=" + mcConst.SessionID + "&hash=" + info.Hash + whereWeWork);
            uploader.addFile(file);
        } else {
            if (successCallback) {
                successCallback.apply(uploaderOptions, [info, file]);

                if (uploaderOptions.dropFile && uploaderOptions.dropFile.files && uploaderOptions.fileIdx < uploaderOptions.dropFile.files.length){
                    uploaderOptions.fileIdx ++;

                    _goUploadFile.apply(uploaderOptions.dropFile, [uploaderOptions.fileIdx]);
                }
            } else {
                console.trace('No set success callback on file uploading');
            }
        }
    }

    function startUploadAfterCheck(info, file) {
        uploaderOptions.Hash     = info.Hash; // SHA1 хэш файла
        uploaderOptions.Width    = $rootScope.thumbsSize.x; // произвольная ширина уменьшеной копии изображения
        uploaderOptions.Height   = $rootScope.thumbsSize.y; // произвольная высота уменьшенной копии изображения
        uploaderOptions.FileName = info.FileName; // локальное название файла

        if (file.type === "image/png" || file.type === "image/jpeg" || file.type === "image/gif"){
            var tempImg = document.createElement("img");
            tempImg.src = window.URL.createObjectURL(file);

            tempImg.onload = function () {
                uploaderOptions.resolution = tempImg.width + "x" + tempImg.height; // локальное название файла

                tempImg = null;

                _startUpload(info, file);
            };

            tempImg.onerror = function (err) {
                console.warn(err);

                errorCallback && errorCallback(err);

                errorCallback = null;
            };
        } else {
            _startUpload(info, file);
        }
    }

    function electronUploader() {
        $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
            mcConst._CMD_.ce_file_upload_start,
            uploaderOptions.filePath,
            {
                Where    : uploaderOptions.Where,
                ID       : uploaderOptions.ID,
                Type     : uploaderOptions.Type,
                chatType : uploaderOptions.chatType
            }
        ]);

        abortUploadingCB = function () {
            abortUploadingCB = null;

            $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                mcConst._CMD_.ce_file_upload_abort
            ]);
        };
        
        abortPrepUploading = function () {
            abortPrepUploading = null;

            $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                mcConst._CMD_.ce_file_upload_prepare_abort
            ]);
        };
    }

    function checkFileExist(params, cb) {
        $rootScope.SendCMDToServer(
            [ mcConst._CMD_.cs_is_file_exists, mcConst.SessionID ]
            .concat(params || [])
            .concat(cb)
        );
    }

    function _goUploadClipboardImage() {
        var calcSha1   = mcService.SHA1();
        var Hash       = "";
        var fileReader = new FileReader();

        fileReader.onloadend = function() {
            var screenImg = this.result;
            var imageInfo = uploaderOptions.clipboardImage;

            calcSha1.append(screenImg);

            if (!imageInfo.name || (imageInfo.name && imageInfo.name.indexOf("image.") === 0 && !imageInfo.path) ){
                try{
                    imageInfo["name"] = "Screenshot (" + mcConst.UserInfo.Nick + ") " + mcService.formatDate(new Date(), "dd-mm-yyyy hh-nn-ss") + ".png";
                } catch (e) {}
            }

            Hash = calcSha1.end();

            checkFileExist([
                Hash,                         // SHA1 хэш файла
                // mcConst._CMD_.msgType.IMAGE,  // тип файла. 2 - изображение, 4 - обычный файл
                mcConst._CMD_.msgType.IMAGE_NEW,  // тип файла. 2 - изображение, 4 - обычный файл
                imageInfo.name,               // локальное название файла
                uploaderOptions.Where,
                uploaderOptions.ID,
                mcService.fileTimeStamp(),
                imageInfo.size
            ], function (data) {
                startUploadAfterCheck(data, imageInfo);
            });
        };

        fileReader.readAsArrayBuffer(uploaderOptions.clipboardImage);
    }

    function _goUploadFile(id) {
        var fileInput = this;
        var calcSha1  = mcService.SHA1();
        var fileIdx   = mcService.isNumber(id) ? id : 0;
        var file      = fileInput.files[fileIdx];
        var MsgType   = uploaderOptions.Type;

        uploaderOptions.dropFile = { files: mcService.Marge({ length : fileInput.files.length }, fileInput.files) };
        uploaderOptions.fileIdx  = fileIdx;

        if (file){
            uploaderOptions.fileSize = file.size;
            uploaderOptions.lastModifiedDate = file.lastModified || file.lastModifiedDate;

            if (!mcConst.UserInfo.MaxUploadFileSize || mcConst.UserInfo.MaxUploadFileSize > uploaderOptions.fileSize){
                setTimeout(function () {
                    parseFile(file, function (err, data, percent) {
                        if (err) {
                            console.error("Read error: " + err);
                        } else {
                            if (data) {
                                if (uploaderOptions.onPrepareProgress){
                                    uploaderOptions.onPrepareProgress(percent);
                                }

                                calcSha1.append(data);
                            } else {
                                var Hash = calcSha1.end();

                                if (uploaderOptions.onBeforeUpload && percent === 100){
                                    uploaderOptions.onBeforeUpload(Hash);
                                }

                                if (fileInput.value) {
                                    fileInput.value = "";
                                }

                                checkFileExist([
                                    Hash    ,                 // SHA1 хэш файла
                                    MsgType ,                 // тип файла. 2 - изображение, 4 - обычный файл
                                    // uploaderOptions.Type === mcConst._CMD_.msgType.IMAGE
                                    uploaderOptions.Type === mcConst._CMD_.msgType.IMAGE_NEW
                                        ? file.name           // локальное название файла
                                        : file.path || file.name,
                                    uploaderOptions.Where   , // куда вставлять файл (priv, conf, bbs, broadcast, forum, kanban)
                                    uploaderOptions.ID      , // число-идентификатор, для кого отправлять файл:
                                                              // 1, private    - UIN
                                                              // 2, conference - UID
                                                              // 3, forum      - ID топика
                                                              // 4, kanban     - ID таска
                                                              // 5, bbs        - -1
                                                              // 6, broadcast  - -1
                                    mcService.fileTimeStamp(file.lastModified || file.lastModifiedDate),
                                    uploaderOptions.fileSize
                                ], function (data) {
                                    startUploadAfterCheck(data, file);
                                });
                            }
                        }
                    });
                }, 100);
            } else {
                if (uploader) {
                    uploader.destructor();
                }

                if (errorCallback){
                    errorCallback();

                    $rootScope.$broadcast('ErrorMsg', [255, mcService.Error(255), [mcConst.UserInfo.MaxUploadFileSize], function(){ // "255": "администратор сервера ограничил максимальный размер файла для заливки в %s MB",
                        $rootScope.$broadcast('hide' + mcConst.lockInterface, ["all"]); // unlock
                    }]);
                }

                errorCallback   = null;
                successCallback = null;
                uploaderOptions = {};
            }
        }
    }

    function fileFromDialog() {
        var element  = document.getElementById(inputFileName);

        if (!element){
            element = document.createElement('div');

            element.id        = inputFileName;
            element.className = 'hideFileInput';

            document.body.appendChild(element);
        }

        element.innerHTML = '<input type="file">';

        var fileInput = element.firstChild;

        if (fileInput.value && mcService.isIE()) {
            fileInput.parentNode.replaceChild(
                fileInput.cloneNode(true),
                fileInput
            );

            fileInput = document.getElementById(inputFileName).firstChild;
        }

        switch (uploaderOptions.Type) {
            case mcConst._CMD_.msgType.IMAGE_NEW:
            case mcConst._CMD_.msgType.IMAGE:
                fileInput.accept = "image/png, image/gif, image/jpeg, image/jpg";
            break;

            case mcConst._CMD_.msgType.VIDEO:
                fileInput.accept = "video/mp4, video/x-m4v, .mkv, video/mpeg, video/webm, video/*";
            break;

            default:
                fileInput.accept = "";
        }

        if (fileInput.fileEvent){
            fileInput.removeEventListener('change', fileInput.fileEvent);
        }

        fileInput.fileEvent = _goUploadFile;
        fileInput.multiple  = true;

        fileInput.addEventListener('change', fileInput.fileEvent);

        fileInput.click();
    }

    function uploadFile(_errorCallback, _successCallback, _uploaderOptions){
        errorCallback   = _errorCallback;
        successCallback = _successCallback;
        uploaderOptions = _uploaderOptions || {};

        // -- clipboard image --
        if (uploaderOptions.clipboardImage) {
            _goUploadClipboardImage();
        } else

        // -- drop file into chat text --
        if (uploaderOptions.filePath && uploaderOptions.dropFile){
            _goUploadFile.apply(uploaderOptions.dropFile);
        } else

        // -- clipboard file, only for electron app --
        if (uploaderOptions.filePath){
            electronUploader();
        } else
        
        // -- file from dialog window --
        {
            fileFromDialog();
        }
    }

    // ========================================================

    var _msg = window._messages_.mcFileUploader = {
        abortUploading          : 'abortUploading',
        abortPreparingUploading : 'abortPreparingUploading',
        uploadFile              : 'uploadFile'
    };

    $rootScope.$on(_msg.abortUploading, function () {
        abortNow = true;

        if (abortPrepUploading) {
            abortPrepUploading();
        }

        if (uploader && abortUploadingID) {
            uploader.stopUpload(abortUploadingID);
            uploader.destructor();
        }

        if (abortUploadingCB){
            abortUploadingCB();
        }

        uploaderOptions = {};
    });

    $rootScope.$on(_msg.abortPreparingUploading, function () {
        if (abortPrepUploading) {
            abortPrepUploading();
        }

        if (uploader){
            uploader.destructor();
        }
    });

    $rootScope.$on(_msg.uploadFile, function(e,args){
        uploader = $$("uploadAPI");

        if (uploader){
            uploader.destructor();
        }

        abortUploadingID = null;
        abortPrepUploading = null;
        abortUploadingCB = null;
        abortNow = false;

        whereWeWork = $rootScope.hasOwnProperty("GetChatType") ? "&where=" + $rootScope.GetChatType().toLowerCase() : "";

        var opt = args[2] || {};

        if (!opt.uploadProgress && !$$("uploadProgress")){
            webix.ui({ id: "uploadProgress", view: "list", scroll: false, padding: 0, borderless: true, hidden: true, select: false});
        }

        uploader = webix.ui({
            id     : "uploadAPI",
            view   : "uploader",
            multiple: false,
            link   : opt.uploadProgress || "uploadProgress",
            apiOnly: true,
            on     : {
                onAfterFileAdd: function (file) {
                    abortUploadingID = file.id;

                    if (uploaderOptions.onAfterFileAdd){
                        uploaderOptions.onAfterFileAdd(file);
                    }
                },
                onUploadComplete:function(){
                    if (successCallback) {
                        successCallback.apply(uploaderOptions, []);
                    } else {
                        console.trace('No set success callback on file uploading');
                    }

                    if (uploaderOptions.dropFile && uploaderOptions.dropFile.files && uploaderOptions.fileIdx < uploaderOptions.dropFile.files.length){
                        uploaderOptions.fileIdx ++;

                        _goUploadFile.apply(uploaderOptions.dropFile, [uploaderOptions.fileIdx]);
                    } else {
                        errorCallback = null;
                        successCallback = null;
                    }
                },
                onFileUploadError:function(){
                    console.warn("Error during file uploading");

                    if (errorCallback) {
                        errorCallback.apply(null, arguments);
                    }

                    errorCallback = null;
                    successCallback = null;
                }
            }
        });
 
        uploader.$updateProgress = function(id, percent) {
            var item = this.files.getItem(id);
            item.percent = Math.round(percent);

            this.files.updateItem(id);
        };

       uploadFile.apply(null, args);
    });
}
/*!
 * Bowser - a browser detector
 * https://github.com/ded/bowser
 * MIT License | (c) Dustin Diaz 2014
 */

var browserDetect = (function () {
    var t = true;

    function detect(ua) {

        function getFirstMatch(regex) {
            var match = ua.match(regex);
            return (match && match.length > 1 && match[1]) || '';
        }

        function getSecondMatch(regex) {
            var match = ua.match(regex);
            return (match && match.length > 1 && match[2]) || '';
        }

        var iosdevice = getFirstMatch(/(ipod|iphone|ipad)/i).toLowerCase()
            , likeAndroid = /like android/i.test(ua)
            , android = !likeAndroid && /android/i.test(ua)
            , edgeVersion = getFirstMatch(/edge\/(\d+(\.\d+)?)/i)
            , versionIdentifier = getFirstMatch(/version\/(\d+(\.\d+)?)/i)
            , tablet = /tablet/i.test(ua)
            , mobile = !tablet && /[^-]mobi/i.test(ua)
            , result;

        if (/opera|opr/i.test(ua)) {
            result = {
                name: 'Opera'
                , opera: t
                , version: versionIdentifier || getFirstMatch(/(?:opera|opr)[\s\/](\d+(\.\d+)?)/i)
            }
        }
        else if (/yabrowser/i.test(ua)) {
            result = {
                name: 'Yandex Browser'
                , yandexbrowser: t
                , version: versionIdentifier || getFirstMatch(/(?:yabrowser)[\s\/](\d+(\.\d+)?)/i)
            }
        }
        else if (/(windows phone|lumia)/i.test(ua)) {

            result = {
                name: 'Windows Phone'
                , windowsphone: t
            };
            if (edgeVersion) {
                result.msedge = t;
                result.version = edgeVersion
            }
            else {
                result.msie = t;
                result.version = getFirstMatch(/iemobile\/(\d+(\.\d+)?)/i)
            }
        }
        else if (/msie|trident/i.test(ua)) {
            result = {
                name: 'Internet Explorer'
                , msie: t
                , version: getFirstMatch(/(?:msie |rv:)(\d+(\.\d+)?)/i)
            }
        }
        else if (/chrome.+? edge/i.test(ua)) {
            result = {
                name: 'Microsoft Edge'
                , msedge: t
                , version: edgeVersion
            }
        }
        else if (/chrome|crios|crmo/i.test(ua)) {
            result = {
                name: 'Chrome'
                , chrome: t
                , version: getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)
            }
        }
        else if (iosdevice) {
            result = {
                name : iosdevice == 'iphone' ? 'iPhone' : iosdevice == 'ipad' ? 'iPad' : 'iPod'
            };
            // WTF: version is not part of user agent in web apps
            if (versionIdentifier) {
                result.version = versionIdentifier
            }
        }
        else if (/sailfish/i.test(ua)) {
            result = {
                name: 'Sailfish'
                , sailfish: t
                , version: getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i)
            }
        }
        else if (/seamonkey\//i.test(ua)) {
            result = {
                name: 'SeaMonkey'
                , seamonkey: t
                , version: getFirstMatch(/seamonkey\/(\d+(\.\d+)?)/i)
            }
        }
        else if (/firefox|iceweasel/i.test(ua)) {
            result = {
                name: 'Firefox'
                , firefox: t
                , version: getFirstMatch(/(?:firefox|iceweasel)[ \/](\d+(\.\d+)?)/i)
            };
            if (/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(ua)) {
                result.firefoxos = t
            }
        }
        else if (/silk/i.test(ua)) {
            result =  {
                name: 'Amazon Silk'
                , silk: t
                , version : getFirstMatch(/silk\/(\d+(\.\d+)?)/i)
            }
        }
        else if (android) {
            result = {
                name: 'Android'
                , version: versionIdentifier
            }
        }
        else if (/phantom/i.test(ua)) {
            result = {
                name: 'PhantomJS'
                , phantom: t
                , version: getFirstMatch(/phantomjs\/(\d+(\.\d+)?)/i)
            }
        }
        else if (/blackberry|\bbb\d+/i.test(ua) || /rim\stablet/i.test(ua)) {
            result = {
                name: 'BlackBerry'
                , blackberry: t
                , version: versionIdentifier || getFirstMatch(/blackberry[\d]+\/(\d+(\.\d+)?)/i)
            }
        }
        else if (/(web|hpw)os/i.test(ua)) {
            result = {
                name: 'WebOS'
                , webos: t
                , version: versionIdentifier || getFirstMatch(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i)
            };
            /touchpad\//i.test(ua) && (result.touchpad = t)
        }
        else if (/bada/i.test(ua)) {
            result = {
                name: 'Bada'
                , bada: t
                , version: getFirstMatch(/dolfin\/(\d+(\.\d+)?)/i)
            };
        }
        else if (/tizen/i.test(ua)) {
            result = {
                name: 'Tizen'
                , tizen: t
                , version: getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i) || versionIdentifier
            };
        }
        else if (/safari/i.test(ua)) {
            result = {
                name: 'Safari'
                , safari: t
                , version: versionIdentifier
            }
        }
        else {
            result = {
                name: getFirstMatch(/^(.*)\/(.*) /),
                version: getSecondMatch(/^(.*)\/(.*) /)
            };
        }

        // set webkit or gecko flag for browsers based on these engines
        if (!result.msedge && /(apple)?webkit/i.test(ua)) {
            result.name = result.name || "Webkit";
            result.webkit = t;
            if (!result.version && versionIdentifier) {
                result.version = versionIdentifier
            }
        } else if (!result.opera && /gecko\//i.test(ua)) {
            result.name = result.name || "Gecko";
            result.gecko = t;
            result.version = result.version || getFirstMatch(/gecko\/(\d+(\.\d+)?)/i)
        }

        // set OS flags for platforms that have multiple browsers
        if (!result.msedge && (android || result.silk)) {
            result.android = t
        } else if (iosdevice) {
            result[iosdevice] = t;
            result.ios = t
        }

        // OS version extraction
        var osVersion = '';
        if (result.windowsphone) {
            osVersion = getFirstMatch(/(windows phone|lumia) (?:os)?\s?(\d+(\.\d+)*)/i);
        } else if (iosdevice) {
            osVersion = getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i);
            osVersion = osVersion.replace(/[_\s]/g, '.');
        } else if (android) {
            osVersion = getFirstMatch(/android[ \/-](\d+(\.\d+)*)/i);
        } else if (result.webos) {
            osVersion = getFirstMatch(/(?:web|hpw)os\/(\d+(\.\d+)*)/i);
        } else if (result.blackberry) {
            osVersion = getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i);
        } else if (result.bada) {
            osVersion = getFirstMatch(/bada\/(\d+(\.\d+)*)/i);
        } else if (result.tizen) {
            osVersion = getFirstMatch(/tizen[\/\s](\d+(\.\d+)*)/i);
        }
        if (osVersion) {
            result.osversion = osVersion;
        }

        // device type extraction
        var osMajorVersion = osVersion.split('.')[0];
        if (tablet || iosdevice == 'ipad' || (android && (osMajorVersion == 3 || (osMajorVersion == 4 && !mobile))) || result.silk) {
            result.tablet = t
        } else if (mobile || iosdevice == 'iphone' || iosdevice == 'ipod' || android || result.blackberry || result.webos || result.bada || result.windowsphone) {
            result.mobile = t
        }

        // Graded Browser Support
        // http://developer.yahoo.com/yui/articles/gbs
        if (result.msedge ||
            (result.msie && result.version >= 10) ||
            (result.yandexbrowser && result.version >= 15) ||
            (result.chrome && result.version >= 20) ||
            (result.firefox && result.version >= 20.0) ||
            (result.safari && result.version >= 6) ||
            (result.opera && result.version >= 10.0) ||
            (result.ios && result.osversion && result.osversion.split(".")[0] >= 6) ||
            (result.blackberry && result.version >= 10.1)
            ) {
            result.a = t;
        }
        else if ((result.msie && result.version < 10) ||
            (result.chrome && result.version < 20) ||
            (result.firefox && result.version < 20.0) ||
            (result.safari && result.version < 6) ||
            (result.opera && result.version < 10.0) ||
            (result.ios && result.osversion && result.osversion.split(".")[0] < 6)
            ) {
            result.c = t
        } else result.x = t;

        return result
    }

    var _browser = detect(typeof navigator !== 'undefined' ? navigator.userAgent : '');

    _browser.test = function (browserList) {
        for (var i = 0; i < browserList.length; ++i) {
            var browserItem = browserList[i];
            if (typeof browserItem=== 'string') {
                if (browserItem in _browser) {
                    return true;
                }
            }
        }
        return false;
    };

    /*
     * Set our detect method to the main browser object so we can
     * reuse it to test other user agents.
     * This is needed to implement future tests.
     */
    _browser._detect = detect;

    return _browser
})();

 /**
 * Created by Gifer on 20.02.2017.
 */

 (function (root) {

     // Store setTimeout reference so promise-polyfill will be unaffected by
     // other code modifying setTimeout (like sinon.useFakeTimers())
     var setTimeoutFunc = setTimeout;

     function noop() {}

     // Polyfill for Function.prototype.bind
     function bind(fn, thisArg) {
         return function () {
             fn.apply(thisArg, arguments);
         };
     }

     function Promise(fn) {
         if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
         if (typeof fn !== 'function') throw new TypeError('not a function');
         this._state = 0;
         this._handled = false;
         this._value = undefined;
         this._deferreds = [];

         doResolve(fn, this);
     }

     function handle(self, deferred) {
         while (self._state === 3) {
             self = self._value;
         }
         if (self._state === 0) {
             self._deferreds.push(deferred);
             return;
         }
         self._handled = true;
         Promise._immediateFn(function () {
             var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
             if (cb === null) {
                 (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
                 return;
             }
             var ret;
             try {
                 ret = cb(self._value);
             } catch (e) {
                 reject(deferred.promise, e);
                 return;
             }
             resolve(deferred.promise, ret);
         });
     }

     function resolve(self, newValue) {
         try {
             // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
             if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
             if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
                 var then = newValue.then;
                 if (newValue instanceof Promise) {
                     self._state = 3;
                     self._value = newValue;
                     finale(self);
                     return;
                 } else if (typeof then === 'function') {
                     doResolve(bind(then, newValue), self);
                     return;
                 }
             }
             self._state = 1;
             self._value = newValue;
             finale(self);
         } catch (e) {
             reject(self, e);
         }
     }

     function reject(self, newValue) {
         self._state = 2;
         self._value = newValue;
         finale(self);
     }

     function finale(self) {
         if (self._state === 2 && self._deferreds.length === 0) {
             Promise._immediateFn(function() {
                 if (!self._handled) {
                     Promise._unhandledRejectionFn(self._value);
                 }
             });
         }

         for (var i = 0, len = self._deferreds.length; i < len; i++) {
             handle(self, self._deferreds[i]);
         }
         self._deferreds = null;
     }

     function Handler(onFulfilled, onRejected, promise) {
         this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
         this.onRejected = typeof onRejected === 'function' ? onRejected : null;
         this.promise = promise;
     }

     /**
      * Take a potentially misbehaving resolver function and make sure
      * onFulfilled and onRejected are only called once.
      *
      * Makes no guarantees about asynchrony.
      */
     function doResolve(fn, self) {
         var done = false;
         try {
             fn(function (value) {
                 if (done) return;
                 done = true;
                 resolve(self, value);
             }, function (reason) {
                 if (done) return;
                 done = true;
                 reject(self, reason);
             });
         } catch (ex) {
             if (done) return;
             done = true;
             reject(self, ex);
         }
     }

     Promise.prototype['catch'] = function (onRejected) {
         return this.then(null, onRejected);
     };

     Promise.prototype.then = function (onFulfilled, onRejected) {
         var prom = new (this.constructor)(noop);

         handle(this, new Handler(onFulfilled, onRejected, prom));
         return prom;
     };

     Promise.all = function (arr) {
         var args = Array.prototype.slice.call(arr);

         return new Promise(function (resolve, reject) {
             if (args.length === 0) return resolve([]);
             var remaining = args.length;

             function res(i, val) {
                 try {
                     if (val && (typeof val === 'object' || typeof val === 'function')) {
                         var then = val.then;
                         if (typeof then === 'function') {
                             then.call(val, function (val) {
                                 res(i, val);
                             }, reject);
                             return;
                         }
                     }
                     args[i] = val;
                     if (--remaining === 0) {
                         resolve(args);
                     }
                 } catch (ex) {
                     reject(ex);
                 }
             }

             for (var i = 0; i < args.length; i++) {
                 res(i, args[i]);
             }
         });
     };

     Promise.resolve = function (value) {
         if (value && typeof value === 'object' && value.constructor === Promise) {
             return value;
         }

         return new Promise(function (resolve) {
             resolve(value);
         });
     };

     Promise.reject = function (value) {
         return new Promise(function (resolve, reject) {
             reject(value);
         });
     };

     Promise.race = function (values) {
         return new Promise(function (resolve, reject) {
             for (var i = 0, len = values.length; i < len; i++) {
                 values[i].then(resolve, reject);
             }
         });
     };

     // Use polyfill for setImmediate for performance gains
     Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) ||
         function (fn) {
             setTimeoutFunc(fn, 0);
         };

     Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
         if (typeof console !== 'undefined' && console) {
             console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
         }
     };

     /**
      * Set the immediate function to execute callbacks
      * @param fn {function} Function to execute
      * @deprecated
      */
     Promise._setImmediateFn = function _setImmediateFn(fn) {
         Promise._immediateFn = fn;
     };

     /**
      * Change the function to execute on unhandled rejection
      * @param {function} fn Function to execute on unhandled rejection
      * @deprecated
      */
     Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
         Promise._unhandledRejectionFn = fn;
     };

     if (typeof module !== 'undefined' && module.exports) {
         module.exports = Promise;
     } else if (!root.Promise) {
         root.Promise = Promise;
     }

 })(this);

 (function () {
     function _on(event, cb) {
         if (Object.prototype.toString.call( event ) !== "[object String]"){
             console.error("[events.on] Event name must be string");
         } else
         if (eventList.hasOwnProperty(event)){
             //console.error("[events.on] Event %s already exist", event);

             eventList[event].push(cb);
         } else {
             eventList[event] = [cb];
         }
     }

     function _off(event) {
         if (eventList.hasOwnProperty(event)){
             delete eventList[event];
         } else {
             console.warn("[events.off] Unknown event: " + event);
         }
     }

     function _broadcast(event, args, nolog) {
         var res = null;

         // if (Object.prototype.toString.call( event ) !== "[object String]"){
         //     console.warn("[events.broadcast] Event name must be string");
         // } else
         if (!eventList.hasOwnProperty(event)){
             if (!nolog) {
                 console.warn("[events.broadcast] Unknown event: " + event);
             }
         } else {
             eventList[event].forEach(function (cb) {
                 res = cb.apply(null, [null, args || []]);
             });

             if (res !== void(0)) {
                 return res;
             }
         }
     }

     function _http(url, callback, post, data) {
         var res;

         try{
             res = new Promise(function (resolve, reject) {
                 var _request = new XMLHttpRequest();

                 _request.open(post ? "POST" : "GET", url, post);

                 _request.onreadystatechange = function(){
                     if (_request.readyState === 4) {
                         if (_request.status === 200) {
                             if (callback) {
                                 callback(_request.responseText);
                             }

                             resolve(_request.responseText);
                         } else {
                             reject(_request.statusText);
                         }
                     }
                 };

                 _request.send(post ? data : null);
             });

             res.success = res.then;
         } catch (e){
             console.error(e.message);
         }

         return res;
     }

     function changeLocation() { // info
         if ($location){
             $location.data = purl().data;

             $rootScope.$broadcast("onChangeHrefPath", [$location], true);
         }
     }

     var units      = [];
     var moduleList = {}; // Loaded Modules
     var eventList  = {}; // Event Emitter
     var $rootScope = {   // Global Scope
         $on         : _on,
         $off        : _off,
         $emit       : _broadcast,
         $broadcast  : _broadcast,
         isWebClient : location.protocol !== "file:"
     };
     var $location  = null;

     try {
         $location = purl();
     } catch (e){
         console.error(e.message);
     }

     window.mcComponents = {
         start: function () {
             units.forEach(function (unit) {
                 var selectScope = moduleList;

                 selectScope.$rootScope = $rootScope;
                 selectScope.$http      = _http;
                 selectScope.$location  = $location;
                 selectScope.$scope     = { // Local Scope
                     $on       : _on,
                     $off      : _off,
                     $emit     : _broadcast,
                     $broadcast: _broadcast
                 };

                 try {
                     moduleList[unit.module] = new (Function.prototype.bind.apply(window[unit.module], [null].concat(unit.args.map(function (item) {
                         return selectScope[item];
                     }))));
                 } catch (e){
                     console.error(e);
                 }

                 selectScope = null;
             });

             return this;
         },
         units: function (items) {
             units = Object.prototype.toString.call( items ) === "[object Array]" ? items : [];

             return this;
         },
         _broadcast: _broadcast
     };

     (function(eventInfo){
         window[eventInfo[0]](eventInfo[1] + "popstate", changeLocation, false);
     })(window.addEventListener ? ["addEventListener", ''] : ["attachEvent", "on"]);
 })();

 var _messages_ = {};


function mcUploadProgress() {
    webix.type(webix.ui.list, {
        name: "myUploader",
        template: function (data) {
            var current_size = mcService.formatFileSize((data.size / 100) * data.percent);

            return mcService.myReplaceFormated(
                "<div class='uploader_overall'>" +
                "   <div class='uploader_status'>" +
                "       <div class='uploader_progress' style='width:#{width}'></div>" +
                "       <div class='uploader_message'>#{txt}</div>" +
                "   </div>" +
                "</div>",
                {
                    name: data.name,
                    width: data.percent + "%",
                    txt: current_size + (data.sizetext ? (" / " + data.sizetext + " ") : " ") + data.percent + "%"
                }
            );
        },
        status: function (f) {
            var messages = {
                server: f.sizetext + " - Done!",
                error: "Error",
                client: "Ready",
                transfer: f.sizetext
            };
            return messages[f.status];

        },
        height: 35
    });
}
function McLoadingImages($rootScope) {
    "use strict";

    var deferredLoadImages = new McDeferredLoadImages();
    var loadingImgList = {};
    var self = this;

    function getImgFromLoadList(hash) {
        return loadingImgList[hash] && loadingImgList[hash].idx && loadingImgList[hash].idx.length ? loadingImgList[hash].idx.shift() : null;
    }
    function getImgNameFromLoadList(hash) {
        return loadingImgList[hash] ? loadingImgList[hash].name : null;
    }
    function getTaskIdLoadList(hash) {
        return loadingImgList[hash] && loadingImgList[hash].ID && loadingImgList[hash].ID.length ? loadingImgList[hash].ID.shift() : null;
    }
    function runLoad(queue, idx, done, onLoadImg){
        self.loadThumbs([queue[idx], getImgNameFromLoadList(queue[idx])], onLoadImg, function () {
            idx ++;

            if (idx < queue.length){
                runLoad(queue, idx, done, onLoadImg);
            } else
            if (done) {
                done();
            }
        });
    }

    self.loadAllThumbs = function(done, onLoadImg){
        if (!deferredLoadImages){
            deferredLoadImages = new McDeferredLoadImages();
        }

        var queue = [];

        Object.keys(loadingImgList).forEach(function (hash) {
            loadingImgList[hash].idx.forEach(function () {
                queue.push(hash);
            });
        });

        runLoad(queue, 0, done, onLoadImg);
    };

    self.loadThumbs = function (info, cb, done) {
        var fileInfo = mcService.isObject(info) ? info : {
            Hash    : info[0],
            FileName: info[1],
            idx     : info[2],
            tmp     : info[3]
        };

        if (fileInfo.FileName){
            $rootScope.SendCMDToServer([
                mcConst._CMD_.cs_get_image_thumbs,
                mcConst.SessionID,

                fileInfo.Hash,                //info[0] , //.Hash     , // SHA1 хэш файла
                $rootScope.thumbsSize.x     , // произвольная ширина уменьшеной копии изображения
                $rootScope.thumbsSize.y     , // произвольная высота уменьшенной копии изображения
                fileInfo.FileName,            //info[1] , // FileName , // локальное название файла
                mcService.getCurrentChantTypeForFiles($rootScope.GetChatType()),

                function (requestData) {
                    var idx   = getImgFromLoadList(requestData.Hash) || fileInfo.idx;
                    var where = "/" + $rootScope.GetChatType().toLowerCase() + "/";

                    if (idx) {
                        var _link   = document.getElementById('link_' + requestData.Hash + idx);
                        var _imgOK  = document.getElementById('img_' + requestData.Hash + idx);
                        var fileName= mcService.extractFileName(requestData.FileName);
                        var hasError= false;

                        if (where !== "/kanban/" || where !== "/forum/") {
                            where = "/";
                        }

                        var lnkHref = mcService.getLocalHostPath($rootScope.isWebClient) + "/" + mcConst.pathAliases.AliasFiles + where + requestData.Hash + "/" + (requestData.Exists ? encodeURI(fileName) : mcConst.imagesPath.nofile);

                        if (requestData.Body && requestData.Body.length) {
                            if (!(_imgOK && _link)) return;
                            
                            _imgOK.src       = "data:image/jpeg;base64," + requestData.Body;
                            _imgOK.className = "finger";
                            _link.href       = lnkHref;

                            cb && cb();
                        } else {
                            var imgSrc  = requestData.Exists && ($rootScope.isWebClient || mcConst.ClientSettings.SysEventsShowImagesInChat)
                                ? mcService.getLocalHostPath($rootScope.isWebClient) + "/" + mcConst.pathAliases.AliasFiles + where + requestData.Hash + '-' + $rootScope.thumbsSize.xy + '.jpg'
                                : mcConst.imagesPath.nofile;

                            if (_imgOK && _imgOK.src && _link && _link.href){
                                _imgOK.src = imgSrc;
                                _link.href = lnkHref;

                                _imgOK.onload = function () {
                                    if (!hasError) {
                                        _imgOK.className = "";
                                    }

                                    if (cb) {
                                        cb(this.height, requestData, getTaskIdLoadList(requestData.Hash), imgSrc, lnkHref);
                                    }
                                };

                                _imgOK.onerror = _imgOK.onabort = function (err) {
                                    console.warn(err);

                                    hasError = true;

                                    _imgOK.src = mcConst.imagesPath.noImage;
                                    _imgOK.className = "noImageClick";

                                    try {
                                        this.parentElement.style.height  = "auto";
                                        this.parentElement.style.width   = "auto";
                                        this.parentElement.style.display = "inline";
                                    } catch (e) {}
                                };
                            } else

                            if (deferredLoadImages) {
                                deferredLoadImages.addImage(idx, requestData.Hash, imgSrc, lnkHref);
                            }
                        }
                    } else
                    if (requestData.Exists){
                        var _imgTemp = document.getElementById('img_temp_' + requestData.Hash + "_" + fileInfo.tmp);

                        if (_imgTemp){
                            where = "/kanban/";

                            _imgTemp.src = requestData.Exists && ($rootScope.isWebClient || mcConst.ClientSettings.SysEventsShowImagesInChat)
                                ? mcService.getLocalHostPath($rootScope.isWebClient) + "/" + mcConst.pathAliases.AliasFiles + where + requestData.Hash + '-' + $rootScope.thumbsSize.xy + '.jpg'
                                : mcConst.imagesPath.nofile;

                            _imgTemp.onload = function () {
                                cb && cb();
                            };

                            _imgTemp.onerror = _imgTemp.onabort = function (err) {
                                console.warn(err);

                                hasError = true;

                                _imgTemp.src = mcConst.imagesPath.noImage;

                                try {
                                    this.parentElement.style.height  = "auto";
                                    this.parentElement.style.width   = "auto";
                                    this.parentElement.style.display = "inline";
                                } catch (e) {}
                            };
                        }
                    } else
                    if (!requestData.Exists){
                        var _imgNO  = mcService.getByCssClass('img_temp_' + requestData.Hash);

                        if (_imgNO && _imgNO.length) {
                            for (var i = 0; i < _imgNO.length; i++){
                                _imgNO[i].src = mcConst.imagesPath.noImage;

                                _imgNO[i].parentElement.onclick = function (e) {
                                    e.preventDefault();
                                };
                            }
                        }
                    }

                    if (done){
                        done(requestData);
                    }
                }
            ]);
        }
    };

    self.loadAllImages = function (cb) {
        if (!deferredLoadImages){
            deferredLoadImages = new McDeferredLoadImages();
        }

        deferredLoadImages.load(cb);
    };

    self.addImageToLoad = function (hash, idx, name, taskID) {
        if (!loadingImgList.hasOwnProperty(hash)) {
            loadingImgList[hash] = {
                idx : [idx],
                name: name
            };

            if (taskID) {
                loadingImgList[hash].ID = [taskID];
            }
        } else {
            if (mcService.inArray(idx, loadingImgList[hash].idx) === -1){
                loadingImgList[hash].idx.push(idx);

                if (taskID){
                    if (loadingImgList[hash].ID){
                        loadingImgList[hash].ID.push(taskID);
                    } else {
                        loadingImgList[hash].ID = [taskID];
                    }
                }
            }
        }
    };

    self.destroy = function () {
        deferredLoadImages = null;
    };

    return self;
}

// ==============   DEFERRED LOAD IMAGES  =======================

function McDeferredLoadImages() {
    var items = {};

    this.addImage = function (idx, hash, src, href) {
        if (!items.hasOwnProperty(idx)) {
            items[idx] = [hash, src, href];
        }
    };

    this.load = function (cb) {
        var count      = Object.keys(items).length;
        var currentIdx = 0;

        Object.keys(items).forEach(function (idx) {
            var hash    = items[idx][0];
            var imgSrc  = items[idx][1];
            var lnkHref = items[idx][2];
            var hasError= false;

            var _link   = document.getElementById('link_' + hash + idx);
            var _img    = document.getElementById('img_' + hash + idx);

            if (_img && _img.src && _link && _link.href){
                _img.src   = imgSrc;
                _link.href = lnkHref;

                console.log('loadAll: ' + imgSrc);

                _img.onload = function () {
                    currentIdx ++;

                    if (!hasError) {
                        _img.className = "";
                    }

                    delete items[idx];

                    if (currentIdx === count && cb){
                        cb();
                    }
                };

                _img.onerror =_img.onabort = function (err) {
                    console.warn(err);

                    hasError = true;

                    _img.src = mcConst.imagesPath.noImage;
                    _img.className = "noImageClick";

                    try {
                        this.parentElement.style.height  = "auto";
                        this.parentElement.style.width   = "auto";
                        this.parentElement.style.display = "inline";
                    } catch (e) {}
                }
            }
        });
    };
}

function McVisualisation($rootScope, convUIN) {
    "use strict";

    var _self          = this;
    var _uin           = null;
    var rnd            = mcService.RandomHash(5);
    var items          = {};
    var vFileUploader  = null;
    var vDirectReceive = null;
    var downloadConstants = {
        NOPROCESS       : 0,
        UPLOAD          : 1,
        UPLOADBUFFER    : 2,
        DOWNLOAD        : 3,
        PREPARING       : 4,
        UPLOADIRECT     : 5,
        WAITANSWER      : 6,
        DOWNLOADPREP    : 7,
        DOWNLOADDIRECT  : 8,
        DOWNLOADFTP     : 9
    };
    var currentDuState = downloadConstants.NOPROCESS;
    var rayID = "";

    function __abortTransfer(allOK) {
        switch (currentDuState){
            case downloadConstants.DOWNLOADFTP:
                $rootScope.$broadcast('sendCMDToElectron', [
                    mcConst._CMD_.ce_ftp_abort
                ]);
                break;

            case downloadConstants.DOWNLOAD:
                $rootScope.$broadcast('sendCMDToElectron', [
                    mcConst._CMD_.ce_file_download_abort
                ]);
                break;

            case downloadConstants.WAITANSWER:
                if (vFileUploader && vFileUploader.getUINsend()){
                    $rootScope.$broadcast('sendCMDToElectron', [
                        mcConst._CMD_.ce_file_direct_upload_abort
                    ]);
                }

                $rootScope.SendCMDToServer([
                    mcConst._CMD_.cs_files_transfer_request_abort,
                    mcConst.SessionID,
                    $rootScope.GetChatID()
                ]);
                break;

            case downloadConstants.UPLOADIRECT:
                $rootScope.$broadcast('sendCMDToElectron', [
                    mcConst._CMD_.ce_file_direct_upload_abort
                ]);

                $rootScope.SendCMDToServer([
                    mcConst._CMD_.cs_files_transfer_abort,
                    mcConst.SessionID,
                    $rootScope.GetChatID()
                ]);
                break;

            case downloadConstants.UPLOADBUFFER:
            case downloadConstants.UPLOAD:
                $rootScope.$broadcast('abortUploading');
                break;

            case downloadConstants.PREPARING:
                $rootScope.$broadcast('abortPreparingUploading');
                break;

            case downloadConstants.DOWNLOADPREP:
                vDirectReceive.reject(true);
                break;

            case downloadConstants.DOWNLOADDIRECT:
                $rootScope.$broadcast('sendCMDToElectron', [
                    mcConst._CMD_.ce_remove_ftp_user,
                    _uin
                ]);

                if (!allOK) {
                    $rootScope.SendCMDToServer([
                        mcConst._CMD_.cs_files_transfer_abort,
                        mcConst.SessionID,
                        $rootScope.GetChatID()
                    ]);
                }
                break;
        }

        _self._hideProgressInfo();
    }

    function _acceptFilesReceive() {

    }

    // ===========================================================

    this._getViews  = function (cancelText) {
        return {
            progressWrapper:
                { id: "progressWrapper" + rnd, css: "noBGColor byCenter borderTopBottom", height: 30, padding: 0, borderless: true, hidden: true, cols: [
                    { id: "universalProgress" + rnd, borderless: true, css: "noBGColor byCenter", hidden: true,
                        template: function () {
                            var html = "&nbsp;";

                            if (this.progress){
                                var data = this.progress;

                                html = mcService.myReplaceFormated(
                                    "<div class='uploader_overall'>" +
                                    "   <div class='uploader_status'>" +
                                    "       <div class='uploader_progress' style='width:#{width}'></div>" +
                                    "       <div class='uploader_message'>#{txt}</div>" +
                                    "   </div>"+
                                    "</div>",
                                    {
                                        width   : data.percent !== undefined ? data.percent + "%" : 0,
                                        txt     : data.text + (data.size ? "/" + data.size + " " : " ") + (data.percent !== undefined ? data.percent + "%" : "")
                                    }
                                );
                            }

                            return html;
                        }
                    },

                    { id: "uploadProgress" + rnd, view: "list", type: "myUploader", css: "uploader_nopadding", scroll: false, padding: 0, borderless: true, hidden: true, select: false},
                    { view: "button", value: cancelText || mcLang(33), width: 105, click: function () { // "33" :"Отмена",
                        __abortTransfer();

                        _self.onAbortButton && _self.onAbortButton();
                    }}
                ]},

            downloadFilesQuestion:
                { id: "downloadFilesQuestion" + rnd, css: "borderTopBottom", borderless: true, height: 60, hidden: true, rows: [
                    { id: "downloadFileList", view: "template", template: "&nbsp;", padding: 5, borderless: true, css: "whiteBg monospaceAll", scroll: 'y'},

                    { cols: [
                        {},
                        { view: "button", value: mcLang(592), maxWidth: 130, click: _acceptFilesReceive}, // "592":"Принять",
                        { width: 10},
                        { view: "button", value: mcLang(33),  maxWidth: 130, click: __abortTransfer, type: "danger"}, // "33" :"Отмена",
                        {}
                    ]}
                ]}
        }
    };

    this.getUploadProgress = function () {
        return "uploadProgress" + rnd;
    };

    this._initViews = function () {
        items.progressWrapper       = $$("progressWrapper" + rnd);
        items.universalProgress     = $$("universalProgress" + rnd);
        items.uploadProgress        = $$("uploadProgress" + rnd);
    };

    this._showDownloadFileList = function (list) {
        _uin = list.UIN;

        $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
            mcConst._CMD_.ce_get_user_folder,

            list.UIN,
            list.DisplayName,

            function (path) {
                vDirectReceive.setFilesList(list, path);

                if ($rootScope.customUserOptions.getOption(_uin, "autoReceive")) {
                    setTimeout(function () {
                        vDirectReceive.accept(path);
                    }, 100);
                } else {
                    // $rootScope.$broadcast("unPinTool");
                    $rootScope.$broadcast("selectTool", [mcConst.dataModels.ReceiveFiles]);
                    $rootScope.$broadcast(window._messages_.receiveFiles.setReceiveFilesInfo, [vDirectReceive.getInfo()]);
                }
            }
        ]);
    };

    this._hideProgressInfo = function (allOk){
        if (currentDuState && !allOk && window._messages_.chatFrame){
            // if (!allOk && window._messages_.chatFrame) {
            $rootScope.$broadcast(window._messages_.chatFrame.scrollDownChat);
            // }
        }

        if (allOk){
            switch (currentDuState){
                case downloadConstants.DOWNLOAD:
                case downloadConstants.DOWNLOADFTP:
                    webix.message(mcLang(575)); // "575":"Файл скачан",
                break;

                case  downloadConstants.UPLOAD:
                    webix.message(mcLang(576)); // "576":"Файл загружен на сервер",
                break;
            }
        }

        // currentDuStat e = downloadConstants.NOPROCESS;

        if (items.progressWrapper) {
            _self._showProgressInfo(downloadConstants.NOPROCESS, true);
        }
    };

    this._showProgressInfo = function (state, clear){
        currentDuState = state;

        if (currentDuState !== downloadConstants.NOPROCESS && currentDuState !== downloadConstants.DOWNLOADPREP) {
            items.progressWrapper.show();
        } else {
            items.progressWrapper.hide();
        }

        if (clear){
            items.uploadProgress.clearAll();
        }

        switch (currentDuState){
            case downloadConstants.PREPARING:
            case downloadConstants.DOWNLOAD:
            case downloadConstants.DOWNLOADFTP:
            case downloadConstants.UPLOADBUFFER:
            case downloadConstants.UPLOADIRECT:
            case downloadConstants.DOWNLOADDIRECT:
                _self._updateProgress({
                    percent : 0,
                    text    : ' ... '
                });

                items.universalProgress.show();
                items.uploadProgress.hide();
            break;

            case downloadConstants.WAITANSWER:
                _self._updateProgress({
                    text: mcLang(583) // "583":"Ожидаем согласие на прием файлов...",
                });

                items.universalProgress.show();
                items.uploadProgress.hide();
            break;

            case downloadConstants.UPLOAD:
                items.universalProgress.hide();
                items.uploadProgress.show();
            break;
        }
    };

    this._updateProgress = function (chunk) {
        if (currentDuState){
            items.universalProgress.define('progress', chunk);
            items.universalProgress.refresh();
        }
    };

    this._onPrepareProgress = function (percent) {
        _self._updateProgress({
            percent: percent,
            text   : 'SHA1 calculating'
        });
    };

    this.abortTransfer = __abortTransfer;

    this.setFileUploader = function (_fileUploader) {
        vFileUploader = _fileUploader;
    };

    this.setDirectReceive = function (_directReceive) {
        vDirectReceive = _directReceive;
    };

    this.onAbortButton = null;

    this.active = function (_rayID) {
        // if (!rayID || _rayID === rayID) {
        //     rayID = _rayID;
        //     return true;
        // } else {
        //     return false;
        // }
        return !!items.progressWrapper;
    };

    this.getState = function () {
        return currentDuState;
    };

    this.constants = downloadConstants;
}
function McUploaderViaServer($rootScope, uploadOptions) {
    "use strict";

    var _uin           = null;
    var _filesInfo     = null;
    var fVisualisation = null;
    var Self           = this;
    var UPLOAD         = 1;
    var PREPARING      = 4;
    var pastView       = null;

    function __sendFileMessage(info, allok) {
        // uploadOptions.done(
        //     info.chatType,
        //     info.ID,
        //     info.Hash + mcConst.CRLF + info.FileName + mcConst.CRLF + mcService.fileTimeStamp(info.lastModifiedDate) +
        //                 mcConst.CRLF + info.fileSize + mcConst.CRLF + (info.resolution ? info.resolution : "-") +
        //                 (info.cText ? (mcConst.CRLF + info.cText) : ""), // excel = cText is not empty
        //     info.Type
        // );

        uploadOptions.done(
            info.chatType,
            info.ID,
            JSON.stringify({
                Hash        : info.Hash,
                FileName    : info.FileName,
                UTCWriteTime: mcService.fileTimeStamp(info.lastModifiedDate),
                Size        : info.fileSize,
                Resolution  : info.resolution ? info.resolution : "-",
                Desc        : info.cText ? info.cText : ""
            }),
            info.Type
        );

        fVisualisation._hideProgressInfo(allok);
    }
    function __setUIN(uin, _type) {
        if (_type === $rootScope.chatAliases.UIN) {
            _uin = uin;
        }
    }
    function __getUIN() {
        return _uin;
    }
    function __clearUIN() {
        _uin = null;
        _filesInfo = null;

        fVisualisation.abortTransfer();
    }
    function __onBeforeFileAdd() {
        if (uploadOptions.onBeforeFileAdd && uploadOptions.onBeforeFileAdd.apply(null, arguments)){
            __clearUIN();
        } else {
            fVisualisation._showProgressInfo(PREPARING, true);
        }
    }
    function __getFilesInfo() {
        return _filesInfo;
    }
    function __onAfterFileAdd() {
        if (uploadOptions.onAfterFileAdd && uploadOptions.onAfterFileAdd.apply(null, arguments)){
            __clearUIN();
        } else {
            fVisualisation._showProgressInfo(UPLOAD);
        }
    }
    function __onBeforeUpload(hash) { // todo: upload list

    }
    function __doUploadImage(_ID, type, image, resolution, cText) {
        __setUIN(_ID, type);

        $rootScope.$broadcast(window._messages_.mcFileUploader.uploadFile, [
            function error() { // info
                fVisualisation._hideProgressInfo();

                __clearUIN();

                webix.alert('Error file uploading!');
            },
            function success() { // file
                var info = this;

                __clearUIN();

                // info.Type       = cText === void(0) ? mcConst._CMD_.msgType.IMAGE_NEW : mcConst._CMD_.msgType.CLIPBOARD_EXCEL;
                info.Type       = mcConst._CMD_.msgType.IMAGE_NEW; // todo: добавить обработку CLIPBOARD_EXCEL для нового типа сообщений
                info.chatType   = type;
                info.ID         = _ID;
                info.fileSize   = image.size;
                info.resolution = resolution;
                info.cText      = cText;

                if (info.FileName === "image.png"){
                    info.FileName = "image_" + (new Date()).getTime() + ".png";
                }

                __sendFileMessage(info);
            }, {
                onBeforeFileAdd  : __onBeforeFileAdd,
                onPrepareProgress: fVisualisation._onPrepareProgress,
                onAfterFileAdd   : __onAfterFileAdd,
                uploadProgress   : fVisualisation.getUploadProgress(),

                clipboardImage   : image, // Получаем файл как Blob (бинарные данные)
                Where            : mcService.getCurrentChantTypeForFiles($rootScope.GetChatType()),
                ID               : _ID
            }
        ]);
    }
    function __onPastFile(e) {
        if (!fVisualisation.getState() && (
            ((mcService.checkRights(mcConst._CMD_.RS.QTxtConfSendFiles) || mcService.checkRights(mcConst._CMD_.RS.QSendImages))     && $rootScope.GetChatType() === $rootScope.chatAliases.UID) ||
            ((mcService.checkRights(mcConst._CMD_.RS.QPrivInsertFiles)  || mcService.checkRights(mcConst._CMD_.RS.QImagesPrivates)) && $rootScope.GetChatType() === $rootScope.chatAliases.UIN)
        )) {
            var type    = $rootScope.GetChatType();
            var cText   = "";
            var imgInfo = [];

            // if (e.clipboardData.items.length) {
            //     for (var i = e.clipboardData.items.length - 1; i >= 0; i--) {
            //         if (e.clipboardData.items[i].kind === "file" && e.clipboardData.items[i].type === "image/png") {
            //             e.preventDefault();
            //
            //             Promise.all(Array.prototype.map.call(e.clipboardData.items, function (item){
            //                 if (item.kind === "string" && item.type === "text/plain"){
            //                     return item.getAsString(function (txt){
            //                         cText = txt
            //                     });
            //                 } else
            //                 if (item.kind === "file" && (item.type === "image/png" || item.type === "image/jpg" || item.type === "image/jpeg") ){
            //                     return new Promise(function (resolve){
            //                         var image   = item.getAsFile();
            //                         var _ID     = $rootScope.GetChatID();
            //                         var tempImg = document.createElement("img");
            //
            //                         tempImg.src = window.URL.createObjectURL(image);
            //
            //                         tempImg.onload = function () {
            //                             var resolution = tempImg.width + "x" + tempImg.height;
            //                             tempImg.src = "";
            //                             tempImg     = null;
            //
            //                             if (Self.pastImagePreview) {
            //                                 Self.pastImagePreview(image, function () { // comment
            //                                     imgInfo = [_ID, type, image, resolution];
            //
            //                                     resolve();
            //                                 });
            //                             } else {
            //                                 imgInfo = [_ID, type, image, resolution];
            //
            //                                 resolve();
            //                             }
            //                         };
            //                     })
            //                 }
            //             }))
            //             .then(function (){
            //                 __doUploadImage.apply(null, imgInfo.concat(cText ? [cText] : []));
            //             })
            //             .catch(function (err){
            //                 console.error(err.messsage);
            //             });
            //
            //             break;
            //         } else
            //         if (e.clipboardData.items[i].kind === "file" && e.clipboardData.files[i].size) {
            //
            //         }
            //     }
            // } else

            if (e.clipboardData.files.length) {
                e.preventDefault();

                // если в буфере 1 файл, но несколько итемов - то это скрин из экселя со скопированным тестом таблички
                if (e.clipboardData.files.length === 1 && 
                    (e.clipboardData.files[0].type === "image/png" || e.clipboardData.files[0].type === "image/jpg" || e.clipboardData.files[0].type === "image/jpeg")) {

                    Promise.all(Array.prototype.map.call(e.clipboardData.items, function (item){
                        if (item.kind === "string" && item.type === "text/plain"){
                            return item.getAsString(function (txt){
                                cText = txt
                            });
                        } else
                        if (item.kind === "file" && (item.type === "image/png" || item.type === "image/jpg" || item.type === "image/jpeg") ){
                            return new Promise(function (resolve){
                                var image   = item.getAsFile();
                                var _ID     = $rootScope.GetChatID();
                                var tempImg = document.createElement("img");

                                tempImg.src = window.URL.createObjectURL(image);

                                tempImg.onload = function () {
                                    var resolution = tempImg.width + "x" + tempImg.height;
                                    tempImg.src = "";
                                    tempImg     = null;

                                    if (Self.pastImagePreview) {
                                        Self.pastImagePreview(image, function () { // comment
                                            imgInfo = [_ID, type, image, resolution];

                                            resolve();
                                        });
                                    } else {
                                        imgInfo = [_ID, type, image, resolution];

                                        resolve();
                                    }
                                };
                            })
                        }
                    }))
                    .then(function (){
                        __doUploadImage.apply(null, imgInfo.concat(cText ? [cText] : []));
                    })
                    .catch(function (err){
                        console.error(err.messsage);
                    });
                } else
                if (e.clipboardData.files.length >= 1) {
                    Self._uploadFile(mcConst._CMD_.msgType.FILE_NEW, true, e.clipboardData);
                }
            }/* else

            if (e.clipboardData.files.length >= 0) {
                if (fVisualisation.getState()) {
                    webix.message(mcLang(601)); // "601":"Подождите окончания приема/передачи файлов",
                } else

                if (!$rootScope.isWebClient) {
                    if (Self.pastFilePreview){
                        Self.pastFilePreview(e.clipboardData.files, function () {
                            $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                                mcConst._CMD_.ce_get_clipboard_files_list
                            ]);
                        })
                    } else {
                        $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                            mcConst._CMD_.ce_get_clipboard_files_list
                        ]);
                    }
                }
            }*/
        }
    }

    this.getFilesInfo     = __getFilesInfo;
    this.getUINsend       = __getUIN;
    this.setUINsend       = __setUIN;
    this.abortUploading   = __clearUIN;
    this.sendFileMessage  = __sendFileMessage;
    this.pastImagePreview = null;
    this.pastFilePreview  = null;

    this.removePasteView  = function () {
        if (pastView) {
            pastView.removeEventListener("paste", __onPastFile);
        }
    };

    this._onPasteItemFromClipboard = function (_view){
        pastView = _view;

        pastView.addEventListener("paste", __onPastFile);
    };

    this.setVisualisation = function (_visualisation) {
        fVisualisation = _visualisation;
    };

    this._uploadFile      = function (fileType, filePath, dropFileInfo){
        function _DD_upload(_fileType, _filePath, _dropFileInfo, _currentID, _chatType) {
            __setUIN(_currentID, _chatType);

            $rootScope.$broadcast(window._messages_.mcFileUploader.uploadFile, [
                function error(){
                    fVisualisation._hideProgressInfo();

                    __clearUIN();

                    webix.alert('Error file uploading!');
                },
                function success(){
                    var info = this;

                    if (info.fileIdx === info.dropFile.files.length){
                        __clearUIN();
                    }

                    switch (_fileType){
                        case mcConst._CMD_.msgType.IMAGE_NEW:
                        // case mcConst._CMD_.msgType.IMAGE:
                            __sendFileMessage(info);
                        break;

                        default:
                            __sendFileMessage(info, true);
                    }
                }, {
                    onBeforeFileAdd     : __onBeforeFileAdd,
                    onAfterFileAdd      : __onAfterFileAdd,
                    onBeforeUpload      : __onBeforeUpload,
                    onPrepareProgress   : fVisualisation._onPrepareProgress,
                    uploadProgress      : fVisualisation.getUploadProgress(),

                    filePath: _filePath,
                    chatType: _chatType,
                    dropFile: _dropFileInfo,

                    Type  : _fileType,
                    Where : mcService.getCurrentChantTypeForFiles($rootScope.GetChatType()),
                    ID    : _currentID
                }
            ]);

            if (!$rootScope.isWebClient) {
                $rootScope.$broadcast(window._messages_.mcStatistics.setStatValue, [_dropFileInfo ? "n_file_drag" : "n_file", 1, false]); // false - увеличить значение на "1"
            }
        }

        var currentID = $rootScope.GetChatID();
        var chatType  = $rootScope.GetChatType();

        if (dropFileInfo && (mcConst.ClientSettings && mcConst.ClientSettings.PreviewDropFile)) {
            if (dropFileInfo.items.length === 1 && dropFileInfo.items[0].kind === "file" && Self.pastImagePreview &&
                (
                    dropFileInfo.items[0].type === "image/png" ||
                    dropFileInfo.items[0].type === "image/jpg" ||
                    dropFileInfo.items[0].type === "image/jpeg"||
                    dropFileInfo.items[0].type === "image/gif"
                ))
            {
                var image      = dropFileInfo.items[0].getAsFile();
                var resolution = "";
                var tempImg    = document.createElement("img");

                tempImg.src    = window.URL.createObjectURL(image);
                tempImg.onload = function () {
                    resolution   = tempImg.width + "x" + tempImg.height;
                    tempImg.src  = "";
                    tempImg      = null;

                    if (Self.pastImagePreview){
                        Self.pastImagePreview(image, function () {
                            __doUploadImage(currentID, chatType, image, resolution);
                        });
                    } else {
                        __doUploadImage(currentID, chatType, image, resolution);
                    }
                }
            } else
            if (Self.pastFilePreview) {
                var list = [];

                for (var i=0; i < dropFileInfo.files.length; i++){
                    list.push(dropFileInfo.files[i].name);
                }

                var dd = { files: mcService.Marge({ length : dropFileInfo.files.length }, dropFileInfo.files) };

                Self.pastFilePreview(list, function () {
                    _DD_upload(fileType, filePath, dd, currentID, chatType);
                });
            } else {
                _DD_upload(fileType, filePath, dropFileInfo, currentID, chatType);
            }
        } else {
            _DD_upload(fileType, filePath, dropFileInfo, currentID, chatType);
        }
    };
}
function DownloadUploadManager($rootScope, __opt) {
    "use strict";

    var options = mcService.Marge({
        name    : "",
        upload  : {
            enable          : false,
            done            : function () {},
            onBeforeFileAdd : function () {return false},
            onAfterFileAdd  : function () {return false}
        },
        download: {
            enable: false,
            ftp_download: false
        },
        direct  : {
            enable: false
        },
        images  : {
            enable: false
        }

    }, __opt);

    this._name = __opt.name;

    var duManager     = this;
    var direct        = null;
    var images        = null;
    var fileUploader  = null;
    var visualisation = null;
    var canStart      = false;
    var isStarted     = false;
    var uinDirect     = 0;

    // --- Draw Progress Bar =================================================

    visualisation = new McVisualisation($rootScope);

    // --- Functions =========================================================

    function _downloadUrl(url, fileInfo, auto) {
        if (!isStarted && !$rootScope.isWebClient && visualisation.getState() === visualisation.constants.NOPROCESS) {
            $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                mcConst._CMD_.ce_file_download_url,
                url,
                fileInfo,
                auto
            ]);
        }
    }

    function _openDownloadUrl(url, fileInfo) {
        if (!isStarted && !$rootScope.isWebClient && visualisation.getState() === visualisation.constants.NOPROCESS) {
            $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                mcConst._CMD_.ce_file_open_or_download,
                url,
                fileInfo
            ]);
        }
    }

    function _openInFolder(hash, uin, path) {
        $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
            mcConst._CMD_.ce_file_open_in_folder,
            hash,
            uin,
            path
        ]);
    }

    function _openFolder(path) {
        $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
            mcConst._CMD_.ce_file_open_folder,
            path
        ]);
    }

    function _sendFilesToUser(fromBuffer) {
        var uin = $rootScope.GetChatID();

        if (!isStarted && $rootScope.GetChatType() === $rootScope.chatAliases.UIN && uin && visualisation.getState() === visualisation.constants.NOPROCESS){
            $rootScope.$broadcast(window._messages_.dialogsList.getUserState, [$rootScope.GetChatID(), function (state) {
                if (mcConst.states.offline === state){
                    webix.alert(mcLang(585)); // "585":"Передавать файлы напрямую можно только пользователям онлайн!",
                } else {
                    uinDirect = uin;

                    if (!$rootScope.isWebClient && !isStarted) {
                        $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                            mcConst._CMD_.ce_file_direct_upload_prepare,
                            uinDirect,
                            fromBuffer
                        ]);
                    }
                }
            }]);
        }
    }

    function _ftpDownload(path, fileName, size) {
        if (!isStarted && !$rootScope.isWebClient) {
            $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                mcConst._CMD_.ce_ftp_download,
                path,
                fileName, 
                size
            ]);
        }
    }

    function _destroy(){
        if (images) images.destroy();

        direct        = null;
        images        = null;
        visualisation = null;
        fileUploader  = null;
        isStarted     = false;

        uinDirect = 0;

        for (var i in window._messages_.downloadUpload){
            if (i !== '---') $rootScope.$off(window._messages_.downloadUpload[i]);
        }
    }

    // --- Direct Receive ====================================================

    if (options.direct.enable) {
        direct = new McDirectReceive($rootScope);

        direct.setVisualisation(visualisation);
        direct.onAcceptReceive = function(uin){
            uinDirect = uin;
            isStarted = true;
        };

        visualisation.setDirectReceive(direct);

        duManager.sendFilesToUser         = _sendFilesToUser;
    }

    // --- Loading Images ====================================================

    if (options.images.enable){
        images = new McLoadingImages($rootScope);

        duManager.addImageToLoad          = images.addImageToLoad;
        duManager.loadThumbs              = images.loadThumbs;
        duManager.loadAllImages           = images.loadAllImages;
        duManager.loadAllThumbs           = images.loadAllThumbs;
    }

    // --- Upload Via Server And Direct ======================================

    if (options.upload.enable){
        fileUploader = new McUploaderViaServer($rootScope, options.upload);

        fileUploader .setVisualisation(visualisation);
        visualisation.setFileUploader (fileUploader);
        visualisation.onAbortButton = function(){
            setTimeout(function () { // todo: передалать весь модуль нах
                isStarted = false;
            }, 50);
        };

        duManager.onPasteItemFromClipboard= function () {
            // if (visualisation.getState() === visualisation.constants.NOPROCESS){
                fileUploader._onPasteItemFromClipboard.apply(null, arguments);
            // }
        };
        // duManager.removePasteView         = fileUploader.
        duManager.setPastePreviewWindow   = function (cb) {
            fileUploader.pastImagePreview = cb || null;
        };
        duManager.setPastePreviewWindowFile= function (cb) {
            fileUploader.pastFilePreview = cb || null;
        };
        duManager.uploadFile              = function() {
            if (!isStarted && visualisation.getState() === visualisation.constants.NOPROCESS) {
                fileUploader._uploadFile.apply(null, arguments);
            }
        };
        duManager.abortUploading          = fileUploader.abortUploading;
        duManager.removePasteView         = fileUploader.removePasteView;
    }

    // =======================================================================

    if (options.download.enable){
        duManager.downloadUrl             = _downloadUrl;
        duManager.openDownloadUrl         = _openDownloadUrl;
        duManager.openInFolder            = _openInFolder;
        duManager.openFolder              = _openFolder;
    }

    // =======================================================================

    duManager.getViews    = visualisation._getViews;
    duManager.initViews   = visualisation._initViews;
    duManager.isActive    = visualisation.active;
    duManager.ftpDownload = _ftpDownload;
    duManager.destroy     = _destroy;
    duManager.canStart    = function () { canStart = true;  };
    duManager.noStart     = function () { canStart = false; };

    // ========================================================================
    // ========================================================================

    var _msg = window._messages_.downloadUpload = {
        changeStateForUser      : 'changeStateForUser',
        pastFilesFromClipboard  : 'pastFilesFromClipboard',
        acceptReceiveFiles      : 'acceptReceiveFiles',
        rejectReceiveFiles      : 'rejectReceiveFiles',
        sendFilesToUser         : 'sendFilesToUser',

        download_ftp_file       : 'download_ftp_file',

        on_file_download_progress      : 'on_file_download_progress',
        on_file_download_start         : 'on_file_download_start',
        on_file_download_complete      : 'on_file_download_complete',
        on_file_download_aborted       : 'on_file_download_aborted',
        on_file_download_file_not_found: 'on_file_download_file_not_found',

        on_file_upload_prepare_progress: 'on_file_upload_prepare_progress',
        on_file_upload_start           : 'on_file_upload_start',
        on_file_upload_prepare_start   : 'on_file_upload_prepare_start',
        on_file_upload_aborted         : 'on_file_upload_aborted',
        on_file_upload_progress        : 'on_file_upload_progress',
        on_file_upload_complete        : 'on_file_upload_complete',
        on_file_upload_error           : 'on_file_upload_error',

        on_file_check_exist            : 'on_file_check_exist',
        on_file_drop                   : 'on_file_drop',
        on_files_transfer_progress     : 'on_files_transfer_progress',

        on_file_request_transfer       : 'on_file_request_transfer',

        on_file_direct_upload_wait_accept         : 'on_file_direct_upload_wait_accept',
        on_file_direct_upload_accept              : 'on_file_direct_upload_accept',
        on_file_direct_upload_complete            : 'on_file_direct_upload_complete',
        on_file_direct_upload_no_files            : 'on_file_direct_upload_no_files',
        on_file_direct_download_complete          : 'on_file_direct_download_complete',
        on_file_direct_download_file_received     : 'on_file_direct_download_file_received',
        on_file_direct_receive_client_disconnected: 'on_file_direct_receive_client_disconnected',

        duManagerNoStart : 'duManagerNoStart',


        // openDownloadFolder  : 'openDownloadFolder',

        // on_file_direct_upload_file_sended   : 'on_file_direct_upload_file_sended',

        '---':'---'
    };

    // ========================================================================

    $rootScope.$on(_msg.duManagerNoStart, function () {
        duManager.noStart();
    });

    $rootScope.$on(_msg.on_files_transfer_progress, function (e, args) {
        if (isStarted && visualisation.active()) {
            visualisation._updateProgress.apply(visualisation, args);
        }
    });

    // --- download ===========================================================

    if (options.download.enable){
        if (options.download.ftp_download) {
            $rootScope.$on(_msg.download_ftp_file, function (e, args) {
                if (!isStarted && visualisation.getState() === visualisation.constants.NOPROCESS) {
                    _ftpDownload.apply(null, args);
                }
            });
        }

        $rootScope.$on(_msg.on_file_download_start, function (e, args) {
            if (!isStarted && visualisation.active.apply(null, args) && visualisation.getState() === visualisation.constants.NOPROCESS) {
                visualisation._showProgressInfo(args[1] === "ftp" ? visualisation.constants.DOWNLOADFTP : visualisation.constants.DOWNLOAD, false);
                var _savePath = args[2];

                if (_savePath) {
                    $rootScope.Storage.options.save({
                        currentDownloadFolder : _savePath
                    });

                    mcConst.currentDownloadFolder = _savePath;
                }

                // console.log(options.name);

                isStarted = true;
            }
        });

        $rootScope.$on(_msg.on_file_download_progress, function (e, args) {
            var rayID = args[0].rayID;

            if (isStarted && visualisation.active(rayID)) {
                visualisation._updateProgress.apply(visualisation, args);
            }
        });

        $rootScope.$on(_msg.on_file_download_aborted, function (e, args) {
            if (isStarted && visualisation.active.apply(null, args)) {
                visualisation._hideProgressInfo();

                setTimeout(function () { // todo: передалать весь модуль нах
                    isStarted = false;
                }, 50);
            }
        });

        $rootScope.$on(_msg.on_file_download_file_not_found, function (e, args) {
            if (isStarted && visualisation.active.apply(null, args)) {
                visualisation._hideProgressInfo();

                webix.alert("File not found on the server!");
                
                setTimeout(function () { // todo: передалать весь модуль нах
                    isStarted = false;
                }, 50);
            }
        });

        $rootScope.$on(_msg.on_file_download_complete, function (e, args) {
            if (isStarted && visualisation.active.apply(null, args) && visualisation.getState() !== visualisation.constants.NOPROCESS) {
                isStarted = false;

                visualisation._hideProgressInfo(true);

                // setTimeout(function () { // todo: передалать весь модуль нах
                // }, 50);
            }
        });
    }

    // --- upload =============================================================

    if (fileUploader || direct){
        $rootScope.$on(_msg.pastFilesFromClipboard, function (e, args) {
            if (canStart && !isStarted && visualisation.active()) {
                var files = args[0];

                if (files && files.length) { //  && $rootScope.GetChatType() === $rootScope.chatAliases.UIN
                    if (fileUploader.pastFilePreview){
                        fileUploader.pastFilePreview(files, function () {
                            fileUploader._uploadFile(mcConst._CMD_.msgType.FILE_NEW, files, false);

                            if (!$rootScope.isWebClient) $rootScope.$broadcast(window._messages_.mcStatistics.setStatValue, ["n_file_ctrlc", 1, false]); // false - увеличить значение на "1"
                        });
                    } else {
                        fileUploader._uploadFile(mcConst._CMD_.msgType.FILE_NEW, files, false);

                        if (!$rootScope.isWebClient) $rootScope.$broadcast(window._messages_.mcStatistics.setStatValue, ["n_file_ctrlc", 1, false]); // false - увеличить значение на "1"
                    }
                }
            }
        });

        $rootScope.$on(_msg.on_file_check_exist, function (e, args) {
            if (canStart && visualisation.active()) {
                // var id   = args[0].id;
                var info = args[0].info;

                if (info) {
                    $rootScope.SendCMDToServer([
                        mcConst._CMD_.cs_is_file_exists,
                        mcConst.SessionID,

                        info.Hash,     // SHA1 хэш файла
                        info.Type,     // тип файла. 2 - изображение, 4 - обычный файл
                        info.FileName, //
                        info.Where,    // куда вставлять файл (priv, conf, bbs, broadcast, forum, kanban)
                        info.ID,       // число-идентификатор, для кого отправлять файл:
                        mcService.fileTimeStamp(info.lastModifiedDate),
                        info.fileSize,

                        function (data) {
                            if (data.Present) {
                                fileUploader.sendFileMessage(info);

                                isStarted = false;

                                $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                                    mcConst._CMD_.ce_file_check_exist,
                                    true,
                                    {
                                        Where    : info.Where,
                                        ID       : info.ID,
                                        Type     : info.Type,
                                        chatType : info.chatType
                                    }
                                ]);
                            } else {
                                $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                                    mcConst._CMD_.ce_file_check_exist,
                                    false,
                                    {
                                        Where    : info.Where,
                                        ID       : info.ID,
                                        Type     : info.Type,
                                        chatType : info.chatType
                                    }
                                ]);
                            }
                        }
                    ]);
                }
            }
        });

        $rootScope.$on(_msg.on_file_drop, function (e, args) {
            var haveRights =
                (
                    (mcService.checkRights(mcConst._CMD_.RS.QTxtConfSendFiles) || mcService.checkRights(mcConst._CMD_.RS.QSendImages)) &&
                    $rootScope.GetChatType() === $rootScope.chatAliases.UID
                ) ||
                (
                    (mcService.checkRights(mcConst._CMD_.RS.QPrivInsertFiles)  || mcService.checkRights(mcConst._CMD_.RS.QImagesPrivates)) &&
                    $rootScope.GetChatType() === $rootScope.chatAliases.UIN
                );

            if (visualisation.active() && haveRights) {
                var dataTransfer = args[0];

                !isStarted && canStart && fileUploader._uploadFile(mcConst._CMD_.msgType.FILE_NEW, true, dataTransfer);
            } else
            if (!haveRights) {
                if ($rootScope.GetChatType() === $rootScope.chatAliases.UID) {
                    $rootScope.$broadcast('ErrorMsg', [222, mcService.Error(222)]); //"222": "у вас недостаточно прав для вставки файлов в конференции",
                } else {
                    $rootScope.$broadcast('ErrorMsg', [220, mcService.Error(220)]); //"220": "у вас недостаточно прав для вставки файлов в приват",
                }
            }
        });

        $rootScope.$on(_msg.on_file_upload_start, function () {
            if (isStarted && canStart && visualisation.active()) {
                visualisation._showProgressInfo(visualisation.constants.UPLOADBUFFER);
            }
        });

        $rootScope.$on(_msg.on_file_upload_error, function (e, args) {
            var err = args[0];

            console.warn(err);

            setTimeout(function () { // todo: передалать весь модуль нах
                isStarted = false;
            }, 50);

            visualisation._hideProgressInfo();

            $rootScope.$broadcast('ErrorMsg', [err.code, err.msg]);
        });

        $rootScope.$on(_msg.on_file_upload_progress, function (e, args) {
            if (isStarted && visualisation.active()) {
                visualisation._updateProgress.apply(visualisation, args);
            }
        });

        $rootScope.$on(_msg.on_file_upload_aborted, function (e, args) {
            if (isStarted && visualisation.active()) {
                var uin = args[0];

                if (fileUploader) {
                    fileUploader.abortUploading();
                } else {
                    visualisation.abortTransfer(uin);

                    // isStarted = false;
                    // uinDirect = 0;
                }

                if (uin) {
                    $rootScope.$broadcast(window._messages_.chatFrame.addCustomMSG, [{
                        UIN: uin,
                        Msg: mcLang(584) // "584":"Передача файлов прервана",
                    }]);

                    webix.message(mcLang(584));

                    $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                        mcConst._CMD_.ce_file_direct_upload_abort
                    ]);
                }

                // setTimeout(function () { // todo: передалать весь модуль нах
                    isStarted = false;
                // }, 50);
            }
        });

        $rootScope.$on(_msg.on_file_upload_complete, function (e, args) {
            if (isStarted && visualisation.active()) {
                var info = args[0];

                visualisation._hideProgressInfo(true);

                fileUploader.sendFileMessage(info);

                setTimeout(function () { // todo: передалать весь модуль нах
                    isStarted = false;
                }, 50);
            }
        });

        $rootScope.$on(_msg.on_file_upload_prepare_start, function () {
            if (!isStarted && canStart && visualisation.active()){
                visualisation._showProgressInfo(visualisation.constants.PREPARING);

                isStarted = true;
            }
        });

        $rootScope.$on(_msg.on_file_upload_prepare_progress, function (e, args) {
            if (isStarted && visualisation.active()) {
                var percent = args[0].percent;

                visualisation._onPrepareProgress(percent);
            }
        });
    }

    // --- direct =============================================================

    if (direct) {
        // --- upload ---

        $rootScope.$on(_msg.changeStateForUser, function (e, args) {
            if (isStarted && visualisation.active() && (visualisation.getState() === visualisation.constants.UPLOADIRECT || visualisation.getState() === visualisation.constants.WAITANSWER)) {
                var uin   = args[0];
                var state = args[1];

                if (direct.getCurrentReceiveUIN() == uin && state == mcConst.states.offline) {
                    direct.clear();

                    $rootScope.$broadcast(window._messages_.chatWrapper.hideTool, [mcConst.dataModels.ReceiveFiles]);
                } else
                if (uinDirect == uin) {
                    visualisation._hideProgressInfo();
                }

                isStarted = false;
            }
        });

        $rootScope.$on(_msg.on_file_direct_upload_wait_accept, function (e, args) {
            if (!isStarted && visualisation.active()) {
                visualisation._showProgressInfo(visualisation.constants.WAITANSWER);
            }
        });

        $rootScope.$on(_msg.on_file_direct_upload_accept, function (e, args) {
            if (!isStarted && visualisation.active() && visualisation.getState() === visualisation.constants.WAITANSWER) {
                var info = args[0];

                visualisation._showProgressInfo(visualisation.constants.UPLOADIRECT);

                isStarted = true;

                $rootScope.$broadcast(window._messages_.clientData.sendCMDToElectron, [
                    mcConst._CMD_.ce_file_direct_upload_start,
                    info
                ]);

                if (!$rootScope.isWebClient) {
                    $rootScope.$broadcast(window._messages_.mcStatistics.setStatValue, ["n_file_direct", 1, false]); // false - увеличить значение на "1"
                }
            }
        });

        $rootScope.$on(_msg.on_file_direct_upload_complete, function (e, args) {
            if (isStarted && visualisation.active() && uinDirect) {
                var files = args[0];

                $rootScope.$broadcast(window._messages_.chatFrame.addCustomMSG, [{
                    MsgType: mcConst._CMD_.msgType.SHOW_UPLOADED_FILES,
                    UIN: uinDirect,
                    FilesList: files.FilesList,
                    Count: files.FilesCount,
                    Size: files.FilesSize,
                    Path: files.filesPath
                }]);

                visualisation._hideProgressInfo(true);

                isStarted = false;
            }
        });

        $rootScope.$on(_msg.sendFilesToUser, function (e, args) {
            if (!isStarted && visualisation.active()) {
                _sendFilesToUser.apply(null, args);
            }
        });

        // --- download ---

        $rootScope.$on(_msg.acceptReceiveFiles, function (e, args) {
            if (!isStarted && visualisation.active()){
                direct.accept.apply(direct, args); // isStarted = true in onAccept

                if (!$rootScope.isWebClient) {
                    $rootScope.$broadcast(window._messages_.mcStatistics.setStatValue, ["n_frcvd", 1, false]); // false - увеличить значение на "1"
                }
            }
        });

        $rootScope.$on(_msg.rejectReceiveFiles, function () {
            if (isStarted && visualisation.active()) {
                direct.reject();

                isStarted = false;
            }
        });

        $rootScope.$on(_msg.on_file_request_transfer, function (e, args) {
            if (!isStarted && visualisation.active()) {
                visualisation._showProgressInfo(visualisation.constants.DOWNLOADPREP, true);

                visualisation._showDownloadFileList.apply(visualisation, args);
            }
        });

        $rootScope.$on(_msg.on_file_direct_upload_no_files, function () {
            if (!isStarted && visualisation.active()) {
                visualisation._hideProgressInfo(true);

                isStarted = false
            }
        });

        $rootScope.$on(_msg.on_file_direct_download_complete, function () {
            if (visualisation.active()) {
                var files = direct.getInfo();

                $rootScope.$broadcast(window._messages_.chatFrame.addCustomMSG, [{
                    MsgType: mcConst._CMD_.msgType.SHOW_RECEIVED_FILES,
                    UIN: files.UIN,
                    FilesList: files.FilesList,
                    Count: files.Count,
                    Size: files.Size,
                    Path: files.DownloadPath
                }]);

                visualisation._hideProgressInfo(true);

                isStarted = false;
                uinDirect = 0;
            }
        });

        $rootScope.$on(_msg.on_file_direct_download_file_received, function (e, args) {
            if (isStarted && visualisation.active()) {
                direct.addReceiveFile.apply(null, args);
            }
        });

        $rootScope.$on(_msg.on_file_direct_receive_client_disconnected, function (e, args) {
            if (isStarted && visualisation.active() && direct.getCurrentReceiveUIN() === args[0]) {
                visualisation.abortTransfer(args[1]);

                isStarted = false;
                uinDirect = 0;
            }
        });
    }
}

function servicesController(_api) {
    'use strict';

    var hexCodes = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"];
    var imgIndex = 1;

    var serviceList = {
        ModalTimer : null,
        noNotify   : false,
        notify     : null,
        idNotify   : 0,

        /**
         * @return {string}
         */
        insertIco : function(ico, css, attr){
            if (serviceList.isObject(attr)){
                var tt = [];

                Object.keys(attr).forEach(function (item) {
                    tt.push(item + "='" + attr[item] + "'")
                });

                attr = tt.join(' ');
            }

            return "<span class='webix_icon " + ico + " " + (css || "fa-lg") + "' " + (attr || "") + "></span>"
        },

        /**
         * @return {boolean}
         */
        checkRights: function(id, rightsList){
            var res = false;

            id = parseInt(id);

            if (!isNaN(id)){
                res = rightsList ? rightsList[id - 1] == "1" : mcConst.MyRightsSet[id - 1];
            }

            return res;
        },

        /**
         * @return {boolean}
         */
        isPlainObject : function(obj) {
            var res = false;

            if (!obj || !serviceList.isObject(obj) || obj.nodeType || obj.setInterval){
                res = false;
            } else
            if (obj.constructor && !obj['constructor'] && !obj.constructor['isPrototypeOf']){
                res = false;
            } else {
                var key;
                
                for ( key in obj ) {}

                res = key === undefined || !!obj[key];
            }

            return res;
        },

        /**
         * @return {object}
         */
        oldMarge : function () {
            var options, name, src, copy, copyIsArray, clone,
                target = arguments[0] || {},
                i = 1,
                length = arguments.length,
                deep = false;

            // Handle a deep copy situation
            if ( typeof target === "boolean" ) {
                deep = target;
                target = arguments[1] || {};
                // skip the boolean and the target
                i = 2;
            }

            // Handle case when target is a string or something (possible in deep copy)
            if ( typeof target !== "object" && typeof target !== "function") {
                target = {};
            }

            // extend jQuery itself if only one argument is passed
            // if ( length === i ) {
            //     target = this;
            //     --i;
            // }

            for ( ; i < length; i++ ) {
                // Only deal with non-null/undefined values
                if ( (options = arguments[ i ]) !== null ) {
                    // Extend the base object
                    for ( name in options ) {
                        src  = target [ name ];
                        copy = options[ name ];

                        // Prevent never-ending loop
                        if ( target === copy ) {
                            continue;
                        }

                        // Recurse if we're merging plain objects or arrays
                        if ( deep && copy && ( serviceList.isPlainObject(copy) || (copyIsArray = serviceList.isArray(copy)) ) ) {
                            if ( copyIsArray ) {
                                copyIsArray = false;
                                clone = src && serviceList.isArray(src) ? src : [];
                            } else {
                                clone = src && serviceList.isPlainObject(src) ? src : {};
                            }

                            // Never move original objects, clone them
                            target[ name ] = serviceList.oldMarge( deep, clone, copy );

                            // Don't bring in undefined values
                        } else if ( copy !== undefined ) {
                            target[ name ] = copy;
                        }
                    }
                }
            }

            // Return the modified object
            return target;
        },

        /**
         * @return {object}
         */
        Marge : function Marge() {
            if (serviceList.isIE()){
                return serviceList.oldMarge.apply(null, arguments);
            } else {
                if (!Object.assign) {
                    Object.defineProperty(Object, 'assign', {
                        enumerable  : false,
                        configurable: true,
                        writable    : true,
                        value       : function(target, firstSource) {
                            'use strict';

                            if (target === undefined || target === null) {
                                throw new TypeError('Cannot convert first argument to object');
                            }

                            var to = Object(target);

                            for (var i = 1; i < arguments.length; i++) {
                                var nextSource = arguments[i];

                                if (nextSource === undefined || nextSource === null) {
                                    continue;
                                }

                                var keysArray = Object.keys(Object(nextSource));

                                for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
                                    var nextKey = keysArray[nextIndex];
                                    var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);

                                    if (desc !== undefined && desc.enumerable) {
                                        to[nextKey] = nextSource[nextKey];
                                    }
                                }
                            }

                            return to;
                        }
                    });
                }

                return Object.assign.apply(null, arguments);
            }
        },

        /**
         * @return {object}
         */
        deepExtend : function(out) {
            out = out || {};

            for (var i = 1; i < arguments.length; i++) {
                var obj = arguments[i];

                if (!obj)
                    continue;

                for (var key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        if (typeof obj[key] === 'object')
                            out[key] = serviceList.deepExtend(out[key], obj[key]);
                        else
                            out[key] = obj[key];
                    }
                }
            }

            return out;
        },

        /**
         * @return {boolean}
         */
        RGBToHex : function (color) {
            var res = false;

            if (color) {
                if (color.substr(0, 1) === '#') {
                    res = color;
                } else {
                    var digits = /(.*?)rgb\((\d+), (\d+), (\d+)\)/.exec(color);

                    var red = parseInt(digits[2]);
                    var green = parseInt(digits[3]);
                    var blue = parseInt(digits[4]);

                    var rgb = blue | (green << 8) | (red << 16);

                    res = digits[1] + '#' + rgb.toString(16);
                }
            }

            return res;
        },

        /**
         * @return {boolean}
         */
        IntToBool : function (Int){
            return serviceList.isBoolean(val) ? val : val == '1';
        },

        /**
         * @return {number}
         */
        BoolToIntEx : function (b){
            return (b === true) ? 0 : -1;
        },

        /**
         * @return {boolean}
         */
        isIE : function (){
            //return '\v'=='v';

            var tmp = document.documentMode, _isIE;

            // Try to force this property to be a string.
            try {
                document.documentMode = "";
            } catch(e){ }

            // If document.documentMode is a number, then it is a read-only property, and so
            // we have IE 8+.
            // Otherwise, if conditional compilation works, then we have IE < 11.
            // Otherwise, we have a non-IE browser.
            _isIE = typeof document.documentMode === "number" || eval("/*@cc_on!@*/!1");

            // Switch back the value to be unobtrusive for non-IE browsers.
            try{document.documentMode = tmp;}
            catch(e){ }

            return _isIE;
        },

        /**
         * @return {boolean}
         */
        isIE6 : function (){
            var gg = navigator['appVersion'];
            var ggg = gg.indexOf("MSIE");
            var gggg = gg.slice(ggg + 5, ggg + 6);

            return gggg === "6";
        },

        /**
         * @return {boolean}
         */
        isFuckingFirefox : function() {
            return typeof InstallTrigger !== 'undefined';
        },

        /**
         * @return {object}
         */
        LessOfTwo : function (itm1, itm2, inString){
            var u1, u2;

            if (parseInt(itm1) < parseInt(itm2)) {
                u1 = itm1;
                u2 = itm2;
            } else {
                u1 = itm2;
                u2 = itm1;
            }

            return inString ? u1 + "_" + u2 : {min:u1, more:u2};
        },

        /**
         * @return {string}
         */
        formatDate : function (formatDate, formatString){
            if (!formatDate) return formatDate;

            var mcDate = serviceList.isString(formatDate);

            if (mcDate) formatDate = formatDate.split('.');

            var nowYY = (new Date()).getFullYear();
            var yyyy = mcDate ? parseInt(formatDate[2]) : formatDate.getFullYear();
            var yy = yyyy.toString().substring(2);
            var m = mcDate ? parseInt(formatDate[1]) : formatDate.getMonth() + 1;
            var mm = m < 10 ? "0" + m : m;
            var d = mcDate ? parseInt(formatDate[0]) : formatDate.getDate();
            var dd = d < 10 ? "0" + d : d;

            var  h = mcDate ? parseInt(formatDate[3]) : formatDate.getHours();
            var  hh = h < 10 ? "0" + h : h;
            var  n = mcDate ? parseInt(formatDate[4]) : formatDate.getMinutes();
            var  nn = n < 10 ? "0" + n : n;
            var  s = mcDate ? parseInt(formatDate[5]) : formatDate.getSeconds();
            var  ss = s < 10 ? "0" + s : s;
            var  zzz = mcDate ? parseInt(formatDate[6] || "0") : formatDate.getMilliseconds();
            
            if (zzz < 10) {
                zzz = "00" + zzz;
            } else
            if (zzz >= 10 && zzz < 100){
                zzz = "0" + zzz;
            }

            var lang = "en";

            if (typeof mcConst === 'object'){
                lang = mcConst.Lang;
            }

            if (typeof require === 'function'){
                window.moment = require('moment');
            }

            var MMM = moment().locale(lang).month(parseInt(m) - 1).format('MMMM');
            var MM  = moment().locale(lang).month(parseInt(m) - 1).format('MMM');

            formatString = formatString.replace(/\?yyyy/, yyyy == nowYY ? "" : yyyy);
            formatString = formatString.replace(/yyyy/, yyyy);
            formatString = formatString.replace(/yy/, yy);
            formatString = formatString.replace(/mm/, mm);
            formatString = formatString.replace(/m/, m);
            formatString = formatString.replace(/dd/, dd);
            formatString = formatString.replace(/d/, d);
            formatString = formatString.replace(/hh/, hh);
            formatString = formatString.replace(/h/, h);
            formatString = formatString.replace(/nn/, nn);
            formatString = formatString.replace(/n/, n);
            formatString = formatString.replace(/ss/, ss);
            formatString = formatString.replace(/s/, s);
            formatString = formatString.replace(/zzz/, zzz);
            formatString = formatString.replace(/MMM/, MMM);
            formatString = formatString.replace(/MM/, MM);

            return formatString;
        },

        /**
         * @return {string}
         */
        DIV : function (x1, x2){
            var y1 = (x1 / x2).toString();
            var idx = y1.indexOf(".");

            return y1.slice(0, (idx !== -1) ? idx : y1.length);
        },

        CopyArray : function (arr){
            return [].concat(arr);
        },

        StringToArray : function (_msg, separator){
            var res =[];
            var idx = -1;

            if (_msg){
                if (serviceList.isIE() === false) {
                    res = _msg.split(separator) || [];
                } else {
                    do {
                        idx = _msg.indexOf(separator);
                        if (idx >= 0) {
                            res.push(_msg.slice(0, idx));
                            _msg = _msg.slice(idx + 1, _msg.length + 1);
                        }
                    } while (idx !== -1);
                }

                if (res.length && res[res.length - 1].length === 0) {
                    res.pop();
                }
            }

            return res;
        },

        GetSubstring : function (str, term){
            var idx = str.indexOf(term);
            return (idx !== -1)? str.slice(0, idx) : str.slice(0, str.length + 1);
        },

        GetSubstringFrom : function (str, term){
            var idx = str.indexOf(term);
            return (idx !== -1)? str.slice(idx + 1, str.length + 1) : str.slice(0, str.length + 1);
        },

        DelSubstring : function (str, term){
            var idx = str.indexOf(term);
            return (idx !== -1)? str.slice(idx + 1, str.length + 1) : "";
        },

        delArrayItem : function(arr, pos, len){
            if ( pos >= 0 ) {
                arr.splice(pos,(len || 1));
            }
        },

        doGetCaretPosition : function (ctrl) {
            var CaretPos = 0;	// IE Support

            if (document.selection) {
                var Sel = document.selection.createRange();

                ctrl.focus ();
                Sel.moveStart ('character', -ctrl.value.length);

                CaretPos = Sel.text.length;
            } else { // Firefox support
                if (ctrl.selectionStart || ctrl.selectionStart == '0'){
                    CaretPos = ctrl.selectionStart;
                }
            }

            return CaretPos;
        },

        inArray: function( elem, arr, i ) {
            var len;

            if ( arr ) {
                len = arr.length;
                i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

                for ( ; i < len; i++ ) {
                    if ( i in arr && arr[ i ] === elem ) {
                        return i;
                    }
                }
            }

            return -1;
        },

        inArrayB: function (){
            return serviceList.inArray.apply(null, arguments) !== -1;
        },

        inArrayLowerCase: function( elem, arr, i ) {
            var len;

            if ( arr ) {
                len = arr.length;
                i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

                for ( ; i < len; i++ ) {
                    if ( i in arr && arr[ i ].toLowerCase() === elem.toLowerCase() ) {
                        return i;
                    }
                }
            }

            return -1;
        },

        inArrayNoStrict: function( elem, arr, i ) {
            var len;

            if ( arr ) {
                len = arr.length;
                i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

                for ( ; i < len; i++ ) {
                    if ( i in arr && arr[ i ] == elem ) {
                        return i;
                    }
                }
            }

            return -1;
        },

        isArray : function (obj) {
            return Object.prototype.toString.call( obj ) === '[object Array]';
        },

        isString : function (obj) {
            return Object.prototype.toString.call( obj ) === '[object String]';
        },

        isDate : function (obj) {
            return Object.prototype.toString.call( obj ) === "[object Date]";
        },

        isHtmlElement : function (obj) {
            return Object.prototype.toString.call( obj ).indexOf('[object HTML') === 0;
        },

        isNumber : function (obj) {
            return Object.prototype.toString.call( obj ) === '[object Number]';
        },

        isObject : function (obj) {
            return Object.prototype.toString.call( obj ) === '[object Object]';
        },

        isFunction : function (obj) {
            return Object.prototype.toString.call( obj ) === '[object Function]';
        },

        isBoolean : function (obj) {
            return Object.prototype.toString.call( obj ) === '[object Boolean]';
        },

        RandomHash : function (len, isPwd) {
            var letters =  isPwd
                ? 'JjKAEeaMpsTt9QqRxBcDdF2fGgHh3ibC-kL7mNnPV85yr6SUuZ1WwXY4._vz!@#%$&*'
                : 'JjKAEeaMpsTt9QqRxBcDdF2fGgHhI3ibC-kL7mNnOPV85yr6SUuZo1WwXlY40._vz';
            var res = '';

            for (var i=0; i <  len; i++) {
                res += letters[Math.floor(Math.random() * letters.length)];
            }

            return res;
        },

        getRandomInt : function (min, max){
            return Math.floor(Math.random() * (max - min + 1)) + min;
        },

        GetType : function (obj){
            return Object.prototype.toString.call( obj );
        },

        isFormatedLikeIP : function(ip){
            var reg = /((((\d){1,3}\.){3})((\d){1,3}))/; // |(((^|:)([0-9a-fA-F]{0,4})){1,8})

            return reg.test(ip);
        },

        isValidFIO : function (fio){
            var pattern = new RegExp(/^([\x27а-яА-Яa-zA-ZїЇіІєЄ]{2,}|[\x27а-яА-Яa-zA-ZїЇіІєЄ]{2,}\-[\x27а-яА-Яa-zA-ZїЇіІєЄ]{2,})\040[\x27а-яА-Яa-zA-ZїЇіІєЄ]{2,}|[\x27а-яА-Яa-zA-ZїЇіІєЄ]{2,}\040[\x27а-яА-Яa-zA-ZїЇіІєЄ]{2,}$/);
            return pattern.test(fio);
        },

        isValidEmailAddress : function (emailAddress) {
            var pattern = new RegExp(/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i);
            return pattern.test(emailAddress);
        },   

        isValidIpAddress : function (ip) {
            var reg = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/; // |(((^|:)([0-9a-fA-F]{0,4})){1,8})

            return reg.test(ip);
        },

        isValidIpAddressV6 : function (ip) {
            var reg = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
            // var reg = /^(:([0-9a-fA-F]{0,4})){1,8}$/;

            return reg.test(ip);
        },

        isValidHostName : function (ip) {
            // var reg = /^[0-9a-zA-Z\-.]*$/;
            // var reg = new RegExp("^[\\p{L}0-9a-zA-Z\\-.]*$", "u");
            var reg = /^[0-9a-zA-Zа-яА-Я\-.]*$/;

            return reg.test(ip) || serviceList.isValidIpAddressV6(ip);
        },

        isValidIpRange : function(ip){
            var reg = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)(-)((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;

            return reg.test(ip);
        },

        isValidIpMask : function(ip){
            var reg = /^(((25([0-5]|\?)|2([0-4]|\?)(\d|\?)|([01]|\?)?(\d|\?)(\d|\?)?)|\*)\.){3}((25([0-5]|\?)|2([0-4]|\?)(\d|\?)|([01]|\?)?(\d|\?)(\d|\?)?)|\*)$/;

            return reg.test(ip);
        },

        isValidMACAddress : function(mac){
            var reg = /^([0-9a-fA-F]{2}([:-]|$)){6}$|([0-9a-fA-F]{4}([.]|$)){3}$/;

            return reg.test(mac);
        },

        isValidPhoneNumber: function (tel){
            var reg = /^(\s*)?(\+)?([- _():=+]?\d[- _():=+]?){10,14}(\s*)?$/;

            return reg.test(tel);
        },

        isHasNoRussianSymbol : function (sText) {
            var pattern = new RegExp(/[a-zA-Z0-9]/i);

            return pattern.test(sText);//(pattern.test(sText) || (sText.search(" ") == -1));
        },

        isSymbolicNumbers : function (sText) {
            var pattern = new RegExp(/[a-zA-Z0-9а-яА-ЯїЇіІєЄ\s]/i);

            return pattern.test(sText);
        },

        isURL : function (_url){
            var u = /(((http|https|ftp|ftps)(:\/\/)?|(:\/\/)(www\.)?|(www\.))[a-zа-я0-9-]+\.[a-zа-я0-9-]{2,6})/gi;

            return u.test(_url);
        },

        trim : function ( str ){
            return str.toString().replace(/^\s+|\s+$/g,'');
        },

        /**
         * @return {string}
         */
        GetUserOS : function (){
            var os = "";
            var appV = navigator.appVersion;
            var oscpu = navigator.oscpu;

            if (serviceList.isIE()){
                os = appV.split(";")[2];
            } else {
                if (oscpu !== null || oscpu !== undefined) {
                    os = oscpu;
                } else os = appV.slice(appV.indexOf("(") + 1, ((appV.indexOf(";") != -1) ? appV.indexOf(";") : appV.indexOf(")")) );
            }

            return navigator.platform + " " + os;
        },

        /**
         * @return {string}
         */
        GetUserLanguage : function (){
            return  (navigator.language       ? navigator.language       : "") +
                    (navigator.systemLanguage ? navigator.systemLanguage : "");
        },

        /**
         * @return {string}
         */
        GetUserRefLink : function (){
            return document.referrer;
        },

        /**
         * @return {string}
         */
        GetUserAgent : function (){
            return navigator.userAgent;
        },

        /**
         * @return {string}
         */
        GetUserBrowser : function (){
            var browser = "";

            if (this.isIE()){
                browser = "IE";
            } else
            if (navigator.appName === "Opera"){
                browser = "Opera";
            } else
            if (navigator.vendor === "Apple Computer, Inc."){
                browser = "Safari";
            } else
            if (navigator.vendor === "Google Inc."){
                browser = "Chrome";
            } else
            if (navigator.oscpu){
                browser = "FireFox";
            } else {
                browser = "Other Brouser";
            }

            return browser;
        },

        pos  : function (FindStr, Src){
            return Src.indexOf(FindStr);
        },

        copy : function (Src, From, Count){
            return (Src) ? Src.slice(From, From + (Count || (Src.length - From))) : "";
        },

        ReplaceURLs : function( txt, options ) {
            var
                SCHEME = "[a-z\\d.-]+://",
                IPV4 = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])",
                HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+",
                TLD  =  "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|" +
                        "bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|" +
                        "do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|" +
                        "gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|" +
                        "kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|" +
                        "mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|" +
                        "py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|" +
                        "tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|" +
                        "xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|" +
                        "xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw|dev)",
                PORT = "(?::\\d{1,5})?",
                HOST_OR_IP = "(?:" + HOSTNAME + TLD + PORT + "|" + IPV4 + PORT + ")",
                PATH = "(?:[;/][^#?<>()\\s]*)?",
                QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?",
                URI1 = "\\b" + SCHEME + "[^<>(\\s|\\)|\\(|\\[|\\])]+", //"[^<>\\s]+",
                URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)",

                MAILTO = "mailto:",
                EMAIL = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)",

                URI_RE = new RegExp( "(?:" + URI1 + "|" + URI2 + "|" + EMAIL + ")", "ig" ),
                SCHEME_RE = new RegExp( "^" + SCHEME, "i" );

            var quotes = {
                "'": "`",
                '>': '<',
                ')': '(',
                ']': '[',
                '}': '{',
                'В»': 'В«',
                'вЂє': 'вЂ№'
            };

            var default_options = {
                callback: function( text, href ) {
                    return href ? '<a href="' + href + '" title="' + href + '">' + text + '<\/a>' : text;
                },
                punct_regexp: /(?:[!?.,:;'"]|(?:&|&amp;)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/
            };

            options = options || {};

            // Temp variables.
            var arr,
                i,
                link,
                href,

                // Output HTML.
                html = '',

                // Store text / link parts, in order, for re-combination.
                parts = [],

                // Used for keeping track of indices in the text.
                idx_prev,
                idx_last,
                idx,
                link_last,

                // Used for trimming trailing punctuation and quotes from links.
                matches_begin,
                matches_end,
                quote_begin,
                quote_end;

            // Initialize options.
            for ( i in default_options ) {
                if ( options[ i ] === undefined ) {
                    options[ i ] = default_options[ i ];
                }
            }

            // Find links.
            while ( arr = URI_RE.exec( txt ) ) {

                link = arr[0];
                idx_last = URI_RE.lastIndex;
                idx = idx_last - link.length;

                // Not a link if preceded by certain characters.
                if ( /[\/:]/.test( txt.charAt( idx - 1 ) ) ) {
                    continue;
                }

                // Trim trailing punctuation.
                do {
                    // If no changes are made, we don't want to loop forever!
                    link_last = link;

                    quote_end = link.substr( -1 );
                    quote_begin = quotes[ quote_end ];

                    // Ending quote character?
                    if ( quote_begin ) {
                        matches_begin = link.match( new RegExp( '\\' + quote_begin + '(?!$)', 'g' ) );
                        matches_end = link.match( new RegExp( '\\' + quote_end, 'g' ) );

                        // If quotes are unbalanced, remove trailing quote character.
                        if ( ( matches_begin ? matches_begin.length : 0 ) < ( matches_end ? matches_end.length : 0 ) ) {
                            link = link.substr( 0, link.length - 1 );
                            idx_last--;
                        }
                    }

                    // Ending non-quote punctuation character?
                    if ( options.punct_regexp ) {
                        link = link.replace( options.punct_regexp, function(a){
                            idx_last -= a.length;
                            return '';
                        });
                    }
                } while ( link.length && link !== link_last );

                href = link;

                // Add appropriate protocol to naked links.
                if ( !SCHEME_RE.test( href ) ) {
                    href = ( href.indexOf( '@' ) !== -1 ? ( !href.indexOf( MAILTO ) ? '' : MAILTO )
                        : !href.indexOf( 'irc.' ) ? 'irc://'
                            : !href.indexOf( 'ftp.' ) ? 'ftp://'
                                : 'http://' )
                        + href;
                }

                // Push preceding non-link text onto the array.
                if ( idx_prev != idx ) {
                    parts.push([ txt.slice( idx_prev, idx ) ]);
                    idx_prev = idx_last;
                }

                // Push massaged link onto the array
                parts.push([ link, href ]);
            }

            // Push remaining non-link text onto the array.
            parts.push([ txt.substr( idx_prev ) ]);

            // Process the array items.
            for ( i = 0; i < parts.length; i++ ) {
                html += options.callback.apply( window, parts[i] );
            }

            // In case of catastrophic failure, return the original text;
            return html || txt;
        },

        isObjectEmpty : function (obj){
            var flag = true;

            if (obj && (this.isObject(obj))){
                for (var i in obj){
                    flag = false;
                }
            }

            return flag;
        },

        StringToObj : function (_data){
            var res = null;

            if (this.isString(_data)){
                try {
                    res = JSON.parse(_data);
                } catch(e){
                    // console.error('Error. Can\'t convert to JSON:\n' + _data);
                }
            }

            return res || _data;
        },

        isNewNotificationSupported: function () {
            if (!window.Notification || !Notification.requestPermission)
                return false;
            if (Notification.permission !== 'granted')
                // throw new Error('You must only call this *before* calling Notification.requestPermission(), otherwise this feature detect would bug the user with an actual notification!');
                return false;

            // try {
            //     new Notification('');
            // } catch (e) {
            //     if (e.name === 'TypeError')
            //     return false;
            // }
            return true;
        },

        CreateNotification : function (opt) {
            if (serviceList.notify !== null){
                clearTimeout(serviceList.idNotify);

                serviceList.notify.close();
                serviceList.idNotify = 0;
                serviceList.notify = null;
            }

            if (!serviceList.noNotify && serviceList.isNewNotificationSupported()){
                serviceList.notify = new window.Notification(opt.title || " ", {
                    icon   : mcConst.imagesPath.all + '75x75.png',
                    tag    : opt.tag || "mcNotify",
                    body   : opt.body || "Empty"
                });

                serviceList.idNotify = setTimeout(function(){
                    serviceList.notify.close();
                    serviceList.idNotify = 0;
                    serviceList.notify   = null;
                }, opt.timeOut);

                serviceList.notify.onerror = function(){
                    if (console) {
                        console.warn("permission state = default or denied");
                    }

                    clearTimeout(serviceList.idNotify);

                    serviceList.idNotify = 0;
                    serviceList.notify = null;
                    serviceList.noNotify = true;
                };

                if (opt.onShow){
                    serviceList.notify.onshow = function(){
                        opt.onShow();
                    };
                }

                if (opt.click){
                    serviceList.notify.onclick = function() {
                        clearTimeout(serviceList.idNotify);

                        if (serviceList.notify) {
                            serviceList.notify.close();
                            serviceList.notify = null;
                        }

                        serviceList.idNotify = 0;

                        window.focus();

                        opt.click();
                    };
                }

                if (serviceList.notify.show) {
                    serviceList.notify.show();
                }
            }
        },

        CheckNotification : function (rrr){
            var res = false;

            if (window.webkitNotifications || window.Notification) {
                if ((window.webkitNotifications && window.webkitNotifications.checkPermission() === 0) || Notification.permission === "granted") { // 0 is PERMISSION_ALLOWED
                    res = true;
                    serviceList.noNotify = false;
                } else {
                    if (window.Notification){
                        Notification.requestPermission(function (permission) { // Whatever the user answers, we make sure Chrome stores the information
                            if(!('permission' in Notification)) {
                                Notification.permission = permission;
                            }
                        });
                    } else {
                        window.webkitNotifications.requestPermission(function(ssss){
                            rrr = ssss === 'granted';
                            serviceList.noNotify = !rrr;
                        });
                    }
                }
            } else {
                console.warn("Notifications are not supported for this Browser/OS version yet.");
            }

            return res;
        },

        mobileDetection : {
            Android:function () {
                return navigator.userAgent.match(/Android/i);
            },
            BlackBerry:function () {
                return navigator.userAgent.match(/BlackBerry/i);
            },
            iOS:function () {
                return navigator.userAgent.match(/iPhone|iPad|iPod/i);
            },
            Opera:function () {
                return navigator.userAgent.match(/Opera Mini/i);
            },
            Windows:function () {
                return navigator.userAgent.match(/IEMobile/i);
            },
            any:function () {
                return (this.Android() || this.BlackBerry() || this.iOS() || this.Opera() || this.Windows()) || false;
            }
        },

        MyFormatTime : function (time, short){
            var res = "";

            var ss = parseInt(time % 60);
            var hh = parseInt(time / 3600);
            var nn = parseInt(time / 60) - (hh * 60);

            if (short){
                res = ((nn < 10) ? ("0" + nn) : nn) + ":" + ((ss < 10) ? ("0" + ss) : ss);
            } else {
                res = ((hh < 10) ? ("0" + hh) : hh) + ":" + ((nn < 10) ? ("0" + nn) : nn) + ":" + ((ss < 10) ? ("0" + ss) : ss);
            }

            return res;
        },

        startOfDay : function(day){
            var now = day.getTime();

            return  now - (day.getHours()*1000*60*60 + day.getMinutes()*1000*60 + day.getSeconds()*1000 + day.getMilliseconds());
        },

        storage: {
            get: function () {
                let res;

                try {
                    res = webix.storage.local.get.apply(null, arguments);
                } catch (e) {}
                
                return res;
            },
            put: function () {
                try {
                    webix.storage.local.put.apply(null, arguments);
                } catch (e) {}
            },
            remove: function () {
                try {
                    webix.storage.local.remove.apply(null, arguments);
                } catch (e) {}
            }
        },

        detectLang : function (init){
            init = init === "undefined" || !init ? null : init;
            
            var locale = init || ( (window.navigator.language || window.navigator.userLanguage).slice(0, 2).toLowerCase() );
            var lang = locale;

            if (locale !== 'en' && locale !== 'ru' && locale !== 'uk') {
                locale = MC_RESOURCE.TextSource.www[_api] + 'en';
                lang = 'en';
            } else {
                locale = MC_RESOURCE.TextSource.www[_api] + locale;
            }

            // webix.storage.local.put(mcConst.storageFields.lang, lang);
            serviceList.storage.put(mcConst.storageFields.lang, lang);

            return [locale, lang];
        },

        Lang : function(id, items, term){
            var opt = {};

            if (serviceList.isObject(id)){
                opt = id;
            } else {
                opt = {
                    id   : id,
                    items: items,
                    term : term,
                    html : false
                };
            }

            var res = mcConst[opt.from ? "LMSG_" + opt.from : "LMSG"][opt.id.toString()];

            if (opt.html){
                if (serviceList.isArray(opt.items)){
                    opt.items.forEach(function (t, i) {
                        if (t) {
                            opt.items[i] = t.replace(/</ig, "&lt;");
                        }
                    });
                } else
                if (opt.items) {
                    opt.items = opt.items.replace(/</ig, "&lt;");
                }
            }

            return opt.items ? serviceList.myReplace(res, opt.items, opt.term) : res;
        },

        getElementByClass : function(classList, _node){
            var res  = null;
            var node = _node || document;

            if(document.getElementsByClassName) {
                res = node.getElementsByClassName(classList)
            } else {
                var list = node.getElementsByTagName('*');
                var length = list.length;
                var classArray = classList.split(/\s+/);
                var classes = classArray.length;
                var result = [];

                for (var i = 0; i < length; i++) {
                    for (var j = 0; j < classes; j++)  {
                        if (list[i].className.search('\\b' + classArray[j] + '\\b') != -1) {
                            result.push(list[i]);

                            break
                        }
                    }
                }

                res = result;
            }

            return res;
        },

        myReplace : function(source, _items, term){
            var res = source;
            var items = [];

            if (_items !== void(0) && source){
                var _term = term || "%s";

                if (!serviceList.isArray(_items)) {
                    items.push(_items);
                } else {
                    items = _items;
                }

                if (items.length > 0){
                    res = source.toString().replace(_term, items.shift());
                    res = serviceList.myReplace(res, items, _term);
                }
            }

            return res;
        },

        initResize : function(source, Name){
            mcConst.scrResize = source;
            mcConst.isShowed  = Name;
        },

        showView : function(view){
            if (view && view.className.indexOf('hidden') !== -1) {
                view.className = view.className.replace(' hidden', "");
            }
        },

        hideView : function(view){
            if (view && view.className.indexOf('hidden') === -1) {
                view.className += ' hidden';
            }
        },

        showCurrentView : function(Name, $rootScope, view, options){
            options = options || {};

            if (mcConst.isShowed !== Name) {
                mcConst.isShowed && $rootScope.$broadcast('hide' + mcConst.isShowed);
                $rootScope.$broadcast('setLocation', ['/' + $rootScope.currentRootPath + '/' + Name + '/', options.callBack, options.title]);

                serviceList.showView(view);

                mcConst.isShowed = Name;
            }
        },

        getLangItemsFromList : function(from, to, correct){
            var res = [];

            correct = correct || 0;

            for (var i = from; i <= to; i++){ // 249 492
                res.push({
                    id    : (i - from + correct).toString(),
                    value : serviceList.Lang(i)
                })
            }

            return res;
        },

        getLanguagesItems : function(from, to, correct){
            var res = [];

            correct = correct || 0;

            for (var i = from; i <= to; i++){ // 249 492
                res.push({
                    id    : (i - from + correct).toString(),
                    value : mcConst.LanguagesList[i]
                })
            }

            return res;
        },

        GetFioFormatList : function(){
            return serviceList.getLangItemsFromList(975, 983);/*.sort(function(a, b){
            return serviceList.sortAB(a.value.toLowerCase(), b.value.toLowerCase());
        });*/
        },

        sortAB : function(a,b,invert){
            return (a > b) ? (invert ? -1 : 1) : ((a < b) ? (invert ? 1 : -1) : 0);
        },

        GetCountriesList : function(){
            var res = serviceList.getLanguagesItems(249, 492, 1).sort(function(a, b){
                return serviceList.sortAB(a.value.toLowerCase(), b.value.toLowerCase());
            });

            res.unshift({
                id   : '-1',
                value: serviceList.Lang({id: 140, from: "admin"}) // "140":"Ничего не выбрано",
            });

            return res;
        },

        GetMaritalStatusList : function(){
            return serviceList.getLangItemsFromList(151, 157, 1);
        },

        GetSpokenLangList : function(){
            var res = serviceList.getLanguagesItems(180, 248, 1).sort(function(a, b){
                return serviceList.sortAB(a.value.toLowerCase(), b.value.toLowerCase());
            });

            res.unshift({
                id: '-1',
                value: serviceList.Lang({id: 140, from: "admin"}) // "140":"Ничего не выбрано",
            });

            return res;
        },

        GetInterestsList : function(){
            var res = serviceList.getLangItemsFromList(494, 543, 1);

            res.unshift({
                id: '-1',
                value: serviceList.Lang(140) // "140":"Ничего не выбрано",
            });

            return res;
        },

        GetPastList : function(){
            var res = serviceList.getLangItemsFromList(546, 553, 1);

            res.unshift({
                id: '-1',
                value: serviceList.Lang(140) // "140":"Ничего не выбрано",
            });

            return res;
        },

        GetPastOrgList : function(){
            var res = serviceList.getLangItemsFromList(554, 573, 1);

            res.unshift({
                id: '-1',
                value: serviceList.Lang(140) // "140":"Ничего не выбрано",
            });

            return res;
        },

        GetWeekDays : function(){
            return serviceList.getLangItemsFromList(646, 652);
        },

        SaveSessionSettings : function(login, pwd, servPwd, rm, email){
            if (serviceList.isObject(login)){
                pwd     = login.pwd;
                servPwd = login.servPwd;
                rm      = login.rm;
                login   = login.login;
                // email   = login.Email;
            }

                serviceList.storage.put(mcConst.storageFields.Login, login);

                if (rm){
                    serviceList.storage.put(mcConst.storageFields.Pwd, pwd);
                    serviceList.storage.put(mcConst.storageFields.ServPwd, servPwd);
                    serviceList.storage.put(mcConst.storageFields.Rm, true);

                    // if (mcConst.storageFields.Email) webix.storage.local.put(mcConst.storageFields.Email, email); // for websupport
                } else {
                    serviceList.storage.remove(mcConst.storageFields.Pwd);
                    serviceList.storage.remove(mcConst.storageFields.ServPwd);
                    serviceList.storage.remove(mcConst.storageFields.Rm);

                    // if (mcConst.storageFields.Email) webix.storage.local.remove(mcConst.storageFields.Email); // for websupport
                }

        },

        LoadSessionSettings : function(){
            var res = {};

                res.login   = serviceList.storage.get(mcConst.storageFields.Login);
                res.pwd     = serviceList.storage.get(mcConst.storageFields.Pwd);
                res.servPwd = serviceList.storage.get(mcConst.storageFields.ServPwd);
                res.rm      = serviceList.storage.get(mcConst.storageFields.Rm);

            // if (mcConst.storageFields.Email) res.Email = webix.storage.local.get(mcConst.storageFields.Email);

            return res;
        },

        ClearSessionSettings: function(leaveUIN, noAutoconnectOnReload){
                if (!leaveUIN) {
                    serviceList.storage.remove(mcConst.storageFields.Login);
                }

                serviceList.storage.remove(mcConst.storageFields.Pwd);
                serviceList.storage.remove(mcConst.storageFields.ServPwd);
                serviceList.storage.remove(mcConst.storageFields.Rm);

                if (noAutoconnectOnReload){
                    serviceList.storage.put(mcConst.storageFields.AutoRld, true);
                } else {
                    serviceList.storage.remove(mcConst.storageFields.AutoRld);
                }
        },

        toNormalDate : function (value, needTime){
            if (serviceList.isString(value)) {
                value = value.split('.');
            }

            var date = value[0] + '.' + //day
                value[1] + '.' + //month
                value[2];        //year

            var time = value[3] + ':' + //hour
                value[4] + ':' + //minutes
                value[5];        //seconds

            return date + ((needTime)? " " + time : "");
        },

        sortableDate : function(date, noSec){
            var dd = date.split('.');

            return dd[2] + '.' + dd[1] + '.' + dd[0] + ' ' + dd[3] + ':' + dd[4] + (!noSec ? ':' + dd[5] : '');
        },

        secToTime : function(sec){
            var ss = sec % 60;
            var dd = parseInt(sec / 86400);
            sec = sec - 86400*dd;
            var hh = parseInt(sec / 3600);
            var nn = parseInt(sec / 60) - (hh * 60);

            return (dd) + ':' + ((hh < 10) ? ("0" + hh) : hh) + ":" + ((nn < 10) ? ("0" + nn) : nn) + ":" + ((ss < 10) ? ("0" + ss) : ss);
        },

        formatNumber : function(numb){
            var stNumb = numb.toString();
            var res = [];
            var id = 1;

            for (var i = stNumb.length - 1; i >= 0; i --){
                res.unshift(((id === 3) ? " " : "") + stNumb[i]);

                id = (id === 3) ? 1 : (id + 1);
            }

            return res.join('');
        },

        convertBool : function (val) {
            switch (val) {
                case true:  val = '1'; break;
                case false: val = '0'; break;
                default:    val = val + "";
            }

            return val;
        },

        convertIntToBool : function (val) {
            return serviceList.isBoolean(val) ? val : val == 1;
        },

        clearPlaneObj : function(obj){
            for (var i in obj){
                obj[i] = null;
            }
        },

        Queue : function(lookupTime, loop){
            var self      = this;
            var needRun   = [];
            var canNext   = loop || false;
            var paramList = [];
            var nowRunned = false;
            var time      = lookupTime || 100;

            var lookUp    = null;

            function start(){
                return setInterval(function(){
                    if (!nowRunned && needRun.length > 0){
                        nowRunned = true;

                        var arg = (serviceList.isArray(paramList[0])) ? paramList[0] : [paramList[0]];

                        needRun[0].apply(self, arg);
                    }
                }, time);
            }

            this.next = function(){
                if (needRun.length > 0){
                    needRun.shift();
                    paramList.shift();

                    nowRunned = false;
                } else
                if (!canNext){
                    clearInterval(lookUp);

                    self = null;
                }
            };

            this.addTask = function (qItem, params){
                if (needRun.length === 0 && !nowRunned) {
                    lookUp = start();
                }

                needRun.push(qItem);
                paramList.push(params);

                return needRun.length;
            };
        },

        TaskList : function(_scope){
            var self  = this;
            var list  = [];
            var scope = {};

            this.AddTask = function(task){
                list.push(task);
            };

            this.Next = function(){
                if (list[0]){
                    list.shift().apply(scope, arguments);
                } else {
                    self = null;
                }
            };

            this.Run = function(){
                self.Next();
            };

            scope = serviceList.Marge({
                next : self.Next,
                Next : self.Next
            }, _scope || {});
        },

        transformDataObject : function(source, dependency){
            var res = [];

            if (source && dependency){
                for ( var i = 0; i < source.length; i++){
                    var item = {};

                    for (var j = 0; j < dependency.length; j++){
                        item[dependency[j][1]] = source[i][dependency[j][0]].toString();
                    }

                    res.push(item);
                }
            } else {
                res = source;
            }

            return res;
        },

        networkInterfaces: function(IPVer, IPlist){
            var myType =(IPVer == 0) ? "IPv4" : "IPv6"; // $scope.data.IPVer
            var IPs    = [].concat(IPlist[myType]); // $scope.data.networkInterfaces[myType]

            for (var i = 0; i < IPs.length; i++){
                IPs[i] = {id : IPs[i], value: (( IPs[i] == '0.0.0.0') || (IPs[i] == '::')) ? serviceList.Lang(667) : IPs[i]};// "667": "слушать всё",
            }

            return IPs;
        },

        correctStringWithCRLF : function(val){
            return (serviceList.isString(val)) ? val.replace(/(?!\r)\n/g, '\r\n') : val;
        },

        removeCRFL: function(text){
            return text.toString().replace(/\n/g, '<br>').replace(/\r/g, '');
        },

        initChangesApplyForSettings: function($scope, $rootScope, CMD, applyText, storage, cancelSave){
            var backupItems = {};

            storage = storage || mcConst.dataModels.SettingsNetwork;

            function clearChangedData (){
                $scope.changedData.data.Items    = {};
                $scope.changedData.data.Desc     = "";
                $scope.changedData.data.ShowDesc = false;
            }

            $scope.changedData = {
                data : {
                    Desc   : "",
                    Items  : {},
                    ShowDesc : false,
                    Reopen : false
                },
                clearChangedData: clearChangedData,
                onApply : function() {
                    $scope.data = serviceList.Marge($scope.data, $scope.changedData.data.Items);
                    backupItems = serviceList.Marge({}, $scope.changedData.data.Items);

                    var data = {};

                    if ($scope.changedData.data.Items.hasOwnProperty("TURNPortTCP")){
                        data.turn = $scope.changedData.data.Items["TURNPortTCP"];
                    }
                    if ($scope.changedData.data.Items.hasOwnProperty("WEBTCPPort")){
                        data.web = $scope.changedData.data.Items["WEBTCPPort"];
                    }
                    if ($scope.changedData.data.Items.hasOwnProperty("TCPPort")){
                        data.core = $scope.changedData.data.Items["TCPPort"];
                    }
                    if ($scope.changedData.data.Items.hasOwnProperty("FTPPort")){
                        data.ftp = $scope.changedData.data.Items["FTPPort"];
                    }
                    if ($scope.changedData.data.Items.hasOwnProperty("FTPPortsPool")){
                        data.ftprange = $scope.changedData.data.Items["FTPPortsPool"];
                    }
                    if ($scope.changedData.data.Items.hasOwnProperty("TURNExternalConfig")){
                        data.turnconfig = $scope.changedData.data.Items["TURNExternalConfig"];
                    }

                    if (Object.keys(data).length){
                        $rootScope.$broadcast('SendCMDToServer', [
                            mcConst._CMD_.cs_adm_check_ports,
                            mcConst.SessionID,
                            JSON.stringify(data),
                            function () {
                                $scope.applyChange($scope.data);

                                clearChangedData();
                            }
                        ]);
                    } else {
                        $scope.applyChange($scope.data);

                        clearChangedData();
                    }
                },
                onClose : function(){
                    $scope.$emit("show" + $scope.Name, []);

                    clearChangedData();
                }
            };

            function changeApplyData (id, val){
                $scope.changedData.data.ShowDesc  = true;
                $scope.changedData.data.Items[id] = serviceList.correctStringWithCRLF(val);
                $scope.changedData.data.Desc      = serviceList.Lang(590); // "590": "Список измененных параметров:<br>",

                var item = "";

                for (var i in $scope.changedData.data.Items){
                    item = $scope.changedData.data.Items[i].toString().replace(/\\/g, '\\\\').replace(/</g, "&lt;");

                    $scope.changedData.data.Desc += "<br>" + i + ": <b>" + (
                        (
                            (i.indexOf('PWD') !== -1) ||
                            (i.toLowerCase().indexOf('pass') !== -1) ||
                            (i === "UseConnPass")
                        ) ? "*****" : item ) + "</b>"; // &#092;
                }

                $rootScope.$broadcast('show' + mcConst.dataModels.ApplyPanel, [$scope.changedData]);
            }

            function checkLastSlash(data, where) {
                if (data[where] && ["\\", "/"].indexOf(data[where][data[where].length - 1]) === -1){
                    data[where] += '\\';
                }
                // if (data.BackupsDir && (data.BackupsDir[data.BackupsDir.length - 1] != '\\') && (data.BackupsDir[data.BackupsDir.length - 1] != '/')){
                //     data.BackupsDir += '\\';
                // }
            }

            $scope.change = function(newV, oldV){
                if ($scope.skipChangeNow) return;

                var id = serviceList.copy(this.config.id, 3);
                var valid = true;
                // var restoreValue = false;

                if (newV == undefined){
                    newV = this.getValue();
                }

                if (oldV === undefined){
                    oldV = $rootScope.mcStorage[storage][id];
                }

                if (this.config.pathAlias){
                    newV = serviceList.trim(newV.replace(/\W/ig, ""));

                    this.$setValue(newV);
                } else
                if (this.config.view === "checkbox"){
                    newV = serviceList.convertIntToBool(newV);
                    oldV = serviceList.convertIntToBool(oldV);
                } else
                if (this.config.mcType === "port"){
                    valid = !isNaN(parseInt(newV)) && newV && (newV > 0) && (newV < 65534); //^\d+$/.test(newV);
                } else
                // if (this.config.id === "stgRobotAvatarNum"){
                //     valid = (/^\d+$/.test(newV)) && (newV >= 0) && (newV <= 239);
                // } else
                // if (this.config.id === "stgMaxUploadFileSize"){
                //     newV = serviceList.convertIntToBool(newV);
                // } else
                if (this.config.id === "stgRobotName"){
                    valid = serviceList.trim(newV) !== "";
                } else
                if (this.config.id === "stgExternalIP" || this.config.id === "stwExternalIP"){
                    valid = serviceList.isValidIpAddress(newV) || serviceList.isValidHostName(newV) ? serviceList.trim(newV).indexOf(":") === -1 : false;

                    if (!valid){
                        newV = $rootScope.mcStorage[storage][id];
                    }

                    changeApplyData(id, newV);
                }

                if (valid && (mcConst.isShowed === $scope.Name) && (serviceList.correctStringWithCRLF(newV) !== oldV)){
                    if (this.config.pathAlias && !newV){
                        newV = oldV;
                    }

                    changeApplyData(id, newV);
                } else {
                    if (((this.config.view !== 'text') && (this.config.view !== 'textarea'))) {
                        this.$setValue(oldV);
                    }
                }
            };

            $scope.changeExc = function () {
                if (this.config && this.config.view === 'counter' || this.config.view === 'text') {
                    var newV = this.getValue();
                    var oldV = $scope.data[serviceList.copy(this.config.id, 3)];

                    if (!isNaN(newV)) {
                        $scope.change.apply(this, [newV, oldV]);
                    } else {
                        $scope.change.apply(this, [oldV, 1]);
                        this.$setValue(oldV);
                    }
                }
            };

            $scope.applyChange = function(data){
                // webix.message(applyText($scope.changedData.data.Items));

                if (cancelSave && cancelSave()){
                    return;
                }

                if ($scope._onApply){
                    $scope._onApply(data);
                }

                ["BackupsDir", "FTPPublicDir", "FTPPersonalDir", "FilesDir", "LogsDir"].forEach(function (t) {
                    checkLastSlash(data, t);
                });

                var res = JSON.stringify(data, function(key, value){
                    return value;
                });

                $rootScope.$broadcast('SendCMDToServer', [
                    CMD,
                    mcConst.SessionID,
                    res,
                    function () {
                        webix.message(applyText(backupItems));
                        
                        $rootScope.mcStorage[storage] = serviceList.Marge($rootScope.mcStorage[storage], backupItems || {});
                    }
                ]);

                $rootScope.$broadcast('SendCMDToServer', [
                    mcConst._CMD_.cs_adm_get_server_options,
                    mcConst.SessionID
                ]);
            };
        },

        openNewWindow : function(url, blank){
            var newWindow = window.open(url, blank ? '_blank' : '', blank ? '' : 'menubar=no,resizable=0,directories=no,location=no,toolbar=no,status=no,screenX=0,screenY=0,width=1000,height=600');

            if (newWindow) newWindow.focus();
        },

        onOffToBool : function(val){
            return serviceList.isBoolean(val) ? val : val == 'on';
        },

        InObject : function(item, obj){
            var res = false;

            for(var i in obj) {
                if (item == obj[i]) {
                    res = true;
                    break;
                }
            }

            return res;
        },

        getIdxByIdFromData : function(data, id){
            var res = null;

            for (var i = 0; i < data.length; i ++){
                if (data[i].id == id) {
                    res = i;
                    break;
                }
            }

            return res;
        },

        getIdxByNameFromData : function(data, key, val){
            var res = null;

            for (var i = 0; i < data.length; i ++){
                if ((data[i][key] !== undefined) && (data[i][key] == val)) {
                    res = i;
                    break;
                }
            }

            return res;
        },

        getValueByNameFromData : function(data, key, val){
            var res = "";

            for (var i = 0; i < data.length; i ++){
                if ((data[i][key] !== undefined) && (data[i][key] == val)) {
                    res = data[i][key];
                    break;
                }
            }

            return res;
        },

        oldLicenseKey : function(key){
            var res = false;
            var reg = /^([A-Z0-9]{4}\-){6}[A-Z0-9]{4}$/;

            res = reg.test(serviceList.trim(key));

            if (res) {
                res = serviceList.trim(key);
            }

            return res;
        },

        checkLicenseKey : function(key){
            var res = false;
            var reg = /^([A-F0-9]{4}\-){10}[A-F0-9]{4}$/;

            res = reg.test(serviceList.trim(key));

            if (res) {
                res = serviceList.trim(key);
            }

            return res;
        },

        checkGuestKey : function(key){
            var res = false;
            var reg = /^([A-F0-9]{4}\-){5}[A-F0-9]{4}$/;

            res = reg.test(serviceList.trim(key));

            if (res) {
                res = serviceList.trim(key);
            }

            return res;
        },

        checkQuizKey : function(key){
            var res = false;
            var reg = /^[A-F0-9]{6,8}\-([A-F0-9]{4}\-){3}[A-F0-9]{4}$/;

            res = reg.test(serviceList.trim(key));

            if (res) {
                res = serviceList.trim(key);
            }

            return res;
        },

        renameProperty : function (oldName, newName) {
            if (this.hasOwnProperty(oldName)) {
                this[newName] = serviceList.trim(this[oldName]);

                delete this[oldName];
            }
            return this;
        },

        ListButtonSet: function(opt){
            function add(){
                $$(_idBtn).hide();
                $$(_idBFld).show();

                if (options.actions.beforeAdd){
                    options.actions.beforeAdd();
                }

                if ( options.hideSearchField ) $$(options.hideSearchField).hide();

                if (items.fields[0]) {
                    if (types[options.type] !== types.combo) {
                        items.fields[0].setValue("");
                        if (items.fields[1]) {
                            items.fields[1].setValue("");
                        }
                    }

                    items.fields[0].focus();

                    if (options.actions.preview){
                        options.actions.preview(items.fields[0]);
                    }
                } else {
                    throw new Error('No fields found!');
                }

                if (options.actions.afterShow){
                    options.actions.afterShow(items.fields);
                }
            }

            function save (){
                if (options.actions.save && options.actions.save.apply(null, items.fields)){
                    back();
                }
            }

            function back (){
                if (items.fields[0] && items.fields[0].hasOwnProperty('getPopup')){
                    items.fields[0].getPopup().hide();
                }

                $$(_idBFld).hide();
                $$(_idBtn).show();

                if ( options.hideSearchField ) $$(options.hideSearchField).show();

                if (options.actions.afterClose){
                    options.actions.afterClose();
                }
            }

            function del(){
                var _list        = $$(options.list);
                var selectedItem = _list.getSelectedItem();

                if (selectedItem && options.actions.del){
                    var len     = selectedItem[options.selectedItem] ? selectedItem[options.selectedItem].length : 0;
                    var delText = serviceList.Lang(1078, len > 15 ? serviceList.copy(selectedItem[options.selectedItem], 0, 15) + "..." : selectedItem[options.selectedItem]); // "1078":"Удалить %s из списка?",

                    if (serviceList.isArray(selectedItem)){
                        delText = serviceList.Lang(1142); // "1142":"Удалить отмеченные элементы?",
                    }

                    options.regShowWindow(serviceList.RandomHash(4), webix.confirm({
                        type  : "confirm-warning",
                        text  : delText,
                        ok    : serviceList.Lang(581), // "581": "Удалить",
                        cancel: serviceList.Lang(103), // "103": "Отмена",
                        callback:function(yes){
                            if (yes){
                                options.actions.del(selectedItem, _list);

                                if (serviceList.isArray(selectedItem)){
                                    for (var i = 0; i < selectedItem.length; i ++){
                                        _list.remove(selectedItem[i].id);
                                    }

                                    webix.message(serviceList.Lang(1506)); // "1506":"Элементы удалены",
                                } else {
                                    webix.message(serviceList.Lang(1077, selectedItem[options.selectedItem])); // "1077":"%s удален",

                                    _list.remove(selectedItem.id);
                                }

                                if (options.actions.afterDel) options.actions.afterDel(_list);
                            }
                        }
                    }));
                }
            }

            function clear(){
                //if ($$(options.list).serialize().length){
                options.regShowWindow(serviceList.RandomHash(4), webix.confirm({
                    type  : "confirm-warning",
                    text  : serviceList.Lang(1072),// "1072":"Очистить список?",
                    ok    : serviceList.Lang(581), // "619": "Очистить",
                    cancel: serviceList.Lang(103), // "103": "Отмена",
                    callback:function(yes){
                        if (yes){
                            if (options.actions.clear()){
                                $$(options.list).clearAll();
                            }
                        }
                    }
                }));
                //}
            }

            function afterRender(){
                // items.fields[0] = this;

                webix.UIManager.addHotKey("Enter", function() {
                    save();

                    return false;
                }, this);

                webix.UIManager.addHotKey("Esc", function() {
                    back();

                    return false;
                }, this);
            }
            
            function edit() {
                var _list        = $$(options.list);
                var selectedItem = _list.getSelectedItem();

                if (selectedItem && options.actions.save) {
                    $$(_idBtn).hide();
                    $$(_idBFld).show();

                    if ( options.hideSearchField ) $$(options.hideSearchField).hide();

                    items.fields[0].setValue(selectedItem.IP);
                    items.fields[1].setValue(selectedItem.ClientType);
                    items.fields[2].setValue(selectedItem.Comment);
                    items.fields[3] = selectedItem.id;

                    items.fields[0].focus();

                    if (options.actions.preview){
                        options.actions.preview(items.fields[0]);
                    }

                    if (options.actions.afterShow){
                        options.actions.afterShow(items.fields);
                    }
                }
            }

            var types   = {
                'textComment'   : 1,
                '1'             : 1,
                'text'          : 2,
                '2'             : 2,
                'combo'         : 3,
                '3'             : 3,
                'textRichselectComment' : 4,
                '4'             : 4,
                'textRichselectCommentEdit' : 5,
                '5'             : 5
            };

            var options = serviceList.Marge({
                type   : types.textComment,
                idBtn  : serviceList.RandomHash(10),
                gravity: 1,
                actions: {
                    del    : null,
                    clear  : null,
                    save   : null,
                    preview: null,
                    enable : null,
                    disable: null,
                    beforeAdd: null,
                    afterClose: null
                },
                text : {
                    field1 : "", // "1075":"Введите IP, маску или диапазон",
                    field2 : "", // serviceList.Lang(1032) "1032":"Комментарий:",
                    field3 : ""  // serviceList.Lang(1032) "1032":"Комментарий:",
                },
                visibleBtns: {
                    add: true,
                    del: true,
                    cls: true
                },
                titleBtns: {
                    add: "",
                    del: "",
                    cls: "",
                    save: "",
                    cancel: "",
                },
                list : null,
                listData : [],
                regShowWindow: null,
                selectedItem: 'value'
            }, opt || {});

            var res     = null;

            if (serviceList.isFunction(options._back)){
                options._back(back);
            }

            if (!options.regShowWindow){
                var errtext = 'Needs \"regShowWindow\" for show popup windows in ' + options.list;

                console.error(errtext);

                webix.message({ type:"error", text: errtext, expire : mcConst.errMessageExpire * 2});
            } else {
                var _idBtn  = options.idBtn;
                var _idBFld = serviceList.RandomHash(10);
                var items   = {
                    fieldBlock: null,
                    fields    : [],
                    list      : null
                };

                res = { gravity: options.gravity, rows : [
                    { id: _idBtn, cols: [] // блок с кнопками: добавить удалить очистить
                        .concat([{ view: "button", value: options.titleBtns.add || serviceList.Lang(50), hidden: !options.visibleBtns.add, click: add, css: "applyButton"}]) // "50" : "Добавить",
                        .concat([{ view: "button", value: options.titleBtns.del || serviceList.Lang(52), hidden: !options.visibleBtns.del, click: del, type: "danger"}])     // "52" : "Удалить",
                        .concat(types[options.type] === types.textRichselectCommentEdit ? [{ view: "button", value: serviceList.Lang(51), hidden: !options.visibleBtns.del, click: edit}] : [])    // "51" : "Изменить",
                        .concat(options.visibleBtns.cls ? [{ view: "button", value: options.titleBtns.cls || serviceList.Lang(619), hidden: !options.visibleBtns.cls,click: clear}] : []) // "619": "Очистить",
                    },
                    { id: _idBFld, hidden: true, rows: [{}]} // появляющийся блок
                ], on: {
                    onShow: function (){
                        
                    }
                }};

                res.emit = {
                    add : add,
                    edit: edit,
                    del : del
                };

                switch (types[options.type]){
                    case types.textComment:
                        res.rows[1].rows = [
                            { view: "text", placeholder: options.text.field1, on: { onAfterRender: function() { items.fields[0] = this; afterRender.apply(this, arguments); } } },
                            { cols: [
                                { view: "text", placeholder: options.text.field2, // id: "ipfAcceptAddComment",
                                    on: { onAfterRender: function() { items.fields[1] = this; afterRender.apply(this, arguments); } }
                                },
                                { view: "button", value: options.titleBtns.save || serviceList.Lang(104), width: 100, click: save}, // "104": "Сохранить",
                                { view: "button", value: options.titleBtns.cancel || serviceList.Lang(103), width: 100, click: back, type: "danger"}  // "103": "Отмена",
                            ]}
                        ];
                    break;

                    case types.text:
                        res.rows[1].cols = [
                            { view: "text", placeholder: options.text.field1, on: { onAfterRender: function() { items.fields[0] = this; afterRender.apply(this, arguments); } } },// "1075":"Введите IP, маску или диапазон",
                            { view: "button", value: options.titleBtns.save || serviceList.Lang(104), width: 100, click: save}, // "104": "Сохранить",
                            { view: "button", value: options.titleBtns.cancel || serviceList.Lang(103), width: 100, click: back, type: "danger"} // "103": "Отмена",
                        ];
                    break;

                    case types.combo:
                        res.rows[1].cols = [
                            { view: "combo", options: options.listData, on: { onAfterRender: function() { items.fields[0] = this; afterRender.apply(this, arguments); } } },
                            { view: "button", value: options.titleBtns.save || serviceList.Lang(104), width: 100, click: save, css: "applyButton"}, // "104": "Сохранить",
                            { view: "button", value: options.titleBtns.cancel || serviceList.Lang(103), width: 100, click: back, type: "danger"} // "103": "Отмена",
                        ];
                    break;

                    case types.textRichselectComment:
                        res.rows[1].rows = [
                            { cols: [
                                { view: "text", placeholder: options.text.field1, on: { onAfterRender: function() { items.fields[0] = this; afterRender.apply(this, arguments); } } },
                                { view: "richselect", placeholder: options.text.field2, options: options.listData, on: { onAfterRender: function() { items.fields[1] = this } } }
                            ]},
                            { cols: [
                                { view: "text", placeholder: options.text.field3, // id: "ipfAcceptAddComment",
                                    on: { onAfterRender: function() { items.fields[2] = this; afterRender.apply(this, arguments); } }
                                },
                                { view: "button", value: options.titleBtns.save || serviceList.Lang(104), width: 100, click: save}, // "104": "Сохранить",
                                { view: "button", value: options.titleBtns.cancel || serviceList.Lang(103), width: 100, click: back, type: "danger"}  // "103": "Отмена",
                            ]}
                        ];
                    break;

                    case types.textRichselectCommentEdit:
                        res.rows[1].rows = [
                            { cols: [
                                { view: "text", placeholder: options.text.field1, on: {
                                    onAfterRender: function() {
                                        items.fields[0] = this;
                                        afterRender.apply(this, arguments);
                                    } } },
                                { view: "richselect", placeholder: options.text.field2, options: options.listData, on: {
                                    onAfterRender: function() {
                                        items.fields[1] = this
                                    } } }
                            ]},
                            { cols: [
                                { view: "text", placeholder: options.text.field3, // id: "ipfAcceptAddComment",
                                    on: { onAfterRender: function() { items.fields[2] = this; afterRender.apply(this, arguments); } }
                                },
                                { view: "button", value: options.titleBtns.save || serviceList.Lang(104), width: 100, click: function () {save(types[options.type])}}, // "104": "Сохранить",
                                { view: "button", value: options.titleBtns.cancel || serviceList.Lang(103), width: 100, click: back, type: "danger"}  // "103": "Отмена",
                            ]}
                        ];
                    break;
                }
            }

            return res;
        },

        findItemInArrayOfObj: function( arr, val, id, lowerCase ){
            for (var i = 0; i < arr.length; i++){
                if (!id){
                    for (var j in arr[i]){
                        if (lowerCase){
                            if (arr[i][j].toLowerCase() == val.toLowerCase()){
                                return i;
                            }
                        } else
                        if (arr[i][j] == val){
                            return i;
                        }
                    }
                } else
                if (lowerCase){
                    if (arr[i][id].toLowerCase() == val.toLowerCase()){
                        return i;
                    }
                } else
                if (arr[i][id] == val){
                    return i;
                }
            }

            return -1;
        },

        ContextMenu : function(_master, oldMenu, cb, onShow, removePrev, addItems, itemsCB){
            function clearSelection() {
                if (master.unselectAll){
                    master.unselectAll()
                } else {
                    master.clearSelection();
                }
            }

            function selectAll (){
                if (master.openAll){
                    master.openAll();
                }

                // clearSelection();

                if (master.selectAll){
                    master.selectAll();
                } else {
                    master.selectRange(master.getFirstId(), master.getLastId());
                }
            }

            var master = _master || {};

            if (oldMenu && removePrev) {
                oldMenu.destructor();
            }

            if (!master.contextTitle) {
                master.contextTitle = {};
            }

            var contextMenu = !oldMenu || (oldMenu && removePrev) ? {
                view  : "context",
                width : 220,
                master: master,
                body  : { rows: []},
                on: {
                    onShow: function() {
                        if (onShow) onShow.apply(this, []);
                    }
                }
            } : [];

            var items = [
                { view: "button", value: serviceList.Lang(master.contextTitle.all || 1105), click: function(){ // "1105":"Выделить все",
                    var context = this.getTopParentView();

                    if (cb) cb.call(context.getContext().obj, 'beforeSelectAll');

                    selectAll();

                    if (cb) cb.call(context.getContext().obj, 'selectAll');

                    context.hide();
                }},
                { view: "button", value: serviceList.Lang(master.contextTitle.clear || 1106), click: function(){ // "1106":"Снять выделение",
                    var context = this.getTopParentView();

                    if (cb) cb.call(context.getContext().obj, 'beforeClear');

                    clearSelection();

                    if (cb) cb.call(context.getContext().obj, 'clear');

                    context.hide();
                }},
                { view: "button", value: serviceList.Lang(master.contextTitle.invert || 1107), click: function(){ // "1107":"Инвертировать выделение",
                    var selectedItems = master.getSelectedItem();
                    var context = this.getTopParentView();

                    if (cb) cb.call(context.getContext().obj, 'beforeInvert');

                    if (serviceList.isObject(selectedItems)){
                        selectedItems = [selectedItems];
                    }

                    selectAll();

                    for (var i = 0; selectedItems && i < selectedItems.length; i ++){
                        master.unselect(selectedItems[i].id);
                    }

                    if (cb) cb.call(context.getContext().obj, 'invert');

                    context.hide();
                }}
            ].concat(addItems && addItems.length && itemsCB && itemsCB.length === addItems.length
                ? addItems.map(function (item, id){
                    item.click = function () {
                        var selectedItems = master.getSelectedItem(true);
                        
                        itemsCB[id](selectedItems);

                        this.getTopParentView().hide();
                    }

                    return item;
                })
                : []
            );

            if (oldMenu === true && !removePrev){
                contextMenu = items;
            } else {
                contextMenu.body.rows = contextMenu.body.rows.concat(items);
                
                contextMenu = webix.ui(contextMenu);
            }

            return contextMenu;
        },

        getAllUserForFillList: function(list, cb, $rootScope){
            var usersCash = $rootScope.mcStorage[mcConst.dataModels.UserManager];

            if (usersCash && usersCash.Params === "All" && !list.length){
                cb.apply(usersCash, [usersCash.Users]);
            } else
            if (!usersCash || usersCash.Params !== "All" || (list.length > 0 && usersCash.Users.length !== list.length)){
                $rootScope.$broadcast('getAllUserList', [false, function() {
                    usersCash = $rootScope.mcStorage[mcConst.dataModels.UserManager];

                    cb.apply(usersCash, [usersCash.Users]);
                }]);
            }
        },

        getAllUserWithParams: function(cb, $rootScope, params){
            var usersCash = $rootScope.mcStorage[mcConst.dataModels.UserManager];

            if (usersCash && usersCash.Params === "All" &&  params === "All"){
                cb.apply(usersCash, [usersCash.Users]);
            } else {
                $rootScope.$broadcast('SendCMDToServer', [
                    mcConst.lockInterface,
                    mcConst._CMD_.cs_adm_list_users,
                    mcConst.SessionID,
                    !params ? 'All' : params.Params || 'All',
                    !params ? mcConst.userFields : params.Filters || mcConst.userFields,
                    function (data) {
                        $rootScope.mcStorage.UserManager = data;

                        cb.apply(data, [data.Users]);
                    }
                ]);
            }
        },

        getMcSettings: function($rootScope, show, always){
            if (!$rootScope.mcStorage.SettingsNetwork || always){
                $rootScope.$broadcast('SendCMDToServer', [
                    mcConst.lockInterface,
                    mcConst._CMD_.cs_adm_get_server_options,
                    mcConst.SessionID,

                    function (data){
                        $rootScope.mcStorage.SettingsNetwork = data;

                        $rootScope.$broadcast('SendCMDToServer', [
                            mcConst.lockInterface,
                            mcConst._CMD_.cs_adm_get_server_info,
                            mcConst.SessionID,

                            function( data ){
                                $rootScope.mcStorage.ServerInfo = data;

                                if ( show ) show();
                            }
                        ]);
                    }
                ]);
            } else {
                if ( show ) show();
            }
        },

        getTitle: function(path){
            var res = "";

            switch (path){
                case "ServerInfo":      res = serviceList.Lang(10);   break; // "10" : "Информация о сервере",
                case "UserManager":     res = serviceList.Lang(49);   break; // "49" : "Список пользователей",
                case "UserProfile":     res = serviceList.Lang(1484); break; // "1484":"Профиль пользователя",
                case "GroupRights":     res = serviceList.Lang(57);   break; // "57" : "Группы прав"
                case "GroupUsers":      res = serviceList.Lang(916);  break; // "916":"Пользователи групп",
                case "Contacts":        res = serviceList.Lang(587);  break; // "587": "Общий список контактов",
                case "WorkPositions":   res = serviceList.Lang(588);  break; // "588": "Должности компании",
                case "WebSupport":      res = serviceList.Lang(938);  break; // "938":"Web-поддержка",
                case "MyChatGuest":     res = serviceList.Lang(945);  break; // "945":"MyChat Guest",
                case "LicenseMyChat":   res = serviceList.Lang(944);  break; // "944":"MyChat Server",
                case "LicenseGuest":    res = serviceList.Lang(945);  break; // "945":"MyChat Guest",
                case "UsersImports":    res = serviceList.Lang(960);  break; // "960":"Импорт пользователей",
                case "Operators":       res = serviceList.Lang(817);  break; // "817":"Модераторы",
                case "Bans":            res = serviceList.Lang(1029); break; // "1029":"Забаненные пользователи",
                case "AutoChannels":    res = serviceList.Lang(1039); break; // "1039":"Автосоздаваемые конференции",
                case "RemoteSettings":  res = serviceList.Lang(1329); break; // "1329":"Дистанционные настройки клиента",
                case "History":         res = serviceList.Lang(1134); break; // "1134":"Просмотр протоколов",
                case "FilterAntiflood": res = serviceList.Lang(1057); break; // "1057":"Антифлуд",
                case "FilterBadWords":  res = serviceList.Lang(1058); break; // "1058":"Плохие слова",
                case "FilterIP":        res = serviceList.Lang(1059); break; // "1059":"IP фильтры",
                case "FilterMAC":       res = serviceList.Lang(1060); break; // "1060":"MAC фильтры",
                case "FilterNames":     res = serviceList.Lang(1061); break; // "1061":"Блокировки названий",
                case "SettingsNetwork": res = serviceList.Lang(591);  break; // "591": "Сетевые настройки MyChat",
                case "SettingsInfo":    res = serviceList.Lang(596);  break; // "596": "Информация",
                case "SettingsLogs":    res = serviceList.Lang(598);  break; // "598": "Протоколирование",
                case "SettingsFTP":     res = serviceList.Lang(599);  break; // "599": "FTP файловый сервер",
                case "SettingsWEB":     res = serviceList.Lang(600);  break; // "600": "WEB-сервисы",
                case "SettingsBackup":  res = serviceList.Lang(601);  break; // "601": "Резервные копии",
                case "SettingsService": res = serviceList.Lang(602);  break; // "602": "Обслуживание",
                case "SettingsAPI":     res = serviceList.Lang(603);  break; // "603": "Integration API",
                case "SettingsSMTP":    res = serviceList.Lang(921);  break; // "921":"Настройка SMTP",
                case "Others":          res = serviceList.Lang(672);  break; // "672": "Дополнительно",
            }

            return res;
        },

        formatDeptList: function( data ){
            var separator = '';
            var levels = {};
            var i = 0;
            var res = [];
            var normalData = [];
            var idx;
            var deptList = [];

            while (data.length > 0 || i < data.length){
                if (data[i].ParentID == 0) {
                    normalData.push(data[i]);

                    data.splice(i, 1);
                } else {
                    idx = serviceList.findItemInArrayOfObj(normalData, data[i].ParentID, "GroupID");

                    if (idx == -1) {
                        i++;
                    } else {
                        normalData.push(data[i]);

                        data.splice(i, 1);
                    }
                }

                if (data.length > 0 && i == data.length){
                    i = 0;
                }
            }

            for (i = 0; i < normalData.length; i++){
                if (normalData[i].ParentID == 0) {
                    levels[normalData[i].GroupID] = 0;
                } else {
                    levels[normalData[i].GroupID] = levels[normalData[i].ParentID] + 1;
                }

                separator = new Array(levels[normalData[i].GroupID] + 1).join('&#151;');

                res.push({
                    id: normalData[i].GroupID,
                    value: separator + normalData[i].GroupName
                });
            }

            for (i = 0; i < normalData.length; i++){
                if (normalData[i].ParentID == 0){
                    deptList.push(res[i]);
                } else {
                    deptList.splice(serviceList.findItemInArrayOfObj(deptList, normalData[i].ParentID, "id") + 1, 0, res[i]);
                }
            }

            return deptList;
        },

        copyToBuffer: function(){
            setTimeout(function(){
                webix.message(serviceList.Lang(1483)); // "1483":"Лог скопирован в буфер обмена!",
            }, 10);

            return $$("logsContainer").$view.innerText.replace(/\n/g, '\r\n');
        },

        convertDomainToDn : function(dn){
            if (dn.indexOf('=') === -1){
                dn = dn.split('.');

                for (var i = 0; i < dn.length; i++){
                    dn[i] = 'DC=' + dn[i];
                }

                dn = dn.join(',').toUpperCase();
            } else {
                dn = dn.toUpperCase();
            }

            return dn;
        },

        convertDataToTree: function(data, withMe){
            function add(current, parent){
                var item = null;

                if (current.IsGroup){
                    item = {
                        DisplayName: current.GroupName || current.DisplayName,
                        id         : current.ID || current.id,
                        ParentID   : current.ParentID,
                        GroupID    : current.GroupID,
                        IsGroup    : true,
                        folder     : true,
                        data       : current.data || []
                    };
                } else {
                    if (!withMe && current.UIN == mcConst.UserInfo.UIN){
                        return false;
                    }

                    item = {
                        DisplayName: current.DisplayName,
                        id         : current.ID || current.id,
                        ParentID   : current.ParentID,
                        State      : current.State,
                        onlineState: current.onlineState,
                        UIN        : current.UIN,
                        TeamLead   : current.TeamLead
                    };
                }

                parent.push(item);

                return true;
            }

            var res = [];
            var structure = [];
            var _data = [].concat(data);
            var i = 0;
            var needAdd = null;

            while (_data.length > 0){
                if (_data[i].ParentID == -1){
                    _data.splice(i, 1);
                } else
                if (_data[i].ParentID == 0){
                    needAdd = add(_data[i], res);
                    if (needAdd) add(res[res.length - 1], structure);

                    _data.splice(i, 1);
                } else {
                    var idx = serviceList.findItemInArrayOfObj(structure, _data[i].ParentID, "GroupID");

                    if (idx >= 0){
                        needAdd = add(_data[i], structure);
                        if (needAdd) add(structure[structure.length - 1], structure[idx].data);

                        _data.splice(i, 1);
                    } else {
                        i++;
                    }
                }

                if (i == _data.length) {
                    i = 0;
                }
            }

            return res;
        },

        hideFrame: function(id){
            var view = document.getElementById(id);

            if (view){
                view.style.display = 'none';
            }
        },

        showFrame: function(id, inline){
            var view = document.getElementById(id);

            if (view){
                view.style.display = (inline ? 'inline-' : '') + 'block';
            }
        },

        getFrame: function(id, src){
            var res = src || document.getElementById(id);

            if (res && !res.showFrame && !res.hideFrame) {
                res.showFrame = function() {
                    serviceList.showFrame(id) };
                res.hideFrame = function() {
                    serviceList.hideFrame(id) };
            }

            return res;
        },

        convertObjToArray: function(obj, keyToField, copy){
            var res = [];

            for (var i in obj){
                if (serviceList.isObject(obj[i])){
                    if (keyToField) obj[i][keyToField] = i;

                    res.push(copy ? serviceList.Marge({}, obj[i]) : obj[i] );
                }
            }

            return res;
        },

        getStateStatuses: function(state){
            return {
                Online  : state.Online  ? serviceList.StringToArray(state.Online, ',') : serviceList.StringToArray(state.Users, ','),
                Offline : state.Offline ? serviceList.StringToArray(state.Offline, ',') : [],
                States  : state.States  ? serviceList.StringToArray(state.States, ',') : serviceList.StringToArray(state.Statuses, ',')
            };
        },

        addEvent : function(object, type, callback) {
            if (object == null || typeof(object) == 'undefined') return;
            if (object.addEventListener) {
                object.addEventListener(type, callback, false);
            } else if (object.attachEvent) {
                object.attachEvent("on" + type, callback);
            } else {
                object["on"+type] = callback;
            }
        },

        getWindowSize: function(){
            // var wndWidth = 0;
            // var wndHeight = 0;

            /*if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
                wndWidth = document.documentElement.clientWidth;
                wndHeight = document.documentElement.clientHeight;
            } else {
                if (typeof (window.innerWidth) == 'number') {
                    wndWidth = window.innerWidth;
                    wndHeight = window.innerHeight;
                } else
                if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
                    wndWidth = document.body.clientWidth;
                    wndHeight = document.body.clientHeight;
                }
            }*/

            // return {
            //     width  : wndWidth,
            //     height : wndHeight
            // };
            let {
                    width,
                    height,
                    widthOrig = null,
                    heightOrig = null,
                    scale,
                    offsetTop,
                    offsetLeft,
                    offsetTopOrig = null,
                    offsetLeftOrig = null,
                    pageLeft,
                    pageTop,
                    pageLeftOrig = null,
                    pageTopOrig = null,
                } = window.visualViewport;

            return Object.fromEntries(
                Object.entries({
                    width,
                    widthOrig,
                    height,
                    heightOrig,
                    scale,
                    offsetTop,
                    offsetTopOrig,
                    offsetLeft,
                    offsetLeftOrig,
                    pageLeft,
                    pageLeftOrig,
                    pageTop,
                    pageTopOrig,
                }).filter(([k, v]) => v !== null));
        },

        convertDnArrayToTreeTable: function(dnList, base){
            var res = [];
            var dnStructure = {};
            var dn = "distinguishedName";

            function createStructure(obj, structure, parent){
                function add (arr, item){
                    var link = {
                        value: item
                    };

                    if (structure.length === 0){
                        link = serviceList.Marge(link, obj);
                    } else {
                        // link['$row'] = "value";
                        // link['$parent'] = 0;
                        link['open'] = true;
                        link['data'] = [];
                    }

                    arr.push(link);

                    return link;
                }

                if (structure.length){
                    var item = structure.pop().replace('CN=', '');

                    parent = parent || res;

                    var idx = serviceList.findItemInArrayOfObj(parent, item, "value");

                    createStructure(obj, structure, idx === -1 ? add(parent, item).data : parent[idx].data);
                }
            }

            for (var i = 0; i < dnList.length; i++){
                dnList[i][dn] = dnList[i][dn].replace(',' + base, '');

                if (!dnStructure[dnList[i][dn]]){
                    dnStructure[dnList[i][dn]] = dnList[i][dn].split(',');

                    createStructure(dnList[i], [].concat(dnStructure[dnList[i][dn]]), null);
                }
            }

            return res;
        },

        makeIceServers: function (data, isWeb){
            /** @namespace data.IPList*/
            /** @namespace data.TURNPort*/
            /** @namespace data.Credential */
            var res = [];
            var ipList = data.IPList.split(',');

            if (ipList[0] === ""){
                ipList[0] = isWeb ? window.location.host : mcConst.ServerInfo.Host;
            }

            for (var i = 0; i < ipList.length; i++){
                var tcp  = 'turn:' + ipList[i] + ':' + data.TURNPort +'?transport=tcp';
                var udp  = 'turn:' + ipList[i] + ':' + data.TURNPort +'?transport=udp';
                var stun = 'stun:' + ipList[i] + ':' + data.TURNPort;

                res.push({
                    urls       : [stun, udp, tcp],
                        //.concat(isWeb ? tcp : [])
                        // .concat(isWeb || (mcConst.ClientSettings && mcConst.ClientSettings.MediaNetworkTransportTCP) ? stun : [])
                        // .concat(isWeb || (mcConst.ClientSettings && mcConst.ClientSettings.MediaNetworkTransportTCP) ? tcp  : [])
                        // .concat(isWeb || (mcConst.ClientSettings && mcConst.ClientSettings.MediaNetworkTransportUDP) ? udp  : []),
                        // .concat(['stun:stun.l.google.com'])

                    credential : data.Credential,
                    username   : data.UserName
                });
            }

            return res;
        },

        checkRightsCollection: function(_ids, equals){
            var res = false;
            var id = null;

            for (var i = 0; i < _ids.length; i ++){
                id = parseInt(_ids[i]) - 1;

                res = equals
                    ? res && (mcConst.MyRightsSet.RightsSet[id] == '1')
                    : res || (mcConst.MyRightsSet.RightsSet[id] == '1');
            }

            return res;
        },

        MyTimer: function (_target){
            var Now = 0;
            var id = null;
            var view = _target;

            this.setView = function (newView) {
                view = newView;
            };

            this.Start = function(){
                Now = 0;

                clearInterval(id);

                id = setInterval(function(){
                    Now++;

                    if (view) {
                        if (view.innerText !== void(0)) {
                            view.innerText = serviceList.MyFormatTime(Now, true);
                        } else {
                            view.text(serviceList.MyFormatTime(Now, true));
                        }
                    }
                }, 1000);
            };

            this.Pause = function(newTime){
                clearInterval(id);

                Now = newTime || Now;
            };

            this.Stop = function(){
                if (view) {
                    if (view.innerText !== void(0)) {
                        view.innerText = serviceList.MyFormatTime(0, true);
                    } else {
                        view.text(serviceList.MyFormatTime(0, true));
                    }
                }

                clearInterval(id);

                return serviceList.MyFormatTime(Now, true);
            };
        },
        
        openLink: function(link, param){
            var params = param || "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes";
            window.open(link, "", params);
        },

        showImportResult: function(data, $rootScope){
            var add = [];
            var upd = [];
            var err = [];
            var res = "";

            if (data && data.Results && data.Users){
                for (var i = 0; i < data.Results.length; i++){
                    switch (data.Results[i]){
                        case '0': case 0:
                        add.push(data.Users[i]);
                        break;

                        case '1': case 1:
                        upd.push(data.Users[i]);
                        break;

                        default:
                            err.push(data.Users[i]);
                    }
                }

                if (add.length){
                    res += "<span class='blue _grName'>" + mcLang(1552) + '</span><br>';// "1552":"Добавлены пользователи:",
                    res += add.join('<br>') + '<br><br>';
                }

                if (upd.length){
                    res += "<span class='green _grName'>" + mcLang(1553) + '</span><br>';// "1553":"Обновлены пользователи:",
                    res += upd.join('<br>') + '<br><br>';
                }

                if (err.length){
                    res += "<span class='red _grName'>" + mcLang(1554) + '</span><br>';// "1554":"Не добавленные пользователи:",
                    res += err.join('<br>');
                }

                var wnd = webix.ui({
                    view: "window",
                    position: "center",
                    modal: true,
                    width: 300,
                    height: 500,
                    head: mcLang(971), // "971":"Результат:",
                    body: { rows: [
                        { template: res, borderless: true, scroll: "y" },
                        { view: "button", value: mcLang(12), click: function(){
                            this.getTopParentView().hide();
                            this.getTopParentView().destructor();
                        }} //"12" : "Закрыть",
                    ]}
                });

                $rootScope.regShowWindow(serviceList.RandomHash(4), wnd);

                wnd.show();

                if (add.length || upd.length){
                    $rootScope.$broadcast('refreshUserManagerOnNextShow', []);
                }
            }
        },

        getUserStateText: function(_state, noColor){
            var state = "";

            switch (_state){
                case mcConst.states.offline:
                    state = serviceList.Lang(1641); break; // "1641":"Не в сети",

                case mcConst.states.online:
                    state = (!noColor ? '<span class="green">' : "") + serviceList.Lang(1642) + (!noColor ? '</span>' : ""); break; // "1642":"В сети",

                case mcConst.states.webOnline:
                    state = (!noColor ? '<span class="green">' : "") + serviceList.Lang(1642) + (!noColor ? '</span>' : ""); break; // "1642":"В сети",

                case mcConst.states.away:
                    state = (!noColor ? '<span class="blue">' : "") + serviceList.Lang(1643) + (!noColor ? '</span>' : ""); break; // "1643":"Нет на месте",

                case mcConst.states.dnd:
                    state = (!noColor ? '<span class="red">' : "") + serviceList.Lang(1644) + (!noColor ? '</span>' : ""); break; // "1644":"Не беспокоить",
            }

            return state;
        },

        getColorForStatus: function(status){
            var res = "#545454";

            status = parseInt(status);

            switch (status){
                case mcConst.states.offline: res = '#929292';  break; // "29" : "Не в сети",

                case mcConst.states.online: res = '#2bca2e'; break; // "30" : "В сети",

                case mcConst.states.webOnline: res = '#2bca2e'; break; // "30" : "В сети",

                case mcConst.states.away: res = 'blue'; break; // "31" : "Нет на месте",

                case mcConst.states.dnd: res = 'red'; break; // "32" : "Не беспокоить",
            }

            return res;
        },

        smartReplace: function (source, options) {
            for (var item in options){
                source = source.replace(new RegExp('{' + item + '}', 'g'), options[item]);
            }

            return source;
        },

        getIPs: function (callback){
            function handleCandidate(candidate){
                //match just the IP address
                var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
                var ip_addr = ip_regex.exec(candidate)[1];

                //remove duplicates
                if(ip_dups[ip_addr] === undefined)
                    callback(ip_addr);

                ip_dups[ip_addr] = true;
            }

            var ip_dups = {};

            //compatibility for firefox and chrome
            var RTCPeerConnection = window.RTCPeerConnection
                || window.mozRTCPeerConnection
                || window.webkitRTCPeerConnection;
            var useWebKit = !!window.webkitRTCPeerConnection;

            //bypass naive webrtc blocking using an iframe
            if(!RTCPeerConnection){
                //NOTE: you need to have an iframe in the page right above the script tag
                //
                //<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
                //<script>...getIPs called in here...
                //
                var win = iframe.contentWindow;
                RTCPeerConnection = win.RTCPeerConnection
                    || win.mozRTCPeerConnection
                    || win.webkitRTCPeerConnection;
                useWebKit = !!win.webkitRTCPeerConnection;
            }

            //minimal requirements for data connection
            var mediaConstraints = {
                optional: [{RtpDataChannels: true}]
            };

            var servers = {iceServers: [{urls: "stun:stun.services.mozilla.com"}]};

            //construct a new RTCPeerConnection
            var pc = new RTCPeerConnection(servers, mediaConstraints);

            //listen for candidate events
            pc.onicecandidate = function(ice){

                //skip non-candidate events
                if(ice.candidate)
                    handleCandidate(ice.candidate.candidate);
            };

            //create a bogus data channel
            pc.createDataChannel("");

            //create an offer sdp
            pc.createOffer(function(result){

                //trigger the stun server request
                pc.setLocalDescription(result, function(){}, function(){});

            }, function(){});

            //wait for a while to let everything done
            setTimeout(function(){
                //read candidate info from local description
                var lines = pc.localDescription.sdp.split('\n');

                lines.forEach(function(line){
                    if(line.indexOf('a=candidate:') === 0)
                        handleCandidate(line);
                });
            }, 1000);

            //Test: Print the IP addresses into the console
            //getIPs(function(ip){console.log(ip);});
        },

        treeWalker: function(treeArray, deepIn, cb){
            if (serviceList.inArray(treeArray)){
                for (var i = 0; i < treeArray.length; i++){
                    if (cb) cb(treeArray[i]);

                    if (treeArray[i].hasOwnProperty(deepIn)) serviceList.treeWalker(treeArray[i][deepIn], deepIn, cb);
                }
            }
        },

        updateStatuses: function (stList, dataList){
            function setState(_user) {
                if (_user.hasOwnProperty("UIN")){
                    if (stList.Statuses && stList.Users) {
                        _user.onlineState = stList.Statuses[stList.Users.indexOf(_user.UIN.toString())];
                    } else {
                        // _user.State = stList[_user.UIN.toString()] !== undefined ? stList[_user.UIN.toString()] : mcConst.states.offline;
                        _user.onlineState = stList[_user.UIN] !== undefined ? stList[_user.UIN] : mcConst.states.offline;
                    }

                    dataList.updateItem && dataList.updateItem(_user.id, _user);
                }
            }

            if (stList && dataList){
                if (serviceList.isArray(dataList)){
                    dataList.forEach(setState);
                } else {
                    dataList.each(setState);
                }
            }
        },

        getCaretPosition: function  (ctrl) {
            var CaretPos = 0;

            if (document.selection) {
                var Sel = document.selection.createRange();

                Sel.moveStart('character', - ctrl.value.length);

                CaretPos = Sel.text.length;
            } else

            // Fucking firefox support
            if (ctrl.selectionStart || ctrl.selectionStart == '0'){
                CaretPos = ctrl.selectionStart;
            }

            return CaretPos;

        },

        setCaretPosition: function (ctrl, pos, selectCount){
            pos = parseInt(pos);
            
            if(ctrl.setSelectionRange) {
                ctrl.setSelectionRange(pos, pos + (selectCount || 0));
            } else
            if (ctrl.createTextRange) {
                var range = ctrl.createTextRange();

                range.collapse(true);
                range.moveEnd('character', pos + (selectCount || 0));
                range.moveStart('character', pos);
                range.select();
            }
        },

        goToHelp : function (_path, go) {
            var path = (_path || window.location.hash).replace(/^#\/|^\/|\/$/g, "");
            var localPath =  serviceList.isIE()
                ? window.location.protocol + "//" + window.location.host
                : window.location.origin;

            if (_path && _path.toLowerCase().indexOf("http") === 0){
                window.open(_path, '', '');
            } else
            if (mcConst._CMD_.accordance.hasOwnProperty(path) && mcConst._CMD_.accordance[path] !== ""){
                window.open(localPath + serviceList.myReplaceFormated(mcConst._CMD_.accordance[path], {lang: mcConst.Lang === "uk" ? "ua" : mcConst.Lang}), '', '');
                // window.open(localPath + serviceList.myReplaceFormated(mcConst._CMD_.accordance[path], {lang: mcConst.Lang === "en" ? "_en" : ""}), '', '');
            } else
            if (go){
                window.open(localPath + "/" + path, '', '');
            }
        },

        getHelpLink : function (_path, text, _class, _id) {
            var path = (_path || window.location.hash).replace(/^#\/|^\/|\/$/g, "");
            var localPath =  serviceList.isIE()
                ? window.location.protocol + "//" + window.location.host
                : window.location.origin;
            var res = "";

            if (_path){
                res = serviceList.myReplaceFormated(
                    "<a href='#{link}' target='_blank' class='#{class}' id='#{id}'>#{text}</a>",
                    {
                        text : text || "Link",
                        link : (_path.toLowerCase().indexOf("http") === 0 ? _path : localPath + _path),
                        class: _class || "",
                        id   : _id || ""
                    }
                );
            } else
            if (mcConst._CMD_.accordance.hasOwnProperty(path) && mcConst._CMD_.accordance[path] !== ""){
                res = serviceList.myReplaceFormated(
                    "<a href='${link}' target='_blank' class='${class}' id='${id}'>${text}</a>",
                    {
                        text : text || "Link",
                        link : localPath + serviceList.myReplaceFormated(mcConst._CMD_.accordance[path], {lang: mcConst.Lang === "uk" ? "ua" : mcConst.Lang}),
                        class: _class || "",
                        id   : _id || ""
                    }
                );
            }

            return res;
        },

        HotkeyManager: function(debug){
            var keyList     = {};
            var presetList  = {};
            var maxKeyLevel = 0;
            var firedKeys   = {};
            var sourceMap   = {}; // key: [marker1, marker2, ...]
            var sourceMarker= "hotKeyMarker" + serviceList.RandomHash(5);
            var disableAll  = false;
            var removeList  = [];
            var removeTimer = null;

            function convertToKeyString(obj, code){
                return  code
                    ? (obj.ctrlKey ? "ctrl+" : "") +
                      (obj.shiftKey? "shift+": "") +
                      (obj.altKey  ? "alt+"  : "") +
                      code.toString()
                    : null;
            }

            function findByKeyCode(name, key) {
                var res = null;

                presetList[name].source.forEach(function (preset) {
                    if (convertToKeyString(preset, preset.key) === key.toString()){
                        res = preset;
                    }
                });

                return res;
            }

            function registerKeys(name, preset){
                var source = preset.source;

                for (var i = 0; i < source.length; i++){
                    var key = convertToKeyString(source[i], source[i].key);

                    if (!keyList.hasOwnProperty(key)){
                        keyList[key] = {};
                    }

                    // if (debug) {
                    //     console.log(
                    //       `+ Registered key: ${key}`+
                    //       `  Preset        : ${name}` +
                    //       `  maxKeyLevel   : ${maxKeyLevel}`);
                    // }

                    keyList[key][name] = source[i].func;

                    if (sourceMap[key]){
                        sourceMap[key].push(preset.marker);
                    } else {
                        sourceMap[key] = [preset.marker];
                    }
                }

                if (preset.target && !preset.ID){
                    preset.ID = webix.event(preset.target, "keydown", function(e){
                        if (disableAll) {
                            e.preventDefault();
                            
                            return false;
                        }

                        var keyCode     = convertToKeyString(e, e.keyCode);
                        var skipDefault = false;
                        var where       = this;

                        if (keyList.hasOwnProperty(keyCode) &&
                            where.hasOwnProperty(sourceMarker) &&
                            sourceMap.hasOwnProperty(keyCode) &&
                            sourceMap[keyCode].indexOf(where[sourceMarker]) !== -1
                        ) {
                            if (!firedKeys.hasOwnProperty(keyCode)) {
                                if (debug) {
                                    console.log('Fired key: '+ keyCode);
                                    // console.log(e);
                                }

                                firedKeys[keyCode] = 'fired';

                                for (var i in keyList[keyCode]){
                                    if (where[sourceMarker] === presetList[i].marker){
                                        if (presetList[i].level >= maxKeyLevel){
                                            var _preset = findByKeyCode(i, keyCode); // находим совпадающий индекс сочетания клавиш

                                            if (!_preset._locked){                            // если у пресета нет лока - выполняем
                                                if (debug) {
                                                    console.log('Run func: '+ _preset.func.name);
                                                }

                                                skipDefault = !keyList[keyCode][i](e, where); // запуск событие
                                            } else {
                                                if (debug) {
                                                    console.log('! Func  locked: '+ _preset.func.name);
                                                    console.log('! locked value: '+ _preset._locked);
                                                }
                                            }
                                        } else {
                                            var source = presetList[i].source;

                                            for (var j in source){
                                                if (convertToKeyString(source[j], source[j].key) === keyCode){
                                                    skipDefault = source[j].skipDefault;

                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }

                                (function (key) {
                                    setTimeout(function () {
                                        delete firedKeys[key];
                                    }, 10);
                                })(keyCode);
                            }

                            if (skipDefault){
                                e.preventDefault();

                                return false;
                            }
                        }
                    });
                }
            }

            function removeKeys(_name){
                function _doRemove(name) {
                    for (var keyCode in keyList){
                        if (keyList[keyCode].hasOwnProperty(name)){
                            if (presetList[name].level === maxKeyLevel && maxKeyLevel > 0){
                                var entries = 0;
                                var prevKeyLevel = 0;

                                Object.keys(presetList).forEach(function (preset) {
                                    var curLevel = presetList[preset].level;

                                    if (curLevel === maxKeyLevel){
                                        entries ++;
                                    }

                                    if (curLevel > prevKeyLevel && curLevel < maxKeyLevel){
                                        prevKeyLevel = curLevel;
                                    }
                                });

                                if (entries === 1){
                                    maxKeyLevel = prevKeyLevel;
                                }
                            }

                            if (debug) {
                                console.log('- Removing key: ' + keyCode);
                                console.log('- Preset: ' + name);
                                console.log('- maxKeyLevel: ' + maxKeyLevel);
                            }

                            removeLock(keyCode, presetList[name].key);

                            sourceMap[keyCode].splice(sourceMap[keyCode].indexOf(presetList[name].marker), 1);

                            if (sourceMap[keyCode].length === 0){
                                delete sourceMap[keyCode];
                            }

                            delete keyList[keyCode][name];
                        }
                    }

                    if (presetList[name]){
                        webix.eventRemove(presetList[name].ID);

                        delete presetList[name];
                    }

                    if (removeList.length){
                        return _doRemove(removeList.shift());
                    } else {
                        clearTimeout(removeTimer);
                        removeTimer = null;
                    }
                }
                
                if (_name) removeList.push(_name);

                if (!removeTimer){
                    removeTimer = setTimeout(function (){
                        _doRemove(removeList.shift());
                    }, 10);
                }
            }

            function lockAction(key, except) {
                for (var i in presetList){
                    presetList[i].source.forEach(function (preset) {
                        if (i !== except && key.toString() === convertToKeyString(preset, preset.key)){
                            if (preset._locked){
                                preset._locked ++;
                            } else {
                                preset._locked = 1;
                            }

                            if (debug) {
                                console.log('Add lock key: '+ key + ', value: ' + preset._locked);
                                console.log(presetList[i]);
                            }
                        } else {
                            preset._locked = 0;
                        }
                    });
                }
            }

            function removeLock(key) {
                for (var i in presetList){
                    presetList[i].source.forEach(function (preset) {
                        if (key.toString() === convertToKeyString(preset, preset.key) && preset.hasOwnProperty("_locked") && preset._locked > 0){
                            preset._locked --;

                            if (debug) {
                                console.log('remove lock key: '+ key + ', value: ' + preset._locked);
                                console.log(preset);
                            }
                        }
                    });
                }
            }

            this.disable = function (){
                disableAll = true;
            };

            this.enable = function (){
                disableAll = false;
            };

            this.debug = function (state){
                debug = !!state;

                console.log(`HotkeyManager debugger is ${!!state ? 'on': 'off'}`);
            };

            this.showPresetList = function () {
                console.log("showPresetList");
                
                Object.keys(presetList).forEach(function (name) {
                    console.log(presetList[name]);
                    // console.log(
                    //     `========================` +
                    //     `name  : ${name}` +
                    //     `preset: ${mcService.ObjToString(presetList[name].preset)}` +
                    //     `target: ${presetList[name].target.tagName || presetList[name].target.nodeName}` +
                    //     `ID    : ${presetList[name].ID}`
                    // );
                });
            };

            this.clearPresetLevel = function (level) {
                debug && console.log("clearPresetLevel: " + level);

                Object.keys(presetList).forEach(function (name) {
                    if (presetList[name].level >= level){
                        removeKeys(name);
                    }
                })
            }

            this.addPreset = function(name, preset, target, disablePrevious){
                if (!presetList.hasOwnProperty(name)){
                    var marker = serviceList.RandomHash(10);

                    if (target.hasOwnProperty(sourceMarker)){
                        marker = target[sourceMarker];
                    } else {
                        target[sourceMarker] = marker;
                    }

                    presetList[name] = {
                        name  : name,
                        source: preset,
                        target: target,
                        marker: marker,
                        ID    : null
                    };

                    if (disablePrevious) {
                        maxKeyLevel ++;
                    }

                    preset.forEach(function (item) {
                        if (item.lockPrev){
                            lockAction(convertToKeyString(item, item.key), name);
                        }
                    });

                    presetList[name].level = maxKeyLevel;

                    if (debug) {
                        console.log("addPreset:");
                        console.log(presetList[name]);
                        // console.log(
                        //     `+++++\n` +
                        //     `addPreset: ${name}\n` +
                        //     `preset   : ${mcService.ObjToString(preset)}\n` +
                        //     `target   : ${target.tagName || target.nodeName}\n` +
                        //     `level    : ${maxKeyLevel}\n` +
                        //     `disablePrevious: ${!!disablePrevious}\n`
                        // );
                    }

                    registerKeys(name, presetList[name]);
                } else {
                    // console.warn("Preset \"" + name + "\" is present");

                    return false;
                }
            };

            this.removePreset = function(name){
                if (presetList.hasOwnProperty(name)){
                    removeKeys(name);
                } else {
                    return false;
                }
            };
        },

        utcTime: function(msg, decode, format) {
            var dt = null;

            if (mcService.isDate(msg)){
                dt = msg;
            } else
            if (serviceList.isNumber(msg)) {
                dt = new Date(msg);
            } else {
                dt = msg.dtUTC || msg.dt || msg;
            }

            return dt
                ? serviceList.formatDate(new Date(
                    (new (Function.prototype.bind.apply(
                        Date,
                        [null].concat(serviceList.formatDate(dt, "yyyy.mm.dd.hh.nn.ss.zzz").split('.').map(function (val, idx) {
                            return idx === 1 ? val - 1 : val;
                        }))
                    ))).getTime() + (new Date()).getTimezoneOffset() * 1000 * 60 * (!decode ? -1 : 1)
                ), format || "dd.mm.yyyy.hh.nn.ss.zzz")
                : ""
            ;
        },

        reloadOrChangeProtocol: function () {
            if (mcConst.changeProtocol && mcConst.changeProtocol !== location.protocol) {
                location.protocol = mcConst.changeProtocol;

                window.open(location.href);
            } else {
                location.search = "";
                location.reload(true);
            }
        },

        myReplaceFormated : function(source, items){
            if (serviceList.isObject(items) && serviceList.isString(source)){
                for (var i in items) {
                    source = source.replace(new RegExp("#{" + i + "}", "g"), items[i]);
                }
            }

            return source;
        },

        myReplaceUni : function(source, items){
            if (serviceList.isObject(items) && serviceList.isString(source)){
                for (var i in items){
                    source = source.replace(new RegExp(i, "g"), items[i]);
                }
            }

            return source;
        },

        createKanbanLink: function (projectID, taskID, commentID) {
            return serviceList.getLocalHostPath(location.protocol !== 'file:') +
                "/" + mcConst.pathAliases.AliasKanban + "/#project=" + projectID + (taskID ? "&task=" + taskID + (commentID ? "&comment=" + commentID : "") : "");
        },

        createForumLink: function (path){
            return serviceList.getLocalHostPath(location.protocol !== 'file:') + "/" + mcConst.pathAliases.AliasForum + "/#/" + path;
        },

        getCommonFilesPath: function (isWebClient) {
            return this.myReplaceFormated("ftp://#{user}:#{pwd}@#{host}:#{port}/", {
                host : isWebClient ? window.location.host : mcConst.ServerInfo.Host,
                user : mcConst.FTP.CommonUser,
                pwd  : mcConst.FTP.CommonPWD,
                port : mcConst.UserInfo.PortFTP
            });
        },

        SHA1: function () {
            var sha1 = new Rusha();
            sha1.resetState();

            return sha1;
        },

        convertToEntities : function(tstr) {
            function dec2hex(i){
                var result = "0000";

                if      (i >= 0    && i <= 15)    { result = "000" + i.toString(16); }
                else if (i >= 16   && i <= 255)   { result = "00"  + i.toString(16); }
                else if (i >= 256  && i <= 4095)  { result = "0"   + i.toString(16); }
                else if (i >= 4096 && i <= 65535) { result =         i.toString(16); }

                return result
            }
            var bstr = "";

            if (tstr){
                for(var i = 0; i < tstr.length; ++i)
                {
                    if(tstr.charCodeAt(i) > 127)
                    {
                        bstr += "\\u" + dec2hex(tstr.charCodeAt(i));
                    } else {
                        bstr += tstr.charAt(i);
                    }
                }
            } else {
                throw new Error('convertToEntities: incoming data is empty or undefined: ' + tstr);
                //console.error('convertToEntities: incoming data is empty or undefined', true);
            }

            return bstr;
        },

        extractFileName : function(Path){
            var count = 0;

            if (Path){
                for (var i = Path.length - 1; i >= 0; i--){
                    count ++;

                    if ((Path[i] === '\\') || (Path[i] === '/')) {
                        return Path.slice(i + 1, i + count);
                    }
                }
            }

            return Path;
        },

        extractFileExtension : function(Path){
            var path = (Path || "").split('.');

            return path[path.length - 1];
        },

        extractFilePath : function(Path){
            for (var i = Path.length - 1; i >= 0; i--){
                if ((Path[i] === '\\') || (Path[i] === '/')) {
                    return Path.slice(0, i);
                }
            }

            return Path;
        },

        getLocalHostPath: function (isWebClient) {
            var res = "";

            if (isWebClient) {
                res = serviceList.isIE() ? window.location.protocol + "//" + window.location.host : window.location.origin
            } else {
                var host = mcConst.UserInfo.useAltAddr ? mcConst.ServerInfo.AltHost : mcConst.ServerInfo.Host;
                var port = mcConst.UserInfo.PortNode;

                res = (mcConst.UserInfo.HTTPS ? "https://" : "http://") + (mcConst.ServerInfo.IPv6 ? "[" + host + "]" : host) +
                      (mcConst.UserInfo.HTTPS ? (port != 443 ? ':' + port : '') : (port != 80  ? ':' + port : ''))
            }

            return res;
        },

        createHostPath: function (_host, _port, noProtocol) {
            var port  = _port ? _port : mcConst.UserInfo.PortNode;
            var host  = _host ? _host : mcConst.ServerInfo.Host;
            var https = window.location.protocol !== 'file:' ? window.location.protocol === "https:" : mcConst.UserInfo.HTTPS;
            
            return (
                noProtocol
                    ? ""
                    : (https ? "https://" : "http://")
                ) +
                (host.indexOf(":") === -1 // no port
                    ? host + (https
                            ? (port != 443 ? ':' + port : '')
                            : (port != 80  ? ':' + port : '')
                      )
                    : host
                );
        },

        formatFileSize: function(size, round){
            var index = 0;

            while (size > 1024){
                index ++;

                size = size / 1024;
            }

            return (round ? Math.round(Math.round(size * 100)/100) : Math.round(size * 100)/100) + " " + ["B","KB","MB","GB","TB","PB","EB"][index];
        },

        fileTimeStamp: function (dd) {
            // var currentTimeZoneOffsetInMS = (new Date()).getTimezoneOffset() * 60 * 1000;
            // var now  = new Date((new (Function.prototype.bind.apply(
            //     Date,
            //     [null].concat(serviceList.formatDate(dd ? new Date(dd) : new Date(), "yyyy.mm.dd.hh.nn.ss.zzz").split('.').map(function (val, idx) {
            //         return idx === 1 ? val - 1 : val;
            //     }))
            // ))).getTime() - currentTimeZoneOffsetInMS);
            // var then = new Date(
            //     now.getFullYear(),
            //     now.getMonth(),
            //     now.getDate(),
            //     0,0,0
            // );
            // var diff = ((now.getTime() - then.getTime()) || (24 * 60 * 60 * 1000));// + currentTimeZoneOffsetInMS;
            if (typeof require === 'function') {
                window.moment = require('moment');
            }

            var now   = serviceList.utcTime(dd ? new Date(dd) : new Date(), true).split('.');
            // 0  1  2    3  4  5  6
            // dd.mm.yyyy.hh.nn.ss.zzz
            var diff  = parseInt(now[6]) + parseInt(now[5])*1000 + parseInt(now[4])*1000*60 + parseInt(now[3])*1000*60*60;
            var diff2 = moment(new Date(now[2], parseInt(now[1]) - 1, now[0], now[3], now[4], now[5])).startOf('day').diff(moment({year:"1", month: "0", day: "1"}).startOf('day'), 'days') + 1;
                                                                                                                           
            return diff + "." + diff2;
        },

        getSelectedText: function (element){
            var res = {
                selectionStart : element.selectionStart,
                selectionEnd   : element.selectionEnd
            };

            // nothing is selected
            if (res.selectionStart !== res.selectionEnd) {
                var string = element.value;

                res.prefix  = string.substring(0, res.selectionStart);
                res.infix   = string.substring(res.selectionStart, res.selectionEnd);
                res.postfix = string.substring(res.selectionEnd);
            }

            // element.value = prefix + 'REPLACED TEXT' + postfix;

            return  res;
        },

        setSelectionText: function(inputBox, start, end){
            if (start > end) {
                start = end;
            }

            if ("selectionStart" in inputBox) { //gecko
                inputBox.setSelectionRange(start, end);
            } else {
                var r = inputBox.createTextRange();
                r.collapse(true);
                r.moveStart('character',start);
                r.moveEnd('character',end - start);
                r.select();
            }
        },
        
        convertArrayToObj: function (arr, item) {
            var res = {};

            if (item && serviceList.isString(item) && serviceList.isArray(arr)){
                arr.forEach(function (val) {
                    res[val[item]] = val;
                })
            } else {
                console.error('convertArrayToObj: is not Array or Item is not string!');
            }

            return res;
        },

        convertArrayObjToObj: function(arr, keyField){
            var res = {};

            for (var i = 0; i < arr.length; i++){
                if (arr[i].hasOwnProperty(keyField)){
                    res[arr[i][keyField]] = arr[i];
                }
            }

            return res;
        },

        getAvatar: function (sex) {
            var avatar = "";

            switch (sex){
                case 0: // alien
                    avatar = serviceList.insertIco("fa-user taskAvatar"); break;

                case 1: // male
                    avatar = serviceList.insertIco("fa-male taskAvatar"); break;

                case 2: // female
                    avatar = serviceList.insertIco("fa-female taskAvatar"); break;
            }

            return avatar;
        },

        getPriority: function (priority) {
            var res = "";

            switch (parseInt(priority)){
                case mcConst.priority.LOW      : res = mcLang(61); break;
                case mcConst.priority.MEDIUM   : res = mcLang(62); break;
                case mcConst.priority.HIGH     : res = mcLang(63); break;
                case mcConst.priority.IMPORTANT: res = mcLang(64); break;
                case mcConst.priority.URGENT   : res = mcLang(65); break;
                case mcConst.priority.CRITICAL : res = mcLang(66); break;
            }

            return res;
        },

        openKanbanLinkIfCan : function (e, $rootScope) {
            var res = false;

            if (e.target.nodeName === "A" && (
                    (e.target.origin + e.target.pathname == window.location.origin + window.location.pathname) ||
                    ((serviceList.isIE() && e.target.protocol + "//" + e.target.host + e.target.pathname) == (window.location.protocol + "//" + window.location.host + window.location.pathname))
                )){
                setTimeout(function () {
                    $rootScope.$broadcast('openKanbanURI', [e.target.href]);
                }, 10);

                e.preventDefault();

                res = true;
            }

            return res;
        },

        replaceKanbanLink: function (link, text) {
            var res = '<a href="' + link + '" title="' + link + '" ' + (link.indexOf('mailto') == -1 ? 'target="_blank"' : '') + ' class="internalLink">' + (text || link) + '</a>';

            if ((new RegExp(serviceList.isIE()
                    ? window.location.protocol + "//" + window.location.host + window.location.pathname
                    : window.location.origin + window.location.pathname
                )).test(link)){

                var parse = purl(link);

                var projectID = parse.fparam("project");
                var taskID    = parse.fparam("task");
                var commentID = parse.fparam("comment");

                res = '<a href="' + link + '" title="' + link + '" ' + (link.indexOf('mailto') == -1 ? 'target="_blank"' : '') + ' class="internalLink">';

                if (commentID){
                    res += "COMMENT #" + commentID;
                } else
                if (taskID){
                    res += "TASK #" + taskID;
                } else
                if (projectID){
                    res += "PROJECT #" + projectID;
                }

                res += '</a>';
            }

            return res;
        },

        getByCssClass: function(classList, _node){
            var res = null;
            var node = _node || document;

            if (document.getElementsByClassName) {
                res = node.getElementsByClassName(classList)
            } else {
                var list = node.getElementsByTagName('*');
                var length = list.length;
                var classArray = classList.split(/\s+/);
                var classes = classArray.length;
                var result = [];

                for (var i = 0; i < length; i++) {
                    for (var j = 0; j < classes; j++) {
                        if (list[i].className.search('\\b' + classArray[j] + '\\b') !== -1) {
                            result.push(list[i]);

                            break
                        }
                    }
                }

                res = result;
            }

            return res;
        },

        disableContextMenu : function(_view, exception){
            webix.event(_view.$view, "contextmenu", function(e){
                function skipItems(items, where) {
                    var flag = true;

                    if (items && items.length){
                        items.forEach(function (item) {
                            if (where.length && where.indexOf(item) !== -1){
                                flag = false;
                            }
                        });
                    }

                    return flag;
                }

                if ((
                        e.target.nodeName !== "INPUT" &&
                        e.target.nodeName !== "TEXTAREA" &&
                        e.target.parentNode && e.target.parentNode.parentNode &&
                        skipItems(exception, e.target.className.split(" ").concat(e.target.parentNode.className.split(" ")).concat(e.target.parentNode.parentNode.className.split(" ")))
                    ) ||
                    (e.target.type === "button")) {

                    e.preventDefault();
                }
            });
            // webix.event(_view.$view, "contextmenu", function(e){
            //     if ((e.target.nodeName != "INPUT") && (e.target.nodeName != "TEXTAREA") || (e.target.type == "button")) {
            //         e.preventDefault();
            //     }
            // });
        },

        setStatusForUser: function(_user, state){
            var users = serviceList.getElementByClass(mcConst.storageOpts.STATUSICO + _user);

            for (var i = 0; i < users.length; i++){
                users[i].style.color = serviceList.getColorForStatus(state);

                if (users[i].className.indexOf("fa-user smaller") === -1){
                    users[i].style.backgroundColor = serviceList.getColorForStatus(state);
                }
            }
        },

        Base64 : {
            // private property
            _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

            // public method for encoding
            encode : function (input) {
                var output = "";
                var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
                var i = 0;

                input = serviceList.Base64._utf8_encode(input);

                while (i < input.length) {
                    chr1 = input.charCodeAt(i++);
                    chr2 = input.charCodeAt(i++);
                    chr3 = input.charCodeAt(i++);

                    enc1 = chr1 >> 2;
                    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                    enc4 = chr3 & 63;

                    if (isNaN(chr2)) {
                        enc3 = enc4 = 64;
                    } else if (isNaN(chr3)) {
                        enc4 = 64;
                    }

                    output = output +
                        this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                        this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

                }

                return output;
            },

            // public method for decoding
            decode : function (input) {
                var output = "";
                var chr1, chr2, chr3;
                var enc1, enc2, enc3, enc4;
                var i = 0;

                input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

                while (i < input.length) {

                    enc1 = serviceList.Base64._keyStr.indexOf(input.charAt(i++));
                    enc2 = serviceList.Base64._keyStr.indexOf(input.charAt(i++));
                    enc3 = serviceList.Base64._keyStr.indexOf(input.charAt(i++));
                    enc4 = serviceList.Base64._keyStr.indexOf(input.charAt(i++));

                    chr1 = (enc1 << 2) | (enc2 >> 4);
                    chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                    chr3 = ((enc3 & 3) << 6) | enc4;

                    output = output + String.fromCharCode(chr1);

                    if (enc3 !== 64) {
                        output = output + String.fromCharCode(chr2);
                    }

                    if (enc4 !== 64) {
                        output = output + String.fromCharCode(chr3);
                    }

                }

                output = serviceList.Base64._utf8_decode(output);

                return output;

            },

            // private method for UTF-8 encoding
            _utf8_encode : function (string) {
                string = string.replace(/\r\n/g,"\n");
                var utftext = "";

                for (var n = 0; n < string.length; n++) {

                    var c = string.charCodeAt(n);

                    if (c < 128) {
                        utftext += String.fromCharCode(c);
                    } else
                    if((c > 127) && (c < 2048)) {
                        utftext += String.fromCharCode((c >> 6) | 192);
                        utftext += String.fromCharCode((c & 63) | 128);
                    } else {
                        utftext += String.fromCharCode((c >> 12) | 224);
                        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                        utftext += String.fromCharCode((c & 63) | 128);
                    }

                }

                return utftext;
            },

            // private method for UTF-8 decoding
            _utf8_decode : function (utftext) {
                var string = "";
                var i  = 0;
                var c  = 0;
                var c1 = 0;
                var c2 = 0;
                var c3 = 0;

                while ( i < utftext.length ) {

                    c = utftext.charCodeAt(i);

                    if (c < 128) {
                        string += String.fromCharCode(c);
                        i++;
                    } else
                    if((c > 191) && (c < 224)) {
                        c2 = utftext.charCodeAt(i+1);
                        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                        i += 2;
                    } else {
                        c2 = utftext.charCodeAt(i+1);
                        c3 = utftext.charCodeAt(i+2);
                        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                        i += 3;
                    }

                }

                return string;
            }
        },

        isOdd : function (num) { return num % 2;},

        replaceMcComponents : function (text, isWeb, $rs, nick) {
            var items = text.split(mcConst.terminator2);
            var elem  = "";
            var smileFile = "";

            items.forEach(function (item, idx) {
                if (serviceList.isOdd(idx)){
                    elem = item.split(mcConst.terminator3);

                    switch (item[0]) {
                        case ")":
                            try {
                                elem[0] = elem[0].replace(")", "");

                                if (mcConst.SmileysSet[elem[0]]){
                                    smileFile = mcConst.SmileysSet[elem[0]][elem[1]];
                                }

                                if (smileFile && (isWeb || mcConst.ClientSettings.SysEventsDisableEmotions == '0')){
                                    items[idx] = serviceList.myReplaceFormated(
                                        "<img src='#{path}/#{smile}'>",
                                        {
                                            path : MC_RESOURCE.TextSource.smileys + mcConst.SmileysSet[elem[0]].name,
                                            smile: smileFile
                                        }
                                    );
                                } else {
                                    items[idx] = elem[1];
                                }
                            } catch (e) {
                                items[idx] = ":)";
                            }
                        break;

                        case "F":
                            var style = elem[0].split("#");
                            style = style[style.length - 1];

                            items[idx] = elem[1];

                            for (var i = 0; i < style.length; i++){
                                switch (style[i]) {
                                    case "B": items[idx] = "<b>" + items[idx] + "</b>"; break;
                                    case "I": items[idx] = "<i>" + items[idx] + "</i>"; break;
                                    case "U": items[idx] = "<u>" + items[idx] + "</u>"; break;
                                    case "S": items[idx] = "<strike>" + items[idx] + "</strike>"; break;
                                }
                            }
                        break;
                            
                        case ">":
                            var uin = elem[0].replace(">", "");

                            var name = elem[1];

                            if (name.indexOf('noname (') === 0) {
                                name = nick ? nick : $rs.$broadcast(window._messages_.usersSmallInfo.getUserSmallInfo, [uin]).DisplayName;
                            }

                            items[idx] = "<span uin='" + uin +"' class='brown bolder'>@" + name + "</span>";
                        break;

                        case "A":
                            var link = elem[0].replace("A", "");

                            if (link.indexOf("\\") === 0) {
                                link = link.replace("\\\\", "local://").replace(/\\/g, "/");
                            }

                            items[idx] = link + mcConst.myChatLinkTitle + elem[1];
                        break;

                        case "f": // public FTP
                            items[idx] = mcConst.ftpPublic;
                        break;

                        case "m": // personal FTP                 // uin
                            items[idx] = mcConst.ftpPersonal + elem[0].replace("m", "");
                        break;
                    }
                }
            });
            
            return items.join("");
        },

        numberList: function (length, forwardZero) {
            var strLength = length.toString().length;
            var res = (new Array(length || 1)).join('.').split('.');

            res = res.map(function (itm, idx) {
                var st = idx.toString();

                return forwardZero ? (new Array(strLength + 1 - st.length)).join('0') + st : st;
            });

            return res;
        },

        forvardZero: function (st, strLength, symbol) {
            st = st.toString();
            
            return (new Array(strLength + 1 - st.length)).join(symbol || '0') + st;
        },

        toHex: function (numb, hexLen){
            var str = "";

            numb = parseInt(numb, 10);

            while (numb > 0){
                str  = hexCodes[numb % 16] + str;
                numb = Math.floor(numb / 16);
            }

            while (str.length < hexLen){
                str = "0" + str;
            }

            return str;
        },

        hexToDec: function(hex){
            return parseInt(hex, 16);
        },

        FadeControl: function Fade(elem) {
            var el   = elem;
            var stop = null;

            this.in = function () {
                stop = "out";
    
                var last = new Date();
                var tick = function() {
                    if (stop === "in") {
                        stop = null;
                    } else {
                        el.style.opacity = + el.style.opacity + (new Date() - last) / 400;

                        last = new Date();

                        if (el.style.opacity <= 1) {
                            (window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16);
                        } else {
                            stop = null;
                        }
                    }
                };

                tick();
            };

            this.out = function () {
                stop = "in";

                var last = new Date();
                var tick = function() {
                    if (stop === "out"){
                        stop = null;
                    } else {
                        el.style.opacity = + el.style.opacity - (new Date() - last) / 400;

                        last = new Date();

                        if (el.style.opacity > 0) {
                            (window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16);
                        } else {
                            stop = null;
                        }
                    }
                };

                tick();
            };
        },

        Capitalize: function (str) {
            return str[0].toUpperCase() + str.substring(1).toLowerCase();
        },

        ObjToString : function(_obj){
            var res = "";

            if (serviceList.isString(_obj) || serviceList.isNumber(_obj)){
                res = _obj.toString() + "";
            } else {
                try {
                    res = JSON.stringify(_obj);
                } catch (e){
                    res = _obj.toString() + "";
                }
            }

            return res;
        },

        typeFlag: function (displayFlag, lookField) {
            var lng = $$(displayFlag);
            var lastChar = $$(lookField).getValue();
            lastChar = lastChar[lastChar.length - 1];

            if (lastChar){
                if (lastChar.match(/[a-zA-Z]/)) {
                    lng.define('template', 'EN');
                    lng.refresh();
                } else
                if (lastChar.match(/[а-яА-Я]/)){
                    lng.define('template', 'RU');
                    lng.refresh();
                } else
                if (lastChar.match(/[їЇіІєЄ]/)){
                    lng.define('template', 'UA');
                    lng.refresh();
                }
            }
        },

        buildForumStructure: function (struct, data, ids, structCash, exeptTopicks) {
            var newIds  = [];
            var newData = [];
            var idx     = 0;
            var item    = null;

            while (idx < data.length){
                item = data[idx];

                if (item){
                    if (item.Type === mcConst.msgTypes.topic && exeptTopicks){
                        data.splice(idx, 1);
                    } else

                    if (serviceList.inArrayNoStrict(item.ParentID, ids) !== -1){
                        item.id = item.Type + "_" + item.ID;

                        structCash[mcConst.msgTypes.section + "_" + item.ParentID].Item.data.push(item);

                        structCash[item.id] = {
                            Item: item
                        };

                        if (item.Type === mcConst.msgTypes.section) {
                            newIds.push(item.ID);

                            structCash[item.id].Item.data = [];
                            structCash[item.id].Data      = structCash[item.id].Item.data;
                        }

                        data.splice(idx, 1);
                    } else {
                        newData.push(item);

                        idx ++;
                    }
                } else {
                    i ++;
                }

                item = null;
            }

            return data.length > 0 ? serviceList.buildForumStructure(struct, newData, newIds, structCash, exeptTopicks) : struct;
        },

        insertAtCursor: function (textarea, val, isPara) {
            var res = 0;

            if (document.selection) {
                var sel = document.selection.createRange();

                textarea.focus();

                sel.text = val;

                res = sel.text.toString().length - 1;
            } else
            if (textarea.selectionStart || textarea.selectionStart == '0') {
                var startPos = textarea.selectionStart;
                var endPos   = textarea.selectionEnd;

                var prevText = textarea.value.substring(0, startPos);
                var nextText = textarea.value.substring(endPos, textarea.value.length);

                textarea.value =
                    (isPara && prevText.length && prevText[prevText.length - 1] !== "\n" ? prevText + "\n" : prevText) +
                     val +
                    (isPara && nextText.length && nextText[0] !== "\n" ? "\n" + nextText : nextText);

                textarea.selectionStart = startPos + val.length + (isPara ? 1 : 0);
                textarea.selectionEnd   = startPos + val.length + (isPara ? 1 : 0);

                res = textarea.selectionEnd;
            } else {
                textarea.value += (isPara && textarea.value.length && textarea.value[textarea.value.length - 1] !== "\n") ? "\n" + val : val;

                res = textarea.value.length - 1;
            }

            return res;
        },

        parseAttachment: function (text, files, pref, cb, duManager, generateLinks, isComment){
            var link  = "";
            var closeSymbol = "}";
            var isWeb = location.protocol !== 'file:';

            if (text.indexOf(pref) >=0 ){
                text = text.split(pref);

                for (var i = 0; i < text.length; i++){
                    if (!i && text[i].length){
                        if (cb) {
                            text[i] = cb(text[i], files);
                        }

                        continue;
                    }

                    var idx       = text[i].indexOf(closeSymbol);
                    var name      = serviceList.copy(text[i], 0, idx);
                    var endOfLine = serviceList.copy(text[i], idx + 1, text[i].length - 1);
                    var parsedEndLine = "";

                    if (files && files.hasOwnProperty(name)){
                        if (pref === mcConst.prefix.file){
                            link = serviceList.myReplaceFormated(
                                '<a href="#{link}" class="internalLink" title="#{link}" target="_blank">#{name}</a>',
                                {
                                    link: serviceList.getLocalHostPath(isWeb) + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + files[name].Hash + "/" + name,
                                    name: serviceList.insertIco("fa-file-o", "fs90") + name
                                }
                            );
                        } else

                        if (pref === mcConst.prefix.image){
                            imgIndex++;

                            if (generateLinks){
                                generateLinks(files[name], name);
                            }

                            var resolutionW = files[name].width  ? Math.round(parseInt(files[name].width))  : "auto";
                            var resolutionH = files[name].height ? Math.round(parseInt(files[name].height)) : "auto";

                            if (files[name].width){
                                if (resolutionH === resolutionW && resolutionW <= mcConst.imageSize.big.y){
                                    // nothing 
                                } else
                                if (resolutionH >= resolutionW) {
                                    if (resolutionH > mcConst.imageSize.big.y){
                                        resolutionH = mcConst.imageSize.big.y;
                                    }

                                    resolutionW = Math.round(files[name].width / (files[name].height / mcConst.imageSize.big.y));
                                } else {
                                    if (resolutionW > mcConst.imageSize.big.x){
                                        resolutionW = mcConst.imageSize.big.x;
                                    }

                                    resolutionH = Math.round(files[name].height / (files[name].width / mcConst.imageSize.big.x));
                                }
                            }

                            var now = serviceList.RandomHash(7);

                            link = serviceList.myReplaceFormated(
                                '<div><a href="#{hrefImg}" class="internalLink" target="_blank" style="#{widthPX} #{heightPX};">' + // background-image: url(#{tmp})
                                    '<img #{idClass}="img_temp_#{hash}" src="#{loadImage}" alt="" width="#{width}" height="#{height}" >' +  // style="background-image: url(#{tmp})"
                                '</a></div>',
                                {
                                    idClass  : isComment ? "id" : "class",
                                    tmp      : mcConst.imagesPath.noFile,
                                    width    : resolutionW || "",
                                    height   : resolutionH || "",
                                    widthPX  : "width:"  + (resolutionW && resolutionW !== "auto" ? resolutionW + "px": resolutionW) + ";",
                                    heightPX : "height:" + (resolutionH && resolutionH !== "auto" ? resolutionH + "px": resolutionH) + ";",
                                    hrefImg  : files[name].lnkHref ? files[name].lnkHref : "#",
                                    loadImage: isComment ? "" : (files[name].imgSrc  ? files[name].imgSrc  : ""),
                                    // loadImage: "", //mcConst.imagesPath.noImage,
                                    hash     : files[name].Hash + (isComment ? "_" + now : "")
                                }
                            );

                            duManager.loadThumbs([files[name].Hash, name, null, now]);
                        }

                        if (cb) {
                            parsedEndLine = cb(endOfLine, files);
                        }

                        text[i] = link + (parsedEndLine || endOfLine);
                    } else {
                        if (cb) {
                            parsedEndLine = cb(endOfLine, files);

                            text[i] = link + (parsedEndLine || endOfLine);
                        }
                    }
                }
                
                text = text.join('');
            } else
            if (cb) {
                text = cb(text, files);
            }

            return text;
        },

        checkNumbersCounter: function(code){
            if (this.config.view === "counter" && serviceList.inArray(code, mcConst.keyCodes.Digits.concat(mcConst.keyCodes.EditSymbols)) === -1){
                return false;
            }
        },

        isImage: function (file) {
            return serviceList.inArrayNoStrict(serviceList.extractFileExtension(file).toLowerCase(), ["png", "jpg", "jpeg", "gif", "bmp"]) !== -1;
        },

        getCurrentChantTypeForFiles: function (type) {
            // var currentChat = type;
            var res         = 1;

            switch (type){
                case "UIN"   : res = mcConst.whereFiles.private;break;
                case "UID"   : res = mcConst.whereFiles.conf;   break;
                case "Forum" : res = mcConst.whereFiles.forum;  break;
                case "Kanban": res = mcConst.whereFiles.kanban; break;
                case "BBS"   : res = mcConst.whereFiles.bbs;    break;
            }

            return res;
        },

        copyTextToClipboard: function (text, message) {
            function fallbackCopyTextToClipboard(text) {
                var textArea = document.createElement("textarea");

                textArea.value = text;
                textArea.className = "__terminal-clipcopy";

                document.body.appendChild(textArea);

                textArea.focus();
                textArea.select();

                try {
                    document.execCommand('copy');

                    webix.message('Text copied to clipboard');
                } catch (err) {
                    console.error('Fallback: could not copy text to clipboard: ', err);
                }

                document.body.removeChild(textArea);
            }

            if (!navigator.clipboard) {
                fallbackCopyTextToClipboard(text);

                return;
            }

            navigator.clipboard.writeText(text).then(function() {
                if (message !== false) {
                    webix.message(message || 'Text copied to clipboard');
                }
            }, function(err) {
                console.error('Could not copy text to clipboard: ', err);
            });
        },

        presenterTemp: function (){
            return "<!DOCTYPE html>" +
                "<html lang=\"en\">" +
                "<head>" +
                "    <meta charset=\"UTF-8\">" +
                "    <title>#{title}</title>" +
                "    <link rel=\"stylesheet\" type=\"text/css\" href=\"/mctools/simplemde/simplemde.min.css\">" +
                "    <link rel=\"stylesheet\" type=\"text/css\" href=\"/frameworks/webix/codebase/css/font-awesome.min.css\">" +
                "    <link rel=\"stylesheet\" type=\"text/css\" href=\"/frameworks/webix/codebase/css/github-markdown.css\">" +
                "</head>" +
                "<body class='markdown-body'>" +
                "    #{content}" +
                "    #{mc_sign}" +
                "</body>" +
                "</html>"
            ;
        },

        getSmileNumbByName : function (name) {
            return Object.keys(mcConst.SmileysSet).filter(function (item) {
                if (mcConst.SmileysSet[item].name === name) {
                    return true;
                }
            }).join("") || name;
        },

        getMyStatus: function () {
            switch (mcConst.UserInfo.onlineState) {
                case mcConst.states.online : return mcLang(30);   // "30" :"В сети",
                case mcConst.states.offline: return mcLang(29);   // "29" :"Не в сети",
                case mcConst.states.away   : return mcLang(31);   // "31" :"Нет на месте",
                case mcConst.states.dnd    : return mcLang(32);   // "32" :"Не беспокоить",
                case mcConst.states.webOnline: return mcLang(30); // "30" :"В сети",
            }
        },

        getTwoLettersName : function (name) {
            var rex = null;

            try {
                rex = (new RegExp("((\\p{L}|\\d).+[\\s\\-._()]+(\\p{L}|\\d))|(\\p{L}|\\d)", "iu")).exec(name);
            } catch (e) {
                rex = (new RegExp("((\\S|\\d).+[\\s\\-._()]+(\\S|\\d))|(\\S|\\d)", "i")).exec(name);
            }

            var res = "";

            if (!rex){
                res = (name || "U").toString()[0];
            } else
            if (rex[4]){
                res = rex[4];
            } else {
                res = rex[2] + rex[3];
            }

            return res.toUpperCase();
        },

        Error: function (id, params) {
            return serviceList.myReplace(mcConst.ErrorText[id], params);
        },

        /**
         * @return {boolean}
         */
        CheckLink: function (text) {
            var SCHEME   = "[a-z\\d.-]+://";
            var IPV4     = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])";
            var HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+";
            var TLD      =  "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|" +
                    "bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|" +
                    "do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|" +
                    "gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|" +
                    "kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|" +
                    "mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|" +
                    "py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|" +
                    "tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|" +
                    "xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|" +
                    "xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw|dev)";
            var PORT = "(?::\\d{1,5})?";
            var HOST_OR_IP = "(?:" + HOSTNAME + TLD + PORT + "|" + IPV4 + PORT + ")";
            var PATH = "(?:[;/][^#?<>\\s]*)?";
            var QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?";
            var URI1 = "\\b" + SCHEME + "[^<>(\\s|\\)|\\(|\\[|\\])]+"; //"[^<>\\s]+";
            var URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)";

            var MAILTO = "mailto:";
            var EMAIL  = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)";
            var URI_RE = new RegExp( "^(?:" + URI1 + "|" + URI2 + ")$", "i" ); // "|" + EMAIL +

            return URI_RE.test(text);
        },

        checkNoRestoreTool: function (restoreTool){
            return restoreTool === mcConst.dataModels.Settings         || restoreTool === mcConst.dataModels.UserProfile ||
                   restoreTool === mcConst.dataModels.HistoryBroadcast || restoreTool === mcConst.dataModels.ViewLogs ||
                   restoreTool === mcConst.dataModels.CreateConf;
        },

        tree_icon: function (obj){
            if (obj.$count || obj.needLoad){
                if (obj.open)
                    return "<div class='webix_tree_open'></div>";
                else
                    // return "<div class='_tree_close'></div>";
                    return "<div class='webix_tree_close'></div>";
            } else
                return "<div class='webix_tree_none'></div>";
        },

        IM: function (_uin){
            var uin = parseInt(_uin);
            var me  = parseInt(mcConst.UserInfo.UIN);
            
            return !isNaN(uin) && uin === me;
        },

        checkMediaTypesSupport : function (){
            var types = [
                "audio/webm;codecs=opus",
                "audio/webm",
                
                "video/webm",
                "video/ogg",
                "video/mp4",
                "video/mpeg",

                'video/webm;codecs="vp08.00.41.08,vorbis"',
                'video/webm;codecs="vp09.02.10.10.01.09.16.09.01,opus"',

                "video/webm;codecs=h264",
                "video/webm;codecs=vp8",
                "video/webm;codecs=vp9",
            ];

            for (var i in types) {
                console.log( (MediaRecorder.isTypeSupported(types[i]) ? "Yes" : "No ") + " - " + types[i]);
            }

            console.log("https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter");
        },

        getSearchQuery: function (src, condition, simple) {
            var data = null;

            if (condition === "ref") {
                data = src.split('&ref=')[1];
            } else {
                src = serviceList.copy(src, 1).split('&');

                for (var i = 0; i < src.length; i++){
                    if ((condition && serviceList.isFunction(condition) && condition(src[i])) || !condition || src[i].indexOf(condition + "=") === 0){
                        if (simple) {
                            data = decodeURI(src[i].split('=')[1]);

                            break;
                        } else {
                            if (!data){
                                data = [];
                            }

                            data.push(src[i]);
                        }
                    }
                }
            }

            return data;
        },

        jsonp: function (url, callback, err, term) {
            var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());

            window[callbackName] = function(data) {
                delete window[callbackName];

                document.body.removeChild(script);

                callback(data);
            };

            var script  = document.createElement('script');
            script.src  = url +  (term ? term : "&") + 'callback=' + callbackName;
            script.onerror = function (e){
                delete window[callbackName];

                document.body.removeChild(script);

                err && err(e);
            };

            try {
                document.body.appendChild(script);
            } catch (e){}
        },

        getVisualViewPortValues : function (clampOffsets, resizeDimensions) {
            const clamp = (val, min, max) => Math.min(Math.max(val, min), max);
            
            let {
                    width,
                    height,
                    widthOrig = null,
                    heightOrig = null,
                    scale,
                    offsetTop,
                    offsetLeft,
                    offsetTopOrig = null,
                    offsetLeftOrig = null,
                    pageLeft,
                    pageTop,
                    pageLeftOrig = null,
                    pageTopOrig = null,
                } = window.visualViewport;

            // Adjust values by clamping
            if (clampOffsets) {
                // Cache original values
                pageTopOrig = pageTop;
                pageLeftOrig = pageLeft;
                offsetTopOrig = offsetTop;
                offsetLeftOrig = offsetLeft;

                // Offsets measured against the page cannot go negative, nor exceed the max scroll offset
                // @note: Values below only used for absolute positioning
                pageTop = clamp(pageTop, 0, document.body.offsetHeight - height);
                pageLeft = clamp(pageLeft, 0, document.body.offsetWidth - width);

                // Offsets measured against the Layout Viewport cannot go negative, nor exceed the max offset within the Layout Viewport
                // @note: Values below only used for fixed positioning
                const layoutViewportHeight = (window.innerHeight * scale); // @TODO: This only is true when window.innerHeight resizes. Which is not always the case when pinch-zooming (or which happens too late as UA UI shows/hides)
                const layoutViewportWidth = (window.innerWidth * scale); // @TODO: This only is true when window.innerHeight resizes. Which is not always the case when pinch-zooming (or which happens too late as UA UI shows/hides)
                offsetTop = clamp(offsetTop, 0, layoutViewportHeight - height);
                offsetLeft = clamp(offsetLeft, 0, layoutViewportWidth - width);
            }

            // Adjust values by resizing height/width
            // Only needed for browsers that expose overscrolling through scrollX/scrollY
            if (resizeDimensions) {
                widthOrig = width;
                heightOrig = height;
                pageTopOrig = pageTop;
                pageLeftOrig = pageLeft;
                offsetTopOrig = offsetTop;
                offsetLeftOrig = offsetLeft;

                // Mobile Safari: Fixed Viewport does overscroll. Max value can overshoot 0 (inverse of overScrollY) and min value can exceed inverted max scroll distance
                // Desktop Safari: Fixed Viewport does not overscroll. Max value is 0, Min value is inverted max scroll distance.
                // We need a way to distinguish between both, because they need a different fix
                // @TODO: No longer rely on UA sniffing here, and find a way to detect wether the Fixed Viewport can overscroll or not …
                const isMobileSafari = (!!window.navigator.userAgent.match(/iPad/i) || !!window.navigator.userAgent.match(/iPhone/i));
                const isDesktopSafari = !isMobileSafari;

                // Overscrolling at the right edge
                if (width + pageLeft > document.body.offsetWidth) {
                    width = document.body.offsetWidth - pageLeft;

                    // @note: Value below only used for fixed positioning
                    if (isMobileSafari) {
                        if (scale == 1) offsetLeft -= (widthOrig - width);
                        if (offsetLeft < 0) offsetLeft = -offsetLeft;
                    }
                    else {
                        offsetLeft += (widthOrig - width); // Add overscroll value to the offset
                    }
                }

                // Overscrolling at the bottom edge
                if (height + pageTop > document.body.offsetHeight) {
                    height = document.body.offsetHeight - pageTop;

                    // @note: Value below only used for fixed positioning
                    if (isMobileSafari) {
                        if (scale == 1) offsetTop -= (heightOrig - height);
                        if (offsetTop < 0) offsetTop = -offsetTop;
                    }
                    else {
                        offsetTop += (heightOrig - height); // Add overscroll value to the offset
                    }
                }

                // Overscrolling at the left edge
                // @note: Might be tricky to achieve as you might trigger back navigation
                if (pageLeft < 0) {
                    width += pageLeft;

                    // @note: Value below only used for abs position
                    pageLeft = 0;

                    // @note: Value below only used for fixed positioning
                    offsetLeft = Math.abs(offsetLeft);
                }

                // Overscrolling at the top edge
                // @note: Might be tricky to achieve as you might trigger pull-to-refresh
                if (pageTop < 0) {
                    height += pageTop;

                    // @note: Value below only used for abs positioning
                    pageTop = 0;

                    // @note: Value below only used for fixed positioning
                    offsetTop = Math.abs(offsetTop);
                }

            }

            return Object.fromEntries(
                Object.entries({
                    width,
                    widthOrig,
                    height,
                    heightOrig,
                    scale,
                    offsetTop,
                    offsetTopOrig,
                    offsetLeft,
                    offsetLeftOrig,
                    pageLeft,
                    pageLeftOrig,
                    pageTop,
                    pageTopOrig,
                }).filter(([k, v]) => v !== null)
            );
        },

        _min: function (d0, d1, d2, bx, ay) {
            return d0 < d1 || d2 < d1
                ? d0 > d2
                    ? d2 + 1
                    : d0 + 1
                : bx === ay
                    ? d1
                    : d1 + 1;
        },

        levenshteinDistance : function(a, b){
            if (a === b) return 0;

            if (a.length > b.length) {
                let tmp = a;
                a = b;
                b = tmp;
            }

            let la = a.length;
            let lb = b.length;
            let x  = 0;
            let offset = 0;
            let vector = [];
            let y, d0, d1, d2, d3, dd, dy, ay, bx0, bx1, bx2, bx3;

            while (la > 0 && (a.charCodeAt(la - 1) === b.charCodeAt(lb - 1))) {
                la--;
                lb--;
            }

            while (offset < la && (a.charCodeAt(offset) === b.charCodeAt(offset))) {
                offset++;
            }

            la -= offset;
            lb -= offset;

            if (la === 0 || lb < 3) {
                return lb;
            }

            for (y = 0; y < la; y++) {
                vector.push(y + 1);
                vector.push(a.charCodeAt(offset + y));
            }

            let len = vector.length - 1;

            for (; x < lb - 3;) {
                bx0 = b.charCodeAt(offset + (d0 = x));
                bx1 = b.charCodeAt(offset + (d1 = x + 1));
                bx2 = b.charCodeAt(offset + (d2 = x + 2));
                bx3 = b.charCodeAt(offset + (d3 = x + 3));
                dd = (x += 4);
                for (y = 0; y < len; y += 2) {
                    dy = vector[y];
                    ay = vector[y + 1];
                    d0 = _min(dy, d0, d1, bx0, ay);
                    d1 = _min(d0, d1, d2, bx1, ay);
                    d2 = _min(d1, d2, d3, bx2, ay);
                    dd = _min(d2, d3, dd, bx3, ay);
                    vector[y] = dd;
                    d3 = d2;
                    d2 = d1;
                    d1 = d0;
                    d0 = dy;
                }
            }

            for (; x < lb;) {
                bx0 = b.charCodeAt(offset + (d0 = x));
                dd = ++x;
                for (y = 0; y < len; y += 2) {
                    dy = vector[y];
                    vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1]);
                    d0 = dy;
                }
            }

            return dd;
        }
    };

    var multiple    = {
        kanban: {
            FilterFiled : function (options){
                function clearFilter(filter, noFocus){
                    if (clearShowed && !options.noCloseBtn) {
                        $$("clearBtn" + rnd).hide();

                        clearShowed = false;
                    }

                    options.thisFilter.setValue('');
                    options.thisFilter.$setValue('');

                    if (!filter) {
                        doFilter(noFocus);
                    } else {
                        options.List.filter("");
                    }

                    if (options.onClear) {
                        options.onClear();
                    }
                }

                function checkParams(text, obj, paramList, strict) {
                    var fp = [];
                    var show = true;

                    for (var i = 0; i < paramList.length; i++){
                        var param = paramList[i];

                        if (serviceList.isArray(obj[param])){
                            fp.push(obj[param].join('|').toLowerCase());
                        } else
                        if (param.indexOf(':') === -1){
                            if (obj[param] !== undefined) fp.push(obj[param].toString().toLowerCase());
                        } else {
                            var _param = param.split(':');

                            fp = fp.concat(obj[_param[0]].toString().toLowerCase().split(_param[1]));
                        }
                    }

                    if (serviceList.isString(text)){
                        if (strict){
                            show = fp.indexOf(text.toString().toLowerCase()) !== -1;
                        } else {
                            show = fp.join('|').indexOf(text) !== -1;
                        }
                    } else

                    if (serviceList.isBoolean(text)){
                        show = fp.indexOf(text.toString().toLowerCase()) !== -1;
                    } else

                    if (serviceList.isArray(text)){
                        for (var i = 0; i < text.length; i ++){
                            if (fp.indexOf(text[i].toString().toLowerCase()) === -1) {
                                show = false;

                                break;
                            }
                        }
                    }

                    return show;
                }

                function doFilter(noFocus) {
                    if (options.List && options.thisFilter) {
                        var text        = serviceList.trim(options.thisFilter.getValue().toString()).toLowerCase();
                        var listFilters = options.List._otherFilterOptions ? Object.keys(options.List._otherFilterOptions) : [];
                        var ofo         = options.List._otherFilterOptions;

                        if (text === "" && !listFilters.length){
                            clearFilter(true);

                            if (!noFocus) {
                                options.thisFilter.focus();
                            }
                        } else {
                            var res = [];
                            var pos = serviceList.getCaretPosition(options.thisFilter.getInputNode());

                            // console.log('=======================================');

                            options.List.filter(function(obj){
                                var filterResult = true;
                                var subFilter    = true;

                                // console.log(obj);
                                // console.log(obj.Tags);

                                if (!options.filterParams || options.filterParams.length === 0){
                                    options.filterParams = ['value'];
                                }

                                if (text !== ""){
                                    filterResult = checkParams(text, obj, options.filterParams);
                                }

                                if (filterResult && listFilters.length){
                                    for (var i = 0; i < listFilters.length; i ++){
                                        var searchText = ofo[listFilters[i]].items;

                                        if (serviceList.isString(searchText)){
                                            subFilter = subFilter && checkParams(searchText, obj, ofo[listFilters[i]].fields);
                                        } else

                                        if (serviceList.isArray(searchText)){
                                            subFilter = ofo[listFilters[i]].union === mcConst.methods.AND;

                                            for (var j = 0; j < searchText.length; j ++){
                                                subFilter = ofo[listFilters[i]].union === mcConst.methods.OR
                                                    ? subFilter || checkParams(searchText[j], obj, ofo[listFilters[i]].fields, true)
                                                    : subFilter && checkParams(searchText[j], obj, ofo[listFilters[i]].fields, true)
                                                ;

                                                if (ofo[listFilters[i]].union === mcConst.methods.AND){
                                                    break;
                                                }
                                            }
                                        }

                                        if (!subFilter) {
                                            break;
                                        }
                                    }
                                }

                                filterResult = filterResult && subFilter;

                                return filterResult ? !!res.push(obj) : false;
                            });

                            if (options.onAfterFilter){
                                options.onAfterFilter(res);

                                res = null;
                            }
                        }

                        if (!clearShowed && text !== "" && !options.noCloseBtn) {
                            clearShowed = true;

                            $$("clearBtn" + rnd).show();

                            if (!noFocus) setTimeout(function () {
                                options.thisFilter.focus();

                                serviceList.setCaretPosition(options.thisFilter.getInputNode(), pos);
                            }, 5);
                        }
                    }
                }

                var clearShowed = false;
                var rnd         = Math.random();
                var _filter     = { rows: [
                    { height: (options.topSpacer === undefined)? 0 : options.topSpacer },

                    { cols: [
                        { view: "search", padding: 0, css: 'noPaddingChild', placeholder: options.searchText || serviceList.Lang(10), keyPressTimeout: 400, on: { // "10" :"Введите текст для поиска",
                            onKeyPress: function (code, e) {
                                if (code === 27 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
                                    var needFocus = this.getValue().length;

                                    clearFilter();

                                    if (needFocus) {
                                        this.focus();
                                    }
                                }
                            },
                            onTimedKeypress : function () {
                                doFilter();
                            },
                            onAfterRender : function closeFilter(){
                                options.thisFilter = this;
                                _filter.thisFilter = this;
                                
                                options.List = $$(options.listName);

                                setTimeout(function () {
                                    _filter.thisFilter.getInputNode().setAttribute("autocomplete", "off");
                                }, 100);
                            }
                        }},

                        { id: "clearBtn" + rnd, view: "button", type: "iconButton", icon: "times", width: 27, hidden: true, click: function () {
                            setTimeout(function () {
                                clearFilter();
                            }, 50);
                        }}
                    ]},

                    { height: options.bottomSpacer === undefined ? 4 : options.bottomSpacer }
                ]};

                if (options.id){
                    _filter.id = options.id;
                }

                if (options.rows){
                    _filter = serviceList.Marge(_filter, options.rows);
                }

                _filter.relocateList = function (newList, noFocus) {
                    var needClear = !!options.List;

                    options.listName = newList;
                    options.List = $$(newList);

                    if (options.List){
                        if (needClear) {
                            clearFilter(null, noFocus);
                        }
                    } else {
                        options.listName = "";
                        options.List = null;
                    }
                };

                _filter.doFilter = doFilter;
                _filter.clearFilter = clearFilter;

                return _filter;
            },
            createLink  : function (projectID, taskID, commentID) {
                return serviceList.isIE()
                    ? serviceList.myReplaceFormated("#{protocol}//#{host}#{path}#project=#{project}#{task}",
                        {
                            protocol: window.location.protocol,
                            host    : window.location.host,
                            path    : window.location.pathname,
                            project : projectID,
                            task    : taskID ? "&task=" + taskID + (commentID ? "&comment=" + commentID : "") : ""
                        })
                    : serviceList.myReplaceFormated("#{origin}#{path}#project=#{project}#{task}",
                        {
                            origin  : window.location.origin,
                            path    : window.location.pathname,
                            project : projectID,
                            task    : taskID ? "&task=" + taskID + (commentID ? "&comment=" + commentID : "") : ""
                        });
            },
            insertIco   : function (ico, size, attr){
                return "<span class='webix_icon byCenter " + ico + " " + ((size) ? size : "fa-lg") + "' " + (attr ? (serviceList.isString(attr) ? attr : attr.join(' ')) : "") + "></span>"
            }
        },

        admin: {
            FilterFiled : function (options){
                function clearFilter(){
                    if (clearShowed) {
                        $$("clearBtn" + rnd).hide();

                        clearShowed = false;
                    }

                    options.prevText = "";

                    options.thisFilter.$setValue('');
                    options.List.filter("");

                    if (options.onClear) {
                        options.onClear();
                    }
                }

                function doFilter(reFilter, noFocus) {
                    if (options.thisFilter){
                        var saveText = options.thisFilter.getValue().toString();
                        var text = serviceList.trim(saveText).toLowerCase();

                        if (text == ""){
                            clearFilter();

                            if (!options.escPressed && !noFocus) {
                                options.thisFilter.focus();
                            }
                        } else {
                            var res = [];

                            if (!options.prevText || options.prevText != text || reFilter){
                                options.prevText = text;

                                if (options.onBeforeFilter){
                                    options.onBeforeFilter(text);
                                }

                                options.List.filter(function(obj){
                                    var fp = [];

                                    if (!options.filterParams || options.filterParams.length == 0){
                                        options.filterParams = ['value'];
                                    }

                                    for (var i = 0; i < options.filterParams.length; i++){
                                        if (serviceList.isArray(obj[options.filterParams[i]])){
                                            fp.push(obj[options.filterParams[i]].join('|'));
                                        } else {
                                            fp.push(obj[options.filterParams[i]]);
                                        }
                                    }

                                    var filter = fp.join('|');

                                    filter = filter.toString().toLowerCase();

                                    if (filter.indexOf(text) != -1) {
                                        res.push(obj);
                                    }

                                    return (filter.indexOf(text) != -1);
                                });

                                if (options.onAfterFilter){
                                    options.onAfterFilter(res);

                                    res = null;
                                }

                                if (!clearShowed) {
                                    clearShowed = true;

                                    var pos = serviceList.getCaretPosition(options.thisFilter.getInputNode());

                                    $$("clearBtn" + rnd).show();

                                    options.thisFilter.$setValue(saveText);

                                    if (!noFocus) {
                                        options.thisFilter.focus();

                                        serviceList.setCaretPosition(options.thisFilter.getInputNode(), pos);
                                    }
                                }
                            }
                        }
                    }
                }

                var rnd = Math.random();
                var clearShowed = false;
                var _filter = { rows: [
                    { cols: [
                        { view: "text", padding: 0, css: 'noPaddingChild', tooltip: options.tooltip || "", placeholder: options.searchText || serviceList.Lang(71), keyPressTimeout: 300, on: { // "71" : "Введите текст для поиска"
                            onKeyPress: function (code, e) {
                                options.escPressed = false;

                                if (code === 27 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
                                    options.escPressed = true;

                                    options.nowCleared = options.thisFilter.getValue().toString() != "";

                                    clearFilter();

                                    if (options.nowCleared) {
                                        this.focus();
                                    } else {
                                        this.blur();

                                        webix.UIManager.setFocus(document);
                                    }
                                }
                            },
                            onTimedKeypress : function () {
                                if (webix.UIManager.hasFocus(this)) {
                                    doFilter();
                                }
                            },
                            onAfterRender : function closeFilter(){
                                options.thisFilter = this;
                                _filter.thisFilter = this;
                                options.List       = $$(options.listName);

                                this.getInputNode().setAttribute("autocomplete", "off");
                            }
                        }},

                        { id: "clearBtn" + rnd, view: "button", type: "iconButton", icon: "times", height: 28, width: 27, hidden: true, click: clearFilter } // , css: "noBorder webixBlue"
                    ]},

                    { height: (options.bottomSpacer == undefined)? 4 : options.bottomSpacer }
                ]};

                if (options.id){
                    _filter.id = options.id;
                }

                _filter.doFilter = doFilter;
                _filter.clearFilter = clearFilter;

                return _filter;
            },
            checkRights : function (_id){
                var id = parseInt(_id) - 1;

                return mcConst.MyRightsSet.RightsSet[id] === '1' && (!_gLock.navAccess || (_gLock.navAccess && !_gLock.navAccess[_id]));
            }
        },

        chat: {
            FilterFiled : function (options){
                function clearFilter(){
                    if (clearShowed) {
                        $$(CLEAR).hide();

                        clearShowed = false;
                    }

                    options.thisFilter.$setValue('');
                    options.List.filter("");

                    if (options.onClear) {
                        options.onClear();
                    }
                }

                var rnd         = serviceList.RandomHash(6);
                var EDITOR      = (options.id || rnd) + "Editor";
                var CLEAR       = "clearBtn" + (options.clearID || rnd);
                var clearShowed = false;

                var _filter     = { id: options.id || (rnd + 'FilterBlock'), rows: [
                    { cols: [
                        { id: EDITOR, view: "text", padding: 0, css: 'noPaddingChild', placeholder: options.placeholder || "Search", keyPressTimeout: 500, on: { // "71" : "Введите текст для поиска"
                            onKeyPress: function(code, e){
                                if (options.keyEvents && options.keyEvents.hasOwnProperty(code)){
                                    if (options.keyEvents[code](options.List.getSelectedItem())) {
                                        e.preventDefault();
                                    }
                                }
                            },

                            onTimedKeypress : function(){
                                var origin = serviceList.trim(this.getValue().toString());
                                var text   = origin.toLowerCase();

                                if (text === ""){
                                    clearFilter();

                                    this.focus();
                                } else {
                                    var pos = serviceList.getCaretPosition(this.getInputNode());
                                    
                                    options.List.filter(function(obj){
                                        var fp = [];

                                        if (!options.filterParams || options.filterParams.length === 0){
                                            options.filterParams = ['value'];
                                        }

                                        for (var i = 0; i < options.filterParams.length; i++){
                                            if (serviceList.isArray(obj[options.filterParams[i]])){
                                                fp.push(obj[options.filterParams[i]].join('|'));
                                            } else {
                                                fp.push(obj[options.filterParams[i]]);
                                            }
                                        }

                                        var filter = fp.join('|');

                                        filter = filter.toString().toLowerCase();

                                        return (filter.indexOf(text) !== -1);
                                    });

                                    if (!clearShowed) {
                                        clearShowed = true;

                                        if (!options.noClearBtn) {
                                            $$(CLEAR).show();
                                        }

                                        // var pos = serviceList.getCaretPosition(this.getInputNode());

                                        this.$setValue(origin);
                                        this.focus();

                                        serviceList.setCaretPosition(options.thisFilter.getInputNode(), pos);
                                    }
                                }

                                if (options.OnEnterText){
                                    options.OnEnterText(origin);
                                }
                            },
                            onAfterRender : function(){
                                options.thisFilter = this;
                                options.List       = $$(options.listName);
                                options.hotkeyID   = serviceList.RandomHash(10);

                                $$(options.id || (rnd + 'FilterBlock')).onClear = function (cb) {
                                    options.onClear = cb;
                                };

                                if (serviceList._hotkeys){
                                    serviceList._hotkeys.addPreset(options.hotkeyID, [{
                                        key : mcConst.keyCodes.esc,
                                        // lockPrev: true,
                                        func: function closeFilter() {
                                            if (options.thisFilter.getValue().toString() !== "") {
                                                options.nowCleared = true;
                                            }

                                            clearFilter();

                                            options.thisFilter.focus();
                                        }
                                    }], options.thisFilter.getNode());
                                }
                            },
                            onDestruct: function () {
                                serviceList._hotkeys.removePreset(options.hotkeyID);
                            }
                        }},

                        { id: CLEAR, view: "button", type: "iconButton", icon: "times", width: 27, hidden: true, on: {onItemClick: clearFilter}, css: "icoButton button_primary byCenter iconNoPadding" } //
                    ]},

                    { height: (options.bottomSpacer == undefined)? 4 : options.bottomSpacer }
                ]};

                _filter.EDITOR = EDITOR;
                _filter.CLEAR  = CLEAR ;
                _filter.clearFilter = clearFilter;

                return _filter;
            }
        },

        forum: {
            FilterFiled : function (options){
                function clearFilter(){
                    if (clearShowed) {
                        $$("clearBtn" + rnd).hide();

                        clearShowed = false;
                    }

                    options.thisFilter.$setValue('');
                    options.List.filter("");

                    if (options.onClear) {
                        options.onClear();
                    }
                }

                var rnd = Math.random();
                var clearShowed = false;
                var res = { rows: [
                    { height: (options.topSpacer === undefined)? 0 : options.topSpacer },

                    { cols: [
                        { view: "search", padding: 0, css: 'noPaddingChild', placeholder: serviceList.Lang(options.placeHolder || 10), keyPressTimeout: 600, on: { // "10" :"Введите текст для поиска",
                            onKeyPress: function (code, e) {
                                if (code === 27 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
                                    clearFilter();

                                    this.focus();
                                }
                            },
                            onTimedKeypress : function(){
                                var text = serviceList.trim(this.getValue().toString()).toLowerCase();

                                if (!options.List) {
                                    options.List = $$(options.listName);
                                }

                                if (text == ""){
                                    clearFilter();

                                    this.focus();
                                } else {
                                    options.List.filter(function(obj){
                                        var fp = [];

                                        if (!options.filterParams || options.filterParams.length == 0){
                                            options.filterParams = ['value'];
                                        }

                                        for (var i = 0; i < options.filterParams.length; i++){
                                            fp.push(obj[options.filterParams[i]]);
                                        }

                                        var filter = fp.join('|');

                                        filter = filter.toString().toLowerCase();

                                        return (filter.indexOf(text) != -1);
                                    });

                                    if (!clearShowed) {
                                        clearShowed = true;

                                        var pos = serviceList.getCaretPosition(this.getInputNode());

                                        $$("clearBtn" + rnd).show();

                                        this.$setValue(text);
                                        this.focus();

                                        serviceList.setCaretPosition(this.getInputNode(), pos);
                                    }
                                }
                            },
                            onAfterRender : function(){
                                options.thisFilter = this;
                                options.List = $$(options.listName);
                            }
                        }},

                        { id: "clearBtn" + rnd, view: "button", type: "iconButton", icon: "times", width: 27, hidden: true, click: clearFilter } // , css: "noBorder webixBlue"
                    ]},

                    { height: (options.bottomSpacer == undefined)? 4 : options.bottomSpacer }
                ]};

                if (options.id){
                    res.id = options.id;
                }

                if (options.rows){
                    res = serviceList.Marge(res, options.rows);
                }

                res.relocateList = function (newList) {
                    options.List = $$(newList);

                    clearFilter();
                };

                return res;
            },
            createLink  : function (path) {
                return serviceList.isIE()
                    ? window.location.protocol + "//" + window.location.host + window.location.pathname + "#" + path
                    : window.location.origin + window.location.pathname + "#" + path;
            },
            disableContextMenu : function (_view, exception){
                webix.event(_view.$view, "contextmenu", function(e){
                    function emersion(target, count) {
                        var res = [];
                        var parent = target.parentElement;

                        if (parent){
                            res = res.concat(parent.className.split(" "));

                            if (count){
                                count --;
                            }

                            if (parent.nodeName !== "#document" && count > 0){
                                res = res.concat(emersion(parent, count));
                            }
                        }

                        return res;
                    }

                    function skipItems(items, where, target) {
                        var flag = true;

                        if (items && items.length){
                            items.forEach(function (item) {
                                if ((where.length && where.indexOf(item) !== -1) || emersion(target, 4).indexOf(item) !== -1){
                                    flag = false;
                                }
                            });
                        }

                        return flag;
                    }

                    if (((e.target.nodeName !== "INPUT") && (e.target.nodeName !== "TEXTAREA") && skipItems(exception, e.target.className.split(" "), e.target))
                        || (e.target.type === "button")) {
                        e.preventDefault();
                    }
                });
            }
        }
    };

    if (_api && multiple[_api]){
        Object.keys(multiple[_api]).forEach(function (keyName) {
            serviceList[keyName] = multiple[_api][keyName];
        });
    }

    return serviceList;
}


if (typeof exports === 'object') {
    exports.srv = servicesController();
}


function mcCmdParser (mcConnect, $rootScope){
    "use strict";

    var SendCMDToServer = {};
    var ProcessCMD      = {};
    var pCmd = {};

    this.ProcessCMD = ProcessCMD;
    this.SendCMDToServer = SendCMDToServer;

    var _CMD_ = mcConst._CMD_;

    // ====================================================================================

    function customCallBack (cmd, data){
        if ($rootScope.customCallBack[cmd] && $rootScope.customCallBack[cmd].length > 0){
            $rootScope.customCallBack[cmd].shift()(data);

            if ($rootScope.customCallBack[cmd].length == 0){
                delete $rootScope.customCallBack[cmd];
            }

            return true;
        }
    }

    /**
     * @return {boolean}
     */
    this.MultiSender = function(){
        var cmd  = arguments[0];
        var sID  = arguments[1];
        var next = true;

        if (!SendCMDToServer[cmd] && mcService.InObject(cmd, _CMD_)){
            var data = "";

            for (var i = 1; i < arguments.length; i++){
                data += arguments[i] + ((i !== arguments.length)?  mcConst.CR : "");
            }

            mcConnect.SendDataToServer({
                CMD  : cmd,
                Data : data
            });

            mcConnect.SendDataToServer({
                CMD  : _CMD_.Ping,
                Data : sID
            });

            next = false;
        }

        return next;
    };

    /**
     * @return {boolean}
     */
    this.MultiParser = function (cmd, data) {
        if (!pCmd.hasOwnProperty(cmd)){
            return true;
        }

        if ($rootScope.lockedCMD[pCmd[cmd]]){
            $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

            delete $rootScope.lockedCMD[cmd];
        }

        customCallBack(pCmd[cmd], mcService.StringToObj(data));
    };

    function criticalError (err){
        return ((mcService.inArray(err, [3, 4, 14, 15, /*17,*/ 23, 24, 44, 60, 71, 79, 80, 81, 82, 92]) != -1) && mcConst.LoggedIn) || !mcConst.LoggedIn;
    }

    function runError(numb, params){
        if ($rootScope.ErrorActions[numb]){
            $rootScope.ErrorActions[numb](params);
        } else {
            return false;
        }
    }

    function initSocketIo() {
        if (window.hasOwnProperty("io") && mcService.isFunction(window.io) && !mcConst.socketIO){
            mcConst.socketIO = io({
                transports: mcConst.io_transport,
                'reconnection': true,
                'reconnectionAttempts': 5,
                'timeout' : 20000
            });

            mcConst.initWS(mcConst.socketIO);
        }
    }

    // ====================================================================================

    pCmd[_CMD_.sc_adm_user_created]                    = _CMD_.cs_adm_create_user;
    pCmd[_CMD_.sc_private_info]                        = _CMD_.cs_get_private_info;
    pCmd[_CMD_.sc_kanban_project_tags_list]            = _CMD_.cs_kanban_get_project_tags_list;
    pCmd[_CMD_.sc_kanban_dashboard]                    = _CMD_.cs_kanban_get_dashboard;
    pCmd[_CMD_.sc_removed_user_info]                   = _CMD_.cs_get_removed_user_info;
    pCmd[_CMD_.sc_file_exists]                         = _CMD_.cs_is_file_exists;
    pCmd[_CMD_.sc_image_thumbs]                        = _CMD_.cs_get_image_thumbs;
    pCmd[_CMD_.sc_kanban_task_taken]                   = _CMD_.cs_kanban_task_take;
    // pCmd[_CMD_.sc_personal_users_groups_list]          = _CMD_.cs_adm_get_personal_user_groups;
    pCmd[_CMD_.sc_personal_users_groups_names_list]    = _CMD_.cs_get_personal_user_groups;
    pCmd[_CMD_.sc_public_users_groups_list]            = _CMD_.cs_get_public_user_groups;
    pCmd[_CMD_.sc_adm_list_roles]                      = _CMD_.cs_adm_list_roles;
    pCmd[_CMD_.sc_kanban_rights_group_list]            = _CMD_.cs_get_kanban_rights_group_list;
    pCmd[_CMD_.sc_new_personal_user_group_created]     = _CMD_.cs_create_personal_user_group;
    pCmd[_CMD_.sc_kanban_project_group_list]           = _CMD_.cs_get_kanban_get_projects_groups;
    pCmd[_CMD_.sc_kanban_user_group_added_to_project]  = _CMD_.cs_kanban_add_user_group_to_project;
    // pCmd[_CMD_.sc_personal_user_group_data]            = _CMD_.cs_get_personal_user_group_data;
    pCmd[_CMD_.sc_kanban_removed_user_groups_from_project] = _CMD_.cs_kanban_remove_user_groups_from_project;
    pCmd[_CMD_.sc_common_user_group_data]              = _CMD_.cs_get_common_user_group_data;
    pCmd[_CMD_.sc_adm_list_users]                      = _CMD_.cs_adm_list_users;
    // pCmd[_CMD_.sc_personal_user_group_data]            = _CMD_.cs_add_users_to_personal_user_group;
    pCmd[_CMD_.sc_users_from_personal_user_group_removed]   = _CMD_.cs_remove_users_from_personal_user_group;
    // pCmd[_CMD_.sc_kanban_user_groups_access_to_stage_added] = _CMD_.cs_kanban_add_access_user_groups_to_stage;

    // ====================================================================================

    ProcessCMD[_CMD_.sc_login] = function sc_login(_data) {
        mcService.Marge(mcConst.UserInfo, _data || {
            "UIN"             : 17,        // UIN пользователя, который залогинен в системе
            "Nick"            : "Chapay",  // ник пользователя, который сообщил ему сервер
            "Sex"             : 1,         // пол пользователя
            "Avatar"          : 54,        // номер аватара пользователя
            "LoginStyle"      : "login",   // тип логина: "login" - обычный логин, "domain" - доменная авторизация
            "AutoAwayTime"    : 15,        // время в минутах, после которого будет считаться, что пользователь неактивен за компьютером, если он не нажимал ничего на клавиатуре и не шевелил мышкой
            "ServerSignature" : "MyChat4/16....", // специальная сигнатура сервера MyChat, нужна для системы статистики

            "Domain"          : "" // если доменная авторизация, "LoginStyle" = "domain", то в этом параметре передаётся ещё и имя домена, с которого проходит аторизация.
                                             // Если доменной авторизации нет - этот параметр не передаётся
        });

        mcConst.UserInfo.MaxUploadFileSize = mcConst.UserInfo.MaxUploadFileSize * 1024 * 1024;
        
        mcConst.LoggedIn = true;

        if (!mcConst.LoginInfo.login) {
            mcConst.LoginInfo.login = _data.InternalNick;
        }

        mcService.SaveSessionSettings(mcConst.LoginInfo);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock
    };

    ProcessCMD[_CMD_.sc_error] = function sc_error(_data){
        var err = mcService.StringToObj(_data) || {
            "ErrNum" : 21,        // номер ошибки
            "Params" : [          // параметры ошибки, если есть, параметров может быть любое количество, все - текстовые строки
                "bla-bla-bla",
                "test-test-test"
            ]
        };

        console.error(err);

        if (criticalError(err.ErrNum)){
            mcConst.socketIO.disconnect();
            mcConst.socketIO = null;

            $rootScope.$broadcast('StopPingTimer', []);
        }

        if (!mcConst.LoggedIn && $rootScope.loginFormNotShowed) {
            $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

            runError(err.ErrNum, err.Params);
        } else {
            if (mcConst.LoggedIn && err.ErrNum !== 17 || !mcConst.LoggedIn) {// skip error
                $rootScope.$broadcast('ErrorMsg', [err.ErrNum, mcConst.ErrorText[err.ErrNum], err.Params, function () {
                    if (!mcConst.LoggedIn) {
                        $rootScope.$broadcast('enable' + mcConst.dataModels.Login, []);
                    } else if (criticalError(err.ErrNum)) {
                        location.search = "";
                        location.reload();
                    }

                    $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

                    runError(err.ErrNum, err.Params);
                }]);
            } else {
                runError(err.ErrNum, err.Params);
            }
        }
    };

    ProcessCMD[_CMD_.sc_halt] = function(){
        if (mcConst.LoggedIn || mcConst.PingTimer) {
            $rootScope.$broadcast('StopPingTimer' ,[]);

            mcConst.socketIO.disconnect();
            mcConst.socketIO = null;

            $rootScope.$broadcast('ErrorMsg', [_CMD_.Errors.Disconnected, mcService.Lang(14), '', function(){
                mcService.SaveSessionSettings(mcConst.LoginInfo.login, "", "", "");

                location.search = "";
                location.reload();
            }]);
        }
    };

    ProcessCMD[_CMD_.sc_get_all_rights] = function sc_get_all_rights(_data){
        var rSet = _data.RightsSet;

        for (var i = 0; i < rSet.length; i++){
            mcConst.MyRightsSet[i] = (rSet[i] !== '0');
        }

        mcConst.originRightsSet = rSet;

        if (!mcConst.customLogin) {
            $rootScope.$broadcast('hideLogin', []);
            $rootScope.$broadcast('show' + mcConst.dataModels.Wrapper, []);

            console.log('Logged in!');
        }
    };

    ProcessCMD[_CMD_.sc_drop_connect] = function sc_drop_connect(){
        mcConst.socketIO.disconnect();
        mcConst.socketIO = null;

        if (!mcConst.LoggedIn && mcConst.PingTimer){
            mcService.SaveSessionSettings(mcConst.LoginInfo.login, "", "", false);

            $rootScope.$broadcast('StopPingTimer' ,[]);
            $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

            mcConst.LoginInfo.pwd     = "";
            mcConst.LoginInfo.servPwd = "";
            mcConst.LoginInfo.rm      = false;

            $rootScope.$broadcast('show' + mcConst.dataModels.Login, []);
        } else
        if (mcConst.LoggedIn || mcConst.PingTimer) {
            $rootScope.$broadcast('StopPingTimer' ,[]);

            $rootScope.$broadcast('ErrorMsg', [_CMD_.Errors.Disconnected, mcService.Lang(14), '', function(){
                location.search = "";
                location.reload();
            }]);
        }
    };

    ProcessCMD[_CMD_.sc_adm_command_complete ] = function sc_adm_command_complete(data){
        var info = mcService.StringToObj(data);

        switch (info.Cmd){
            case _CMD_.cs_adm_set_user_foto:
                $rootScope.$broadcast('profileFotoRefresh');
            break;

//          cs_adm_remove_all_logs:
//          cs_adm_make_backup_server_db:
//          cs_adm_job_positions_del:
//          cs_adm_job_positions_set_sequence:
//          cs_adm_role_delete:
//          cs_adm_delete_websupport_group:
//          cs_adm_remove_user:
//          cs_adm_del_bans:
//          cs_adm_delete_moderators:
//          cs_adm_add_moderator:
//          cs_adm_del_moderator_confs
//          cs_adm_kill_users
//          cs_adm_halt_users

//          cs_kanban_task_move
//          cs_kanban_task_remove
//          cs_kanban_close_project
//          cs_kanban_reopen_project
//          cs_kanban_delete_project

            default:
                if ($rootScope.lockedCMD[info.Cmd]){
                    $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

                    delete $rootScope.lockedCMD[info.Cmd];
                }

                customCallBack(info.Cmd);
        }
    };

    ProcessCMD[_CMD_.sc_adm_user_created] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_adm_create_user, info);
    };

    ProcessCMD[_CMD_.sc_kanban_projects_list] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_get_projects_list, info);
    };

    ProcessCMD[_CMD_.sc_kanban_stages_list] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_get_stages_list, info);
    };

    ProcessCMD[_CMD_.sc_kanban_tags_list] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_get_tags_list, info);
    };

    ProcessCMD[_CMD_.sc_kanban_tasks_list] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_get_tasks_list, info);
    };

    ProcessCMD[_CMD_.sc_kanban_project_created] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_add_project, info);
    };

    ProcessCMD[_CMD_.sc_kanban_stage_created] = function(data){
        var info = mcService.StringToObj(data);

        //$rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_add_stage, info);
    };

    ProcessCMD[_CMD_.sc_kanban_task_created] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        // $rootScope.$broadcast('addProjectToList', info);

        customCallBack(_CMD_.cs_kanban_add_task, info);
    };

    ProcessCMD[_CMD_.sc_kanban_task_comments_list] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_get_comments, info);
    };

    ProcessCMD[_CMD_.sc_kanban_task_comment_added] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_add_comment, info);
    };

    ProcessCMD[_CMD_.sc_kanban_personal_options_list] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_get_personal_options, info);
    };

    ProcessCMD[_CMD_.sc_kanban_event] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('onKanbanEvent', info);
    };

    ProcessCMD[_CMD_.sc_kanban_project_info] = function(data){
        var info = mcService.StringToObj(data);

        customCallBack(_CMD_.cs_kanban_get_project_info, info);
    };

    ProcessCMD[_CMD_.sc_kanban_task_info] = function(data){
        var info = mcService.StringToObj(data);

        customCallBack(_CMD_.cs_kanban_get_task_info, info);
    };

    ProcessCMD[_CMD_.sc_users_list] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_get_users_list, info);
    };

    ProcessCMD[_CMD_.sc_kanban_last_performers_list] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_kanban_get_last_performers, info);
    };

    ProcessCMD[_CMD_.sc_personal_user_group_data] = function(data){
        var info = mcService.StringToObj(data);

        $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

        customCallBack(_CMD_.cs_get_personal_user_group_data, info);
        customCallBack(_CMD_.cs_add_users_to_personal_user_group, info);
    };

    ProcessCMD[_CMD_.sc_web_services_info]  = function (data) {
        var info = mcService.StringToObj(data);

        // "AliasAdmin" : "admin", // папка-псевдоним, путь к админке
        // "AliasAPI" : "API", // папка-псевдоним, путь к Integration API
        // "AliasChat" : "chat", // папка-псевдоним, путь к WEB-чату
        // "AliasFiles" : "files", // папка-псевдоним, путь к файлам, залитым на сервер
        // "AliasForum" : "forum", // папка-псевдоним, путь ко встроенному форуму
        // "AliasKanban" : "kanban", // папка-псевдоним, путь к канбан-доске
        //
        // "LobbyEnable" : true, // включен доступ к списку сервисов MyChat
        // "ForumEnable" : true, // включить доступ ко встроенному форуму
        // "KanbanEnable" : true, // включить доступ к канбан-доске
        // "ChatEnable" : true // включить доступ к WEB-чату
        // "AdminEnable" : true // включить доступ к WEB-админке

        mcConst.pathAliases = info;
    };

    // ==================================================================================

    SendCMDToServer[_CMD_.Login] = function CS_CMD_Login(_Auth, _PWD, _servPWD, _RM) {
        mcConst.LoginInfo.login = _Auth.replace('\\', "/");

        // mcConst.socketIO = io({
        //     'reconnection': true,
        //     'reconnectionAttempts': 5,
        //     'timeout' : 7000
        // });
        //
        // mcConst.initWS(mcConst.socketIO);
        initSocketIo();

        mcConnect.SetCallBackFunctionByCMD(_CMD_.OK, function () {
            var data = this.split(mcConst.CR);

            mcConst.SessionID = data[0];

            //console.log('SessionID: ' + mcConst.SessionID);

            if (data.length > 1) {
                mcConst.LoginInfo.pwd     = data[1];
                mcConst.LoginInfo.servPwd = data[2];
            }

            $rootScope.$broadcast('StartPingTimer', []);
        });

        mcConnect.SetCallBackFunctionByCMD(_CMD_.Blocked, function () {
            var errCode = this;

            mcService.SaveSessionSettings("", "", "", false);

            $rootScope.$broadcast('enable' + mcConst.dataModels.Login, []);
            $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

            $rootScope.$broadcast('ErrorMsg', [errCode, mcConst.ErrorText[errCode]]);
        });

        mcConnect.SendDataToServer({
            CMD: _CMD_.Login,
            Data: {
                UIN       : mcConst.LoginInfo.login,
                Pass      : _PWD,
                ServPass  : _servPWD,
                RM        : _RM,
                ClientType: 'kanban',
                PingDelay : 1,
                Style     : mcConst.LoginInfo.login.indexOf('/') == -1 ? 0 : 1,
                UTC       : (new Date()).getTimezoneOffset() / -60
            }
        });
    };

    SendCMDToServer[_CMD_.cs_login_by_token] = function cs_login_by_token(token) {
        // mcConst.socketIO = io({
        //     'reconnection': true,
        //     'reconnectionAttempts': 5,
        //     'timeout' : 7000
        // });
        //
        // mcConst.initWS(mcConst.socketIO);
        initSocketIo();

        mcConnect.SetCallBackFunctionByCMD(_CMD_.OK, function () {
            var data = this.split(mcConst.CR);

            mcConst.SessionID = data[0];

            $rootScope.$broadcast('StartPingTimer', []);
        });

        mcConnect.SetCallBackFunctionByCMD(_CMD_.Blocked, function () {
            var errCode = this;

            mcService.SaveSessionSettings("", "", "", false);

            $rootScope.$broadcast('enable' + mcConst.dataModels.Login, []);
            $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock

            $rootScope.$broadcast('ErrorMsg', [errCode, mcConst.ErrorText[errCode]]);
        });

        mcConnect.SendDataToServer({
            CMD : _CMD_.cs_login_by_token,
            Data: {
                Token     : token,
                ClientType: 'kanban',
                PingDelay : 1,
                UserAgent : navigator.userAgent || " ",
                Referral  : document.referrer || " ",
                State     : mcConst.currentState,
                UTC       : (new Date()).getTimezoneOffset() / -60
            }
        });
    };

    SendCMDToServer[_CMD_.cs_quit] = function cs_quit(sID){
        mcConnect.SendDataToServer({
            CMD  : _CMD_.cs_quit,
            Data : sID,
            immediately: true
        })
    };

    SendCMDToServer[_CMD_.Ping] = function CS_CMD_Ping(sID){
        mcConnect.SendDataToServer({
            CMD  : _CMD_.Ping,
            Data : sID
        })
    };

}
/**
 * Created by Gifer on 21.07.2015.
 */

var _Dragging = function(){
    return {
        move : function(divid, xpos, ypos){
            divid.style.left = xpos + 'px';
            divid.style.top  = ypos + 'px';
        },
        startMoving : function(divid, container, evt){
            evt = evt || window.event;

            var posX  = evt.clientX,
                posY  = evt.clientY,
                diffX = posX - divid.offsetLeft,
                diffY = posY - divid.offsetTop,

                parent = document.getElementById(container),

                eWi = parseInt(divid.offsetWidth),
                eHe = parseInt(divid.offsetHeight),
                cWi = parseInt(parent.offsetWidth),
                cHe = parseInt(parent.offsetHeight);

            document.onmousemove = function(evt){
                evt = evt || window.event;

                var posX = evt.clientX,
                    posY = evt.clientY,
                    aX = posX - diffX,
                    aY = posY - diffY;

                if (aX < 0) aX = 0;
                if (aY < 0) aY = 0;
                if (aX + eWi > cWi) aX = cWi - eWi;
                if (aY + eHe > cHe) aY = cHe -eHe;

                _Dragging.move(divid,aX,aY);
            }
        },
        stopMoving : function(){
            document.onmousemove = function(){}
        }
    }
}();

// =================================================================

function setLocalizations(){
    webix.i18n.parseFormat = '%d.%m.%Y';
    webix.i18n.locales["en"] = webix.i18n.locales["en-US"];

    webix.i18n.locales["ru"]={
        groupDelimiter:",",
        groupSize:3,
        decimalDelimeter:".",
        decimalSize:2,

        dateFormat:"%d.%m.%Y",
        timeFormat:"%h:%i %A",
        longDateFormat:"%d %F %Y",
        fullDateFormat:"%d/%m/%Y %h:%i %A",

        price:"${obj}",
        priceSettings:null, //use number defaults


        fileSize: ["b","Kb","Mb","Gb","Tb","Pb","Eb"],

        calendar: {
            monthFull  : ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь" ],
            monthShort : ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек" ],
            dayFull    : ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"],
            dayShort   : ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
            hours      : "Час",
            minutes    : "Минут",
            done       : "Готово"
        },
        controls:{
            select:"Выбрать"
        }
    };

    webix.i18n.locales["uk"]={
        groupDelimiter:",",
        groupSize:3,
        decimalDelimeter:".",
        decimalSize:2,

        dateFormat:"%d.%m.%Y",
        timeFormat:"%h:%i %A",
        longDateFormat:"%d %F %Y",
        fullDateFormat:"%d/%m/%Y %h:%i %A",

        price:"${obj}",
        priceSettings:null, //use number defaults


        fileSize: ["b","Kb","Mb","Gb","Tb","Pb","Eb"],

        calendar: {
            monthFull  : ["Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень" ],
            monthShort : ["Січ", "Лют", "Бер", "Кві", "Тра", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Гру" ],
            dayFull    : ["Неділя", "Понеділок", "Вівторок", "Середа", "Четвер", "П'ятниця", "Субота"],
            dayShort   : ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
            hours      : "Годинн",
            minutes    : "Хвилин",
            done       : "Готово"
        },
        controls:{
            select:"Обрати"
        }
    };
}

Date.prototype.startOfDay = function(){
    var now = this.getTime();

    return  now - (this.getHours()*1000*60*60 + this.getMinutes()*1000*60 + this.getSeconds()*1000 + this.getMilliseconds());
};


 function mcKanbanManager($rootScope){
     "use strict";

     var listProjects = {};
     var listStages   = {};
     var eventsList   = {
         what : {
             project : 0,
             stage   : 1,
             task    : 2,
             comment : 3,
             tags    : 4
         }
     };
     var dashboardTasksCount = 0;

     function getProjectsList(cb, reload){
         $rootScope.SendCMDToServer([
             mcConst.lockInterface,
             mcConst._CMD_.cs_kanban_get_projects_list,
             mcConst.SessionID,
             function( data ){
                 // if (reload){
                 //     listProjects = mcService.Marge(listProjects, mcService.convertArrayObjToObj(data, "ID"));
                 // } else {
                 //     listProjects = mcService.convertArrayObjToObj(data, "ID");
                 // }
                 var newList = {};

                 Object.keys(data).forEach(function (item) {
                     var id = data[item].ID;

                     if (listProjects.hasOwnProperty(id)){
                         newList[id] = mcService.Marge(listProjects[id], data[item]);
                     } else {
                         newList[id] = data[item];
                     }
                 });

                 listProjects = newList;

                 if (cb){
                     cb(webix.copy(listProjects));
                 }
             }
         ]);
     }

     function getStagesList(idProject, cb){
         if (listProjects && listProjects.hasOwnProperty(idProject)){
             $rootScope.SendCMDToServer([
                 mcConst._CMD_.cs_kanban_get_stages_list,
                 mcConst.SessionID,

                 idProject,

                 function ( _data ){
                     var data = _data.Data;
                     
                     listProjects[idProject].Stages  = data;
                     listProjects[idProject]._stages = [];

                     for (var i = 0; i < data.length; i++){
                         listStages[data[i].ID] = data[i];
                         listStages[data[i].ID].taskList = [];

                         listProjects[idProject]._stages.push(data[i].ID);
                     }

                     if (cb){
                         cb(webix.copy(listProjects[idProject].Stages));
                     }
                 }
             ]);
         } else {
             if (cb) cb(null);
         }
     }

     function getCurrentStagesList(cb) {
         if ($rootScope.currentProject && listProjects.hasOwnProperty($rootScope.currentProject) && cb){
             cb(listProjects[$rootScope.currentProject]._stages);
         }
     }

     function getTagsList(params, cb)   {
         $rootScope.SendCMDToServer([
             mcConst._CMD_.cs_kanban_get_tags_list,
             mcConst.SessionID,
             
             params.typical,
             params.done,
             params.archive,
             true,

             function( data ){
                 if (cb){
                     cb({
                         Tags: data
                     });
                 }
             }
         ]);
     }

     function getTasksList(idProject, cb){
         if (listProjects && listProjects.hasOwnProperty(idProject) && cb){
             var stages = listProjects[idProject]._stages || "";

             $rootScope.SendCMDToServer([
                 mcConst.lockInterface,
                 mcConst._CMD_.cs_kanban_get_tasks_list,
                 mcConst.SessionID,
                 stages.join(','),
                 
                 function( data ){
                     for (var i = 0; i < data.length; i++){
                         var id = mcService.findItemInArrayOfObj(listStages[data[i].status].taskList, data[i].ID, "ID");

                         if (id === -1){
                             listStages[data[i].status].taskList.push(data[i]);
                         } else {
                             listStages[data[i].status].taskList[id] = data[i];
                         }
                     }

                     if (cb){
                         getTasksOfStage(stages, cb);
                     }
                 }
             ]);
         } else {
             if (cb) cb(null);
         }
     }

     function getTasksOfStage(stageList, cb){
         var res = [];

         for (var i = 0; i < stageList.length; i++){
             listStages[stageList[i]].taskList.forEach(function (itm) {
                 res.push(mcService.Marge({}, itm));
             });
         }

         if (cb) cb(res);
     }

     function getDashboardList(params, cb){
         $rootScope.SendCMDToServer([
             mcConst.lockInterface,
             mcConst._CMD_.cs_kanban_get_dashboard,
             mcConst.SessionID,
             params.typical,
             params.done,
             params.archive,
             function (data) {
                 dashboardTasksCount = data.length;

                 if (cb){
                     cb(data);
                 } else {
                     console.log('getDashboardList: not set callback');
                 }
             }
         ]);
     }

     function getDashboardTasksCount() {
         return dashboardTasksCount;
     }

     function getProjectTags(projectID, cb) {
         $rootScope.SendCMDToServer([
             mcConst._CMD_.cs_kanban_get_project_tags_list,
             mcConst.SessionID,
             projectID,
             
             function fillTags(data) {
                 if (listProjects.hasOwnProperty(projectID)) {
                     listProjects[projectID].Tags = data;

                     if (cb) {
                         cb(data);
                     }
                 }
             }
         ]);
     }

     function getOptions(cb) {
         $rootScope.SendCMDToServer([
             mcConst._CMD_.cs_kanban_get_personal_options,
             mcConst.SessionID,
             function( data ){
                 $rootScope.kanbanOptions = mcService.Marge($rootScope.kanbanOptions, data.KANBAN);

                 if (cb) {
                     cb($rootScope.kanbanOptions);
                 }
             }
         ]);
     }

     function sendKanbanOptions(options, target) {
         var sendData = [
             mcConst._CMD_.cs_kanban_save_personal_options,
             mcConst.SessionID
         ];

         var data     = { KANBAN: {}};
         var pathWay  = [];
         var path     = $rootScope.kanbanOptions;
         var dataPath = data.KANBAN;

         if (mcService.isString(target)){
             pathWay = target.split('.');

             for (var i = 0; i < pathWay.length; i++){
                 if (!path.hasOwnProperty(pathWay[i])){
                     if (i == pathWay.length - 1){
                         path[pathWay[i]] = "";
                     } else {
                         console.error("Kanban hasn't options: " + target);

                         return;
                     }
                 }

                 if (mcService.isObject(path[pathWay[i]])){
                     path = path[pathWay[i]];

                     dataPath[pathWay[i]] = {};
                     dataPath = dataPath[pathWay[i]];
                 } else
                 if (i !== pathWay.length - 1){
                     console.error("Kanban hasn't options: " + target);

                     return;
                 }
             }

             i--;

             if (mcService.isObject(options)){
                 path     = mcService.Marge(path, options);
                 dataPath = mcService.Marge(dataPath, path);
             } else {
                 path[pathWay[i]]     = options;
                 dataPath[pathWay[i]] = options;
             }
         } else
         if (mcService.isObject(target)){
             target = mcService.Marge(target, options);

             data = { KANBAN: $rootScope.kanbanOptions };
         } else
         if (!target && mcService.isObject(options)) {
             $rootScope.kanbanOptions = mcService.Marge($rootScope.kanbanOptions, options);

             data = { KANBAN: $rootScope.kanbanOptions };
         } else {
             console.error("Options must be Object or set target for set options");

             return;
         }

         sendData.push(JSON.stringify(data));

         $rootScope.SendCMDToServer( sendData );
     }

     function kanbanEvents(event) {
         // "What"      : 0, // 0 - project (ProjectID), 1 - stage (ProjectID + StageID), 2 - task (ProjectID + TaskID), 3 - comment (ProjectID + TaskID + CommentID)
         // "Action"    : 0, // 0 - created, 1 - changed, 2 - deleted
         //
         // "ProjectID" : 1,  // идентификатор проекта
         // "StageID"   : 2,  // идентификатор этапа
         // "TaskID"    : 3,  // идентификатор задачи
         // "CommentID" : 4   // идентификатор комментария

         switch (event.What){
             case eventsList.what.project:
                 $rootScope.$broadcast(window._messages_.Toolbar.onProjectEvent, [event.Action, event.ProjectID]);
             break;

             case eventsList.what.task:
                 $rootScope.$broadcast(window._messages_.KanbanBody.onTaskEvent, [event.Action, event.ProjectID, event.TaskID]);
             break;

             case eventsList.what.stage:
                 $rootScope.$broadcast(window._messages_.KanbanBody.onStageEvent, [event.Action, event.ProjectID, event.StageID]);
             break;

             case eventsList.what.comment:
                 $rootScope.$broadcast(window._messages_.KanbanBody.onCommentEvent, [event.Action, event.ProjectID, event.TaskID, event.CommentID]);
             break;

             case eventsList.what.tags:
                 $rootScope.$broadcast(window._messages_.Menu.onTagsEvent, [event.Action, event.ProjectID]);
             break;
         }
     }

     function changeStageInfo(info) {
         if (listStages.hasOwnProperty(info.ID)){
             info.Name     = info.Name || listStages[info.ID].Name;
             info.Position = listStages[info.ID].Position;

             $rootScope.SendCMDToServer([
                 mcConst._CMD_.cs_kanban_modify_stage,
                 mcConst.SessionID,
                 
                 info.ID,
                 info.Name,
                 info.Description || "",
                 info.Type || 0,
                 info.NamePosition,
                 
                 function(){
                     listStages[info.ID] = info;

                     if (info.done){
                         info.done(info);

                         delete info.done;
                     }
                 }
             ]);
         }
     }

     //==========================================

     var _msg = _messages_.kanbanManager = {
         sendKanbanOptions: 'sendKanbanOptions',
         addedNewProject  : 'addedNewProject',
         removingProject  : 'removingProject',
         modifyingProject : 'modifyingProject',
         getOptions       : 'getOptions',
         getTasksOfStage  : 'getTasksOfStage',
         getTasksList     : 'getTasksList',
         getProjectName   : 'getProjectName',
         getTagsList      : 'getTagsList',
         getStagesList    : 'getStagesList',
         getCurrentStagesList : 'getCurrentStagesList',
         getProjectsList  : 'getProjectsList',
         getProjectTags   : 'getProjectTags',
         onKanbanEvent    : 'onKanbanEvent',
         getDashboardList : 'getDashboardList',
         getDashboardTasksCount : 'getDashboardTasksCount',
         ChangeStageInfo  : 'ChangeStageInfo'
     };

     $rootScope.$on(_msg.sendKanbanOptions, function (err, args) {
         sendKanbanOptions.apply(null, args);
     });

     $rootScope.$on(_msg.addedNewProject, function(err, args){
         var pr = args[0];

         listProjects[pr.ID] = pr;

         getStagesList(getStagesList);
     });

     $rootScope.$on(_msg.removingProject, function(err, args){
         var project = args[0];

         if (listProjects.hasOwnProperty(project)) {
             delete listProjects[project];
         }
     });

     $rootScope.$on(_msg.modifyingProject, function(err, args){
         var project = args[0];

         if (listProjects.hasOwnProperty(project.ID)) {
             listProjects[project.ID] = mcService.Marge(listProjects[project.ID], project);
         }
     });

     $rootScope.$on(_msg.getOptions, function(e, args){
         getOptions.apply(null, args);
     });

     $rootScope.$on(_msg.getTasksOfStage, function(e, args){
         getTasksOfStage.apply(null, args);
     });

     $rootScope.$on(_msg.getTasksList, function(e, args){
         getTasksList.apply(null, args);
     });

     $rootScope.$on(_msg.getProjectName, function(e, args){
         var ID = args[0];
         var cb = args[1];

         cb(listProjects[ID].Name);
     });

     $rootScope.$on(_msg.getTagsList, function(e, args){
         getTagsList.apply(null, args);
     });

     $rootScope.$on(_msg.getStagesList, function(e, args){
         getStagesList.apply(null, args);
     });

     $rootScope.$on(_msg.getCurrentStagesList, function(e, args){
         getCurrentStagesList.apply(null, args);
     });

     $rootScope.$on(_msg.getProjectsList, function(e, args){
         getProjectsList.apply(null, args);
     });

     $rootScope.$on(_msg.getProjectTags, function(e, args){
         getProjectTags.apply(null, args);
     });

     $rootScope.$on(_msg.onKanbanEvent, function(e, kbEvnt){
         kanbanEvents.call(null, kbEvnt);
     });

     $rootScope.$on(_msg.getDashboardList, function(e, args){
         getDashboardList.apply(null, args);
     });

     $rootScope.$on(_msg.getDashboardTasksCount, function(e, cb){
         if (mcService.isFunction(cb)){
             cb(getDashboardTasksCount());
         }
     });

     $rootScope.$on(_msg.ChangeStageInfo, function(e, args){
         changeStageInfo.apply(null, args);
     });
 }

 /**
 * Created by Gifer on 12.05.2016.
 */

 function mcKanbanPrepare($rootScope) {
     "use strict";

     var oneday = 60*60*24;

     webix.ready(function(){
         /*{
          "ID"               : 17,                    // идентификатор задачи, число
          "status"           : 23,                    // идентификатор этапа
          "UINOwner"         : 22,                    // идентификатор пользователя, который поставил задачу
          "DisplayNameOwner" : "John Smith",          // имя пользователя, поставившего задачу
          "SexOwner"         : 1,                     // пол
          "AvatarOwner"      : 83,                    // аватар
          "Name"             : "qwerty",
          "Desc"             : "New task",            // описание задачи
          "Priority"         : 0,                     // важность задачи, приоритет
          "dtCreated"        : "dd.mm.yyyy.hh.nn.ss", // дата и время создания задачи
          "dtDeadLine"       : "dd.mm.yyyy.hh.nn.ss", // дата и время дедлайна, если есть. Если нет - 01.01.3000 года
          "dtClosed"         : "dd.mm.yyyy.hh.nn.ss", // дата и время закрітия задачи, если нету - 01.01.3000 года
          "State"            : 0                      // статус задачи
          }*/

         webix.type(webix.ui.kanbanlist, {
             name : "mcTask",
             gears: [],

             templateBody: function(obj){
                 return obj.parseName;
             },

             templateAvatar: function(obj){
                 return mcService.getAvatar(obj.SexPerformer);
             },

             templateIcons: function (obj, common) {
                 var icons = [];
                 var icon  = null;
                 var id    = "";
                 var html  = "";

                 for (var i = 0; i < common.icons.length; i++){
                     icon = common.icons[i];

                     if (!icon.isGear && (!icon.show || icon.show(obj))){
                         id  = icon.id || icon.icon;
                         var css = mcService.trim(icon.css || "") || "";

                         html = '<span webix_icon_id="' + id + '" class="webix_kanban_icon ' + css +
                             '" title="' + (icon.hint ? (mcService.isFunction(icon.hint) ? icon.hint(obj) : icon.hint) : "") + '">' +
                             ( icon.link
                                     ? '<a href="' + obj.Link + '" class="fa-' + icon.icon + ' webix_icon f12" target="_blank"></a>'
                                     : '<span class="fa-' + icon.icon + ' webix_icon"></span>'
                             );

                         if (icon.template){
                             html += '<span class="webix_kanban_icon_text">' + webix.template(icon.template)(obj) + '</span>';
                         }

                         obj.iconCout ++;

                         html += '</span>';

                         icons.push(html);
                     }
                 }

                 return '<div class="webix_kanban_icons">' + icons.join(" ") + '</div>';
             },

             templateGears: function (obj, common) {
                 var gears = [];
                 var gear  = null;
                 var id    = "";
                 var html  = "";

                 for (var i = 0; i < common.icons.length; i++){
                     gear = common.icons[i];

                     if (gear.isGear && (!gear.show || gear.show(obj))){
                         id  = gear.id || gear.icon;
                         var css = mcService.trim(gear.css || "") || "";

                         html = '<span webix_icon_id="' + id + '" class="webix_kanban_icon ' + css +
                             '" title="' + (gear.hint ? (mcService.isFunction(gear.hint) ? gear.hint(obj) : gear.hint) : "") + '">' +
                             ( gear.link
                                     ? '<a href="' + obj.Link + '" class="fa-' + gear.icon + ' webix_icon f12" target="_blank"></a>'
                                     : '<span class="fa-' + gear.icon + ' webix_icon"></span>'
                             );

                         if (gear.template){
                             html += '<span class="webix_kanban_icon_text">' + webix.template(gear.template)(obj) + '</span>';
                         }

                         html += '</span>';

                         gears.push(html);
                     }
                 }

                 return gears.length ? '<div class="kanban_gear_block needShow">' + mcService.insertIco('fa-gear') + '<div class="kanban_gear_options">' + gears.join(" ") + '</div></div>' : '';
             },

             templateDataName: function (obj, common) {
                 var notThisYear = obj.dtDeadLine.split('.')[2] !== (new Date()).getFullYear().toString();
                 var getTaskBtn  = "";
                 var time        = "";
                 var namePerformer= "";
                 var dtCrt       = mcService.formatDate(obj.dtCreated, "dd MM ?yyyy hh:nn");

                 if (obj.UINPerformer == -1) {
                     getTaskBtn = mcService.myReplaceFormated(
                         "<div class='task_take' taskid='#{taskID}' onclick='window.__takeTask(this, event)'>#{title}</div><div class='taskUserName'>#{time}</div>",
                     {
                         taskID : obj.ID,
                         title  : mcLang(163).toUpperCase(), // "163":"Взять задание",
                         time   : dtCrt
                     });
                 } else

                 if (!obj.DisplayNamePerformer && !obj.DisplayNameOwner) {
                     getTaskBtn = mcService.myReplaceFormated("<div class='task_timestamp'>#{name}</div>", {
                         name:  mcLang(155, obj.UINOwner) // "155":"UIN-%s удален",
                     });
                 } else

                 if (obj.DisplayNamePerformer) {
                     namePerformer = mcService.myReplaceFormated("<div class='taskUserName' title='#{name}, UIN: #{uin}' >#{name}</div>", // style='#{margin}'
                         {
                             uin   : obj.UINPerformer,
                             name  : obj.DisplayNamePerformer,
                             margin: obj.dtDeadLine === mcConst.maxAvailableDate ? "margin-top: 7px" : ""
                         });
                 }

                 if (obj.dtDeadLine !== mcConst.maxAvailableDate) {
                     var diffDT= "";
                     var deadT = mcService.utcTime(obj.dtDeadLine).split('.');
                     // var deadT = obj.dtDeadLine.split('.');
                     var isDead = false;

                     var from = moment(new Date());
                     var to   = moment(new Date(deadT[2], parseInt(deadT[1]) - 1, deadT[0], deadT[3], deadT[4]));

                     var diff = to.diff(from, 'seconds', true);

                     if (diff < 0){
                         diff *= -1;
                         isDead = true;
                     }

                     var days    = parseInt(diff / oneday);
                     var lastSec = parseInt(diff - (days * oneday));
                     var hours   = parseInt(lastSec / (60 * 60));

                     lastSec = parseInt(lastSec - (hours * 60 * 60));

                     var minutes = parseInt(lastSec / 60);
                     // var seconds = parseInt(lastSec - (minutes * 60));

                     var stDays = " days ";

                     if (mcConst.Lang !== "ru" && mcConst.Lang !== "uk") {
                         stDays = days === 1 ? " day " : " days ";
                     } else {
                         if (days % 10 === 1) {
                             stDays = " " + mcLang(184) + " "; // "184":"день",
                         } else
                         if (days % 10 >=2 && days % 10 <=4) {
                             stDays = " " + mcLang(185) + " "; // "185":"дня",
                         } else
                         if ((days >=5 && days <=19) || (days % 10 >=5 && days % 10 <=9) || (days % 10 === 0)) {
                             stDays = " " + mcLang(186) + " "; // "186":"дней",
                         }
                     }

                     diffDT = (days ? days + stDays : "") + (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes);

                     time = mcService.myReplaceFormated(
                         '<div class="taskUserName" title="#{title}" style="margin-left: -2px">#{clock}#{diff}</div>',
                         {
                             clock : mcService.insertIco("fa-clock-o", isDead ? "red" : "green"),
                             diff  : diffDT,
                             title : mcLang(55) + ": " + (notThisYear ? obj.dtDeadLine.split('.')[2] + ", " : "") + mcService.utcTime(obj.dtDeadLine, false, "MM dd, hh:nn") // "55" :"Крайний срок"
                         }
                     );
                 } else
                 if (obj.UINPerformer != -1) {
                     time = "<div class='taskUserName'>" + dtCrt + "</div>";
                 }

                 return '<div style="height: 32px; width:calc(100% - ' + (obj.iconCout ? (obj.iconCout * 23) + 10 : 0) + 'px)">' + namePerformer + getTaskBtn + time + '</div>';
             },

             templateFooter: function(obj, common){
                 var icons  = common.templateIcons(obj, common);
                 var name   = common.templateDataName(obj, common);

                 return '<div class="webix_kanban_footer ' + (obj.UINPerformer == -1 ? 'webix_kanban_footer_noname' : '') +'" style="position: relative">' +
                     name + icons +
                 '</div>';
             },

             templateCircleName: function(obj, common){
                 var res = "";

                 if (obj.UINPerformer != -1) {
                     // var rColor     = $rootScope.colorPalette[Math.floor(Math.random() * $rootScope.colorPalette.length)];
                     var rColor     = $rootScope.colorPalette[obj.UINPerformer % $rootScope.colorPalette.length];
                     var twoLetters = mcService.getTwoLettersName(obj.DisplayNamePerformer);
                     
                     res = mcService.myReplaceFormated(
                         '<div class="webix_kanban_user_avatar">' +
                         '    <span class="circleAvatar" style="background-color: ##{rColor};" title="#{name}, UIN: #{uin}">#{twoLetters}</span>' +
                         '</div>',
                         {
                             uin        : obj.UINPerformer,
                             rColor     : rColor,
                             twoLetters : twoLetters,
                             name       : obj.DisplayNamePerformer
                         }
                     );
                 }

                 return res;
             },

             template: function(obj, common){
                 var projectName = "";

                 obj.iconCout = 0;

                 if (obj.ProjectName) {
                     projectName = '<div class="project_name webixBlue">' + obj.ProjectName + '</div>';
                 }

                 // var avatar  = '<div class="webix_kanban_user_avatar priority_' + obj.Priority + '">' + (common.templateAvatar(obj,common) || common.templateNoAvatar(obj,common)) + '</div>';
                 var gear    = '<div class="kanban_gear">' + common.templateGears(obj,common) + '</div>';
                 var avatar  = common.templateCircleName(obj, common);
                 // var border  = '<div class="borderShadow"></div>';
                 var border2 = obj.dashboard ? '<div class="borderFooter"></div>' : '';
                 var body    = '<div class="webix_kanban_body " style="position: relative; font-size: 110%">' + common.templateBody(obj,common) + border2 + '</div>'; // fitContentAuto
                 var footer  = common.templateFooter(obj,common);
                 var overlay = '<div class="priority_light_' + obj.Priority + '"></div>';

                 return avatar + '<div class="webix_kanban_list_content priority_' + obj.Priority + ' showOnHover" style="position: relative">' +
                     overlay + projectName + gear /*+ border*/ + body + footer +
                 '</div>';
             }
         });
     })
 }

"use strict";

function initLogin($scope){
    var labelWidth = 140;

    if ($$($scope.Name)) $$($scope.Name).destructor();

    var srvPwdPopup = webix.ui({
        view  : "popup",
        width : 300,
        height: 150,
        body  : { autoheight: true, css:"wrap", borderless: true, template: mcService.insertIco("fa-info-circle") + mcService.Lang(126)} // "126" : "Общий пароль, который проверяется перед авторизацией пользователя.<br><br>Устанавливается на сервере в разделе \"Настройки\" -&gt; \"Сетевые настройки\"",
    });

    var loginPopup = webix.ui({
        view  : "popup",
        width : 300,
        height: 120,
        body  : { autoheight: true, css:"wrap", borderless: true, template: mcService.insertIco("fa-info-circle") + mcService.Lang(124)} // "124":"Используйте для входа
    });

    var loginBody = {
        id   : 'loginForm',
        view : 'form',
        rules:{
            "loginField"    : webix.rules.isNotEmpty,
            "passwordField" : webix.rules.isNotEmpty
        },
        elements:[
            {
                template: "MyChat Kanban",
                autoheight: true,
                css: {
                    'text-align': 'center',
                    'font-size': '14pt',
                    'color': '#555',
                    'border-bottom': '1px solid #bbb !important'
                },
                borderless: true,
                id: "loginTitle"
            },
            { cols: [
            {
                view       : "text",
                label      : mcService.Lang(1), // "1" : "Логин"
                placeholder: mcService.Lang(864), // "864":"Введите здесь...",
                labelWidth : labelWidth,
                id         : 'loginField',
                name       : 'loginField',
                value      : $scope.login,
                required   : "true"
            },{
                view: "button",
                    template : '<div style="text-align: center; line-height: 30px; color: #777; font-size: 13pt; cursor: pointer"><b>?</b></div>', // mcService.insertIco('fa-info')
                    width: 21, borderless: true, css: "finger webixBlue loginHelper", click: function(){
                    loginPopup.show(this.getNode());
                }
            }]},

            { cols: [{
                view       : "text",
                label      : mcService.Lang(2), // "2" : "Пароль"
                placeholder: mcService.Lang(864), // "864":"Введите здесь...",
                labelWidth : labelWidth,
                type       : 'password',
                id         : 'passwordField',
                name       : 'passwordField',
                required   : "true",
                keyPressTimeout: 100,
                on         : {
                    onKeyPress : $scope.keyLogin,
                    onTimedKeyPress : function() {
                        $scope.checkLangPass($scope.loginDispLang, $scope.passwordField);
                    }
                }
            },{
                id: 'loginDispLang', template: 'EN', width: 21, css: 'loginHelper', borderless: true
            }]},

            { cols: [{
                view       : "text",
                label      : mcService.Lang(3), // "3" : "Пароль к серверу"
                placeholder: mcService.Lang(864), // "864":"Введите здесь...",
                labelWidth : labelWidth,
                type       : 'password',
                id         : 'servPassField',
                on         : {
                    onKeyPress : $scope.keyLogin
                }
            }, {
                view: "button",
                template : '<div style="text-align: center; line-height: 30px; color: #777; font-size: 13pt; cursor: pointer"><b>?</b></div>', // mcService.insertIco('fa-info')
                width: 21, borderless: true, css: "finger webixBlue loginHelper", click: function(){
                    srvPwdPopup.show(this.getNode());
                }
            }]},

            { cols: [
                {
                    view       : "richselect",
                    id         : "loginLang",
                    labelWidth : labelWidth,
                    label      : mcService.Lang(6), // "6" : "Язык интерфейса"
                    value      : $scope.lang,
                    options    : {
                        template : "#value#",
                        data     : [
                            { id : 'ru', value : "Русский"},
                            { id : 'uk', value : "Українська"},
                            { id : 'en', value : "English"}
                        ]
                    },
                    on : {
                        onChange : $scope.changeLang
                    }
                },{
                    id         : "flagImg",
                    template   : "&nbsp;",
                    width      : 21,
                    borderless : true,
                    css        : "lh25"
                }
            ]},

            { view : "checkbox",
                labelRight : mcService.Lang(4), // "4":"Запомнить меня на этом компьютере",
                labelWidth : 0,
                id         : "rememberField",
                value      : $scope.remember,
                on         : { onChange: function(newV){
                    $scope.remember = mcService.convertIntToBool(newV);
                }}
            },

            { view: "button", value: mcService.Lang(5), id: "loginBtn", click: $scope.loginBtnClick }     // "5" : "Войти"
        ]
    };

    var login = null;

    if (isMobile){
        loginBody.maxWidth = 550;
        loginBody.minWidth = 300;

        login = webix.ui({
            id       : $scope.Name,
            view     : "scrollview",
            css      : "noBGColor",
            body     : loginBody
        });
    } else {
        login = webix.ui({
            id       : $scope.Name,
            view     : "window",
            width    : 330,
            position : "center",
            head     : false,
            body     : loginBody
        });
    }

    login.show();

    $scope.loginForm     = $$('loginForm');
    $scope.passwordField = $$('passwordField');
    $scope.loginDispLang = $$('loginDispLang');
    $scope.loginField    = $$("loginField");
    $scope.servPassField = $$("servPassField");

    $scope.loginTitle    = $$("loginTitle");
    $scope.flagImg       = $$("flagImg");

    $scope.changeFlag($scope.lang);

    $scope.loginField.focus();

    return login;
}

function loginController ($scope, $rootScope, $location){
    "use strict";

    $scope.Name = mcConst.dataModels.Login;
    $scope.remember = false;

    var _view = null;

    // ======= Error Actions ==========

    $rootScope.ErrorActions[14] = function(){ // "14"  : "Неверный пароль пользователя",
        mcService.SaveSessionSettings(mcConst.LoginInfo.login, "", "", false);

        mcConst.LoginInfo.pwd     = "";
        mcConst.LoginInfo.servPwd = "";
        mcConst.LoginInfo.rm      = false;

        initLogin($scope);

        $rootScope.loginFormNotShowed = false;

        webix.ready(function(){
            setTimeout(function(){
                $scope.passwordField.focus();
            }, 100);
        });
    };

    $rootScope.ErrorActions[172] = function () {

    };

    $rootScope.ErrorActions[83] =
        $rootScope.ErrorActions[80] =
            $rootScope.ErrorActions[17] =
                $rootScope.ErrorActions[14]; // остался мусор в локал сторажде от предыдущего логина

    $rootScope.ErrorActions[215] =
        $rootScope.ErrorActions[216] = function () {
            location.search = "";
        };

            // ==================================

    $scope.changeFlag = function(lang){
        $scope.flagImg.define('template', '<img src="' + mcConst.imagesPath.flags + lang + '.png">');
        $scope.flagImg.refresh();
    };

    $scope.keyLogin = function(code , e){
        if (code === 13 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
            $scope.loginBtnClick.apply(this);

            return false;
        }
    };

    $scope.checkLangPass = function(displayLang, field){
        var lng      = displayLang;
        var lastChar = field.getValue()[field.getValue().length - 1];

        if (lastChar){
            if (lastChar.match(/[a-zA-Z]/)) {
                lng.define('template', 'EN');
                lng.refresh();
            } else
            if (lastChar.match(/[а-яА-Я]/)){
                lng.define('template', 'RU');
                lng.refresh();
            } else
            if (lastChar.match(/[їЇіІєЄ]/)){
                lng.define('template', 'UA');
                lng.refresh();
            }
        }
    };

    $scope.loginBtnClick = function (){
        var isValid = this.getFormView().validate();

        if (isValid) {
            $rootScope.SendCMDToServer([
                mcConst.lockInterface,
                mcConst._CMD_.Login,

                $scope.loginField.   $getValue(), //mcConst.LoginInfo.login  ,
                $scope.passwordField.$getValue(), //mcConst.LoginInfo.pwd    ,
                $scope.servPassField.$getValue(), //mcConst.LoginInfo.servPwd,

                mcConst.LoginInfo.rm             //mcConst.LoginInfo.rm
            ]);

            mcConst.LoginInfo.login = $scope.loginField.$getValue();
            mcConst.LoginInfo.rm    = $scope.remember;

            this.blur();
        } else {
            webix.message({ type:"error", text: mcService.Lang(7) , expire : mcConst.errMessageExpire}); // "7" : "Ошибка входа: неправильно введены данные"
        }
    };

    $scope.changeLang = function(lang){
        $rootScope.$broadcast('ChangLanguage', mcService.detectLang(lang));

        $scope.changeFlag(lang);
    };

    $scope.show = function(lang){
        if (mcConst.LoginInfo.login == null){
            mcService.ClearSessionSettings();
        }

        $scope.lang  = lang || mcConst.Lang;
        $scope.login = mcConst.LoginInfo.login || "";
        $scope.remember = mcConst.LoginInfo.rm;

        webix.storage.local.put(mcConst.storageFields.lang, $scope.lang);

        var token = $location.param('token') || $location.fparam("token");

        if (token){
            $rootScope.SendCMDToServer([
                mcConst.lockInterface,
                mcConst._CMD_.cs_login_by_token,
                token
            ]);

            window.history.pushState(null, document.title, window.location.pathname + window.location.hash.replace("token=" + token, ""));
        } else

        if ($scope.remember) {
            $rootScope.loginFormNotShowed = true;

            $rootScope.SendCMDToServer([
                mcConst.lockInterface,
                mcConst._CMD_.Login,
                mcConst.LoginInfo.login  ,
                mcConst.LoginInfo.pwd    ,
                mcConst.LoginInfo.servPwd,
                mcConst.LoginInfo.rm
            ]);
        } else {
            _view = initLogin($scope);

            $rootScope.loginFormNotShowed = false;
        }

        if (isMobile){
            $rootScope.$broadcast('windowResize', [function(){
                if (_view) _view.resize();
            }]);
        }
    };

    $scope.$on('enable' + $scope.Name, function enableForm (){
        var item = $$($scope.Name);

        if (item) item.enable();
    });

    $scope.$on('hide' + $scope.Name, function(){
        if ($$($scope.Name)) {
            $$($scope.Name).hide();
        }
    });

    $scope.$on('show' + $scope.Name, function(e, args){
        $scope.show.apply(null, args);
    });
}

"use strict";

function initWrapper($scope){
    var view = $$($scope.Name);

    if (!view){
        view = webix.ui({
            id: $scope.Name,
            // container: $scope.Name,
            cols: [{
                rows: [
                    { height: 5},
                    { id: "wrap" + mcConst.dataModels.Toolbar, height: 37, cols: [{ gravity: 0 }]},
                    { cols: [
                        { id: "wrap" + mcConst.dataModels.Menu, type: "space", rows: [{ gravity: 0}], hidden: true, width: 350, borderless: true, padding: 0},
                        { id: "wrap" + mcConst.dataModels.KanbanBody, rows: [{ gravity: 0 }] },
                        { id: "wrap" + mcConst.dataModels.Dashboard,  rows: [{ gravity: 0 }], hidden: true}
                    ]}
                ]
            }]
        });
    } else {
        view.show();
    }

    $scope.__wrapper = $$($scope.Name);

    return view;
}
"use strict";

function wrapperController($scope, $rootScope){
    $scope.Name = mcConst.dataModels.Wrapper;

    var view = null;
    var currentView = mcConst.dataModels.KanbanBody;

    // ======================================

    $scope.show = function(){
        view = initWrapper($scope);

        $rootScope.$broadcast('show' + mcConst.dataModels.Toolbar, []);
    };

    // ================================================

    var _msg = _messages_.wrapper = {
        changeView : 'changeView'
    };

    $scope.$on(_msg.changeView, function (e, args) {
        var newView = args[0];

        if (newView && newView !== currentView){
            $$("wrap" + currentView).hide();
            $$("wrap" + newView).show();

            currentView = newView;
        }

        view.adjust();
    });

    $scope.$on('hide' + $scope.Name, function(){
        view.hide();
    });

    $scope.$on('show' + $scope.Name, function(){
        $scope.show();
    });

    $rootScope.$broadcast('windowResize', [function(w, h){
        if ($scope.__wrapper){
            $scope.__wrapper.define('height', h);
            $scope.__wrapper.define('width', w);
            $scope.__wrapper.resize();
        }
    }]);
}

function initKanbanBody($scope){
    "use strict";

    var view = $$($scope.Name);

    var kanban = {
        view : "kanban",
        type : "space",
        css  : "noBG hiddenScrollMenu",
        id   : "body" + $scope.Name,
        cols : $scope.Stages,
        data : $scope.taskList,
        padding: 3,
        borderless: true,
        select: false,
        icons: [
            // { id: "iconLink", icon: "link", link: true, css: "",
            //     hint: function (obj) {
            //         return "Link: " + obj.Link;
            //     }
            // },
            { id: "iconDesc", icon: "info", hint: mcLang(78), show: function (obj) { // "78" :"Дополнительная информация",
                return !!obj.Desc;
            }},
            { id: "iconComments", icon: "comment-o", hint: mcLang(20), template: function (obj) { // "20" :"Комментарии",
                return obj.CommentsCount || "";
            }},
            { id: "iconEdit", isGear: true, icon: "pencil", hint: mcLang(79), show: function (obj) { // "79" :"Редактировать",
                return (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanTaskEdit, $scope.currentProject.RightsSet)) && ($scope.currentProject.dtClosed === mcConst.maxAvailableDate);
            }},
            { id: "iconRemove", isGear: true, icon: "trash-o", hint: mcLang(127), show: function (obj) { // "127":"Удалить",
                return (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanTaskRemove, $scope.currentProject.RightsSet)) && ($scope.currentProject.dtClosed === mcConst.maxAvailableDate);
            }}
        ],
        on   : {
            onListItemDblClick : $scope.openTask,
            // onListItemClick    : $scope.openElement,
            onListIconClick    : function(iconId, itemId,ev,node,list){
                switch (iconId){
                    case "iconLink":
                        mcService.copyTextToClipboard(ev.target.href, mcLang(162)); // "162":"Ссылка скопирована",

                        ev.preventDefault();
                    break;

                    case "iconDesc":
                        $scope.openTask(itemId, ev, node, list);
                    break;

                    case "iconEdit":
                        $scope.editTask(itemId, ev, node, list);
                    break;

                    case "iconComments":
                        $scope.taskComments(itemId, ev, node, list);
                    break;

                    case "iconRemove":
                        $scope.removeTask(itemId, ev, node, list);
                    break;

                    default:
                        $scope.openElement.apply(null, arguments);
                }
            },
            onAfterStatusChange: $scope.changeStage,
            onAfterCollapse    : $scope.collapseStage,
            onAfterExpand      : $scope.expandStage,
            onListAfterDrop    : $scope.srtkb,
            
            // onBeforeSelect     : function(){
            //     return false;
            // },
            // onListBeforeSelect : function(){
            //     return false;
            // },

            // onAfterAdd         : function () {
                // setTimeout(function () {
                //     var list = $scope.kanban.getChildViews()[0].getChildViews()[0];
                //     list.sort($scope.sortConditions);
                //     list.scrollTo(0, 9999999);
                // }, 10);
            // },
            onListBeforeDrag   : function () {
                return $scope.currentProject.dtClosed === mcConst.maxAvailableDate &&
                    (mcService.checkRights(mcConst._CMD_.RS.QKanbanTaskMoveBetweenStages, $scope.currentProject.RightsSet) || mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess));
            }
        }
    };

    if (view){
        view.getNode().onwheel = null;

        $scope.kanban.destructor();

        $$('wrap' + $scope.Name).removeView($scope.Name);
    }

    $$('wrap' + $scope.Name).addView({
        id  : $scope.Name,
        rows: [
            // { height: 4},
            //
            // { id: "btnAddButton" + $scope.Name, view: "button", type: "icon", icon: "plus", label: mcLang(15), css: "byCenter whiteButton shadow1px",
            //     tooltip: mcLang(15) + " | CTRL + INS", click: $scope.addNewTask, hidden: $scope.currentProject.dtClosed != mcConst.maxAvailableDate}, //"15" :"Добавить задание",

            {   view   : "scrollview",
                scroll : "x",
                css    : "noBG noBorder hiddenScrollMenu",
                body   : { rows: [
                    { type: "space", borderless: true, padding: 3, rows: [
                        { id: "closedInfo" + $scope.Name, view: "label", hidden: $scope.currentProject.dtClosed == mcConst.maxAvailableDate,
                            label: mcLang(60), css: "errorRed", align: "center" } // "60" :"Проект закрыт!",
                    ]},

                    { height: 4},

                    { cols: [
                        kanban,
                    ]}
                ]}
            }
        ]
    });

    view = $$($scope.Name);

    view.getNode().onwheel = function(e){
        if (e.target.getAttribute("view_id") == "bodyKanbanBody"){
            e.stopPropagation();
            e.preventDefault();

            e.returnValue = false;
            e.cancelBubble = true;

            return false;
        }
    };

    mcService.disableContextMenu(view, ['fa-hashtag', 'internalLink', 'webix_kanban_icon_text']);

    $scope.kanban       = $$("body" + $scope.Name);
    $scope.closedInfo   = $$("closedInfo" + $scope.Name);
    // $scope.btnAddButton = $$("btnAddButton" + $scope.Name);

    $scope.kanban._otherFilterOptions = {};

    return view;
}

function kanbanBodyController($scope, $rootScope){
    "use strict";
    
    $scope.Name = mcConst.dataModels.KanbanBody;
    $scope.taskList = [];
    $scope.Stages   = [];

    var view         = null;
    var currentTask  = null;
    var kanbanShowed = false;
    // var TASK_LIST    = "_taskList";
    var priorityTagList   = {};

    $scope.stageHeaderHandler = null;
    $scope.currentProject     = null;

    window.__takeTask = function (item, e) {
        var ID = item.getAttribute("taskid");

        if (ID){
            e.preventDefault();

            $rootScope.SendCMDToServer([
                mcConst._CMD_.cs_kanban_task_take,
                mcConst.SessionID,

                ID,

                function(data){
                    var idx  = mcService.findItemInArrayOfObj($scope.taskList, data.ID, "ID");

                    currentTask = $scope.taskList[idx].id;

                    modifyTask({
                        ID          : data.ID,
                        UINPerformer: mcConst.UserInfo.UIN,
                        DisplayNamePerformer: mcConst.UserInfo.Nick
                    });
                }
            ]);
        }
    };

    // ======================================

    function KanbanHeaders() {
        var headersList = {};
        var self        = this;
        var kanban      = null;
        var countTasksInStage = {};
        var stageElements     = {};
        var CHANGE_STAGE_TYPE = "change_stage_type";
        var KANBAN_GEARS      = "kanban_gears";
        var ACTION_ADD_TASK   = "_action_add_task_";

        // ===========================

        function addTask(e) {
            var id = e.target.getAttribute("stageID");

            $rootScope.$broadcast("show" + mcConst.dataModels.TaskEdit, ["new", id]);
        }

        function mItemStage(info) {
            info.icoByType = getIconByType(info.type);
            info.sType     = CHANGE_STAGE_TYPE;

            return mcService.myReplaceFormated(
                '<span id="#{id}_menu_item_#{type}" class="webix_kanban_icon #{sType}" _stage_id="#{id}" _stage_type="#{type}" >#{icoByType}</span>', // title="#{title}"
                info
            );
        }

        function mItemAddTask(stageID) {
            return mcService.myReplaceFormated(
                '<span class="webix_kanban_icon kanban_plus_task #{type}" stageID="#{stageID}">#{plus}</span>', //
                {
                    type   : ACTION_ADD_TASK,
                    stageID: stageID,
                    plus   : mcService.insertIco('_stage_type_icon_ fa-plus', " ", 'title="' + mcLang(15) + '" stageID="' + stageID + '"') // "15" :"Добавить задание",
                }
            );
        }

        function mItemSeparator() {
            return "<span style='width: 2px; border-left: 1px #3498db solid;'></span>";
        }

        function stageHeaderTemplate(stage) {
            headersList[stage.ID] = stage;

            return mcService.myReplaceFormated(
                '<span id="#{markerID}">#{marker}</span>' +
                '<span id="#{nameID}" class="stageName" title="#{name}">#{name}</span>' +
                '<span id="#{countID}">#{count}</span>' +

                (stage.Type !== mcConst.stagesTypes.archive && (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanAddTask, $scope.currentProject.RightsSet))
                    ? mItemAddTask(stage.ID)
                    : ''
                )
                // (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanStageEdit, $scope.currentProject.RightsSet)
                //     ? '<div id="#{menuID}" class="kanban_gear_stage kanban_gear_block #{gearClass}">' + mcService.insertIco('fa-gear') +
                //           '<div class="kanban_gear_options">#{menuList}</div>' +
                //       '</div>'
                //     : ''
                // )
                ,
                {
                    markerID    : stage.ID + "_marker_",
                    nameID      : stage.ID + "_name_",
                    menuID      : stage.ID + "_menu_",
                    countID     : stage.ID + "_counter_",

                    gearClass   : KANBAN_GEARS,
                    name        : stage.Name,
                    marker      : getIconByType(stage.Type || mcConst.stagesTypes.typical),
                    count       : countTasksInStage[stage.ID] ? "[" + countTasksInStage[stage.ID] + "]" : "",

                    // menuList    : [
                    //     mItemStage({id: stage.ID, type: mcConst.stagesTypes.typical/*, title: mcLang(148)*/}), // "148":"Стандартный этап",
                    //     mItemStage({id: stage.ID, type: mcConst.stagesTypes.done/*,    title: mcLang(149)*/}), // "149":"Этап с выполненными заданиями",
                    //     mItemStage({id: stage.ID, type: mcConst.stagesTypes.archive/*, title: mcLang(150)*/})  // "150":"Этап с архивными заданиями",
                    // ].join('')
                }
            );
        }

        function setStageEvent(cname, func) {
            var items = mcService.getElementByClass(cname);

            if (items && items.length){
                for (var i = 0; i < items.length; i++){
                    (function (elem) {
                        elem.stageEvent = elem.addEventListener("click", func);
                    })(items[i]);
                }
            }
        }

        function attachStageSettings() {
            detachStageSettings(CHANGE_STAGE_TYPE);
            detachStageSettings(KANBAN_GEARS);
            detachStageSettings(ACTION_ADD_TASK);

            setStageEvent(CHANGE_STAGE_TYPE, function (e) {
                var elem = this;
                
                $rootScope.$broadcast(window._messages_.kanbanManager.ChangeStageInfo, [{
                    ID  : elem.getAttribute("_stage_id"),
                    Type: elem.getAttribute("_stage_type"),
                    done: function (data) {
                        updateTaskCount(data.ID, countTasksInStage[data.ID], data.Type, data.Name);
                    }
                }]);

                e.stopPropagation();
            });
            
            setStageEvent(KANBAN_GEARS, function (e) {
                var gear = this;

                gear.style.display = "block";

                e.stopPropagation();
            });

            setStageEvent(ACTION_ADD_TASK, function (e) {
                addTask(e);

                e.stopPropagation();
            });
        }

        function detachStageSettings(cName) {
            var items = mcService.getElementByClass(cName);

            if (items){
                for (var i = 0; i < items.length; i++){
                    (function (elem) {
                        if (elem.stageEvent) {
                            elem.removeEventListener(elem.stageEvent);
                            elem.stageEvent = null;
                        }
                    })(items[i]);
                }
            }
        }

        function updateTaskCount(stageID, count, typeStage, name) {
            if (stageElements && $scope.kanban && $scope.kanban.isVisible()){
                kanban.getChildViews().forEach(function (stage) {
                    if (stage.config.id == stageID && headersList.hasOwnProperty(stageID) && stageElements.hasOwnProperty(stageID)) {
                        headersList[stageID].Name = name || headersList[stageID].Name;
                        headersList[stageID].Type = typeStage !== undefined ? typeStage : headersList[stageID].Type;

                        countTasksInStage[stageID] = count || 0;

                        stageElements[stageID].typeStage.innerHTML = getIconByType(headersList[stageID].Type);
                        stageElements[stageID].count.innerHTML     = countTasksInStage[stageID] ? "[" + countTasksInStage[stageID] + "]" : "";
                        stageElements[stageID].name.innerHTML      = headersList[stageID].Name;
                    }
                });
            }
        }

        function updateAllCounters(taskList) {
            countTasksInStage = {};

            updateHearLinks();
            
            taskList.forEach(function (task) {
                if (countTasksInStage[task.status]) { // status === stageID
                    countTasksInStage[task.status] ++;
                } else {
                    countTasksInStage[task.status] = 1;
                }
            });

            if (kanban){
                Object.keys(headersList).forEach(function (tsk) {
                    updateTaskCount(headersList[tsk].ID, countTasksInStage[headersList[tsk].ID]);
                });
            }
        }

        function updateHearLinks() {
            Object.keys(headersList).forEach(function (item) {
                var stage = headersList[item];

                stageElements[stage.ID]           = {};
                stageElements[stage.ID].typeStage = document.getElementById(stage.ID + "_marker_");
                stageElements[stage.ID].count     = document.getElementById(stage.ID + "_counter_");
                stageElements[stage.ID].name      = document.getElementById(stage.ID + "_name_");
            });

            attachStageSettings();
        }

        // ===========================

        this.updateTaskCount     = updateTaskCount;
        this.updateAllCounters   = updateAllCounters;
        this.attachStageSettings = attachStageSettings;
        this.destroy             = function () {
            detachStageSettings(CHANGE_STAGE_TYPE);
            detachStageSettings(KANBAN_GEARS);

            stageElements    = {};
            headersList      = {};
            self             = null;
            kanban           = null;
            countTasksInStage= {};
        };
        this.getHeadersHTML      = function (stage) {
            return stageHeaderTemplate(stage);
        };
        this.setKanban           = function (_kanban) {
            kanban = _kanban;

            updateHearLinks();
        };
    }

    // ======================================

    function getIconByType(id) {
        switch (parseInt(id)){
            case mcConst.stagesTypes.typical: return mcService.insertIco('_stage_type_icon_ fa-bookmark', " ", 'title="' + mcLang(148) + '"');break;       // "148":"Стандартный этап",
            case mcConst.stagesTypes.done:    return mcService.insertIco('_stage_type_icon_ fa-check-square-o', " ", 'title="' + mcLang(149) + '"');break; // "149":"Этап с выполненными заданиями",
            case mcConst.stagesTypes.archive: return mcService.insertIco('_stage_type_icon_ fa-archive', " ", 'title="' + mcLang(150) + '"');break;        // "150":"Этап с архивными заданиями",
        }
    }

    function addNewTask(data) {
        var count = 0;

        data.status        = data.IDStage;
        data.Link          = mcService.createLink($rootScope.currentProject, data.ID);
        data.CommentsCount = "";
        data.parseName     = parseTaskImages(data);
        data.dashboard     = false;

        $scope.kanban.add(data);

        $scope.taskList.push(data);

        $scope.taskList.forEach(function (task) {
            if (task.status == data.IDStage){
                count ++;
            }
        });

        $scope.stageHeaderHandler.updateTaskCount(data.status, count);

        sortKanban();

        if (!data.Tags || !data.Tags.length) {
            data.Tags = Object.keys(mcConst.priority).reduce(function (res, id) {
                if (isNaN(parseInt(data.Priority))){
                    res = 'medium';
                } else
                if (mcConst.priority[id] === parseInt(data.Priority)){
                    res = id.toLowerCase();
                }

                return res;
            }, "");
        }

        $rootScope.$broadcast(window._messages_.Menu.updateTags, [data.Tags.split(',')]);

        $rootScope.$broadcast(window._messages_.Toolbar.doFilterKanban);

        // $scope.kanban.select(data.id);
    }

    function delTaskFromStage(idx, item) {
        var count = 0;

        $scope.taskList.splice(idx, 1);

        $scope.taskList.forEach(function (task) {
            if (task.status == item.status){
                count ++;
            }
        });

        $scope.kanban.remove(item.id);

        $scope.stageHeaderHandler.updateTaskCount(item.status, count);

        sortKanban();
    }

    function removeTask(id) {
        var item = $scope.kanban.getItem(id);
        var idx  = mcService.findItemInArrayOfObj($scope.taskList, item.ID, "ID");

        if (idx !== -1){
            $rootScope.SendCMDToServer([
                mcConst.lockInterface,
                mcConst._CMD_.cs_kanban_task_remove,
                mcConst.SessionID,
                item.ID,
                
                function(){
                    delTaskFromStage(idx, item);
                }
            ]);
        }
    }

    function modifyTask(data) {
        if (currentTask){
            var oldData = $scope.kanban.getItem(currentTask);
            var previousPriority = Object.keys(mcConst.priority).reduce(function (res, id) {
                if (mcConst.priority[id] === parseInt(oldData.Priority)){
                    res = id.toLowerCase();
                }

                return res;
            }, "");
            var newData = mcService.Marge(oldData, data);

            newData.status    = newData.IDStage || newData.status;
            newData.parseName = parseTaskImages(newData);

            $scope.taskList[mcService.findItemInArrayOfObj($scope.taskList, newData.ID, "ID")] = newData;

            $scope.kanban.updateItem(currentTask, newData);

            currentTask = null;

            if (!data.newTags) data.newTags = [];

            var priorityTag = Object.keys(mcConst.priority).reduce(function (res, id) {
                if (mcConst.priority[id] === parseInt(data.Priority)){
                    res = id.toLowerCase();
                }

                return res;
            }, "");

            if (data.newTags.indexOf(priorityTag) === -1 && data.Tags.indexOf(priorityTag) === -1) {
                data.newTags.push(priorityTag);
                data.Tags += "," + priorityTag;
            }

            if (previousPriority !== priorityTag) {
                if (!data.removeTags) data.removeTags = [];
                
                data.removeTags.push(previousPriority);
            }

            data.newTags.forEach(function (tag) { // добавляем новые теги, если их нет в общем списке тегов
                if (mcService.findItemInArrayOfObj($scope.currentProject.Tags, tag, "id") === -1){ //$scope.currentProject.Tags.indexOf(tag) === -1){
                // if ($scope.currentProject.Tags.indexOf(tag) === -1){
                    $scope.currentProject.Tags.push({id: tag, value: 1});
                }
            });

            $rootScope.$broadcast(window._messages_.Menu.updateTags, [data.newTags, data.removeTags]);

            sortKanban();

            $rootScope.$broadcast(window._messages_.Toolbar.doFilterKanban);
        }
    }

    function updateCommentsCount(id, comments, ID) {
        var idx  = mcService.findItemInArrayOfObj($scope.taskList, ID, "ID");

        if ($scope.kanban){
            var task = $scope.kanban.getItem(id);

            if (!task && idx !== -1){
                task = $scope.taskList[idx];
            }

            if (task) {
                task.CommentsCount = comments;

                $scope.kanban.updateItem(task.id, task);

                sortKanban();
            }
        }
    }

    function sortKanban() {
        $scope.kanban.getChildViews().forEach(function (list) {
            list.getChildViews()[0].sort($scope.sortConditions);
        });
    }

    function getTaskInfo(taskID, cb) {
        $rootScope.SendCMDToServer([
            mcConst._CMD_.cs_kanban_get_task_info,
            mcConst.SessionID,
            taskID,
            cb
        ]);
    }

    function openTaskOrComment(openTaskID, openCommentID) {
        if (openTaskID != undefined) {
            var idx = mcService.findItemInArrayOfObj($scope.taskList, openTaskID, "ID");

            if (idx != -1){
                if (openCommentID != undefined){
                    $rootScope.$broadcast('show' + mcConst.dataModels.Task, [$scope.taskList[idx], true, openCommentID]);
                } else {
                    $rootScope.$broadcast('show' + mcConst.dataModels.Task, [$scope.taskList[idx]]);
                }
            } else {
                webix.alert(mcLang(76, openTaskID)); // "76" :"Задание <b>#%s</b> не найдено!",
            }
        }
    }

    function genLink(file, name) {
        file.imgSrc  = $rootScope.localPath + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + file.Hash + '-' + $rootScope.thumbsSize.xy + '.jpg';
        file.lnkHref = $rootScope.localPath + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + file.Hash + "/" + encodeURI(name);
    }

    function parseTaskImages(task) {
        return "<div class='breakWord'>" + mcService.parseAttachment(mcService.ReplaceURLs((task.Name || "").replace(/\n/g, "<br>"), {
            callback: function (text, link) {
                var info = text.split('/');

                var type = info[3];
                var hash = info[5];
                var name = info[6];

                // if (type === mcConst.nodePathes.images){
                //     text = "<img class='s200x200' src='" + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + hash + '/' + name + "' alt=''>";
                // } else
                // if (type === mcConst.nodePathes.files){
                //     text = mcService.insertIco("fa-file-o", "fs90") + decodeURI(name);
                // } else
                if (type === mcConst.pathAliases.AliasFiles){
                    if (mcService.isImage(name)){
                        text = "<img class='s200x200' src='" + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + hash + '/' + name + "' alt=''>";
                    } else {
                        text = mcService.insertIco("fa-file-o", "fs90") + decodeURI(name);
                    }
                } else
                if (mcService.isArray(text)){
                    text = text.join("/");
                }

                return link ? mcService.replaceKanbanLink(link, text) : text;
            }
        }), task.Files, mcConst.prefix.file, function (txt, files) {
            return mcService.parseAttachment(txt, files, mcConst.prefix.image, null, $rootScope.duManager, genLink);
        }, $rootScope.duManager, genLink) + "</div>";
    }

    function getTasksCountByStages(projectID){
        var res = {};

        if ($scope.currentProject && projectID == $scope.currentProject.ID){
            $scope.taskList.forEach(function (task) {
                if (res[task.status]){
                    res[task.status] ++;
                } else {
                    res[task.status] = 1;
                }
            });
        }

        return res;
    }

    function fillTasks(tasks){
        var countByStages = {};
        priorityTagList   = {};

        priorityTagList[mcConst.priority.LOW]       = 0;
        priorityTagList[mcConst.priority.MEDIUM]    = 0;
        priorityTagList[mcConst.priority.HIGH]      = 0;
        priorityTagList[mcConst.priority.IMPORTANT] = 0;
        priorityTagList[mcConst.priority.URGENT]    = 0;
        priorityTagList[mcConst.priority.CRITICAL]  = 0;

        tasks.forEach(function (task) {
            var id = $scope.Stages[mcService.findItemInArrayOfObj($scope.Stages, task.status, 'id')].position;

            if (!task.CommentsCount){
                task.CommentsCount = "";
            }

            if (!task.Link){
                task.Link = mcService.createLink($rootScope.currentProject, task.ID);
            }

            if (task.Tags) {
                task.Tags += "," + mcLang(60 + parseInt(task.Priority)); //",_PRIORITY_" + task.Priority;
            } else {
                task.Tags = mcLang(60 + parseInt(task.Priority)); //"_PRIORITY_" + task.Priority;
            }

            priorityTagList[task.Priority] ++;

            if (!countByStages[id]) {
                countByStages[id] = 0;
            }

            countByStages[id] ++;

            task.parseName = parseTaskImages(task);
            task.dashboard = false;
        });
    }

    function fillStages(data){
        if ($scope.stageHeaderHandler){
            $scope.stageHeaderHandler.destroy();
        }

        $scope.Stages             = new Array(data.length);
        $scope.stageHeaderHandler = new KanbanHeaders();

        $rootScope.noStages = !data.length;

        for (var i = 0; i < data.length; i++) {
            $scope.Stages[i] = { // config stages
                header   : $scope.stageHeaderHandler.getHeadersHTML(data[i]),
                css      : "glassBG ",
                id       : data[i].ID,
                position : data[i].Position,
                Type     : data[i].Type || mcConst.stagesTypes.typical,
                collapsed: $rootScope.kanbanOptions.stageState[data[i].ID],
                select   : false,
                minWidth : 300,

                body     : {
                    view   : "kanbanlist",
                    id     : data[i].ID + "_taskList", //TASK_LIST,
                    status : data[i].ID.toString(),
                    css    : "glassBG hiddenScrollMenu fixedHeightForItems",
                    type   : "mcTask",
                    select   : true,
                    multiselect: true,
                    sort   : ""
                }
            }
        }
    }

    function showAndFill(){
        $scope.show();

        $scope.stageHeaderHandler.setKanban($scope.kanban);
        $scope.stageHeaderHandler.updateAllCounters($scope.taskList);
        // $scope.stageHeaderHandler.attachStageSettings();

        // if (dtClosed === mcConst.maxAvailableDate) {
        //     $scope.btnAddButton.show();
        // } else {
        //     $scope.btnAddButton.hide();
        // }

        // window.history.pushState(null, null, '#project=' + $rootScope.currentProject);
        window.history.replaceState({}, document.title, '#project=' + $rootScope.currentProject);
    }

    function onTaskEvent(Action, ProjectID, TaskID, internalInfo){
        if (ProjectID == $scope.currentProject.ID){
            var idx  = mcService.findItemInArrayOfObj($scope.taskList, TaskID, "ID");

            switch (Action){
                case mcConst.kanbanActions.created:
                    getTaskInfo(TaskID, function (info) {
                        addNewTask(info);
                    });
                break;

                case mcConst.kanbanActions.changed:
                    if (idx !== -1) {
                        if (internalInfo){
                            currentTask = $scope.taskList[idx].id;

                            if (internalInfo.status !== $scope.taskList[idx].status || internalInfo.needChange){
                                $scope.changeStage(currentTask, internalInfo.status, "change");

                                internalInfo.IDStage = internalInfo.status;
                            }

                            modifyTask(internalInfo);
                        } else
                            getTaskInfo(TaskID, function (info) {
                                currentTask = $scope.taskList[idx].id;

                                modifyTask(info);

                                $scope.stageHeaderHandler.updateAllCounters($scope.taskList);
                            });
                    }
                break;

                case mcConst.kanbanActions.deleted:
                    if (idx !== -1){
                        delTaskFromStage(idx, $scope.kanban.getItem($scope.taskList[idx].id));
                    } else {
                        console.log("Can't find task " + TaskID + " in project " + ProjectID);
                    }
                break;
            }
        }
    }

    function onCommentEvent(Action, ProjectID, TaskID, CommentID){
        var idx = mcService.findItemInArrayOfObj($scope.taskList, TaskID, "ID");

        if (ProjectID == $scope.currentProject.ID && idx != -1){
            switch (Action){
                case mcConst.kanbanActions.created:
                    if ($scope.taskList[idx].CommentsCount) {
                        $scope.taskList[idx].CommentsCount = parseInt($scope.taskList[idx].CommentsCount) + 1;
                    } else {
                        $scope.taskList[idx].CommentsCount = 1;
                    }

                    currentTask = $scope.taskList[idx].id;

                    modifyTask($scope.taskList[idx]);
                break;

                case mcConst.kanbanActions.changed:

                break;

                case mcConst.kanbanActions.deleted:
                    if ($scope.taskList[idx].CommentsCount) {
                        $scope.taskList[idx].CommentsCount = parseInt($scope.taskList[idx].CommentsCount) - 1;
                    }

                    if (!$scope.taskList[idx].CommentsCount) {
                        $scope.taskList[idx].CommentsCount = "";
                    }

                    currentTask = $scope.taskList[idx].id;

                    modifyTask($scope.taskList[idx]);
                break;
            }
        }
    }

    function onStageEvent(Action, ProjectID, StageID){ // todo: no stage actions !!!!!!!
        // console.log(arguments);
    }

    function addKanbanFilter(name, items, fields, method, doFilter){
        if (view && view.isVisible()){
            if (items.length){
                $scope.kanban._otherFilterOptions[name] = {
                    items : items,
                    fields: mcService.isString(fields) ? [fields] : fields,
                    union : method
                };
            } else

            if ($scope.kanban._otherFilterOptions.hasOwnProperty(name)){
                delete $scope.kanban._otherFilterOptions[name];
            }

            if (doFilter){
                $rootScope.$broadcast(window._messages_.Toolbar.doFilterKanban);
            }
        }
    }

    // ======================================

    $scope.addNewTask = function(){
        $rootScope.$broadcast('show' + mcConst.dataModels.TaskEdit, 'new');

        this.blur();
    };

    $scope.openElement = function (id, e) {
        // if (!id) {
            mcService.openKanbanLinkIfCan(e, $rootScope);
        // }
    };

    $scope.sortConditions = function (a, b) {
        return mcService.sortAB(mcService.sortableDate(b.dtCreated), mcService.sortableDate(a.dtCreated));
    };

    $scope.openTask = function (id, e, node, list) {
        var item = list.getItem(id);

        currentTask = id;
        item.allTags = $scope.currentProject.Tags;

        $rootScope.$broadcast('show' + mcConst.dataModels.Task, [item]);
    };

    $scope.editTask = function (id, e, node, list){
        var item = list.getItem(id);

        currentTask  = id;
        item.allTags = $scope.currentProject.Tags; // теги беруться у проекта, а не у таска, нужны все доступные теги

        $rootScope.$broadcast('show' + (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanTaskEdit, $scope.currentProject.RightsSet) ? mcConst.dataModels.TaskEdit : mcConst.dataModels.Task), [item]);
    };

    $scope.taskComments = function(id, e, node, list){
        $rootScope.$broadcast('show' + mcConst.dataModels.Task, [list.getItem(id), true]);
        // $rootScope.$broadcast('show' + mcConst.dataModels.Comments, [list.getItem(id), $scope.currentProject.dtClosed != mcConst.maxAvailableDate]);
    };

    $scope.collapseStage = function (stageID) {
        if (kanbanShowed) {
            $scope.stageHeaderHandler.updateAllCounters($scope.taskList);

            $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [true, "stageState." + stageID]);
        }
    };

    $scope.expandStage = function (stageID) {
        if (kanbanShowed) {
            $scope.stageHeaderHandler.updateAllCounters($scope.taskList);
            
            $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [false, "stageState." + stageID]);
        }
    };

    $scope.projectDtClosedChange = function (projectID, dtClosed) {
        if ($scope.currentProject && $scope.currentProject.ID == projectID){
            if (dtClosed === mcConst.maxAvailableDate) {
                // $scope.btnAddButton.show();
                $scope.closedInfo.hide();
            } else {
                // $scope.btnAddButton.hide();
                $scope.closedInfo.show();
            }

            // $scope.currentProject.dtClosed = dtClosed;

            $scope.kanban.refresh();
        }
    };

    $scope.getData = function(project, openTaskID, openCommentID){
        if (
            $scope.currentProject &&
            project &&
            project.ID == $scope.currentProject.ID &&
            project.dtClosed === $scope.currentProject.dtClosed &&
            (openTaskID || openCommentID)) {

            openTaskOrComment(openTaskID, openCommentID);
        } else {
            var task = new mcService.TaskList();
            var tags = [];

            $scope.currentProject = project ? project : $scope.currentProject;

            if (!project) {
                project = $scope.currentProject;
            }

            $rootScope.currentProject = project.ID;

            $rootScope.$broadcast(mcConst.lockInterface, []);

            // === Get Stages ===
            task.AddTask(function () {
                $rootScope.$broadcast(window._messages_.kanbanManager.getStagesList, [$rootScope.currentProject, function(data) {
                    fillStages(data);

                    task.Next();
                }]);
            });

            // === Get Task List ===
            task.AddTask(function () {
                $rootScope.$broadcast(window._messages_.kanbanManager.getTasksList, [$rootScope.currentProject,  function (tasks){
                    fillTasks(tasks);

                    $scope.taskList = tasks;

                    tags = [
                        mcLang(60 + mcConst.priority.LOW),      // "_PRIORITY_" + mcConst.priority.LOW,
                        mcLang(60 + mcConst.priority.MEDIUM),   // "_PRIORITY_" + mcConst.priority.MEDIUM,
                        mcLang(60 + mcConst.priority.HIGH),     // "_PRIORITY_" + mcConst.priority.HI,
                        mcLang(60 + mcConst.priority.IMPORTANT),// "_PRIORITY_" + mcConst.priority.IMPORTANT,
                        mcLang(60 + mcConst.priority.URGENT),   // "_PRIORITY_" + mcConst.priority.URGENT,
                        mcLang(60 + mcConst.priority.CRITICAL), // "_PRIORITY_" + mcConst.priority.CRITICAL
                    ];

                    task.Next();
                }]);
            });

            // === Get Tags ===
            task.AddTask(function () {
                $rootScope.$broadcast(window._messages_.kanbanManager.getProjectTags, [$rootScope.currentProject, function (tagsList) {
                    // $scope.currentProject.Tags = Object.keys(tagsList.Tags || {}); // need array of strings
                    $scope.currentProject.Tags = tags.concat(Object.keys(tagsList.Tags || {})); // need array of strings

                    task.Next();
                }]);
            });

            // === Show ===
            task.AddTask(function () {
                showAndFill();

                sortKanban();

                openTaskOrComment(openTaskID, openCommentID);

                $rootScope.$broadcast('hide' + mcConst.lockInterface, []); // unlock
                $rootScope.$broadcast(window._messages_.Toolbar.changeFilterList, ["body" + mcConst.dataModels.KanbanBody]);
                $rootScope.$broadcast(window._messages_.Menu.updateTagsList, [priorityTagList, mcConst.dataModels.KanbanBody]);
            });

            task.Run();
        }
    };

    $scope.removeTask = function (id) { // , e, node, list
        var name = $scope.kanban.getItem(id).Name;

        webix.confirm({
            type  : "confirm-warning",
            text  : mcService.Lang(44, mcService.copy(name, 0, 70) + (name.length > 70 ? "..." : "")), // "44" :"Удалить задание?",
            ok    : mcService.Lang(32), // "32" :"да",
            cancel: mcService.Lang(33), // "33" :"нет",
            callback:function(yes){
                if (yes) {
                    removeTask(id);
                }
            }
        });
    };

    $scope.srtkb = function(context,ev,list) {
        sortKanban();

        list.showItem(context.start);
        list.select(context.start);

        setTimeout(function () {
            var task = $scope.dropTask;

            if (task && task.UINPerformer == -1 && task.DisplayNamePerformer === ""){
                currentTask = task.id;

                modifyTask({
                    UINPerformer: mcConst.UserInfo.UIN,
                    DisplayNamePerformer: mcConst.UserInfo.Nick
                });
            }

            
            $scope.dropTask = null;
        }, 150);
    };

    $scope.changeStage = function(itemId, newStage, isAction){
        var task = $scope.kanban.getItem(itemId);

        $rootScope.SendCMDToServer([
            mcConst._CMD_.cs_kanban_task_move,
            mcConst.SessionID,

            task.ID,
            newStage,

            function () {
                task.status  = newStage;
                task.IDStage = newStage;

                $scope.dropTask = isAction === "change" ? null : task;

                $scope.stageHeaderHandler.updateAllCounters($scope.taskList);
            }
        ]);
    };

    $scope.show = function(){
        kanbanShowed = false;

        view = initKanbanBody($scope);

        kanbanShowed = true;
    };

    // ================================================

    var _msg = _messages_.KanbanBody = {
        addKanbanFilter        : 'addKanbanFilter',
        commentsTaskCount      : 'commentsTaskCount',
        projectDtClosedChange  : 'projectDtClosedChange',
        onCommentEvent         : 'onCommentEvent',
        onTaskEvent            : 'onTaskEvent',
        modifyTaskToKanban     : 'modifyTaskToKanban',
        removeTaskFromKanban   : 'removeTaskFromKanban',
        addNewTaskToKanban     : 'addNewTaskToKanban',
        updateStageCount       : 'updateStageCount',
        onStageEvent           : 'onStageEvent',
        getTasksCountByStages  : 'getTasksCountByStages',
        modifyKanbanStages     : 'modifyKanbanStages'
    };

    $scope.$on(_msg.addKanbanFilter, function (e, args) {
        addKanbanFilter.apply(null, args);
    });

    $scope.$on(_msg.projectDtClosedChange, function (e, args) {
        $scope.projectDtClosedChange.apply(null, args);
    });

    $scope.$on(_msg.getTasksCountByStages, function (e, args) {
        return getTasksCountByStages.apply(null, args);
    });

    $scope.$on(_msg.onCommentEvent, function (e, args) {
        onCommentEvent.apply(null, args);
    });

    $scope.$on(_msg.onStageEvent, function (e, args) {
        onStageEvent.apply(null, args);
    });

    $scope.$on(_msg.onTaskEvent, function (e, args) {
        onTaskEvent.apply(null, args);
    });

    $scope.$on(_msg.commentsTaskCount, function (e, args) {
        updateCommentsCount.apply(null, args);
    });

    $scope.$on(_msg.modifyTaskToKanban, function (e, task) {
        var idx = mcService.findItemInArrayOfObj($scope.taskList, task.ID, "ID");

        if (idx !== -1){
            currentTask = $scope.taskList[idx].id;

            task.parseName = parseTaskImages(task);

            modifyTask(task);
        }
    });

    $scope.$on(_msg.removeTaskFromKanban, function (e, id) {
        removeTask(id);
    });

    $scope.$on(_msg.addNewTaskToKanban, function (e, task) {
        addNewTask(task);
    });

    $scope.$on(_msg.updateStageCount,   function (e, args) {
        $scope.stageHeaderHandler.updateTaskCount.apply(null, args);
    });

    $scope.$on(_msg.modifyKanbanStages, function (e, args) {
        var stagesList = args[0];

        fillStages(stagesList);
        fillTasks($scope.taskList);

        showAndFill();
    });

    $scope.$on('hide' + $scope.Name, function(){
        view.hide();
    });

    $scope.$on('show' + $scope.Name, function(e, args){
        $scope.getData.apply(null, args);
    });
}



function initToolbar($scope){
    "use strict";

    var view = $$($scope.Name);

    if (!view){
        var filter = new mcService.FilterFiled({
            listName     : "body" + mcConst.dataModels.KanbanBody,
            rows         : {
                width : 350,
                id    : "searchField" + $scope.Name,
                hidden: true,
                // css   : "projectSearch"
                css   : "noBoxShadow"
            },
            noCloseBtn   : true,
            searchText   : mcLang(118), // "118":"Поиск по заданиям",
            topSpacer    : 4,
            filterParams : [
                "Name",
                "Desc",
                "ProjectName",
                "dtCreated",
                "DisplayNameOwner"
            ],
            onAfterFilter: $scope.updateToolbarTaskCount,
            onClear      : $scope.updateToolbarTaskCount
        });

        $$('wrap' + $scope.Name).addView({
            id: $scope.Name,
            view: "toolbar",
            css: "noBG",
            borderless: true,
            padding: 0,
            rows: [{  cols: [
                { width: 352, cols: [
                    { width: 4},

                    { id: "btnMenu" + $scope.Name, view: "toggle", type: "icon", offIcon: "bars", onIcon: "bars", css: "byCenter", value: 0,
                        tooltip: "Menu | CTRL + ~", width: 35, click: $scope.showHideMenu}, // "49" :"Kanban",

                    { id: "dashboard" + $scope.Name, view: "segmented", type: "icon", /*width: 270,*/ css: "byCenter noBorder noRound", value: $scope.projectView,
                        options: [
                            { id: $scope.listTaskView, value: mcService.insertIco("fa-th", " ") + " " + mcLang(45)}, // "45" :"Задания",
                            { id: $scope.projectView, value: mcService.insertIco("fa-tasks fa-rotate-270", " ") + " " + mcLang(52)} // "52" :"Проекты",
                        ],
                        on: { onChange: $scope.switchMenu}
                    },

                    // { width: 5},
                    //
                    // { id: "addProjectBTN" + $scope.Name, view: "button", type: "icon", icon: "file", label: "", click: $scope.addNewProject, tooltip: mcLang(22) + " | CTRL + P", width: 29}, // "22" :"Добавить проект",
                ]},

                { width: 6},

                filter,

                { view: "resizer", css: "noBG", borderless: true},

                { cols: [
                    { id: "filteredTasksCount", view: "label", label: " ", width: 130, hidden: true},

                    { id: "addFirstProject" + $scope.Name, view: "button", value: mcLang(22), tooltip: mcLang(22), width: 150, click: $scope.addNewProject, hidden: true}, // "22" :"Создать проект",

                    { view: "combo", id: "projectList" + $scope.Name, css: "projectsListMenu noBorderAll glassDarkBgLR noBoxShadow", on: {
                        onChange     : $scope.selectProject,
                        onDestruct   : $scope.unregisterEsc,
                        onAfterRender: $scope.registerEsc
                    }, options: []}, // если не установить пустой options: [] - не будет отрабатывать $scope.projectList.getPopup()

                    { id: "toolbarSpacer", hidden: true},
                    // { width: 10 },
                ]},
            ]}]
        });

        view = $$($scope.Name);

        $scope.kanbanBodyFilter = filter;

        $scope.toolbarSpacer = $$("toolbarSpacer");
        $scope.projectList   = $$("projectList" + $scope.Name);
        $scope.dashboard     = $$("dashboard" + $scope.Name);
        $scope.btnMenu       = $$("btnMenu" + $scope.Name);
        // $scope.addProjectBTN = $$("addProjectBTN" + $scope.Name);

        $scope.wrapperTasksCount  = $$("wrapperTasksCount");
        $scope.filteredTasksCount = $$("filteredTasksCount");

        $scope.addFirstProject = $$("addFirstProject" + $scope.Name);
        $scope.searchField     = $$("searchField" + $scope.Name);

        $scope.relocateFilterList = filter.relocateList;

        mcService.disableContextMenu(view, ["projectLink", "fa-external-link"]);
    } else {
        view.show();
    }

    return view;
}
function toolbarController($scope, $rootScope, $location){
    "use strict";

    $scope.Name = mcConst.dataModels.Toolbar;

    var view           = null;
    var projectClosed  = false;
    var PRESET_TOOLBAR = "PRESET_TOOLBAR";
    var PRESET_TOOLBAR_SELECT_PROJECT = "PRESET_TOOLBAR_SELECTPROJECT";
    var openTarget     = {
        needOpen  : false,
        projectID : null,
        taskID    : null,
        commentID : null
    };
    var currentView    = mcConst.dataModels.KanbanBody;
    var __projectList  = [];
    var canChange      = true;
    var CLOSE_MENU     = '0';
    var OPEN_MENU      = '1';

    $scope.projectView    = "0";
    $scope.listTaskView   = "1";

    $scope.currentProject = null;

    function onOffProjects (projectsPresents) {
        if (projectsPresents){
            $scope.searchField.show();
            $scope.dashboard.show();
            // $scope.addProjectBTN.show();
            $scope.projectList.show();

            $scope.addFirstProject.hide();
        } else {
            $rootScope.currentProject = null;

            $scope.searchField.hide();
            $scope.dashboard.hide();
            // $scope.addProjectBTN.hide();
            $scope.projectList.hide();

            $scope.addFirstProject.show();
        }
    }

    function openCloseMenu(val) {
        if (val == undefined){
            val = !$scope.btnMenu.getValue();
        }

        $scope.showHideMenu(val);
        //$scope.btnMenu.setValue(!$scope.btnMenu.getValue());
        $scope.btnMenu.setValue(mcService.convertBool(val));
    }

    function getInfoFromURI(uri) {
        $rootScope.Purl = purl(uri ? uri : "");

        var projectID = $rootScope.Purl.fparam("project");
        var taskID    = $rootScope.Purl.fparam("task");
        var commentID = $rootScope.Purl.fparam("comment");

        openTarget = {
            needOpen  : false,
            projectID : null,
            taskID    : null,
            commentID : null
        };

        if (projectID && !isNaN(parseInt(projectID))){
            openTarget.needOpen = true;
            openTarget.projectID = projectID;

            if (taskID && !isNaN(parseInt(taskID))){
                openTarget.taskID = taskID;

                if (commentID && !isNaN(parseInt(commentID))){
                    openTarget.commentID = commentID;
                }
            }
        }
}

    function showTasksCount(total, current) {
        $scope.filteredTasksCount.define('label', mcService.myReplaceFormated("#{current} #{from} #{totalCount}", {
            current   : current,
            totalCount: total,
            from      : mcLang(151) // "151":"из",
        }));

        $scope.filteredTasksCount.refresh();
    }

    function removeProject(projectID) {
        var idx = mcService.findItemInArrayOfObj(__projectList, projectID || $rootScope.currentProject, "ID");

        __projectList.splice(idx, 1);

        var list = $scope.projectList.getPopup().getList();

        list.remove($scope.projectList.getValue());
        // list.remove(list.getSelectedId());

        $rootScope.$broadcast(window._messages_.kanbanManager.removingProject, [projectID]);

        if (__projectList.length){
            $scope.projectList.setValue(list.getFirstId());
            // list.select(list.getList().getFirstId());

        } else {
            $scope.descriptionProject.define("template", "");
            $scope.createdProject.define("template", "");
            $scope.deadLineProject.define('template', "");
            $scope.nameProject.define('template', "");
            $scope.ownerDisplayName.define('template', "");

            $scope.ownerDisplayName.refresh();
            $scope.nameProject.refresh();
            $scope.deadLineProject.refresh();
            $scope.createdProject.refresh();
            $scope.descriptionProject.refresh();

            // $scope.closedProject.hide();
            // $scope.btnAddButton.hide();
            // $scope.btnCloseProject.hide();
            // $scope.closedProjectWrap.hide();
            // $scope.btnDeleteProject.hide();

            //$scope.projectList.setValue("");


            $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [null]);

            // $rootScope.$broadcast(_messages_.Toolbar.showHideAddTaskButton, true);
            projectClosed = true;

            $rootScope.$broadcast('hide' + mcConst.dataModels.KanbanBody);

            $rootScope.$broadcast(window._messages_.Toolbar.projectsMode, false); // -> $rootScope.currentProject = null;

            //$rootScope.$broadcast(_messages_.Toolbar.showProjectName, [""]);
        }
    }

    function addProject(list, data, clear) {
        if (clear) {
            __projectList = [];
        }

        Object.keys(data).forEach(function (key) {
            if (data[key].AccessType == mcConst.accessType.fullAccess){
                data[key].RightsSet = mcConst.MyRightsSet;
            }
            
            __projectList.push(data[key]);
        });

        // $scope.noProjects = false;

        var projectID = clear ? $rootScope.currentProject || list.getFirstId() : __projectList[__projectList.length - 1].ID;

        if (__projectList.length){
            list.parse(mcService.transformDataObject(mcService.convertObjToArray(data), [['ID', 'id'], ['Name', 'value'], ['dtClosed', 'dtClosed']]));

            $rootScope.$broadcast(window._messages_.kanbanManager.getStagesList, [projectID]);

            if (clear) {
                canChange = false;

                $scope.projectList.setValue(projectID);
                // $scope.projectList.select(projectID);

                canChange = true;
            }
        } else {
            // $scope.noProjects = true;
            openCloseMenu(CLOSE_MENU);

            $rootScope.currentProject = null;

            // $rootScope.$broadcast(_messages_.Toolbar.showHideAddTaskButton, true);
            projectClosed = true;

            // $scope.btnDeleteProject.hide();
        }
    }

    function getProjectInfo(projectID, cb) {
        $rootScope.SendCMDToServer([
            mcConst._CMD_.cs_kanban_get_project_info,
            mcConst.SessionID,
            projectID,
            cb
        ]);
    }

    function addedNewProject(project, skipThisAction){
        if (skipThisAction) {
            return;
        }

        __projectList.push(project);

        if ($scope.projectList){
            $scope.projectList.getPopup().getList().add({
                id   : project.ID,
                value: project.Name
            });
            $scope.projectList.setValue(project.ID);
        } else {
            $rootScope.$broadcast('show' + mcConst.dataModels.KanbanBody, [project]);
            //$rootScope.$broadcast(_messages_.Toolbar.showProjectName, [project.Name, project.ID]);
        }
    }

    function projectEvents(Action, ProjectID){
        switch (Action){
            case mcConst.kanbanActions.created:
                getProjectInfo(ProjectID, function (info) {
                    var data = {};
                    data[info.ID] = info;

                    addedNewProject(info, true);

                    addProject($scope.projectList, data);
                });
            break;

            case mcConst.kanbanActions.changed:
                getProjectInfo(ProjectID, function (info) {
                    var idx = mcService.findItemInArrayOfObj(__projectList, ProjectID, "ID");

                    if (idx !== -1){
                        __projectList[idx] = mcService.Marge(__projectList[idx], info);

                        if ($scope.projectList){
                            editProjectName(info);

                            if ($rootScope.currentProject == info.ID){
                                $rootScope.$broadcast(window._messages_.kanbanManager.modifyingProject, [info]);
                                $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [info]);
                                $rootScope.$broadcast('show' + mcConst.dataModels.KanbanBody, [info]);
                            }
                        }
                    }
                });
            break;

            case mcConst.kanbanActions.deleted:
                removeProject(ProjectID);

                if (ProjectID == $rootScope.currentProject){
                    $rootScope.$broadcast('hide' + mcConst.dataModels.Task);
                    $rootScope.$broadcast('hide' + mcConst.dataModels.Comments);
                }
            break;

            case mcConst.kanbanActions.open:
                canChange = false;

                $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [ProjectID, "currentProject", true]);

                if ($scope.projectList) {
                    $scope.projectList.setValue(ProjectID);
                    // $scope.projectList.select(ProjectID);
                }

                canChange = true;
            break;
        }
    }

    function closeProject() {
        webix.confirm({
            type  : "confirm-warning",
            //text  : mcService.Lang(58) + "?" + "<br><b>" + $scope.projectList.getText() + "</b>", // "58" :"Закрыть проект",
            text  : mcService.Lang(58) + "?" + "<br><b>" + $scope.projectList.getText() + "</b>", // "58" :"Закрыть проект",
            ok    : mcService.Lang(32), // "32" :"да",
            cancel: mcService.Lang(33), // "33" :"нет",
            callback:function(yes){
                if (yes) {
                    $rootScope.SendCMDToServer([
                        mcConst.lockInterface,
                        mcConst._CMD_.cs_kanban_close_project,
                        mcConst.SessionID,
                        $rootScope.currentProject,

                        function () {
                            var project = __projectList[mcService.findItemInArrayOfObj(__projectList, $rootScope.currentProject, "ID")];

                            project.dtClosed = mcService.formatDate(new Date(), "dd.mm.yyyy.hh.nn.ss");

                            // fillProjectInfo(project);
                            $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [project]);

                            $rootScope.$broadcast(window._messages_.KanbanBody.projectDtClosedChange, [project.ID, project.dtClosed]);
                        }
                    ]);
                }
            }
        });
    }

    function resumeProject() {
        webix.confirm({
            type  : "confirm-warning",
            //text  : mcService.Lang(59) + "?" + "<br><b>" + $scope.projectList.getText() + "</b>", // "59" :"Возобновить проект",
            // text  : mcService.Lang(59) + "?" + "<br><b>" + $scope.projectList.getSelectedItem().value + "</b>", // "59" :"Возобновить проект",
            text  : mcService.Lang(59) + "?" + "<br><b>" + $scope.projectList.getText() + "</b>", // "59" :"Возобновить проект",
            ok    : mcService.Lang(32), // "32" :"да",
            cancel: mcService.Lang(33), // "33" :"нет",
            callback:function(yes){
                if (yes) {
                    $rootScope.SendCMDToServer([
                        mcConst.lockInterface,
                        mcConst._CMD_.cs_kanban_reopen_project,
                        mcConst.SessionID,
                        $rootScope.currentProject,
                        function () {
                            var project = __projectList[mcService.findItemInArrayOfObj(__projectList, $rootScope.currentProject, "ID")];

                            project.dtClosed = mcConst.maxAvailableDate;

                            // fillProjectInfo(project);

                            $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [project]);
                            $rootScope.$broadcast(window._messages_.KanbanBody.projectDtClosedChange, [project.ID, project.dtClosed]);
                        }
                    ]);
                }
            }
        });
    }

    function deleteProject() {
        setTimeout(function () {
            webix.confirm({
                type  : "confirm-warning",
                //text  : mcService.Lang(68) + "?" + "<br><b>" + $scope.projectList.getText() + "</b>", // "68" :"Удалить проект",
                // text  : mcService.Lang(68) + "?<br><b>" + $scope.projectList.getSelectedItem().value + "</b>", // "68" :"Удалить проект",
                text  : mcService.Lang(68) + "?<br><b>" + $scope.projectList.getText() + "</b>", // "68" :"Удалить проект",
                ok    : mcService.Lang(32), // "32" :"да",
                cancel: mcService.Lang(33), // "33" :"нет",
                callback:function(yes){
                    if (yes) {
                        $rootScope.SendCMDToServer([
                            mcConst.lockInterface,
                            mcConst._CMD_.cs_kanban_delete_project,
                            mcConst.SessionID,
                            $rootScope.currentProject,
                            removeProject
                        ]);
                    }
                }
            });
        }, 50);
    }

    function editProjectName(info){
        if ($scope.projectList){
            $scope.projectList.getPopup().getList().parse([{id: info.ID, value: info.Name}]);

            if ($scope.projectList.getValue() == info.ID) {
                $scope.projectList.refresh();
            }
        }
    }

    function getProjectRights(projectID){
        var idx = mcService.findItemInArrayOfObj(__projectList, projectID, "ID");
        var res = "";

        if (idx !== -1) {
            res = __projectList[idx].RightsSet;
        }

        return res;
    }

    // ======================================

    $scope.registerHotKeys = function(){
        $rootScope.hotKeyDispatcher.addPreset(PRESET_TOOLBAR, [{
            key    :  mcConst.keyCodes.q,
            ctrlKey: true,
            func   : function () {
                if ($rootScope.currentProject != null && !projectClosed && currentView === mcConst.dataModels.KanbanBody && (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanAddTask, $scope.currentProject.RightsSet))){
                    $scope.addNewTask();
                }
            }
        }, {
            key    :  mcConst.keyCodes.insert,
            // ctrlKey: true,
            func   : function () {
                if ($rootScope.currentProject != null && !projectClosed && currentView === mcConst.dataModels.KanbanBody && (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanAddTask, $scope.currentProject.RightsSet))){
                    $scope.addNewTask();
                }
            }
        }/*, {
            key    :  mcConst.keyCodes.p,
            ctrlKey: true,
            skipDefault: true,
            func   : $scope.addNewProject
        }*/, {
            key    :  mcConst.keyCodes.f,
            ctrlKey: true,
            skipDefault: true,
            func   : function () {
                $scope.searchField.getChildViews()[1].getChildViews()[0].focus();
            }
        }, {
            key    :  mcConst.keyCodes.gravis,
            ctrlKey: true,
            func   : openCloseMenu
        }], document);
    };

    $scope.registerEsc = function(){
        var combo = $scope.projectList || this;

        if (combo){
            $scope.unregisterEsc();

            $rootScope.hotKeyDispatcher.addPreset(PRESET_TOOLBAR_SELECT_PROJECT, [{
                key    : mcConst.keyCodes.esc,
                func   : function () {
                    if (!combo.getPopup().getList().isVisible()) {
                        combo.refresh();
                        combo.focus();
                    }
                }
            }], combo.getInputNode());
        }
    };

    $scope.unregisterEsc = function(){
        $rootScope.hotKeyDispatcher.removePreset(PRESET_TOOLBAR_SELECT_PROJECT);
    };

    // ======================================

    $scope.onProjectFilterClear = function () {
        if ($rootScope.currentProject && $scope.projectList.getItem($rootScope.currentProject)){
            $scope.projectList.setValue($rootScope.currentProject);
            // $scope.projectList.select($rootScope.currentProject);
        }
    };

    $scope.selectProject = function(){
        var id = $scope.projectList.getValue();
        // var id = $scope.projectList.getSelectedId();

        if (id === -1 || id === "") return;

        var currentProject = __projectList[mcService.findItemInArrayOfObj(__projectList, id, "ID")];

        $scope.currentProject = currentProject;

        if (id == $rootScope.currentProject){
            // fillProjectInfo(currentProject);
            $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [currentProject]);

            return
        }

        $rootScope.$broadcast(mcConst.lockInterface, []);

        if (canChange) {
            $rootScope.currentProject = id;

            $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [currentProject.ID, "currentProject"]);

            $rootScope.$broadcast('show' + mcConst.dataModels.KanbanBody, [currentProject]);
        }

        // fillProjectInfo(currentProject);
        $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [currentProject]);
    };

    $scope.saveListHeight = function () {
        $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [$scope.projectList.$height, "common.menuListHeight"]);
    };

    // ======================================

    $scope.switchMenu = function (val) {
        currentView = mcService.convertIntToBool(val) ? mcConst.dataModels.Dashboard : mcConst.dataModels.KanbanBody;

        $rootScope.$broadcast(window._messages_.wrapper.changeView, [currentView]);

        $scope.kanbanBodyFilter.clearFilter(true);

        if (mcService.convertIntToBool(val)) {   // Dashboard
            $rootScope.$broadcast("show" + mcConst.dataModels.Dashboard, []);
            $rootScope.$broadcast(window._messages_.Menu.changeMenu, [mcConst.dataModels.Dashboard]);

            // $scope.addProjectBTN.hide();
            $scope.projectList.hide();

            $scope.toolbarSpacer.show();
            $scope.filteredTasksCount.show();

            // $scope.projectName.hide();
            // $scope.wrapperTasksCount.show();
        } else {                                 // KanbanBody
            $rootScope.$broadcast(window._messages_.Menu.changeMenu, [mcConst.dataModels.KanbanBody]);

            // $scope.addProjectBTN.show();
            $scope.projectList.show();

            $scope.toolbarSpacer.hide();
            $scope.filteredTasksCount.hide();

            // $scope.projectName.show();
            // $scope.wrapperTasksCount.hide();
        }

        $rootScope.$broadcast(window._messages_.Menu.clearFilters);
        $rootScope.$broadcast(window._messages_.Toolbar.changeFilterList, ["body" + currentView]);
    };

    $scope.updateToolbarTaskCount = function (data) {
        $rootScope.$broadcast(window._messages_.kanbanManager.getDashboardTasksCount, function (totalCount) {
            showTasksCount(totalCount, data ? data.length : totalCount);
        });
    };

    $scope.addNewTask = function(){
        $rootScope.$broadcast('show' + mcConst.dataModels.TaskEdit, 'new');
    };

    $scope.addNewProject = function(){
        $rootScope.$broadcast('show' + mcConst.dataModels.EditProject, []);
    };

    $scope.openLinkSource = function () {
        if (mcConst.LoggedIn){
            $rootScope.$broadcast('openKanbanURI', [location.href]);

            // !$rootScope.noChangeURI && history.replaceState({}, document.title, ".");
            !$rootScope.noChangeURI && window.history.replaceState({}, document.title, '#project=' + $rootScope.currentProject);
        } else {
            var token = $location.param('token') || $location.fparam("token");

            if (token){
                $rootScope.$broadcast('show' + mcConst.dataModels.Login);
            }
        }
    };

    $scope.drawProjectList = function (obj){
        return obj.dtClosed === mcConst.maxAvailableDate ? obj.value : "<span class='red'>" + obj.value + "</span>";
    };

    $scope.getData = function(){
        var taskList = new mcService.TaskList();

        taskList.AddTask(function(){
            $scope.show();

            webix.ready(taskList.Next);
        });

        taskList.AddTask(function(){
            $rootScope.$broadcast(window._messages_.kanbanManager.getOptions, [taskList.Next]);
        });

        taskList.AddTask(function () {
            $rootScope.$broadcast(window._messages_.kanbanManager.getProjectsList, [function(data){
                var pList = $scope.projectList.getPopup().getList();

                addProject(pList, data, true);

                openTarget.needOpen = openTarget.needOpen && data.hasOwnProperty(openTarget.projectID);

                var project = openTarget.needOpen
                    ? data[openTarget.projectID]
                    : (data.hasOwnProperty($rootScope.kanbanOptions.currentProject)
                        ? data[$rootScope.kanbanOptions.currentProject]
                        : data[Object.keys(data)[0]]);

                if (project){
                    $rootScope.currentProject = project.ID;

                    projectClosed = project.dtClosed !== mcConst.maxAvailableDate;

                    if (openTarget.needOpen) {
                        $scope.dashboard.setValue($scope.projectView);
                    }

                    $rootScope.$broadcast('show' + mcConst.dataModels.KanbanBody,
                        [project]
                        .concat(openTarget.needOpen && openTarget.taskID ? openTarget.taskID : [])
                        .concat(openTarget.needOpen && openTarget.taskID && openTarget.commentID ? openTarget.commentID : []));

                    // projectName(project.Name, project.ID);

                    openCloseMenu($rootScope.kanbanOptions.common.openMenu);
                    
                    document.documentElement.style.setProperty('--card-height', $rootScope.kanbanOptions.CardSize ? '108px' : 'auto');

                    $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [project]);

                    canChange = false;

                    if ($scope.projectList) {
                        $scope.projectList.setValue(project.ID);

                        $scope.sortProjectList();
                    }

                    canChange = true;
                }

                onOffProjects(project);

                $scope.registerHotKeys();
            }]);
        });

        taskList.Run();
    };

    $scope.sortProjectList = function(){
        var pList = $scope.projectList.getPopup().getList();

        pList.sort(function (a, b) {
            return mcService.sortAB(a.value, b.value);
        });

        pList.sort(function (a, b) {
            return mcService.sortAB(a.dtClosed, b.dtClosed);
        });

        pList.refresh();
    };

    $scope.showHideMenu = function (val) {
        if (val == undefined || val == "btnMenuToolbar"){
            val = !$scope.btnMenu.getValue();
        }

        var projectID = $scope.projectList.getValue(); 

        $rootScope.$broadcast((val ? 'show' : 'hide') + mcConst.dataModels.Menu, [projectID]);

        if (val){
            $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [__projectList[mcService.findItemInArrayOfObj(__projectList, $scope.projectList.getValue(), "ID")]]);
            $rootScope.$broadcast(window._messages_.Menu.changeMenu, [$scope.dashboard.getValue() === $scope.projectView ? mcConst.dataModels.KanbanBody : mcConst.dataModels.Dashboard]);
        }

        $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [val, "common.openMenu"]);
    };

    $scope.show = function(){
        view = initToolbar($scope);

        var list = $scope.projectList.getPopup().getList();

        list.define("template", $scope.drawProjectList);
    };

    // ================================================

    var _msg = _messages_.Toolbar = {
        doFilterKanban        : 'doFilterKanban',
        onOffDashboard        : 'onOffDashboard',
        changeFilterList      : 'changeFilterList',
        showProjectName       : 'showProjectName',
        projectsMode          : 'projectsMode',
        openKanbanURI         : 'openKanbanURI',
        updateToolbarTaskCount: 'updateToolbarTaskCount',
        onProjectEvent        : 'onProjectEvent',
        addedNewProject       : 'addedNewProject',
        getProjectData        : 'getProjectData',
        setProjectTags        : 'setProjectTags',

        closeProject          : 'closeProject',
        resumeProject         : 'resumeProject',
        deleteProject         : 'deleteProject',
        editProjectName       : 'editProjectName',

        getProjectRights      : 'getProjectRights',
        
        '-----'               : '-----'
    };

    $scope.$on(_msg.doFilterKanban, function () {
        if (view && $scope.kanbanBodyFilter) {
            $scope.kanbanBodyFilter.doFilter(true);
        }
    });

    $scope.$on(_msg.closeProject, function () {
        closeProject();
    });

    $scope.$on(_msg.resumeProject, function () {
        resumeProject();
    });

    $scope.$on(_msg.deleteProject, function () {
        deleteProject();
    });

    $scope.$on(_msg.onOffDashboard, function (e, args) {
        if (mcService.convertBool(args[0])){
            $scope.dashboard.setValue($scope.listTaskView);
        } else {
            $scope.dashboard.setValue($scope.projectView);
        }
    });

    $scope.$on(_msg.getProjectRights, function (e, args) {
        return getProjectRights.apply(null, args);
    });

    $scope.$on(_msg.getProjectData, function (e, args) {
        var id = mcService.findItemInArrayOfObj(__projectList, args[0], "ID");

        return id === -1 ? null : __projectList[id];
    });

    $scope.$on(_msg.setProjectTags, function (e, args) {
        var id = mcService.findItemInArrayOfObj(__projectList, args[0], "ID");

        if (id !== -1 && __projectList && __projectList[id]){
            __projectList[id].Tags = args[1];
        }
    });

    $scope.$on(_msg.changeFilterList, function (e, args) {
        var newList = args && args[0] ? args[0] : "body" + mcConst.dataModels.KanbanBody;

        $scope.relocateFilterList(newList, true);
    });

    $scope.$on(_msg.projectsMode, function (e, projectsPresents) {
        onOffProjects.call(null, projectsPresents);
    });

    $scope.$on(_msg.openKanbanURI, function(e, args){
        if (!$rootScope.noChangeURI) {
            $rootScope.$broadcast(window._messages_.kanbanManager.getProjectsList, [function(data){
                getInfoFromURI(args[0]);

                var project = data[openTarget.projectID];

                if (openTarget.needOpen && project){
                    $rootScope.$broadcast('show' + mcConst.dataModels.KanbanBody,
                        [project]
                        .concat(openTarget.needOpen && openTarget.taskID ? openTarget.taskID : [])
                        .concat(openTarget.needOpen && openTarget.taskID && openTarget.commentID ? openTarget.commentID : []));

                    projectEvents(mcConst.kanbanActions.open, openTarget.projectID);
                } else
                if (openTarget.projectID !== null) {
                    webix.alert(mcLang(75, openTarget.projectID)); // "75" :"Проект <b>#%s</b> не найден!",
                }
            }]);
        }
    });

    $scope.$on(_msg.updateToolbarTaskCount, function () {
        $scope.updateToolbarTaskCount();
    });

    $scope.$on(_msg.addedNewProject, function(err, args){
        addedNewProject.apply(null, args);
    });

    $scope.$on(_msg.onProjectEvent, function (e, args) {
        projectEvents.apply(null, args);
    });

    $scope.$on(_msg.editProjectName, function (e, args) {
        editProjectName.apply(null, args);
    });

    $scope.$on('hide' + $scope.Name, function(){
        view.hide();
    });

    $scope.$on('show' + $scope.Name, function(){
        getInfoFromURI();

        $scope.getData();
    });

    (function(eventInfo){
        window[eventInfo[0]](eventInfo[1] + 'popstate', $scope.openLinkSource, false);
    })(window.addEventListener ? ['addEventListener', ''] : ['attachEvent', 'on']);

    // $rootScope.$broadcast('windowResize', [function(w, h){
    //     if (view){
    //         view.adjust();
    //     }
    // }]);
}

function initTask($scope){
    "use strict";

    var view         = $$($scope.Name);
    var labelWidth   = 100;
    var wndSize      = mcService.getWindowSize();

    var deadlineText = $scope.currentTask._new === 'new' || $scope.currentTask.dtDeadLine === mcConst.maxAvailableDate
                        ? mcLang(69)
                        : mcService.utcTime($scope.currentTask.dtDeadLine, false, "MMM d, yyyy hh:nn");

    if (view){
        view.destructor();
    }

    var timeCreate = "";

    if ($scope.currentTask.dtCreated) {
        var time = mcService.utcTime($scope.currentTask.dtCreated);
        
        timeCreate =
            mcService.formatDate(time, "MMM dd") +
            (time.split('.')[2] !== (new Date()).getFullYear().toString() ? ", " + time.split('.')[2] : "") +
            mcService.formatDate(time, ", hh:nn")
    }

    var label = mcService.myReplaceFormated(
        "<div class='byCenter sizeBigger priorityBG_#{prior}'>" +
            "<span class='inlineBlock noOverflow'>#{taskPrev} [</span>" +
            "<a class='taskLink textEllipsis inlineBlock' style='max-width: 70%; line-height: 21px' href='#{taskLink}' target='_blank' onclick='#{copyLink}'>" +
                "<span class='f10'>#{linkIco}</span>#{projName}" +
            "</a>" +
            "<span class='inlineBlock noOverflow'>]</span>" +
        "</div>",
        {
            prior   : $scope.currentTask.Priority,
            taskPrev: mcLang(17),           // "17" :"Задание",
            projName: $scope.currentTask.prName,
            taskLink: $scope.taskLink,
            linkIco : mcService.insertIco("fa-external-link"),
            copyLink: "window.__copyLinkToClipBoard.apply(this, arguments)"
        }
    );

    view = webix.ui({
        view     : "window",
        position : "center",
        modal    : true,
        width    : wndSize.width >= 650 ? 600 : wndSize.width * 0.95,
        height   : wndSize.height * 0.95,// $scope.canEdit || taskDesc ? 500 : "",
        move     : true,
        css      : "highlightLink",
        head     : { padding: 0, cols: [
            { view: "button", type: "icon", icon: "pencil", css: "closeBtn noShadow", width: 30, click: $scope.editThisTask, tooltip: mcLang(79), // "79" :"Редактировать"
                hidden: !(mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanTaskEdit, $scope.projectRights))
            },
            { view: "label", label: label },
            { view: "button", type: "icon", icon: "close",  css: "closeBtn noShadow", width: 30, click: $scope.close, tooltip: mcLang(12) + " | ESC"}  // "12" :"Закрыть",
        ]},

        autoheight: true,
        body      : { type: "layout", padding: 4, rows: [
            $scope.dwlMngr.progressWrapper,

            { rows: [

                { rows: [
                    { id: "taskWrapper" + $scope.Name, view: "scrollview", scroll: 'y', scrollSpeed:"100ms", css: "hiddenScrollMenu", borderless: true, body: { rows: [
                        { id: "taskNameView" + $scope.Name, template: $scope.taskNameText, css: "selectable breakWord fitContentAuto", autoheight: true, borderless: true},

                        { height: 10},
                        { id: "commentLabel", view: "label", label: mcLang(20).toUpperCase(), css: "brown", hidden: true}, // "20" :"Комментарии",

                        // { id: "commentsList" + $scope.Name, view: "activeDataView", css: "commentsList selectable",
                        //     scroll        : false,
                        //     borderless    : true,
                        //     xCount        : 1,
                        //     autoheight    : true,
                        //     sizeToContent : true,
                        //     template      : $scope.commentTemplate,
                        //     type          : { width: wndSize.width >= 650 ? 600 : wndSize.width * 0.95, height: "auto" },
                        //     activeContent : {
                        //         deleteButton: { view: "button", type: "icon", icon: "trash-o", width: 24, css: "byCenter toRight cPointer noShadow fs85All", height: 22, padding: 0,
                        //             click: function() {
                        //                 $scope.delComment(this.getNode().parentNode.parentNode.getAttribute("webix_f_id"));
                        //             }
                        //         }
                        //     }
                        // },

                        { id: "commentsList" + $scope.Name, view: "template", css: "commentsList selectable",
                            template: "&nbsp;", borderless: true, autoheight: true },

                        { height: 10}
                    ]}, on: {
                        onFocus: function () {
                            $scope.setNoBlur(true);
                        }
                    }},

                    { height: 10},

                    { id: "uploadedFiles" + $scope.Name, view: "list", css: "hiddenScrollMenu", scroll: "x", layout: "x", type: {height: 30}, borderless: true, hidden: true,
                        template: function (obj) {
                            return mcService.insertIco('fa-file-o', 'fs90') + obj.id;
                        },
                        on: {
                            onItemClick: function(id) {
                                $scope.setNoBlur(false);
                                $scope.insertFileToEdit(id);
                            }
                        }
                    },
                ]},

                { view: "resizer", borderless: true},

                { minHeight: 32, maxHeight: 200, height: 34, cols: [
                    { view: "button", css: "webixBlue noShadow", type: "icon", icon: "paperclip", width: 29, //"156":"Вставить",
                        on: { onItemClick: function () {
                            var btn = this.getNode();

                            $scope.setNoBlur(false);

                            // if ($scope.commentText.config.height !== 150){
                            //     $scope.focusCommentText();
                            // }

                            setTimeout(function () {
                                $scope.clipPopupWindow.show(btn, {pos: 'top', x: 0, y: 2});
                            }, 100);
                        }}
                    },

                    { id: "commentText" + $scope.Name, view: "textarea", value: "", height: 34, placeholder: mcLang(40), css: "hiddenScrollMenu", // "40" :"Введите комментавий здесь",
                        on: {
                            onItemClick: function () {
                                // $scope.commentText.define("height", 150);
                                // $scope.commentText.resize();
                            },
                            onFocus: $scope.focusCommentText,
                            onBlur : $scope.blurCommText
                        }
                    },
                    { view: "button", value: mcLang(183), css: "webixBlue noShadow", type: "icon", icon: "send", width: 32, tooltip: mcLang(183) + " | CTRL + ENTER", // "183":"Отправить",
                        on: {
                            onItemClick: function () {
                                $scope.addComment();
                                $scope.isCommentSendNow = false;
                            }
                        }
                    }
                ]},


                { rows: [
                    { cols: [
                        { view: "label", label: mcLang(67) + ":", width: labelWidth}, // "67" :"Приоритет",
                        { view: "label", label: "<span class='circle priorityBG_" + $scope.currentTask.Priority + "'></span>" + " " + mcService.getPriority($scope.currentTask.Priority)}
                    ]},

                    { cols: [
                        { view: "label", label: mcLang(55) + ":", width: labelWidth}, // "55" :"Крайний срок",
                        { view: "label", label: deadlineText}
                    ]},

                    { height: 54, cols: [
                        { view: "label", label: mcLang(19) + ":", width: labelWidth}, // "19" :"Теги",
                        { id: "viewTags" + $scope.Name, view: "dataview", css: "noBG noBorderAll tagCloud", autoheight: true, borderless: true, data: $scope.fillTagsData(),
                            type: {
                                height  : "auto",
                                width   : "auto",
                                template: "<div class='brdRound6 menuTagBlob byCenter' style='padding: 0 10px;'>#value#</div>"
                            }
                        }
                    ]}
                ]},

                { hidden: !$scope.currentTask.DisplayNameOwner, cols: [
                    { view: "label", label: mcLang(16), width: labelWidth},     // "16" :"Кто создал: %s",
                    { view: "label", label: $scope.currentTask.DisplayNameOwner + " [ " + timeCreate + " ]"}
                ]},

                { cols: [
                    { view: "label", label: mcLang(57) + ":", width: labelWidth},     // "57" :"Исполнитель",

                    { id: "taskPerformerField" + $scope.Name, view: "label", label: $scope.currentTask.DisplayNamePerformer || mcLang(71)}, // "57" :"Исполнитель", "71" :"нету",
                    { view: "richselect", value: $scope.currentTask.status, label: mcLang(70) + ":", labelWidth: 70, css: "byRightLabel", options: $scope.stagesList || [], // "70" :"Этап",
                        hidden: !(mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanTaskMoveBetweenStages, $scope.projectRights)),
                        on    : {
                            onChange: $scope.changeStage
                        }
                    }
                ]}
            ]}
        ]},
        on : {
            onShow    : $scope.registerHotKeys,
            onDestruct: $scope.removeHotKey
        }
    });

    $scope.taskWrapper          = $$("taskWrapper" + $scope.Name);
    $scope.taskNameView         = $$("taskNameView" + $scope.Name);
    $scope.viewTags             = $$("viewTags" + $scope.Name);
    $scope.taskPerformerField   = $$("taskPerformerField" + $scope.Name);
    $scope.commentsList         = $$("commentsList" + $scope.Name);
    $scope.commentText          = $$("commentText" + $scope.Name);
    $scope.commentLabel         = $$("commentLabel");
    $scope.uploadedFiles        = $$("uploadedFiles" + $scope.Name);

    mcService.disableContextMenu(view, ["selectable", "taskLink", "_link"]);

    if (!$scope.clipPopupWindow){
        $scope.clipPopupWindow = webix.ui({
            view : "context",
            id   : "clipPopup" + $scope.Name,
            head : false,
            css  : "noUpperCase noShadow",
            padding: 0,

            body : { width: 200, rows: [
                { view: "button", type: "icon", icon: "file-image-o", css: "icoButton",
                    label: mcService.Lang(158), click: $scope.popupTaskImage }, // "158":"Картинка",
                { view: "button", type: "icon", icon: "files-o", css: "icoButton",
                    label: mcService.Lang(157), click: $scope.popupTaskFile }   // "157":"Файл",
            ]},
            on: {
                onShow: function () {
                    $scope.setNoBlur(false);
                },
                onHide: function () {
                    $scope.setNoBlur(true);
                }
            }
        });
    }

    $scope.taskWrapper.getNode().firstChild.style.overflow = "initial";

    view.show();

    $scope.taskNameView.getNode().onclick = $scope.openElement;

    $scope.viewTags.getParentView().config.height = $scope.viewTags.getNode().firstChild.offsetHeight;
    $scope.viewTags.getParentView().resize();

    $scope.initDU();

    return view;
}

function taskController($scope, $rootScope){
    "use strict";

    $scope.Name = mcConst.dataModels.Task;
    $scope.currentTask = null;

    $scope.isCommentSendNow = false;

    $scope.projectRights = "";

    var canBlur = true;
    var view    = null;
    var IDStage = null;
    var PRESET_VIEW  = "PRESET_VIEW";
    var PRESET_VIEW_COMM = "PRESET_VIEW_COMM";
    var duManagerTask = null;
    var cursorPos = null;

    // === Comments controller ===============

    var _comments = new function () {
        function _add(comment) {
            var thisYear   = comment.dtPosted.split('.')[2] == (new Date()).getFullYear();
            var rColor     = $rootScope.colorPalette[comment.UINOwner % $rootScope.colorPalette.length];
            var twoLetters = mcService.getTwoLettersName(comment.DisplayNameOwner);

            var avatar     = mcService.myReplaceFormated(
                '<span class="circleAvatarSmall" style="background-color: ##{rColor};" title="#{name}">#{twoLetters}</span>',
                {
                    rColor: rColor,
                    twoLetters: twoLetters,
                    name: comment.DisplayNameOwner
                }
            );

            var text       = mcService.parseAttachment(
                mcService.ReplaceURLs((comment.CommentText || "").replace(/\n/g, "<br>"), {
                    callback: parseImagesAndLinks
                }),
                comment.Files,
                mcConst.prefix.file,
                function nextItem (txt, files) {
                    return mcService.parseAttachment(txt, files, mcConst.prefix.image, null, duManagerTask, genLink, true);
                },
                duManagerTask,
                genLink
            );

            var res        = mcService.myReplaceFormated(
                "<div class='#{hlComm} borderTop framePaddingР5' id='#{id}' jData='#{jData}'>#{delBtn}" +
                    "<div>#{avatar}<span class='#{mColor}'><b>#{name}</b></span>" + // href='#{link}' _link finger
                    "<span class='taskCommTime'>#{time}</span></div>" +
                    "<div class='marginTop5'>#{text}</div>" +
                "</div>",
                {
                    id    : "__comment_id_" + comment.IDComment,
                    // jData : JSON.stringify(comment),
                    jData : JSON.stringify({
                        IDComment  : comment.IDComment,
                        CommentText: comment.CommentText
                    }),
                    hlComm: $scope.highlightID == comment.IDComment ? "highlightComment" : "",
                    delBtn: (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanTaskRemove, $scope.projectRights)) && !$scope.taskClosed
                        ? mcService.insertIco("fa-trash-o", "toRight cPointer noShadow fs85All", "onclick='window._userActions_.delComment.apply(null, [" + comment.IDComment + "])'")
                        : "",
                    mColor: 'blue',
                    avatar: avatar,
                    name  : comment.DisplayNameOwner,
                    time  : mcService.utcTime(comment.dtPosted, false, thisYear ? "dd MM, hh:nn" : "dd MM, yyyy hh:nn"),
                    text  : text
                }
            );

            var templ = $scope.commentsList.getNode().firstChild;

            $scope.commentsList.setHTML(res + templ.innerHTML);
        }
        function _getNode(id){
            return document.getElementById("__comment_id_" + id);
        }
        function _get(id) {
            // return $scope.commentsList.getItem(id); // dataview

            var item = _getNode(id);
            var res  = null;

            if (item){
                res = mcService.StringToObj(item.getAttribute("jData"));
            }

            return res;
        }
        function _del(id) {
            // $scope.commentsList.remove(id); // dataview

            var item = _getNode(id);

            if (item) {
                $scope.commentsList.getNode().firstChild.removeChild(item);

                $scope.commentsList.setHTML($scope.commentsList.getNode().firstChild.innerHTML);
            }

            $scope.commentsList.refresh();
        }
        function _offset() {
            // return $scope.commentsList.getNode().offsetTop; // dataview

            return $scope.commentsList.getNode().firstChild.offsetTop;
        }
        function _clear() {
            // $scope.commentsList.clearAll(); // dataview

            $scope.commentsList.getNode().firstChild.innerHTML = "";
        }
        function _fill(data) {
            data.forEach(function (comm) {
                if (comm.Files) {
                    Object.keys(comm.Files).forEach(function (id) {
                        try {
                            comm.Files[id].width  = comm.Files[id].width  || $rootScope.thumbsSize.x;
                            comm.Files[id].height = comm.Files[id].height || $rootScope.thumbsSize.y;
                        } catch (e) {}
                    });
                }

                _add(comm);
            });

            // setTimeout(function () {
            //     $scope.commentsList.refresh();
            // }, 100);
        }

        this.add   = _add;
        this.get   = _get;
        this.del   = _del;
        this.offset= _offset;
        this.clear = _clear;
        this.fill  = _fill;
    };

    // =======================================

    window._userActions_.delComment = $scope.delComment = function (id) {
        var idComment = _comments.get(id).IDComment;
        var text      = _comments.get(id).CommentText;

        webix.confirm({
            type  : "confirm-warning",
            text  : mcService.Lang(53) + "<br><span style='word-break: break-all'>" + text + "</span>", // "53" :"Удалить комментарий?",
            ok    : mcService.Lang(32), // "32" :"да",
            cancel: mcService.Lang(33), // "33" :"нет",
            callback:function(yes){
                if (yes) {
                    $rootScope.SendCMDToServer([
                        mcConst._CMD_.cs_kanban_delete_comment,
                        mcConst.SessionID,

                        $scope.currentTask.ID,
                        idComment,

                        function () {
                            $scope.cList.splice(mcService.findItemInArrayOfObj($scope.cList, idComment, "IDComment"), 1);

                            _comments.del(id);

                            $rootScope.$broadcast(window._messages_.KanbanBody.commentsTaskCount, [$scope.currentTask.id, $scope.cList.length]);
                        }
                    ]);
                }
            }
        });
    };

    // =======================================

    function close() {
        if (view){
            duManagerTask = null;

            view.hide();
            view.destructor();

            if ($scope.currentTask && $scope.stagesList) {
                var idx = mcService.findItemInArrayOfObj($scope.stagesList, $scope.currentTask.status, "ID");

                if (idx !== -1){
                    $scope.currentTask.Type = $scope.stagesList[idx].Type;

                    $rootScope.$broadcast("closeViewTask", [$scope.currentTask]);
                }
            }

            $scope.currentTask = null;

            window.history.replaceState({}, document.title, '#project=' + $rootScope.currentProject);
        }
    }

    function parseImagesAndLinks(text, link){
        var info = text.split('/');
        //   0    1    2     3      4       5       6
        // http:/  /sdfsdf/files/kanban/dfgdfgdfg/file.ext
        var type = info[3];
        var hash = info[5];
        var name = info[6];

        if (type === mcConst.pathAliases.AliasFiles){
            if (mcService.isImage(name)){
                text = "<img class='s200x200' src='" + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + hash + '/' + name + "' alt=''>";
            } else {
                text = mcService.insertIco("fa-file-o", "fs90") + decodeURI(name);
            }
        } else
        if (mcService.isArray(text)){
            text = text.join("/");
        }

        return link ? mcService.replaceKanbanLink(link, text) : text;
    }

    function genLink(file, name) {
        file.imgSrc  = $rootScope.localPath + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + file.Hash + '-' + $rootScope.thumbsSize.xy + '.jpg';
        file.lnkHref = $rootScope.localPath + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + file.Hash + "/" + encodeURI(name);
    }

    function addFileToUploadList(chatType, ID, info, Type) { // info = Hash + CRLF + FileName + CRLF + lastModifiedDate + CRLF + fileSize
        var fileInfo = mcService.StringToObj(info);//.split(mcConst.CRLF);

        if (!$scope.uploadedFiles.isVisible()) {
            $scope.uploadedFiles.show();
        }

        if (!$scope.uploadedFiles.getItem(fileInfo.FileName)){
            var item = {
                Hash  : fileInfo.Hash,
                id    : fileInfo.FileName,
                Size  : fileInfo.Size
            };

            if (Type === mcConst._CMD_.msgType.IMAGE) {
                item.width  = fileInfo.Resolution && fileInfo.Resolution !== "-" ? fileInfo.Resolution.split("x")[0] : $rootScope.thumbsSize.x;
                item.height = fileInfo.Resolution && fileInfo.Resolution !== "-" ? fileInfo.Resolution.split("x")[1] : $rootScope.thumbsSize.y;
            }

            $scope.uploadedFiles.add(item);
            $scope.uploadedFiles.show();
        }

        $scope.insertFileToEdit(fileInfo.FileName, Type === mcConst._CMD_.msgType.IMAGE);
    }

    // ======================================

    $scope.editThisTask = function(){
        if (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanTaskEdit, $scope.projectRights)){
            var task = $scope.currentTask;

            close();

            $rootScope.$broadcast('show' + mcConst.dataModels.TaskEdit, [task]);
        }
    };

    $scope.popupTaskImage = function(){
        this.getTopParentView().hide();

        $scope.uploadFile(mcConst._CMD_.msgType.IMAGE, false, false);
    };

    $scope.popupTaskFile = function(){
        this.getTopParentView().hide();

        $scope.uploadFile(mcConst._CMD_.msgType.FILE, false, false);
    };

    $scope.insertFileToEdit = function(fileName, image){
        var fileAlias = (image || mcService.isImage(fileName) ? mcConst.prefix.image : mcConst.prefix.file) + fileName + "}";
        var edit      = $scope.commentText;
        var node      = edit.getInputNode();

        if (cursorPos) {
            mcService.setCaretPosition(node, cursorPos);
        }

        mcService.insertAtCursor(node, fileAlias);

        edit.config.value = node.value;

        // setTimeout(function () {
        //     $scope.setNoBlur(true);
            
            // edit.focus();
        // }, 50);
    };

    $scope.setNoBlur = function (_canBlur){
        canBlur = _canBlur;
    };

    $scope.fillTagsData = function() {
        var tags = [];

        if ($scope.currentTask.Tags){
            var data = $scope.currentTask.Tags.split(',');

            if (data.length) {
                data.forEach(function (tag, idx) {
                    tags.push({
                        id: idx, value: tag
                    })
                });
            }
        }

        return tags;
    };

    $scope.openElement = function (e) {
        if (mcService.openKanbanLinkIfCan(e, $rootScope)){
            close();
        }
    };

    $scope.changeStage = function (stageID) {
        $scope.currentTask.status = stageID;
        
        $rootScope.$broadcast(window._messages_.KanbanBody.onTaskEvent, [mcConst.kanbanActions.changed, $rootScope.currentProject, $scope.currentTask.ID, {status: stageID, needChange: true}]);
    };

    $scope.registerHotKeys = function(){
        $rootScope.hotKeyDispatcher.addPreset(PRESET_VIEW, [{
            key : mcConst.keyCodes.esc,
            func: function () {
                if (!webix.UIManager.hasFocus($scope.taskTags)) {
                    close();
                } else {
                    webix.UIManager.setFocus(view);
                }
            }
        }], document, mcConst.lockPrevHotKeys);
    };

    $scope.removeHotKey = function(){
        $rootScope.hotKeyDispatcher.removePreset(PRESET_VIEW);
    };

    $scope.blurCommText = function () {
        if (canBlur) {
            $scope.blurCommTimer = setTimeout(function () {
                if (canBlur && $scope.commentText && $scope.commentText.isVisible()){
                    if (!$scope.isCommentSendNow && $scope.currentTask) {
                        // $scope.commentText.define("height", 33);
                        // $scope.commentText.resize();

                        $rootScope.hotKeyDispatcher.removePreset(PRESET_VIEW_COMM);
                    } else {
                        $scope.isCommentSendNow = false;
                    }

                    cursorPos = mcService.getCaretPosition($scope.commentText.getInputNode());
                }

                $scope.blurCommTimer = null;
            }, 150);
        } else
        if ($scope.commentText && $scope.commentText.isVisible()){
            cursorPos = mcService.getCaretPosition($scope.commentText.getInputNode());
        }
    };

    $scope.focusCommentText = function(){
        // $scope.commentText.define("height", 150);
        // $scope.commentText.resize();

        $rootScope.hotKeyDispatcher.removePreset(PRESET_VIEW_COMM);
        $rootScope.hotKeyDispatcher.addPreset(PRESET_VIEW_COMM, [{
            key : mcConst.keyCodes.enter,
            ctrlKey: true,
            func: function () {
                $scope.isCommentSendNow = false;
                $scope.addComment();
            }
        }], $scope.commentText.getInputNode());
    };

    $scope.close = close;

    $scope.addComment = function(){
        clearTimeout($scope.blurCommTimer);
        
        $scope.blurCommTimer = null;

        var text  = mcService.trim($scope.commentText.getValue());
        var files = {};

        $scope.uploadedFiles.serialize().forEach(function (item) {
            files[item.id] = {
                Hash: item.Hash,
                Size: item.Size
            }
            if (item.hasOwnProperty("width") && item.hasOwnProperty("height")){
                files[item.id].width = item.width;
                files[item.id].height= item.heigh;
            }
        });

        if (text !== ""){
            $scope.commentText.setValue("");

            $rootScope.SendCMDToServer([
                mcConst._CMD_.cs_kanban_add_comment,
                mcConst.SessionID,

                $scope.currentTask.ID,
                0,    //   "CommentType" : 0,    тип комментария, число
                text,
                JSON.stringify(files),

                function (comment) {
                    comment.Files = mcService.StringToObj(comment.Files);

                    $scope.cList.push(comment);

                    _comments.add(comment);

                    $scope.taskWrapper.resizeChildren();
                    $scope.taskWrapper.getNode().scroll({top: _comments.offset() - 50, left: 0, behavior: 'smooth'});

                    if (!$scope.commentLabel.isVisible()) {
                        $scope.commentLabel.show();
                    }

                    $rootScope.$broadcast(window._messages_.KanbanBody.commentsTaskCount, [$scope.currentTask.id, $scope.cList.length, $scope.currentTask.ID]);

                    $scope.commentText.getInputNode().value = ""; // webix bug, не чистит поле ввода
                    $scope.commentText.focus();
                }
            ]);
        } else {
            webix.alert(mcLang(50)); // "50" :"Комментарий не может быть пустым!",
        }
    };
    
    $scope.getTaskComments = function(taskId, show_comments, commentID){
        $rootScope.SendCMDToServer([
            // mcConst.lockInterface,
            mcConst._CMD_.cs_kanban_get_comments,
            mcConst.SessionID,
            taskId,

            function (list) {
                /*{
                 "IDComment"        : 987,                   // уникальный идентификатор комментария, число
                 "UINOwner"         : 6,                     // идентификатор создателя задачи
                 "CommentType"      : 0,                     // тип комментария, число
                 "CommentText"      : "Bla-bla-bla",         // текст комментария
                 "dtPosted"         : "17.05.2013.23.20.17", // дата и время создания комментария
                 "DisplayNameOwner" : "Alexey",              // отображаемое имя создателя задачи
                 "SexOwner"         : 1,                     // пол создателя задачи
                 "AvatarOwner"      : 87                     // аватар создателя задачи
                 "Files"            : {                      // (8.1+) список файлов, набор вложенных объектов, может быть пустым
                     "file_name": {                          // название файла
                         Hash: "df5df54gdf5g4df5g4df4gd5f64gdsf564g6df54g", // контрольная сумма файла
                         Size: "654765858"                                  // размер файла в байтах
                     },
                     ...
                 }
                },*/
                $scope.cList = list.Data;

                _comments.clear();

                if ($scope.cList.length) {
                    _comments.fill($scope.cList);

                    $scope.commentLabel.show();
                }

                if (show_comments) {
                    $scope.taskWrapper.getNode().scroll({top: _comments.offset() - 50, left:0, behavior: 'smooth'});
                }
            }
        ]);
    };

    $scope.show = function(data, show_comments, commentID){
        close();

        $scope.getTaskComments(data.ID, show_comments, commentID);

        $scope.currentTask = data;

        duManagerTask = new DownloadUploadManager($rootScope, {
            upload  : {
                enable : true,
                done   : addFileToUploadList,
            },
            images : { enable : true }
        });

        $scope.uploadFile = duManagerTask.uploadFile;
        $scope.initDU     = duManagerTask.initViews;
        $scope.dwlMngr    = duManagerTask.getViews(mcLang(24)); // "24" :"Отмена",

        $scope.taskNameText = mcService.parseAttachment(mcService.ReplaceURLs(($scope.currentTask.Name || "").replace(/\n/g, "<br>"), {
            callback: parseImagesAndLinks
        }), $scope.currentTask.Files, mcConst.prefix.file, function (txt, files) {
            return mcService.parseAttachment(txt, files, mcConst.prefix.image, null, duManagerTask, genLink);
        }, duManagerTask, genLink);

        $scope.taskDescText = mcService.parseAttachment(mcService.ReplaceURLs(($scope.currentTask.Desc || "").replace(/\n/g, "<br>"), {
            callback: parseImagesAndLinks
        }), $scope.currentTask.Files, mcConst.prefix.file, function (txt, files) {
            return mcService.parseAttachment(txt, files, mcConst.prefix.image, null, duManagerTask, genLink);
        }, duManagerTask, genLink);

        $scope.taskNameText = ($scope.taskNameText + ($scope.taskDescText ? "<div class='highlightComment noBG'></div>" + $scope.taskDescText : "")).replace(/#/g, "&nbsp;\u0023");

        // ------
        $rootScope.noChangeURI = true;

        location.hash = $scope.taskLink = "#project=" + ($scope.currentTask.ProjectID || $rootScope.currentProject) + "&task=" + $scope.currentTask.ID;

        $rootScope.noChangeURI = false;
        // ------

        view = initTask($scope);

        duManagerTask.loadAllThumbs();
    };

    // ================================================

    var _msg = window._messages_.Task = {
        onCommentEvent : 'onCommentEvent'
    };

    $scope.$on(_msg.onCommentEvent, function (e, args) {
        var Action    = args[0];
        var ProjectID = args[1];
        var TaskID    = args[2];
        var CommentID = args[3];

        if (ProjectID == $rootScope.currentProject && $scope.currentTask !== null && $scope.currentTask.ID == TaskID){
            switch (Action){
                case mcConst.kanbanActions.created:
                    $rootScope.SendCMDToServer([
                        mcConst._CMD_.cs_kanban_get_comments,
                        mcConst.SessionID,
                        TaskID,
                        function (list) {
                            $scope.cList = list.Data;

                            _comments.clear();
                            _comments.fill($scope.cList);

                            $scope.highlightID = CommentID;

                            // todo: scroll to comment on event
                            // var lastItem = $scope.highlightID
                            //     ? $scope.commentsList.getItemNode($scope.cList[mcService.findItemInArrayOfObj($scope.cList, $scope.highlightID, 'IDComment')].id)
                            //     : $scope.commentsList.getItemNode($scope.commentsList.getLastId());
                            //
                            // if (lastItem) {
                            //     $scope.commentsList.scrollTo(0, $scope.highlightID ? lastItem.offsetTop - lastItem.offsetHeight : lastItem.offsetTop);
                            // }
                        }
                    ]);
                break;

                case mcConst.kanbanActions.deleted:
                    var idx = mcService.findItemInArrayOfObj($scope.cList, CommentID, "IDComment");

                    if (idx !== -1){
                        _comments.del($scope.cList[idx].id);
                    }
                break;
            }
        }
    });

    $scope.$on('hide' + $scope.Name, function(){
        close();
    });

    $scope.$on('show' + $scope.Name, function(err, args){
        IDStage = null;
        $scope.currentTask = null;
        cursorPos = null;

        var taskData = args[0] || {};

        $rootScope.$broadcast(window._messages_.kanbanManager.getProjectName, [taskData.ProjectID || $rootScope.currentProject, function(prName) {
            $rootScope.$broadcast(window._messages_.kanbanManager.getStagesList, [taskData.ProjectID || $rootScope.currentProject, function(data) {
                $scope.stagesList = data;

                $scope.projectRights = $rootScope.$broadcast(window._messages_.Toolbar.getProjectRights, [taskData.ProjectID || $rootScope.currentProject]);

                $scope.stagesList.forEach(function (item) {
                    item.id    = item.ID;
                    item.value = item.Name;
                });

                args[0].prName = prName;

                if (!taskData.allTags || (taskData.allTags && taskData.allTags.length === 0)){
                    $rootScope.$broadcast(window._messages_.kanbanManager.getProjectTags, [taskData.ProjectID || $rootScope.currentProject, function (allTags) {
                        taskData.allTags = Object.keys(allTags.Tags || {});

                        $scope.show.apply(null, args);
                    }]);
                } else {
                    $scope.show.apply(null, args);
                }
            }]);
        }]);
    });
}

function initTaskEdit($scope){
    "use strict";

    var view         = $$($scope.Name);
    var labelWidth   = 100;
    var hideOnView   = !$scope.data.Desc;
    var deadlineFlag = !!$scope.data.dtDeadLine && ($scope.data.dtDeadLine !== mcConst.maxAvailableDate);
    var wndSize      = mcService.getWindowSize();

    if (view){
        view.destructor();
    }

    var timeCreate = "";

    if ($scope.data.dtCreated) {
        timeCreate =
            mcService.formatDate( $scope.data.dtCreated, "MMM dd") +
            ($scope.data.dtCreated.split('.')[2] !== (new Date()).getFullYear().toString() ? ", " + $scope.data.dtCreated.split('.')[2] : "") +
            // mcService.formatDate( $scope.data.dtCreated, ", hh:nn")
            mcService.utcTime( $scope.data.dtCreated, false, ", hh:nn")
        ;
    }

    var label = $scope.data.DisplayNameOwner
        ? mcService.myReplaceFormated("<div class='byCenter sizeBigger priorityBG_#{prior}'>#{taskPrev} [ " +
            "<a class='taskLink' href='#{taskLink}' target='_blank' onclick='#{copyLink}'>#{projName}<span class='f10 lh9'>#{linkIco}</span></a>]</div>",
            {
                prior   : $scope.data.Priority,
                taskPrev: mcLang(17),           // "17" :"Задание",
                projName: $scope.data.prName,
                taskLink: $scope.taskLink,
                linkIco : mcService.insertIco("fa-external-link"),
                copyLink: "window.__copyLinkToClipBoard.apply(this, arguments)"
            }
        )
        : "<div class='byCenter sizeBigger'>" + mcLang(41) + "</div>"; //"41" :"Новое задание",

    view = webix.ui({
        view     : "window",
        position : "center",
        modal    : true,
        width    : wndSize.width >= 650 ? 600 : wndSize.width * 0.95,
        height   : wndSize.height * 0.95,// $scope.canEdit || taskDesc ? 500 : "",
        move     : true,
        css      : "highlightLink",
        head     : { padding: 0, cols: [
            { view: "label", label: label },
            { view: "button", type: "icon", icon: "close", css: "closeBtn noShadow", width: 30, click: $scope.close, tooltip: mcLang(12) + " | ESC"} // "12" :"Закрыть",
        ]},

        autoheight: true,
        body      : { padding: 4, rows: [
            
            $scope.dwlMngr.progressWrapper,

            { id: "taskName" + $scope.Name, view: "textarea", placeholder: mcLang(38), value: $scope.data.Name || "", // "38" :"Название",
                on: {
                    onTimedKeyPress: $scope.dataIsChanged,
                    onFocus        : $scope.onChangePosition,
                    onItemClick    : function (){
                        $scope.onChangePosition(this);
                    },
                    onAfterRender  : $scope.updatePasteView
                }
            },

            { id: "taskResizer" + $scope.Name, view: "resizer", borderless: true, hidden: hideOnView}, //, hidden: !$scope.canEdit

            { id: "taskDescription" + $scope.Name, view: "textarea", placeholder: mcLang(39), value: $scope.data.Desc || "", hidden: hideOnView, //!$scope.canEdit, // "39" :"Описание",
                on: {
                    onTimedKeyPress: $scope.dataIsChanged,
                    onFocus        : $scope.onChangePosition,
                    onItemClick    : function (){
                        $scope.onChangePosition(this);
                    },
                    onAfterRender  : $scope.updatePasteView
                }
            },

            { id: "showDescription" + $scope.Name, view: "button", type: "icon", label: mcLang(160), icon:"file-text-o", css: "byCenter noShadow", click: $scope.showDescriptionEditor, hidden: !hideOnView}, // "160":"Добавить описание",
                
            { id: "uploadedFiles" + $scope.Name, view: "list", scroll: "x", layout: "x", type: {height: 30}, borderless: true,
                template: function (obj) {
                    return mcService.insertIco('fa-file-o', 'fs90') + obj.id;
                },
                on: {
                    onItemClick: function(id) {
                        $scope.insertFileToEdit(id);
                    }
                }
            },

            {  cols: [
                { id: "taskPriority" + $scope.Name, view: "richselect", label: mcLang(67) + ":", value: $scope.data.Priority || 2, labelWidth: labelWidth, width: 260, options: [  // "67" :"Приоритет",
                    {id: '1', value: "<span class='circle priority_LOW'      >&nbsp;</span><span>" + mcLang(61) + "</span>"}, // "61" :"низкий",
                    {id: '2', value: "<span class='circle priority_MEDIUM'   >&nbsp;</span><span>" + mcLang(62) + "</span>"}, // "62" :"средний",
                    {id: '3', value: "<span class='circle priority_HI'       >&nbsp;</span><span>" + mcLang(63) + "</span>"}, // "63" :"высоий",
                    {id: '4', value: "<span class='circle priority_IMPORTANT'>&nbsp;</span><span>" + mcLang(64) + "</span>"}, // "64" :"важный",
                    {id: '5', value: "<span class='circle priority_URGENT'   >&nbsp;</span><span>" + mcLang(65) + "</span>"}, // "65" :"срочный",
                    {id: '6', value: "<span class='circle priority_CRITICAL' >&nbsp;</span><span>" + mcLang(66) + "</span>"}  // "66" :"критический",
                ], on: { onChange: $scope.dataIsChanged } },

                { width: 5},

                { cols: [
                    { id: "enableDeadend" + $scope.Name, view: "checkbox", width: 130, labelWidth: 0, labelRight: mcLang(55), css: "finger", value: deadlineFlag, // "55" :"Крайний срок",
                        on: {
                            onChange: function (val) {
                                if (val){
                                    $$("deadendDummy" + $scope.Name).hide();

                                    $scope.deadend.show();
                                    $scope.deadend.setValue(new Date());
                                } else {
                                    $$("deadendDummy" + $scope.Name).show();

                                    $scope.deadend.hide();
                                    $scope.deadend.setValue(new Date(3000,0,1,0,0,0));
                                }

                                $scope.dataIsChanged.apply(this, arguments);
                            }
                    }},
                    { id: "deadend" + $scope.Name, view:"datepicker", timepicker: true, icons: false, format: "%M %d, %Y %H:%i", value: new Date(mcService.utcTime($scope.data.dtDeadLine || (new Date()), false, "yyyy-mm-dd hh:nn")), hidden: !deadlineFlag,
                        on: { onChange: $scope.dataIsChanged }
                    },
                        
                    // { view: "datepicker", type: "time", stringResult: true },

                    { id: "deadendDummy" + $scope.Name, hidden: deadlineFlag}
                ]}
            ]},

            { id: "taskTags" + $scope.Name, view:"multicombo", labelWidth: labelWidth, value: $scope.data.Tags || [], label: mcLang(19) + ":", // "19" :"Теги",
                keepText: false, newValues: true,
                suggest:{
                    body:{
                        autoheight: true,
                        data      : (function() {
                            var tags = [];

                            if ($scope.data.allTags){
                                var data = $scope.data.allTags;

                                if (data.length) data.forEach(function (tag) {
                                    var res = "";

                                    if (mcService.isString(tag)) {
                                        res = tag;
                                    } else {
                                        res = tag.id;
                                    }

                                    if (res && !mcConst.priority.hasOwnProperty(res.toUpperCase())) {
                                        tags.push(res);
                                    }
                                });
                            }

                            return tags;
                        })()
                    }
                },
                on: {
                    onKeyPress: function (code) {
                        if (code === mcConst.keyCodes.enter){
                            this.getInputNode().value = "";
                        } else
                        if (code === mcConst.keyCodes.space){
                            this.callEvent("onBlur");
                            this.focus();
                        } else
                        if (this.getInputNode().value.indexOf(',') !== -1){
                            this.getInputNode().value = this.getInputNode().value.replace(',', '');
                        }
                    },
                    onChange: $scope.dataIsChanged,
                    onTimedKeyPress: $scope.dataIsChanged
                }
            },

            { hidden: !$scope.data.DisplayNameOwner, cols: [
                { view: "label", label: mcLang(16) + ":", width: labelWidth}, // "16" :"Кто создал: %s",
                { view: "label", label: $scope.data.DisplayNameOwner + " [ " + timeCreate + " ]"}
            ]},

            { cols: [
                { view: "label", label: mcLang(57) + ":", width: labelWidth}, // "57" :"Исполнитель",
                { id: "removePerformer" + $scope.Name, view: "button", type: "icon", icon: "times", css: "red", click: $scope.removePerformer, hidden: $scope.data.UINPerformer == -1, width: 28, tooltip: mcLang(182)}, //  "182":"Убрать исполнителя",
                { id: "taskPerformerField" + $scope.Name, view: "label", label: $scope.data.DisplayNamePerformer || mcLang(71)}, // "57" :"Исполнитель", "71" :"нету",
                { view: "button", type: "icon", icon: "user-o", width: 200, label: mcLang(72), css: "noShadow", click: $scope.wndSelectPerformer}, // "72" :"Выбрать исполнителя",
            ]},

            { cols: [
                { id: "saveButton" + $scope.Name, view: "button", value: mcLang(23), width: 200, click: $scope.saveTask, tooltip: mcLang(23) + " | CTRL + ENTER"}, // "23" :"Сохранить",
                {},
                { view: "button", type: "icon", icon: "paperclip", label: mcLang(156), width: 200, css: "noShadow", //"156":"Вставить",
                    on: { onItemClick: function () {
                            var btn = this.getNode();

                            setTimeout(function () {
                                $scope.clipPopupWindow.show(btn, {pos: 'top', x: 0, y: 2});
                            }, 100);
                        }
                    }
                }
            ]}

        ]},
        on : {
            onShow    : $scope.registerHotKeys,
            onDestruct: $scope.removeHotKey
        }
    });

    $scope.taskName          = $$("taskName" + $scope.Name);
    $scope.taskResizer       = $$("taskResizer" + $scope.Name);
    $scope.taskDescription   = $$("taskDescription" + $scope.Name);
    $scope.showDescription   = $$("showDescription" + $scope.Name);
    $scope.uploadedFiles     = $$("uploadedFiles" + $scope.Name);
    $scope.taskPriority      = $$("taskPriority" + $scope.Name);
    $scope.deadend           = $$("deadend" + $scope.Name);
    $scope.enableDeadend     = $$("enableDeadend" + $scope.Name);
    $scope.taskTags          = $$("taskTags" + $scope.Name);
    $scope.saveButton        = $$("saveButton" + $scope.Name);
    $scope.removePerformerBtn= $$("removePerformer" + $scope.Name);
    $scope.taskPerformerField= $$("taskPerformerField" + $scope.Name);

    $scope.deadend.getPopup().getBody().define("minDate", (new Date()).getTime() - mcConst.oneDay);

    mcService.disableContextMenu(view, ["selectable", "taskLink"]);

    view.show();

    $scope.taskName.focus();

    if (!$scope.clipPopupWindow){
        $scope.clipPopupWindow = webix.ui({
            view : "context",
            id   : "clipPopup",
            head : false,
            css  : "noUpperCase noShadow",
            padding: 0,

            body : { width: 200, rows: [
                { view: "button", type: "icon", icon: "file-image-o", css: "icoButton",
                    label: mcService.Lang(158), click: $scope.popupTaskImage }, // "158":"Картинка",
                { view: "button", type: "icon", icon: "files-o", css: "icoButton",
                    label: mcService.Lang(157), click: $scope.popupTaskFile }   // "157":"Файл",
            ]}
        });
    }

    $scope.initDU();

    return view;
}

function taskEditController($scope, $rootScope){
    "use strict";

    $scope.Name = mcConst.dataModels.TaskEdit;
    $scope.data = null;

    var view    = null;
    var IDStage = null;
    var taskChanged  = false;
    var newParagraph = true;
    var PRESET_EDIT  = "PRESET_EDIT";
    var currentEdit  = {};
    var SELECTALLID  = "0";
    var duManagerEditTask = null;
    var newPerformer = -1;

    // ======================================

    function close() {
        if (view){
            if (duManagerEditTask) {
                duManagerEditTask.abortUploading();
            }

            duManagerEditTask = null;

            view.hide();
            view.destructor();

            if ($scope.data && $scope.stagesList) {
                var idx = mcService.findItemInArrayOfObj($scope.stagesList, $scope.data.status, "ID");

                if (idx !== -1){
                    $scope.data.Type = $scope.stagesList[idx].Type;

                    $rootScope.$broadcast("closeViewTask", [$scope.data]);
                }
            }

            newPerformer = -1;

            window.history.replaceState({}, document.title, '#project=' + $rootScope.currentProject);
        }
    }

    function selectPerformerWnd(performerField, initList) {
        function selectUser(id) {
            if (id != SELECTALLID){
                var form = this.getTopParentView();
                var user = form.list.getSelectedItem();

                form.hide();
                form.destructor();

                if (user){
                    performerField.define("label", user.Nick);
                    performerField.refresh();

                    $scope.removePerformerBtn.show();

                    // $scope.data.UINPerformer = user.UIN;
                    newPerformer = user.UIN;

                    if (!taskChanged){
                        taskChanged = true;

                        $scope.saveButton.enable();
                    }
                }
            }
        }

        return webix.ui({
            view    : "window",
            modal   : true,
            position: "center",
            width   : 350,
            head    : false,
            height  : 400,
            body    : { padding: 5, rows: [
                new mcService.FilterFiled({
                    listName     : "selectPerformer" + $scope.Name,
                    topSpacer    : 2,
                    filterParams : [ "Nick", "UIN" ]
                }),

                { id: "selectPerformer" + $scope.Name, view: "list", borderless: true, select: true, data: initList,
                    template: function (obj) {
                        return obj.Nick + (obj.UIN ?"<span class='toRight'>(" + parseInt(obj.UIN) + ")</span>" : "");
                    },
                    on: {
                        onAfterSelect: function (id) {
                            if (id == SELECTALLID){
                                var list = this;

                                list.remove(SELECTALLID);

                                $rootScope.SendCMDToServer([
                                    mcConst.lockInterface,
                                    mcConst._CMD_.cs_get_users_list,
                                    mcConst.SessionID,
                                    2,   // тип запроса, 1 - все онлайн-пользователи, 2 - все зарегистрированніе пользователи на сервере, 3 - пользователи согласно запрошенному списку UIN-ов, 4 - список пользователей согласно номеру пресета для оповещения
                                    965, // произвольное целое число, для удобства определения типа запроса, если таких запросов делается несколько. Будет возвращено сервером в команде-ответе
                                    "",  // список запрашиваемых UIN-ов пользователей, через запятую. Если нет - просто пустая строка
                                    -1,  // номер пресета для оповещения. Если нет - то равно -1
                                    function (data) {
                                        var arr = mcService.convertObjToArray(data, "UIN");

                                        list.clearAll();
                                        list.parse(arr.sort(function (a, b){
                                            return mcService.sortAB(a.UIN, b.UIN);
                                        }));
                                    }
                                ]);
                            } else {

                            }
                        },
                        onItemDblClick: selectUser
                    }
                },

                { cols: [
                    { view: "button", value: mcLang(13), click: selectUser}, // "13" :"Ок",

                    { view: "button", value: mcLang(12), type: "danger", click: function () { // "12" :"Закрыть",
                        this.getTopParentView().hide();
                        this.getTopParentView().destructor();
                    }, tooltip: mcLang(12) + " | ESC"}
                ]}
            ]}, on: {
                onShow: function () {
                    var self = this;

                    self.keyPreset = mcService.RandomHash(7);

                    this.list = $$("selectPerformer" + $scope.Name);

                    $rootScope.hotKeyDispatcher.addPreset(self.keyPreset, [{
                        key :  mcConst.keyCodes.esc,
                        func: function () {
                            self.hide();
                            self.destructor();
                        }
                    }], document, mcConst.lockPrevHotKeys);
                },
                onDestruct: function () {
                    $rootScope.hotKeyDispatcher.removePreset(this.keyPreset);
                }
            }
        })
    }

    function addFileToUploadList(chatType, ID, info, Type) { // info = Hash + CRLF + FileName + CRLF + lastModifiedDate + CRLF + fileSize
        var fileInfo = mcService.StringToObj(info);

        if (!$scope.uploadedFiles.isVisible()) {
            $scope.uploadedFiles.show();
        }

        if (!$scope.uploadedFiles.getItem(fileInfo[1])){
            $scope.uploadedFiles.add({
                id  : fileInfo.FileName,
                Hash: fileInfo.Hash,
                Size: fileInfo.Size
            });
        }

        $scope.insertFileToEdit(fileInfo.FileName, Type === mcConst._CMD_.msgType.IMAGE);

        if (mcService.isImage(fileInfo.FileName)) {
            $rootScope.SendCMDToServer([
                mcConst._CMD_.cs_get_image_thumbs,
                mcConst.SessionID,

                fileInfo.Hash,                //info[0] , //.Hash     , // SHA1 хэш файла
                $rootScope.thumbsSize.x     , // произвольная ширина уменьшеной копии изображения
                $rootScope.thumbsSize.y     , // произвольная высота уменьшенной копии изображения
                fileInfo.FileName,            //info[1] , // FileName , // локальное название файла
                mcConst._CMD_.whereType.KANBAN
            ]);
        }
    }

    function imagePastePreview(image, cb) {
        webix.confirm({
            width : 350,
            height: 350,
            position: "center",
            type  : "confirm-warning",
            text  : mcService.myReplaceFormated("#{title}<br><br><div style='height: 235px; vertical-align: center'><img src='#{img}' style='max-height: 230px; max-width: 330px'></div>",{
                title: mcLang(178), // "178":"Добавить изображение",
                img  : window.URL.createObjectURL(image)
            }),
            ok    : mcService.Lang(156), // "156":"Вставить",
            cancel: mcService.Lang(24),  // "24" :"Отмена",

            callback: function(yes){
                if (yes){
                    cb();
                } else {
                    $scope.taskName.focus();
                }
            }
        });
    }

    // ======================================

    $scope.onChangePosition = function(edit){
        currentEdit = {
            id : edit.config.id,
            pos: mcService.getCaretPosition(edit.getInputNode())
        };

        $scope.updatePasteView.call(edit);
    };

    $scope.insertFileToEdit = function(fileName, image){
        var node = null;
        var item = $scope.uploadedFiles.getItem(fileName);
        var fileAlias = (image || mcService.isImage(fileName) ? mcConst.prefix.image : mcConst.prefix.file) + fileName + "}";

        if (currentEdit.id && (currentEdit.id !== "taskNameTaskEdit" && currentEdit.id !== "taskDescriptionTaskEdit")){
            $scope.taskName.focus();
        }

        if (currentEdit.id){
            var edit    = $$(currentEdit.id);
            var lastPos = currentEdit.pos + fileAlias.length;

            if (edit && item){
                node = edit.getInputNode();

                if (!currentEdit.pos && (!node.value || !node.value.length)){
                    edit.setValue(fileAlias);

                    lastPos = fileAlias.length;
                } else {
                    mcService.setCaretPosition(node, currentEdit.pos);

                    lastPos = mcService.insertAtCursor(node, fileAlias, newParagraph);
                }

                edit.config.value = node.value;
                edit.focus();

                setTimeout(function () {
                    mcService.setCaretPosition(node, lastPos);

                    currentEdit.pos = lastPos;
                }, 100);

                if (!taskChanged){
                    taskChanged = true;

                    $scope.saveButton.enable();
                }
            }
        }

        $scope.dataIsChanged.apply(edit, []);
    };

    $scope.removePerformer = function(){
        $scope.removePerformerBtn.hide();
        $scope.taskPerformerField.define("label", mcLang(71)); // "71" :"нету",
        $scope.taskPerformerField.refresh();

        // $scope.data.UINPerformer = -1;
        newPerformer = -1;

        if (!taskChanged){
            taskChanged = true;

            $scope.saveButton.enable();
        }
    };

    $scope.popupTaskImage = function(){
        this.getTopParentView().hide();

        $scope.uploadFile(mcConst._CMD_.msgType.IMAGE, false, false);
    };

    $scope.popupTaskFile = function(){
        this.getTopParentView().hide();

        $scope.uploadFile(mcConst._CMD_.msgType.FILE, false, false);
    };

    $scope.openElement = function (e) {
        if (mcService.openKanbanLinkIfCan(e, $rootScope)){
            close();
        }
    };

    $scope.wndSelectPerformer = function () {
        var initList = [];
        var performerField = $scope.taskPerformerField;

        $rootScope.SendCMDToServer([
            mcConst.lockInterface,
            mcConst._CMD_.cs_kanban_get_last_performers,
            mcConst.SessionID,
            //$scope.data.ID,
            $rootScope.currentProject,
            5, // users count, last performers
            function (users) {
                initList.push({
                    UIN:  mcConst.UserInfo.UIN,
                    Nick: mcConst.UserInfo.Nick
                });

                users.forEach(function (user) {
                    if (user.UIN === -1){
                        user.Nick = user.DisplayName = mcLang(164); // "164":"Без исполнителя",
                    } else {
                        user.Nick = user.DisplayName; // "164":"Без исполнителя",
                    }

                    initList.push(user);
                });

                initList.push(
                    { id: SELECTALLID, Nick: mcLang(73)} // "73" :"Показать всех пользователей",
                );

                selectPerformerWnd(performerField, initList).show();
            }
        ]);
    };

    $scope.dataIsChanged = function () {
        if (!taskChanged){
            var oldVal = "";
            var newVal = "";
            var id     = this.config.id;

            switch (this.config.view){
                case "textarea":
                    oldVal = id == ("taskName" + $scope.Name) ? $scope.data.Name : $scope.data.Desc;
                break;

                case "richselect":
                    oldVal = $scope.data.Priority;
                break;

                case "checkbox":
                    oldVal = !!$scope.data.dtDeadLine && ($scope.data.dtDeadLine != mcConst.maxAvailableDate);
                break;

                case "datepicker":
                    oldVal = $scope.data.dtDeadLine;
                break;

                case "text":
                    oldVal = $scope.data.Tags;
                break;

                case "multicombo":
                    oldVal = $scope.data.Tags;
                break;
            }

            newVal = this.getValue();

            if (oldVal != newVal) {
                $scope.saveButton.enable();

                taskChanged = true;
            }
        }

        $scope.onChangePosition(this);
    };

    $scope.changeStage = function (stageID) {
        $scope.data.status = stageID;
        
        $rootScope.$broadcast(window._messages_.KanbanBody.onTaskEvent, [mcConst.kanbanActions.changed, $rootScope.currentProject, $scope.data.ID, {status: stageID, needChange: true}]);
    };

    $scope.saveTask = function(){
        if (taskChanged){
            var name = mcService.trim($scope.taskName.getValue());

            if (name != ""){
                var desc     = mcService.trim($scope.taskDescription.getValue());
                var priority = $scope.taskPriority.getValue();
                // var deadline = $scope.enableDeadend.getValue() ? mcService.formatDate($scope.deadend.getValue(), 'dd.mm.yyyy.hh.nn.ss') : mcConst.maxAvailableDate;
                var deadline = $scope.enableDeadend.getValue() ? mcService.utcTime($scope.deadend.getValue(), true, 'dd.mm.yyyy.hh.nn.ss') : mcConst.maxAvailableDate;
                var tags     = $scope.taskTags.getValue().replace(/\s|(_PRIORITY_\d)/g, '').replace(',,', ',');
                var files    = {};

                $scope.uploadedFiles.serialize().forEach(function (item) {
                    files[item.id] = {
                        Hash: item.Hash,
                        Size: item.Size
                    }
                });

                if ($scope.data._new === 'new') {
                    // $scope.data.UINPerformer = $scope.data.UINPerformer || mcConst.UserInfo.UIN;

                    $rootScope.SendCMDToServer([
                        mcConst.lockInterface,
                        mcConst._CMD_.cs_kanban_add_task,
                        mcConst.SessionID,

                        IDStage,                    // идентификатор этапа
                        newPerformer,   // идентификатор пользователя, который будет выполнять задачу
                        name,                       // название задачи
                        desc,                       // описание задачи
                        priority,                   // важность задачи, приоритет
                        deadline,                   // дата и время дедлайна, если есть. Если нет - 01.01.3000 года
                        0,                          // статус задачи
                        tags,
                        JSON.stringify(files),

                        function (data) {
                            $scope.data.UINPerformer = newPerformer;

                            // $rootScope.$broadcast(_messages_.KanbanBody.renderKanbanImages, data.ID);

                            close();

                            $rootScope.$broadcast(window._messages_.KanbanBody.addNewTaskToKanban, data);
                        }
                    ]);
                } else {
                    var TagsAdded   = [];
                    var TagsDeleted = [];
                    var newTags = tags.split(',');
                    var oldTags = $scope.data.Tags.split(',');
                    var del = "";

                    newTags = newTags.reduce(function (total, tag) {
                        if (tag && mcConst.priority.hasOwnProperty(tag.toUpperCase())){
                            del = tag;
                        } else {
                            total.push(tag);
                        }

                        return total;
                    }, []);

                    oldTags = oldTags.reduce(function (total, tag) {
                        if (tag && mcConst.priority.hasOwnProperty(tag.toUpperCase())){

                        } else {
                            total.push(tag);
                        }

                        return total;
                    }, []);

                    newTags.forEach(function (tag) {
                        var tagID = oldTags.indexOf(tag);

                        if (tagID === -1) {
                            TagsAdded.push(tag);
                        } else {
                            oldTags[tagID] = '';
                        }
                    });

                    oldTags.forEach(function (tag) {
                        if (tag){
                            TagsDeleted.push(tag);
                        }
                    });

                    $rootScope.SendCMDToServer([
                        mcConst.lockInterface,
                        mcConst._CMD_.cs_kanban_task_edit,
                        mcConst.SessionID,

                        $scope.data.ID,            // уникальный идентификатор задачи, число
                        newPerformer,  // (5.21+) идентификатор пользователя, который будет выполнять задачу. Можно заменить его
                        name,                      // название задачи
                        desc,                      // описание задачи
                        priority,                  // важность задачи, приоритет
                        deadline,                  // дата и время дедлайна, если есть. Если нет - 01.01.3000 года
                        0,                         // статус задачи
                        TagsAdded.join(','),
                        TagsDeleted.join(','),
                        JSON.stringify(files),

                        function () {
                            $scope.data.UINPerformer = newPerformer;

                            // TagsAdded.push(Object.keys(mcConst.priority).reduce(function (res, id) {
                            //     if (mcConst.priority[id] === parseInt(priority)){
                            //         res = id.toLowerCase();
                            //     }
                            //
                            //     return res;
                            // }, ""));

                            // TagsDeleted.push(del);

                            $rootScope.$broadcast(window._messages_.KanbanBody.modifyTaskToKanban, {
                                "ID"               : $scope.data.ID,           // идентификатор задачи, число
                                "UINPerformer"     : $scope.data.UINPerformer,
                                "DisplayNamePerformer" : $scope.taskPerformerField.config.label,
                                "Name"             : name,                     // название задачи
                                "Desc"             : desc,                     // описание задачи
                                "Priority"         : priority,                 // важность задачи, приоритет
                                "CommentsCount"    : $scope.data.CommentsCount || 0, // comments
                                "dtDeadLine"       : deadline,                 // дата и время дедлайна, если есть. Если нет - 01.01.3000 года
                                "State"            : 0,                        // статус задачи
                                "newTags"          : TagsAdded,                // новые теги
                                "removeTags"       : TagsDeleted,
                                "Tags"             : newTags.join(","), //tags,                     // список актуальных тегов
                                "Files"            : files
                            });

                            // $rootScope.$broadcast(_messages_.KanbanBody.renderKanbanImages, $scope.data.ID);

                            close();
                        }
                    ]);
                }
            } else {
                webix.alert({
                    text: mcLang(51), // "51" :"Название задания не может быть пустым!",
                    callback: function () {
                        setTimeout(function () {
                            $scope.taskName.focus();
                        }, 20);
                    }
                });
            }
        }
    };

    $scope.registerHotKeys = function(){
        $rootScope.hotKeyDispatcher.addPreset(PRESET_EDIT, [{
            key : mcConst.keyCodes.esc,
            func: function () {
                if (!webix.UIManager.hasFocus($scope.taskTags)) {
                    $scope.close();
                } else {
                    webix.UIManager.setFocus(view);
                }
            }
        }, {
            key    : mcConst.keyCodes.enter,
            ctrlKey: true,
            func   : $scope.saveTask
        }], document, mcConst.lockPrevHotKeys);
    };

    $scope.removeHotKey = function(){
        $rootScope.hotKeyDispatcher.removePreset(PRESET_EDIT);
    };

    $scope.showDescriptionEditor = function(){
        $scope.taskResizer.show();
        $scope.taskDescription.show();

        $scope.showDescription.hide();
    };

    $scope.close = function(){
        if (taskChanged) {
            webix.confirm({
                type  : "confirm-warning",
                text  : mcLang(207), // "207":"Закрыть без сохранения?",
                ok    : mcService.Lang(12), // "12" :"Закрыть",
                cancel: mcService.Capitalize(mcService.Lang(33)), // "33" :"Отмена",
                callback:function(yes){
                    if (yes){
                        close();
                    }
                }
            });
        } else {
            close();
        }
    };

    $scope.updatePasteView = function(){
        duManagerEditTask.removePasteView();
        duManagerEditTask.onPasteItemFromClipboard(this.getInputNode());
    };

    $scope.show = function(data){
        close();
        
        $scope.data = data;

        if ($scope.data._new) {
            newPerformer = $scope.data.UINPerformer = -1;
        } else {
            newPerformer = $scope.data.UINPerformer;
        }

        duManagerEditTask = new DownloadUploadManager($rootScope, {
            upload  : {
                enable          : true,
                done            : addFileToUploadList,
            },
            images  : {
                enable: true
            }
        });

        $scope.uploadFile = duManagerEditTask.uploadFile;
        $scope.initDU     = duManagerEditTask.initViews;
        $scope.dwlMngr    = duManagerEditTask.getViews(mcLang(24)); // "24" :"Отмена",

        if (!$scope.data._new) {
            $rootScope.noChangeURI = true;

            location.hash = $scope.taskLink = "#project=" + ($scope.data.ProjectID || $rootScope.currentProject) + "&task=" + $scope.data.ID;

            $rootScope.noChangeURI = false;
        }

        view = initTaskEdit($scope);

        taskChanged = false;

        currentEdit = {
            id : $scope.taskName.config.id,
            pos: 0
        };

        if (!mcService.isObjectEmpty($scope.data.Files)){
            $scope.uploadedFiles.parse(mcService.convertObjToArray($scope.data.Files, "id"));
            $scope.uploadedFiles.show();
        } else {
            $scope.uploadedFiles.hide();
        }

        if ($scope.data.DisplayNameOwner){
            $scope.saveButton.disable();
        } else {
            $scope.saveButton.enable();
        }

        duManagerEditTask.setPastePreviewWindow(imagePastePreview);

        setTimeout(function () {
            $scope.taskName.focus();
        }, 100);
    };

    // ================================================

    $scope.$on('hide' + $scope.Name, function(){
        close();
    });

    $scope.$on('show' + $scope.Name, function(err, args){
        IDStage = null;
        $scope.data = null;

        if (args === 'new' || args[0] === 'new'){
            $rootScope.$broadcast(window._messages_.kanbanManager.getCurrentStagesList, [function (stages) {
                if (mcService.isArray(args)){
                    IDStage = args[1];
                } else {
                    IDStage = stages[0];
                }
            }]);

            $rootScope.$broadcast(window._messages_.kanbanManager.getProjectTags, [$rootScope.currentProject, function (allTags) {
                $scope.show({_new : "new", allTags: Object.keys(allTags.Tags || {})}, true);
            }]);
        } else {
            var taskData = args[0] || {};

            $rootScope.$broadcast(window._messages_.kanbanManager.getProjectName, [taskData.ProjectID || $rootScope.currentProject, function(prName) {
                $rootScope.$broadcast(window._messages_.kanbanManager.getStagesList, [taskData.ProjectID || $rootScope.currentProject, function(data) {
                    $scope.stagesList = data;

                    $scope.stagesList.forEach(function (item) {
                        item.id    = item.ID;
                        item.value = item.Name;
                    });

                    args[0].prName = prName;

                    if (!taskData.allTags || (taskData.allTags && taskData.allTags.length === 0)){
                        $rootScope.$broadcast(window._messages_.kanbanManager.getProjectTags, [taskData.ProjectID || $rootScope.currentProject, function (allTags) {
                            taskData.allTags = Object.keys(allTags.Tags || {});

                            $scope.show.apply(null, args);
                        }]);
                    } else {
                        $scope.show.apply(null, args);
                    }
                }]);
            }]);
        }
    });
}

function initMenu($scope, currentMenu){
    "use strict";

    var view = $$($scope.Name);

    if (!view){
        var filter = new mcService.FilterFiled({
            listName     : "projectList" + $scope.Name,
            topSpacer    : 4,
            searchText   : mcLang(119), // "119":"Поиск по проектам",
            rows         : {
                css: "projectSearch"
            },
            filterParams : [ "value" ],
            noCloseBtn   : true,
            onClear      : $scope.onProjectFilterClear
        });

        $$('wrap' + $scope.Name).addView({
            id        : $scope.Name,
            view      : "toolbar",
            css       : "glassDarkBG",
            borderless: true,
            rows      : [
                { view: "scrollview", css: "noBG hiddenScrollMenu", borderless: true, body: { rows: [

                    // ================== KANAAN ===========================

                    { id: "menuFor" + mcConst.dataModels.KanbanBody, hidden: !(currentMenu === mcConst.dataModels.KanbanBody), rows: [
                        { id: "btnAddTaskButton" + $scope.Name, view: "button", type: "icon", icon: "plus", label: mcLang(15), css: "shadow1px byCenter greenBGgButton",
                            tooltip: mcLang(15) + " | INS", click: $scope.addNewTask, hidden: true}, //"15" :"Добавить задание",

                        { height: 6},

                        { rows: [
                            // { css: "glassDarkBG shadow1px lh22", cols: [
                            //     { view: "label", label: mcLang(168), align: "center"}, // "168":"Проект",
                            // ]},

                            { id: "addProjectBTN" + $scope.Name, view: "button", type: "icon", icon: "file", label: mcLang(22), hidden: true,
                                tooltip: mcLang(79), css: "shadow1px byCenter", click: $scope.addNewProject}, //"22" :"Добавить проект",

                            { id: "btnEditProject" + $scope.Name, view: "button", type: "icon", icon: "pencil", label: mcLang(205), hidden: true,
                                tooltip: mcLang(79), css: "shadow1px byCenter", click: $scope.editProject}, //"205":"Редактировать проект",

                            { id: "btnCloseProject" + $scope.Name, view: "button", type: "icon", icon: "lock", label: mcLang(58), hidden: true,
                                tooltip: mcLang(58), css: "byCenter shadow1px", click: $scope.menuCloseProject}, // "58" :"Закрыть проект",

                            { id: "btnResumeProject" + $scope.Name, view: "button", type: "icon", icon: "unlock", label: mcLang(59), hidden: true,
                                tooltip: mcLang(59), css: "byCenter shadow1px", click: $scope.menuResumeProject}, // "59" :"Возобновить проект",

                            { id: "btnDeleteProject" + $scope.Name, view: "button", type: "icon", icon: "trash", label: mcLang(123), hidden: $scope.canDelProject(),
                                tooltip: mcLang(123), css: "byCenter", click: $scope.menuDeleteProject}, // "123":"Удалить проект",

                            { id: "btnHistoryProject" + $scope.Name, view: "button", type: "icon", icon: "clock-o", label: mcLang(189), hidden: true,
                                tooltip: mcLang(189), css: "byCenter", click: $scope.historyProject}, // "189":"История проекта",

                            { id: "btnFixedCardSize" + $scope.Name, view: "checkbox", value: $scope.CardSize, css: "whiteAll finger shadow1px", labelWidth : 0,
                                labelRight: mcService.Lang(208), // "208":"Фиксированная высота карточек",
                                on: { onChange: $scope.changeCardSize }
                            },

                        ]},

                            // { id: "dateFilter" + $scope.Name, view:"calendar", multiselect: "touch", css: "glassBG",
                        //     on : {
                        //         onChange: $scope.dateFilerSelect
                        //     }
                        // }
                    ]},

                    // ================== DASHBOARD ========================

                    { id: "menuFilters" + $scope.Name, hidden: true, rows: [
                        { cols: [
                            { width: 2 },
                            { id: "selectMyTasks" + $scope.Name, view: "segmented", type: "icon", css: "byCenter noBorder noRound", value: "1",
                                options: [
                                    { id: "1", value: mcLang(154)}, // "154":"Сделать",
                                    { id: "0", value: mcLang(165)}  // "165":"Проверить",
                                ],
                                on: { onChange: $scope.filterMyTasks}
                            }
                        ]},

                        { view: "label", label: mcLang(179), align: "center", css: "glassDarkBG shadow1px lh22"}, // "179":"Отображение списка",
                        { id: "selectListView" + $scope.Name, view: "segmented", type: "icon", css: "byCenter noBorder noRound", value: $scope.dashboardView,
                            options: [
                                { id: "1", value: mcLang(181)}, // "181":"Карточки",
                                { id: "0", value: mcLang(180)}  // "180":"Таблица",
                            ],
                            on: { onChange: $scope.setListView}
                        },

                        { view: "label", label: mcLang(175), align: "center", css: "glassDarkBG shadow1px lh22"}, // "175":"Этапы проектов",

                        { id: "menuStageSwitcher", height: 30, cols: [

                            { id: "selectStages" + $scope.Name, view: "segmented", type: "icon", css: "byCenter noBorder noRound", value: "1",
                                options: [
                                    { id: "1", value: "<div title='" + mcLang(148) + "'>" + mcService.insertIco("fa-bookmark"      ) + "</div>"}, // "148":"Стандартный этап",
                                    { id: "2", value: "<div title='" + mcLang(149) + "'>" + mcService.insertIco("fa-check-square-o") + "</div>"}, // "149":"Этап с выполненными заданиями",
                                    { id: "3", value: "<div title='" + mcLang(150) + "'>" + mcService.insertIco("fa-archive"       ) + "</div>"}  // "150":"Этап с архивными заданиями",
                                ],
                                on: { onChange: $scope.filterStages}
                            },

/*
                            { id: "btnStageToggle" + mcConst.stagesTypes.typical, view: "toggle", type: "icon", icon: "bookmark", css: "byCenter", value: 0, hidden: true,
                                tooltip: mcLang(148), // "148":"Стандартный этап",
                                click: function () {
                                    $scope.refreshDashboard(!mcService.convertIntToBool(this.getValue()), 'typical');

                                    setTimeout(function () {
                                        $$("btnStageToggle" + mcConst.stagesTypes.typical).blur();
                                    }, 150);
                                }},
                            { id: "btnStageToggle" + mcConst.stagesTypes.done, view: "toggle", type: "icon", icon: "check-square-o", css: "byCenter", value: 0, hidden: true,
                                tooltip: mcLang(149), // "149":"Этап с выполненными заданиями",
                                click: function () {
                                    $scope.refreshDashboard(!mcService.convertIntToBool(this.getValue()), 'done');

                                    setTimeout(function () {
                                        $$("btnStageToggle" + mcConst.stagesTypes.done).blur();
                                    }, 150);
                                }},
                            { id: "btnStageToggle" + mcConst.stagesTypes.archive, view: "toggle", type: "icon", icon: "archive", css: "byCenter", value: 0, hidden: true,
                                tooltip: mcLang(150), // "150":"Этап с архивными заданиями",
                                click: function () {
                                    $scope.refreshDashboard(!mcService.convertIntToBool(this.getValue()), 'archive');

                                    setTimeout(function () {
                                        $$("btnStageToggle" + mcConst.stagesTypes.archive).blur();
                                    }, 150);
                                }}
*/
                        ]},

                        { view: "resizer", css: "noBG", hidden: true},

                        { hidden: true, rows: [
                            { view: "label", label: mcLang(120), align: "center", css: "glassDarkBG shadow1px lh22"}, // "120":"Исполнители",

                            { view: "list", select: true, multiselect: "touch", css: "noBG projectsListMenu", borderless: true}
                        ]}
                    ]},

                    { id: "tagsWrapper" + $scope.Name, rows: [
                        { css: "glassDarkBG shadow1px lh22", cols: [
                            { view: "label", label: mcLang(19), align: "center"}, // "19" :"Теги",

                            { id: "menuClearTags", view: "button", type: "icon", label: mcLang(117), css: "byCenter", icon: "times", hidden: true, // "117":"Очистить",
                                click: $scope.clearTagsFilter
                            }
                        ]},

                        { id: "tagsCloud", view: "dataview", css: "noBG noBorderAll tagCloud noBorderSelection", autoheight: true, borderless: true,
                            select     : true,
                            multiselect: false,
                            type       : {
                                height: "auto",
                                width: "auto",
                                template: function (obj) {
                                    return mcService.myReplaceFormated(
                                        "<div class='brdRound6 menuTagBlob byCenter' style='padding-left: 10px;'>#{name}<span class='brdRound6 toRight tagBadge'>#{value}</span></div>",
                                        {
                                            name : obj.id,
                                            value: obj.value
                                        }
                                    );
                                }
                            },
                            on: {
                                onSelectChange: $scope.filterKanbanByTags
                            }
                        },
                    ]}
                ]}},

                { view: "button", type: "icon", icon: "sign-out", label: mcLang(42), css: "closeSession shadow1px", click: $scope.logout} // "42" :"Выход",
            ]
        });

        view = $$($scope.Name);

        $scope.dateFilter         = $$("dateFilter" + $scope.Name);
        // $scope.ownerDisplayName   = $$("ownerDisplayName" + $scope.Name);
        // $scope.nameProject        = $$("nameProject" + $scope.Name);
        // $scope.projectList        = $$("projectList" + $scope.Name);
        // $scope.descriptionProject = $$("descriptionProject" + $scope.Name);
        // $scope.createdProject     = $$("createdProject" + $scope.Name);
        // $scope.deadLineProject    = $$("deadLineProject" + $scope.Name);
        // $scope.closedProject      = $$("closedProject" + $scope.Name);
        // $scope.closedProjectWrap  = $$("closedProjectWrap" + $scope.Name);
        $scope.selectListView     = $$("selectListView" + $scope.Name);
        $scope.selectStages       = $$("selectStages" + $scope.Name);

        $scope.tagsWrapper        = $$("tagsWrapper" + $scope.Name);
        $scope.tagsCloud          = $$("tagsCloud");
        $scope.selectMyTasks      = $$("selectMyTasks" + $scope.Name);

        $scope.menuStageSwitcher  = $$("menuStageSwitcher");
        $scope.menuClearTags      = $$("menuClearTags");
        $scope.menuFilters        = $$("menuFilters" + $scope.Name);
        $scope.menuForDashboard   = $$("menuFor" + mcConst.dataModels.Dashboard);
        $scope.menuForKanbanBody  = $$("menuFor" + mcConst.dataModels.KanbanBody);

        $scope.btnShowFilters     = $$("btnShowFilters" + $scope.Name);
        $scope.btnEditProject     = $$("btnEditProject" + $scope.Name);
        $scope.btnHistoryProject  = $$("btnHistoryProject" + $scope.Name);

        $scope.btnAddTaskButton   = $$("btnAddTaskButton" + $scope.Name);
        $scope.btnCloseProject    = $$("btnCloseProject" + $scope.Name);
        $scope.btnResumeProject   = $$("btnResumeProject" + $scope.Name);
        $scope.btnDeleteProject   = $$("btnDeleteProject" + $scope.Name);
        $scope.addProjectBTN      = $$("addProjectBTN" + $scope.Name);

        $scope.filterFiled = filter;

        mcService.disableContextMenu(view);
    }

    return view;
}
function menuController($scope, $rootScope){
    "use strict";

    $scope.Name = mcConst.dataModels.Menu;

    var view        = null;
    // var canChange   = true;
    var doFilter    = true;
    var currentMenu = mcConst.dataModels.KanbanBody;
    var projectInfo = {};
    var bashBoardListView = 1;
    var backupTagsPriority = {};

    backupTagsPriority[mcConst.dataModels.KanbanBody] = {};
    backupTagsPriority[mcConst.dataModels.Dashboard] = {};

    $rootScope.currentProject  = null;
    $rootScope.removerUserInfo = {};

    // ======= Error Actions ==========

    // function regError() {
    //     $rootScope.ErrorActions[17] = function(params){ // "17"  : "Пользователь с таким UIN (%s) не зарегистрирован на сервере",
    //         if (mcConst.LoggedIn){
    //             var uin = params[0];
    //
    //             if ($rootScope.removerUserInfo.hasOwnProperty(uin)){
    //                 $scope.ownerDisplayName.define("template", mcLang(155, uin)); // "155":"Пользователь UIN-%s удален",
    //                 $scope.ownerDisplayName.refresh();
    //             } else {
    //                 $rootScope.SendCMDToServer([
    //                     mcConst._CMD_.cs_get_removed_user_info,
    //                     mcConst.SessionID,
    //
    //                     uin,
    //
    //                     function (data) {
    //                         // "UIN"         : 89, // уникальный идентификатор пользователя MyChat
    //                         // "Nick"        : "john",
    //                         // "DisplayName" : "John Connor",
    //                         // "Email"       : "john@cyberdyne.com",
    //                         // "Sex"         : 0
    //
    //                         $rootScope.removerUserInfo[data.UIN] = data;
    //
    //                         $rootScope.ErrorActions[17]([data.UIN]);
    //                     }
    //                 ]);
    //             }
    //         }
    //     };
    // }

    // ==================================

    $scope.currentProject  = null;
    $scope.showMenuFilters = true;

    function fillTags(data) {
        var projectID = data && data.ProjectID ? data.ProjectID : projectInfo.ID;

        $scope.clearTagsFilter();

        if (data && data.Tags) {
            $scope.data = data.Tags;
        }
        
        $scope.tagsCloud.clearAll();

        if (mcService.isObject($scope.data)){
            var tags = [];

            Object.keys($scope.data).forEach(function (tag) {
                tags.push({
                    id   : tag,
                    value: $scope.data[tag]
                })
            });

            var updateTagsList = backupTagsPriority[currentMenu][projectInfo.ID];

            if (updateTagsList){
                Object.keys(updateTagsList).forEach(function (tag) {
                    tags.push({
                        id   : mcLang(60 + parseInt(tag)),
                        value: updateTagsList[tag]
                    });
                });
            }

            $rootScope.$broadcast(window._messages_.Toolbar.setProjectTags, [projectID, tags]);
        }

        $scope.tagsCloud.parse(tags);
        $scope.tagsCloud.sort(function (b, a) {
            return mcService.sortAB(a.value, b.value);
        });
    }

    function fillProjectInfo(_currentProject) {
        if (view){
            if (_currentProject && mcService.isObject(_currentProject)){
                var link = mcService.createLink(_currentProject.ID);

                $scope.currentProject = _currentProject;

                if (_currentProject.ID == projectInfo.ID){
                    projectInfo = mcService.Marge(projectInfo, _currentProject);
                } else {
                    projectInfo = _currentProject;
                }

                // $scope.nameProject       .define("template", _currentProject.Name + " " + mcService.insertIco("fa-external-link", "cPointer fs90", "title='Link: " + link + "' link='" + link + "' onclick='window.__copyLinkToClipBoard.apply(this, arguments)'"));
                // $scope.descriptionProject.define("template", _currentProject.Description);
                // $scope.createdProject    .define("template", mcService.formatDate(_currentProject.dtCreated, "MMM d, yyyy hh:nn"));
                // $scope.deadLineProject   .define('template', _currentProject.dtDeadLine === mcConst.maxAvailableDate ? mcLang(69) : mcService.formatDate(_currentProject.dtDeadLine, "MMM d, yyyy hh:nn")); // "69" :"не установлен",

                if (_currentProject.dtClosed !== mcConst.maxAvailableDate){
                    // $scope.closedProject.define('template', mcService.formatDate(_currentProject.dtClosed, "MMM d, yyyy hh:nn"));
                    // $scope.closedProject.refresh();

                    // $scope.closedProject     .show();
                    // $scope.closedProjectWrap .show();
                    $scope.btnResumeProject[mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanProjectClose, $scope.currentProject.RightsSet) ? "show" : "hide"]();

                    $scope.btnCloseProject   .hide();
                    $scope.btnAddTaskButton  .hide();
                    $scope.btnEditProject    .hide();
                    $scope.addProjectBTN     .hide();
                } else {
                    // $scope.closedProject     .hide();
                    // $scope.closedProjectWrap .hide();
                    $scope.btnResumeProject  .hide();

                    $scope.btnCloseProject   [mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanProjectClose, $scope.currentProject.RightsSet) ? "show" : "hide"]();
                    $scope.btnAddTaskButton  [!$rootScope.noStages && (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanAddTask, $scope.currentProject.RightsSet)) ? "show" : "hide"]();
                    $scope.addProjectBTN     [mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanProjectAdd, $scope.currentProject.RightsSet) ? "show" : "hide"]();
                    $scope.btnEditProject    [mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) || mcService.checkRights(mcConst._CMD_.RS.QKanbanProjectEdit, $scope.currentProject.RightsSet) ? "show" : "hide"]();
                }

                // $scope.nameProject        .refresh();
                // $scope.deadLineProject    .refresh();
                // $scope.createdProject     .refresh();
                // $scope.descriptionProject .refresh();

                // $rootScope.SendCMDToServer([
                //     mcConst._CMD_.cs_get_private_info,
                //     mcConst.SessionID,
                //
                //     projectInfo.UINOwner,
                //     "DISPLAYNAME",
                //
                //     function (data) {
                //         $scope.ownerDisplayName.define("template", data.DISPLAYNAME + " (UIN: " + projectInfo.UINOwner + ")");
                //         $scope.ownerDisplayName.refresh();
                //     }
                // ]);

                $rootScope.$broadcast(_messages_.kanbanManager.getProjectTags, [$rootScope.currentProject, fillTags]);
            } else {
                projectInfo = {};

                // $scope.closedProject     .hide();
                // $scope.closedProjectWrap .hide();
                $scope.btnAddTaskButton  .hide();
                $scope.btnCloseProject   .hide();
                $scope.btnEditProject    .hide();
                $scope.btnResumeProject  .hide();
                $scope.addProjectBTN     .hide();
            }
        }

        $scope.canDelProject();
    }

    // ======================================

    $scope.changeCardSize = function (val) {
        $scope.CardSize = !!val;

        document.documentElement.style.setProperty('--card-height', $scope.CardSize ? '108px' : 'auto');

        $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [$scope.CardSize, "CardSize"]);
    };

    $scope.canDelProject = function(){
        var hide = true;

        if ($rootScope.currentProject && projectInfo && projectInfo.dtClosed !== mcConst.maxAvailableDate && mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess)){
            hide = false;
        }

        if ($scope.btnDeleteProject) {
            $scope.btnDeleteProject[hide ? "hide" : "show"]();
        }

        return hide;
    };

    $scope.addNewProject = function(){
        $rootScope.$broadcast('show' + mcConst.dataModels.EditProject, []);
    };

    $scope.editProject = function(){
        $rootScope.$broadcast("show" + mcConst.dataModels.EditProject, [projectInfo]);
    };

    $scope.historyProject = function(){
        
    };

    $scope.menuCloseProject = function(){
        $rootScope.$broadcast(window._messages_.Toolbar.closeProject);
    };

    $scope.menuResumeProject = function(){
        $rootScope.$broadcast(window._messages_.Toolbar.resumeProject);
    };

    $scope.menuDeleteProject = function(){
        $rootScope.$broadcast(window._messages_.Toolbar.deleteProject);
    };

    $scope.refreshDashboard = function (value, stageName) {
        $scope.clearTagsFilter();

        $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [value, "StageTypes." + stageName]);
        $rootScope.$broadcast('reload' + mcConst.dataModels.Dashboard);
        $rootScope.$broadcast(window._messages_.kanbanManager.getTagsList, [$rootScope.kanbanOptions.StageTypes, fillTags]);
    };

    $scope.showAdditionalFilters = function () {
        $scope.menuForKanbanBody.hide();
        $scope.menuFilters.show();

        $scope.showMenuFilters = true;
    };

    $scope.showKanbanBody = function () {
        $scope.tagsCloud.unselectAll();
        $scope.menuFilters.hide();
        $scope.menuForKanbanBody.show();
        $scope.menuClearTags.hide();

        $scope.showMenuFilters = false;
    };

    $scope.filterKanbanByTags = function () {
        var selected = $scope.tagsCloud.getSelectedId(true);

        doFilter = true;
        
        if (selected && selected.length) {
            $scope.menuClearTags.show();
        } else {
            $scope.menuClearTags.hide();
        }

        $rootScope.$broadcast(window._messages_.Dashboard.addKanbanFilter, ['tags', selected, 'Tags:,', mcConst.methods.AND, doFilter]);
    };

    $scope.dateFilerSelect = function(){
        // console.log($scope.dateFilter.getValue());
    };

    $scope.clearTagsFilter = function () {
        if (view && view.isVisible()){
            $scope.tagsCloud.unselectAll();
            $scope.menuClearTags.hide();
        }
    };

    $scope.addNewTask = function(){
        $rootScope.$broadcast('show' + mcConst.dataModels.TaskEdit, 'new');

        this.blur();
    };

    $scope.getData = function(){

    };

    $scope.openKanbanBody = function () {
        $rootScope.$broadcast(window._messages_.Toolbar.onOffDashboard, [false]);
    };

    $scope.logout = function () {
        webix.confirm({
            type  : "confirm-warning",
            text  : mcService.Lang(43), // "43" :"Завершить работу?",
            ok    : mcService.Lang(32), // "32" :"да",
            cancel: mcService.Lang(33), // "33" :"нет",
            callback:function(yes){
                if (yes) {
                    mcService.SaveSessionSettings(mcConst.LoginInfo.login, "", "", false);

                    $rootScope.SendCMDToServer([
                        mcConst.lockInterface,
                        mcConst._CMD_.cs_quit,
                        mcConst.SessionID
                    ]);

                    setTimeout(function(){
                        location.search = "";
                        location.reload();
                    }, 200);
                }
            }
        });
    };

    $scope.show = function(){
        $scope.showMyTasks   = $rootScope.kanbanOptions.onlyMyTasks;
        $scope.dashboardView = $rootScope.kanbanOptions.dashBoardView.toString();
        $scope.CardSize      = !!$rootScope.kanbanOptions.CardSize;
        
        view = initMenu($scope, currentMenu);

        if ($rootScope.kanbanOptions.common.openMenu){
            $$('wrap' + $scope.Name).show();
        }

        $scope.menuListHeight = Math.min(view.getNode().offsetHeight ? view.getNode().offsetHeight - 300 : $rootScope.kanbanOptions.common.menuListHeight, $rootScope.kanbanOptions.common.menuListHeight);

        // $$("btnStageToggle" + mcConst.stagesTypes.typical).setValue($rootScope.kanbanOptions.StageTypes.typical ||
        //     (!$rootScope.kanbanOptions.StageTypes.typical &&
        //      !$rootScope.kanbanOptions.StageTypes.done &&
        //      !$rootScope.kanbanOptions.StageTypes.archive
        //     )
        // );
        // $$("btnStageToggle" + mcConst.stagesTypes.done).setValue($rootScope.kanbanOptions.StageTypes.done);
        // $$("btnStageToggle" + mcConst.stagesTypes.archive).setValue($rootScope.kanbanOptions.StageTypes.archive);

        $scope.selectStages.setValue(
            (function () {
                var res = 1;

                if ($rootScope.kanbanOptions.StageTypes.typical) {
                    res = 1;
                } else
                if ($rootScope.kanbanOptions.StageTypes.done) {
                    res = 2;
                } else
                if ($rootScope.kanbanOptions.StageTypes.archive) {
                    res = 3;
                }

                return res;
            })()
        );

        // if (!$rootScope.kanbanOptions.StageTypes.typical && !$rootScope.kanbanOptions.StageTypes.done && !$rootScope.kanbanOptions.StageTypes.archive) {
        //     $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [true, "StageTypes.typical"]);
        // }

        // $scope.projectList.define("height", $scope.menuListHeight);
        //
        // if (view.getNode().offsetHeight) {
        //     $scope.projectList.define("maxHeight", view.getNode().offsetHeight - 300);
        // }

        // $scope.projectList.refresh();
    };

    $scope.filterMyTasks = function () {
        $scope.showMyTasks = $rootScope.kanbanOptions.onlyMyTasks = mcService.convertIntToBool(parseInt($scope.selectMyTasks.getValue()));

        $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [$rootScope.kanbanOptions.onlyMyTasks, "onlyMyTasks"]);
        $rootScope.$broadcast(window._messages_.Dashboard.onlyMyTasks, [$rootScope.kanbanOptions.onlyMyTasks]);
    };

    $scope.filterStages = function () {
        switch ($scope.selectStages.getValue()) {
            case "1":
                $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [false, "StageTypes." + 'done']);
                $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [false, "StageTypes." + 'archive']);

                $scope.refreshDashboard(true, 'typical');
            break;

            case "2":
                $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [false, "StageTypes." + 'typical']);
                $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [false, "StageTypes." + 'archive']);

                $scope.refreshDashboard(true, 'done');
            break;

            case "3":
                $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [false, "StageTypes." + 'typical']);
                $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [false, "StageTypes." + 'done']);

                $scope.refreshDashboard(true, 'archive');
            break;
        }
    };

    $scope.setListView = function () {
        bashBoardListView = $rootScope.kanbanOptions.dashBoardView = parseInt($scope.selectListView.getValue());

        $rootScope.$broadcast(window._messages_.kanbanManager.sendKanbanOptions, [$rootScope.kanbanOptions.dashBoardView, "dashBoardView"]);
        $rootScope.$broadcast(window._messages_.Dashboard.dashBoardView, [$rootScope.kanbanOptions.dashBoardView]);

        $scope.filterKanbanByTags();
    };

    // ================================================

    var _msg = _messages_.Menu = {
        clearFilters      : 'clearFilters',
        changeMenu        : 'changeMenu',
        reloadTagsByStage : 'reloadTagsByStage',
        onTagsEvent       : 'onTagsEvent',
        updateTags        : 'updateTags',
        updateTagsList    : 'updateTagsList',
        fillProjectInfo   : 'fillProjectInfo'
    };

    $scope.$on(_msg.clearFilters, function () {
        doFilter = false;

        if (view && view.isVisible()) {
            $scope.tagsCloud.unselectAll();
        }
    });

    $scope.$on(_msg.fillProjectInfo, function (e, args) {
        $rootScope.$broadcast(window._messages_.kanbanManager.getStagesList, [$rootScope.currentProject, function(data) {
            if (data) {
                $rootScope.noStages = !data.length;
            }
            
            fillProjectInfo.apply(null, args);
        }]);

    });

    $scope.$on(_msg.changeMenu, function (e, args) {
        var menu = args[0];

        $scope.showMenuFilters = menu === mcConst.dataModels.Dashboard;

        doFilter = true;

        if (!$scope.menuClearTags){
            $scope.show();
        }

        $scope.menuClearTags.hide();

        if (!$scope.showMenuFilters){
            $scope.menuFilters.hide();

            $scope.menuForKanbanBody.show();
            // $scope.btnBackToKanban.show();
            $scope.menuStageSwitcher.hide();
        } else {
            $scope.menuForKanbanBody.hide();
            // $scope.btnBackToKanban.hide();

            $scope.menuFilters.show();
            $scope.menuStageSwitcher.show();
        }

        switch (menu){
            case mcConst.dataModels.Dashboard:
                $rootScope.$broadcast(window._messages_.kanbanManager.getTagsList, [$rootScope.kanbanOptions.StageTypes, fillTags]);
            break;

            case mcConst.dataModels.KanbanBody:
                $rootScope.$broadcast(_messages_.kanbanManager.getProjectTags, [$rootScope.currentProject, fillTags]);
            break;
        }

        currentMenu = menu;
    });

    $scope.$on(_msg.reloadTagsByStage, function () {
        $rootScope.$broadcast(window._messages_.kanbanManager.getTagsList, [$rootScope.kanbanOptions.StageTypes, fillTags]);
    });

    $scope.$on(_msg.onTagsEvent, function (e, args) {
        var Action    = args[0];
        var ProjectID = args[1];

    });

    $scope.$on(_msg.updateTags, function (e, args) {
        var incTags = args[0];
        var decTags = args[1];

        if (incTags && incTags.length){
            incTags.forEach(function (tag) {
                var item = $scope.tagsCloud.getItem(tag);

                if (item){
                    item.value ++;

                    $scope.tagsCloud.updateItem(tag, item);
                } else {
                    $scope.tagsCloud.add({
                        id: tag,
                        value: 1
                    });
                }
            });
        }

        if (decTags && decTags.length){
            decTags.forEach(function (tag) {
                var item = $scope.tagsCloud.getItem(tag);

                if (item){
                    item.value --;

                    if (item.value) {
                        $scope.tagsCloud.updateItem(tag, item);
                    } else {
                        if (!mcConst.priority.hasOwnProperty(item.id.toUpperCase())){
                            $scope.tagsCloud.remove(tag);
                        }
                    }
                }
            });
        }

        if ($scope.tagsCloud){
            $scope.tagsCloud.sort(function (b, a) {
                return mcService.sortAB(a.value, b.value);
            });
        }
    });

    $scope.$on(_msg.updateTagsList, function (e, args) {
        var type = args[1];

        backupTagsPriority[type][projectInfo.ID || $rootScope.currentProject] = args[0];

        // if ($scope.tagsCloud){
        //     $scope.tagsCloud.sort(function (b, a) {
        //         return mcService.sortAB(a.value, b.value);
        //     });
        // }

        if (view && view.isVisible()) {
            fillTags();
        }
    });

    $scope.$on('hide' + $scope.Name, function(){
        $$('wrap' + $scope.Name).hide();
    });

    $scope.$on('show' + $scope.Name, function(e, args){
        doFilter = true;

        // regError();

        $scope.show();

        // $scope.getData();

        $$('wrap' + $scope.Name).show();

        // fillTags();
    });
}

function initEditProject($scope){
    "use strict";

    var view = $$($scope.Name);
    var labelW = 150;

    if (view){
        view.destructor();
    }

    view = webix.ui({
        // view     : "window",
        // position : "center",
        // modal    : true,
        // width    : 600,
        // height   : 600,
        // head     : { padding: 0, cols: [
        //     { view: "label", label: "<div class='byCenter sizeBigger'>" + ($scope.createProject ? mcLang(26) : mcLang(25)) + "</div>"}, // "25" :"Редактировать проект", "26" :"Новый проект",
        //     { view: "button", type: "icon", icon: "close", css: "closeBtn noShadow", width: 30, click: function () {$scope.close(true)}, tooltip: mcLang(12) + " | ESC"} // "12" :"Закрыть",
        // ]},
        // autoheight: true,
        // body     : { padding: 4, rows: [
        css : " ",
        padding: 10,
        cols: [
            { gravity: 1},

            { gravity: 10, maxWidth: 1200, css: "bgNormal", padding: 7, rows: [
                { css: "", cols: [
                    { view: "label", label: "<div class='byCenter sizeBigger'>" + mcLang(25) + "</div>"}, // "25" :"Редактировать проект",
                    { view: "button", type: "icon", icon: "close", css: "closeBtn noShadow", width: 30, click: function () {$scope.close(true)}, tooltip: mcLang(12) + " | ESC"} // "12" :"Закрыть",
                ]},

                { cols: [
                    { template: mcLang(203), autoheight: true, borderless: true, width: labelW}, // "203":"Владелец проекта",
                    { template: $scope.currentProject.DisplayName + " (UIN: " + $scope.currentProject.UINOwner + ")", autoheight: true, borderless: true},
                ]},

                { cols: [
                    { template: mcLang(38), autoheight: true, borderless: true, width: labelW}, // "38" :"Название",
                    { id: "projectName" + $scope.Name, view: "text", value: ""},
                ]},

                { cols: [
                    { template: mcLang(39), autoheight: true, borderless: true, width: labelW}, // "39" :"Описание",
                    { id: "projectDesc" + $scope.Name, view: "text", value: ""},
                ]},

                { cols: [
                    { id: "enableDeadend" + $scope.Name, view: "checkbox", width: labelW, labelWidth: 0, labelRight: mcLang(55), css: "finger", value: false, // "55" :"Крайний срок",
                        on: {
                            onChange: function (val) {
                                if (val){
                                    $scope.deadend.show();
                                    $scope.deadend.setValue(new Date());
                                } else {
                                    $scope.deadend.hide();
                                    $scope.deadend.setValue(new Date(3000,0,1,0,0,0));
                                }
                            }
                        }},
                    { id: "deadend" + $scope.Name, view:"datepicker", icons: false, format: "%M %d, %Y", value: new Date(mcService.utcTime($scope.data.dtDeadLine || (new Date()), false, "yyyy-mm-dd hh:nn")), hidden: true, width: 200},
                    {}
                ]},

                { id: "fullAccessAll" + $scope.Name, view: "checkbox", labelWidth: 0, labelRight: mcLang(192), css: "finger", value: !$scope.currentProject.AccessType, // "192":"Полный доступ всем пользователям",
                    on: { onChange: function (val) {
                        if (val){
                            $scope.groupEditWrapper.hide();

                            $scope.currentProject.AccessType = mcConst.accessType.fullAccess;

                            $scope.stagesList.hideColumn("GroupsID");
                            $scope.stagesList.config.columns[1].fillspace = true;
                        } else {
                            $scope.currentProject.AccessType = mcConst.accessType.customAccess;

                            $scope.groupEditWrapper.show();

                            var id = $scope.groupsList.getFirstId();

                            if (id){
                                $scope.groupsList.select(id);
                            }

                            $scope.stagesList.showColumn("GroupsID");
                            $scope.stagesList.config.columns[1].fillspace = false;
                            $scope.stagesList.config.columns[1].width = 120;
                        }

                        $scope.stagesList.refreshColumns();
                    }}
                },

                { height: 10},

                { id: "groupEditWrapper" + $scope.Name, hidden: !$scope.currentProject.AccessType, cols: [
                    { rows: [
                        { cols: [
                            { id: "titleGroups" + $scope.Name, template: mcLang(187), autoheight: true, borderless: true, gravity: 3}, // "187":"Группы и права",
                            // { gravity: 1},
                            { view: "button", value: mcLang(190), gravity: 2, click: $scope.addGroup}, //"190":"Добавить",
                            { view: "button", value: mcLang(127), gravity: 2, click: $scope.removeGroupRights}, //"127":"Удалить",
                        ]},

                            
                        { id: "groups" + $scope.Name, view: "datatable", editable: true, select: "row", resizeColumn: true, //height: 160,
                            columns: [
                                { id: "GroupID",    header: "ID",        width: 30},
                                { id: "GroupName",  header: mcLang(195), width: 250}, // "195":"Группа пользователей",
                                { id: "RightsName", header: mcLang(196), fillspace: true} // "196":"Набор прав доступа",
                            ],
                            data : [],
                            on   : {
                                onSelectChange : $scope.selectGroupRights
                            }
                        },
                    ]},

                    { width: 10},

                    { rows: [
                        { cols: [
                            { id: "titleUserGroup" + $scope.Name, template: mcLang(188), autoheight: true, borderless: true, gravity: 3}, // "188":"Пользователи группы",
                            // { gravity: 1},
                            // new mcService.ListButtonSet({
                            //     gravity: 4,
                            //     idBtn  : 'usersGroup' + $scope.Name,
                            //     type   : 'combo',
                            //     actions: $scope.usersOfGroup,
                            //     list   : 'usersInGroup' + $scope.Name,
                            //     selectedItem : "DisplayName",
                            //     regShowWindow: function () {},
                            //     visibleBtns: {
                            //         add: true,
                            //         del: true,
                            //         cls: false
                            //     }
                            // }),
                            //
                            { id: "addUser" + $scope.Name, view: "button", disabled: true, value: mcLang(190), gravity: 2, click: $scope.addUserToPersonalGroup}, //"190":"Добавить",
                            { id: "delUser" + $scope.Name, view: "button", disabled: true, value: mcLang(127), gravity: 2, click: $scope.delUserToPersonalGroup}, //"127":"Удалить",
                        ]},

                        { id: "usersInGroup" + $scope.Name, view: "datatable", select: "row",
                            columns: [
                                { id: "UIN",         header: "UIN",  width: 70},
                                { id: "DisplayName", header: mcLang(197), fillspace: true}, // "197":"Имя пользователя",
                            ],
                            data : [],
                            on   : {
                                // onAfterEditStart : $scope.editStage,
                                // onAfterEditStop  : $scope.endEditStage,
                            }
                        },
                    ]},
                ]},

                { height: 10},

                { cols: [
                    { template: mcLang(27), autoheight: true, borderless: true}, // "27" :"Этапы проекта:",
                    {},
                    { view: "button", value: mcLang(28), click: $scope.addStage, width: 200 }, //"28" :"Добавить этап",
                ]},

                { id: "stages" + $scope.Name, view: "datatable", editable: true, editaction: "click", drag: true    /*"order"*/,
                    tooltip: function(obj, common){
                        //obj - row object
                        //common.column - configuration of related column
                        return common.column.id === "Name" ? mcLang(29) : "";
                    },
                    columns: [
                        { id: "Move", header: "", width: 30, css: "moveMouse",},
                        { id: "Name", header: mcLang(38), width: 120, editor: "text", fillspace: !$scope.currentProject.AccessType }, // "38" :"Название", "29" :"Нажмите, чтобы редактировать",
                        { id: "GroupsID", header: mcLang(194), editor: "multiselect", fillspace: true, hidden: !$scope.currentProject.AccessType }, // "194":"Группы, имеющие доступ к этапу",
                        { id: "Type", header: mcLang(193), width: 90, template  : function (obj) { // "193":"Тип",
                            return "<span class='_project_type_" + obj.Type + "'>" +
                                "<span class='_archive_handle_ webix_icon fa-archive fa-lg' title='" + mcLang(150) + "'></span>" +     // "150":"Этап с архивными заданиями",
                                "<span class='_done_handle_ webix_icon fa-check-square-o fa-lg' title='" + mcLang(149) + "'></span>" + // "149":"Этап с выполненными заданиями",
                                "<span class='_normal_handle_ webix_icon fa-bookmark fa-lg' title='" + mcLang(148) + "'></span>" +     // "148":"Стандартный этап",
                            "</span>";
                        }},
                        { id: "Type", header: "", width: 35, template  : function (obj) { // "193":"Тип этапа",
                            return  (!obj.hasTask ? "<span class='_delete_handle_ webix_icon fa-trash-o fa-lg' title='" + mcLang(77) + "'></span>" : "<span style='display: inline-block; float: right; width: 25px' '>&nbsp;</span>"); // "77" :"Удалить этап",
                        }}
                    ],
                    data: [
                        { id: '1', Name: mcLang(45), Type: mcConst.stagesTypes.typical, Move: $scope.moveIcon}, // "45" :"Задания",
                        { id: '2', Name: mcLang(46), Type: mcConst.stagesTypes.typical, Move: $scope.moveIcon}, // "46" :"В процессе",
                        { id: '3', Name: mcLang(47), Type: mcConst.stagesTypes.typical, Move: $scope.moveIcon}, // "47" :"Проверка",
                        { id: '4', Name: mcLang(48), Type: mcConst.stagesTypes.done,    Move: $scope.moveIcon}  // "48" :"Выполнено",
                    ],
                    on: {
                        onAfterEditStart : $scope.editStage,
                        onAfterEditStop  : $scope.endEditStage,
                        onItemClick      : $scope.stageTypeOrDelete,
                        onAfterDrop      : $scope.changeOrderStage
                    }
                },

                { height: 5},

                { autoheight: true, borderless: true, css: "_info",
                    template:
                        mcService.insertIco("fa-bookmark") + " — " + mcService.Lang(148) + "<br>" +       // "148":"Стандартный этап",
                        mcService.insertIco("fa-check-square-o") + " — " + mcService.Lang(149) + "<br>" + // "149":"Этап с выполненными заданиями",
                        mcService.insertIco("fa-archive") + " — " + mcService.Lang(150)                   // "150":"Этап с архивными заданиями",
                },

                { cols: [
                    {},
                    { view: "button", value: mcLang(23), click: $scope.saveProject, width: 200} // "23" :"Сохранить",
                ]}
            ]},

            {gravity: 1}
        ],
        on : {
            onShow : function(){
                $scope.registerHotKeys();
            },
            onDestruct: function(){
                $scope.removeHotKey();
            }
        }
    });

    $scope.projectName   = $$("projectName" + $scope.Name);
    $scope.projectDesc   = $$("projectDesc" + $scope.Name);
    $scope.stagesList    = $$("stages" + $scope.Name);
    $scope.deadend       = $$("deadend" + $scope.Name);
    $scope.enableDeadend = $$("enableDeadend" + $scope.Name);
    $scope.fullAccessAll = $$("fullAccessAll" + $scope.Name);
    $scope.groupsList    = $$("groups" + $scope.Name);
    $scope.titleGroups   = $$("titleGroups" + $scope.Name);
    $scope.createGroup   = $$("createGroup" + $scope.Name);
    $scope.usersInGroup  = $$("usersInGroup" + $scope.Name);
    $scope.groupEditWrapper = $$("groupEditWrapper" + $scope.Name);
    $scope.addUser       = $$("addUser" + $scope.Name);
    $scope.delUser       = $$("delUser" + $scope.Name);

    $scope.deadend.getPopup().getBody().define("minDate", (new Date()).getTime() - mcConst.oneDay);

    mcService.disableContextMenu(view);

    view.show();

    $scope.projectName.focus();

    return view;
}

/*{
 "Name"        : Name,       // название проекта
 "dtDeadLine"  : dtDeadLine, // дата и время дедлайна, если его нет, то "01.01.3000.00.00.00"
 "State"       : State,      // статус проекта
 "Description" : Description // описание проекта
 }*/

function editProjectController($scope, $rootScope){
    "use strict";

    $scope.Name     = mcConst.dataModels.EditProject;
    $scope.moveIcon = mcService.insertIco("fa-exchange", "fa-rotate-90");

    var view     = null;
    var PERSONAL = 1;
    var COMMON   = 0;

    $scope.currentProject  = null;
    $scope.usersGroupsList = [];

    // ======================================

    function close() {
        if (view){
            view.hide();
            view.destructor();
        }

        $scope.removeHotKey();
        
        $rootScope.$broadcast('show' + mcConst.dataModels.Wrapper);
    }

    function updateProjectInfo(list){
        $rootScope.$broadcast(window._messages_.Menu.fillProjectInfo, [$scope.currentProject]);
        $rootScope.$broadcast(window._messages_.Toolbar.editProjectName, [$scope.currentProject]);
        $rootScope.$broadcast(window._messages_.KanbanBody.modifyKanbanStages, [list]);
    }

    // ======================================

    $scope.addGroup = function () {
        function add(){
            var groupID   = $$("selectGroup" + $scope.Name).getValue();
            var groupName = $$("selectGroup" + $scope.Name).getText();
            var rightID   = $$("selectRights" + $scope.Name).getValue();
            var rightName = $$("selectRights" + $scope.Name).getText();

            var list = $scope.groupsList.serialize();

            if (mcService.findItemInArrayOfObj(list, groupID, "GroupID") === -1) {
                $rootScope.SendCMDToServer([
                    mcConst.lockInterface,
                    mcConst._CMD_.cs_kanban_add_user_group_to_project,
                    mcConst.SessionID,

                    $scope.currentProject.ID,
                    groupID,
                    rightID,

                    function(data){
                        // "ID"        : 789, // идентификатор созданной записи в базе данных
                        // "ProjectID" : 17,  // идентификатор kanban-проекта
                        // "GroupID"   : 5,   // группа пользователей, общая, с доступом "kanban" либо личная, нумерация сквозная
                        // "RightsID"  : 18   // идентификатор группы прав

                        data.id         = data.ID;
                        data.GroupName  = groupName;
                        data.RightsName = rightName;
                        data.Type       = $scope.usersGroupsList[mcService.findItemInArrayOfObj($scope.usersGroupsList, data.GroupID, "id")].Type;
                        data.UINOwner   = $scope.usersGroupsList[mcService.findItemInArrayOfObj($scope.usersGroupsList, data.GroupID, "id")].UINOwner;

                        $scope.groupsList.add(data);
                        $scope.groupsList.select(data.id);
                    }
                ]);

                wnd.hide();
                wnd.destructor();
            } else {
                webix.alert({
                    type : "alert-error",
                    ok   : mcLang(12), // "12" : "Закрыть",
                    text : mcLang(204, [groupName]) // "204":"Группа \"%s\" уже есть в списке",
                });
            }
        }

        function create() {
            var name = mcService.trim($$("newGroup" + $scope.Name).getValue());

            if (name && (function (){
                return mcService.findItemInArrayOfObj(wnd.wndElements.selectGroup.getList().serialize(), name, "value", true) === -1;
            })(name)){
                $rootScope.SendCMDToServer([
                    mcConst.lockInterface,
                    mcConst._CMD_.cs_create_personal_user_group,
                    mcConst.SessionID,

                    name,
                    "",
                    "any,kanban",

                    function(data){
                        // "ID"    : 17,            // уникальный идентификатор группы, целое число
                        // "Name"  : "bla-bla-bla", // название группы пользователей
                        // "Users" : "17,4,56,789"  // список пользователей группы, UIN-ы через запятую
                        var item = {
                            id   : data.ID,
                            value: data.Name,
                            Type : PERSONAL,
                            UINOwner: mcConst.UserInfo.UIN
                        };

                        wnd.wndElements.selectGroup.getList().add(item);

                        $scope.usersGroupsList.push(item);

                        wnd.wndElements.selectGroup.setValue(item.id);

                        $$("newGroup" + $scope.Name).setValue("");

                        backToSelect();
                    }
                ]);
            } else {
                webix.alert({
                    type : "alert-error",
                    ok   : mcLang(12), // "12" : "Закрыть",
                    text : mcLang(201), // "201":"Название не может быть пустым и совпадать с существующим",
                    callback: function(){
                        setTimeout(function(){
                            $$("newGroup" + $scope.Name).focus();
                        }, 20);
                    }
                });

            }
        }

        function backToSelect() {
            $$("newGroup" + $scope.Name).setValue("");

            $$("wrapCreate" + $scope.Name).hide();
            $$("wrapSelect" + $scope.Name).show();

            isSelectGroup = true;
        }

        var isSelectGroup = true;

        var wnd = webix.ui({
            view    : "window",
            position: "center",
            modal   : true,
            width   : 500,
            height  : 300,
            head    : false,
            body    : { rows : [
                { cols: [
                    { width: 30},
                    { view: "label", label: "<div class='byCenter sizeBigger'>" + mcLang(198) + "</div>"}, // "198":"Выбор группы пользователей и прав",
                    { view: "button", type: "icon", icon: "close", css: "closeBtn noShadow", width: 30, tooltip: mcLang(12) + " | ESC", click: function () { // "12" :"Закрыть",
                        wnd.hide();
                        wnd.destructor();
                    }}
                ]},

                { id: "wrapSelect" + $scope.Name, padding: 5, rows: [
                    { id: "selectGroup" + $scope.Name,  view: "richselect", label: mcLang(195), labelWidth: 190, options: [] }, // "195":"Группа пользователей",
                    { id: "selectRights" + $scope.Name, view: "richselect", label: mcLang(196), labelWidth: 190, options: [] }, // "196":"Набор прав доступа",

                    { cols: [
                        { view: "button", value: mcLang(199), click: function () { // "199":"Создать новыю группу",
                            $$("wrapSelect" + $scope.Name).hide();
                            $$("wrapCreate" + $scope.Name).show();

                            $$("newGroup" + $scope.Name).focus();

                            isSelectGroup = false;
                        }},

                        {},
                            
                        { view: "button", value: mcLang(190), click: add } // "190":"Добавить",
                    ]}
                ]},

                { id: "wrapCreate" + $scope.Name, padding: 5, hidden: true, rows: [
                    { id: "newGroup" + $scope.Name, view: "text", label: mcLang(200), labelWidth: 190}, // "200":"Название группы",

                    { cols: [
                        { view: "button", value: mcLang(24), click: backToSelect }, // "24" :"Отмена",

                        {},

                        { view: "button", value: mcLang(190), click: create } // "191":"Создать",
                    ]}
                ]}
            ]},
            on: {
                onShow: function () {
                    var taskList = new mcService.TaskList();

                    $scope.usersGroupsList = [];

                    wnd.wndElements = {
                        selectGroup : $$("selectGroup" + $scope.Name),
                        selectRights: $$("selectRights" + $scope.Name)
                    };

                    taskList.AddTask(function(){
                        $rootScope.SendCMDToServer([
                            mcConst.lockInterface,
                            mcConst._CMD_.cs_get_personal_user_groups,
                            mcConst.SessionID,

                            function(data){
                                var list = wnd.wndElements.selectGroup.getList();
                                var groupList = mcService.transformDataObject(data, [["Name", "value"], ["ID", "id"]]);

                                groupList.forEach(function (item){
                                    item.Type = PERSONAL;
                                    item.UINOwner = mcConst.UserInfo.UIN;
                                });

                                $scope.usersGroupsList = $scope.usersGroupsList.concat(groupList);

                                list.clearAll();
                                list.parse(groupList);

                                taskList.Next();
                            }
                        ]);
                    });

                    taskList.AddTask(function(){
                        $rootScope.SendCMDToServer([
                            mcConst.lockInterface,
                            mcConst._CMD_.cs_get_public_user_groups,
                            mcConst.SessionID,

                            "kanban",

                            function(data){
                                var list = wnd.wndElements.selectGroup.getList();
                                var groupList = mcService.transformDataObject(data, [["Name", "value"], ["ID", "id"]]);

                                groupList.forEach(function (item){
                                    item.Type = COMMON;
                                });

                                $scope.usersGroupsList = $scope.usersGroupsList.concat(groupList);
                                
                                list.parse(groupList);

                                wnd.wndElements.selectGroup.setValue(list.getFirstId());

                                taskList.Next();
                            }
                        ]);
                    });

                    taskList.AddTask(function(){
                        $rootScope.SendCMDToServer([
                            mcConst.lockInterface,
                            mcConst._CMD_.cs_get_kanban_rights_group_list,
                            mcConst.SessionID,

                            function(data){
                                var list = wnd.wndElements.selectRights.getList();

                                list.clearAll();
                                list.parse(mcService.transformDataObject(data, [["Name", "value"], ["ID", "id"]]));

                                wnd.wndElements.selectRights.setValue(list.getFirstId());

                                taskList.Next();
                            }
                        ]);
                    });

                    taskList.Run();

                    $rootScope.hotKeyDispatcher.addPreset("addGroup" + $scope.Name, [{
                        key:  mcConst.keyCodes.esc,
                        func: function (){
                            if (isSelectGroup){
                                wnd.hide();
                                wnd.destructor();
                            } else {
                                backToSelect();
                            }
                        }
                    }, {
                        key: mcConst.keyCodes.enter,
                        // ctrlKey: true,
                        func: function () {
                            if (isSelectGroup){
                                add();
                            } else {
                                create();
                            }
                        }
                    }], document, mcConst.lockPrevHotKeys);
                },
                onHide: function (){
                    $rootScope.hotKeyDispatcher.removePreset("addGroup" + $scope.Name);
                }
            }
        });

        wnd.show(); 
    };

    $scope.removeGroupRights = function(){
        if ($scope.selectedGroupRights) {
            webix.confirm({
                type  : "confirm-warning",
                text  : mcService.Lang(127) + " <span style='word-break: break-all'>" + $scope.selectedGroupRights.GroupName + "</span>", // "127":"Удалить",
                ok    : mcService.Lang(32), // "32" :"да",
                cancel: mcService.Lang(33), // "33" :"нет",
                callback:function(yes){
                    if (yes) {
                        var groupID = $scope.selectedGroupRights.GroupID;
                        
                        $rootScope.SendCMDToServer([
                            mcConst._CMD_.cs_kanban_remove_user_groups_from_project,
                            mcConst.SessionID,

                            $scope.currentProject.ID,
                            $scope.selectedGroupRights.ID,

                            function () {
                                $scope.usersInGroup.clearAll();

                                $scope.groupsList.remove($scope.selectedGroupRights.ID);

                                if (!$scope.groupsList.count()){
                                    $scope.selectedGroupRights = null;

                                    $scope.addUser.disable();
                                    $scope.delUser.disable();
                                } else {
                                    $scope.groupsList.select($scope.groupsList.getFirstId());
                                }
                            }
                        ]);

                        var stageList = $scope.stagesList.serialize();

                        for (var i = 0; i < stageList.length; i++) {
                            var arr = stageList[i].GroupsID.split(",");
                            var idx = mcService.inArrayNoStrict(groupID, arr);

                            if (idx >= 0) {
                                (function(position, idList, itemID){
                                    if (stageList[position].ID){
                                        var item = $scope.stagesList.getItem(stageList[position].id);
                                        idList.splice(itemID, 1);

                                        item.GroupsID = idList.join(",");

                                        $rootScope.SendCMDToServer([
                                            mcConst._CMD_.cs_kanban_add_access_user_groups_to_stage,
                                            mcConst.SessionID,

                                            stageList[position].ID,
                                            item.GroupsID
                                        ]);

                                        $scope.stagesList.updateItem(stageList[position].id, item);
                                    }
                                })(i, arr, idx);
                            }
                        }
                    }
                }
            });
        }
    };

    $scope.endEditStage = function (state, editor, ignoreUpdate){
        if (!ignoreUpdate){
            if (state.value != state.old && editor.config.editor === "multiselect"){
                var stageID = this.getItem(editor.row).ID;

                $rootScope.SendCMDToServer([
                    mcConst._CMD_.cs_kanban_add_access_user_groups_to_stage,
                    mcConst.SessionID,

                    stageID,
                    state.value
                ]);
            } else
            if (state.value != state.old && editor.config.editor === "text") {
                var item = $scope.stagesList.getItem(editor.row);
                var name = state.value;

                $rootScope.SendCMDToServer([
                    mcConst._CMD_.cs_kanban_modify_stage,
                    mcConst.SessionID,

                    item.ID,
                    name,
                    "", // Description,
                    item.Type,
                    item.Position, // позиция нового этапа равна длинне списка, так как нумерация идет с 0
                ]);

                updateProjectInfo($scope.stagesList.serialize());
            }
        }
    };

    $scope.editStage = function(args){
        if (this.getEditor().config.editor === "multiselect"){
            var list = this.getEditor().getPopup().getList();
            var data = $scope.groupsList.serialize();
            var ids  = this.getItem(args.row).GroupsID;

            data.forEach(function (item, i) {
                data[i]= {
                    value : item.GroupName,
                    id    : item.GroupID
                }
            });

            list.clearAll();
            list.parse(data);

            if (ids) {
                list.select(ids.split(","));
            }
        }

    };

    $scope.addStage = function(){
        var name = mcLang(30); // "30" :"Введите название этапа здесь",

        $rootScope.SendCMDToServer([
            mcConst._CMD_.cs_kanban_add_stage,
            mcConst.SessionID,

            name,
            "", // Description,
            $scope.stagesList.count(), // позиция нового этапа равна длинне списка, так как нумерация идет с 0
            $scope.currentProject.ID,
            mcConst.stagesTypes.typical,

            function (info) {
                $scope.stagesList.add({
                    Move: $scope.moveIcon,
                    Name: name,
                    ID  : info.ID,
                    Type: mcConst.stagesTypes.typical
                });
            }
        ]);
    };

    $scope.changeOrderStage = function (){
        var stageList = $scope.stagesList.serialize();

        for (var i = 0; i < stageList.length; i++) {
            stageList[i].Position = i;

            (function(position){
                if (stageList[position].ID){
                    $rootScope.SendCMDToServer([
                        mcConst._CMD_.cs_kanban_modify_stage,
                        mcConst.SessionID,

                        stageList[position].ID,
                        stageList[position].Name,
                        "",
                        stageList[position].Type,
                        position
                    ]);
                }
            })(i);
        }

        updateProjectInfo(stageList);
    };

    $scope.addUserToPersonalGroup = function (){
        function _save() {
            var uin  = $$("allUserList" + $scope.Name).getValue();

            $rootScope.SendCMDToServer([
                mcConst._CMD_.cs_add_users_to_personal_user_group,
                mcConst.SessionID,

                $scope.selectedGroupRights.GroupID,
                uin,

                function (data){
                    $scope.usersInGroup.clearAll();
                    $scope.usersInGroup.parse(data.Users);

                    wnd.hide();
                    wnd.destructor();
                }
            ]);
        }

        var wnd = webix.ui({
            view    : "window",
            position: "center",
            modal   : true,
            width   : 400,
            height  : 300,
            head    : false,
            body    : { rows: [
                { cols: [
                    { width: 30},
                    { view: "label", label: "<div class='byCenter sizeBigger'>" + mcLang(202) + "</div>"}, // "202":"Список пользователей",
                    { view: "button", type: "icon", icon: "close", css: "closeBtn noShadow", width: 30, tooltip: mcLang(12) + " | ESC", click: function () { // "12" :"Закрыть",
                        wnd.hide();
                        wnd.destructor();
                    }}
                ]},

                { id: "allUserList" + $scope.Name, view: "combo"}, //

                { cols: [
                    {},
                    { view: "button", value: mcLang(190), click: _save }, // "190":"Добавить",
                    {}
                ]}
            ]},
            on: { onShow: function (){
                if (!$scope.userListData){
                    $rootScope.SendCMDToServer([
                        mcConst.lockInterface,
                        mcConst._CMD_.cs_adm_list_users,
                        mcConst.SessionID,

                        "All",
                        "DISPLAYNAME,UIN",

                        function (data){
                            $scope.userListData = mcService.transformDataObject(webix.copy(data.Users), [['DisplayName', 'value'], ['UIN', 'id']]);

                            $scope.userListData.forEach(function (t, i) {
                                t.value = t.value + " <span style='float:right'>(UIN: " + t.id + ")</span>"
                            });

                            var list = $$("allUserList" + $scope.Name);

                            list.define('options', $scope.userListData);
                            list.refresh();
                            list.focus();
                        }
                    ]);
                } else {
                    var list = $$("allUserList" + $scope.Name);

                    list.define('options', $scope.userListData);
                    list.refresh();
                    list.focus();
                }

                $rootScope.hotKeyDispatcher.addPreset("addUserToPersonalGroup" + $scope.Name, [{
                    key:  mcConst.keyCodes.esc,
                    func: function (){
                        wnd.hide();
                        wnd.destructor();
                    }
                }, {
                    key: mcConst.keyCodes.enter,
                    // ctrlKey: true,
                    func: function () {
                        _save();
                    }
                }], document, mcConst.lockPrevHotKeys);
            }, onHide: function (){
                $rootScope.hotKeyDispatcher.removePreset("addUserToPersonalGroup" + $scope.Name);
            }}
        });

        wnd.show();
    };

    $scope.delUserToPersonalGroup = function (){
        var selected = $scope.usersInGroup.getSelectedItem();
        
        if (selected) {
            webix.confirm({
                type  : "confirm-warning",
                text  : mcService.Lang(127) + " <span style='word-break: break-all'>" + selected.DisplayName + "</span>", // "127":"Удалить",
                ok    : mcService.Lang(32), // "32" :"да",
                cancel: mcService.Lang(33), // "33" :"нет",
                callback:function(yes){
                    if (yes) {
                        $rootScope.SendCMDToServer([
                            mcConst._CMD_.cs_remove_users_from_personal_user_group,
                            mcConst.SessionID,

                            $scope.selectedGroupRights.GroupID,
                            selected.UIN,

                            function (data) {
                                $scope.usersInGroup.remove(selected.id);
                            }
                        ]);
                    }
                }
            });
        }
    };

    $scope.selectGroupRights = function (){
        $scope.selectedGroupRights = $scope.groupsList.getSelectedItem();

        if ($scope.selectedGroupRights) {
            $scope.usersInGroup.clearAll();

            switch ($scope.selectedGroupRights.Type){
                case PERSONAL:
                    $rootScope.SendCMDToServer([
                        mcConst._CMD_.cs_get_personal_user_group_data,
                        mcConst.SessionID,

                        $scope.selectedGroupRights.GroupID,

                        function (data) {
                            $scope.usersInGroup.parse(data.Users);
                        }
                    ]);

                    if ($scope.selectedGroupRights.UINOwner == mcConst.UserInfo.UIN){
                        $scope.addUser.enable();
                        $scope.delUser.enable();
                    } else {
                        $scope.addUser.disable();
                        $scope.delUser.disable();
                    }
                break;

                case COMMON:
                    $rootScope.SendCMDToServer([
                        mcConst._CMD_.cs_get_common_user_group_data,
                        mcConst.SessionID,

                        $scope.selectedGroupRights.GroupID,

                        function (data) {
                            $scope.usersInGroup.parse(data.Users);

                            // $scope.groupsList.remove($scope.selectedGroupRights.ID);
                            // $scope.groupsList.select($scope.groupsList.getFirstId());
                        }
                    ]);

                    $scope.addUser.disable();
                    $scope.delUser.disable();
                break;
            }
        }
    };

    $scope.stageTypeOrDelete = function(id, e){
        var elementClass = (e.target || e.srcElement).className;
        var element = (e.target || e.srcElement);
        var list = this;
        var item = list.getItem(id);
        var sendChange = false;

        if (elementClass.indexOf("_delete_handle_") === 0){
            list.editCancel(id);
            list.remove(id);

            if (item.ID) {
                $rootScope.SendCMDToServer([
                    mcConst._CMD_.cs_kanban_delete_stage,
                    mcConst.SessionID,

                    item.ID,
                    $scope.currentProject.ID
                ]);

                var stageList = $scope.stagesList.serialize();
                
                for (var i = 0; i < stageList.length; i++) {
                    stageList[i].Position = i;

                    (function(position){
                        if (stageList[position].ID){
                            $rootScope.SendCMDToServer([
                                mcConst._CMD_.cs_kanban_modify_stage,
                                mcConst.SessionID,

                                stageList[position].ID,
                                stageList[position].Name,
                                "",
                                stageList[position].Type,
                                position
                            ]);
                        }
                    })(i);
                }

                updateProjectInfo(stageList);
            }
        } else

        if (elementClass.indexOf("_archive_handle_") === 0){
            list.editCancel(id);

            item.Type = mcConst.stagesTypes.archive;

            element.parentNode.className = "_project_type_" + item.Type;

            sendChange = true;
        } else

        if (elementClass.indexOf("_done_handle_") === 0){
            list.editCancel(id);

            item.Type = mcConst.stagesTypes.done;

            element.parentNode.className = "_project_type_" + item.Type;

            sendChange = true;
        } else

        if (elementClass.indexOf("_normal_handle_") === 0){
            list.editCancel(id);

            item.Type = mcConst.stagesTypes.typical;

            element.parentNode.className = "_project_type_" + item.Type;

            sendChange = true;
        }

        if (sendChange){
            $rootScope.SendCMDToServer([
                mcConst._CMD_.cs_kanban_modify_stage,
                mcConst.SessionID,

                item.ID,
                item.Name,
                "", // Description,
                item.Type,
                item.Position, // позиция нового этапа равна длинне списка, так как нумерация идет с 0
            ]);

            updateProjectInfo($scope.stagesList.serialize());
        }
    };

    $scope.registerHotKeys = function(){
        $rootScope.hotKeyDispatcher.addPreset("main" + $scope.Name, [{
            key:  mcConst.keyCodes.esc,
            func: function (){
                close();
            }
        }, /*{
            key: mcConst.keyCodes.enter,
            func: function () {
                if (webix.UIManager.hasFocus($scope.projectName)){
                    $scope.projectDesc.focus();
                } else {
                    $scope.saveProject();
                }
            }
        }, */{
            key: mcConst.keyCodes.enter,
            ctrlKey: true,
            func: $scope.saveProject
        }], document, mcConst.lockPrevHotKeys);
    };

    $scope.removeHotKey = function(){
        $rootScope.hotKeyDispatcher.removePreset("main" + $scope.Name);
    };

    $scope.saveProject = function(){
        var name     = mcService.trim($scope.projectName.getValue());
        var list     = $scope.stagesList.serialize();
        var desc     = $scope.projectDesc.getValue();
        var deadline = $scope.enableDeadend.getValue() ? mcService.formatDate($scope.deadend.getValue(), 'dd.mm.yyyy.hh.nn.ss') : mcConst.maxAvailableDate;

        if (name === ""){
            webix.alert({
                type : "alert-error",
                ok   : mcLang(12), // "12" : "Закрыть",
                text : mcLang(36), // "36" :"Название проекта не может быть пустым",
                callback: function(){
                    setTimeout(function(){
                        $scope.projectName.focus();
                    }, 20);
                }
            });
        } else
        if (list.length < 2) {
            webix.alert({
                type : "alert-error",
                ok   : mcLang(12), // "12" : "Закрыть",
                text : mcLang(37), // "37" :"Проект должен содержать не менее 2-х этапов",
                callback: function(){
                    setTimeout(function(){
                        $scope.projectName.focus();
                    }, 20);
                }
            });
        } else {
            var taskList = new mcService.TaskList();

            taskList.AddTask(function(){
                $rootScope.SendCMDToServer([
                    mcConst.lockInterface,
                    mcConst._CMD_.cs_kanban_edit_project,
                    mcConst.SessionID,

                    $scope.currentProject.ID,
                    name,
                    desc,
                    deadline,
                    // $scope.currentProject.State,
                    $scope.currentProject.AccessType,

                    function(){
                        $scope.currentProject.Name        = name;
                        $scope.currentProject.Description = desc;
                        $scope.currentProject.dtDeadLine  = deadline;

                        taskList.Next();
                    }
                ]);
            });

            taskList.AddTask(function(){
                $rootScope.$broadcast(mcConst.lockInterface, []);

                taskList.Next();
            });

            taskList.AddTask(function(){
                $rootScope.$broadcast(window._messages_.kanbanManager.getStagesList, [$scope.currentProject.ID, function(data) {
                    list = data;

                    taskList.Next();
                }]);
            });

            taskList.AddTask(function(){
                $rootScope.$broadcast('hide' + mcConst.lockInterface, []);

                updateProjectInfo(list);

                close();
            });

            taskList.Run();
        }
    };

    $scope.saveNewProject = function(name){
        var deadline = mcConst.maxAvailableDate;
        var desc = "";
        var list = [
            { Name: mcLang(45), Type: mcConst.stagesTypes.typical}, // "45" :"Задания",
            { Name: mcLang(46), Type: mcConst.stagesTypes.typical}, // "46" :"В процессе",
            { Name: mcLang(47), Type: mcConst.stagesTypes.typical}, // "47" :"Проверка",
            { Name: mcLang(48), Type: mcConst.stagesTypes.done}     // "48" :"Выполнено",
        ];

        if (name){
            $rootScope.SendCMDToServer([
                mcConst.lockInterface,
                mcConst._CMD_.cs_kanban_add_project,
                mcConst.SessionID,

                name,
                desc,
                deadline,
                0, // State

                function( data ){
                    $rootScope.$broadcast(mcConst.lockInterface, []);

                    $scope.currentProject = data;
                    $scope.currentProject.DisplayName = mcConst.UserInfo.Nick;
                    $scope.currentProject.AccessType = mcConst.accessType.fullAccess;

                    var taskList = new mcService.TaskList();

                    for (var i = 0; i < list.length; i++){
                        (function(ii){
                            taskList.AddTask(function(){
                                $rootScope.SendCMDToServer([
                                    mcConst._CMD_.cs_kanban_add_stage,
                                    mcConst.SessionID,
                                    list[ii].Name,     // name
                                    "",                // description
                                    ii,                // position
                                    $scope.currentProject.ID,
                                    list[ii].Type,
                                    this.next
                                ]);
                            });
                        })(i);
                    }

                    taskList.AddTask(function(){
                        $rootScope.$broadcast('hide' + mcConst.lockInterface, []);

                        $rootScope.$broadcast(window._messages_.Toolbar.addedNewProject, [$scope.currentProject]);
                        $rootScope.$broadcast(window._messages_.Toolbar.projectsMode, true);

                        $scope.show();

                        $scope.getData($scope.currentProject.ID);

                        $scope.registerHotKeys();
                    });

                    taskList.Run();
                }
            ]);
        }
    };

    $scope.getData = function(ID){
        $rootScope.$broadcast(window._messages_.kanbanManager.getStagesList, [ID, function(data) {
            var tasks = $rootScope.$broadcast(window._messages_.KanbanBody.getTasksCountByStages, [ID]);

            $scope.Stages = data;
            $scope.Stages.forEach(function (stage) {
                stage.hasTask = false;
                stage.Move    = $scope.moveIcon;
            });

            Object.keys(tasks).forEach(function (stageID) {
                var item = $scope.Stages[mcService.findItemInArrayOfObj($scope.Stages, stageID, "ID")];

                if (item) {
                    item.hasTask = true;
                }
            });

            $rootScope.SendCMDToServer([
                mcConst._CMD_.cs_get_kanban_get_projects_groups,
                mcConst.SessionID,

                $scope.currentProject.ID,

                function (info) {
                    // "ProjectID" : 17,  // идентификатор kanban-проекта
                    // "Data" : [ { // массив объектов
                    //     "ID"         : 178,     // идентификатор записи
                    //     "GroupID"    : 45,      // идентификатор группы пользователей
                    //     "RightsID"   : 165,     // идентификатор группы прав
                    //     "Type"       : 0,       // 0 - общая группа, 1 - личная
                    //     "GroupName"  : "Group", // название группы пользователей
                    //     "RightsName" : "Guests" // название группы прав
                    //     "UINOwner"   " 3        // UIN владельца
                    // } ]

                    if (info.Data) {
                        info.Data.forEach(function (group) {
                            group.id = group.ID;
                        });
                    } else {
                        info.Data = [];
                    }

                    // ===================================
                    // заполняем список групп доступа
                    $scope.stagesList   .clearAll();
                    $scope.stagesList   .parse($scope.Stages);
                    $scope.projectName  .setValue($scope.currentProject.Name);
                    $scope.projectDesc  .setValue($scope.currentProject.Description);
                    $scope.enableDeadend.setValue($scope.currentProject.dtDeadLine !== mcConst.maxAvailableDate);// ? mcLang(69) : mcService.formatDate(_$scope.currentProject.dtDeadLine, "MMM d, yyyy hh:nn"));

                    if ($scope.currentProject.dtDeadLine !== mcConst.maxAvailableDate) {
                        $scope.deadend.setValue($scope.currentProject.dtDeadLine);
                    }

                    // ===================================
                    // заполняем список этапов
                    $scope.groupsList.clearAll();
                    $scope.groupsList.parse(info.Data);

                    if ($scope.groupsList.getFirstId() && $scope.currentProject.AccessType) {
                        $scope.groupsList.select($scope.groupsList.getFirstId());
                    }
                }
            ]);

        }]);
    };

    $scope.show = function(data){
        if (!view || (view && !view.isVisible())){
            // $scope.currentProject = null;

            $scope.data = data || {};

            view = initEditProject($scope);
        }
    };

    $scope.close = close;

    $scope.showWindowCreateProject = function (){
        function _save(){
            var prName = $$("newProjectName" + $scope.Name);
            var name   = mcService.trim(prName.getValue());

            if (!name){
                webix.alert({
                    type : "alert-error",
                    ok   : mcLang(12), // "12" : "Закрыть",
                    text : mcLang(36), // "36" :"Название проекта не может быть пустым",
                    callback: function(){
                        setTimeout(function(){
                            prName.focus();
                        }, 20);
                    }
                });
            } else {
                wnd.hide();
                wnd.destructor();

                $scope.saveNewProject(name);
            }
        }

        function _close(){
            wnd.hide();
            wnd.destructor();

            $rootScope.$broadcast('show' + mcConst.dataModels.Wrapper);
        }

        var wnd = webix.ui({
            view    : "window",
            position: "center",
            modal   : true,
            width   : 400,
            height  : 300,
            head    : false,
            body    : { rows: [
                { cols: [
                    { width: 30},
                    { view: "label", label: "<div class='byCenter sizeBigger'>" + mcLang(26) + "</div>"}, // "26" :"Новый проект",
                    { view: "button", type: "icon", icon: "close", css: "closeBtn noShadow", width: 30, tooltip: mcLang(12) + " | ESC", click: _close } // "12" :"Закрыть",
                ]},

                { id: "newProjectName" + $scope.Name, view: "text", value: "", placeholder: mcLang(38)}, // "38" :"Название",

                { cols: [
                    {},
                    { view: "button", value: mcLang(22), click: function(){ // "22" :"Создать проект",
                        _save();
                    }},
                    {}
                ]}
            ]},
            on: { onShow: function (){
                $$("newProjectName" + $scope.Name).focus();

                $rootScope.hotKeyDispatcher.addPreset("showWindowCreateProject" + $scope.Name, [{
                    key:  mcConst.keyCodes.esc,
                    func: function (){
                        _close();
                    }
                }, {
                    key: mcConst.keyCodes.enter,
                    // ctrlKey: true,
                    func: function () {
                        _save();
                    }
                }], document, mcConst.lockPrevHotKeys);
            }, onHide: function (){
                $rootScope.hotKeyDispatcher.removePreset("showWindowCreateProject" + $scope.Name);
            }}
        });

        wnd.show();
    };

    // ================================================

    $scope.$on('hide' + $scope.Name, function(){
        close();
    });

    $scope.$on('show' + $scope.Name, function(e, args){
        $scope.currentProject = args[0];

        $rootScope.$broadcast('hide' + mcConst.dataModels.Wrapper);

        $scope.userListData = null;

        if ($scope.currentProject && $scope.currentProject.ID) {
            // $scope.createProject = false;

            $scope.show();

            $scope.getData($scope.currentProject.ID);

            $scope.registerHotKeys();
        } else {
            $scope.showWindowCreateProject();
            // $scope.createProject = true;
            
            // $scope.show();
        }
    });
}

function initDashboard($scope, rebuild){
    "use strict";

    var view    = $$($scope.Name);
    var nowYear = (new Date()).getFullYear();
    var wrapper = $$('wrap' + $scope.Name);

    if (rebuild && view){
        wrapper.removeView($scope.Name);
        view = null;
    }

    if (!view){
        wrapper.addView({ id: $scope.Name, css: "noBG", rows: [
            { height: 4},

            { type: "space", borderless: true, padding: 3, rows: []
                .concat($scope.viewType == 1 ? { id: "allTasks" + $scope.Name, view: "kanbanlist", css: "glassBG fixedHeightForItems hiddenScrollMenu",
                    drag     : false,
                    dashboard: true,
                    master   : "allTasks" + $scope.Name,
                    data     : $scope.taskList,
                    type     : "mcTask",
                    xCount   : (wrapper.$width / mcConst.cardWidth).toFixed(),
                    on       : {
                        onListItemDblClick: $scope.openTask,
                        // onListItemClick: $scope.openElement,
                        onListIconClick: function(iconId, itemId,ev,node,list) {
                            switch (iconId) {
                                case "iconLink":
                                    mcService.copyTextToClipboard(ev.target.href, mcLang(162)); // "162":"Ссылка скопирована",

                                    ev.preventDefault();
                                break;

                                case "iconDesc":
                                    $scope.openTask(itemId, ev, node, list);
                                break;

                                case "iconEdit":
                                    $scope.editTask(itemId, ev, node, list);
                                break;

                                case "iconComments":
                                    $scope.taskComments(itemId, ev, node, list);
                                break;

                                case "iconRemove":
                                    $scope.removeTask(itemId, ev, node, list);
                                break;

                                default:
                                    $scope.openElement.apply(null, arguments);
                            }
                        },
                        onSelectChange : $scope.afterSelectItem,
                        onAfterUnSelect: $scope.afterSelectItem
                    }
                } : [])

                .concat($scope.viewType == 0 ? { id: "allTasks" + $scope.Name, view: "datatable",
                    resizeColumn: true,
                    data    : $scope.taskList,
                    select  : true,
                    css     : "hiddenScrollMenu",
                    columns : [
                        { id:"Priority",             header: "&nbsp;&nbsp;&nbsp;&nbsp;", sort: "int", css: "byCenter left_area", adjust: "header", // "67" :"Приоритет",
                            template: function (obj) {
                                return "<span class='circle priorityBG_" + obj.Priority + "'>&nbsp;</span><span>"
                            }
                        },
                        { id:"ProjectName",          header: mcLang(168), sort: "string", css: "left_area" },   // "168":"Проект",
                        { id:"DisplayNameOwner",     header: mcLang(16),  sort: "string", css: "grid_area" },    // "16" :"Кто создал",
                        { id:"DisplayNamePerformer", header: mcLang(57),  sort: "string", css: "byCenter grid_area" }, // "57" :"Исполнитель",
                        { id:"dtCreated",            header: mcLang(166), sort: "sortableDate", css: "grid_area",     // "166":"Создано",
                            template: function (obj) {
                                // return mcService.formatDate(obj.dtCreated, "dd MM" + (obj.dtCreated.indexOf(nowYear) === -1 ? " yyyy" : ""))
                                return mcService.utcTime(obj.dtCreated, false, "dd MM" + (obj.dtCreated.indexOf(nowYear) === -1 ? " yyyy" : ""))
                            }
                        },
                        { id:"dtDeadLine",           header: mcLang(167), sort: "string", css: "grid_area",     // "167":"Сделать до",
                            template: function (obj) {
                                return obj.dtDeadLine === mcConst.maxAvailableDate ? "---" : mcService.utcTime(obj.dtDeadLine, false, "dd MM" + (obj.dtCreated.indexOf(nowYear) === -1 ? " yyyy" : ""))
                            }
                        },
                        { id:"Name",                 header: mcLang(17),  sort: "string", fillspace: true,  css: "grid_area"  }// "17" :"Задание",
                    ],
                    on: {
                        onItemDblClick: $scope.openTask
                    }
                } : [])
            },

            { height: 3}
        ]});

        view = $$($scope.Name);

        $scope.allTasks  = $$("allTasks" + $scope.Name);

        $scope.allTasks.detachEvent("onAfterSelect");

        $scope.allTasks.type.icons = [
            { id: "iconLink", icon: "link", link: true, css: "",
                hint: function (obj) {
                    return "Link: " + obj.Link;
                },
                /*template: function (obj) {
                 return obj.ID;
                 }*/
            },
            { id: "iconDesc", icon: "info", hint: mcLang(78), show: function (obj) { // "78" :"Дополнительная информация",
                return !!obj.Desc;
            }},
            { id: "iconComments", icon: "comment-o", hint: mcLang(20), template: function (obj) { // "20" :"Комментарии",
                return obj.CommentsCount || "";
            }},
            { id: "iconEdit", isGear: true, icon: "pencil", hint: mcLang(79), show: function (obj) { // "79" :"Редактировать",
                return mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess);
            }},
            { id: "iconRemove", isGear: true, icon: "trash-o", hint: mcLang(127), show: function (obj) { // "127":"Удалить",
                return mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess);
            }}
        ];

        $scope.allTasks._otherFilterOptions = {};

        $scope.allTasks.refresh();

        mcService.disableContextMenu(view);
    } else {
        $scope.allTasks.clearAll();
        $scope.allTasks.parse($scope.taskList);
    }

    view.show();

    return view;
}
function dashboardController($scope, $rootScope){
    "use strict";

    $scope.Name          = mcConst.dataModels.Dashboard;
    $scope.taskList      = [];
    $scope.projectsList  = {};
    $scope.onlyMyTask    = false;

    var view             = null;
    var PRESET_DASHBOARD = "PRESET_DASHBOARD";
    var noImagesList     = [];
    var priorityTagList  = {};

    function updateCommentsCount(id, comments, ID) {
        var idx  = mcService.findItemInArrayOfObj($scope.taskList, ID, "ID");

        if ($scope.allTasks){
            var task = $scope.allTasks.getItem(id);

            if (!task && idx !== -1){
                task = $scope.taskList[idx];
            }

            if (task) {
                task.CommentsCount = comments;

                $scope.allTasks.updateItem(task.id, task);
            }
        }
    }

    function removeTask(id) {
        var item = $scope.allTasks.getItem(id);
        var idx  = mcService.findItemInArrayOfObj($scope.taskList, item.ID, "ID");

        if (idx >= 0){
            $rootScope.SendCMDToServer([
                mcConst.lockInterface,
                mcConst._CMD_.cs_kanban_task_remove,
                mcConst.SessionID,
                item.ID,
                function(){
                    $rootScope.$broadcast(window._messages_.KanbanBody.onTaskEvent, [mcConst.kanbanActions.deleted, item.ProjectID, item.ID]);
                }
            ]);
        }
    }

    function deleteTask(id) {
        $scope.allTasks.remove(id);
    }

    function getTaskInfo(taskID, cb) {
        $rootScope.SendCMDToServer([
            mcConst._CMD_.cs_kanban_get_task_info,
            mcConst.SessionID,
            taskID,
            cb
        ]);
    }

    function addNewTask(data) {
        data.status        = data.IDStage;
        data.CommentsCount = "";

        $scope.allTasks.add(data);
        $scope.taskList.push(data);
    }

    function modifyTask(data) {
        var idx     = mcService.findItemInArrayOfObj($scope.taskList, data.ID, "ID");
        var id      = $scope.taskList[idx].id;
        var newData = mcService.Marge($scope.allTasks.getItem(id), data);

        newData.status = newData.IDStage || newData.status;
        newData.parseName = parseTaskImages(newData);

        $scope.taskList[idx] = newData;
        $scope.allTasks.updateItem(id, newData);
    }

    function genLink(file, name) {
        file.imgSrc  = $rootScope.localPath + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + file.Hash + '-' + $rootScope.thumbsSize.xy + '.jpg';
        file.lnkHref = $rootScope.localPath + "/" + mcConst.pathAliases.AliasFiles + "/kanban/" + file.Hash + "/" + encodeURI(name);
    }

    function parseTaskImages(task) {
        return "<div class='breakWord'>" + mcService.parseAttachment(mcService.ReplaceURLs((task.Name || "").replace(/\n/g, "<br>"), {
            callback: function (text, link) {
                var info = text.split('/');

                var type = info[3];
                var hash = info[5];
                var name = info[6];

                if (type === mcConst.pathAliases.AliasFiles){
                    if (mcService.isImage(name)){
                        text = mcService.myReplaceFormated("<img class='s200x200' src='/#{filePath}/kanban/#{hash}/#{name}' alt=''>",
                            {
                                filePath    : mcConst.pathAliases.AliasFiles,
                                hash        : hash,
                                name        : name
                            });
                    } else {
                        text = mcService.insertIco("fa-file-o", "fs90") + decodeURI(name);
                    }
                } else
                if (mcService.isArray(text)){
                    text = text.join("/");
                }

                return link ? mcService.replaceKanbanLink(link, text) : text;
            }
        }), task.Files, mcConst.prefix.file, function (txt, files) {
            return mcService.parseAttachment(txt, files, mcConst.prefix.image, null, $rootScope.duManager, genLink);
        }, $rootScope.duManager, genLink) + "</div>";
    }

    function onlyMyTasks(my){
        if (view && view.isVisible()){
            $scope.onlyMyTask = my;
            
            $rootScope.$broadcast(window._messages_.Dashboard.addKanbanFilter, [
                'myTasks',
                [my],
                'isMyTask',
                mcConst.methods.AND,
                true
            ]);

            if ($scope.viewType == 0) {
                if ($scope.onlyMyTask) {
                    if ($scope.allTasks.isColumnVisible("DisplayNamePerformer")) $scope.allTasks.hideColumn("DisplayNamePerformer");
                    if (!$scope.allTasks.isColumnVisible("DisplayNameOwner"))    $scope.allTasks.showColumn("DisplayNameOwner");
                } else {
                    if ($scope.allTasks.isColumnVisible("DisplayNameOwner"))      $scope.allTasks.hideColumn("DisplayNameOwner");
                    if (!$scope.allTasks.isColumnVisible("DisplayNamePerformer")) $scope.allTasks.showColumn("DisplayNamePerformer");
                }
            }
        }
    }

    function changeStage(item, newStage){
        $rootScope.SendCMDToServer([
            mcConst._CMD_.cs_kanban_task_move,
            mcConst.SessionID,

            item,
            newStage
        ]);
    }

    function fillTasks(tasks){
        priorityTagList   = {};

        priorityTagList[mcConst.priority.LOW]       = 0;
        priorityTagList[mcConst.priority.MEDIUM]    = 0;
        priorityTagList[mcConst.priority.HIGH]      = 0;
        priorityTagList[mcConst.priority.IMPORTANT] = 0;
        priorityTagList[mcConst.priority.URGENT]    = 0;
        priorityTagList[mcConst.priority.CRITICAL]  = 0;

        tasks.forEach(function (task) {
            if (!task.CommentsCount){
                task.CommentsCount = "";
            }

            if (!task.Link){
                task.Link  = mcService.createLink(task.ProjectID, task.ID);
            }

            if (task.Tags) {
                task.Tags += "," + mcLang(60 + parseInt(task.Priority)); //",_PRIORITY_" + task.Priority;
            } else {
                task.Tags = mcLang(60 + parseInt(task.Priority)); //"_PRIORITY_" + task.Priority;
            }

            priorityTagList[task.Priority] ++;

            task.parseName = parseTaskImages(task);
            task.isMyTask  = task.UINPerformer === mcConst.UserInfo.UIN;
            task.dashboard = true;
        });
    }
    
    // ======================================

    $scope.registerHotKeys = function(){
        // $rootScope.hotKeyDispatcher.addPreset(PRESET_DASHBOARD, [{
        //     key    :  mcConst.keyCodes.q,
        //     ctrlKey: true,
        //     func   : function () {
        //     }
        // }], document);
    };

    $scope.afterSelectItem = function (id) {
        var item = this.getItem(mcService.isArray(id) ? id[0] : id);

        if (item && item.Files){
            Object.keys(item.Files).forEach(function (file) {
                if (mcService.isImage(file)){
                    $rootScope.SendCMDToServer([
                        mcConst._CMD_.cs_get_image_thumbs,
                        mcConst.SessionID,

                        item.Files[file].Hash  , //info[0] , //.Hash     , // SHA1 хэш файла
                        $rootScope.thumbsSize.x, // произвольная ширина уменьшеной копии изображения
                        $rootScope.thumbsSize.y, // произвольная высота уменьшенной копии изображения
                        file,                    //info[1] , // FileName , // локальное название файла
                        mcService.getCurrentChantTypeForFiles($rootScope.GetChatType()),

                        function (data) {
                            if (!data.Exists){
                                var _imgNO  = mcService.getByCssClass('img_temp_' + data.Hash);

                                if (_imgNO && _imgNO.length) {
                                    for (var i = 0; i < _imgNO.length; i++){
                                        _imgNO[i].src = mcConst.imagesPath.noImage;

                                        _imgNO[i].parentElement.onclick = function (e) {
                                            e.preventDefault();
                                        };
                                    }
                                }
                            }
                        }
                    ]);
                }
            })
        }
    };

    $scope.openElement = function (id, e) {
        mcService.openKanbanLinkIfCan(e, $rootScope);
    };

    $scope.openTask = function (id, e, node, list) {
        $rootScope.$broadcast('show' + mcConst.dataModels.Task, [(list || $scope.allTasks).getItem(id.row || id)]);
    };

    $scope.editTask = function (id, e, node, list){
        var item = list.getItem(id);

        $rootScope.$broadcast('show' + (mcService.checkRights(mcConst._CMD_.RS.QKanbanFullAccess) ? mcConst.dataModels.TaskEdit : mcConst.dataModels.Task), [item]);
    };

    $scope.removeTask = function (id) { // , e, node, list
        var name = $scope.allTasks.getItem(id).Name;

        webix.confirm({
            type  : "confirm-warning",
            text  : mcService.Lang(44, mcService.copy(name, 0, 70) + (name.length > 70 ? "..." : "")), // "44" :"Удалить задание?",
            ok    : mcService.Lang(32), // "32" :"да",
            cancel: mcService.Lang(33), // "33" :"нет",
            callback:function(yes){
                if (yes) {
                    removeTask(id);
                }
            }
        });
    };

    $scope.taskComments = function(id, e, node, list){
        $rootScope.$broadcast('show' + mcConst.dataModels.Task, [list.getItem(id), true]);
        // $rootScope.$broadcast('show' + mcConst.dataModels.Comments, [list.getItem(id), list.getItem(id).dtClosed != mcConst.maxAvailableDate]);
    };

    $scope.getData = function(){
        $scope.taskList = [];

        $rootScope.$broadcast(window._messages_.kanbanManager.getDashboardList, [$rootScope.kanbanOptions.StageTypes, function (tasks) {
            fillTasks(tasks);

            $scope.taskList = tasks;

            $scope.onlyMyTask = $rootScope.kanbanOptions.onlyMyTasks;
            $scope.viewType   = $rootScope.kanbanOptions.dashBoardView;

            $scope.show();

            onlyMyTasks($rootScope.kanbanOptions.onlyMyTasks);

            $rootScope.$broadcast(window._messages_.Menu.updateTagsList, [priorityTagList, mcConst.dataModels.Dashboard]);
        }]);
    };

    $scope.show = function(repaint){
        view = initDashboard($scope, repaint);

        $rootScope.$broadcast(window._messages_.Toolbar.changeFilterList, ["allTasks" + mcConst.dataModels.Dashboard]);
        $rootScope.$broadcast(window._messages_.Toolbar.updateToolbarTaskCount);
    };

    // ================================================

    var _msg = _messages_.Dashboard = {
        addKanbanFilter   : 'addKanbanFilter',
        commentsTaskCount : 'commentsTaskCount',
        onTaskEvent       : 'onTaskEvent',
        onCommentEvent    : 'onCommentEvent',
        modifyTaskToKanban: 'modifyTaskToKanban',
        closeViewTask     : 'closeViewTask',
        onlyMyTasks       : 'onlyMyTasks',
        dashBoardView     : 'dashBoardView'
    };

    $scope.$on(_msg.onlyMyTasks, function (e, args) {
        onlyMyTasks.apply(null, args);
    });

    $scope.$on(_msg.dashBoardView, function (e, args) {
        $scope.viewType = $rootScope.kanbanOptions.dashBoardView;

        $scope.show(true);

        onlyMyTasks($rootScope.kanbanOptions.onlyMyTasks);
    });

    $scope.$on(_msg.closeViewTask, function (e, args) {
        var task  = args[0];
        var sType = {
            "0": "typical",
            "1": "done"   ,
            "2": "archive"
        };

        if (view && view.isVisible() && !$rootScope.kanbanOptions.StageTypes[sType[task.Type]]){
            var idx = mcService.findItemInArrayOfObj($scope.taskList, task.ID, "ID");

            if (idx !== -1){
                deleteTask($scope.taskList[idx].id);

                mcService.delArrayItem($scope.taskList, idx, 1);
            }
        }
    });

    $scope.$on(_msg.addKanbanFilter, function (e, args) {
        var name     = args[0];
        var items    = args[1];
        var fields   = args[2];
        var method   = args[3];
        var doFilter = args[4];

        if (view && view.isVisible()) {
            if (items.length) {
                $scope.allTasks._otherFilterOptions[name] = {
                    items : items,
                    fields: mcService.isString(fields) ? [fields] : fields,
                    union : method
                };
            } else
            if ($scope.allTasks._otherFilterOptions.hasOwnProperty(name)) {
                delete $scope.allTasks._otherFilterOptions[name];
            }

            if (doFilter) {
                $rootScope.$broadcast(window._messages_.Toolbar.doFilterKanban);
            }
        }
    });

    $scope.$on(_msg.commentsTaskCount, function (e, args) {
        updateCommentsCount.apply(null, args);
    });

    $scope.$on(_msg.modifyTaskToKanban, function (e, task) {
        var idx = mcService.findItemInArrayOfObj($scope.taskList, task.ID, "ID");

        if (idx !== -1){
            task.parseName = parseTaskImages(task);

            modifyTask(task);
        }/* else {
            console.warn('modifyTaskToKanban: wrong task ID!');
        }*/
    });

    $scope.$on(_msg.onTaskEvent, function (e, args) {
        if (view && view.isVisible()) {
            var Action    = args[0];
            var ProjectID = args[1];
            var TaskID    = args[2];
            var internalInfo = args[3];
            var idx       = mcService.findItemInArrayOfObj($scope.taskList, TaskID, "ID");

            switch (Action) {
                case mcConst.kanbanActions.created:
                    getTaskInfo(TaskID, function (info) {
                        if (info.UINPerformer == mcConst.UserInfo.UIN || info.UINCreator == mcConst.UserInfo.UIN) {
                            addNewTask(info);
                        }
                    });
                break;

                case mcConst.kanbanActions.changed:
                    if (idx !== -1) {
                        if (internalInfo && internalInfo.status){
                            changeStage(TaskID, internalInfo.status);
                        } else

                        getTaskInfo(TaskID, function (info) {
                            if (info.UINPerformer == mcConst.UserInfo.UIN || info.UINCreator == mcConst.UserInfo.UIN) {
                                modifyTask(info);
                            } else {
                                deleteTask($scope.taskList[idx].id);

                                mcService.delArrayItem($scope.taskList, idx, 1);
                            }
                        });
                    } else {
                        getTaskInfo(TaskID, function (info) {
                            if (info.UINPerformer == mcConst.UserInfo.UIN || info.UINCreator == mcConst.UserInfo.UIN) {
                                addNewTask(info);
                            }
                        });
                    }
                break;

                case mcConst.kanbanActions.deleted:
                    if (idx !== -1) {
                        deleteTask($scope.taskList[idx].id);

                        mcService.delArrayItem($scope.taskList, idx, 1);
                    } else {
                        console.log("Can't find task " + TaskID + " in project " + ProjectID);
                    }
                break;
            }
        }
    });

    $scope.$on(_msg.onCommentEvent, function (e, args) {
        if (view && view.isVisible()) {
            var Action    = args[0];
            var ProjectID = args[1];
            var TaskID    = args[2];
            var CommentID = args[3];
            var idx       = mcService.findItemInArrayOfObj($scope.taskList, TaskID, "ID");

            if (idx != -1){
                switch (Action){
                    case mcConst.kanbanActions.created:
                        if ($scope.taskList[idx].CommentsCount) {
                            $scope.taskList[idx].CommentsCount = parseInt($scope.taskList[idx].CommentsCount) + 1;
                        } else {
                            $scope.taskList[idx].CommentsCount = 1;
                        }

                        modifyTask($scope.taskList[idx]);
                    break;

                    case mcConst.kanbanActions.changed:

                    break;

                    case mcConst.kanbanActions.deleted:
                        if ($scope.taskList[idx].CommentsCount) {
                            $scope.taskList[idx].CommentsCount = parseInt($scope.taskList[idx].CommentsCount) - 1;
                        }

                        if (!$scope.taskList[idx].CommentsCount) {
                            $scope.taskList[idx].CommentsCount = "";
                        }

                        modifyTask($scope.taskList[idx]);
                    break;
                }
            }
        }
    });

    $scope.$on('reload' + $scope.Name, function(){
        $scope.getData();
    });

    $scope.$on('hide' + $scope.Name, function(){
        view.hide();
    });

    $scope.$on('show' + $scope.Name, function(){
        $scope.getData();
    });
}

function mainController($scope, $rootScope, mcCmdParser, /*mcCmdLoop,*/ $http, $location, mcConnect){
    "use strict";

    function lockInterface() {
        if ($scope.lockCount === 0 && lockWindow){
            lockWindow.show();

            var back = mcService.getElementByClass('webix_modal');

            mcService.getElementByClass('webix_modal')[0].className += ' noLockScreen';
        }

        $scope.lockCount ++;
    }

    function runCmd(cmd, args){
        if (mcService.isArray(args)){
            var cmdNumb = args.shift();

            if (mcCmdParser[cmd][cmdNumb]){
                mcCmdParser[cmd][cmdNumb].apply(null, args);
            } else {
                console.warn(mcService.myReplace(mcService.Lang(9), [cmd, cmdNumb])); // "9" : "%s: %s not found!"
            }
        } else {
            throw new Error(mcService.myReplace(mcService.Lang(8), [cmd])); // "8" : "%s: нет входящих аргументов"
        }
    }

    function initLockWindow(){
        if (lockWindow){
            lockWindow.destructor();
        }

        lockWindow = webix.ui({
            view     : "window",
            modal    : true,
            head     : mcService.Lang(18), // "18" : "Обработка запроса",
            position : "center",
            width    : 220,
            height   : mcConst.lockWindowHeight, // 65
            body     : {
                template : "<div align='center'><img src='" + mcConst.imagesPath.loadingImg + "'></div>"
            }
        });
    }

    function displayErrorOld(txtCode, txt, txtParam, callback){
        $scope.errorMessagesIsShowed = true;

        webix.alert({
            title: mcService.Lang(11, txtCode), // mcService.Lang(11, err.ErrNum), // "11" : "Ошибка №"
            ok   : mcService.Lang(12),          // "12" : "Закрыть",
            type : "alert-error",
            text : mcService.myReplace(txt, txtParam),
            callback: function(){
                if (callback){
                    callback();
                }

                var args = $scope.errorMessages.shift();

                if (args !== undefined){
                    setTimeout(function(){
                        displayError.apply(null, args);
                    }, 20);
                } else {
                    $scope.errorMessagesIsShowed = false;
                }
            }
        });
    }

    function displayError(txtCode, txt, txtParam, callback){
        $scope.errorMessagesIsShowed = true;

        webix.confirm({
            title  : mcService.Lang(11, txtCode), // mcService.Lang(11, err.ErrNum), // "11" : "Ошибка №" "1386":"Ошибка",
            ok     : mcLang(206),// "206":"Помощь",
            cancel : mcLang(12),  // "12" : "Закрыть",
            type   : "alert-error",
            padding: 12,
            text   : mcService.myReplace(txt, [].concat(txtParam)),
            callback: function(res) {
                if (res){
                    if (txtCode === 'client') {
                        mcService.goToHelp("/serverhelp/", true);
                    } else {
                        var code = ['0','0','0','0'];
                        var cLen = txtCode.toString().length - 1;
                        var anchor = "";

                        for (var i = 3; i >= 0; i--){
                            if (cLen < 0) {
                                break;
                            }

                            code[i] = txtCode.toString()[cLen];
                            cLen --;
                        }

                        code = code.join('');

                        if (code == "0165"){
                            anchor = "#" + txtParam[0];
                        }

                        if (txtCode == mcConst._CMD_.Errors.Disconnected) {
                            mcService.goToHelp("https://nsoft-s.com/" + (mcConst.Lang === "ru" ? "" : (mcConst.Lang === "uk" ? "ua/" : "en/")) + "mcserverhelp/index.html?mychatclienterrors" + code + ".htm" + anchor, true);
                        } else {
                            mcService.goToHelp("/serverhelp/" + (mcConst.Lang === "uk" ? "ua" : mcConst.Lang) + "/index.html?mychatclienterrors" + code + ".htm" + anchor, true);
                        }
                    }
                }

                if (callback){
                    callback();
                }

                var args = $scope.errorMessages.shift();

                if (args !== undefined){
                    setTimeout(function(){
                        displayError.apply(null, args);
                    }, 20);
                } else {
                    $scope.errorMessagesIsShowed = false;
                }
            }
        });
    }

    function sendCMD(args){
        if (args[0] === mcConst.lockInterface){
            args.shift();

            lockInterface();

            $rootScope.lockedCMD[args[0]] = true;
        }

        if (mcService.isFunction(args[args.length - 1])){
            if (!$rootScope.customCallBack[args[0]]){
                $rootScope.customCallBack[args[0]] = [];
            }

            $rootScope.customCallBack[args[0]].push(args.pop());
        }

        if (mcCmdParser.MultiSender.apply(null , args)){
            runCmd('SendCMDToServer', args);
        }
    }

    function receiveCMD(args) {
        if (mcCmdParser.MultiParser.apply(null, args)){
            runCmd('ProcessCMD', args);
        }
    }

    function setWindowSize() {
        $rootScope.wndSize = mcService.getWindowSize();

        for (var i = 0; i < resizeWndCb.length; i++){
            resizeWndCb[i]($rootScope.wndSize.width, $rootScope.wndSize.height);
        }
    }

    // =============================================================

    window.idMcPingTimer = null;

    var pingOptions =  {
        onError : null,
        onStop  : null,
        onWarn  : null
    };

    function StartPingTimer(_time, opt){
        pingOptions = opt;

        if (mcConst.SessionID) {
            window.idMcPingTimer = setInterval(function(){
                if (mcConst.SessionID) {
                    $rootScope.SendCMDToServer([mcConst._CMD_.Ping, mcConst.SessionID]);
                } else {
                    console.error("mcConst.SessionID is not defined. StopPingTimer()");

                    StopPingTimer();
                }
            }, _time);
        }

        function RunCMD(CMD_Block, debug) {
            if (debug) {
                console.log("CMD_Block:\n" + CMD_Block);
            }

            if (CMD_Block && CMD_Block.length > 0){
                CMD_Block = mcService.StringToObj(CMD_Block);

                for (var CMD_Numb in CMD_Block){
                    $rootScope.ProcessCMD([CMD_Numb, CMD_Block[CMD_Numb]]);
                }
            }
        }

        mcConnect.ClearAllCallBackFunctionByCMD();
        mcConnect.LeaveCallBacks = true;

        mcConnect.SetCallBackFunctionByCMD(mcConst._CMD_.Ping, function(){
            var data = this;

            if ((data !== "{}") && (data.length > 0)) {
                RunCMD(data);
            }
        });

        mcConnect.OnError = function(err){
            if (console) {
                console.warn('An error occurred: Ping-Timer has been Stopped');
            }

            StopPingTimer();

            if (pingOptions.onError){
                pingOptions.onError(err);
            }
        };

        return window.idMcPingTimer;
    }

    function StopPingTimer(){
        if (window.idMcPingTimer !== null){
            clearInterval(window.idMcPingTimer);

            window.idMcPingTimer = null;

            mcConnect.ClearAllCallBackFunctionByCMD();

            if (pingOptions.onStop) {
                pingOptions.onStop();
            }
        }
    }

    // =============================================================

    var resizeWndCb = [];
    var lockWindow  = null;

    // =============================================================

    window.__copyLinkToClipBoard = function(e){
        mcService.copyTextToClipboard(this.href ? this.href : (this.attributes.link ? this.attributes.link.value : "/kanban/"), mcLang(162)); // "162":"Ссылка скопирована",

        e.preventDefault();
    };

    window._userActions_ = {};

    $scope.lockCount = 0;
    $scope.lockTimer = null;
    $scope.errorMessages = [];
    $scope.errorMessagesIsShowed = false;

    $rootScope.lockedCMD      = {};
    $rootScope.mcStorage      = mcService.Marge({}, mcConst.dataModels);
    $rootScope.ErrorActions   = {};
    $rootScope.customCallBack = {};
    $rootScope.currentProject = null;
    $rootScope.Purl           = $location;
    $rootScope.noChangeURI    = false;

    $rootScope.kanbanOptions  = {
        projectState   : {},
        stageState     : {},
        onlyMyTasks    : false,
        dashBoardView  : 1,
        common         : {
            openMenu : false
        },
        StageTypes     : {
            typical : true,
            done    : false,
            archive : false
        },
        currentProject : null
    };

    $rootScope.SendCMDToServer = sendCMD;
    $rootScope.ProcessCMD      = receiveCMD;

    $rootScope.wndSize = mcService.getWindowSize();

    $rootScope.chatAliases = {
        Conf  : "ConfUserList",
        UIN   : "UIN",
        UID   : "UID",
        Forum : "Forum",
        Privat: "PrivateInfo",
        Kanban: "Kanban",
        BBS   : "BBS"
    };

    $rootScope.GetChatID   = function () { return 1 };
    $rootScope.GetChatType = function () { return $rootScope.chatAliases.Kanban };

    $rootScope.thumbsSize  = mcConst.imageSize.big;

    $rootScope.isWebClient = true;

    $rootScope.duManager = new DownloadUploadManager($rootScope, { images: {enable: true} });

    $rootScope.localPath = mcService.getLocalHostPath($rootScope.isWebClient);

    // =============================================================

    $rootScope.hotKeyDispatcher = new mcService.HotkeyManager(); // set value "true" to debug

    // =============================================================

    var toolShowed     = null;

    $rootScope.setTool = function(name){
        if (toolShowed != name) {
            $rootScope.hidePrevTool(toolShowed);

            toolShowed = name;
        }
    };

    $rootScope.getTool = function(){
        return toolShowed;
    };

    $rootScope.hidePrevTool = function(){
        if (toolShowed)
            $rootScope.$broadcast('hide' + toolShowed, []);
    };

    // ====================================================

    $rootScope.colorPalette = [
        "FFB900", "E74856", "0078D7", "0099BC", "7A7574", "767676",
        "FF8C00", "E81123", "0063B1", "2D7D9A", "5D5A58", "4C4A48",
        "F7630C", "EA005E", "8E8CD8", "00B7C3", "68768A", "69797E",
        "CA5010", "C30052", "6B69D6", "038387", "515C6B", "4A5459",
        "DA3B01", "E3008C", "8764B8", "00B294", "567C73", "647C64",
        "EF6950", "BF0077", "744DA9", "018574", "486860", "525E54",
        "D13438", "C239B3", "B146C2", "00CC6A", "498205", "847545",
        "FF4343", "9A0089", "881798", "10893E", "107C10", "7E735F"
    ];

    // ====================================================

    window.moment = moment || {};
    window.isMobile = browserDetect.mobile;

    mcService.clearPlaneObj($rootScope.mcStorage);

    mcConst.LoginInfo = mcService.LoadSessionSettings();

    $scope.$on('SendCMDToServer', function(e, args){
        sendCMD(args);
    });

    $scope.$on('ProcessCMD', function(e, args){
        receiveCMD(args);
    });

    $scope.$on('StartPingTimer', function(){
        mcConst.PingTimer = StartPingTimer(mcConst.PingInterval, {
            onError : function(){
                mcConst.LoggedIn = false;

                $scope.$emit('ErrorMsg', [mcConst._CMD_.Errors.Disconnected, mcConst.ErrorText['223'], '', function(){
                    location.search = "";
                    location.reload();
                }]);
            }
        });
    });

    $scope.$on('StopPingTimer', function(){
        StopPingTimer();
    });

    $scope.$on('ChangLanguage', function(e, args){
        (function(locale, lang){
            setLocalizations();

            lang = $location.param('lang') || lang;

            if (!mcService.inArrayB(lang, ['ua', 'ru', 'en', 'uk'])){
                lang = 'en';
            }

            if (lang.toLowerCase() === 'ua') {
                lang = 'uk';
            }

            if (lang.toLowerCase() !== "en") {
                webix.Date.startOnMonday = true;
            }

            moment.locale(lang);

            mcConst.Lang = lang;

            $http(MC_RESOURCE.TextSource.languages + lang + '.languages.json').success(function(txt){
                mcConst.LanguagesList = mcService.isObject(txt) ? txt : JSON.parse(txt);
            });

            $http(MC_RESOURCE.TextSource.errors.client + lang + '.errors.json').success(function(txt){
                mcConst.ErrorText = mcService.isObject(txt) ? txt : JSON.parse(txt);
            });

            $http(MC_RESOURCE.TextSource.www.kanban + lang + '.json').success(function(txt){
                mcConst.LMSG = mcService.isObject(txt) ? txt : JSON.parse(txt);

                webix.i18n.setLocale(lang);

                webix.ready(function(){
                    $scope.$broadcast('show' + mcConst.dataModels.Login, [lang]);

                    initLockWindow();
                });
            });
        }).apply(null, args)
    });

    $scope.$on(mcConst.lockInterface, lockInterface);

    $scope.$on('hide' + mcConst.lockInterface, function(){
        if ($scope.lockCount > 0) {
            $scope.lockCount --;
        }

        if ($scope.lockCount === 0 || !mcConst.PingTimer) {
            clearTimeout($scope.lockTimer);

            lockWindow.hide();
        }
    });

    $scope.$on('ErrorMsg', function(e, args){
        if ($scope.errorMessagesIsShowed){
            $scope.errorMessages.push(args);
        } else {
            displayError.apply(null, args);
        }
    });

    $scope.$on('windowResize', function(e, args){
        if (mcService.isFunction(args[0])){
            resizeWndCb.push(args[0]);
        }
    });

    // =============================================================

    $scope.$emit('ChangLanguage', mcService.detectLang(webix.storage.local.get(mcConst.storageFields.lang)));

    webix.ready(function(){
        webix.rules.isNotEmpty = function(val){
            return mcService.trim(val) != '';
        };

        webix.DataStore.prototype.sorting.as.sortableDate = function(a,b){
            return mcService.sortAB(mcService.sortableDate(a), mcService.sortableDate(b));
        };

        webix.protoUI({
            name:"activeList"
        }, webix.ui.unitlist, webix.ActiveContent);

        webix.protoUI({
            name:"activeDataView"
        }, webix.ui.dataview, webix.ActiveContent);

        webix.protoUI({
            name:"activePlainList"
        }, webix.ui.list, webix.ActiveContent);

        webix.protoUI({
            name:"suggest",
            defaults:{
                filter:function(item,value){
                    return item.value.toString().toLowerCase().indexOf(value.toLowerCase()) !== -1;
                }
            }
        }, webix.ui.suggest);

        webix.protoUI({
            name: "hr",
            defaults: {
                height: 10,
                borderless: true
            },
            $init: function(){
                this.$view.innerHTML = "<hr>";
            }
        }, webix.ui.view);

        webix.protoUI({
            name:"editlist"
        }, webix.EditAbility, webix.ui.list);

        // webix.protoUI({
        //     name:"editlist"
        // }, webix.EditAbility, webix.ui.list);

        webix.protoUI({
            name:"carouselKanban"
        }, webix.ui.carousel, webix.ui.kanban);

        // if (!webix.env.touch && webix.ui.scrollSize && !window.isMobile){
        //     webix.CustomScroll.init();
        // }
    });

    // ========================================================

    /*window.onbeforeunload = function() {
        if (mcConst.LoggedIn){
            return mcService.Lang(40); // "40" : "Вы хотите завершить сеанс?",
        }
    };*/

    window.onunload = function(){
        if (mcConst.LoggedIn) {
            // $rootScope.$broadcast('savePrivatesToStorage', []);

            sendCMD([
                mcConst._CMD_.cs_quit,
                mcConst.SessionID
            ]);
        }
    };

    mcService.addEvent(window, "resize", setWindowSize);

}

"use strict";

if (!window.old_IE) {
    var units   = []
        .concat({ module: 'mcKanbanPrepare',       args: ['$rootScope']})
        .concat({ module: 'mcConnect',             args: ['$http']})
        // .concat({ module: 'mcCmdLoop',             args: ['mcConnect',  '$rootScope']})
        .concat({ module: 'mcCmdParser',           args: ['mcConnect',  '$rootScope']})
        .concat({ module: 'mcKanbanManager',       args: ['$rootScope']})
        .concat({ module: 'mcUploadProgress',      args: ['$rootScope']})
        .concat({ module: 'mcFileUploading',       args: ['$rootScope']})

        .concat({ module: 'mainController',        args: ['$scope',      '$rootScope',
                                                          'mcCmdParser', //'mcCmdLoop',
                                                          '$http',       '$location',
                                                          'mcConnect']})

        .concat({ module: 'loginController',       args: ['$scope', '$rootScope', '$location']})
        .concat({ module: 'wrapperController',     args: ['$scope', '$rootScope']})
        .concat({ module: 'toolbarController',     args: ['$scope', '$rootScope', '$location']})
        .concat({ module: 'kanbanBodyController',  args: ['$scope', '$rootScope']})
        .concat({ module: 'taskController',        args: ['$scope', '$rootScope']})
        .concat({ module: 'taskEditController',    args: ['$scope', '$rootScope']})
        .concat({ module: 'menuController',        args: ['$scope', '$rootScope']})
        // .concat({ module: 'newProjectController',  args: ['$scope', '$rootScope']})
        .concat({ module: 'editProjectController', args: ['$scope', '$rootScope']})
        .concat({ module: 'dashboardController',   args: ['$scope', '$rootScope']})
    ;

    window.mcService = servicesController('kanban');
    window.mcLang    = mcService.Lang;

    mcComponents.units(units).start();
}






