MediaWiki: Tweeki.js: Perbedaan antara revisi

Dari GBI Danau Bogor Raya
Lompat ke: navigasi, cari
Leo (bicara | kontrib)
kTidak ada ringkasan suntingan
Leo (bicara | kontrib)
Tweeki.js: ganti penanda bagian aktif di daftar isi (ScrollSpy Bootstrap menandai judul berikutnya bila bagian pendek); judul yang diklik tetap aktif.
 
(11 revisi perantara oleh pengguna yang sama tidak ditampilkan)
Baris 1: Baris 1:
/* Any JavaScript here will be loaded for users using the Tweeki skin */
/* Any JavaScript here will be loaded for users using the Tweeki skin */
/* Setiap blok dijaga: kalau elemen/library yang dicari tidak ada di halaman ini,
  blok itu dilewati. Dulu satu error menghentikan seluruh file. */


// Bootstrap 5: Tweeki tidak membuka global `bootstrap`, pakai jembatan jQuery-nya.
// Plugin jQuery-nya bisa belum terdaftar (urutan muat), jadi dicek dulu.
$(function () {
$(function () {
   $('[data-toggle="tooltip"]').tooltip()
   if ($.fn.tooltip) {
})
    $('[data-toggle="tooltip"]').tooltip();
  }
  if ($.fn.alert) {
    $('.alert').alert();
  }
  if ($.fn.popover) {
    $('[data-bs-toggle="popover"]').popover();
  }
});
 
// Header tabel #table-1 menempel saat digulir (butuh #header-fixed di halaman)
(function () {
  var $table = $('#table-1');
  var $fixedHeader = $('#header-fixed');
  if (!$table.length || !$fixedHeader.length) {
    return;
  }
  var tableOffset = $table.offset().top;
  $fixedHeader.append($table.children('thead').clone());
 
  $(window).on('scroll', function () {
    var offset = $(this).scrollTop();
 
    if (offset >= tableOffset && $fixedHeader.is(':hidden')) {
      $fixedHeader.show();
    } else if (offset < tableOffset) {
      $fixedHeader.hide();
    }
  });
}());
 
// Pindahkan kategori ke sidebar kanan
(function () {
  var one = document.querySelector('#sidebar-right');
  var catlinks = document.querySelector('.catlinks');
  if (!one || !catlinks) {
    return;
  }
  // salin ke array dulu: .children ikut berubah saat elemen dipindah (dulu yang terpindah hanya selang-seling)
  Array.prototype.slice.call(catlinks.children).forEach(function (child) {
    one.appendChild(child);
  });
}());
 
 
// MASONRY: .grid > .grid-item. Library dimuat async dari CDN (hook BeforePageDisplay di
// LocalSettings), jadi bisa belum tersedia saat file ini jalan -> coba lagi saat window load.
// Hanya .grid yang benar-benar berisi .grid-item: badan accordion di Templat:BootstrapAccordion2Collapse
// juga berkelas .grid tanpa item, dan Masonry membuatnya setinggi 0 sehingga isinya menimpa bagian bawahnya.
var msnrys = [];
 
function initMasonry() {
  if (msnrys.length || typeof Masonry === 'undefined') {
    return;
  }
  Array.prototype.forEach.call(document.querySelectorAll('.grid'), function (grid) {
    if (grid.querySelector(':scope > .grid-item')) {
      msnrys.push(new Masonry(grid, {
        itemSelector: '.grid-item',
        percentPosition: true
      }));
    }
  });
}
initMasonry();
window.addEventListener('load', initMasonry);


