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('&').split('<').join('<').split('>').join('>').split('"').join('"').split('\'').join('''); 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¶m2=abcde¶m3=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.