'use strict';

var g_BroadcastChannel = new BroadcastChannel(window.location.hostname);
var g_Cookies = ['AllowCookies', 'Design', 'ImageViewer_FitImage', 'ImageViewer_FullScreen', 'InitStats', 'LoadExternalContent'];
var g_Settings = {};

SetDesign(); // vorab, damit die Seite nicht "flackert"

function InitDefault()
{
	InitMenu();
	InitCSS();
	InitDesign();
	PrettifyText();
	InitLinksPreview();
	InitFratzenbuch();
	InitArticleLinks();
	InitLinkTitles();
	InitSettings(); // muss vor InitLinkPopups() aufgerufen werden
	InitLinkPopups();
	InitImageCarousel();
	InitImageGrid();
	InitImageViewer();
	InitTableOfContents();
	InitTranslateLink();
	InitAudioPlayer();
	InitExternalContent()
	InitStats();
	InitScrollToAnchor();
	InitArticle();
}

function InitPreview()
{
	PrettifyText();
	InitFratzenbuch();
	InitLinkTitles();
	InitSettings(); // muss vor InitLinkPopups() aufgerufen werden
	InitLinkPopups();
	InitImageCarousel();
	InitImageGrid();
	InitImageViewer();
	InitTableOfContents();
	InitTranslateLink();
	InitAudioPlayer();
	InitExternalContent()
	InitScrollToAnchor();
	InitArticle();
}

function InitCSS()
{
	document.documentElement.style.setProperty('--ScrollbarWidth', (window.innerWidth - document.documentElement.clientWidth) + "px");
	document.documentElement.style.setProperty('--MenuHeight', (document.getElementsByTagName('nav')[0].offsetHeight) + "px");
}

// Design (hell/dunkel)

function InitDesign()
{
	SetDesign();
	window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() { SetDesign(); });
}

function SwitchDesign(design)
{
	SaveSetting('Design', design, 'auto', true, SwitchDesign, [design]);

	design = LoadSetting('Design', 'auto');
	SetDesign(design);
	UpdateDesignControls(design);
}

function SetDesign(design)
{
	if (!design)
	{
		design = LoadSetting('Design', 'auto');
		UpdateDesignControls(design);
	}

	if (design === 'auto')
		design = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

	if (design === 'dark')
		document.documentElement.classList.add('dark');
	else
		document.documentElement.classList.remove('dark');
}

function UpdateDesignControls(design)
{
	UpdateDesignIcon('icon-design-auto', design);
	UpdateDesignIcon('icon-design-light', design);
	UpdateDesignIcon('icon-design-dark', design);
}

function UpdateDesignIcon(idIcon, design)
{
	var elemIcon = document.getElementById(idIcon);
	if (!elemIcon) return;
	var icon = (idIcon === 'icon-design-' + design) ? ' <b>✔</b>' : '';
	elemIcon.innerHTML = icon;
}

// Hauptmenü

function InitMenu()
{
	var elemNav = document.getElementsByTagName('nav')[0];
	var elemLogo = elemNav.getElementsByClassName('logo')[0];
	var elemMenu = elemNav.getElementsByClassName('menu')[0];

	var elemMenuEmpty = document.createElement('div');
	elemMenuEmpty.classList.add('menu-empty');
	elemMenuEmpty.innerHTML = '&nbsp;';
	elemNav.appendChild(elemMenuEmpty);

	var elemMenuOpenButton = document.createElement('div');
	elemMenuOpenButton.classList.add('menu-open-button');
	elemMenuOpenButton.innerHTML = '&#x2630;';
	elemNav.appendChild(elemMenuOpenButton);

	var elemMenuCloseButton = document.createElement('div');
	elemMenuCloseButton.classList.add('menu-close-button');
	elemMenuCloseButton.innerHTML = '&#x2715;';
	elemNav.appendChild(elemMenuCloseButton);

	function OpenMenu()
	{
		msSubMenuOpened = Date.now();

		elemNav.classList.remove('closed');
		elemNav.classList.add('opened');
	}

	function CloseMenu()
	{
		elemNav.classList.remove('opened');
		elemNav.classList.add('closed');

		CloseSubMenus(elemMenu);
	}

	function OpenSubMenu(elemSubMenu, elemItem)
	{
		CloseSubMenus(elemSubMenu.parentElement);
		msSubMenuOpened = Date.now();

		elemSubMenu.classList.remove('closed');
		elemSubMenu.classList.add('opened');

		if (elemNav.classList.contains('opened'))
		{
			elemSubMenu.style.top = '0';
			elemSubMenu.style.left = '0';
		}
		else
		{
			var itemRect = elemItem.getBoundingClientRect();
			elemSubMenu.style.top = (itemRect.top + itemRect.height + 1) + 'px';
			elemSubMenu.style.left = '0';
			var width = elemSubMenu.clientWidth + elemSubMenu.offsetLeft;
			var left = Math.floor(itemRect.left);
			var right = left + width;
			var docWidth  = document.documentElement.clientWidth;
			if (right >= docWidth) left -= (right - docWidth + 1);
			if (left < 0) left = 0;
			elemSubMenu.style.left = left + 'px';
		}
	}

	function CloseSubMenu(elemSubMenu)
	{
		CloseSubMenus(elemSubMenu);
		msSubMenuOpened = Date.now();

		elemSubMenu.classList.remove('opened');
		elemSubMenu.classList.add('closed');
	}

	function CloseSubMenus(elemMenu)
	{
		var elemsSubMenu = elemMenu.getElementsByTagName('div');
		for (var i = 0; i < elemsSubMenu.length; i++)
			CloseSubMenu(elemsSubMenu[i]);
	}

	function OnClickSubMenu(evt)
	{
		var elemItem = evt.target;
		var elemSubMenu = elemItem.nextElementSibling;
		if (elemSubMenu && elemSubMenu.tagName.toLowerCase() === 'div')
		{
			if (elemSubMenu.classList.contains('closed'))
				OpenSubMenu(elemSubMenu, elemItem);
			else
				CloseSubMenu(elemSubMenu);
		}
	}

	function ResetMenu()
	{
		elemNav.classList.remove('closed');
		elemNav.classList.remove('opened');

		if (elemMenu.offsetWidth + elemLogo.offsetWidth >= elemNav.offsetWidth)
			elemNav.classList.add('closed');
	}

	function SetupMenu(elemMenu, bSubmenu)
	{
		var loc = MakeComparableURL(window.location.href);
		var elemsChild = elemMenu.children;
		for (var i = 0; i < elemsChild.length; i++)
		{
			var elemChild = elemsChild[i];
			if (elemChild.tagName.toLowerCase() === 'div')
			{
				CloseSubMenu(elemChild);
				SetupMenu(elemChild, true);
			}
			else if (elemChild.tagName.toLowerCase() === 'a')
			{
				if (elemChild.getAttribute('href'))
				{
					if (loc === MakeComparableURL(elemChild.href))
						elemChild.classList.add('active');
					else
						elemChild.classList.remove('active');
				}
				else
				{
					elemChild.addEventListener('click', OnClickSubMenu);
				}
			}
			else if (elemChild.tagName.toLowerCase() === 'button' && elemChild.classList.contains('link'))
			{
				var idForm = elemChild.getAttribute('form');
				if (idForm)
				{
					var elemForm = document.getElementById(idForm);
					if (elemForm.getAttribute('action'))
					{
						if (loc === MakeComparableURL(elemForm.action))
							elemChild.classList.add('active');
						else
							elemChild.classList.remove('active');
					}
				}
			}
		}
		function MakeComparableURL(url)
		{
			var url = url.split('#')[0]; // Hash wegschmeißen
			var urlParts = url.split('?');
			var path = urlParts[0].replace(/\.php$/, '');
			if (path.slice(-1) === '/') path += 'index';
			urlParts[0] = path;
			url = urlParts.join('?');

			if (urlParts.length > 1)
			{
				var search = urlParts[1];
				var name = search.split('=')[0];
				if (name !== 'cats' && name !== 'tags' && name !== 'month' && name !== 'search')
					url = urlParts[0];
			}
			return url;
		}
	}

	SetupMenu(elemMenu);

	var prevWidth = elemNav.offsetWidth;
	window.addEventListener('resize', function()
	{
		CloseSubMenus(elemMenu);
		if (prevWidth != elemNav.offsetWidth)
		{
			ResetMenu(); 
			prevWidth = elemNav.offsetWidth;
		}
	});

	var msSubMenuOpened = 0;
	window.addEventListener('click', function(evt)
	{
		if (msSubMenuOpened > 0 && Date.now() - msSubMenuOpened < 100)
			return;

		CloseSubMenus(elemMenu);
		if (elemNav.classList.contains('opened'))
		{
			elemNav.classList.remove('opened');
			elemNav.classList.add('closed');
		}
	});

	elemMenuOpenButton.addEventListener('click', OpenMenu);
	elemMenuCloseButton.addEventListener('click', CloseMenu);

	ResetMenu();

	elemMenu.style.visibility = 'visible';
}

