PageSourceSearch

http://www.sasgis.org/mantis/js/common.js

js sasgis.org collected 2026-09-28 05:36:46 UTC 28,302 bytes, 897 lines download raw bytes

1/*
2# Mantis - a php based bugtracking system
3
4# Copyright 2000 - 2002  Kenzaburo Ito - [email protected]
5# Copyright 2002 MantisBT Team   - [email protected]
6
7# Mantis is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 2 of the License, or
10# (at your option) any later version.
11#
12# Mantis is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21/**
22 * MantisBT config options.
23 * Initialized in javascript_config.php
24 * @property {string} config.collapse_settings_cookie
25 * @property {string} config.cookie_path
26 * @property {string} config.cookie_domain
27 * @property {string} config.cookie_samesite
28 */
29/* global config */
30
31// global code to determine how to set visibility
32var a = navigator.userAgent.indexOf("MSIE");
33var style_display;
34
35if (a!= -1) {
36	style_display = 'block';
37} else {
38	style_display = 'table-row';
39}
40style_display = 'block';
41
42$(document).ready( function() {
43	$('.collapse-open').show();
44	$('.collapse-closed').hide();
45	$('.collapse-link').click( function(event) {
46		event.preventDefault();
47		var id = $(this).attr('id');
48		var t_pos = id.indexOf('_closed_link' );
49		if( t_pos == -1 ) {
50			t_pos = id.indexOf('_open_link' );
51		}
52		var t_div = id.substring(0, t_pos );
53		ToggleDiv( t_div );
54	});
55
56	/**
57	 * Manage the navbar project menu initializacion and events
58	 * for focus and key presses.
59	 */
60
61	/* initialize the list */
62	var projects_list_options = {
63		valueNames: [ 'project-link' ]
64	};
65	var listprojects = new List('projects-list', projects_list_options);
66	if( listprojects.items.length <= 10 ) {
67		$('#projects-list .projects-searchbox').hide();
68	}
69
70	/**
71	 * Events to manage focus when displaying the dropdown.
72	 * - Focus on the active item to position the scrollable list on that item.
73	 * - If there is no items to show, focus on the search box.
74	 */
75	$(document).on('shown.bs.dropdown', '#dropdown_projects_menu', function() {
76		var li_active = $(this).find('.dropdown-menu li.active a');
77		if( li_active.length ) {
78			li_active.focus();
79		} else {
80			$('#projects-list .search').focus();
81		}
82	});
83
84	/**
85	 * When pressing a key in the search box, targeted at navigating the list,
86	 * switch focus to the list.
87	 * When pressing Escape, close the dropdown.
88	 */
89	$('#projects-list .search').keydown( function(event){
90		switch (event.key) {
91			case 'ArrowDown':
92			case 'ArrowUp':
93			case 'Down':
94			case 'Up':
95			case 'PageDown':
96			case 'PageUp':
97				var list = $('#projects-list .list');
98				if( list.find('li.active').length ) {
99					list.find('li.active a').focus();
100				} else {
101					list.find('li a').first().focus();
102				}
103				break;
104			case 'Escape':
105				$('#dropdown_projects_menu').removeClass('open');
106		}
107	});
108
109	/**
110	 * When pressing a key in the list, which is not targeted at navigating the list,
111	 * for example, typing a string, toggle focus to the search box.
112	 */
113	$('#projects-list .list').keydown( function(event){
114		switch (event.key) {
115			case 'Enter':
116			case 'ArrowDown':
117			case 'ArrowUp':
118			case 'Down':
119			case 'Up':
120			case 'PageDown':
121			case 'PageUp':
122			case 'Home':
123			case 'End':
124				return;
125		}
126		$('#projects-list .search').focus();
127	});
128
129	$('.widget-box').on('shown.ace.widget', function(event) {
130		SetCookie(config.collapse_settings_cookie, $(this).attr('id'), false);
131	}).on('hidden.ace.widget', function(event) {
132		SetCookie(config.collapse_settings_cookie, $(this).attr('id'), true);
133	}).each( function() {
134		// Restore widget collapse state from cookies
135		var t_collapse = GetCookieId(config.collapse_settings_cookie, $(this).attr('id'));
136		if( t_collapse != -1 && $(this).find('> .widget-header a[data-action=collapse]').length ) {
137			t_collapse = ( t_collapse == '1' );
138			if( t_collapse != $(this).hasClass('collapsed') ) {
139
140				var t_icon = $(this).find('> .widget-header a .ace-icon');
141				var t_icon_1 = t_icon.data('icon-hide');
142				if ( !t_icon_1 ) t_icon_1 = 'fa-chevron-up';
143				var t_icon_2 = t_icon.data('icon-show');
144				if ( !t_icon_2 ) t_icon_2 = 'fa-chevron-down';
145
146				if ( t_collapse ) {
147					$(this).addClass('collapsed');
148					t_icon.removeClass(t_icon_1).addClass(t_icon_2);
149				} else {
150					$(this).removeClass('collapsed');
151					t_icon.removeClass(t_icon_2).addClass(t_icon_1);
152				}
153			}
154		}
155	});
156
157	$('#sidebar-btn.sidebar-toggle').on('click', function (event) {
158		var t_sidebar = $(this).closest('.sidebar');
159		SetCookie(config.collapse_settings_cookie, t_sidebar.attr('id'), !t_sidebar.hasClass("menu-min"));
160	}).each( function() {
161		// Restore sidebar collapse state from cookies
162		var t_sidebar = $(this).closest('.sidebar');
163		var t_collapse = GetCookieId(config.collapse_settings_cookie, t_sidebar.attr('id'));
164		if( t_collapse != -1 ) {
165			t_collapse = ( t_collapse == '1' );
166			if( t_collapse != t_sidebar.hasClass('menu-min') ) {
167
168				var t_icon = $(this).find('> .ace-icon');
169				var t_icon_1 = t_icon.data('icon1');
170				if( !t_icon_1 ) t_icon_1 = 'fa-angle-double-left';
171				var t_icon_2 = t_icon.data('icon2');
172				if( !t_icon_2 ) t_icon_2 = 'fa-angle-double-right';
173
174				if ( t_collapse ) {
175					t_sidebar.addClass('menu-min');
176					t_icon.removeClass(t_icon_1).addClass(t_icon_2);
177				} else {
178					t_sidebar.removeClass('menu-min');
179					t_icon.removeClass(t_icon_2).addClass(t_icon_1);
180				}
181			}
182		}		
183	});
184
185	$('input[type=text].typeahead').each(function() {
186		var $this = $(this);
187		$(this).typeahead({
188			minLength: 1,
189			highlight: true
190		}, {
191			source: function (query, undefined, callback) {
192				var params = {};
193				params['field'] = $this[0].id;
194				params['prefix'] = query;
195				$.getJSON('api/rest/index.php/internal/autocomplete', params, function (data) {
196					var results = [];
197					$.each(data, function (i, value) {
198						results.push(value);
199					});
200					callback(results);
201				});
202			}
203		});
204	});
205
206	$('a.dynamic-filter-expander').click(function(event) {
207		event.preventDefault();
208		var fieldID = $(this).attr('id');
209		var filter_id = $(this).data('filter_id');
210		var filter_tmp_id = $(this).data('filter');
211		var targetID = fieldID + '_target';
212		var viewType = $('#filters_form_open input[name=view_type]').val();
213		$('#' + targetID).html('<span class="dynamic-filter-loading">' + translations['loading'] + "</span>");
214		var params = 'view_type=' + viewType + '&filter_target=' + fieldID;
215		if( undefined !== filter_id ) {
216			params += '&filter_id=' + filter_id;
217		}
218		if( undefined !== filter_tmp_id ) {
219			params += '&filter=' + filter_tmp_id;
220		}
221		$.ajax({
222			url: 'return_dynamic_filters.php',
223			data: params,
224			cache: false,
225			context: $('#' + targetID),
226			success: function(html) {
227				$(this).html(html);
228			},
229			error: function(obj,status,error) {
230				$(this).html('<span class="error-msg">' + status + ': ' + error + '</span>');
231			}
232		});
233	});
234
235	$('input.autofocus:first, select.autofocus:first, textarea.autofocus:first').focus();
236
237	var checkAllSelectors = '';
238	$(':checkbox.check_all').each(function() {
239		var baseFieldName = $(this).attr('name').replace(/_all$/, '');
240		if (checkAllSelectors.length > 0) {
241			checkAllSelectors += ', ';
242		}
243		checkAllSelectors += ':checkbox[name="' + baseFieldName + '[]"]';
244	});
245	if (checkAllSelectors.length > 0) {
246		$(checkAllSelectors).click(function() {
247			var fieldName = $(this).attr('name').replace(/\[\]/g, '');
248			var checkedCount = $(this).closest('form').find(':checkbox[name="' + fieldName + '[]"]:checked').length;
249			var totalCount = $(this).closest('form').find(':checkbox[name="' + fieldName + '[]"]').length;
250			var allSelected = checkedCount == totalCount;
251			$(this).closest('form').find(':checkbox[name=' + fieldName + '_all]').prop('checked', allSelected);
252		});
253		$(':checkbox.check_all').click(function() {
254			var baseFieldName = $(this).attr('name').replace(/_all$/, '');
255			$(this).closest('form').find(':checkbox[name="' + baseFieldName + '[]"]').prop('checked', $(this).is(':checked'));
256		});
257	}
258
259	/**
260	 * Prepare a table where the checkboxes range selection has been applied
261	 * Save row and column index in each cell for easier iteration.
262	 * This assumes no rowspan or colspan is used in the area where the ckecboxes are rendered.
263	 */
264	$('table.checkbox-range-selection').each(function(){
265		$(this).find('tr').each( function(row_index){
266			$(this).children('th, td').each(function(col_index){
267				$(this).data('col_index', col_index);
268				$(this).data('row_index', row_index);
269			});
270		});
271	});
272
273	/**
274	 * Enable range selection for checkboxes, inside a container having class "checkbox-range-selection"
275	 * Assumes the bootstrap/ace styled checkboxes:
276	 *		<label>
277	 *			<input type="checkbox" class="ace">
278	 *			<span class="lbl"></span>
279	 *		</label>
280	 */
281	$('.checkbox-range-selection').on('click', 'label', function (e) {
282		if( $(this).children('input:checkbox').length == 0 ) {
283			return;
284		}
285		var jcontainer = $(this).closest('.checkbox-range-selection');
286		var last_clicked = jcontainer.data('checkbox-range-last-clicked');
287		if (!last_clicked) {
288			last_clicked = this;
289		}
290		if (e.shiftKey) {
291			// Because shift-click is triggered in a label/span, some browsers
292			// will activate a text selection. Remove text selection.
293			window.getSelection().removeAllRanges();
294			var cb_label_list = jcontainer.find('label').has('input:checkbox');
295			// The actual input hasn't been changed yet, so we want to set the
296			// opposite value for all the checkboxes
297			var clicked_current_st = $(this).find('input:checkbox').first().prop('checked');
298			// The currently clicked one is also modified, becasue shift-click is not
299			// recognised correctly by the framework. See #25215
300			if( jcontainer.is('table') ) {
301				// Special case for a table container:
302				// we traverse the table cells for a rectangular area
303				var cell_1 = $(this).closest('th, td');
304				var row_1 = cell_1.data('row_index');
305				var col_1 = cell_1.data('col_index');
306				var cell_2 = $(last_clicked).closest('th, td');
307				var row_2 = cell_2.data('row_index');
308				var col_2 = cell_2.data('col_index');
309				var row_start = Math.min(row_1, row_2);
310				var row_end = Math.max(row_1, row_2);
311				var col_start = Math.min(col_1, col_2);
312				var col_end = Math.max(col_1, col_2);
313				for (i = 0 ; i <= cb_label_list.length ; i++) {
314					var it_td = $(cb_label_list[i]).closest('th, td');
315					var it_row = it_td.data('row_index');
316					var it_col = it_td.data('col_index');
317					if(    row_start <= it_row && it_row <= row_end
318						&& col_start <= it_col && it_col <= col_end ) {
319						$(cb_label_list[i]).find('input:checkbox').prop('checked', !clicked_current_st);
320					}
321				}
322			} else {
323				// General case: we traverse the items by their relative index
324				var start = cb_label_list.index(this);
325				var end = cb_label_list.index(last_clicked);
326				var index_start = Math.min(start, end);
327				var index_end = Math.max(start, end);
328				for (i = index_start ; i <= index_end ; i++) {
329					$(cb_label_list[i]).find('input:checkbox').prop('checked', !clicked_current_st);
330				}
331			}
332		}
333		jcontainer.data('checkbox-range-last-clicked', this);
334	});
335
336	var stopwatch = {
337		timerID: 0,
338		startTime: null,
339		zeroTime: moment('0', 's'),
340		tick: function() {
341			var elapsedDiff = moment().diff(this.startTime),
342				elapsedTime = this.zeroTime.clone().add(elapsedDiff);
343
344			$('input[type=text].stopwatch_time').val(elapsedTime.format('HH:mm:ss'));
345		},
346		reset: function() {
347			this.stop();
348			$('input[type=text].stopwatch_time').val('');
349		},
350		start: function() {
351			var self = this,
352				timeFormat = '',
353				stoppedTime = $('input[type=text].stopwatch_time').val();
354
355			this.stop();
356
357			if (stoppedTime) {
358				switch (stoppedTime.split(':').length) {
359					case 1:
360						timeFormat = 'ss';
361						break;
362
363					case 2:
364						timeFormat = 'mm:ss';
365						break;
366
367					default:
368						timeFormat = 'HH:mm:ss';
369				}
370
371				this.startTime = moment().add(this.zeroTime.clone().diff(moment(stoppedTime, timeFormat)));
372			} else {
373				this.startTime = moment();
374			}
375
376			this.timerID = window.setInterval(function() {
377				self.tick();
378			}, 1000);
379
380			$('input[type=button].stopwatch_toggle').val(translations['time_tracking_stopwatch_stop']);
381		},
382		stop: function() {
383			if (this.timerID) {
384				window.clearInterval(this.timerID);
385				this.timerID = 0;
386			}
387
388			$('input[type=button].stopwatch_toggle').val(translations['time_tracking_stopwatch_start']);
389		}
390	};
391
392	$('input[type=button].stopwatch_toggle').click(function() {
393		if (!stopwatch.timerID) {
394			stopwatch.start();
395		} else {
396			stopwatch.stop();
397		}
398	});
399
400	$('input[type=button].stopwatch_reset').click(function() {
401		stopwatch.reset();
402	});
403
404	$('input[type=text].datetimepicker').each(function(index, element) {
405		$(this).datetimepicker({
406			locale: $(this).data('picker-locale'),
407			format: $(this).data('picker-format'),
408			useCurrent: false,
409			icons: {
410				time: 'fa fa-clock-o',
411				date: 'fa fa-calendar',
412				up: 'fa fa-chevron-up',
413				down: 'fa fa-chevron-down',
414				previous: 'fa fa-chevron-left',
415				next: 'fa fa-chevron-right',
416				today: 'fa fa-arrows ',
417				clear: 'fa fa-trash',
418				close: 'fa fa-times'
419			}
420		}).next().on(ace.click_event, function() {
421			$(this).prev().focus();
422		});
423	});
424
425	$( 'form .dropzone' ).each(function(){
426		var classPrefix = 'dropzone';
427		var autoUpload = $(this).hasClass('auto-dropzone');
428		var zoneObj = enableDropzone( classPrefix, autoUpload );
429		if( zoneObj ) {
430			/* Attach image paste handler to report-bug & add-note forms */
431			$( '#bugnoteadd, #report_bug_form' ).bind( 'paste', function( event ) {
432				var items = ( event.clipboardData || event.originalEvent.clipboardData ).items;
433				for( index in items ) {
434					var item = items[index];
435					if( item.kind === 'file' ) {
436						zoneObj.addFile( item.getAsFile() )
437					}
438				}
439			});
440		}
441	});
442
443	$('.bug-jump').find('[name=bug_id]').focus( function() {
444		var bug_label = $('.bug-jump-form').find('[name=bug_label]').val();
445		if( $(this).val() == bug_label ) {
446			$(this).val('');
447			$(this).removeClass('field-default');
448		}
449	});
450	$('.bug-jump').find('[name=bug_id]').blur( function() {
451		var bug_label = $('.bug-jump-form').find('[name=bug_label]').val();
452		if( $(this).val() == '' ) {
453			$(this).val(bug_label);
454			$(this).addClass('field-default');
455		}
456	});
457	$('[name=source_query_id]').change( function() {
458		$(this.form).submit();
459	});
460
461	/* Project selector: auto-switch on select */
462	$('#form-set-project-id').change( function() {
463		$('#form-set-project').submit();
464	});
465	$('#project-selector').children('.button').hide();
466
467	setBugLabel();
468
469	/* Handle standard filter date fields */
470	$(document).on('change', '.js_switch_date_inputs_trigger', function() {
471		$(this).closest('table')
472				.find('select')
473				.prop('disabled', !$(this).prop('checked'));
474	});
475
476	/* Handle custom field of date type */
477	$(document).on('change', 'select[name^=custom_field_][name$=_control]', function() {
478		var table = $(this).closest('table');
479		switch(this.value) {
480			case '2': // between
481				$(table).find("select[name*=_start_year]").prop('disabled', false);
482				$(table).find("select[name*=_start_month]").prop('disabled', false);
483				$(table).find("select[name*=_start_day]").prop('disabled', false);
484				$(table).find("select[name*=_end_year]").prop('disabled', false);
485				$(table).find("select[name*=_end_month]").prop('disabled', false);
486				$(table).find("select[name*=_end_day]").prop('disabled', false);
487				break;
488
489			case '3': // on or before
490			case '4': // before
491			case '5': // on
492			case '6': // after
493			case '7': // on or after
494				$(table).find("select[name*=_start_year]").prop('disabled', false);
495				$(table).find("select[name*=_start_month]").prop('disabled', false);
496				$(table).find("select[name*=_start_day]").prop('disabled', false);
497				$(table).find("select[name*=_end_year]").prop('disabled', true);
498				$(table).find("select[name*=_end_month]").prop('disabled', true);
499				$(table).find("select[name*=_end_day]").prop('disabled', true);
500				break;
501
502			case '0': // any
503			case '1': // none
504			default:
505				$(table).find("select[name*=_start_year]").prop('disabled', true);
506				$(table).find("select[name*=_start_month]").prop('disabled', true);
507				$(table).find("select[name*=_start_day]").prop('disabled', true);
508				$(table).find("select[name*=_end_year]").prop('disabled', true);
509				$(table).find("select[name*=_end_month]").prop('disabled', true);
510				$(table).find("select[name*=_end_day]").prop('disabled', true);
511				break;
512		}
513	});
514
515	/* For Period.php bundled with the core MantisGraph plugin */
516	$('#dates > input[type=image].datetime').hide();
517	$('#period_menu > select#interval').change(function() {
518		if ($(this).val() == 10) {
519			$('#dates > input[type=text].datetime').prop('disabled', false);
520			$('#dates > input[type=image].datetime').show();
521		} else {
522			$('#dates > input[type=text].datetime').prop('disabled', true);
523			$('#dates > input[type=image].datetime').hide();
524		}
525	});
526
527	$(document).on('change', '#tag_select', function() {
528		var tagSeparator = $('#tag_separator').val();
529		var currentTagString = $('#tag_string').val();
530		var newTagOptionID = $(this).val();
531		var newTag = $('#tag_select option[value=' + newTagOptionID + ']').text();
532		var tagArray = currentTagString.split(tagSeparator);
533		if (tagArray.indexOf(newTag) == -1) {
534			if (currentTagString.length > 0) {
535				$('#tag_string').val(currentTagString + tagSeparator + newTag);
536			} else {
537				$('#tag_string').val(newTag);
538			}
539		}
540		$(this).val(0);
541	});
542
543	$('a.click-url').bind("click", function() {
544		$(this).attr("href", $(this).data("url"));
545	});
546
547	$('input[name=private].ace').bind("click", function() {
548		if ($(this).is(":checked")){
549			$('textarea[name=bugnote_text]').addClass("bugnote-private");
550		} else {
551			$('textarea[name=bugnote_text]').removeClass("bugnote-private");
552		}
553	});
554
555	/**
556	 * Manage visiblity on hover trigger objects
557	 */
558	if( $('.visible-on-hover-toggle').length ) {
559		$('.visible-on-hover-toggle').hover(
560			function(e){ // handlerIn
561				$(e.currentTarget).find('.visible-on-hover').removeClass('invisible');
562			},
563			function(e){ // handlerOut
564				$(e.currentTarget).find('.visible-on-hover').addClass('invisible');
565			}
566		);
567		$('.visible-on-hover').addClass('invisible');
568	}
569
570	/**
571	 * Enhance tables with sortable columns using list.js
572	 */
573	$('.table-responsive.sortable').each(function(){
574		var jtable = $(this).find('table').first();
575		var ths = jtable.find('thead > tr > th');
576		if( !ths.length ) {
577			// exit if there is no headers
578			return;
579		}
580		var th_count = ths.length
581
582		var trs = jtable.find('tbody > tr');
583		if( trs.length > 1000 ) {
584			// don't run on big tables to avoid perfomance issues in client side
585			return;
586		}
587
588		var options_valuenames = [];
589		var exclude_index = [];
590		ths.each(function(index){
591			if( $(this).hasClass('no-sort') ) {
592				// if the column says no sorting, save this index for later checks and skip
593				exclude_index.push(index);
594				return;
595			}
596			// wrap the contents into a crafted div
597			var new_div = $('<div />').addClass('sort')
598					.attr('data-sort','sortkey_'+index)
599					.attr('role','button')
600					.html($(this).html());
601			$(this).html(new_div);
602
603			options_valuenames.push( { name:'sortkey_'+index, attr:'data-sortval' } );
604		});
605		trs.each(function(){
606			var tds = $(this).children('td');
607			if( tds.length != th_count ) {
608				// exit if different number of cells than headers, possibly colspan, etc
609				return;
610			}
611			tds.each(function(index){
612				if( exclude_index.indexOf(index) >= 0 ) {
613					// if this column was marked as no-sorting, skip.
614					return;
615				}
616				$(this).addClass( 'sortkey_'+index ).attr( 'data-sortval', $(this).text() );
617			});
618		});
619		jtable.find('tbody').addClass('list');
620
621		var listoptions = { valueNames: options_valuenames };
622		var listobject =  new List( this, listoptions );
623		$(this).data('listobject',listobject).data('listoptions',listoptions).addClass('listjs-table');
624	});
625
626	/**
627	 * Change status color box's color when a different status is selected.
628	 * To achieve that we need to store the current value in a data attribute,
629	 * to compute the class name to remove in the change event.
630	 */
631	var statusColor = $('#status');
632	// Store current value
633	statusColor.data('prev', statusColor.val());
634	statusColor.change(function () {
635		function getColorClassName (statusCode) {
636			return  'status-' + statusCode + '-fg';
637		}
638
639		var me = $(this);
640		me.siblings('i')
641			.removeClass(getColorClassName(me.data('prev')))
642			.addClass(getColorClassName(me.val()));
643		me.data('prev', me.val());
644	});
645
646	/**
647	 * Adjust footer height based on its content
648	 */
649	var bottom = $('.navbar-fixed-bottom');
650	var bottom_placeholder = $('<div>', {
651		'class': 'col-xs-12',
652		'width': '100%'
653	}).insertBefore(bottom);
654	var footer = $('.footer');
655	var footer_content = $('.footer-content');
656	$(window).on('resize.footer', function() {
657		bottom_placeholder.css('height', parseInt(bottom.height()) + 'px');
658		footer.css('padding-top', parseInt(footer_content.height()) + 'px');
659		}).triggerHandler('resize.footer');
660});
661
662function setBugLabel() {
663	var bug_label = $('.bug-jump-form').find('[name=bug_label]').val();
664	var field = $('.bug-jump').find('[name=bug_id]');
665	if( field.val() == '' ) {
666		field.val(bug_label);
667		field.addClass('field-default');
668	}
669}
670
671/*
672 * String manipulation
673 */
674function Trim( p_string ) {
675	if (typeof p_string != "string") {
676		return p_string;
677	}
678
679	var t_string = p_string;
680	var t_ch = '';
681
682	// Trim beginning spaces
683
684	t_ch = t_string.substring( 0, 1 );
685	while ( t_ch == " " ) {
686		t_string = t_string.substring( 1, t_string.length );
687		t_ch = t_string.substring( 0, 1 );
688	}
689
690	// Trim trailing spaces
691
692	t_ch = t_string.substring( t_string.length-1, t_string.length );
693	while ( t_ch == " " ) {
694		t_string = t_string.substring( 0, t_string.length-1 );
695		t_ch = t_string.substring( t_string.length-1, t_string.length );
696	}
697
698	return t_string;
699}
700
701/*
702 * Cookie functions
703 */
704function GetCookie( p_cookie ) {
705	var t_cookies = document.cookie.split(';');
706	for( var i in t_cookies ) {
707		var t_cookie = t_cookies[i].split('=');
708		if ( Trim( t_cookie[0] ) == p_cookie ) {
709			return t_cookie[1].split('|');
710		}
711	}
712
713	return -1;
714}
715
716function SetCookie( p_cookie, p_id, p_value ) {
717	var t_expires = new Date();
718	t_expires.setTime(t_expires.getTime() + (365 * 24 * 60 * 60 * 1000));
719
720	var t_new_cookies = new Array();
721	var t_cookies = GetCookie(p_cookie);
722	if( t_cookies != -1 ) {
723		for( var i in t_cookies ) {
724			if ( t_cookies[i].indexOf(p_id + ':') != 0 ) {
725				t_new_cookies.push(t_cookies[i]);
726			}
727		}
728	}
729	t_new_cookies.push(p_id + ':' + (p_value ? '1' : '0'));
730
731	document.cookie = p_cookie + "=" + t_new_cookies.join('|') +
732		"; expires=" + t_expires.toUTCString() +
733		( config.cookie_domain ? "; domain=" + config.cookie_domain : '' ) +
734		"; path=" + config.cookie_path +
735		"; samesite=" + config.cookie_samesite;
736}
737
738function GetCookieId( p_cookie, p_id ) {
739	var t_cookies = GetCookie(p_cookie);
740	if( t_cookies != -1 ) {
741		for( var i in t_cookies ) {	
742			if( t_cookies[i].indexOf(p_id + ':') == 0 ) {
743				return t_cookies[i].at(p_id.length + 1);
744			}
745		}
746	}
747
748	return -1;
749}
750
751function ToggleDiv( p_div ) {
752	var t_open_div = '#' + p_div + "_open";
753	var t_closed_div = '#' + p_div + "_closed";
754	var t_open_display = $(t_open_div).css('display');
755	$(t_open_div).toggle();
756
757	if( $(t_closed_div).length ) {
758		$(t_closed_div).toggle();
759	}
760
761	SetCookie( config.collapse_settings_cookie, p_div, ( t_open_display == "none" )	);
762}
763
764function setDisplay(idTag, state)
765{
766	if(!document.getElementById(idTag)) alert('SetDisplay(): id '+idTag+' is empty');
767	// change display visibility
768	if ( state != 0 ) {
769		document.getElementById(idTag).style.display = style_display;
770	} else {
771		document.getElementById(idTag).style.display = 'none';
772	}
773}
774
775function toggleDisplay(idTag)
776{
777	setDisplay( idTag, (document.getElementById(idTag).style.display == 'none')?1:0 );
778}
779
780// Dropzone handler
781Dropzone.autoDiscover = false;
782function enableDropzone( classPrefix, autoUpload ) {
783	var zone_class =  '.' + classPrefix;
784	var zone = $( zone_class );
785	var form = zone.closest('form');
786	var max_filesize_bytes = zone.data('max-filesize-bytes');
787	var max_filesize_mb = Math.ceil( max_filesize_bytes / ( 1024*1024) );
788	var max_filename_length = zone.data( 'max-filename-length' );
789	var options = {
790		forceFallback: zone.data('force-fallback'),
791		paramName: "ufile",
792		autoProcessQueue: autoUpload,
793		clickable: zone_class,
794		previewsContainer: '#' + classPrefix + '-previews-box',
795		uploadMultiple: true,
796		parallelUploads: 100,
797		maxFilesize: max_filesize_mb,
798		timeout: 0,
799		addRemoveLinks: false,
800		acceptedFiles: zone.data('accepted-files'),
801		thumbnailWidth: 150,
802		thumbnailMethod: 'contain',
803		dictDefaultMessage: zone.data('default-message'),
804		dictFallbackMessage: zone.data('fallback-message'),
805		dictFallbackText: zone.data('fallback-text'),
806		dictFileTooBig: zone.data('file-too-big'),
807		dictInvalidFileType: zone.data('invalid-file-type'),
808		dictResponseError: zone.data('response-error'),
809		dictCancelUpload: zone.data('cancel-upload'),
810		dictCancelUploadConfirmation: zone.data('cancel-upload-confirmation'),
811		dictRemoveFile: zone.data('remove-file'),
812		dictRemoveFileConfirmation: zone.data('remove-file-confirmation'),
813		dictMaxFilesExceeded: zone.data('max-files-exceeded'),
814
815		init: function () {
816			var dropzone = this;
817			var form = $( this.options.clickable ).closest('form');
818			form.on('submit', function (e) {
819				if( dropzone.getQueuedFiles().length ) {
820					e.preventDefault();
821					e.stopPropagation();
822					dropzone.processQueue();
823				}
824			});
825			this.on( "successmultiple", function( files, response ) {
826				document.open();
827				document.write( response );
828				document.close();
829			});
830			this.on( "complete", function( file ) {
831				// Set progress bar as inactive
832				let progressbar = file.previewElement.querySelector('.progress');
833				progressbar.classList.remove('active');
834			});
835			/**
836			 * 'addedfiles' is undocumented but works similar to 'addedfile'
837			 * It's triggered once after a multiple file addition, and receives
838			 * an array with the added files.
839			 */
840			this.on("addedfiles", function (files) {
841				var bullet = '-\u00A0';
842				var error_file_too_big = '';
843				var error_filename_too_long = '';
844				for (var i = 0; i < files.length; i++) {
845					if( files[i].size > max_filesize_bytes ) {
846						var size_mb = files[i].size / ( 1024*1024 );
847						var dec = size_mb < 0.01 ? 3 : 2;
848						error_file_too_big += bullet + '"' + files[i].name + '" (' + size_mb.toFixed(dec) + ' MiB)\n';
849						this.removeFile( files[i] );
850					} else if( files[i].name.length > 250 ) {
851						error_filename_too_long += bullet + '"' + files[i].name + '" (' + files[i].name.length + ')\n';
852						// this.removeFile( files[i] );
853					}
854				}
855
856				var text = '';
857				var error_message = '';
858				if( error_file_too_big ) {
859					var max_mb = max_filesize_bytes / ( 1024*1024 );
860					var max_mb_dec = max_mb < 0.01 ? 3 : 2;
861					text = zone.data( 'dropzone_multiple_files_too_big' ) + "\n";
862					text = text.replace( '{{files}}', '\n' + error_file_too_big );
863					text = text.replace( '{{maxFilesize}}', max_mb.toFixed(max_mb_dec) );
864					error_message += text;
865				}
866
867				if( error_filename_too_long ) {
868					text = zone.data( 'dropzone_multiple_filenames_too_long' ) + "\n";
869					text = text.replace( '{{maxFilenameLength}}', max_filename_length );
870					text = text.replace( '{{files}}', '\n' + error_filename_too_long );
871					error_message += text;
872				}
873				if( error_message ) {
874					alert(error_message);
875				}
876			});
877		},
878		fallback: function() {
879			if( $( "." + classPrefix ).length ) {
880				$( "." + classPrefix ).hide();
881			}
882		}
883	};
884	var preview_template = document.getElementById('dropzone-preview-template');
885	if( preview_template ) {
886		options.previewTemplate = preview_template.innerHTML;
887	}
888
889	var zone_object = null;
890	try {
891		zone_object = new Dropzone( form[0], options );
892	} catch (e) {
893		alert( zone.data('dropzone-not-supported') );
894	}
895
896	return zone_object;
897}

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.