MediaWiki:Minerva.js

Not: Yayımladıktan sonra değişiklikleri görmeniz için tarayıcınızın önbelleğini temizlemeniz gerekebilir.

  • Firefox / Safari: Shift tuşuna basılıyken Yeniden Yükle'ye tıklayın ya da Ctrl-F5 ya da Ctrl-R tıklayın (Mac için ⌘-R)
  • Google Chrome: Ctrl-Shift-R'ye basın. (Mac için ⌘-Shift-R)
  • Edge: Ctrl basılıyken Yenile'ye tıklayın ya da Ctrl-F5'e basın.
/* ============================================================
   Minerva.js BİRLEŞİK BETİK (v3)
   1) Timeless'ın "Gezinti" (site-navigation) içeriği sayfaya
      GÖRÜNMEZ şekilde yerleştirilir (sayfa altı gizli alan).
      - Çekmeceye/hamburger menüye HİÇBİR ŞEY eklenmez.
      - Başlıklı alt portletler (h3 + link listeleri) KORUNUR,
        böylece 📚 popup'ta linklerin başlıkları da görünür.
      - İçerik sayfanın Timeless görünümünden bir kez çekilir,
        sessionStorage'da tutulur.
   2) FENPEDIA SAF POPUP MODAL SİSTEMİ (yüzen 📚/📋 butonlar):
      Timeless.js ile aynı mantık — Minerva'da da çalışır.
      📚 Ünite Menüsü (yalnızca Gezinti portletleri),
      📋 İçindekiler.
   ============================================================ */
