MediaWiki:Common.js: различия между версиями
Pok (обсуждение | вклад) мНет описания правки Метка: ручная отмена |
Pok (обсуждение | вклад) мНет описания правки |
||
| Строка 282: | Строка 282: | ||
// Функция для увеличения яркости цвета | // Функция для увеличения яркости цвета | ||
function brightenColor(color, factor) { | function brightenColor(color, factor) { | ||
function getBrightness(r, g, b) { | |||
return 0.2126 * r + 0.7152 * g + 0.0722 * b; | |||
} | |||
function isGray(r, g, b) { | |||
var maxDiff = 20; // Максимальное допустимое отклонение между RGB для серого | |||
return Math.abs(r - g) < maxDiff && Math.abs(g - b) < maxDiff && Math.abs(r - b) < maxDiff; | |||
} | |||
var rgbValues = color.match(/\d+(\.\d+)?/g); | |||
var r = parseInt(rgbValues[0], 10); | |||
var g = parseInt(rgbValues[1], 10); | |||
var b = parseInt(rgbValues[2], 10); | |||
var a = rgbValues.length === 4 ? parseFloat(rgbValues[3]) : 1; | |||
var brightness = getBrightness(r, g, b); | |||
if (factor === undefined) { | |||
if (brightness <= 180) { | |||
factor = 4; | |||
} else { | |||
factor = 0; | |||
} | |||
} | |||
// Если цвет серый, уменьшаем фактор | |||
if (isGray(r, g, b)) { | |||
factor = Math.max(1, factor * 0.85); | |||
} | |||
// Добавляем фактор вместо умножения | |||
r = Math.min(255, r + factor); | |||
g = Math.min(255, g + factor); | |||
b = Math.min(255, b + factor); | |||
return a < 1 ? 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')' : 'rgb(' + r + ', ' + g + ', ' + b + ')'; | |||
} | } | ||
| Строка 336: | Строка 336: | ||
return row.parentElement === tbody && !row.querySelector('table'); | return row.parentElement === tbody && !row.querySelector('table'); | ||
}); | }); | ||
// Пропускаем первую строку, если нет <thead> и нет класса 'no-header-table' | // Пропускаем первую строку, если нет <thead> и нет класса 'no-header-table' | ||
var topLevelRows = (!thead && !noHeader) ? rows.slice(1) : rows; | var topLevelRows = (!thead && !noHeader) ? rows.slice(1) : rows; | ||
var hasInvalidRowspan = false; | var hasInvalidRowspan = false; | ||
var hasTooManyRowspan = false; | var hasTooManyRowspan = false; | ||
// Проверка на ошибки в rowspan | // Проверка на ошибки в rowspan | ||
Array.prototype.forEach.call(topLevelRows, function(row) { | Array.prototype.forEach.call(topLevelRows, function(row) { | ||
| Строка 347: | Строка 349: | ||
var cellCount = cells.length; | var cellCount = cells.length; | ||
var rowspanCount = cells.filter(function(cell) { | var rowspanCount = cells.filter(function(cell) { | ||
return cell.hasAttribute('rowspan') && cell.getAttribute('rowspan') !== '1'; | return cell.hasAttribute('rowspan') && cell.getAttribute('rowspan') !== '1'; // Игнорируем rowspan="1" | ||
}).length; | }).length; | ||
// Проверка на слишком большое количество rowspan или неправильное расположение rowspan | // Проверка на слишком большое количество rowspan или неправильное расположение rowspan | ||
if (rowspanCount > 2 || (cellCount <= 3 && rowspanCount > 1)) { | if (rowspanCount > 2 || (cellCount <= 3 && rowspanCount > 1)) { | ||
| Строка 354: | Строка 357: | ||
return; | return; | ||
} | } | ||
// Проверяем, что ячейки с rowspan находятся по краям строки | |||
var hasValidRowspanEdge = cells.some(function(cell, index) { | var hasValidRowspanEdge = cells.some(function(cell, index) { | ||
return cell.hasAttribute('rowspan') && cell.getAttribute('rowspan') !== '1' && (index === 0 || | return cell.hasAttribute('rowspan') && cell.getAttribute('rowspan') !== '1' && (index === 0 || index === cells.length - 1); | ||
}); | }); | ||
// Если ячейки с rowspan находятся не по краям | |||
if (!hasValidRowspanEdge && cells.some(function(cell, index) { | if (!hasValidRowspanEdge && cells.some(function(cell, index) { | ||
return cell.hasAttribute('rowspan') && cell.getAttribute('rowspan') !== '1' && index > 0 && index < cells.length - 1; | |||
})) { | |||
hasInvalidRowspan = true; | hasInvalidRowspan = true; | ||
} | } | ||
}); | }); | ||
// Если есть ошибки в rowspan, выходим | // Если есть ошибки в rowspan, выходим | ||
if (hasTooManyRowspan || hasInvalidRowspan) return; | if (hasTooManyRowspan || hasInvalidRowspan) return; | ||
// Обработка наведения на строки и ячейки | // Обработка наведения на строки и ячейки | ||
Array.prototype.forEach.call(topLevelRows, function(row, rowIndex) { | Array.prototype.forEach.call(topLevelRows, function(row, rowIndex) { | ||
| Строка 377: | Строка 384: | ||
}; | }; | ||
}); | }); | ||
// Наведение на строку (tr) | // Наведение на строку (tr) | ||
row.addEventListener('mouseover', function() { | row.addEventListener('mouseover', function() { | ||
| Строка 384: | Строка 392: | ||
highlightRow(row, rowIndex, false); | highlightRow(row, rowIndex, false); | ||
}); | }); | ||
// Наведение на ячейки с rowspan | // Наведение на ячейки с rowspan | ||
Array.prototype.forEach.call(cells, function(cell, cellIndex) { | Array.prototype.forEach.call(cells, function(cell, cellIndex) { | ||
// Обрабатываем только ячейки с rowspan > 1 | |||
if (cell.hasAttribute('rowspan') && parseInt(cell.getAttribute('rowspan')) > 1) { | if (cell.hasAttribute('rowspan') && parseInt(cell.getAttribute('rowspan')) > 1) { | ||
var rowspan = parseInt(cell.getAttribute('rowspan')); | var rowspan = parseInt(cell.getAttribute('rowspan')); | ||
// Обработка rowspan ячейки | // Обработка rowspan ячейки | ||
cell.addEventListener('mouseover', function() { | cell.addEventListener('mouseover', function() { | ||
| Строка 394: | Строка 405: | ||
highlightRowspanArea(rowIndex, cellIndex, rowspan, true); | highlightRowspanArea(rowIndex, cellIndex, rowspan, true); | ||
}); | }); | ||
cell.addEventListener('mouseout', function() { | cell.addEventListener('mouseout', function() { | ||
// Восстанавливаем состояние строки при уходе курсора | // Восстанавливаем состояние строки при уходе курсора | ||
| Строка 401: | Строка 413: | ||
} | } | ||
}); | }); | ||
// Функция подсветки строки (только текущее tr) | // Функция подсветки строки (только текущее tr) | ||
function highlightRow(row, rowIndex, highlight) { | function highlightRow(row, rowIndex, highlight) { | ||
| Строка 406: | Строка 419: | ||
var rowCells = Array.prototype.slice.call(row.querySelectorAll('td, th')); | var rowCells = Array.prototype.slice.call(row.querySelectorAll('td, th')); | ||
Array.prototype.forEach.call(rowCells, function(cell, cellIndex) { | Array.prototype.forEach.call(rowCells, function(cell, cellIndex) { | ||
// Подсвечиваем только ячейки, не имеющие атрибут rowSpan | // Подсвечиваем только ячейки, не имеющие атрибут rowSpan или с rowspan="1" | ||
if (!cell.hasAttribute('rowspan')) { | if (!cell.hasAttribute('rowspan') || parseInt(cell.getAttribute('rowspan')) === 1) { | ||
var cellStyle = getComputedStyle(cell); | var cellStyle = getComputedStyle(cell); | ||
cell.style.setProperty('background-color', highlight ? brightenColor(cellStyle.backgroundColor) : | cell.style.setProperty('background-color', highlight ? brightenColor(cellStyle.backgroundColor) : | ||
originalStyles[cellIndex].backgroundColor, 'important'); | originalStyles[cellIndex].backgroundColor, 'important'); | ||
cell.style.setProperty('color', highlight ? brightenColor(cellStyle.color) : originalStyles[ | cell.style.setProperty('color', highlight ? brightenColor(cellStyle.color) : originalStyles[cellIndex].color, 'important'); | ||
} | } | ||
}); | }); | ||
} | } | ||
// Функция подсветки для ячеек с rowspan (подсвечивает все строки, которые охватывает ячейка) | // Функция подсветки для ячеек с rowspan (подсвечивает все строки, которые охватывает ячейка) | ||
function highlightRowspanArea(rowIndex, cellIndex, rowspan, highlight) { | function highlightRowspanArea(rowIndex, cellIndex, rowspan, highlight) { | ||
| Строка 423: | Строка 436: | ||
var targetCells = Array.prototype.slice.call(targetRow.querySelectorAll('td, th')); | var targetCells = Array.prototype.slice.call(targetRow.querySelectorAll('td, th')); | ||
// Обработка ячейки в каждой строке, затронутой rowspan | // Обработка ячейки в каждой строке, затронутой rowspan | ||
Array.prototype.forEach.call(targetCells, function(targetCell | Array.prototype.forEach.call(targetCells, function(targetCell) { | ||
var targetCellStyle = getComputedStyle(targetCell); | var targetCellStyle = getComputedStyle(targetCell); | ||
// Сохраняем оригинальные стили для каждой ячейки, если подсветка включена | // Сохраняем оригинальные стили для каждой ячейки, если подсветка включена | ||
| Строка 445: | Строка 458: | ||
// Функции для добавления кастомных заголовков в TOC | // Функции для добавления кастомных заголовков в TOC | ||
function addHeadingsWithTOC() { | function addHeadingsWithTOC() { | ||
mw.hook('wikipage.content').add(function($content) { | |||
var $toc = $('#toc ul'); // Находим TOC | |||
$toc.empty(); | |||
var $headings = $content.find('h1, h2, h3, h4, h5, h6, .custom-heading'); | |||
var tocCounters = []; | |||
var $currentList = $toc; // Текущий список для вставки элементов | |||
var levelStack = []; // Стек для отслеживания уровней заголовков | |||
var maxLevel = 7; // Изначально максимальный уровень не определён | |||
// Первый проход для нахождения минимального уровня заголовков | |||
$headings.each(function() { | |||
var $heading = $(this); | |||
var level; | |||
if ($heading.hasClass('custom-heading')) { | |||
level = 1; // Для кастомных заголовков | |||
} else { | |||
var tagName = $heading.prop('tagName').toLowerCase(); | |||
level = parseInt(tagName.charAt(1), 10); // Определяем уровень h1-h6 | |||
} | |||
if (level < maxLevel) { | |||
maxLevel = level; // Находим минимальный уровень заголовков | |||
} | |||
}); | |||
function updateCounters(level) { | |||
if (level > tocCounters.length) { | |||
tocCounters.push(0); | |||
} else if (level < tocCounters.length) { | |||
tocCounters = tocCounters.slice(0, level); | |||
} | |||
tocCounters[level - 1]++; | |||
} | |||
function getSectionNumber() { | |||
return tocCounters.join('.'); | |||
} | |||
function createNestedList($parent) { | |||
var $nestedList = $('<ul>'); | |||
$parent.append($nestedList); | |||
return $nestedList; | |||
} | |||
function closeNestedLists(targetLevel) { | |||
while (levelStack.length > 0 && levelStack[levelStack.length - 1] >= targetLevel) { | |||
$currentList = $currentList.parent().closest('ul'); | |||
levelStack.pop(); | |||
} | |||
} | |||
$headings.each(function() { | |||
var $heading = $(this); | |||
var level; | |||
var $headlineSpan = $heading.find('span.mw-headline'); | |||
if ($heading.hasClass('custom-heading')) { | |||
level = 1; | |||
var customId = $heading.attr('id') || 'custom-heading-' + getSectionNumber(); | |||
$heading.attr('id', customId); | |||
} else if ($headlineSpan.length > 0) { | |||
var tagName = $heading.prop('tagName').toLowerCase(); | |||
level = parseInt(tagName.charAt(1), 10); | |||
var existingId = $headlineSpan.attr('id'); | |||
if (!existingId) { | |||
var sectionId = 'heading-' + getSectionNumber(); | |||
$headlineSpan.attr('id', sectionId); | |||
} | |||
} else { | |||
return; // Пропускаем элементы без заголовков | |||
} | |||
updateCounters(level); | |||
var headingText = $headlineSpan.text().trim() || $heading.text().trim(); | |||
if (headingText.length > 0) { | |||
// Закрываем вложенные списки, если текущий уровень меньше | |||
closeNestedLists(level); | |||
var tocItem = $('<li>').addClass('toclevel-' + (level - maxLevel)).append( | |||
$('<a>').attr('href', '#' + ($headlineSpan.attr('id') || customId)).append( | |||
$('<span>').addClass('tocnumber').text(getSectionNumber()), | |||
$('<span>').addClass('toctext').text(headingText) | |||
) | |||
); | |||
// Если текущий уровень равен максимальному (высший уровень), добавляем его в основной список | |||
if (level === maxLevel) { | |||
$toc.append(tocItem); // Главный список | |||
$currentList = $toc; // Сбрасываем на основной список | |||
} else { | |||
// Если уровень ниже максимального, создаем вложенный список | |||
if (level > levelStack[levelStack.length - 1]) { | |||
var $nestedList = createNestedList($currentList); | |||
$currentList = $nestedList; | |||
} | |||
$currentList.append(tocItem); | |||
} | |||
// Добавляем уровень в стек для дальнейшего отслеживания | |||
levelStack.push(level); | |||
} | |||
}); | |||
}); | |||
} | } | ||
// Функция для логики меню создаваемым модулем CategoryMenu | // Функция для логики меню создаваемым модулем CategoryMenu | ||
function initCategorySwitcher() { | function initCategorySwitcher() { | ||
var categories = document.querySelectorAll('.categories div'); | |||
var menus = document.querySelectorAll('.menu'); | |||
var contentDivs = document.querySelectorAll('.content div'); | |||
var menuItems = document.querySelectorAll('.menu div'); | |||
function clearActiveContent() { | |||
for (var i = 0; i < contentDivs.length; i++) { | |||
contentDivs[i].classList.remove('active'); | |||
} | |||
} | |||
function clearActiveMenu() { | |||
for (var i = 0; i < menus.length; i++) { | |||
menus[i].classList.remove('active'); | |||
} | |||
} | |||
function clearActiveMenuItems() { | |||
for (var i = 0; i < menuItems.length; i++) { | |||
menuItems[i].classList.remove('active'); | |||
} | |||
} | |||
function switchCategory(categoryClass) { | |||
clearActiveMenu(); | |||
clearActiveContent(); | |||
var selectedMenu = document.querySelector('.' + categoryClass + '-menu'); | |||
if (selectedMenu) { | |||
selectedMenu.classList.add('active'); | |||
var firstParagraph = selectedMenu.querySelector('div'); | |||
if (firstParagraph) { | |||
switchContent(firstParagraph); | |||
} | |||
} | |||
} | |||
function switchContent(menuItem) { | |||
clearActiveMenuItems(); | |||
clearActiveContent(); | |||
var contentClass = menuItem.className + '-content'; | |||
var content = document.querySelector('.' + contentClass); | |||
if (content) { | |||
content.classList.add('active'); | |||
menuItem.classList.add('active'); | |||
} | |||
} | |||
for (var i = 0; i < categories.length; i++) { | |||
categories[i].addEventListener('click', function() { | |||
var categoryClass = this.classList[0]; | |||
switchCategory(categoryClass); | |||
}); | |||
} | |||
for (var i = 0; i < menuItems.length; i++) { | |||
menuItems[i].addEventListener('click', function() { | |||
switchContent(this); | |||
}); | |||
} | |||
// Открытие первой категории и первого пункта | |||
var firstCategory = categories.length > 0 ? categories[0].classList[0] : null; | |||
if (firstCategory) { | |||
switchCategory(firstCategory); | |||
} | |||
} | } | ||
const currentPageTitle = document.title; | const currentPageTitle = document.title; | ||
| Строка 708: | Строка 721: | ||
}; | }; | ||
} | } | ||
var categoriesExist = document.querySelectorAll('.categories'); | |||
if (categoriesExist.length > 0) { | |||
initCategorySwitcher(); | |||
} | |||
}); | }); | ||
/*WikiEditor/Викификатор*/ | /*WikiEditor/Викификатор*/ | ||