mylittleforum/js/main.js

1511 lines
45 KiB
JavaScript

/***********************************************************************
* MyLittleJavaScript *
************************************************************************
* Created by Michael Loesler <https://github.com/loesler> *
* *
* This script is part of my little forum <https://mylittleforum.net> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***********************************************************************/
/***********************************************************************
* NOTICE: In order to reduce bandwidth usage, a minimized version of *
* this script is used by default (main.min.js). Changes in this file *
* do not have any effect unless it is loaded by the template *
* (themes/[THEME FOLDER]/main.tpl). *
* The minimized version was created with the YUI Compressor *
***********************************************************************/
/**
* Returns the CSS properties, e.g. the text color, of the element
*
* @param el
* @param propertyName
* @return propertyValue
*/
document.getStyle = function(el,propertyName) {
return document.defaultView.getComputedStyle(el,null).getPropertyValue(propertyName);
};
/**
* Pre-load images of a given file path.
* Please note: This fuction needs runtime and should, thus,
* be called at the end of ONLOAD
*
* @param images
* @param path
*/
document.preloadImages = function(images, path) {
if (typeof images != "object")
images = [images];
path = path || "";
var img = [];
for(var i = 0; i<images.length; i++) {
img[i] = new Image();
img[i].src = path + images[i];
}
};
/**
* Returns the element, which fires the event
* @return target
*/
document.getTarget = function(e) {
e = e || window.event;
return e.target || e.srcElement || false;
};
/**
* Checks, if an element contains an element
* @return contains
* @see http://forum.de.selfhtml.org/archiv/2010/2/t195270/#m1306879
*/
if (window.Node && Node.prototype && !Node.prototype.contains) {
Node.prototype.contains = function (arg) {
try {
return !!(this.compareDocumentPosition(arg) & 16);
}
catch(e) {
return false;
}
};
}
/**
* Creates and returns an element with further attributes
* attributes are specified by an object-list like e.g.
* {"type": "text", "class": "foo", "name": "bar", "href": "#"}
* Optionally the parent element can be specified.
*
* @param tagName
* @param attributes
* @param parentElement
* @return element
*/
document.createElementWithAttributes = function(tagName, attributes, parentElement) {
var el = document.createElement(tagName);
for (const attribute in attributes) {
el[attribute] = attributes[attribute]; //el.setAttribute(attribute, attributes[attribute]); cannot be used because function are evaluated in attributes e.g. {"onclick": function(e) { return false; }}
}
if (parentElement)
parentElement.appendChild(el);
return el;
};
/**
* Returns the current scroll position of the window
* @return [left top]
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY
*/
document.getScrollPosition = function() {
var l = 0, t = 0;
if( typeof window.pageYOffset == "number" ) {
t = window.pageYOffset; // window.scrollY
l = window.pageXOffset; // window.scrollX
}
return {
left: l,
top: t
};
};
/**
* Returns an array containing the window size as well as the page size
* @return [pageWidth, pageHeight, windowWidth, windowHeight]
*/
document.getWindowSize = function() {
var pageWidth = document.body.scrollWidth;
var pageHeight = document.body.scrollHeight;
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
pageHeight = pageHeight < windowHeight ? windowHeight : pageHeight;
pageWidth = pageWidth < windowWidth ? windowWidth : pageWidth;
return {
pageWidth: pageWidth,
pageHeight: pageHeight,
windowWidth: windowWidth,
windowHeight: windowHeight
};
};
/**
* Returns the position and the dimension of an element
* @param el
* @return elemPosDim
* @see http://www.quirksmode.org/js/findpos.html
*/
document.getElementPoSi = function(el){
var r = { top:0, left:0, width:0, height:0 };
if(!el || typeof(el) != 'object')
return r;
if(typeof(el.offsetTop) != 'undefined') {
r.height = el.offsetHeight;
r.width = el.offsetWidth;
r.left = r.top = 0;
while(el && el.tagName != 'BODY') {
r.top += parseInt( el.offsetTop );
r.left += parseInt( el.offsetLeft );
el = el.offsetParent;
}
}
return r;
};
/**
* Returns the first child element of a parent (optionally: having a css class).
* If no child exists, the function returns null.
*
* @param par
* @param tagName
* @param cssClasses
* return el
*/
document.getFirstChildByElement = function(par, tagName, cssClasses) {
if (cssClasses && typeof(cssClasses) != "object")
cssClasses = [cssClasses];
if (par && par.hasChildNodes()) {
var childNodeFromPar = par.firstChild;
while (childNodeFromPar != null) {
if (childNodeFromPar.nodeName.toLowerCase() == tagName) {
if (!cssClasses)
return childNodeFromPar;
else {
var teststr = ","+childNodeFromPar.className.split(" ").join(",")+",";
for (var i=0; i<cssClasses.length; i++)
if (teststr.indexOf(","+cssClasses[i]+",") != -1)
return childNodeFromPar;
}
}
childNodeFromPar = childNodeFromPar.nextSibling;
}
}
return null;
};
/**
* Returns the coordinates of the mouse event
* @param e
* @return position
* @see http://forum.de.selfhtml.org/archiv/2006/1/t121722/#m782727
*/
document.getMousePosition = function(e) {
return {
top: e.pageY,
left: e.pageX
};
};
/**
* Checks, if string A contains string B
* @param str
* @return includes
*/
if (typeof String.prototype.includes != "function") {
String.prototype.includes = function(str) {
return this.indexOf(str) !== -1;
};
}
/**
* Returns true, if the string contains a line break
* @return lineBreak
*/
String.prototype.containsLineBreak = function() {
var newLineRegExp = new RegExp(/(\n|\r|\r\n)./);
return newLineRegExp.test(this);
}
/**
* Removes slashes of a string like the PHP function stripslashes()
* @return str
*/
String.prototype.stripslashes = function() {
var str = this;
str=str.replace(/\\'/g,'\'');
str=str.replace(/\\"/g,'"');
str=str.replace(/\\0/g,'\0');
str=str.replace(/\\\\/g,'\\');
return str;
};
/**
* Drag and drop table, which allows for changing the order of the rows (TR elements) of the TBODY.
*
* @param table
* @see http://www.isocra.com/2007/07/dragging-and-dropping-table-rows-in-javascript/
*/
function DragAndDropTable(table,mode,queryKey) {
if (!table)
return;
var isChanged = false;
var rows = table.tBodies[0].rows;
var dragObject = null;
var oldOnMouseUpFunc = window.document.onmouseup;
var oldOnMouseMoveFunc = window.document.onmousemove;
var tableTop = 0;
var rowList = [];
var getLocationQueryByParameter = function(par) {
var q = window.document.location.search.substring(1).split('&');
if(!q.length)
return false;
for(var i=0; i<q.length; i++){
var v = q[i].split('=');
if (decodeURIComponent(v[0]) == par)
return v.length>1?decodeURIComponent(v[1]):"";
}
};
var saveNewOrder = function() {
if (!isChanged)
return;
var page = getLocationQueryByParameter(queryKey);
var order = getRowOrder();
if (!page || !order)
return;
var querys = [
new Query("mode", mode),
new Query("action", "reorder"),
new Query(page, order)
];
new Request("index.php", "POST", querys);
};
var updateClasses = function() {
for (var i=0; i<rows.length; i++)
rows[i].className = (i%2==0)?"a":"b";
};
var getRowOrder = function() {
var order = "";
for (var i=0; i<rows.length; i++)
if (rows[i].id.length > 3)
order += rows[i].id.substring(3) + ",";
return order.substr(0, order.length-1);
};
var ondrag = function(row) {
if (!row)
return;
};
var ondrop = function(row) {
if (!row)
return;
updateClasses();
saveNewOrder();
};
var start = function() {
window.document.onmousemove = function(e) {
if (typeof oldOnMouseMoveFunc == "function")
oldOnMouseMoveFunc(e);
if (!dragObject)
return;
var mPos = document.getMousePosition(e);
var currentTop = mPos.top - dragObject.handlePos.top + dragObject.elementPos.top;
var currentRow = findDropTargetRow( currentTop );
if (tableTop != currentTop && currentRow && dragObject != currentRow) {
var movingDown = currentTop > tableTop;
tableTop = currentTop;
if (movingDown)
currentRow = currentRow.nextSibling;
dragObject.parentNode.insertBefore(dragObject, currentRow);
isChanged = true;
ondrag(dragObject);
}
if(e && e.preventDefault)
e.preventDefault();
return false;
};
window.document.onmouseup = function (e) {
window.document.onmouseup = window.document.onmousemove = null;
if (typeof oldOnMouseUpFunc == "function")
oldOnMouseUpFunc(e);
if (typeof oldOnMouseMoveFunc == "function")
window.document.onmousemove = oldOnMouseMoveFunc;
ondrop(dragObject);
dragObject = null;
isChanged = false;
return false;
};
};
var findDropTargetRow = function(top) {
for (var i=0; i<rows.length; i++) {
var rowPoSi = document.getElementPoSi(rows[i]);
var h = rowPoSi.height;
if (h == 0 && row[i].firstChild) {
rowPoSi = document.getElementPoSi(row[i].firstChild);
h = row[i].firstChild.offsetHeight;
}
h /= 2;
if ((top >= (rowPoSi.top - h)) && (top < (rowPoSi.top + h))) {
return rows[i];
}
}
return null;
};
var add = function(row) {
row.classList.add("js-cursor-move");
row.title = lang["drag_and_drop_title"];
row.onmousedown = function(e){
isChanged = false;
var obj = document.getTarget(e);
if (obj && obj.className.search(/control/) != -1)
return false;
this.className = "drag";
this.elementPos = document.getElementPoSi(this);
this.handlePos = document.getMousePosition(e);
dragObject = this;
start();
return false;
};
var links = row.cells[row.cells.length-1].getElementsByTagName("a");
if (links && links.length > 0) {
for (var i=0; i<links.length; i++) {
if (links[i].href.search(/move_up/) != -1)
links[i].onclick = function(e) {
row.parentNode.insertBefore(row, rows[Math.max(row.rowIndex-2,0)]);
isChanged = true;
updateClasses();
saveNewOrder();
return false;
};
else if (links[i].href.search(/move_down/) != -1)
links[i].onclick = function(e) {
row.parentNode.insertBefore(row, rows[Math.min(row.rowIndex+1, rows.length)]);
updateClasses();
isChanged = true;
saveNewOrder();
return false;
};
}
}
};
(function() {
for (var i=0; i<rows.length; i++){
add(rows[i]);
}
}());
};
/************************ MyLittleForum-Objekte *************************************/
/**
* Query-Object having a key and a value
* @param k
* @param v
*/
function Query(k, v){
v = v || "";
var key = k.trim();
var value = encodeURIComponent(v.toString().trim());
this.toString = function(){
return key + "=" + value + "&";
};
};
/**
* Create a HTTP request the returned values are handeled by calling a handling function (func)
*
* @param uri
* @param method
* @param query
* @param obj
* @param func
* @param resXML
* @param mimeType
*
*/
function Request(uri,method,q,obj,func,args,resXML,mimeType){
args = args?(typeof args == "object"||typeof args == "function"?args:[args]):[];
resXML = resXML || false;
mimeType = mimeType?mimeType:resXML?"text/xml":"text/plain";
obj = obj || null;
var httpRequest = false;
try{
if (window.XMLHttpRequest)
httpRequest = new XMLHttpRequest();
if (httpRequest.overrideMimeType)
httpRequest.overrideMimeType(mimeType);
else if (window.ActiveXObject) {
try {
httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
} catch (er) {
httpRequest = false;
}
}
}
}
catch(err) {
httpRequest = false;
}
if (!httpRequest) {
if (obj && typeof obj[func] == "function")
obj[func](false, args);
return;
}
var qStr = "";
if (q instanceof Query)
qStr = q.toString();
else if((typeof q == "object"||typeof q == "function") && q.length > 0)
for (var i=0; i<q.length; i++)
qStr += q[i].toString();
qStr += new Date().getTime();
httpRequest.abort();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState == 4) {
if (obj && typeof obj[func] == "function") {
obj[func]( (resXML?httpRequest.responseXML:httpRequest.responseText), args);
}
httpRequest = false;
}
};
if (method.toLowerCase() == "post"){
httpRequest.open("POST", uri, true);
httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
httpRequest.send( qStr );
}
else {
httpRequest.open("GET", uri+"?"+qStr, true);
httpRequest.send(null);
}
};
/**
* The sidbar object
* @param templatePath
*/
function Sidebar(templatePath) {
templatePath = templatePath || "";
var main = document.getElementById("sidebar") || document.getElementById("bottombar") || false;
var icon = document.getElementById("sidebartoggle");
var self = this;
if (!main || !icon)
return;
this.setVisible = function(visible) {
if (visible) {
main.classList.remove("js-display-fold");
icon.src = templatePath + settings["hide_sidebar_image"];
icon.classList.remove("show-sidebar");
icon.classList.add("hide-sidebar");
}
else {
main.classList.add("js-display-fold");
icon.src = templatePath + settings["show_sidebar_image"];
icon.classList.remove("hide-sidebar");
icon.classList.add("show-sidebar");
}
};
this.isVisible = function() {
return !main.classList.contains("js-display-fold");
};
var links = main.getElementsByTagName("a");
for (var i=0; i<links.length; i++) {
if (links[i].href.search(/toggle_sidebar/) != -1) {
links[i].onclick = function(e) {
self.setVisible(!self.isVisible());
new Request("index.php", "POST", new Query("toggle_sidebar", true));
return false;
}
}
}
};
/**
* Thread object, which is created by an UL element or the numerical ID of the UL element,
* which is used to collapse the tree
*
* @param ul or id
* @param templatePath
*/
function Thread(ul, templatePath) {
var tid = false;
if (!isNaN( parseInt(ul) )) {
tid = ul;
ul = document.getElementById("thread-"+tid);
}
else {
var tidRegExp = new RegExp(/thread-([0-9])+/);
var q = tidRegExp.exec(ul.id);
if (!q)
return;
tid = q&&q.length>1?q[1]:0;
}
var lis = ul.getElementsByTagName("li");
var uls = ul.getElementsByTagName("ul");
var self = this;
var icon = new Image();
var repliesInfo = null;
if (ul.parentNode.nodeName != "TD") {
var tail = document.getFirstChildByElement(lis[0], "span", ["tail"]);
if (tail && lis.length > 1) {
repliesInfo = document.getFirstChildByElement(tail, "span", ["replies"]);
if (!repliesInfo) {
repliesInfo = document.createElementWithAttributes("span", {"className": "replies"}, tail);
repliesInfo.appendChild( document.createTextNode( " (" + (lis.length-1) + ")" ) );
}
}
}
this.isFold = function() {
return uls.length > 0 && uls[0].classList.contains("js-display-none");
};
this.setFold = function(fold, changeCSS) {
changeCSS = changeCSS || false;
if (fold) {
icon.src = templatePath + settings["expand_thread_image"];
icon.classList.remove("fold-thread");
icon.classList.add("expand-thread");
icon.alt = "";
icon.onerror = function(e) { this.alt = "[+]"; };
icon.title = lang["expand_fold_thread_linktitle"];
if (repliesInfo)
repliesInfo.classList.remove("js-display-none");
if (changeCSS) {
ul.classList.remove("expanded");
ul.classList.add("folded");
}
}
else {
icon.src = templatePath + settings["fold_thread_image"];
icon.classList.remove("expand-thread");
icon.classList.add("fold-thread");
icon.alt = "";
icon.onerror = function(e) { this.alt = "[-]"; };
icon.title = lang["expand_fold_thread_linktitle"];
if (repliesInfo)
repliesInfo.classList.add("js-display-none");
if (changeCSS) {
ul.classList.remove("folded");
ul.classList.add("expanded");
}
}
for (var i=0; i<uls.length; i++) {
if (fold)
uls[i].classList.add("js-display-none");
else
uls[i].classList.remove("js-display-none");
}
};
var setIcon = function(el) {
if (!el)
return;
if (lis.length > 0 && lis[0].firstChild)
lis[0].insertBefore(el, lis[0].firstChild);
else
lis[0].appendChild(el);
};
var foldExpandWrapper = document.createElementWithAttributes("span", {"className": "fold-expand"}, null);
if (lis.length == 1) {
var inactiveFoldExpandImg = document.createElementWithAttributes("img", {"src": templatePath + settings["expand_thread_inactive_image"], "className": "expand-thread-inactive", "alt": "", "onerror": function(e) { this.alt = "[]"; } }, foldExpandWrapper)
setIcon(foldExpandWrapper);
}
else {
var link = document.createElementWithAttributes("a", {"href": "#", "onclick": function(e) {self.setFold(!self.isFold()); this.blur(); return false;} }, foldExpandWrapper);
this.setFold(this.isFold());
link.appendChild(icon);
setIcon(foldExpandWrapper);
}
};
/**
* Posting object with collapse property, which is created by a nummeric posting id
* @param pid
*/
function Posting(pid) {
if (!pid)
return;
var pWrapper = document.getElementById("p" + pid);
var pHeadline = document.getElementById("headline-" + pid);
if (!pWrapper || !pHeadline)
return;
var self = this;
pHeadline.classList.add("js-cursor-pointer");
pHeadline.title = lang["fold_posting_title"];
pHeadline.onclick = function(e) {
self.setFold(!self.isFold());
};
this.isFold = function() {
return pWrapper.classList.contains("js-display-fold");
};
this.setFold = function(fold) {
if (fold) {
pWrapper.classList.add("js-display-fold");
}
else {
pWrapper.classList.remove("js-display-fold");
}
};
this.setFold(this.isFold());
};
/**
* Create a full-size image object within an element
* @param el
*/
function FullSizeImage(els) {
if (!els) return;
els = (typeof els == "object" || typeof els == "function") && typeof els.length == "number"?els:[els];
var hashTrigger = null;
var body = document.body;
var imageCanvas = document.getElementById("image-canvas") || document.createElementWithAttributes("div", {"id": "image-canvas"}, body);
imageCanvas.setVisible = function(visible) {
if (visible)
this.classList.remove("js-display-none");
else
this.classList.add("js-display-none");
};
var stopTrigger = function() {
if (hashTrigger) {
window.clearInterval(hashTrigger);
var scrollPos = document.getScrollPosition();
window.history.back();
// Fuer den Fall, dass man bei eingeblendeten Bild gescrollt hat
window.scrollTo(scrollPos.left, scrollPos.top);
}
};
var oldOnKeyPressFunc = window.document.onkeypress;
window.document.onkeypress = function(e) {
if (e.key == "Esc") {
imageCanvas.setVisible(false);
stopTrigger();
}
if (typeof oldOnKeyPressFunc == "function")
oldOnKeyPressFunc(e);
};
imageCanvas.onclick = function(e) {
imageCanvas.setVisible(false);
stopTrigger();
};
imageCanvas.setVisible(false);
var fullSizeImage = document.getElementById("fullSizeImage") || document.createElementWithAttributes("img", {"id": "fullSizeImage"}, imageCanvas);
for (var i=0; i<els.length; i++) {
var links = els[i].getElementsByTagName("a");
for (var j=0; j<links.length; j++) {
if(links[j].rel.search(/thumbnail/) != -1) {
links[j].onclick = function(e) {
window.location.hash="image";
var currentHash = window.location.hash;
fullSizeImage.src = this.href;
imageCanvas.setVisible(true);
var imgPoSi = document.getElementPoSi(fullSizeImage);
var scrollPos = document.getScrollPosition();
var winSize = document.getWindowSize();
imageCanvas.style.height=winSize.pageHeight+"px";
fullSizeImage.style.marginTop = (scrollPos.top+(winSize.windowHeight-imgPoSi.height)/2) + "px";
hashTrigger = window.setInterval(
function() {
if ( this.location.hash != currentHash ) {
imageCanvas.setVisible(false);
}
},50
);
return false;
};
}
}
}
}
/**
* Preview window for ajax requests to e.g. postings. The window contains the given HTML structure
*
* @param structure
* @param templatePath
*/
function AjaxPreviewWindow(structure, templatePath) {
templatePath = templatePath?templatePath:"";
var hideURI = false;
var pinned = false;
var win = document.getElementById('ajax-preview');
var self = this;
if (!win) {
win = document.createElementWithAttributes("div", {"id": "ajax-preview", "className": "js-display-none"}, null);
document.body.appendChild( win );
}
win.innerHTML = structure.stripslashes().trim();
var opEl = null;
var xShift = 0;
var closeEl = document.getElementById("ajax-preview-close");
var contentEl = document.getElementById("ajax-preview-content");
var mainEl = document.getElementById("ajax-preview-main");
if (!closeEl || !contentEl || !mainEl)
console.log("main.js: Fail to init ajax-Elements!");
var oldOnMouseDownFunc = window.document.onmousedown;
window.document.onmousedown = function(e) {
self.closeByOutSideClick(e);
if (typeof oldOnMouseDownFunc == "function")
oldOnMouseDownFunc(e);
};
var oldOnKeyPressFunc = window.document.onkeypress;
window.document.onkeypress = function(e) {
if (e.key == "Esc") {
self.setVisible(false);
}
if (typeof oldOnKeyPressFunc == "function")
oldOnKeyPressFunc(e);
};
if (settings["ajax_preview_onmouseover"]) {
var oldOnMouseOver = window.document.onmouseover;
window.document.onmouseover = function(e) {
if (!self.isPinned())
self.closeByOutSideClick(e);
if (typeof oldOnMouseOver == "function")
oldOnMouseOver(e);
};
}
closeEl.onclick = function() { self.setVisible(false); return false; };
var throbberIcon = document.createElementWithAttributes("img", {"id": "ajax-preview-throbber", "src": templatePath + settings["ajax_preview_throbber_image"], "alt": "[*]"}, contentEl);
var replylinkWrapper = document.createElementWithAttributes("p", {"id": "ajax-preview-replylink-wrapper", "className": "js-display-none"}, contentEl);
var replylinkLink = document.createElementWithAttributes("a", {"id": "ajax-preview-replylink", "href": "#"}, null);
replylinkLink.appendChild( document.createTextNode( lang["reply_link"] ));
this.closeByOutSideClick = function(e) {
var imgCanvas = document.getElementById("image-canvas");
if (self.isVisible() && imgCanvas && imgCanvas.classList.contains("js-display-none")) {
var obj = document.getTarget(e);
if (obj && obj != self.getOpener().firstChild && obj != self.getContentElement() && obj != self.getMainElement()) {
var evtPos = document.getMousePosition(e);
var posX = evtPos.left;
var posY = evtPos.top;
var boxX = self.getDocumentPosition().left;
var boxY = self.getDocumentPosition().top;
var boxWidth = self.getWidth();
var boxHeight = self.getHeight();
if ((posX < boxX || posX > (boxX+boxWidth) || posY < boxY || posY > (boxY+boxHeight)) && obj.className != 'ap') {
self.setVisible(false);
}
}
}
};
this.pin = function() {
pinned = !pinned;
};
this.isPinned = function() {
return pinned;
};
this.getContentElement = function() {
return contentEl;
};
this.getMainElement = function() {
return mainEl;
};
this.hideURI = function(hide) {
hideURI = hide;
};
this.setPosition = function(x, y) {
win.style.left = x + "px";
win.style.top = y + "px";
var winWidth = this.getWidth();
var documentWidth = document.getWindowSize().windowWidth;
if ((x+winWidth) >= documentWidth) {
this.moveHorizontal( documentWidth-25-(x+winWidth) );
}
else {
this.moveHorizontal( 0 );
}
};
this.getWidth = function() {
return mainEl.offsetWidth;
};
this.getHeight = function() {
return win.offsetHeight + mainEl.offsetHeight;
};
this.setOpener = function(op) {
opEl = op;
};
this.getOpener = function() {
return opEl;
};
this.isVisible = function() {
return !win.classList.contains("js-display-none");
};
this.getDocumentPosition = function() {
var left = win.offsetLeft;
var top = win.offsetTop;
return {
top: top,
left: left + xShift
};
};
this.moveHorizontal = function(val) {
xShift = val;
mainEl.style.left = val + "px";
};
this.setVisible = function(visible) {
if (visible) {
win.classList.remove("js-display-none");
win.classList.add("js-display-block");
}
else {
win.classList.remove("js-display-block");
win.classList.add("js-display-none");
pinned = false;
}
};
this.setText = function(str) {
contentEl.innerHTML = str;
if (str != "") {
if (!replylinkLink.firstChild)
replylinkLink.appendChild( document.createTextNode( lang["reply_link"] ));
if (!hideURI) {
replylinkWrapper.appendChild( replylinkLink );
contentEl.appendChild( replylinkWrapper );
}
new FullSizeImage(contentEl);
}
else {
contentEl.appendChild( throbberIcon );
}
};
this.setURI = function(uri) {
if (!uri) {
replylinkLink.href = "#";
replylinkWrapper.classList.remove("js-display-block");
replylinkWrapper.classList.add("js-display-none");
}
else {
replylinkWrapper.classList.remove("js-display-none");
replylinkWrapper.classList.add("js-display-block");
replylinkLink.href = uri;
}
};
}
/**
* Entry object, to handle link targets of an entry depending on user preferences/forum settings
* @param el
*/
function Entry(el) {
if (!el)
return;
this.setLinkTarget = function(trg, defaultTarget) {
var entryBodies = el.getElementsByClassName("body");
for (var i=0; i<entryBodies.length; i++) {
var links = entryBodies[i].getElementsByTagName("a");
for (var j=0; j<links.length; j++) {
if (trg.toUpperCase() == "NONE") {
links[j].target = ""; // this is not the default case, because the global forum settings may set _blank
}
else if (trg.toUpperCase() == "EXTERNAL" || trg.toUpperCase() == "ALL") {
// skip internal links
if (trg.toUpperCase() == "EXTERNAL" && links[j].href.includes(window.document.location.origin))
continue;
links[j].target = "_blank";
}
else {
links[j].target = defaultTarget; // default case - forum settings
}
}
}
};
}
/**
* Main object of the forum
*/
function MyLittleJavaScript() {
var templatePath = null;
var ajaxPreviewWindow = null;
var sidebar = null;
var strURL = 'index.php';
var threads = [];
var postings = [];
var regExpFID = new RegExp(/[?|&]id=([0-9]+)(#p([0-9]+))?/);
var self = this;
/**
* Returns the posting id given by the URI
* @param link
* @return id
*/
var getPostingId = function(link) {
if (link && regExpFID.test(link.href)) {
var q = regExpFID.exec(link.href);
return q[3]?q[3]:q[1];
}
return false;
}
/**
* Returns the path to the template,
* which was extracted by a LINK element.
* @return path
*/
this.getTemplatePath = function() {
if (templatePath != null)
return templatePath;
var el = document.getElementsByTagName("link");
for (var i=0; i<el.length; i++) {
if (el[i].rel == "stylesheet") {
return el[i].href.substring(0, el[i].href.lastIndexOf("/")+1);
}
}
return "";
};
/**
* Create a link to open the ajax preview window
* @param id
* @return link
*/
var createAjaxPreviewLink = function(id) {
var link = document.createElementWithAttributes("a", {"pid": id, "title": lang["ajax_preview_title"], "href": strURL+"?id="+id, "onclick": function(e) {self.showAjaxPreviewWindow(this, true); this.blur(); return false; }, "onmouseover": function(e) { if (settings["ajax_preview_onmouseover"]) {self.showAjaxPreviewWindow(this, false); this.blur(); } return false; }, "tabIndex": -1 }, null);
var img = document.createElementWithAttributes("img", {"src": templatePath + settings["ajax_preview_image"], "title": lang["ajax_preview_title"], "alt": "", "onload": function(e) { this.alt = "[…]"; }, "onerror": function(e) { this.alt = "[…]"; } }, link);
return link;
};
/**
* Set the preview window to the user profile. The link is added to the specified element
* @param el
*/
var setPreviewBoxToProfil = function(el) {
if (!el || !ajaxPreviewWindow)
return;
var pid = getPostingId(el);
if (pid && el.parentNode) {
el.parentNode.appendChild( document.createTextNode( String.fromCharCode(160) ) );
el.parentNode.appendChild( createAjaxPreviewLink(pid) );
}
};
/**
* Set the preview window to the posting page of an entry. The link is added to the specified element
* @param el
*/
var setPreviewBoxToReplyPage = function(el) {
if (!el || !ajaxPreviewWindow)
return;
ajaxPreviewWindow.hideURI( true );
var f = document.getElementById("postingform");
var pid = false;
if (f && f.elements["id"]) {
pid = parseInt(f.elements["id"].value);
}
if (pid) {
el.appendChild( document.createTextNode( String.fromCharCode(160) ) );
el.appendChild( createAjaxPreviewLink(pid) );
}
};
/**
* Marked an posting. Please note: This is an admin function.
* @param param1 id || xml
* @param param2 null || args
*/
this.selectPosting = function(par1, par2) {
var isResponse = par2 && (typeof par2 == "object" || typeof par2 == "function") && par2.length > 0;
var pid = isResponse?par2[0]:par1;
var xml = isResponse?par1:false;
var imgEl = null;
if (!pid || !(imgEl = document.getElementById('markimg_'+pid)))
return;
imgEl.src = templatePath + settings["mark_process_image"];
imgEl.alt = '[ ]';
var querys = [
new Query("mode", "posting"),
new Query("mark", pid),
new Query("method", "ajax")
];
if (!isResponse)
new Request(strURL, "POST", querys, this, "selectPosting", pid, true);
else if (isResponse && xml && document.getElementById('marklink_'+pid)) {
var linkEl = document.getElementById('marklink_'+pid);
var selectPosting = xml.getElementsByTagName('action') && xml.getElementsByTagName('action')[0].firstChild.data == "1";
if(selectPosting) {
imgEl.src = templatePath + settings["marked_image"];
imgEl.alt = '[●]';
linkEl.title = lang["unmark_linktitle"];
imgEl.title = lang["unmark_linktitle"];
}
else {
imgEl.src = templatePath + settings["unmarked_image"];
imgEl.alt = '[○]';
linkEl.title = lang["mark_linktitle"];
imgEl.title = lang["mark_linktitle"];
}
}
};
/**
* Collape/expand all threads
* @param expand
*/
var expandAllThreads = function(expand) {
expand = expand || false;
for (var i=0; i<threads.length; i++) {
threads[i].setFold(!expand, true);
}
var querys = [
new Query("fold_threads", expand ? "0" : "1"),
new Query("ajax", "true")
];
new Request(strURL, "GET", querys);
};
var initThreadFoldingInSubMenu = function() {
if (!document.getElementById("subnavmenu"))
return;
var menuLinks = document.getElementById("subnavmenu").getElementsByTagName("a");
var foldingLink = null;
var foldRegExp = new RegExp(/fold-([0-9])+/);
for (var i=0; i<menuLinks.length; i++) {
if (menuLinks[i].className.search( foldRegExp ) != -1) {
foldingLink = menuLinks[i];
break;
}
}
if (foldingLink) {
var q = foldRegExp.exec(foldingLink.className);
var isExpand = q.length>1&&q[1]=="1";
foldingLink.onclick = function(e) {
expandAllThreads( !isExpand );
this.className = this.className.replace(foldRegExp, "fold-" + (isExpand ? 2 : 1) );
this.firstChild.replaceData(0, this.firstChild.nodeValue.length, (isExpand?lang["expand_threads"]:lang["fold_threads"]) );
this.title = isExpand?lang["expand_threads_linktitle"]:lang["fold_threads_linktitle"];
isExpand = !isExpand;
this.blur();
return false;
}
}
};
/**
* Add a links to the preview window of the main page, if and only if, the posting contains content.
* Links are added to els
* @param els
*/
var setPreviewBoxToMainPage = function(els) {
if (!els)
return;
initThreadFoldingInSubMenu();
for (var i=0; i<els.length; i++) {
var el = els[i];
var li = el.closest("li");
var pLink = li.querySelector(":scope > a.subject");
var pEmptyEl = li.querySelector(":scope > .metadata .tail img.no-text");
var pEmpty = (pEmptyEl !== null) ? true: false;
var pid = parseInt( el.id.substring(1) );
if (!pid)
continue;
var links = el.getElementsByTagName("a");
if (links.length >= 2) {
for (var j=0; j<links.length; j++) {
if (links[j].href.search(/mark/) != -1) {
links[j].pid = pid;
links[j].onclick = function(e) {
self.selectPosting( this.pid );
this.blur();
return false;
};
}
else if (links[j].href.search(/delete_posting/) != -1) {
links[j].onclick = function(e) {
var confirmed = window.confirm( lang["delete_posting_confirm"] );
if (confirmed)
this.href += '&delete_posting_confirm=true';
this.blur();
return confirmed;
};
}
}
}
if (!pEmpty && pLink && ajaxPreviewWindow) {
if (links.length >= 1) {
var link = links[0];
el.insertBefore(createAjaxPreviewLink(pid), link);
el.insertBefore(document.createTextNode( String.fromCharCode(160) ), link);
}
else {
el.appendChild(document.createTextNode( String.fromCharCode(160) ));
el.appendChild(createAjaxPreviewLink(pid));
}
}
// thread, folded oder expanded - Reicht eigentlich die Suche nach thread?
if (li.parentNode.className.search(/thread/) != -1 && li.parentNode.className.search(/[folded|expanded]/) != -1) {
threads.push( new Thread( li.parentNode, templatePath) );
}
}
var editAreas = document.getElementsByClassName("options");
if (editAreas.length > 0) {
for (var i=0; i<editAreas.length; i++) {
var links = editAreas[i].getElementsByTagName("a");
if (links.length > 0) {
for (var j=0; j<links.length; j++) {
if (links[j].href.search(/delete_posting/) != -1) {
links[j].onclick = function(e) {
var confirmed = window.confirm( lang["delete_posting_confirm"] );
if (confirmed)
this.href += '&delete_posting_confirm=true';
return confirmed;
};
break;
}
}
}
}
}
var pEls = document.getElementsByClassName("posting");
pEls = pEls.length>0?pEls:document.getElementsByClassName("thread-posting");
new FullSizeImage(pEls);
};
/**
* Set the default value to an INPUT element
*/
var setDefaultInputValue = function(id) {
var inp = document.getElementById(id);
if (!inp)
return;
var value = (inp.alt) ? inp.alt : inp.value;
inp.onfocus = function(e) {
if (this.value == value)
this.value="";
};
inp.onblur = function(e) {
if(this.value.trim() == "")
this.value = value;
};
};
/**
* Set focus to first INPUT element on the page, if exists
*/
var setFocusToContentForm = function() {
var par = document.getElementById("content");
if (par) {
var f = par.getElementsByTagName("form");
if (f && f.length>0) {
for (var i=0; i<f[0].elements.length; i++) {
if (f[0].elements[i].type == "text" && f[0].elements[i].name != "search_user" && f[0].elements[i].name != "smiley_code" && f[0].elements[i].name != "new_category") {
f[0].elements[i].focus();
break;
}
}
}
}
};
/**
* Add a checkbox to a INPUT element of type PASSWORD to show/hid the entered password
*/
var togglePasswordVisibility = function() {
if (document.getElementById("content")) {
var f = document.getElementById("content").getElementsByTagName("form");
if (f && f.length>0) {
var passwordFields = [];
for (var i=0; i<f.length; i++) {
var fields = f[i].getElementsByTagName("input");
for (var j=0; j<fields.length; j++) {
if (fields[j].type == "password") {
var passwordField = fields[j];
// lang["fold_postings_title"]
var cb = document.createElementWithAttributes("input", {"type": "checkbox", "checked": false, "value": false, "field": passwordField, "title": lang["show_password_title"]}, passwordField.parentNode);
cb.onclick = function(e) {
var isShown = this.field.type == "text";
this.value = isShown;
this.title = isShown ? lang["show_password_title"] : lang["hide_password_title"];
this.field.type = isShown ? "password" : "text";
};
}
}
}
}
}
};
/**
* Collape/expand all replies of a posting
* @param expand
*/
var expandAllPostings = function(expand) {
expand = expand || false;
for (var i=0; i<postings.length; i++) {
postings[i].setFold(!expand);
}
};
/**
* Init. option to collape/expand threads
* @param els
*/
var initPostingFolding = function(els) {
// Postings suchen
if (!els)
return;
for (var i=0; i<els.length; i++) {
var el = els[i];
var pid = parseInt( el.id.substring(1) );
if (!pid)
continue;
postings.push( new Posting(pid) );
}
// Menü anpassen
var menu = null;
if (postings.length == 0 || !(menu=document.getElementById("subnavmenu")))
return;
var listEntry = document.createElementWithAttributes("li", {}, menu);
var link = document.createElementWithAttributes("a", {"isExpand": true, "title": lang["fold_postings_title"],"href": "#", "className": "fold-postings"}, listEntry);
link.appendChild( document.createTextNode( lang["fold_postings"] ) );
link.onclick = function(e) {
this.isExpand = !this.isExpand;
expandAllPostings(this.isExpand);
this.blur();
return false;
}
};
/**
* Init. pop-up window for e.g. terms of use
*/
var initPopUpLinks = function() {
var els = [[document.getElementById("terms_of_use") || false, settings["terms_of_use_popup_width"], settings["terms_of_use_popup_height"]],
[document.getElementById("data_privacy_statement") || false, settings["terms_of_use_popup_width"], settings["terms_of_use_popup_height"]],
[document.getElementById("edit_avatar") || false, settings["avatar_popup_width"], settings["avatar_popup_height"]]];
for (var i=0; i<els.length; i++) {
if (els[i][0]) {
var docSize = document.getWindowSize();
var w = els[i][1];
var h = els[i][2];
var l = parseInt(0.5*(docSize.windowWidth-w));
var t = parseInt(0.25*(docSize.windowHeight-h));
els[i][0].onclick = function(e) {
window.open(this.href,"MyLittleForum","width="+w+",height="+h+",left="+l+",top="+t+",scrollbars,resizable");
return false;
};
}
}
};
/**
* Refresh content of preview window
* @param xml
*/
this.updateAjaxPreviewWindow = function(xml) {
if (xml === false || !ajaxPreviewWindow)
return;
var content = xml.getElementsByTagName('content');
var isLocked = xml.getElementsByTagName('locked');
isLocked = !isLocked?true:isLocked[0].firstChild.data == "1";
content = !content?"":content[0].firstChild.data;
if (isLocked)
ajaxPreviewWindow.setURI(false);
else if (ajaxPreviewWindow.getOpener() && ajaxPreviewWindow.getOpener().pid)
ajaxPreviewWindow.setURI("index.php?mode=posting&id=" + ajaxPreviewWindow.getOpener().pid);
if (content.trim() == "")
content = "<p>"+lang["no_text"]+"</p>";
ajaxPreviewWindow.setText( content );
};
/**
* Set the preview window visible and pin the window, if desired
* @param obj
* @param pin
*/
this.showAjaxPreviewWindow = function(obj, pin) {
if (!obj || !ajaxPreviewWindow)
return;
if (obj == ajaxPreviewWindow.getOpener() && ajaxPreviewWindow.isVisible() && pin) {
ajaxPreviewWindow.pin();
if (!ajaxPreviewWindow.isPinned()) {
ajaxPreviewWindow.setVisible(false);
ajaxPreviewWindow.setOpener(null);
}
}
else if (!ajaxPreviewWindow.isPinned()) {
if (pin && !ajaxPreviewWindow.isPinned())
ajaxPreviewWindow.pin();
var elPos = document.getElementPoSi(obj);
ajaxPreviewWindow.setOpener(obj);
ajaxPreviewWindow.setText("");
ajaxPreviewWindow.setVisible(true);
ajaxPreviewWindow.setPosition( elPos.left, elPos.top );
var querys = [
new Query("mode", "entry"),
new Query("ajax_preview", "true"),
new Query("id", obj.pid)
];
new Request(strURL, "POST", querys, this, "updateAjaxPreviewWindow", null, true);
}
};
/**
* Returns the current preview window
* @return win
*/
this.getAjaxPreviewWindow = function() {
return ajaxPreviewWindow;
}
/**
* Submit the form, if the value of a drop down menu is changed
*/
var setAutoSubmitSubNaviForms = function() {
var subNav = document.getElementById("subnav-2");
if (subNav) {
var f = subNav.getElementsByTagName("form");
for (var i=0; i<f.length; i++) {
var els = f[i].getElementsByTagName("select");
for (var j=0; j<els.length; j++) {
els[j].f = f[i];
els[j].onchange = function(e) { this.f.submit(); return false; };
}
}
}
};
/**
* Add target to links in entries depending on user preferences
*/
var addUserDefinedLinkTargetInEntries = function() {
var cEl = document.getElementById("content");
if (!cEl)
return;
var trg = 'DEFAULT';
var dflTrg = '';
if (typeof user_settings == "object" && typeof user_settings["open_links_in_new_window"] == "string")
trg = user_settings["open_links_in_new_window"];
if (typeof settings == "object" && typeof settings["forum_based_link_target"] == "string")
dflTrg = settings["forum_based_link_target"];
if (trg != 'DEFAULT' || dflTrg != '') {
var pEls = cEl.getElementsByClassName("posting");
pEls = pEls.length > 0 ? pEls : cEl.getElementsByClassName("thread-posting");
pEls = (typeof pEls == "object" || typeof pEls == "function") && typeof pEls.length == "number"?pEls:[pEls];
for (var i=0; i<pEls.length; i++) {
var entry = new Entry(pEls[i]);
entry.setLinkTarget(trg, dflTrg);
}
}
};
/**
* Init. MyLittelJavaScript
* @param ajaxPreviewStructure
*/
this.init = function( ajaxPreviewStructure ) {
ajaxPreviewStructure = ajaxPreviewStructure || false;
setFocusToContentForm();
setDefaultInputValue("search-input");
setDefaultInputValue("search-user");
templatePath = this.getTemplatePath();
if (ajaxPreviewStructure)
ajaxPreviewWindow = new AjaxPreviewWindow( ajaxPreviewStructure, templatePath );
setPreviewBoxToProfil( document.getElementById("user-last-posting") );
setPreviewBoxToReplyPage( document.getElementById("reply-to") );
setPreviewBoxToMainPage( document.getElementsByClassName("tail") );
addUserDefinedLinkTargetInEntries();
initPostingFolding( document.getElementsByClassName("thread-posting") );
initPopUpLinks();
setAutoSubmitSubNaviForms();
sidebar = new Sidebar(templatePath);
togglePasswordVisibility();
if (typeof preload == "object")
document.preloadImages(preload, templatePath);
};
}
document.addEventListener("DOMContentLoaded", function(e) {
var mlf = new MyLittleJavaScript();
var ajaxPreviewStructure = typeof settings != "undefined" && typeof settings["ajaxPreviewStructure"] == "string"?settings["ajaxPreviewStructure"]:false;
if (mlf && typeof lang == "object")
mlf.init(ajaxPreviewStructure);
new DragAndDropTable(document.getElementById("sortable"), "bookmarks", "mode", "admin", "action");
});