( function () {
	'use strict';

	/* ================= BÖLÜM 1: Yüzen 📚/📋 butonlar (jQuery) ================= */

	// Ders menüsünü gizli "Gezinti" kaynağından çıkarır (TEK tanım, üst kapsam)
	function extractLessonMenu() {
		var $ = window.jQuery;
		if ( !$ ) { return false; }

		// Anasayfa'da ders/ünite menüsü gösterilmez (orijinal kural)
		var mw = window.mw;
		if ( mw && mw.config && mw.config.get( 'wgPageName' ) === 'Anasayfa' ) { return false; }

		var $lessonMenuSource = null;
		var useHidden = false;

		// ÖNCELİK 1: Sayfa altına gizlice yerleştirilen Gezinti kutusu.
		// İçinde EN AZ BİR bağlantı varsa gösterilir (ünite adından bağımsız —
		// CategorySidebar/DynamicSidebar uygulanan HER sayfada çalışır).
		var $hidden = $( '#fn-minerva-sidebar-source .sidebar-chunk' );
		if ( $hidden.length && $hidden.find( 'a' ).length ) {
			$lessonMenuSource = $hidden;
			useHidden = true;
		}

		// Yedek: gizli kaynak yoksa genel tarama (Timeless benzeri).
		// Çekmecenin (#mw-mf-page-left) kendisi veya içindeki öğeler ASLA
		// kaynak seçilmez — aksi halde popup'a tüm hamburger menüsü dolar.
		if ( !$lessonMenuSource ) {
			$( '.sidebar-chunk, #mw-panel, .mw-sidebar-inner, nav, .custom-sidebar' ).each( function () {
				var $this = $( this );
				if ( $this.closest( '#mw-mf-page-left' ).length ) { return true; } // çekmece içini atla
				var textContent = $this.text();

				if ( textContent && ( /Konu Anlatim/i.test( textContent ) || ( /Güneş/i.test( textContent ) && /Dünya/i.test( textContent ) && /Ay/i.test( textContent ) ) ) ) {
					$lessonMenuSource = $this;
					return false; // İlk doğru paneli bulduğunda aramayı durdur
				}
			} );
		}

		if ( $lessonMenuSource && $lessonMenuSource.length > 0 ) {
			var $compiledContent = $( '<div></div>' );
			var seenHeaders = {}; // Eklenen başlıkların metinlerini tutacak nesne

			$lessonMenuSource.find( 'h2, h3, ul' ).each( function () {
				var $element = $( this ).clone();
				$element.removeAttr( 'class' ).removeAttr( 'id' );

				// Eğer element bir başlıksa (h2 veya h3)
				if ( $element.is( 'h2, h3' ) ) {
					var headerText = $.trim( $element.text() ).toUpperCase();

					// "Gezinti" gibi gezinme etiketi başlıkları popup'ta gösterilmez
					if ( useHidden && ( headerText === 'GEZINTI' || headerText === 'GEZİNTİ' ) ) {
						return true; // continue anlamında
					}

					// Bu başlık metni daha önce eklendiyse pas geç (Çift başlığı engeller)
					if ( seenHeaders[ headerText ] ) {
						return true; // continue anlamında
					}
					seenHeaders[ headerText ] = true; // İlk kez görüldüyse listeye kaydet
				}

				$element.find( 'li, a' ).removeAttr( 'class' ).removeAttr( 'id' ); // İç stilleri temizle
				$compiledContent.append( $element );
			} );

			if ( $compiledContent.children().length > 0 ) {
				$( '.id-lesson-content .fn-modal-body' ).html( $compiledContent.html() );
				$( '.btn-lesson' ).addClass( 'btn-visible' );
				return true;
			}
		}
		return false;
	}

	// Ağır tarama başarısız olursa sidebar'ı SADECE KISA SÜRE izle (TBT düzeltmesi)
	function observeForLessonMenu() {
		var mw = window.mw;
		// Anasayfa ise süreci anında bitirir
		if ( mw && mw.config && mw.config.get( 'wgPageName' ) === 'Anasayfa' ) { return; }

		var observer = new MutationObserver( function ( mutations, obs ) {
			if ( extractLessonMenu() ) {
				obs.disconnect();
			}
		} );
		observer.observe( document.body, { childList: true, subtree: true } );

		// Menü bulunamazsa 3 saniye sonra gözlemciyi KESİN OLARAK kapat.
		setTimeout( function () {
			observer.disconnect();
		}, 3000 );
	}

	// Ağır sidebar taramasını idle anına ertele (requestIdleCallback)
	function scheduleLessonMenuExtraction() {
		if ( window.requestIdleCallback ) {
			window.requestIdleCallback( function () {
				if ( !extractLessonMenu() ) {
					observeForLessonMenu();
				}
			}, { timeout: 2000 } );
		} else {
			// Eski tarayıcılar için fallback: load + 500ms gecikme
			window.addEventListener( 'load', function () {
				setTimeout( function () {
					if ( !extractLessonMenu() ) {
						observeForLessonMenu();
					}
				}, 500 );
			} );
		}
	}

	// Yüzen buton grubu + modal pencereleri kur (tek sefer)
	function initFloatingUI() {
		var $ = window.jQuery;
		if ( !$ ) { return; }

		// Sağ alttaki buton grubunu ve gizli pencereleri sayfaya bas (tek sefer)
		if ( $( '.fn-floating-btn-group' ).length === 0 ) {
			$( 'body' ).append(
				'<div class="fn-floating-btn-group">' +
				'  <button class="fn-floating-btn btn-lesson" type="button" title="Ünite Menüsü">📚</button>' +
				'  <button class="fn-floating-btn btn-toc" type="button" title="İçindekiler">📋</button>' +
				'</div>' +
				'<div class="fn-modal-overlay">' +
				'  <div class="fn-modal-content id-lesson-content"><div class="fn-modal-body"></div></div>' +
				'  <div class="fn-modal-content id-toc-content"><h2>İÇİNDEKİLER</h2><div class="fn-modal-body"></div></div>' +
				'</div>'
			);
		}

		// 📋 (mavi) buton kontrolü: TOC içeriğini çeker
		var hasTOC = $( '#toc' ).length || $( '.toc' ).length || $( '.mw-toc' ).length;
		if ( hasTOC ) {
			var $cleanTOC = $( '#toc ul, .toc ul, .mw-toc ul' ).first().clone();
			$( '.id-toc-content .fn-modal-body' ).html( $cleanTOC );
			$( '.btn-toc' ).addClass( 'btn-visible' );

			// Orijinal TOC elemanını boş kabuk bırakmamak için taşı (hedef yoksa no-op)
			$( '#toc, .toc, .mw-toc' ).appendTo( '.fn-toc-wrapper' );
		}

		// 🟢 Yeşil Buton (📚) Tıklama
		$( document ).on( 'click', '.btn-lesson', function ( e ) {
			e.stopPropagation();
			$( '.fn-modal-overlay' ).fadeIn( 200 );
			$( '.fn-modal-content' ).removeClass( 'modal-active' );
			$( '.id-lesson-content' ).addClass( 'modal-active' );
		} );

		// 🔵 Mavi Buton (📋) Tıklama
		$( document ).on( 'click', '.btn-toc', function ( e ) {
			e.stopPropagation();
			$( '.fn-modal-overlay' ).fadeIn( 200 );
			$( '.fn-modal-content' ).removeClass( 'modal-active' );
			$( '.id-toc-content' ).addClass( 'modal-active' );
		} );

		function closeAllModals() {
			$( '.fn-modal-overlay' ).fadeOut( 200 );
			$( '.fn-modal-content' ).removeClass( 'modal-active' );
		}

		$( document ).on( 'click', '.fn-modal-overlay, .fn-modal-body a', function () {
			closeAllModals();
		} );

		// MediaWiki'nin tıklama engelini baypas eder
		$( window ).on( 'hashchange', function () {
			closeAllModals();
		} );

		$( '.fn-modal-content' ).on( 'click', function ( e ) {
			e.stopPropagation();
		} );

		// Ağır sidebar taramasını idle anına ertele
		scheduleLessonMenuExtraction();
	}

	/* ========= BÖLÜM 2: Gizli "Gezinti" kaynağını sayfaya yerleştir ========= */

	function esc( s ) {
		return String( s ).replace( /&/g, '&amp;' ).replace( /</g, '&lt;' )
			.replace( />/g, '&gt;' ).replace( /"/g, '&quot;' );
	}

	function timelessUrl() {
		var u;
		try {
			u = new URL( window.location.href );
		} catch ( e ) {
			u = new URL( window.location.pathname, window.location.origin );
		}
		u.searchParams.set( 'useskin', 'timeless' );
		u.searchParams.delete( 'action' );
		return u.href;
	}

	function cacheKey() {
		var p = window.location.pathname.replace( /\/+$/, '' );
		return 'fn-minerva-sidebar:' + p;
	}

	// Timeless'ın "Gezinti" (site-navigation) bloğunu alır; başlıklı alt
	// portlet yapısını (h3 + ul) korur. Dış "Gezinti" etiketi ve boş
	// portletler atılır; id/class temizlenir.
	function extractSidebar( doc ) {
		var chunks = doc.querySelectorAll( '.sidebar-chunk' );
		var pick = null;

		chunks.forEach( function ( ch ) {
			if ( !pick && ch.id === 'site-navigation' ) { pick = ch; }
		} );
		if ( !pick ) {
			chunks.forEach( function ( ch ) {
				if ( !pick ) {
					var h = ch.querySelector( 'h2, h3' );
					var t = h ? h.textContent.trim().toLowerCase() : '';
					if ( t === 'gezinti' ) { pick = ch; }
				}
			} );
		}
		if ( !pick ) { return ''; }

		var clone = pick.cloneNode( true );

		// MediaWiki:Sidebar'da STATİK tanımlı portlet başlıkları (CategorySidebar
		// üretimi DEĞİLDİR; gizli kaynaktan atılır).
		var STATIC_PORTLETS = { 'SINIFINI SEÇ': true };

		// Boş portletleri ve statik portletleri at (ör: boş p-navigation)
		clone.querySelectorAll( '.mw-portlet' ).forEach( function ( pt ) {
			var h = pt.querySelector( 'h3' );
			var t = h ? ( h.textContent || '' ).trim().toUpperCase() : '';
			var ul = pt.querySelector( 'ul' );
			var empty = !ul || !ul.querySelector( 'a' );
			if ( empty || STATIC_PORTLETS[ t ] ) {
				if ( pt.parentNode ) { pt.parentNode.removeChild( pt ); }
			}
		} );

		// Dış "Gezinti" (h2) etiketini kaldır — yalnız iç portletler kalsın
		var outer = clone.querySelector( ':scope > h2' ) || clone.querySelector( 'h2' );
		if ( outer && outer.parentNode ) { outer.parentNode.removeChild( outer ); }

		// id/class temizliği (çakışma olmasın)
		clone.querySelectorAll( '[id]' ).forEach( function ( el ) { el.removeAttribute( 'id' ); } );
		clone.querySelectorAll( '[class]' ).forEach( function ( el ) { el.removeAttribute( 'class' ); } );

		// Kalan içerikte bağlantı yoksa boş dön
		if ( !clone.querySelector( 'a' ) ) { return ''; }

		return clone.innerHTML;
	}

	function sourceWrap() {
		return document.getElementById( 'fn-minerva-sidebar-source' );
	}

	function ensureSource() {
		if ( !sourceWrap() && document.body ) {
			var div = document.createElement( 'div' );
			div.id = 'fn-minerva-sidebar-source';
			div.hidden = true;
			div.setAttribute( 'data-ready', '0' );
			document.body.appendChild( div );
		}
	}

	function applyHiddenSidebar( html ) {
		ensureSource();
		var wrap = sourceWrap();
		if ( !wrap ) { return; }
		wrap.innerHTML = '<div class="sidebar-chunk">' + html + '</div>';
		wrap.setAttribute( 'data-ready', '1' );
		// Gizli kaynak hazır olduktan sonra 📚 çıkarımını dene
		try { extractLessonMenu(); } catch ( e ) {}
	}

	function loadSidebar() {
		ensureSource();
		var wrap = sourceWrap();
		if ( !wrap ) { return; }
		if ( wrap.getAttribute( 'data-ready' ) === '1' ) { try { extractLessonMenu(); } catch ( e ) {} return; }

		var cached = null;
		try { cached = sessionStorage.getItem( cacheKey() ); } catch ( e ) {}
		if ( cached ) {
			applyHiddenSidebar( cached );
			return;
		}

		fetch( timelessUrl(), { credentials: 'same-origin' } )
			.then( function ( r ) { return r.text(); } )
			.then( function ( html ) {
				var doc = new DOMParser().parseFromString( html, 'text/html' );
				var out = extractSidebar( doc );
				if ( !out ) { return; }
				try { sessionStorage.setItem( cacheKey(), out ); } catch ( e ) {}
				applyHiddenSidebar( out );
			} )
			.catch( function () { /* sessiz geç */ } );
	}

	/* ================= BAŞLAT ================= */
	function boot() {
		if ( window.jQuery ) {
			initFloatingUI();
		} else {
			// jQuery henüz yoksa DOMContentLoaded'de tekrar dene
			document.addEventListener( 'DOMContentLoaded', initFloatingUI, { once: true } );
		}
		loadSidebar(); // Gezinti'yi önden, görünmez şekilde yükle
	}

	if ( document.readyState === 'loading' ) {
		document.addEventListener( 'DOMContentLoaded', boot );
	} else {
		boot();
	}
}() );