@ -275,39 +275,134 @@ document.addEventListener('DOMContentLoaded', function() {
try {
const articlePage = document . querySelector ( '.article-page' ) ;
if ( articlePage ) {
const scrollspyNav = document . querySelector ( '.scrollspy-nav' ) ;
const mobileHeader = document . querySelector ( '.mobile-article-header' ) ;
const mobileTitle = document . getElementById ( 'mobile-article-title' ) ;
const mobileDropdown = document . querySelector ( '.mobile-scrollspy-dropdown' ) ;
const mobileLinksContainer = document . getElementById ( 'mobile-scrollspy-links' ) ;
const progressBar = document . querySelector ( '.progress-bar' ) ;
const headings = Array . from ( articlePage . querySelectorAll ( '.article-body h2' ) ) ;
const desktopNavLinks = scrollspyNav ? Array . from ( scrollspyNav . querySelectorAll ( 'a' ) ) : [ ] ;
const articleContainer = articlePage . querySelector ( '.article-container' ) ;
const articleBody = articlePage . querySelector ( '.article-body' ) ;
const headings = articleBody ? Array . from ( articleBody . querySelectorAll ( 'h2' ) ) : [ ] ;
const articleTitleEl = articlePage . querySelector ( '.article-header h1' ) ;
const articleTitle = articleTitleEl ? articleTitleEl . textContent . trim ( )
: ( document . title || '' ) . trim ( ) ;
// --- Dinamično ustvarjanje mobilnega spustnega seznama ---
if ( headings . length > 0 && mobileLinksContainer ) {
// Label for a heading in the table of contents. An optional
// data-nav-title attribute lets an article override the full <h2>
// text with a shorter label; otherwise we use the heading text.
const navLabel = ( heading ) =>
( heading . getAttribute ( 'data-nav-title' ) || heading . textContent ) . trim ( ) ;
// --- 5a. Zagotovi, da ima vsak naslov enoličen id (za sidra) ---
// Transliterate then slugify so anchors are readable in every language:
// Slovenian diacritics (č/š/ž/…) and Macedonian Cyrillic map to ASCII.
// Anything still unmapped is dropped and falls back to section-N below.
const TRANSLIT = {
// Slovenian / Latin diacritics
'č' : 'c' , 'ć' : 'c' , 'š' : 's' , 'ž' : 'z' , 'đ' : 'dj' ,
'á' : 'a' , 'à' : 'a' , 'â' : 'a' , 'ä' : 'a' , 'ã' : 'a' , 'é' : 'e' , 'è' : 'e' , 'ê' : 'e' , 'ë' : 'e' ,
'í' : 'i' , 'ì' : 'i' , 'î' : 'i' , 'ï' : 'i' , 'ó' : 'o' , 'ò' : 'o' , 'ô' : 'o' , 'ö' : 'o' , 'õ' : 'o' ,
'ú' : 'u' , 'ù' : 'u' , 'û' : 'u' , 'ü' : 'u' , 'ñ' : 'n' , 'ç' : 'c' , 'ß' : 'ss' ,
// Macedonian Cyrillic
'а ' : 'a' , 'б ' : 'b' , 'в' : 'v' , 'г ' : 'g' , 'д' : 'd' , 'ѓ' : 'gj' , 'е ' : 'e' , 'ж' : 'zh' , 'з' : 'z' ,
'ѕ ' : 'dz' , 'и' : 'i' , 'ј ' : 'j' , 'к' : 'k' , 'л' : 'l' , 'љ' : 'lj' , 'м' : 'm' , 'н' : 'n' , 'њ' : 'nj' ,
'о ' : 'o' , 'п' : 'p' , 'р ' : 'r' , 'с ' : 's' , 'т' : 't' , 'ќ' : 'kj' , 'у ' : 'u' , 'ф' : 'f' , 'х ' : 'h' ,
'ц' : 'c' , 'ч' : 'ch' , 'џ' : 'dj' , 'ш' : 'sh'
} ;
const usedIds = new Set ( ) ;
const transliterate = ( text ) =>
text . replace ( /[^\x00-\x7F]/g , ( ch ) => ( ch in TRANSLIT ? TRANSLIT [ ch ] : '' ) ) ;
const slugify = ( text ) => transliterate ( text . toLowerCase ( ) )
. replace ( /[^a-z0-9]+/g , '-' )
. replace ( /^-+|-+$/g , '' ) ;
headings . forEach ( ( heading , i ) => {
let id = heading . id || slugify ( heading . textContent ) ;
if ( id . length < 2 ) id = 'section-' + ( i + 1 ) ;
let unique = id , n = 2 ;
while ( usedIds . has ( unique ) ) { unique = id + '-' + ( n ++ ) ; }
usedIds . add ( unique ) ;
heading . id = unique ;
} ) ;
// --- 5b. Zagotovi postavitveni ovoj in stransko vrstico (Desktop) ---
// Older article pages (SI/MK and some EN) ship without the TOC
// scaffolding, so we build it here from the page's own headings.
let layoutContainer = articlePage . querySelector ( '.article-layout-container' ) ;
if ( ! layoutContainer && articleContainer ) {
layoutContainer = document . createElement ( 'div' ) ;
layoutContainer . className = 'article-layout-container' ;
articleContainer . parentNode . insertBefore ( layoutContainer , articleContainer ) ;
layoutContainer . appendChild ( articleContainer ) ;
}
let sidebar = articlePage . querySelector ( '.article-sidebar' ) ;
if ( ! sidebar && layoutContainer && headings . length > 0 ) {
sidebar = document . createElement ( 'aside' ) ;
sidebar . className = 'article-sidebar' ;
sidebar . innerHTML = '<nav class="scrollspy-nav"><ul></ul></nav>' ;
layoutContainer . insertBefore ( sidebar , layoutContainer . firstChild ) ;
}
const scrollspyNav = sidebar ? sidebar . querySelector ( '.scrollspy-nav' ) : null ;
const desktopUl = scrollspyNav ? scrollspyNav . querySelector ( 'ul' ) : null ;
// --- 5c. Napolni namizni seznam, če je prazen (ohrani ročno napisanega) ---
if ( desktopUl && desktopUl . children . length === 0 && headings . length > 0 ) {
headings . forEach ( heading => {
const li = document . createElement ( 'li' ) ;
const a = document . createElement ( 'a' ) ;
a . href = '#' + heading . id ;
a . textContent = navLabel ( heading ) ;
li . appendChild ( a ) ;
desktopUl . appendChild ( li ) ;
} ) ;
}
// --- 5d. Naslov članka nad kazalom (Desktop) ---
if ( sidebar && ! sidebar . querySelector ( '.article-sidebar-title' ) && articleTitle ) {
const titleEl = document . createElement ( 'p' ) ;
titleEl . className = 'article-sidebar-title' ;
titleEl . textContent = articleTitle ;
sidebar . insertBefore ( titleEl , sidebar . firstChild ) ;
}
// --- 5e. Zagotovi lepljivo mobilno glavo z naslovom + progress bar ---
let mobileHeader = articlePage . querySelector ( '.mobile-article-header' ) ;
if ( ! mobileHeader && headings . length > 0 ) {
mobileHeader = document . createElement ( 'div' ) ;
mobileHeader . className = 'mobile-article-header' ;
mobileHeader . innerHTML =
'<span id="mobile-article-title"></span>' +
'<i class="fas fa-chevron-down"></i>' +
'<div class="progress-bar-container"><div class="progress-bar"></div></div>' +
'<div class="mobile-scrollspy-dropdown"><ul id="mobile-scrollspy-links"></ul></div>' ;
articlePage . insertBefore ( mobileHeader , articlePage . firstChild ) ;
}
const mobileTitle = document . getElementById ( 'mobile-article-title' ) ;
if ( mobileTitle && ! mobileTitle . textContent . trim ( ) ) {
mobileTitle . textContent = articleTitle ; // known at load; scrollspy refines it
}
const mobileDropdown = articlePage . querySelector ( '.mobile-scrollspy-dropdown' ) ;
const mobileLinksContainer = document . getElementById ( 'mobile-scrollspy-links' ) ;
const progressBar = articlePage . querySelector ( '.progress-bar' ) ;
const desktopNavLinks = desktopUl ? Array . from ( desktopUl . querySelectorAll ( 'a' ) ) : [ ] ;
// --- 5f. Napolni mobilni spustni seznam ---
if ( headings . length > 0 && mobileLinksContainer && mobileLinksContainer . children . length === 0 ) {
headings . forEach ( heading => {
const listItem = document . createElement ( 'li' ) ;
const link = document . createElement ( 'a' ) ;
link . href = ` # ${ heading . id } ` ;
link . textContent = heading . textContent ;
link . href = '#' + heading . id ;
link . textContent = navLabel( heading ) ;
listItem . appendChild ( link ) ;
mobileLinksContainer . appendChild ( listItem ) ;
} ) ;
}
// --- Logika za odpiranje/zapiranje mobilnega spustnega seznama ---
// --- 5g. O dpiranje/zapiranje mobilnega spustnega seznama ---
if ( mobileHeader && mobileDropdown ) {
mobileHeader . addEventListener ( 'click' , ( e ) => {
// Preprečimo, da se meni zapre, če kliknemo na povezavo znotraj njega
if ( ! e . target . closest ( 'a' ) ) {
mobileHeader . classList . toggle ( 'open' ) ;
mobileDropdown . classList . toggle ( 'open' ) ;
mobileHeader . classList . toggle ( 'open' ) ;
mobileDropdown . classList . toggle ( 'open' ) ;
}
} ) ;
// Dodamo dogodek za zapiranje menija ob kliku na povezavo
mobileDropdown . querySelectorAll ( 'a' ) . forEach ( link => {
link . addEventListener ( 'click' , ( ) => {
mobileHeader . classList . remove ( 'open' ) ;
@ -316,56 +411,55 @@ document.addEventListener('DOMContentLoaded', function() {
} ) ;
}
// --- Logika za Progress Bar ---
// --- 5h. Progress Bar ---
function updateProgressBar ( ) {
if ( ! progressBar ) return ;
const scrollableHeight = document . documentElement . scrollHeight - window . innerHeight ;
const scrollTop = window . scrollY ;
if ( scrollableHeight > 0 ) {
const progress = ( scrollTop / scrollableHeight ) * 100 ;
progressBar . style . width = ` ${ progress } % ` ;
} else {
progressBar . style . width = '0%' ;
}
progressBar . style . width = scrollableHeight > 0
? ` ${ ( scrollTop / scrollableHeight ) * 100 } % `
: '0%' ;
}
// --- Logika za Scrollspy ---
// --- 5i. Scrollspy (posodobi navigacijo, naslov in URL sidro) ---
if ( headings . length > 0 ) {
let activeId = null ;
const observer = new IntersectionObserver ( ( entries ) => {
entries . forEach ( entry => {
const id = entry . target . getAttribute ( 'id' ) ;
const desktopNavLink = desktopNavLinks . find ( link => link . getAttribute ( 'href' ) === ` # ${ id } ` ) ;
const mobileNavLink = mobileLinksContainer ? mobileLinksContainer . querySelector ( ` a[href="# ${ id } "] ` ) : null ;
if ( ! entry . isIntersecting ) return ;
const id = entry . target . id ;
if ( id === activeId ) return ;
activeId = id ;
if ( entry . isIntersecting ) {
// Posodobi namizno navigacijo
if ( desktopNavLink ) {
desktopNavLinks . forEach ( link => link . classList . remove ( 'active' ) ) ;
desktopNavLink . classList . add ( 'active' ) ;
}
// Posodobi mobilni naslov in aktivno povezavo v spustnem seznamu
if ( mobileTitle ) {
mobileTitle . textContent = entry . target . textContent ;
}
// Namizna navigacija
const desktopNavLink = desktopNavLinks . find ( link => link . getAttribute ( 'href' ) === ` # ${ id } ` ) ;
if ( desktopNavLink ) {
desktopNavLinks . forEach ( link => link . classList . remove ( 'active' ) ) ;
desktopNavLink . classList . add ( 'active' ) ;
}
// Mobilni naslov + aktivna povezava
if ( mobileTitle ) mobileTitle . textContent = navLabel ( entry . target ) ;
if ( mobileLinksContainer ) {
const mobileNavLink = mobileLinksContainer .querySelector ( ` a[href="# ${ id }"] ` ) ;
if ( mobileNavLink ) {
mobileLinksContainer . querySelectorAll ( 'a' ) . forEach ( link => link . classList . remove ( 'active' ) ) ;
mobileNavLink . classList . add ( 'active' ) ;
}
}
// Posodobi URL sidro brez skoka in brez polnjenja zgodovine
if ( window . history && history . replaceState ) {
history . replaceState ( null , '' , '#' + id ) ;
}
} ) ;
} , {
rootMargin : "-100px 0px -50% 0px" // Sproži, ko je naslov v zgornjem delu zaslona
rootMargin : "-100px 0px -5 5 % 0px" // Sproži, ko je naslov v zgornjem delu zaslona
} ) ;
headings . forEach ( heading => {
observer . observe ( heading ) ;
} ) ;
headings . forEach ( heading => observer . observe ( heading ) ) ;
}
// Dodaj poslušalca dogodkov za vrstico napredka
window . addEventListener ( 'scroll' , updateProgressBar ) ;
updateProgressBar ( ) ; // Klic ob nalaganju strani
updateProgressBar ( ) ;
}
} catch ( error ) {
console . error ( 'Error in Article Scrollspy/Progress Bar setup:' , error ) ;