مدیاویکی:Gadget-QuranTab.js
ظاهر
نکته: پس از انتشار ممکن است برای دیدن تغییرات نیاز باشد که حافظهٔ نهانی مرورگر خود را پاک کنید.
- فایرفاکس / سافاری: کلید Shift را نگه دارید و روی دکمهٔ Reload کلیک کنید، یا کلیدهای Ctrl-F5 یا Ctrl-R را با هم فشار دهید (در رایانههای اپل مکینتاش کلیدهای ⌘-R)
- گوگل کروم: کلیدهای Ctrl+Shift+R را با هم فشار دهید (در رایانههای اپل مکینتاش کلیدهای ⌘-Shift-R)
- Edge: کلید Ctrl را نگهدارید و روی دکمهٔ Refresh کلیک کنید، یا کلیدهای Ctrl-F5 را با هم فشار دهید
(function (mw, $) {
'use strict';
// =================================================================================
// == بخش اول: کد مربوط به نمایش تبها و مدیریت ترجمه (اسکریپت شماره ۱)
// =================================================================================
var script1Initialized = false; // برای جلوگیری از اجرای چندباره منطق اصلی
/**
* متغیرها و توابع مربوط به اسکریپت اول
*/
var translatorVersions = null;
var CACHE_PREFIX = 'quran-translation-';
var MAX_CACHE_ITEMS = 1000; // محدودیت تعداد آیتمهای کش شده
function cleanupCache() {
try {
var keys = Object.keys(localStorage);
var cacheItems = [];
for (var i = 0; i < keys.length; i++) {
if (keys[i].startsWith(CACHE_PREFIX)) {
var item = JSON.parse(localStorage.getItem(keys[i]));
if (item && item.timestamp) {
cacheItems.push({
key: keys[i],
timestamp: item.timestamp
});
} else {
localStorage.removeItem(keys[i]);
}
}
}
if (cacheItems.length > MAX_CACHE_ITEMS) {
cacheItems.sort(function (a, b) {
return a.timestamp - b.timestamp;
});
var itemsToDelete = cacheItems.length - MAX_CACHE_ITEMS;
for (var j = 0; j < itemsToDelete; j++) {
localStorage.removeItem(cacheItems[j].key);
}
}
} catch (e) {
console.error('QuranGadget: Error during cache cleanup.', e);
}
}
function getTranslation(sura, ayah, translator, callback) {
if (!translatorVersions) {
console.error('Translator version data is not available. Falling back to API.');
fetchTranslationFromApi(sura, ayah, translator, callback);
return;
}
var currentVersion = translatorVersions[translator];
var storageKey = CACHE_PREFIX + sura + '-' + ayah + '-' + translator;
try {
var cachedItem = localStorage.getItem(storageKey);
if (cachedItem) {
var cachedData = JSON.parse(cachedItem);
if (cachedData && cachedData.version === currentVersion) {
cachedData.timestamp = Date.now();
localStorage.setItem(storageKey, JSON.stringify(cachedData));
callback(cachedData.html);
return;
}
}
} catch (e) {
console.warn('Could not read from localStorage:', e);
}
fetchTranslationFromApi(sura, ayah, translator, function (htmlResult) {
if (htmlResult && !htmlResult.includes('class="error"')) {
cleanupCache();
try {
var dataToStore = {
version: currentVersion,
html: htmlResult,
timestamp: Date.now()
};
localStorage.setItem(storageKey, JSON.stringify(dataToStore));
} catch (e) {
console.warn('Could not save translation to localStorage.', e);
}
}
callback(htmlResult);
});
}
function fetchTranslationFromApi(sura, ayah, translator, callback) {
var wikitextToParse = '{{مترجم|translator=' + translator + '|سوره=' + sura.trim() + '|آیه=' + ayah.trim() + '}}';
var blockSelector = '.translation-block[data-sura="' + sura + '"][data-ayah="' + ayah + '"]';
var block = document.querySelector(blockSelector);
if (block) {
block.innerHTML = '<span class="loading-translation" style="font-style:italic; color:#777;">در حال بارگذاری...</span>';
}
new mw.Api().parse(wikitextToParse, {
uselang: mw.config.get('wgUserLanguage')
})
.done(function (htmlResultString) {
callback(htmlResultString);
})
.fail(function (errorCode, errorInfo) {
console.error('API error:', errorCode, errorInfo);
callback('<span class="error-translation" style="color:red;">خطا در بارگذاری ترجمه.</span>');
});
}
function initializeQuranTabView(viewElement) {
const instanceId = viewElement.getAttribute('data-instance-id-quran');
if (!instanceId || viewElement.dataset.quranTabInitialized === 'true') {
return;
}
viewElement.dataset.quranTabInitialized = 'true';
const tabHeaders = Array.from(viewElement.querySelectorAll('.quran-tab-header .quran-tab'));
const contentPanesWrapper = viewElement.querySelector('.quran-tab-wrapper');
const increaseButton = viewElement.querySelector(`#quran-increase-font-${instanceId}`);
const decreaseButton = viewElement.querySelector(`#quran-decrease-font-${instanceId}`);
const resetButton = viewElement.querySelector(`#quran-reset-font-${instanceId}`);
const MIN_FONT_SIZE = 10,
DEFAULT_FONT_SIZE = 16,
MAX_FONT_SIZE = 32;
const RESIZABLE_TEXT_CLASS = 'quran-resizable-text-content';
function setActiveTab(clickedTabHeader) {
if (!clickedTabHeader) return;
tabHeaders.forEach(th => th.classList.remove('active'));
if (contentPanesWrapper) {
Array.from(contentPanesWrapper.querySelectorAll('.quran-tab-content')).forEach(cp => {
cp.classList.remove('active');
cp.classList.add('quran-tab-hidden');
});
}
clickedTabHeader.classList.add('active');
const targetContentId = clickedTabHeader.getAttribute('data-tab-target-id');
if (targetContentId) {
const targetContentPane = viewElement.querySelector(`#${targetContentId}`);
if (targetContentPane) {
targetContentPane.classList.add('active');
targetContentPane.classList.remove('quran-tab-hidden');
initializeFontSizeForActiveTab();
}
}
}
tabHeaders.forEach(tabHeader => {
const tabEventHandler = function (event) {
if (event.type === 'click' || (event.type === 'keydown' && (event.key === 'Enter' || event.key === ' '))) {
if (event.type === 'keydown') event.preventDefault();
setActiveTab(this);
const newTabUrlKey = this.getAttribute('data-tab-url-key');
if (newTabUrlKey && window.history && window.history.pushState) {
try {
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set('ttq_tab', newTabUrlKey);
let newUrlString = currentUrl.toString().replace(/%7C$|\|$/, '');
window.history.pushState({
tabId: newTabUrlKey,
instanceId
}, '', newUrlString);
} catch (e) {
console.error('Error updating URL for Quran tabs:', e);
}
}
}
};
tabHeader.addEventListener('click', tabEventHandler);
tabHeader.addEventListener('keydown', tabEventHandler);
});
const getActiveResizableElements = () => {
const activeContentPane = viewElement.querySelector('.quran-tab-content.active');
return activeContentPane ? activeContentPane.querySelectorAll(`.${RESIZABLE_TEXT_CLASS}, .quran-resizable-text`) : [];
};
const applyFontSizeToElements = (elements, size) => {
elements.forEach(el => {
el.style.fontSize = `${size}px`;
});
};
const setDefaultFontSize = () => {
applyFontSizeToElements(getActiveResizableElements(), DEFAULT_FONT_SIZE);
try {
localStorage.setItem(`font-size-${instanceId}`, `${DEFAULT_FONT_SIZE}px`);
} catch (e) { /* ignore */ }
};
const adjustFontSize = (delta) => {
const elements = getActiveResizableElements();
if (elements.length === 0) return;
let currentSize = NaN;
try {
currentSize = parseFloat(localStorage.getItem(`font-size-${instanceId}`));
} catch (e) { /* ignore */ }
if (isNaN(currentSize)) {
let initialSizeStr = '';
if (elements[0] && elements[0].style.fontSize) {
initialSizeStr = elements[0].style.fontSize;
} else if (elements[0] && elements[0].offsetParent !== null) {
initialSizeStr = window.getComputedStyle(elements[0]).fontSize;
}
currentSize = parseFloat(initialSizeStr) || DEFAULT_FONT_SIZE;
}
let newSize = currentSize + delta;
newSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, newSize));
applyFontSizeToElements(elements, newSize);
try {
localStorage.setItem(`font-size-${instanceId}`, `${newSize}px`);
} catch (e) { /* ignore */ }
};
const initializeFontSizeForActiveTab = () => {
const elements = getActiveResizableElements();
if (elements.length === 0) return;
let storedSizePx;
try {
storedSizePx = localStorage.getItem(`font-size-${instanceId}`);
} catch (e) { /* ignore */ }
const sizeToApply = storedSizePx ? parseFloat(storedSizePx) : DEFAULT_FONT_SIZE;
applyFontSizeToElements(elements, sizeToApply);
if (!storedSizePx) {
try {
localStorage.setItem(`font-size-${instanceId}`, `${DEFAULT_FONT_SIZE}px`);
} catch (e) { /* ignore */ }
}
};
if (increaseButton) increaseButton.addEventListener('click', () => adjustFontSize(1));
if (decreaseButton) decreaseButton.addEventListener('click', () => adjustFontSize(-1));
if (resetButton) resetButton.addEventListener('click', setDefaultFontSize);
if (viewElement.querySelector('.quran-tab-content.active')) {
initializeFontSizeForActiveTab();
}
viewElement.quranTabView = {
initializeFontSizeForActiveTab: initializeFontSizeForActiveTab
};
}
function handleTranslatorSelection(event) {
const clickedSpan = event.target.closest('span.translator-button');
if (!clickedSpan) return;
const linkElement = clickedSpan.closest('a');
if (!linkElement || !linkElement.href) return;
event.preventDefault();
let targetUrl;
try {
targetUrl = new URL(linkElement.href);
} catch (e) {
return;
}
const newTranslator = targetUrl.searchParams.get('1');
if (!newTranslator) return;
if (window.history && window.history.pushState) {
let newUrlString = targetUrl.toString().replace(/%7C$|\|$/, '');
window.history.pushState({
translator: newTranslator
}, '', newUrlString);
} else {
window.location.href = targetUrl.toString().replace(/%7C$|\|$/, '');
return;
}
document.querySelectorAll('div.translator-list span.translator-button.active').forEach(el => el.classList.remove('active'));
clickedSpan.classList.add('active');
const activeTripleTabView = clickedSpan.closest('.quran-TripleTabView') || document;
activeTripleTabView.querySelectorAll('.quran-tab-content.active .translation-block').forEach(block => {
const sura = block.dataset.sura;
const ayah = block.dataset.ayah;
if (sura && ayah) {
getTranslation(sura, ayah, newTranslator, function (html) {
if (block.dataset.sura === sura && block.dataset.ayah === ayah) {
block.innerHTML = html;
const viewElement = block.closest('.quran-TripleTabView');
if (viewElement && viewElement.quranTabView) {
viewElement.quranTabView.initializeFontSizeForActiveTab();
}
}
});
}
});
}
/**
* تابع اصلی برای مقداردهی اولیه اسکریپت اول
*/
function initQuranTabViewFeatures() {
if (mw.config.get('wgAction') !== 'view') return;
var allowedCategoryNames = ['صفحات قرآنی'];
// تابع کمکی برای چک فعال بودن گجت بر اساس دستهها
function checkAndActivate(pageCategories) {
var isGadgetActivated = pageCategories.some(function (category) {
return allowedCategoryNames.indexOf(category) > -1;
});
if (!isGadgetActivated) return;
if (script1Initialized) return; // اگر قبلا اجرا شده، خارج شو
script1Initialized = true;
var translatorListElement = document.querySelector('.translator-list[data-translator-versions]');
if (translatorListElement && translatorListElement.dataset.translatorVersions) {
try {
translatorVersions = JSON.parse(translatorListElement.dataset.translatorVersions);
} catch (e) {
console.error('QuranGadget: Could not parse translator versions.', e);
}
}
document.querySelectorAll('.quran-TripleTabView').forEach(initializeQuranTabView);
if (!document.body.dataset.translatorClickHandlerAttachedQuranTab) {
$(document.body).on('click', 'span.translator-button', handleTranslatorSelection);
document.body.dataset.translatorClickHandlerAttachedQuranTab = 'true';
}
}
// چک پوسته فعلی
var skin = mw.config.get('skin');
if (skin === 'minerva') {
// در مینروا، از API برای گرفتن دستهها استفاده کن
var pageName = mw.config.get('wgPageName');
new mw.Api().get({
action: 'query',
prop: 'categories',
titles: pageName,
cllimit: 'max' // همه دستهها را بگیر
}).done(function (data) {
var pageCategories = [];
var pages = data.query.pages;
for (var pageId in pages) {
if (pages[pageId].categories) {
pages[pageId].categories.forEach(function (cat) {
pageCategories.push(cat.title.replace(/^رده:/, '')); // حذف پیشوند "رده:"
});
}
}
checkAndActivate(pageCategories);
}).fail(function (error) {
console.error('QuranGadget: Error fetching categories via API in Minerva.', error);
});
} else {
// در پوستههای دیگر (مثل وکتور)، از wgCategories استفاده کن
var pageCategories = mw.config.get('wgCategories') || [];
checkAndActivate(pageCategories);
}
}
// =================================================================================
// == بخش دوم: کد مربوط به کنترلهای متن قرآن (اسکریپت شماره ۲)
// =================================================================================
const QuranTextTranslate = {
setupQuranEventListeners(container) {
if (container.dataset.quranTextTranslateInitialized === 'true') {
return;
}
container.dataset.quranTextTranslateInitialized = 'true';
const buttons = {
smaller: container.querySelector(".quran-font-smaller"),
larger: container.querySelector(".quran-font-larger"),
nightMode: container.querySelector(".quran-toggle-night-mode"),
};
if (buttons.smaller) {
buttons.smaller.addEventListener("click", () => {
container.classList.remove("quran-font-larger");
container.classList.add("quran-font-smaller");
});
}
if (buttons.larger) {
buttons.larger.addEventListener("click", () => {
container.classList.remove("quran-font-smaller");
container.classList.add("quran-font-larger");
});
}
if (buttons.nightMode) {
buttons.nightMode.addEventListener("click", () => {
container.classList.toggle("quran-night-mode");
});
}
},
initializeForContainer(containerElement) {
if (containerElement) {
this.setupQuranEventListeners(containerElement);
}
},
initAllInstances() {
document.querySelectorAll(".quran-table-container").forEach(container => {
this.initializeForContainer(container);
});
}
};
// =================================================================================
// == بخش مقداردهی اولیه کلی (تلفیق هر دو اسکریپت)
// =================================================================================
// اجرای اولیه پس از بارگذاری کامل صفحه
$(function () {
initQuranTabViewFeatures();
QuranTextTranslate.initAllInstances();
});
// برای محتوای بارگذاری شده به صورت پویا (مانند VisualEditor یا AJAX)
mw.hook('wikipage.content').add(function ($contentNode) {
// اجرای منطق اسکریپت اول برای محتوای جدید
if ($contentNode.find('.quran-TripleTabView').length > 0 || $contentNode.find('.translator-list').length > 0) {
script1Initialized = false; // اجازه اجرای مجدد برای محتوای جدید
initQuranTabViewFeatures();
}
// اجرای منطق اسکریپت دوم برای محتوای جدید
$contentNode.find(".quran-table-container").each(function () {
QuranTextTranslate.initializeForContainer(this);
});
});
})(mediaWiki, jQuery);