// Versieht die Artikel auf der Beitragsliste (Blog-Seite) mit dem in der H1-Überschrift hinterlegten Link,
// so dass zum Öffnen des Artikels nicht unbedingt auf die Überschrift geklickt werden muss, sondern irgendwo
// innerhalb des Artikel-DIVs.

function InitArticleLinks()
{
	var elemsArticle = document.getElementsByTagName('article');

	for (var i = 0; i < elemsArticle.length; i++)
	{
		var elemArticle = elemsArticle[i];
		var elemClick = elemArticle;
		if (elemArticle.classList.contains('front'))
			elemClick = elemArticle.getElementsByClassName('article-header')[0];
		if (!elemClick)
			continue;

		var elemsHeading = elemArticle.getElementsByTagName('h1');
		var elemHeading = elemsHeading[0];
		var elemsLink = elemHeading.getElementsByTagName('a');

		for (var k = 0; k < elemsLink.length; k++)
		{
			var elemLink = elemsLink[k];
			var url = elemLink.getAttribute('href');

			elemClick.style.cursor = 'pointer';
			elemClick.setAttribute('onclick', 'window.location="' + url + '";');
			elemArticle.classList.add('hover');
			break;
		}
	}
}

// Popup-Fenster für Links
// Wenn einem Link die Klasse "popup" zugewiesen wird (z.B. <a class="popup" href="..." target="_blank" rel="noopener" title="...">),
// dann wird der Link beim Anklicken nicht sofort geöffnet, sondern ein Popup mit dem Text im title-Attribut angezeigt und darunter
// 2 Buttons: "Link öffnen" und "Schließen". Der title-Text kann mittels BB-Code formatiert werden ([b|i|s|u|url=]). Fehlt das href-
// Attribut, wird kein Buttons "Link öffnen" angezeigt - so können einfache Popups (ähnlich wie Tooltips) realisiert werden. Enthält
// href eine URL zu www.bibleserver.com, wird der title-Text kursiv angezeigt (Bibelzitat) und darunter die Bibel-Übersetzung(en).

