js.js 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512
  1. /**
  2. * Disable console output unless DEBUG mode is enabled.
  3. * Add
  4. * define('DEBUG', true);
  5. * To the end of config/config.php to enable debug mode.
  6. * The undefined checks fix the broken ie8 console
  7. */
  8. var oc_debug;
  9. var oc_webroot;
  10. var oc_current_user = document.getElementsByTagName('head')[0].getAttribute('data-user');
  11. var oc_requesttoken = document.getElementsByTagName('head')[0].getAttribute('data-requesttoken');
  12. window.oc_config = window.oc_config || {};
  13. if (typeof oc_webroot === "undefined") {
  14. oc_webroot = location.pathname;
  15. var pos = oc_webroot.indexOf('/index.php/');
  16. if (pos !== -1) {
  17. oc_webroot = oc_webroot.substr(0, pos);
  18. }
  19. else {
  20. oc_webroot = oc_webroot.substr(0, oc_webroot.lastIndexOf('/'));
  21. }
  22. }
  23. if (oc_debug !== true || typeof console === "undefined" || typeof console.log === "undefined") {
  24. if (!window.console) {
  25. window.console = {};
  26. }
  27. var noOp = function() { };
  28. var methods = ['log', 'debug', 'warn', 'info', 'error', 'assert', 'time', 'timeEnd'];
  29. for (var i = 0; i < methods.length; i++) {
  30. console[methods[i]] = noOp;
  31. }
  32. }
  33. function initL10N(app) {
  34. if (!( t.cache[app] )) {
  35. $.ajax(OC.filePath('core', 'ajax', 'translations.php'), {
  36. async: false,//todo a proper solution for this without sync ajax calls
  37. data: {'app': app},
  38. type: 'POST',
  39. success: function (jsondata) {
  40. t.cache[app] = jsondata.data;
  41. t.plural_form = jsondata.plural_form;
  42. }
  43. });
  44. // Bad answer ...
  45. if (!( t.cache[app] )) {
  46. t.cache[app] = [];
  47. }
  48. }
  49. if (typeof t.plural_function[app] === 'undefined') {
  50. t.plural_function[app] = function (n) {
  51. var p = (n !== 1) ? 1 : 0;
  52. return { 'nplural' : 2, 'plural' : p };
  53. };
  54. /**
  55. * code below has been taken from jsgettext - which is LGPL licensed
  56. * https://developer.berlios.de/projects/jsgettext/
  57. * http://cvs.berlios.de/cgi-bin/viewcvs.cgi/jsgettext/jsgettext/lib/Gettext.js
  58. */
  59. var pf_re = new RegExp('^(\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;a-zA-Z0-9_\\(\\)])+)', 'm');
  60. if (pf_re.test(t.plural_form)) {
  61. //ex english: "Plural-Forms: nplurals=2; plural=(n != 1);\n"
  62. //pf = "nplurals=2; plural=(n != 1);";
  63. //ex russian: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2)
  64. //pf = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)";
  65. var pf = t.plural_form;
  66. if (! /;\s*$/.test(pf)) {
  67. pf = pf.concat(';');
  68. }
  69. /* We used to use eval, but it seems IE has issues with it.
  70. * We now use "new Function", though it carries a slightly
  71. * bigger performance hit.
  72. var code = 'function (n) { var plural; var nplurals; '+pf+' return { "nplural" : nplurals, "plural" : (plural === true ? 1 : plural ? plural : 0) }; };';
  73. Gettext._locale_data[domain].head.plural_func = eval("("+code+")");
  74. */
  75. var code = 'var plural; var nplurals; '+pf+' return { "nplural" : nplurals, "plural" : (plural === true ? 1 : plural ? plural : 0) };';
  76. t.plural_function[app] = new Function("n", code);
  77. } else {
  78. console.log("Syntax error in language file. Plural-Forms header is invalid ["+t.plural_forms+"]");
  79. }
  80. }
  81. }
  82. /**
  83. * translate a string
  84. * @param {string} app the id of the app for which to translate the string
  85. * @param {string} text the string to translate
  86. * @param [vars] FIXME
  87. * @param {number} [count] number to replace %n with
  88. * @return {string}
  89. */
  90. function t(app, text, vars, count){
  91. initL10N(app);
  92. var _build = function (text, vars, count) {
  93. return text.replace(/%n/g, count).replace(/{([^{}]*)}/g,
  94. function (a, b) {
  95. var r = vars[b];
  96. return typeof r === 'string' || typeof r === 'number' ? r : a;
  97. }
  98. );
  99. };
  100. var translation = text;
  101. if( typeof( t.cache[app][text] ) !== 'undefined' ){
  102. translation = t.cache[app][text];
  103. }
  104. if(typeof vars === 'object' || count !== undefined ) {
  105. return _build(translation, vars, count);
  106. } else {
  107. return translation;
  108. }
  109. }
  110. t.cache = {};
  111. // different apps might or might not redefine the nplurals function correctly
  112. // this is to make sure that a "broken" app doesn't mess up with the
  113. // other app's plural function
  114. t.plural_function = {};
  115. /**
  116. * translate a string
  117. * @param {string} app the id of the app for which to translate the string
  118. * @param {string} text_singular the string to translate for exactly one object
  119. * @param {string} text_plural the string to translate for n objects
  120. * @param {number} count number to determine whether to use singular or plural
  121. * @param [vars] FIXME
  122. * @return {string} Translated string
  123. */
  124. function n(app, text_singular, text_plural, count, vars) {
  125. initL10N(app);
  126. var identifier = '_' + text_singular + '_::_' + text_plural + '_';
  127. if( typeof( t.cache[app][identifier] ) !== 'undefined' ){
  128. var translation = t.cache[app][identifier];
  129. if ($.isArray(translation)) {
  130. var plural = t.plural_function[app](count);
  131. return t(app, translation[plural.plural], vars, count);
  132. }
  133. }
  134. if(count === 1) {
  135. return t(app, text_singular, vars, count);
  136. }
  137. else{
  138. return t(app, text_plural, vars, count);
  139. }
  140. }
  141. /**
  142. * Sanitizes a HTML string by replacing all potential dangerous characters with HTML entities
  143. * @param {string} s String to sanitize
  144. * @return {string} Sanitized string
  145. */
  146. function escapeHTML(s) {
  147. return s.toString().split('&').join('&amp;').split('<').join('&lt;').split('"').join('&quot;');
  148. }
  149. /**
  150. * Get the path to download a file
  151. * @param {string} file The filename
  152. * @param {string} dir The directory the file is in - e.g. $('#dir').val()
  153. * @return {string} Path to download the file
  154. * @deprecated use Files.getDownloadURL() instead
  155. */
  156. function fileDownloadPath(dir, file) {
  157. return OC.filePath('files', 'ajax', 'download.php')+'?files='+encodeURIComponent(file)+'&dir='+encodeURIComponent(dir);
  158. }
  159. var OC={
  160. PERMISSION_CREATE:4,
  161. PERMISSION_READ:1,
  162. PERMISSION_UPDATE:2,
  163. PERMISSION_DELETE:8,
  164. PERMISSION_SHARE:16,
  165. PERMISSION_ALL:31,
  166. webroot:oc_webroot,
  167. appswebroots:(typeof oc_appswebroots !== 'undefined') ? oc_appswebroots:false,
  168. currentUser:(typeof oc_current_user!=='undefined')?oc_current_user:false,
  169. coreApps:['', 'admin','log','search','settings','core','3rdparty'],
  170. /**
  171. * Get an absolute url to a file in an app
  172. * @param {string} app the id of the app the file belongs to
  173. * @param {string} file the file path relative to the app folder
  174. * @return {string} Absolute URL to a file
  175. */
  176. linkTo:function(app,file){
  177. return OC.filePath(app,'',file);
  178. },
  179. /**
  180. * Creates a relative url for remote use
  181. * @param {string} service id
  182. * @return {string} the url
  183. */
  184. linkToRemoteBase:function(service) {
  185. return OC.webroot + '/remote.php/' + service;
  186. },
  187. /**
  188. * @brief Creates an absolute url for remote use
  189. * @param {string} service id
  190. * @return {string} the url
  191. */
  192. linkToRemote:function(service) {
  193. return window.location.protocol + '//' + window.location.host + OC.linkToRemoteBase(service);
  194. },
  195. /**
  196. * Generates the absolute url for the given relative url, which can contain parameters.
  197. * @param {string} url
  198. * @param params
  199. * @return {string} Absolute URL for the given relative URL
  200. */
  201. generateUrl: function(url, params) {
  202. var _build = function (text, vars) {
  203. return text.replace(/{([^{}]*)}/g,
  204. function (a, b) {
  205. var r = vars[b];
  206. return typeof r === 'string' || typeof r === 'number' ? r : a;
  207. }
  208. );
  209. };
  210. if (url.charAt(0) !== '/') {
  211. url = '/' + url;
  212. }
  213. return OC.webroot + '/index.php' + _build(url, params);
  214. },
  215. /**
  216. * Get the absolute url for a file in an app
  217. * @param {string} app the id of the app
  218. * @param {string} type the type of the file to link to (e.g. css,img,ajax.template)
  219. * @param {string} file the filename
  220. * @return {string} Absolute URL for a file in an app
  221. */
  222. filePath:function(app,type,file){
  223. var isCore=OC.coreApps.indexOf(app)!==-1,
  224. link=OC.webroot;
  225. if((file.substring(file.length-3) === 'php' || file.substring(file.length-3) === 'css') && !isCore){
  226. link+='/index.php/apps/' + app;
  227. if (file != 'index.php') {
  228. link+='/';
  229. if(type){
  230. link+=encodeURI(type + '/');
  231. }
  232. link+= file;
  233. }
  234. }else if(file.substring(file.length-3) !== 'php' && !isCore){
  235. link=OC.appswebroots[app];
  236. if(type){
  237. link+= '/'+type+'/';
  238. }
  239. if(link.substring(link.length-1) !== '/'){
  240. link+='/';
  241. }
  242. link+=file;
  243. }else{
  244. if ((app == 'settings' || app == 'core' || app == 'search') && type == 'ajax') {
  245. link+='/index.php/';
  246. }
  247. else {
  248. link+='/';
  249. }
  250. if(!isCore){
  251. link+='apps/';
  252. }
  253. if (app !== '') {
  254. app+='/';
  255. link+=app;
  256. }
  257. if(type){
  258. link+=type+'/';
  259. }
  260. link+=file;
  261. }
  262. return link;
  263. },
  264. /**
  265. * Redirect to the target URL, can also be used for downloads.
  266. * @param {string} targetURL URL to redirect to
  267. */
  268. redirect: function(targetURL) {
  269. window.location = targetURL;
  270. },
  271. /**
  272. * get the absolute path to an image file
  273. * if no extension is given for the image, it will automatically decide
  274. * between .png and .svg based on what the browser supports
  275. * @param {string} app the app id to which the image belongs
  276. * @param {string} file the name of the image file
  277. * @return {string}
  278. */
  279. imagePath:function(app,file){
  280. if(file.indexOf('.')==-1){//if no extension is given, use png or svg depending on browser support
  281. file+=(OC.Util.hasSVGSupport())?'.svg':'.png';
  282. }
  283. return OC.filePath(app,'img',file);
  284. },
  285. /**
  286. * Load a script for the server and load it. If the script is already loaded,
  287. * the event handler will be called directly
  288. * @param {string} app the app id to which the script belongs
  289. * @param {string} script the filename of the script
  290. * @param ready event handler to be called when the script is loaded
  291. */
  292. addScript:function(app,script,ready){
  293. var deferred, path=OC.filePath(app,'js',script+'.js');
  294. if(!OC.addScript.loaded[path]){
  295. if(ready){
  296. deferred=$.getScript(path,ready);
  297. }else{
  298. deferred=$.getScript(path);
  299. }
  300. OC.addScript.loaded[path]=deferred;
  301. }else{
  302. if(ready){
  303. ready();
  304. }
  305. }
  306. return OC.addScript.loaded[path];
  307. },
  308. /**
  309. * Loads a CSS file
  310. * @param {string} app the app id to which the css style belongs
  311. * @param {string} style the filename of the css file
  312. */
  313. addStyle:function(app,style){
  314. var path=OC.filePath(app,'css',style+'.css');
  315. if(OC.addStyle.loaded.indexOf(path)===-1){
  316. OC.addStyle.loaded.push(path);
  317. if (document.createStyleSheet) {
  318. document.createStyleSheet(path);
  319. } else {
  320. style=$('<link rel="stylesheet" type="text/css" href="'+path+'"/>');
  321. $('head').append(style);
  322. }
  323. }
  324. },
  325. /**
  326. * @todo Write the documentation
  327. */
  328. basename: function(path) {
  329. return path.replace(/\\/g,'/').replace( /.*\//, '' );
  330. },
  331. /**
  332. * @todo Write the documentation
  333. */
  334. dirname: function(path) {
  335. return path.replace(/\\/g,'/').replace(/\/[^\/]*$/, '');
  336. },
  337. /**
  338. * Do a search query and display the results
  339. * @param {string} query the search query
  340. */
  341. search:function(query){
  342. if(query){
  343. OC.addStyle('search','results');
  344. $.getJSON(OC.filePath('search','ajax','search.php')+'?query='+encodeURIComponent(query), function(results){
  345. OC.search.lastResults=results;
  346. OC.search.showResults(results);
  347. });
  348. }
  349. },
  350. dialogs:OCdialogs,
  351. mtime2date:function(mtime) {
  352. mtime = parseInt(mtime,10);
  353. var date = new Date(1000*mtime);
  354. return date.getDate()+'.'+(date.getMonth()+1)+'.'+date.getFullYear()+', '+date.getHours()+':'+date.getMinutes();
  355. },
  356. /**
  357. * Parses a URL query string into a JS map
  358. * @param {string} queryString query string in the format param1=1234&param2=abcde&param3=xyz
  359. * @return map containing key/values matching the URL parameters
  360. */
  361. parseQueryString:function(queryString){
  362. var parts,
  363. pos,
  364. components,
  365. result = {},
  366. key,
  367. value;
  368. if (!queryString){
  369. return null;
  370. }
  371. pos = queryString.indexOf('?');
  372. if (pos >= 0){
  373. queryString = queryString.substr(pos + 1);
  374. }
  375. parts = queryString.replace(/\+/g, '%20').split('&');
  376. for (var i = 0; i < parts.length; i++){
  377. // split on first equal sign
  378. var part = parts[i];
  379. pos = part.indexOf('=');
  380. if (pos >= 0) {
  381. components = [
  382. part.substr(0, pos),
  383. part.substr(pos + 1)
  384. ];
  385. }
  386. else {
  387. // key only
  388. components = [part];
  389. }
  390. if (!components.length){
  391. continue;
  392. }
  393. key = decodeURIComponent(components[0]);
  394. if (!key){
  395. continue;
  396. }
  397. // if equal sign was there, return string
  398. if (components.length > 1) {
  399. result[key] = decodeURIComponent(components[1]);
  400. }
  401. // no equal sign => null value
  402. else {
  403. result[key] = null;
  404. }
  405. }
  406. return result;
  407. },
  408. /**
  409. * Builds a URL query from a JS map.
  410. * @param params parameter map
  411. * @return {string} String containing a URL query (without question) mark
  412. */
  413. buildQueryString: function(params) {
  414. if (!params) {
  415. return '';
  416. }
  417. return $.map(params, function(value, key) {
  418. var s = encodeURIComponent(key);
  419. if (value !== null && typeof(value) !== 'undefined') {
  420. s += '=' + encodeURIComponent(value);
  421. }
  422. return s;
  423. }).join('&');
  424. },
  425. /**
  426. * Opens a popup with the setting for an app.
  427. * @param {string} appid The ID of the app e.g. 'calendar', 'contacts' or 'files'.
  428. * @param {boolean|string} loadJS If true 'js/settings.js' is loaded. If it's a string
  429. * it will attempt to load a script by that name in the 'js' directory.
  430. * @param {boolean} [cache] If true the javascript file won't be forced refreshed. Defaults to true.
  431. * @param {string} [scriptName] The name of the PHP file to load. Defaults to 'settings.php' in
  432. * the root of the app directory hierarchy.
  433. */
  434. appSettings:function(args) {
  435. if(typeof args === 'undefined' || typeof args.appid === 'undefined') {
  436. throw { name: 'MissingParameter', message: 'The parameter appid is missing' };
  437. }
  438. var props = {scriptName:'settings.php', cache:true};
  439. $.extend(props, args);
  440. var settings = $('#appsettings');
  441. if(settings.length === 0) {
  442. throw { name: 'MissingDOMElement', message: 'There has be be an element with id "appsettings" for the popup to show.' };
  443. }
  444. var popup = $('#appsettings_popup');
  445. if(popup.length === 0) {
  446. $('body').prepend('<div class="popup hidden" id="appsettings_popup"></div>');
  447. popup = $('#appsettings_popup');
  448. popup.addClass(settings.hasClass('topright') ? 'topright' : 'bottomleft');
  449. }
  450. if(popup.is(':visible')) {
  451. popup.hide().remove();
  452. } else {
  453. var arrowclass = settings.hasClass('topright') ? 'up' : 'left';
  454. var jqxhr = $.get(OC.filePath(props.appid, '', props.scriptName), function(data) {
  455. popup.html(data).ready(function() {
  456. popup.prepend('<span class="arrow '+arrowclass+'"></span><h2>'+t('core', 'Settings')+'</h2><a class="close svg"></a>').show();
  457. popup.find('.close').bind('click', function() {
  458. popup.remove();
  459. });
  460. if(typeof props.loadJS !== 'undefined') {
  461. var scriptname;
  462. if(props.loadJS === true) {
  463. scriptname = 'settings.js';
  464. } else if(typeof props.loadJS === 'string') {
  465. scriptname = props.loadJS;
  466. } else {
  467. throw { name: 'InvalidParameter', message: 'The "loadJS" parameter must be either boolean or a string.' };
  468. }
  469. if(props.cache) {
  470. $.ajaxSetup({cache: true});
  471. }
  472. $.getScript(OC.filePath(props.appid, 'js', scriptname))
  473. .fail(function(jqxhr, settings, e) {
  474. throw e;
  475. });
  476. }
  477. if(!OC.Util.hasSVGSupport()) {
  478. OC.Util.replaceSVG();
  479. }
  480. }).show();
  481. }, 'html');
  482. }
  483. },
  484. /**
  485. * For menu toggling
  486. * @todo Write documentation
  487. */
  488. registerMenu: function($toggle, $menuEl) {
  489. $menuEl.addClass('menu');
  490. $toggle.addClass('menutoggle');
  491. $toggle.on('click.menu', function(event) {
  492. if ($menuEl.is(OC._currentMenu)) {
  493. $menuEl.hide();
  494. OC._currentMenu = null;
  495. OC._currentMenuToggle = null;
  496. return false;
  497. }
  498. // another menu was open?
  499. else if (OC._currentMenu) {
  500. // close it
  501. OC._currentMenu.hide();
  502. }
  503. $menuEl.show();
  504. OC._currentMenu = $menuEl;
  505. OC._currentMenuToggle = $toggle;
  506. return false;
  507. });
  508. },
  509. /**
  510. * @todo Write documentation
  511. */
  512. unregisterMenu: function($toggle, $menuEl) {
  513. // close menu if opened
  514. if ($menuEl.is(OC._currentMenu)) {
  515. $menuEl.hide();
  516. OC._currentMenu = null;
  517. OC._currentMenuToggle = null;
  518. }
  519. $toggle.off('click.menu').removeClass('menutoggle');
  520. $menuEl.removeClass('menu');
  521. },
  522. /**
  523. * Wrapper for matchMedia
  524. *
  525. * This is makes it possible for unit tests to
  526. * stub matchMedia (which doesn't work in PhantomJS)
  527. * @todo Write documentation
  528. */
  529. _matchMedia: function(media) {
  530. if (window.matchMedia) {
  531. return window.matchMedia(media);
  532. }
  533. return false;
  534. }
  535. };
  536. OC.search.customResults={};
  537. OC.search.currentResult=-1;
  538. OC.search.lastQuery='';
  539. OC.search.lastResults={};
  540. OC.addStyle.loaded=[];
  541. OC.addScript.loaded=[];
  542. /**
  543. * @todo Write documentation
  544. */
  545. OC.msg={
  546. /**
  547. * @param selector
  548. * @todo Write documentation
  549. */
  550. startSaving:function(selector){
  551. OC.msg.startAction(selector, t('core', 'Saving...'));
  552. },
  553. /**
  554. * @param selector
  555. * @param data
  556. * @todo Write documentation
  557. */
  558. finishedSaving:function(selector, data){
  559. OC.msg.finishedAction(selector, data);
  560. },
  561. /**
  562. * @param selector
  563. * @param {string} message Message to display
  564. * @todo WRite documentation
  565. */
  566. startAction:function(selector, message){
  567. $(selector)
  568. .html( message )
  569. .removeClass('success')
  570. .removeClass('error')
  571. .stop(true, true)
  572. .show();
  573. },
  574. /**
  575. * @param selector
  576. * @param data
  577. * @todo Write documentation
  578. */
  579. finishedAction:function(selector, data){
  580. if( data.status === "success" ){
  581. $(selector).html( data.data.message )
  582. .addClass('success')
  583. .stop(true, true)
  584. .delay(3000)
  585. .fadeOut(900);
  586. }else{
  587. $(selector).html( data.data.message ).addClass('error');
  588. }
  589. }
  590. };
  591. /**
  592. * @todo Write documentation
  593. */
  594. OC.Notification={
  595. queuedNotifications: [],
  596. getDefaultNotificationFunction: null,
  597. /**
  598. * @param callback
  599. * @todo Write documentation
  600. */
  601. setDefault: function(callback) {
  602. OC.Notification.getDefaultNotificationFunction = callback;
  603. },
  604. /**
  605. * Hides a notification
  606. * @param callback
  607. * @todo Write documentation
  608. */
  609. hide: function(callback) {
  610. $('#notification').fadeOut('400', function(){
  611. if (OC.Notification.isHidden()) {
  612. if (OC.Notification.getDefaultNotificationFunction) {
  613. OC.Notification.getDefaultNotificationFunction.call();
  614. }
  615. }
  616. if (callback) {
  617. callback.call();
  618. }
  619. $('#notification').empty();
  620. if(OC.Notification.queuedNotifications.length > 0){
  621. OC.Notification.showHtml(OC.Notification.queuedNotifications[0]);
  622. OC.Notification.queuedNotifications.shift();
  623. }
  624. });
  625. },
  626. /**
  627. * Shows a notification as HTML without being sanitized before.
  628. * If you pass unsanitized user input this may lead to a XSS vulnerability.
  629. * Consider using show() instead of showHTML()
  630. * @param {string} html Message to display
  631. */
  632. showHtml: function(html) {
  633. var notification = $('#notification');
  634. if((notification.filter('span.undo').length == 1) || OC.Notification.isHidden()){
  635. notification.html(html);
  636. notification.fadeIn().css("display","inline");
  637. }else{
  638. OC.Notification.queuedNotifications.push(html);
  639. }
  640. },
  641. /**
  642. * Shows a sanitized notification
  643. * @param {string} text Message to display
  644. */
  645. show: function(text) {
  646. var notification = $('#notification');
  647. if((notification.filter('span.undo').length == 1) || OC.Notification.isHidden()){
  648. notification.text(text);
  649. notification.fadeIn().css("display","inline");
  650. }else{
  651. OC.Notification.queuedNotifications.push($('<div/>').text(text).html());
  652. }
  653. },
  654. /**
  655. * Returns whether a notification is hidden.
  656. * @return {boolean}
  657. */
  658. isHidden: function() {
  659. return ($("#notification").text() === '');
  660. }
  661. };
  662. /**
  663. * @todo Write documentation
  664. */
  665. OC.Breadcrumb={
  666. container:null,
  667. /**
  668. * @todo Write documentation
  669. * @param dir
  670. * @param leafName
  671. * @param leafLink
  672. */
  673. show:function(dir, leafName, leafLink){
  674. if(!this.container){//default
  675. this.container=$('#controls');
  676. }
  677. this._show(this.container, dir, leafName, leafLink);
  678. },
  679. _show:function(container, dir, leafname, leaflink){
  680. var self = this;
  681. this._clear(container);
  682. // show home + path in subdirectories
  683. if (dir) {
  684. //add home
  685. var link = OC.linkTo('files','index.php');
  686. var crumb=$('<div/>');
  687. crumb.addClass('crumb');
  688. var crumbLink=$('<a/>');
  689. crumbLink.attr('href',link);
  690. var crumbImg=$('<img/>');
  691. crumbImg.attr('src',OC.imagePath('core','places/home'));
  692. crumbLink.append(crumbImg);
  693. crumb.append(crumbLink);
  694. container.prepend(crumb);
  695. //add path parts
  696. var segments = dir.split('/');
  697. var pathurl = '';
  698. jQuery.each(segments, function(i,name) {
  699. if (name !== '') {
  700. pathurl = pathurl+'/'+name;
  701. var link = OC.linkTo('files','index.php')+'?dir='+encodeURIComponent(pathurl);
  702. self._push(container, name, link);
  703. }
  704. });
  705. }
  706. //add leafname
  707. if (leafname && leaflink) {
  708. this._push(container, leafname, leaflink);
  709. }
  710. },
  711. /**
  712. * @todo Write documentation
  713. * @param {string} name
  714. * @param {string} link
  715. */
  716. push:function(name, link){
  717. if(!this.container){//default
  718. this.container=$('#controls');
  719. }
  720. return this._push(OC.Breadcrumb.container, name, link);
  721. },
  722. _push:function(container, name, link){
  723. var crumb=$('<div/>');
  724. crumb.addClass('crumb').addClass('last');
  725. var crumbLink=$('<a/>');
  726. crumbLink.attr('href',link);
  727. crumbLink.text(name);
  728. crumb.append(crumbLink);
  729. var existing=container.find('div.crumb');
  730. if(existing.length){
  731. existing.removeClass('last');
  732. existing.last().after(crumb);
  733. }else{
  734. container.prepend(crumb);
  735. }
  736. return crumb;
  737. },
  738. /**
  739. * @todo Write documentation
  740. */
  741. pop:function(){
  742. if(!this.container){//default
  743. this.container=$('#controls');
  744. }
  745. this.container.find('div.crumb').last().remove();
  746. this.container.find('div.crumb').last().addClass('last');
  747. },
  748. /**
  749. * @todo Write documentation
  750. */
  751. clear:function(){
  752. if(!this.container){//default
  753. this.container=$('#controls');
  754. }
  755. this._clear(this.container);
  756. },
  757. _clear:function(container) {
  758. container.find('div.crumb').remove();
  759. }
  760. };
  761. if(typeof localStorage !=='undefined' && localStorage !== null){
  762. /**
  763. * User and instance aware localstorage
  764. */
  765. OC.localStorage={
  766. namespace:'oc_'+OC.currentUser+'_'+OC.webroot+'_',
  767. /**
  768. * Whether the storage contains items
  769. * @param {string} name
  770. * @return {boolean}
  771. */
  772. hasItem:function(name){
  773. return OC.localStorage.getItem(name)!==null;
  774. },
  775. /**
  776. * Add an item to the storage
  777. * @param {string} name
  778. * @param {string} item
  779. */
  780. setItem:function(name,item){
  781. return localStorage.setItem(OC.localStorage.namespace+name,JSON.stringify(item));
  782. },
  783. /**
  784. * Removes an item from the storage
  785. * @param {string} name
  786. * @param {string} item
  787. */
  788. removeItem:function(name,item){
  789. return localStorage.removeItem(OC.localStorage.namespace+name);
  790. },
  791. /**
  792. * Get an item from the storage
  793. * @param {string} name
  794. * @return {null|string}
  795. */
  796. getItem:function(name){
  797. var item = localStorage.getItem(OC.localStorage.namespace+name);
  798. if(item === null) {
  799. return null;
  800. } else if (typeof JSON === 'undefined') {
  801. //fallback to jquery for IE6/7/8
  802. return $.parseJSON(item);
  803. } else {
  804. return JSON.parse(item);
  805. }
  806. }
  807. };
  808. }else{
  809. //dummy localstorage
  810. OC.localStorage={
  811. hasItem:function(){
  812. return false;
  813. },
  814. setItem:function(){
  815. return false;
  816. },
  817. getItem:function(){
  818. return null;
  819. }
  820. };
  821. }
  822. /**
  823. * check if the browser support svg images
  824. * @return {boolean}
  825. */
  826. function SVGSupport() {
  827. return SVGSupport.checkMimeType.correct && !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', "svg").createSVGRect;
  828. }
  829. SVGSupport.checkMimeType=function(){
  830. $.ajax({
  831. url: OC.imagePath('core','breadcrumb.svg'),
  832. success:function(data,text,xhr){
  833. var headerParts=xhr.getAllResponseHeaders().split("\n");
  834. var headers={};
  835. $.each(headerParts,function(i,text){
  836. if(text){
  837. var parts=text.split(':',2);
  838. if(parts.length===2){
  839. var value=parts[1].trim();
  840. if(value[0]==='"'){
  841. value=value.substr(1,value.length-2);
  842. }
  843. headers[parts[0].toLowerCase()]=value;
  844. }
  845. }
  846. });
  847. if(headers["content-type"]!=='image/svg+xml'){
  848. OC.Util.replaceSVG();
  849. SVGSupport.checkMimeType.correct=false;
  850. }
  851. }
  852. });
  853. };
  854. SVGSupport.checkMimeType.correct=true;
  855. /**
  856. * Replace all svg images with png for browser compatibility
  857. * @param $el
  858. * @deprecated use OC.Util.replaceSVG instead
  859. */
  860. function replaceSVG($el){
  861. return OC.Util.replaceSVG($el);
  862. }
  863. /**
  864. * prototypical inheritance functions
  865. * @todo Write documentation
  866. * usage:
  867. * MySubObject=object(MyObject)
  868. */
  869. function object(o) {
  870. function F() {}
  871. F.prototype = o;
  872. return new F();
  873. }
  874. /**
  875. * Fills height of window. (more precise than height: 100%;)
  876. * @param selector
  877. */
  878. function fillHeight(selector) {
  879. if (selector.length === 0) {
  880. return;
  881. }
  882. var height = parseFloat($(window).height())-selector.offset().top;
  883. selector.css('height', height + 'px');
  884. if(selector.outerHeight() > selector.height()){
  885. selector.css('height', height-(selector.outerHeight()-selector.height()) + 'px');
  886. }
  887. console.warn("This function is deprecated! Use CSS instead");
  888. }
  889. /**
  890. * Fills height and width of window. (more precise than height: 100%; or width: 100%;)
  891. * @param selector
  892. */
  893. function fillWindow(selector) {
  894. if (selector.length === 0) {
  895. return;
  896. }
  897. fillHeight(selector);
  898. var width = parseFloat($(window).width())-selector.offset().left;
  899. selector.css('width', width + 'px');
  900. if(selector.outerWidth() > selector.width()){
  901. selector.css('width', width-(selector.outerWidth()-selector.width()) + 'px');
  902. }
  903. console.warn("This function is deprecated! Use CSS instead");
  904. }
  905. /**
  906. * Initializes core
  907. */
  908. function initCore() {
  909. /**
  910. * Calls the server periodically to ensure that session doesn't
  911. * time out
  912. */
  913. function initSessionHeartBeat(){
  914. // interval in seconds
  915. var interval = 900;
  916. if (oc_config.session_lifetime) {
  917. interval = Math.floor(oc_config.session_lifetime / 2);
  918. }
  919. // minimum one minute
  920. if (interval < 60) {
  921. interval = 60;
  922. }
  923. var url = OC.generateUrl('/heartbeat');
  924. setInterval(function(){
  925. $.post(url);
  926. }, interval * 1000);
  927. }
  928. // session heartbeat (defaults to enabled)
  929. if (typeof(oc_config.session_keepalive) === 'undefined' ||
  930. !!oc_config.session_keepalive) {
  931. initSessionHeartBeat();
  932. }
  933. if(!OC.Util.hasSVGSupport()){ //replace all svg images with png images for browser that dont support svg
  934. OC.Util.replaceSVG();
  935. }else{
  936. SVGSupport.checkMimeType();
  937. }
  938. $('form.searchbox').submit(function(event){
  939. event.preventDefault();
  940. });
  941. $('#searchbox').keyup(function(event){
  942. if(event.keyCode===13){//enter
  943. if(OC.search.currentResult>-1){
  944. var result=$('#searchresults tr.result a')[OC.search.currentResult];
  945. window.location = $(result).attr('href');
  946. }
  947. }else if(event.keyCode===38){//up
  948. if(OC.search.currentResult>0){
  949. OC.search.currentResult--;
  950. OC.search.renderCurrent();
  951. }
  952. }else if(event.keyCode===40){//down
  953. if(OC.search.lastResults.length>OC.search.currentResult+1){
  954. OC.search.currentResult++;
  955. OC.search.renderCurrent();
  956. }
  957. }else if(event.keyCode===27){//esc
  958. OC.search.hide();
  959. if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
  960. FileList.unfilter();
  961. }
  962. }else{
  963. var query=$('#searchbox').val();
  964. if(OC.search.lastQuery!==query){
  965. OC.search.lastQuery=query;
  966. OC.search.currentResult=-1;
  967. if (FileList && typeof FileList.filter === 'function') { //TODO add hook system
  968. FileList.filter(query);
  969. }
  970. if(query.length>2){
  971. OC.search(query);
  972. }else{
  973. if(OC.search.hide){
  974. OC.search.hide();
  975. }
  976. }
  977. }
  978. }
  979. });
  980. var setShowPassword = function(input, label) {
  981. input.showPassword().keyup();
  982. };
  983. setShowPassword($('#adminpass'), $('label[for=show]'));
  984. setShowPassword($('#pass2'), $('label[for=personal-show]'));
  985. setShowPassword($('#dbpass'), $('label[for=dbpassword]'));
  986. //use infield labels
  987. $("label.infield").inFieldLabels({
  988. pollDuration: 100
  989. });
  990. var checkShowCredentials = function() {
  991. var empty = false;
  992. $('input#user, input#password').each(function() {
  993. if ($(this).val() === '') {
  994. empty = true;
  995. }
  996. });
  997. if(empty) {
  998. $('#submit').fadeOut();
  999. $('#remember_login').hide();
  1000. $('#remember_login+label').fadeOut();
  1001. } else {
  1002. $('#submit').fadeIn();
  1003. $('#remember_login').show();
  1004. $('#remember_login+label').fadeIn();
  1005. }
  1006. };
  1007. // hide log in button etc. when form fields not filled
  1008. // commented out due to some browsers having issues with it
  1009. // checkShowCredentials();
  1010. // $('input#user, input#password').keyup(checkShowCredentials);
  1011. // user menu
  1012. $('#settings #expand').keydown(function(event) {
  1013. if (event.which === 13 || event.which === 32) {
  1014. $('#expand').click();
  1015. }
  1016. });
  1017. $('#settings #expand').click(function(event) {
  1018. $('#settings #expanddiv').slideToggle(200);
  1019. event.stopPropagation();
  1020. });
  1021. $('#settings #expanddiv').click(function(event){
  1022. event.stopPropagation();
  1023. });
  1024. //hide the user menu when clicking outside it
  1025. $(document).click(function(){
  1026. $('#settings #expanddiv').slideUp(200);
  1027. });
  1028. // all the tipsy stuff needs to be here (in reverse order) to work
  1029. $('.displayName .action').tipsy({gravity:'se', fade:true, live:true});
  1030. $('.password .action').tipsy({gravity:'se', fade:true, live:true});
  1031. $('#upload').tipsy({gravity:'w', fade:true});
  1032. $('.selectedActions a').tipsy({gravity:'s', fade:true, live:true});
  1033. $('a.action.delete').tipsy({gravity:'e', fade:true, live:true});
  1034. $('a.action').tipsy({gravity:'s', fade:true, live:true});
  1035. $('td .modified').tipsy({gravity:'s', fade:true, live:true});
  1036. $('input').tipsy({gravity:'w', fade:true});
  1037. // toggle for menus
  1038. $(document).on('mouseup.closemenus', function(event) {
  1039. var $el = $(event.target);
  1040. if ($el.closest('.menu').length || $el.closest('.menutoggle').length) {
  1041. // don't close when clicking on the menu directly or a menu toggle
  1042. return false;
  1043. }
  1044. if (OC._currentMenu) {
  1045. OC._currentMenu.hide();
  1046. }
  1047. OC._currentMenu = null;
  1048. OC._currentMenuToggle = null;
  1049. });
  1050. /**
  1051. * Set up the main menu toggle to react to media query changes.
  1052. * If the screen is small enough, the main menu becomes a toggle.
  1053. * If the screen is bigger, the main menu is not a toggle any more.
  1054. */
  1055. function setupMainMenu() {
  1056. // toggle the navigation on mobile
  1057. if (!OC._matchMedia) {
  1058. return;
  1059. }
  1060. var mq = OC._matchMedia('(max-width: 768px)');
  1061. var lastMatch = mq.matches;
  1062. var $toggle = $('#header #owncloud');
  1063. var $navigation = $('#navigation');
  1064. function updateMainMenu() {
  1065. // mobile mode ?
  1066. if (lastMatch && !$toggle.hasClass('menutoggle')) {
  1067. // init the menu
  1068. OC.registerMenu($toggle, $navigation);
  1069. $toggle.data('oldhref', $toggle.attr('href'));
  1070. $toggle.attr('href', '#');
  1071. $navigation.hide();
  1072. }
  1073. else {
  1074. OC.unregisterMenu($toggle, $navigation);
  1075. $toggle.attr('href', $toggle.data('oldhref'));
  1076. $navigation.show();
  1077. }
  1078. }
  1079. updateMainMenu();
  1080. // TODO: debounce this
  1081. $(window).resize(function() {
  1082. if (lastMatch !== mq.matches) {
  1083. lastMatch = mq.matches;
  1084. updateMainMenu();
  1085. }
  1086. });
  1087. }
  1088. if (window.matchMedia) {
  1089. setupMainMenu();
  1090. }
  1091. }
  1092. $(document).ready(initCore);
  1093. /**
  1094. * Filter Jquery selector by attribute value
  1095. */
  1096. $.fn.filterAttr = function(attr_name, attr_value) {
  1097. return this.filter(function() { return $(this).attr(attr_name) === attr_value; });
  1098. };
  1099. /**
  1100. * Returns a human readable file size
  1101. * @param {number} size Size in bytes
  1102. * @return {string}
  1103. */
  1104. function humanFileSize(size) {
  1105. var humanList = ['B', 'kB', 'MB', 'GB', 'TB'];
  1106. // Calculate Log with base 1024: size = 1024 ** order
  1107. var order = size?Math.floor(Math.log(size) / Math.log(1024)):0;
  1108. // Stay in range of the byte sizes that are defined
  1109. order = Math.min(humanList.length - 1, order);
  1110. var readableFormat = humanList[order];
  1111. var relativeSize = (size / Math.pow(1024, order)).toFixed(1);
  1112. if(order < 2){
  1113. relativeSize = parseFloat(relativeSize).toFixed(0);
  1114. }
  1115. else if(relativeSize.substr(relativeSize.length-2,2)==='.0'){
  1116. relativeSize=relativeSize.substr(0,relativeSize.length-2);
  1117. }
  1118. return relativeSize + ' ' + readableFormat;
  1119. }
  1120. /**
  1121. * Format an UNIX timestamp to a human understandable format
  1122. * @param {number} date UNIX timestamp
  1123. * @return {string} Human readable format
  1124. */
  1125. function formatDate(date){
  1126. if(typeof date=='number'){
  1127. date=new Date(date);
  1128. }
  1129. return $.datepicker.formatDate(datepickerFormatDate, date)+' '+date.getHours()+':'+((date.getMinutes()<10)?'0':'')+date.getMinutes();
  1130. }
  1131. //
  1132. /**
  1133. * Get the value of a URL parameter
  1134. * @link http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery
  1135. * @param {string} name URL parameter
  1136. * @return {string}
  1137. */
  1138. function getURLParameter(name) {
  1139. return decodeURI(
  1140. (RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1]
  1141. );
  1142. }
  1143. /**
  1144. * Takes an absolute timestamp and return a string with a human-friendly relative date
  1145. * @param {number} timestamp A Unix timestamp
  1146. */
  1147. function relative_modified_date(timestamp) {
  1148. var timeDiff = Math.round((new Date()).getTime() / 1000) - timestamp;
  1149. var diffMinutes = Math.round(timeDiff/60);
  1150. var diffHours = Math.round(diffMinutes/60);
  1151. var diffDays = Math.round(diffHours/24);
  1152. var diffMonths = Math.round(diffDays/31);
  1153. if(timeDiff < 60) { return t('core','seconds ago'); }
  1154. else if(timeDiff < 3600) { return n('core','%n minute ago', '%n minutes ago', diffMinutes); }
  1155. else if(timeDiff < 86400) { return n('core', '%n hour ago', '%n hours ago', diffHours); }
  1156. else if(timeDiff < 86400) { return t('core','today'); }
  1157. else if(timeDiff < 172800) { return t('core','yesterday'); }
  1158. else if(timeDiff < 2678400) { return n('core', '%n day ago', '%n days ago', diffDays); }
  1159. else if(timeDiff < 5184000) { return t('core','last month'); }
  1160. else if(timeDiff < 31556926) { return n('core', '%n month ago', '%n months ago', diffMonths); }
  1161. else if(timeDiff < 63113852) { return t('core','last year'); }
  1162. else { return t('core','years ago'); }
  1163. }
  1164. /**
  1165. * Utility functions
  1166. */
  1167. OC.Util = {
  1168. // TODO: remove original functions from global namespace
  1169. humanFileSize: humanFileSize,
  1170. formatDate: formatDate,
  1171. /**
  1172. * Returns whether the browser supports SVG
  1173. * @return {boolean} true if the browser supports SVG, false otherwise
  1174. */
  1175. // TODO: replace with original function
  1176. hasSVGSupport: SVGSupport,
  1177. /**
  1178. * If SVG is not supported, replaces the given icon's extension
  1179. * from ".svg" to ".png".
  1180. * If SVG is supported, return the image path as is.
  1181. * @param {string} file image path with svg extension
  1182. * @return {string} fixed image path with png extension if SVG is not supported
  1183. */
  1184. replaceSVGIcon: function(file) {
  1185. if (!OC.Util.hasSVGSupport()) {
  1186. var i = file.lastIndexOf('.svg');
  1187. if (i >= 0) {
  1188. file = file.substr(0, i) + '.png' + file.substr(i+4);
  1189. }
  1190. }
  1191. return file;
  1192. },
  1193. /**
  1194. * Replace SVG images in all elements that have the "svg" class set
  1195. * with PNG images.
  1196. *
  1197. * @param $el root element from which to search, defaults to $('body')
  1198. */
  1199. replaceSVG: function($el) {
  1200. if (!$el) {
  1201. $el = $('body');
  1202. }
  1203. $el.find('img.svg').each(function(index,element){
  1204. element=$(element);
  1205. var src=element.attr('src');
  1206. element.attr('src',src.substr(0, src.length-3) + 'png');
  1207. });
  1208. $el.find('.svg').each(function(index,element){
  1209. element = $(element);
  1210. var background = element.css('background-image');
  1211. if (background){
  1212. var i = background.lastIndexOf('.svg');
  1213. if (i >= 0){
  1214. background = background.substr(0,i) + '.png' + background.substr(i + 4);
  1215. element.css('background-image', background);
  1216. }
  1217. }
  1218. element.find('*').each(function(index, element) {
  1219. element = $(element);
  1220. var background = element.css('background-image');
  1221. if (background) {
  1222. var i = background.lastIndexOf('.svg');
  1223. if(i >= 0){
  1224. background = background.substr(0,i) + '.png' + background.substr(i + 4);
  1225. element.css('background-image', background);
  1226. }
  1227. }
  1228. });
  1229. });
  1230. }
  1231. };
  1232. /**
  1233. * Utility class for the history API,
  1234. * includes fallback to using the URL hash when
  1235. * the browser doesn't support the history API.
  1236. */
  1237. OC.Util.History = {
  1238. _handlers: [],
  1239. /**
  1240. * Push the current URL parameters to the history stack
  1241. * and change the visible URL.
  1242. * Note: this includes a workaround for IE8/IE9 that uses
  1243. * the hash part instead of the search part.
  1244. *
  1245. * @param params to append to the URL, can be either a string
  1246. * or a map
  1247. */
  1248. pushState: function(params) {
  1249. var strParams;
  1250. if (typeof(params) === 'string') {
  1251. strParams = params;
  1252. }
  1253. else {
  1254. strParams = OC.buildQueryString(params);
  1255. }
  1256. if (window.history.pushState) {
  1257. var url = location.pathname + '?' + strParams;
  1258. window.history.pushState(params, '', url);
  1259. }
  1260. // use URL hash for IE8
  1261. else {
  1262. window.location.hash = '?' + strParams;
  1263. // inhibit next onhashchange that just added itself
  1264. // to the event queue
  1265. this._cancelPop = true;
  1266. }
  1267. },
  1268. /**
  1269. * Add a popstate handler
  1270. *
  1271. * @param handler function
  1272. */
  1273. addOnPopStateHandler: function(handler) {
  1274. this._handlers.push(handler);
  1275. },
  1276. /**
  1277. * Parse a query string from the hash part of the URL.
  1278. * (workaround for IE8 / IE9)
  1279. */
  1280. _parseHashQuery: function() {
  1281. var hash = window.location.hash,
  1282. pos = hash.indexOf('?');
  1283. if (pos >= 0) {
  1284. return hash.substr(pos + 1);
  1285. }
  1286. return '';
  1287. },
  1288. _decodeQuery: function(query) {
  1289. return query.replace(/\+/g, ' ');
  1290. },
  1291. /**
  1292. * Parse the query/search part of the URL.
  1293. * Also try and parse it from the URL hash (for IE8)
  1294. *
  1295. * @return map of parameters
  1296. */
  1297. parseUrlQuery: function() {
  1298. var query = this._parseHashQuery(),
  1299. params;
  1300. // try and parse from URL hash first
  1301. if (query) {
  1302. params = OC.parseQueryString(this._decodeQuery(query));
  1303. }
  1304. // else read from query attributes
  1305. if (!params) {
  1306. params = OC.parseQueryString(this._decodeQuery(location.search));
  1307. }
  1308. return params || {};
  1309. },
  1310. _onPopState: function(e) {
  1311. if (this._cancelPop) {
  1312. this._cancelPop = false;
  1313. return;
  1314. }
  1315. var params;
  1316. if (!this._handlers.length) {
  1317. return;
  1318. }
  1319. params = (e && e.state) || this.parseUrlQuery() || {};
  1320. for (var i = 0; i < this._handlers.length; i++) {
  1321. this._handlers[i](params);
  1322. }
  1323. }
  1324. };
  1325. // fallback to hashchange when no history support
  1326. if (window.history.pushState) {
  1327. window.onpopstate = _.bind(OC.Util.History._onPopState, OC.Util.History);
  1328. }
  1329. else {
  1330. $(window).on('hashchange', _.bind(OC.Util.History._onPopState, OC.Util.History));
  1331. }
  1332. /**
  1333. * Get a variable by name
  1334. * @param {string} name
  1335. * @return {*}
  1336. */
  1337. OC.get=function(name) {
  1338. var namespaces = name.split(".");
  1339. var tail = namespaces.pop();
  1340. var context=window;
  1341. for(var i = 0; i < namespaces.length; i++) {
  1342. context = context[namespaces[i]];
  1343. if(!context){
  1344. return false;
  1345. }
  1346. }
  1347. return context[tail];
  1348. };
  1349. /**
  1350. * Set a variable by name
  1351. * @param {string} name
  1352. * @param {*} value
  1353. */
  1354. OC.set=function(name, value) {
  1355. var namespaces = name.split(".");
  1356. var tail = namespaces.pop();
  1357. var context=window;
  1358. for(var i = 0; i < namespaces.length; i++) {
  1359. if(!context[namespaces[i]]){
  1360. context[namespaces[i]]={};
  1361. }
  1362. context = context[namespaces[i]];
  1363. }
  1364. context[tail]=value;
  1365. };
  1366. // fix device width on windows phone
  1367. (function() {
  1368. if ("-ms-user-select" in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/)) {
  1369. var msViewportStyle = document.createElement("style");
  1370. msViewportStyle.appendChild(
  1371. document.createTextNode("@-ms-viewport{width:auto!important}")
  1372. );
  1373. document.getElementsByTagName("head")[0].appendChild(msViewportStyle);
  1374. }
  1375. })();
  1376. /**
  1377. * Namespace for apps
  1378. */
  1379. window.OCA = {};
  1380. /**
  1381. * select a range in an input field
  1382. * @link http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
  1383. * @param {type} start
  1384. * @param {type} end
  1385. */
  1386. jQuery.fn.selectRange = function(start, end) {
  1387. return this.each(function() {
  1388. if (this.setSelectionRange) {
  1389. this.focus();
  1390. this.setSelectionRange(start, end);
  1391. } else if (this.createTextRange) {
  1392. var range = this.createTextRange();
  1393. range.collapse(true);
  1394. range.moveEnd('character', end);
  1395. range.moveStart('character', start);
  1396. range.select();
  1397. }
  1398. });
  1399. };
  1400. /**
  1401. * check if an element exists.
  1402. * allows you to write if ($('#myid').exists()) to increase readability
  1403. * @link http://stackoverflow.com/questions/31044/is-there-an-exists-function-for-jquery
  1404. */
  1405. jQuery.fn.exists = function(){
  1406. return this.length > 0;
  1407. };