var tableOffset = $("#table-1").offset().top;
// Daftar isi (ScrollSpy): judul berawalan "#" (mis. "#1 Kanak-kanak") atau berisi "?" menghasilkan
var $header = $("#table-1 > thead").clone();
// href seperti "##1_Kanak-kanak" yang bukan selector CSS sah -> ScrollSpy Bootstrap error dan
var $fixedHeader = $("#header-fixed").append($header);
// berhenti, sehingga sub-daftar H3 tidak pernah terbuka. Escape href-nya, lalu tangani kliknya
// sendiri (href yang di-escape tidak lagi cocok dengan id heading).
//
// Penanda bagian aktif juga tidak lagi memakai ScrollSpy: ScrollSpy hanya mengamati judul (bukan
// seluruh bagian) dengan rootMargin "70px 0px -30%", jadi bila bagian yang diklik pendek, judul
// berikutnya ikut terlihat dan dianggap aktif, dan saat judul keluar layar penandanya hilang.
// Gantinya: bagian aktif = judul terakhir yang sudah melewati garis LINE di bawah navbar; judul yang
// baru diklik tetap aktif selama masih terlihat (judul di ujung halaman tidak bisa naik ke atas).
$(function () {
  var LINE = 120;
  var $links;
  var items = [];
  var current = null;
  var clicked = null;
  var clickedAt = 0;
  var ticking = false;


$(window).bind("scroll", function() {
  function activate(link) {
  var offset = $(this).scrollTop();
    if (link === current) {
      return;
    }
    current = link;
    $('#toc a.active').removeClass('active');
    if (link) {
      // tautan induk ikut aktif supaya sub-daftarnya (.active + ul) tetap terbuka
      $(link).addClass('active').parents('li').children('a').addClass('active');
    }
  }


   if (offset >= tableOffset && $fixedHeader.is(":hidden")) {
   function update() {
    $fixedHeader.show();
    var i, top, pick = null;
  } else if (offset < tableOffset) {
    var line = LINE;
     $fixedHeader.hide();
    var doc = document.documentElement;
    ticking = false;
    // sudah mentok di bawah: judul terakhir yang terlihat dianggap aktif (tidak bisa naik ke garis)
    if (window.innerHeight + window.pageYOffset >= doc.scrollHeight - 2) {
      line = window.innerHeight;
    }
    if (clicked) {
      top = clicked.el.getBoundingClientRect().top;
      // selama ±1 detik setelah klik halaman mungkin masih bergulir menuju judulnya
      if ((top >= 0 && top < window.innerHeight) || Date.now() - clickedAt < 1000) {
        activate(clicked.link);
        return;
      }
      clicked = null;
    }
    for (i = 0; i < items.length; i++) {
      if (items[i].el.getBoundingClientRect().top > line) {
        break;
      }
      pick = items[i].link;
    }
     activate(pick);
   }
   }
});


var one = document.querySelector("#sidebar-right");
  function find(id) {
var children = document.querySelector(".catlinks").children;
    for (var i = 0; i < items.length; i++) {
      if (items[i].el.id === id) {
        return items[i];
      }
    }
    return null;
  }


Array.prototype.forEach.call(children, function (child) {
  // ScrollSpy dibuat oleh skin Tweeki, bisa sesudah blok ini jalan, jadi coba beberapa kali (±5 detik)
    one.appendChild(child);
  function stopSpy(tries) {
});
    var SS = $.fn.scrollspy && $.fn.scrollspy.Constructor;
    var spy = SS && SS.getInstance ? SS.getInstance(document.body) : null;
    if (spy) {
      spy.dispose();
      current = null;
      update();
    } else if (tries > 0) {
      setTimeout(function () {
        stopSpy(tries - 1);
      }, 200);
    }
  }


  $links = $('#toc ul a[href^="#"]');
  $links.each(function () {
    var id = $(this).data('tocId');
    var el;
    try {
      id = id || decodeURIComponent(this.hash.slice(1));
    } catch (e) {
      return;
    }
    el = document.getElementById(id);
    if (el) {
      items.push({ link: this, el: el });
    }
  });
  if (!items.length) {
    return;
  }


// Bootstrap 5
  // href tetap di-escape: ScrollSpy Tweeki dibuat dulu sebelum dimatikan dan error pada selector tidak sah
  if (window.CSS && CSS.escape) {
    items.forEach(function (item) {
      var safe = '#' + CSS.escape(item.el.id);
      if (item.link.getAttribute('href') !== safe) {
        $(item.link).attr('href', safe).data('tocId', item.el.id);
      }
    });
  }


var alertList = document.querySelectorAll('.alert')
  $.each(items, function (i, item) {
alertList.forEach(function (alert) {
    $(item.link).on('click', function (e) {
  new bootstrap.Alert(alert)
      if ($(this).data('tocId')) {
});
        e.preventDefault();
        // '#' tambahan wajib: setter location.hash membuang satu '#' di depan
        location.hash = '#' + item.el.id;
      }
      clicked = item;
      clickedAt = Date.now();
      update();
    });
  });


const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
  // halaman dibuka lewat tautan langsung ke suatu bagian (mis. .../Halaman#Judul)
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
  if (location.hash) {
    try {
      clicked = find(decodeURIComponent(location.hash.slice(1)));
      clickedAt = Date.now();
    } catch (e) {}
  }


  $(window).on('scroll resize', function () {
    if (!ticking) {
      ticking = true;
      window.requestAnimationFrame(update);
    }
  });
  update();
  setTimeout(function () {
    stopSpy(25);
  }, 0);
});


