PageSourceSearch

https://truasiacompliance.stc.group/core/js/js.js?v=63a85e9fef19baeb6bbcc24c6e582808

js stc.group collected 2026-10-04 07:48:27 UTC 60,498 bytes, 2,350 lines download raw bytes

1/**
2 * Disable console output unless DEBUG mode is enabled.
3 * Add
4 *      'debug' => true,
5 * To the definition of $CONFIG in config/config.php to enable debug mode.
6 * The undefined checks fix the broken ie8 console
7 */
8
9/* global oc_isadmin */
10
11var oc_debug;
12var oc_webroot;
13
14var oc_requesttoken = document.getElementsByTagName('head')[0].getAttribute('data-requesttoken');
15
16window.oc_config = window.oc_config || {};
17
18if (typeof oc_webroot === "undefined") {
19	oc_webroot = location.pathname;
20	var pos = oc_webroot.indexOf('/index.php/');
21	if (pos !== -1) {
22		oc_webroot = oc_webroot.substr(0, pos);
23	}
24	else {
25		oc_webroot = oc_webroot.substr(0, oc_webroot.lastIndexOf('/'));
26	}
27}
28
29/**
30 * Sanitizes a HTML string by replacing all potential dangerous characters with HTML entities
31 * @param {string} s String to sanitize
32 * @return {string} Sanitized string
33 */
34function escapeHTML(s) {
35	return s.toString().split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;').split('"').join('&quot;').split('\'').join('&#039;');
36}
37
38/**
39 * Get the path to download a file
40 * @param {string} file The filename
41 * @param {string} dir The directory the file is in - e.g. $('#dir').val()
42 * @return {string} Path to download the file
43 * @deprecated use Files.getDownloadURL() instead
44 */
45function fileDownloadPath(dir, file) {
46	return OC.filePath('files', 'ajax', 'download.php') + '?files=' + encodeURIComponent(file) + '&dir=' + encodeURIComponent(dir);
47}
48
49/** @namespace */
50var OC = {
51	PERMISSION_CREATE: 4,
52	PERMISSION_READ: 1,
53	PERMISSION_UPDATE: 2,
54	PERMISSION_DELETE: 8,
55	PERMISSION_SHARE: 16,
56	PERMISSION_ALL: 31,
57	TAG_FAVORITE: '_$!<Favorite>!$_',
58	/* jshint camelcase: false */
59	/**
60	 * Relative path to ownCloud root.
61	 * For example: "/owncloud"
62	 *
63	 * @type string
64	 *
65	 * @deprecated since 8.2, use OC.getRootPath() instead
66	 * @see OC#getRootPath
67	 */
68	webroot: oc_webroot,
69
70	/**
71	 * Capabilities
72	 *
73	 * @type array
74	 */
75	_capabilities: window.oc_capabilities || null,
76
77	appswebroots: (typeof oc_appswebroots !== 'undefined') ? oc_appswebroots : false,
78	/**
79	 * Currently logged in user or null if none
80	 *
81	 * @type String
82	 * @deprecated use {@link OC.getCurrentUser} instead
83	 */
84	currentUser: (typeof oc_user !== 'undefined') ? oc_user.uid : false,
85	config: window.oc_config,
86	appConfig: window.oc_appconfig || {},
87	theme: window.oc_defaults || {},
88	coreApps: ['', 'admin', 'log', 'core/search', 'settings', 'core', '3rdparty'],
89	requestToken: oc_requesttoken,
90	menuSpeed: 50,
91	currentTheme: window.theme || {},
92
93	/**
94	 * Get an absolute url to a file in an app
95	 * @param {string} app the id of the app the file belongs to
96	 * @param {string} file the file path relative to the app folder
97	 * @return {string} Absolute URL to a file
98	 */
99	linkTo: function (app, file) {
100		return OC.filePath(app, '', file);
101	},
102
103	/**
104	 * Creates a relative url for remote use
105	 * @param {string} service id
106	 * @return {string} the url
107	 */
108	linkToRemoteBase: function (service) {
109		return OC.getRootPath() + '/remote.php/' + service;
110	},
111
112	/**
113	 * @brief Creates an absolute url for remote use
114	 * @param {string} service id
115	 * @return {string} the url
116	 */
117	linkToRemote: function (service) {
118		return window.location.protocol + '//' + window.location.host + OC.linkToRemoteBase(service);
119	},
120
121	/**
122	 * Gets the base path for the given OCS API service.
123	 * @param {string} service name
124	 * @param {int} version OCS API version
125	 * @return {string} OCS API base path
126	 */
127	linkToOCS: function (service, version) {
128		version = (version !== 2) ? 1 : 2;
129		return OC.getProtocol() + '://' + OC.getHost() + OC.getRootPath() + '/ocs/v' + version + '.php/' + service + '/';
130	},
131
132	/**
133	 * Generates the absolute url for the given relative url, which can contain parameters.
134	 * Parameters will be URL encoded automatically.
135	 * @param {string} url
136	 * @param [params] params
137	 * @param [options] options
138	 * @param {bool} [options.escape=true] enable/disable auto escape of placeholders (by default enabled)
139	 * @return {string} Absolute URL for the given relative URL
140	 */
141	generateUrl: function (url, params, options) {
142		var defaultOptions = {
143				escape: true
144			},
145			allOptions = options || {};
146		_.defaults(allOptions, defaultOptions);
147
148		var _build = function (text, vars) {
149			var vars = vars || [];
150			return text.replace(/{([^{}]*)}/g,
151				function (a, b) {
152					var r = (vars[b]);
153					if (allOptions.escape) {
154						return (typeof r === 'string' || typeof r === 'number') ? encodeURIComponent(r) : encodeURIComponent(a);
155					} else {
156						return (typeof r === 'string' || typeof r === 'number') ? r : a;
157					}
158				}
159			);
160		};
161		if (url.charAt(0) !== '/') {
162			url = '/' + url;
163
164		}
165
166		var webRoot = OC.getRootPath();
167		if (oc_config.modRewriteWorking == true) {
168			return webRoot + _build(url, params);
169		}
170
171		return webRoot + '/index.php' + _build(url, params);
172	},
173
174	/**
175	 * Get the absolute url for a file in an app
176	 * @param {string} app the id of the app
177	 * @param {string} type the type of the file to link to (e.g. css,img,ajax.template)
178	 * @param {string} file the filename
179	 * @return {string} Absolute URL for a file in an app
180	 */
181	filePath: function (app, type, file) {
182		var isCore = OC.coreApps.indexOf(app) !== -1,
183			link = OC.getRootPath();
184		if (file.substring(file.length - 3) === 'php' && !isCore) {
185			link += '/index.php/apps/' + app;
186			if (file != 'index.php') {
187				link += '/';
188				if (type) {
189					link += encodeURI(type + '/');
190				}
191				link += file;
192			}
193		} else if (file.substring(file.length - 3) !== 'php' && !isCore) {
194			link = OC.appswebroots[app];
195			if (type) {
196				link += '/' + type + '/';
197			}
198			if (link.substring(link.length - 1) !== '/') {
199				link += '/';
200			}
201			link += file;
202		} else {
203			if ((app == 'settings' || app == 'core' || app == 'search') && type == 'ajax') {
204				link += '/index.php/';
205			}
206			else {
207				link += '/';
208			}
209			if (!isCore) {
210				link += 'apps/';
211			}
212			if (app !== '') {
213				app += '/';
214				link += app;
215			}
216			if (type) {
217				link += type + '/';
218			}
219			link += file;
220		}
221		return link;
222	},
223
224    /**
225     * Check if a user file is allowed to be handled.
226     * @param {string} file to check
227     */
228	fileIsBlacklisted: function(file) {
229		return !!(file.match(oc_config.blacklist_files_regex));
230	},
231
232	/**
233	 * Redirect to the target URL, can also be used for downloads.
234	 * @param {string} targetURL URL to redirect to
235	 */
236	redirect: function (targetURL) {
237		window.location = targetURL;
238	},
239
240	/**
241	 * Reloads the current page
242	 */
243	reload: function () {
244		window.location.reload();
245	},
246
247	/**
248	 * Protocol that is used to access this ownCloud instance
249	 * @return {string} Used protocol
250	 */
251	getProtocol: function () {
252		return window.location.protocol.split(':')[0];
253	},
254
255	/**
256	 * Returns the host used to access this ownCloud instance
257	 * Host is sometimes the same as the hostname but now always.
258	 *
259	 * Examples:
260	 * http://example.com => example.com
261	 * https://example.com => example.com
262	 * http://example.com:8080 => example.com:8080
263	 *
264	 * @return {string} host
265	 *
266	 * @since 8.2
267	 */
268	getHost: function () {
269		return window.location.host;
270	},
271
272	/**
273	 * Returns the hostname used to access this ownCloud instance
274	 * The hostname is always stripped of the port
275	 *
276	 * @return {string} hostname
277	 * @since 9.0
278	 */
279	getHostName: function () {
280		return window.location.hostname;
281	},
282
283	/**
284	 * Returns the port number used to access this ownCloud instance
285	 *
286	 * @return {int} port number
287	 *
288	 * @since 8.2
289	 */
290	getPort: function () {
291		return window.location.port;
292	},
293
294	/**
295	 * Returns the web root path where this ownCloud instance
296	 * is accessible, with a leading slash.
297	 * For example "/owncloud".
298	 *
299	 * @return {string} web root path
300	 *
301	 * @since 8.2
302	 */
303	getRootPath: function () {
304		return OC.webroot;
305	},
306
307	/**
308	 * Returns the capabilities
309	 *
310	 * @return {array} capabilities
311	 */
312	getCapabilities: function() {
313		return OC._capabilities;
314	},
315
316	/**
317	 * Returns the currently logged in user or null if there is no logged in
318	 * user (public page mode)
319	 *
320	 * @return {OC.CurrentUser} user spec
321	 * @since 9.0.0
322	 */
323	getCurrentUser: function () {
324		if (!_.isUndefined(window.oc_user)) {
325			return oc_user;
326		}
327		return {
328			uid: null,
329			displayName: null,
330			email: null
331		};
332	},
333
334	/**
335	 * get the absolute path to an image file
336	 * if no extension is given for the image, it will automatically decide
337	 * between .png and .svg based on what the browser supports
338	 * @param {string} app the app id to which the image belongs
339	 * @param {string} file the name of the image file
340	 * @return {string}
341	 */
342	imagePath: function (app, file) {
343		if (file.indexOf('.') == -1) {//if no extension is given, use svg
344			file += '.svg';
345		}
346		return OC.filePath(app, 'img', file);
347	},
348
349	/**
350	 * URI-Encodes a file path but keep the path slashes.
351	 *
352	 * @param path path
353	 * @return encoded path
354	 */
355	encodePath: function (path) {
356		if (!path) {
357			return path;
358		}
359		var parts = path.split('/');
360		var result = [];
361		for (var i = 0; i < parts.length; i++) {
362			result.push(encodeURIComponent(parts[i]));
363		}
364		return result.join('/');
365	},
366
367	/**
368	 * Load a script for the server and load it. If the script is already loaded,
369	 * the event handler will be called directly
370	 * @param {string} app the app id to which the script belongs
371	 * @param {string} script the filename of the script
372	 * @param ready event handler to be called when the script is loaded
373	 */
374	addScript: function (app, script, ready) {
375		var deferred, path = OC.filePath(app, 'js', script + '.js');
376		if (!OC.addScript.loaded[path]) {
377			if (ready) {
378				deferred = $.getScript(path, ready);
379			} else {
380				deferred = $.getScript(path);
381			}
382			OC.addScript.loaded[path] = deferred;
383		} else {
384			if (ready) {
385				ready();
386			}
387		}
388		return OC.addScript.loaded[path];
389	},
390	/**
391	 * Loads a CSS file
392	 * @param {string} app the app id to which the css style belongs
393	 * @param {string} style the filename of the css file
394	 */
395	addStyle: function (app, style) {
396		var path = OC.filePath(app, 'css', style + '.css');
397		if (OC.addStyle.loaded.indexOf(path) === -1) {
398			OC.addStyle.loaded.push(path);
399			if (document.createStyleSheet) {
400				document.createStyleSheet(path);
401			} else {
402				style = $('<link rel="stylesheet" type="text/css" href="' + path + '"/>');
403				$('head').append(style);
404			}
405		}
406	},
407
408	/**
409	 * Loads translations for the given app asynchronously.
410	 *
411	 * @param {String} app app name
412	 * @param {Function} callback callback to call after loading
413	 * @return {Promise}
414	 */
415	addTranslations: function (app, callback) {
416		return OC.L10N.load(app, callback);
417	},
418
419	/**
420	 * Returns the base name of the given path.
421	 * For example for "/abc/somefile.txt" it will return "somefile.txt"
422	 *
423	 * @param {String} path
424	 * @return {String} base name
425	 */
426	basename: function (path) {
427		return path.replace(/\\/g, '/').replace(/.*\//, '');
428	},
429
430	/**
431	 * Returns the dir name of the given path.
432	 * For example for "/abc/somefile.txt" it will return "/abc"
433	 *
434	 * @param {String} path
435	 * @return {String} dir name
436	 */
437	dirname: function (path) {
438		return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, '');
439	},
440
441	/**
442	 * Returns whether the given paths are the same, without
443	 * leading, trailing or doubled slashes and also removing
444	 * the dot sections.
445	 *
446	 * @param {String} path1 first path
447	 * @param {String} path2 second path
448	 * @return {bool} true if the paths are the same
449	 *
450	 * @since 9.0
451	 */
452	isSamePath: function (path1, path2) {
453		var filterDot = function (p) {
454			return p !== '.';
455		};
456		var pathSections1 = _.filter((path1 || '').split('/'), filterDot);
457		var pathSections2 = _.filter((path2 || '').split('/'), filterDot);
458		path1 = OC.joinPaths.apply(OC, pathSections1);
459		path2 = OC.joinPaths.apply(OC, pathSections2);
460		return path1 === path2;
461	},
462
463	/**
464	 * Join path sections
465	 *
466	 * @param {...String} path sections
467	 *
468	 * @return {String} joined path, any leading or trailing slash
469	 * will be kept
470	 *
471	 * @since 8.2
472	 */
473	joinPaths: function () {
474		if (arguments.length < 1) {
475			return '';
476		}
477		var path = '';
478		// convert to array
479		var args = Array.prototype.slice.call(arguments);
480		// discard empty arguments
481		args = _.filter(args, function (arg) {
482			return arg.length > 0;
483		});
484		if (args.length < 1) {
485			return '';
486		}
487
488		var lastArg = args[args.length - 1];
489		var leadingSlash = args[0].charAt(0) === '/';
490		var trailingSlash = lastArg.charAt(lastArg.length - 1) === '/';
491		var sections = [];
492		var i;
493		for (i = 0; i < args.length; i++) {
494			sections = sections.concat(args[i].split('/'));
495		}
496		var first = !leadingSlash;
497		for (i = 0; i < sections.length; i++) {
498			if (sections[i] !== '') {
499				if (first) {
500					first = false;
501				} else {
502					path += '/';
503				}
504				path += sections[i];
505			}
506		}
507
508		if (trailingSlash) {
509			// add it back
510			path += '/';
511		}
512		return path;
513	},
514
515	/**
516	 * Do a search query and display the results
517	 * @param {string} query the search query
518	 */
519	search: function (query) {
520		OC.Search.search(query, null, 0, 30);
521	},
522	/**
523	 * Dialog helper for jquery dialogs.
524	 *
525	 * @namespace OC.dialogs
526	 */
527	dialogs: OCdialogs,
528	/**
529	 * Parses a URL query string into a JS map
530	 * @param {string} queryString query string in the format param1=1234&param2=abcde&param3=xyz
531	 * @return {Object.<string, string>} map containing key/values matching the URL parameters
532	 */
533	parseQueryString: function (queryString) {
534		var parts,
535			pos,
536			components,
537			result = {},
538			key,
539			value;
540		if (!queryString) {
541			return null;
542		}
543		pos = queryString.indexOf('?');
544		if (pos >= 0) {
545			queryString = queryString.substr(pos + 1);
546		}
547		parts = queryString.replace(/\+/g, '%20').split('&');
548		for (var i = 0; i < parts.length; i++) {
549			// split on first equal sign
550			var part = parts[i];
551			pos = part.indexOf('=');
552			if (pos >= 0) {
553				components = [
554					part.substr(0, pos),
555					part.substr(pos + 1)
556				];
557			}
558			else {
559				// key only
560				components = [part];
561			}
562			if (!components.length) {
563				continue;
564			}
565			key = decodeURIComponent(components[0]);
566			if (!key) {
567				continue;
568			}
569			// if equal sign was there, return string
570			if (components.length > 1) {
571				result[key] = decodeURIComponent(components[1]);
572			}
573			// no equal sign => null value
574			else {
575				result[key] = null;
576			}
577		}
578		return result;
579	},
580
581	/**
582	 * Builds a URL query from a JS map.
583	 * @param {Object.<string, string>} params map containing key/values matching the URL parameters
584	 * @return {string} String containing a URL query (without question) mark
585	 */
586	buildQueryString: function (params) {
587		if (!params) {
588			return '';
589		}
590		return $.map(params, function (value, key) {
591			var s = encodeURIComponent(key);
592			if (value !== null && typeof(value) !== 'undefined') {
593				s += '=' + encodeURIComponent(value);
594			}
595			return s;
596		}).join('&');
597	},
598
599	/**
600	 * Opens a popup with the setting for an app.
601	 * @param {string} appid The ID of the app e.g. 'calendar', 'contacts' or 'files'.
602	 * @param {boolean|string} loadJS If true 'js/settings.js' is loaded. If it's a string
603	 * it will attempt to load a script by that name in the 'js' directory.
604	 * @param {boolean} [cache] If true the javascript file won't be forced refreshed. Defaults to true.
605	 * @param {string} [scriptName] The name of the PHP file to load. Defaults to 'settings.php' in
606	 * the root of the app directory hierarchy.
607	 */
608	appSettings: function (args) {
609		if (typeof args === 'undefined' || typeof args.appid === 'undefined') {
610			throw {name: 'MissingParameter', message: 'The parameter appid is missing'};
611		}
612		var props = {scriptName: 'settings.php', cache: true};
613		$.extend(props, args);
614		var settings = $('#appsettings');
615		if (settings.length === 0) {
616			throw {
617				name: 'MissingDOMElement',
618				message: 'There has be be an element with id "appsettings" for the popup to show.'
619			};
620		}
621		var popup = $('#appsettings_popup');
622		if (popup.length === 0) {
623			$('body').prepend('<div class="popup hidden" id="appsettings_popup"></div>');
624			popup = $('#appsettings_popup');
625			popup.addClass(settings.hasClass('topright') ? 'topright' : 'bottomleft');
626		}
627		if (popup.is(':visible')) {
628			popup.hide().remove();
629		} else {
630			var arrowclass = settings.hasClass('topright') ? 'up' : 'left';
631			var jqxhr = $.get(OC.filePath(props.appid, '', props.scriptName), function (data) {
632				popup.html(data).ready(function () {
633					popup.prepend('<span class="arrow ' + arrowclass + '"></span><h2>' + t('core', 'Settings') + '</h2><a class="close"></a>').show();
634					popup.find('.close').bind('click', function () {
635						popup.remove();
636					});
637					if (typeof props.loadJS !== 'undefined') {
638						var scriptname;
639						if (props.loadJS === true) {
640							scriptname = 'settings.js';
641						} else if (typeof props.loadJS === 'string') {
642							scriptname = props.loadJS;
643						} else {
644							throw {
645								name: 'InvalidParameter',
646								message: 'The "loadJS" parameter must be either boolean or a string.'
647							};
648						}
649						if (props.cache) {
650							$.ajaxSetup({cache: true});
651						}
652						$.getScript(OC.filePath(props.appid, 'js', scriptname))
653							.fail(function (jqxhr, settings, e) {
654								throw e;
655							});
656					}
657				}).show();
658			}, 'html');
659		}
660	},
661
662	/**
663	 * For menu toggling
664	 * @todo Write documentation
665	 */
666	registerMenu: function ($toggle, $menuEl) {
667		var self = this;
668		$menuEl.addClass('menu');
669		$toggle.on('click.menu', function (event) {
670			// prevent the link event (append anchor to URL)
671			event.preventDefault();
672
673			if ($menuEl.is(OC._currentMenu)) {
674				self.hideMenus();
675				return;
676			}
677			// another menu was open?
678			else if (OC._currentMenu) {
679				// close it
680				self.hideMenus();
681			}
682			$menuEl.slideToggle(OC.menuSpeed);
683			OC._currentMenu = $menuEl;
684			OC._currentMenuToggle = $toggle;
685		});
686	},
687
688	/**
689	 *  @todo Write documentation
690	 */
691	unregisterMenu: function ($toggle, $menuEl) {
692		// close menu if opened
693		if ($menuEl.is(OC._currentMenu)) {
694			this.hideMenus();
695		}
696		$toggle.off('click.menu').removeClass('menutoggle');
697		$menuEl.removeClass('menu');
698	},
699
700	/**
701	 * Hides any open menus
702	 *
703	 * @param {Function} complete callback when the hiding animation is done
704	 */
705	hideMenus: function (complete) {
706		if (OC._currentMenu) {
707			var lastMenu = OC._currentMenu;
708			OC._currentMenu.trigger(new $.Event('beforeHide'));
709			OC._currentMenu.slideUp(OC.menuSpeed, function () {
710				lastMenu.trigger(new $.Event('afterHide'));
711				if (complete) {
712					complete.apply(this, arguments);
713				}
714			});
715		}
716		OC._currentMenu = null;
717		OC._currentMenuToggle = null;
718	},
719
720	/**
721	 * Shows a given element as menu
722	 *
723	 * @param {Object} [$toggle=null] menu toggle
724	 * @param {Object} $menuEl menu element
725	 * @param {Function} complete callback when the showing animation is done
726	 */
727	showMenu: function ($toggle, $menuEl, complete) {
728		if ($menuEl.is(OC._currentMenu)) {
729			return;
730		}
731		this.hideMenus();
732		OC._currentMenu = $menuEl;
733		OC._currentMenuToggle = $toggle;
734		$menuEl.trigger(new $.Event('beforeShow'));
735		$menuEl.show();
736		$menuEl.trigger(new $.Event('afterShow'));
737		// no animation
738		if (_.isFunction(complete)) {
739			complete();
740		}
741	},
742
743	/**
744	 * Wrapper for matchMedia
745	 *
746	 * This is makes it possible for unit tests to
747	 * stub matchMedia (which doesn't work in PhantomJS)
748	 * @private
749	 */
750	_matchMedia: function (media) {
751		if (window.matchMedia) {
752			return window.matchMedia(media);
753		}
754		return false;
755	},
756
757	/**
758	 * Returns the user's locale
759	 *
760	 * @return {String} locale string
761	 */
762	getLocale: function () {
763		return $('html').prop('lang');
764	},
765
766	/**
767	 * Returns whether the current user is an administrator
768	 *
769	 * @return {bool} true if the user is an admin, false otherwise
770	 * @since 9.0.0
771	 */
772	isUserAdmin: function () {
773		return oc_isadmin;
774	},
775
776	/**
777	 * Process ajax error, redirects to main page
778	 * if an error/auth error status was returned.
779	 */
780	_processAjaxError: function (xhr) {
781		var self = this;
782		// purposefully aborted request ?
783		// this._userIsNavigatingAway needed to distinguish ajax calls cancelled by navigating away
784		// from calls cancelled by failed cross-domain ajax due to SSO redirect
785		if (xhr.status === 0 && (xhr.statusText === 'abort' || xhr.statusText === 'timeout' || self._reloadCalled)) {
786			return;
787		}
788
789		if (_.contains([0, 302, 303, 307, 401], xhr.status)) {
790			// sometimes "beforeunload" happens later, so need to defer the reload a bit
791			setTimeout(function () {
792				if (!self._userIsNavigatingAway && !self._reloadCalled) {
793					OC.Notification.show(t('core', 'Problem loading page, reloading in 5 seconds'));
794					setTimeout(OC.reload, 5000);
795					// only call reload once
796					self._reloadCalled = true;
797				}
798			}, 100);
799		}
800	},
801
802	/**
803	 * Registers XmlHttpRequest object for global error processing.
804	 *
805	 * This means that if this XHR object returns 401 or session timeout errors,
806	 * the current page will automatically be reloaded.
807	 *
808	 * @param {XMLHttpRequest} xhr
809	 */
810	registerXHRForErrorProcessing: function (xhr) {
811		var loadCallback = function () {
812			if (xhr.readyState !== 4) {
813				return;
814			}
815
816			if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
817				return;
818			}
819
820			// fire jquery global ajax error handler
821			$(document).trigger(new $.Event('ajaxError'), xhr);
822		};
823
824		var errorCallback = function () {
825			// fire jquery global ajax error handler
826			$(document).trigger(new $.Event('ajaxError'), xhr);
827		};
828
829		if (xhr.addEventListener) {
830			xhr.addEventListener('load', loadCallback);
831			xhr.addEventListener('error', errorCallback);
832		}
833
834	}
835};
836
837/**
838 * Current user attributes
839 *
840 * @typedef {Object} OC.CurrentUser
841 *
842 * @property {String} uid user id
843 * @property {String} displayName display name
844 */
845
846/**
847 * @namespace OC.Plugins
848 */
849OC.Plugins = {
850	/**
851	 * @type Array.<OC.Plugin>
852	 */
853	_plugins: {},
854
855	/**
856	 * Register plugin
857	 *
858	 * @param {String} targetName app name / class name to hook into
859	 * @param {OC.Plugin} plugin
860	 */
861	register: function (targetName, plugin) {
862		var plugins = this._plugins[targetName];
863		if (!plugins) {
864			plugins = this._plugins[targetName] = [];
865		}
866		plugins.push(plugin);
867	},
868
869	/**
870	 * Returns all plugin registered to the given target
871	 * name / app name / class name.
872	 *
873	 * @param {String} targetName app name / class name to hook into
874	 * @return {Array.<OC.Plugin>} array of plugins
875	 */
876	getPlugins: function (targetName) {
877		return this._plugins[targetName] || [];
878	},
879
880	/**
881	 * Call attach() on all plugins registered to the given target name.
882	 *
883	 * @param {String} targetName app name / class name
884	 * @param {Object} object to be extended
885	 * @param {Object} [options] options
886	 */
887	attach: function (targetName, targetObject, options) {
888		var plugins = this.getPlugins(targetName);
889		for (var i = 0; i < plugins.length; i++) {
890			if (plugins[i].attach) {
891				plugins[i].attach(targetObject, options);
892			}
893		}
894	},
895
896	/**
897	 * Call detach() on all plugins registered to the given target name.
898	 *
899	 * @param {String} targetName app name / class name
900	 * @param {Object} object to be extended
901	 * @param {Object} [options] options
902	 */
903	detach: function (targetName, targetObject, options) {
904		var plugins = this.getPlugins(targetName);
905		for (var i = 0; i < plugins.length; i++) {
906			if (plugins[i].detach) {
907				plugins[i].detach(targetObject, options);
908			}
909		}
910	},
911
912	/**
913	 * Plugin
914	 *
915	 * @todo make this a real class in the future
916	 * @typedef {Object} OC.Plugin
917	 *
918	 * @property {String} name plugin name
919	 * @property {Function} attach function that will be called when the
920	 * plugin is attached
921	 * @property {Function} [detach] function that will be called when the
922	 * plugin is detached
923	 */
924
925};
926
927/**
928 * @namespace OC.search
929 */
930OC.search.customResults = {};
931/**
932 * @deprecated use get/setFormatter() instead
933 */
934OC.search.resultTypes = {};
935
936OC.addStyle.loaded = [];
937OC.addScript.loaded = [];
938
939/**
940 * A little class to manage a status field for a "saving" process.
941 * It can be used to display a starting message (e.g. "Saving...") and then
942 * replace it with a green success message or a red error message.
943 *
944 * @namespace OC.msg
945 */
946OC.msg = {
947	/**
948	 * Displayes a "Saving..." message in the given message placeholder
949	 *
950	 * @param {Object} selector    Placeholder to display the message in
951	 */
952	startSaving: function (selector) {
953		this.startAction(selector, t('core', 'Saving...'));
954	},
955
956	/**
957	 * Displayes a custom message in the given message placeholder
958	 *
959	 * @param {Object} selector    Placeholder to display the message in
960	 * @param {string} message    Plain text message to display (no HTML allowed)
961	 */
962	startAction: function (selector, message) {
963		$(selector).text(message)
964			.removeClass('success')
965			.removeClass('error')
966			.stop(true, true)
967			.show();
968	},
969
970	/**
971	 * Displayes an success/error message in the given selector
972	 *
973	 * @param {Object} selector    Placeholder to display the message in
974	 * @param {Object} response    Response of the server
975	 * @param {Object} response.data    Data of the servers response
976	 * @param {string} response.data.message    Plain text message to display (no HTML allowed)
977	 * @param {string} response.status    is being used to decide whether the message
978	 * is displayed as an error/success
979	 */
980	finishedSaving: function (selector, response) {
981		this.finishedAction(selector, response);
982	},
983
984	/**
985	 * Displayes an success/error message in the given selector
986	 *
987	 * @param {Object} selector    Placeholder to display the message in
988	 * @param {Object} response    Response of the server
989	 * @param {Object} response.data Data of the servers response
990	 * @param {string} response.data.message Plain text message to display (no HTML allowed)
991	 * @param {string} response.status is being used to decide whether the message
992	 * is displayed as an error/success
993	 */
994	finishedAction: function (selector, response) {
995		if (response.status === "success") {
996			this.finishedSuccess(selector, response.data.message);
997		} else {
998			this.finishedError(selector, response.data.message);
999		}
1000	},
1001
1002	/**
1003	 * Displayes an success message in the given selector
1004	 *
1005	 * @param {Object} selector Placeholder to display the message in
1006	 * @param {string} message Plain text success message to display (no HTML allowed)
1007	 */
1008	finishedSuccess: function (selector, message) {
1009		$(selector).text(message)
1010			.addClass('success')
1011			.removeClass('error')
1012			.stop(true, true)
1013			.delay(3000)
1014			.fadeOut(900)
1015			.show();
1016	},
1017
1018	/**
1019	 * Displayes an error message in the given selector
1020	 *
1021	 * @param {Object} selector Placeholder to display the message in
1022	 * @param {string} message Plain text error message to display (no HTML allowed)
1023	 */
1024	finishedError: function (selector, message) {
1025		$(selector).text(message)
1026			.addClass('error')
1027			.removeClass('success')
1028			.show();
1029	}
1030};
1031
1032/**
1033 * @todo Write documentation
1034 * @namespace
1035 */
1036OC.Notification = {
1037	queuedNotifications: [],
1038	getDefaultNotificationFunction: null,
1039
1040	/**
1041	 * @type Array.<int> array of notification timers
1042	 */
1043	notificationTimers: [],
1044
1045	/**
1046	 * @param callback
1047	 * @todo Write documentation
1048	 */
1049	setDefault: function (callback) {
1050		OC.Notification.getDefaultNotificationFunction = callback;
1051	},
1052
1053	/**
1054	 * Hides a notification.
1055	 *
1056	 * If a row is given, only hide that one.
1057	 * If no row is given, hide all notifications.
1058	 *
1059	 * @param {jQuery} [$row] notification row
1060	 * @param {Function} [callback] callback
1061	 */
1062	hide: function ($row, callback) {
1063		var self = this;
1064		var $notification = $('#notification');
1065
1066		if (_.isFunction($row)) {
1067			// first arg is the callback
1068			callback = $row;
1069			$row = undefined;
1070		}
1071
1072		if (!$row) {
1073			console.warn('Missing argument $row in OC.Notification.hide() call, caller needs to be adjusted to only dismiss its own notification');
1074			// assume that the row to be hidden is the first one
1075			$row = $notification.find('.row:first');
1076		}
1077
1078		if ($row && $notification.find('.row').length > 1) {
1079			// remove the row directly
1080			$row.remove();
1081			if (callback) {
1082				callback.call();
1083			}
1084			return;
1085		}
1086
1087		_.defer(function () {
1088			// fade out is supposed to only fade when there is a single row
1089			// however, some code might call hide() and show() directly after,
1090			// which results in more than one element
1091			// in this case, simply delete that one element that was supposed to
1092			// fade out
1093			//
1094			// FIXME: remove once all callers are adjusted to only hide their own notifications
1095			if ($notification.find('.row').length > 1) {
1096				$row.remove();
1097				return;
1098			}
1099
1100			// else, fade out whatever was present
1101			$notification.fadeOut('400', function () {
1102				if (self.isHidden()) {
1103					if (self.getDefaultNotificationFunction) {
1104						self.getDefaultNotificationFunction.call();
1105					}
1106				}
1107				if (callback) {
1108					callback.call();
1109				}
1110				$notification.empty();
1111			});
1112		});
1113	},
1114
1115	/**
1116	 * Shows a notification as HTML without being sanitized before.
1117	 * If you pass unsanitized user input this may lead to a XSS vulnerability.
1118	 * Consider using show() instead of showHTML()
1119	 *
1120	 * @param {string} html Message to display
1121	 * @param {Object} [options] options
1122	 * @param {string] [options.type] notification type
1123	 * @param {int} [options.timeout=0] timeout value, defaults to 0 (permanent)
1124	 * @return {jQuery} jQuery element for notification row
1125	 */
1126	showHtml: function (html, options) {
1127		options = options || {};
1128		_.defaults(options, {
1129			timeout: 0
1130		});
1131
1132		var self = this;
1133		var $notification = $('#notification');
1134		if (this.isHidden()) {
1135			$notification.fadeIn().css('display', 'inline-block');
1136		}
1137		var $row = $('<div class="row"></div>');
1138		if (options.type) {
1139			$row.addClass('type-' + options.type);
1140		}
1141		if (options.type === 'error') {
1142			// add a close button
1143			var $closeButton = $('<a class="action close icon-close" href="#"></a>');
1144			$closeButton.attr('alt', t('core', 'Dismiss'));
1145			$row.append($closeButton);
1146			$closeButton.one('click', function () {
1147				self.hide($row);
1148				return false;
1149			});
1150			$row.addClass('closeable');
1151		}
1152
1153		$row.prepend(html);
1154		$notification.append($row);
1155
1156		if (options.timeout > 0) {
1157			// register timeout to vanish notification
1158			this.notificationTimers.push(setTimeout(function () {
1159				self.hide($row);
1160			}, (options.timeout * 1000)));
1161		}
1162
1163		return $row;
1164	},
1165
1166	/**
1167	 * Shows a sanitized notification
1168	 *
1169	 * @param {string} text Message to display
1170	 * @param {Object} [options] options
1171	 * @param {string] [options.type] notification type
1172	 * @param {int} [options.timeout=0] timeout value, defaults to 0 (permanent)
1173	 * @return {jQuery} jQuery element for notification row
1174	 */
1175	show: function (text, options) {
1176		return this.showHtml($('<div/>').text(text).html(), options);
1177	},
1178
1179	/**
1180	 * Shows a notification that disappears after x seconds, default is
1181	 * 7 seconds
1182	 *
1183	 * @param {string} text Message to show
1184	 * @param {array} [options] options array
1185	 * @param {int} [options.timeout=7] timeout in seconds, if this is 0 it will show the message permanently
1186	 * @param {boolean} [options.isHTML=false] an indicator for HTML notifications (true) or text (false)
1187	 * @param {string] [options.type] notification type
1188	 */
1189	showTemporary: function (text, options) {
1190		var self = this;
1191		var defaults = {
1192			isHTML: false,
1193			timeout: 7
1194		};
1195		options = options || {};
1196		// merge defaults with passed in options
1197		_.defaults(options, defaults);
1198
1199		var $row;
1200		if (options.isHTML) {
1201			$row = this.showHtml(text, options);
1202		} else {
1203			$row = this.show(text, options);
1204		}
1205		return $row;
1206	},
1207
1208	/**
1209	 * Returns whether a notification is hidden.
1210	 * @return {boolean}
1211	 */
1212	isHidden: function () {
1213		return !$("#notification").find('.row').length;
1214	}
1215};
1216
1217/**
1218 * Breadcrumb class
1219 *
1220 * @namespace
1221 *
1222 * @deprecated will be replaced by the breadcrumb implementation
1223 * of the files app in the future
1224 */
1225OC.Breadcrumb = {
1226	container: null,
1227	/**
1228	 * @todo Write documentation
1229	 * @param dir
1230	 * @param leafName
1231	 * @param leafLink
1232	 */
1233	show: function (dir, leafName, leafLink) {
1234		if (!this.container) {//default
1235			this.container = $('#controls');
1236		}
1237		this._show(this.container, dir, leafName, leafLink);
1238	},
1239	_show: function (container, dir, leafname, leaflink) {
1240		var self = this;
1241
1242		this._clear(container);
1243
1244		// show home + path in subdirectories
1245		if (dir) {
1246			//add home
1247			var link = OC.linkTo('files', 'index.php');
1248
1249			var crumb = $('<div/>');
1250			crumb.addClass('crumb');
1251
1252			var crumbLink = $('<a/>');
1253			crumbLink.attr('href', link);
1254
1255			var crumbImg = $('<img/>');
1256			crumbImg.attr('src', OC.imagePath('core', 'places/home'));
1257			crumbLink.append(crumbImg);
1258			crumb.append(crumbLink);
1259			container.prepend(crumb);
1260
1261			//add path parts
1262			var segments = dir.split('/');
1263			var pathurl = '';
1264			jQuery.each(segments, function (i, name) {
1265				if (name !== '') {
1266					pathurl = pathurl + '/' + name;
1267					var link = OC.linkTo('files', 'index.php') + '?dir=' + encodeURIComponent(pathurl);
1268					self._push(container, name, link);
1269				}
1270			});
1271		}
1272
1273		//add leafname
1274		if (leafname && leaflink) {
1275			this._push(container, leafname, leaflink);
1276		}
1277	},
1278
1279	/**
1280	 * @todo Write documentation
1281	 * @param {string} name
1282	 * @param {string} link
1283	 */
1284	push: function (name, link) {
1285		if (!this.container) {//default
1286			this.container = $('#controls');
1287		}
1288		return this._push(OC.Breadcrumb.container, name, link);
1289	},
1290	_push: function (container, name, link) {
1291		var crumb = $('<div/>');
1292		crumb.addClass('crumb').addClass('last');
1293
1294		var crumbLink = $('<a/>');
1295		crumbLink.attr('href', link);
1296		crumbLink.text(name);
1297		crumb.append(crumbLink);
1298
1299		var existing = container.find('div.crumb');
1300		if (existing.length) {
1301			existing.removeClass('last');
1302			existing.last().after(crumb);
1303		} else {
1304			container.prepend(crumb);
1305		}
1306		return crumb;
1307	},
1308
1309	/**
1310	 * @todo Write documentation
1311	 */
1312	pop: function () {
1313		if (!this.container) {//default
1314			this.container = $('#controls');
1315		}
1316		this.container.find('div.crumb').last().remove();
1317		this.container.find('div.crumb').last().addClass('last');
1318	},
1319
1320	/**
1321	 * @todo Write documentation
1322	 */
1323	clear: function () {
1324		if (!this.container) {//default
1325			this.container = $('#controls');
1326		}
1327		this._clear(this.container);
1328	},
1329	_clear: function (container) {
1330		container.find('div.crumb').remove();
1331	}
1332};
1333
1334if (typeof localStorage !== 'undefined' && localStorage !== null) {
1335	/**
1336	 * User and instance aware localstorage
1337	 * @namespace
1338	 */
1339	OC.localStorage = {
1340		namespace: 'oc_' + OC.currentUser + '_' + OC.webroot + '_',
1341
1342		/**
1343		 * Whether the storage contains items
1344		 * @param {string} name
1345		 * @return {boolean}
1346		 */
1347		hasItem: function (name) {
1348			return OC.localStorage.getItem(name) !== null;
1349		},
1350
1351		/**
1352		 * Add an item to the storage
1353		 * @param {string} name
1354		 * @param {string} item
1355		 */
1356		setItem: function (name, item) {
1357			return localStorage.setItem(OC.localStorage.namespace + name, JSON.stringify(item));
1358		},
1359
1360		/**
1361		 * Removes an item from the storage
1362		 * @param {string} name
1363		 * @param {string} item
1364		 */
1365		removeItem: function (name, item) {
1366			return localStorage.removeItem(OC.localStorage.namespace + name);
1367		},
1368
1369		/**
1370		 * Get an item from the storage
1371		 * @param {string} name
1372		 * @return {null|string}
1373		 */
1374		getItem: function (name) {
1375			var item = localStorage.getItem(OC.localStorage.namespace + name);
1376			if (item === null) {
1377				return null;
1378			} else {
1379				return JSON.parse(item);
1380			}
1381		}
1382	};
1383} else {
1384	//dummy localstorage
1385	OC.localStorage = {
1386		hasItem: function () {
1387			return false;
1388		},
1389		setItem: function () {
1390			return false;
1391		},
1392		getItem: function () {
1393			return null;
1394		}
1395	};
1396}
1397
1398/**
1399 * prototypical inheritance functions
1400 * @todo Write documentation
1401 * usage:
1402 * MySubObject=object(MyObject)
1403 */
1404function object(o) {
1405	function F() {
1406	}
1407
1408	F.prototype = o;
1409	return new F();
1410}
1411
1412/**
1413 * Initializes core
1414 */
1415function initCore() {
1416	/**
1417	 * Disable automatic evaluation of responses for $.ajax() functions (and its
1418	 * higher-level alternatives like $.get() and $.post()).
1419	 *
1420	 * If a response to a $.ajax() request returns a content type of "application/javascript"
1421	 * JQuery would previously execute the response body. This is a pretty unexpected
1422	 * behaviour and can result in a bypass of our Content-Security-Policy as well as
1423	 * multiple unexpected XSS vectors.
1424	 */
1425	$.ajaxSetup({
1426		contents: {
1427			script: false
1428		}
1429	});
1430
1431	/**
1432	 * Set users locale to moment.js as soon as possible
1433	 */
1434	moment.locale(OC.getLocale());
1435
1436	var userAgent = window.navigator.userAgent;
1437	var msie = userAgent.indexOf('MSIE ');
1438	var trident = userAgent.indexOf('Trident/');
1439	var edge = userAgent.indexOf('Edge/');
1440
1441	if (msie > 0 || trident > 0) {
1442		// (IE 10 or older) || IE 11
1443		$('html').addClass('ie');
1444	} else if (edge > 0) {
1445		// for edge
1446		$('html').addClass('edge');
1447	}
1448
1449	$(window).on('unload.main', function () {
1450		OC._unloadCalled = true;
1451	});
1452	$(window).on('beforeunload.main', function () {
1453		// super-trick thanks to http://stackoverflow.com/a/4651049
1454		// in case another handler displays a confirmation dialog (ex: navigating away
1455		// during an upload), there are two possible outcomes: user clicked "ok" or
1456		// "cancel"
1457
1458		// first timeout handler is called after unload dialog is closed
1459		setTimeout(function () {
1460			OC._userIsNavigatingAway = true;
1461
1462			// second timeout event is only called if user cancelled (Chrome),
1463			// but in other browsers it might still be triggered, so need to
1464			// set a higher delay...
1465			setTimeout(function () {
1466				if (!OC._unloadCalled) {
1467					OC._userIsNavigatingAway = false;
1468				}
1469			}, 10000);
1470		}, 1);
1471	});
1472	$(document).on('ajaxError.main', function (event, request, settings) {
1473		if (settings && settings.allowAuthErrors) {
1474			return;
1475		}
1476		OC._processAjaxError(request);
1477	});
1478
1479	/**
1480	 * Calls the server periodically to ensure that session doesn't
1481	 * time out
1482	 */
1483	function initSessionHeartBeat() {
1484		// max interval in seconds set to 24 hours
1485		var maxInterval = 24 * 3600;
1486		// interval in seconds
1487		var interval = 900;
1488		if (oc_config.session_lifetime) {
1489			interval = Math.floor(oc_config.session_lifetime / 2);
1490		}
1491		// minimum one minute
1492		if (interval < 60) {
1493			interval = 60;
1494		}
1495		if (interval > maxInterval) {
1496			interval = maxInterval;
1497		}
1498		var url = OC.generateUrl('/heartbeat');
1499		var heartBeatTimeout = null;
1500		var heartBeat = function () {
1501			clearInterval(heartBeatTimeout);
1502			heartBeatTimeout = setInterval(function () {
1503				$.post(url);
1504			}, interval * 1000);
1505		};
1506		$(document).ajaxComplete(heartBeat);
1507		heartBeat();
1508	}
1509
1510	// session heartbeat (defaults to enabled)
1511	if (typeof(oc_config.session_keepalive) === 'undefined' || !!oc_config.session_keepalive) {
1512
1513		initSessionHeartBeat();
1514	}
1515
1516	OC.registerMenu($('#expand'), $('#expanddiv'));
1517
1518	// toggle for menus
1519	$(document).on('mouseup.closemenus', function (event) {
1520		var $el = $(event.target);
1521		if ($el.closest('.menu').length || $el.closest('.menutoggle').length) {
1522			// don't close when clicking on the menu directly or a menu toggle
1523			return false;
1524		}
1525
1526		OC.hideMenus();
1527	});
1528
1529	/**
1530	 * Set up the main menu toggle to react to media query changes.
1531	 * If the screen is small enough, the main menu becomes a toggle.
1532	 * If the screen is bigger, the main menu is not a toggle any more.
1533	 */
1534	function setupMainMenu() {
1535		// toggle the navigation
1536		var $toggle = $('#header .header-appname-container');
1537		var $navigation = $('#navigation');
1538
1539		// init the menu
1540		OC.registerMenu($toggle, $navigation);
1541		$toggle.data('oldhref', $toggle.attr('href'));
1542		$toggle.attr('href', '#');
1543		$navigation.hide();
1544
1545		// show loading feedback
1546		$navigation.delegate('a', 'click', function (event) {
1547			var $app = $(event.target);
1548			if (!$app.is('a')) {
1549				$app = $app.closest('a');
1550			}
1551			if (!event.ctrlKey) {
1552				$app.addClass('app-loading');
1553			} else {
1554				// Close navigation when opening app in
1555				// a new tab
1556				OC.hideMenus();
1557			}
1558		});
1559	}
1560
1561	function setupUserMenu() {
1562		var $menu = $('#header #settings');
1563
1564		$menu.delegate('a', 'click', function (event) {
1565			var $page = $(event.target);
1566			if (!$page.is('a')) {
1567				$page = $page.closest('a');
1568			}
1569			$page.find('img').remove();
1570			$page.find('div').remove(); // prevent odd double-clicks
1571			$page.prepend($('<div/>').addClass('icon-loading-small-dark'));
1572		});
1573	}
1574
1575	setupMainMenu();
1576	setupUserMenu();
1577
1578	// just add snapper for logged in users
1579	if ($('#app-navigation').length && !$('html').hasClass('lte9')) {
1580
1581		// App sidebar on mobile
1582		var snapper = new Snap({
1583			element: document.getElementById('app-content'),
1584			disable: 'right',
1585			maxPosition: 250,
1586			minDragDistance: 100
1587		});
1588		$('#app-content').prepend('<div id="app-navigation-toggle" class="icon-menu" style="display:none;"></div>');
1589		$('#app-navigation-toggle').click(function () {
1590			if (snapper.state().state == 'left') {
1591				snapper.close();
1592			} else {
1593				snapper.open('left');
1594			}
1595		});
1596		// close sidebar when switching navigation entry
1597		var $appNavigation = $('#app-navigation');
1598		$appNavigation.delegate('a, :button', 'click', function (event) {
1599			var $target = $(event.target);
1600			// don't hide navigation when changing settings or adding things
1601			if ($target.is('.app-navigation-noclose') ||
1602				$target.closest('.app-navigation-noclose').length) {
1603				return;
1604			}
1605			if ($target.is('.add-new') ||
1606				$target.closest('.add-new').length) {
1607				return;
1608			}
1609			if ($target.is('#app-settings') ||
1610				$target.closest('#app-settings').length) {
1611				return;
1612			}
1613			snapper.close();
1614		});
1615
1616		var toggleSnapperOnSize = function () {
1617			if ($(window).width() > 768) {
1618				snapper.close();
1619				snapper.disable();
1620			} else {
1621				snapper.enable();
1622			}
1623		};
1624
1625		$(window).resize(_.debounce(toggleSnapperOnSize, 250));
1626
1627		// initial call
1628		toggleSnapperOnSize();
1629
1630		// adjust controls bar width
1631		var adjustControlsWidth = function () {
1632			if ($('#controls').length) {
1633				var controlsWidth;
1634				// if there is a scrollbar …
1635				if ($('#app-content').get(0).scrollHeight > $('#app-content').height()) {
1636					if ($(window).width() > 768) {
1637						controlsWidth = $('#content').width() - $('#app-navigation').width() - getScrollBarWidth();
1638						if (!$('#app-sidebar').hasClass('hidden') && !$('#app-sidebar').hasClass('disappear')) {
1639							controlsWidth -= $('#app-sidebar').width();
1640						}
1641					} else {
1642						controlsWidth = $('#content').width() - getScrollBarWidth();
1643					}
1644				} else { // if there is none
1645					if ($(window).width() > 768) {
1646						controlsWidth = $('#content').width() - $('#app-navigation').width();
1647						if (!$('#app-sidebar').hasClass('hidden') && !$('#app-sidebar').hasClass('disappear')) {
1648							controlsWidth -= $('#app-sidebar').width();
1649						}
1650					} else {
1651						controlsWidth = $('#content').width();
1652					}
1653				}
1654				$('#controls').css('width', controlsWidth);
1655				$('#controls').css('min-width', controlsWidth);
1656			}
1657		};
1658
1659		$(window).resize(_.debounce(adjustControlsWidth, 250));
1660
1661		$('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth);
1662
1663	}
1664}
1665
1666/**
1667 * Disable the use of globalEval in jQuery 2.1.4.
1668 * This is required for API compatibility, yet should not be available all the
1669 * same.
1670 *
1671 * @see https://github.com/jquery/jquery/issues/2432 for further details.
1672 */
1673$.fn.globalEval = function(){};
1674
1675$(document).ready(initCore);
1676
1677/**
1678 * Filter Jquery selector by attribute value
1679 */
1680$.fn.filterAttr = function (attr_name, attr_value) {
1681	return this.filter(function () {
1682		return $(this).attr(attr_name) === attr_value;
1683	});
1684};
1685
1686/**
1687 * Returns a human readable file size
1688 * @param {number} size Size in bytes
1689 * @param {boolean} skipSmallSizes return '< 1 kB' for small files
1690 * @return {string}
1691 */
1692function humanFileSize(size, skipSmallSizes) {
1693	var humanList = ['B', 'KB', 'MB', 'GB', 'TB'];
1694	// Calculate Log with base 1024: size = 1024 ** order
1695	var order = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0;
1696	// Stay in range of the byte sizes that are defined
1697	order = Math.min(humanList.length - 1, order);
1698	var readableFormat = humanList[order];
1699	var relativeSize = (size / Math.pow(1024, order)).toFixed(1);
1700	if (skipSmallSizes === true && order === 0) {
1701		if (relativeSize !== "0.0") {
1702			return '< 1 KB';
1703		} else {
1704			return '0 KB';
1705		}
1706	}
1707	if (order < 2) {
1708		relativeSize = parseFloat(relativeSize).toFixed(0);
1709	}
1710	else if (relativeSize.substr(relativeSize.length - 2, 2) === '.0') {
1711		relativeSize = relativeSize.substr(0, relativeSize.length - 2);
1712	}
1713	return relativeSize + ' ' + readableFormat;
1714}
1715
1716/**
1717 * Format an UNIX timestamp to a human understandable format
1718 * @param {number} timestamp UNIX timestamp
1719 * @return {string} Human readable format
1720 */
1721function formatDate(timestamp) {
1722	return OC.Util.formatDate(timestamp);
1723}
1724
1725//
1726/**
1727 * Get the value of a URL parameter
1728 * @link http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery
1729 * @param {string} name URL parameter
1730 * @return {string}
1731 */
1732function getURLParameter(name) {
1733	return decodeURI(
1734		(RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1]
1735	);
1736}
1737
1738/**
1739 * Takes an absolute timestamp and return a string with a human-friendly relative date
1740 * @param {number} timestamp A Unix timestamp
1741 */
1742function relative_modified_date(timestamp) {
1743	/*
1744	 Were multiplying by 1000 to bring the timestamp back to its original value
1745	 per https://github.com/owncloud/core/pull/10647#discussion_r16790315
1746	 */
1747	return OC.Util.relativeModifiedDate(timestamp * 1000);
1748}
1749
1750/**
1751 * Utility functions
1752 * @namespace
1753 */
1754OC.Util = {
1755	// TODO: remove original functions from global namespace
1756	humanFileSize: humanFileSize,
1757	
1758	/**
1759	* regular expression to parse size in bytes from a humanly readable string
1760	* see computerFileSize(string)
1761	*/
1762	_computerFileSizeRegexp: /^[\s+]?([0-9]*)(\.([0-9]+))?( +)?([kmgtp]?b?)$/i,
1763
1764	/**
1765	 * Returns a file size in bytes from a humanly readable string
1766	 * Makes 2kB to 2048.
1767	 * Inspired by computerFileSize in helper.php
1768	 * @param  {string} string file size in human readable format
1769	 * @return {number} or null if string could not be parsed
1770	 *
1771	 *
1772	 */
1773	computerFileSize: function (string) {
1774		if (typeof string !== 'string') {
1775			return null;
1776		}
1777
1778		var s = string.toLowerCase().trim();
1779		var bytes = null;
1780		
1781		var bytesArray = {
1782			'b': 1,
1783			'k': 1024,
1784			'kb': 1024,
1785			'mb': 1024 * 1024,
1786			'm': 1024 * 1024,
1787			'gb': 1024 * 1024 * 1024,
1788			'g': 1024 * 1024 * 1024,
1789			'tb': 1024 * 1024 * 1024 * 1024,
1790			't': 1024 * 1024 * 1024 * 1024,
1791			'pb': 1024 * 1024 * 1024 * 1024 * 1024,
1792			'p': 1024 * 1024 * 1024 * 1024 * 1024
1793		};
1794
1795		var matches = s.match(this._computerFileSizeRegexp);
1796		if (matches !== null) {
1797			bytes = parseFloat(s);
1798			if (!isFinite(bytes)) {
1799				return null;
1800			}
1801		} else {
1802			return null;
1803		}
1804		if (matches[5]) {
1805			bytes = bytes * bytesArray[matches[5]];
1806		}
1807
1808		bytes = Math.round(bytes);
1809		return bytes;
1810	},
1811
1812	/**
1813	 * @param timestamp
1814	 * @param format
1815	 * @returns {string} timestamp formatted as requested
1816	 */
1817	formatDate: function (timestamp, format) {
1818		format = format || "LLL";
1819		return moment(timestamp).format(format);
1820	},
1821
1822	/**
1823	 * @param timestamp
1824	 * @returns {string} human readable difference from now
1825	 */
1826	relativeModifiedDate: function (timestamp) {
1827		var diff = moment().diff(moment(timestamp));
1828		if (diff >= 0 && diff < 45000) {
1829			return t('core', 'seconds ago');
1830		}
1831		return moment(timestamp).fromNow();
1832	},
1833	/**
1834	 * Returns whether the browser supports SVG
1835	 * @deprecated SVG is always supported (since 9.0)
1836	 * @return {boolean} true if the browser supports SVG, false otherwise
1837	 */
1838	hasSVGSupport: function () {
1839		return true
1840	},
1841	/**
1842	 * If SVG is not supported, replaces the given icon's extension
1843	 * from ".svg" to ".png".
1844	 * If SVG is supported, return the image path as is.
1845	 * @param {string} file image path with svg extension
1846	 * @deprecated SVG is always supported (since 9.0)
1847	 * @return {string} fixed image path with png extension if SVG is not supported
1848	 */
1849	replaceSVGIcon: function (file) {
1850		return file;
1851	},
1852	/**
1853	 * Replace SVG images in all elements that have the "svg" class set
1854	 * with PNG images.
1855	 *
1856	 * @param $el root element from which to search, defaults to $('body')
1857	 * @deprecated SVG is always supported (since 9.0)
1858	 */
1859	replaceSVG: function ($el) {
1860	},
1861
1862	/**
1863	 * Fix image scaling for IE8, since background-size is not supported.
1864	 *
1865	 * This scales the image to the element's actual size, the URL is
1866	 * taken from the "background-image" CSS attribute.
1867	 *
1868	 * @deprecated IE8 isn't supported since 9.0
1869	 * @param {Object} $el image element
1870	 */
1871	scaleFixForIE8: function ($el) {
1872	},
1873
1874	/**
1875	 * Returns whether this is IE
1876	 *
1877	 * @deprecated Use bowser.mise instead (since 9.1)
1878	 * @return {bool} true if this is IE, false otherwise
1879	 */
1880	isIE: function () {
1881		return bowser.msie;
1882	},
1883
1884	/**
1885	 * Returns whether this is IE8
1886	 *
1887	 * @deprecated IE8 isn't supported since 9.0
1888	 * @return {bool} false (IE8 isn't supported anymore)
1889	 */
1890	isIE8: function () {
1891		return false;
1892	},
1893
1894	/**
1895	 * Returns the width of a generic browser scrollbar
1896	 *
1897	 * @return {int} width of scrollbar
1898	 */
1899	getScrollBarWidth: function () {
1900		if (this._scrollBarWidth) {
1901			return this._scrollBarWidth;
1902		}
1903
1904		var inner = document.createElement('p');
1905		inner.style.width = "100%";
1906		inner.style.height = "200px";
1907
1908		var outer = document.createElement('div');
1909		outer.style.position = "absolute";
1910		outer.style.top = "0px";
1911		outer.style.left = "0px";
1912		outer.style.visibility = "hidden";
1913		outer.style.width = "200px";
1914		outer.style.height = "150px";
1915		outer.style.overflow = "hidden";
1916		outer.appendChild(inner);
1917
1918		document.body.appendChild(outer);
1919		var w1 = inner.offsetWidth;
1920		outer.style.overflow = 'scroll';
1921		var w2 = inner.offsetWidth;
1922		if (w1 === w2) {
1923			w2 = outer.clientWidth;
1924		}
1925
1926		document.body.removeChild(outer);
1927
1928		this._scrollBarWidth = (w1 - w2);
1929
1930		return this._scrollBarWidth;
1931	},
1932
1933	/**
1934	 * Remove the time component from a given date
1935	 *
1936	 * @param {Date} date date
1937	 * @return {Date} date with stripped time
1938	 */
1939	stripTime: function (date) {
1940		// FIXME: likely to break when crossing DST
1941		// would be better to use a library like momentJS
1942		return new Date(date.getFullYear(), date.getMonth(), date.getDate());
1943	},
1944
1945	_chunkify: function (t) {
1946		// Adapted from http://my.opera.com/GreyWyvern/blog/show.dml/1671288
1947		var tz = [], x = 0, y = -1, n = 0, code, c;
1948
1949		while (x < t.length) {
1950			c = t.charAt(x);
1951			// only include the dot in strings
1952			var m = ((!n && c === '.') || (c >= '0' && c <= '9'));
1953			if (m !== n) {
1954				// next chunk
1955				y++;
1956				tz[y] = '';
1957				n = m;
1958			}
1959			tz[y] += c;
1960			x++;
1961		}
1962		return tz;
1963	},
1964	/**
1965	 * Compare two strings to provide a natural sort
1966	 * @param a first string to compare
1967	 * @param b second string to compare
1968	 * @return -1 if b comes before a, 1 if a comes before b
1969	 * or 0 if the strings are identical
1970	 */
1971	naturalSortCompare: function (a, b) {
1972		var x;
1973		var aa = OC.Util._chunkify(a);
1974		var bb = OC.Util._chunkify(b);
1975
1976		for (x = 0; aa[x] && bb[x]; x++) {
1977			if (aa[x] !== bb[x]) {
1978				var aNum = Number(aa[x]), bNum = Number(bb[x]);
1979				// note: == is correct here
1980				if (aNum == aa[x] && bNum == bb[x]) {
1981					return aNum - bNum;
1982				} else {
1983					// Forcing 'en' locale to match the server-side locale which is
1984					// always 'en'.
1985					//
1986					// Note: This setting isn't supported by all browsers but for the ones
1987					// that do there will be more consistency between client-server sorting
1988					return aa[x].localeCompare(bb[x], 'en');
1989				}
1990			}
1991		}
1992		return aa.length - bb.length;
1993	},
1994	/**
1995	 * Calls the callback in a given interval until it returns true
1996	 * @param {function} callback
1997	 * @param {integer} interval in milliseconds
1998	 */
1999	waitFor: function (callback, interval) {
2000		var internalCallback = function () {
2001			if (callback() !== true) {
2002				setTimeout(internalCallback, interval);
2003			}
2004		};
2005
2006		internalCallback();
2007	},
2008	/**
2009	 * Checks if a cookie with the given name is present and is set to the provided value.
2010	 * @param {string} name name of the cookie
2011	 * @param {string} value value of the cookie
2012	 * @return {boolean} true if the cookie with the given name has the given value
2013	 */
2014	isCookieSetToValue: function (name, value) {
2015		var cookies = document.cookie.split(';');
2016		for (var i = 0; i < cookies.length; i++) {
2017			var cookie = cookies[i].split('=');
2018			if (cookie[0].trim() === name && cookie[1].trim() === value) {
2019				return true;
2020			}
2021		}
2022		return false;
2023	},
2024	
2025	/**
2026	 * Checks if an element is completely visible and scrolls the screen if not
2027	 * @param {jQuery} jQuery element that has to be displayed 
2028	 * @param {jQuery} scroll container if null scrollContainer will be set to $('#app-content')
2029	 */
2030	scrollIntoView: function (toViewEl, scrollContainer) {
2031		
2032		var toViewElTopLocation = toViewEl.offset().top;
2033		var toViewElHeight = toViewEl.outerHeight();
2034		var toViewElBottomLocation = toViewElTopLocation + toViewElHeight + 50;
2035		var windowHeight = $(window).height();
2036		
2037		if (scrollContainer === null) {
2038			scrollContainer = $('#app-content');
2039		}
2040
2041		if (toViewElBottomLocation > windowHeight) {
2042			var currentPosition = scrollContainer[0].scrollTop;
2043			var scrollDistance = toViewElBottomLocation - windowHeight;
2044			scrollContainer.stop();
2045			scrollContainer.animate({
2046				scrollTop: currentPosition + scrollDistance
2047			}, 700);
2048		}
2049	}
2050};
2051
2052/**
2053 * Utility class for the history API,
2054 * includes fallback to using the URL hash when
2055 * the browser doesn't support the history API.
2056 *
2057 * @namespace
2058 */
2059OC.Util.History = {
2060	_handlers: [],
2061
2062	/**
2063	 * Push the current URL parameters to the history stack
2064	 * and change the visible URL.
2065	 * Note: this includes a workaround for IE8/IE9 that uses
2066	 * the hash part instead of the search part.
2067	 *
2068	 * @param params to append to the URL, can be either a string
2069	 * or a map
2070	 * @param {boolean} [replace=false] whether to replace instead of pushing
2071	 */
2072	_pushState: function (params, replace) {
2073		var strParams;
2074		if (typeof(params) === 'string') {
2075			strParams = params;
2076		}
2077		else {
2078			strParams = OC.buildQueryString(params);
2079		}
2080		if (window.history.pushState) {
2081			var url = location.pathname + '?' + strParams;
2082			if (replace) {
2083				window.history.replaceState(params, '', url);
2084			} else {
2085				window.history.pushState(params, '', url);
2086			}
2087		}
2088		// use URL hash for IE8
2089		else {
2090			window.location.hash = '?' + strParams;
2091			// inhibit next onhashchange that just added itself
2092			// to the event queue
2093			this._cancelPop = true;
2094		}
2095	},
2096
2097	/**
2098	 * Push the current URL parameters to the history stack
2099	 * and change the visible URL.
2100	 * Note: this includes a workaround for IE8/IE9 that uses
2101	 * the hash part instead of the search part.
2102	 *
2103	 * @param params to append to the URL, can be either a string
2104	 * or a map
2105	 */
2106	pushState: function (params) {
2107		return this._pushState(params, false);
2108	},
2109
2110	/**
2111	 * Push the current URL parameters to the history stack
2112	 * and change the visible URL.
2113	 * Note: this includes a workaround for IE8/IE9 that uses
2114	 * the hash part instead of the search part.
2115	 *
2116	 * @param params to append to the URL, can be either a string
2117	 * or a map
2118	 */
2119	replaceState: function (params) {
2120		return this._pushState(params, true);
2121	},
2122
2123	/**
2124	 * Add a popstate handler
2125	 *
2126	 * @param handler function
2127	 */
2128	addOnPopStateHandler: function (handler) {
2129		this._handlers.push(handler);
2130	},
2131
2132	/**
2133	 * Parse a query string from the hash part of the URL.
2134	 * (workaround for IE8 / IE9)
2135	 */
2136	_parseHashQuery: function () {
2137		var hash = window.location.hash,
2138			pos = hash.indexOf('?');
2139		if (pos >= 0) {
2140			return hash.substr(pos + 1);
2141		}
2142		if (hash.length) {
2143			// remove hash sign
2144			return hash.substr(1);
2145		}
2146		return '';
2147	},
2148
2149	_decodeQuery: function (query) {
2150		return query.replace(/\+/g, ' ');
2151	},
2152
2153	/**
2154	 * Parse the query/search part of the URL.
2155	 * Also try and parse it from the URL hash (for IE8)
2156	 *
2157	 * @return map of parameters
2158	 */
2159	parseUrlQuery: function () {
2160		var query = this._parseHashQuery(),
2161			params;
2162		// try and parse from URL hash first
2163		if (query) {
2164			params = OC.parseQueryString(this._decodeQuery(query));
2165		}
2166		// else read from query attributes
2167		params = _.extend(params || {}, OC.parseQueryString(this._decodeQuery(location.search)));
2168		return params || {};
2169	},
2170
2171	_onPopState: function (e) {
2172		if (this._cancelPop) {
2173			this._cancelPop = false;
2174			return;
2175		}
2176		var params;
2177		if (!this._handlers.length) {
2178			return;
2179		}
2180		params = (e && e.state);
2181		if (_.isString(params)) {
2182			params = OC.parseQueryString(params);
2183		} else if (!params) {
2184			params = this.parseUrlQuery() || {};
2185		}
2186		for (var i = 0; i < this._handlers.length; i++) {
2187			this._handlers[i](params);
2188		}
2189	}
2190};
2191
2192// fallback to hashchange when no history support
2193if (window.history.pushState) {
2194	window.onpopstate = _.bind(OC.Util.History._onPopState, OC.Util.History);
2195}
2196else {
2197	$(window).on('hashchange', _.bind(OC.Util.History._onPopState, OC.Util.History));
2198}
2199
2200/**
2201 * Get a variable by name
2202 * @param {string} name
2203 * @return {*}
2204 */
2205OC.get = function (name) {
2206	var namespaces = name.split(".");
2207	var tail = namespaces.pop();
2208	var context = window;
2209
2210	for (var i = 0; i < namespaces.length; i++) {
2211		context = context[namespaces[i]];
2212		if (!context) {
2213			return false;
2214		}
2215	}
2216	return context[tail];
2217};
2218
2219/**
2220 * Set a variable by name
2221 * @param {string} name
2222 * @param {*} value
2223 */
2224OC.set = function (name, value) {
2225	var namespaces = name.split(".");
2226	var tail = namespaces.pop();
2227	var context = window;
2228
2229	for (var i = 0; i < namespaces.length; i++) {
2230		if (!context[namespaces[i]]) {
2231			context[namespaces[i]] = {};
2232		}
2233		context = context[namespaces[i]];
2234	}
2235	context[tail] = value;
2236};
2237
2238// fix device width on windows phone
2239(function () {
2240	if ("-ms-user-select" in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/)) {
2241		var msViewportStyle = document.createElement("style");
2242		msViewportStyle.appendChild(
2243			document.createTextNode("@-ms-viewport{width:auto!important}")
2244		);
2245		document.getElementsByTagName("head")[0].appendChild(msViewportStyle);
2246	}
2247})();
2248
2249/**
2250 * Namespace for apps
2251 * @namespace OCA
2252 */
2253window.OCA = {};
2254
2255/**
2256 * select a range in an input field
2257 * @link http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
2258 * @param {type} start
2259 * @param {type} end
2260 */
2261jQuery.fn.selectRange = function (start, end) {
2262	return this.each(function () {
2263		if (this.setSelectionRange) {
2264			this.focus();
2265			this.setSelectionRange(start, end);
2266		} else if (this.createTextRange) {
2267			var range = this.createTextRange();
2268			range.collapse(true);
2269			range.moveEnd('character', end);
2270			range.moveStart('character', start);
2271			range.select();
2272		}
2273	});
2274};
2275
2276/**
2277 * check if an element exists.
2278 * allows you to write if ($('#myid').exists()) to increase readability
2279 * @link http://stackoverflow.com/questions/31044/is-there-an-exists-function-for-jquery
2280 */
2281jQuery.fn.exists = function () {
2282	return this.length > 0;
2283};
2284
2285/**
2286 * @deprecated use OC.Util.getScrollBarWidth() instead
2287 */
2288function getScrollBarWidth() {
2289	return OC.Util.getScrollBarWidth();
2290}
2291
2292/**
2293 * jQuery tipsy shim for the bootstrap tooltip
2294 */
2295jQuery.fn.tipsy = function (argument) {
2296	console.warn('Deprecation warning: tipsy is deprecated. Use tooltip instead.');
2297	if (typeof argument === 'object' && argument !== null) {
2298
2299		// tipsy defaults
2300		var options = {
2301			placement: 'bottom',
2302			delay: {'show': 0, 'hide': 0},
2303			trigger: 'hover',
2304			html: false,
2305			container: 'body'
2306		};
2307		if (argument.gravity) {
2308			switch (argument.gravity) {
2309				case 'n':
2310				case 'nw':
2311				case 'ne':
2312					options.placement = 'bottom';
2313					break;
2314				case 's':
2315				case 'sw':
2316				case 'se':
2317					options.placement = 'top';
2318					break;
2319				case 'w':
2320					options.placement = 'right';
2321					break;
2322				case 'e':
2323					options.placement = 'left';
2324					break;
2325			}
2326		}
2327		if (argument.trigger) {
2328			options.trigger = argument.trigger;
2329		}
2330		if (argument.delayIn) {
2331			options.delay["show"] = argument.delayIn;
2332		}
2333		if (argument.delayOut) {
2334			options.delay["hide"] = argument.delayOut;
2335		}
2336		if (argument.html) {
2337			options.html = true;
2338		}
2339		if (argument.fallback) {
2340			options.title = argument.fallback;
2341		}
2342		// destroy old tooltip in case the title has changed
2343		jQuery.fn.tooltip.call(this, 'destroy');
2344		jQuery.fn.tooltip.call(this, options);
2345	} else {
2346		this.tooltip(argument);
2347		jQuery.fn.tooltip.call(this, argument);
2348	}
2349	return this;
2350}

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.