function InitLinkPopups()
{
	var elemPopup = document.createElement('div');
	elemPopup.id = 'popup-window';
	document.body.appendChild(elemPopup);

	var elemArrow = document.createElement('div');
	elemArrow.id = 'popup-arrow';
	document.body.appendChild(elemArrow);

	// Ein Klick außerhalb des Popup schließt dieses
	var msPopupOpened = 0;
	document.body.addEventListener('click', function(event)
	{
		if (msPopupOpened > 0 && Date.now() - msPopupOpened < 100) return;
		if (elemPopup.contains(event.target)) return;
		if (elemArrow.contains(event.target)) return;
		ClosePopup();
	});

	// Links initialisieren
	var elemsLink = document.querySelectorAll('a.popup');
	for (var i = 0; i < elemsLink.length; i++)
	{
		var elemLink = elemsLink[i];
		elemLink.classList.add('normal');
		elemLink.setAttribute('data-title', elemLink.getAttribute('title'));
		elemLink.setAttribute('title', '');
		elemLink.addEventListener('click', ShowPopup.bind(this, elemLink));
	}

	function ClosePopup()
	{
		elemPopup.classList.remove('show');
		elemArrow.classList.remove('show');

		var elemButtonClose = document.getElementById('button-close-popup');
		if (elemButtonClose)
			elemButtonClose.removeEventListener('click', ClosePopup);
	}

	function ShowPopup(a)
	{
		event.preventDefault(); // Link beim Anklicken nicht öffnen
		msPopupOpened = Date.now();

		var html = ''; // Inhalt des Popups
		var elemsTemplate = a.getElementsByTagName('template');
		if (elemsTemplate.length > 0) html = elemsTemplate[0].innerHTML;
		html +=
			 '<p>'
			 + a.getAttribute('data-title')
			 .replaceAll('\n', '</p><p class="left">')
			 .replace(/\[([\/]?)([bisu]|br)\]/g, '<$1$2>')
			 .replaceAll('[p]', '</p><p>')
			 .replaceAll('[right]', '</p><p class="right">')
			 .replaceAll('[center]', '</p><p class="center">')
			 .replace(/\[url=(http.*?)\]([^\[]*)\[\/url\]/g, '<a href="$1" target="_blank" rel="noopener">$2</a>')
			 .replace(/\[url=(.*?)\]([^\[]*)\[\/url\]/g, '<a href="$1">$2</a>')
			 .replace(/\[img\](.*?)\[\/img\]/g, '<img src="$1" style="max-width: 100%;">')
			 .replace(/\[img (.*?)\](.*?)\[\/img\]/g, '<img src="$2" style="width: $1; max-width: 100%;">')
			 .replace(/\[id\](.*?)\[\/id\]/g, function(match, p1) { var el = document.getElementById(p1); return el ? el.innerHTML : '(???)'; })
			 .replace(/🗩\uFE0E (.*?)<\/p>/, '<i><?php echo GetString("Typo.DoubleQuoteOpening");?>$1<?php echo GetString("Typo.DoubleQuoteClosing");?></i>')
			 .replace('文\uFE0E', '<sup>文\uFE0E</sup><sub style="position: relative; left: -0.5em; top: -0.25em; margin-right: -0.5em;">A</sub>')
			 .replace('➤\uFE0E ', '')
			 + '</p>';
		html += '<p class="center">';
		if (a.getAttribute('href'))
			html += '<a class="button-link" href="' + a.getAttribute('href') + '" target="' + a.target + '" rel="' + a.rel + '" title="' + a.title + '"><?php echo GetString("OpenLink");?></a> ';
		html += '<span class="button-link" id="button-close-popup"><?php echo GetString("Close");?></span></p>';

		var clickX = event.pageX;
		var clickY = event.pageY;
		var elemNav = document.getElementsByTagName('nav')[0];
		var navHeight = elemNav ? elemNav.offsetHeight : 0;

		// Breite des Popups setzen und mit Inhalt füllen; danach erst kann die Höhe bestimmt werden:
		var popupWidth = Math.min(400, document.documentElement.clientWidth);
		elemPopup.style.width = popupWidth + 'px';
		elemPopup.innerHTML = html;

		document.getElementById('button-close-popup').addEventListener('click', ClosePopup);

		function SetPopupPosition()
		{
			// Horizontale Position des Popups berechnen:
			var popupLeft = clickX - popupWidth / 2;
			if (popupLeft < 0)
				popupLeft = 0;
			else if (popupLeft > document.documentElement.clientWidth - popupWidth) 
				popupLeft = document.documentElement.clientWidth - popupWidth;

			// Vertikale Position von Popup und Pfeil berechnen:
			var popupHeight = elemPopup.clientHeight;
			var popupTop = clickY - 23 - popupHeight;
			var arrowTop = clickY - 2 * 23 + 1;
			var arrowPosition = 'bottom';

			var maxTop = document.documentElement.scrollTop + navHeight;
			if (popupTop < maxTop)
			{
				popupTop = clickY + 23;
				arrowTop = clickY;
				arrowPosition = 'top';
			}

			// Position von Popup und Pfeil setzen und beide anzeigen:
			elemPopup.style.left = popupLeft + 'px';
			elemPopup.style.top = popupTop + 'px';
			elemPopup.style.transformOrigin = (clickX - popupLeft) + 'px ' + arrowPosition;
			elemPopup.classList.add('show');

			elemArrow.style.left = clickX + 'px';
			elemArrow.style.top = arrowTop + 'px';
			elemArrow.className = arrowPosition;
			elemArrow.classList.add('show');
		}

		// Asynchroner Aufruf der Positionierung, da die Höhe des Popups erst feststeht, nachdem die aktualisierte Seite angezeigt wurde
		var elemsImg = elemPopup.getElementsByTagName('IMG');
		for (var i = 0; i < elemsImg.length; i++) // Bilder werden ggf. verzögert geladen, daher Position nach dem Laden der Bilder nochmal anpassen.
			elemsImg[i].addEventListener('load', SetPopupPosition, { once: true });
		if (!elemsImg.length)
			setTimeout(SetPopupPosition, 0);
	}
}

// Setzt das TITLE-Attribut aller Links innerhalb eines Artikels auf den Link-Text, falls das TITLE-Attribut leer ist.
// Enthält das href-Attribut eine URL zu www.bibleserver.com, wird die Bibel-Übersetzung ermittelt und in einer weiteren Zeile unter dem TITLE-Text angezeigt.
// In einer weiteren Zeile darunter wird angezeigt, ob es sich um einen internen oder einen externen Link handelt, und zu welchem Server dieser führt.
// Enthält der Link das Attribut "x-data-translate-from", dann wird bei abweichender Sprache die URL in eine Google-Translate-URL umgewandelt.

function InitLinkTitles()
{
	// Das JSON muss als String gekapselt (und dann geparst) werden, damit default.js per "<script src=...>" eingebunden werden kann (z.B. durch gb.html):
	var bibleTranslations = JSON.parse('<?php echo GetString("BibleTranslations");?>');
	var thisServer = window.location.hostname;

	var elemsLink = document.querySelectorAll("article a");
	for (var iLink = 0; iLink < elemsLink.length; iLink++)
	{
		var translationsDisplay = [];
		var elemLink = elemsLink[iLink];
		var href = elemLink.getAttribute('href');
		if (href)
		{
			// "Translate this page" link
			var sourceLang = elemLink.getAttribute('x-data-translate-from');
			if (sourceLang !== null)
			{
				var url = GetGoogleTranslateUrl(sourceLang, elemLink.href);
				if (url) elemLink.href = url;
			}

			// Bible verse
			var translations = (href.match(/^https:\/\/www.bibleserver.com\/([^\/]+)\//) || ['', ''])[1];
			if (translations)
			{
				for (var translation of translations.split('.'))
				{
					var key = bibleTranslations[translation];
					translationsDisplay.push(key || '<?php echo GetString("unknown");?>' + translation);
				}
			}

			// Anchor Icon
			if (href.substring(0, 1) === '#')
			{
				var id = href.substring(1);
				if (id)
				{
					var elemDest = document.getElementById(id);
					if (elemDest)
					{
						if (elemDest.offsetTop < elemLink.offsetTop)
							elemLink.classList.add('above');
					}
				}
			}
		}

		// Compose the link title
		var title = '';

		var linkText = elemLink.title || elemLink.innerText;
		if (translationsDisplay.length > 0)
		{
			var bibelstelle = '';
			var lastPosOpening = linkText.lastIndexOf('(');
			var lastPosClosing = linkText.lastIndexOf(')');
			if (lastPosOpening !== -1 && lastPosClosing !== -1 && lastPosClosing === linkText.length - 1 && lastPosOpening < lastPosClosing)
			{
				bibelstelle = linkText.substring(lastPosOpening + 1, lastPosClosing).trim();
				linkText = linkText.substring(0, lastPosOpening).trim();
			}
			else
			{
				var sep, lastPosSep = -1;
				for (var sep of ['|', ' — ', ' – ', ' - '])
					lastPosSep = Math.max(lastPosSep, linkText.lastIndexOf(sep));
				if (lastPosSep !== -1)
				{
					bibelstelle = linkText.substring(lastPosSep + sep.length, linkText.length).trim();
					linkText = linkText.substring(0, lastPosSep).trim();
				}
			}
			title = '🗩\uFE0E ' + linkText;
			if (bibelstelle)
				title += '\n📖\uFE0E <?php echo GetString("BibleVerse");?>: ' + bibelstelle;
			title += '\n文\uFE0E <?php echo GetString("Translation");?>: ' + translationsDisplay.join('; ');
		}
		else if (linkText)
			title = '➤\uFE0E ' + linkText;

		if (title)
		{
			if (href)
			{
				var server = (href.match(/^(?:https|http):\/\/(.*?)(\/|$)/) || ['', ''])[1];
				if (server !== '' && server !== thisServer)
					title += '\n🌎\uFE0E <?php echo GetString("ExternalLink");?>: ' + server;
				else
					title += '\n🏠\uFE0E <?php echo GetString("InternalLink");?>: ' + thisServer;
			}
			elemLink.title = title;
		}
	}

/*	(Wirklich sinnvoll?)
	var elemsLink = document.querySelectorAll("article button.link");
	for (var iLink = 0; iLink < elemsLink.length; iLink++)
	{
		var elemLink = elemsLink[iLink];
		var title = elemLink.title;
		if (!title)
			elemLink.title = elemLink.innerHTML;
	}
*/
}

function InitLinksPreview()
{
	var params = new URLSearchParams(window.location.search);
	if (params.get('preview') === null) return;
	var thisServer = window.location.hostname;
	var elemsLink = document.getElementsByTagName('a');
	for (var iLink = 0; iLink < elemsLink.length; iLink++)
	{
		var elemLink = elemsLink[iLink];
		var href = elemLink.href;
		if (href === '' || href.indexOf('#') !== -1) continue;
		var server = (href.match(/^https:\/\/([^\/]*)\//) || ['', ''])[1];
		if (server !== '' && server !== thisServer) continue;
		href += (href.indexOf('?') === -1) ? '?' : '&';
		href += 'preview';
		elemLink.href = href;
	}
}

function InitImageCarousel()
{
	function GetItemInView(elemImageCarousel)
	{
		var rectImageCarousel = elemImageCarousel.getBoundingClientRect();
		var centerImageCarousel = (rectImageCarousel.right + rectImageCarousel.left) / 2;

		var elemsItem = elemImageCarousel.children;
		for (var i = 0; i < elemsItem.length; i++)
		{
			var elemItem = elemsItem[i];
			var rectItem = elemItem.getBoundingClientRect();

			if (elemItem.id)
			{
				var centerItem = (rectItem.right + rectItem.left) / 2;
				if (centerItem >= rectImageCarousel.left)
				{
					return elemItem.id; 
				}
			}
		}
		return undefined;
	}

	function ScrollToItem(elemImageCarousel, elemPageInfo, skip)
	{
		var elemItem = document.getElementById(GetItemInView(elemImageCarousel));
		var itemIDs = ItemIds[elemImageCarousel.id];
		var indexItem = itemIDs.indexOf(elemItem.id) + skip;
		if (indexItem < 0) indexItem = itemIDs.length - 1;
		else if (indexItem >= itemIDs.length) indexItem = 0;
		elemItem = document.getElementById(itemIDs[indexItem]);
		var itemPos = elemItem.offsetLeft - elemImageCarousel.offsetLeft;
		elemImageCarousel.scrollTo({ top: 0, left: itemPos, behavior: "smooth" });
	}

	function OnScroll(elemImageCarousel, elemPageInfo)
	{
		var itemIDs = ItemIds[elemImageCarousel.id];
		var elemItem = document.getElementById(GetItemInView(elemImageCarousel));
		var indexItem = itemIDs.indexOf(elemItem.id);
		if (indexItem !== -1) elemPageInfo.innerHTML = (indexItem + 1) + ' / ' + itemIDs.length;
	}

	var ItemIds = {};

	// Initialisierung
	var elemsImageCarousel = document.getElementsByClassName('image-carousel');
	for (var iImageCarousel = 0; iImageCarousel < elemsImageCarousel.length; iImageCarousel++)
	{
		var elemImageCarousel = elemsImageCarousel[iImageCarousel];
		var idImageCarousel = 'image-carousel-' + iImageCarousel;
		elemImageCarousel.id = idImageCarousel;

		ItemIds[idImageCarousel] = [];

		var elemsItem = elemImageCarousel.children;
		for (var iItem = 0; iItem < elemsItem.length; iItem++)
		{
			var elemItem = elemsItem[iItem];
			elemItem.id = idImageCarousel + '-image-' + (iItem + 1);
			ItemIds[idImageCarousel].push(elemItem.id);
		}

		var elemPageInfo = document.createElement('div');

		var elemButtonLeft = document.createElement('button');
		elemButtonLeft.innerHTML = '&#xEA38;';
		elemButtonLeft.addEventListener('click', ScrollToItem.bind(this, elemImageCarousel, elemPageInfo, -1));

		var elemButtonRight = document.createElement('button');
		elemButtonRight.innerHTML = '&#xEA34;';
		elemButtonRight.addEventListener('click', ScrollToItem.bind(this, elemImageCarousel, elemPageInfo, 1));

		var elemPanel = document.createElement('div'); // Flex-Container
		elemPanel.appendChild(elemButtonLeft);
		elemPanel.appendChild(elemPageInfo);
		elemPanel.appendChild(elemButtonRight);

		elemImageCarousel.parentNode.insertBefore(elemPanel, elemImageCarousel.nextSibling);

		elemPageInfo.innerHTML = '1 / ' + ItemIds[idImageCarousel].length;
		elemImageCarousel.addEventListener('scroll', OnScroll.bind(this, elemImageCarousel, elemPageInfo));
	}
}

// Bildbetrachter (ImageViewer)
// - Es werden alle Bilder auf der Seite berücksichtigt, die innerhalb einer FIGURE mit der Klasse "viewer" enthalten sind (<figure class="viewer"><img>...),
//   oder die innerhalb einer FIGURE innerhalb eines DIV mit der Klasse "viewer" enthalten sind (<div class="viewer"><figure><img>...)
// - Wenn zusätzlich zum src-Attribut des Bildes ein "data-src"-Attribut angegeben ist, wird das dort angegebene Bild angezeigt (z.B. ein Bild in höherer Auflösung).
// - Eine innerhalb von <figcaption> angegebene Bildbeschreibung wird im Bildbetrachter unterhalb des Bildes angezeigt, sofern vorhanden.
// - Ein im alt-Attribut zum Bild angegebener Text wird gegenüber <figcaption> bevorzugt. Innerhalb des alt-Textes dürfen keine HTML-eigenen Zeichen wie <>" verwendet werden. Links und einige Textformatierungen können in BB-Code angegeben werden, z.B.: [url=bild.jpg]Bild[/url] [i]kursiv[/i] [b]fett[/b]
// - Bilder können durch Angabe eines name-Attributes gruppiert werden (z.B. <figure class="viewer" name="gruppe1">). So können mehrere "Alben" auf einer Seite angelegt werden.
// - Bilder innerhalb von FIGUREs innerhalb eines DIV mit der Klasse "viewer" werden automatisch gruppiert.
// - Bei Klick auf ein Bild öffnet sich der Bildbetrachter und zeigt das angeklickte Bild an.
// - Mittels Klick auf die Links/Rechts-Buttons, Drücken der Links/Rechts-Pfeiltasten oder Links/Rechts-Wischen wird das vorherige/nächste Bild innerhalb der Gruppe (des "Albums") angezeigt.
// - Leertaste oder Klick auf das Bild oder daneben blenden die Beschriftung aus/ein und dimmt die Buttons, so dass das Bild (fast) ohne störende Überlagerungen betrachtet werden kann.
// - Links oben befinden sich 3 Buttons mit folgenden Funktionen: 1.) Bild-Link in neuem Tab öffnen, 2.) Bild einpassen/vergrößern, 3.) Fullscreen-Modus. Die Einstellungen 2) + 3.) werden gespeichert.
// - Klick auf den X-Button oben rechts oder Drücken der Esc-Taste schließt den Bildbetrachter.

function InitImageViewer()
{
	var elemViewer = null;
	var elemImage, swipeX, elemDivCaption, elemButtonPrev, elemButtonNext, elemButtonClose, elemButtonNewTab, elemLinkNewTab, elemButtonFitImage, elemButtonFullScreen;

	var elemsArticle = document.getElementsByTagName('article');
	if (!elemsArticle) return;
	var elemArticle = elemsArticle[0];

	var name;
	var images = {}; // hash of arrays with image data per group (name)
	var imageCount = {}; // hash of integers with number of images per group (name)
	var imageIndex = 0;
	var totalImageCount = 0;

	// collect image data of all images inside a 'viewer' element
	var elems = elemArticle.getElementsByClassName('viewer');
	for (var i = 0; i < elems.length; i++)
	{
		var elem = elems[i];
		var elemName = elem.getAttribute('name');
		if (elem.tagName.toLowerCase() === 'figure')
			AddFigure(elem, elemName);
		else if (elem.tagName.toLowerCase() === 'div')
		{
			var elemsFigure = elem.getElementsByTagName('figure');
			for (var f = 0; f < elemsFigure.length; f++)
				AddFigure(elemsFigure[f], elemName ? elemName : 'group-div-' + (i + 1)); // if no name is given for the DIV, then create one
		}
	}

	function AddFigure(elemFigure, groupName)
	{
		name = groupName;
		var figcaption = '';
		var elemsCaption = elemFigure.getElementsByTagName('figcaption');
		if (elemsCaption.length > 0) figcaption = elemsCaption[0].innerHTML;

		var elemsImage = elemFigure.getElementsByTagName('img');
		for (var j = 0; j < elemsImage.length; j++)
		{
			var elemImage = elemsImage[j];
			var src = elemImage.getAttribute('data-src');
			if (!src || src === '') src = elemImage.getAttribute('src');
			if (!src || src === '') continue;

			var caption = elemImage.getAttribute('alt');
			if (!caption) caption = '';
			if (caption !== '')
			{
				caption = caption.replace(/\[url=([^\]]*)\]([^\[]*)\[\/url\]/g, '<a href="$1" target="_blank" rel="noopener">$2</a>');
				caption = caption.replaceAll('[', '<');
				caption = caption.replaceAll(']', '>');
			}
			if (caption === '' && figcaption) caption = figcaption;

			if (images[name] === undefined) images[name] = [];
			if (imageCount[name] === undefined) imageCount[name] = 0;

			images[name].push({ 'src': src, 'caption': caption });

			// surround the image with a new div to display an overlay icon
			var elemDivImage = document.createElement('div');
			elemDivImage.classList.add('viewer-img-container');
			elemImage.replaceWith(elemDivImage);
			elemDivImage.appendChild(elemImage);

			elemDivImage.addEventListener('click', Viewer.bind(this, { 'name': name, 'imageIndex': imageCount[name] }, null), false);
			elemDivImage.style.cursor = 'zoom-in';

			imageCount[name]++;
			totalImageCount++;
		}
	}

	if (totalImageCount > 0)
		SetupViewer();

	function SetupViewer()
	{
		// create viewer
		elemViewer = document.createElement('div');
		elemViewer.classList.add('view');
		elemViewer.classList.add('click-target');
		elemViewer.addEventListener('click', Toggle, false);

		elemImage = document.createElement('img');
		elemImage.classList.add('click-target');
		elemViewer.appendChild(elemImage);

		elemDivCaption = document.createElement('div');
		elemDivCaption.classList.add('caption');
		elemViewer.appendChild(elemDivCaption);

		elemButtonPrev = document.createElement('button');
		elemButtonPrev.innerHTML = '&#xEA38;';
		elemButtonPrev.classList.add('prev');
		elemButtonPrev.addEventListener('click', function() { Prev(1); }, false);
		elemViewer.appendChild(elemButtonPrev);

		elemButtonNext = document.createElement('button');
		elemButtonNext.innerHTML = '&#xEA34;';
		elemButtonNext.classList.add('next');
		elemButtonNext.addEventListener('click', function() { Next(1); }, false);
		elemViewer.appendChild(elemButtonNext);

		elemButtonClose = document.createElement('button');
		elemButtonClose.innerHTML = '&#xEA0F;';
		elemButtonClose.classList.add('close');
		elemButtonClose.addEventListener('click', Close, false);
		elemViewer.appendChild(elemButtonClose);

		elemButtonNewTab = document.createElement('button');
		elemButtonNewTab.innerHTML = '&#xEA7E;';
		elemButtonNewTab.classList.add('newtab');
		elemLinkNewTab = document.createElement('a');
		elemLinkNewTab.setAttribute('target', '_blank');
		elemLinkNewTab.appendChild(elemButtonNewTab);
		elemViewer.appendChild(elemLinkNewTab);

		elemButtonFitImage = document.createElement('button');
		elemButtonFitImage.innerHTML = '&#xE987;';
		elemButtonFitImage.classList.add('fitimg');
		elemButtonFitImage.addEventListener('click', FitImage, false);
		elemViewer.appendChild(elemButtonFitImage);

		elemButtonFullScreen = document.createElement('button');
		elemButtonFullScreen.innerHTML = '&#xE989;';
		elemButtonFullScreen.classList.add('fullscreen');
		elemButtonFullScreen.addEventListener('click', FullScreen, false);
		elemViewer.appendChild(elemButtonFullScreen);

		document.body.appendChild(elemViewer);

		FitImage(null);

		// keyboard shortcuts
		document.documentElement.addEventListener('keydown', function(event)
		{
			if (!elemViewer.classList.contains('open'))
				return;

			if (event.code === 'ArrowLeft' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Prev(1); }
			else if (event.code === 'ArrowRight' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Next(1); }
			else if (event.code === 'PageUp' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Prev(10); }
			else if (event.code === 'PageDown' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Next(10); }
			else if (event.code === 'Home' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Prev(1000000); }
			else if (event.code === 'End' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Next(1000000); }
			else if (event.code === 'Space' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Toggle(); }
			else if (event.code === 'Escape' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Close(); }
		});

		// swipe actions
		var touchStartX, touchEndX, touchStartY, touchEndY;
		elemViewer.addEventListener('touchstart', function(evt)
		{
			touchStartX = evt.changedTouches[0].screenX;
			touchStartY = evt.changedTouches[0].screenY;
		});
		elemViewer.addEventListener('touchend', function(evt)
		{
			touchEndX = evt.changedTouches[0].screenX;
			touchEndY = evt.changedTouches[0].screenY;
			if (imageCount[name] > 1 && Math.abs(touchEndX - touchStartX) * 0.577 > Math.abs(touchEndY - touchStartY)) // horizontal swipe angle > 30°
			{
				swipeX = touchEndX - touchStartX;
				if (touchEndX < touchStartX - 20) Next(1);
				else if (touchEndX > touchStartX + 20) Prev(1);
				else swipeX = 0, elemImage.style.left = '0';
			}
			else elemImage.style.left = '0';
		});
		elemViewer.addEventListener('touchmove', function(evt)
		{
			var touchMoveX = evt.touches[0].screenX;
			var touchMoveY = evt.touches[0].screenY;
			elemImage.style.left = '' + (touchMoveX - touchStartX) + 'px';
		});
		elemViewer.addEventListener('touchcancel', function(evt)
		{
			elemImage.style.left = '0';
		});
	}

	function Viewer(imageData, callback)
	{
		if (imageData) // Bild wurde angeklickt
		{
			Open();
			name = imageData.name;
			imageIndex = imageData.imageIndex;
			Toggle(null, true);
			elemButtonPrev.style.visibility = elemButtonNext.style.visibility = (imageCount[name] > 1 ? 'visible' : 'hidden');
		}

		swipeX = 0;
		elemImage.addEventListener('load', callback ? callback : function() { elemImage.style.visibility = 'visible'; }, { once: true });
		elemImage.src = images[name][imageIndex].src;
		elemLinkNewTab.setAttribute('href', elemImage.src);

		var caption = images[name][imageIndex].caption;
		var hasCaption = (caption !== '');
		if (hasCaption) caption += ' (';
		caption += (imageIndex + 1) + '/' + imageCount[name];
		if (hasCaption) caption += ')';
		elemDivCaption.innerHTML = caption;
	}

	function Prev(amount)
	{
		var i = imageIndex;
		i -= amount;
		if (i < 0)
			i = (amount === 1 ? imageCount[name] -1 : 0);
		if (i !== imageIndex)
		{
			imageIndex = i;
			MoveImage(0, 1, function()
			{
					Viewer(null, function() { MoveImage(-1, 0); });
			});
		}
	}

	function Next(amount)
	{
		var i = imageIndex;
		i += amount;
		if (i >= imageCount[name])
			i = (amount === 1 ?  0 : imageCount[name] - 1);
		if (i !== imageIndex)
		{
			imageIndex = i;
			MoveImage(0, -1, function()
			{
					Viewer(null, function() { MoveImage(1, 0); });
			});
		}
	}

	function Toggle(event, visible)
	{
		if (event && !event.target.classList.contains('click-target'))
			return;
		
		if (visible === undefined)
			visible = (elemDivCaption.style.opacity === '0');

		elemDivCaption.style.opacity = (visible ? '1.0' : '0');
		var opacity = (visible ? '1.0' : '0.2');
		elemButtonPrev.style.opacity = opacity;
		elemButtonNext.style.opacity = opacity;
		elemButtonClose.style.opacity = opacity;
		elemButtonNewTab.style.opacity = opacity;
		elemButtonFitImage.style.opacity = opacity;
		elemButtonFullScreen.style.opacity = opacity;
	}

	function FitImage(evt)
	{
		var fit = (LoadSetting('ImageViewer_FitImage') === 'true' ? true : false);
		if (evt) fit = !fit;
		SaveSetting('ImageViewer_FitImage', fit ? 'true' : '', '', false, FitImage, [evt]);
		if (fit)
		{
			elemImage.classList.add('fit');
			elemButtonFitImage.innerHTML = '&#xE988;';
		}
		else
		{
			elemImage.classList.remove('fit');
			elemButtonFitImage.innerHTML = '&#xE987;';
		}
	}

	function FullScreen(evt)
	{
		var fullscreen = (LoadSetting('ImageViewer_FullScreen') === 'true' ? true : false);
		if (evt) fullscreen = !fullscreen;
		SaveSetting('ImageViewer_FullScreen', fullscreen ? 'true' : '', '', false, FullScreen, [evt]);
		if (fullscreen)
		{
			OpenFullscreen(elemViewer);
			elemButtonFullScreen.innerHTML = '&#xE98A;';
		}
		else
		{
			CloseFullscreen();
			elemButtonFullScreen.innerHTML = '&#xE989;';
		}
	}

	function Open()
	{
		elemViewer.classList.add('open');
		document.documentElement.classList.add('noscroll');
		FullScreen(null);
	}

	function Close()
	{
		elemViewer.classList.remove('open');
		elemImage.style.visibility = 'hidden';
		document.documentElement.classList.remove('noscroll');
		CloseFullscreen();
	}

	function OpenFullscreen(elem)
	{
		if (elem.requestFullscreen)
			elem.requestFullscreen();
		else if (elem.webkitRequestFullscreen) /* Safari */
			elem.webkitRequestFullscreen();
		else if (elem.msRequestFullscreen) /* IE11 */
			elem.msRequestFullscreen();
	}

	function CloseFullscreen()
	{
		if (document.exitFullscreen)
			document.exitFullscreen().catch(function(){});
		else if (document.webkitExitFullscreen) /* Safari */
			document.webkitExitFullscreen();
		else if (document.msExitFullscreen) /* IE11 */
			document.msExitFullscreen();
	}

	function MoveImage(from, to, callback)
	{
		var stepCount = 10;
		var stepTime = 10; // milliseconds

		var docWidth  = document.documentElement.clientWidth;
		from *= docWidth; from += swipeX; to *= docWidth;
		var stepWidth = docWidth / stepCount;

		if (to < from) MoveImageLeft(from, to, callback);
		else if (to > from) MoveImageRight(from, to, callback);

		function MoveImageLeft(from, to, callback)
		{
			if (from < to) from = to;
			elemImage.style.left = '' + from + 'px';
			if (to < from)
			{
				from -= stepWidth;
				setTimeout(function() { MoveImageLeft(from, to, callback); }, stepTime);
			}
			else if (callback) (callback)();
		}

		function MoveImageRight(from, to, callback)
		{
			if (from > to) from = to;
			elemImage.style.left = '' + from + 'px';
			if (to > from)
			{
				from += stepWidth;
				setTimeout(function() { MoveImageRight(from, to, callback); }, stepTime);
			}
			else if (callback) (callback)();
		}
	}
}

// Bildraster (ImageGrid)
// Ordnet die enthaltenen Bilder automatisch in Form eines Rasters/Gitters an.
// Die Anzahl der Spalten hängt von der verfügbaren Breite ab.
// Die Bilder (Vorschaubilder/Thumbnails) sind quadratisch und haben eine Mindestgröße von 200px.
// Für Bilder, die sich innerhalb einer FIGURE befinden, wird der Bildbetrachter (ImageViewer) aktiviert.
// Eine Bildbeschreibung in der FIGCAPTION wird nicht angezeigt, jedoch vom Bildbetrachter (ImageViewer) benutzt
// und außerdem als TITLE gesetzt, so dass er beim Darüberhalten des Mauszeigers als Tooltip erscheint.
// Anwendung:
//	<div class="image-grid cover viewer">
//		<figure><img src="..."><figcaption>...</figcaption></figure>
//		...
//	</div>
// Die optionale Klasse "cover" bewirkt, dass die Bildquadrate vollständig ausgefüllt (und dazu ggf. beschnitten) werden.
// Ohne die Klasse "cover" werden die Bilder vollständig angezeigt, aber ggf. verkleinert und mit "Füll-Hintergrund".

function InitImageGrid()
{
	var elemsArticle = document.getElementsByTagName('article');
	if (!elemsArticle) return;
	var elemArticle = elemsArticle[0];

	var elemsImageGrid = elemArticle.getElementsByClassName('image-grid');
	for (var i = 0; i < elemsImageGrid.length; i++)
	{
		var elemImageGrid = elemsImageGrid[i];

		var elemsFigure = elemImageGrid.getElementsByTagName('figure');
		for (var f = 0; f < elemsFigure.length; f++)
		{
			var elemFigure = elemsFigure[f];
			var elemsFigCaption = elemFigure.getElementsByTagName('figcaption');
			if (elemsFigCaption)
				elemFigure.setAttribute('title', elemsFigCaption[0].innerText);
		}
	}
}

// Automatisches Inhaltsverzeichnis für Artikel erstellen
// - Erstellt anhand der Überschriften (H1...H6) innerhalb eines Artikels ein Inhaltsverzeichnis.
// - Es werden nur Überschriften berücksichtigt, die eine ID haben. Die ID wird als Sprungziel verwendet.
// - Das erzeugte Inhaltsverzeichnis wird in Elemente mit der Klasse "toc" eingefügt.
// - Es werden nur Elemente in das Inhaltsverzeichnis eingefügt, deren Attribute "lang" (falls vorhanden) denselben Wert haben.
// - Wenn das Element mit der Klasse "toc" ein Attribut "data-list-tag" enthält, wird dieses anstelle von "ol" verwendet.
// - Per CSS kann das Inhaltsverzeichnis über den Selektor ".toc ol" gestyled werden.
function InitTableOfContents()
{
	var elemsArticle = document.getElementsByTagName('article');
	for (var a = 0; a < elemsArticle.length; a++)
	{
		var elemArticle = elemsArticle[a];
		var elemsTOC = elemArticle.getElementsByClassName('toc');
//		var html = '';
		for (var d = 0; d < elemsTOC.length; d++)
		{
			var elemTOC = elemsTOC[d];
			var langTOC = elemTOC.getAttribute('lang');
//			if (html === '') html = CreateTOC(elemArticle, langTOC);
//			elemTOC.innerHTML += html;
			elemTOC.innerHTML += CreateTOC(elemArticle, langTOC);
		}
	}

	function CreateTOC(elemArticle, langTOC)
	{
		var elems = elemArticle.getElementsByTagName('*');
		var levelStart = -1
		var level = levelStart;
		var html = '';
		var ids = [];
		for (var i = 0; i < elems.length; i++)
		{
			var elem = elems[i];
			var tagName = elem.tagName;
			var matches = tagName.match(/^H([1-6])$/);
			if (matches)
			{
				if (!elem.id) continue;
				var langElem = elem.getAttribute('lang');
				if (langTOC && langElem && langTOC !== langElem) continue;
				var txtDuplicateId = '';
				if (ids.includes(elem.id))
					txtDuplicateId = '<span class="error"> &ndash; DUPLICATE ID: ' + elem.id + '</span>';
				else
					ids.push(elem.id);

				var levelNew = matches[1];
				if (levelStart === -1)
				{
					levelStart = levelNew - 1;
					level = levelStart;
				}

				if (level < levelNew)
				{
					while (level < levelNew)
					{
						html += '<ol><li>';
						level++;
					}
					html += '<a href="#' + elem.id + '">' + elem.innerText + '</a>' + txtDuplicateId;
				}
				else
				{
					while (level > levelNew)
					{
						html += '</li></ol>';
						level--;
					}
					html += '</li><li><a href="#' + elem.id + '">' + elem.innerText + '</a>' + txtDuplicateId;
				}
			}
		}

		while (level > levelStart)
		{
			html += '</li></ol>';
			level--;
		}

		return html;
	}
}

function InitTranslateLink()
{
	if (window.location.hostname.includes('.translate.goog')) return;

	var elemsTranslate = document.getElementsByClassName('translate');
	for (var i = 0; i < elemsTranslate.length; i++)
	{
		var elemTranslate = elemsTranslate[i];
		var sourceLang = elemTranslate.getAttribute('x-data-translate-from');
		if (sourceLang !== null)
		{
			var url = GetGoogleTranslateUrl(sourceLang);
			if (url)
			{
				var linkText = elemTranslate.innerHTML;
				if (!linkText) linkText = '<span style="position: relative; top: -0.1em; font-size: initial;">文</span><span style="position: relative; top: 0.1em; left: -0.1em; font-size: initial;">A</span> <?php echo GetString("TranslateThisPage");?>';
				elemTranslate.innerHTML = '<a href="' + url + '" class="button-link">' + linkText + '</a>';
				elemTranslate.classList.remove('hidden');
			}
		}
	}
}

function GetGoogleTranslateUrl(sourceLang, url)
{
	if (!sourceLang) sourceLang = document.documentElement.getAttribute('lang');

	var userLang = navigator.language.split(/[-_]/)[0];
	if (sourceLang === userLang) return null; // no translation needed

	// url = 'http://dev.gottsucher.de/translate/test#anchor'
	if (url)
	{
		var [prot, uri]  = url.split('//'); // -> prot = 'http:', uri = 'dev.gottsucher.de/translate/test#anchor'
		var [host, path] = uri.split(/\/(.*)/); // -> host = 'dev.gottsucher.de', path = 'translate/test#anchor'
		var [path, hash] = path.split('#'); // -> path = 'translate/test', hash = 'anchor'
		path = '/' + path; // -> path = '/translate/test'
		hash = hash ? '#' + hash : ''; // -> hash = '#anchor'
	}
	else
	{
		var prot = window.location.protocol; // -> prot = 'http:'
		var host = window.location.hostname; // -> host = 'dev.gottsucher.de'
		var path = window.location.pathname; // -> path = '/translate/test'
		var hash = window.location.hash; // -> hash = '#anchor'
	}

	var subdomain = host.replaceAll('.', '-'); // -> subdomain = 'dev-gottsucher-de'

	var params = [];
	if (prot === 'http:') params.push('_x_tr_sch=http');
	params.push('_x_tr_sl=' + sourceLang);
	params.push('_x_tr_tl=' + userLang);
	params.push('_x_tr_hl=' + userLang);
	params.push('_x_tr_pto=wapp');

	var url = 'https://' + subdomain + '.translate.goog' + path + '?' + params.join('&') + hash;

	return url;
}

// Artikeltext verschönern
function PrettifyText()
{
	if (typeof g_noPrettify !== 'undefined' && g_noPrettify === true) return;
	var elemsArticle = document.getElementsByTagName('article');
	for (var a = 0; a < elemsArticle.length; a++)
	{
		var elemArticle = elemsArticle[a];
		var html = elemArticle.innerHTML.split(''); // HTML-Array mit den Zeichen des Artikeltextes inklusive aller HTML-Tags
		var text = []; // Text-Array mit den Zeichen des Artikeltextes ohne die HTML-Tags (nur der reine Text)
		var text2html = []; // Zuordnungs-Array für die Indizes der Zeichen im Text-Array zu denen im HTML-Array

		// 1. Durchgang: Den Artikeltext von den HTML-Tags befreien und in das Text-Array kopieren; dabei das Zuordnungs-Array erstellen
		var bTag = false; // innerhalb eines HTML-Tags
		var bIgnore = false; // innerhalb eines zu ignorierenden Abschnittes des HTML-Textes
		var tag = ''; // aktuell gelesenes HTML-Tag
		var n = html.length;
		for (var i = 0; i < n; i++)
		{
			var c = html[i];
			if (c === '<') // Beginn des HTML-Tags
			{
				bTag = true;
				tag = '';
			}
			else if (c === '>') // Ende des HTML-Tags
			{
				bTag = false;
				// JavaScript-, Style- und Code-Abschnitte ignorieren:
				if (tag === 'script' || tag === 'style' || tag === 'pre' || tag === 'code' || tag.substring(0, 4) === 'pre ' || tag.substring(0, 5) === 'code ')
				{
					bIgnore = true;
				}
				else if (tag === '/script' || tag === '/style' || tag === '/pre' || tag === '/code')
				{
					bIgnore = false;
				}
				// Besondere HTML-Tags, die für den 2. Durchgang berücksichtigt werden müssen:
				else if (tag === 'br')
				{
					text.push("\n");
					text2html.push(-1);
				}
			}
			else if (bTag) // Innerhalb des HTML-Tags
			{
				tag += c;
			}
			else if (!bIgnore) // Außerhalb von HTML-Tags und zu ignorierender Abschnitte
			{
				text.push(c);
				text2html.push(i);
			}
		}

		// 2. Durchgang: Das Text-Array durchlaufen, die zu ersetzenden Zeichen bestimmen und ins HTML-Array zurückschreiben
		var quotes = 0;
		var charsBeforeDash = "\r\n\t ";
		var charsAfterDash = "\r\n\t ,;";
		var n = text.length;
		for (var i = 0; i < n; i++)
		{
			var cBefore = (i > 0) ? text[i - 1] : '';
			var cCurrent = text[i];
			var cAfter = (i < n - 1) ? text[i + 1] : '';
			var c = cCurrent;

			// Gerade Anführungszeichen durch "typographische" (öffnende und schließende) Anführungszeichen ersetzen.
			// Es wird vorausgesetzt, dass auf ein öffnendes immer auch ein schließendes Anführungszeichen folgt.
			// Verschachtelungen von Texten in Anführungszeichen sind mit diesem einfachen Algorithmus nicht möglich. Aus
			// "Machen Sie Gottes Wort zu Ihrer "Gedankennahrung"!" wird daher
			// „Machen Sie Gottes Wort zu Ihrer “Gedankennahrung„!“ und nicht
			// „Machen Sie Gottes Wort zu Ihrer „Gedankennahrung“!“ (wie es eigentlich gemeint ist).
			if (c === '"')
			{
				c = (quotes % 2) ? '<?php echo GetString("Typo.DoubleQuoteClosing");?>' : '<?php echo GetString("Typo.DoubleQuoteOpening");?>';
				quotes++;
			}
			// Hochkommata durch Apostrophe ersetzen
			else if (c === "'")
			{
				c = '<?php echo GetString("Typo.Apostrophe");?>';
			}
			// Bindestriche durch Gedankenstriche ersetzen
			else if (c === "-")
			{
				// html = html.replace(/([ >])-([ <])/g, '$1 – $2');
				if ((cBefore == '' || charsBeforeDash.indexOf(cBefore) != -1) && (cAfter  == '' || charsAfterDash.indexOf(cAfter) != -1))
				{
					c = '<?php echo GetString("Typo.Dash");?>';
				}
			}

			if (c !== cCurrent)
				html[text2html[i]] = c;
		}

		// Das verschönerte HTML ins DOM schreiben
		elemArticle.innerHTML = html.join('');
	}
}

//
// Audio-Player initialisieren
//

function InitAudioPlayer()
{
	var elemsPlayer = document.getElementsByTagName('audio');

	for (var i = 0; i < elemsPlayer.length; i++)
	{
		var elemPlayer = elemsPlayer[i];

		elemPlayer.addEventListener('play', function() {
			PlayerStop(this);
		}, false);

		elemPlayer.addEventListener('playing', function() {
			this.classList.add("playing");
		}, false);

		elemPlayer.addEventListener('pause', function() {
			this.classList.remove("playing");
		}, false);

		elemPlayer.addEventListener('ended', function() {
			this.classList.remove("playing");
			PlayerNext(this);
		}, false);
	}
}

function PlayerStop(elemExcept)
{
	var elemsPlayer = document.getElementsByTagName('audio');

	for (var i = 0; i < elemsPlayer.length; i++)
	{
		var elemPlayer = elemsPlayer[i];
		if(elemExcept === undefined || elemExcept !== elemPlayer)
		{
			elemPlayer.pause();
			if (elemPlayer.currentTime > 0) // Workaround für MSIE11
				elemPlayer.currentTime = 0;
		}
	}
}

function PlayerNext(elemCurrent)
{
//	var elemsPlayer = document.getElementsByTagName('audio');
	var elemsPlayer = document.querySelectorAll('figure.playlist audio');

	for (var i = 0; i < elemsPlayer.length; i++)
	{
		var elemPlayer = elemsPlayer[i];
		if(elemCurrent === elemPlayer)
		{
			var elemPlayerNext = elemsPlayer[i + 1];
			if (elemPlayerNext !== undefined)
			{
				elemPlayerNext.play();
				return;
			}
		}
	}
}

function InitStats()
{
	if (LoadSetting('InitStats', '', true) === 'skip') return;
	var xhr = new XMLHttpRequest();
	xhr.open('POST', 'lib/cgi-bin/stats.pl', true);
	xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	xhr.send
	(
		'target=' + encodeURIComponent(window.location) +
		'&referrer=' + encodeURIComponent(document.referrer) +
		'&language=' + encodeURIComponent(navigator.language) +
		'&winsize=' + encodeURIComponent(window.innerWidth + 'x' + window.innerHeight) +
		'&userAgent=' + encodeURIComponent(navigator.userAgent) +
		'&errorCode=' + '<?php echo get_cats() === "error" ? get_tags() : ""; ?>' +
		'&postVars=' + '<?php echo GetPostDataAsBase64();?>'
	);
}

function InitExternalContent()
{
	var settingLoadExternalContent = (LoadSetting('LoadExternalContent', '', true) === 'true');
	var elemsIframe = document.getElementsByTagName('iframe');
	var dataSources = [];
	for (var i = 0; i < elemsIframe.length; i++)
	{
		var elemIframe = elemsIframe[i];
		var dataSrc = dataSources[i] = elemIframe.getAttribute('data-src');
		var done = elemIframe.getAttribute('done');
		if (dataSrc && !done)
		{
			elemIframe.removeAttribute('loading'); // ein ggf. vorhandenes Attribut loading="lazy" entfernen
			if (settingLoadExternalContent)
			{
				elemIframe.contentWindow.location.replace(dataSrc);
				elemIframe.setAttribute('done', 'true');
				continue;
			}
			var hostname = new URL(dataSrc).hostname;
			var bgimage = elemIframe.getAttribute('data-img') || '';
			if (bgimage === '')
				bgimage = 'media/' + new URL(dataSrc).pathname.split('/').reverse()[0] + '.jpg';
			if (!bgimage.match(/^http/))
				bgimage = '<?php echo $baseUrl; ?>/' + bgimage;
			var html = '\
				<!DOCTYPE html>\
				<html lang="<?php echo $lang; ?>">\
				<head>\
					<meta charset="UTF-8" />\
					<meta name="viewport" content="width=device-width, initial-scale=1.0" />\
					<style>\
						body {\
							color: white;\
							background-image: linear-gradient(rgba(255, 255, 255, 0.0), rgba(255, 255, 255, 0.0)), url(\'' + bgimage + '\');\
							background-repeat: no-repeat;\
							background-size: cover;\
							font-family: sans-serif;\
							user-select: none;\
							-webkit-tap-highlight-color: transparent;\
							margin: 10px;\
						}\
						h3, p, button {\
							margin: 8px 0;\
						}\
						#container {\
							display: flex;\
							width: 100%;\
							height: calc(100vh - 2 * 10px);\
							align-items: center;\
							justify-content: center;\
							flex-direction: column;\
							text-align: center;\
						}\
						#content {\
							padding: 0 0.5em;\
							background-color: #00000080;\
							backdrop-filter: blur(5px);\
						}\
						span.button {\
							background-color: #00C000;\
							text-decoration: none;\
							line-height: 1.25em;\
							margin-top: 0.5em;\
							margin-bottom: 0.5em;\
							border-radius: 9999px;\
							padding: calc(0.5em + 2px) calc(1em + 2px);\
							font-size: 1.125em;\
							cursor: pointer;\
							display: inline-block;\
						}\
						span.button:hover {\
							background-color: #00EA00;\
						}\
						button.link {\
							display: inline;\
							border: none;\
							outline: none;\
							background: none;\
							padding: 0;\
							margin: 0;\
							font-family: inherit;\
							font-size: inherit;\
							color: gold;\
							text-decoration: underline;\
							cursor: pointer;\
						}\
						@media screen and (min-width: 401px) {\
						.smallscreen {\
							display: none;\
						}}\
						@media screen and (max-width: 400px) {\
						.bigscreen {\
							display: none;\
						}\
						h2, p, button {\
							margin: 4px 0;\
						}\
						body {\
							font-size: 0.9em;\
						}}\
					</style>\
				</head>\
				<body>\
					<div id="container">\
					<div id="content">\
						<h3 class="bigscreen"><?php echo GetString("ExternalContent");?></h3>\
						<p><?php echo GetString("SectionHasExternalContent");?> <b>' + hostname + '</b>.</p>\
						<span class="button" onclick="window.parent.postMessage({\
							type: \'load-content\',\
							payload: {\
								index: ' + i + ',\
								save: document.getElementById(\'save\').checked\
							}\
						}, \'*\');"><?php echo GetString("ShowExternalContent");?></span>\
						<p><input type="checkbox" id="save"><label for="save"><span class="bigscreen"><?php echo GetString("AlwaysShowExternalContent");?></span><span class="smallscreen"><?php echo GetString("AlwaysShowExternalContentShort");?></span></label></p>\
						<form action="<?php echo $baseUrl; ?>/<?php echo $privacyLink;?>#external-content" method="post" target="_blank"><p><button class="link" type="submit" name="token" value="<?php echo $token;?>"><?php echo GetString("PrivacyPolicy");?></button></p></form>\
					</div>\
					</div>\
				</body>';
			var src = 'data:text/html;base64,' + window.btoa(encodeURIComponent(html).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) { return String.fromCharCode('0x' + p1); }));
			elemIframe.contentWindow.location.replace(src);
		}
	}

	window.addEventListener('message', (evt) => {
		if(evt.data.type && evt.data.type === 'load-content')
		{
			var index = evt.data.payload.index;
			var save = evt.data.payload.save;
			if (save)
			{
				SaveSetting('LoadExternalContent', 'true', '', true, InitExternalContent);
				InitExternalContent();
			}
			else
			{
				var elemIframe = elemsIframe[index];
				var dataSrc = dataSources[index];
				//elemIframe.setAttribute('src', dataSrc); // erzeugt History-Eintrag, daher besser so:
				elemIframe.contentWindow.location.replace(dataSrc);
				elemIframe.setAttribute('done', 'true');
			}
		}
	}, false);
}

// BEGINN der Funktionen für persistente Einstellungen

function OnChangeSetting(id, settingValue)
{
	var elemSetting = document.getElementById(id);
	if (!elemSetting) return;
	var settingName = elemSetting.getAttribute('data-name');
	var settingValueSet = elemSetting.getAttribute('data-value-set');

	if (settingValue === settingValueSet)
	{
		if (settingName === 'AllowCookies' || localStorage.getItem('AllowCookies') === 'yes')
			localStorage.setItem(settingName, settingValueSet);
		else
			Keksdialog(OnChangeSetting, [id, settingValue]);
	}
	else
	{
		if (settingName === 'AllowCookies')
		{
			for (var i = 0; i < g_Cookies.length; i++)
				localStorage.removeItem(g_Cookies[i]);
		}
		else
			localStorage.removeItem(settingName);
	}

	EnableControlsSettings();
	g_BroadcastChannel.postMessage('EnableControlsSettings');
}

function EnableControlsSettings()
{
	var elemsSetting = document.getElementsByClassName('settings');
	for (var i = 0; i < elemsSetting.length; i++)
	{
		var elemSetting = elemsSetting[i];

		var settingName = elemSetting.getAttribute('data-name');
		var settingValueSet = elemSetting.getAttribute('data-value-set');

		var settingValue = localStorage.getItem(settingName);
		document.getElementById(elemSetting.id + '-set').checked = (settingValue === settingValueSet);
		document.getElementById(elemSetting.id + '-unset').checked = (settingValue !== settingValueSet);
	}
}

function InitSettings()
{
	var elemSetting = document.createElement('div');
	elemSetting.id = 'keksdialog';

	elemSetting.innerHTML = `
	<div>
		<p class="center bigger"><b><?php echo GetString("CookieDialogTitle"); ?></b></p>
		<p><?php printf(GetString("CookieDialogText"), GetInternalProtectedLink("$privacyLink#setting-cookies", GetString("CookieDialogText_Link"), GetString("PrivacyPolicy"), '_blank'));?></p>
		<p class="center">
			<span id="button-i-love-cookies" class="button-link"><?php echo GetString("CookieDialogButtonYes"); ?></span>
			<span id="button-i-hate-cookies" class="button-link"><?php echo GetString("CookieDialogButtonNo"); ?></span>
		</p>
	</div>
`
	document.body.appendChild(elemSetting);

	window.addEventListener('focus', EnableControlsSettings);
	g_BroadcastChannel.addEventListener('message', function(event) { if (event.data === 'EnableControlsSettings') EnableControlsSettings(); } );
	EnableControlsSettings();
}

function Keksdialog(callback1, args1, callback2, args2 = [])
{
	var elemKeksdialog = document.getElementById('keksdialog');
	if (!elemKeksdialog) return;

	elemKeksdialog.classList.add('show');
	//document.documentElement.classList.add('noscroll');
	document.getElementById('button-i-love-cookies').addEventListener('click', function() { AcceptCookies(true); });
	document.getElementById('button-i-hate-cookies').addEventListener('click', function() { AcceptCookies(false); });

	function AcceptCookies(yes)
	{
		elemKeksdialog.classList.remove('show');
		//document.documentElement.classList.remove('noscroll');

		if (yes)
		{
			localStorage.setItem('AllowCookies', 'yes');
			if (callback1) (callback1(...args1));
			if (callback2) (callback2(...args2)); // Stand ursprünglich vor callback1, aber dann funktioniert InitExternalContent nicht. Testweise vertauscht.
		}
		else
		{
			for (var i = 0; i < g_Cookies.length; i++)
				localStorage.removeItem(g_Cookies[i]);
		}

		EnableControlsSettings();
		g_BroadcastChannel.postMessage('EnableControlsSettings');
	}
}

function SaveSetting(settingName, settingValue, defaultValue, persist, callback, args)
{
	g_Settings[settingName] = settingValue;
	if (!settingValue || settingValue === defaultValue)
		localStorage.removeItem(settingName);
	else if (settingName === 'AllowCookies' || localStorage.getItem('AllowCookies') === 'yes')
		localStorage.setItem(settingName, settingValue);
	else if (persist)
		Keksdialog(SaveSetting, [settingName, settingValue], callback, args);
}

function LoadSetting(settingName, defaultValue, persist)
{
	if(persist || localStorage.getItem('AllowCookies') === 'yes')
	{
		var settingValue = localStorage.getItem(settingName) || defaultValue;
		g_Settings[settingName] = settingValue;
		return settingValue;
	}
	else
		return g_Settings[settingName] || defaultValue;
}

// ENDE der Funktionen für persistente Einstellungen

function FromFratzenbuch()
{
	return window.location.href.includes('fbclid') || document.referrer.includes('facebook.com');
}

function InitFratzenbuch()
{
	if (FromFratzenbuch())
	{
		var elemsLink  = document.getElementsByTagName('A');
		for (var i = 0; i < elemsLink.length; i++)
		{
			var elemLink = elemsLink[i];
			var href = elemLink.getAttribute('href');
			if (href && (href.substring(0,4) !== 'http' || href.includes('/' + window.location.hostname)))
			{
				href += href.includes('?') ? '&fbclid' : '?fbclid';
				elemLink.setAttribute('href', href);
			}
		}
	}
}

// Nachdem alle Ressourcen (Bilder etc.) geladen sind, nochmal zum ggf. angegebenen Anker (#id) scrollen
// und dabei auch gleich ein ggf. vorhandenes DETAILS-Element öffnen:

function InitScrollToAnchor()
{
	window.addEventListener('load', function()
	{
		var hash = window.location.hash;
		if (hash)
		{
			var elemAnchor = document.getElementById(hash.substring(1));
			if (elemAnchor && elemAnchor.tagName === 'DETAILS')
				elemAnchor.setAttribute('open', 'open');
			elemAnchor.scrollIntoView();
		}
	});
}

// Wenn der Artikel JavaScript enthält, das Event-Handler hinzufügt, dann darf dieses erst nach PrettifyText() aufgerufen werden.
// Hierzu im Artikel die Funktion OnLoadLocal() definieren und die Initialisierungen dort vornehmen.

function InitArticle()
{
	if (typeof OnLoadLocal === "function")
		OnLoadLocal();
}
