MediaWiki:Common.js: различия между версиями
Pok (обсуждение | вклад) Нет описания правки Метка: отменено |
Pok (обсуждение | вклад) Нет описания правки |
||
| (не показано 16 промежуточных версий этого же участника) | |||
| Строка 273: | Строка 273: | ||
// Функция для увеличения яркости цвета | // Функция для увеличения яркости цвета | ||
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) { | |||
const maxDiff = 20; | |||
return Math.abs(r - g) < maxDiff | |||
&& Math.abs(g - b) < maxDiff | |||
&& Math.abs(r - b) < maxDiff; | |||
} | |||
const vals = color.match(/-?\d+(\.\d+)?/g).map(parseFloat); | |||
let [r, g, b, a = 1] = vals; | |||
if (r <= 1 && g <= 1 && b <= 1) { | |||
r *= 255; | |||
g *= 255; | |||
b *= 255; | |||
} | |||
r = Math.round(r); | |||
g = Math.round(g); | |||
b = Math.round(b); | |||
const brightness = getBrightness(r, g, b); | |||
if (factor === undefined) { | |||
if (brightness <= 40) { | |||
factor = 6; | |||
} else if (brightness <= 180) { | |||
factor = 4; | |||
} else { | |||
factor = 0; | |||
} | |||
} | |||
if (isGray(r, g, b)) { | |||
factor = Math.max(1, factor * 0.5); | |||
} | |||
r = Math.min(255, r + factor); | |||
g = Math.min(255, g + factor); | |||
b = Math.min(255, b + factor); | |||
if (a < 1) { | |||
return `rgba(${r}, ${g}, ${b}, ${a})`; | |||
} else { | |||
return `rgb(${r}, ${g}, ${b})`; | |||
} | |||
} | } | ||
| Строка 621: | Строка 621: | ||
} | } | ||
function layerIndex() { | function layerIndex() { | ||
var z_index = 1000; | |||
document.querySelectorAll('.z-index-position').forEach(function(domEl) { | |||
domEl.style.zIndex = z_index--; | |||
}); | |||
} | } | ||
// Для "Шаблон:Ajax" | // Для "Шаблон:Ajax" | ||
function initAjaxLoader() { | function initAjaxLoader() { | ||
var ajaxContainers = document.querySelectorAll('.ajax-load, .ajax-load-link'); | |||
var BATCH_SIZE = 1000; | |||
var queue = []; | |||
var processing = false; | |||
// HTML для спиннера (анимация загрузки) | |||
var loadingImgHTML = '<img alt="Файл:Silva-loading.gif" src="/images/4/45/Silva-loading.gif?20260316201152" decoding="async" width="16" height="16" data-file-width="16" data-file-height="16" style="vertical-align:middle;margin-right:6px">'; | |||
function fetchParsedData(wikiText) { | |||
var apiUrl = "https://station14.ru/api.php?action=parse&format=json&prop=text&text=" + encodeURIComponent(wikiText) + "&origin=*"; | |||
return $.ajax({ | |||
url: apiUrl, | |||
method: "GET", | |||
dataType: "json" | |||
}); | |||
} | |||
function applyParsedHTML(parsedHTML, placeholder) { | |||
var newContainer = document.createElement("span"); | |||
newContainer.innerHTML = parsedHTML; | |||
if (placeholder && placeholder.parentNode) { | |||
placeholder.replaceWith(newContainer); | |||
} | |||
var scripts = Array.prototype.slice.call(newContainer.querySelectorAll("script")); | |||
scripts.forEach(function (scr) { | |||
var parent = scr.parentNode; | |||
var scriptNode = document.createElement("script"); | |||
if (scr.src) { | |||
scriptNode.src = scr.src; | |||
if (scr.defer) scriptNode.defer = true; | |||
if (scr.async) scriptNode.async = true; | |||
parent.replaceChild(scriptNode, scr); | |||
} else { | |||
try { $.globalEval(scr.textContent || scr.innerText || ""); } catch (e) {} | |||
parent.removeChild(scr); | |||
} | |||
}); | |||
mw.loader.using(['jquery.tablesorter', 'jquery.makeCollapsible'], function () { | |||
$(newContainer).find('table.sortable').tablesorter(); | |||
$(newContainer).find('.mw-collapsible').makeCollapsible(); | |||
}); | |||
mw.hook('wikipage.content').fire(newContainer); | |||
mw.loader.load('//station14.ru/w/index.php?title=MediaWiki:Common.js&action=raw&ctype=text/javascript'); | |||
} | |||
function processQueue() { | |||
if (processing) return; | |||
processing = true; | |||
function nextBatch() { | |||
if (queue.length === 0) { | |||
processing = false; | |||
return; | |||
} | |||
var batch = queue.splice(0, BATCH_SIZE); | |||
var remaining = batch.length; | |||
batch.forEach(function (job) { | |||
fetchParsedData(job.wikiText) | |||
.done(function (data) { | |||
if (data.parse && data.parse.text) { | |||
var parsedHTML = data.parse.text["*"] || ""; | |||
applyParsedHTML(parsedHTML, job.placeholder); | |||
} else { | |||
if (job.placeholder) job.placeholder.textContent = "API не вернул ожидаемых данных."; | |||
} | |||
}) | |||
.fail(function () { | |||
if (job.placeholder) job.placeholder.textContent = "Ошибка при выполнении запроса к API."; | |||
}) | |||
.always(function () { | |||
remaining--; | |||
if (remaining === 0) nextBatch(); | |||
}); | |||
}); | |||
} | |||
nextBatch(); | |||
} | |||
ajaxContainers.forEach(function (container) { | |||
var contentEl = container.querySelector('.ajax-load-content'); | |||
var loadingEl = container.querySelector('.ajax-load-loading'); | |||
if (!contentEl) return; | |||
var wikiText = (contentEl.textContent || contentEl.innerText || "").trim(); | |||
if (!wikiText) return; | |||
var loadingText = (loadingEl && (loadingEl.textContent || loadingEl.innerText || "").trim()) || "Загрузка..."; | |||
// Если есть элемент .ajax-load-loading — можно удалить (как в оригинале), | |||
// но плейсхолдер мы всё равно создаём с картинкой + текстом. | |||
if (loadingEl && loadingEl.parentNode) { | |||
loadingEl.parentNode.removeChild(loadingEl); | |||
loadingEl = null; | |||
} | |||
function createPlaceholderWithSpinner(text) { | |||
var placeholder = document.createElement("span"); | |||
placeholder.className = "ajax-load-placeholder"; | |||
// Добавляем картинку в начало и текст после неё | |||
placeholder.innerHTML = loadingImgHTML + '<span class="ajax-load-text">' + (text || '') + '</span>'; | |||
return placeholder; | |||
} | |||
if (container.classList.contains('ajax-load-link')) { | |||
container.addEventListener('click', function (ev) { | |||
ev.preventDefault && ev.preventDefault(); | |||
if (container.dataset.ajaxLoaded === "1") return; | |||
container.dataset.ajaxLoaded = "1"; | |||
var placeholder = createPlaceholderWithSpinner(loadingText); | |||
if (container && container.parentNode) { | |||
container.parentNode.replaceChild(placeholder, container); | |||
} else { | |||
if (contentEl && contentEl.parentNode) { | |||
contentEl.parentNode.replaceChild(placeholder, contentEl); | |||
} | |||
} | |||
queue.push({ wikiText: wikiText, placeholder: placeholder }); | |||
processQueue(); | |||
}, { once: true }); | |||
} else { | |||
var placeholder = createPlaceholderWithSpinner(loadingText); | |||
if (contentEl && contentEl.parentNode) { | |||
contentEl.parentNode.replaceChild(placeholder, contentEl); | |||
} | |||
queue.push({ wikiText: wikiText, placeholder: placeholder }); | |||
} | |||
}); | |||
if (queue.length > 0) processQueue(); | |||
} | |||
// Для "Шаблон:CheckboxCreator" | |||
function initCheckboxCreator() { | |||
var containers = document.getElementsByClassName('js-checkbox-generator'); | |||
function parseBool(v) { | |||
if (v === true) return true; | |||
if (v === false || v === undefined || v === null) return false; | |||
var s = String(v).trim().toLowerCase(); | |||
return s === '1' || s === 'true' || s === 'yes' || s === 'checked'; | |||
} | |||
function escHtml(s) { | |||
return String(s === undefined || s === null ? '' : s) | |||
.replace(/&/g, '&') | |||
.replace(/</g, '<') | |||
.replace(/>/g, '>') | |||
.replace(/"/g, '"') | |||
.replace(/'/g, '''); | |||
} | |||
var HTML_BATCH_THRESHOLD = 200; | |||
for (var ci = 0; ci < containers.length; ci++) { | |||
var container = containers[ci]; | |||
if (container.getAttribute('data-checkbox-initialized') === '1') { | |||
continue; | |||
} | |||
var defaultCls = container.getAttribute('data-cbox-class') || ''; | |||
var itemsRaw = container.getAttribute('data-cbox-items'); | |||
if (itemsRaw) { | |||
var arr = null; | |||
try { | |||
arr = JSON.parse(itemsRaw); | |||
} catch (e) { | |||
container.setAttribute('data-checkbox-initialized', '1'); | |||
continue; | |||
} | |||
if (!Array.isArray(arr) || arr.length === 0) { | |||
container.setAttribute('data-checkbox-initialized', '1'); | |||
continue; | |||
} | |||
if (arr.length >= HTML_BATCH_THRESHOLD) { | |||
var html = ''; | |||
for (var i = 0; i < arr.length; i++) { | |||
var it = arr[i]; | |||
var label = ''; | |||
var cls = defaultCls; | |||
var checked = false; | |||
var name = ''; | |||
var id = ''; | |||
var disabled = false; | |||
if (typeof it === 'string' || typeof it === 'number') { | |||
label = it; | |||
} else if (typeof it === 'object' && it !== null) { | |||
label = it.label || ''; | |||
cls = (it["class"] || it.cls || defaultCls); | |||
checked = parseBool(it.checked); | |||
name = it.name || ''; | |||
id = it.id || ''; | |||
disabled = parseBool(it.disabled); | |||
} | |||
html += '<span class="js-checkbox-gen-item">'; | |||
html += '<input type="checkbox"'; | |||
if (cls) html += ' class="' + escHtml(cls) + '"'; | |||
if (name) html += ' name="' + escHtml(name) + '"'; | |||
if (id) html += ' id="' + escHtml(id) + '"'; | |||
if (checked) html += ' checked'; | |||
if (disabled) html += ' disabled'; | |||
html += '>'; | |||
html += '<label'; | |||
if (id) html += ' for="' + escHtml(id) + '"'; | |||
html += '>' + escHtml(label) + '</label>'; | |||
html += '</span>'; | |||
} | |||
container.insertAdjacentHTML('beforeend', html); | |||
} else { | |||
var frag = document.createDocumentFragment(); | |||
for (var j = 0; j < arr.length; j++) { | |||
var item = arr[j]; | |||
var labelText = ''; | |||
var itemCls = defaultCls; | |||
var itemChecked = false; | |||
var itemName = ''; | |||
var itemId = ''; | |||
var itemDisabled = false; | |||
if (typeof item === 'string' || typeof item === 'number') { | |||
labelText = item; | |||
} else if (typeof item === 'object' && item !== null) { | |||
labelText = item.label || ''; | |||
itemCls = (item["class"] || item.cls || defaultCls); | |||
itemChecked = parseBool(item.checked); | |||
itemName = item.name || ''; | |||
itemId = item.id || ''; | |||
itemDisabled = parseBool(item.disabled); | |||
} | |||
var wrapper = document.createElement('span'); | |||
wrapper.className = 'js-checkbox-gen-item'; | |||
var input = document.createElement('input'); | |||
input.type = 'checkbox'; | |||
if (itemCls) input.className = itemCls; | |||
if (itemName) input.name = itemName; | |||
if (itemId) input.id = itemId; | |||
if (itemChecked) input.checked = true; | |||
if (itemDisabled) input.disabled = true; | |||
var labelEl = document.createElement('label'); | |||
if (itemId) labelEl.htmlFor = itemId; | |||
labelEl.textContent = labelText; | |||
wrapper.appendChild(input); | |||
wrapper.appendChild(labelEl); | |||
frag.appendChild(wrapper); | |||
} | |||
container.appendChild(frag); | |||
} | |||
} else { | |||
var singleLabel = container.getAttribute('data-cbox-label') || ''; | |||
var singleCls = container.getAttribute('data-cbox-class') || ''; | |||
var singleChecked = parseBool(container.getAttribute('data-cbox-checked')); | |||
var singleName = container.getAttribute('data-cbox-name') || ''; | |||
var singleId = container.getAttribute('data-cbox-id') || ''; | |||
var singleDisabled = parseBool(container.getAttribute('data-cbox-disabled')); | |||
var w = document.createElement('span'); | |||
w.className = 'js-checkbox-gen-item'; | |||
var inp = document.createElement('input'); | |||
inp.type = 'checkbox'; | |||
if (singleCls) inp.className = singleCls; | |||
if (singleName) inp.name = singleName; | |||
if (singleId) inp.id = singleId; | |||
if (singleChecked) inp.checked = true; | |||
if (singleDisabled) inp.disabled = true; | |||
var lab = document.createElement('label'); | |||
if (singleId) lab.htmlFor = singleId; | |||
lab.textContent = singleLabel; | |||
w.appendChild(inp); | |||
w.appendChild(lab); | |||
container.appendChild(w); | |||
} | |||
container.setAttribute('data-checkbox-initialized', '1'); | |||
} | |||
} | |||
// Привязка expand/collapse к чекбоксу по id | |||
function registerCheckboxExpander(checkboxId, options) { | |||
options = options || {}; | |||
// если нужно — использовать только expand (по умолчанию false = делаем и collapse) | |||
var onlyExpand = !!options.onlyExpand; | |||
var checkbox = document.getElementById(checkboxId); | |||
if (!checkbox) { | |||
// Если чекбокс ещё не на странице — попробуем навесить через делегирование (MutationObserver опционально) | |||
console.warn('registerCheckboxExpander: checkbox not found by id:', checkboxId); | |||
return; | |||
} | |||
function expandAll() { | |||
var collapsed = document.querySelectorAll('.mw-collapsed'); | |||
for (var i = 0; i < collapsed.length; i++) { | |||
collapsed[i].classList.remove('mw-collapsed'); | |||
} | |||
var togglesCollapsed = document.querySelectorAll('.mw-collapsible-toggle-collapsed'); | |||
for (var j = 0; j < togglesCollapsed.length; j++) { | |||
togglesCollapsed[j].classList.remove('mw-collapsible-toggle-collapsed'); | |||
togglesCollapsed[j].classList.add('mw-collapsible-toggle-expanded'); | |||
} | |||
var contents = document.querySelectorAll('.mw-collapsible-content'); | |||
for (var k = 0; k < contents.length; k++) { | |||
if (contents[k].style && contents[k].style.display) { | |||
contents[k].style.removeProperty('display'); | |||
} | |||
} | |||
} | |||
function collapseAll() { | |||
var collapsibleContainers = document.querySelectorAll('.mw-collapsible'); | |||
for (var i = 0; i < collapsibleContainers.length; i++) { | |||
var el = collapsibleContainers[i]; | |||
if (!el.classList.contains('mw-collapsed')) el.classList.add('mw-collapsed'); | |||
} | |||
var togglesExpanded = document.querySelectorAll('.mw-collapsible-toggle-expanded'); | |||
for (var j = 0; j < togglesExpanded.length; j++) { | |||
togglesExpanded[j].classList.remove('mw-collapsible-toggle-expanded'); | |||
togglesExpanded[j].classList.add('mw-collapsible-toggle-collapsed'); | |||
} | |||
var contents = document.querySelectorAll('.mw-collapsible-content'); | |||
for (var k = 0; k < contents.length; k++) { | |||
contents[k].style.setProperty('display', 'none', 'important'); | |||
} | |||
} | |||
checkbox.addEventListener('change', function (e) { | |||
if (checkbox.checked) { | |||
try { expandAll(); } catch (err) { console.error(err); } | |||
} else { | |||
if (!onlyExpand) { | |||
try { collapseAll(); } catch (err) { console.error(err); } | |||
} | |||
} | |||
}); | |||
if (checkbox.checked) { | |||
try { expandAll(); } catch (err) { console.error(err); } | |||
} | |||
} | } | ||
const currentPageTitle = document.title; | const currentPageTitle = document.title; | ||
| Строка 837: | Строка 1085: | ||
var layerIndexLoad = document.querySelectorAll('.z-index-position'); | var layerIndexLoad = document.querySelectorAll('.z-index-position'); | ||
if (layerIndexLoad.length > 0) { | if (layerIndexLoad.length > 0) { | ||
layerIndex(); | |||
} | } | ||
if (document.querySelectorAll('.ajax-load-content').length > 0) { | if (document.querySelectorAll('.ajax-load-content').length > 0) { | ||
initAjaxLoader(); | |||
} | } | ||
var checkboxExist = document.querySelectorAll('.js-checkbox-generator'); | |||
if (checkboxExist.length > 0) { | |||
initCheckboxCreator(); | |||
} | |||
registerCheckboxExpander('js-checkbox-mw-collapsible'); | |||
}); | }); | ||
/*WikiEditor/Викификатор*/ | /*WikiEditor/Викификатор*/ | ||
| Строка 894: | Строка 1147: | ||
/* Добавляет кнопку «Вверх» слева */ | /* Добавляет кнопку «Вверх» слева */ | ||
function scrollTop(){ | function scrollTop(){ | ||
$(window).scroll(function(e) { | |||
if($(window).scrollTop()>0) { | |||
$("#scroll-top").fadeIn(300); | |||
} else{ | |||
$("#scroll-top").fadeOut(300); | |||
} | |||
}); | |||
} | } | ||
| Строка 1327: | Строка 1580: | ||
(function($, mw) { | (function($, mw) { | ||
mw.loader.using('ext.gadget.theme').then(function() { | |||
const { loadTheme, initThemeMenu } = mw.themeUtils; | |||
function createSettingsDropdown(currentTheme) { | |||
const $dropdown = $('<div>', { class: 'theme-dropdown' }); | |||
const $details = $('<details>', { id: 'theme-preferences-details', class: 'theme-dropdown-details' }); | |||
const $summary = $('<summary>', { class: 'theme-dropdown-summary', 'data-tooltip-initialized': 'true' }) | |||
.append($('<span>', { class: 'theme-icon theme-icon-settings' })); | |||
$details.append($summary); | |||
const $window = $('<div>', { id: 'theme-preferences', class: 'theme-window' }).hide(); | |||
const $header = $('<div>', { | |||
id: 'theme-preferences__header', | |||
class: 'theme-window__header', | |||
text: 'Параметры' | |||
}); | |||
$window.append($header); | |||
const $content = $('<div>', { | |||
id: 'theme-preferences__content', | |||
class: 'theme-window__content' | |||
}); | |||
$window.append($content); | |||
initThemeMenu($content, currentTheme); | |||
$dropdown.append($details, $window); | |||
const $container = $('#user-tools').length ? $('#user-tools') : $('.minerva-search-form'); | |||
$container.append($dropdown); | |||
const $cover = $('#menus-cover'); | |||
$details.on('toggle', function() { | |||
const isOpen = $(this).prop('open'); | |||
$window.toggle(isOpen); | |||
if ($cover.length) { | |||
$cover.css('display', isOpen ? 'block' : 'none'); | |||
} | |||
}); | |||
$(document).on('click', function(e) { | |||
const isClickInside = $(e.target).closest('.theme-window, .theme-dropdown-summary').length > 0; | |||
if (!isClickInside && $details.prop('open')) { | |||
$details.removeAttr('open'); | |||
if ($cover.length) { | |||
$cover.css('display', 'none'); | |||
} | |||
} | |||
}); | |||
} | |||
function init() { | |||
const theme = loadTheme(); | |||
createSettingsDropdown(theme); | |||
} | |||
$(function() { | |||
setTimeout(init, 100); | |||
}); | |||
}); | |||
}(jQuery, mediaWiki)); | }(jQuery, mediaWiki)); | ||