Completed
Pull Request — release-2.1 (#4823)
by
unknown
08:48
created

Themes/default/scripts/script.js (12 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
var smf_formSubmitted = false;
2
var lastKeepAliveCheck = new Date().getTime();
3
var smf_editorArray = new Array();
4
5
// Some very basic browser detection - from Mozilla's sniffer page.
6
var ua = navigator.userAgent.toLowerCase();
7
8
var is_opera = ua.indexOf('opera') != -1;
9
var is_ff = (ua.indexOf('firefox') != -1 || ua.indexOf('iceweasel') != -1 || ua.indexOf('icecat') != -1 || ua.indexOf('shiretoko') != -1 || ua.indexOf('minefield') != -1) && !is_opera;
10
var is_gecko = ua.indexOf('gecko') != -1 && !is_opera;
11
12
var is_chrome = ua.indexOf('chrome') != -1;
13
var is_safari = ua.indexOf('applewebkit') != -1 && !is_chrome;
14
var is_webkit = ua.indexOf('applewebkit') != -1;
15
16
var is_ie = ua.indexOf('msie') != -1 && !is_opera;
17
// Stupid Microsoft...
18
var is_ie11 = ua.indexOf('trident') != -1 && ua.indexOf('gecko') != -1;
19
var is_iphone = ua.indexOf('iphone') != -1 || ua.indexOf('ipod') != -1;
20
var is_android = ua.indexOf('android') != -1;
21
22
var ajax_indicator_ele = null;
23
24
// Some older versions of Mozilla don't have this, for some reason.
25
if (!('forms' in document))
26
	document.forms = document.getElementsByTagName('form');
27
28
// Versions of ie < 9 do not have this built in
29
if (!('getElementsByClassName' in document))
30
{
31
	document.getElementsByClassName = function(className)
32
	{
33
		return $('".' + className + '"');
34
	}
35
}
36
37
// Get a response from the server.
38
function getServerResponse(sUrl, funcCallback, sType, sDataType)
39
{
40
	var oCaller = this;
41
	var oMyDoc = $.ajax({
42
		type: sType,
43
		url: sUrl,
44
		cache: false,
45
		dataType: sDataType,
46
		success: function(response) {
47
			if (typeof(funcCallback) != 'undefined')
48
			{
49
				funcCallback.call(oCaller, response);
50
			}
51
		},
52
	});
53
54
	return oMyDoc;
55
}
56
57
// Load an XML document.
58
function getXMLDocument(sUrl, funcCallback)
59
{
60
	var oCaller = this;
61
	var oMyDoc = $.ajax({
62
		type: 'GET',
63
		url: sUrl,
64
		cache: false,
65
		dataType: 'xml',
66
		success: function(responseXML) {
67
			if (typeof(funcCallback) != 'undefined')
68
			{
69
				funcCallback.call(oCaller, responseXML);
70
			}
71
		},
72
	});
73
74
	return oMyDoc;
75
}
76
77
// Send a post form to the server.
78
function sendXMLDocument(sUrl, sContent, funcCallback)
79
{
80
	var oCaller = this;
81
	var oSendDoc = $.ajax({
82
		type: 'POST',
83
		url: sUrl,
84
		data: sContent,
85
		beforeSend: function(xhr) {
86
			xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
87
		},
88
		dataType: 'xml',
89
		success: function(responseXML) {
90
			if (typeof(funcCallback) != 'undefined')
91
			{
92
				funcCallback.call(oCaller, responseXML);
93
			}
94
		},
95
	});
96
97
	return true;
98
}
99
100
// A property we'll be needing for php_to8bit.
101
String.prototype.oCharsetConversion = {
102
	from: '',
103
	to: ''
104
};
105
106
// Convert a string to an 8 bit representation (like in PHP).
107
String.prototype.php_to8bit = function ()
108
{
109
	if (smf_charset == 'UTF-8')
110
	{
111
		var n, sReturn = '';
112
113
		for (var i = 0, iTextLen = this.length; i < iTextLen; i++)
114
		{
115
			n = this.charCodeAt(i);
116
			if (n < 128)
117
				sReturn += String.fromCharCode(n);
118
			else if (n < 2048)
119
				sReturn += String.fromCharCode(192 | n >> 6) + String.fromCharCode(128 | n & 63);
120
			else if (n < 65536)
121
				sReturn += String.fromCharCode(224 | n >> 12) + String.fromCharCode(128 | n >> 6 & 63) + String.fromCharCode(128 | n & 63);
122
			else
123
				sReturn += String.fromCharCode(240 | n >> 18) + String.fromCharCode(128 | n >> 12 & 63) + String.fromCharCode(128 | n >> 6 & 63) + String.fromCharCode(128 | n & 63);
124
		}
125
126
		return sReturn;
127
	}
128
129
	else if (this.oCharsetConversion.from.length == 0)
130
	{
131
		switch (smf_charset)
132
		{
133
			case 'ISO-8859-1':
134
				this.oCharsetConversion = {
135
					from: '\xa0-\xff',
136
					to: '\xa0-\xff'
137
				};
138
			break;
139
140
			case 'ISO-8859-2':
141
				this.oCharsetConversion = {
142
					from: '\xa0\u0104\u02d8\u0141\xa4\u013d\u015a\xa7\xa8\u0160\u015e\u0164\u0179\xad\u017d\u017b\xb0\u0105\u02db\u0142\xb4\u013e\u015b\u02c7\xb8\u0161\u015f\u0165\u017a\u02dd\u017e\u017c\u0154\xc1\xc2\u0102\xc4\u0139\u0106\xc7\u010c\xc9\u0118\xcb\u011a\xcd\xce\u010e\u0110\u0143\u0147\xd3\xd4\u0150\xd6\xd7\u0158\u016e\xda\u0170\xdc\xdd\u0162\xdf\u0155\xe1\xe2\u0103\xe4\u013a\u0107\xe7\u010d\xe9\u0119\xeb\u011b\xed\xee\u010f\u0111\u0144\u0148\xf3\xf4\u0151\xf6\xf7\u0159\u016f\xfa\u0171\xfc\xfd\u0163\u02d9',
143
					to: '\xa0-\xff'
144
				};
145
			break;
146
147
			case 'ISO-8859-5':
148
				this.oCharsetConversion = {
149
					from: '\xa0\u0401-\u040c\xad\u040e-\u044f\u2116\u0451-\u045c\xa7\u045e\u045f',
150
					to: '\xa0-\xff'
151
				};
152
			break;
153
154
			case 'ISO-8859-9':
155
				this.oCharsetConversion = {
156
					from: '\xa0-\xcf\u011e\xd1-\xdc\u0130\u015e\xdf-\xef\u011f\xf1-\xfc\u0131\u015f\xff',
157
					to: '\xa0-\xff'
158
				};
159
			break;
160
161
			case 'ISO-8859-15':
162
				this.oCharsetConversion = {
163
					from: '\xa0-\xa3\u20ac\xa5\u0160\xa7\u0161\xa9-\xb3\u017d\xb5-\xb7\u017e\xb9-\xbb\u0152\u0153\u0178\xbf-\xff',
164
					to: '\xa0-\xff'
165
				};
166
			break;
167
168
			case 'tis-620':
169
				this.oCharsetConversion = {
170
					from: '\u20ac\u2026\u2018\u2019\u201c\u201d\u2022\u2013\u2014\xa0\u0e01-\u0e3a\u0e3f-\u0e5b',
171
					to: '\x80\x85\x91-\x97\xa0-\xda\xdf-\xfb'
172
				};
173
			break;
174
175
			case 'windows-1251':
176
				this.oCharsetConversion = {
177
					from: '\u0402\u0403\u201a\u0453\u201e\u2026\u2020\u2021\u20ac\u2030\u0409\u2039\u040a\u040c\u040b\u040f\u0452\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u2122\u0459\u203a\u045a\u045c\u045b\u045f\xa0\u040e\u045e\u0408\xa4\u0490\xa6\xa7\u0401\xa9\u0404\xab-\xae\u0407\xb0\xb1\u0406\u0456\u0491\xb5-\xb7\u0451\u2116\u0454\xbb\u0458\u0405\u0455\u0457\u0410-\u044f',
178
					to: '\x80-\x97\x99-\xff'
179
				};
180
			break;
181
182
			case 'windows-1253':
183
				this.oCharsetConversion = {
184
					from: '\u20ac\u201a\u0192\u201e\u2026\u2020\u2021\u2030\u2039\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u2122\u203a\xa0\u0385\u0386\xa3-\xa9\xab-\xae\u2015\xb0-\xb3\u0384\xb5-\xb7\u0388-\u038a\xbb\u038c\xbd\u038e-\u03a1\u03a3-\u03ce',
185
					to: '\x80\x82-\x87\x89\x8b\x91-\x97\x99\x9b\xa0-\xa9\xab-\xd1\xd3-\xfe'
186
				};
187
			break;
188
189
			case 'windows-1255':
190
				this.oCharsetConversion = {
191
					from: '\u20ac\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\u2039\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u02dc\u2122\u203a\xa0-\xa3\u20aa\xa5-\xa9\xd7\xab-\xb9\xf7\xbb-\xbf\u05b0-\u05b9\u05bb-\u05c3\u05f0-\u05f4\u05d0-\u05ea\u200e\u200f',
192
					to: '\x80\x82-\x89\x8b\x91-\x99\x9b\xa0-\xc9\xcb-\xd8\xe0-\xfa\xfd\xfe'
193
				};
194
			break;
195
196
			case 'windows-1256':
197
				this.oCharsetConversion = {
198
					from: '\u20ac\u067e\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\u0679\u2039\u0152\u0686\u0698\u0688\u06af\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u06a9\u2122\u0691\u203a\u0153\u200c\u200d\u06ba\xa0\u060c\xa2-\xa9\u06be\xab-\xb9\u061b\xbb-\xbe\u061f\u06c1\u0621-\u0636\xd7\u0637-\u063a\u0640-\u0643\xe0\u0644\xe2\u0645-\u0648\xe7-\xeb\u0649\u064a\xee\xef\u064b-\u064e\xf4\u064f\u0650\xf7\u0651\xf9\u0652\xfb\xfc\u200e\u200f\u06d2',
199
					to: '\x80-\xff'
200
				};
201
			break;
202
203
			default:
204
				this.oCharsetConversion = {
205
					from: '',
206
					to: ''
207
				};
208
			break;
209
		}
210
		var funcExpandString = function (sSearch) {
211
			var sInsert = '';
212
			for (var i = sSearch.charCodeAt(0), n = sSearch.charCodeAt(2); i <= n; i++)
213
				sInsert += String.fromCharCode(i);
214
			return sInsert;
215
		};
216
		this.oCharsetConversion.from = this.oCharsetConversion.from.replace(/.\-./g, funcExpandString);
217
		this.oCharsetConversion.to = this.oCharsetConversion.to.replace(/.\-./g, funcExpandString);
218
	}
219
220
	var sReturn = '', iOffsetFrom = 0;
221
	for (var i = 0, n = this.length; i < n; i++)
222
	{
223
		iOffsetFrom = this.oCharsetConversion.from.indexOf(this.charAt(i));
224
		sReturn += iOffsetFrom > -1 ? this.oCharsetConversion.to.charAt(iOffsetFrom) : (this.charCodeAt(i) > 127 ? '&#' + this.charCodeAt(i) + ';' : this.charAt(i));
225
	}
226
227
	return sReturn
228
}
229
230
// Character-level replacement function.
231
String.prototype.php_strtr = function (sFrom, sTo)
232
{
233
	return this.replace(new RegExp('[' + sFrom + ']', 'g'), function (sMatch) {
234
		return sTo.charAt(sFrom.indexOf(sMatch));
235
	});
236
}
237
238
// Simulate PHP's strtolower (in SOME cases PHP uses ISO-8859-1 case folding).
239
String.prototype.php_strtolower = function ()
240
{
241
	return typeof(smf_iso_case_folding) == 'boolean' && smf_iso_case_folding == true ? this.php_strtr(
242
		'ABCDEFGHIJKLMNOPQRSTUVWXYZ\x8a\x8c\x8e\x9f\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde',
243
		'abcdefghijklmnopqrstuvwxyz\x9a\x9c\x9e\xff\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe'
244
	) : this.php_strtr('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
245
}
246
247
String.prototype.php_urlencode = function()
248
{
249
	return escape(this).replace(/\+/g, '%2b').replace('*', '%2a').replace('/', '%2f').replace('@', '%40');
250
}
251
252
String.prototype.php_htmlspecialchars = function()
253
{
254
	return this.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
255
}
256
257
String.prototype.php_unhtmlspecialchars = function()
258
{
259
	return this.replace(/&quot;/g, '"').replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&amp;/g, '&');
260
}
261
262
String.prototype.php_addslashes = function()
263
{
264
	return this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'');
265
}
266
267
String.prototype._replaceEntities = function(sInput, sDummy, sNum)
268
{
269
	return String.fromCharCode(parseInt(sNum));
270
}
271
272
String.prototype.removeEntities = function()
273
{
274
	return this.replace(/&(amp;)?#(\d+);/g, this._replaceEntities);
275
}
276
277
String.prototype.easyReplace = function (oReplacements)
278
{
279
	var sResult = this;
280
	for (var sSearch in oReplacements)
281
		sResult = sResult.replace(new RegExp('%' + sSearch + '%', 'g'), oReplacements[sSearch]);
282
283
	return sResult;
284
}
285
286
// Open a new window
287
function reqWin(desktopURL, alternateWidth, alternateHeight, noScrollbars)
288
{
289
	if ((alternateWidth && self.screen.availWidth * 0.8 < alternateWidth) || (alternateHeight && self.screen.availHeight * 0.8 < alternateHeight))
290
	{
291
		noScrollbars = false;
292
		alternateWidth = Math.min(alternateWidth, self.screen.availWidth * 0.8);
293
		alternateHeight = Math.min(alternateHeight, self.screen.availHeight * 0.8);
294
	}
295
	else
296
		noScrollbars = typeof(noScrollbars) == 'boolean' && noScrollbars == true;
297
298
	window.open(desktopURL, 'requested_popup', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=' + (noScrollbars ? 'no' : 'yes') + ',width=' + (alternateWidth ? alternateWidth : 480) + ',height=' + (alternateHeight ? alternateHeight : 220) + ',resizable=no');
299
300
	// Return false so the click won't follow the link ;).
301
	return false;
302
}
303
304
// Open a overlay div
305
function reqOverlayDiv(desktopURL, sHeader, sIcon)
306
{
307
	// Set up our div details
308
	var sAjax_indicator = '<div class="centertext"><img src="' + smf_images_url + '/loading_sm.gif"></div>';
309
	var sIcon = smf_images_url + '/' + (typeof(sIcon) == 'string' ? sIcon : 'helptopics.png');
310
	var sHeader = typeof(sHeader) == 'string' ? sHeader : help_popup_heading_text;
311
312
	// Create the div that we are going to load
313
	var oContainer = new smc_Popup({heading: sHeader, content: sAjax_indicator, icon: sIcon});
314
	var oPopup_body = $('#' + oContainer.popup_id).find('.popup_content');
315
316
	// Load the help page content (we just want the text to show)
317
	$.ajax({
318
		url: desktopURL,
319
		type: "GET",
320
		dataType: "html",
321
		beforeSend: function () {
322
		},
323
		success: function (data, textStatus, xhr) {
324
			var help_content = $('<div id="temp_help">').html(data).find('a[href$="self.close();"]').hide().prev('br').hide().parent().html();
325
			oPopup_body.html(help_content);
326
		},
327
		error: function (xhr, textStatus, errorThrown) {
328
			oPopup_body.html(textStatus);
329
		},
330
		statusCode: {
331
			500: function() {
332
				if (sHeader == 'Login')
333
					oPopup_body.html(banned_text);
334
				else
335
					oPopup_body.html('500 Internal Server Error');
336
			}
337
		}
338
	});
339
	return false;
340
}
341
342
// Create the popup menus for the top level/user menu area.
343
function smc_PopupMenu(oOptions)
344
{
345
	this.opt = (typeof oOptions == 'object') ? oOptions : {};
346
	this.opt.menus = {};
347
}
348
349
smc_PopupMenu.prototype.add = function (sItem, sUrl)
350
{
351
	var $menu = $('#' + sItem + '_menu'), $item = $('#' + sItem + '_menu_top');
352
	if ($item.length == 0)
353
		return;
354
355
	this.opt.menus[sItem] = {open: false, loaded: false, sUrl: sUrl, itemObj: $item, menuObj: $menu };
356
357
	$item.click({obj: this}, function (e) {
358
		e.preventDefault();
359
360
		e.data.obj.toggle(sItem);
361
	});
362
}
363
364
smc_PopupMenu.prototype.toggle = function (sItem)
365
{
366
	if (!!this.opt.menus[sItem].open)
367
		this.close(sItem);
368
	else
369
		this.open(sItem);
370
}
371
372
smc_PopupMenu.prototype.open = function (sItem)
373
{
374
	this.closeAll();
375
376
	if (!this.opt.menus[sItem].loaded)
377
	{
378
		this.opt.menus[sItem].menuObj.html('<div class="loading">' + (typeof(ajax_notification_text) != null ? ajax_notification_text : '') + '</div>');
379
		this.opt.menus[sItem].menuObj.load(this.opt.menus[sItem].sUrl, function() {
380
			if ($(this).hasClass('scrollable'))
381
				$(this).customScrollbar({
382
					skin: "default-skin",
383
					hScroll: false,
384
					updateOnWindowResize: true
385
				});
386
		});
387
		this.opt.menus[sItem].loaded = true;
388
	}
389
390
	this.opt.menus[sItem].menuObj.addClass('visible');
391
	this.opt.menus[sItem].itemObj.addClass('open');
392
	this.opt.menus[sItem].open = true;
393
394
	// Now set up closing the menu if we click off.
395
	$(document).on('click.menu', {obj: this}, function(e) {
396
		if ($(e.target).closest(e.data.obj.opt.menus[sItem].menuObj.parent()).length)
397
			return;
398
		e.data.obj.closeAll();
399
		$(document).off('click.menu');
400
	});
401
}
402
403
smc_PopupMenu.prototype.close = function (sItem)
404
{
405
	this.opt.menus[sItem].menuObj.removeClass('visible');
406
	this.opt.menus[sItem].itemObj.removeClass('open');
407
	this.opt.menus[sItem].open = false;
408
	$(document).off('click.menu');
409
}
410
411
smc_PopupMenu.prototype.closeAll = function ()
412
{
413
	for (var prop in this.opt.menus)
414
		if (!!this.opt.menus[prop].open)
415
			this.close(prop);
416
}
417
418
// *** smc_Popup class.
419
function smc_Popup(oOptions)
420
{
421
	this.opt = oOptions;
422
	this.popup_id = this.opt.custom_id ? this.opt.custom_id : 'smf_popup';
423
	this.show();
424
}
425
426
smc_Popup.prototype.show = function ()
427
{
428
	popup_class = 'popup_window ' + (this.opt.custom_class ? this.opt.custom_class : 'description');
0 ignored issues
show
The variable popup_class seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.popup_class.
Loading history...
429
	if (this.opt.icon_class)
430
		icon = '<span class="' + this.opt.icon_class + '"></span> ';
0 ignored issues
show
The variable icon seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.icon.
Loading history...
431
	else
432
		icon = this.opt.icon ? '<img src="' + this.opt.icon + '" class="icon" alt=""> ' : '';
433
434
	// Create the div that will be shown
435
	$('body').append('<div id="' + this.popup_id + '" class="popup_container"><div class="' + popup_class + '"><div class="catbg popup_heading"><a href="javascript:void(0);" class="generic_icons hide_popup"></a>' + icon + this.opt.heading + '</div><div class="popup_content">' + this.opt.content + '</div></div></div>');
436
437
	// Show it
438
	this.popup_body = $('#' + this.popup_id).children('.popup_window');
439
	this.popup_body.parent().fadeIn(300);
440
441
	// Trigger hide on escape or mouse click
442
	var popup_instance = this;
443
	$(document).mouseup(function (e) {
444
		if ($('#' + popup_instance.popup_id).has(e.target).length === 0)
445
			popup_instance.hide();
446
	}).keyup(function(e){
447
		if (e.keyCode == 27)
448
			popup_instance.hide();
449
	});
450
	$('#' + this.popup_id).find('.hide_popup').click(function (){ return popup_instance.hide(); });
451
452
	return false;
453
}
454
455
smc_Popup.prototype.hide = function ()
456
{
457
	$('#' + this.popup_id).fadeOut(300, function(){ $(this).remove(); });
458
459
	return false;
460
}
461
462
// Remember the current position.
463
function storeCaret(oTextHandle)
464
{
465
	// Only bother if it will be useful.
466
	if ('createTextRange' in oTextHandle)
467
		oTextHandle.caretPos = document.selection.createRange().duplicate();
468
}
469
470
// Replaces the currently selected text with the passed text.
471
function replaceText(text, oTextHandle)
472
{
473
	// Attempt to create a text range (IE).
474
	if ('caretPos' in oTextHandle && 'createTextRange' in oTextHandle)
475
	{
476
		var caretPos = oTextHandle.caretPos;
477
478
		caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
479
		caretPos.select();
480
	}
481
	// Mozilla text range replace.
482
	else if ('selectionStart' in oTextHandle)
483
	{
484
		var begin = oTextHandle.value.substr(0, oTextHandle.selectionStart);
485
		var end = oTextHandle.value.substr(oTextHandle.selectionEnd);
486
		var scrollPos = oTextHandle.scrollTop;
487
488
		oTextHandle.value = begin + text + end;
489
490
		if (oTextHandle.setSelectionRange)
491
		{
492
			oTextHandle.focus();
493
			var goForward = is_opera ? text.match(/\n/g).length : 0;
494
			oTextHandle.setSelectionRange(begin.length + text.length + goForward, begin.length + text.length + goForward);
495
		}
496
		oTextHandle.scrollTop = scrollPos;
497
	}
498
	// Just put it on the end.
499
	else
500
	{
501
		oTextHandle.value += text;
502
		oTextHandle.focus(oTextHandle.value.length - 1);
503
	}
504
}
505
506
// Surrounds the selected text with text1 and text2.
507
function surroundText(text1, text2, oTextHandle)
508
{
509
	// Can a text range be created?
510
	if ('caretPos' in oTextHandle && 'createTextRange' in oTextHandle)
511
	{
512
		var caretPos = oTextHandle.caretPos, temp_length = caretPos.text.length;
513
514
		caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text1 + caretPos.text + text2 + ' ' : text1 + caretPos.text + text2;
515
516
		if (temp_length == 0)
517
		{
518
			caretPos.moveStart('character', -text2.length);
519
			caretPos.moveEnd('character', -text2.length);
520
			caretPos.select();
521
		}
522
		else
523
			oTextHandle.focus(caretPos);
524
	}
525
	// Mozilla text range wrap.
526
	else if ('selectionStart' in oTextHandle)
527
	{
528
		var begin = oTextHandle.value.substr(0, oTextHandle.selectionStart);
529
		var selection = oTextHandle.value.substr(oTextHandle.selectionStart, oTextHandle.selectionEnd - oTextHandle.selectionStart);
530
		var end = oTextHandle.value.substr(oTextHandle.selectionEnd);
531
		var newCursorPos = oTextHandle.selectionStart;
532
		var scrollPos = oTextHandle.scrollTop;
533
534
		oTextHandle.value = begin + text1 + selection + text2 + end;
535
536
		if (oTextHandle.setSelectionRange)
537
		{
538
			var goForward = is_opera ? text1.match(/\n/g).length : 0, goForwardAll = is_opera ? (text1 + text2).match(/\n/g).length : 0;
539
			if (selection.length == 0)
540
				oTextHandle.setSelectionRange(newCursorPos + text1.length + goForward, newCursorPos + text1.length + goForward);
541
			else
542
				oTextHandle.setSelectionRange(newCursorPos, newCursorPos + text1.length + selection.length + text2.length + goForwardAll);
543
			oTextHandle.focus();
544
		}
545
		oTextHandle.scrollTop = scrollPos;
546
	}
547
	// Just put them on the end, then.
548
	else
549
	{
550
		oTextHandle.value += text1 + text2;
551
		oTextHandle.focus(oTextHandle.value.length - 1);
552
	}
553
}
554
555
// Checks if the passed input's value is nothing.
556
function isEmptyText(theField)
557
{
558
	// Copy the value so changes can be made..
559
	if (typeof(theField) == 'string')
560
		var theValue = theField;
561
	else
562
		var theValue = theField.value;
563
564
	// Strip whitespace off the left side.
565
	while (theValue.length > 0 && (theValue.charAt(0) == ' ' || theValue.charAt(0) == '\t'))
566
		theValue = theValue.substring(1, theValue.length);
567
	// Strip whitespace off the right side.
568
	while (theValue.length > 0 && (theValue.charAt(theValue.length - 1) == ' ' || theValue.charAt(theValue.length - 1) == '\t'))
569
		theValue = theValue.substring(0, theValue.length - 1);
570
571
	if (theValue == '')
572
		return true;
573
	else
574
		return false;
575
}
576
577
// Only allow form submission ONCE.
578
function submitonce(theform)
579
{
580
	smf_formSubmitted = true;
581
582
	// If there are any editors warn them submit is coming!
583
	for (var i = 0; i < smf_editorArray.length; i++)
584
		smf_editorArray[i].doSubmit();
585
}
586
function submitThisOnce(oControl)
587
{
588
	// oControl might also be a form.
589
	var oForm = 'form' in oControl ? oControl.form : oControl;
590
591
	var aTextareas = oForm.getElementsByTagName('textarea');
592
	for (var i = 0, n = aTextareas.length; i < n; i++)
593
		aTextareas[i].readOnly = true;
594
595
	return !smf_formSubmitted;
596
}
597
598
// Deprecated, as innerHTML is supported everywhere.
599
function setInnerHTML(oElement, sToValue)
600
{
601
	oElement.innerHTML = sToValue;
602
}
603
604
function getInnerHTML(oElement)
605
{
606
	return oElement.innerHTML;
607
}
608
609
// Set the "outer" HTML of an element.
610
function setOuterHTML(oElement, sToValue)
611
{
612
	if ('outerHTML' in oElement)
613
		oElement.outerHTML = sToValue;
614
	else
615
	{
616
		var range = document.createRange();
617
		range.setStartBefore(oElement);
618
		oElement.parentNode.replaceChild(range.createContextualFragment(sToValue), oElement);
619
	}
620
}
621
622
// Checks for variable in theArray.
623
function in_array(variable, theArray)
624
{
625
	for (var i in theArray)
626
		if (theArray[i] == variable)
627
			return true;
628
629
	return false;
630
}
631
632
// Checks for variable in theArray.
633
function array_search(variable, theArray)
634
{
635
	for (var i in theArray)
636
		if (theArray[i] == variable)
637
			return i;
638
639
	return null;
640
}
641
642
// Find a specific radio button in its group and select it.
643
function selectRadioByName(oRadioGroup, sName)
644
{
645
	if (!('length' in oRadioGroup))
646
		return oRadioGroup.checked = true;
647
648
	for (var i = 0, n = oRadioGroup.length; i < n; i++)
649
		if (oRadioGroup[i].value == sName)
650
			return oRadioGroup[i].checked = true;
651
652
	return false;
653
}
654
655
function selectAllRadio(oInvertCheckbox, oForm, sMask, sValue, bIgnoreDisabled)
656
{
657
	for (var i = 0; i < oForm.length; i++)
658
		if (oForm[i].name != undefined && oForm[i].name.substr(0, sMask.length) == sMask && oForm[i].value == sValue && (!oForm[i].disabled || (typeof(bIgnoreDisabled) == 'boolean' && bIgnoreDisabled)))
659
			oForm[i].checked = true;
660
}
661
662
// Invert all checkboxes at once by clicking a single checkbox.
663
function invertAll(oInvertCheckbox, oForm, sMask, bIgnoreDisabled)
664
{
665
	for (var i = 0; i < oForm.length; i++)
666
	{
667
		if (!('name' in oForm[i]) || (typeof(sMask) == 'string' && oForm[i].name.substr(0, sMask.length) != sMask && oForm[i].id.substr(0, sMask.length) != sMask))
668
			continue;
669
670
		if (!oForm[i].disabled || (typeof(bIgnoreDisabled) == 'boolean' && bIgnoreDisabled))
671
			oForm[i].checked = oInvertCheckbox.checked;
672
	}
673
}
674
675
// Keep the session alive - always!
676
var lastKeepAliveCheck = new Date().getTime();
677
function smf_sessionKeepAlive()
678
{
679
	var curTime = new Date().getTime();
680
681
	// Prevent a Firefox bug from hammering the server.
682
	if (smf_scripturl && curTime - lastKeepAliveCheck > 900000)
683
	{
684
		var tempImage = new Image();
685
		tempImage.src = smf_prepareScriptUrl(smf_scripturl) + 'action=keepalive;time=' + curTime;
686
		lastKeepAliveCheck = curTime;
687
	}
688
689
	window.setTimeout('smf_sessionKeepAlive();', 1200000);
690
}
691
window.setTimeout('smf_sessionKeepAlive();', 1200000);
692
693
// Set a theme option through javascript.
694
function smf_setThemeOption(theme_var, theme_value, theme_id, theme_cur_session_id, theme_cur_session_var, theme_additional_vars)
695
{
696
	// Compatibility.
697
	if (theme_cur_session_id == null)
698
		theme_cur_session_id = smf_session_id;
699
	if (typeof(theme_cur_session_var) == 'undefined')
700
		theme_cur_session_var = 'sesc';
701
702
	if (theme_additional_vars == null)
703
		theme_additional_vars = '';
704
705
	var tempImage = new Image();
706
	tempImage.src = smf_prepareScriptUrl(smf_scripturl) + 'action=jsoption;var=' + theme_var + ';val=' + theme_value + ';' + theme_cur_session_var + '=' + theme_cur_session_id + theme_additional_vars + (theme_id == null ? '' : '&th=' + theme_id) + ';time=' + (new Date().getTime());
707
}
708
709
// Shows the page numbers by clicking the dots (in compact view).
710
function expandPages(spanNode, baseLink, firstPage, lastPage, perPage)
711
{
712
	var replacement = '', i, oldLastPage = 0;
713
	var perPageLimit = 50;
714
715
	// Prevent too many pages to be loaded at once.
716
	if ((lastPage - firstPage) / perPage > perPageLimit)
717
	{
718
		oldLastPage = lastPage;
719
		lastPage = firstPage + perPageLimit * perPage;
720
	}
721
722
	// Calculate the new pages.
723
	for (i = firstPage; i < lastPage; i += perPage)
724
		replacement += baseLink.replace(/%1\$d/, i).replace(/%2\$s/, 1 + i / perPage).replace(/%%/g, '%');
725
726
	// Add the new page links.
727
	$(spanNode).before(replacement);
728
729
	if (oldLastPage)
730
		// Access the raw DOM element so the native onclick event can be overridden.
731
		spanNode.onclick = function ()
732
		{
733
			expandPages(spanNode, baseLink, lastPage, oldLastPage, perPage);
734
		};
735
	else
736
		$(spanNode).remove();
737
}
738
739
function smc_preCacheImage(sSrc)
740
{
741
	if (!('smc_aCachedImages' in window))
742
		window.smc_aCachedImages = [];
743
744
	if (!in_array(sSrc, window.smc_aCachedImages))
745
	{
746
		var oImage = new Image();
747
		oImage.src = sSrc;
748
	}
749
}
750
751
752
// *** smc_Cookie class.
753
function smc_Cookie(oOptions)
754
{
755
	this.opt = oOptions;
756
	this.oCookies = {};
757
	this.init();
758
}
759
760
smc_Cookie.prototype.init = function()
761
{
762
	if ('cookie' in document && document.cookie != '')
763
	{
764
		var aCookieList = document.cookie.split(';');
765
		for (var i = 0, n = aCookieList.length; i < n; i++)
766
		{
767
			var aNameValuePair = aCookieList[i].split('=');
768
			this.oCookies[aNameValuePair[0].replace(/^\s+|\s+$/g, '')] = decodeURIComponent(aNameValuePair[1]);
769
		}
770
	}
771
}
772
773
smc_Cookie.prototype.get = function(sKey)
774
{
775
	return sKey in this.oCookies ? this.oCookies[sKey] : null;
776
}
777
778
smc_Cookie.prototype.set = function(sKey, sValue)
779
{
780
	document.cookie = sKey + '=' + encodeURIComponent(sValue);
781
}
782
783
784
// *** smc_Toggle class.
785
function smc_Toggle(oOptions)
786
{
787
	this.opt = oOptions;
788
	this.bCollapsed = false;
789
	this.oCookie = null;
790
	this.init();
791
}
792
793
smc_Toggle.prototype.init = function ()
794
{
795
	// The master switch can disable this toggle fully.
796
	if ('bToggleEnabled' in this.opt && !this.opt.bToggleEnabled)
797
		return;
798
799
	// If cookies are enabled and they were set, override the initial state.
800
	if ('oCookieOptions' in this.opt && this.opt.oCookieOptions.bUseCookie)
801
	{
802
		// Initialize the cookie handler.
803
		this.oCookie = new smc_Cookie({});
804
805
		// Check if the cookie is set.
806
		var cookieValue = this.oCookie.get(this.opt.oCookieOptions.sCookieName)
807
		if (cookieValue != null)
808
			this.opt.bCurrentlyCollapsed = cookieValue == '1';
809
	}
810
811
	// Initialize the images to be clickable.
812
	if ('aSwapImages' in this.opt)
813
	{
814
		for (var i = 0, n = this.opt.aSwapImages.length; i < n; i++)
815
		{
816
			this.opt.aSwapImages[i].isCSS = (typeof this.opt.aSwapImages[i].srcCollapsed == 'undefined');
817
			if (this.opt.aSwapImages[i].isCSS)
818
			{
819
				if (!this.opt.aSwapImages[i].cssCollapsed)
820
					this.opt.aSwapImages[i].cssCollapsed = 'toggle_down';
821
				if (!this.opt.aSwapImages[i].cssExpanded)
822
					this.opt.aSwapImages[i].cssExpanded = 'toggle_up';
823
			}
824
			else
825
			{
826
				// Preload the collapsed image.
827
				smc_preCacheImage(this.opt.aSwapImages[i].srcCollapsed);
828
			}
829
830
			// Display the image in case it was hidden.
831
			$('#' + this.opt.aSwapImages[i].sId).show();
832
			var oImage = document.getElementById(this.opt.aSwapImages[i].sId);
833
			if (typeof(oImage) == 'object' && oImage != null)
834
			{
835
				oImage.instanceRef = this;
836
				oImage.onclick = function () {
837
					this.instanceRef.toggle();
838
					this.blur();
839
				}
840
				oImage.style.cursor = 'pointer';
841
			}
842
		}
843
	}
844
845
	// Initialize links.
846
	if ('aSwapLinks' in this.opt)
847
	{
848
		for (var i = 0, n = this.opt.aSwapLinks.length; i < n; i++)
849
		{
850
			var oLink = document.getElementById(this.opt.aSwapLinks[i].sId);
851
			if (typeof(oLink) == 'object' && oLink != null)
852
			{
853
				// Display the link in case it was hidden.
854
				if (oLink.style.display == 'none')
855
					oLink.style.display = '';
856
857
				oLink.instanceRef = this;
858
				oLink.onclick = function () {
859
					this.instanceRef.toggle();
860
					this.blur();
861
					return false;
862
				}
863
			}
864
		}
865
	}
866
867
	// If the init state is set to be collapsed, collapse it.
868
	if (this.opt.bCurrentlyCollapsed)
869
		this.changeState(true, true);
870
}
871
872
// Collapse or expand the section.
873
smc_Toggle.prototype.changeState = function(bCollapse, bInit)
874
{
875
	// Default bInit to false.
876
	bInit = typeof(bInit) == 'undefined' ? false : true;
877
878
	// Handle custom function hook before collapse.
879
	if (!bInit && bCollapse && 'funcOnBeforeCollapse' in this.opt)
880
	{
881
		this.tmpMethod = this.opt.funcOnBeforeCollapse;
882
		this.tmpMethod();
883
		delete this.tmpMethod;
884
	}
885
886
	// Handle custom function hook before expand.
887
	else if (!bInit && !bCollapse && 'funcOnBeforeExpand' in this.opt)
888
	{
889
		this.tmpMethod = this.opt.funcOnBeforeExpand;
890
		this.tmpMethod();
891
		delete this.tmpMethod;
892
	}
893
894
	// Loop through all the images that need to be toggled.
895
	if ('aSwapImages' in this.opt)
896
	{
897
		for (var i = 0, n = this.opt.aSwapImages.length; i < n; i++)
898
		{
899
			if (this.opt.aSwapImages[i].isCSS)
900
			{
901
				$('#' + this.opt.aSwapImages[i].sId).toggleClass(this.opt.aSwapImages[i].cssCollapsed, bCollapse).toggleClass(this.opt.aSwapImages[i].cssExpanded, !bCollapse).attr('title', bCollapse ? this.opt.aSwapImages[i].altCollapsed : this.opt.aSwapImages[i].altExpanded);
902
			}
903
			else
904
			{
905
				var oImage = document.getElementById(this.opt.aSwapImages[i].sId);
906
				if (typeof(oImage) == 'object' && oImage != null)
907
				{
908
					// Only (re)load the image if it's changed.
909
					var sTargetSource = bCollapse ? this.opt.aSwapImages[i].srcCollapsed : this.opt.aSwapImages[i].srcExpanded;
910
					if (oImage.src != sTargetSource)
911
						oImage.src = sTargetSource;
912
913
					oImage.alt = oImage.title = bCollapse ? this.opt.aSwapImages[i].altCollapsed : this.opt.aSwapImages[i].altExpanded;
914
				}
915
			}
916
		}
917
	}
918
919
	// Loop through all the links that need to be toggled.
920
	if ('aSwapLinks' in this.opt)
921
	{
922
		for (var i = 0, n = this.opt.aSwapLinks.length; i < n; i++)
923
		{
924
			var oLink = document.getElementById(this.opt.aSwapLinks[i].sId);
925
			if (typeof(oLink) == 'object' && oLink != null)
926
				setInnerHTML(oLink, bCollapse ? this.opt.aSwapLinks[i].msgCollapsed : this.opt.aSwapLinks[i].msgExpanded);
927
		}
928
	}
929
930
	// Now go through all the sections to be collapsed.
931
	for (var i = 0, n = this.opt.aSwappableContainers.length; i < n; i++)
932
	{
933
		if (this.opt.aSwappableContainers[i] == null)
934
			continue;
935
936
		var oContainer = document.getElementById(this.opt.aSwappableContainers[i]);
937
		if (typeof(oContainer) == 'object' && oContainer != null)
938
		{
939
			if (!!this.opt.bNoAnimate || bInit)
940
			{
941
				$(oContainer).toggle(!bCollapse);
942
			}
943
			else
944
			{
945
				if (bCollapse)
946
				{
947
					if (this.opt.aHeader != null && this.opt.aHeader.hasClass('cat_bar'))
948
						$(this.opt.aHeader).addClass('collapsed');
949
					$(oContainer).slideUp();
950
				}
951
				else
952
				{
953
					if (this.opt.aHeader != null && this.opt.aHeader.hasClass('cat_bar'))
954
						$(this.opt.aHeader).removeClass('collapsed');
955
					$(oContainer).slideDown();
956
				}
957
			}
958
		}
959
	}
960
961
	// Update the new state.
962
	this.bCollapsed = bCollapse;
963
964
	// Update the cookie, if desired.
965
	if ('oCookieOptions' in this.opt && this.opt.oCookieOptions.bUseCookie)
966
		this.oCookie.set(this.opt.oCookieOptions.sCookieName, this.bCollapsed | 0);
967
968
	if (!bInit && 'oThemeOptions' in this.opt && this.opt.oThemeOptions.bUseThemeSettings)
969
		smf_setThemeOption(this.opt.oThemeOptions.sOptionName, this.bCollapsed | 0, 'sThemeId' in this.opt.oThemeOptions ? this.opt.oThemeOptions.sThemeId : null, smf_session_id, smf_session_var, 'sAdditionalVars' in this.opt.oThemeOptions ? this.opt.oThemeOptions.sAdditionalVars : null);
970
}
971
972
smc_Toggle.prototype.toggle = function()
973
{
974
	// Change the state by reversing the current state.
975
	this.changeState(!this.bCollapsed);
976
}
977
978
979
function ajax_indicator(turn_on)
980
{
981
	if (ajax_indicator_ele == null)
982
	{
983
		ajax_indicator_ele = document.getElementById('ajax_in_progress');
984
985
		if (ajax_indicator_ele == null && typeof(ajax_notification_text) != null)
986
		{
987
			create_ajax_indicator_ele();
988
		}
989
	}
990
991
	if (ajax_indicator_ele != null)
992
	{
993
		ajax_indicator_ele.style.display = turn_on ? 'block' : 'none';
994
	}
995
}
996
997
function create_ajax_indicator_ele()
998
{
999
	// Create the div for the indicator.
1000
	ajax_indicator_ele = document.createElement('div');
1001
1002
	// Set the id so it'll load the style properly.
1003
	ajax_indicator_ele.id = 'ajax_in_progress';
1004
1005
	// Set the text.  (Note:  You MUST append here and not overwrite.)
1006
	ajax_indicator_ele.innerHTML += ajax_notification_text;
1007
1008
	// Finally attach the element to the body.
1009
	document.body.appendChild(ajax_indicator_ele);
1010
}
1011
1012
function createEventListener(oTarget)
1013
{
1014
	if (!('addEventListener' in oTarget))
1015
	{
1016
		if (oTarget.attachEvent)
1017
		{
1018
			oTarget.addEventListener = function (sEvent, funcHandler, bCapture) {
1019
				oTarget.attachEvent('on' + sEvent, funcHandler);
1020
			}
1021
			oTarget.removeEventListener = function (sEvent, funcHandler, bCapture) {
1022
				oTarget.detachEvent('on' + sEvent, funcHandler);
1023
			}
1024
		}
1025
		else
1026
		{
1027
			oTarget.addEventListener = function (sEvent, funcHandler, bCapture) {
1028
				oTarget['on' + sEvent] = funcHandler;
1029
			}
1030
			oTarget.removeEventListener = function (sEvent, funcHandler, bCapture) {
1031
				oTarget['on' + sEvent] = null;
1032
			}
1033
		}
1034
	}
1035
}
1036
1037
// This function will retrieve the contents needed for the jump to boxes.
1038
function grabJumpToContent(elem)
1039
{
1040
	var oXMLDoc = getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=jumpto;xml');
1041
	var aBoardsAndCategories = [];
1042
1043
	ajax_indicator(true);
1044
1045
	oXMLDoc.done(function(data, textStatus, jqXHR){
1046
1047
		var items = $(data).find('item');
1048
			items.each(function(i) {
1049
			aBoardsAndCategories[i] = {
1050
				id: parseInt($(this).attr('id')),
1051
				isCategory: $(this).attr('type') == 'category',
1052
				name: this.firstChild.nodeValue.removeEntities(),
1053
				is_current: false,
1054
				childLevel: parseInt($(this).attr('childlevel'))
1055
			}
1056
		});
1057
1058
		ajax_indicator(false);
1059
1060
		for (var i = 0, n = aJumpTo.length; i < n; i++)
1061
		{
1062
			aJumpTo[i].fillSelect(aBoardsAndCategories);
1063
		}
1064
	});
1065
}
1066
1067
// This'll contain all JumpTo objects on the page.
1068
var aJumpTo = new Array();
1069
1070
// *** JumpTo class.
1071
function JumpTo(oJumpToOptions)
1072
{
1073
	this.opt = oJumpToOptions;
1074
	this.dropdownList = null;
1075
	this.showSelect();
1076
1077
	// Register a change event after the select has been created.
1078
	$('#' + this.opt.sContainerId).one('mouseenter', function() {
1079
		grabJumpToContent(this);
1080
	});
1081
}
1082
1083
// Show the initial select box (onload). Method of the JumpTo class.
1084
JumpTo.prototype.showSelect = function ()
1085
{
1086
	var sChildLevelPrefix = '';
1087
	for (var i = this.opt.iCurBoardChildLevel; i > 0; i--)
1088
		sChildLevelPrefix += this.opt.sBoardChildLevelIndicator;
1089
	setInnerHTML(document.getElementById(this.opt.sContainerId), this.opt.sJumpToTemplate.replace(/%select_id%/, this.opt.sContainerId + '_select').replace(/%dropdown_list%/, '<select ' + (this.opt.bDisabled == true ? 'disabled ' : '') + (this.opt.sClassName != undefined ? 'class="' + this.opt.sClassName + '" ' : '') + 'name="' + (this.opt.sCustomName != undefined ? this.opt.sCustomName : this.opt.sContainerId + '_select') + '" id="' + this.opt.sContainerId + '_select"><option value="' + (this.opt.bNoRedirect != undefined && this.opt.bNoRedirect == true ? this.opt.iCurBoardId : '?board=' + this.opt.iCurBoardId + '.0') + '">' + sChildLevelPrefix + this.opt.sBoardPrefix + this.opt.sCurBoardName.removeEntities() + '</option></select>&nbsp;' + (this.opt.sGoButtonLabel != undefined ? '<input type="button" class="button" value="' + this.opt.sGoButtonLabel + '" onclick="window.location.href = \'' + smf_prepareScriptUrl(smf_scripturl) + 'board=' + this.opt.iCurBoardId + '.0\';">' : '')));
1090
	this.dropdownList = document.getElementById(this.opt.sContainerId + '_select');
1091
}
1092
1093
// Fill the jump to box with entries. Method of the JumpTo class.
1094
JumpTo.prototype.fillSelect = function (aBoardsAndCategories)
1095
{
1096
	var iIndexPointer = 0;
1097
1098
	// Create an option that'll be above and below the category.
1099
	var oDashOption = document.createElement('option');
1100
	oDashOption.appendChild(document.createTextNode(this.opt.sCatSeparator));
1101
	oDashOption.disabled = 'disabled';
1102
	oDashOption.value = '';
1103
1104
	if ('onbeforeactivate' in document)
1105
		this.dropdownList.onbeforeactivate = null;
1106
	else
1107
		this.dropdownList.onfocus = null;
1108
1109
	if (this.opt.bNoRedirect)
1110
		this.dropdownList.options[0].disabled = 'disabled';
1111
1112
	// Create a document fragment that'll allowing inserting big parts at once.
1113
	var oListFragment = document.createDocumentFragment();
1114
1115
	// Loop through all items to be added.
1116
	for (var i = 0, n = aBoardsAndCategories.length; i < n; i++)
1117
	{
1118
		var j, sChildLevelPrefix, oOption;
1119
1120
		// If we've reached the currently selected board add all items so far.
1121
		if (!aBoardsAndCategories[i].isCategory && aBoardsAndCategories[i].id == this.opt.iCurBoardId)
1122
		{
1123
				this.dropdownList.insertBefore(oListFragment, this.dropdownList.options[0]);
1124
				oListFragment = document.createDocumentFragment();
1125
				continue;
1126
		}
1127
1128
		if (aBoardsAndCategories[i].isCategory)
1129
			oListFragment.appendChild(oDashOption.cloneNode(true));
1130
		else
1131
			for (j = aBoardsAndCategories[i].childLevel, sChildLevelPrefix = ''; j > 0; j--)
1132
				sChildLevelPrefix += this.opt.sBoardChildLevelIndicator;
1133
1134
		oOption = document.createElement('option');
1135
		oOption.appendChild(document.createTextNode((aBoardsAndCategories[i].isCategory ? this.opt.sCatPrefix : sChildLevelPrefix + this.opt.sBoardPrefix) + aBoardsAndCategories[i].name));
0 ignored issues
show
The variable sChildLevelPrefix seems to not be initialized for all possible execution paths.
Loading history...
1136
		if (!this.opt.bNoRedirect)
1137
			oOption.value = aBoardsAndCategories[i].isCategory ? '#c' + aBoardsAndCategories[i].id : '?board=' + aBoardsAndCategories[i].id + '.0';
1138
		else
1139
		{
1140
			if (aBoardsAndCategories[i].isCategory)
1141
				oOption.disabled = 'disabled';
1142
			else
1143
				oOption.value = aBoardsAndCategories[i].id;
1144
		}
1145
		oListFragment.appendChild(oOption);
1146
1147
		if (aBoardsAndCategories[i].isCategory)
1148
			oListFragment.appendChild(oDashOption.cloneNode(true));
1149
	}
1150
1151
	// Add the remaining items after the currently selected item.
1152
	this.dropdownList.appendChild(oListFragment);
1153
1154
	// Add an onchange action
1155
	if (!this.opt.bNoRedirect)
1156
		this.dropdownList.onchange = function() {
1157
			if (this.selectedIndex > 0 && this.options[this.selectedIndex].value)
1158
				window.location.href = smf_scripturl + this.options[this.selectedIndex].value.substr(smf_scripturl.indexOf('?') == -1 || this.options[this.selectedIndex].value.substr(0, 1) != '?' ? 0 : 1);
1159
		}
1160
}
1161
1162
// A global array containing all IconList objects.
1163
var aIconLists = new Array();
1164
1165
// *** IconList object.
1166
function IconList(oOptions)
1167
{
1168
	if (!window.XMLHttpRequest)
1169
		return;
1170
1171
	this.opt = oOptions;
1172
	this.bListLoaded = false;
1173
	this.oContainerDiv = null;
1174
	this.funcMousedownHandler = null;
1175
	this.funcParent = this;
1176
	this.iCurMessageId = 0;
1177
	this.iCurTimeout = 0;
1178
1179
	// Add backwards compatibility with old themes.
1180
	if (!('sSessionVar' in this.opt))
1181
		this.opt.sSessionVar = 'sesc';
1182
1183
	this.initIcons();
1184
}
1185
1186
// Replace all message icons by icons with hoverable and clickable div's.
1187
IconList.prototype.initIcons = function ()
1188
{
1189
	for (var i = document.images.length - 1, iPrefixLength = this.opt.sIconIdPrefix.length; i >= 0; i--)
1190
		if (document.images[i].id.substr(0, iPrefixLength) == this.opt.sIconIdPrefix)
1191
			setOuterHTML(document.images[i], '<div title="' + this.opt.sLabelIconList + '" onclick="' + this.opt.sBackReference + '.openPopup(this, ' + document.images[i].id.substr(iPrefixLength) + ')" onmouseover="' + this.opt.sBackReference + '.onBoxHover(this, true)" onmouseout="' + this.opt.sBackReference + '.onBoxHover(this, false)" style="background: ' + this.opt.sBoxBackground + '; cursor: pointer; padding: 3px; text-align: center;"><img src="' + document.images[i].src + '" alt="' + document.images[i].alt + '" id="' + document.images[i].id + '"></div>');
1192
}
1193
1194
// Event for the mouse hovering over the original icon.
1195
IconList.prototype.onBoxHover = function (oDiv, bMouseOver)
1196
{
1197
	oDiv.style.border = bMouseOver ? this.opt.iBoxBorderWidthHover + 'px solid ' + this.opt.sBoxBorderColorHover : '';
1198
	oDiv.style.background = bMouseOver ? this.opt.sBoxBackgroundHover : this.opt.sBoxBackground;
1199
	oDiv.style.padding = bMouseOver ? (3 - this.opt.iBoxBorderWidthHover) + 'px' : '3px'
1200
}
1201
1202
// Show the list of icons after the user clicked the original icon.
1203
IconList.prototype.openPopup = function (oDiv, iMessageId)
1204
{
1205
	this.iCurMessageId = iMessageId;
1206
1207
	if (!this.bListLoaded && this.oContainerDiv == null)
1208
	{
1209
		// Create a container div.
1210
		this.oContainerDiv = document.createElement('div');
1211
		this.oContainerDiv.id = 'iconList';
1212
		this.oContainerDiv.style.display = 'none';
1213
		this.oContainerDiv.style.cursor = 'pointer';
1214
		this.oContainerDiv.style.position = 'absolute';
1215
		this.oContainerDiv.style.background = this.opt.sContainerBackground;
1216
		this.oContainerDiv.style.border = this.opt.sContainerBorder;
1217
		this.oContainerDiv.style.padding = '6px 0px';
1218
		document.body.appendChild(this.oContainerDiv);
1219
1220
		// Start to fetch its contents.
1221
		ajax_indicator(true);
1222
		sendXMLDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=messageicons;board=' + this.opt.iBoardId + ';xml', '', this.onIconsReceived);
1223
1224
		createEventListener(document.body);
1225
	}
1226
1227
	// Set the position of the container.
1228
	var aPos = smf_itemPos(oDiv);
1229
1230
	this.oContainerDiv.style.top = (aPos[1] + oDiv.offsetHeight) + 'px';
1231
	this.oContainerDiv.style.left = (aPos[0] - 1) + 'px';
1232
	this.oClickedIcon = oDiv;
1233
1234
	if (this.bListLoaded)
1235
		this.oContainerDiv.style.display = 'block';
1236
1237
	document.body.addEventListener('mousedown', this.onWindowMouseDown, false);
1238
}
1239
1240
// Setup the list of icons once it is received through xmlHTTP.
1241
IconList.prototype.onIconsReceived = function (oXMLDoc)
1242
{
1243
	var icons = oXMLDoc.getElementsByTagName('smf')[0].getElementsByTagName('icon');
1244
	var sItems = '';
1245
1246
	for (var i = 0, n = icons.length; i < n; i++)
1247
		sItems += '<span onmouseover="' + this.opt.sBackReference + '.onItemHover(this, true)" onmouseout="' + this.opt.sBackReference + '.onItemHover(this, false);" onmousedown="' + this.opt.sBackReference + '.onItemMouseDown(this, \'' + icons[i].getAttribute('value') + '\');" style="padding: 2px 3px; line-height: 20px; border: ' + this.opt.sItemBorder + '; background: ' + this.opt.sItemBackground + '"><img src="' + icons[i].getAttribute('url') + '" alt="' + icons[i].getAttribute('name') + '" title="' + icons[i].firstChild.nodeValue + '" style="vertical-align: middle"></span>';
1248
1249
	setInnerHTML(this.oContainerDiv, sItems);
1250
	this.oContainerDiv.style.display = 'block';
1251
	this.bListLoaded = true;
1252
1253
	if (is_ie)
1254
		this.oContainerDiv.style.width = this.oContainerDiv.clientWidth + 'px';
1255
1256
	ajax_indicator(false);
1257
}
1258
1259
// Event handler for hovering over the icons.
1260
IconList.prototype.onItemHover = function (oDiv, bMouseOver)
1261
{
1262
	oDiv.style.background = bMouseOver ? this.opt.sItemBackgroundHover : this.opt.sItemBackground;
1263
	oDiv.style.border = bMouseOver ? this.opt.sItemBorderHover : this.opt.sItemBorder;
1264
	if (this.iCurTimeout != 0)
1265
		window.clearTimeout(this.iCurTimeout);
1266
	if (bMouseOver)
1267
		this.onBoxHover(this.oClickedIcon, true);
1268
	else
1269
		this.iCurTimeout = window.setTimeout(this.opt.sBackReference + '.collapseList();', 500);
1270
}
1271
1272
// Event handler for clicking on one of the icons.
1273
IconList.prototype.onItemMouseDown = function (oDiv, sNewIcon)
1274
{
1275
	if (this.iCurMessageId != 0)
1276
	{
1277
		ajax_indicator(true);
1278
		this.tmpMethod = getXMLDocument;
1279
		var oXMLDoc = this.tmpMethod(smf_prepareScriptUrl(smf_scripturl) + 'action=jsmodify;topic=' + this.opt.iTopicId + ';msg=' + this.iCurMessageId + ';' + smf_session_var + '=' + smf_session_id + ';icon=' + sNewIcon + ';xml'),
1280
		oThis = this;
1281
		delete this.tmpMethod;
1282
		ajax_indicator(false);
1283
1284
		oXMLDoc.done(function(data, textStatus, jqXHR){
1285
			oMessage = $(data).find('message')
0 ignored issues
show
The variable oMessage seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.oMessage.
Loading history...
1286
			curMessageId = oMessage.attr('id').replace( /^\D+/g, '');
0 ignored issues
show
The variable curMessageId seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.curMessageId.
Loading history...
1287
1288
			if (oMessage.find('error').length == 0)
1289
			{
1290
				if (oThis.opt.bShowModify && oMessage.find('modified').length != 0)
1291
					$('#modified_' + curMessageId).html(oMessage.find('modified').text());
1292
1293
				oThis.oClickedIcon.getElementsByTagName('img')[0].src = oDiv.getElementsByTagName('img')[0].src;
1294
			}
1295
		});
1296
	}
1297
}
1298
1299
// Event handler for clicking outside the list (will make the list disappear).
1300
IconList.prototype.onWindowMouseDown = function ()
1301
{
1302
	for (var i = aIconLists.length - 1; i >= 0; i--)
1303
	{
1304
		aIconLists[i].funcParent.tmpMethod = aIconLists[i].collapseList;
1305
		aIconLists[i].funcParent.tmpMethod();
1306
		delete aIconLists[i].funcParent.tmpMethod;
1307
	}
1308
}
1309
1310
// Collapse the list of icons.
1311
IconList.prototype.collapseList = function()
1312
{
1313
	this.onBoxHover(this.oClickedIcon, false);
1314
	this.oContainerDiv.style.display = 'none';
1315
	this.iCurMessageId = 0;
1316
	document.body.removeEventListener('mousedown', this.onWindowMouseDown, false);
1317
}
1318
1319
// Handy shortcuts for getting the mouse position on the screen - only used for IE at the moment.
1320
function smf_mousePose(oEvent)
1321
{
1322
	var x = 0;
1323
	var y = 0;
1324
1325
	if (oEvent.pageX)
1326
	{
1327
		y = oEvent.pageY;
1328
		x = oEvent.pageX;
1329
	}
1330
	else if (oEvent.clientX)
1331
	{
1332
		x = oEvent.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
1333
		y = oEvent.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
1334
	}
1335
1336
	return [x, y];
1337
}
1338
1339
// Short function for finding the actual position of an item.
1340
function smf_itemPos(itemHandle)
1341
{
1342
	var itemX = 0;
1343
	var itemY = 0;
1344
1345
	if ('offsetParent' in itemHandle)
1346
	{
1347
		itemX = itemHandle.offsetLeft;
1348
		itemY = itemHandle.offsetTop;
1349
		while (itemHandle.offsetParent && typeof(itemHandle.offsetParent) == 'object')
1350
		{
1351
			itemHandle = itemHandle.offsetParent;
1352
			itemX += itemHandle.offsetLeft;
1353
			itemY += itemHandle.offsetTop;
1354
		}
1355
	}
1356
	else if ('x' in itemHandle)
1357
	{
1358
		itemX = itemHandle.x;
1359
		itemY = itemHandle.y;
1360
	}
1361
1362
	return [itemX, itemY];
1363
}
1364
1365
// This function takes the script URL and prepares it to allow the query string to be appended to it.
1366
function smf_prepareScriptUrl(sUrl)
1367
{
1368
	return sUrl.indexOf('?') == -1 ? sUrl + '?' : sUrl + (sUrl.charAt(sUrl.length - 1) == '?' || sUrl.charAt(sUrl.length - 1) == '&' || sUrl.charAt(sUrl.length - 1) == ';' ? '' : ';');
1369
}
1370
1371
var aOnloadEvents = new Array();
1372
function addLoadEvent(fNewOnload)
1373
{
1374
	// If there's no event set, just set this one
1375
	if (typeof(fNewOnload) == 'function' && (!('onload' in window) || typeof(window.onload) != 'function'))
1376
		window.onload = fNewOnload;
1377
1378
	// If there's just one event, setup the array.
1379
	else if (aOnloadEvents.length == 0)
1380
	{
1381
		aOnloadEvents[0] = window.onload;
1382
		aOnloadEvents[1] = fNewOnload;
1383
		window.onload = function() {
1384
			for (var i = 0, n = aOnloadEvents.length; i < n; i++)
1385
			{
1386
				if (typeof(aOnloadEvents[i]) == 'function')
1387
					aOnloadEvents[i]();
1388
				else if (typeof(aOnloadEvents[i]) == 'string')
1389
					eval(aOnloadEvents[i]);
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
1390
			}
1391
		}
1392
	}
1393
1394
	// This isn't the first event function, add it to the list.
1395
	else
1396
		aOnloadEvents[aOnloadEvents.length] = fNewOnload;
1397
}
1398
1399
// Get the text in a code tag.
1400
function smfSelectText(oCurElement, bActOnElement)
1401
{
1402
	// The place we're looking for is one div up, and next door - if it's auto detect.
1403
	if (typeof(bActOnElement) == 'boolean' && bActOnElement)
1404
		var oCodeArea = document.getElementById(oCurElement);
1405
	else
1406
		var oCodeArea = oCurElement.parentNode.nextSibling;
1407
1408
	if (typeof(oCodeArea) != 'object' || oCodeArea == null)
1409
		return false;
1410
1411
	// Start off with my favourite, internet explorer.
1412
	if ('createTextRange' in document.body)
1413
	{
1414
		var oCurRange = document.body.createTextRange();
1415
		oCurRange.moveToElementText(oCodeArea);
1416
		oCurRange.select();
1417
	}
1418
	// Firefox at el.
1419
	else if (window.getSelection)
1420
	{
1421
		var oCurSelection = window.getSelection();
1422
		// Safari is special!
1423
		if (oCurSelection.setBaseAndExtent)
1424
		{
1425
			var oLastChild = oCodeArea.lastChild;
1426
			oCurSelection.setBaseAndExtent(oCodeArea, 0, oLastChild, 'innerText' in oLastChild ? oLastChild.innerText.length : oLastChild.textContent.length);
1427
		}
1428
		else
1429
		{
1430
			var curRange = document.createRange();
1431
			curRange.selectNodeContents(oCodeArea);
1432
1433
			oCurSelection.removeAllRanges();
1434
			oCurSelection.addRange(curRange);
1435
		}
1436
	}
1437
1438
	return false;
1439
}
1440
1441
// A function used to clean the attachments on post page
1442
function cleanFileInput(idElement)
1443
{
1444
	// Simpler solutions work in Opera, IE, Safari and Chrome.
1445
	if (is_opera || is_ie || is_safari || is_chrome)
1446
	{
1447
		document.getElementById(idElement).outerHTML = document.getElementById(idElement).outerHTML;
1448
	}
1449
	// What else can we do? By the way, this doesn't work in Chrome and Mac's Safari.
1450
	else
1451
	{
1452
		document.getElementById(idElement).type = 'input';
1453
		document.getElementById(idElement).type = 'file';
1454
	}
1455
}
1456
1457
function applyWindowClasses(oList)
1458
{
1459
	var bAlternate = false;
1460
	oListItems = oList.getElementsByTagName("LI");
0 ignored issues
show
The variable oListItems seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.oListItems.
Loading history...
1461
	for (i = 0; i < oListItems.length; i++)
0 ignored issues
show
The variable i seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.i.
Loading history...
1462
	{
1463
		// Skip dummies.
1464
		if (oListItems[i].id == "")
1465
			continue;
1466
		oListItems[i].className = "windowbg" + (bAlternate ? "2" : "");
1467
		bAlternate = !bAlternate;
1468
	}
1469
}
1470
1471
function reActivate()
1472
{
1473
	document.forms.postmodify.message.readOnly = false;
1474
}
1475
1476
// The actual message icon selector.
1477
function showimage()
1478
{
1479
	document.images.icons.src = icon_urls[document.forms.postmodify.icon.options[document.forms.postmodify.icon.selectedIndex].value];
1480
}
1481
1482
function pollOptions()
1483
{
1484
	var expire_time = document.getElementById('poll_expire');
1485
1486
	if (isEmptyText(expire_time) || expire_time.value == 0)
1487
	{
1488
		document.forms.postmodify.poll_hide[2].disabled = true;
1489
		if (document.forms.postmodify.poll_hide[2].checked)
1490
			document.forms.postmodify.poll_hide[1].checked = true;
1491
	}
1492
	else
1493
		document.forms.postmodify.poll_hide[2].disabled = false;
1494
}
1495
1496
function generateDays(offset)
1497
{
1498
	// Work around JavaScript's lack of support for default values...
1499
	offset = typeof(offset) != 'undefined' ? offset : '';
1500
1501
	var days = 0, selected = 0;
1502
	var dayElement = document.getElementById("day" + offset), yearElement = document.getElementById("year" + offset), monthElement = document.getElementById("month" + offset);
1503
1504
	var monthLength = [
1505
		31, 28, 31, 30,
1506
		31, 30, 31, 31,
1507
		30, 31, 30, 31
1508
	];
1509
	if (yearElement.options[yearElement.selectedIndex].value % 4 == 0)
1510
		monthLength[1] = 29;
1511
1512
	selected = dayElement.selectedIndex;
1513
	while (dayElement.options.length)
1514
		dayElement.options[0] = null;
1515
1516
	days = monthLength[monthElement.value - 1];
1517
1518
	for (i = 1; i <= days; i++)
0 ignored issues
show
The variable i seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.i.
Loading history...
1519
		dayElement.options[dayElement.length] = new Option(i, i);
1520
1521
	if (selected < days)
1522
		dayElement.selectedIndex = selected;
1523
}
1524
1525
function toggleLinked(form)
1526
{
1527
	form.board.disabled = !form.link_to_board.checked;
1528
}
1529
1530
function initSearch()
1531
{
1532
	if (document.forms.searchform.search.value.indexOf("%u") != -1)
1533
		document.forms.searchform.search.value = unescape(document.forms.searchform.search.value);
1534
}
1535
1536
function selectBoards(ids, aFormID)
1537
{
1538
	var toggle = true;
1539
	var aForm = document.getElementById(aFormID);
1540
1541
	for (i = 0; i < ids.length; i++)
0 ignored issues
show
The variable i seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.i.
Loading history...
1542
		toggle = toggle & aForm["brd" + ids[i]].checked;
1543
1544
	for (i = 0; i < ids.length; i++)
1545
		aForm["brd" + ids[i]].checked = !toggle;
1546
}
1547
1548
function updateRuleDef(optNum)
1549
{
1550
	if (document.getElementById("ruletype" + optNum).value == "gid")
1551
	{
1552
		document.getElementById("defdiv" + optNum).style.display = "none";
1553
		document.getElementById("defseldiv" + optNum).style.display = "";
1554
	}
1555
	else if (document.getElementById("ruletype" + optNum).value == "bud" || document.getElementById("ruletype" + optNum).value == "")
1556
	{
1557
		document.getElementById("defdiv" + optNum).style.display = "none";
1558
		document.getElementById("defseldiv" + optNum).style.display = "none";
1559
	}
1560
	else
1561
	{
1562
		document.getElementById("defdiv" + optNum).style.display = "";
1563
		document.getElementById("defseldiv" + optNum).style.display = "none";
1564
	}
1565
}
1566
1567
function updateActionDef(optNum)
1568
{
1569
	if (document.getElementById("acttype" + optNum).value == "lab")
1570
	{
1571
		document.getElementById("labdiv" + optNum).style.display = "";
1572
	}
1573
	else
1574
	{
1575
		document.getElementById("labdiv" + optNum).style.display = "none";
1576
	}
1577
}
1578
1579
function smc_resize(selector)
1580
{
1581
	var allElements = [];
1582
1583
	$(selector).each(function(){
1584
		$thisElement = $(this);
0 ignored issues
show
The variable $thisElement seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.$thisElement.
Loading history...
1585
1586
		// Get rid of the width and height attributes.
1587
		$thisElement.removeAttr('width').removeAttr('height');
1588
1589
		// Get the default vars.
1590
		$thisElement.basedElement = $thisElement.parent();
1591
		$thisElement.defaultWidth = $thisElement.width();
1592
		$thisElement.defaultHeight = $thisElement.height();
1593
		$thisElement.aspectRatio = $thisElement.defaultHeight / $thisElement.defaultWidth;
1594
1595
		allElements.push($thisElement);
1596
	});
1597
1598
	$(window).resize(function(){
1599
		$(allElements).each(function(){
1600
			_innerElement = this;
0 ignored issues
show
The variable _innerElement seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window._innerElement.
Loading history...
1601
1602
			// Get the new width and height.
1603
			var newWidth = _innerElement.basedElement.width();
1604
			var newHeight = (newWidth * _innerElement.aspectRatio) <= _innerElement.defaultHeight ? (newWidth * _innerElement.aspectRatio) : _innerElement.defaultHeight;
1605
1606
			// If the new width is lower than the "default width" then apply some resizing. No? then go back to our default sizes
1607
			var applyResize = (newWidth <= _innerElement.defaultWidth),
1608
				applyWidth = !applyResize ? _innerElement.defaultWidth : newWidth,
1609
				applyHeight = !applyResize ? _innerElement.defaultHeight : newHeight;
1610
1611
			// Gotta check the applied width and height is actually something!
1612
			if (applyWidth <= 0 && applyHeight <= 0) {
1613
				applyWidth = _innerElement.defaultWidth;
1614
				applyHeight = _innerElement.defaultHeight;
1615
			}
1616
1617
			// Finally resize the element!
1618
			_innerElement.width(applyWidth).height(applyHeight);
1619
		});
1620
1621
	// Kick off one resize to fix all elements on page load.
1622
	}).resize();
1623
}
1624
1625
$(function() {
1626
	$('.buttonlist > .dropmenu').each(function(index, item) {
1627
		$(item).prev().click(function(e) {
1628
			e.stopPropagation();
1629
			e.preventDefault();
1630
1631
			if ($(item).is(':visible')) {
1632
				$(item).css('display', 'none');
1633
1634
				return true;
1635
			}
1636
1637
			$(item).css('display', 'block');
1638
			$(item).css('top', $(this).offset().top + $(this).height());
1639
			$(item).css('left', Math.max($(this).offset().left - $(item).width() + $(this).outerWidth(), 0));
1640
			$(item).height($(item).find('div:first').height());
1641
		});
1642
		$(document).click(function() {
1643
			$(item).css('display', 'none');
1644
		});
1645
	});
1646
1647
	// Generic confirmation message.
1648
	$(document).on('click', '.you_sure', function() {
1649
		var custom_message = $(this).attr('data-confirm');
1650
1651
		return confirm(custom_message ? custom_message.replace(/-n-/g, "\n") : smf_you_sure);
1652
	});
1653
1654
	// Generic event for smfSelectText()
1655
	$('.smf_select_text').on('click', function(e) {
1656
		e.preventDefault();
1657
1658
		// Do you want to target yourself?
1659
		var actOnElement = $(this).attr('data-actonelement');
1660
1661
		return typeof actOnElement !== "undefined" ? smfSelectText(actOnElement, true) : smfSelectText(this);
1662
	});
1663
});
1664
1665
function avatar_fallback(e) {
1666
    var e = window.e || e;
1667
	var default_avatar = '/avatars/default.png';
1668
	var default_url = document.URL.substr(0,smf_scripturl.lastIndexOf('/')) + default_avatar;
1669
1670
    if (e.target.tagName !== 'IMG' || !e.target.classList.contains('avatar') || e.target.src === default_url )
1671
        return;
1672
1673
	e.target.src = default_url;
1674
	return true;
1675
}
1676
1677
if (document.addEventListener)
1678
    document.addEventListener("error", avatar_fallback, true);
1679
else
1680
    document.attachEvent("error", avatar_fallback);
1681