PageSourceSearch

http://www.fork-cms.com/frontend/core/js/frontend.js

js fork-cms.com collected 2026-10-03 22:00:32 UTC 19,424 bytes, 761 lines download raw bytes

1var SWU__currentScript = document.currentScript || {};
2var SWU__DOMReady = function(callback) {
3  document.readyState === 'interactive' || document.readyState === 'complete' ? callback() : document.addEventListener('DOMContentLoaded', callback);
4};
5SWU__DOMReady(function() {
6  const el = document.createElement('div');
7
8  // Options
9  const url = 'https://www.fork-cms.com/blog/detail/fork-stands-with-ukraine';
10  const title = SWU__currentScript.getAttribute('title');
11  const position = SWU__currentScript.getAttribute('position');
12
13  // Set custom title
14  el.title = title || 'We stand with Ukraine';
15
16  // Set custom URL
17  if (url) {
18    el.style.cursor = 'pointer'
19    el.target = '_blank'
20    el.addEventListener('click', function() {
21      window.open(url, '_blank')
22    })
23  }
24
25  // Set custom position (left, right)
26  if (position === 'right') {
27    el.style.right = '-80px'
28    el.style.bottom = '20px'
29    el.style.transform = 'rotate(135deg)'
30    el.style.background = 'linear-gradient(-360deg, #005BBB 50%, #FFD500 50%)'
31  } else {
32    el.style.left = '-80px'
33    el.style.bottom = '20px'
34    el.style.transform = 'rotate(45deg)'
35    el.style.background = 'linear-gradient(-180deg, #005BBB 50%, #FFD500 50%)'
36  }
37
38  el.style.width = '300px'
39  el.style.height = '84px'
40  el.style.position = 'fixed';
41  el.style.zIndex = '999';
42
43  el.setAttribute('id', 'we-stand-with-ukraine')
44  document.body.appendChild(el);
45});
46
47/**
48 * Frontend related objects
49 *
50 * @author	Tijs Verkoyen <[email protected]>
51 * @author	Thomas Deceuninck <[email protected]>
52 */
53var jsFrontend =
54{
55	debug: false,
56	current: {},
57
58	// init, something like a constructor
59	init: function()
60	{
61		jsFrontend.current.language = jsFrontend.data.get('FRONTEND_LANGUAGE');
62
63		// init stuff
64		jsFrontend.initAjax();
65
66		// init controls
67		jsFrontend.controls.init();
68
69		// init form
70		jsFrontend.forms.init();
71
72		// init gravatar
73		jsFrontend.gravatar.init();
74
75		// init search
76		jsFrontend.search.init();
77
78		// init statistics
79		jsFrontend.statistics.init();
80
81		// init twitter
82		jsFrontend.twitter.init();
83	},
84
85	// init
86	initAjax: function()
87	{
88		// set defaults for AJAX
89		$.ajaxSetup(
90		{
91			url: '/frontend/ajax.php',
92			cache: false,
93			type: 'POST',
94			dataType: 'json',
95			timeout: 10000,
96			data: { fork: { module: null, action: null, language: jsFrontend.current.language } }
97		});
98	}
99}
100
101/**
102 * Controls related javascript
103 *
104 * @author	Tijs Verkoyen <[email protected]>
105 */
106jsFrontend.controls =
107{
108	// init, something like a constructor
109	init: function()
110	{
111		jsFrontend.controls.bindTargetBlank();
112	},
113
114	// bind target blank
115	bindTargetBlank: function()
116	{
117		$('a.targetBlank').attr('target', '_blank');
118	}
119}
120
121/**
122 * Data related methods
123 *
124 * @author Tijs Verkoyen <[email protected]>
125 */
126jsFrontend.data =
127{
128	initialized: false,
129	data: {},
130
131	init: function()
132	{
133		// check if var is available
134		if(typeof jsData == 'undefined') throw 'jsData is not available';
135
136		// populate
137		jsFrontend.data.data = jsData;
138		jsFrontend.data.initialized = true;
139	},
140
141	exists: function(key)
142	{
143		return (typeof eval('jsFrontend.data.data.' + key) != 'undefined');
144	},
145
146	get: function(key)
147	{
148		// init if needed
149		if(!jsFrontend.data.initialized) jsFrontend.data.init();
150
151		// return
152		return eval('jsFrontend.data.data.' + key);
153	}
154}
155
156/**
157 * Facebook related
158 *
159 * @author	Tijs Verkoyen <[email protected]>
160 */
161jsFrontend.facebook =
162{
163	// will be called after Facebook is initialized
164	afterInit: function()
165	{
166		// is GA available?
167		if(typeof _gaq == 'object')
168		{
169			// subscribe and track like
170			FB.Event.subscribe('edge.create', function(targetUrl) { _gaq.push(['_trackSocial', 'facebook', 'like', targetUrl]); });
171
172			// subscribe and track unlike
173			FB.Event.subscribe('edge.remove', function(targetUrl) { _gaq.push(['_trackSocial', 'facebook', 'unlike', targetUrl]); });
174
175			// subscribe and track message
176			FB.Event.subscribe('message.send', function(targetUrl) { _gaq.push(['_trackSocial', 'facebook', 'send', targetUrl]); });
177		}
178	}
179}
180
181/**
182 * Form related javascript
183 *
184 * @author	Tijs Verkoyen <[email protected]>
185 */
186jsFrontend.forms =
187{
188	// init, something like a constructor
189	init: function()
190	{
191		jsFrontend.forms.placeholders();
192		jsFrontend.forms.datefields();
193		jsFrontend.forms.filled();
194	},
195
196	// once text has been filled add another class to it (so it's possible to style it differently)
197	filled: function()
198	{
199		$(document).on('blur', 'form input, form textarea, form select', function()
200		{
201			if($(this).val() == '') $(this).removeClass('filled');
202			else $(this).addClass('filled');
203		});
204	},
205
206	// initialize the date fields
207	datefields: function()
208	{
209		var $inputDatefields = $('.inputDatefieldNormal, .inputDatefieldFrom, .inputDatefieldTill, .inputDatefieldRange')
210		var $inputDatefieldNormal = $('.inputDatefieldNormal');
211		var $inputDatefieldFrom = $('.inputDatefieldFrom');
212		var $inputDatefieldTill = $('.inputDatefieldTill');
213		var $inputDatefieldRange = $('.inputDatefieldRange');
214
215		if($inputDatefields.length > 0)
216		{
217			var dayNames = [jsFrontend.locale.loc('DayLongSun'), jsFrontend.locale.loc('DayLongMon'), jsFrontend.locale.loc('DayLongTue'), jsFrontend.locale.loc('DayLongWed'), jsFrontend.locale.loc('DayLongThu'), jsFrontend.locale.loc('DayLongFri'), jsFrontend.locale.loc('DayLongSat')];
218			var dayNamesMin = [jsFrontend.locale.loc('DayShortSun'), jsFrontend.locale.loc('DayShortMon'), jsFrontend.locale.loc('DayShortTue'), jsFrontend.locale.loc('DayShortWed'), jsFrontend.locale.loc('DayShortThu'), jsFrontend.locale.loc('DayShortFri'), jsFrontend.locale.loc('DayShortSat')];
219			var dayNamesShort = [jsFrontend.locale.loc('DayShortSun'), jsFrontend.locale.loc('DayShortMon'), jsFrontend.locale.loc('DayShortTue'), jsFrontend.locale.loc('DayShortWed'), jsFrontend.locale.loc('DayShortThu'), jsFrontend.locale.loc('DayShortFri'), jsFrontend.locale.loc('DayShortSat')];
220			var monthNames = [jsFrontend.locale.loc('MonthLong1'), jsFrontend.locale.loc('MonthLong2'), jsFrontend.locale.loc('MonthLong3'), jsFrontend.locale.loc('MonthLong4'), jsFrontend.locale.loc('MonthLong5'), jsFrontend.locale.loc('MonthLong6'), jsFrontend.locale.loc('MonthLong7'), jsFrontend.locale.loc('MonthLong8'), jsFrontend.locale.loc('MonthLong9'), jsFrontend.locale.loc('MonthLong10'), jsFrontend.locale.loc('MonthLong11'), jsFrontend.locale.loc('MonthLong12')];
221			var monthNamesShort = [jsFrontend.locale.loc('MonthShort1'), jsFrontend.locale.loc('MonthShort2'), jsFrontend.locale.loc('MonthShort3'), jsFrontend.locale.loc('MonthShort4'), jsFrontend.locale.loc('MonthShort5'), jsFrontend.locale.loc('MonthShort6'), jsFrontend.locale.loc('MonthShort7'), jsFrontend.locale.loc('MonthShort8'), jsFrontend.locale.loc('MonthShort9'), jsFrontend.locale.loc('MonthShort10'), jsFrontend.locale.loc('MonthShort11'), jsFrontend.locale.loc('MonthShort12')];
222
223			$inputDatefields.datepicker({
224				dayNames: dayNames,
225				dayNamesMin: dayNamesMin,
226				dayNamesShort: dayNamesShort,
227				hideIfNoPrevNext: true,
228				monthNames: monthNames,
229				monthNamesShort: monthNamesShort,
230				nextText: jsFrontend.locale.lbl('Next'),
231				prevText: jsFrontend.locale.lbl('Previous'),
232				showAnim: 'slideDown'
233			});
234
235			// the default, nothing special
236			$inputDatefieldNormal.each(function()
237			{
238				// get data
239				var data = $(this).data();
240				var value = $(this).val();
241
242				// set options
243				$(this).datepicker('option', {
244					dateFormat: data.mask,
245					firstDay: data.firstday
246				}).datepicker('setDate', value);
247			});
248
249			// date fields that have a certain start date
250			$inputDatefieldFrom.each(function()
251			{
252				// get data
253				var data = $(this).data();
254				var value = $(this).val();
255
256				// set options
257				$(this).datepicker('option', {
258					dateFormat: data.mask, firstDay: data.firstday,
259					minDate: new Date(parseInt(data.startdate.split('-')[0], 10), parseInt(data.startdate.split('-')[1], 10) - 1, parseInt(data.startdate.split('-')[2], 10))
260				}).datepicker('setDate', value);
261			});
262
263			// date fields that have a certain enddate
264			$inputDatefieldTill.each(function()
265			{
266				// get data
267				var data = $(this).data();
268				var value = $(this).val();
269
270				// set options
271				$(this).datepicker('option',
272				{
273					dateFormat: data.mask,
274					firstDay: data.firstday,
275					maxDate: new Date(parseInt(data.enddate.split('-')[0], 10), parseInt(data.enddate.split('-')[1], 10) -1, parseInt(data.enddate.split('-')[2], 10))
276				}).datepicker('setDate', value);
277			});
278
279			// date fields that have a certain range
280			$inputDatefieldRange.each(function()
281			{
282				// get data
283				var data = $(this).data();
284				var value = $(this).val();
285
286				// set options
287				$(this).datepicker('option',
288				{
289					dateFormat: data.mask,
290					firstDay: data.firstday,
291					minDate: new Date(parseInt(data.startdate.split('-')[0], 10), parseInt(data.startdate.split('-')[1], 10) - 1, parseInt(data.startdate.split('-')[2], 10), 0, 0, 0, 0),
292					maxDate: new Date(parseInt(data.enddate.split('-')[0], 10), parseInt(data.enddate.split('-')[1], 10) - 1, parseInt(data.enddate.split('-')[2], 10), 23, 59, 59)
293				}).datepicker('setDate', value);
294			});
295		}
296	},
297
298	// placeholder fallback for browsers that don't support placeholder
299	placeholders: function()
300	{
301		// detect if placeholder-attribute is supported
302		jQuery.support.placeholder = ('placeholder' in document.createElement('input'));
303
304		if(!jQuery.support.placeholder)
305		{
306			// bind focus
307			$('input[placeholder], textarea[placeholder]').on('focus', function()
308			{
309				// grab element
310				var input = $(this);
311
312				// only do something when the current value and the placeholder are the same
313				if(input.val() == input.attr('placeholder'))
314				{
315					// clear
316					input.val('');
317
318					// remove class
319					input.removeClass('placeholder');
320				}
321			});
322
323			$('input[placeholder], textarea[placeholder]').on('blur', function()
324			{
325				// grab element
326				var input = $(this);
327
328				// only do something when the input is empty or the value is the same as the placeholder
329				if(input.val() == '' || input.val() == input.attr('placeholder'))
330				{
331					// set placeholder
332					input.val(input.attr('placeholder'));
333
334					// add class
335					input.addClass('placeholder');
336				}
337			});
338
339			// call blur to initialize
340			$('input[placeholder], textarea[placeholder]').blur();
341
342			// hijack the form so placeholders aren't submitted as values
343			$('input[placeholder], textarea[placeholder]').parents('form').submit(function()
344			{
345				// find elements with placeholders
346				$(this).find('input[placeholder]').each(function()
347				{
348					// grab element
349					var input = $(this);
350
351					// if the value and the placeholder are the same reset the value
352					if(input.val() == input.attr('placeholder')) input.val('');
353				});
354			});
355		}
356	}
357}
358
359/**
360 * Gravatar related javascript
361 *
362 * @author	Tijs Verkoyen <[email protected]>
363 */
364jsFrontend.gravatar =
365{
366	// init, something like a constructor
367	init: function()
368	{
369		$('.replaceWithGravatar').each(function()
370		{
371			var element = $(this);
372			var gravatarId = element.data('gravatarId');
373			var size = element.attr('height');
374
375			// valid gravatar id
376			if(gravatarId != '')
377			{
378				// build url
379				var url = 'http://www.gravatar.com/avatar/' + gravatarId + '?r=g&d=404';
380
381				// add size if set before
382				if(size != '') url += '&s=' + size;
383
384				// create new image
385				var gravatar = new Image();
386				gravatar.src = url;
387
388				// reset src
389				gravatar.onload = function()
390				{
391					element.attr('src', url).addClass('gravatarLoaded');
392				}
393			}
394		});
395	}
396}
397
398/**
399 * Locale
400 *
401 * @author Tijs Verkoyen <[email protected]>
402 */
403jsFrontend.locale =
404{
405	initialized: false,
406	data: {},
407
408	// init, something like a constructor
409	init: function()
410	{
411		$.ajax({
412			url: '/frontend/cache/locale/' + jsFrontend.current.language + '.json',
413			type: 'GET',
414			dataType: 'json',
415			async: false,
416			success: function(data)
417			{
418				jsFrontend.locale.data = data;
419				jsFrontend.locale.initialized = true;
420			},
421			error: function(jqXHR, textStatus, errorThrown)
422			{
423				throw 'Regenerate your locale-files.';
424			}
425		});
426	},
427
428	// get an item from the locale
429	get: function(type, key)
430	{
431		// initialize if needed
432		if(!jsFrontend.locale.initialized) jsFrontend.locale.init();
433
434		// validate
435		if(typeof jsFrontend.locale.data[type][key] == 'undefined') return '{$' + type + key + '}';
436
437		return jsFrontend.locale.data[type][key];
438	},
439
440	// get an action
441	act: function(key)
442	{
443		return jsFrontend.locale.get('act', key);
444	},
445
446	// get an error
447	err: function(key)
448	{
449		return jsFrontend.locale.get('err', key);
450	},
451
452	// get a label
453	lbl: function(key)
454	{
455		return jsFrontend.locale.get('lbl', key);
456	},
457
458	// get localization
459	loc: function(key)
460	{
461		return jsFrontend.locale.get('loc', key);
462	},
463
464	// get a message
465	msg: function(key)
466	{
467		return jsFrontend.locale.get('msg', key);
468	}
469}
470
471/**
472 * Search controls
473 *
474 * @author	Matthias Mullie <[email protected]>
475 */
476jsFrontend.search =
477{
478	// init, something like a constructor
479	init: function()
480	{
481		// auto suggest (search widget)
482		if($('input.autoSuggest').length > 0) jsFrontend.search.autosuggest(55);
483
484		// autocomplete (search results page: autocomplete based on known search terms)
485		if($('input.autoComplete').length > 0) jsFrontend.search.autocomplete();
486
487		// live suggest (search results page: live feed of matches)
488		if($('input.liveSuggest').length > 0 && $('#searchContainer').length > 0) jsFrontend.search.livesuggest();
489	},
490
491	// autocomplete (search results page: autocomplete based on known search terms)
492	autocomplete: function()
493	{
494		// grab element
495		var $input = $('input.autoComplete');
496
497		// autocomplete (based on saved search terms) on results page
498		$input.autocomplete(
499		{
500			minLength: 1,
501			source: function(request, response)
502			{
503				// ajax call!
504				$.ajax(
505				{
506					data:
507					{
508						fork: { module: 'search', action: 'autocomplete' },
509						term: request.term
510					},
511					success: function(data, textStatus)
512					{
513						// init var
514						var realData = [];
515
516						// alert the user
517						if(data.code != 200 && jsFrontend.debug) { alert(data.message); }
518
519						if(data.code == 200)
520						{
521							for(var i in data.data) realData.push({ label: data.data[i].term, value: data.data[i].term, url: data.data[i].url });
522						}
523
524						// set response
525						response(realData);
526					}
527				});
528			},
529			select: function(e, ui)
530			{
531				window.location.href = ui.item.url
532			}
533		})
534		// when we have been typing in the search textfield and we blur out of it, we're ready to save it
535		.on('blur', function()
536		{
537			if($(this).val() != '')
538			{
539				// ajax call!
540				$.ajax(
541				{
542					data:
543					{
544						fork: { module: 'search', action: 'save' },
545						term: $(this).val()
546					}
547				});
548			}
549		});
550	},
551
552	// auto suggest (search widget)
553	autosuggest: function(length)
554	{
555		// set default values
556		if(typeof length == 'undefined') length = 100;
557
558		// grab element
559		var $input = $('input.autoSuggest');
560
561		// search widget suggestions
562		$input.autocomplete(
563		{
564			minLength: 1,
565			source: function(request, response)
566			{
567				// ajax call!
568				$.ajax(
569				{
570					data:
571					{
572						fork: { module: 'search', action: 'autosuggest' },
573						term: request.term,
574						length: length
575					},
576					success: function(data, textStatus)
577					{
578						// init var
579						var realData = [];
580
581						// alert the user
582						if(data.code != 200 && jsFrontend.debug) { alert(data.message); }
583
584						if(data.code == 200)
585						{
586							for(var i in data.data) realData.push({ label: data.data[i].title, value: data.data[i].title, url: data.data[i].full_url, desc: data.data[i].text });
587						}
588
589						// set response
590						response(realData);
591					}
592				});
593			},
594			select: function(e, ui)
595			{
596				window.location.href = ui.item.url
597			}
598		})
599		// when we have been typing in the search textfield and we blur out of it, we're ready to save it
600		.on('blur', function()
601		{
602			if($(this).val() != '')
603			{
604				// ajax call!
605				$.ajax(
606				{
607					data:
608					{
609						fork: { module: 'search', action: 'save' },
610						term: $(this).val()
611					}
612				});
613			}
614		})
615		// and also: alter the autocomplete style: add description!
616		.data('autocomplete')._renderItem = function(ul, item)
617		{
618			return $('<li></li>')
619			.data('item.autocomplete', item)
620			.append('<a><strong>' + item.label + '</strong><br \>' + item.desc + '</a>' )
621			.appendTo(ul);
622		};
623	},
624
625	// livesuggest (search results page: live feed of matches)
626	livesuggest: function()
627	{
628		// check if calls for live suggest are allowed
629		var allowCall = true;
630
631		// grab element
632		var $input = $('input.liveSuggest');
633
634		// change in input = do the dance: live search results completion
635		$input.on('keyup', function()
636		{
637			var $searchContainer = $('#searchContainer');
638
639			// make sure we're allowed to do the call (= previous call is no longer processing)
640			if(allowCall)
641			{
642				// temporarily allow no more calls
643				allowCall = false;
644
645				// fade out
646				$searchContainer.fadeTo(0, 0.5);
647
648				// ajax call!
649				$.ajax(
650				{
651					data:
652					{
653						fork: { module: 'search', action: 'livesuggest' },
654						term: $(this).val()
655					},
656					success: function(data, textStatus)
657					{
658						// allow for new calls
659						allowCall = true;
660
661						// alert the user
662						if(data.code != 200 && jsFrontend.debug) { alert(data.message); }
663
664						if(data.code == 200)
665						{
666							// replace search results
667							$searchContainer.html(utils.string.html5(data.data));
668
669							// fade in
670							$searchContainer.fadeTo(0, 1);
671						}
672					},
673					error: function()
674					{
675						// allow for new calls
676						allowCall = true;
677
678						// replace search results
679						$searchContainer.html('');
680
681						// fade in
682						$searchContainer.fadeTo(0, 1);
683					}
684				});
685			}
686		});
687	}
688}
689
690/**
691 * Gravatar related javascript
692 *
693 * @author	Tijs Verkoyen <[email protected]>
694 */
695jsFrontend.statistics =
696{
697	// init, something like a constructor
698	init: function()
699	{
700		jsFrontend.statistics.trackOutboundLinks();
701	},
702
703	// track all outbound links
704	trackOutboundLinks: function()
705	{
706		// check if Google Analytics is available
707		if(typeof _gaq == 'object')
708		{
709			// create a new selector
710			$.expr[':'].external = function(obj) {
711				return (typeof obj.href != 'undefined' && !obj.href.match(/^mailto\:/) && (obj.hostname != location.hostname));
712			};
713
714			// bind on all links that don't have the class noTracking
715			$(document).on('click', 'a:external:not(.noTracking)', function(e)
716			{
717				var $this = $(this);
718				var link = $this.attr('href');
719				var title = $this.attr('title');
720				if(typeof title == 'undefined' || title == '') title = $this.html();
721
722				// track in Google Analytics
723				_gaq.push(['_trackEvent', 'Outbound Links', link, title]);
724			});
725		}
726	}
727}
728
729/**
730 * Twitter related stuff
731 *
732 * @author	Tijs Verkoyen <[email protected]>
733 */
734jsFrontend.twitter =
735{
736	init: function()
737	{
738		// if GA is integrated and a tweet button is used
739		if(typeof _gaq == 'object' && typeof twttr == 'object')
740		{
741			// bind event, so we can track the tweets
742			twttr.events.on('tweet', function(e)
743			{
744				// valid event?
745				if(e)
746				{
747					// init var
748					var targetUrl = null;
749
750					// get url
751					if(e.target && e.target.nodeName == 'IFRAME') targetUrl = utils.url.extractParamFromUri(e.target.src, 'url');
752
753					// push to GA
754					_gaq.push(['_trackSocial', 'twitter', 'tweet', targetUrl]);
755				}
756			});
757		}
758	}
759}
760
761$(jsFrontend.init());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.