Telegram-style чат + опциональная тёмная тема интерфейса. Автозагрузка до 10 страниц при открытии тикета.
// ==UserScript==
// @name HDE Chat Compact
// @namespace http://tampermonkey.net/
// @version 14.45
// @description Telegram-style чат + опциональная тёмная тема интерфейса. Автозагрузка до 10 страниц при открытии тикета.
// @author Eban
// @license MIT
// @match https://*.helpdeskeddy.com/*
// @grant none
// @run-at document-start
// @language ru
// ==/UserScript==
(function () {
'use strict';
// ============================================================
// HDE Chat Compact — Module Map
// ============================================================
// 1) Constants + Config Storage
// 2) Theme Engine (dark/pink remap)
// 3) Compact Chat Rendering
// 4) History / Previous Dialogs Loader
// 5) Colleague Statuses + Staffs Drawer
// 6) Left Tabs Panel (standalone entries/sort/polling/ui)
// 7) Settings Panel + Menu Actions
// 8) Boot / Init / Cleanup
// ============================================================
const STYLE_ID = 'hde-compact-v10-style';
const DONE_CLASS = 'hde-v10-done';
const DATE_SEP_CLASS = 'hde-date-separator';
const TICKET_SEP_CLASS = 'hde-ticket-separator';
const MENU_STYLE_ID = 'hde-tools-menu-style';
const MENU_PANEL_ID = 'hde-tools-panel';
const THEME_STYLE_ID = 'hde-tools-theme-style';
const THEME_BOOT_STYLE_ID = 'hde-tools-theme-boot-style';
const LEFT_TABS_BOOT_STYLE_ID = 'hde-left-tabs-boot-style';
const DARK_HTML_CLASS = 'hde-tools-theme-dark';
const PINK_HTML_CLASS = 'hde-tools-theme-pink';
const MIDNIGHT_BLUE_HTML_CLASS = 'hde-tools-theme-midnight-blue';
const TRICOLOR_HTML_CLASS = 'hde-tools-theme-tricolor';
const MONOCHROME_HTML_CLASS = 'hde-tools-theme-monochrome';
const LEGACY_DARK_HTML_CLASS = 'hde-tools-dark';
const LEGACY_THEME_STYLE_ID = 'hde-tools-dark-theme-style';
const THEME_MODE_STANDARD = 'standard';
const THEME_MODE_DARK = 'dark';
const THEME_MODE_PINK = 'pink';
const THEME_MODE_MIDNIGHT_BLUE = 'midnight-blue';
const THEME_MODE_TRICOLOR = 'tricolor-contrast';
const THEME_MODE_MONOCHROME = 'bw-contrast';
const THEME_MODES = [THEME_MODE_STANDARD, THEME_MODE_DARK, THEME_MODE_PINK, THEME_MODE_MIDNIGHT_BLUE, THEME_MODE_TRICOLOR, THEME_MODE_MONOCHROME];
const THEME_HTML_CLASSES = [DARK_HTML_CLASS, PINK_HTML_CLASS, MIDNIGHT_BLUE_HTML_CLASS, TRICOLOR_HTML_CLASS, MONOCHROME_HTML_CLASS, LEGACY_DARK_HTML_CLASS];
const THEME_MODE_TO_HTML_CLASS = {
[THEME_MODE_DARK]: DARK_HTML_CLASS,
[THEME_MODE_PINK]: PINK_HTML_CLASS,
[THEME_MODE_MIDNIGHT_BLUE]: MIDNIGHT_BLUE_HTML_CLASS,
[THEME_MODE_TRICOLOR]: TRICOLOR_HTML_CLASS,
[THEME_MODE_MONOCHROME]: MONOCHROME_HTML_CLASS
};
const THEME_MODE_TO_LABEL = {
[THEME_MODE_STANDARD]: 'Стандартная',
[THEME_MODE_DARK]: 'Тёмная',
[THEME_MODE_PINK]: 'Розовая',
[THEME_MODE_MIDNIGHT_BLUE]: 'Midnight Blue',
[THEME_MODE_TRICOLOR]: 'Контрастная (бело-сине-красная)',
[THEME_MODE_MONOCHROME]: 'Контрастная ч/б'
};
const THEME_BOOT_COLORS = {
[THEME_MODE_DARK]: { bg: '#121418', fg: '#dcdfe6' },
[THEME_MODE_PINK]: { bg: '#fbe9f2', fg: '#6f4d60' },
[THEME_MODE_MIDNIGHT_BLUE]: { bg: '#0b1220', fg: '#cfddf5' },
[THEME_MODE_TRICOLOR]: { bg: '#ffffff', fg: '#1d3f73' },
[THEME_MODE_MONOCHROME]: { bg: '#0c0c0c', fg: '#f1f1f1' }
};
/** Увеличивать при добавлении полей в панель — пересборка после обновления скрипта */
const SETTINGS_PANEL_VERSION = '8';
const STAFFS_DRAWER_ID = 'hde-staffs-drawer';
const LEFT_TABS_MIRROR_ID = 'hde-left-tabs-mirror';
const LEFT_TABS_COLUMN_ID = 'hde-left-tabs-column';
const LEFT_TABS_ONLY_CLASS = 'hde-left-tabs-only';
const LEFT_TABS_CACHE_KEY = 'hde-left-tabs-cache-v1';
const LEFT_TABS_CACHE_KEY_LEGACY_V2 = 'hde-left-tabs-cache-v2';
const MSG_BLOCK_SELECTOR = ':scope > .ticket-conversation__message-block';
const MSG_HTML_SELECTOR = ':scope > .ticket-conversation__message-text > .ticket-conversation__message-html';
const DEFAULT_PREVIOUS_TICKETS_TO_LOAD = 5;
const MAX_PAGES_PER_PREVIOUS_TICKET = 3;
const DEBUG_LOGS = false;
function debugLog(...args) {
if (!DEBUG_LOGS) return;
console.log(...args);
}
// ─── Конфигурация модулей ────────────────────────────────
const CONFIG = {
compactChat: false,
autoLoadHistory: false,
autoLoadPreviousDialogs: false,
previousDialogsLimit: 1,
themeMode: THEME_MODE_STANDARD,
colleagueStatuses: false,
leftTabsMirror: false,
hideNativeTicketTabs: false
};
function normalizeThemeMode(value) {
const mode = String(value || '').toLowerCase();
return THEME_MODES.includes(mode) ? mode : THEME_MODE_STANDARD;
}
function applyThemeClass(mode, root = document.documentElement) {
if (!root) return false;
root.classList.remove(...THEME_HTML_CLASSES);
const nextClass = THEME_MODE_TO_HTML_CLASS[mode];
if (!nextClass) return false;
root.classList.add(nextClass);
return true;
}
function buildThemeBootCss() {
return Object.entries(THEME_BOOT_COLORS).map(([mode, palette]) => {
const cls = THEME_MODE_TO_HTML_CLASS[mode];
if (!cls) return '';
return `html.${cls}, html.${cls} body { background: ${palette.bg} !important; color: ${palette.fg} !important; }`;
}).join('\n');
}
function renderThemeOptions(selectedMode) {
return THEME_MODES.map((mode) => {
const label = THEME_MODE_TO_LABEL[mode] || mode;
const selected = selectedMode === mode ? 'selected' : '';
return `<option value="${mode}" ${selected}>${label}</option>`;
}).join('');
}
function getEarlyThemeModeFromStorage() {
try {
const saved = localStorage.getItem('hde-tools-config');
if (!saved) return THEME_MODE_STANDARD;
const parsed = JSON.parse(saved);
if (parsed && typeof parsed.themeMode === 'string') {
return normalizeThemeMode(parsed.themeMode);
}
if (parsed && typeof parsed.darkTheme === 'boolean') {
return parsed.darkTheme ? THEME_MODE_DARK : THEME_MODE_STANDARD;
}
} catch (e) {}
return THEME_MODE_STANDARD;
}
function getEarlyLeftTabsMirrorFromStorage() {
try {
const saved = localStorage.getItem('hde-tools-config');
if (!saved) return false;
const parsed = JSON.parse(saved);
return !!(parsed && parsed.leftTabsMirror === true);
} catch (e) {}
return false;
}
function getEarlyHideNativeTicketTabsFromStorage() {
try {
const saved = localStorage.getItem('hde-tools-config');
if (!saved) return false;
const parsed = JSON.parse(saved);
return !!(parsed && parsed.hideNativeTicketTabs === true);
} catch (e) {}
return false;
}
function shouldHideNativeTicketTabs() {
return !!CONFIG.hideNativeTicketTabs;
}
function syncNativeTicketTabsVisibilityClass(root = document.documentElement) {
if (!root) return;
root.classList.toggle(LEFT_TABS_ONLY_CLASS, shouldHideNativeTicketTabs());
}
function applyEarlyThemeBoot() {
const mode = getEarlyThemeModeFromStorage();
const root = document.documentElement;
if (!root) return;
if (!applyThemeClass(mode, root)) return;
if (document.getElementById(THEME_BOOT_STYLE_ID)) return;
const style = document.createElement('style');
style.id = THEME_BOOT_STYLE_ID;
style.textContent = buildThemeBootCss();
if (document.head) document.head.appendChild(style);
else root.appendChild(style);
}
applyEarlyThemeBoot();
function applyEarlyLeftTabsBoot() {
const earlyLeftTabsMirror = getEarlyLeftTabsMirrorFromStorage();
const earlyHideNativeTabs = getEarlyHideNativeTicketTabsFromStorage();
if (!earlyLeftTabsMirror && !earlyHideNativeTabs) return;
const root = document.documentElement;
if (!root) return;
if (earlyHideNativeTabs) root.classList.add(LEFT_TABS_ONLY_CLASS);
if (document.getElementById(LEFT_TABS_BOOT_STYLE_ID)) return;
const style = document.createElement('style');
style.id = LEFT_TABS_BOOT_STYLE_ID;
style.textContent = `
html.${LEFT_TABS_ONLY_CLASS} #ticket-app .ticket-topbar__tabs {
display: none !important;
}
html.${LEFT_TABS_ONLY_CLASS} .ticket-tabs__show-more-popper {
display: none !important;
}
#${LEFT_TABS_COLUMN_ID} {
flex: 0 0 180px;
width: 180px;
min-width: 180px;
box-sizing: border-box;
margin-top: 0;
}
#${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton {
margin: 0;
border-radius: 6px;
overflow: hidden;
background: #ffffff;
}
#${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__title {
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
color: #909399;
background: #f5f7fa;
}
#${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__list {
padding: 6px 0;
}
#${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__skeleton-item {
height: 34px;
margin: 0 8px 8px;
border-radius: 8px;
background: linear-gradient(90deg, #eef1f6 25%, #f5f7fa 45%, #eef1f6 65%);
background-size: 220% 100%;
animation: hdeLeftTabsSkeletonPulse 1.2s linear infinite;
}
#${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__skeleton-item:last-child {
margin-bottom: 0;
}
html.${DARK_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton {
background: #1e2529;
}
html.${DARK_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__title {
color: #c0c4cc;
background: #252b33;
}
html.${DARK_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__skeleton-item {
background: linear-gradient(90deg, #2a3038 25%, #343b45 45%, #2a3038 65%);
}
html.${PINK_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton {
background: #fff6fb;
}
html.${PINK_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__title {
color: #7a5a6b;
background: #f8eaf2;
}
html.${PINK_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__skeleton-item {
background: linear-gradient(90deg, #f0deea 25%, #f6e8f0 45%, #f0deea 65%);
}
html.${MIDNIGHT_BLUE_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton {
background: #0f1728;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__title {
color: #b7cae7;
background: #16243b;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__skeleton-item {
background: linear-gradient(90deg, #1b2d4a 25%, #23395f 45%, #1b2d4a 65%);
}
html.${TRICOLOR_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton {
background: #ffffff;
}
html.${TRICOLOR_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__title {
color: #2a4d82;
background: #edf4ff;
}
html.${TRICOLOR_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__skeleton-item {
background: linear-gradient(90deg, #e7edf6 25%, #f1f5fb 45%, #e7edf6 65%);
}
html.${MONOCHROME_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton {
background: #151515;
}
html.${MONOCHROME_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__title {
color: #e6e6e6;
background: #1f1f1f;
}
html.${MONOCHROME_HTML_CLASS} #${LEFT_TABS_MIRROR_ID}.hde-left-tabs-mirror--skeleton .hde-left-tabs-mirror__skeleton-item {
background: linear-gradient(90deg, #252525 25%, #323232 45%, #252525 65%);
}
@keyframes hdeLeftTabsSkeletonPulse {
0% { background-position: 100% 0; }
100% { background-position: -100% 0; }
}
`;
if (document.head) document.head.appendChild(style);
else root.appendChild(style);
}
applyEarlyLeftTabsBoot();
function ensureEarlyLeftTabsSkeleton(column) {
if (!(column instanceof Element)) return;
if (document.getElementById(LEFT_TABS_MIRROR_ID)) return;
const mirror = document.createElement('div');
mirror.id = LEFT_TABS_MIRROR_ID;
mirror.className = 'hde-left-tabs-mirror hde-left-tabs-mirror--skeleton';
mirror.innerHTML = `
<div class="hde-left-tabs-mirror__title">
<span class="hde-left-tabs-mirror__title-text">Тикеты</span>
</div>
<div class="hde-left-tabs-mirror__list">
<div class="hde-left-tabs-mirror__skeleton-item"></div>
<div class="hde-left-tabs-mirror__skeleton-item"></div>
<div class="hde-left-tabs-mirror__skeleton-item"></div>
</div>
`;
column.appendChild(mirror);
}
function loadConfig() {
try {
const saved = localStorage.getItem('hde-tools-config');
if (saved) {
Object.assign(CONFIG, JSON.parse(saved));
}
const limit = parseInt(CONFIG.previousDialogsLimit, 10);
CONFIG.previousDialogsLimit = Number.isFinite(limit) ? Math.min(10, Math.max(1, limit)) : DEFAULT_PREVIOUS_TICKETS_TO_LOAD;
const hasThemeMode = typeof CONFIG.themeMode === 'string' && THEME_MODES.includes(CONFIG.themeMode.toLowerCase());
if (hasThemeMode) {
CONFIG.themeMode = normalizeThemeMode(CONFIG.themeMode);
} else if (typeof CONFIG.darkTheme === 'boolean') {
CONFIG.themeMode = CONFIG.darkTheme ? THEME_MODE_DARK : THEME_MODE_STANDARD;
} else {
CONFIG.themeMode = THEME_MODE_STANDARD;
}
if (typeof CONFIG.colleagueStatuses !== 'boolean') CONFIG.colleagueStatuses = false;
if (typeof CONFIG.leftTabsMirror !== 'boolean') CONFIG.leftTabsMirror = false;
if (typeof CONFIG.hideNativeTicketTabs !== 'boolean') CONFIG.hideNativeTicketTabs = false;
delete CONFIG.darkTheme;
} catch (e) {
console.warn('Ошибка загрузки конфига:', e);
}
}
function saveConfig() {
CONFIG.themeMode = normalizeThemeMode(CONFIG.themeMode);
const payload = Object.assign({}, CONFIG);
delete payload.darkTheme;
localStorage.setItem('hde-tools-config', JSON.stringify(payload));
}
// ============================================================
// 2) Theme Engine (dark/pink/midnight-blue/tricolor remap)
// ============================================================
// ─── Theme Engine: Dark Theme ────────────────────────────
// Структурировано секциями, чтобы было проще добавлять новые темы.
const DARK_THEME_CSS_BASE = `
html.${DARK_HTML_CLASS},
html.${DARK_HTML_CLASS} body {
background-color: #121418 !important;
color: #dcdfe6 !important;
scrollbar-color: #3d4654 #1a1f26;
}
html.${DARK_HTML_CLASS} {
--menu-background: #161a1f;
--menu-item-color: #e8eaed;
--menu-item-background: #161a1f;
--menu-item-color-hover: #e8f4f7;
--menu-item-background-hover: #1f2a32;
--ticket-user-post-color: #e8eaed;
--ticket-user-post-background: #2d333b;
--ticket-user-href-color: #7ec8e0;
--ticket-user-href-hover: #a6dff0;
--ticket-user-background: #1a1f26;
--ticket-user-href-border: 0px;
--ticket-staff-post-color: #f0f2f5;
--ticket-staff-post-background: #4a5563;
--ticket-staff-href-color: #fff;
--ticket-staff-href-hover: #e2e8f0;
--ticket-staff-own-post-color: #f0f2f5;
--ticket-staff-own-post-background: #3d4654;
--ticket-staff-comment-color: #f0fafc;
--ticket-staff-comment-background: #1a5f6e;
--ticket-staff-comment-href-color: #e0f7fa;
--ticket-staff-comment-href-hover: #fff;
--ticket-staff-own-comment-color: #e8f4f7;
--ticket-staff-own-comment-background: #154a56;
--ticket-post-input-color: #e4e7ed;
--ticket-post-input-background: #252b33;
--ticket-comment-input-color: #f0fafc;
--ticket-comment-input-background: #1a5f6e;
--ticket-primary-button-color: #0f1419;
--ticket-primary-button-background: #5eb3c9;
--ticket-primary-button-color-hover: #0f1419;
--ticket-primary-button-background-hover: #7ec8e0;
--ticket-secondary-button-color: #dcdfe6;
--ticket-secondary-button-background: #2a3038;
--ticket-secondary-button-color-hover: #7ec8e0;
--ticket-secondary-button-background-hover: #343b45;
}
`;
const DARK_THEME_CSS_COMPONENTS = `
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__heading,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__container {
background-color: #1a1f26 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__heading-text,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter a {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__heading-button,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__heading-button i,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__heading-button .el-icon-setting {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__heading-button:hover,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__heading-button:hover i,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__heading-button:hover .el-icon-setting {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .sidebar,
html.${DARK_HTML_CLASS} .sidebar__item,
html.${DARK_HTML_CLASS} .sidebar__item-icon,
html.${DARK_HTML_CLASS} .sidebar__item-name,
html.${DARK_HTML_CLASS} i.hde-macro {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .sidebar__item:hover {
background-color: #252b33 !important;
}
html.${DARK_HTML_CLASS} .macro__row,
html.${DARK_HTML_CLASS} .macro__row.macro__row_heading,
html.${DARK_HTML_CLASS} .macro__col {
background-color: #1e2529 !important;
color: #c0c4cc !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__heading,
html.${DARK_HTML_CLASS} #ticket-app .ticket-topbar {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-user,
html.${DARK_HTML_CLASS} #ticket-app .ticket-modules {
background-color: #1a1f26 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__filter-count span {
background: #2d333b !important;
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .menu__item-badge,
html.${DARK_HTML_CLASS} .menu__item-avatar-status {
color: #121418 !important;
font-weight: 700 !important;
}
html.${DARK_HTML_CLASS} .user-card-info,
html.${DARK_HTML_CLASS} .create-ticket-btn,
html.${DARK_HTML_CLASS} .etabs.user-card-tabs-nav,
html.${DARK_HTML_CLASS} .user-card-custom-title {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .etabs.user-card-tabs-nav li {
background-color: #252b33 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .etabs.user-card-tabs-nav li.active {
background-color: #2d333b !important;
}
html.${DARK_HTML_CLASS} .etabs.user-card-tabs-nav li a,
html.${DARK_HTML_CLASS} .etabs.user-card-tabs-nav li i,
html.${DARK_HTML_CLASS} .user-card-name,
html.${DARK_HTML_CLASS} .user-card-name span,
html.${DARK_HTML_CLASS} .user-card-info a,
html.${DARK_HTML_CLASS} .user-card-info i,
html.${DARK_HTML_CLASS} #user-audit-btn {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .etabs.user-card-tabs-nav li.active a,
html.${DARK_HTML_CLASS} .etabs.user-card-tabs-nav li.active i,
html.${DARK_HTML_CLASS} .hde-comment-dots.active {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .user-card-toggle,
html.${DARK_HTML_CLASS} .user-card-toggle.pull-right,
html.${DARK_HTML_CLASS} .user-card-toggle.pull-right.ml-2 {
background-color: #2d333b !important;
color: #e4e7ed !important;
border: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} .user-card-info input,
html.${DARK_HTML_CLASS} .user-card-custom-title input {
background-color: #252b33 !important;
color: #e4e7ed !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .user-card-custom-toggle .user-card-toggle-div table,
html.${DARK_HTML_CLASS} .user-card-custom-toggle .user-card-toggle-div table tr,
html.${DARK_HTML_CLASS} .user-card-custom-toggle .user-card-toggle-div table td {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .user-card-custom-toggle .user-card-toggle-div table tr:hover,
html.${DARK_HTML_CLASS} .user-card-custom-toggle .user-card-toggle-div table tr:hover td {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .user-card-custom-toggle .user-card-toggle-div table tr.border-top td,
html.${DARK_HTML_CLASS} .user-card-custom-toggle .user-card-toggle-div table tr.border-bottom td {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .user-card-custom-toggle .depart-ul li {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} i.hde-pencil,
html.${DARK_HTML_CLASS} i.icon-attachment {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs {
background: #161a1f !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs__tab {
background: #252b33 !important;
color: #c0c4cc !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs .ticket-tabs__tab-divider {
background: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs__close-all,
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs__more-close-all,
html.${DARK_HTML_CLASS} .ticket-tabs__close-all,
html.${DARK_HTML_CLASS} .ticket-tabs__more-close-all {
background-color: #1e2529 !important;
border: 1px solid #3d4654 !important;
color: #c0c4cc !important;
box-shadow: none !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs__close-all:hover,
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs__more-close-all:hover,
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs__close-all:focus,
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs__more-close-all:focus,
html.${DARK_HTML_CLASS} .ticket-tabs__close-all:hover,
html.${DARK_HTML_CLASS} .ticket-tabs__more-close-all:hover,
html.${DARK_HTML_CLASS} .ticket-tabs__close-all:focus,
html.${DARK_HTML_CLASS} .ticket-tabs__more-close-all:focus {
background-color: #252b33 !important;
border-color: #4a5563 !important;
color: #e4e7ed !important;
outline: none !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-tabs__tab:hover {
background: #2d333b !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-sla-overdue {
color: #d97a7a !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-status[title*="offline" i],
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-status[aria-label*="offline" i],
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-status_offline,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-status.status-offline {
--status-background-color: #8f4a4a !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-status[style*="--status-background-color:#D31616"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-status[style*="--status-background-color: #D31616"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-select-text[style*="--status-background-color:#D31616"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-list__column-select-text[style*="--status-background-color: #D31616"] {
--status-background-color: #8f4a4a !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail,
html.${DARK_HTML_CLASS} #ticket-app .ticket-container,
html.${DARK_HTML_CLASS} #ticket-app .el-main {
background-color: #121418 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail,
html.${DARK_HTML_CLASS} #ticket-app .ticket-container,
html.${DARK_HTML_CLASS} #ticket-app .el-main,
html.${DARK_HTML_CLASS} #ticket-app .ticket-conversation__messages {
background-color: #1a1f26 !important;
}
/* Metro: боковые колонки и блок полей — в theme жёстко #fff */
html.${DARK_HTML_CLASS} #ticket-app .ticket .ticket-left-block,
html.${DARK_HTML_CLASS} #ticket-app .ticket .ticket-right-block {
background-color: #1a1f26 !important;
border-color: #3d4654 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__image {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__title,
html.${DARK_HTML_CLASS} #ticket-app .ticket-right-block {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-topbar,
html.${DARK_HTML_CLASS} #ticket-app .ticket .ticket-topbar {
background-color: #1e2529 !important;
border-bottom-color: #3d4654 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-aside {
background-color: #1a1f26 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-table {
background-color: #1a1f26 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-table tr,
html.${DARK_HTML_CLASS} #ticket-app .el-table__body tr {
background-color: #1a1f26 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-table th.el-table__cell {
background-color: #161a1f !important;
color: #c0c4cc !important;
border-bottom-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-table td.el-table__cell {
border-bottom-color: #2d333b !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-table--striped .el-table__body tr.el-table__row--striped td {
background: #1e2529 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-table--enable-row-hover .el-table__body tr:hover > td {
background-color: #252b33 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-input__inner,
html.${DARK_HTML_CLASS} #ticket-app .el-textarea__inner {
background-color: #252b33 !important;
color: #e4e7ed !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-input__inner {
background-color: #252b33 !important;
color: #e4e7ed !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-input__inner:hover,
html.${DARK_HTML_CLASS} .el-input__inner:focus,
html.${DARK_HTML_CLASS} .el-input.is-focus .el-input__inner {
background-color: #2d333b !important;
color: #e4e7ed !important;
border-color: #4a5563 !important;
}
html.${DARK_HTML_CLASS} .el-checkbox__inner {
background-color: #252b33 !important;
border-color: #4a5563 !important;
}
html.${DARK_HTML_CLASS} .el-checkbox__inner:hover,
html.${DARK_HTML_CLASS} .el-checkbox__input:hover .el-checkbox__inner {
border-color: #7ec8e0 !important;
}
html.${DARK_HTML_CLASS} .el-checkbox__input.is-checked .el-checkbox__inner,
html.${DARK_HTML_CLASS} .el-checkbox__input.is-indeterminate .el-checkbox__inner {
background-color: #3d4654 !important;
border-color: #7ec8e0 !important;
}
html.${DARK_HTML_CLASS} .el-checkbox__input.is-checked + .el-checkbox__label,
html.${DARK_HTML_CLASS} .el-checkbox__label {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-checkbox__input.is-disabled .el-checkbox__inner {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-checkbox__input.is-disabled + span.el-checkbox__label,
html.${DARK_HTML_CLASS} .el-checkbox__input.is-disabled.is-checked + span.el-checkbox__label {
color: #8b9199 !important;
}
html.${DARK_HTML_CLASS} .select,
html.${DARK_HTML_CLASS} .select.is-active,
html.${DARK_HTML_CLASS} .select__options {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .select__options *,
html.${DARK_HTML_CLASS} .select * {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .select__options .is-active,
html.${DARK_HTML_CLASS} .select__options .active,
html.${DARK_HTML_CLASS} .select__options .selected,
html.${DARK_HTML_CLASS} .select__options *:hover {
background-color: #2d333b !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-toolbar,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-toolbar .ck-toolbar__items {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-editor__ckeditor .ck.ck-editor,
html.${DARK_HTML_CLASS} #ticket-app .ticket-editor__ckeditor .ck.ck-editor__top,
html.${DARK_HTML_CLASS} #ticket-app .ticket-editor__ckeditor .ck.ck-sticky-panel,
html.${DARK_HTML_CLASS} #ticket-app .ticket-editor__ckeditor .ck.ck-sticky-panel__content,
html.${DARK_HTML_CLASS} #ticket-app .ticket-editor__ckeditor .ck.ck-sticky-panel__placeholder {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
box-shadow: none !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-toolbar__separator,
html.${DARK_HTML_CLASS} #ticket-app .ck .ck-toolbar__separator {
background-color: #3d4654 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-toolbar button,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-toolbar .ck-button {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel .ck-list,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel .ck-list__item {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel .ck-list-item-button,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel .ck-button.ck-list-item-button {
background-color: #1e2529 !important;
color: #c0c4cc !important;
border-color: transparent !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel .ck-list-item-button:hover,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel .ck-button.ck-list-item-button:hover,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel .ck-list-item-button.ck-on,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-dropdown__panel .ck-list-item-button[aria-checked="true"] {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-toolbar .ck-button:hover,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-toolbar .ck-splitbutton__action:hover,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-toolbar .ck-splitbutton__arrow:hover {
background-color: #252b33 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-button.ck-source-editing-button,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-button.ck-source-editing-button:hover,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-button.ck-source-editing-button:focus,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-button.ck-source-editing-button.ck-on,
html.${DARK_HTML_CLASS} #ticket-app .ck.ck-button.ck-source-editing-button.ck-off {
background-color: #252b33 !important;
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-select-dropdown,
html.${DARK_HTML_CLASS} #ticket-app .el-cascader__dropdown,
html.${DARK_HTML_CLASS} #ticket-app .el-select-dropdown__item,
html.${DARK_HTML_CLASS} #ticket-app .el-cascader-node {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-select-dropdown,
html.${DARK_HTML_CLASS} .el-cascader__dropdown,
html.${DARK_HTML_CLASS} .el-select-dropdown__item,
html.${DARK_HTML_CLASS} .el-cascader-node {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-cascader__suggestion-panel,
html.${DARK_HTML_CLASS} #ticket-app .el-cascader__suggestion-list,
html.${DARK_HTML_CLASS} #ticket-app .el-cascader__suggestion-item,
html.${DARK_HTML_CLASS} .el-cascader__suggestion-panel,
html.${DARK_HTML_CLASS} .el-cascader__suggestion-list,
html.${DARK_HTML_CLASS} .el-cascader__suggestion-item {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-cascader__suggestion-item:hover,
html.${DARK_HTML_CLASS} #ticket-app .el-cascader__suggestion-item:focus,
html.${DARK_HTML_CLASS} #ticket-app .el-cascader__suggestion-item.is-checked,
html.${DARK_HTML_CLASS} #ticket-app .el-cascader__suggestion-item.is-active,
html.${DARK_HTML_CLASS} .el-cascader__suggestion-item:hover,
html.${DARK_HTML_CLASS} .el-cascader__suggestion-item:focus,
html.${DARK_HTML_CLASS} .el-cascader__suggestion-item.is-checked,
html.${DARK_HTML_CLASS} .el-cascader__suggestion-item.is-active {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-scrollbar__view.el-select-dropdown__list,
html.${DARK_HTML_CLASS} #ticket-app .el-scrollbar__view.el-cascader-menu__list,
html.${DARK_HTML_CLASS} #ticket-app .el-cascader-menu {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-scrollbar__view.el-select-dropdown__list,
html.${DARK_HTML_CLASS} .el-scrollbar__view.el-cascader-menu__list,
html.${DARK_HTML_CLASS} .el-cascader-menu {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-select-dropdown__item:hover,
html.${DARK_HTML_CLASS} #ticket-app .el-select-dropdown__item.hover,
html.${DARK_HTML_CLASS} #ticket-app .el-select-dropdown__item.selected,
html.${DARK_HTML_CLASS} #ticket-app .el-select-dropdown__item.is-selected,
html.${DARK_HTML_CLASS} .el-select-dropdown__item:hover,
html.${DARK_HTML_CLASS} .el-select-dropdown__item.hover,
html.${DARK_HTML_CLASS} .el-select-dropdown__item.selected,
html.${DARK_HTML_CLASS} .el-select-dropdown__item.is-selected {
background-color: #2d333b !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item > *,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-cascader-menu__list .el-cascader-node,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-cascader-menu__list .el-cascader-node > * {
background-color: transparent !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item:hover,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item.hover,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item.selected,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item.is-selected,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item:hover > *,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item.hover > *,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item.selected > *,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item.is-selected > *,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-cascader-node:hover,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-cascader-node.in-active-path,
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-cascader-node.is-active {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item span[style*="color: rgb(0, 0, 0)"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item span[style*="color:#000"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-fields__field-input .el-select-dropdown__item span[style*="color: #000000"] {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-form-item__label {
color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .common-value,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list-column-title__container .ticket-list-column-title__icon,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list-column-title__post-count,
html.${DARK_HTML_CLASS} #ticket-app .user-color,
html.${DARK_HTML_CLASS} #ticket-app .user-color[data-v-e3f175be] {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list-popover__watching-btn {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list-column-title__info-button button,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list-column-title__info-button button i {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list_ticket-type-icon,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list-column-title__new-window button,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list-column-title__new-window button i,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list .ticket-list-column-title__new-window .icon-new-tab {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter.ticket-sidebar__filter_child {
border-color: #3d4654 !important;
border-radius: 14px !important;
overflow: hidden !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter.ticket-sidebar__filter_child:hover {
background-color: #252b33 !important;
border-radius: 14px !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button.router-link-exact-active,
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button.active {
background-color: #252b33 !important;
border-radius: 14px !important;
border: none !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button:hover {
border-radius: 14px !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-audit__item {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-audit__item:hover {
background-color: #252b33 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-audit__item::before,
html.${DARK_HTML_CLASS} #ticket-app .ticket-audit__item::after,
html.${DARK_HTML_CLASS} #ticket-app .ticket-audit__line,
html.${DARK_HTML_CLASS} #ticket-app .ticket-audit hr {
border-color: #3d4654 !important;
background-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-dialog {
background: #1e2529 !important;
border: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-dialog__title,
html.${DARK_HTML_CLASS} #ticket-app .el-dialog__body {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-dialog {
background: #1e2529 !important;
border: 1px solid #3d4654 !important;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45) !important;
}
html.${DARK_HTML_CLASS} .el-dialog__header,
html.${DARK_HTML_CLASS} .el-dialog__body,
html.${DARK_HTML_CLASS} .el-dialog__footer {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-dialog__header {
border-bottom: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-dialog__footer {
border-top: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-dialog__title,
html.${DARK_HTML_CLASS} .el-dialog__headerbtn .el-dialog__close,
html.${DARK_HTML_CLASS} .el-dialog__body,
html.${DARK_HTML_CLASS} .el-dialog__body * {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-dialog .el-dialog__headerbtn:hover .el-dialog__close {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .el-dialog table,
html.${DARK_HTML_CLASS} .el-dialog thead,
html.${DARK_HTML_CLASS} .el-dialog tbody,
html.${DARK_HTML_CLASS} .el-dialog tr,
html.${DARK_HTML_CLASS} .el-dialog th,
html.${DARK_HTML_CLASS} .el-dialog td {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-dialog thead th,
html.${DARK_HTML_CLASS} .el-dialog tr:first-child th,
html.${DARK_HTML_CLASS} .el-dialog tr:first-child td {
background-color: #252b33 !important;
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .el-dialog tbody tr:hover,
html.${DARK_HTML_CLASS} .el-dialog tbody tr:hover td {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .el-dialog input,
html.${DARK_HTML_CLASS} .el-dialog .el-input__inner,
html.${DARK_HTML_CLASS} .el-dialog .el-date-editor .el-input__inner {
background-color: #252b33 !important;
border-color: #3d4654 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .staffs-contact-columns__content,
html.${DARK_HTML_CLASS} .staffs-contact-columns__column,
html.${DARK_HTML_CLASS} .sortable__columns {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .staffs-contact-columns__column_heading,
html.${DARK_HTML_CLASS} .staffs-contact-columns__column_footer {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .staffs-contact-columns__order,
html.${DARK_HTML_CLASS} .staffs-contact-columns__value,
html.${DARK_HTML_CLASS} .staffs-contact-columns__width,
html.${DARK_HTML_CLASS} .staffs-contact-columns__delete {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .staffs-contact-columns__column .el-input-number,
html.${DARK_HTML_CLASS} .staffs-contact-columns__column .el-input-number .el-input__inner,
html.${DARK_HTML_CLASS} .staffs-contact-columns__column .el-input-number__decrease,
html.${DARK_HTML_CLASS} .staffs-contact-columns__column .el-input-number__increase {
background-color: #252b33 !important;
border-color: #3d4654 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .ticket-group-actions,
html.${DARK_HTML_CLASS} .ticket-group-actions .el-dialog__body,
html.${DARK_HTML_CLASS} .ticket-group-actions__content,
html.${DARK_HTML_CLASS} .ticket-group-actions__columns,
html.${DARK_HTML_CLASS} .ticket-group-actions__column,
html.${DARK_HTML_CLASS} .ticket-group-actions__footer,
html.${DARK_HTML_CLASS} .ticket-group-actions__upload-file,
html.${DARK_HTML_CLASS} .ticket-group-actions__send-mail,
html.${DARK_HTML_CLASS} .ticket-group-actions__submit-button {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .ticket-group-actions .ticket-group-actions__field-label,
html.${DARK_HTML_CLASS} .ticket-group-actions .ticket-group-actions__field-name {
color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} .ticket-group-actions .ticket-detail__tabs.el-tabs--card > .el-tabs__header,
html.${DARK_HTML_CLASS} .ticket-group-actions .ticket-detail__tabs.el-tabs--card > .el-tabs__header .el-tabs__nav,
html.${DARK_HTML_CLASS} .ticket-group-actions .ticket-detail__tabs.el-tabs--card > .el-tabs__header .el-tabs__item,
html.${DARK_HTML_CLASS} .ticket-group-actions .ticket-detail__tabs .el-tabs__content,
html.${DARK_HTML_CLASS} .ticket-group-actions .ticket-editor {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .ticket-group-actions .ticket-detail__tabs.el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .el-dialog {
background-color: #1e2529 !important;
border: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .el-dialog__header,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .el-dialog__body,
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .el-dialog__footer {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .ticket-list-columns__column {
background-color: #252b33 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .ticket-list-columns__column_heading {
background-color: transparent !important;
color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .el-button.el-button--default {
background-color: #2d333b !important;
color: #dcdfe6 !important;
border-color: #4a5563 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .el-button.el-button--default:hover {
background-color: #343b45 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-list-columns .el-button.el-button--primary {
color: #0f1419 !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list,
html.${DARK_HTML_CLASS} .ticket-filter-list__filter {
background: #1e2529 !important;
background-color: #1e2529 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list,
html.${DARK_HTML_CLASS} .ticket-filter-list__filter,
html.${DARK_HTML_CLASS} .ticket-filter-list__row,
html.${DARK_HTML_CLASS} .ticket-filter-list__row_heading,
html.${DARK_HTML_CLASS} .ticket-filter-list__col,
html.${DARK_HTML_CLASS} .ticket-filter-list__col_name,
html.${DARK_HTML_CLASS} .ticket-filter-list__col_filter-type,
html.${DARK_HTML_CLASS} .ticket-filter-list__col_activity {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list__row {
background: #252b33 !important;
background-color: #252b33 !important;
color: #dcdfe6 !important;
border-bottom-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list__row_heading {
background: #1e2529 !important;
background-color: #1e2529 !important;
color: #a8abb2 !important;
border-bottom-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list__row:hover {
background: #2d333b !important;
background-color: #2d333b !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list__col,
html.${DARK_HTML_CLASS} .ticket-filter-list__col_name,
html.${DARK_HTML_CLASS} .ticket-filter-list__col_filter-type,
html.${DARK_HTML_CLASS} .ticket-filter-list__col_activity {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list .ticket-filter-list__col,
html.${DARK_HTML_CLASS} .ticket-filter-list .ticket-filter-list__col * {
background-color: transparent !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list .el-button,
html.${DARK_HTML_CLASS} .ticket-filter-list button {
background-color: #2d333b !important;
color: #dcdfe6 !important;
border-color: #4a5563 !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list .el-button:hover,
html.${DARK_HTML_CLASS} .ticket-filter-list button:hover {
background-color: #343b45 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .ticket-filter-list .el-button--primary {
color: #0f1419 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket .ticket-user__field,
html.${DARK_HTML_CLASS} #ticket-app .ticket .ticket-user__fields,
html.${DARK_HTML_CLASS} #ticket-app .ticket .ticket-user__fields-heading,
html.${DARK_HTML_CLASS} #ticket-app .ticket-user__fields-heading {
border-bottom: none !important;
border-bottom-color: #1a1f26 !important;
background-color: #1a1f26 !important;
margin-bottom: 0 !important;
padding-bottom: 0 !important;
box-shadow: none !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-user__fields-heading::before,
html.${DARK_HTML_CLASS} #ticket-app .ticket-user__fields-heading::after {
border-bottom: none !important;
border-bottom-color: #1a1f26 !important;
background-color: #1a1f26 !important;
box-shadow: none !important;
content: none !important;
}
html.${DARK_HTML_CLASS} .el-message-box {
background: #1e2529 !important;
border: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-message-box__title,
html.${DARK_HTML_CLASS} .el-message-box__message,
html.${DARK_HTML_CLASS} .el-message-box__content,
html.${DARK_HTML_CLASS} .el-message-box__headerbtn .el-message-box__close {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-message-box__input input,
html.${DARK_HTML_CLASS} .el-message-box__input textarea {
background-color: #252b33 !important;
color: #e4e7ed !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-message-box .el-button.el-button--default,
html.${DARK_HTML_CLASS} .el-button.el-button--default.el-button--small {
background-color: #252b33 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-message-box .el-button.el-button--default:hover,
html.${DARK_HTML_CLASS} .el-button.el-button--default.el-button--small:hover {
background-color: #2d333b !important;
color: #e4e7ed !important;
border-color: #4a5563 !important;
}
html.${DARK_HTML_CLASS} .el-tag.el-tag--info,
html.${DARK_HTML_CLASS} .el-tag.el-tag--info.el-tag--mini,
html.${DARK_HTML_CLASS} .el-tag.el-tag--info.el-tag--mini.el-tag--light {
background-color: #252b33 !important;
color: #c0c4cc !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-picker-panel,
html.${DARK_HTML_CLASS} .el-date-picker,
html.${DARK_HTML_CLASS} .el-picker-panel__sidebar,
html.${DARK_HTML_CLASS} .el-date-picker__time-header,
html.${DARK_HTML_CLASS} .el-time-panel {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-picker-panel [class*="time"],
html.${DARK_HTML_CLASS} .el-picker-panel [class*="date"],
html.${DARK_HTML_CLASS} .el-picker-panel__content,
html.${DARK_HTML_CLASS} .el-date-table td,
html.${DARK_HTML_CLASS} .el-time-spinner__item {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-picker-panel__footer {
background-color: #1e2529 !important;
border-top-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-picker-panel__footer .el-button {
background-color: #252b33 !important;
color: #c0c4cc !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-picker-panel__footer .el-button.el-button--text {
background-color: transparent !important;
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .el-picker-panel__footer .el-button:hover {
background-color: #2d333b !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .el-time-spinner__item:hover,
html.${DARK_HTML_CLASS} .el-date-table td.available:hover {
background-color: #2d333b !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .profile,
html.${DARK_HTML_CLASS} .sidebar {
background-color: #1a1f26 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .profile * ,
html.${DARK_HTML_CLASS} .sidebar * {
color: inherit;
}
html.${DARK_HTML_CLASS} .el-plus-input__inner {
background-color: #252b33 !important;
color: #e4e7ed !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-plus-button,
html.${DARK_HTML_CLASS} .el-plus-radio-button__inner {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-plus-button:not(.el-plus-button--primary):not(.el-plus-button--danger) {
background-color: #252b33 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-plus-radio-button__inner {
background-color: #252b33 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-plus-button.el-plus-button--primary,
html.${DARK_HTML_CLASS} .el-plus-button.el-plus-button--danger,
html.${DARK_HTML_CLASS} .el-plus-radio-button.is-active .el-plus-radio-button__inner,
html.${DARK_HTML_CLASS} .el-plus-radio-button__orig-radio:checked + .el-plus-radio-button__inner {
color: #121418 !important;
font-weight: 600 !important;
}
html.${DARK_HTML_CLASS} .el-popover,
html.${DARK_HTML_CLASS} .el-popper,
html.${DARK_HTML_CLASS} .el-popconfirm {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-popover[x-placement^="top"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popover[x-placement^="bottom"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popover[x-placement^="left"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popover[x-placement^="right"] .popper__arrow {
border-top-color: #3d4654 !important;
border-bottom-color: #3d4654 !important;
border-left-color: #3d4654 !important;
border-right-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-popover .popper__arrow::after {
border-top-color: #1e2529 !important;
border-bottom-color: #1e2529 !important;
border-left-color: #1e2529 !important;
border-right-color: #1e2529 !important;
}
html.${DARK_HTML_CLASS} .el-popover[data-popper-placement^="top"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popover[data-popper-placement^="bottom"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popover[data-popper-placement^="left"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popover[data-popper-placement^="right"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popper[data-popper-placement^="top"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popper[data-popper-placement^="bottom"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popper[data-popper-placement^="left"] .popper__arrow,
html.${DARK_HTML_CLASS} .el-popper[data-popper-placement^="right"] .popper__arrow {
border-top-color: #3d4654 !important;
border-bottom-color: #3d4654 !important;
border-left-color: #3d4654 !important;
border-right-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-popper[data-popper-placement] .popper__arrow::after {
border-top-color: #1e2529 !important;
border-bottom-color: #1e2529 !important;
border-left-color: #1e2529 !important;
border-right-color: #1e2529 !important;
}
html.${DARK_HTML_CLASS} .ticket-tabs__close-all-popper,
html.${DARK_HTML_CLASS} .ticket-tabs__close-all-popper .el-popconfirm,
html.${DARK_HTML_CLASS} .ticket-tabs__close-all-popper .el-popconfirm__action {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .ticket-tabs__close-all-popper .el-popconfirm__action {
border-top: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-popconfirm__main,
html.${DARK_HTML_CLASS} .el-popconfirm__main *,
html.${DARK_HTML_CLASS} .el-popover *,
html.${DARK_HTML_CLASS} .el-popper * {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .el-upload-dragger {
background-color: #1e2529 !important;
color: #c0c4cc !important;
border: 1px dashed #3d4654 !important;
}
html.${DARK_HTML_CLASS} .el-upload-dragger:hover {
background-color: #252b33 !important;
border-color: #4a5563 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-card {
background: #1a1f26 !important;
border-color: #3d4654 !important;
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-tabs__item {
color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-tabs__item.is-active {
color: #7ec8e0 !important;
}
html.${DARK_HTML_CLASS} .el-tabs__item {
color: #a8abb2 !important;
opacity: 1 !important;
-webkit-text-fill-color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} .el-tabs__item.is-active {
color: #e4e7ed !important;
opacity: 1 !important;
-webkit-text-fill-color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__tabs.el-tabs--card > .el-tabs__header,
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__tabs.el-tabs--card > .el-tabs__header .el-tabs__nav,
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__tabs.el-tabs--card > .el-tabs__header .el-tabs__item {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__tabs.el-tabs--card > .el-tabs__header .el-tabs__item {
background-color: #1e2529 !important;
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__tabs.el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__tabs .el-tabs__nav-wrap::after {
background-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-pagination,
html.${DARK_HTML_CLASS} #ticket-app .el-pagination button,
html.${DARK_HTML_CLASS} #ticket-app .el-pager li {
color: #c0c4cc !important;
background: transparent !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-loading-mask {
background-color: rgba(18, 20, 24, 0.85) !important;
}
html.${DARK_HTML_CLASS} .dashboard,
html.${DARK_HTML_CLASS} .dashboard__header,
html.${DARK_HTML_CLASS} .dashboard-tabs,
html.${DARK_HTML_CLASS} .dashboard-overview,
html.${DARK_HTML_CLASS} .dashboard-overview__ticket-stats,
html.${DARK_HTML_CLASS} .dashboard-overview__filters,
html.${DARK_HTML_CLASS} .dashboard-overview__tickets-by-channels,
html.${DARK_HTML_CLASS} .dashboard-overview__chart,
html.${DARK_HTML_CLASS} .dashboard-overview__ticket-stats-chart {
background-color: #121418 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .dashboard .el-tabs__header,
html.${DARK_HTML_CLASS} .dashboard .el-tabs__nav-wrap::after,
html.${DARK_HTML_CLASS} .dashboard .el-tabs__item {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .dashboard .el-tabs__item {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .dashboard .el-tabs__item.is-active {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .dashboard .el-input__inner,
html.${DARK_HTML_CLASS} .dashboard .el-select .el-input__inner,
html.${DARK_HTML_CLASS} .dashboard .el-select__tags,
html.${DARK_HTML_CLASS} .dashboard .el-select-dropdown,
html.${DARK_HTML_CLASS} .dashboard .el-select-dropdown__item,
html.${DARK_HTML_CLASS} .dashboard .el-scrollbar__view.el-select-dropdown__list {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .dashboard .el-select-dropdown__item.hover,
html.${DARK_HTML_CLASS} .dashboard .el-select-dropdown__item:hover,
html.${DARK_HTML_CLASS} .dashboard .el-select-dropdown__item.selected,
html.${DARK_HTML_CLASS} .dashboard .el-select-dropdown__item.is-selected {
background-color: #252b33 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__header,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__row,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__column {
background-color: #121418 !important;
color: #c0c4cc !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:hover,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:focus,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:focus-within {
background-color: #252b33 !important;
}
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:hover .dashboard-staffs__column,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:focus .dashboard-staffs__column,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:focus-within .dashboard-staffs__column {
background-color: transparent !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:hover .el-button--text,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:hover .el-button--text span,
html.${DARK_HTML_CLASS} .dashboard .dashboard-staffs__content .dashboard-staffs__row:hover .el-button--text i {
color: #9fd3e4 !important;
}
html.${DARK_HTML_CLASS} .dashboard .dashboard-overview__ticket-stats-col[style] {
background: #1e2529 !important;
border-color: #3d4654 !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .dashboard .dashboard-overview__ticket-stats-col[style*="211, 22, 22"] {
background: #8f4a4a !important;
border-color: #8f4a4a !important;
color: #f2dede !important;
}
html.${DARK_HTML_CLASS} .dashboard .dashboard-overview__ticket-stats-label,
html.${DARK_HTML_CLASS} .dashboard .dashboard-overview__ticket-stats-value,
html.${DARK_HTML_CLASS} .dashboard [class*="dashboard-overview"] {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .dashboard [style*="z-index: 9999999"][style*="background-color: rgb(255, 255, 255)"] {
background-color: #1e2529 !important;
border-color: #3d4654 !important;
color: #dcdfe6 !important;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45) !important;
}
html.${DARK_HTML_CLASS} .dashboard [style*="z-index: 9999999"][style*="background-color: rgb(255, 255, 255)"] * {
color: #dcdfe6 !important;
}
html.${DARK_HTML_CLASS} .dashboard canvas {
filter: saturate(0.82) brightness(0.92);
}
html.${DARK_HTML_CLASS} .contact-users,
html.${DARK_HTML_CLASS} .contact-users__filters,
html.${DARK_HTML_CLASS} .contacts__content,
html.${DARK_HTML_CLASS} .contacts__row,
html.${DARK_HTML_CLASS} .contacts__column,
html.${DARK_HTML_CLASS} .contacts__row_header,
html.${DARK_HTML_CLASS} .pagination,
html.${DARK_HTML_CLASS} .pagination__total {
background-color: #121418 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-tabs__header,
html.${DARK_HTML_CLASS} .contact-users .el-tabs__nav-wrap::after,
html.${DARK_HTML_CLASS} .contact-users .el-tabs__item {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-tabs__item {
color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-tabs__item.is-active {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-tabs__nav .el-tabs__item.is-top,
html.${DARK_HTML_CLASS} .contact-users .el-tabs__nav .el-tabs__item[id^="tab-"] {
color: #a8abb2 !important;
opacity: 1 !important;
-webkit-text-fill-color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-tabs__nav .el-tabs__item.is-top.is-active,
html.${DARK_HTML_CLASS} .contact-users .el-tabs__nav .el-tabs__item[id^="tab-"].is-active {
color: #e4e7ed !important;
opacity: 1 !important;
-webkit-text-fill-color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .contact-users #tab-users,
html.${DARK_HTML_CLASS} .contact-users #tab-companies,
html.${DARK_HTML_CLASS} .contact-users #tab-equipments {
color: #a8abb2 !important;
opacity: 1 !important;
-webkit-text-fill-color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} .contact-users #tab-users.is-active,
html.${DARK_HTML_CLASS} .contact-users #tab-companies.is-active,
html.${DARK_HTML_CLASS} .contact-users #tab-equipments.is-active {
color: #e4e7ed !important;
opacity: 1 !important;
-webkit-text-fill-color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .contact-users__filters,
html.${DARK_HTML_CLASS} .contact-users__filters .el-row,
html.${DARK_HTML_CLASS} .contact-users__filters [class*="tabs"],
html.${DARK_HTML_CLASS} .contact-users__filters [class*="tabs"] * {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .contacts__row_header,
html.${DARK_HTML_CLASS} .contacts__row {
border-color: #2d333b !important;
}
html.${DARK_HTML_CLASS} .contacts__row:hover {
background-color: #1e2529 !important;
}
html.${DARK_HTML_CLASS} .contacts__column .el-button--text,
html.${DARK_HTML_CLASS} .contacts__column .el-button--text span,
html.${DARK_HTML_CLASS} .contacts__column .el-button--text i {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .contacts__column .el-button--text:hover,
html.${DARK_HTML_CLASS} .contacts__column .el-button--text:hover span,
html.${DARK_HTML_CLASS} .contacts__column .el-button--text:hover i {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-switch__core {
background-color: #252b33 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-switch.is-checked .el-switch__core {
background-color: #23869b !important;
border-color: #2da1b8 !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-pagination button,
html.${DARK_HTML_CLASS} .contact-users .el-pager li {
background-color: transparent !important;
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .contact-users .el-pager li.active {
color: #e4e7ed !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-editor__ckeditor .ck-content,
html.${DARK_HTML_CLASS} #ticket-app .ticket-editor__ckeditor_comment .ck-content {
background-color: #252b33 !important;
color: #e4e7ed !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-cc__copy-append,
html.${DARK_HTML_CLASS} #ticket-app .ticket-cc__copy-prepend {
background-color: #252b33 !important;
border-color: #3d4654 !important;
color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-button.ticket-editor__add-files-button,
html.${DARK_HTML_CLASS} #ticket-app .el-button.ticket-editor__add-files-button.el-button--text {
background-color: #1e2529 !important;
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .el-button.ticket-editor__add-files-button:hover {
background-color: #2d333b !important;
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-conversation__messages {
background-color: #1a1f26 !important;
}
html.${DARK_HTML_CLASS} .${DATE_SEP_CLASS} {
color: #8b9199 !important;
}
html.${DARK_HTML_CLASS} .${DATE_SEP_CLASS}::before,
html.${DARK_HTML_CLASS} .${DATE_SEP_CLASS}::after {
background: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .${TICKET_SEP_CLASS} {
color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} .${TICKET_SEP_CLASS}::after {
background: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .ticket-conversation__message_user .hde-name-inside {
color: #7ec8e0 !important;
}
html.${DARK_HTML_CLASS} .ticket-conversation__message_user .hde-time-inside {
color: #c0c4cc !important;
opacity: 0.75 !important;
}
html.${DARK_HTML_CLASS} .hde-tools-panel {
background: #1e2529 !important;
color: #e4e7ed !important;
box-shadow: 0 4px 20px rgba(0,0,0,0.55) !important;
border: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} .hde-tools-panel h3 {
color: #e4e7ed !important;
border-bottom-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .hde-tools-field input[type="number"],
html.${DARK_HTML_CLASS} .hde-tools-field select {
background: #252b33 !important;
color: #e4e7ed !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .hde-staffs-drawer__overlay {
background: rgba(15, 20, 25, 0.65) !important;
}
html.${DARK_HTML_CLASS} .hde-staffs-drawer__panel {
background: #1e2529 !important;
border-left-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .hde-staffs-drawer__header {
color: #e4e7ed !important;
border-bottom-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .hde-staffs-drawer__close {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} .hde-staffs-drawer__close:hover {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .hde-staffs-drawer__frame {
background: #1e2529 !important;
}
html.${DARK_HTML_CLASS} .hde-tools-link-btn {
color: #c0c4cc !important;
border: none !important;
background: transparent !important;
}
html.${DARK_HTML_CLASS} .hde-tools-link-btn:hover {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .hde-history-close {
background: #252b33 !important;
color: #c0c4cc !important;
border-bottom-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-tickets,
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-container,
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-ticket {
background-color: #1e2529 !important;
color: #dcdfe6 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-ticket_current {
background-color: #252b33 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-ticket a,
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-ticket i,
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-ticket span {
color: #c0c4cc !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-ticket [style*="color: #000000"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-detail__history-ticket [style*="color:#000000"] {
color: #e4e7ed !important;
}
`;
const DARK_THEME_CSS = [
DARK_THEME_CSS_BASE,
DARK_THEME_CSS_COMPONENTS
].join('\n\n');
function escapeRegExp(value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function remapCssColors(cssText, paletteMap) {
const entries = Object.entries(paletteMap).sort((a, b) => b[0].length - a[0].length);
let mapped = cssText;
entries.forEach(([from, to]) => {
mapped = mapped.replace(new RegExp(escapeRegExp(from), 'gi'), to);
});
return mapped;
}
function buildPinkThemeCssFromDark(darkCssText) {
const pinkPaletteMap = {
'#121418': '#fff6fa',
'#161a1f': '#fdeff5',
'#1a1f26': '#fbe9f2',
'#1e2529': '#f7e1ec',
'#1f2a32': '#f5dbe8',
'#252b33': '#f1d3e2',
'#2a3038': '#edcade',
'#2d333b': '#e8c1d7',
'#343b45': '#e3b8d0',
'#3d4654': '#d2a9bf',
'#4a5563': '#c699b1',
'#154a56': '#c08aa8',
'#1a5f6e': '#b97aa0',
'#5eb3c9': '#be6f9d',
'#7ec8e0': '#c57bab',
'#a6dff0': '#d58fbb',
'#23869b': '#b56794',
'#409eff': '#bf73a0',
'#0f1419': '#5f4352',
'#dcdfe6': '#6f4d60',
'#e4e7ed': '#5f4352',
'#e8eaed': '#674b5b',
'#f0f2f5': '#604453',
'#c0c4cc': '#7a5a6b',
'#a8abb2': '#8c6a7c',
'#e8f4f7': '#6a4e5d',
'#f0fafc': '#67495a',
'#e2e8f0': '#6f5162',
'#f5f7fa': '#f7e6ee',
'#ecf5ff': '#f3dbe7',
'#ebeef5': '#e8c8d9',
'#8f4a4a': '#b9748f',
'#000000': '#5f4352',
'#ffffff': '#5f4352',
'#fff': '#5f4352'
};
const prefixed = darkCssText.replaceAll(`html.${DARK_HTML_CLASS}`, `html.${PINK_HTML_CLASS}`);
return remapCssColors(prefixed, pinkPaletteMap);
}
function buildMidnightBlueThemeCssFromDark(darkCssText) {
const midnightPaletteMap = {
'#121418': '#0b1220',
'#161a1f': '#0f1728',
'#1a1f26': '#111c2f',
'#1e2529': '#15223a',
'#1f2a32': '#172843',
'#252b33': '#1b2d4a',
'#2a3038': '#203255',
'#2d333b': '#23395f',
'#343b45': '#29446f',
'#3d4654': '#33517f',
'#4a5563': '#3f6292',
'#154a56': '#1d416f',
'#1a5f6e': '#245082',
'#5eb3c9': '#74c2ff',
'#7ec8e0': '#8dd0ff',
'#a6dff0': '#b0e2ff',
'#23869b': '#4ea4df',
'#409eff': '#63b4ff',
'#0f1419': '#08101b',
'#dcdfe6': '#cfddf5',
'#e4e7ed': '#d8e4f8',
'#e8eaed': '#e2ebfa',
'#f0f2f5': '#edf4ff',
'#c0c4cc': '#a8b8d6',
'#a8abb2': '#94a4c2',
'#e8f4f7': '#deefff',
'#f0fafc': '#e8f6ff',
'#e2e8f0': '#d5e2f6',
'#f5f7fa': '#f3f8ff',
'#ecf5ff': '#e6f2ff',
'#ebeef5': '#e4ecfa',
'#8f4a4a': '#b87f7f',
'#000000': '#08101b',
'#ffffff': '#e2ebfa',
'#fff': '#e2ebfa'
};
const prefixed = darkCssText.replaceAll(`html.${DARK_HTML_CLASS}`, `html.${MIDNIGHT_BLUE_HTML_CLASS}`);
return remapCssColors(prefixed, midnightPaletteMap);
}
function buildTricolorThemeCssFromDark(darkCssText) {
const tricolorPaletteMap = {
'#121418': '#ffffff',
'#161a1f': '#f9fbff',
'#1a1f26': '#f6f9ff',
'#1e2529': '#f3f6fb',
'#1f2a32': '#f0f4fa',
'#252b33': '#eef2f8',
'#2a3038': '#e7edf6',
'#2d333b': '#e2e9f4',
'#343b45': '#d8e1ef',
'#3d4654': '#c3cfdf',
'#4a5563': '#acbdd4',
'#154a56': '#b63b47',
'#1a5f6e': '#c94956',
'#5eb3c9': '#d85a66',
'#7ec8e0': '#e06e79',
'#a6dff0': '#ea8992',
'#23869b': '#bf4854',
'#409eff': '#245fb3',
'#0f1419': '#1d3f73',
'#dcdfe6': '#294d82',
'#e4e7ed': '#1d3f73',
'#e8eaed': '#234678',
'#f0f2f5': '#1f4578',
'#c0c4cc': '#4d6793',
'#a8abb2': '#6379a1',
'#e8f4f7': '#23497d',
'#f0fafc': '#20477a',
'#e2e8f0': '#2f5386',
'#f5f7fa': '#ffffff',
'#ecf5ff': '#f4f7ff',
'#ebeef5': '#e7effc',
'#8f4a4a': '#c53b49',
'#000000': '#1d3f73',
'#ffffff': '#1d3f73',
'#fff': '#1d3f73'
};
const prefixed = darkCssText.replaceAll(`html.${DARK_HTML_CLASS}`, `html.${TRICOLOR_HTML_CLASS}`);
return remapCssColors(prefixed, tricolorPaletteMap);
}
function buildMonochromeThemeCssFromDark(darkCssText) {
const monochromePaletteMap = {
'#121418': '#0c0c0c',
'#161a1f': '#101010',
'#1a1f26': '#131313',
'#1e2529': '#171717',
'#1f2a32': '#1b1b1b',
'#252b33': '#212121',
'#2a3038': '#252525',
'#2d333b': '#292929',
'#343b45': '#303030',
'#3d4654': '#3a3a3a',
'#4a5563': '#474747',
'#154a56': '#525252',
'#1a5f6e': '#5f5f5f',
'#5eb3c9': '#808080',
'#7ec8e0': '#9a9a9a',
'#a6dff0': '#b3b3b3',
'#c0c4cc': '#c8c8c8',
'#dcdfe6': '#e5e5e5',
'#e4e7ed': '#f1f1f1',
'#ecf5ff': '#f6f6f6',
'#409eff': '#8b8b8b',
'#337ecc': '#767676',
'#67c23a': '#9f9f9f',
'#e6a23c': '#8b8b8b',
'#f56c6c': '#a9a9a9',
'#ff8f8f': '#c0c0c0',
'#87bdf2': '#9d9d9d'
};
const prefixed = darkCssText.replaceAll(`html.${DARK_HTML_CLASS}`, `html.${MONOCHROME_HTML_CLASS}`);
return remapCssColors(prefixed, monochromePaletteMap);
}
const PINK_THEME_CSS = buildPinkThemeCssFromDark(DARK_THEME_CSS);
const MIDNIGHT_BLUE_THEME_CSS = buildMidnightBlueThemeCssFromDark(DARK_THEME_CSS);
const TRICOLOR_THEME_CSS = buildTricolorThemeCssFromDark(DARK_THEME_CSS);
const MONOCHROME_THEME_CSS = buildMonochromeThemeCssFromDark(DARK_THEME_CSS);
const THEME_CSS = [DARK_THEME_CSS, PINK_THEME_CSS, MIDNIGHT_BLUE_THEME_CSS, TRICOLOR_THEME_CSS, MONOCHROME_THEME_CSS].join('\n\n');
function injectThemeStyles() {
const legacyStyle = document.getElementById(LEGACY_THEME_STYLE_ID);
if (legacyStyle) legacyStyle.remove();
const bootStyle = document.getElementById(THEME_BOOT_STYLE_ID);
if (document.getElementById(THEME_STYLE_ID)) {
if (bootStyle) bootStyle.remove();
return;
}
const style = document.createElement('style');
style.id = THEME_STYLE_ID;
style.textContent = THEME_CSS;
const target = document.head || document.documentElement;
if (!target) return;
target.appendChild(style);
if (bootStyle) bootStyle.remove();
}
// Подключаем полный набор CSS темы как можно раньше, чтобы убрать flash default-theme.
injectThemeStyles();
function applyThemeFromConfig() {
injectThemeStyles();
CONFIG.themeMode = normalizeThemeMode(CONFIG.themeMode);
applyThemeClass(CONFIG.themeMode);
}
// ─── Стили ──────────────────────────────────────────────
const CSS = `
/* ── Layout ── */
.ticket-conversation__message-block {
display: flex !important; flex-direction: row !important;
align-items: flex-start !important; gap: 8px !important;
flex: 1 !important; overflow: visible !important;
margin-left: 0px !important;
}
.ticket-conversation__message_user > .ticket-conversation__message-block,
.ticket-conversation__message_comment > .ticket-conversation__message-block {
justify-content: flex-start !important;
}
.ticket-conversation__message_staff > .ticket-conversation__message-block {
justify-content: flex-end !important;
}
/* Avatar */
.ticket-conversation__message-image {
width: 30px !important; height: 30px !important;
padding: 2px !important; border-radius: 50% !important;
}
.ticket-conversation__message-image_user,
.ticket-conversation__message-image_staff { margin-top: 0 !important; }
/* Bubble / text */
.ticket-conversation__message-text {
font-size: 13px !important; margin: 0 !important; padding: 0 !important;
}
.ticket-conversation__message-html {
padding: 6px 10px !important; min-height: auto !important;
position: relative !important;
border-radius: 12px !important;
}
/* Системные сообщения (уходят клиенту) */
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_staff.ticket-conversation__message-text_system > .ticket-conversation__message-html {
background: #4a5563 !important;
color: #f0f2f5 !important;
border: 1px solid rgba(0, 0, 0, 0.08) !important;
border-radius: 12px !important;
overflow: hidden !important;
}
/* Системные комментарии (внутренние) */
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_comment.ticket-conversation__message-text_system > .ticket-conversation__message-html {
background: #1a5f6e !important;
color: #f0fafc !important;
border: 1px solid rgba(0, 0, 0, 0.08) !important;
border-radius: 12px !important;
overflow: hidden !important;
}
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_system > .ticket-conversation__message-html a {
color: inherit !important;
}
/* У системных сообщений отключаем "хвосты"/углы базовой темы */
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_system {
border-radius: 12px !important;
background: transparent !important;
}
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_system > .ticket-conversation__message-html {
border-radius: 12px !important;
}
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_system::before,
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_system::after,
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_system > .ticket-conversation__message-html::before,
#ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_system > .ticket-conversation__message-html::after {
content: none !important;
display: none !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_staff.ticket-conversation__message-text_system > .ticket-conversation__message-html {
background: #4a5563 !important;
color: #f0f2f5 !important;
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_comment.ticket-conversation__message-text_system > .ticket-conversation__message-html {
background: #1a5f6e !important;
color: #f0fafc !important;
border-color: #154a56 !important;
}
html.${PINK_HTML_CLASS} #ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_staff.ticket-conversation__message-text_system > .ticket-conversation__message-html {
background: #d4b4c3 !important;
color: #5f4352 !important;
border-color: #c699b1 !important;
}
html.${PINK_HTML_CLASS} #ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_comment.ticket-conversation__message-text_system > .ticket-conversation__message-html {
background: #c08aa8 !important;
color: #67495a !important;
border-color: #b97aa0 !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_staff.ticket-conversation__message-text_system > .ticket-conversation__message-html {
background: #23395f !important;
color: #edf4ff !important;
border-color: #33517f !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket .ticket-conversation__message-text.ticket-conversation__message-text_comment.ticket-conversation__message-text_system > .ticket-conversation__message-html {
background: #245082 !important;
color: #e8f6ff !important;
border-color: #1d416f !important;
}
.ticket-conversation__message-html-emoji { font-size: 1.8rem !important; }
/* Meta — hidden (всё внутри пузыря теперь) */
.ticket-conversation__message-meta { display: none !important; }
/* Пагинация скрывается через отдельный стиль autoLoadHistory */
/* Имя в пузыре */
.hde-name-inside {
display: block !important; font-size: 12px !important;
font-weight: 600 !important; line-height: 1.2 !important;
margin-bottom: 2px !important;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif !important;
}
.ticket-conversation__message_user .hde-name-inside { text-align: left !important; color: #1a627a !important; }
.ticket-conversation__message_staff .hde-name-inside,
.ticket-conversation__message-text_post-own .hde-name-inside { color: #e0e0e0 !important; text-align: right !important; }
.ticket-conversation__message-text_system .hde-name-inside { color: rgba(255,255,255,0.7) !important; }
/* Время в пузыре */
.hde-time-inside {
display: block !important; clear: both !important;
font-size: 9px !important; line-height: 1 !important;
text-align: right !important; margin: 2px 0 0 0 !important;
padding: 0 4px 1px 0 !important;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif !important;
}
.ticket-conversation__message_user .hde-time-inside { color: #000000 !important; opacity: 0.4 !important; }
.ticket-conversation__message_staff .hde-time-inside,
.ticket-conversation__message-text_post-own .hde-time-inside { color: #fff !important; opacity: 0.5 !important; }
.hde-edited-inside {
display: none !important;
font-size: 9px !important;
line-height: 1 !important;
margin: 2px 0 0 0 !important;
padding: 0 0 1px 0 !important;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif !important;
opacity: 0.55 !important;
}
.ticket-conversation__message_user .hde-edited-inside { color: #000000 !important; }
.ticket-conversation__message_staff .hde-edited-inside,
.ticket-conversation__message-text_post-own .hde-edited-inside { color: #fff !important; }
.ticket-conversation__message-html:has(.hde-edited-inside) .hde-edited-inside {
display: inline-block !important;
float: left !important;
margin-right: 5px !important;
}
.ticket-conversation__message-html:has(.hde-edited-inside) .hde-time-inside {
display: inline-block !important;
float: right !important;
clear: none !important;
margin-top: 2px !important;
}
.ticket-conversation__message-updated { display: none !important; }
/* Padding bubble */
.ticket-conversation__message-html:has(.hde-time-inside) { padding-bottom: 3px !important; }
.ticket-conversation__message-html:has(.hde-name-inside) { padding-top: 4px !important; }
/* Кнопки действий */
.ticket-conversation__actions {
clear: none !important; padding: 0 !important; margin: 0 !important;
line-height: 1 !important; display: inline-flex !important;
align-items: center !important; opacity: 0 !important;
transition: opacity 0.15s ease !important;
}
.ticket-conversation__message:hover .ticket-conversation__actions { opacity: 1 !important; }
.hde-actions-row { display: inline-flex !important; flex-direction: row !important; align-items: center !important; gap: 6px !important; }
.ticket-conversation__actions-btn { display: inline-flex !important; align-items: center !important; gap: 2px !important; flex: none !important; }
.ticket-conversation__actions_button { padding: 2px 3px !important; margin: 0 1px !important; }
/* Лайки */
.ticket-conversation__like {
display: inline-flex !important; width: auto !important; font-size: 10px !important;
margin: 0 !important; padding: 0 !important; gap: 2px !important;
}
.ticket-conversation__like button { padding: 2px 3px !important; margin: 0 !important; font-size: 12px !important; }
/* Кнопки под пузырём (вынесены из meta) */
.hde-actions-inline {
display: inline-flex !important; flex-direction: row !important;
align-items: center !important; gap: 6px !important;
align-self: center !important;
opacity: 0 !important; transition: opacity 0.15s ease !important;
}
.ticket-conversation__message:hover .hde-actions-inline { opacity: 1 !important; }
.ticket-conversation__message.hde-has-reaction > .ticket-conversation__message-block > .hde-actions-inline {
opacity: 1 !important;
}
.ticket-conversation__message_user > .ticket-conversation__message-block > .hde-actions-inline,
.ticket-conversation__message_comment > .ticket-conversation__message-block > .hde-actions-inline {
justify-content: flex-end !important; order: 2 !important;
}
.ticket-conversation__message_staff > .ticket-conversation__message-block > .hde-actions-inline {
justify-content: flex-start !important; order: -1 !important;
}
/* Разделитель даты */
.${DATE_SEP_CLASS} {
display: block !important; width: 100% !important;
text-align: center !important; clear: both !important;
margin: 0 !important; padding: 0 !important;
font-size: 13px !important; line-height: 1.3 !important;
font-weight: 700 !important; letter-spacing: 1px !important;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif !important;
color: #999 !important; position: relative !important;
}
.${DATE_SEP_CLASS}::before,
.${DATE_SEP_CLASS}::after {
content: none !important;
display: none !important;
}
.${TICKET_SEP_CLASS} {
display: block !important;
width: 100% !important;
text-align: center !important;
margin: 16px 0 8px 0 !important;
font-size: 12px !important;
font-weight: 600 !important;
color: #7f8c8d !important;
font-family: inherit !important;
box-sizing: border-box !important;
line-height: 1.4 !important;
}
.${TICKET_SEP_CLASS}::after {
content: '' !important;
display: block !important;
width: 100% !important;
height: 1px !important;
background: #c8d6db !important;
margin-top: 5px !important;
}
/* Переопределяем line-height параграфов внутри пузырей */
#ticket-app .ticket .ticket-conversation__message-text p {
margin: 0 !important;
line-height: 1.1 !important;
}
/* Убираем разделитель/отступ у полей пользователя */
#ticket-app .ticket .ticket-user__field {
padding-bottom: 0 !important;
border-bottom: none !important;
margin-bottom: 0 !important;
}
#ticket-app .ticket .ticket-user__fields {
padding-bottom: 0 !important;
border-bottom: none !important;
}
#ticket-app .ticket .ticket-user__fields-heading {
border-bottom: none !important;
margin: 0 !important;
padding-bottom: 0 !important;
}
#ticket-app .ticket-user__fields-heading,
#ticket-app .ticket-user__fields-heading::before,
#ticket-app .ticket-user__fields-heading::after {
border-bottom: none !important;
margin-bottom: 0 !important;
box-shadow: none !important;
content: normal !important;
}
.ticket-tabs .ticket-tabs__more .ticket-tabs__more-close-all {
border-top: none !important;
}
.ticket-editor__with-borders {
border-top: none !important;
}
.el-tabs--card > .el-tabs__header .el-tabs__nav {
border-color: #e4e7ed !important;
}
/* Отступы между сообщениями */
.ticket-conversation__message { margin-top: 5px !important; }
.ticket-conversation__message:first-child { margin-top: 2px !important; }
`;
const PAGINATION_STYLE_ID = 'hde-pagination-hide-style';
const TICKET_SEP_STYLE_ID = 'hde-ticket-sep-style';
// ============================================================
// 3) Compact Chat Rendering
// ============================================================
function injectTicketSepStyle() {
if (document.getElementById(TICKET_SEP_STYLE_ID)) return;
const style = document.createElement('style');
style.id = TICKET_SEP_STYLE_ID;
style.textContent = `
.${TICKET_SEP_CLASS} {
display: block !important;
width: 100% !important;
text-align: center !important;
margin: 16px 0 8px 0 !important;
font-size: 12px !important;
font-weight: 600 !important;
color: #7f8c8d !important;
font-family: inherit !important;
box-sizing: border-box !important;
line-height: 1.4 !important;
}
.${TICKET_SEP_CLASS}::after {
content: '' !important;
display: block !important;
width: 100% !important;
height: 1px !important;
background: #c8d6db !important;
margin-top: 5px !important;
}
`;
document.head.appendChild(style);
}
function injectPaginationStyle() {
if (document.getElementById(PAGINATION_STYLE_ID)) return;
const style = document.createElement('style');
style.id = PAGINATION_STYLE_ID;
style.textContent = '.ticket-conversation__pagination { display: none !important; }';
document.head.appendChild(style);
}
function removePaginationStyle() {
const el = document.getElementById(PAGINATION_STYLE_ID);
if (el) el.remove();
}
function injectStyle() {
if (!CONFIG.compactChat) return;
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = CSS;
document.head.appendChild(style);
}
function removeCompactStyle() {
// Удаляем тег стилей
const styleEl = document.getElementById(STYLE_ID);
if (styleEl) styleEl.remove();
// Убираем разделители дат
document.querySelectorAll('.' + DATE_SEP_CLASS + ', .' + TICKET_SEP_CLASS).forEach(el => el.remove());
// Убираем вставленные элементы и классы с каждого сообщения
document.querySelectorAll('.' + DONE_CLASS).forEach(msgEl => {
msgEl.classList.remove(DONE_CLASS, 'hde-compact-grouped');
msgEl.querySelectorAll('.hde-name-inside, .hde-time-inside, .hde-edited-inside').forEach(el => el.remove());
// Возвращаем кнопки/лайки обратно в meta из hde-actions-inline
const block = msgEl.querySelector(MSG_BLOCK_SELECTOR);
const meta = block && block.querySelector(':scope > .ticket-conversation__message-meta');
const actionsInline = block && block.querySelector(':scope > .hde-actions-inline');
if (actionsInline && meta) {
Array.from(actionsInline.children).forEach(child => meta.appendChild(child));
actionsInline.remove();
}
});
}
function getMessageParts(msgEl) {
const block = msgEl.querySelector(MSG_BLOCK_SELECTOR);
if (!block) return { block: null, htmlEl: null };
return {
block,
htmlEl: block.querySelector(MSG_HTML_SELECTOR)
};
}
// ─── Парсинг мета ────────────────────────────────────────
function extractTime(metaText) {
const match = metaText.match(/(\d{1,2}:\d{2})\s*$/);
return match ? match[1] : metaText;
}
function extractName(metaText) {
return metaText.replace(/\s+\d{1,2}\.\d{2}\.?\d{0,4}\s+\d{1,2}:\d{2}\s*$/, '').trim();
}
function extractDate(metaText) {
const m = metaText.match(/(\d{1,2}\.\d{2}\.?\d{0,4})/);
if (!m) return null;
const parts = m[1].split('.');
const day = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10);
const year = parts.length > 2 ? parts[2] : new Date().getFullYear();
return String(day).padStart(2, '0') + '.' + String(month).padStart(2, '0') + '.' + year;
}
function extractEditedTime(msgEl) {
const updatedEl = msgEl.querySelector(':scope > .ticket-conversation__message-block .ticket-conversation__message-updated');
if (!updatedEl) return null;
const text = (updatedEl.textContent || '').trim();
const m = text.match(/(\d{1,2}:\d{2})\s*$/);
return m ? m[1] : null;
}
// ─── Работа с пузырями ───────────────────────────────────
function addInsideBubble(msgEl, className, textContent) {
const { htmlEl } = getMessageParts(msgEl);
if (!htmlEl) return null;
let el = htmlEl.querySelector(':scope > .' + className);
if (!el) {
el = document.createElement('span');
el.className = className;
if (className === 'hde-name-inside') {
htmlEl.insertBefore(el, htmlEl.firstChild);
} else {
htmlEl.appendChild(el);
}
}
el.textContent = textContent;
return el;
}
function removeFromBubble(msgEl, className) {
const { htmlEl } = getMessageParts(msgEl);
if (!htmlEl) return;
const el = htmlEl.querySelector(':scope > .' + className);
if (el) el.remove();
}
function getMessageMetaText(msgEl) {
const block = msgEl.querySelector(MSG_BLOCK_SELECTOR);
if (!block) return '';
const meta = block.querySelector(':scope > .ticket-conversation__message-meta');
if (!meta) return '';
if (!meta.dataset.origText) {
meta.dataset.origText = meta.textContent.trim();
}
return meta.dataset.origText || '';
}
function ensureActionsInlineContainer(block, meta) {
let actionsRow = meta.querySelector(':scope > .hde-actions-row')
|| block.querySelector(':scope > .hde-actions-inline');
if (!actionsRow) {
actionsRow = document.createElement('div');
actionsRow.className = 'hde-actions-inline';
block.appendChild(actionsRow);
return actionsRow;
}
if (!actionsRow.classList.contains('hde-actions-inline')) {
actionsRow.classList.remove('hde-actions-row');
actionsRow.classList.add('hde-actions-inline');
block.appendChild(actionsRow);
}
return actionsRow;
}
function hasReactionSelected(likeBlock) {
if (!likeBlock) return false;
if (!likeBlock.classList.contains('ticket-conversation__show-on-hover')) return true;
if (likeBlock.querySelector('.ticket-conversation__like-icon.active')) return true;
if (likeBlock.querySelector('[aria-pressed="true"]')) return true;
if (likeBlock.querySelector('.active, .is-active, .selected, [class*="active"], [class*="selected"]')) return true;
const text = (likeBlock.textContent || '').trim();
if (!text) return false;
const nums = text.match(/\d+/g);
if (!nums) return false;
return nums.some(function (n) { return parseInt(n, 10) > 0; });
}
function getLikeBlock(msgEl) {
return msgEl.querySelector(':scope > .ticket-conversation__message-user-block > .ticket-conversation__like')
|| msgEl.querySelector(':scope > .ticket-conversation__message-block > .hde-actions-inline > .ticket-conversation__like');
}
// ─── Обработка одного сообщения ─────────────────────────
function processMessage(msgEl, isGrouped) {
const origMetaText = getMessageMetaText(msgEl);
const editedTime = extractEditedTime(msgEl);
const expectedEditedText = editedTime ? ('Изм. ' + editedTime) : '';
const currentEditedTextEl = msgEl.querySelector(':scope > .ticket-conversation__message-block > .ticket-conversation__message-text > .ticket-conversation__message-html > .hde-edited-inside');
const currentEditedText = currentEditedTextEl ? (currentEditedTextEl.textContent || '').trim() : '';
const editedIsSynced = expectedEditedText === currentEditedText;
const currentLikeBlock = getLikeBlock(msgEl);
const reactionShouldBeVisible = hasReactionSelected(currentLikeBlock);
const reactionVisibilitySynced = msgEl.classList.contains('hde-has-reaction') === reactionShouldBeVisible;
// Пропускаем уже обработанные, если grouped-статус не изменился
const wasGrouped = msgEl.classList.contains('hde-compact-grouped');
if (msgEl.classList.contains(DONE_CLASS) && wasGrouped === isGrouped && editedIsSynced && reactionVisibilitySynced) {
return extractDate(origMetaText);
}
msgEl.classList.add(DONE_CLASS);
const block = msgEl.querySelector(MSG_BLOCK_SELECTOR);
if (!block) return;
const meta = block.querySelector(':scope > .ticket-conversation__message-meta');
const actions = block.querySelector(':scope > .ticket-conversation__actions');
const userBlock = msgEl.querySelector(':scope > .ticket-conversation__message-user-block');
const likeBlock = currentLikeBlock || getLikeBlock(msgEl);
const orig = origMetaText;
// Кнопки + лайки → выносим из meta в block
if (meta && (actions || likeBlock)) {
const actionsRow = ensureActionsInlineContainer(block, meta);
if (actions && actions.parentElement !== actionsRow && actions.parentElement === block) {
actionsRow.appendChild(actions);
}
if (likeBlock && likeBlock.parentElement !== actionsRow && likeBlock.parentElement === userBlock) {
actionsRow.appendChild(likeBlock);
}
}
msgEl.classList.toggle('hde-has-reaction', hasReactionSelected(likeBlock));
if (isGrouped) {
msgEl.classList.add('hde-compact-grouped');
addInsideBubble(msgEl, 'hde-time-inside', extractTime(orig));
if (editedTime) {
addInsideBubble(msgEl, 'hde-edited-inside', 'Изм. ' + editedTime);
} else {
removeFromBubble(msgEl, 'hde-edited-inside');
}
removeFromBubble(msgEl, 'hde-name-inside');
} else {
msgEl.classList.remove('hde-compact-grouped');
addInsideBubble(msgEl, 'hde-name-inside', extractName(orig));
addInsideBubble(msgEl, 'hde-time-inside', extractTime(orig));
if (editedTime) {
addInsideBubble(msgEl, 'hde-edited-inside', 'Изм. ' + editedTime);
} else {
removeFromBubble(msgEl, 'hde-edited-inside');
}
}
return extractDate(orig);
}
function insertDateSeparator(parent, dateStr, beforeEl) {
const sep = document.createElement('span');
sep.className = DATE_SEP_CLASS;
sep.textContent = dateStr;
parent.insertBefore(sep, beforeEl || null);
return sep;
}
function insertTicketSeparator(parent, label, beforeEl) {
const sep = document.createElement('span');
sep.className = TICKET_SEP_CLASS;
sep.textContent = label;
parent.insertBefore(sep, beforeEl || null);
return sep;
}
function clearPreviousDialogsFromView() {
const container = getMessagesContainer();
if (!container) return;
container.querySelectorAll('[data-hde-history-ticket-id]').forEach(el => el.remove());
container.querySelectorAll('.' + TICKET_SEP_CLASS).forEach(el => el.remove());
processAllMessages();
}
function getPreviousDialogsLimit() {
const n = parseInt(CONFIG.previousDialogsLimit, 10);
if (!Number.isFinite(n)) return DEFAULT_PREVIOUS_TICKETS_TO_LOAD;
return Math.min(10, Math.max(1, n));
}
// ─── Контейнеры ──────────────────────────────────────────
function getMessagesContainer() {
return document.querySelector('.ticket-conversation__messages');
}
function getTicketId() {
const m = location.pathname.match(/\/ticket\/(\d+)/);
return m ? parseInt(m[1], 10) : null;
}
// ─── Обработка всех сообщений ────────────────────────────
var _lastProcessedHash = ''; // хеш для пропуска идентичных прогонов
function processAllMessages() {
if (!CONFIG.compactChat) return;
const container = getMessagesContainer();
if (!container) return;
// Быстрый хеш: кол-во сообщений + их data-user-id + даты из meta
const msgs = container.querySelectorAll('.ticket-conversation__message');
let hash = msgs.length + '|';
msgs.forEach(function (m) { hash += (m.getAttribute('data-user-id') || '') + ','; });
// Добавляем текстовые даты из meta
const metas = container.querySelectorAll('.ticket-conversation__message-meta');
metas.forEach(function (m) { hash += (m.textContent || '').trim() + ';'; });
const updated = container.querySelectorAll('.ticket-conversation__message-updated');
updated.forEach(function (u) { hash += (u.textContent || '').trim() + ';'; });
const likes = container.querySelectorAll('.ticket-conversation__like');
likes.forEach(function (likeEl) { hash += (likeEl.textContent || '').trim() + ';'; });
const reactionButtons = container.querySelectorAll('.ticket-conversation__like button');
reactionButtons.forEach(function (btn) {
hash += (btn.className || '') + '|';
hash += (btn.getAttribute('aria-pressed') || '') + ';';
});
if (hash === _lastProcessedHash) return; // ничего не изменилось — пропускаем
_lastProcessedHash = hash;
container.querySelectorAll('.' + DATE_SEP_CLASS).forEach(el => el.remove());
const messages = container.querySelectorAll('.ticket-conversation__message');
let lastUserId = null;
let lastDate = null;
messages.forEach((msgEl) => {
const userId = msgEl.getAttribute('data-user-id') || '-1';
const isComment = msgEl.classList.contains('ticket-conversation__message_comment');
const isSystem = userId === '-1' || userId === '-0' || userId === '-2';
const isSameUser = (userId === lastUserId && !isSystem && !isComment);
const msgDate = processMessage(msgEl, isSameUser);
if (msgDate && msgDate !== lastDate) {
insertDateSeparator(container, msgDate, msgEl);
lastDate = msgDate;
} else if (msgDate) {
lastDate = msgDate;
}
lastUserId = userId;
});
}
// ═══════════════════════════════════════════════════════════
// БЕСКОНЕЧНЫЙ СКРОЛЛ (загрузка старых страниц)
// ═══════════════════════════════════════════════════════════
const MAX_PAGES_TO_LOAD = 10;
let _loadingOlder = false;
let _loadedPages = new Set();
let _totalPages = 1;
let _currentTicketId = null;
let _historyLoadedForTicketId = null;
/** Строит DOM-элемент сообщения из данных API (копируя реальную структуру HDE) */
function buildMessageDOM(msg, users) {
const user = users[String(msg.userId)] || {};
const userName = user.name || 'Unknown';
const userType = user.type || 'staff';
const userImg = user.image || '';
let typeClass = '_staff'; // default
if (userType === 'user' || userType === 'client') typeClass = '_user';
if (msg.type === 'comment' || msg.type === 'note') typeClass = '_comment';
if (msg.userId == -1 || msg.userId == -2) typeClass = '_comment'; // Система
// Основной контейнер сообщения
const msgEl = document.createElement('div');
msgEl.setAttribute('data-v-1638218a', '');
msgEl.setAttribute('data-v-4a60d6b5', '');
msgEl.setAttribute('data-user-id', msg.userId);
// HDE использует data-post-id или data-comment-id
if (typeClass === '_comment') {
msgEl.setAttribute('data-comment-id', msg.id);
} else {
msgEl.setAttribute('data-post-id', msg.id);
}
msgEl.className = 'ticket-conversation__message ticket-conversation__message' + typeClass;
// ── user-block (аватар) ──
const userBlock = document.createElement('div');
userBlock.setAttribute('data-v-1638218a', '');
userBlock.setAttribute('data-v-169b4e77', '');
userBlock.className = 'ticket-conversation__message-user-block';
// Аватар — div с background-image (как в оригинале)
const avatarDiv = document.createElement('div');
avatarDiv.className = 'ticket-conversation__message-image ticket-conversation__message-image' + typeClass;
avatarDiv.style.cssText = 'background-image: url("' + userImg + '"); cursor: pointer;';
userBlock.appendChild(avatarDiv);
// ── message-block ──
const block = document.createElement('div');
block.className = 'ticket-conversation__message-block';
// Meta (скрытая CSS)
const meta = document.createElement('div');
meta.className = 'ticket-conversation__message-meta ticket-conversation__message-meta' + typeClass;
meta.textContent = ' ' + userName + ' ' + msg.createdAt + ' ';
meta.dataset.origText = userName + ' ' + msg.createdAt;
block.appendChild(meta);
// Text wrapper
const textWrap = document.createElement('div');
textWrap.className = 'ticket-conversation__message-text ticket-conversation__message-text' + typeClass;
if (typeClass === '_comment') {
textWrap.classList.add('ticket-conversation__message-text_system');
}
// HTML bubble
const htmlDiv = document.createElement('div');
htmlDiv.className = 'ticket-conversation__message-html';
htmlDiv.innerHTML = msg.text || '';
// Файлы — миниатюры если есть
if (msg.files && msg.files.length > 0) {
msg.files.forEach((file) => {
const fileLink = document.createElement('div');
fileLink.style.cssText = 'margin-top:4px;';
if (file.previewType === 'image' || file.icon === 'icon-image') {
const img = document.createElement('img');
img.src = file.thumb || file.preview;
img.style.cssText = 'max-width:200px;max-height:150px;border-radius:4px;cursor:pointer;';
img.onclick = () => window.open(file.download || file.preview, '_blank');
fileLink.appendChild(img);
} else {
const a = document.createElement('a');
a.href = file.download;
a.target = '_blank';
a.textContent = file.name || 'Файл';
a.style.cssText = 'color:#23869b;font-size:11px;text-decoration:none;display:inline-flex;align-items:center;gap:4px;';
fileLink.appendChild(a);
}
htmlDiv.appendChild(fileLink);
});
}
textWrap.appendChild(htmlDiv);
block.appendChild(textWrap);
// ★ v12.9: Аватар ВСЕГДА перед block — одинаковый порядок для обоих типов!
// Оригинальный HDE DOM: msgEl > [userBlock-avatar, block-bubble]
// Позиционирование через CSS flex-direction на самом msgEl:
// _staff: row-reverse → [avatar|block] рендерится как [block | avatar] = справа ✅
// _user: row → [avatar|block] рендерится как [avatar | block] = слева ✅
msgEl.appendChild(userBlock); // аватар
msgEl.appendChild(block); // пузырь (всегда после аватара в DOM)
return msgEl;
}
/** Находит первый видимый элемент сообщения (для якоря скролла) */
function findFirstVisibleMessage(container) {
var msgs = container.querySelectorAll(':scope > .ticket-conversation__message');
var ctTop = container.getBoundingClientRect().top;
for (var i = 0; i < msgs.length; i++) {
var r = msgs[i].getBoundingClientRect();
if (r.top - ctTop >= -50) return msgs[i];
}
return null;
}
/** Загружает страницу старых сообщений через API HDE */
async function loadOlderPage(pageNum) {
debugLog('[HDE Compact] → loadOlderPage(' + pageNum + ') start');
var ticketId = getTicketId();
if (!ticketId) { console.warn('[HDE Compact] нет ticketId'); return; }
_loadingOlder = true;
showLoadIndicator();
try {
var url = '/ru/ticket/data/conversation/id/' + ticketId + '/?page=' + pageNum;
debugLog('[HDE Compact] fetch:', url);
var resp = await fetch(url, {
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
});
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
debugLog('[HDE Compact] ответ: messages=', data.messages?.length, 'pages=', data.pagination?.totalPages);
if (data.pagination) {
_totalPages = data.pagination.totalPages;
} else if (data.messages && data.messages.length > 0) {
// Если пагинации нет в ответе но сообщения есть — продолжаем пробовать
if (pageNum >= _totalPages) _totalPages = pageNum + 1;
}
if (!data.messages || data.messages.length === 0) { hideLoadIndicator(); return; }
var container = getMessagesContainer();
if (!container) return;
// Якорь скролла
var anchorMsg = findFirstVisibleMessage(container);
var anchorOffset = anchorMsg ? anchorMsg.getBoundingClientRect().top - container.getBoundingClientRect().top : 0;
// ★ Вставляем перед самым первым элементом контейнера — выше всех уже загруженных
var firstExisting = container.firstChild;
// ★ Вставка: API [новее→старее]; insertBefore перед одной refNode
// каждый новый толкает предыдущие вниз → итог [старее...новее | существующие]
for (var mi = 0; mi < data.messages.length; mi++) {
var msg = data.messages[mi];
var eid = (msg.type === 'comment' || msg.type === 'note' || msg.userId == -1 || msg.userId == -2)
? '[data-comment-id="' + msg.id + '"]'
: '[data-post-id="' + msg.id + '"]';
if (container.querySelector(eid)) continue;
var el = buildMessageDOM(msg, data.users || {});
el.setAttribute('data-hde-loaded', 'true');
container.insertBefore(el, firstExisting);
}
debugLog('[HDE Compact] сообщения вставлены');
// ★ Перемещаем кнопку истории наверх если есть
var histBtn = container.querySelector(':scope > .ticket-detail__history');
if (histBtn && firstExisting) {
container.insertBefore(histBtn, container.firstChild);
}
// Компенсация
requestAnimationFrame(function () {
if (anchorMsg && anchorMsg.parentElement) {
var nOff = anchorMsg.getBoundingClientRect().top - container.getBoundingClientRect().top;
container.scrollTop += (nOff - anchorOffset);
}
});
processAllMessages();
_loadedPages.add(pageNum);
debugLog('[HDE Compact] ← loadOk page=' + pageNum + ' total=' + _totalPages);
// Автозагрузка следующей страницы цепочкой (не более MAX_PAGES_TO_LOAD)
var nextP = pageNum + 1;
var loadedCount = _loadedPages.size - 1; // минус изначальная страница
var shouldAuto = !_loadedPages.has(nextP)
&& (nextP <= _totalPages || data.messages.length > 0)
&& loadedCount < MAX_PAGES_TO_LOAD;
if (shouldAuto) {
debugLog('[HDE Compact] автозагрузка page=' + nextP + ' (загружено ' + loadedCount + '/' + MAX_PAGES_TO_LOAD + ')');
setTimeout(function () { loadOlderPage(nextP); }, 300);
} else if (loadedCount >= MAX_PAGES_TO_LOAD) {
debugLog('[HDE Compact] лимит ' + MAX_PAGES_TO_LOAD + ' страниц достигнут, остановка');
hideLoadIndicator();
}
} catch (err) {
console.warn('[HDE Compact] Ошибка загрузки:', err);
console.warn('[HDE Compact] ошибка загрузки:', err.message);
} finally {
_loadingOlder = false;
hideLoadIndicator();
}
}
function showLoadIndicator() {}
function hideLoadIndicator() {}
// ============================================================
// 4) History / Previous Dialogs Loader
// ============================================================
async function fetchHistoryTickets(ticketId) {
const url = '/ru/ticket/data/history/id/' + ticketId + '/?page=1';
const resp = await fetch(url, {
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
});
if (!resp.ok) throw new Error('History HTTP ' + resp.status);
return resp.json();
}
async function fetchConversationPage(ticketId, pageNum) {
const url = '/ru/ticket/data/conversation/id/' + ticketId + '/?page=' + pageNum;
const resp = await fetch(url, {
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
});
const finalUrl = String(resp.url || '');
if (!resp.ok || /captcha|recaptcha|hcaptcha/i.test(finalUrl)) {
const err = new Error('Conversation HTTP ' + resp.status);
err.status = Number(resp.status || 0);
err.url = finalUrl;
throw err;
}
return resp.json();
}
function getMessageSelectorByPayload(msg) {
const isComment = msg.type === 'comment' || msg.type === 'note' || msg.userId == -1 || msg.userId == -2;
return isComment ? '[data-comment-id="' + msg.id + '"]' : '[data-post-id="' + msg.id + '"]';
}
/** Первая нода «текущего» чата: не разделители и не подгруженные прошлые обращения */
function getPreviousDialogsInsertAnchor(container) {
for (let c = container.firstChild; c; c = c.nextSibling) {
if (c.nodeType !== 1) continue;
if (c.classList && c.classList.contains('ticket-conversation__message')) {
if (!c.hasAttribute('data-hde-history-ticket-id')) return c;
}
}
return container.firstChild;
}
function captureScrollAnchor(container) {
if (!container) return null;
const anchorEl = findFirstVisibleMessage(container);
if (!anchorEl) {
return { el: null, offsetTop: 0, scrollTop: container.scrollTop };
}
return {
el: anchorEl,
offsetTop: anchorEl.getBoundingClientRect().top - container.getBoundingClientRect().top,
scrollTop: container.scrollTop
};
}
function restoreScrollAnchor(container, anchor) {
if (!container || !anchor) return;
if (anchor.el && anchor.el.parentElement) {
const nextOffsetTop = anchor.el.getBoundingClientRect().top - container.getBoundingClientRect().top;
container.scrollTop += (nextOffsetTop - anchor.offsetTop);
return;
}
if (Number.isFinite(anchor.scrollTop)) {
container.scrollTop = anchor.scrollTop;
}
}
async function loadPreviousTicketsHistory() {
if (!CONFIG.autoLoadHistory || !CONFIG.autoLoadPreviousDialogs) return;
injectTicketSepStyle();
const currentTicketId = getTicketId();
if (!currentTicketId || _historyLoadedForTicketId === currentTicketId) return;
const container = getMessagesContainer();
if (!container) return;
const scrollAnchor = captureScrollAnchor(container);
try {
const historyItems = await fetchHistoryTickets(currentTicketId);
if (!Array.isArray(historyItems) || historyItems.length === 0) {
_historyLoadedForTicketId = currentTicketId;
return;
}
// Берём N самых свежих прошлых диалогов.
// Независимо от порядка в ответе API — сортируем по id (старше = меньший id)
// и берём последние N (самые новые).
const candidates = historyItems
.filter(item => item && item.id && item.id !== currentTicketId)
.filter(item => item.watchAccess !== false)
.sort((a, b) => parseInt(a.id, 10) - parseInt(b.id, 10))
.slice(-getPreviousDialogsLimit());
// Якорь — первое сообщение текущего тикета (не кнопка истории / не hde-date-separator).
// Иначе insertBefore(..., firstChild) вешает прошлые диалоги не туда и порядок «ломается».
const insertAnchor = getPreviousDialogsInsertAnchor(container);
if (!insertAnchor) {
_historyLoadedForTicketId = currentTicketId;
return;
}
// Итерируем от самого НОВОГО к самому СТАРОМУ.
// Каждый блок вставляется перед одним и тем же insertAnchor (выше предыдущих вставок).
// Итог (сверху вниз): [самый старый прошлый] … [самый новый прошлый] [текущий тикет]
for (let i = candidates.length - 1; i >= 0; i--) {
const t = candidates[i];
let allMessages = [];
let usersMap = {};
let pagesToRead = 1;
try {
const firstPage = await fetchConversationPage(t.id, 1);
usersMap = Object.assign({}, firstPage.users || {});
if (Array.isArray(firstPage.messages)) {
allMessages = allMessages.concat(firstPage.messages);
}
const totalPages = firstPage.pagination?.totalPages || 1;
pagesToRead = Math.min(totalPages, MAX_PAGES_PER_PREVIOUS_TICKET);
} catch (e) {
continue;
}
for (let p = 2; p <= pagesToRead; p++) {
try {
const pageData = await fetchConversationPage(t.id, p);
usersMap = Object.assign(usersMap, pageData.users || {});
if (Array.isArray(pageData.messages)) {
allMessages = allMessages.concat(pageData.messages);
}
} catch (e) {
break;
}
}
if (allMessages.length === 0) continue;
// API отдаёт [новее -> старее], приводим к [старее -> новее]
allMessages.reverse();
const insertBefore = insertAnchor;
let insertedCount = 0;
// Дата создания тикета = createdAt самого первого сообщения.
// Страницы грузятся [новее→старее], поэтому самое старое — на последней странице,
// последний элемент массива на ней (до reverse). После всех загрузок и reverse
// это allMessages[0] — но только если мы загрузили последнюю страницу.
// Если totalPages > pagesToRead — загружаем последнюю страницу отдельно только ради даты.
let ticketDateStr = '';
try {
const totalPagesForDate = (await fetchConversationPage(t.id, 1)).pagination?.totalPages || 1;
const lastPageData = totalPagesForDate > 1 ? await fetchConversationPage(t.id, totalPagesForDate) : null;
const oldestMsg = lastPageData && Array.isArray(lastPageData.messages) && lastPageData.messages.length > 0
? lastPageData.messages[0] // первый на последней странице = самый старый (API [новее→старее])
: (allMessages.length > 0 ? allMessages[0] : null); // fallback: первый после reverse
if (oldestMsg && oldestMsg.createdAt) {
const raw = String(oldestMsg.createdAt);
const m = raw.match(/(\d{1,2}\.\d{2}\.\d{2,4})\s+(\d{1,2}:\d{2})/);
if (m) ticketDateStr = ' · ' + m[1] + ' ' + m[2];
else { const d = raw.match(/(\d{1,2}\.\d{2}\.\d{2,4})/); if (d) ticketDateStr = ' · ' + d[1]; }
}
} catch (e) { /* дата не критична */ }
const sep = document.createElement('span');
sep.className = TICKET_SEP_CLASS;
const sepLink = document.createElement('a');
// URL формат: /ru/ticket/list/filter/id/CURRENT/ticket/TARGET
const _base = location.pathname.match(/(\/ru\/ticket\/list\/filter\/id\/\d+)/);
const _ticketBase = _base ? _base[1] : '/ru/ticket/list';
sepLink.href = _ticketBase + '/ticket/' + t.id;
sepLink.target = '_blank';
sepLink.rel = 'noopener noreferrer';
sepLink.textContent = '#' + (t.uniqueId || t.id);
sepLink.style.cssText = 'color: inherit; text-decoration: none; border-bottom: 1px dashed currentColor;';
sepLink.addEventListener('mouseover', function () { sepLink.style.opacity = '0.7'; });
sepLink.addEventListener('mouseout', function () { sepLink.style.opacity = '1'; });
sep.appendChild(document.createTextNode('Обращение '));
sep.appendChild(sepLink);
sep.appendChild(document.createTextNode(ticketDateStr));
container.insertBefore(sep, insertBefore);
// Затем вставляем сообщения в обратном порядке (от новых к старым),
// чтобы после всех insertBefore они легли [старые → новые]
for (let mi = allMessages.length - 1; mi >= 0; mi--) {
const msg = allMessages[mi];
const selector = getMessageSelectorByPayload(msg);
if (container.querySelector(selector)) continue;
const el = buildMessageDOM(msg, usersMap);
el.setAttribute('data-hde-history-ticket-id', String(t.id));
container.insertBefore(el, sep);
insertedCount++;
}
// Если ничего не вставили — убираем сепаратор
if (insertedCount === 0) sep.remove();
else restoreScrollAnchor(container, scrollAnchor);
}
_historyLoadedForTicketId = currentTicketId;
processAllMessages();
requestAnimationFrame(() => {
restoreScrollAnchor(container, scrollAnchor);
});
} catch (err) {
console.warn('[HDE Compact] Ошибка подгрузки прошлых обращений:', err);
}
}
/** Запускает цепочку загрузки страниц начиная с nextPage, но не более MAX_PAGES_TO_LOAD */
function startLoadChain() {
var ticketId = getTicketId();
if (!ticketId) return;
// Сброс если сменился тикет
if (ticketId !== _currentTicketId) {
_currentTicketId = ticketId;
_loadedPages.clear();
var curPageMatch = location.pathname.match(/\/page\/(\d+)/);
var curPage = curPageMatch ? parseInt(curPageMatch[1], 10) : 1;
for (var i = 1; i <= curPage; i++) _loadedPages.add(i);
}
// Ищем первую незагруженную страницу
var nextPage = 1;
while (_loadedPages.has(nextPage)) nextPage++;
// Сколько страниц уже догружено (не считая изначально открытую)
var alreadyLoaded = _loadedPages.size - 1; // минус текущая страница
if (alreadyLoaded >= MAX_PAGES_TO_LOAD) {
debugLog('[HDE Compact] достигнут лимит ' + MAX_PAGES_TO_LOAD + ' страниц');
return;
}
if (nextPage <= _totalPages || _totalPages <= 1) {
loadOlderPage(nextPage);
}
}
/** Парсим totalPages из пагинации на странице */
function detectTotalPages() {
if (!CONFIG.autoLoadHistory) return;
var pagerItems = document.querySelectorAll('.el-pager .number');
var maxPage = 1;
pagerItems.forEach(function (el) {
var n = parseInt(el.textContent.trim(), 10);
if (n && n > maxPage && n < 1000) maxPage = n;
});
if (maxPage > 1) _totalPages = maxPage;
}
function initAutoLoad() {
if (!CONFIG.autoLoadHistory && !CONFIG.autoLoadPreviousDialogs) return;
_currentTicketId = getTicketId();
_loadedPages.clear();
// Фиксируем текущую страницу как уже загруженную
var curPageMatch = location.pathname.match(/\/page\/(\d+)/);
var curPage = curPageMatch ? parseInt(curPageMatch[1], 10) : 1;
for (var i = 1; i <= curPage; i++) _loadedPages.add(i);
if (CONFIG.autoLoadHistory) detectTotalPages();
// Запускаем цепочку через небольшую паузу — дать странице отрендериться
if (CONFIG.autoLoadHistory) setTimeout(startLoadChain, 800);
if (CONFIG.autoLoadHistory && CONFIG.autoLoadPreviousDialogs) setTimeout(loadPreviousTicketsHistory, 1200);
}
// ─── Observer ────────────────────────────────────────────
// RAF-дебаунс: один запуск за кадр, не накапливаем очередь
var _rafId = null;
function scheduleProcess() {
if (_rafId) return;
_rafId = requestAnimationFrame(function () {
_rafId = null;
processAllMessages();
if (CONFIG.leftTabsMirror) {
// Для активного тикета превью берём прямо из DOM,
// поэтому перерисовываем левую панель сразу на каждую порцию изменений.
scheduleLeftTabsMirrorSync();
}
});
}
function observeMessages() {
var target = document.getElementById('ticket-app') || document.body;
new MutationObserver(scheduleProcess).observe(target, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeFilter: ['class', 'aria-pressed']
});
}
// ─── Статусы коллег в sidebar ────────────────────────────
let _colleagueStatusesInterval = null;
let _colleagueStatusesInFlight = false;
let _colleagueStatusesRetryTimer = null;
// ============================================================
// 5) Colleague Statuses + Staffs Drawer
// ============================================================
function removeColleagueStatusesBlock() {
document.querySelectorAll('.staffs-injected-block').forEach((el) => el.remove());
}
function stopColleagueStatuses() {
if (_colleagueStatusesInterval) {
clearInterval(_colleagueStatusesInterval);
_colleagueStatusesInterval = null;
}
if (_colleagueStatusesRetryTimer) {
clearTimeout(_colleagueStatusesRetryTimer);
_colleagueStatusesRetryTimer = null;
}
_colleagueStatusesInFlight = false;
removeColleagueStatusesBlock();
}
function updateColleagueStatusesBlock() {
if (_colleagueStatusesInFlight) return;
_colleagueStatusesInFlight = true;
const sidebar = document.querySelector('.ticket-sidebar');
if (!sidebar) {
_colleagueStatusesInFlight = false;
return;
}
let container = sidebar.querySelector('.staffs-injected-block');
if (!container) {
container = document.createElement('div');
container.className = 'staffs-injected-block';
sidebar.appendChild(container);
}
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = '/ru/dashboard/staffs/';
document.body.appendChild(iframe);
let attempts = 0;
const finish = () => {
setTimeout(() => iframe.remove(), 500);
_colleagueStatusesInFlight = false;
};
const check = () => {
try {
const doc = iframe.contentDocument;
const staffsDiv = doc?.querySelector('.dashboard-staffs__content.ps');
const headerRow = doc?.querySelector('.dashboard-staffs__header .dashboard-staffs__row');
if (!staffsDiv || !headerRow) {
if (++attempts < 5) {
_colleagueStatusesRetryTimer = setTimeout(check, 1500);
return;
}
container.innerHTML = '';
finish();
return;
}
const columns = Array.from(headerRow.querySelectorAll('.dashboard-staffs__column:not(.dashboard-staffs__column_columns-settings)'));
const nameIndex = columns.findIndex((col) => col.textContent.trim() === 'Имя');
const groupIndex = columns.findIndex((col) => col.textContent.trim() === 'Группа');
const statusIndex = columns.findIndex((col) => col.textContent.trim() === 'Статус');
const ticketsIndex = columns.findIndex((col) => col.textContent.trim() === 'Активные заявки');
if (nameIndex < 0 || groupIndex < 0 || statusIndex < 0 || ticketsIndex < 0) {
container.innerHTML = '';
finish();
return;
}
const groups = {};
const groupOrder = [];
staffsDiv.querySelectorAll('.dashboard-staffs__row').forEach((row) => {
const cols = Array.from(row.querySelectorAll('.dashboard-staffs__column:not(.dashboard-staffs__column_columns-settings)'));
const nameEl = cols[nameIndex]?.querySelector('.el-button span');
const name = nameEl?.textContent.trim() || 'Неизвестно';
const group = cols[groupIndex]?.textContent.trim() || 'Неизвестно';
let ticketsCount = 0;
const ticketsBtn = cols[ticketsIndex]?.querySelector('.el-button span');
if (ticketsBtn) {
const match = ticketsBtn.textContent.match(/\d+/);
ticketsCount = match ? parseInt(match[0], 10) : 0;
}
let statusText = 'Неизвестно';
const selected = row.querySelector('.el-select-dropdown__item.selected span');
if (selected) {
statusText = selected.textContent.trim().replace(/^\[.*?\]\s*/, '').trim();
} else {
const input = row.querySelector('.el-input__inner');
if (input?.value) statusText = input.value.trim().replace(/^\[.*?\]\s*/, '').trim();
}
let statusClass = 'status-offline';
if (statusText.includes('В сети')) statusClass = 'status-online';
else if (statusText.includes('Невидимка')) statusClass = 'status-invisible';
else if (statusText.includes('Перерыв') || statusText.includes('Обед')) statusClass = 'status-break';
if (!groups[group]) {
groups[group] = [];
groupOrder.push(group);
}
groups[group].push({ name, statusText, statusClass, ticketsCount });
});
let html = '';
groupOrder.forEach((groupName) => {
const totalTickets = groups[groupName].reduce((sum, member) => sum + member.ticketsCount, 0);
html += `<div class="staffs-injected-group-header">
<span class="staffs-injected-group-total">${totalTickets > 0 ? totalTickets : ''}</span>
<span class="staffs-injected-group-name">${groupName}</span>
</div>
<ul class="staffs-injected-list">`;
groups[groupName].forEach((member) => {
const badgeValue = member.ticketsCount > 9 ? '9+' : (member.ticketsCount > 0 ? member.ticketsCount : '');
const badgeClasses = `staffs-injected-badge ${member.statusClass} ${member.ticketsCount > 0 ? 'has-tickets' : 'no-tickets'}`;
html += `<li class="staffs-injected-member-row">
<span class="${badgeClasses}">${badgeValue}</span>
<span class="staffs-injected-member-name ${member.statusClass}">${member.name}</span>
<span class="staffs-injected-member-status ${member.statusClass}">${member.statusText}</span>
</li>`;
});
html += '</ul>';
});
container.innerHTML = html;
} catch (e) {
container.innerHTML = '';
} finally {
finish();
}
};
iframe.onload = () => setTimeout(check, 2000);
iframe.onerror = () => {
container.innerHTML = '';
iframe.remove();
_colleagueStatusesInFlight = false;
};
}
function startColleagueStatuses() {
if (_colleagueStatusesInterval) return;
updateColleagueStatusesBlock();
_colleagueStatusesInterval = setInterval(updateColleagueStatusesBlock, 10000);
}
function syncColleagueStatusesModule() {
if (CONFIG.colleagueStatuses) startColleagueStatuses();
else stopColleagueStatuses();
}
// ============================================================
// 6) Left Tabs Panel (standalone)
// ============================================================
function removeLeftTabsMirror() {
document.getElementById(LEFT_TABS_MIRROR_ID)?.remove();
document.getElementById(LEFT_TABS_COLUMN_ID)?.remove();
}
let _leftTabsMirrorSyncScheduled = false;
let _leftTabsMirrorSyncInProgress = false;
let _leftTabsMirrorSyncPending = false;
let _leftTabsMirrorLastSignature = '';
const _leftTabsPreviewByName = Object.create(null);
const _leftTabsPreviewByTicketId = Object.create(null);
const _leftTabsLastMessageTsByName = Object.create(null);
const _leftTabsLastMessageTsByTicketId = Object.create(null);
const _leftTabsLastMessageTimeTextByName = Object.create(null);
const _leftTabsLastMessageTimeTextByTicketId = Object.create(null);
const _leftTabsSeenOrderByName = Object.create(null);
const _leftTabsSeenOrderByTicketId = Object.create(null);
const _leftTabsSeenOrderByStoreKey = Object.create(null);
// Полностью автономный режим: без bridge к нативным вкладкам.
let _leftTabsSeenOrderSeq = 0;
const FILE_ONLY_PREVIEW = '📝';
let _leftTabsPreviewPollInterval = null;
let _leftTabsDepartmentPollInterval = null;
let _leftTabsPreviewPollInFlight = false;
let _leftTabsPreviewPollForcePending = false;
let _leftTabsPreviewPollForceScheduled = false;
const _leftTabsForcedPreviewTicketIds = new Set();
let _leftTabsPreviewLastRunAt = 0;
let _leftTabsPreviewBackoffUntil = 0;
let _leftTabsPreviewErrorStreak = 0;
let _leftTabsCaptchaCooldownUntil = 0;
const _leftTabsLastPollAtByTicketId = Object.create(null);
const _leftTabsRecentRequestTs = [];
let _leftTabsCacheSaveTimer = null;
const _leftTabsCacheSuppressByTicketId = Object.create(null);
const _leftTabsCacheSuppressByName = Object.create(null);
let _leftTabsCacheGlobalSuppressUntil = 0;
const _leftTabsHydratedByTicketId = Object.create(null);
const _leftTabsHydratedByName = Object.create(null);
let _leftTabsNativeTabsVm = null;
/** Собственный список открытых тикетов (только numeric ticketId), без родной панели вкладок */
let _leftTabsOpenOrder = [];
const _leftTabsOpenMeta = Object.create(null);
const _leftTabsBadgeByTicketId = Object.create(null);
/**
* После закрытия активного тикета URL ненадолго остаётся прежним — иначе ensureOpen снова
* добавит id в order и debounce перезатрёт localStorage. Пока URL = этому id, не регистрируем.
*/
let _leftTabsBlockEnsureForClosedId = null;
let _leftTabsLastHrefForSync = location.href;
const LEFT_TABS_PREVIEW_POLL_MS = 8000;
const LEFT_TABS_PREVIEW_MIN_GAP_MS = 5000;
const LEFT_TABS_PREVIEW_FORCE_MIN_GAP_MS = 7000;
const LEFT_TABS_MAX_REQUESTS_PER_RUN = 1;
const LEFT_TABS_PER_TICKET_MIN_GAP_MS = 45 * 1000;
const LEFT_TABS_MAX_REQUESTS_PER_MINUTE = 6;
const LEFT_TABS_DEPARTMENT_REFRESH_MS = 10000;
const LEFT_TABS_CAPTCHA_COOLDOWN_MS = 10 * 60 * 1000;
const LEFT_TABS_ERROR_BASE_BACKOFF_MS = 2000;
const LEFT_TABS_ERROR_MAX_BACKOFF_MS = 60 * 1000;
const LEFT_TABS_CACHE_SUPPRESS_MS = 8000;
function nowMs() {
return Date.now();
}
function pruneLeftTabsCacheSuppress() {
const now = nowMs();
for (const [k, until] of Object.entries(_leftTabsCacheSuppressByTicketId)) {
if (Number(until) <= now) delete _leftTabsCacheSuppressByTicketId[k];
}
for (const [k, until] of Object.entries(_leftTabsCacheSuppressByName)) {
if (Number(until) <= now) delete _leftTabsCacheSuppressByName[k];
}
}
function markLeftTabsCacheSuppress(entry, ms = LEFT_TABS_CACHE_SUPPRESS_MS) {
const until = nowMs() + ms;
const ticketId = Number.isFinite(entry?.ticketId) ? entry.ticketId : null;
const name = String(entry?.name || '').trim();
const norm = normalizeTabName(name);
if (Number.isFinite(ticketId)) _leftTabsCacheSuppressByTicketId[ticketId] = until;
if (name) _leftTabsCacheSuppressByName[name] = until;
if (norm) _leftTabsCacheSuppressByName[norm] = until;
}
function isLeftTabsCacheSuppressed(name, ticketId) {
pruneLeftTabsCacheSuppress();
const now = nowMs();
if (_leftTabsCacheGlobalSuppressUntil > now) return true;
if (Number.isFinite(ticketId) && Number(_leftTabsCacheSuppressByTicketId[ticketId] || 0) > now) return true;
const raw = String(name || '').trim();
const norm = normalizeTabName(raw);
return Number(_leftTabsCacheSuppressByName[raw] || 0) > now
|| Number(_leftTabsCacheSuppressByName[norm] || 0) > now;
}
function scheduleLeftTabsCacheSave() {
if (_leftTabsCacheSaveTimer) return;
_leftTabsCacheSaveTimer = setTimeout(() => {
_leftTabsCacheSaveTimer = null;
try {
const payload = {
previewByName: _leftTabsPreviewByName,
previewByTicketId: _leftTabsPreviewByTicketId,
timeTextByName: _leftTabsLastMessageTimeTextByName,
timeTextByTicketId: _leftTabsLastMessageTimeTextByTicketId,
tsByName: _leftTabsLastMessageTsByName,
tsByTicketId: _leftTabsLastMessageTsByTicketId,
openTabs: {
order: _leftTabsOpenOrder.slice(),
meta: { ..._leftTabsOpenMeta },
badgeByTicketId: { ..._leftTabsBadgeByTicketId }
}
};
localStorage.setItem(LEFT_TABS_CACHE_KEY, JSON.stringify(payload));
} catch (e) {}
}, 300);
}
function flushLeftTabsCacheNow() {
try {
if (_leftTabsCacheSaveTimer) {
clearTimeout(_leftTabsCacheSaveTimer);
_leftTabsCacheSaveTimer = null;
}
const payload = {
previewByName: _leftTabsPreviewByName,
previewByTicketId: _leftTabsPreviewByTicketId,
timeTextByName: _leftTabsLastMessageTimeTextByName,
timeTextByTicketId: _leftTabsLastMessageTimeTextByTicketId,
tsByName: _leftTabsLastMessageTsByName,
tsByTicketId: _leftTabsLastMessageTsByTicketId,
openTabs: {
order: _leftTabsOpenOrder.slice(),
meta: { ..._leftTabsOpenMeta },
badgeByTicketId: { ..._leftTabsBadgeByTicketId }
}
};
localStorage.setItem(LEFT_TABS_CACHE_KEY, JSON.stringify(payload));
} catch (e) {}
}
function clearLeftTabsCacheForEntry(entry) {
markLeftTabsCacheSuppress(entry);
const name = String(entry?.name || '').trim();
const norm = normalizeTabName(name);
const ticketId = Number.isFinite(entry?.ticketId) ? entry.ticketId : null;
if (name) {
delete _leftTabsPreviewByName[name];
delete _leftTabsLastMessageTimeTextByName[name];
delete _leftTabsLastMessageTsByName[name];
delete _leftTabsSeenOrderByName[name];
}
if (Number.isFinite(ticketId)) {
delete _leftTabsPreviewByTicketId[ticketId];
delete _leftTabsLastMessageTimeTextByTicketId[ticketId];
delete _leftTabsLastMessageTsByTicketId[ticketId];
delete _leftTabsSeenOrderByTicketId[ticketId];
delete _leftTabsHydratedByTicketId[ticketId];
_leftTabsOpenOrder = _leftTabsOpenOrder.filter((id) => id !== ticketId);
delete _leftTabsOpenMeta[ticketId];
delete _leftTabsBadgeByTicketId[ticketId];
}
if (norm) delete _leftTabsHydratedByName[norm];
flushLeftTabsCacheNow();
}
function clearLeftTabsCacheAll() {
_leftTabsCacheGlobalSuppressUntil = nowMs() + LEFT_TABS_CACHE_SUPPRESS_MS;
pruneLeftTabsCacheSuppress();
const clearObj = (obj) => Object.keys(obj).forEach((k) => { delete obj[k]; });
clearObj(_leftTabsPreviewByName);
clearObj(_leftTabsPreviewByTicketId);
clearObj(_leftTabsLastMessageTimeTextByName);
clearObj(_leftTabsLastMessageTimeTextByTicketId);
clearObj(_leftTabsLastMessageTsByName);
clearObj(_leftTabsLastMessageTsByTicketId);
clearObj(_leftTabsSeenOrderByName);
clearObj(_leftTabsSeenOrderByTicketId);
clearObj(_leftTabsHydratedByTicketId);
clearObj(_leftTabsHydratedByName);
_leftTabsOpenOrder = [];
Object.keys(_leftTabsOpenMeta).forEach((k) => { delete _leftTabsOpenMeta[k]; });
Object.keys(_leftTabsBadgeByTicketId).forEach((k) => { delete _leftTabsBadgeByTicketId[k]; });
_leftTabsBlockEnsureForClosedId = null;
try {
localStorage.removeItem(LEFT_TABS_CACHE_KEY);
localStorage.removeItem(LEFT_TABS_CACHE_KEY_LEGACY_V2);
} catch (e) {}
flushLeftTabsCacheNow();
}
function loadLeftTabsCache() {
try {
const mergeObject = (target, source) => {
if (!source || typeof source !== 'object') return;
for (const [k, v] of Object.entries(source)) {
if (v == null) continue;
target[k] = v;
}
};
let raw = localStorage.getItem(LEFT_TABS_CACHE_KEY);
if (!raw) {
const legacyV2 = localStorage.getItem(LEFT_TABS_CACHE_KEY_LEGACY_V2);
if (legacyV2) {
const v2 = JSON.parse(legacyV2);
mergeObject(_leftTabsPreviewByTicketId, v2.previewByTicketId);
mergeObject(_leftTabsLastMessageTimeTextByTicketId, v2.timeTextByTicketId);
mergeObject(_leftTabsLastMessageTsByTicketId, v2.tsByTicketId);
mergeObject(_leftTabsSeenOrderByTicketId, v2.seenOrderByTicketId);
try {
localStorage.removeItem(LEFT_TABS_CACHE_KEY_LEGACY_V2);
} catch (e) {}
flushLeftTabsCacheNow();
}
return;
}
const parsed = JSON.parse(raw);
mergeObject(_leftTabsPreviewByName, parsed.previewByName);
mergeObject(_leftTabsPreviewByTicketId, parsed.previewByTicketId);
mergeObject(_leftTabsLastMessageTimeTextByName, parsed.timeTextByName);
mergeObject(_leftTabsLastMessageTimeTextByTicketId, parsed.timeTextByTicketId);
mergeObject(_leftTabsLastMessageTsByName, parsed.tsByName);
mergeObject(_leftTabsLastMessageTsByTicketId, parsed.tsByTicketId);
if (parsed.openTabs && Array.isArray(parsed.openTabs.order)) {
_leftTabsOpenOrder = parsed.openTabs.order
.map((id) => Number(id))
.filter((id) => Number.isFinite(id) && id > 0);
const meta = parsed.openTabs.meta || {};
Object.keys(meta).forEach((k) => {
const id = Number(k);
if (Number.isFinite(id) && id > 0) _leftTabsOpenMeta[id] = meta[k];
});
const badges = parsed.openTabs.badgeByTicketId || {};
Object.keys(badges).forEach((k) => {
const id = Number(k);
if (Number.isFinite(id) && id > 0) _leftTabsBadgeByTicketId[id] = !!badges[k];
});
}
try {
localStorage.removeItem(LEFT_TABS_CACHE_KEY_LEGACY_V2);
} catch (e) {}
} catch (e) {}
}
loadLeftTabsCache();
function leftTabsFieldLabelIncludes(fieldEl, needle) {
if (!(fieldEl instanceof Element) || !needle) return false;
const lab = fieldEl.querySelector('.ticket-fields__field-name, .ticket-user__field-label');
const t = (lab?.textContent || '').replace(/\s+/g, ' ').trim().toLowerCase();
return t.includes(String(needle).toLowerCase());
}
function extractTicketDepartmentFromPage() {
const fields = Array.from(document.querySelectorAll('#ticket-app .ticket-fields__field'));
for (const field of fields) {
if (!leftTabsFieldLabelIncludes(field, 'департамент')) continue;
const input = field.querySelector('.ticket-fields__field-input .el-input__inner');
const v = input ? String(input.value || '').replace(/\s+/g, ' ').trim() : '';
if (v && v !== 'Выбрать') return v;
const sel = field.querySelector('.el-select-dropdown__item.selected, .el-select-dropdown__item.is-selected');
const fromLi = (sel?.textContent || '').replace(/\s+/g, ' ').trim();
if (fromLi) return fromLi;
}
return '';
}
function extractTicketClientNameFromPage() {
const fields = Array.from(document.querySelectorAll('#ticket-app .ticket-user .ticket-user__field'));
for (const field of fields) {
if (!leftTabsFieldLabelIncludes(field, 'клиент')) continue;
const input = field.querySelector('.el-input__inner');
const v = input ? String(input.value || '').replace(/\s+/g, ' ').trim() : '';
if (v && !/^поиск/i.test(v)) return v;
const sel = field.querySelector('.el-select-dropdown__item.selected, .el-select-dropdown__item.is-selected');
const fromLi = (sel?.textContent || '').replace(/\s+/g, ' ').trim();
if (fromLi) return fromLi;
}
return '';
}
function syncLeftTabsAfterNavigationShell() {
applyThemeFromConfig();
syncColleagueStatusesModule();
injectStyle();
processAllMessages();
initAutoLoad();
moveHistoryButton();
}
function buildPathToTicket(ticketId) {
if (!Number.isFinite(ticketId)) return null;
const path = location.pathname || '';
if (/\/ticket\/\d+/.test(path)) return path.replace(/\/ticket\/\d+/, `/ticket/${ticketId}`);
const listWithFilter = path.match(/^(.*\/ticket\/list\/filter\/id\/\d+)/);
if (listWithFilter) return `${listWithFilter[1]}/ticket/${ticketId}`;
const locPrefix = path.match(/^(\/[^/]+)/);
const loc = locPrefix ? locPrefix[1] : '/ru';
return `${loc}/ticket/list/filter/id/11/ticket/${ticketId}`;
}
/** Абсолютный pathname → путь для $router.push (без дубля base, иначе бывает …/list/ru/ticket/list/…). */
function pathnameForVueRouterPush(router, absolutePathname) {
const p = String(absolutePathname || '').startsWith('/')
? String(absolutePathname)
: `/${absolutePathname}`;
let base = router && router.options && router.options.base != null
? String(router.options.base)
: '/';
if (!base || base === '/') return p;
base = base.replace(/\/+$/, '');
if (!base) return p;
if (p === base || p.startsWith(`${base}/`)) {
const rest = p.slice(base.length);
return rest.startsWith('/') ? rest : `/${rest}`;
}
return p;
}
function navigateToTicketId(ticketId) {
if (!Number.isFinite(ticketId)) return false;
if (Number.isFinite(ticketId) && _leftTabsBlockEnsureForClosedId === ticketId) {
_leftTabsBlockEnsureForClosedId = null;
}
const nextPath = buildPathToTicket(ticketId);
if (!nextPath) return false;
const nextFull = nextPath + (location.search || '');
if (`${location.pathname}${location.search}` === nextFull) {
ensureOpenTicketFromCurrentUrl();
return true;
}
const root = document.querySelector('#ticket-app');
try {
const vue = root && root.__vue__;
if (vue && vue.$router && typeof vue.$router.push === 'function') {
const routerPath = pathnameForVueRouterPush(vue.$router, nextPath) || '/';
vue.$router.push(routerPath + (location.search || '')).catch(() => {});
setTimeout(() => {
ensureOpenTicketFromCurrentUrl();
syncLeftTabsMirror();
syncLeftTabsAfterNavigationShell();
}, 120);
return true;
}
} catch (e) {}
try {
window.history.pushState({}, '', nextFull);
window.dispatchEvent(new PopStateEvent('popstate', { state: history.state }));
} catch (e) {
window.location.assign(nextFull);
return true;
}
ensureOpenTicketFromCurrentUrl();
_leftTabsLastHrefForSync = location.href;
setTimeout(() => {
syncLeftTabsMirror();
syncLeftTabsAfterNavigationShell();
}, 80);
return true;
}
function navigateToTicketListHome() {
const m = location.pathname.match(/^(\/[^/]+)\/ticket\//);
const prefix = m ? m[1] : '/ru';
const nextFull = `${prefix}/ticket/list` + (location.search || '');
try {
window.history.pushState({}, '', nextFull);
window.dispatchEvent(new PopStateEvent('popstate', { state: history.state }));
} catch (e) {
window.location.assign(nextFull);
return;
}
_leftTabsLastHrefForSync = location.href;
setTimeout(() => {
syncLeftTabsMirror();
syncLeftTabsAfterNavigationShell();
}, 80);
}
function ensureOpenTicketFromCurrentUrl() {
const tid = getTicketId();
if (!Number.isFinite(tid)) {
_leftTabsBlockEnsureForClosedId = null;
return;
}
if (_leftTabsBlockEnsureForClosedId != null) {
if (tid === _leftTabsBlockEnsureForClosedId) {
return;
}
_leftTabsBlockEnsureForClosedId = null;
}
if (!_leftTabsOpenOrder.includes(tid)) _leftTabsOpenOrder.push(tid);
if (!_leftTabsOpenMeta[tid]) _leftTabsOpenMeta[tid] = { name: '', department: '' };
const name = extractTicketClientNameFromPage();
const dep = extractTicketDepartmentFromPage();
if (name) _leftTabsOpenMeta[tid].name = name;
if (dep) _leftTabsOpenMeta[tid].department = dep;
scheduleLeftTabsCacheSave();
}
function syncLeftTabsOnHrefChange() {
if (!CONFIG.leftTabsMirror) {
_leftTabsLastHrefForSync = location.href;
return;
}
if (location.href === _leftTabsLastHrefForSync) return;
_leftTabsLastHrefForSync = location.href;
_loadedPages.clear();
_historyLoadedForTicketId = null;
ensureOpenTicketFromCurrentUrl();
syncLeftTabsMirror();
scheduleImmediateLeftTabsPreviewPoll();
processAllMessages();
initAutoLoad();
}
function refreshLeftTabsDepartmentMeta() {
if (!CONFIG.leftTabsMirror) return;
const tid = getTicketId();
if (!Number.isFinite(tid)) return;
if (!_leftTabsOpenMeta[tid]) _leftTabsOpenMeta[tid] = { name: '', department: '' };
let changed = false;
const dep = extractTicketDepartmentFromPage();
if (dep && _leftTabsOpenMeta[tid].department !== dep) {
_leftTabsOpenMeta[tid].department = dep;
changed = true;
}
const name = extractTicketClientNameFromPage();
if (name && _leftTabsOpenMeta[tid].name !== name) {
_leftTabsOpenMeta[tid].name = name;
changed = true;
}
if (changed) {
scheduleLeftTabsCacheSave();
scheduleLeftTabsMirrorSync();
}
}
function inferUnreadBadgeFromConversation(payload, isActive) {
if (isActive) return false;
const messages = Array.isArray(payload?.messages) ? payload.messages : [];
const users = payload?.users || {};
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
if (!msg || msg.type === 'comment' || msg.type === 'note') continue;
const u = users[String(msg.userId)] || {};
const t = String(u.type || '').toLowerCase();
if (t === 'user' || t === 'client') return true;
return false;
}
return false;
}
function markLeftTabsHydrated(name, ticketId) {
if (Number.isFinite(ticketId)) _leftTabsHydratedByTicketId[ticketId] = 1;
const norm = normalizeTabName(name);
if (norm) _leftTabsHydratedByName[norm] = 1;
}
function isLeftTabsHydrated(name, ticketId) {
if (Number.isFinite(ticketId) && _leftTabsHydratedByTicketId[ticketId]) return true;
const norm = normalizeTabName(name);
return !!(norm && _leftTabsHydratedByName[norm]);
}
function ensureSeenOrder(name, ticketId, storeKey) {
const normalizedStoreKey = String(storeKey || '').trim();
if (normalizedStoreKey) {
if (_leftTabsSeenOrderByStoreKey[normalizedStoreKey] == null) _leftTabsSeenOrderByStoreKey[normalizedStoreKey] = _leftTabsSeenOrderSeq++;
return;
}
if (Number.isFinite(ticketId)) {
if (_leftTabsSeenOrderByTicketId[ticketId] == null) _leftTabsSeenOrderByTicketId[ticketId] = _leftTabsSeenOrderSeq++;
if (_leftTabsSeenOrderByName[name] == null) _leftTabsSeenOrderByName[name] = _leftTabsSeenOrderByTicketId[ticketId];
return;
}
if (_leftTabsSeenOrderByName[name] == null) _leftTabsSeenOrderByName[name] = _leftTabsSeenOrderSeq++;
}
function getSeenOrder(name, ticketId, storeKey) {
const normalizedStoreKey = String(storeKey || '').trim();
if (normalizedStoreKey && _leftTabsSeenOrderByStoreKey[normalizedStoreKey] != null) {
return _leftTabsSeenOrderByStoreKey[normalizedStoreKey];
}
if (Number.isFinite(ticketId) && _leftTabsSeenOrderByTicketId[ticketId] != null) {
return _leftTabsSeenOrderByTicketId[ticketId];
}
return _leftTabsSeenOrderByName[name] ?? Number.MAX_SAFE_INTEGER;
}
function getLeftTabsPreview(name, ticketId) {
if (Number.isFinite(ticketId)) return _leftTabsPreviewByTicketId[ticketId] || '';
const byName = name ? (_leftTabsPreviewByName[name] || '') : '';
return byName || '';
}
function setLeftTabsPreview(name, ticketId, preview) {
if (!preview) return;
const next = String(preview).trim();
if (!next) return;
// Маркер "Новый ответ" используем для UI, но не затираем им кэш реального текста.
if (next === 'Новый ответ') return;
if (!Number.isFinite(ticketId)) return;
if (isLeftTabsCacheSuppressed(name, ticketId)) return;
let changed = false;
if (_leftTabsPreviewByTicketId[ticketId] !== next) {
_leftTabsPreviewByTicketId[ticketId] = next;
changed = true;
}
// Жёсткое правило: новое превью -> сразу в кэш.
if (changed) {
// Если превью обновилось раньше, чем пришёл точный message ts из API,
// ставим "свежий" штамп, чтобы после reload не побеждал старый snapshot.
const nowTs = Date.now();
_leftTabsLastMessageTsByTicketId[ticketId] = Math.max(Number(_leftTabsLastMessageTsByTicketId[ticketId] || 0), nowTs);
flushLeftTabsCacheNow();
}
}
function getLeftTabsTimeText(name, ticketId) {
if (Number.isFinite(ticketId)) return _leftTabsLastMessageTimeTextByTicketId[ticketId] || '';
const byName = name ? (_leftTabsLastMessageTimeTextByName[name] || '') : '';
return byName || '';
}
function setLeftTabsTimeText(name, ticketId, timeText) {
if (!timeText) return;
if (!Number.isFinite(ticketId)) return;
if (isLeftTabsCacheSuppressed(name, ticketId)) return;
_leftTabsLastMessageTimeTextByTicketId[ticketId] = timeText;
scheduleLeftTabsCacheSave();
}
function getLeftTabsTs(name, ticketId) {
if (!Number.isFinite(ticketId)) return 0;
return Number(_leftTabsLastMessageTsByTicketId[ticketId] || 0);
}
function setLeftTabsTs(name, ticketId, ts) {
if (!Number.isFinite(ts) || ts <= 0) return;
if (!Number.isFinite(ticketId)) return;
if (isLeftTabsCacheSuppressed(name, ticketId)) return;
_leftTabsLastMessageTsByTicketId[ticketId] = ts;
scheduleLeftTabsCacheSave();
}
function syncLeftTabsCacheFromEntries(entries) {
for (const entry of entries || []) {
if (!entry) continue;
const preview = String(entry.preview || '').trim();
if (preview && preview !== 'Новый ответ') {
setLeftTabsPreview(entry.name, entry.ticketId, preview);
}
const timeText = String(entry.lastTimeText || '').trim();
if (timeText) {
setLeftTabsTimeText(entry.name, entry.ticketId, timeText);
}
const ts = Number(entry.lastMessageTs || 0);
if (Number.isFinite(ts) && ts > 0) {
setLeftTabsTs(entry.name, entry.ticketId, ts);
}
}
}
function sortLeftTabsEntries(entries) {
entries.sort((a, b) => {
const aTs = Number(a.lastMessageTs || getLeftTabsTs(a.name, a.ticketId) || 0);
const bTs = Number(b.lastMessageTs || getLeftTabsTs(b.name, b.ticketId) || 0);
if (aTs !== bTs) return bTs - aTs;
const aSeen = getSeenOrder(a.name, a.ticketId, a.storeKey);
const bSeen = getSeenOrder(b.name, b.ticketId, b.storeKey);
return aSeen - bSeen;
});
return entries;
}
function getLeftTabsSignature(entries) {
return entries.map((entry, idx) => {
const isActive = entry.isActive ? '1' : '0';
const hasBadge = entry.hasBadge ? '1' : '0';
const tid = Number.isFinite(entry.ticketId) ? entry.ticketId : '';
const dep = entry.department || '';
return `${idx}:${tid}:${isActive}:${hasBadge}:${entry.name}:${dep}:${entry.preview || ''}:${entry.lastTimeText || ''}`;
}).join('|');
}
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function isRedLikeColor(color) {
if (!color) return false;
const c = String(color).toLowerCase();
if (c.includes('f56c6c') || c.includes('fb0a0a') || c.includes('ff0000')) return true;
const m = c.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (!m) return false;
const r = Number(m[1]); const g = Number(m[2]); const b = Number(m[3]);
return r >= 170 && g <= 120 && b <= 120;
}
function extractDateTimeText(value) {
const str = String(value || '');
const full = str.match(/\b(\d{1,2}\.\d{1,2}\.\d{2,4}\s+\d{1,2}:\d{2}(?::\d{2})?)\b/);
if (full) return full[1];
const hm = str.match(/\b(\d{1,2}:\d{2})\b/);
return hm ? hm[1] : '';
}
function parseMessageDateToTs(value) {
if (!value) return 0;
if (Number.isFinite(value)) return Number(value);
const str = String(value).trim();
const full = str.match(/^(\d{1,2})\.(\d{1,2})\.(\d{2,4})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
if (full) {
let year = Number(full[3]);
if (year < 100) year += 2000;
const d = new Date(year, Number(full[2]) - 1, Number(full[1]), Number(full[4]), Number(full[5]), Number(full[6] || 0));
const ts = d.getTime();
return Number.isFinite(ts) ? ts : 0;
}
const hm = str.match(/^(\d{1,2}):(\d{2})$/);
if (hm) {
const now = new Date();
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate(), Number(hm[1]), Number(hm[2]), 0);
const ts = d.getTime();
return Number.isFinite(ts) ? ts : 0;
}
const fallback = Date.parse(str);
return Number.isFinite(fallback) ? fallback : 0;
}
function formatMessageTimeLabel(value) {
const str = String(value || '').trim();
if (!str) return '';
const hm = str.match(/\b(\d{1,2}:\d{2})\b/);
return hm ? hm[1] : str;
}
function extractCurrentTicketPreviewMeta() {
const bubbles = Array.from(document.querySelectorAll('#ticket-app .ticket-conversation__messages .ticket-conversation__message .ticket-conversation__message-html'));
for (let i = bubbles.length - 1; i >= 0; i--) {
const bubble = bubbles[i];
if (!bubble) continue;
const clone = bubble.cloneNode(true);
clone.querySelectorAll('.hde-name-inside, .hde-time-inside, .hde-edited-inside, .hde-actions-inline, .ticket-conversation__message-updated').forEach((el) => el.remove());
const txt = clone.textContent?.replace(/\s+/g, ' ').trim();
const msgEl = bubble.closest('.ticket-conversation__message');
const hasFiles = !!msgEl?.querySelector('.ticket-conversation__message-files .ticket-conversation__message-file');
const timeInside = msgEl?.querySelector('.hde-time-inside')?.textContent?.trim() || '';
const metaRaw = msgEl?.querySelector('.ticket-conversation__message-meta')?.textContent || '';
const dateText = extractDateTimeText(metaRaw) || extractDateTimeText(timeInside);
const timeText = formatMessageTimeLabel(dateText || timeInside);
const ts = parseMessageDateToTs(dateText || timeInside);
if (hasFiles && (!txt || txt === '_')) {
return { preview: FILE_ONLY_PREVIEW, timeText, ts };
}
if (!txt) continue;
return {
preview: txt.length > 140 ? `${txt.slice(0, 140)}…` : txt,
timeText,
ts
};
}
return null;
}
function htmlToPlainText(html) {
if (!html) return '';
const div = document.createElement('div');
div.innerHTML = String(html);
return (div.textContent || '').replace(/\s+/g, ' ').trim();
}
function pickLatestPreviewFromConversation(payload) {
const messages = Array.isArray(payload?.messages) ? payload.messages : [];
// API обычно отдает сообщения в хронологическом порядке (старые -> новые),
// поэтому идем с конца, чтобы взять последнее актуальное сообщение.
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
const txt = htmlToPlainText(msg?.text || '');
const hasFiles = Array.isArray(msg?.files) && msg.files.length > 0;
const rawTime = msg?.createdAt || msg?.date || msg?.created_at || '';
const timeText = formatMessageTimeLabel(rawTime);
const ts = parseMessageDateToTs(rawTime);
if (hasFiles && (!txt || txt === '_')) {
return { preview: FILE_ONLY_PREVIEW, timeText, ts };
}
if (txt) {
return {
preview: txt.length > 140 ? `${txt.slice(0, 140)}…` : txt,
timeText,
ts
};
}
}
return null;
}
function normalizeTabName(name) {
return String(name || '').replace(/\s+/g, ' ').trim().toLowerCase();
}
function normalizeLooseTabName(name) {
return normalizeTabName(name)
.replace(/[.…]+$/g, '')
.replace(/[^\p{L}\p{N}\s-]/gu, '')
.trim();
}
// Ранее здесь был парсер нативных вкладок/Vue-состояния.
// Удален, т.к. панель теперь полностью автономна и не зеркалит HDE tabs.
/**
* Единая разметка #ticket-app: колонка зеркала вставляется между sidebar и чатом.
*/
function findTicketMetroLayout() {
const metroContainer = Array.from(document.querySelectorAll('section.el-container.metro-padding'))
.find((el) => el.querySelector('.ticket-sidebar') && el.querySelector('section.el-container.ticket-container'));
if (!metroContainer) return null;
const sidebar = metroContainer.querySelector('.ticket-sidebar');
const ticketContainer = metroContainer.querySelector('section.el-container.ticket-container');
if (!sidebar || !ticketContainer) return null;
return { metroContainer, sidebar, ticketContainer };
}
function ensureLeftTabsColumnInLayout(metroContainer, ticketContainer) {
let column = document.getElementById(LEFT_TABS_COLUMN_ID);
if (!column) {
column = document.createElement('div');
column.id = LEFT_TABS_COLUMN_ID;
column.className = 'hde-left-tabs-column';
}
if (column.parentElement !== metroContainer || column.nextElementSibling !== ticketContainer) {
metroContainer.insertBefore(column, ticketContainer);
}
return column;
}
function leftTabsLooksLikeNativeTabsVm(vm) {
if (!vm || typeof vm !== 'object') return false;
const tabs = vm.ticketTabs;
if (!Array.isArray(tabs)) return false;
return tabs.every((tab) => tab && typeof tab === 'object' && 'id' in tab && 'type' in tab);
}
function getLeftTabsNativeTabsVm() {
if (leftTabsLooksLikeNativeTabsVm(_leftTabsNativeTabsVm)) return _leftTabsNativeTabsVm;
const root = document.querySelector('.ticket-topbar__tabs');
if (!(root instanceof Element)) return null;
const queue = [];
if (root.__vue__) queue.push(root.__vue__);
if (root.__vueParentComponent?.proxy) queue.push(root.__vueParentComponent.proxy);
let cur = root.parentElement;
let depth = 0;
while (cur && depth < 6) {
if (cur.__vue__) queue.push(cur.__vue__);
if (cur.__vueParentComponent?.proxy) queue.push(cur.__vueParentComponent.proxy);
cur = cur.parentElement;
depth += 1;
}
const visited = new WeakSet();
while (queue.length) {
const node = queue.shift();
if (!node || typeof node !== 'object') continue;
if (visited.has(node)) continue;
visited.add(node);
if (leftTabsLooksLikeNativeTabsVm(node)) {
_leftTabsNativeTabsVm = node;
return node;
}
const buckets = [node.$children, node.$parent, node.$root, node.$refs, node._provided];
for (const bucket of buckets) {
if (!bucket) continue;
if (Array.isArray(bucket)) {
bucket.forEach((child) => { if (child && typeof child === 'object') queue.push(child); });
} else if (typeof bucket === 'object') {
Object.values(bucket).forEach((child) => { if (child && typeof child === 'object') queue.push(child); });
}
}
}
return null;
}
function collectNativeTicketTabs() {
const vm = getLeftTabsNativeTabsVm();
const tabs = Array.isArray(vm?.ticketTabs) ? vm.ticketTabs : [];
return tabs
.filter((tab) => tab && typeof tab === 'object' && String(tab.type) === 'ticket')
.map((tab) => {
const id = Number(tab.id);
return {
id: Number.isFinite(id) && id > 0 ? id : null,
type: String(tab.type || 'ticket'),
title: String(tab.title || '').trim(),
updated: !!tab.updated
};
})
.filter((tab) => Number.isFinite(tab.id));
}
function syncOpenOrderFromNativeTabs(currentTicketId) {
const nativeTabs = collectNativeTicketTabs();
if (!nativeTabs.length) {
if (Number.isFinite(currentTicketId) && !_leftTabsOpenOrder.includes(currentTicketId)) {
_leftTabsOpenOrder.push(currentTicketId);
scheduleLeftTabsCacheSave();
}
return [];
}
const ids = nativeTabs.map((tab) => tab.id);
const changed = ids.length !== _leftTabsOpenOrder.length
|| ids.some((id, idx) => id !== _leftTabsOpenOrder[idx]);
if (changed) {
_leftTabsOpenOrder = ids.slice();
scheduleLeftTabsCacheSave();
}
const badgeRaisedIds = [];
nativeTabs.forEach((tab) => {
if (!_leftTabsOpenMeta[tab.id]) _leftTabsOpenMeta[tab.id] = { name: '', department: '' };
if (tab.title) _leftTabsOpenMeta[tab.id].name = tab.title;
const prevBadge = !!_leftTabsBadgeByTicketId[tab.id];
_leftTabsBadgeByTicketId[tab.id] = !!tab.updated;
if (!prevBadge && !!tab.updated) badgeRaisedIds.push(tab.id);
});
badgeRaisedIds.forEach((ticketId) => {
scheduleImmediateLeftTabsPreviewPollForTicket(ticketId);
});
return nativeTabs;
}
function normalizeLeftTabsOpenOrder() {
const normalized = [];
const seen = new Set();
for (const idRaw of _leftTabsOpenOrder) {
const id = Number(idRaw);
if (!Number.isFinite(id) || id <= 0) continue;
if (seen.has(id)) continue;
seen.add(id);
normalized.push(id);
}
if (normalized.length !== _leftTabsOpenOrder.length
|| normalized.some((id, idx) => id !== _leftTabsOpenOrder[idx])) {
_leftTabsOpenOrder = normalized;
scheduleLeftTabsCacheSave();
}
}
function getNextTicketIdAfterClose(closingTicketId) {
const orderCopy = _leftTabsOpenOrder.slice();
const idx = orderCopy.indexOf(closingTicketId);
if (idx < 0) return null;
return orderCopy[idx + 1] ?? orderCopy[idx - 1] ?? null;
}
function closeTicketViaNativeBridge(ticketId) {
const vm = getLeftTabsNativeTabsVm();
if (!vm || typeof vm.removeTab !== 'function' || !Array.isArray(vm.ticketTabs)) return false;
const nativeTab = vm.ticketTabs.find((tab) => Number(tab?.id) === Number(ticketId) && String(tab?.type) === 'ticket');
if (!nativeTab) return false;
try {
vm.removeTab(nativeTab);
return true;
} catch (e) {
return false;
}
}
function collectTicketTabEntries() {
const currentTicketId = getTicketId();
ensureOpenTicketFromCurrentUrl();
const nativeTabs = syncOpenOrderFromNativeTabs(currentTicketId);
normalizeLeftTabsOpenOrder();
const nativeById = Object.create(null);
nativeTabs.forEach((tab) => { nativeById[tab.id] = tab; });
const entries = _leftTabsOpenOrder
.filter((ticketId) => Number.isFinite(ticketId) && ticketId > 0)
.map((ticketId) => {
const nativeTab = nativeById[ticketId] || null;
const meta = _leftTabsOpenMeta[ticketId] || {};
const nativeTitle = String(nativeTab?.title || '').trim();
const name = nativeTitle || (meta.name && String(meta.name).trim()) || `Заявка ${ticketId}`;
const department = String(meta.department || '').trim();
const isActive = Number.isFinite(currentTicketId) && ticketId === currentTicketId;
const hasBadge = nativeTab ? !!nativeTab.updated : !!_leftTabsBadgeByTicketId[ticketId];
_leftTabsBadgeByTicketId[ticketId] = hasBadge;
const liveMeta = isActive ? extractCurrentTicketPreviewMeta() : null;
const cachedPreview = getLeftTabsPreview(name, ticketId);
const preview = liveMeta?.preview
|| cachedPreview
|| (hasBadge ? 'Новый ответ' : '');
setLeftTabsPreview(name, ticketId, preview);
if (liveMeta?.timeText) setLeftTabsTimeText(name, ticketId, liveMeta.timeText);
if (liveMeta?.ts) setLeftTabsTs(name, ticketId, liveMeta.ts);
const storeKey = `id:${ticketId}`;
ensureSeenOrder(name, ticketId, storeKey);
return {
source: 'registry',
domUid: '',
storeKey,
name,
department,
ticketId,
isActive,
hasBadge,
preview,
lastTimeText: getLeftTabsTimeText(name, ticketId),
lastMessageTs: getLeftTabsTs(name, ticketId),
onOpen: () => {
navigateToTicketId(ticketId);
},
onClose: () => {
const wasActive = getTicketId() === ticketId;
const nextId = getNextTicketIdAfterClose(ticketId);
if (closeTicketViaNativeBridge(ticketId)) {
clearLeftTabsCacheForEntry({ name, ticketId });
scheduleLeftTabsMirrorSync();
return;
}
clearLeftTabsCacheForEntry({ name, ticketId });
if (wasActive) {
_leftTabsBlockEnsureForClosedId = ticketId;
if (Number.isFinite(nextId)) navigateToTicketId(nextId);
else navigateToTicketListHome();
}
scheduleLeftTabsMirrorSync();
}
};
});
return sortLeftTabsEntries(entries);
}
function scheduleLeftTabsMirrorSync() {
if (!CONFIG.leftTabsMirror || _leftTabsMirrorSyncScheduled) return;
_leftTabsMirrorSyncScheduled = true;
requestAnimationFrame(() => {
_leftTabsMirrorSyncScheduled = false;
syncLeftTabsMirror();
});
}
function scheduleImmediateLeftTabsPreviewPoll() {
if (!CONFIG.leftTabsMirror || _leftTabsPreviewPollForceScheduled) return;
_leftTabsPreviewPollForceScheduled = true;
requestAnimationFrame(() => {
_leftTabsPreviewPollForceScheduled = false;
pollLeftTabsPreviews(true);
});
}
function scheduleImmediateLeftTabsPreviewPollForTicket(ticketId) {
if (!Number.isFinite(ticketId) || ticketId <= 0) return;
_leftTabsForcedPreviewTicketIds.add(ticketId);
scheduleImmediateLeftTabsPreviewPoll();
}
function clickNativeCloseAllTabs() {
const nativeCloseAllBtn = document.querySelector('.ticket-tabs__close-all, .ticket-tabs__more-close-all');
if (!(nativeCloseAllBtn instanceof HTMLElement)) return false;
nativeCloseAllBtn.click();
return true;
}
function clickNativeCloseAllConfirm() {
const candidates = Array.from(document.querySelectorAll('.el-popover .el-button.el-button--primary.el-button--mini, .el-popconfirm .el-button.el-button--primary.el-button--mini'));
const confirmBtn = candidates.find((btn) => /закрыть/i.test((btn.textContent || '').trim()));
if (!(confirmBtn instanceof HTMLElement)) return false;
confirmBtn.click();
return true;
}
function closeAllTabsFromMirror() {
const openedBefore = _leftTabsOpenOrder
.map((id) => Number(id))
.filter((id) => Number.isFinite(id) && id > 0);
let nativeCloseTriggered = false;
const vm = getLeftTabsNativeTabsVm();
if (vm && typeof vm.removeTab === 'function' && Array.isArray(vm.ticketTabs)) {
const nativeTicketTabs = vm.ticketTabs
.filter((tab) => Number.isFinite(Number(tab?.id)) && String(tab?.type) === 'ticket')
.slice();
nativeTicketTabs.forEach((tab) => {
try {
vm.removeTab(tab);
nativeCloseTriggered = true;
} catch (e) {}
});
}
if (!nativeCloseTriggered && clickNativeCloseAllTabs()) {
nativeCloseTriggered = true;
setTimeout(() => { clickNativeCloseAllConfirm(); }, 80);
setTimeout(() => { clickNativeCloseAllConfirm(); }, 220);
}
if (nativeCloseTriggered) {
navigateToTicketListHome();
setTimeout(() => {
const remainingIds = new Set(
collectNativeTicketTabs()
.map((tab) => Number(tab.id))
.filter((id) => Number.isFinite(id) && id > 0)
);
if (remainingIds.size === 0) {
clearLeftTabsCacheAll();
} else {
openedBefore.forEach((id) => {
if (remainingIds.has(id)) return;
const meta = _leftTabsOpenMeta[id] || {};
clearLeftTabsCacheForEntry({ ticketId: id, name: meta.name || '' });
});
}
scheduleLeftTabsMirrorSync();
}, 260);
return;
}
// Fallback: если нативный close-all недоступен, не стираем всё без фактического закрытия.
navigateToTicketListHome();
scheduleLeftTabsMirrorSync();
}
function leftTabsIsCaptchaLikely() {
const href = String(location.href || '');
if (/captcha|recaptcha|hcaptcha/i.test(href)) return true;
const title = String(document.title || '');
if (/captcha|капча|я не робот/i.test(title)) return true;
return false;
}
function markLeftTabsPollError(err) {
const now = Date.now();
_leftTabsPreviewErrorStreak += 1;
const backoff = Math.min(
LEFT_TABS_ERROR_MAX_BACKOFF_MS,
LEFT_TABS_ERROR_BASE_BACKOFF_MS * Math.pow(2, Math.min(_leftTabsPreviewErrorStreak, 5))
);
_leftTabsPreviewBackoffUntil = now + backoff;
const msg = String(err?.message || err || '');
const status = Number(err?.status || 0);
const url = String(err?.url || '');
if (status === 403 || status === 429 || /captcha|recaptcha|hcaptcha/i.test(`${msg} ${url}`)) {
_leftTabsCaptchaCooldownUntil = now + LEFT_TABS_CAPTCHA_COOLDOWN_MS;
}
}
function markLeftTabsPollSuccess() {
_leftTabsPreviewErrorStreak = 0;
_leftTabsPreviewBackoffUntil = 0;
}
function pruneLeftTabsRecentRequests(now = Date.now()) {
const edge = now - 60 * 1000;
while (_leftTabsRecentRequestTs.length && _leftTabsRecentRequestTs[0] < edge) {
_leftTabsRecentRequestTs.shift();
}
}
function canLeftTabsDoRequest(now = Date.now()) {
pruneLeftTabsRecentRequests(now);
return _leftTabsRecentRequestTs.length < LEFT_TABS_MAX_REQUESTS_PER_MINUTE;
}
function markLeftTabsRequest(now = Date.now()) {
pruneLeftTabsRecentRequests(now);
_leftTabsRecentRequestTs.push(now);
}
async function pollLeftTabsPreviews(force = false) {
if (!CONFIG.leftTabsMirror) return;
if (_leftTabsPreviewPollInFlight) {
if (force) _leftTabsPreviewPollForcePending = true;
return;
}
const now = Date.now();
if (_leftTabsCaptchaCooldownUntil > now) return;
if (_leftTabsPreviewBackoffUntil > now) return;
if (!force && document.hidden) return;
if (leftTabsIsCaptchaLikely()) {
_leftTabsCaptchaCooldownUntil = now + LEFT_TABS_CAPTCHA_COOLDOWN_MS;
return;
}
const minGap = force ? LEFT_TABS_PREVIEW_FORCE_MIN_GAP_MS : LEFT_TABS_PREVIEW_MIN_GAP_MS;
if ((now - _leftTabsPreviewLastRunAt) < minGap) return;
_leftTabsPreviewLastRunAt = now;
_leftTabsPreviewPollInFlight = true;
try {
const entries = collectTicketTabEntries();
const known = entries.filter((e) => Number.isFinite(e.ticketId));
if (!known.length) return;
// Активный тикет читаем прямо из DOM (extractCurrentTicketPreviewMeta).
// Приоритет №1: принудительный точечный тикет (когда badge поднялся только у него).
const forcedTargets = [];
if (_leftTabsForcedPreviewTicketIds.size > 0) {
for (const forcedId of _leftTabsForcedPreviewTicketIds) {
const forcedEntry = known.find((e) => e.ticketId === forcedId && !e.isActive);
if (forcedEntry) forcedTargets.push(forcedEntry);
}
}
const targets = forcedTargets.length
? forcedTargets
: known.filter((e) => e.hasBadge && !e.isActive);
if (!targets.length) return;
const nowTs = Date.now();
const dueTargets = forcedTargets.length
? forcedTargets
: targets
.filter((e) => {
const lastAt = Number(_leftTabsLastPollAtByTicketId[e.ticketId] || 0);
if (!lastAt) return true;
return (nowTs - lastAt) >= LEFT_TABS_PER_TICKET_MIN_GAP_MS;
})
.sort((a, b) => {
const aLast = Number(_leftTabsLastPollAtByTicketId[a.ticketId] || 0);
const bLast = Number(_leftTabsLastPollAtByTicketId[b.ticketId] || 0);
return aLast - bLast;
});
if (!dueTargets.length) return;
let changed = false;
const maxRequestsThisRun = Math.max(1, LEFT_TABS_MAX_REQUESTS_PER_RUN);
let requestsDone = 0;
for (const entry of dueTargets) {
if (requestsDone >= maxRequestsThisRun) break;
if (!canLeftTabsDoRequest()) break;
try {
markLeftTabsRequest();
const payload = await fetchConversationPage(entry.ticketId, 1);
requestsDone += 1;
_leftTabsLastPollAtByTicketId[entry.ticketId] = Date.now();
_leftTabsForcedPreviewTicketIds.delete(entry.ticketId);
const latestMeta = pickLatestPreviewFromConversation(payload);
markLeftTabsHydrated(entry.name, entry.ticketId);
const latest = latestMeta?.preview || '';
if (latest) {
const prevByIdPreview = Number.isFinite(entry.ticketId) ? String(_leftTabsPreviewByTicketId[entry.ticketId] || '') : '';
setLeftTabsPreview(entry.name, entry.ticketId, latest);
if (prevByIdPreview !== latest) changed = true;
}
if (latestMeta?.timeText) {
const prevByIdTimeText = Number.isFinite(entry.ticketId) ? String(_leftTabsLastMessageTimeTextByTicketId[entry.ticketId] || '') : '';
setLeftTabsTimeText(entry.name, entry.ticketId, latestMeta.timeText);
if (prevByIdTimeText !== latestMeta.timeText) changed = true;
}
if (latestMeta?.ts) {
const prevByIdTs = Number.isFinite(entry.ticketId) ? Number(_leftTabsLastMessageTsByTicketId[entry.ticketId] || 0) : 0;
setLeftTabsTs(entry.name, entry.ticketId, latestMeta.ts);
if (prevByIdTs !== latestMeta.ts) changed = true;
}
const nextBadge = inferUnreadBadgeFromConversation(payload, !!entry.isActive);
if (_leftTabsBadgeByTicketId[entry.ticketId] !== nextBadge) {
_leftTabsBadgeByTicketId[entry.ticketId] = nextBadge;
changed = true;
}
} catch (e) {
_leftTabsForcedPreviewTicketIds.delete(entry.ticketId);
markLeftTabsPollError(e);
}
}
if (requestsDone > 0) {
markLeftTabsPollSuccess();
}
if (changed) {
flushLeftTabsCacheNow();
}
// Рендерим каждую секунду: актуализируем preview/department даже без API-дельты.
scheduleLeftTabsMirrorSync();
} finally {
_leftTabsPreviewPollInFlight = false;
if (_leftTabsPreviewPollForcePending) {
_leftTabsPreviewPollForcePending = false;
setTimeout(() => {
pollLeftTabsPreviews(true);
}, LEFT_TABS_PREVIEW_FORCE_MIN_GAP_MS);
}
}
}
function syncLeftTabsPreviewPolling() {
if (!CONFIG.leftTabsMirror) {
if (_leftTabsPreviewPollInterval) {
clearInterval(_leftTabsPreviewPollInterval);
_leftTabsPreviewPollInterval = null;
}
_leftTabsForcedPreviewTicketIds.clear();
_leftTabsPreviewLastRunAt = 0;
return;
}
if (!_leftTabsPreviewPollInterval) {
_leftTabsPreviewPollInterval = setInterval(pollLeftTabsPreviews, LEFT_TABS_PREVIEW_POLL_MS);
pollLeftTabsPreviews(true);
return;
}
if ((Date.now() - _leftTabsPreviewLastRunAt) > LEFT_TABS_PREVIEW_POLL_MS * 2) {
pollLeftTabsPreviews(true);
}
}
function syncLeftTabsDepartmentPolling() {
if (!CONFIG.leftTabsMirror) {
if (_leftTabsDepartmentPollInterval) {
clearInterval(_leftTabsDepartmentPollInterval);
_leftTabsDepartmentPollInterval = null;
}
return;
}
if (!_leftTabsDepartmentPollInterval) {
_leftTabsDepartmentPollInterval = setInterval(refreshLeftTabsDepartmentMeta, LEFT_TABS_DEPARTMENT_REFRESH_MS);
refreshLeftTabsDepartmentMeta();
}
}
function syncLeftTabsMirror() {
if (_leftTabsMirrorSyncInProgress) {
_leftTabsMirrorSyncPending = true;
return;
}
syncNativeTicketTabsVisibilityClass();
if (!CONFIG.leftTabsMirror) {
removeLeftTabsMirror();
_leftTabsMirrorLastSignature = '';
syncLeftTabsPreviewPolling();
syncLeftTabsDepartmentPolling();
return;
}
const layout = findTicketMetroLayout();
if (!layout) {
removeLeftTabsMirror();
_leftTabsMirrorLastSignature = '';
syncLeftTabsPreviewPolling();
syncLeftTabsDepartmentPolling();
return;
}
const { metroContainer, ticketContainer } = layout;
const entries = collectTicketTabEntries();
if (!entries.length) {
removeLeftTabsMirror();
_leftTabsMirrorLastSignature = '';
syncLeftTabsPreviewPolling();
syncLeftTabsDepartmentPolling();
return;
}
syncLeftTabsCacheFromEntries(entries);
const signature = getLeftTabsSignature(entries);
const mirror = document.getElementById(LEFT_TABS_MIRROR_ID);
if (mirror && signature === _leftTabsMirrorLastSignature) {
const col = ensureLeftTabsColumnInLayout(metroContainer, ticketContainer);
if (mirror.parentElement !== col) col.appendChild(mirror);
syncLeftTabsPreviewPolling();
syncLeftTabsDepartmentPolling();
return;
}
_leftTabsMirrorSyncInProgress = true;
try {
const column = ensureLeftTabsColumnInLayout(metroContainer, ticketContainer);
let mirrorEl = document.getElementById(LEFT_TABS_MIRROR_ID);
if (!mirrorEl) {
mirrorEl = document.createElement('div');
mirrorEl.id = LEFT_TABS_MIRROR_ID;
mirrorEl.className = 'hde-left-tabs-mirror';
mirrorEl.innerHTML = `
<div class="hde-left-tabs-mirror__title">
<span class="hde-left-tabs-mirror__title-text">Тикеты</span>
<button type="button" class="hde-left-tabs-mirror__close-all" title="Закрыть все тикеты" aria-label="Закрыть все тикеты">✕</button>
</div>
<div class="hde-left-tabs-mirror__list"></div>
`;
column.appendChild(mirrorEl);
const closeAllBtn = mirrorEl.querySelector('.hde-left-tabs-mirror__close-all');
closeAllBtn?.addEventListener('click', (e) => {
e.stopPropagation();
closeAllTabsFromMirror();
});
} else if (mirrorEl.parentElement !== column) {
column.appendChild(mirrorEl);
}
mirrorEl.className = 'hde-left-tabs-mirror';
const titleEl = mirrorEl.querySelector('.hde-left-tabs-mirror__title');
if (titleEl && !titleEl.querySelector('.hde-left-tabs-mirror__close-all')) {
const rawTitle = (titleEl.textContent || '').trim() || 'Тикеты';
titleEl.innerHTML = `
<span class="hde-left-tabs-mirror__title-text">${escapeHtml(rawTitle)}</span>
<button type="button" class="hde-left-tabs-mirror__close-all" title="Закрыть все тикеты" aria-label="Закрыть все тикеты">✕</button>
`;
titleEl.querySelector('.hde-left-tabs-mirror__close-all')?.addEventListener('click', (e) => {
e.stopPropagation();
closeAllTabsFromMirror();
});
}
const list = mirrorEl.querySelector('.hde-left-tabs-mirror__list');
if (!list) {
return;
}
list.innerHTML = '';
const duplicateNameCount = entries.reduce((acc, entry) => {
const key = normalizeTabName(entry.name || '');
if (!key) return acc;
acc[key] = (acc[key] || 0) + 1;
return acc;
}, Object.create(null));
entries.forEach((entry) => {
const normName = normalizeTabName(entry.name || '');
const hasDuplicateName = normName && duplicateNameCount[normName] > 1;
const displayName = hasDuplicateName && Number.isFinite(entry.ticketId)
? `${entry.name} #${entry.ticketId}`
: entry.name;
const item = document.createElement('button');
item.type = 'button';
item.className = `hde-left-tabs-mirror__item${entry.isActive ? ' is-active' : ''}`;
item.innerHTML = `
<span class="hde-left-tabs-mirror__indicator${entry.hasBadge ? ' has-badge' : ''}" aria-hidden="true"></span>
<span class="hde-left-tabs-mirror__content">
<span class="hde-left-tabs-mirror__head">
<span class="hde-left-tabs-mirror__name">${escapeHtml(displayName)}</span>
<span class="hde-left-tabs-mirror__time">${escapeHtml(entry.lastTimeText || '')}</span>
</span>
<span class="hde-left-tabs-mirror__department">${escapeHtml(String(entry.department || '').trim())}</span>
<span class="hde-left-tabs-mirror__preview">${escapeHtml(entry.preview || (entry.hasBadge ? 'Новый ответ' : ''))}</span>
</span>
<span class="hde-left-tabs-mirror__close" aria-hidden="true" role="button" tabindex="0">✕</span>
`;
item.addEventListener('click', (e) => {
if (e.target instanceof Element
&& e.target.closest('.hde-left-tabs-mirror__close')) {
return;
}
entry.onOpen?.();
});
const close = item.querySelector('.hde-left-tabs-mirror__close');
close?.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
entry.onClose?.();
});
list.appendChild(item);
});
_leftTabsMirrorLastSignature = signature;
} finally {
_leftTabsMirrorSyncInProgress = false;
if (_leftTabsMirrorSyncPending) {
_leftTabsMirrorSyncPending = false;
requestAnimationFrame(() => {
syncLeftTabsMirror();
});
} else {
syncLeftTabsPreviewPolling();
syncLeftTabsDepartmentPolling();
}
}
}
function initLeftTabsMirrorObserver() {
if (window.__hdeLeftTabsMirrorObserver) return;
window.__hdeLeftTabsMirrorObserver = true;
const onLocationMaybeChanged = () => {
syncLeftTabsOnHrefChange();
};
window.addEventListener('popstate', onLocationMaybeChanged);
window.addEventListener('hashchange', onLocationMaybeChanged);
// Vue-router часто делает history.pushState без popstate — ловим расхождение href.
if (!window.__hdeLeftTabsHrefPollInterval) {
window.__hdeLeftTabsHrefPollInterval = setInterval(() => {
if (location.href !== _leftTabsLastHrefForSync) onLocationMaybeChanged();
}, 400);
}
if (!window.__hdeLeftTabsMirrorClickBound) {
window.__hdeLeftTabsMirrorClickBound = true;
document.addEventListener('click', (e) => {
const target = e.target;
if (!(target instanceof Element)) return;
if (target.closest('.hde-left-tabs-mirror__item')) {
setTimeout(scheduleLeftTabsMirrorSync, 0);
setTimeout(scheduleImmediateLeftTabsPreviewPoll, 0);
}
});
}
}
// ============================================================
// 5.1) Staffs Drawer
// ============================================================
function applyStaffsFrameTypography(frame) {
const doc = frame?.contentDocument;
if (!doc?.head) return;
if (doc.getElementById('hde-staffs-frame-font-style')) return;
const style = doc.createElement('style');
style.id = 'hde-staffs-frame-font-style';
style.textContent = `
body,
input,
textarea,
select,
button,
.el-input__inner,
.el-select-dropdown__item,
.el-tag,
.el-cascader-node__label,
.el-checkbox__label {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif !important;
}
`;
doc.head.appendChild(style);
}
function ensureStaffsDrawer() {
let drawer = document.getElementById(STAFFS_DRAWER_ID);
if (drawer) return drawer;
drawer = document.createElement('div');
drawer.id = STAFFS_DRAWER_ID;
drawer.className = 'hde-staffs-drawer';
drawer.innerHTML = `
<div class="hde-staffs-drawer__overlay" data-role="overlay"></div>
<div class="hde-staffs-drawer__panel" role="dialog" aria-label="Управление статусами сотрудников">
<div class="hde-staffs-drawer__header">
<span>Статусы сотрудников</span>
<button type="button" class="hde-staffs-drawer__close" aria-label="Закрыть">×</button>
</div>
<iframe class="hde-staffs-drawer__frame" title="Staff statuses" loading="lazy"></iframe>
</div>
`;
document.body.appendChild(drawer);
const closeBtn = drawer.querySelector('.hde-staffs-drawer__close');
const overlay = drawer.querySelector('[data-role="overlay"]');
const frame = drawer.querySelector('.hde-staffs-drawer__frame');
const closeDrawer = () => {
drawer.classList.remove('active');
if (CONFIG.colleagueStatuses) updateColleagueStatusesBlock();
};
if (frame && !frame.dataset.hdeFontBound) {
frame.dataset.hdeFontBound = '1';
frame.addEventListener('load', () => {
applyStaffsFrameTypography(frame);
});
}
closeBtn?.addEventListener('click', closeDrawer);
overlay?.addEventListener('click', closeDrawer);
if (!window.__hdeStaffsDrawerEscBound) {
window.__hdeStaffsDrawerEscBound = true;
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const d = document.getElementById(STAFFS_DRAWER_ID);
if (d?.classList.contains('active')) {
d.classList.remove('active');
if (CONFIG.colleagueStatuses) updateColleagueStatusesBlock();
}
}
});
}
return drawer;
}
function openStaffsDrawer() {
const drawer = preloadStaffsDrawer();
drawer.classList.add('active');
}
function preloadStaffsDrawer() {
const drawer = ensureStaffsDrawer();
const frame = drawer.querySelector('.hde-staffs-drawer__frame');
if (frame && !frame.src) frame.src = '/ru/dashboard/staffs/';
if (frame?.contentDocument?.readyState === 'complete') applyStaffsFrameTypography(frame);
return drawer;
}
// ─── UI Настроек ─────────────────────────────────────────
// ============================================================
// 7) Settings Panel + Menu Actions
// ============================================================
function initSettingsMenu() {
// Не возвращаемся сразу, если кнопка есть — нам важно уметь переткнуть её при SPA-перерисовке.
// Но если кнопка уже есть и она уже стоит в правильном месте — выходим.
const existingBtn = document.getElementById('hde-tools-menu');
const menu = document.querySelector('.menu');
const menuPlugins = document.querySelector('.menu-plugins');
// Правильное место: внутрь '.menu' (или в крайнем случае рядом с menu__items)
function isButtonInExpectedPlace() {
if (!existingBtn) return false;
if (menu && menu.contains(existingBtn)) return true;
return false;
}
if (existingBtn && isButtonInExpectedPlace()) {
const existingPanel = document.getElementById(MENU_PANEL_ID);
const hasColleagueToggle = !!existingPanel?.querySelector('#hde-cfg-colleague-statuses');
const hasLeftTabsToggle = !!existingPanel?.querySelector('#hde-cfg-left-tabs-mirror');
if (existingPanel && existingPanel.dataset.hdePanelVersion === SETTINGS_PANEL_VERSION && hasColleagueToggle && hasLeftTabsToggle) return;
}
if (!menu && !menuPlugins && !existingBtn) {
const observer = new MutationObserver(function () {
if (document.querySelector('.menu') || document.querySelector('.menu-plugins')) {
observer.disconnect();
initSettingsMenu();
}
});
observer.observe(document.body, { childList: true, subtree: true });
return;
}
let style = document.getElementById(MENU_STYLE_ID);
if (!style) {
style = document.createElement('style');
style.id = MENU_STYLE_ID;
style.textContent = `
.hde-tools-panel {
position: fixed; width: 280px;
background: #fff; color: #333; border-radius: 6px;
box-shadow: 0 4px 16px rgba(0,0,0,0.25); padding: 15px; display: none; z-index: 99999;
font-family: sans-serif; cursor: default;
}
.hde-tools-panel.active { display: block; }
.hde-tools-panel h3 { margin: 0 0 15px 0; font-size: 14px; border-bottom: 1px solid #eee; padding-bottom: 8px; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-weight: 600; color: #333; }
.hde-tools-toggle {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
column-gap: 10px;
margin-bottom: 10px;
font-size: 13px;
cursor: pointer;
}
.hde-tools-note {
font-size: 11px;
opacity: 0.8;
}
.hde-tools-toggle input[type="checkbox"] { appearance: none; width: 34px; height: 18px; background: #ddd; border-radius: 9px; position: relative; outline: none; cursor: pointer; transition: 0.3s; }
.hde-tools-toggle input[type="checkbox"]::after { content: ''; position: absolute; left: 2px; top: 2px; width: 14px; height: 14px; background: #fff; border-radius: 50%; transition: 0.3s; }
.hde-tools-toggle input[type="checkbox"]:checked { background: #4caf50; }
.hde-tools-toggle input[type="checkbox"]:checked::after { left: 18px; }
.hde-tools-toggle-actions { display: inline-flex; align-items: center; margin-left: auto; }
.hde-tools-link-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
padding: 0;
border: none;
background: transparent;
color: #606266;
text-decoration: none;
line-height: 1 !important;
}
.hde-tools-link-btn i {
font-size: 18px;
line-height: 1;
}
.hde-tools-toggle-actions .hde-tools-link-btn {
margin-right: 8px;
}
.hde-tools-link-btn:hover {
color: #409eff;
}
.hde-tools-field { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; font-size: 13px; }
.hde-tools-field input[type="number"],
.hde-tools-field select {
width: 72px; padding: 4px 6px; font-size: 12px; border: 1px solid #dcdfe6;
border-radius: 6px; text-align: right; outline: none;
}
.hde-tools-field select {
width: 120px; text-align: left; background: #fff;
}
.hde-staffs-drawer {
position: fixed;
inset: 0;
z-index: 100001;
visibility: hidden;
pointer-events: none;
transition: visibility 0s linear 0.3s;
}
.hde-staffs-drawer.active {
visibility: visible;
pointer-events: auto;
transition-delay: 0s;
}
.hde-staffs-drawer__overlay {
position: absolute;
inset: 0;
background: rgba(15, 20, 25, 0.45);
opacity: 0;
transition: opacity 0.3s ease;
}
.hde-staffs-drawer__panel {
position: absolute;
top: 0;
right: 0;
width: min(900px, 88vw);
height: 100%;
background: #fff;
border-left: 1px solid #dcdfe6;
box-shadow: -8px 0 24px rgba(0,0,0,0.18);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.hde-staffs-drawer.active .hde-staffs-drawer__overlay {
opacity: 1;
}
.hde-staffs-drawer.active .hde-staffs-drawer__panel {
transform: translateX(0);
}
.hde-staffs-drawer__header {
height: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
font-weight: 600;
border-bottom: 1px solid #ebeef5;
}
.hde-staffs-drawer__close {
width: 28px;
height: 28px;
border: none;
background: transparent;
color: #606266;
font-size: 22px;
line-height: 1;
cursor: pointer;
}
.hde-staffs-drawer__close:hover {
color: #303133;
}
.hde-staffs-drawer__frame {
width: 100%;
height: calc(100% - 44px);
border: 0;
background: #fff;
}
.hde-left-tabs-column {
flex: 0 0 180px;
width: 180px;
min-width: 180px;
box-sizing: border-box;
background: #ffffff;
}
.hde-left-tabs-column .hde-left-tabs-mirror {
margin: 0;
}
.hde-left-tabs-mirror {
margin: 0 0 10px 0;
border-radius: 6px;
background: #fff;
overflow: hidden;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif !important;
font-size: 12px !important;
color: #606266;
}
.hde-left-tabs-mirror,
.hde-left-tabs-mirror * {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif !important;
}
.hde-left-tabs-mirror__title {
padding: 6px 8px;
background: #F5F7FA;
display: flex;
width: 100%;
box-sizing: border-box;
min-height: 44px;
max-height: 44px;
align-items: center;
justify-content: space-between;
text-align: center;
font-size: 12px !important;
font-weight: 600;
color: #303133;
border-bottom: 1px solid #E5E5E5;
}
.hde-left-tabs-mirror__title-text {
flex: 1;
min-width: 0;
text-align: center;
padding-left: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hde-left-tabs-mirror__close-all {
position: relative;
z-index: 2;
flex: 0 0 auto;
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
font-size: 13px !important;
font-weight: 700;
line-height: 1;
padding: 0 8px;
opacity: .72;
}
.hde-left-tabs-mirror__close-all:hover {
opacity: 1;
}
.hde-left-tabs-mirror__list {
max-height: 100%;
overflow: auto;
}
.hde-left-tabs-mirror__item {
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
width: 100%;
border: 0;
border-bottom: 1px solid #f0f2f5;
background: transparent;
color: #606266;
font-size: 12px !important;
font-weight: 400;
line-height: 1.4;
font-family: inherit !important;
text-align: left;
padding: 6px 0px;
cursor: pointer;
}
.hde-left-tabs-mirror__indicator {
width: 8px;
min-width: 8px;
height: 8px;
border-radius: 50%;
background: transparent;
margin-right: 6px;
margin-top: 4px;
}
.hde-left-tabs-mirror__indicator.has-badge {
background: #f56c6c;
}
.hde-left-tabs-mirror__content {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.hde-left-tabs-mirror__head {
display: flex;
align-items: baseline;
gap: 6px;
min-width: 0;
width: 100%;
}
.hde-left-tabs-mirror__department {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
font-size: 10px !important;
line-height: 1.2 !important;
opacity: 0.75;
font-weight: 500;
color: #909399;
}
.hde-left-tabs-mirror__department:empty {
display: none;
}
.hde-left-tabs-mirror__item:last-child {
border-bottom: 0;
}
.hde-left-tabs-mirror__item:hover {
background: #f5f7fa;
}
.hde-left-tabs-mirror__item.is-active {
background: #ecf5ff;
color: #409eff;
}
.hde-left-tabs-mirror__name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
flex: 1;
font-family: inherit !important;
font-size: inherit !important;
font-weight: 600;
}
.hde-left-tabs-mirror__time {
flex: 0 0 auto;
font-size: 10px !important;
line-height: 1.1 !important;
opacity: 0.7;
white-space: nowrap;
margin-left: auto;
padding-right: 4px;
}
.hde-left-tabs-mirror__preview {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
font-size: 11px !important;
line-height: 1.25 !important;
opacity: 0.8;
}
.hde-left-tabs-mirror__close {
position: absolute;
right: 2px;
top: 50%;
transform: translateY(-50%);
z-index: 1;
flex: 0 0 auto;
align-self: flex-start;
border: 0;
background: transparent;
color: inherit;
font-size: inherit !important;
font-family: inherit !important;
cursor: pointer;
padding: 2px 4px;
margin: 0;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity .15s ease, visibility .15s ease;
}
.hde-left-tabs-mirror__item:hover .hde-left-tabs-mirror__close,
.hde-left-tabs-mirror__item:focus-within .hde-left-tabs-mirror__close {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
html.${DARK_HTML_CLASS} .hde-left-tabs-mirror {
background: #1e2529;
border-color: #3d4654;
}
html.${DARK_HTML_CLASS} .hde-left-tabs-column {
border-right: 1px solid #2d333b;
background: #1a1f26;
}
html.${DARK_HTML_CLASS} .hde-left-tabs-mirror__title {
background: #252b33;
color: #e4e7ed;
border-bottom-color: #3d4654;
}
html.${DARK_HTML_CLASS} .hde-left-tabs-mirror__item {
color: #c0c4cc;
border-bottom-color: #2d333b;
}
html.${DARK_HTML_CLASS} .hde-left-tabs-mirror__item:hover {
background: #252b33;
}
html.${DARK_HTML_CLASS} .hde-left-tabs-mirror__item.is-active {
background: #2d333b;
color: #e4e7ed;
}
html.${DARK_HTML_CLASS} .hde-left-tabs-mirror__department {
color: #909399;
opacity: 0.9;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-column {
border-right: 1px solid #e3ccd8;
background: #fbe9f2;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror {
background: #fff6fb;
border: 1px solid #eed9e5;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror__title {
background: #f8eaf2;
color: #7a5a6b;
border-bottom-color: #e3ccd8;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror__item {
color: #7a5a6b;
border-bottom-color: #edd9e5;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror__item:hover {
background: #f9edf4;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror__item.is-active {
background: #f4e1eb;
color: #6b4c5d;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror__department {
color: #9a778b;
opacity: 0.95;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror__preview {
color: #8b687c;
opacity: 0.92;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror__time {
color: #a78699;
}
html.${PINK_HTML_CLASS} .hde-left-tabs-mirror__indicator.has-badge {
background: #cf7a8f;
}
html.${PINK_HTML_CLASS} #ticket-app .el-loading-mask,
html.${PINK_HTML_CLASS} .el-loading-mask {
background-color: rgba(251, 233, 242, 0.92) !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-column {
border-right: 1px solid #2a4266;
background: #111c2f;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror {
background: #0f1728;
border: 1px solid #2a4266;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror__title {
background: #16243b;
color: #d4e4fb;
border-bottom-color: #2a4266;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror__item {
color: #b7cae7;
border-bottom-color: #233a5d;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror__item:hover {
background: #1b2d4a;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror__item.is-active {
background: #23395f;
color: #e5efff;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror__department {
color: #89a2c5;
opacity: 0.95;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror__preview {
color: #9bb3d4;
opacity: 0.93;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror__time {
color: #7f98bb;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .hde-left-tabs-mirror__indicator.has-badge {
background: #6cb8ff;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-column {
border-right: 1px solid #bfd0ec;
background: #f6f9ff;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror {
background: #ffffff;
border: 1px solid #d7e3f8;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror__title {
background: #edf4ff;
color: #1d3f73;
border-bottom-color: #bfd0ec;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror__item {
color: #2a4d82;
border-bottom-color: #e4ecfb;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror__item:hover {
background: #f3f7ff;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror__item.is-active {
background: #e1ebff;
color: #173968;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror__department {
color: #5570a0;
opacity: 0.95;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror__preview {
color: #4d6793;
opacity: 0.93;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror__time {
color: #6a81ac;
}
html.${TRICOLOR_HTML_CLASS} .hde-left-tabs-mirror__indicator.has-badge {
background: #d63445;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-column {
border-right: 1px solid #3a3a3a;
background: #121212;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror {
background: #171717;
border: 1px solid #3a3a3a;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror__title {
background: #202020;
color: #efefef;
border-bottom-color: #3a3a3a;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror__item {
color: #dedede;
border-bottom-color: #323232;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror__item:hover {
background: #232323;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror__item.is-active {
background: #2b2b2b;
color: #ffffff;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror__department {
color: #bbbbbb;
opacity: 0.95;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror__preview {
color: #c4c4c4;
opacity: 0.93;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror__time {
color: #a6a6a6;
}
html.${MONOCHROME_HTML_CLASS} .hde-left-tabs-mirror__indicator.has-badge {
background: #f1f1f1;
}
/* Унификация цвета стрелок popper под текущую тему */
:root {
--hde-popper-bg: #ffffff;
--hde-popper-border: #dcdfe6;
}
html.${DARK_HTML_CLASS} {
--hde-popper-bg: #1e2529;
--hde-popper-border: #3d4654;
}
html.${PINK_HTML_CLASS} {
--hde-popper-bg: #fff6fb;
--hde-popper-border: #e3ccd8;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} {
--hde-popper-bg: #15223a;
--hde-popper-border: #2a4266;
}
html.${TRICOLOR_HTML_CLASS} {
--hde-popper-bg: #ffffff;
--hde-popper-border: #bfd0ec;
}
html.${MONOCHROME_HTML_CLASS} {
--hde-popper-bg: #1b1b1b;
--hde-popper-border: #3a3a3a;
}
.el-popover,
.el-popper {
border-color: var(--hde-popper-border) !important;
}
.el-popover[x-placement^="top"] .popper__arrow,
.el-popover[x-placement^="bottom"] .popper__arrow,
.el-popover[x-placement^="left"] .popper__arrow,
.el-popover[x-placement^="right"] .popper__arrow,
.el-popper[data-popper-placement^="top"] .popper__arrow,
.el-popper[data-popper-placement^="bottom"] .popper__arrow,
.el-popper[data-popper-placement^="left"] .popper__arrow,
.el-popper[data-popper-placement^="right"] .popper__arrow,
.el-popover[data-popper-placement^="top"] .popper__arrow,
.el-popover[data-popper-placement^="bottom"] .popper__arrow,
.el-popover[data-popper-placement^="left"] .popper__arrow,
.el-popover[data-popper-placement^="right"] .popper__arrow {
border-top-color: var(--hde-popper-border) !important;
border-bottom-color: var(--hde-popper-border) !important;
border-left-color: var(--hde-popper-border) !important;
border-right-color: var(--hde-popper-border) !important;
}
.el-popover .popper__arrow::after,
.el-popper[data-popper-placement] .popper__arrow::after {
border-top-color: var(--hde-popper-bg) !important;
border-bottom-color: var(--hde-popper-bg) !important;
border-left-color: var(--hde-popper-bg) !important;
border-right-color: var(--hde-popper-bg) !important;
}
/* Popup уведомлений — явные цвета под темы */
html.${DARK_HTML_CLASS} .notifications {
background: #1e2529 !important;
color: #e4e7ed !important;
border: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} .notifications__heading,
html.${DARK_HTML_CLASS} .notifications__filters,
html.${DARK_HTML_CLASS} .notifications__item {
border-color: #3d4654 !important;
}
html.${DARK_HTML_CLASS} .notifications__heading-text,
html.${DARK_HTML_CLASS} .notifications__item-name,
html.${DARK_HTML_CLASS} .notifications__item-text,
html.${DARK_HTML_CLASS} .notifications__close-button,
html.${DARK_HTML_CLASS} .notifications__item-icon i {
color: #e4e7ed !important;
}
html.${DARK_HTML_CLASS} .notifications__item-date {
color: #a8abb2 !important;
}
html.${DARK_HTML_CLASS} .notifications__filter button {
background: #252b33 !important;
color: #c0c4cc !important;
border: 1px solid #3d4654 !important;
}
html.${DARK_HTML_CLASS} .notifications__filter button.active,
html.${DARK_HTML_CLASS} .notifications__filter button:hover {
background: #2d333b !important;
color: #e4e7ed !important;
border-color: #4a5563 !important;
}
html.${DARK_HTML_CLASS} .notifications__item:hover,
html.${DARK_HTML_CLASS} .notifications__item_unread {
background: #252b33 !important;
}
html.${PINK_HTML_CLASS} .notifications {
background: #fff6fb !important;
color: #6b4c5d !important;
border: 1px solid #e3ccd8 !important;
}
html.${PINK_HTML_CLASS} .notifications__heading,
html.${PINK_HTML_CLASS} .notifications__filters,
html.${PINK_HTML_CLASS} .notifications__item {
border-color: #e3ccd8 !important;
}
html.${PINK_HTML_CLASS} .notifications__heading-text,
html.${PINK_HTML_CLASS} .notifications__item-name,
html.${PINK_HTML_CLASS} .notifications__item-text,
html.${PINK_HTML_CLASS} .notifications__close-button,
html.${PINK_HTML_CLASS} .notifications__item-icon i {
color: #6b4c5d !important;
}
html.${PINK_HTML_CLASS} .notifications__item-date {
color: #9a778b !important;
}
html.${PINK_HTML_CLASS} .notifications__filter button {
background: #f8eaf2 !important;
color: #7a5a6b !important;
border: 1px solid #e3ccd8 !important;
}
html.${PINK_HTML_CLASS} .notifications__filter button.active,
html.${PINK_HTML_CLASS} .notifications__filter button:hover {
background: #f4e1eb !important;
color: #6b4c5d !important;
border-color: #dcbdd0 !important;
}
html.${PINK_HTML_CLASS} .notifications__item:hover,
html.${PINK_HTML_CLASS} .notifications__item_unread {
background: #fdf1f7 !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications {
background: #0f1728 !important;
color: #d4e4fb !important;
border: 1px solid #2a4266 !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__heading,
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__filters,
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__item {
border-color: #2a4266 !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__heading-text,
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__item-name,
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__item-text,
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__close-button,
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__item-icon i {
color: #d4e4fb !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__item-date {
color: #89a2c5 !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__filter button {
background: #16243b !important;
color: #b7cae7 !important;
border: 1px solid #2a4266 !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__filter button.active,
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__filter button:hover {
background: #23395f !important;
color: #e5efff !important;
border-color: #3f6292 !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__item:hover,
html.${MIDNIGHT_BLUE_HTML_CLASS} .notifications__item_unread {
background: #1b2d4a !important;
}
html.${TRICOLOR_HTML_CLASS} .notifications {
background: #ffffff !important;
color: #1d3f73 !important;
border: 1px solid #bfd0ec !important;
}
html.${TRICOLOR_HTML_CLASS} .notifications__heading,
html.${TRICOLOR_HTML_CLASS} .notifications__filters,
html.${TRICOLOR_HTML_CLASS} .notifications__item {
border-color: #d6e2f7 !important;
}
html.${TRICOLOR_HTML_CLASS} .notifications__heading-text,
html.${TRICOLOR_HTML_CLASS} .notifications__item-name,
html.${TRICOLOR_HTML_CLASS} .notifications__item-text,
html.${TRICOLOR_HTML_CLASS} .notifications__close-button,
html.${TRICOLOR_HTML_CLASS} .notifications__item-icon i {
color: #1d3f73 !important;
}
html.${TRICOLOR_HTML_CLASS} .notifications__item-date {
color: #6a81ac !important;
}
html.${TRICOLOR_HTML_CLASS} .notifications__filter button {
background: #edf4ff !important;
color: #2a4d82 !important;
border: 1px solid #bfd0ec !important;
}
html.${TRICOLOR_HTML_CLASS} .notifications__filter button.active,
html.${TRICOLOR_HTML_CLASS} .notifications__filter button:hover {
background: #e1ebff !important;
color: #173968 !important;
border-color: #a8c0e7 !important;
}
html.${TRICOLOR_HTML_CLASS} .notifications__item:hover,
html.${TRICOLOR_HTML_CLASS} .notifications__item_unread {
background: #f3f7ff !important;
}
html.${MONOCHROME_HTML_CLASS} .notifications {
background: #171717 !important;
color: #f1f1f1 !important;
border: 1px solid #3a3a3a !important;
}
html.${MONOCHROME_HTML_CLASS} .notifications__heading,
html.${MONOCHROME_HTML_CLASS} .notifications__filters,
html.${MONOCHROME_HTML_CLASS} .notifications__item {
border-color: #3a3a3a !important;
}
html.${MONOCHROME_HTML_CLASS} .notifications__heading-text,
html.${MONOCHROME_HTML_CLASS} .notifications__item-name,
html.${MONOCHROME_HTML_CLASS} .notifications__item-text,
html.${MONOCHROME_HTML_CLASS} .notifications__close-button,
html.${MONOCHROME_HTML_CLASS} .notifications__item-icon i {
color: #f1f1f1 !important;
}
html.${MONOCHROME_HTML_CLASS} .notifications__item-date {
color: #a9a9a9 !important;
}
html.${MONOCHROME_HTML_CLASS} .notifications__filter button {
background: #222222 !important;
color: #d8d8d8 !important;
border: 1px solid #3a3a3a !important;
}
html.${MONOCHROME_HTML_CLASS} .notifications__filter button.active,
html.${MONOCHROME_HTML_CLASS} .notifications__filter button:hover {
background: #2c2c2c !important;
color: #ffffff !important;
border-color: #535353 !important;
}
html.${MONOCHROME_HTML_CLASS} .notifications__item:hover,
html.${MONOCHROME_HTML_CLASS} .notifications__item_unread {
background: #242424 !important;
}
/* Контрастные сине-красные акценты для основных action-кнопок */
html.${TRICOLOR_HTML_CLASS} #ticket-app .el-button.el-button--primary,
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-editor__action_type_post,
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-editor__action_type_comment {
background: #d63445 !important;
border-color: #bf2f3f !important;
color: #ffffff !important;
}
html.${TRICOLOR_HTML_CLASS} #ticket-app .el-button.el-button--primary:hover,
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-editor__action_type_post:hover,
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-editor__action_type_comment:hover {
background: #c42f3f !important;
border-color: #a92836 !important;
}
html.${MONOCHROME_HTML_CLASS} #ticket-app .el-button.el-button--primary,
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-editor__action_type_post,
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-editor__action_type_comment {
background: #f1f1f1 !important;
border-color: #d3d3d3 !important;
color: #121212 !important;
}
html.${MONOCHROME_HTML_CLASS} #ticket-app .el-button.el-button--primary:hover,
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-editor__action_type_post:hover,
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-editor__action_type_comment:hover {
background: #dcdcdc !important;
border-color: #c4c4c4 !important;
}
/* Акцентные фильтры sidebar: сохраняем смысл цвета, но смягчаем оттенок в темах */
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(255, 0, 0)"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(225, 18, 18)"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(255, 0, 0)"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(225, 18, 18)"] {
color: #ff8f8f !important;
}
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(81, 169, 251)"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(13, 113, 207)"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(81, 169, 251)"],
html.${DARK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(13, 113, 207)"] {
color: #87bdf2 !important;
}
html.${PINK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(255, 0, 0)"],
html.${PINK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(225, 18, 18)"],
html.${PINK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(255, 0, 0)"],
html.${PINK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(225, 18, 18)"] {
color: #c8657b !important;
}
html.${PINK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(81, 169, 251)"],
html.${PINK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(13, 113, 207)"],
html.${PINK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(81, 169, 251)"],
html.${PINK_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(13, 113, 207)"] {
color: #6f9ccc !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(255, 0, 0)"],
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(225, 18, 18)"],
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(255, 0, 0)"],
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(225, 18, 18)"] {
color: #ff9a9a !important;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(81, 169, 251)"],
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(13, 113, 207)"],
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(81, 169, 251)"],
html.${MIDNIGHT_BLUE_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(13, 113, 207)"] {
color: #8ec2ff !important;
}
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(255, 0, 0)"],
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(225, 18, 18)"],
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(255, 0, 0)"],
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(225, 18, 18)"] {
color: #d63445 !important;
}
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(81, 169, 251)"],
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(13, 113, 207)"],
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(81, 169, 251)"],
html.${TRICOLOR_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(13, 113, 207)"] {
color: #1f6fc5 !important;
}
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(255, 0, 0)"],
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(225, 18, 18)"],
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(255, 0, 0)"],
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(225, 18, 18)"] {
color: #d6d6d6 !important;
}
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(81, 169, 251)"],
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-sidebar__filter-button[style*="rgb(13, 113, 207)"],
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(81, 169, 251)"],
html.${MONOCHROME_HTML_CLASS} #ticket-app .ticket-sidebar__filter-name[style*="rgb(13, 113, 207)"] {
color: #a6a6a6 !important;
}
.staffs-injected-block {
border-radius: 3px;
padding-bottom: 10px;
margin-bottom: 12px;
text-align: center;
--staff-status-online: #3e9a55;
--staff-status-invisible: #3f78b8;
--staff-status-break: #c9872c;
--staff-status-offline: #c25353;
--staffs-muted: #c5c9d0;
--staffs-group-color: #555;
}
.staffs-injected-group-header {
display: flex;
align-items: center;
margin: 0;
font-weight: 700;
color: var(--staffs-group-color);
}
.staffs-injected-group-total {
flex: 0 0 auto;
padding-right: 0;
margin: 0 0 0 6px;
}
.staffs-injected-group-name {
flex: 1;
text-align: center;
}
.staffs-injected-list {
list-style: none;
padding: 0;
margin: 0 0 8px 0;
}
.staffs-injected-member-row {
display: flex;
align-items: center;
padding: 0;
}
.staffs-injected-badge {
display: inline-block;
width: 18px;
text-align: center;
font-size: 13px;
line-height: 1;
}
.staffs-injected-badge.no-tickets {
color: var(--staffs-muted);
}
.staffs-injected-member-name {
font-weight: 400;
flex: 1;
text-align: left;
}
.staffs-injected-member-status {
white-space: nowrap;
margin: 0 5px 0 0;
}
.staffs-injected-member-name.status-online,
.staffs-injected-member-status.status-online,
.staffs-injected-badge.status-online.has-tickets {
color: var(--staff-status-online);
}
.staffs-injected-member-name.status-invisible,
.staffs-injected-member-status.status-invisible,
.staffs-injected-badge.status-invisible.has-tickets {
color: var(--staff-status-invisible);
}
.staffs-injected-member-name.status-break,
.staffs-injected-member-status.status-break,
.staffs-injected-badge.status-break.has-tickets {
color: var(--staff-status-break);
}
.staffs-injected-member-name.status-offline,
.staffs-injected-member-status.status-offline,
.staffs-injected-badge.status-offline.has-tickets {
color: var(--staff-status-offline);
}
html.${DARK_HTML_CLASS} .staffs-injected-block {
--staff-status-online: #78c18b;
--staff-status-invisible: #7cb5ff;
--staff-status-break: #f4b562;
--staff-status-offline: #ef8a8a;
--staffs-muted: #6f7883;
--staffs-group-color: #c0c4cc;
}
html.${PINK_HTML_CLASS} .staffs-injected-block {
--staff-status-online: #7fae7f;
--staff-status-invisible: #8aa4d6;
--staff-status-break: #d5a06a;
--staff-status-offline: #cf7a8f;
--staffs-muted: #b79fb0;
--staffs-group-color: #7a5a6b;
}
html.${MIDNIGHT_BLUE_HTML_CLASS} .staffs-injected-block {
--staff-status-online: #7bcf9a;
--staff-status-invisible: #8ec2ff;
--staff-status-break: #e3b679;
--staff-status-offline: #ff9a9a;
--staffs-muted: #7f98bb;
--staffs-group-color: #b7cae7;
}
html.${TRICOLOR_HTML_CLASS} .staffs-injected-block {
--staff-status-online: #4cae7d;
--staff-status-invisible: #1f6fc5;
--staff-status-break: #c08a45;
--staff-status-offline: #d63445;
--staffs-muted: #6a81ac;
--staffs-group-color: #2a4d82;
}
html.${MONOCHROME_HTML_CLASS} .staffs-injected-block {
--staff-status-online: #dcdcdc;
--staff-status-invisible: #c4c4c4;
--staff-status-break: #adadad;
--staff-status-offline: #8f8f8f;
--staffs-muted: #787878;
--staffs-group-color: #efefef;
}
/* Чтобы пункт меню/кнопка НИКОГДА не прятались (Vue может менять display/opacity) */
#hde-tools-menu { display: inline-flex !important; opacity: 1 !important; visibility: visible !important; }
#hde-tools-menu .menu__item { display: inline-flex !important; opacity: 1 !important; visibility: visible !important; }
#hde-tools-menu * { opacity: 1 !important; }
a.menu__item.menu__item_logo {
position: relative !important;
background-image: none !important;
background-color: transparent !important;
color: var(--menu-item-color, #606266) !important;
}
a.menu__item.menu__item_logo::before {
content: '';
position: absolute;
inset: 0;
background: currentColor;
-webkit-mask: url("//cdn5.helpdeskeddy.com//img/menu-logo-white.svg") center / contain no-repeat;
mask: url("//cdn5.helpdeskeddy.com//img/menu-logo-white.svg") center / contain no-repeat;
}
a.menu__item.menu__item_logo:hover {
color: var(--menu-item-color-hover, #409eff) !important;
}
a.menu__item[href="/ru/dashboard"],
a.menu__item[href="/ru/dashboard/"] {
display: none !important;
}
#hde-tools-btn {
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
gap: 4px !important;
text-align: center !important;
}
#hde-tools-btn .menu__item-icon,
#hde-tools-btn .menu__item-name {
display: block !important;
margin: 0 !important;
}
#hde-tools-btn .menu__item-icon {
position: relative;
width: 25px;
height: 25px;
}
#hde-tools-btn .menu__item-icon .hde-dino-source {
display: none !important;
}
#hde-tools-btn .menu__item-icon .hde-dino-mask {
position: absolute;
inset: 0;
background: currentColor;
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
}
html.${LEFT_TABS_ONLY_CLASS} #ticket-app .ticket-topbar__tabs {
display: none !important;
}
html.${LEFT_TABS_ONLY_CLASS} .ticket-tabs__show-more-popper {
display: none !important;
}
`;
document.head.appendChild(style);
}
const menuItems = document.querySelector('.menu__items');
const btnContainer = existingBtn || document.createElement('div');
btnContainer.id = 'hde-tools-menu';
btnContainer.style.position = 'relative';
if (!existingBtn) {
const btn = document.createElement('a');
btn.className = 'menu__item';
btn.href = 'javascript:void(0)';
btn.id = 'hde-tools-btn';
btn.title = 'HDE Tools Settings';
btn.innerHTML = `<div data-v-808034fc="" class="menu__item-icon"><img width="25" height="25" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAFOElEQVR4nO2ae4jVRRTHz9WydbPWntKLyiyIMoLsjwqR7OWiZe8MooLe9MTIKLZcgtISgiIjUwMrSgx7/VFbLaZkBVbW+kel9jC3tujllrmaun7itN/f7ni7j9+9d+7uFfrC5d6Z35wzc34z851zzlyz/1EagLOB9cB3wFm2KwI4AeiiD5uA462WABwHLAHeAM7J08afO54C5uh3q9UQMsAnwZveAUwNG/ib17NOM9vbzBqAP1RXG7MCnKIBfQM0A9tlzJVBm/uT2QjqnlbdfVYLAO7SgB5X+VqVN5jZQap7XXUXB3KXqO41qwUAj2lAt6lqEPCB6tqBDs1QPnQCs4DTzWy3gTRktgZ0O3AzsI7y4cvzejMbMhCGJOt/SzCgtcADQKOZHWxmg3OI7g4cAZwBzATWBPJtzoT9bcjdwQC+Aib78ipDlS/JS4HV0uVnzoXVoFjn/1+BX4C3geuAy4Gt6niBmdVF6GtP4BnpdN3nWywA44qs7egUCjwk3ZujLTNgkpS+ZWYHaDYSJpofpZPc/c5THyujEABwrBR2aC3fqvKXZlZv1cMw7TvEZlH2iFOjY7K8V8dFVmXQd3CuzcN+JSu8Qwp/1/dqN7BA+4nA9zoUG0voZ2KW3GDRuWN8xYaY2R6BQseDRQbkA0mwvgRD2rPldM44ZhaSTcv5f5uZ+0pbVF5q/YdWfY+NplHT7jiySLtGvV2PCCeUoL8xWw44XH1+a5FQl7gi7mJY/2GoDOmqSAtwMvBSGKpGG2L6MfyLShTcC3T3qOn9Llkh8K4cw2k6qf38qVf0OE1EsrwqhgBjJL8NeETearmGLKU4qmbIaZLf4CFthQrdI5gAPK/TepM+PhPPaWPnZdCKDNGJ/oJ0eCw+t1KF5YII/Q5RKOqG9ML6GcTqV5sy1YwA7wPLygywciL6C0xpSBK2TordL9DiJFSOjv3lyF0DTElpSOJgtpQ98ixksdt2BXOZtMJXK0LLxp9Bm2XACs8eBqINYqRuM9snVWfFx5Lg0WC/3plW2HNSjveUGVys8sdBm5Wqeyd0WzyajLm8EPT73CAJmEo4yelOUfk8lWcHbY5WUsLxchIxAtNV1xzTEOAm4FP9XpNW+EYJdMgDXRAaFrQ7SRlDZPyYIByeFdmQBJ4IPDGtvEdnyyX4ufaLxyP75uhodBBfOzbGjLXpwyvAVR7olarjQGBVoOjFAm0bFM0ls+MkMMwiAKFSJb50UAoozXT6QXhoxAOxrtR4xH2rJ7WEfnYm8qWhm6gkmzgQOEz9t6dqDZxKfjjVjSjgkrjxP4qqnesv0+1UxaCPLVtLuXV1vOkHGTAjMOSKAnI++FzwiHI+MKpCQ+ZI38NpBUZJwBPVI4NEw7MpxD2BPQI4E7hHl5/dQTK6ucy0Z31AHp4vmJ5KT5C78j3iWFIO1UnXSOVvE4M8hTS8RB1NwcvdEYxpeFpBR1upHefROTZIvH2WlpLpOZvcZ0M3AuOCVdKW5d/tJDg+cDc+NLP9LC7zfCHdC1O0PyQ4YOfl0dPyH4rX/Z8nGRyvViPLDhwF/KY+bigyE1+r3Uee18p67u7ST3p+S7YRCWbEjOxyDPKC4IpiaI6N3RQspxX5wgC/yVKbH8LK5C01VcuAPH/nmKoUk7vlTwSZfsfcQqtCs+ubf2vy4v00X2Vmo/25mXkGfJ2ZbTazjZIbFGysvUQAyacsRouIxZlMpuePCMAxwKKAcncFdIk0ej3xneJenzKxlbsX/kluibaZ2V9m5o5bZyaT8T/IuNviM1cT+AdBA3PwGaO09gAAAABJRU5ErkJggg==" alt="kawaii-dinosaur--v2"></div><div data-v-808034fc="" class="menu__item-name"> HDE toys </div>`;
btnContainer.appendChild(btn);
}
const btn = btnContainer.querySelector('#hde-tools-btn') || btnContainer.querySelector('a');
const dinoIconWrap = btn.querySelector('.menu__item-icon');
const dinoSourceImg = dinoIconWrap?.querySelector('img');
if (dinoIconWrap && dinoSourceImg) {
dinoSourceImg.classList.add('hde-dino-source');
let dinoMask = dinoIconWrap.querySelector('.hde-dino-mask');
if (!dinoMask) {
dinoMask = document.createElement('span');
dinoMask.className = 'hde-dino-mask';
dinoMask.setAttribute('aria-hidden', 'true');
dinoIconWrap.appendChild(dinoMask);
}
const src = dinoSourceImg.currentSrc || dinoSourceImg.src;
dinoMask.style.webkitMaskImage = `url("${src}")`;
dinoMask.style.maskImage = `url("${src}")`;
}
const panel = document.getElementById(MENU_PANEL_ID) || document.createElement('div');
panel.id = MENU_PANEL_ID;
panel.className = 'hde-tools-panel';
panel.innerHTML = `
<label class="hde-tools-field">
<span>Тема интерфейса</span>
<select id="hde-cfg-theme-mode">
${renderThemeOptions(CONFIG.themeMode)}
</select>
</label>
<label class="hde-tools-toggle">
<span>Компактный чат</span>
<input type="checkbox" id="hde-cfg-compact" ${CONFIG.compactChat ? 'checked' : ''}>
</label>
<label class="hde-tools-toggle">
<span>Левая панель тикетов<br><span class="hde-tools-note">может вызывать капчку</span></span>
<input type="checkbox" id="hde-cfg-left-tabs-mirror" ${CONFIG.leftTabsMirror ? 'checked' : ''}>
</label>
<label class="hde-tools-toggle">
<span>Скрыть родную панель тикетов<br><span class="hde-tools-note">временный режим для тестов</span></span>
<input type="checkbox" id="hde-cfg-hide-native-tabs" ${CONFIG.hideNativeTicketTabs ? 'checked' : ''}>
</label>
<label class="hde-tools-toggle hde-tools-toggle-colleague">
<span>Статусы коллег</span>
<span class="hde-tools-toggle-actions">
<a
id="hde-colleague-staffs-link"
class="hde-tools-link-btn"
href="/ru/dashboard/staffs/"
target="_blank"
rel="noopener noreferrer"
title="Открыть аналитику сотрудников"
aria-label="Открыть аналитику сотрудников"
><i class="el-icon-setting"></i></a>
<input type="checkbox" id="hde-cfg-colleague-statuses" ${CONFIG.colleagueStatuses ? 'checked' : ''}>
</span>
</label>
<label class="hde-tools-toggle">
<span>Подгружать историю</span>
<input type="checkbox" id="hde-cfg-autoload" ${CONFIG.autoLoadHistory ? 'checked' : ''}>
</label>
<label class="hde-tools-toggle">
<span>Подгружать прошлые диалоги</span>
<input type="checkbox" id="hde-cfg-prev-dialogs" ${CONFIG.autoLoadPreviousDialogs ? 'checked' : ''}>
</label>
<label class="hde-tools-field">
<span>Кол-во подгружаемых диалогов</span>
<input type="number" id="hde-cfg-prev-limit" min="1" max="10" step="1" value="${getPreviousDialogsLimit()}">
</label>
`;
panel.dataset.hdePanelVersion = SETTINGS_PANEL_VERSION;
// Иконка динозаврика в меню
function positionPanel() {
const rect = btn.getBoundingClientRect();
panel.style.left = (rect.right + 8) + 'px';
panel.style.top = rect.top + 'px';
}
// Клик по нативному menu__item — предотвращаем переход по href
if (!btn.dataset.hdeToolsBound) {
btn.dataset.hdeToolsBound = '1';
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (CONFIG.colleagueStatuses) preloadStaffsDrawer();
const isActive = panel.classList.contains('active');
if (isActive) {
panel.classList.remove('active');
} else {
positionPanel();
panel.classList.add('active');
}
});
}
if (!window.__hdeToolsDocClickBound) {
window.__hdeToolsDocClickBound = true;
document.addEventListener('click', (e) => {
const currentPanel = document.getElementById(MENU_PANEL_ID);
const currentBtn = document.getElementById('hde-tools-btn');
if (!currentPanel || !currentBtn) return;
if (!currentPanel.contains(e.target) && e.target !== currentBtn && !currentBtn.contains(e.target)) {
currentPanel.classList.remove('active');
}
});
}
panel.querySelector('#hde-cfg-theme-mode').onchange = (e) => {
CONFIG.themeMode = normalizeThemeMode(e.target.value);
saveConfig();
applyThemeFromConfig();
};
panel.querySelector('#hde-cfg-compact').onchange = (e) => {
CONFIG.compactChat = e.target.checked;
saveConfig();
if (CONFIG.compactChat) {
injectStyle();
processAllMessages();
} else {
alert('Для отключения компактного вида необходимо обновить страницу');
}
};
panel.querySelector('#hde-cfg-left-tabs-mirror').onchange = (e) => {
CONFIG.leftTabsMirror = e.target.checked;
saveConfig();
syncNativeTicketTabsVisibilityClass();
syncLeftTabsOnHrefChange();
syncLeftTabsMirror();
};
panel.querySelector('#hde-cfg-hide-native-tabs').onchange = (e) => {
CONFIG.hideNativeTicketTabs = e.target.checked;
saveConfig();
syncNativeTicketTabsVisibilityClass();
syncLeftTabsMirror();
};
panel.querySelector('#hde-cfg-colleague-statuses').onchange = (e) => {
CONFIG.colleagueStatuses = e.target.checked;
saveConfig();
syncColleagueStatusesModule();
syncColleagueStaffsLinkVisibility();
if (!CONFIG.colleagueStatuses) {
const drawer = document.getElementById(STAFFS_DRAWER_ID);
if (drawer) drawer.classList.remove('active');
}
};
panel.querySelector('#hde-cfg-autoload').onchange = (e) => {
CONFIG.autoLoadHistory = e.target.checked;
saveConfig();
syncPrevDialogsVisibility();
if (CONFIG.autoLoadHistory) {
injectPaginationStyle();
initAutoLoad();
} else {
removePaginationStyle();
}
};
panel.querySelector('#hde-cfg-prev-dialogs').onchange = (e) => {
CONFIG.autoLoadPreviousDialogs = e.target.checked;
const prevLimitField = panel.querySelector('#hde-cfg-prev-limit')?.closest('label');
if (prevLimitField) prevLimitField.style.display = CONFIG.autoLoadPreviousDialogs ? 'flex' : 'none';
saveConfig();
if (CONFIG.autoLoadPreviousDialogs) {
_historyLoadedForTicketId = null;
initAutoLoad();
} else {
clearPreviousDialogsFromView();
}
};
panel.querySelector('#hde-cfg-prev-limit').onchange = (e) => {
const nextValue = parseInt(e.target.value, 10);
CONFIG.previousDialogsLimit = Number.isFinite(nextValue)
? Math.min(10, Math.max(1, nextValue))
: DEFAULT_PREVIOUS_TICKETS_TO_LOAD;
e.target.value = String(CONFIG.previousDialogsLimit);
saveConfig();
if (CONFIG.autoLoadPreviousDialogs) {
_historyLoadedForTicketId = null;
clearPreviousDialogsFromView();
initAutoLoad();
}
};
const prevLimitField = panel.querySelector('#hde-cfg-prev-limit')?.closest('label');
const prevDialogsToggle = panel.querySelector('#hde-cfg-prev-dialogs');
const prevDialogsLabel = prevDialogsToggle && prevDialogsToggle.closest('label');
const colleagueStaffsLink = panel.querySelector('#hde-colleague-staffs-link');
if (colleagueStaffsLink && !colleagueStaffsLink.dataset.hdeBound) {
colleagueStaffsLink.dataset.hdeBound = '1';
colleagueStaffsLink.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
openStaffsDrawer();
});
}
function syncPrevDialogsVisibility() {
const show = CONFIG.autoLoadHistory;
if (prevDialogsLabel) prevDialogsLabel.style.display = show ? '' : 'none';
if (prevLimitField) prevLimitField.style.display = (show && CONFIG.autoLoadPreviousDialogs) ? 'flex' : 'none';
}
function syncColleagueStaffsLinkVisibility() {
if (!colleagueStaffsLink) return;
colleagueStaffsLink.style.display = CONFIG.colleagueStatuses ? 'inline-flex' : 'none';
}
syncPrevDialogsVisibility();
syncColleagueStaffsLinkVisibility();
// btnContainer мог уже существовать (SPA-перерисовка). Не дублируем btn.
if (!btnContainer.querySelector('#hde-tools-btn')) {
btnContainer.appendChild(btn);
}
if (!panel.parentElement) {
document.body.appendChild(panel);
}
// Вставляем на уровень выше, чем '.menu-plugins' (внутрь '.menu')
// Иначе SPA может выкинуть/пересобрать узел '.menu__items'.
if (menu && btnContainer && !menu.contains(btnContainer)) {
menu.appendChild(btnContainer);
} else if (!menu && (menuItems && !menuItems.contains(btnContainer))) {
menuItems.appendChild(btnContainer);
}
// Если '.menu-plugins' уже есть — можно попробовать вставить рядом, но без риска отката
// (главное — чтобы контейнер был в '.menu' и был виден сразу)
if (menuPlugins && menuItems && menu && btnContainer) {
try { menuItems.insertBefore(btnContainer, menuPlugins); } catch (e) {}
}
// Наблюдатель: если Vue снова пересобирает '.menu__items' — переткнём в ближайший '.menu'
// (а не в "первый найденный .menu" — это ломается, если элементов несколько)
if (!window.__hdeToolsMenuObserver) {
window.__hdeToolsMenuObserver = true;
const menuObserver = new MutationObserver(function () {
const b = document.getElementById('hde-tools-menu');
if (!b) return;
const targetMenu = b.closest('.menu') || document.querySelector('.menu');
if (targetMenu && !targetMenu.contains(b)) {
try { targetMenu.appendChild(b); } catch (e) {}
}
});
menuObserver.observe(document.body, { childList: true, subtree: true });
}
}
// ─── Перемещение кнопки истории в .ticket-detail__title ────────
// Физически переносим в нужное место. Vue-обработчики сидят на элементе,
// поэтому дропдаун работает. Проблема закрытия — Vue слушает document click
// и проверяет contains() относительно исходного парента —
// после переноса это сохраняется, поэтому всё должно работать.
function moveHistoryButton() {
if (!CONFIG.autoLoadHistory || !CONFIG.autoLoadPreviousDialogs) return;
const origBtn = document.querySelector('.ticket-detail__history');
const titleEl = document.querySelector('.ticket-detail__title');
if (!origBtn || !titleEl) return;
if (titleEl.contains(origBtn)) return;
titleEl.appendChild(origBtn);
}
function onBodyReady(fn) {
if (document.body) {
fn();
return;
}
document.addEventListener('DOMContentLoaded', fn, { once: true });
}
// Ранний плейсхолдер левой колонки, чтобы не было "вспышки" при перезагрузке.
(function bootLeftTabsColumnEarly() {
if (!getEarlyLeftTabsMirrorFromStorage()) return;
const tryInsert = () => {
const layout = findTicketMetroLayout();
if (!layout) return false;
const column = ensureLeftTabsColumnInLayout(layout.metroContainer, layout.ticketContainer);
ensureEarlyLeftTabsSkeleton(column);
return true;
};
onBodyReady(() => {
if (tryInsert()) return;
const observer = new MutationObserver(() => {
if (tryInsert()) observer.disconnect();
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => observer.disconnect(), 5000);
});
})();
// Ранний запуск меню: показываем кнопку сразу после появления body/menu,
// не дожидаясь полного init(), чтобы убрать визуальную задержку.
(function bootSettingsMenuEarly() {
if (window.__hdeToolsMenuEarlyBoot) return;
window.__hdeToolsMenuEarlyBoot = true;
const isReady = () => {
const btn = document.getElementById('hde-tools-btn');
const panel = document.getElementById(MENU_PANEL_ID);
return !!(btn && panel && btn.dataset.hdeToolsBound === '1');
};
const start = () => {
loadConfig();
initSettingsMenu();
// На некоторых загрузках Vue дорисовывает меню с задержкой.
// Короткий "быстрый догон" делает появление кнопки визуально мгновенным.
if (window.__hdeToolsMenuEarlyRetryTimer) {
clearInterval(window.__hdeToolsMenuEarlyRetryTimer);
}
const startedAt = Date.now();
window.__hdeToolsMenuEarlyRetryTimer = setInterval(() => {
if (isReady() || (Date.now() - startedAt) > 5000) {
clearInterval(window.__hdeToolsMenuEarlyRetryTimer);
window.__hdeToolsMenuEarlyRetryTimer = null;
return;
}
initSettingsMenu();
}, 80);
if (!window.__hdeToolsMenuEarlyObserver && document.body) {
window.__hdeToolsMenuEarlyObserver = new MutationObserver(() => {
if (!isReady()) initSettingsMenu();
});
window.__hdeToolsMenuEarlyObserver.observe(document.body, { childList: true, subtree: true });
}
};
if (document.body) {
start();
return;
}
const root = document.documentElement;
if (!root) return;
const observer = new MutationObserver(() => {
if (!document.body) return;
observer.disconnect();
start();
});
observer.observe(root, { childList: true });
})();
(function initHistoryButtonMover() {
onBodyReady(function () {
moveHistoryButton();
if (!window.__hdeHistoryBtnObserver) {
window.__hdeHistoryBtnObserver = true;
new MutationObserver(function () {
moveHistoryButton();
injectHistoryCloseButton();
}).observe(document.body, { childList: true, subtree: true });
}
});
})();
// ─── Кнопка «закрыть» в дропдауне истории ────────────────
function injectHistoryCloseButton() {
if (!CONFIG.autoLoadHistory || !CONFIG.autoLoadPreviousDialogs) return;
const panel = document.querySelector('.ticket-detail__history-tickets');
if (!panel) return;
if (panel.querySelector('.hde-history-close')) return;
const btn = document.createElement('button');
btn.className = 'hde-history-close';
btn.title = 'Скрыть';
btn.textContent = '✕ Скрыть';
btn.style.cssText = [
'display:block',
'width:100%',
'margin:0',
'padding:6px 12px',
'background:#f5f7fa',
'border:none',
'border-bottom:1px solid #ebeef5',
'cursor:pointer',
'font-size:12px',
'color:#606266',
'text-align:left'
].join(';');
btn.addEventListener('mouseenter', function () { btn.style.background = '#ecf5ff'; btn.style.color = '#409eff'; });
btn.addEventListener('mouseleave', function () { btn.style.background = '#f5f7fa'; btn.style.color = '#606266'; });
btn.addEventListener('click', function (e) {
e.stopPropagation();
panel.style.display = 'none';
});
panel.insertBefore(btn, panel.firstChild);
}
// ─── Init ────────────────────────────────────────────────
// ============================================================
// 8) Boot / Init / Cleanup
// ============================================================
function init() {
loadConfig();
syncNativeTicketTabsVisibilityClass();
applyThemeFromConfig();
initSettingsMenu();
initLeftTabsMirrorObserver();
syncLeftTabsOnHrefChange();
syncLeftTabsMirror();
syncColleagueStatusesModule();
injectStyle();
if (CONFIG.autoLoadHistory) injectPaginationStyle();
processAllMessages();
initAutoLoad();
observeMessages();
moveHistoryButton();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.addEventListener('beforeunload', () => {
flushLeftTabsCacheNow();
stopColleagueStatuses();
if (_leftTabsPreviewPollInterval) clearInterval(_leftTabsPreviewPollInterval);
if (_leftTabsDepartmentPollInterval) clearInterval(_leftTabsDepartmentPollInterval);
});
window.addEventListener('pagehide', () => {
flushLeftTabsCacheNow();
});
// SPA навигация — перезапуск при смене URL
var lastUrl = location.href;
onBodyReady(function () {
new MutationObserver(function () {
if (location.href !== lastUrl) {
lastUrl = location.href;
_loadedPages.clear(); // сбрасываем загруженные страницы
_historyLoadedForTicketId = null;
syncLeftTabsOnHrefChange();
setTimeout(function () {
syncNativeTicketTabsVisibilityClass();
applyThemeFromConfig();
syncLeftTabsOnHrefChange();
syncLeftTabsMirror();
syncColleagueStatusesModule();
injectStyle();
processAllMessages();
initAutoLoad();
moveHistoryButton();
}, 500);
}
// Только прямые дети body — нам нужна лишь смена URL, не весь DOM
}).observe(document.body, { childList: true });
});
})();