js.js 63 KB

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