Mediawiki:common.js
| Строка 1: | Строка 1: | ||
(function() { | (function () { | ||
"use strict"; | |||
window.MediaWikiTooltips = { | window.MediaWikiTooltips = { | ||
loaded: false, | loaded: false, | ||
loading: false, | |||
callbacks: [], | callbacks: [], | ||
load: function(callback) { | load: function (callback) { | ||
if (this.loaded && window.tippy) { | if (this.loaded && window.tippy) { | ||
callback(); | callback(); | ||
return; | return; | ||
} | } | ||
this.callbacks.push(callback); | this.callbacks.push(callback); | ||
if (this.loading) return; | if (this.loading) return; | ||
this.loading = true; | this.loading = true; | ||
var self = this; | var self = this; | ||
var | var cdns = [ | ||
{ base: "https://unpkg.com", popper: "/@popperjs/core@2/dist/umd/popper.min.js", tippy: "/tippy.js@6/dist/tippy-bundle.umd.min.js", css: "/tippy.js@6/dist/tippy.css" }, | |||
{ base: "https://cdn.jsdelivr.net/npm", popper: "/@popperjs/core@2/dist/umd/popper.min.js", tippy: "/tippy.js@6/dist/tippy-bundle.umd.min.js", css: "/tippy.js@6/dist/tippy.css" } | |||
]; | |||
var cdnIndex = 0; | |||
var tryFallback = function (which) { | |||
self.loading = false; | |||
console.error( | |||
"[MediaWikiTooltips] Не удалось загрузить " + which + ". " + | |||
"Возможные причины: CSP вики блокирует внешние скрипты, нет сети, или CDN недоступен." | |||
); | |||
self.callbacks = []; | |||
}; | |||
var runCallbacks = function () { | |||
self.loaded = true; | |||
self.loading = false; | |||
self.callbacks.forEach(function (cb) { | |||
try { cb(); } catch (e) { console.error("[MediaWikiTooltips] callback error:", e); } | |||
}); | |||
self.callbacks = []; | |||
}; | |||
function loadTippyAndCss() { | |||
var cdn = cdns[cdnIndex]; | |||
var tippyScript = document.createElement("script"); | |||
tippyScript.src = cdn.base + cdn.tippy; | |||
tippyScript.onerror = function () { tryFallback("Tippy.js"); }; | |||
tippyScript.onload = function () { | |||
if (!window.tippy) { | |||
tryFallback("Tippy.js (не в глобальной области)"); | |||
return; | |||
} | |||
var tippyCSS = document.createElement("link"); | |||
tippyCSS.rel = "stylesheet"; | |||
tippyCSS.href = cdn.base + cdn.css; | |||
tippyCSS.onerror = function () { console.warn("[MediaWikiTooltips] Стили Tippy не загрузились."); }; | |||
document.head.appendChild(tippyCSS); | |||
console.log("[MediaWikiTooltips] Tippy.js загружен с", cdn.base); | |||
runCallbacks(); | |||
}; | }; | ||
document.head.appendChild(tippyScript); | document.head.appendChild(tippyScript); | ||
} | } | ||
function loadPopper() { | |||
var cdn = cdns[cdnIndex]; | |||
var popperScript = document.createElement("script"); | |||
popperScript.src = cdn.base + cdn.popper; | |||
popperScript.onerror = function () { | |||
} | if (cdnIndex + 1 < cdns.length) { | ||
cdnIndex++; | |||
console.warn("[MediaWikiTooltips] Повтор с CDN:", cdns[cdnIndex].base); | |||
loadPopper(); | |||
} else { | |||
tryFallback("Popper.js"); | |||
} | |||
}; | |||
popperScript.onload = loadTippyAndCss; | |||
document.head.appendChild(popperScript); | |||
} | |||
loadPopper(); | |||
}, | |||
}; | }; | ||
var SidebarModule = { | var SidebarModule = { | ||
init: function() { | init: function () { | ||
var buttons = document.querySelectorAll( | var buttons = document.querySelectorAll(".боковая-панель-кнопка"); | ||
var sections = document.querySelectorAll( | var sections = document.querySelectorAll(".боковая-панель-раздел"); | ||
if (buttons.length === 0) return; | if (buttons.length === 0) return; | ||
buttons.forEach(function(button) { | buttons.forEach(function (button) { | ||
button.addEventListener( | button.addEventListener("click", function () { | ||
var targetId = this.dataset.target; | var targetId = this.dataset.target; | ||
buttons.forEach(function(btn) { btn.classList.remove( | buttons.forEach(function (btn) { | ||
this.classList.add( | btn.classList.remove("active"); | ||
}); | |||
sections.forEach(function(section) { section.classList.remove( | this.classList.add("active"); | ||
sections.forEach(function (section) { | |||
section.classList.remove("default"); | |||
}); | |||
var targetSection = document.getElementById(targetId); | var targetSection = document.getElementById(targetId); | ||
if (targetSection) { | if (targetSection) { | ||
targetSection.classList.add( | targetSection.classList.add("default"); | ||
} | } | ||
}); | }); | ||
}); | }); | ||
buttons[0].click(); | buttons[0].click(); | ||
} | }, | ||
}; | }; | ||
var AccessTooltipsModule = { | var AccessTooltipsModule = { | ||
init: function() { | init: function () { | ||
var containers = document.querySelectorAll( | var containers = document.querySelectorAll(".допуск-контейнер"); | ||
if (containers.length === 0) return; | if (containers.length === 0) return; | ||
MediaWikiTooltips.load(function() { | MediaWikiTooltips.load(function () { | ||
containers.forEach(function(container) { | containers.forEach(function (container) { | ||
var contentElement = container.querySelector( | var contentElement = | ||
container.querySelector(".допуск-подсказка"); | |||
if (!contentElement) return; | if (!contentElement) return; | ||
var content = contentElement.innerHTML; | var content = contentElement.innerHTML; | ||
tippy(container, { | tippy(container, { | ||
| Строка 81: | Строка 131: | ||
allowHTML: true, | allowHTML: true, | ||
interactive: true, | interactive: true, | ||
placement: | placement: "auto", | ||
maxWidth: 500, | maxWidth: 500, | ||
theme: | theme: "dark", | ||
arrow: true, | arrow: true, | ||
duration: [200, 200], | duration: [200, 200], | ||
| Строка 89: | Строка 139: | ||
modifiers: [ | modifiers: [ | ||
{ | { | ||
name: | name: "preventOverflow", | ||
options: { padding: 8 } | options: { padding: 8 }, | ||
}, | }, | ||
{ | { | ||
name: | name: "flip", | ||
options: { | options: { | ||
fallbackPlacements: [ | fallbackPlacements: [ | ||
} | "top", | ||
} | "bottom", | ||
] | "right", | ||
} | "left", | ||
], | |||
}, | |||
}, | |||
], | |||
}, | |||
}); | }); | ||
}); | }); | ||
}); | }); | ||
} | }, | ||
}; | }; | ||
function run() { | var LawTooltipsModule = { | ||
init: function () { | |||
console.log("[LawTooltips] init()"); | |||
var tableCells = document.querySelectorAll( | |||
".law-tooltips td, .law-tooltips th", | |||
); | |||
console.log( | |||
return | "[LawTooltips] .law-tooltips ячеек:", | ||
tableCells.length, | |||
); | |||
if (tableCells.length === 0) { | |||
console.warn("[LawTooltips] Нет ячеек .law-tooltips — выход"); | |||
return; | |||
} | |||
function run() { | |||
console.log("[LawTooltips] run() — извлечение данных..."); | |||
var lawData = LawTooltipsModule.extractLawData(); | |||
var count = Object.keys(lawData).length; | |||
console.log( | |||
"[LawTooltips] Загружено записей законов:", | |||
count, | |||
count | |||
? Object.keys(lawData).slice(0, 10).join(", ") + | |||
(count > 10 ? "..." : "") | |||
: "", | |||
); | |||
if (count > 0) { | |||
LawTooltipsModule.initCellTooltips(tableCells, lawData); | |||
return true; | |||
} | |||
return false; | |||
} | } | ||
if (typeof MediaWikiTooltips === "undefined") { | |||
console.error( | |||
"[LawTooltips] MediaWikiTooltips не найден — тултипы не загрузятся", | |||
); | |||
run(); | |||
return; | return; | ||
} | } | ||
MediaWikiTooltips.load(function () { | |||
console.log("[LawTooltips] MediaWikiTooltips.load вызван"); | |||
if (run()) { | if (run()) { | ||
console.log( | console.log("[LawTooltips] Инициализация с первой попытки"); | ||
return; | return; | ||
} | } | ||
setTimeout(function() { | setTimeout(function () { | ||
if (run()) console.log( | if (run()) { | ||
console.log("[LawTooltips] Инициализация после 300ms"); | |||
return; | |||
} | |||
}); | setTimeout(function () { | ||
if (run()) | |||
console.log( | |||
"[LawTooltips] Инициализация после 800ms", | |||
); | |||
else | |||
console.warn( | |||
"[LawTooltips] Данные законов не найдены после всех попыток", | |||
); | |||
}, 500); | |||
}, 300); | |||
}); | |||
}, | |||
extractLawData: function () { | |||
var lawData = {}; | |||
var containers = document.querySelectorAll(".mw-collapsible"); | |||
console.log( | |||
"[LawTooltips] extractLawData: коллапсибл-блоков:", | |||
containers.length, | |||
); | |||
containers.forEach(function (block) { | |||
var content = block.querySelector(".mw-collapsible-content"); | |||
if (content && block.classList.contains("mw-collapsed")) { | |||
block.classList.remove("mw-collapsed"); | |||
var toggle = content.querySelector( | |||
".mw-collapsible-toggle", | |||
); | |||
if (toggle) toggle.setAttribute("aria-expanded", "true"); | |||
} | |||
}); | |||
var detailTables = document.querySelectorAll( | |||
".mw-collapsible-content table", | |||
); | |||
console.log( | |||
"[LawTooltips] extractLawData: таблиц в .mw-collapsible-content:", | |||
detailTables.length, | |||
); | |||
detailTables.forEach(function (table, tableIndex) { | |||
if (table.classList.contains("law-tooltips")) { | |||
console.log( | |||
"[LawTooltips] extractLawData: таблица", | |||
tableIndex, | |||
"— пропуск (law-tooltips)", | |||
); | |||
return; | |||
} | |||
var rows = table.querySelectorAll("tr"); | |||
rows.forEach(function (row) { | |||
var cells = row.querySelectorAll("td"); | |||
if (cells.length < 3) return; | |||
var crimeCell = cells[0]; | |||
var descriptionCell = cells[1]; | |||
var exampleCell = cells[2]; | |||
var anchor = crimeCell.querySelector("[id]"); | |||
if (!anchor) return; | |||
var lawCode = anchor.id; | |||
var titleElement = crimeCell.querySelector("b"); | |||
if (!titleElement || !lawCode.match(/^\d{3}$/)) return; | |||
var titleText = titleElement.textContent.trim(); | |||
var lawTitle = titleText | |||
.replace(/^\d{3}\s*/, "") | |||
.replace(/^\d{3}/, "") | |||
.trim(); | |||
var description = descriptionCell.innerHTML.trim(); | |||
var examples = []; | |||
var exampleItems = exampleCell.querySelectorAll("li"); | |||
exampleItems.forEach(function (item) { | |||
var html = item.innerHTML | |||
.trim() | |||
.replace(/<br\s*\/?>/gi, ""); | |||
if (html) examples.push(html); | |||
}); | |||
lawData[lawCode] = { | |||
title: lawTitle, | |||
description: description, | |||
examples: examples, | |||
}; | |||
}); | }); | ||
}); | }); | ||
console.log( | |||
"[LawTooltips] extractLawData: итого кодов:", | |||
Object.keys(lawData).length, | |||
); | |||
return lawData; | |||
}, | |||
createTooltipContent: function (lawCode, lawData) { | |||
var data = lawData[lawCode]; | |||
if (!data) return null; | |||
var html = '<div class="law-tooltip">'; | |||
html += | |||
'<h4 class="law-tooltip-title">' + | |||
lawCode + | |||
" - " + | |||
data.title + | |||
"</h4>"; | |||
html += | |||
'<p class="law-tooltip-description">' + | |||
data.description + | |||
"</p>"; | |||
if (data.examples && data.examples.length > 0) { | |||
html += '<div class="law-tooltip-examples">'; | |||
html += "<strong>Примеры:</strong><ul>"; | |||
data.examples.slice(0, 5).forEach(function (example) { | |||
html += "<li>" + example + "</li>"; | |||
}); | |||
html += "</ul></div>"; | |||
} | |||
html += "</div>"; | |||
return html; | |||
}, | |||
initCellTooltips: function (tableCells, lawData) { | |||
var withLink = 0, | |||
withMatch = 0, | |||
withContent = 0, | |||
tooltipsCreated = 0; | |||
tableCells.forEach(function (cell) { | |||
var link = cell.querySelector('a[href*="#"]'); | |||
if (!link) return; | |||
withLink++; | |||
var href = link.getAttribute("href"); | |||
var match = href.match(/#(\d{3})/); | |||
if (!match) return; | |||
withMatch++; | |||
var lawCode = match[1]; | |||
var tooltipContent = LawTooltipsModule.createTooltipContent( | |||
lawCode, | |||
lawData, | |||
); | |||
if (!tooltipContent) return; | |||
withContent++; | |||
cell.classList.add("law-cell-with-tooltip"); | |||
tooltipsCreated++; | |||
cell.addEventListener("click", function (e) { | |||
e.preventDefault(); | |||
window.location.hash = lawCode; | |||
}); | |||
if (typeof tippy === "undefined") { | |||
console.error( | |||
"[LawTooltips] tippy не найден — подключи библиотеку Tippy.js", | |||
); | |||
return; | |||
} | } | ||
tippy(cell, { | |||
content: tooltipContent, | |||
allowHTML: true, | |||
interactive: true, | |||
placement: "top", | |||
maxWidth: 400, | |||
theme: "law-theme", | |||
arrow: true, | |||
duration: [200, 150], | |||
delay: [0, 50], | |||
appendTo: document.body, | |||
boundary: "viewport", | |||
popperOptions: { | |||
strategy: "fixed", | |||
modifiers: [ | |||
{ | |||
name: "preventOverflow", | |||
options: { | |||
boundary: "viewport", | |||
padding: 20, | |||
altAxis: true, | |||
altBoundary: true, | |||
}, | |||
}, | |||
{ | |||
name: "flip", | |||
options: { | |||
fallbackPlacements: [ | |||
"bottom", | |||
"left", | |||
"right", | |||
], | |||
}, | |||
}, | |||
{ | |||
name: "computeStyles", | |||
options: { adaptive: false }, | |||
}, | |||
], | |||
}, | |||
onShow: function (instance) { | |||
document | |||
.querySelectorAll("[data-tippy-root]") | |||
.forEach(function (tooltip) { | |||
if ( | |||
tooltip._tippy && | |||
tooltip._tippy !== instance | |||
) { | |||
tooltip._tippy.hide(); | |||
} | |||
}); | |||
}, | |||
}); | |||
}); | }); | ||
console.log( | |||
"[LawTooltips] initCellTooltips: ячеек с ссылкой", | |||
withLink, | |||
}; | "| с #XXX", | ||
withMatch, | |||
"| с данными", | |||
withContent, | |||
"| тултипов создано", | |||
tooltipsCreated, | |||
); | |||
}, | |||
}; | |||
var DataTooltipsModule = { | var DataTooltipsModule = { | ||
init: function() { | init: function () { | ||
var elements = document.querySelectorAll( | var elements = document.querySelectorAll( | ||
".data-tooltip[data-text]", | |||
); | |||
if (elements.length === 0) return; | if (elements.length === 0) return; | ||
MediaWikiTooltips.load(function() { | MediaWikiTooltips.load(function () { | ||
elements.forEach(function(element) { | elements.forEach(function (element) { | ||
var tooltipText = element.getAttribute( | var tooltipText = element.getAttribute("data-text"); | ||
if (!tooltipText || !tooltipText.trim()) return; | if (!tooltipText || !tooltipText.trim()) return; | ||
tippy(element, { | tippy(element, { | ||
content: tooltipText, | content: tooltipText, | ||
allowHTML: true, | allowHTML: true, | ||
interactive: false, | interactive: false, | ||
placement: | placement: "top", | ||
maxWidth: 300, | maxWidth: 300, | ||
theme: | theme: "data-tooltip-theme", | ||
arrow: true, | arrow: true, | ||
duration: [200, 150], | duration: [200, 150], | ||
delay: [0, 50] | delay: [0, 50], | ||
}); | }); | ||
}); | }); | ||
}); | }); | ||
var observer = new MutationObserver(function(mutations) { | var observer = new MutationObserver(function (mutations) { | ||
var shouldReinit = false; | var shouldReinit = false; | ||
mutations.forEach(function(mutation) { | mutations.forEach(function (mutation) { | ||
mutation.addedNodes.forEach(function(node) { | mutation.addedNodes.forEach(function (node) { | ||
if (node.nodeType === 1) { | if (node.nodeType === 1) { | ||
if (node.classList && node.classList.contains( | if ( | ||
node.classList && | |||
node.classList.contains("data-tooltip") && | |||
node.hasAttribute("data-text") | |||
) { | |||
shouldReinit = true; | shouldReinit = true; | ||
} else if (node.querySelectorAll) { | } else if (node.querySelectorAll) { | ||
var newElements = node.querySelectorAll( | var newElements = node.querySelectorAll( | ||
".data-tooltip[data-text]", | |||
); | |||
if (newElements.length > 0) { | if (newElements.length > 0) { | ||
shouldReinit = true; | shouldReinit = true; | ||
| Строка 353: | Строка 490: | ||
}); | }); | ||
}); | }); | ||
if (shouldReinit) { | if (shouldReinit) { | ||
setTimeout(function() { DataTooltipsModule.init(); }, 100); | setTimeout(function () { | ||
DataTooltipsModule.init(); | |||
}, 100); | |||
} | } | ||
}); | }); | ||
observer.observe(document.body, { | observer.observe(document.body, { | ||
childList: true, | childList: true, | ||
subtree: true | subtree: true, | ||
}); | }); | ||
} | }, | ||
}; | }; | ||
var CopyTextModule = { | |||
init: function () { | |||
CopyTextModule.initDirectElements(); | |||
CopyTextModule.initAutoContainers(); | |||
}, | |||
initDirectElements: function () { | |||
var elements = document.querySelectorAll(".copyable-text"); | |||
if (elements.length === 0) return; | |||
elements.forEach(function (element) { | |||
CopyTextModule.cleanPreContent(element); | |||
CopyTextModule.setupElement(element); | |||
}); | |||
}, | |||
initAutoContainers: function () { | |||
var containers = document.querySelectorAll( | |||
".copyable-pre-container", | |||
); | |||
if (containers.length === 0) return; | |||
containers.forEach(function (container) { | |||
CopyTextModule.processContainerPre(container); | |||
}); | |||
// Наблюдатель за динамически появляющимися элементами | |||
var observer = new MutationObserver(function (mutations) { | |||
mutations.forEach(function (mutation) { | |||
mutation.addedNodes.forEach(function (node) { | |||
if (node.nodeType === 1) { | |||
// Проверяем сам элемент | |||
if (node.tagName === "PRE") { | |||
var container = node.closest( | |||
".copyable-pre-container", | |||
); | |||
if ( | |||
container && | |||
!node.classList.contains("copyable-text") | |||
) { | |||
node.classList.add("copyable-text"); | |||
CopyTextModule.cleanPreContent(node); | |||
CopyTextModule.setupElement(node); | |||
} | |||
} | |||
// Проверяем дочерние элементы | |||
else if (node.querySelectorAll) { | |||
var containers = node.querySelectorAll( | |||
".copyable-pre-container", | |||
); | |||
containers.forEach(function (container) { | |||
CopyTextModule.processContainerPre( | |||
container, | |||
); | |||
}); | |||
// Также проверяем pre внутри уже существующих контейнеров | |||
var preElements = node.querySelectorAll( | |||
".copyable-pre-container pre", | |||
); | |||
preElements.forEach(function (pre) { | |||
if ( | |||
!pre.classList.contains("copyable-text") | |||
) { | |||
pre.classList.add("copyable-text"); | |||
CopyTextModule.cleanPreContent(pre); | |||
CopyTextModule.setupElement(pre); | |||
} | |||
}); | |||
} | |||
} | |||
}); | |||
// Обрабатываем изменения атрибутов (например, style="display: none" -> "") | |||
if ( | |||
mutation.type === "attributes" && | |||
mutation.attributeName === "style" | |||
) { | |||
var target = mutation.target; | |||
if (target.style.display !== "none") { | |||
var containers = target.querySelectorAll( | |||
".copyable-pre-container", | |||
); | |||
containers.forEach(function (container) { | |||
CopyTextModule.processContainerPre(container); | |||
}); | |||
} | |||
} | |||
}); | |||
}); | |||
observer.observe(document.body, { | |||
childList: true, | |||
subtree: true, | |||
attributes: true, | |||
attributeFilter: ["style"], | |||
}); | |||
}, | |||
processContainerPre: function (container) { | |||
var preElements = container.querySelectorAll("pre"); | |||
preElements.forEach(function (pre) { | |||
if (!pre.classList.contains("copyable-text")) { | |||
pre.classList.add("copyable-text"); | |||
CopyTextModule.cleanPreContent(pre); | |||
CopyTextModule.setupElement(pre); | |||
} | |||
}); | |||
}, | |||
cleanPreContent: function (preElement) { | |||
var text = preElement.textContent || preElement.innerText; | |||
var cleanedText = text | |||
.split("\n") | |||
.map(function (line) { | |||
return line.replace(/^\s*\|\s*/, ""); | |||
}) | |||
.join("\n"); | |||
if (cleanedText !== text) { | |||
preElement.textContent = cleanedText; | |||
} | |||
}, | |||
setupElement: function (element) { | |||
element.style.cursor = "pointer"; | |||
element.addEventListener("click", CopyTextModule.handleClick); | |||
var isIOS = | |||
/iPad|iPhone|iPod/.test(navigator.userAgent) && | |||
!window.MSStream; | |||
if (isIOS) { | |||
element.style.webkitUserSelect = "text"; | |||
element.style.userSelect = "text"; | |||
} | |||
}, | |||
handleClick: function (e) { | |||
e.preventDefault(); | |||
var element = this; | |||
var textToCopy = element.textContent; | |||
if (navigator.clipboard && window.isSecureContext) { | |||
navigator.clipboard | |||
.writeText(textToCopy) | |||
'<div class="violations-list"></div>' + | '<div class="violations-list"></div>' + | ||
"</div>" + | |||
'<div class="calculation-result" style="display: none;"></div>' + | |||
'<div class="calculator-actions">' + | |||
'<button class="clear-btn" style="margin-right: 10px;">Очистить</button>' + | '<button class="clear-btn" style="margin-right: 10px;">Очистить</button>' + | ||
'<button class="calculate-btn">Рассчитать</button>' + | '<button class="calculate-btn">Рассчитать</button>' + | ||
"</div>"; | |||
// Обработчики событий | |||
calculatorInterface | |||
.querySelector(".clear-btn") | |||
.addEventListener("click", function () { | |||
LawCalculatorModule.clearSelection(); | |||
}); | |||
calculatorInterface | |||
.querySelector(".calculate-btn") | |||
.addEventListener("click", function () { | |||
LawCalculatorModule.calculateSentence(); | |||
}); | |||
return calculatorInterface; | |||
}, | |||
makeTableInteractive: function (table) { | |||
var cells = table.querySelectorAll("td"); | |||
cells.forEach( | |||
function (cell) { | |||
var link = cell.querySelector('a[href*="#"]'); | |||
if (!link) return; | |||
var href = link.getAttribute("href"); | |||
var match = href.match(/#(\d{3})/); | |||
if (!match) return; | |||
var lawCode = match[1]; | |||
if (!this.lawData[lawCode]) return; | |||
cell.classList.add("law-cell-calculator"); | |||
cell.dataset.lawCode = lawCode; | |||
// Сохраняем оригинальный обработчик клика | |||
var originalClickHandler = cell.onclick; | |||
cell.addEventListener("click", function (e) { | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
LawCalculatorModule.toggleViolation(lawCode, cell); | |||
}); | |||
}.bind(this), | |||
); | |||
}, | |||
makeTableNormal: function (table) { | |||
var cells = table.querySelectorAll(".law-cell-calculator"); | |||
cells.forEach(function (cell) { | |||
cell.classList.remove("law-cell-calculator", "selected"); | |||
// Восстанавливаем оригинальную функциональность навигации | |||
var lawCode = cell.dataset.lawCode; | |||
if (lawCode) { | |||
cell.addEventListener("click", function (e) { | |||
e.preventDefault(); | |||
window.location.hash = lawCode; | |||
}); | |||
} | |||
}); | |||
}, | |||
toggleViolation: function (lawCode, cell) { | |||
var index = this.selectedViolations.indexOf(lawCode); | |||
if (index > -1) { | |||
// Убираем нарушение | |||
this.selectedViolations.splice(index, 1); | |||
cell.classList.remove("selected"); | |||
} else { | |||
// Добавляем нарушение | |||
this.selectedViolations.push(lawCode); | |||
cell.classList.add("selected"); | |||
} | } | ||
this.updateViolationsList(); | |||
}, | |||
updateViolationsList: function () { | |||
var violationsList = document.querySelector(".violations-list"); | |||
if (!violationsList) return; | |||
violationsList.innerHTML = ""; | |||
this.selectedViolations.forEach( | |||
function (lawCode) { | |||
var law = this.lawData[lawCode]; | |||
if (!law) return; | |||
var item = document.createElement("div"); | |||
item.className = "violation-item"; | |||
item.innerHTML = | |||
lawCode + | |||
" - " + | |||
law.title + | |||
'<span class="remove-btn" data-law-code="' + | |||
lawCode + | |||
'">×</span>'; | |||
item.querySelector(".remove-btn").addEventListener( | |||
"click", | |||
function () { | |||
LawCalculatorModule.removeViolation(lawCode); | |||
}, | |||
); | |||
violationsList.appendChild(item); | |||
}.bind(this), | |||
); | |||
}, | |||
removeViolation: function (lawCode) { | |||
var index = this.selectedViolations.indexOf(lawCode); | |||
if (index > -1) { | |||
this.selectedViolations.splice(index, 1); | |||
// Убираем выделение с ячейки | |||
var cell = document.querySelector( | |||
'[data-law-code="' + lawCode + '"]', | |||
); | |||
if (cell) { | |||
cell.classList.remove("selected"); | |||
} | |||
this.updateViolationsList(); | |||
} | |||
}, | |||
clearSelection: function () { | |||
this.selectedViolations = []; | |||
// Убираем выделение со всех ячеек | |||
var selectedCells = document.querySelectorAll( | |||
".law-cell-calculator.selected", | |||
); | |||
selectedCells.forEach(function (cell) { | |||
cell.classList.remove("selected"); | |||
}); | |||
this.updateViolationsList(); | |||
this.hideResult(); | |||
}, | |||
calculateSentence: function () { | |||
if (this.selectedViolations.length === 0) { | |||
alert("Выберите хотя бы одно нарушение"); | |||
return; | |||
} | |||
var result = this.calculateSentenceLogic(this.selectedViolations); | |||
this.showResult(result); | |||
}, | |||
calculateSentenceLogic: function (violations) { | |||
// Группируем нарушения по категориям | |||
var categories = {}; | |||
violations.forEach( | |||
function (lawCode) { | |||
var law = this.lawData[lawCode]; | |||
if (!law || !law.category) return; | |||
// Используем номер статьи как ключ категории | |||
var categoryKey = this.getCategoryKeyByLawCode(lawCode); | |||
if (!categories[categoryKey]) { | |||
categories[categoryKey] = []; | |||
} | |||
categories[categoryKey].push(law); | |||
}.bind(this), | |||
); | |||
var totalMinTime = 0; | |||
var totalMaxTime = 0; | |||
var totalFine = 0; | |||
var hasCritical = false; | |||
var resultText = ""; | |||
// Обрабатываем каждую категорию | |||
Object.keys(categories).forEach( | |||
function (categoryKey) { | |||
var laws = categories[categoryKey]; | |||
var category = laws[0].category; | |||
var categoryName = this.getCategoryName(categoryKey); | |||
if (category.isCritical) { | |||
hasCritical = true; | |||
resultText += | |||
"• " + | |||
categoryName + | |||
": " + | |||
(category.punishment || "Перевод в Д-класс/казнь") + | |||
"\n"; | |||
} else { | |||
// Для одной категории берем самую тяжкую статью | |||
var maxLaw = laws.reduce(function (max, current) { | |||
var currentCode = parseInt(lawCode); | |||
var maxCode = parseInt(max.title.match(/\d{3}/)[0]); | |||
return currentCode > maxCode ? current : max; | |||
}); | |||
totalMinTime += category.minTime || 0; | |||
totalMaxTime += category.maxTime || 0; | |||
totalFine += category.fine || 0; | |||
var punishmentText = ""; | |||
if (category.punishment) { | |||
punishmentText = category.punishment; | |||
} else if (category.minTime !== undefined) { | |||
punishmentText = | |||
category.minTime === category.maxTime | |||
? category.minTime + " минут" | |||
: category.minTime + | |||
"-" + | |||
category.maxTime + | |||
" минут"; | |||
} | |||
resultText += | |||
"• " + | |||
categoryName + | |||
": " + | |||
punishmentText + | |||
" (статья " + | |||
lawCode + | |||
")\n"; | |||
} | |||
}.bind(this), | |||
); | |||
if (hasCritical) { | |||
return { | |||
isCritical: true, | |||
text: | |||
"КРИТИЧЕСКИЕ НАРУШЕНИЯ\n" + | |||
resultText + | |||
"\nПриговор: Перевод в Д-класс или казнь", | |||
}; | |||
} else if (this.pageType === "d-class") { | |||
// Для Д-класса показываем штрафы и время отдельно | |||
var summaryText = ""; | |||
if (totalFine > 0) { | if (totalFine > 0) { | ||
summaryText += | summaryText += "Общий штраф: " + totalFine + " кредитов\n"; | ||
} | } | ||
if (totalMinTime > 0 || totalMaxTime > 0) { | if (totalMinTime > 0 || totalMaxTime > 0) { | ||
summaryText += | summaryText += | ||
"Общее время: " + | |||
this.formatTime(totalMinTime, totalMaxTime) + | |||
"\n"; | |||
} | |||
return { | |||
isCritical: false, | |||
text: | |||
"РАСЧЕТ НАКАЗАНИЙ Д-КЛАССА\n" + | |||
resultText + | |||
"\n" + | |||
summaryText, | |||
}; | |||
} else if (totalMinTime > 0 || totalMaxTime > 0) { | |||
var timeText = this.formatTime(totalMinTime, totalMaxTime); | |||
return { | |||
isCritical: false, | |||
text: | |||
"РАСЧЕТ СРОКА ЗАКЛЮЧЕНИЯ\n" + | |||
resultText + | |||
"\nОбщий срок: " + | |||
timeText, | |||
}; | |||
} else { | |||
return { | |||
isCritical: false, | |||
text: | |||
"РАСЧЕТ НАКАЗАНИЙ\n" + | |||
resultText + | |||
"\nНе удалось определить наказания", | |||
}; | |||
} | |||
}, | |||
getCategoryKeyByLawCode: function (lawCode) { | |||
var code = parseInt(lawCode); | |||
if (code >= 100 && code <= 109) return "100-109"; | |||
else if (code >= 200 && code <= 211) return "200-211"; | |||
else if (code >= 300 && code <= 311) return "300-311"; | |||
else if (code >= 400 && code <= 411) return "400-411"; | |||
else if (code >= 500 && code <= 511) return "500-511"; | |||
return "unknown"; | |||
}, | |||
getCategoryName: function (categoryKey) { | |||
var names = { | |||
"100-109": "Лёгкие нарушения", | |||
"200-211": "Средние нарушения", | |||
"300-311": "Тяжкие нарушения", | |||
"400-411": "Особо тяжкие нарушения", | |||
"500-511": "Критические нарушения", | |||
}; | |||
return names[categoryKey] || "Неизвестная категория"; | |||
}, | |||
formatTime: function (minTime, maxTime) { | |||
if (minTime === maxTime) { | |||
return this.formatMinutes(minTime); | |||
} else { | |||
return ( | |||
this.formatMinutes(minTime) + | |||
" - " + | |||
this.formatMinutes(maxTime) | |||
); | |||
} | |||
}, | |||
formatMinutes: function (minutes) { | |||
if (minutes < 60) { | |||
return minutes + " минут"; | |||
} else { | |||
var hours = Math.floor(minutes / 60); | |||
var remainingMinutes = minutes % 60; | |||
var result = hours + " час"; | |||
if (hours > 1 && hours < 5) result += "а"; | |||
if (hours >= 5) result += "ов"; | |||
if (remainingMinutes > 0) { | |||
result += " " + remainingMinutes + " минут"; | |||
} | |||
return result; | |||
} | |||
}, | |||
showResult: function (result) { | |||
var resultDiv = document.querySelector(".calculation-result"); | |||
if (!resultDiv) return; | |||
resultDiv.textContent = result.text; | |||
resultDiv.className = | |||
"calculation-result" + (result.isCritical ? " critical" : ""); | |||
resultDiv.style.display = "block"; | |||
}, | |||
hideResult: function () { | |||
var resultDiv = document.querySelector(".calculation-result"); | |||
if (resultDiv) { | |||
resultDiv.style.display = "none"; | |||
} | |||
}, | |||
observeTables: function () { | |||
// Наблюдаем за динамически добавляемыми таблицами | |||
var observer = new MutationObserver(function (mutations) { | |||
mutations.forEach(function (mutation) { | |||
mutation.addedNodes.forEach(function (node) { | |||
if (node.nodeType === 1) { | |||
if ( | |||
node.classList && | |||
node.classList.contains("citizen-table-wrapper") | |||
) { | |||
setTimeout(function () { | |||
LawCalculatorModule.createModeToggle(); | |||
}, 100); | |||
} else if (node.querySelectorAll) { | |||
var tables = node.querySelectorAll( | |||
".citizen-table-wrapper", | |||
); | |||
if (tables.length > 0) { | |||
setTimeout(function () { | |||
LawCalculatorModule.createModeToggle(); | |||
}, 100); | |||
} | |||
} | |||
} | |||
}); | |||
}); | |||
}); | |||
observer.observe(document.body, { | |||
childList: true, | |||
subtree: true, | |||
}); | |||
}, | |||
}; | |||
var SnowflakesModule = { | |||
snowflakes: [], | |||
createInterval: null, | |||
isActive: false, | |||
snowflakeCount: 30, | |||
random: function (min, max) { | |||
return Math.random() * (max - min) + min; | |||
}, | |||
init: function () { | |||
if (this.isActive) return; | |||
this.addStyles(); | |||
this.startContinuousCreation(); | |||
this.isActive = true; | |||
}, | |||
addStyles: function () { | |||
if (document.getElementById("snowflakes-styles")) return; | |||
var style = document.createElement("style"); | |||
style.id = "snowflakes-styles"; | |||
style.textContent = | |||
".snowflake {" + | |||
"position: fixed;" + | |||
"top: -10px;" + | |||
"color: #fff;" + | |||
"font-family: Arial, sans-serif;" + | |||
"user-select: none;" + | |||
"pointer-events: none;" + | |||
"z-index: 1;" + | |||
"animation-name: snowflake-fall;" + | |||
"animation-timing-function: linear;" + | |||
"animation-iteration-count: infinite;" + | |||
"will-change: transform;" + | |||
"}" + | |||
"@keyframes snowflake-fall {" + | |||
"0% {" + | |||
"transform: translateY(0) translateX(0) rotate(0deg);" + | |||
"}" + | |||
"25% {" + | |||
"transform: translateY(25vh) translateX(10px) rotate(90deg);" + | |||
"}" + | |||
"50% {" + | |||
"transform: translateY(50vh) translateX(-10px) rotate(180deg);" + | |||
"}" + | |||
"75% {" + | |||
"transform: translateY(75vh) translateX(10px) rotate(270deg);" + | |||
"}" + | |||
"100% {" + | |||
"transform: translateY(calc(100vh + 100px)) translateX(0) rotate(360deg);" + | |||
"}" + | |||
"}" + | |||
".snowflake.flare {" + | |||
"text-shadow: 0 0 5px rgba(255, 255, 255, 0.4), 0 0 10px rgba(255, 255, 255, 0.2);" + | |||
"}" + | |||
".snowflake.blurred {" + | |||
"filter: blur(2px);" + | |||
"}"; | |||
document.head.appendChild(style); | |||
}, | |||
createSnowflake: function () { | |||
var snowflake = document.createElement("div"); | |||
snowflake.className = "snowflake"; | |||
var symbol = this.getSnowflakeSymbol(); | |||
snowflake.textContent = symbol; | |||
var fontSize = this.random(0.5, 1.2); | |||
snowflake.style.fontSize = fontSize + "em"; | |||
snowflake.style.left = this.random(0, 100) + "vw"; | |||
snowflake.style.opacity = this.random(0.25, 0.5); | |||
var fallDuration = this.random(10, 30); | |||
snowflake.style.animationDuration = fallDuration + "s"; | |||
snowflake.style.animationDelay = this.random(0, 2) + "s"; | |||
if (Math.random() > 0.95) { | |||
snowflake.classList.add("flare"); | |||
} | |||
if (Math.random() > 0.4) { | |||
snowflake.classList.add("blurred"); | |||
} | |||
document.body.appendChild(snowflake); | |||
this.snowflakes.push(snowflake); | |||
var self = this; | |||
var checkRemove = setInterval(function () { | |||
var rect = snowflake.getBoundingClientRect(); | |||
if (rect.top > window.innerHeight + 50) { | |||
if (snowflake.parentNode) { | |||
snowflake.remove(); | |||
} | |||
var index = self.snowflakes.indexOf(snowflake); | |||
if (index > -1) { | |||
self.snowflakes.splice(index, 1); | |||
} | |||
clearInterval(checkRemove); | |||
} | |||
}, 100); | |||
}, | |||
getSnowflakeSymbol: function () { | |||
var symbols = ["❄", "❅", "❆"]; | |||
return symbols[Math.floor(Math.random() * symbols.length)]; | |||
}, | |||
startContinuousCreation: function () { | |||
var self = this; | |||
this.createInterval = setInterval(function () { | |||
if ( | |||
self.isActive && | |||
self.snowflakes.length < self.snowflakeCount * 1.5 | |||
) { | |||
self.createSnowflake(); | |||
} | |||
}, 500); | |||
}, | |||
stop: function () { | |||
this.isActive = false; | |||
if (this.createInterval) { | |||
clearInterval(this.createInterval); | |||
this.createInterval = null; | |||
} | |||
this.snowflakes.forEach(function (snowflake) { | |||
if (snowflake && snowflake.parentNode) { | |||
snowflake.remove(); | |||
} | } | ||
}); | |||
this.snowflakes = []; | |||
var styles = document.getElementById("snowflakes-styles"); | |||
if (styles) { | |||
styles.remove(); | |||
} | |||
}, | |||
toggle: function () { | |||
if (this.isActive) { | |||
this.stop(); | |||
} else { | |||
this.init(); | |||
} | |||
}, | |||
}; | |||
window.SnowflakesModule = SnowflakesModule; | |||
var FireworksModule = { | |||
fireworks: null, | |||
container: null, | |||
checkInterval: null, | |||
isActive: false, | |||
loaded: false, | |||
loading: false, | |||
callbacks: [], | |||
init: function () { | |||
this.startTimeCheck(); | |||
}, | |||
loadFireworks: function (callback) { | |||
// Проверяем разные варианты экспорта библиотеки | |||
if ( | |||
this.loaded && | |||
(window.Fireworks || | |||
(window.Fireworks && window.Fireworks.default)) | |||
) { | |||
if (callback) callback(); | |||
return; | |||
} | |||
if (callback) { | |||
this.callbacks.push(callback); | |||
} | |||
if (this.loading) return; | |||
this.loading = true; | |||
var self = this; | |||
// Загружаем библиотеку fireworks.js через UMD версию | |||
var script = document.createElement("script"); | |||
script.src = "https://unpkg.com/fireworks-js@2/dist/index.umd.js"; | |||
script.onload = function () { | |||
self.loaded = true; | |||
self.loading = false; | |||
// Вызываем все колбэки | |||
self.callbacks.forEach(function (cb) { | |||
cb(); | |||
}); | |||
self.callbacks = []; | |||
}; | |||
script.onerror = function () { | |||
console.error("Ошибка загрузки fireworks.js"); | |||
self.loading = false; | |||
self.callbacks = []; | |||
}; | |||
document.head.appendChild(script); | |||
}, | |||
getMoscowTime: function () { | |||
// МСК = UTC+3 | |||
var now = new Date(); | |||
var utc = now.getTime() + now.getTimezoneOffset() * 60000; | |||
var moscowTime = new Date(utc + 3 * 3600000); // UTC+3 | |||
return moscowTime; | |||
}, | |||
isFireworksTime: function () { | |||
var moscowTime = this.getMoscowTime(); | |||
var hours = moscowTime.getHours(); | |||
// С 00:00 до 01:00 по МСК | |||
return hours === 0; | |||
}, | |||
startTimeCheck: function () { | |||
var self = this; | |||
// Проверяем сразу при загрузке | |||
this.checkTime(); | |||
// Проверяем каждую минуту | |||
this.checkInterval = setInterval(function () { | |||
self.checkTime(); | |||
}, 60000); | |||
}, | |||
checkTime: function () { | |||
if (this.isFireworksTime()) { | |||
if (!this.isActive) { | |||
this.startFireworks(); | |||
} | |||
} else { | |||
if (this.isActive) { | |||
this.stopFireworks(); | |||
} | |||
} | |||
}, | |||
getFireworksConstructor: function () { | |||
// Пробуем разные варианты доступа к конструктору | |||
if (window.Fireworks && typeof window.Fireworks === "function") { | |||
return window.Fireworks; | |||
} | |||
if ( | |||
window.Fireworks && | |||
window.Fireworks.default && | |||
typeof window.Fireworks.default === "function" | |||
) { | |||
return window.Fireworks.default; | |||
} | |||
if (window.fireworks && typeof window.fireworks === "function") { | |||
return window.fireworks; | |||
} | |||
// Если библиотека экспортируется по-другому | |||
if ( | |||
typeof Fireworks !== "undefined" && | |||
typeof Fireworks === "function" | |||
) { | |||
return Fireworks; | |||
} | |||
return null; | |||
}, | |||
startFireworks: function () { | |||
var self = this; | |||
this.loadFireworks(function () { | |||
if (!self.container) { | |||
self.container = document.createElement("div"); | |||
self.container.id = "fireworks-container"; | |||
self.container.style.cssText = | |||
"position: fixed;" + | |||
"top: 0;" + | |||
"left: 0;" + | |||
"width: 100%;" + | |||
"height: 100%;" + | |||
"pointer-events: none;" + | |||
"z-index: 99998;"; | |||
document.body.appendChild(self.container); | |||
} | |||
var FireworksConstructor = self.getFireworksConstructor(); | |||
if (!FireworksConstructor) { | |||
console.error( | |||
"Не удалось найти конструктор Fireworks. Проверьте загрузку библиотеки.", | |||
); | |||
console.log("Доступные объекты:", { | |||
"window.Fireworks": window.Fireworks, | |||
"window.Fireworks.default": | |||
window.Fireworks && window.Fireworks.default, | |||
"window.fireworks": window.fireworks, | |||
}); | |||
return; | |||
} | |||
if (!self.fireworks) { | |||
try { | |||
self.fireworks = new FireworksConstructor( | |||
self.container, | |||
{ | |||
autoresize: true, | |||
opacity: 0.5, | |||
// Замедляем фейерверки | |||
acceleration: 1.02, // Уменьшено с 1.05 для более медленного движения | |||
friction: 0.98, // Увеличено с 0.97 для большего сопротивления | |||
gravity: 1.2, // Уменьшено с 1.5 для более медленного падения | |||
particles: 50, | |||
traceLength: 5, // Увеличено с 3 для более длинного следа | |||
traceSpeed: 5, // Уменьшено с 10 для более медленного следа | |||
explosion: 5, | |||
intensity: 30, | |||
flickering: 50, | |||
lineStyle: "round", | |||
hue: { min: 0, max: 360 }, | |||
// Увеличиваем задержку между запусками для более медленного темпа | |||
delay: { min: 30, max: 60 }, // Увеличено с 15-30 до 30-60 | |||
rocketsPoint: { min: 50, max: 50 }, | |||
lineWidth: { | |||
explosion: { min: 1, max: 3 }, | |||
trace: { min: 1, max: 2 }, | |||
}, | |||
brightness: { min: 50, max: 80 }, | |||
decay: { min: 0.01, max: 0.02 }, // Уменьшено для более медленного затухания | |||
mouse: { click: false, move: false, max: 1 }, | |||
// Включаем звук | |||
sound: { | |||
enable: true, | |||
files: [ | |||
"https://fireworks.js.org/sounds/explosion0.mp3", | |||
"https://fireworks.js.org/sounds/explosion1.mp3", | |||
"https://fireworks.js.org/sounds/explosion2.mp3", | |||
], | |||
volume: { min: 0.3, max: 0.6 }, // Громкость от 30% до 60% | |||
}, | |||
}, | |||
); | |||
} catch (e) { | |||
console.error( | |||
"Ошибка создания экземпляра Fireworks:", | |||
e, | |||
); | |||
return; | |||
} | |||
} | |||
if (self.fireworks) { | |||
self.fireworks.start(); | |||
self.isActive = true; | |||
} | |||
}); | |||
}, | |||
stopFireworks: function () { | |||
if (this.fireworks) { | |||
this.fireworks.stop(); | |||
this.isActive = false; | |||
} | |||
}, | |||
toggleFireworks: function () { | |||
if (this.isActive) { | |||
this.stopFireworks(); | |||
} else { | |||
this.startFireworks(); | |||
} | |||
}, | |||
}; | |||
window.FireworksModule = FireworksModule; | |||
function initAllModules() { | function initAllModules() { | ||
| Строка 2033: | Строка 2417: | ||
// LawCalculatorModule.init(); | // LawCalculatorModule.init(); | ||
} | } | ||
if (document.readyState === | if (document.readyState === "loading") { | ||
document.addEventListener( | document.addEventListener("DOMContentLoaded", initAllModules); | ||
} else { | } else { | ||
initAllModules(); | initAllModules(); | ||
} | } | ||
})(); | })(); | ||