{ "version": 3, "sources": ["../../../node_modules/@rails/actioncable/app/assets/javascripts/action_cable.js", "../../javascript/channels/consumer.js", "../../javascript/packs/telegram_notifications.js"], "sourcesContent": ["(function(global, factory) {\n typeof exports === \"object\" && typeof module !== \"undefined\" ? factory(exports) : typeof define === \"function\" && define.amd ? define([ \"exports\" ], factory) : factory(global.ActionCable = {});\n})(this, function(exports) {\n \"use strict\";\n var adapters = {\n logger: self.console,\n WebSocket: self.WebSocket\n };\n var logger = {\n log: function log() {\n if (this.enabled) {\n var _adapters$logger;\n for (var _len = arguments.length, messages = Array(_len), _key = 0; _key < _len; _key++) {\n messages[_key] = arguments[_key];\n }\n messages.push(Date.now());\n (_adapters$logger = adapters.logger).log.apply(_adapters$logger, [ \"[ActionCable]\" ].concat(messages));\n }\n }\n };\n var _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function(obj) {\n return typeof obj;\n } : function(obj) {\n return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n };\n var classCallCheck = function(instance, Constructor) {\n if (!(instance instanceof Constructor)) {\n throw new TypeError(\"Cannot call a class as a function\");\n }\n };\n var createClass = function() {\n function defineProperties(target, props) {\n for (var i = 0; i < props.length; i++) {\n var descriptor = props[i];\n descriptor.enumerable = descriptor.enumerable || false;\n descriptor.configurable = true;\n if (\"value\" in descriptor) descriptor.writable = true;\n Object.defineProperty(target, descriptor.key, descriptor);\n }\n }\n return function(Constructor, protoProps, staticProps) {\n if (protoProps) defineProperties(Constructor.prototype, protoProps);\n if (staticProps) defineProperties(Constructor, staticProps);\n return Constructor;\n };\n }();\n var now = function now() {\n return new Date().getTime();\n };\n var secondsSince = function secondsSince(time) {\n return (now() - time) / 1e3;\n };\n var clamp = function clamp(number, min, max) {\n return Math.max(min, Math.min(max, number));\n };\n var ConnectionMonitor = function() {\n function ConnectionMonitor(connection) {\n classCallCheck(this, ConnectionMonitor);\n this.visibilityDidChange = this.visibilityDidChange.bind(this);\n this.connection = connection;\n this.reconnectAttempts = 0;\n }\n ConnectionMonitor.prototype.start = function start() {\n if (!this.isRunning()) {\n this.startedAt = now();\n delete this.stoppedAt;\n this.startPolling();\n addEventListener(\"visibilitychange\", this.visibilityDidChange);\n logger.log(\"ConnectionMonitor started. pollInterval = \" + this.getPollInterval() + \" ms\");\n }\n };\n ConnectionMonitor.prototype.stop = function stop() {\n if (this.isRunning()) {\n this.stoppedAt = now();\n this.stopPolling();\n removeEventListener(\"visibilitychange\", this.visibilityDidChange);\n logger.log(\"ConnectionMonitor stopped\");\n }\n };\n ConnectionMonitor.prototype.isRunning = function isRunning() {\n return this.startedAt && !this.stoppedAt;\n };\n ConnectionMonitor.prototype.recordPing = function recordPing() {\n this.pingedAt = now();\n };\n ConnectionMonitor.prototype.recordConnect = function recordConnect() {\n this.reconnectAttempts = 0;\n this.recordPing();\n delete this.disconnectedAt;\n logger.log(\"ConnectionMonitor recorded connect\");\n };\n ConnectionMonitor.prototype.recordDisconnect = function recordDisconnect() {\n this.disconnectedAt = now();\n logger.log(\"ConnectionMonitor recorded disconnect\");\n };\n ConnectionMonitor.prototype.startPolling = function startPolling() {\n this.stopPolling();\n this.poll();\n };\n ConnectionMonitor.prototype.stopPolling = function stopPolling() {\n clearTimeout(this.pollTimeout);\n };\n ConnectionMonitor.prototype.poll = function poll() {\n var _this = this;\n this.pollTimeout = setTimeout(function() {\n _this.reconnectIfStale();\n _this.poll();\n }, this.getPollInterval());\n };\n ConnectionMonitor.prototype.getPollInterval = function getPollInterval() {\n var _constructor$pollInte = this.constructor.pollInterval, min = _constructor$pollInte.min, max = _constructor$pollInte.max, multiplier = _constructor$pollInte.multiplier;\n var interval = multiplier * Math.log(this.reconnectAttempts + 1);\n return Math.round(clamp(interval, min, max) * 1e3);\n };\n ConnectionMonitor.prototype.reconnectIfStale = function reconnectIfStale() {\n if (this.connectionIsStale()) {\n logger.log(\"ConnectionMonitor detected stale connection. reconnectAttempts = \" + this.reconnectAttempts + \", pollInterval = \" + this.getPollInterval() + \" ms, time disconnected = \" + secondsSince(this.disconnectedAt) + \" s, stale threshold = \" + this.constructor.staleThreshold + \" s\");\n this.reconnectAttempts++;\n if (this.disconnectedRecently()) {\n logger.log(\"ConnectionMonitor skipping reopening recent disconnect\");\n } else {\n logger.log(\"ConnectionMonitor reopening\");\n this.connection.reopen();\n }\n }\n };\n ConnectionMonitor.prototype.connectionIsStale = function connectionIsStale() {\n return secondsSince(this.pingedAt ? this.pingedAt : this.startedAt) > this.constructor.staleThreshold;\n };\n ConnectionMonitor.prototype.disconnectedRecently = function disconnectedRecently() {\n return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;\n };\n ConnectionMonitor.prototype.visibilityDidChange = function visibilityDidChange() {\n var _this2 = this;\n if (document.visibilityState === \"visible\") {\n setTimeout(function() {\n if (_this2.connectionIsStale() || !_this2.connection.isOpen()) {\n logger.log(\"ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = \" + document.visibilityState);\n _this2.connection.reopen();\n }\n }, 200);\n }\n };\n return ConnectionMonitor;\n }();\n ConnectionMonitor.pollInterval = {\n min: 3,\n max: 30,\n multiplier: 5\n };\n ConnectionMonitor.staleThreshold = 6;\n var INTERNAL = {\n message_types: {\n welcome: \"welcome\",\n disconnect: \"disconnect\",\n ping: \"ping\",\n confirmation: \"confirm_subscription\",\n rejection: \"reject_subscription\"\n },\n disconnect_reasons: {\n unauthorized: \"unauthorized\",\n invalid_request: \"invalid_request\",\n server_restart: \"server_restart\"\n },\n default_mount_path: \"/cable\",\n protocols: [ \"actioncable-v1-json\", \"actioncable-unsupported\" ]\n };\n var message_types = INTERNAL.message_types, protocols = INTERNAL.protocols;\n var supportedProtocols = protocols.slice(0, protocols.length - 1);\n var indexOf = [].indexOf;\n var Connection = function() {\n function Connection(consumer) {\n classCallCheck(this, Connection);\n this.open = this.open.bind(this);\n this.consumer = consumer;\n this.subscriptions = this.consumer.subscriptions;\n this.monitor = new ConnectionMonitor(this);\n this.disconnected = true;\n }\n Connection.prototype.send = function send(data) {\n if (this.isOpen()) {\n this.webSocket.send(JSON.stringify(data));\n return true;\n } else {\n return false;\n }\n };\n Connection.prototype.open = function open() {\n if (this.isActive()) {\n logger.log(\"Attempted to open WebSocket, but existing socket is \" + this.getState());\n return false;\n } else {\n logger.log(\"Opening WebSocket, current state is \" + this.getState() + \", subprotocols: \" + protocols);\n if (this.webSocket) {\n this.uninstallEventHandlers();\n }\n this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);\n this.installEventHandlers();\n this.monitor.start();\n return true;\n }\n };\n Connection.prototype.close = function close() {\n var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {\n allowReconnect: true\n }, allowReconnect = _ref.allowReconnect;\n if (!allowReconnect) {\n this.monitor.stop();\n }\n if (this.isActive()) {\n return this.webSocket.close();\n }\n };\n Connection.prototype.reopen = function reopen() {\n logger.log(\"Reopening WebSocket, current state is \" + this.getState());\n if (this.isActive()) {\n try {\n return this.close();\n } catch (error) {\n logger.log(\"Failed to reopen WebSocket\", error);\n } finally {\n logger.log(\"Reopening WebSocket in \" + this.constructor.reopenDelay + \"ms\");\n setTimeout(this.open, this.constructor.reopenDelay);\n }\n } else {\n return this.open();\n }\n };\n Connection.prototype.getProtocol = function getProtocol() {\n if (this.webSocket) {\n return this.webSocket.protocol;\n }\n };\n Connection.prototype.isOpen = function isOpen() {\n return this.isState(\"open\");\n };\n Connection.prototype.isActive = function isActive() {\n return this.isState(\"open\", \"connecting\");\n };\n Connection.prototype.isProtocolSupported = function isProtocolSupported() {\n return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;\n };\n Connection.prototype.isState = function isState() {\n for (var _len = arguments.length, states = Array(_len), _key = 0; _key < _len; _key++) {\n states[_key] = arguments[_key];\n }\n return indexOf.call(states, this.getState()) >= 0;\n };\n Connection.prototype.getState = function getState() {\n if (this.webSocket) {\n for (var state in adapters.WebSocket) {\n if (adapters.WebSocket[state] === this.webSocket.readyState) {\n return state.toLowerCase();\n }\n }\n }\n return null;\n };\n Connection.prototype.installEventHandlers = function installEventHandlers() {\n for (var eventName in this.events) {\n var handler = this.events[eventName].bind(this);\n this.webSocket[\"on\" + eventName] = handler;\n }\n };\n Connection.prototype.uninstallEventHandlers = function uninstallEventHandlers() {\n for (var eventName in this.events) {\n this.webSocket[\"on\" + eventName] = function() {};\n }\n };\n return Connection;\n }();\n Connection.reopenDelay = 500;\n Connection.prototype.events = {\n message: function message(event) {\n if (!this.isProtocolSupported()) {\n return;\n }\n var _JSON$parse = JSON.parse(event.data), identifier = _JSON$parse.identifier, message = _JSON$parse.message, reason = _JSON$parse.reason, reconnect = _JSON$parse.reconnect, type = _JSON$parse.type;\n switch (type) {\n case message_types.welcome:\n this.monitor.recordConnect();\n return this.subscriptions.reload();\n\n case message_types.disconnect:\n logger.log(\"Disconnecting. Reason: \" + reason);\n return this.close({\n allowReconnect: reconnect\n });\n\n case message_types.ping:\n return this.monitor.recordPing();\n\n case message_types.confirmation:\n return this.subscriptions.notify(identifier, \"connected\");\n\n case message_types.rejection:\n return this.subscriptions.reject(identifier);\n\n default:\n return this.subscriptions.notify(identifier, \"received\", message);\n }\n },\n open: function open() {\n logger.log(\"WebSocket onopen event, using '\" + this.getProtocol() + \"' subprotocol\");\n this.disconnected = false;\n if (!this.isProtocolSupported()) {\n logger.log(\"Protocol is unsupported. Stopping monitor and disconnecting.\");\n return this.close({\n allowReconnect: false\n });\n }\n },\n close: function close(event) {\n logger.log(\"WebSocket onclose event\");\n if (this.disconnected) {\n return;\n }\n this.disconnected = true;\n this.monitor.recordDisconnect();\n return this.subscriptions.notifyAll(\"disconnected\", {\n willAttemptReconnect: this.monitor.isRunning()\n });\n },\n error: function error() {\n logger.log(\"WebSocket onerror event\");\n }\n };\n var extend = function extend(object, properties) {\n if (properties != null) {\n for (var key in properties) {\n var value = properties[key];\n object[key] = value;\n }\n }\n return object;\n };\n var Subscription = function() {\n function Subscription(consumer) {\n var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n var mixin = arguments[2];\n classCallCheck(this, Subscription);\n this.consumer = consumer;\n this.identifier = JSON.stringify(params);\n extend(this, mixin);\n }\n Subscription.prototype.perform = function perform(action) {\n var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n data.action = action;\n return this.send(data);\n };\n Subscription.prototype.send = function send(data) {\n return this.consumer.send({\n command: \"message\",\n identifier: this.identifier,\n data: JSON.stringify(data)\n });\n };\n Subscription.prototype.unsubscribe = function unsubscribe() {\n return this.consumer.subscriptions.remove(this);\n };\n return Subscription;\n }();\n var Subscriptions = function() {\n function Subscriptions(consumer) {\n classCallCheck(this, Subscriptions);\n this.consumer = consumer;\n this.subscriptions = [];\n }\n Subscriptions.prototype.create = function create(channelName, mixin) {\n var channel = channelName;\n var params = (typeof channel === \"undefined\" ? \"undefined\" : _typeof(channel)) === \"object\" ? channel : {\n channel: channel\n };\n var subscription = new Subscription(this.consumer, params, mixin);\n return this.add(subscription);\n };\n Subscriptions.prototype.add = function add(subscription) {\n this.subscriptions.push(subscription);\n this.consumer.ensureActiveConnection();\n this.notify(subscription, \"initialized\");\n this.sendCommand(subscription, \"subscribe\");\n return subscription;\n };\n Subscriptions.prototype.remove = function remove(subscription) {\n this.forget(subscription);\n if (!this.findAll(subscription.identifier).length) {\n this.sendCommand(subscription, \"unsubscribe\");\n }\n return subscription;\n };\n Subscriptions.prototype.reject = function reject(identifier) {\n var _this = this;\n return this.findAll(identifier).map(function(subscription) {\n _this.forget(subscription);\n _this.notify(subscription, \"rejected\");\n return subscription;\n });\n };\n Subscriptions.prototype.forget = function forget(subscription) {\n this.subscriptions = this.subscriptions.filter(function(s) {\n return s !== subscription;\n });\n return subscription;\n };\n Subscriptions.prototype.findAll = function findAll(identifier) {\n return this.subscriptions.filter(function(s) {\n return s.identifier === identifier;\n });\n };\n Subscriptions.prototype.reload = function reload() {\n var _this2 = this;\n return this.subscriptions.map(function(subscription) {\n return _this2.sendCommand(subscription, \"subscribe\");\n });\n };\n Subscriptions.prototype.notifyAll = function notifyAll(callbackName) {\n var _this3 = this;\n for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n return this.subscriptions.map(function(subscription) {\n return _this3.notify.apply(_this3, [ subscription, callbackName ].concat(args));\n });\n };\n Subscriptions.prototype.notify = function notify(subscription, callbackName) {\n for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {\n args[_key2 - 2] = arguments[_key2];\n }\n var subscriptions = void 0;\n if (typeof subscription === \"string\") {\n subscriptions = this.findAll(subscription);\n } else {\n subscriptions = [ subscription ];\n }\n return subscriptions.map(function(subscription) {\n return typeof subscription[callbackName] === \"function\" ? subscription[callbackName].apply(subscription, args) : undefined;\n });\n };\n Subscriptions.prototype.sendCommand = function sendCommand(subscription, command) {\n var identifier = subscription.identifier;\n return this.consumer.send({\n command: command,\n identifier: identifier\n });\n };\n return Subscriptions;\n }();\n var Consumer = function() {\n function Consumer(url) {\n classCallCheck(this, Consumer);\n this._url = url;\n this.subscriptions = new Subscriptions(this);\n this.connection = new Connection(this);\n }\n Consumer.prototype.send = function send(data) {\n return this.connection.send(data);\n };\n Consumer.prototype.connect = function connect() {\n return this.connection.open();\n };\n Consumer.prototype.disconnect = function disconnect() {\n return this.connection.close({\n allowReconnect: false\n });\n };\n Consumer.prototype.ensureActiveConnection = function ensureActiveConnection() {\n if (!this.connection.isActive()) {\n return this.connection.open();\n }\n };\n createClass(Consumer, [ {\n key: \"url\",\n get: function get$$1() {\n return createWebSocketURL(this._url);\n }\n } ]);\n return Consumer;\n }();\n function createWebSocketURL(url) {\n if (typeof url === \"function\") {\n url = url();\n }\n if (url && !/^wss?:/i.test(url)) {\n var a = document.createElement(\"a\");\n a.href = url;\n a.href = a.href;\n a.protocol = a.protocol.replace(\"http\", \"ws\");\n return a.href;\n } else {\n return url;\n }\n }\n function createConsumer() {\n var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getConfig(\"url\") || INTERNAL.default_mount_path;\n return new Consumer(url);\n }\n function getConfig(name) {\n var element = document.head.querySelector(\"meta[name='action-cable-\" + name + \"']\");\n if (element) {\n return element.getAttribute(\"content\");\n }\n }\n exports.Connection = Connection;\n exports.ConnectionMonitor = ConnectionMonitor;\n exports.Consumer = Consumer;\n exports.INTERNAL = INTERNAL;\n exports.Subscription = Subscription;\n exports.Subscriptions = Subscriptions;\n exports.adapters = adapters;\n exports.createWebSocketURL = createWebSocketURL;\n exports.logger = logger;\n exports.createConsumer = createConsumer;\n exports.getConfig = getConfig;\n Object.defineProperty(exports, \"__esModule\", {\n value: true\n });\n});\n", "// Action Cable provides the framework to deal with WebSockets in Rails.\n// You can generate new channels where WebSocket features live using the `rails generate channel` command.\n\nimport { createConsumer } from \"@rails/actioncable\"\n\nexport default createConsumer()\n", "import consumer from \"../channels/consumer\";\n\n// \u26A0 24.09.2026. \u0412\u0456\u043A\u043D\u043E \u0447\u0430\u0442\u0443 \u2014 inline-\u0441\u043A\u0440\u0438\u043F\u0442 \u0443 \u0440\u043E\u0437\u043C\u0456\u0442\u0446\u0456, \u043C\u043E\u0434\u0443\u043B\u0456\u0432 \u043F\u0430\u043A\u0430 \u043D\u0435 \u0431\u0430\u0447\u0438\u0442\u044C.\n// \u0412\u0456\u0434\u0434\u0430\u0454\u043C\u043E \u0439\u043E\u043C\u0443 \u0442\u043E\u0439 \u0441\u0430\u043C\u0438\u0439 consumer, \u0449\u043E\u0431 \u0431\u0435\u0441\u0456\u0434\u0430 \u0436\u0438\u043B\u0430 \u043F\u043E ActionCable, \u0430 \u043D\u0435 \u043E\u043F\u0438\u0442\u0443\u0432\u0430\u043D\u043D\u044F\u043C.\nwindow.gCable = consumer;\n\nfunction navBadgeUserKey() {\n const body = document.body;\n const raw = body && body.dataset && body.dataset.currentUserId;\n return (raw && raw.length > 0) ? raw : 'guest';\n}\n\nfunction navBadgeStorageKey(prefix, key) {\n return `${prefix}:${key}:${navBadgeUserKey()}`;\n}\n\nfunction navBadgeLoadValue(prefix, key) {\n try {\n const raw = window.localStorage.getItem(navBadgeStorageKey(prefix, key));\n return raw ? parseInt(raw, 10) || 0 : 0;\n } catch (error) {\n return 0;\n }\n}\n\nfunction navBadgeSaveValue(prefix, key, value) {\n try {\n window.localStorage.setItem(navBadgeStorageKey(prefix, key), String(value || 0));\n } catch (error) {\n // ignore storage issues\n }\n}\n\nconst NavBadges = (() => {\n const counts = {};\n const totals = {};\n const seenPrefix = 'navBadgeSeen';\n const pendingPrefix = 'navBadgePending';\n const loadedPending = {};\n\n const getElements = (key) => {\n return document.querySelectorAll(`.nav-badge[data-nav-badge=\"${key}\"]`);\n };\n\n const renderBadge = (badge, value) => {\n const numeric = parseInt(value, 10) || 0;\n if (numeric > 0) {\n badge.classList.remove('nav-badge--hidden');\n badge.textContent = numeric > 99 ? '99+' : numeric;\n } else {\n badge.classList.add('nav-badge--hidden');\n badge.textContent = '';\n }\n };\n\n const renderAll = (key) => {\n getElements(key).forEach((badge) => renderBadge(badge, counts[key] || 0));\n };\n\n const loadSeen = (key) => navBadgeLoadValue(seenPrefix, key);\n const saveSeen = (key, value) => navBadgeSaveValue(seenPrefix, key, value);\n const loadPending = (key) => navBadgeLoadValue(pendingPrefix, key);\n const savePending = (key, value) => navBadgeSaveValue(pendingPrefix, key, value);\n\n const updateValue = (key, nextValue) => {\n counts[key] = nextValue;\n if (typeof totals[key] !== 'number') {\n savePending(key, nextValue);\n }\n renderAll(key);\n };\n\n return {\n scan() {\n document.querySelectorAll('.nav-badge[data-nav-badge]').forEach((badge) => {\n const key = badge.dataset.navBadge;\n if (typeof counts[key] !== 'number') {\n counts[key] = 0;\n }\n if (badge.dataset.navBadgeTotal !== undefined) {\n const total = parseInt(badge.dataset.navBadgeTotal, 10);\n if (!Number.isNaN(total)) {\n totals[key] = total;\n const seen = loadSeen(key);\n counts[key] = Math.max(0, total - seen);\n }\n } else if (!loadedPending[key]) {\n counts[key] = loadPending(key);\n loadedPending[key] = true;\n }\n renderBadge(badge, counts[key] || 0);\n });\n },\n increment(key, delta) {\n if (typeof counts[key] !== 'number') {\n counts[key] = 0;\n }\n const nextValue = Math.max(0, (counts[key] || 0) + (delta || 0));\n updateValue(key, nextValue);\n },\n set(key, value) {\n if (typeof counts[key] !== 'number') {\n counts[key] = 0;\n }\n const nextValue = Math.max(0, parseInt(value, 10) || 0);\n updateValue(key, nextValue);\n },\n value(key) {\n if (typeof counts[key] !== 'number') {\n return 0;\n }\n return counts[key] || 0;\n },\n reset(key) {\n if (typeof counts[key] !== 'number') {\n counts[key] = 0;\n }\n updateValue(key, 0);\n },\n acknowledge(key) {\n if (typeof totals[key] === 'number') {\n saveSeen(key, totals[key]);\n } else {\n savePending(key, 0);\n }\n this.reset(key);\n }\n };\n})();\n\nwindow.navBadges = NavBadges;\n\nconst TelegramBadgeTracker = (() => {\n const storagePrefix = 'navBadgePendingConversations';\n let pendingSet = null;\n\n const storageKey = () => navBadgeStorageKey(storagePrefix, 'telegram');\n\n const ensureLoaded = () => {\n if (pendingSet !== null) return;\n pendingSet = loadFromStorage();\n };\n\n const loadFromStorage = () => {\n try {\n const raw = window.localStorage.getItem(storageKey());\n if (!raw) return new Set();\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return new Set();\n const cleaned = parsed\n .map((value) => parseInt(value, 10))\n .filter((value) => !Number.isNaN(value));\n return new Set(cleaned);\n } catch (error) {\n return new Set();\n }\n };\n\n const persist = () => {\n try {\n if (!pendingSet || pendingSet.size === 0) {\n window.localStorage.removeItem(storageKey());\n } else {\n window.localStorage.setItem(storageKey(), JSON.stringify(Array.from(pendingSet)));\n }\n } catch (error) {\n // ignore storage issues\n }\n };\n\n const syncBadge = () => {\n if (window.navBadges && typeof window.navBadges.set === 'function' && pendingSet) {\n window.navBadges.set('telegram', pendingSet.size);\n }\n };\n\n return {\n init() {\n ensureLoaded();\n syncBadge();\n },\n add(ids) {\n if (!ids) return;\n ensureLoaded();\n const iterable = Array.isArray(ids) ? ids : Array.from(ids);\n let changed = false;\n iterable.forEach((value) => {\n const numeric = parseInt(value, 10);\n if (Number.isNaN(numeric)) return;\n if (!pendingSet.has(numeric)) {\n pendingSet.add(numeric);\n changed = true;\n }\n });\n if (changed) {\n persist();\n syncBadge();\n }\n },\n clear() {\n ensureLoaded();\n if (!pendingSet || pendingSet.size === 0) return;\n pendingSet.clear();\n persist();\n syncBadge();\n }\n };\n})();\n\nwindow.telegramBadgeTracker = TelegramBadgeTracker;\n\nfunction playBeep() {\n try {\n const ctx = new (window.AudioContext || window.webkitAudioContext)();\n const o = ctx.createOscillator();\n const g = ctx.createGain();\n o.connect(g); g.connect(ctx.destination);\n o.type = 'sine'; o.frequency.value = 880;\n g.gain.value = 0.05;\n o.start(0);\n setTimeout(() => { o.stop(0); ctx.close(); }, 180);\n } catch (e) { /* ignore */ }\n}\n\nfunction pulseOn() {\n const btn = document.querySelector('.tg-fab');\n if (btn) btn.classList.add('pulse');\n navBlinkOn();\n}\n\nfunction pulseOff() {\n const btn = document.querySelector('.tg-fab');\n if (btn) btn.classList.remove('pulse');\n navBlinkOff();\n}\n\nfunction navBlinkOn() {\n document.querySelectorAll('.telegram-nav-link').forEach(link => {\n link.classList.add('blink_me', 'telegram-nav-link--alert');\n });\n}\n\nfunction navBlinkOff() {\n document.querySelectorAll('.telegram-nav-link').forEach(link => {\n link.classList.remove('blink_me', 'telegram-nav-link--alert');\n });\n}\n\nfunction setupClick() {\n const btn = document.querySelector('.tg-fab');\n if (btn) btn.addEventListener('click', () => { pulseOff(); });\n document.querySelectorAll('.telegram-nav-link').forEach(link => {\n link.addEventListener('click', () => { pulseOff(); });\n });\n bindNavBadgeTargets();\n}\n\nfunction bindNavBadgeTargets() {\n document.querySelectorAll('[data-nav-badge-target]').forEach((element) => {\n if (element.dataset.navBadgeBound === 'true') return;\n element.dataset.navBadgeBound = 'true';\n element.addEventListener('click', () => {\n if (!window.navBadges) return;\n const key = element.dataset.navBadgeTarget;\n if (!key) return;\n if (element.dataset.navBadgeReset === 'ack') {\n window.navBadges.acknowledge(key);\n } else {\n window.navBadges.reset(key);\n }\n if (key === 'telegram' && window.telegramBadgeTracker) {\n window.telegramBadgeTracker.clear();\n }\n });\n });\n}\n\nfunction pollPing() {\n const btn = document.querySelector('.tg-fab');\n if (!btn) return; // feature disabled for user\n\n let last = Date.now();\n const tick = () => {\n const url = `/telegram/conversations/ping.json?since=${last/1000.0}`;\n fetch(url, { headers: { 'Accept': 'application/json' } })\n .then(r => r.ok ? r.json() : Promise.reject())\n .then(j => {\n last = Date.now();\n const ev = (j && j.events) || {};\n const collectedIds = new Set();\n (ev.new_conversation_ids || []).forEach((id) => {\n const numeric = parseInt(id, 10);\n if (!Number.isNaN(numeric)) {\n collectedIds.add(numeric);\n }\n });\n (ev.new_message_conversation_ids || []).forEach((id) => {\n const numeric = parseInt(id, 10);\n if (!Number.isNaN(numeric)) {\n collectedIds.add(numeric);\n }\n });\n (ev.assigned_conversation_ids || []).forEach((id) => {\n const numeric = parseInt(id, 10);\n if (!Number.isNaN(numeric)) {\n collectedIds.add(numeric);\n }\n });\n if (collectedIds.size > 0 && window.telegramBadgeTracker) {\n window.telegramBadgeTracker.add(Array.from(collectedIds));\n }\n\n const total = (ev.new_conversations || 0) + (ev.new_messages || 0) + (ev.assigned || 0);\n if (total > 0) {\n playBeep(); pulseOn();\n try {\n if (window.location.pathname.match(/^\\/telegram\\/conversations(\\/)?$/)) {\n // \u0430\u0432\u0442\u043E\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043F\u0438\u0441\u043A\u0430 \u0431\u0435\u0441\u0435\u0434\n window.location.reload();\n }\n } catch (e) { /* ignore */ }\n }\n })\n .catch(() => { /* ignore */ })\n .finally(() => setTimeout(tick, 15000));\n };\n setTimeout(tick, 5000);\n}\n\nlet previewClickHandler = null;\nfunction initPreviewToggles() {\n const table = document.querySelector('table.conv');\n if (!table) return;\n\n if (previewClickHandler) {\n document.removeEventListener('click', previewClickHandler);\n previewClickHandler = null;\n }\n\n previewClickHandler = (event) => {\n const closeBtn = event.target.closest('[data-preview-close]');\n if (closeBtn) {\n event.preventDefault();\n const row = closeBtn.closest('tr');\n restorePreviewRow(row);\n return;\n }\n const button = event.target.closest('.js-toggle-preview');\n if (!button || !table.contains(button)) return;\n event.preventDefault();\n togglePreviewRow(button);\n };\n\n document.addEventListener('click', previewClickHandler, true);\n}\n\nfunction restorePreviewRow(row) {\n if (!row || !row.classList.contains('preview-mode')) return;\n const original = row.dataset.originalHtml;\n if (original) {\n const wrapper = row.querySelector('.preview-wrapper');\n if (wrapper) {\n wrapper.classList.remove('ready');\n setTimeout(() => {\n row.innerHTML = original;\n row.classList.remove('preview-mode');\n delete row.dataset.originalHtml;\n reinitializeConversationRow(row);\n }, 220);\n return;\n }\n row.innerHTML = original;\n reinitializeConversationRow(row);\n }\n row.classList.remove('preview-mode');\n delete row.dataset.originalHtml;\n}\n\nfunction closeAllPreviewRows() {\n document.querySelectorAll('tr.preview-mode').forEach((row) => restorePreviewRow(row));\n}\n\nfunction togglePreviewRow(button) {\n console.log('[PREVIEW] toggle clicked', button);\n closeConversationActionsPanels();\n const conversationId = button.dataset.conversationId;\n const row = button.closest('tr');\n if (!conversationId || !row) {\n console.warn('[PREVIEW] missing id or row');\n return;\n }\n\n if (row.classList.contains('preview-mode')) {\n restorePreviewRow(row);\n return;\n }\n\n closeAllPreviewRows();\n row.dataset.originalHtml = row.innerHTML;\n row.classList.add('preview-mode');\n const colSpan = row.children.length || row.dataset.originalColspan || 1;\n row.innerHTML = `