js.js 63 KB

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