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 groups: [], 332 }; 333 }, 334 335 /** 336 * get the absolute path to an image file 337 * if no extension is given for the image, it will automatically decide 338 * between .png and .svg based on what the browser supports 339 * @param {string} app the app id to which the image belongs 340 * @param {string} file the name of the image file 341 * @return {string} 342 */ 343 imagePath: function (app, file) { 344 if (file.indexOf('.') == -1) {//if no extension is given, use svg 345 file += '.svg'; 346 } 347 return OC.filePath(app, 'img', file); 348 }, 349 350 /** 351 * URI-Encodes a file path but keep the path slashes. 352 * 353 * @param path path 354 * @return encoded path 355 */ 356 encodePath: function (path) { 357 if (!path) { 358 return path; 359 } 360 var parts = path.split('/'); 361 var result = []; 362 for (var i = 0; i < parts.length; i++) { 363 result.push(encodeURIComponent(parts[i])); 364 } 365 return result.join('/'); 366 }, 367 368 /** 369 * Load a script for the server and load it. If the script is already loaded, 370 * the event handler will be called directly 371 * @param {string} app the app id to which the script belongs 372 * @param {string} script the filename of the script 373 * @param ready event handler to be called when the script is loaded 374 */ 375 addScript: function (app, script, ready) { 376 var deferred, path = OC.filePath(app, 'js', script + '.js'); 377 if (!OC.addScript.loaded[path]) { 378 if (ready) { 379 deferred = $.getScript(path, ready); 380 } else { 381 deferred = $.getScript(path); 382 } 383 OC.addScript.loaded[path] = deferred; 384 } else { 385 if (ready) { 386 ready(); 387 } 388 } 389 return OC.addScript.loaded[path]; 390 }, 391 /** 392 * Loads a CSS file 393 * @param {string} app the app id to which the css style belongs 394 * @param {string} style the filename of the css file 395 */ 396 addStyle: function (app, style) { 397 var path = OC.filePath(app, 'css', style + '.css'); 398 if (OC.addStyle.loaded.indexOf(path) === -1) { 399 OC.addStyle.loaded.push(path); 400 if (document.createStyleSheet) { 401 document.createStyleSheet(path); 402 } else { 403 style = $('<link rel="stylesheet" type="text/css" href="' + path + '"/>'); 404 $('head').append(style); 405 } 406 } 407 }, 408 409 /** 410 * Loads translations for the given app asynchronously. 411 * 412 * @param {String} app app name 413 * @param {Function} callback callback to call after loading 414 * @return {Promise} 415 */ 416 addTranslations: function (app, callback) { 417 return OC.L10N.load(app, callback); 418 }, 419 420 /** 421 * Returns the base name of the given path. 422 * For example for "/abc/somefile.txt" it will return "somefile.txt" 423 * 424 * @param {String} path 425 * @return {String} base name 426 */ 427 basename: function (path) { 428 return path.replace(/\\/g, '/').replace(/.*\//, ''); 429 }, 430 431 /** 432 * Returns true if the email regexp matches the email address else false returned 433 * For example if email is "[email protected]", it will return true. 434 * If email address is "[email protected]", then false will be returned. 435 * 436 * @param emailAddress 437 * @returns {boolean} 438 * 439 * @since 10.1.0 440 */ 441 validateEmail: function(emailAddress) { 442 var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@\.]{2,}$/; 443 return (emailRegex.exec(emailAddress) !== null); 444 }, 445 446 /** 447 * Returns the dir name of the given path. 448 * For example for "/abc/somefile.txt" it will return "/abc" 449 * 450 * @param {String} path 451 * @return {String} dir name 452 */ 453 dirname: function (path) { 454 return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, ''); 455 }, 456 457 /** 458 * Returns whether the given paths are the same, without 459 * leading, trailing or doubled slashes and also removing 460 * the dot sections. 461 * 462 * @param {String} path1 first path 463 * @param {String} path2 second path 464 * @return {bool} true if the paths are the same 465 * 466 * @since 9.0 467 */ 468 isSamePath: function (path1, path2) { 469 var filterDot = function (p) { 470 return p !== '.'; 471 }; 472 var pathSections1 = _.filter((path1 || '').split('/'), filterDot); 473 var pathSections2 = _.filter((path2 || '').split('/'), filterDot); 474 path1 = OC.joinPaths.apply(OC, pathSections1); 475 path2 = OC.joinPaths.apply(OC, pathSections2); 476 return path1 === path2; 477 }, 478 479 /** 480 * Join path sections 481 * 482 * @param {...String} path sections 483 * 484 * @return {String} joined path, any leading or trailing slash 485 * will be kept 486 * 487 * @since 8.2 488 */ 489 joinPaths: function () { 490 if (arguments.length < 1) { 491 return ''; 492 } 493 var path = ''; 494 // convert to array 495 var args = Array.prototype.slice.call(arguments); 496 // discard empty arguments 497 args = _.filter(args, function (arg) { 498 return arg.length > 0; 499 }); 500 if (args.length < 1) { 501 return ''; 502 } 503 504 var lastArg = args[args.length - 1]; 505 var leadingSlash = args[0].charAt(0) === '/'; 506 var trailingSlash = lastArg.charAt(lastArg.length - 1) === '/'; 507 var sections = []; 508 var i; 509 for (i = 0; i < args.length; i++) { 510 sections = sections.concat(args[i].split('/')); 511 } 512 var first = !leadingSlash; 513 for (i = 0; i < sections.length; i++) { 514 if (sections[i] !== '') { 515 if (first) { 516 first = false;
517 } else { 518 path += '/'; 519 } 520 path += sections[i]; 521 } 522 } 523 524 if (trailingSlash) { 525 // add it back 526 path += '/'; 527 } 528 return path; 529 }, 530 531 /** 532 * Do a search query and display the results 533 * @param {string} query the search query 534 */ 535 search: function (query) { 536 OC.Search.search(query, null, 0, 30); 537 }, 538 /** 539 * Dialog helper for jquery dialogs. 540 * 541 * @namespace OC.dialogs 542 */ 543 dialogs: OCdialogs, 544 /** 545 * Parses a URL query string into a JS map 546 * @param {string} queryString query string in the format param1=1234¶m2=abcde¶m3=xyz 547 * @return {Object.<string, string>} map containing key/values matching the URL parameters 548 */ 549 parseQueryString: function (queryString) { 550 var parts, 551 pos, 552 components, 553 result = {}, 554 key, 555 value; 556 if (!queryString) { 557 return null; 558 } 559 pos = queryString.indexOf('?'); 560 if (pos >= 0) { 561 queryString = queryString.substr(pos + 1); 562 } 563 parts = queryString.replace(/\+/g, '%20').split('&'); 564 for (var i = 0; i < parts.length; i++) { 565 // split on first equal sign 566 var part = parts[i]; 567 pos = part.indexOf('='); 568 if (pos >= 0) { 569 components = [ 570 part.substr(0, pos), 571 part.substr(pos + 1) 572 ]; 573 } 574 else { 575 // key only 576 components = [part]; 577 } 578 if (!components.length) { 579 continue; 580 } 581 key = decodeURIComponent(components[0]); 582 if (!key) { 583 continue; 584 } 585 // if equal sign was there, return string 586 if (components.length > 1) { 587 result[key] = decodeURIComponent(components[1]); 588 } 589 // no equal sign => null value 590 else { 591 result[key] = null; 592 } 593 } 594 return result; 595 }, 596 597 /** 598 * Builds a URL query from a JS map. 599 * @param {Object.<string, string>} params map containing key/values matching the URL parameters 600 * @return {string} String containing a URL query (without question) mark 601 */ 602 buildQueryString: function (params) { 603 if (!params) { 604 return ''; 605 } 606 return $.map(params, function (value, key) { 607 var s = encodeURIComponent(key); 608 if (value !== null && typeof(value) !== 'undefined') { 609 s += '=' + encodeURIComponent(value); 610 } 611 return s; 612 }).join('&'); 613 }, 614 615 /** 616 * Opens a popup with the setting for an app. 617 * @param {string} appid The ID of the app e.g. 'calendar', 'contacts' or 'files'. 618 * @param {boolean|string}Â loadJS If true 'js/settings.js' is loaded. If it's a string 619 * it will attempt to load a script by that name in the 'js' directory. 620 * @param {boolean} [cache] If true the javascript file won't be forced refreshed. Defaults to true. 621 * @param {string} [scriptName] The name of the PHP file to load. Defaults to 'settings.php' in 622 * the root of the app directory hierarchy. 623 */ 624 appSettings: function (args) { 625 if (typeof args === 'undefined' || typeof args.appid === 'undefined') { 626 throw {name: 'MissingParameter', message: 'The parameter appid is missing'}; 627 } 628 var props = {scriptName: 'settings.php', cache: true}; 629 $.extend(props, args); 630 var settings = $('#appsettings'); 631 if (settings.length === 0) { 632 throw { 633 name: 'MissingDOMElement', 634 message: 'There has be be an element with id "appsettings" for the popup to show.' 635 }; 636 } 637 var popup = $('#appsettings_popup'); 638 if (popup.length === 0) { 639 $('body').prepend('<div class="popup hidden" id="appsettings_popup"></div>'); 640 popup = $('#appsettings_popup'); 641 popup.addClass(settings.hasClass('topright') ? 'topright' : 'bottomleft'); 642 } 643 if (popup.is(':visible')) { 644 popup.hide().remove(); 645 } else { 646 var arrowclass = settings.hasClass('topright') ? 'up' : 'left'; 647 var jqxhr = $.get(OC.filePath(props.appid, '', props.scriptName), function (data) { 648 popup.html(data).ready(function () { 649 popup.prepend('<span class="arrow ' + arrowclass + '"></span><h2>' + t('core', 'Settings') + '</h2><a class="close"></a>').show(); 650 popup.find('.close').bind('click', function () { 651 popup.remove(); 652 }); 653 if (typeof props.loadJS !== 'undefined') { 654 var scriptname; 655 if (props.loadJS === true) { 656 scriptname = 'settings.js'; 657 } else if (typeof props.loadJS === 'string') { 658 scriptname = props.loadJS; 659 } else { 660 throw { 661 name: 'InvalidParameter', 662 message: 'The "loadJS" parameter must be either boolean or a string.' 663 }; 664 } 665 if (props.cache) { 666 $.ajaxSetup({cache: true}); 667 } 668 $.getScript(OC.filePath(props.appid, 'js', scriptname)) 669 .fail(function (jqxhr, settings, e) { 670 throw e; 671 }); 672 } 673 }).show(); 674 }, 'html'); 675 } 676 }, 677 678 /** 679 * For menu toggling 680 * @todo Write documentation 681 */ 682 registerMenu: function ($toggle, $menuEl) { 683 var self = this; 684 $menuEl.addClass('menu'); 685 $toggle.on('click.menu', function (event) { 686 // prevent the link event (append anchor to URL) 687 event.preventDefault(); 688 689 if ($menuEl.is(OC._currentMenu)) { 690 self.hideMenus(); 691 return; 692 } 693 // another menu was open? 694 else if (OC._currentMenu) { 695 // close it 696 self.hideMenus(); 697 } 698 $menuEl.slideToggle(OC.menuSpeed); 699 OC._currentMenu = $menuEl; 700 OC._currentMenuToggle = $toggle; 701 }); 702 }, 703 704 /** 705 * @todo Write documentation 706 */ 707 unregisterMenu: function ($toggle, $menuEl) { 708 // close menu if opened 709 if ($menuEl.is(OC._currentMenu)) { 710 this.hideMenus(); 711 } 712 $toggle.off('click.menu').removeClass('menutoggle'); 713 $menuEl.removeClass('menu'); 714 }, 715 716 /** 717 * Hides any open menus 718 * 719 * @param {Function} complete callback when the hiding animation is done 720 */ 721 hideMenus: function (complete) { 722 if (OC._currentMenu) { 723 var lastMenu = OC._currentMenu; 724 OC._currentMenu.trigger(new $.Event('beforeHide')); 725 OC._currentMenu.slideUp(OC.menuSpeed, function () { 726 lastMenu.trigger(new $.Event('afterHide')); 727 if (complete) { 728 complete.apply(this, arguments); 729 } 730 }); 731 } 732 OC._currentMenu = null; 733 OC._currentMenuToggle = null; 734 }, 735 736 /** 737 * Shows a given element as menu 738 * 739 * @param {Object} [$toggle=null] menu toggle 740 * @param {Object} $menuEl menu element 741 * @param {Function} complete callback when the showing animation is done 742 */ 743 showMenu: function ($toggle, $menuEl, complete) { 744 if ($menuEl.is(OC._currentMenu)) { 745 return; 746 } 747 this.hideMenus(); 748 OC._currentMenu = $menuEl; 749 OC._currentMenuToggle = $toggle; 750 $menuEl.trigger(new $.Event('beforeShow')); 751 $menuEl.show(); 752 $menuEl.trigger(new $.Event('afterShow')); 753 // no animation 754 if (_.isFunction(complete)) { 755 complete(); 756 } 757 }, 758 759 /** 760 * Wrapper for matchMedia 761 * 762 * This is makes it possible for unit tests to 763 * stub matchMedia (which doesn't work in PhantomJS) 764 * @private 765 */ 766 _matchMedia: function (media) { 767 if (window.matchMedia) { 768 return window.matchMedia(media); 769 } 770 return false;
771 }, 772 773 /** 774 * Returns the user's locale 775 * 776 * @return {String} locale string 777 */ 778 getLocale: function () { 779 return $('html').prop('lang'); 780 }, 781 782 /** 783 * Returns whether the current user is an administrator 784 * 785 * @return {bool} true if the user is an admin, false otherwise 786 * @since 9.0.0 787 */ 788 isUserAdmin: function () { 789 return oc_isadmin; 790 }, 791 792 /** 793 * Process ajax error, redirects to main page 794 * if an error/auth error status was returned. 795 */ 796 _processAjaxError: function (xhr) { 797 var self = this; 798 // purposefully aborted request ? 799 // this._userIsNavigatingAway needed to distinguish ajax calls cancelled by navigating away 800 // from calls cancelled by failed cross-domain ajax due to SSO redirect 801 if (xhr.status === 0 && (xhr.statusText === 'abort' || xhr.statusText === 'timeout' || self._reloadCalled)) { 802 return; 803 } 804 805 if (_.contains([0, 302, 303, 307, 401], xhr.status)) { 806 // sometimes "beforeunload" happens later, so need to defer the reload a bit 807 setTimeout(function () { 808 if (!self._userIsNavigatingAway && !self._reloadCalled) { 809 OC.Notification.show(t('core', 'Problem loading page, reloading in 5 seconds')); 810 setTimeout(OC.reload, 5000); 811 // only call reload once 812 self._reloadCalled = true; 813 } 814 }, 100); 815 } 816 }, 817 818 /** 819 * Registers XmlHttpRequest object for global error processing. 820 * 821 * This means that if this XHR object returns 401 or session timeout errors, 822 * the current page will automatically be reloaded. 823 * 824 * @param {XMLHttpRequest} xhr 825 */ 826 registerXHRForErrorProcessing: function (xhr) { 827 var loadCallback = function () { 828 if (xhr.readyState !== 4) { 829 return; 830 } 831 832 if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { 833 return; 834 } 835 836 // fire jquery global ajax error handler 837 $(document).trigger(new $.Event('ajaxError'), xhr); 838 }; 839 840 var errorCallback = function () { 841 // fire jquery global ajax error handler 842 $(document).trigger(new $.Event('ajaxError'), xhr); 843 }; 844 845 if (xhr.addEventListener) { 846 xhr.addEventListener('load', loadCallback); 847 xhr.addEventListener('error', errorCallback); 848 } 849 850 } 851}; 852 853/** 854 * Current user attributes 855 * 856 * @typedef {Object} OC.CurrentUser 857 * 858 * @property {String} uid user id 859 * @property {String} displayName display name 860 * @property {Array} groups users group ids 861 */ 862 863/** 864 * @namespace OC.Plugins 865 */ 866OC.Plugins = { 867 /** 868 * @type Array.<OC.Plugin> 869 */ 870 _plugins: {}, 871 872 /** 873 * Register plugin 874 * 875 * @param {String} targetName app name / class name to hook into 876 * @param {OC.Plugin} plugin 877 * @param {String} priority priority of the plugin. sortPluginsByPriority() 878 * can be used to sort given plugins by their priority. 879 */ 880 register: function (targetName, plugin, priority) { 881 if (!priority) { 882 priority = 50; 883 } 884 var plugins = this._plugins[targetName]; 885 if (!plugins) { 886 plugins = this._plugins[targetName] = []; 887 } 888 plugins.push({plugin: plugin, priority: priority}); 889 }, 890 891 /** 892 * Returns all plugin registered to the given target 893 * name / app name / class name. 894 * 895 * @param {String} targetName app name / class name to hook into 896 * @return {Array.<OC.Plugin>} array of plugins 897 */ 898 getPlugins: function (targetName) { 899 return this._plugins[targetName] || []; 900 }, 901 902 /** 903 * Sort a list of given plugins by their priority. 904 * A higher priority means the plugin is listed before others. 905 * 906 * @param {Array.<OC.Plugin>} plugins 907 */ 908 sortPluginsByPriority: function (plugins) { 909 return plugins.sort(function(a, b) { 910 if (a.priority > b.priority) { 911 return -1; 912 }
913 if (a.priority < b.priority) { 914 return 1; 915 } 916 return 0; 917 }); 918 }, 919 920 /** 921 * Call attach() on all plugins registered to the given target name. 922 * 923 * @param {String} targetName app name / class name 924 * @param {Object} object to be extended 925 * @param {Object} [options] options 926 */ 927 attach: function (targetName, targetObject, options) { 928 var plugins = this.getPlugins(targetName); 929 plugins = this.sortPluginsByPriority(plugins); 930 for (var i = 0; i < plugins.length; i++) { 931 var plugin = plugins[i].plugin; 932 if (plugin.attach) { 933 plugin.attach(targetObject, options); 934 } 935 } 936 }, 937 938 /** 939 * Call detach() on all plugins registered to the given target name. 940 * 941 * @param {String} targetName app name / class name 942 * @param {Object} object to be extended 943 * @param {Object} [options] options 944 */ 945 detach: function (targetName, targetObject, options) { 946 var plugins = this.getPlugins(targetName); 947 plugins = this.sortPluginsByPriority(plugins); 948 for (var i = 0; i < plugins.length; i++) { 949 var plugin = plugins[i].plugin; 950 if (plugin.detach) { 951 plugin.detach(targetObject, options); 952 } 953 } 954 }, 955 956 /** 957 * Plugin 958 * 959 * @todo make this a real class in the future 960 * @typedef {Object} OC.Plugin 961 * 962 * @property {String} name plugin name 963 * @property {Function} attach function that will be called when the 964 * plugin is attached 965 * @property {Function} [detach] function that will be called when the 966 * plugin is detached 967 */ 968 969}; 970 971/** 972 * @namespace OC.search 973 */ 974OC.search.customResults = {}; 975/** 976 * @deprecated use get/setFormatter() instead 977 */ 978OC.search.resultTypes = {}; 979 980OC.addStyle.loaded = []; 981OC.addScript.loaded = []; 982 983/** 984 * A little class to manage a status field for a "saving" process. 985 * It can be used to display a starting message (e.g. "Saving...") and then 986 * replace it with a green success message or a red error message. 987 * 988 * @namespace OC.msg 989 */ 990OC.msg = { 991 /** 992 * Displayes a "Saving..." message in the given message placeholder 993 * 994 * @param {Object} selector Placeholder to display the message in 995 */ 996 startSaving: function (selector) { 997 this.startAction(selector, t('core', 'Saving...')); 998 }, 999 1000 /** 1001 * Displayes a custom message in the given message placeholder 1002 * 1003 * @param {Object} selector Placeholder to display the message in 1004 * @param {string} message Plain text message to display (no HTML allowed) 1005 */ 1006 startAction: function (selector, message) { 1007 $(selector).text(message) 1008 .removeClass('success') 1009 .removeClass('error') 1010 .stop(true, true) 1011 .show(); 1012 }, 1013 1014 /** 1015 * Displayes an success/error message in the given selector 1016 * 1017 * @param {Object} selector Placeholder to display the message in 1018 * @param {Object} response Response of the server 1019 * @param {Object} response.data Data of the servers response 1020 * @param {string} response.data.message Plain text message to display (no HTML allowed) 1021 * @param {string} response.status is being used to decide whether the message 1022 * is displayed as an error/success 1023 */ 1024 finishedSaving: function (selector, response) { 1025 this.finishedAction(selector, response); 1026 }, 1027 1028 /** 1029 * Displayes an success/error message in the given selector 1030 * 1031 * @param {Object} selector Placeholder to display the message in 1032 * @param {Object} response Response of the server 1033 * @param {Object} response.data Data of the servers response 1034 * @param {string} response.data.message Plain text message to display (no HTML allowed) 1035 * @param {string} response.status is being used to decide whether the message 1036 * is displayed as an error/success 1037 */ 1038 finishedAction: function (selector, response) { 1039 if (response.status === "success") { 1040 this.finishedSuccess(selector, response.data.message); 1041 } else { 1042 this.finishedError(selector, response.data.message); 1043 } 1044 }, 1045 1046 /** 1047 * Displayes an success message in the given selector 1048 * 1049 * @param {Object} selector Placeholder to display the message in 1050 * @param {string} message Plain text success message to display (no HTML allowed) 1051 */ 1052 finishedSuccess: function (selector, message) { 1053 $(selector).text(message) 1054 .addClass('success') 1055 .removeClass('error') 1056 .stop(true, true) 1057 .delay(3000) 1058 .fadeOut(900) 1059 .show(); 1060 }, 1061 1062 /** 1063 * Displayes an error message in the given selector 1064 * 1065 * @param {Object} selector Placeholder to display the message in 1066 * @param {string} message Plain text error message to display (no HTML allowed) 1067 */ 1068 finishedError: function (selector, message) { 1069 $(selector).text(message) 1070 .addClass('error') 1071 .removeClass('success') 1072 .show(); 1073 } 1074}; 1075 1076/** 1077 * @todo Write documentation 1078 * @namespace 1079 */ 1080OC.Notification = { 1081 queuedNotifications: [], 1082 getDefaultNotificationFunction: null, 1083 1084 /** 1085 * @type Array.<int> array of notification timers 1086 */ 1087 notificationTimers: [], 1088 1089 /** 1090 * @param callback 1091 * @todo Write documentation 1092 */
1093 setDefault: function (callback) { 1094 OC.Notification.getDefaultNotificationFunction = callback; 1095 }, 1096 1097 /** 1098 * Hides a notification. 1099 * 1100 * If a row is given, only hide that one. 1101 * If no row is given, hide all notifications. 1102 * 1103 * @param {jQuery} [$row] notification row 1104 * @param {Function} [callback] callback 1105 */ 1106 hide: function ($row, callback) { 1107 var self = this; 1108 var $notification = $('#notification'); 1109 1110 if (_.isFunction($row)) { 1111 // first arg is the callback 1112 callback = $row; 1113 $row = undefined; 1114 } 1115 1116 if (!$row) { 1117 console.warn('Missing argument $row in OC.Notification.hide() call, caller needs to be adjusted to only dismiss its own notification'); 1118 // assume that the row to be hidden is the first one 1119 $row = $notification.find('.row:first'); 1120 } 1121 1122 if ($row && $notification.find('.row').length > 1) { 1123 // remove the row directly 1124 $row.remove(); 1125 if (callback) { 1126 callback.call(); 1127 } 1128 return; 1129 } 1130 1131 _.defer(function () { 1132 // fade out is supposed to only fade when there is a single row 1133 // however, some code might call hide() and show() directly after, 1134 // which results in more than one element 1135 // in this case, simply delete that one element that was supposed to 1136 // fade out 1137 // 1138 // FIXME: remove once all callers are adjusted to only hide their own notifications 1139 if ($notification.find('.row').length > 1) { 1140 $row.remove(); 1141 return; 1142 } 1143 1144 // else, fade out whatever was present 1145 $notification.fadeOut('400', function () { 1146 if (self.isHidden()) { 1147 if (self.getDefaultNotificationFunction) { 1148 self.getDefaultNotificationFunction.call(); 1149 } 1150 } 1151 if (callback) { 1152 callback.call(); 1153 } 1154 $notification.empty(); 1155 }); 1156 }); 1157 }, 1158 1159 /** 1160 * Shows a notification as HTML without being sanitized before. 1161 * If you pass unsanitized user input this may lead to a XSS vulnerability. 1162 * Consider using show() instead of showHTML() 1163 * 1164 * @param {string} html Message to display 1165 * @param {Object} [options] options 1166 * @param {string] [options.type] notification type 1167 * @param {int} [options.timeout=0] timeout value, defaults to 0 (permanent) 1168 * @return {jQuery} jQuery element for notification row 1169 */ 1170 showHtml: function (html, options) { 1171 options = options || {}; 1172 _.defaults(options, { 1173 timeout: 0 1174 }); 1175 1176 var self = this; 1177 var $notification = $('#notification'); 1178 if (this.isHidden()) { 1179 $notification.fadeIn().css('display', 'inline-block'); 1180 } 1181 var $row = $('<div class="row"></div>'); 1182 if (options.type) { 1183 $row.addClass('type-' + options.type); 1184 } 1185 if (options.type === 'error') { 1186 // add a close button 1187 var $closeButton = $('<a class="action close icon-close" href="#"></a>'); 1188 $closeButton.attr('alt', t('core', 'Dismiss')); 1189 $row.append($closeButton); 1190 $closeButton.one('click', function () { 1191 self.hide($row); 1192 return false; 1193 }); 1194 $row.addClass('closeable'); 1195 } 1196 1197 $row.prepend(html); 1198 $notification.append($row); 1199 1200 if (options.timeout > 0) { 1201 // register timeout to vanish notification 1202 this.notificationTimers.push(setTimeout(function () { 1203 self.hide($row); 1204 }, (options.timeout * 1000))); 1205 } 1206 1207 return $row; 1208 }, 1209 1210 /** 1211 * Shows a sanitized notification 1212 * 1213 * @param {string} text Message to display 1214 * @param {Object} [options] options 1215 * @param {string} [options.type] notification type 1216 * @param {int} [options.timeout=0] timeout value, defaults to 0 (permanent) 1217 * @return {jQuery} jQuery element for notification row 1218 */ 1219 show: function (text, options) { 1220 return this.showHtml($('<div/>').text(text).html(), options); 1221 }, 1222 1223 /** 1224 * Shows a notification that disappears after x seconds, default is 1225 * 7 seconds 1226 * 1227 * @param {string} text Message to show 1228 * @param {array} [options] options array 1229 * @param {int} [options.timeout=7] timeout in seconds, if this is 0 it will show the message permanently 1230 * @param {boolean} [options.isHTML=false] an indicator for HTML notifications (true) or text (false) 1231 * @param {string] [options.type] notification type 1232 */ 1233 showTemporary: function (text, options) { 1234 var self = this; 1235 var defaults = {
1236 isHTML: false, 1237 timeout: 7 1238 }; 1239 options = options || {}; 1240 // merge defaults with passed in options 1241 _.defaults(options, defaults); 1242 1243 var $row; 1244 if (options.isHTML) { 1245 $row = this.showHtml(text, options); 1246 } else { 1247 $row = this.show(text, options); 1248 } 1249 return $row; 1250 }, 1251 1252 /** 1253 * Returns whether a notification is hidden. 1254 * @return {boolean} 1255 */ 1256 isHidden: function () { 1257 return !$("#notification").find('.row').length; 1258 } 1259}; 1260 1261/** 1262 * Breadcrumb class 1263 * 1264 * @namespace 1265 * 1266 * @deprecated will be replaced by the breadcrumb implementation 1267 * of the files app in the future 1268 */ 1269OC.Breadcrumb = { 1270 container: null, 1271 /** 1272 * @todo Write documentation 1273 * @param dir 1274 * @param leafName 1275 * @param leafLink 1276 */ 1277 show: function (dir, leafName, leafLink) { 1278 if (!this.container) {//default 1279 this.container = $('#controls'); 1280 } 1281 this._show(this.container, dir, leafName, leafLink); 1282 }, 1283 _show: function (container, dir, leafname, leaflink) { 1284 var self = this; 1285 1286 this._clear(container); 1287 1288 // show home + path in subdirectories 1289 if (dir) { 1290 //add home 1291 var link = OC.linkTo('files', 'index.php'); 1292 1293 var crumb = $('<div/>'); 1294 crumb.addClass('crumb'); 1295 1296 var crumbLink = $('<a/>'); 1297 crumbLink.attr('href', link); 1298 1299 var crumbImg = $('<img/>'); 1300 crumbImg.attr('src', OC.imagePath('core', 'places/home')); 1301 crumbLink.append(crumbImg); 1302 crumb.append(crumbLink); 1303 container.prepend(crumb); 1304 1305 //add path parts 1306 var segments = dir.split('/'); 1307 var pathurl = ''; 1308 jQuery.each(segments, function (i, name) { 1309 if (name !== '') { 1310 pathurl = pathurl + '/' + name; 1311 var link = OC.linkTo('files', 'index.php') + '?dir=' + encodeURIComponent(pathurl); 1312 self._push(container, name, link); 1313 } 1314 }); 1315 } 1316 1317 //add leafname 1318 if (leafname && leaflink) { 1319 this._push(container, leafname, leaflink); 1320 } 1321 }, 1322 1323 /** 1324 * @todo Write documentation 1325 * @param {string} name 1326 * @param {string} link 1327 */ 1328 push: function (name, link) { 1329 if (!this.container) {//default 1330 this.container = $('#controls'); 1331 } 1332 return this._push(OC.Breadcrumb.container, name, link); 1333 }, 1334 _push: function (container, name, link) { 1335 var crumb = $('<div/>'); 1336 crumb.addClass('crumb').addClass('last'); 1337 1338 var crumbLink = $('<a/>'); 1339 crumbLink.attr('href', link); 1340 crumbLink.text(name); 1341 crumb.append(crumbLink); 1342 1343 var existing = container.find('div.crumb'); 1344 if (existing.length) { 1345 existing.removeClass('last'); 1346 existing.last().after(crumb); 1347 } else { 1348 container.prepend(crumb); 1349 } 1350 return crumb; 1351 }, 1352 1353 /** 1354 * @todo Write documentation 1355 */ 1356 pop: function () { 1357 if (!this.container) {//default 1358 this.container = $('#controls'); 1359 } 1360 this.container.find('div.crumb').last().remove(); 1361 this.container.find('div.crumb').last().addClass('last'); 1362 }, 1363 1364 /** 1365 * @todo Write documentation 1366 */ 1367 clear: function () { 1368 if (!this.container) {//default 1369 this.container = $('#controls'); 1370 } 1371 this._clear(this.container); 1372 }, 1373 _clear: function (container) { 1374 container.find('div.crumb').remove(); 1375 } 1376}; 1377 1378if (typeof localStorage !== 'undefined' && localStorage !== null) { 1379 /** 1380 * User and instance aware localstorage 1381 * @namespace 1382 */ 1383 OC.localStorage = { 1384 namespace: 'oc_' + OC.currentUser + '_' + OC.webroot + '_', 1385 1386 /** 1387 * Whether the storage contains items 1388 * @param {string} name 1389 * @return {boolean} 1390 */ 1391 hasItem: function (name) { 1392 return OC.localStorage.getItem(name) !== null; 1393 }, 1394 1395 /** 1396 * Add an item to the storage 1397 * @param {string} name 1398 * @param {string} item 1399 */ 1400 setItem: function (name, item) { 1401 return localStorage.setItem(OC.localStorage.namespace + name, JSON.stringify(item)); 1402 }, 1403 1404 /** 1405 * Removes an item from the storage 1406 * @param {string} name 1407 * @param {string} item 1408 */ 1409 removeItem: function (name, item) { 1410 return localStorage.removeItem(OC.localStorage.namespace + name); 1411 }, 1412 1413 /** 1414 * Get an item from the storage 1415 * @param {string} name 1416 * @return {null|string} 1417 */ 1418 getItem: function (name) { 1419 var item = localStorage.getItem(OC.localStorage.namespace + name); 1420 if (item === null) { 1421 return null; 1422 } else { 1423 return JSON.parse(item); 1424 } 1425 } 1426 }; 1427} else { 1428 //dummy localstorage 1429 OC.localStorage = { 1430 hasItem: function () { 1431 return false;
1432 }, 1433 setItem: function () { 1434 return false; 1435 }, 1436 getItem: function () { 1437 return null; 1438 } 1439 }; 1440} 1441 1442/** 1443 * prototypical inheritance functions 1444 * @todo Write documentation 1445 * usage: 1446 * MySubObject=object(MyObject) 1447 */ 1448function object(o) { 1449 function F() { 1450 } 1451 1452 F.prototype = o; 1453 return new F(); 1454} 1455 1456/** 1457 * Initializes core 1458 */ 1459function initCore() { 1460 /** 1461 * Disable automatic evaluation of responses for $.ajax() functions (and its 1462 * higher-level alternatives like $.get() and $.post()). 1463 * 1464 * If a response to a $.ajax() request returns a content type of "application/javascript" 1465 * JQuery would previously execute the response body. This is a pretty unexpected 1466 * behaviour and can result in a bypass of our Content-Security-Policy as well as 1467 * multiple unexpected XSS vectors. 1468 */ 1469 $.ajaxSetup({ 1470 contents: { 1471 script: false 1472 } 1473 }); 1474 1475 $('#body-login form label').css({ 1476 color: $('p.info').css('color') 1477 }); 1478 1479 if ($('#body-login .v-align').length > 0 ) { 1480 $('#body-login .v-align').fadeIn(600); 1481 setTimeout(function () { 1482 $('#body-login footer *').addClass('show'); 1483 }, 250); 1484 } 1485 1486 /** 1487 * Set users locale to moment.js as soon as possible 1488 */ 1489 moment.locale(OC.getLocale()); 1490 1491 /* 1492 * Override browser locales with OC locale in ajax requests 1493 */ 1494 $.ajaxSetup({ 1495 beforeSend: function(xhr) { 1496 xhr.setRequestHeader('Accept-Language', OC.getLocale()); 1497 } 1498 }); 1499 1500 var userAgent = window.navigator.userAgent; 1501 var msie = userAgent.indexOf('MSIE '); 1502 var trident = userAgent.indexOf('Trident/'); 1503 var edge = userAgent.indexOf('Edge/'); 1504 1505 if (msie > 0 || trident > 0) { 1506 // (IE 10 or older) || IE 11 1507 $('html').addClass('ie'); 1508 } else if (edge > 0) { 1509 // for edge 1510 $('html').addClass('edge'); 1511 } 1512 1513 $(window).on('unload.main', function () { 1514 OC._unloadCalled = true; 1515 }); 1516 $(window).on('beforeunload.main', function () { 1517 // super-trick thanks to http://stackoverflow.com/a/4651049 1518 // in case another handler displays a confirmation dialog (ex: navigating away 1519 // during an upload), there are two possible outcomes: user clicked "ok" or 1520 // "cancel" 1521 1522 // first timeout handler is called after unload dialog is closed 1523 setTimeout(function () { 1524 OC._userIsNavigatingAway = true; 1525
1526 // second timeout event is only called if user cancelled (Chrome), 1527 // but in other browsers it might still be triggered, so need to 1528 // set a higher delay... 1529 setTimeout(function () { 1530 if (!OC._unloadCalled) { 1531 OC._userIsNavigatingAway = false; 1532 } 1533 }, 10000); 1534 }, 1); 1535 }); 1536 $(document).on('ajaxError.main', function (event, request, settings) { 1537 if (settings && settings.allowAuthErrors) { 1538 return; 1539 } 1540 OC._processAjaxError(request); 1541 }); 1542 1543 /** 1544 * Calls the server periodically to ensure that session doesn't 1545 * time out 1546 */ 1547 function initSessionHeartBeat() { 1548 // max interval in seconds set to 24 hours 1549 var maxInterval = 24 * 3600; 1550 // interval in seconds 1551 var interval = 900; 1552 if (oc_config.session_lifetime) { 1553 interval = Math.floor(oc_config.session_lifetime / 2); 1554 } 1555 // minimum one minute 1556 if (interval < 60) { 1557 interval = 60; 1558 } 1559 if (interval > maxInterval) { 1560 interval = maxInterval; 1561 } 1562 var url = OC.generateUrl('/heartbeat'); 1563 var heartBeatTimeout = null; 1564 var heartBeat = function () { 1565 clearInterval(heartBeatTimeout); 1566 heartBeatTimeout = setInterval(function () { 1567 $.post(url); 1568 }, interval * 1000); 1569 }; 1570 heartBeat(); 1571 } 1572 1573 /** 1574 * Check mouse movement to send a heartbeat if the mouse moved 1575 */ 1576 function initMouseTrack() { 1577 var interval = 120; // 2 minutes 1578 if (oc_config.session_lifetime) { 1579 interval = Math.floor(oc_config.session_lifetime / 2); 1580 } 1581 interval = Math.min(Math.max(interval, 60), 3600); // ensure interval is in [60, 3600] 1582 1583 var extendableHeartbeat = null; 1584 var extendableMouseMoved = false; 1585 var mouseMoved = false; 1586 var heartbeatTimestamp = 0; 1587 1588 // If the mouse has moved within this interval, set a timeout to send 1589 // a heartbeat request. If the mouse moves again in the next interval, 1590 // the timeout will be extended. Note that the heartbeat request might 1591 // be delayed indefinitely. 1592 setInterval(function() { 1593 if (extendableMouseMoved) { 1594 clearTimeout(extendableHeartbeat); 1595 extendableHeartbeat = setTimeout(function() { 1596 if (!extendableMouseMoved) { 1597 // if the mouse has moved, either this timeout has been cleared and 1598 // a new one is being created, or a new timeout will be created soon 1599 // (if this timeout is executed before the setInterval function) 1600 var currentTimestamp = Date.now(); 1601 if (currentTimestamp > heartbeatTimestamp + (30 * 1000)) { 1602 // if a heartbeat has been sent recently, don't send a new one 1603 console.log('I1 sending heartbeat'); 1604 $.post(OC.generateUrl('/heartbeat')); 1605 heartbeatTimestamp = currentTimestamp; 1606 } else { 1607 console.log('I1 skipping heartbeat'); 1608 } 1609 } else { 1610 console.log('I1 delaying hearbeat because mouse moved.'); 1611 } 1612 }, 30 * 1000); 1613 } 1614 extendableMouseMoved = false; 1615 }, 30 * 1000); 1616 1617 // If the mouse has been moved within this interval, send a heartbeat 1618 // immediately. This will prevent the session to expire. 1619 setInterval(function() { 1620 if (mouseMoved) { 1621 var currentTimestamp = Date.now(); 1622 if (currentTimestamp > heartbeatTimestamp + (interval * 1000)) { 1623 // if a heartbeat has been sent recently, don't send a new one 1624 console.log('I2 sending heartbeat'); 1625 $.post(OC.generateUrl('/heartbeat')); 1626 heartbeatTimestamp = currentTimestamp; 1627 } else { 1628 console.log('I2 skipping heartbeat'); 1629 } 1630 } 1631 mouseMoved = false; // no need to wait for the response 1632 }, interval * 1000); 1633 1634 // set a couple of variables to indicate that the mouse has moved 1635 $(window).on('mousemove.sessiontrack', function() { 1636 mouseMoved = true; 1637 extendableMouseMoved = true; 1638 }); 1639 } 1640 1641 // session heartbeat (defaults to enabled) 1642 if (typeof(oc_config.session_keepalive) === 'undefined' || !!oc_config.session_keepalive) { 1643 1644 initSessionHeartBeat(); 1645 } else { 1646 initMouseTrack(); 1647 } 1648 1649 if (oc_config.session_forced_logout_timeout !== undefined && oc_config.session_forced_logout_timeout > 0) { 1650 $(window).on('beforeunload.sessiontrack', function() { 1651 var requestData = { 1652 method: 'POST', 1653 headers: { 1654 'Content-Type': 'application/json', 1655 'X-Requested-With': 'XMLHttpRequest' 1656 }, 1657 body: JSON.stringify({t: oc_config.session_forced_logout_timeout}), 1658 credentials: 'same-origin', 1659 keepalive: true 1660 }; 1661 var r = undefined; 1662 if (typeof Request !== "undefined") { 1663 // IE 11 doesn't have support "Request", so we'll fallback to ajax
1664 r = new Request(OC.generateUrl('/heartbeat'), requestData); 1665 } 1666 if (r === undefined || r.keepalive === undefined) { 1667 // firefox doesn't support keepalive (checked 11th Apr 2022) 1668 // try a sync ajax call instead 1669 $.ajax({ 1670 method: 'POST', 1671 url: OC.generateUrl('/heartbeat'), 1672 data: {t: oc_config.session_forced_logout_timeout}, 1673 async: false 1674 }); 1675 } else { 1676 fetch(r); 1677 } 1678 }); 1679 } 1680 1681 OC.registerMenu($('#expand'), $('#expanddiv')); 1682 1683 /** 1684 * This event gets fired, if your focused element is for example the navbar 1685 * and you click on an iframe. 1686 * So we can close the open menus, for example the #settings menu 1687 */ 1688 $(window).on('blur.closemenus',function(){ 1689 OC.hideMenus(); 1690 }); 1691 1692 // toggle for menus 1693 $(document).on('mouseup.closemenus', function (event) { 1694 var $el = $(event.target); 1695 if ($el.closest('.menu').length || $el.closest('.menutoggle').length) { 1696 // don't close when clicking on the menu directly or a menu toggle 1697 return false; 1698 } 1699 1700 OC.hideMenus(); 1701 }); 1702 1703 /** 1704 * Set up the main menu toggle to react to media query changes. 1705 * If the screen is small enough, the main menu becomes a toggle. 1706 * If the screen is bigger, the main menu is not a toggle any more. 1707 */ 1708 function setupMainMenu() { 1709 // toggle the navigation 1710 var $toggle = $('#header .header-appname-container'); 1711 var $navigation = $('#navigation'); 1712 1713 // init the menu 1714 OC.registerMenu($toggle, $navigation); 1715 $toggle.data('oldhref', $toggle.attr('href')); 1716 $toggle.attr('href', '#'); 1717 $navigation.hide(); 1718 1719 // show loading feedback 1720 $navigation.delegate('a', 'click', function (event) { 1721 var $app = $(event.target); 1722 if (!$app.is('a')) { 1723 $app = $app.closest('a'); 1724 } 1725 if (!event.ctrlKey) { 1726 $app.addClass('app-loading'); 1727 } else { 1728 // Close navigation when opening app in 1729 // a new tab 1730 OC.hideMenus(); 1731 } 1732 }); 1733 } 1734 1735 function setupUserMenu() { 1736 var $menu = $('#header #settings'); 1737 1738 $menu.delegate('a', 'click', function (event) { 1739 var $page = $(event.target); 1740 if (!$page.is('a')) { 1741 $page = $page.closest('a'); 1742 } 1743 $page.find('img').remove(); 1744 $page.find('div').remove(); // prevent odd double-clicks 1745 $page.prepend($('<div/>').addClass('icon-loading-small-dark')); 1746 }); 1747 } 1748 1749 setupMainMenu(); 1750 setupUserMenu(); 1751 1752 // just add snapper for logged in users 1753 if ($('#app-navigation').length && !$('html').hasClass('lte9')) { 1754 1755 // App sidebar on mobile 1756 var snapper = new Snap({ 1757 element: document.getElementById('app-content'), 1758 disable: 'right', 1759 maxPosition: 250, 1760 minDragDistance: 100 1761 }); 1762 $('#app-content').prepend('<div id="app-navigation-toggle" class="icon-menu" style="display:none;"></div>'); 1763 $('#app-navigation-toggle').click(function () { 1764 if (snapper.state().state == 'left') { 1765 snapper.close(); 1766 } else { 1767 snapper.open('left'); 1768 } 1769 }); 1770 // close sidebar when switching navigation entry 1771 var $appNavigation = $('#app-navigation'); 1772 $appNavigation.delegate('a, :button', 'click', function (event) { 1773 var $target = $(event.target); 1774 // don't hide navigation when changing settings or adding things 1775 if ($target.is('.app-navigation-noclose') || 1776 $target.closest('.app-navigation-noclose').length) { 1777 return; 1778 } 1779 if ($target.is('.add-new') || 1780 $target.closest('.add-new').length) { 1781 return; 1782 } 1783 if ($target.is('#app-settings') || 1784 $target.closest('#app-settings').length) { 1785 return; 1786 } 1787 snapper.close(); 1788 }); 1789 1790 var toggleSnapperOnSize = function () { 1791 if ($(window).width() > 768) { 1792 snapper.close(); 1793 snapper.disable(); 1794 } else { 1795 snapper.enable(); 1796 } 1797 }; 1798 1799 $(window).resize(_.debounce(toggleSnapperOnSize, 250)); 1800 1801 // initial call
1802 toggleSnapperOnSize(); 1803 1804 // adjust controls bar width 1805 var adjustControlsWidth = function () { 1806 if ($('#controls').length) { 1807 var controlsWidth; 1808 // if there is a scrollbar ⦠1809 if ($('#app-content').get(0).scrollHeight > $('#app-content').height()) { 1810 if ($(window).width() > 768) { 1811 controlsWidth = $('#content').width() - $('#app-navigation').width() - getScrollBarWidth(); 1812 if (!$('#app-sidebar').hasClass('hidden') && !$('#app-sidebar').hasClass('disappear')) { 1813 controlsWidth -= $('#app-sidebar').width(); 1814 } 1815 } else { 1816 controlsWidth = $('#content').width() - getScrollBarWidth(); 1817 } 1818 } else { // if there is none 1819 if ($(window).width() > 768) { 1820 controlsWidth = $('#content').width() - $('#app-navigation').width(); 1821 if (!$('#app-sidebar').hasClass('hidden') && !$('#app-sidebar').hasClass('disappear')) { 1822 controlsWidth -= $('#app-sidebar').width(); 1823 } 1824 } else { 1825 controlsWidth = $('#content').width(); 1826 } 1827 } 1828 $('#controls').css('width', controlsWidth); 1829 $('#controls').css('min-width', controlsWidth); 1830 } 1831 }; 1832 1833 $(window).resize(_.debounce(adjustControlsWidth, 250)); 1834 1835 $('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth); 1836 1837 } 1838} 1839 1840/** 1841 * Disable the use of globalEval in jQuery 2.1.4. 1842 * This is required for API compatibility, yet should not be available all the 1843 * same. 1844 * 1845 * @see https://github.com/jquery/jquery/issues/2432 for further details. 1846 */ 1847$.fn.globalEval = function(){}; 1848 1849/** 1850 * Make htmlPrefilter an identity function in jQuery 2.1.4. 1851 * 1852 * @see https://github.com/advisories/GHSA-gxr4-xjj5-5px2 1853 */ 1854$.fn.htmlPrefilter = function(html) { 1855 return html; 1856}; 1857 1858$(document).ready(initCore); 1859 1860/** 1861 * Filter Jquery selector by attribute value 1862 */ 1863$.fn.filterAttr = function (attr_name, attr_value) { 1864 return this.filter(function () { 1865 return $(this).attr(attr_name) === attr_value; 1866 }); 1867}; 1868 1869/** 1870 * Returns a human readable file size 1871 * @param {number} size Size in bytes 1872 * @param {boolean} skipSmallSizes return '< 1 kB' for small files 1873 * @return {string} 1874 */ 1875function humanFileSize(size, skipSmallSizes) { 1876 var humanList = ['B', 'KB', 'MB', 'GB', 'TB']; 1877 // Calculate Log with base 1024: size = 1024 ** order 1878 var order = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0; 1879 // Stay in range of the byte sizes that are defined 1880 order = Math.min(humanList.length - 1, order); 1881 var readableFormat = humanList[order]; 1882 var relativeSize = (size / Math.pow(1024, order)).toFixed(1); 1883 if (skipSmallSizes === true && order === 0) { 1884 if (relativeSize !== "0.0") { 1885 return '< 1 KB'; 1886 } else { 1887 return '0 KB'; 1888 } 1889 } 1890 if (order < 2) { 1891 relativeSize = parseFloat(relativeSize).toFixed(0); 1892 } 1893 else if (relativeSize.substr(relativeSize.length - 2, 2) === '.0') { 1894 relativeSize = relativeSize.substr(0, relativeSize.length - 2); 1895 } 1896 return relativeSize + ' ' + readableFormat; 1897} 1898 1899/** 1900 * Format an UNIX timestamp to a human understandable format 1901 * @param {number} timestamp UNIX timestamp 1902 * @return {string} Human readable format 1903 */ 1904function formatDate(timestamp) { 1905 return OC.Util.formatDate(timestamp); 1906} 1907 1908// 1909/** 1910 * Get the value of a URL parameter 1911 * @link http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery 1912 * @param {string} name URL parameter 1913 * @return {string} 1914 */ 1915function getURLParameter(name) { 1916 return decodeURI( 1917 (RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1] 1918 ); 1919} 1920 1921/** 1922 * Takes an absolute timestamp and return a string with a human-friendly relative date 1923 * @param {number} timestamp A Unix timestamp 1924 */ 1925function relative_modified_date(timestamp) { 1926 /* 1927 Were multiplying by 1000 to bring the timestamp back to its original value 1928 per https://github.com/owncloud/core/pull/10647#discussion_r16790315 1929 */ 1930 return OC.Util.relativeModifiedDate(timestamp * 1000); 1931} 1932 1933/** 1934 * Utility functions 1935 * @namespace 1936 */ 1937OC.Util = { 1938 // TODO: remove original functions from global namespace 1939 humanFileSize: humanFileSize, 1940 1941 /** 1942 * regular expression to parse size in bytes from a humanly readable string 1943 * see computerFileSize(string) 1944 */ 1945 _computerFileSizeRegexp: /^[\s+]?([0-9]*)(\.([0-9]+))?( +)?([kmgtp]?b?)$/i, 1946 1947 /** 1948 * Returns a file size in bytes from a humanly readable string 1949 * Makes 2kB to 2048. 1950 * Inspired by computerFileSize in helper.php 1951 * @param {string} string file size in human readable format 1952 * @return {number} or null if string could not be parsed 1953 * 1954 * 1955 */ 1956 computerFileSize: function (string) { 1957 if (typeof string !== 'string') { 1958 return null; 1959 } 1960 1961 var s = string.toLowerCase().trim(); 1962 var bytes = null; 1963 1964 var bytesArray = { 1965 'b': 1, 1966 'k': 1024, 1967 'kb': 1024, 1968 'mb': 1024 * 1024, 1969 'm': 1024 * 1024, 1970 'gb': 1024 * 1024 * 1024, 1971 'g': 1024 * 1024 * 1024, 1972 'tb': 1024 * 1024 * 1024 * 1024, 1973 't': 1024 * 1024 * 1024 * 1024, 1974 'pb': 1024 * 1024 * 1024 * 1024 * 1024, 1975 'p': 1024 * 1024 * 1024 * 1024 * 1024 1976 }; 1977 1978 var matches = s.match(this._computerFileSizeRegexp); 1979 if (matches !== null) { 1980 bytes = parseFloat(s); 1981 if (!isFinite(bytes)) { 1982 return null; 1983 } 1984 } else { 1985 return null; 1986 } 1987 if (matches[5]) { 1988 bytes = bytes * bytesArray[matches[5]]; 1989 } 1990 1991 bytes = Math.round(bytes); 1992 return bytes; 1993 }, 1994 1995 /** 1996 * @param timestamp 1997 * @param format 1998 * @returns {string} timestamp formatted as requested 1999 */ 2000 formatDate: function (timestamp, format) { 2001 format = format || "LLL"; 2002 return moment(timestamp).format(format); 2003 }, 2004 2005 /** 2006 * @param timestamp 2007 * @returns {string} human readable difference from now 2008 */ 2009 relativeModifiedDate: function (timestamp) { 2010 var diff = moment().diff(moment(timestamp)); 2011 if (diff >= 0 && diff < 45000) { 2012 return t('core', 'seconds ago'); 2013 } 2014 return moment(timestamp).fromNow(); 2015 }, 2016 /** 2017 * Returns whether the browser supports SVG
2018 * @deprecated SVG is always supported (since 9.0) 2019 * @return {boolean} true if the browser supports SVG, false otherwise 2020 */ 2021 hasSVGSupport: function () { 2022 return true 2023 }, 2024 /** 2025 * If SVG is not supported, replaces the given icon's extension 2026 * from ".svg" to ".png". 2027 * If SVG is supported, return the image path as is. 2028 * @param {string} file image path with svg extension 2029 * @deprecated SVG is always supported (since 9.0) 2030 * @return {string} fixed image path with png extension if SVG is not supported 2031 */ 2032 replaceSVGIcon: function (file) { 2033 return file; 2034 }, 2035 /** 2036 * Replace SVG images in all elements that have the "svg" class set 2037 * with PNG images. 2038 * 2039 * @param $el root element from which to search, defaults to $('body') 2040 * @deprecated SVG is always supported (since 9.0) 2041 */ 2042 replaceSVG: function ($el) { 2043 }, 2044 2045 /** 2046 * Fix image scaling for IE8, since background-size is not supported. 2047 * 2048 * This scales the image to the element's actual size, the URL is 2049 * taken from the "background-image" CSS attribute. 2050 * 2051 * @deprecated IE8 isn't supported since 9.0 2052 * @param {Object} $el image element 2053 */ 2054 scaleFixForIE8: function ($el) { 2055 }, 2056 2057 /** 2058 * Returns whether this is IE 2059 * 2060 * @deprecated Use bowser.mise instead (since 9.1) 2061 * @return {bool} true if this is IE, false otherwise 2062 */ 2063 isIE: function () { 2064 return bowser.msie; 2065 }, 2066 2067 /** 2068 * Returns whether this is IE8 2069 * 2070 * @deprecated IE8 isn't supported since 9.0 2071 * @return {bool} false (IE8 isn't supported anymore) 2072 */ 2073 isIE8: function () { 2074 return false; 2075 }, 2076 2077 /** 2078 * Returns the width of a generic browser scrollbar 2079 * 2080 * @return {int} width of scrollbar 2081 */ 2082 getScrollBarWidth: function () { 2083 if (this._scrollBarWidth) { 2084 return this._scrollBarWidth; 2085 } 2086 2087 var inner = document.createElement('p'); 2088 inner.style.width = "100%"; 2089 inner.style.height = "200px"; 2090 2091 var outer = document.createElement('div'); 2092 outer.style.position = "absolute"; 2093 outer.style.top = "0px"; 2094 outer.style.left = "0px"; 2095 outer.style.visibility = "hidden"; 2096 outer.style.width = "200px"; 2097 outer.style.height = "150px"; 2098 outer.style.overflow = "hidden"; 2099 outer.appendChild(inner); 2100 2101 document.body.appendChild(outer); 2102 var w1 = inner.offsetWidth; 2103 outer.style.overflow = 'scroll'; 2104 var w2 = inner.offsetWidth; 2105 if (w1 === w2) { 2106 w2 = outer.clientWidth; 2107 } 2108 2109 document.body.removeChild(outer); 2110 2111 this._scrollBarWidth = (w1 - w2); 2112 2113 return this._scrollBarWidth; 2114 }, 2115 2116 /** 2117 * Remove the time component from a given date 2118 * 2119 * @param {Date} date date 2120 * @return {Date} date with stripped time 2121 */ 2122 stripTime: function (date) { 2123 // FIXME: likely to break when crossing DST 2124 // would be better to use a library like momentJS 2125 return new Date(date.getFullYear(), date.getMonth(), date.getDate()); 2126 }, 2127 2128 _chunkify: function (t) { 2129 // Adapted from http://my.opera.com/GreyWyvern/blog/show.dml/1671288 2130 var tz = [], x = 0, y = -1, n = 0, code, c; 2131 2132 while (x < t.length) { 2133 c = t.charAt(x); 2134 // only include the dot in strings 2135 var m = ((!n && c === '.') || (c >= '0' && c <= '9')); 2136 if (m !== n) { 2137 // next chunk 2138 y++; 2139 tz[y] = ''; 2140 n = m; 2141 } 2142 tz[y] += c; 2143 x++; 2144 } 2145 return tz; 2146 }, 2147 /** 2148 * Compare two strings to provide a natural sort 2149 * @param a first string to compare 2150 * @param b second string to compare 2151 * @return -1 if b comes before a, 1 if a comes before b 2152 * or 0 if the strings are identical 2153 */ 2154 naturalSortCompare: function (a, b) { 2155 var x; 2156 var aa = OC.Util._chunkify(a); 2157 var bb = OC.Util._chunkify(b); 2158 2159 for (x = 0; aa[x] && bb[x]; x++) { 2160 if (aa[x] !== bb[x]) { 2161 var aNum = Number(aa[x]), bNum = Number(bb[x]); 2162 // note: == is correct here 2163 if (aNum == aa[x] && bNum == bb[x]) { 2164 return aNum - bNum; 2165 } else { 2166 // Forcing 'en' locale to match the server-side locale which is 2167 // always 'en'. 2168 // 2169 // Note: This setting isn't supported by all browsers but for the ones 2170 // that do there will be more consistency between client-server sorting 2171 return aa[x].localeCompare(bb[x], 'en'); 2172 } 2173 } 2174 } 2175 return aa.length - bb.length; 2176 }, 2177 /** 2178 * Calls the callback in a given interval until it returns true 2179 * @param {function} callback 2180 * @param {integer} interval in milliseconds 2181 */ 2182 waitFor: function (callback, interval) { 2183 var internalCallback = function () { 2184 if (callback() !== true) { 2185 setTimeout(internalCallback, interval); 2186 } 2187 }; 2188 2189 internalCallback(); 2190 }, 2191 /** 2192 * Checks if a cookie with the given name is present and is set to the provided value. 2193 * @param {string} name name of the cookie 2194 * @param {string} value value of the cookie 2195 * @return {boolean} true if the cookie with the given name has the given value 2196 */ 2197 isCookieSetToValue: function (name, value) { 2198 var cookies = document.cookie.split(';'); 2199 for (var i = 0; i < cookies.length; i++) { 2200 var cookie = cookies[i].split('='); 2201 if (cookie[0].trim() === name && cookie[1].trim() === value) { 2202 return true; 2203 } 2204 } 2205 return false;
2206 }, 2207 2208 /** 2209 * Checks if an element is completely visible and scrolls the screen if not 2210 * @param {jQuery} jQuery element that has to be displayed 2211 * @param {jQuery} scroll container if null scrollContainer will be set to $('#app-content') 2212 */ 2213 scrollIntoView: function (toViewEl, scrollContainer) { 2214 2215 var toViewElTopLocation = toViewEl.offset().top; 2216 var toViewElHeight = toViewEl.outerHeight(); 2217 var toViewElBottomLocation = toViewElTopLocation + toViewElHeight + 50; 2218 var windowHeight = $(window).height(); 2219 2220 if (scrollContainer === null) { 2221 scrollContainer = $('#app-content'); 2222 } 2223 2224 if (toViewElBottomLocation > windowHeight) { 2225 var currentPosition = scrollContainer[0].scrollTop; 2226 var scrollDistance = toViewElBottomLocation - windowHeight; 2227 scrollContainer.stop(); 2228 scrollContainer.animate({ 2229 scrollTop: currentPosition + scrollDistance 2230 }, 700); 2231 } 2232 } 2233}; 2234 2235/** 2236 * Utility class for the history API, 2237 * includes fallback to using the URL hash when 2238 * the browser doesn't support the history API. 2239 * 2240 * @namespace 2241 */ 2242OC.Util.History = { 2243 _handlers: [], 2244 2245 /** 2246 * Push the current URL parameters to the history stack 2247 * and change the visible URL. 2248 * Note: this includes a workaround for IE8/IE9 that uses 2249 * the hash part instead of the search part. 2250 * 2251 * @param params to append to the URL, can be either a string 2252 * or a map 2253 * @param {boolean} [replace=false] whether to replace instead of pushing 2254 */ 2255 _pushState: function (params, replace) { 2256 var strParams; 2257 if (typeof(params) === 'string') { 2258 strParams = params; 2259 } 2260 else { 2261 strParams = OC.buildQueryString(params); 2262 } 2263 if (window.history.pushState) { 2264 var url = location.pathname + '?' + strParams; 2265 if (replace) { 2266 window.history.replaceState(params, '', url); 2267 } else { 2268 window.history.pushState(params, '', url); 2269 } 2270 } 2271 // use URL hash for IE8 2272 else { 2273 window.location.hash = '?' + strParams; 2274 // inhibit next onhashchange that just added itself 2275 // to the event queue 2276 this._cancelPop = true; 2277 } 2278 }, 2279 2280 /** 2281 * Push the current URL parameters to the history stack 2282 * and change the visible URL. 2283 * Note: this includes a workaround for IE8/IE9 that uses 2284 * the hash part instead of the search part. 2285 * 2286 * @param params to append to the URL, can be either a string 2287 * or a map 2288 */ 2289 pushState: function (params) { 2290 return this._pushState(params, false); 2291 }, 2292 2293 /** 2294 * Push the current URL parameters to the history stack 2295 * and change the visible URL. 2296 * Note: this includes a workaround for IE8/IE9 that uses 2297 * the hash part instead of the search part. 2298 * 2299 * @param params to append to the URL, can be either a string 2300 * or a map 2301 */ 2302 replaceState: function (params) { 2303 return this._pushState(params, true); 2304 }, 2305 2306 /** 2307 * Add a popstate handler 2308 * 2309 * @param handler function 2310 */ 2311 addOnPopStateHandler: function (handler) { 2312 this._handlers.push(handler); 2313 }, 2314 2315 /** 2316 * Parse a query string from the hash part of the URL. 2317 * (workaround for IE8 / IE9) 2318 */ 2319 _parseHashQuery: function () { 2320 var hash = window.location.hash, 2321 pos = hash.indexOf('?'); 2322 if (pos >= 0) { 2323 return hash.substr(pos + 1); 2324 } 2325 if (hash.length) { 2326 // remove hash sign 2327 return hash.substr(1); 2328 } 2329 return ''; 2330 }, 2331 2332 _decodeQuery: function (query) { 2333 return query.replace(/\+/g, ' '); 2334 }, 2335 2336 /** 2337 * Parse the query/search part of the URL. 2338 * Also try and parse it from the URL hash (for IE8) 2339 * 2340 * @return map of parameters 2341 */ 2342 parseUrlQuery: function () { 2343 var query = this._parseHashQuery(), 2344 params; 2345 // try and parse from URL hash first 2346 if (query) { 2347 params = OC.parseQueryString(this._decodeQuery(query)); 2348 } 2349 // else read from query attributes 2350 params = _.extend(params || {}, OC.parseQueryString(this._decodeQuery(location.search))); 2351 return params || {}; 2352 }, 2353 2354 _onPopState: function (e) { 2355 if (this._cancelPop) { 2356 this._cancelPop = false; 2357 return; 2358 } 2359 var params; 2360 if (!this._handlers.length) { 2361 return; 2362 } 2363 params = (e && e.state); 2364 if (_.isString(params)) { 2365 params = OC.parseQueryString(params); 2366 } else if (!params) { 2367 params = this.parseUrlQuery() || {}; 2368 } 2369 for (var i = 0; i < this._handlers.length; i++) { 2370 this._handlers[i](params); 2371 } 2372 } 2373}; 2374 2375// fallback to hashchange when no history support 2376if (window.history.pushState) { 2377 window.onpopstate = _.bind(OC.Util.History._onPopState, OC.Util.History); 2378} 2379else { 2380 $(window).on('hashchange', _.bind(OC.Util.History._onPopState, OC.Util.History)); 2381} 2382 2383/** 2384 * Get a variable by name 2385 * @param {string} name 2386 * @return {*} 2387 */ 2388OC.get = function (name) { 2389 var namespaces = name.split("."); 2390 var tail = namespaces.pop(); 2391 var context = window; 2392 2393 for (var i = 0; i < namespaces.length; i++) { 2394 context = context[namespaces[i]]; 2395 if (!context) { 2396 return false;
2397 } 2398 } 2399 return context[tail]; 2400}; 2401 2402/** 2403 * Set a variable by name 2404 * @param {string} name 2405 * @param {*} value 2406 */ 2407OC.set = function (name, value) { 2408 var namespaces = name.split("."); 2409 var tail = namespaces.pop(); 2410 var context = window; 2411 2412 for (var i = 0; i < namespaces.length; i++) { 2413 if (!context[namespaces[i]]) { 2414 context[namespaces[i]] = {}; 2415 } 2416 context = context[namespaces[i]]; 2417 } 2418 context[tail] = value; 2419}; 2420 2421// fix device width on windows phone 2422(function () { 2423 if ("-ms-user-select" in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/)) { 2424 var msViewportStyle = document.createElement("style"); 2425 msViewportStyle.appendChild( 2426 document.createTextNode("@-ms-viewport{width:auto!important}") 2427 ); 2428 document.getElementsByTagName("head")[0].appendChild(msViewportStyle); 2429 } 2430})(); 2431 2432/** 2433 * Namespace for apps 2434 * @namespace OCA 2435 */ 2436window.OCA = {}; 2437 2438/** 2439 * select a range in an input field 2440 * @link http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area 2441 * @param {type} start 2442 * @param {type} end 2443 */ 2444jQuery.fn.selectRange = function (start, end) { 2445 return this.each(function () { 2446 if (this.setSelectionRange) { 2447 this.focus(); 2448 this.setSelectionRange(start, end); 2449 } else if (this.createTextRange) { 2450 var range = this.createTextRange(); 2451 range.collapse(true); 2452 range.moveEnd('character', end); 2453 range.moveStart('character', start); 2454 range.select(); 2455 } 2456 }); 2457}; 2458 2459/** 2460 * check if an element exists. 2461 * allows you to write if ($('#myid').exists()) to increase readability 2462 * @link http://stackoverflow.com/questions/31044/is-there-an-exists-function-for-jquery 2463 */ 2464jQuery.fn.exists = function () { 2465 return this.length > 0; 2466}; 2467 2468/** 2469 * @deprecated use OC.Util.getScrollBarWidth() instead 2470 */ 2471function getScrollBarWidth() { 2472 return OC.Util.getScrollBarWidth(); 2473} 2474 2475/** 2476 * jQuery tipsy shim for the bootstrap tooltip 2477 */ 2478jQuery.fn.tipsy = function (argument) { 2479 console.warn('Deprecation warning: tipsy is deprecated. Use tooltip instead.'); 2480 if (typeof argument === 'object' && argument !== null) { 2481 2482 // tipsy defaults 2483 var options = { 2484 placement: 'bottom', 2485 delay: {'show': 0, 'hide': 0}, 2486 trigger: 'hover', 2487 html: false, 2488 container: 'body' 2489 }; 2490 if (argument.gravity) { 2491 switch (argument.gravity) { 2492 case 'n': 2493 case 'nw': 2494 case 'ne': 2495 options.placement = 'bottom'; 2496 break; 2497 case 's': 2498 case 'sw': 2499 case 'se': 2500 options.placement = 'top'; 2501 break; 2502 case 'w': 2503 options.placement = 'right'; 2504 break; 2505 case 'e': 2506 options.placement = 'left'; 2507 break; 2508 } 2509 } 2510 if (argument.trigger) { 2511 options.trigger = argument.trigger; 2512 } 2513 if (argument.delayIn) { 2514 options.delay["show"] = argument.delayIn; 2515 } 2516 if (argument.delayOut) { 2517 options.delay["hide"] = argument.delayOut; 2518 } 2519 if (argument.html) { 2520 options.html = true; 2521 } 2522 if (argument.fallback) { 2523 options.title = argument.fallback; 2524 } 2525 // destroy old tooltip in case the title has changed 2526 jQuery.fn.tooltip.call(this, 'destroy'); 2527 jQuery.fn.tooltip.call(this, options); 2528 } else { 2529 this.tooltip(argument); 2530 jQuery.fn.tooltip.call(this, argument); 2531 } 2532 return this; 2533} 2534 2535jQuery.extend = jQuery.fn.extend = function() { 2536 var options, name, src, copy, copyIsArray, clone, 2537 target = arguments[0] || {}, 2538 i = 1, 2539 length = arguments.length, 2540 deep = false; 2541 2542 // Handle a deep copy situation 2543 if ( typeof target === "boolean" ) { 2544 deep = target; 2545 2546 // Skip the boolean and the target 2547 target = arguments[ i ] || {}; 2548 i++; 2549 } 2550 2551 // Handle case when target is a string or something (possible in deep copy) 2552 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { 2553 target = {}; 2554 } 2555 2556 // Extend jQuery itself if only one argument is passed 2557 if ( i === length ) { 2558 target = this; 2559 i--; 2560 } 2561 2562 for ( ; i < length; i++ ) { 2563 // Only deal with non-null/undefined values 2564 if ( (options = arguments[ i ]) != null ) { 2565 // Extend the base object 2566 for ( name in options ) { 2567 src = target[ name ]; 2568 copy = options[ name ]; 2569 2570 // Prevent never-ending loop 2571 if ( name === "__proto__" || target === copy ) { 2572 continue; 2573 } 2574 2575 // Recurse if we're merging plain objects or arrays 2576 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { 2577 if ( copyIsArray ) { 2578 copyIsArray = false;
vendor: 4,493 bytes, lines 2579-2748
2579 clone = src && jQuery.isArray(src) ? src : []; 2580 2581 } else { 2582 clone = src && jQuery.isPlainObject(src) ? src : {}; 2583 } 2584 2585 // Never move original objects, clone them 2586 target[ name ] = jQuery.extend( deep, clone, copy ); 2587 2588 // Don't bring in undefined values 2589 } else if ( copy !== undefined ) { 2590 target[ name ] = copy; 2591 } 2592 } 2593 } 2594 } 2595 2596 // Return the modified object 2597 return target; 2598}; 2599 2600function isWindow( obj ) { 2601 return obj != null && obj === obj.window; 2602} 2603 2604function getDimensions( elem ) { 2605 var raw = elem[ 0 ]; 2606 if ( raw.nodeType === 9 ) { 2607 return { 2608 width: elem.width(), 2609 height: elem.height(), 2610 offset: { top: 0, left: 0 } 2611 }; 2612 } 2613 if ( isWindow( raw ) ) { 2614 return { 2615 width: elem.width(), 2616 height: elem.height(), 2617 offset: { top: elem.scrollTop(), left: elem.scrollLeft() } 2618 }; 2619 } 2620 if ( raw.preventDefault ) { 2621 return { 2622 width: 0, 2623 height: 0, 2624 offset: { top: raw.pageY, left: raw.pageX } 2625 }; 2626 } 2627 return { 2628 width: elem.outerWidth(), 2629 height: elem.outerHeight(), 2630 offset: elem.offset() 2631 }; 2632} 2633 2634var _position = $.fn.position, 2635 max = Math.max, 2636 abs = Math.abs, 2637 rhorizontal = /left|center|right/, 2638 rvertical = /top|center|bottom/, 2639 roffset = /[\+\-]\d+(\.[\d]+)?%?/, 2640 rposition = /^\w+/, 2641 rpercent = /%$/; 2642 2643function getOffsets( offsets, width, height ) { 2644 return [ 2645 parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), 2646 parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) 2647 ]; 2648} 2649 2650function parseCss( element, property ) { 2651 return parseInt( $.css( element, property ), 10 ) || 0; 2652} 2653 2654$.fn.position = function( options ) { 2655 if ( !options || !options.of ) { 2656 return _position.apply( this, arguments ); 2657 } 2658 2659 // Make a copy, we don't want to modify arguments 2660 options = $.extend( {}, options ); 2661 2662 var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, 2663 2664 // Make sure string options are treated as CSS selectors 2665 target = typeof options.of === "string" ? 2666 $( document ).find( options.of ) : 2667 $( options.of ), 2668 2669 within = $.position.getWithinInfo( options.within ), 2670 scrollInfo = $.position.getScrollInfo( within ), 2671 collision = ( options.collision || "flip" ).split( " " ), 2672 offsets = {}; 2673 2674 dimensions = getDimensions( target ); 2675 if ( target[ 0 ].preventDefault ) { 2676 2677 // Force left top to allow flipping 2678 options.at = "left top"; 2679 } 2680 targetWidth = dimensions.width; 2681 targetHeight = dimensions.height; 2682 targetOffset = dimensions.offset; 2683 2684 // Clone to reuse original targetOffset later 2685 basePosition = $.extend( {}, targetOffset ); 2686 2687 // Force my and at to have valid horizontal and vertical positions 2688 // if a value is missing or invalid, it will be converted to center 2689 $.each( [ "my", "at" ], function() { 2690 var pos = ( options[ this ] || "" ).split( " " ), 2691 horizontalOffset, 2692 verticalOffset; 2693 2694 if ( pos.length === 1 ) { 2695 pos = rhorizontal.test( pos[ 0 ] ) ? 2696 pos.concat( [ "center" ] ) : 2697 rvertical.test( pos[ 0 ] ) ? 2698 [ "center" ].concat( pos ) : 2699 [ "center", "center" ]; 2700 } 2701 pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; 2702 pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; 2703 2704 // Calculate offsets 2705 horizontalOffset = roffset.exec( pos[ 0 ] ); 2706 verticalOffset = roffset.exec( pos[ 1 ] ); 2707 offsets[ this ] = [ 2708 horizontalOffset ? horizontalOffset[ 0 ] : 0, 2709 verticalOffset ? verticalOffset[ 0 ] : 0 2710 ]; 2711 2712 // Reduce to just the positions without the offsets 2713 options[ this ] = [ 2714 rposition.exec( pos[ 0 ] )[ 0 ], 2715 rposition.exec( pos[ 1 ] )[ 0 ] 2716 ]; 2717 } ); 2718 2719 // Normalize collision option 2720 if ( collision.length === 1 ) { 2721 collision[ 1 ] = collision[ 0 ]; 2722 } 2723 2724 if ( options.at[ 0 ] === "right" ) { 2725 basePosition.left += targetWidth; 2726 } else if ( options.at[ 0 ] === "center" ) { 2727 basePosition.left += targetWidth / 2; 2728 } 2729 2730 if ( options.at[ 1 ] === "bottom" ) { 2731 basePosition.top += targetHeight; 2732 } else if ( options.at[ 1 ] === "center" ) { 2733 basePosition.top += targetHeight / 2; 2734 } 2735 2736 atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); 2737 basePosition.left += atOffset[ 0 ]; 2738 basePosition.top += atOffset[ 1 ]; 2739 2740 return this.each( function() { 2741 var collisionPosition, using, 2742 elem = $( this ), 2743 elemWidth = elem.outerWidth(), 2744 elemHeight = elem.outerHeight(), 2745 marginLeft = parseCss( this, "marginLeft" ), 2746 marginTop = parseCss( this, "marginTop" ), 2747 collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + 2748 scrollInfo.width,
vendor: 15,913 bytes, lines 2749-3158
2749 collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + 2750 scrollInfo.height, 2751 position = $.extend( {}, basePosition ), 2752 myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); 2753 2754 if ( options.my[ 0 ] === "right" ) { 2755 position.left -= elemWidth; 2756 } else if ( options.my[ 0 ] === "center" ) { 2757 position.left -= elemWidth / 2; 2758 } 2759 2760 if ( options.my[ 1 ] === "bottom" ) { 2761 position.top -= elemHeight; 2762 } else if ( options.my[ 1 ] === "center" ) { 2763 position.top -= elemHeight / 2; 2764 } 2765 2766 position.left += myOffset[ 0 ]; 2767 position.top += myOffset[ 1 ]; 2768 2769 collisionPosition = { 2770 marginLeft: marginLeft, 2771 marginTop: marginTop 2772 }; 2773 2774 $.each( [ "left", "top" ], function( i, dir ) { 2775 if ( $.ui.position[ collision[ i ] ] ) { 2776 $.ui.position[ collision[ i ] ][ dir ]( position, { 2777 targetWidth: targetWidth, 2778 targetHeight: targetHeight, 2779 elemWidth: elemWidth, 2780 elemHeight: elemHeight, 2781 collisionPosition: collisionPosition, 2782 collisionWidth: collisionWidth, 2783 collisionHeight: collisionHeight, 2784 offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], 2785 my: options.my, 2786 at: options.at, 2787 within: within, 2788 elem: elem 2789 } ); 2790 } 2791 } ); 2792 2793 if ( options.using ) { 2794 2795 // Adds feedback as second argument to using callback, if present 2796 using = function( props ) { 2797 var left = targetOffset.left - position.left, 2798 right = left + targetWidth - elemWidth, 2799 top = targetOffset.top - position.top, 2800 bottom = top + targetHeight - elemHeight, 2801 feedback = { 2802 target: { 2803 element: target, 2804 left: targetOffset.left, 2805 top: targetOffset.top, 2806 width: targetWidth, 2807 height: targetHeight 2808 }, 2809 element: { 2810 element: elem, 2811 left: position.left, 2812 top: position.top, 2813 width: elemWidth, 2814 height: elemHeight 2815 }, 2816 horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", 2817 vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" 2818 }; 2819 if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { 2820 feedback.horizontal = "center"; 2821 } 2822 if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { 2823 feedback.vertical = "middle"; 2824 } 2825 if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { 2826 feedback.important = "horizontal"; 2827 } else { 2828 feedback.important = "vertical"; 2829 } 2830 options.using.call( this, props, feedback ); 2831 }; 2832 } 2833 2834 elem.offset( $.extend( position, { using: using } ) ); 2835 } ); 2836}; 2837 2838$.datepicker._attachments = function (input, inst) { 2839 var showOn, buttonText, buttonImage, 2840 appendText = this._get(inst, "appendText"), 2841 isRTL = this._get(inst, "isRTL"); 2842 2843 if (inst.append) { 2844 inst.append.remove(); 2845 } 2846 if (appendText) { 2847 inst.append = $("<span>") 2848 .addClass(this._appendClass) 2849 .text(appendText); 2850 input[isRTL ? "before" : "after"](inst.append); 2851 } 2852 2853 input.off("focus", this._showDatepicker); 2854 2855 if (inst.trigger) { 2856 inst.trigger.remove(); 2857 } 2858 2859 showOn = this._get(inst, "showOn"); 2860 if (showOn === "focus" || showOn === "both") { // pop-up date picker when in the marked field 2861 input.on("focus", this._showDatepicker); 2862 } 2863 if (showOn === "button" || showOn === "both") { // pop-up date picker when button clicked 2864 buttonText = this._get(inst, "buttonText"); 2865 buttonImage = this._get(inst, "buttonImage"); 2866 2867 if (this._get(inst, "buttonImageOnly")) { 2868 inst.trigger = $("<img>") 2869 .addClass(this._triggerClass) 2870 .attr({ 2871 src: buttonImage, 2872 alt: buttonText, 2873 title: buttonText 2874 }); 2875 } else { 2876 inst.trigger = $("<button type='button'>") 2877 .addClass(this._triggerClass); 2878 if (buttonImage) { 2879 inst.trigger.html( 2880 $("<img>") 2881 .attr({ 2882 src: buttonImage, 2883 alt: buttonText, 2884 title: buttonText 2885 }) 2886 ); 2887 } else { 2888 inst.trigger.text(buttonText); 2889 } 2890 } 2891 2892 input[isRTL ? "before" : "after"](inst.trigger); 2893 inst.trigger.on("click", function () { 2894 if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { 2895 $.datepicker._hideDatepicker(); 2896 } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { 2897 $.datepicker._hideDatepicker(); 2898 $.datepicker._showDatepicker(input[0]); 2899 } else { 2900 $.datepicker._showDatepicker(input[0]); 2901 } 2902 return false; 2903 }); 2904 } 2905 }; 2906 $.datepicker._generateHTML = function( inst ) { 2907 var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, 2908 controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, 2909 monthNames, monthNamesShort, beforeShowDay, showOtherMonths, 2910 selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, 2911 cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, 2912 printDate, dRow, tbody, daySettings, otherMonth, unselectable, 2913 tempDate = new Date(), 2914 today = this._daylightSavingAdjust( 2915 new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time 2916 isRTL = this._get( inst, "isRTL" ), 2917 showButtonPanel = this._get( inst, "showButtonPanel" ), 2918 hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ), 2919 navigationAsDateFormat = this._get( inst, "navigationAsDateFormat" ), 2920 numMonths = this._getNumberOfMonths( inst ), 2921 showCurrentAtPos = this._get( inst, "showCurrentAtPos" ), 2922 stepMonths = this._get( inst, "stepMonths" ), 2923 isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ), 2924 currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) : 2925 new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ), 2926 minDate = this._getMinMaxDate( inst, "min" ), 2927 maxDate = this._getMinMaxDate( inst, "max" ), 2928 drawMonth = inst.drawMonth - showCurrentAtPos, 2929 drawYear = inst.drawYear; 2930 2931 if ( drawMonth < 0 ) { 2932 drawMonth += 12; 2933 drawYear--; 2934 } 2935 if ( maxDate ) { 2936 maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(), 2937 maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) ); 2938 maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw ); 2939 while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) { 2940 drawMonth--; 2941 if ( drawMonth < 0 ) { 2942 drawMonth = 11; 2943 drawYear--; 2944 } 2945 } 2946 } 2947 inst.drawMonth = drawMonth; 2948 inst.drawYear = drawYear; 2949 2950 prevText = this._get( inst, "prevText" ); 2951 prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText, 2952 this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ), 2953 this._getFormatConfig( inst ) ) ); 2954 2955 if ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ) { 2956 prev = $( "<a>" ) 2957 .attr( { 2958 "class": "ui-datepicker-prev ui-corner-all", 2959 "data-handler": "prev", 2960 "data-event": "click", 2961 title: prevText 2962 } ) 2963 .append( 2964 $( "<span>" ) 2965 .addClass( "ui-icon ui-icon-circle-triangle-" + 2966 ( isRTL ? "e" : "w" ) ) 2967 .text( prevText ) 2968 )[ 0 ].outerHTML; 2969 } else if ( hideIfNoPrevNext ) { 2970 prev = ""; 2971 } else { 2972 prev = $( "<a>" ) 2973 .attr( { 2974 "class": "ui-datepicker-prev ui-corner-all ui-state-disabled", 2975 title: prevText 2976 } ) 2977 .append( 2978 $( "<span>" ) 2979 .addClass( "ui-icon ui-icon-circle-triangle-" + 2980 ( isRTL ? "e" : "w" ) ) 2981 .text( prevText ) 2982 )[ 0 ].outerHTML; 2983 } 2984 2985 nextText = this._get( inst, "nextText" ); 2986 nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText, 2987 this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ), 2988 this._getFormatConfig( inst ) ) ); 2989 2990 if ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ) { 2991 next = $( "<a>" ) 2992 .attr( { 2993 "class": "ui-datepicker-next ui-corner-all", 2994 "data-handler": "next", 2995 "data-event": "click", 2996 title: nextText 2997 } ) 2998 .append( 2999 $( "<span>" ) 3000 .addClass( "ui-icon ui-icon-circle-triangle-" + 3001 ( isRTL ? "w" : "e" ) ) 3002 .text( nextText ) 3003 )[ 0 ].outerHTML; 3004 } else if ( hideIfNoPrevNext ) { 3005 next = ""; 3006 } else { 3007 next = $( "<a>" ) 3008 .attr( { 3009 "class": "ui-datepicker-next ui-corner-all ui-state-disabled", 3010 title: nextText 3011 } ) 3012 .append( 3013 $( "<span>" ) 3014 .attr( "class", "ui-icon ui-icon-circle-triangle-" + 3015 ( isRTL ? "w" : "e" ) ) 3016 .text( nextText ) 3017 )[ 0 ].outerHTML; 3018 } 3019 3020 currentText = this._get( inst, "currentText" ); 3021 gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today ); 3022 currentText = ( !navigationAsDateFormat ? currentText : 3023 this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) ); 3024 3025 controls = ""; 3026 if ( !inst.inline ) { 3027 controls = $( "<button>" ) 3028 .attr( { 3029 type: "button", 3030 "class": "ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all", 3031 "data-handler": "hide", 3032 "data-event": "click" 3033 } ) 3034 .text( this._get( inst, "closeText" ) )[ 0 ].outerHTML; 3035 } 3036 3037 buttonPanel = ""; 3038 if ( showButtonPanel ) { 3039 buttonPanel = $( "<div class='ui-datepicker-buttonpane ui-widget-content'>" ) 3040 .append( isRTL ? controls : "" ) 3041 .append( this._isInRange( inst, gotoDate ) ? 3042 $( "<button>" ) 3043 .attr( { 3044 type: "button", 3045 "class": "ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all", 3046 "data-handler": "today", 3047 "data-event": "click" 3048 } ) 3049 .text( currentText ) : 3050 "" ) 3051 .append( isRTL ? "" : controls )[ 0 ].outerHTML; 3052 } 3053 3054 firstDay = parseInt( this._get( inst, "firstDay" ), 10 ); 3055 firstDay = ( isNaN( firstDay ) ? 0 : firstDay ); 3056 3057 showWeek = this._get( inst, "showWeek" ); 3058 dayNames = this._get( inst, "dayNames" ); 3059 dayNamesMin = this._get( inst, "dayNamesMin" ); 3060 monthNames = this._get( inst, "monthNames" ); 3061 monthNamesShort = this._get( inst, "monthNamesShort" ); 3062 beforeShowDay = this._get( inst, "beforeShowDay" ); 3063 showOtherMonths = this._get( inst, "showOtherMonths" ); 3064 selectOtherMonths = this._get( inst, "selectOtherMonths" ); 3065 defaultDate = this._getDefaultDate( inst ); 3066 html = ""; 3067 3068 for ( row = 0; row < numMonths[ 0 ]; row++ ) { 3069 group = ""; 3070 this.maxRows = 4; 3071 for ( col = 0; col < numMonths[ 1 ]; col++ ) { 3072 selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) ); 3073 cornerClass = " ui-corner-all"; 3074 calender = ""; 3075 if ( isMultiMonth ) { 3076 calender += "<div class='ui-datepicker-group"; 3077 if ( numMonths[ 1 ] > 1 ) { 3078 switch ( col ) { 3079 case 0: calender += " ui-datepicker-group-first"; 3080 cornerClass = " ui-corner-" + ( isRTL ? "right" : "left" ); break; 3081 case numMonths[ 1 ] - 1: calender += " ui-datepicker-group-last"; 3082 cornerClass = " ui-corner-" + ( isRTL ? "left" : "right" ); break; 3083 default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break; 3084 } 3085 } 3086 calender += "'>"; 3087 } 3088 calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" + 3089 ( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : "" ) + 3090 ( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : "" ) + 3091 this._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate, 3092 row > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers 3093 "</div><table class='ui-datepicker-calendar'><thead>" + 3094 "<tr>"; 3095 thead = ( showWeek ? "<th class='ui-datepicker-week-col'>" + this._get( inst, "weekHeader" ) + "</th>" : "" ); 3096 for ( dow = 0; dow < 7; dow++ ) { // days of the week 3097 day = ( dow + firstDay ) % 7; 3098 thead += "<th scope='col'" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "" ) + ">" + 3099 "<span title='" + dayNames[ day ] + "'>" + dayNamesMin[ day ] + "</span></th>"; 3100 } 3101 calender += thead + "</tr></thead><tbody>"; 3102 daysInMonth = this._getDaysInMonth( drawYear, drawMonth ); 3103 if ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) { 3104 inst.selectedDay = Math.min( inst.selectedDay, daysInMonth ); 3105 } 3106 leadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7; 3107 curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate 3108 numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043) 3109 this.maxRows = numRows; 3110 printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) ); 3111 for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows 3112 calender += "<tr>"; 3113 tbody = ( !showWeek ? "" : "<td class='ui-datepicker-week-col'>" + 3114 this._get( inst, "calculateWeek" )( printDate ) + "</td>" ); 3115 for ( dow = 0; dow < 7; dow++ ) { // create date picker days 3116 daySettings = ( beforeShowDay ? 3117 beforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, "" ] ); 3118 otherMonth = ( printDate.getMonth() !== drawMonth ); 3119 unselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] || 3120 ( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate ); 3121 tbody += "<td class='" + 3122 ( ( dow + firstDay + 6 ) % 7 >= 5 ? " ui-datepicker-week-end" : "" ) + // highlight weekends 3123 ( otherMonth ? " ui-datepicker-other-month" : "" ) + // highlight days from other months 3124 ( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key 3125 ( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ? 3126 3127 // or defaultDate is current printedDate and defaultDate is selectedDate 3128 " " + this._dayOverClass : "" ) + // highlight selected day 3129 ( unselectable ? " " + this._unselectableClass + " ui-state-disabled" : "" ) + // highlight unselectable days 3130 ( otherMonth && !showOtherMonths ? "" : " " + daySettings[ 1 ] + // highlight custom dates 3131 ( printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "" ) + // highlight selected day 3132 ( printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "" ) ) + "'" + // highlight today (if different) 3133 ( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? " title='" + daySettings[ 2 ].replace( /'/g, "'" ) + "'" : "" ) + // cell title 3134 ( unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'" ) + ">" + // actions 3135 ( otherMonth && !showOtherMonths ? " " : // display for other months 3136 ( unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" + 3137 ( printDate.getTime() === today.getTime() ? " ui-state-highlight" : "" ) + 3138 ( printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "" ) + // highlight selected day 3139 ( otherMonth ? " ui-priority-secondary" : "" ) + // distinguish dates from other months 3140 "' href='#'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date 3141 printDate.setDate( printDate.getDate() + 1 ); 3142 printDate = this._daylightSavingAdjust( printDate ); 3143 } 3144 calender += tbody + "</tr>"; 3145 } 3146 drawMonth++; 3147 if ( drawMonth > 11 ) { 3148 drawMonth = 0; 3149 drawYear++; 3150 } 3151 calender += "</tbody></table>" + ( isMultiMonth ? "</div>" + 3152 ( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? "<div class='ui-datepicker-row-break'></div>" : "" ) : "" ); 3153 group += calender; 3154 } 3155 html += group; 3156 } 3157 html += buttonPanel; 3158 inst._keyEvent = false;
3159 return html; 3160 }; 3161 $.datepicker._updateAlternate = function( inst ) { 3162 var altFormat, date, dateStr, 3163 altField = this._get( inst, "altField" ); 3164 if ( altField ) { // update alternate field too 3165 altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" ); 3166 date = this._getDate( inst ); 3167 dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) ); 3168 $( altField ).val( dateStr ); 3169 $( document ).find( altField ).val( dateStr ); 3170 } 3171 }; 3172 3173 $.ui.dialog._setOption = function( key, value ) { 3174 if (key === "disabled") { 3175 return; 3176 } 3177 this._super(key, value); 3178 if (key === "appendTo") { 3179 this.uiDialog.appendTo(this._appendTo()); 3180 } 3181 if (key === "buttons") { 3182 this._createButtons(); 3183 } 3184 if (key === "closeText") { 3185 this.uiDialogTitlebarClose.button({ 3186 // Ensure that we always pass a string 3187 label: $("<a>").text("" + this.options.closeText).html() 3188 }); 3189 } 3190 }; 3191 3192 $.ui.dialog.dialog_createTitlebar = function() { 3193 var uiDialogTitle; 3194 this.uiDialogTitlebar = $( "<div>" ); 3195 this._addClass( this.uiDialogTitlebar, 3196 "ui-dialog-titlebar", "ui-widget-header ui-helper-clearfix" ); 3197 this._on( this.uiDialogTitlebar, { 3198 mousedown: function( event ) { 3199 // Don't prevent click on close button (#8838) 3200 // Focusing a dialog that is partially scrolled out of view 3201 // causes the browser to scroll it into view, preventing the click event 3202 if ( !$( event.target ).closest( ".ui-dialog-titlebar-close" ) ) { 3203 // Dialog isn't getting focus when dragging (#8063) 3204 this.uiDialog.trigger( "focus" ); 3205 } 3206 } 3207 } ); 3208 // Support: IE 3209 // Use type="button" to prevent enter keypresses in textboxes from closing the 3210 // dialog in IE (#9312) 3211 this.uiDialogTitlebarClose = $( "<button type='button'></button>" ) 3212 .button( { 3213 label: $( "<a>" ).text( this.options.closeText ).html(), 3214 icon: "ui-icon-closethick", 3215 showLabel: false 3216 } ) 3217 .appendTo( this.uiDialogTitlebar ); 3218 this._addClass( this.uiDialogTitlebarClose, "ui-dialog-titlebar-close" ); 3219 this._on( this.uiDialogTitlebarClose, { 3220 click: function( event ) { 3221 event.preventDefault(); 3222 this.close( event ); 3223 } 3224 } ); 3225 uiDialogTitle = $( "<span>" ).uniqueId().prependTo( this.uiDialogTitlebar ); 3226 this._addClass( uiDialogTitle, "ui-dialog-title" ); 3227 this._title( uiDialogTitle ); 3228 this.uiDialogTitlebar.prependTo( this.uiDialog ); 3229 this.uiDialog.attr( { 3230 "aria-labelledby": uiDialogTitle.attr( "id" ) 3231 } ); 3232};
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.