/ /bootstrap 5.3 accordion troubleshooting overlap the content when showing items with card and masonry
// bootstrap 5.3 accordion/collapse: Masonry di dalam bagian yang tertutup saat halaman dimuat
// Assuming you are using Masonry
// mengukur kartu setinggi/selebar 0 -> menumpuk. Susun ulang setiap Masonry (.grid maupun
var accordion = document.getElementById('myAccordion');
// data-masonry) di dalam bagian yang baru terbuka, apa pun id accordion-nya.
accordion.addEventListener('shown.bs.collapse', function () {
$(document).on('shown.bs.collapse', function (e) {
   // Re-layout Masonry grid here
  if (typeof Masonry === 'undefined') {
  msnry.layout();  
    return;
   }
  $(e.target).find('.grid, [data-masonry]').each(function () {
    var m = Masonry.data(this);
    if (m) {
      m.layout();
    }
  });
});
});

Revisi terkini sejak 2 November 2026 11.04

/* Any JavaScript here will be loaded for users using the Tweeki skin */
/* Setiap blok dijaga: kalau elemen/library yang dicari tidak ada di halaman ini,
   blok itu dilewati. Dulu satu error menghentikan seluruh file. */

// Bootstrap 5: Tweeki tidak membuka global `bootstrap`, pakai jembatan jQuery-nya.
// Plugin jQuery-nya bisa belum terdaftar (urutan muat), jadi dicek dulu.
$(function () {
  if ($.fn.tooltip) {
    $('[data-toggle="tooltip"]').tooltip();
  }
  if ($.fn.alert) {
    $('.alert').alert();
  }
  if ($.fn.popover) {
    $('[data-bs-toggle="popover"]').popover();
  }
});

// Header tabel #table-1 menempel saat digulir (butuh #header-fixed di halaman)
(function () {
  var $table = $('#table-1');
  var $fixedHeader = $('#header-fixed');
  if (!$table.length || !$fixedHeader.length) {
    return;
  }
  var tableOffset = $table.offset().top;
  $fixedHeader.append($table.children('thead').clone());

  $(window).on('scroll', function () {
    var offset = $(this).scrollTop();

    if (offset >= tableOffset && $fixedHeader.is(':hidden')) {
      $fixedHeader.show();
    } else if (offset < tableOffset) {
      $fixedHeader.hide();
    }
  });
}());

// Pindahkan kategori ke sidebar kanan
(function () {
  var one = document.querySelector('#sidebar-right');
  var catlinks = document.querySelector('.catlinks');
  if (!one || !catlinks) {
    return;
  }
  // salin ke array dulu: .children ikut berubah saat elemen dipindah (dulu yang terpindah hanya selang-seling)
  Array.prototype.slice.call(catlinks.children).forEach(function (child) {
    one.appendChild(child);
  });
}());


// MASONRY: .grid > .grid-item. Library dimuat async dari CDN (hook BeforePageDisplay di
// LocalSettings), jadi bisa belum tersedia saat file ini jalan -> coba lagi saat window load.
// Hanya .grid yang benar-benar berisi .grid-item: badan accordion di Templat:BootstrapAccordion2Collapse
// juga berkelas .grid tanpa item, dan Masonry membuatnya setinggi 0 sehingga isinya menimpa bagian bawahnya.
var msnrys = [];

function initMasonry() {
  if (msnrys.length || typeof Masonry === 'undefined') {
    return;
  }
  Array.prototype.forEach.call(document.querySelectorAll('.grid'), function (grid) {
    if (grid.querySelector(':scope > .grid-item')) {
      msnrys.push(new Masonry(grid, {
        itemSelector: '.grid-item',
        percentPosition: true
      }));
    }
  });
}
initMasonry();
window.addEventListener('load', initMasonry);

