js.js 63 KB

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