// Daftar isi (ScrollSpy): judul berawalan "#" (mis. "#1 Kanak-kanak") atau berisi "?" menghasilkan
// href seperti "##1_Kanak-kanak" yang bukan selector CSS sah -> ScrollSpy Bootstrap error dan
// berhenti, sehingga sub-daftar H3 tidak pernah terbuka. Escape href-nya, lalu tangani kliknya
// sendiri (href yang di-escape tidak lagi cocok dengan id heading).
//
// Penanda bagian aktif juga tidak lagi memakai ScrollSpy: ScrollSpy hanya mengamati judul (bukan
// seluruh bagian) dengan rootMargin "70px 0px -30%", jadi bila bagian yang diklik pendek, judul
// berikutnya ikut terlihat dan dianggap aktif, dan saat judul keluar layar penandanya hilang.
// Gantinya: bagian aktif = judul terakhir yang sudah melewati garis LINE di bawah navbar; judul yang
// baru diklik tetap aktif selama masih terlihat (judul di ujung halaman tidak bisa naik ke atas).
$(function () {
  var LINE = 120;
  var $links;
  var items = [];
  var current = null;
  var clicked = null;
  var clickedAt = 0;
  var ticking = false;

  function activate(link) {
    if (link === current) {
      return;
    }
    current = link;
    $('#toc a.active').removeClass('active');
    if (link) {
      // tautan induk ikut aktif supaya sub-daftarnya (.active + ul) tetap terbuka
      $(link).addClass('active').parents('li').children('a').addClass('active');
    }
  }

  function update() {
    var i, top, pick = null;
    var line = LINE;
    var doc = document.documentElement;
    ticking = false;
    // sudah mentok di bawah: judul terakhir yang terlihat dianggap aktif (tidak bisa naik ke garis)
    if (window.innerHeight + window.pageYOffset >= doc.scrollHeight - 2) {
      line = window.innerHeight;
    }
    if (clicked) {
      top = clicked.el.getBoundingClientRect().top;
      // selama ±1 detik setelah klik halaman mungkin masih bergulir menuju judulnya
      if ((top >= 0 && top < window.innerHeight) || Date.now() - clickedAt < 1000) {
        activate(clicked.link);
        return;
      }
      clicked = null;
    }
    for (i = 0; i < items.length; i++) {
      if (items[i].el.getBoundingClientRect().top > line) {
        break;
      }
      pick = items[i].link;
    }
    activate(pick);
  }

  function find(id) {
    for (var i = 0; i < items.length; i++) {
      if (items[i].el.id === id) {
        return items[i];
      }
    }
    return null;
  }

  // ScrollSpy dibuat oleh skin Tweeki, bisa sesudah blok ini jalan, jadi coba beberapa kali (±5 detik)
  function stopSpy(tries) {
    var SS = $.fn.scrollspy && $.fn.scrollspy.Constructor;
    var spy = SS && SS.getInstance ? SS.getInstance(document.body) : null;
    if (spy) {
      spy.dispose();
      current = null;
      update();
    } else if (tries > 0) {
      setTimeout(function () {
        stopSpy(tries - 1);
      }, 200);
    }
  }

  $links = $('#toc ul a[href^="#"]');
  $links.each(function () {
    var id = $(this).data('tocId');
    var el;
    try {
      id = id || decodeURIComponent(this.hash.slice(1));
    } catch (e) {
      return;
    }
    el = document.getElementById(id);
    if (el) {
      items.push({ link: this, el: el });
    }
  });
  if (!items.length) {
    return;
  }

  // href tetap di-escape: ScrollSpy Tweeki dibuat dulu sebelum dimatikan dan error pada selector tidak sah
  if (window.CSS && CSS.escape) {
    items.forEach(function (item) {
      var safe = '#' + CSS.escape(item.el.id);
      if (item.link.getAttribute('href') !== safe) {
        $(item.link).attr('href', safe).data('tocId', item.el.id);
      }
    });
  }

  $.each(items, function (i, item) {
    $(item.link).on('click', function (e) {
      if ($(this).data('tocId')) {
        e.preventDefault();
        // '#' tambahan wajib: setter location.hash membuang satu '#' di depan
        location.hash = '#' + item.el.id;
      }
      clicked = item;
      clickedAt = Date.now();
      update();
    });
  });

  // halaman dibuka lewat tautan langsung ke suatu bagian (mis. .../Halaman#Judul)
  if (location.hash) {
    try {
      clicked = find(decodeURIComponent(location.hash.slice(1)));
      clickedAt = Date.now();
    } catch (e) {}
  }

  $(window).on('scroll resize', function () {
    if (!ticking) {
      ticking = true;
      window.requestAnimationFrame(update);
    }
  });
  update();
  setTimeout(function () {
    stopSpy(25);
  }, 0);
});

// bootstrap 5.3 accordion/collapse: Masonry di dalam bagian yang tertutup saat halaman dimuat
// mengukur kartu setinggi/selebar 0 -> menumpuk. Susun ulang setiap Masonry (.grid maupun
// data-masonry) di dalam bagian yang baru terbuka, apa pun id accordion-nya.
$(document).on('shown.bs.collapse', function (e) {
  if (typeof Masonry === 'undefined') {
    return;
  }
  $(e.target).find('.grid, [data-masonry]').each(function () {
    var m = Masonry.data(this);
    if (m) {
      m.layout();
    }
  });
});