js.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742
  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 (
  24. oc_debug !== true || typeof console === "undefined" ||
  25. typeof console.log === "undefined"
  26. ) {
  27. if (!window.console) {
  28. window.console = {};
  29. }
  30. var noOp = function() { };
  31. var methods = ['log', 'debug', 'warn', 'info', 'error', 'assert', 'time', 'timeEnd'];
  32. for (var i = 0; i < methods.length; i++) {
  33. console[methods[i]] = noOp;
  34. }
  35. }
  36. /**
  37. * Sanitizes a HTML string by replacing all potential dangerous characters with HTML entities
  38. * @param {string} s String to sanitize
  39. * @return {string} Sanitized string
  40. */
  41. function escapeHTML(s) {
  42. return s.toString().split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;').split('"').join('&quot;').split('\'').join('&#039;');
  43. }
  44. /**
  45. * Get the path to download a file
  46. * @param {string} file The filename
  47. * @param {string} dir The directory the file is in - e.g. $('#dir').val()
  48. * @return {string} Path to download the file
  49. * @deprecated use Files.getDownloadURL() instead
  50. */
  51. function fileDownloadPath(dir, file) {
  52. return OC.filePath('files', 'ajax', 'download.php')+'?files='+encodeURIComponent(file)+'&dir='+encodeURIComponent(dir);
  53. }
  54. /** @namespace */
  55. var OC={
  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. webroot:oc_webroot,
  65. appswebroots:(typeof oc_appswebroots !== 'undefined') ? oc_appswebroots:false,
  66. currentUser:(typeof oc_current_user!=='undefined')?oc_current_user:false,
  67. config: window.oc_config,
  68. appConfig: window.oc_appconfig || {},
  69. theme: window.oc_defaults || {},
  70. coreApps:['', 'admin','log','core/search','settings','core','3rdparty'],
  71. menuSpeed: 50,
  72. /**
  73. * Get an absolute url to a file in an app
  74. * @param {string} app the id of the app the file belongs to
  75. * @param {string} file the file path relative to the app folder
  76. * @return {string} Absolute URL to a file
  77. */
  78. linkTo:function(app,file){
  79. return OC.filePath(app,'',file);
  80. },
  81. /**
  82. * Creates a relative url for remote use
  83. * @param {string} service id
  84. * @return {string} the url
  85. */
  86. linkToRemoteBase:function(service) {
  87. return OC.webroot + '/remote.php/' + service;
  88. },
  89. /**
  90. * @brief Creates an absolute url for remote use
  91. * @param {string} service id
  92. * @return {string} the url
  93. */
  94. linkToRemote:function(service) {
  95. return window.location.protocol + '//' + window.location.host + OC.linkToRemoteBase(service);
  96. },
  97. /**
  98. * Gets the base path for the given OCS API service.
  99. * @param {string} service name
  100. * @return {string} OCS API base path
  101. */
  102. linkToOCS: function(service) {
  103. return window.location.protocol + '//' + window.location.host + OC.webroot + '/ocs/v1.php/' + service + '/';
  104. },
  105. /**
  106. * Generates the absolute url for the given relative url, which can contain parameters.
  107. * Parameters will be URL encoded automatically.
  108. * @param {string} url
  109. * @param [params] params
  110. * @param [options] options
  111. * @param {bool} [options.escape=true] enable/disable auto escape of placeholders (by default enabled)
  112. * @return {string} Absolute URL for the given relative URL
  113. */
  114. generateUrl: function(url, params, options) {
  115. var defaultOptions = {
  116. escape: true
  117. },
  118. allOptions = options || {};
  119. _.defaults(allOptions, defaultOptions);
  120. var _build = function (text, vars) {
  121. var vars = vars || [];
  122. return text.replace(/{([^{}]*)}/g,
  123. function (a, b) {
  124. var r = (vars[b]);
  125. if(allOptions.escape) {
  126. return (typeof r === 'string' || typeof r === 'number') ? encodeURIComponent(r) : encodeURIComponent(a);
  127. } else {
  128. return (typeof r === 'string' || typeof r === 'number') ? r : a;
  129. }
  130. }
  131. );
  132. };
  133. if (url.charAt(0) !== '/') {
  134. url = '/' + url;
  135. }
  136. // TODO save somewhere whether the webserver is able to skip the index.php to have shorter links (e.g. for sharing)
  137. return OC.webroot + '/index.php' + _build(url, params);
  138. },
  139. /**
  140. * Get the absolute url for a file in an app
  141. * @param {string} app the id of the app
  142. * @param {string} type the type of the file to link to (e.g. css,img,ajax.template)
  143. * @param {string} file the filename
  144. * @return {string} Absolute URL for a file in an app
  145. * @deprecated use OC.generateUrl() instead
  146. */
  147. filePath:function(app,type,file){
  148. var isCore=OC.coreApps.indexOf(app)!==-1,
  149. link=OC.webroot;
  150. if(file.substring(file.length-3) === 'php' && !isCore){
  151. link+='/index.php/apps/' + app;
  152. if (file != 'index.php') {
  153. link+='/';
  154. if(type){
  155. link+=encodeURI(type + '/');
  156. }
  157. link+= file;
  158. }
  159. }else if(file.substring(file.length-3) !== 'php' && !isCore){
  160. link=OC.appswebroots[app];
  161. if(type){
  162. link+= '/'+type+'/';
  163. }
  164. if(link.substring(link.length-1) !== '/'){
  165. link+='/';
  166. }
  167. link+=file;
  168. }else{
  169. if ((app == 'settings' || app == 'core' || app == 'search') && type == 'ajax') {
  170. link+='/index.php/';
  171. }
  172. else {
  173. link+='/';
  174. }
  175. if(!isCore){
  176. link+='apps/';
  177. }
  178. if (app !== '') {
  179. app+='/';
  180. link+=app;
  181. }
  182. if(type){
  183. link+=type+'/';
  184. }
  185. link+=file;
  186. }
  187. return link;
  188. },
  189. /**
  190. * Redirect to the target URL, can also be used for downloads.
  191. * @param {string} targetURL URL to redirect to
  192. */
  193. redirect: function(targetURL) {
  194. window.location = targetURL;
  195. },
  196. /**
  197. * Protocol that is used to access this ownCloud instance
  198. * @return {string} Used protocol
  199. */
  200. getProtocol: function() {
  201. return window.location.protocol.split(':')[0];
  202. },
  203. /**
  204. * get the absolute path to an image file
  205. * if no extension is given for the image, it will automatically decide
  206. * between .png and .svg based on what the browser supports
  207. * @param {string} app the app id to which the image belongs
  208. * @param {string} file the name of the image file
  209. * @return {string}
  210. */
  211. imagePath:function(app,file){
  212. if(file.indexOf('.')==-1){//if no extension is given, use png or svg depending on browser support
  213. file+=(OC.Util.hasSVGSupport())?'.svg':'.png';
  214. }
  215. return OC.filePath(app,'img',file);
  216. },
  217. /**
  218. * URI-Encodes a file path but keep the path slashes.
  219. *
  220. * @param path path
  221. * @return encoded path
  222. */
  223. encodePath: function(path) {
  224. if (!path) {
  225. return path;
  226. }
  227. var parts = path.split('/');
  228. var result = [];
  229. for (var i = 0; i < parts.length; i++) {
  230. result.push(encodeURIComponent(parts[i]));
  231. }
  232. return result.join('/');
  233. },
  234. /**
  235. * Load a script for the server and load it. If the script is already loaded,
  236. * the event handler will be called directly
  237. * @param {string} app the app id to which the script belongs
  238. * @param {string} script the filename of the script
  239. * @param ready event handler to be called when the script is loaded
  240. */
  241. addScript:function(app,script,ready){
  242. var deferred, path=OC.filePath(app,'js',script+'.js');
  243. if(!OC.addScript.loaded[path]){
  244. if(ready){
  245. deferred=$.getScript(path,ready);
  246. }else{
  247. deferred=$.getScript(path);
  248. }
  249. OC.addScript.loaded[path]=deferred;
  250. }else{
  251. if(ready){
  252. ready();
  253. }
  254. }
  255. return OC.addScript.loaded[path];
  256. },
  257. /**
  258. * Loads a CSS file
  259. * @param {string} app the app id to which the css style belongs
  260. * @param {string} style the filename of the css file
  261. */
  262. addStyle:function(app,style){
  263. var path=OC.filePath(app,'css',style+'.css');
  264. if(OC.addStyle.loaded.indexOf(path)===-1){
  265. OC.addStyle.loaded.push(path);
  266. if (document.createStyleSheet) {
  267. document.createStyleSheet(path);
  268. } else {
  269. style=$('<link rel="stylesheet" type="text/css" href="'+path+'"/>');
  270. $('head').append(style);
  271. }
  272. }
  273. },
  274. /**
  275. * Loads translations for the given app asynchronously.
  276. *
  277. * @param {String} app app name
  278. * @param {Function} callback callback to call after loading
  279. * @return {Promise}
  280. */
  281. addTranslations: function(app, callback) {
  282. return OC.L10N.load(app, callback);
  283. },
  284. /**
  285. * Returns the base name of the given path.
  286. * For example for "/abc/somefile.txt" it will return "somefile.txt"
  287. *
  288. * @param {String} path
  289. * @return {String} base name
  290. */
  291. basename: function(path) {
  292. return path.replace(/\\/g,'/').replace( /.*\//, '' );
  293. },
  294. /**
  295. * Returns the dir name of the given path.
  296. * For example for "/abc/somefile.txt" it will return "/abc"
  297. *
  298. * @param {String} path
  299. * @return {String} dir name
  300. */
  301. dirname: function(path) {
  302. return path.replace(/\\/g,'/').replace(/\/[^\/]*$/, '');
  303. },
  304. /**
  305. * Do a search query and display the results
  306. * @param {string} query the search query
  307. */
  308. search: function (query) {
  309. OC.Search.search(query, null, 0, 30);
  310. },
  311. /**
  312. * Dialog helper for jquery dialogs.
  313. *
  314. * @namespace OC.dialogs
  315. */
  316. dialogs:OCdialogs,
  317. /**
  318. * Parses a URL query string into a JS map
  319. * @param {string} queryString query string in the format param1=1234&param2=abcde&param3=xyz
  320. * @return {Object.<string, string>} map containing key/values matching the URL parameters
  321. */
  322. parseQueryString:function(queryString){
  323. var parts,
  324. pos,
  325. components,
  326. result = {},
  327. key,
  328. value;
  329. if (!queryString){
  330. return null;
  331. }
  332. pos = queryString.indexOf('?');
  333. if (pos >= 0){
  334. queryString = queryString.substr(pos + 1);
  335. }
  336. parts = queryString.replace(/\+/g, '%20').split('&');
  337. for (var i = 0; i < parts.length; i++){
  338. // split on first equal sign
  339. var part = parts[i];
  340. pos = part.indexOf('=');
  341. if (pos >= 0) {
  342. components = [
  343. part.substr(0, pos),
  344. part.substr(pos + 1)
  345. ];
  346. }
  347. else {
  348. // key only
  349. components = [part];
  350. }
  351. if (!components.length){
  352. continue;
  353. }
  354. key = decodeURIComponent(components[0]);
  355. if (!key){
  356. continue;
  357. }
  358. // if equal sign was there, return string
  359. if (components.length > 1) {
  360. result[key] = decodeURIComponent(components[1]);
  361. }
  362. // no equal sign => null value
  363. else {
  364. result[key] = null;
  365. }
  366. }
  367. return result;
  368. },
  369. /**
  370. * Builds a URL query from a JS map.
  371. * @param {Object.<string, string>} params map containing key/values matching the URL parameters
  372. * @return {string} String containing a URL query (without question) mark
  373. */
  374. buildQueryString: function(params) {
  375. if (!params) {
  376. return '';
  377. }
  378. return $.map(params, function(value, key) {
  379. var s = encodeURIComponent(key);
  380. if (value !== null && typeof(value) !== 'undefined') {
  381. s += '=' + encodeURIComponent(value);
  382. }
  383. return s;
  384. }).join('&');
  385. },
  386. /**
  387. * Opens a popup with the setting for an app.
  388. * @param {string} appid The ID of the app e.g. 'calendar', 'contacts' or 'files'.
  389. * @param {boolean|string} loadJS If true 'js/settings.js' is loaded. If it's a string
  390. * it will attempt to load a script by that name in the 'js' directory.
  391. * @param {boolean} [cache] If true the javascript file won't be forced refreshed. Defaults to true.
  392. * @param {string} [scriptName] The name of the PHP file to load. Defaults to 'settings.php' in
  393. * the root of the app directory hierarchy.
  394. */
  395. appSettings:function(args) {
  396. if(typeof args === 'undefined' || typeof args.appid === 'undefined') {
  397. throw { name: 'MissingParameter', message: 'The parameter appid is missing' };
  398. }
  399. var props = {scriptName:'settings.php', cache:true};
  400. $.extend(props, args);
  401. var settings = $('#appsettings');
  402. if(settings.length === 0) {
  403. throw { name: 'MissingDOMElement', message: 'There has be be an element with id "appsettings" for the popup to show.' };
  404. }
  405. var popup = $('#appsettings_popup');
  406. if(popup.length === 0) {
  407. $('body').prepend('<div class="popup hidden" id="appsettings_popup"></div>');
  408. popup = $('#appsettings_popup');
  409. popup.addClass(settings.hasClass('topright') ? 'topright' : 'bottomleft');
  410. }
  411. if(popup.is(':visible')) {
  412. popup.hide().remove();
  413. } else {
  414. var arrowclass = settings.hasClass('topright') ? 'up' : 'left';
  415. var jqxhr = $.get(OC.filePath(props.appid, '', props.scriptName), function(data) {
  416. popup.html(data).ready(function() {
  417. popup.prepend('<span class="arrow '+arrowclass+'"></span><h2>'+t('core', 'Settings')+'</h2><a class="close svg"></a>').show();
  418. popup.find('.close').bind('click', function() {
  419. popup.remove();
  420. });
  421. if(typeof props.loadJS !== 'undefined') {
  422. var scriptname;
  423. if(props.loadJS === true) {
  424. scriptname = 'settings.js';
  425. } else if(typeof props.loadJS === 'string') {
  426. scriptname = props.loadJS;
  427. } else {
  428. throw { name: 'InvalidParameter', message: 'The "loadJS" parameter must be either boolean or a string.' };
  429. }
  430. if(props.cache) {
  431. $.ajaxSetup({cache: true});
  432. }
  433. $.getScript(OC.filePath(props.appid, 'js', scriptname))
  434. .fail(function(jqxhr, settings, e) {
  435. throw e;
  436. });
  437. }
  438. if(!OC.Util.hasSVGSupport()) {
  439. OC.Util.replaceSVG();
  440. }
  441. }).show();
  442. }, 'html');
  443. }
  444. },
  445. /**
  446. * For menu toggling
  447. * @todo Write documentation
  448. */
  449. registerMenu: function($toggle, $menuEl) {
  450. $menuEl.addClass('menu');
  451. $toggle.on('click.menu', function(event) {
  452. if ($menuEl.is(OC._currentMenu)) {
  453. $menuEl.slideUp(OC.menuSpeed);
  454. OC._currentMenu = null;
  455. OC._currentMenuToggle = null;
  456. return false;
  457. }
  458. // another menu was open?
  459. else if (OC._currentMenu) {
  460. // close it
  461. OC._currentMenu.hide();
  462. }
  463. $menuEl.slideToggle(OC.menuSpeed);
  464. OC._currentMenu = $menuEl;
  465. OC._currentMenuToggle = $toggle;
  466. return false;
  467. });
  468. },
  469. /**
  470. * @todo Write documentation
  471. */
  472. unregisterMenu: function($toggle, $menuEl) {
  473. // close menu if opened
  474. if ($menuEl.is(OC._currentMenu)) {
  475. $menuEl.slideUp(OC.menuSpeed);
  476. OC._currentMenu = null;
  477. OC._currentMenuToggle = null;
  478. }
  479. $toggle.off('click.menu').removeClass('menutoggle');
  480. $menuEl.removeClass('menu');
  481. },
  482. /**
  483. * Wrapper for matchMedia
  484. *
  485. * This is makes it possible for unit tests to
  486. * stub matchMedia (which doesn't work in PhantomJS)
  487. * @private
  488. */
  489. _matchMedia: function(media) {
  490. if (window.matchMedia) {
  491. return window.matchMedia(media);
  492. }
  493. return false;
  494. },
  495. /**
  496. * Returns the user's locale
  497. *
  498. * @return {String} locale string
  499. */
  500. getLocale: function() {
  501. return $('html').prop('lang');
  502. }
  503. };
  504. /**
  505. * @namespace OC.Plugins
  506. */
  507. OC.Plugins = {
  508. /**
  509. * @type Array.<OC.Plugin>
  510. */
  511. _plugins: {},
  512. /**
  513. * Register plugin
  514. *
  515. * @param {String} targetName app name / class name to hook into
  516. * @param {OC.Plugin} plugin
  517. */
  518. register: function(targetName, plugin) {
  519. var plugins = this._plugins[targetName];
  520. if (!plugins) {
  521. plugins = this._plugins[targetName] = [];
  522. }
  523. plugins.push(plugin);
  524. },
  525. /**
  526. * Returns all plugin registered to the given target
  527. * name / app name / class name.
  528. *
  529. * @param {String} targetName app name / class name to hook into
  530. * @return {Array.<OC.Plugin>} array of plugins
  531. */
  532. getPlugins: function(targetName) {
  533. return this._plugins[targetName] || [];
  534. },
  535. /**
  536. * Call attach() on all plugins registered to the given target name.
  537. *
  538. * @param {String} targetName app name / class name
  539. * @param {Object} object to be extended
  540. * @param {Object} [options] options
  541. */
  542. attach: function(targetName, targetObject, options) {
  543. var plugins = this.getPlugins(targetName);
  544. for (var i = 0; i < plugins.length; i++) {
  545. if (plugins[i].attach) {
  546. plugins[i].attach(targetObject, options);
  547. }
  548. }
  549. },
  550. /**
  551. * Call detach() on all plugins registered to the given target name.
  552. *
  553. * @param {String} targetName app name / class name
  554. * @param {Object} object to be extended
  555. * @param {Object} [options] options
  556. */
  557. detach: function(targetName, targetObject, options) {
  558. var plugins = this.getPlugins(targetName);
  559. for (var i = 0; i < plugins.length; i++) {
  560. if (plugins[i].detach) {
  561. plugins[i].detach(targetObject, options);
  562. }
  563. }
  564. },
  565. /**
  566. * Plugin
  567. *
  568. * @todo make this a real class in the future
  569. * @typedef {Object} OC.Plugin
  570. *
  571. * @property {String} name plugin name
  572. * @property {Function} attach function that will be called when the
  573. * plugin is attached
  574. * @property {Function} [detach] function that will be called when the
  575. * plugin is detached
  576. */
  577. };
  578. /**
  579. * @namespace OC.search
  580. */
  581. OC.search.customResults = {};
  582. /**
  583. * @deprecated use get/setFormatter() instead
  584. */
  585. OC.search.resultTypes = {};
  586. OC.addStyle.loaded=[];
  587. OC.addScript.loaded=[];
  588. /**
  589. * A little class to manage a status field for a "saving" process.
  590. * It can be used to display a starting message (e.g. "Saving...") and then
  591. * replace it with a green success message or a red error message.
  592. *
  593. * @namespace OC.msg
  594. */
  595. OC.msg = {
  596. /**
  597. * Displayes a "Saving..." message in the given message placeholder
  598. *
  599. * @param {Object} selector Placeholder to display the message in
  600. */
  601. startSaving: function(selector) {
  602. this.startAction(selector, t('core', 'Saving...'));
  603. },
  604. /**
  605. * Displayes a custom message in the given message placeholder
  606. *
  607. * @param {Object} selector Placeholder to display the message in
  608. * @param {string} message Plain text message to display (no HTML allowed)
  609. */
  610. startAction: function(selector, message) {
  611. $(selector).text(message)
  612. .removeClass('success')
  613. .removeClass('error')
  614. .stop(true, true)
  615. .show();
  616. },
  617. /**
  618. * Displayes an success/error message in the given selector
  619. *
  620. * @param {Object} selector Placeholder to display the message in
  621. * @param {Object} response Response of the server
  622. * @param {Object} response.data Data of the servers response
  623. * @param {string} response.data.message Plain text message to display (no HTML allowed)
  624. * @param {string} response.status is being used to decide whether the message
  625. * is displayed as an error/success
  626. */
  627. finishedSaving: function(selector, response) {
  628. this.finishedAction(selector, response);
  629. },
  630. /**
  631. * Displayes an success/error message in the given selector
  632. *
  633. * @param {Object} selector Placeholder to display the message in
  634. * @param {Object} response Response of the server
  635. * @param {Object} response.data Data of the servers response
  636. * @param {string} response.data.message Plain text message to display (no HTML allowed)
  637. * @param {string} response.status is being used to decide whether the message
  638. * is displayed as an error/success
  639. */
  640. finishedAction: function(selector, response) {
  641. if (response.status === "success") {
  642. this.finishedSuccess(selector, response.data.message);
  643. } else {
  644. this.finishedError(selector, response.data.message);
  645. }
  646. },
  647. /**
  648. * Displayes an success message in the given selector
  649. *
  650. * @param {Object} selector Placeholder to display the message in
  651. * @param {string} message Plain text success message to display (no HTML allowed)
  652. */
  653. finishedSuccess: function(selector, message) {
  654. $(selector).text(message)
  655. .addClass('success')
  656. .removeClass('error')
  657. .stop(true, true)
  658. .delay(3000)
  659. .fadeOut(900)
  660. .show();
  661. },
  662. /**
  663. * Displayes an error message in the given selector
  664. *
  665. * @param {Object} selector Placeholder to display the message in
  666. * @param {string} message Plain text error message to display (no HTML allowed)
  667. */
  668. finishedError: function(selector, message) {
  669. $(selector).text(message)
  670. .addClass('error')
  671. .removeClass('success')
  672. .show();
  673. }
  674. };
  675. /**
  676. * @todo Write documentation
  677. * @namespace
  678. */
  679. OC.Notification={
  680. queuedNotifications: [],
  681. getDefaultNotificationFunction: null,
  682. notificationTimer: 0,
  683. /**
  684. * @param callback
  685. * @todo Write documentation
  686. */
  687. setDefault: function(callback) {
  688. OC.Notification.getDefaultNotificationFunction = callback;
  689. },
  690. /**
  691. * Hides a notification
  692. * @param callback
  693. * @todo Write documentation
  694. */
  695. hide: function(callback) {
  696. $('#notification').fadeOut('400', function(){
  697. if (OC.Notification.isHidden()) {
  698. if (OC.Notification.getDefaultNotificationFunction) {
  699. OC.Notification.getDefaultNotificationFunction.call();
  700. }
  701. }
  702. if (callback) {
  703. callback.call();
  704. }
  705. $('#notification').empty();
  706. if(OC.Notification.queuedNotifications.length > 0){
  707. OC.Notification.showHtml(OC.Notification.queuedNotifications[0]);
  708. OC.Notification.queuedNotifications.shift();
  709. }
  710. });
  711. },
  712. /**
  713. * Shows a notification as HTML without being sanitized before.
  714. * If you pass unsanitized user input this may lead to a XSS vulnerability.
  715. * Consider using show() instead of showHTML()
  716. * @param {string} html Message to display
  717. */
  718. showHtml: function(html) {
  719. var notification = $('#notification');
  720. if((notification.filter('span.undo').length == 1) || OC.Notification.isHidden()){
  721. notification.html(html);
  722. notification.fadeIn().css('display','inline-block');
  723. }else{
  724. OC.Notification.queuedNotifications.push(html);
  725. }
  726. },
  727. /**
  728. * Shows a sanitized notification
  729. * @param {string} text Message to display
  730. */
  731. show: function(text) {
  732. var notification = $('#notification');
  733. if((notification.filter('span.undo').length == 1) || OC.Notification.isHidden()){
  734. notification.text(text);
  735. notification.fadeIn().css('display','inline-block');
  736. }else{
  737. OC.Notification.queuedNotifications.push($('<div/>').text(text).html());
  738. }
  739. },
  740. /**
  741. * Shows a notification that disappears after x seconds, default is
  742. * 7 seconds
  743. * @param {string} text Message to show
  744. * @param {array} [options] options array
  745. * @param {int} [options.timeout=7] timeout in seconds, if this is 0 it will show the message permanently
  746. * @param {boolean} [options.isHTML=false] an indicator for HTML notifications (true) or text (false)
  747. */
  748. showTemporary: function(text, options) {
  749. var defaults = {
  750. isHTML: false,
  751. timeout: 7
  752. },
  753. options = options || {};
  754. // merge defaults with passed in options
  755. _.defaults(options, defaults);
  756. // clear previous notifications
  757. OC.Notification.hide();
  758. if(OC.Notification.notificationTimer) {
  759. clearTimeout(OC.Notification.notificationTimer);
  760. }
  761. if(options.isHTML) {
  762. OC.Notification.showHtml(text);
  763. } else {
  764. OC.Notification.show(text);
  765. }
  766. if(options.timeout > 0) {
  767. // register timeout to vanish notification
  768. OC.Notification.notificationTimer = setTimeout(OC.Notification.hide, (options.timeout * 1000));
  769. }
  770. },
  771. /**
  772. * Returns whether a notification is hidden.
  773. * @return {boolean}
  774. */
  775. isHidden: function() {
  776. return ($("#notification").text() === '');
  777. }
  778. };
  779. /**
  780. * Breadcrumb class
  781. *
  782. * @namespace
  783. *
  784. * @deprecated will be replaced by the breadcrumb implementation
  785. * of the files app in the future
  786. */
  787. OC.Breadcrumb={
  788. container:null,
  789. /**
  790. * @todo Write documentation
  791. * @param dir
  792. * @param leafName
  793. * @param leafLink
  794. */
  795. show:function(dir, leafName, leafLink){
  796. if(!this.container){//default
  797. this.container=$('#controls');
  798. }
  799. this._show(this.container, dir, leafName, leafLink);
  800. },
  801. _show:function(container, dir, leafname, leaflink){
  802. var self = this;
  803. this._clear(container);
  804. // show home + path in subdirectories
  805. if (dir) {
  806. //add home
  807. var link = OC.linkTo('files','index.php');
  808. var crumb=$('<div/>');
  809. crumb.addClass('crumb');
  810. var crumbLink=$('<a/>');
  811. crumbLink.attr('href',link);
  812. var crumbImg=$('<img/>');
  813. crumbImg.attr('src',OC.imagePath('core','places/home'));
  814. crumbLink.append(crumbImg);
  815. crumb.append(crumbLink);
  816. container.prepend(crumb);
  817. //add path parts
  818. var segments = dir.split('/');
  819. var pathurl = '';
  820. jQuery.each(segments, function(i,name) {
  821. if (name !== '') {
  822. pathurl = pathurl+'/'+name;
  823. var link = OC.linkTo('files','index.php')+'?dir='+encodeURIComponent(pathurl);
  824. self._push(container, name, link);
  825. }
  826. });
  827. }
  828. //add leafname
  829. if (leafname && leaflink) {
  830. this._push(container, leafname, leaflink);
  831. }
  832. },
  833. /**
  834. * @todo Write documentation
  835. * @param {string} name
  836. * @param {string} link
  837. */
  838. push:function(name, link){
  839. if(!this.container){//default
  840. this.container=$('#controls');
  841. }
  842. return this._push(OC.Breadcrumb.container, name, link);
  843. },
  844. _push:function(container, name, link){
  845. var crumb=$('<div/>');
  846. crumb.addClass('crumb').addClass('last');
  847. var crumbLink=$('<a/>');
  848. crumbLink.attr('href',link);
  849. crumbLink.text(name);
  850. crumb.append(crumbLink);
  851. var existing=container.find('div.crumb');
  852. if(existing.length){
  853. existing.removeClass('last');
  854. existing.last().after(crumb);
  855. }else{
  856. container.prepend(crumb);
  857. }
  858. return crumb;
  859. },
  860. /**
  861. * @todo Write documentation
  862. */
  863. pop:function(){
  864. if(!this.container){//default
  865. this.container=$('#controls');
  866. }
  867. this.container.find('div.crumb').last().remove();
  868. this.container.find('div.crumb').last().addClass('last');
  869. },
  870. /**
  871. * @todo Write documentation
  872. */
  873. clear:function(){
  874. if(!this.container){//default
  875. this.container=$('#controls');
  876. }
  877. this._clear(this.container);
  878. },
  879. _clear:function(container) {
  880. container.find('div.crumb').remove();
  881. }
  882. };
  883. if(typeof localStorage !=='undefined' && localStorage !== null){
  884. /**
  885. * User and instance aware localstorage
  886. * @namespace
  887. */
  888. OC.localStorage={
  889. namespace:'oc_'+OC.currentUser+'_'+OC.webroot+'_',
  890. /**
  891. * Whether the storage contains items
  892. * @param {string} name
  893. * @return {boolean}
  894. */
  895. hasItem:function(name){
  896. return OC.localStorage.getItem(name)!==null;
  897. },
  898. /**
  899. * Add an item to the storage
  900. * @param {string} name
  901. * @param {string} item
  902. */
  903. setItem:function(name,item){
  904. return localStorage.setItem(OC.localStorage.namespace+name,JSON.stringify(item));
  905. },
  906. /**
  907. * Removes an item from the storage
  908. * @param {string} name
  909. * @param {string} item
  910. */
  911. removeItem:function(name,item){
  912. return localStorage.removeItem(OC.localStorage.namespace+name);
  913. },
  914. /**
  915. * Get an item from the storage
  916. * @param {string} name
  917. * @return {null|string}
  918. */
  919. getItem:function(name){
  920. var item = localStorage.getItem(OC.localStorage.namespace+name);
  921. if(item === null) {
  922. return null;
  923. } else if (typeof JSON === 'undefined') {
  924. //fallback to jquery for IE6/7/8
  925. return $.parseJSON(item);
  926. } else {
  927. return JSON.parse(item);
  928. }
  929. }
  930. };
  931. }else{
  932. //dummy localstorage
  933. OC.localStorage={
  934. hasItem:function(){
  935. return false;
  936. },
  937. setItem:function(){
  938. return false;
  939. },
  940. getItem:function(){
  941. return null;
  942. }
  943. };
  944. }
  945. /**
  946. * check if the browser support svg images
  947. * @return {boolean}
  948. */
  949. function SVGSupport() {
  950. return SVGSupport.checkMimeType.correct && !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', "svg").createSVGRect;
  951. }
  952. SVGSupport.checkMimeType=function(){
  953. $.ajax({
  954. url: OC.imagePath('core','breadcrumb.svg'),
  955. success:function(data,text,xhr){
  956. var headerParts=xhr.getAllResponseHeaders().split("\n");
  957. var headers={};
  958. $.each(headerParts,function(i,text){
  959. if(text){
  960. var parts=text.split(':',2);
  961. if(parts.length===2){
  962. var value=parts[1].trim();
  963. if(value[0]==='"'){
  964. value=value.substr(1,value.length-2);
  965. }
  966. headers[parts[0].toLowerCase()]=value;
  967. }
  968. }
  969. });
  970. if(headers["content-type"]!=='image/svg+xml'){
  971. OC.Util.replaceSVG();
  972. SVGSupport.checkMimeType.correct=false;
  973. }
  974. }
  975. });
  976. };
  977. SVGSupport.checkMimeType.correct=true;
  978. /**
  979. * Replace all svg images with png for browser compatibility
  980. * @param $el
  981. * @deprecated use OC.Util.replaceSVG instead
  982. */
  983. function replaceSVG($el){
  984. return OC.Util.replaceSVG($el);
  985. }
  986. /**
  987. * prototypical inheritance functions
  988. * @todo Write documentation
  989. * usage:
  990. * MySubObject=object(MyObject)
  991. */
  992. function object(o) {
  993. function F() {}
  994. F.prototype = o;
  995. return new F();
  996. }
  997. /**
  998. * Initializes core
  999. */
  1000. function initCore() {
  1001. /**
  1002. * Set users locale to moment.js as soon as possible
  1003. */
  1004. moment.locale(OC.getLocale());
  1005. /**
  1006. * Calls the server periodically to ensure that session doesn't
  1007. * time out
  1008. */
  1009. function initSessionHeartBeat(){
  1010. // max interval in seconds set to 24 hours
  1011. var maxInterval = 24 * 3600;
  1012. // interval in seconds
  1013. var interval = 900;
  1014. if (oc_config.session_lifetime) {
  1015. interval = Math.floor(oc_config.session_lifetime / 2);
  1016. }
  1017. // minimum one minute
  1018. if (interval < 60) {
  1019. interval = 60;
  1020. }
  1021. if (interval > maxInterval) {
  1022. interval = maxInterval;
  1023. }
  1024. var url = OC.generateUrl('/heartbeat');
  1025. setInterval(function(){
  1026. $.post(url);
  1027. }, interval * 1000);
  1028. }
  1029. // session heartbeat (defaults to enabled)
  1030. if (typeof(oc_config.session_keepalive) === 'undefined' ||
  1031. !!oc_config.session_keepalive) {
  1032. initSessionHeartBeat();
  1033. }
  1034. if(!OC.Util.hasSVGSupport()){ //replace all svg images with png images for browser that dont support svg
  1035. OC.Util.replaceSVG();
  1036. }else{
  1037. SVGSupport.checkMimeType();
  1038. }
  1039. // user menu
  1040. $('#settings #expand').keydown(function(event) {
  1041. if (event.which === 13 || event.which === 32) {
  1042. $('#expand').click();
  1043. }
  1044. });
  1045. $('#settings #expand').click(function(event) {
  1046. $('#settings #expanddiv').slideToggle(OC.menuSpeed);
  1047. event.stopPropagation();
  1048. });
  1049. $('#settings #expanddiv').click(function(event){
  1050. event.stopPropagation();
  1051. });
  1052. //hide the user menu when clicking outside it
  1053. $(document).click(function(){
  1054. $('#settings #expanddiv').slideUp(OC.menuSpeed);
  1055. });
  1056. // all the tipsy stuff needs to be here (in reverse order) to work
  1057. $('.displayName .action').tipsy({gravity:'se', fade:true, live:true});
  1058. $('.password .action').tipsy({gravity:'se', fade:true, live:true});
  1059. $('#upload').tipsy({gravity:'w', fade:true});
  1060. $('.selectedActions a').tipsy({gravity:'s', fade:true, live:true});
  1061. $('a.action.delete').tipsy({gravity:'e', fade:true, live:true});
  1062. $('a.action').tipsy({gravity:'s', fade:true, live:true});
  1063. $('td .modified').tipsy({gravity:'s', fade:true, live:true});
  1064. $('td.lastLogin').tipsy({gravity:'s', fade:true, html:true});
  1065. $('input').tipsy({gravity:'w', fade:true});
  1066. $('.extra-data').tipsy({gravity:'w', fade:true, live:true});
  1067. // toggle for menus
  1068. $(document).on('mouseup.closemenus', function(event) {
  1069. var $el = $(event.target);
  1070. if ($el.closest('.menu').length || $el.closest('.menutoggle').length) {
  1071. // don't close when clicking on the menu directly or a menu toggle
  1072. return false;
  1073. }
  1074. if (OC._currentMenu) {
  1075. OC._currentMenu.slideUp(OC.menuSpeed);
  1076. }
  1077. OC._currentMenu = null;
  1078. OC._currentMenuToggle = null;
  1079. });
  1080. /**
  1081. * Set up the main menu toggle to react to media query changes.
  1082. * If the screen is small enough, the main menu becomes a toggle.
  1083. * If the screen is bigger, the main menu is not a toggle any more.
  1084. */
  1085. function setupMainMenu() {
  1086. // toggle the navigation
  1087. var $toggle = $('#header .menutoggle');
  1088. var $navigation = $('#navigation');
  1089. // init the menu
  1090. OC.registerMenu($toggle, $navigation);
  1091. $toggle.data('oldhref', $toggle.attr('href'));
  1092. $toggle.attr('href', '#');
  1093. $navigation.hide();
  1094. // show loading feedback
  1095. $navigation.delegate('a', 'click', function(event) {
  1096. var $app = $(event.target);
  1097. if(!$app.is('a')) {
  1098. $app = $app.closest('a');
  1099. }
  1100. if(!event.ctrlKey) {
  1101. $app.addClass('app-loading');
  1102. }
  1103. });
  1104. }
  1105. setupMainMenu();
  1106. // move triangle of apps dropdown to align with app name triangle
  1107. // 2 is the additional offset between the triangles
  1108. if($('#navigation').length) {
  1109. $('#header #owncloud + .menutoggle').one('click', function(){
  1110. var caretPosition = $('.header-appname + .icon-caret').offset().left - 2;
  1111. if(caretPosition > 255) {
  1112. // if the app name is longer than the menu, just put the triangle in the middle
  1113. return;
  1114. } else {
  1115. $('head').append('<style>#navigation:after { left: '+ caretPosition +'px; }</style>');
  1116. }
  1117. });
  1118. }
  1119. // just add snapper for logged in users
  1120. if($('#app-navigation').length && !$('html').hasClass('lte9')) {
  1121. // App sidebar on mobile
  1122. var snapper = new Snap({
  1123. element: document.getElementById('app-content'),
  1124. disable: 'right',
  1125. maxPosition: 250
  1126. });
  1127. $('#app-content').prepend('<div id="app-navigation-toggle" class="icon-menu" style="display:none;"></div>');
  1128. $('#app-navigation-toggle').click(function(){
  1129. if(snapper.state().state == 'left'){
  1130. snapper.close();
  1131. } else {
  1132. snapper.open('left');
  1133. }
  1134. });
  1135. // close sidebar when switching navigation entry
  1136. var $appNavigation = $('#app-navigation');
  1137. $appNavigation.delegate('a', 'click', function(event) {
  1138. var $target = $(event.target);
  1139. // don't hide navigation when changing settings or adding things
  1140. if($target.is('.app-navigation-noclose') ||
  1141. $target.closest('.app-navigation-noclose').length) {
  1142. return;
  1143. }
  1144. if($target.is('.add-new') ||
  1145. $target.closest('.add-new').length) {
  1146. return;
  1147. }
  1148. if($target.is('#app-settings') ||
  1149. $target.closest('#app-settings').length) {
  1150. return;
  1151. }
  1152. snapper.close();
  1153. });
  1154. var toggleSnapperOnSize = function() {
  1155. if($(window).width() > 768) {
  1156. snapper.close();
  1157. snapper.disable();
  1158. } else {
  1159. snapper.enable();
  1160. }
  1161. };
  1162. $(window).resize(_.debounce(toggleSnapperOnSize, 250));
  1163. // initial call
  1164. toggleSnapperOnSize();
  1165. // adjust controls bar width
  1166. var adjustControlsWidth = function() {
  1167. if($('#controls').length) {
  1168. var controlsWidth;
  1169. // if there is a scrollbar …
  1170. if($('#app-content').get(0).scrollHeight > $('#app-content').height()) {
  1171. if($(window).width() > 768) {
  1172. controlsWidth = $('#content').width() - $('#app-navigation').width() - getScrollBarWidth();
  1173. } else {
  1174. controlsWidth = $('#content').width() - getScrollBarWidth();
  1175. }
  1176. } else { // if there is none
  1177. if($(window).width() > 768) {
  1178. controlsWidth = $('#content').width() - $('#app-navigation').width();
  1179. } else {
  1180. controlsWidth = $('#content').width();
  1181. }
  1182. }
  1183. $('#controls').css('width', controlsWidth);
  1184. $('#controls').css('min-width', controlsWidth);
  1185. }
  1186. };
  1187. $(window).resize(_.debounce(adjustControlsWidth, 250));
  1188. $('body').delegate('#app-content', 'apprendered', adjustControlsWidth);
  1189. }
  1190. }
  1191. $(document).ready(initCore);
  1192. /**
  1193. * Filter Jquery selector by attribute value
  1194. */
  1195. $.fn.filterAttr = function(attr_name, attr_value) {
  1196. return this.filter(function() { return $(this).attr(attr_name) === attr_value; });
  1197. };
  1198. /**
  1199. * Returns a human readable file size
  1200. * @param {number} size Size in bytes
  1201. * @param {boolean} skipSmallSizes return '< 1 kB' for small files
  1202. * @return {string}
  1203. */
  1204. function humanFileSize(size, skipSmallSizes) {
  1205. var humanList = ['B', 'kB', 'MB', 'GB', 'TB'];
  1206. // Calculate Log with base 1024: size = 1024 ** order
  1207. var order = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0;
  1208. // Stay in range of the byte sizes that are defined
  1209. order = Math.min(humanList.length - 1, order);
  1210. var readableFormat = humanList[order];
  1211. var relativeSize = (size / Math.pow(1024, order)).toFixed(1);
  1212. if(skipSmallSizes === true && order === 0) {
  1213. if(relativeSize !== "0.0"){
  1214. return '< 1 kB';
  1215. } else {
  1216. return '0 kB';
  1217. }
  1218. }
  1219. if(order < 2){
  1220. relativeSize = parseFloat(relativeSize).toFixed(0);
  1221. }
  1222. else if(relativeSize.substr(relativeSize.length-2,2)==='.0'){
  1223. relativeSize=relativeSize.substr(0,relativeSize.length-2);
  1224. }
  1225. return relativeSize + ' ' + readableFormat;
  1226. }
  1227. /**
  1228. * Format an UNIX timestamp to a human understandable format
  1229. * @param {number} timestamp UNIX timestamp
  1230. * @return {string} Human readable format
  1231. */
  1232. function formatDate(timestamp){
  1233. return OC.Util.formatDate(timestamp);
  1234. }
  1235. //
  1236. /**
  1237. * Get the value of a URL parameter
  1238. * @link http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery
  1239. * @param {string} name URL parameter
  1240. * @return {string}
  1241. */
  1242. function getURLParameter(name) {
  1243. return decodeURI(
  1244. (RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1]
  1245. );
  1246. }
  1247. /**
  1248. * Takes an absolute timestamp and return a string with a human-friendly relative date
  1249. * @param {number} timestamp A Unix timestamp
  1250. */
  1251. function relative_modified_date(timestamp) {
  1252. /*
  1253. Were multiplying by 1000 to bring the timestamp back to its original value
  1254. per https://github.com/owncloud/core/pull/10647#discussion_r16790315
  1255. */
  1256. return OC.Util.relativeModifiedDate(timestamp * 1000);
  1257. }
  1258. /**
  1259. * Utility functions
  1260. * @namespace
  1261. */
  1262. OC.Util = {
  1263. // TODO: remove original functions from global namespace
  1264. humanFileSize: humanFileSize,
  1265. /**
  1266. * @param timestamp
  1267. * @param format
  1268. * @returns {string} timestamp formatted as requested
  1269. */
  1270. formatDate: function (timestamp, format) {
  1271. format = format || "LLL";
  1272. return moment(timestamp).format(format);
  1273. },
  1274. /**
  1275. * @param timestamp
  1276. * @returns {string} human readable difference from now
  1277. */
  1278. relativeModifiedDate: function (timestamp) {
  1279. return moment(timestamp).fromNow();
  1280. },
  1281. /**
  1282. * Returns whether the browser supports SVG
  1283. * @return {boolean} true if the browser supports SVG, false otherwise
  1284. */
  1285. // TODO: replace with original function
  1286. hasSVGSupport: SVGSupport,
  1287. /**
  1288. * If SVG is not supported, replaces the given icon's extension
  1289. * from ".svg" to ".png".
  1290. * If SVG is supported, return the image path as is.
  1291. * @param {string} file image path with svg extension
  1292. * @return {string} fixed image path with png extension if SVG is not supported
  1293. */
  1294. replaceSVGIcon: function(file) {
  1295. if (file && !OC.Util.hasSVGSupport()) {
  1296. var i = file.lastIndexOf('.svg');
  1297. if (i >= 0) {
  1298. file = file.substr(0, i) + '.png' + file.substr(i+4);
  1299. }
  1300. }
  1301. return file;
  1302. },
  1303. /**
  1304. * Replace SVG images in all elements that have the "svg" class set
  1305. * with PNG images.
  1306. *
  1307. * @param $el root element from which to search, defaults to $('body')
  1308. */
  1309. replaceSVG: function($el) {
  1310. if (!$el) {
  1311. $el = $('body');
  1312. }
  1313. $el.find('img.svg').each(function(index,element){
  1314. element=$(element);
  1315. var src=element.attr('src');
  1316. element.attr('src',src.substr(0, src.length-3) + 'png');
  1317. });
  1318. $el.find('.svg').each(function(index,element){
  1319. element = $(element);
  1320. var background = element.css('background-image');
  1321. if (background){
  1322. var i = background.lastIndexOf('.svg');
  1323. if (i >= 0){
  1324. background = background.substr(0,i) + '.png' + background.substr(i + 4);
  1325. element.css('background-image', background);
  1326. }
  1327. }
  1328. element.find('*').each(function(index, element) {
  1329. element = $(element);
  1330. var background = element.css('background-image');
  1331. if (background) {
  1332. var i = background.lastIndexOf('.svg');
  1333. if(i >= 0){
  1334. background = background.substr(0,i) + '.png' + background.substr(i + 4);
  1335. element.css('background-image', background);
  1336. }
  1337. }
  1338. });
  1339. });
  1340. },
  1341. /**
  1342. * Remove the time component from a given date
  1343. *
  1344. * @param {Date} date date
  1345. * @return {Date} date with stripped time
  1346. */
  1347. stripTime: function(date) {
  1348. // FIXME: likely to break when crossing DST
  1349. // would be better to use a library like momentJS
  1350. return new Date(date.getFullYear(), date.getMonth(), date.getDate());
  1351. },
  1352. _chunkify: function(t) {
  1353. // Adapted from http://my.opera.com/GreyWyvern/blog/show.dml/1671288
  1354. var tz = [], x = 0, y = -1, n = 0, code, c;
  1355. while (x < t.length) {
  1356. c = t.charAt(x);
  1357. // only include the dot in strings
  1358. var m = ((!n && c === '.') || (c >= '0' && c <= '9'));
  1359. if (m !== n) {
  1360. // next chunk
  1361. y++;
  1362. tz[y] = '';
  1363. n = m;
  1364. }
  1365. tz[y] += c;
  1366. x++;
  1367. }
  1368. return tz;
  1369. },
  1370. /**
  1371. * Compare two strings to provide a natural sort
  1372. * @param a first string to compare
  1373. * @param b second string to compare
  1374. * @return -1 if b comes before a, 1 if a comes before b
  1375. * or 0 if the strings are identical
  1376. */
  1377. naturalSortCompare: function(a, b) {
  1378. var x;
  1379. var aa = OC.Util._chunkify(a);
  1380. var bb = OC.Util._chunkify(b);
  1381. for (x = 0; aa[x] && bb[x]; x++) {
  1382. if (aa[x] !== bb[x]) {
  1383. var aNum = Number(aa[x]), bNum = Number(bb[x]);
  1384. // note: == is correct here
  1385. if (aNum == aa[x] && bNum == bb[x]) {
  1386. return aNum - bNum;
  1387. } else {
  1388. // Forcing 'en' locale to match the server-side locale which is
  1389. // always 'en'.
  1390. //
  1391. // Note: This setting isn't supported by all browsers but for the ones
  1392. // that do there will be more consistency between client-server sorting
  1393. return aa[x].localeCompare(bb[x], 'en');
  1394. }
  1395. }
  1396. }
  1397. return aa.length - bb.length;
  1398. }
  1399. }
  1400. /**
  1401. * Utility class for the history API,
  1402. * includes fallback to using the URL hash when
  1403. * the browser doesn't support the history API.
  1404. *
  1405. * @namespace
  1406. */
  1407. OC.Util.History = {
  1408. _handlers: [],
  1409. /**
  1410. * Push the current URL parameters to the history stack
  1411. * and change the visible URL.
  1412. * Note: this includes a workaround for IE8/IE9 that uses
  1413. * the hash part instead of the search part.
  1414. *
  1415. * @param params to append to the URL, can be either a string
  1416. * or a map
  1417. */
  1418. pushState: function(params) {
  1419. var strParams;
  1420. if (typeof(params) === 'string') {
  1421. strParams = params;
  1422. }
  1423. else {
  1424. strParams = OC.buildQueryString(params);
  1425. }
  1426. if (window.history.pushState) {
  1427. var url = location.pathname + '?' + strParams;
  1428. window.history.pushState(params, '', url);
  1429. }
  1430. // use URL hash for IE8
  1431. else {
  1432. window.location.hash = '?' + strParams;
  1433. // inhibit next onhashchange that just added itself
  1434. // to the event queue
  1435. this._cancelPop = true;
  1436. }
  1437. },
  1438. /**
  1439. * Add a popstate handler
  1440. *
  1441. * @param handler function
  1442. */
  1443. addOnPopStateHandler: function(handler) {
  1444. this._handlers.push(handler);
  1445. },
  1446. /**
  1447. * Parse a query string from the hash part of the URL.
  1448. * (workaround for IE8 / IE9)
  1449. */
  1450. _parseHashQuery: function() {
  1451. var hash = window.location.hash,
  1452. pos = hash.indexOf('?');
  1453. if (pos >= 0) {
  1454. return hash.substr(pos + 1);
  1455. }
  1456. if (hash.length) {
  1457. // remove hash sign
  1458. return hash.substr(1);
  1459. }
  1460. return '';
  1461. },
  1462. _decodeQuery: function(query) {
  1463. return query.replace(/\+/g, ' ');
  1464. },
  1465. /**
  1466. * Parse the query/search part of the URL.
  1467. * Also try and parse it from the URL hash (for IE8)
  1468. *
  1469. * @return map of parameters
  1470. */
  1471. parseUrlQuery: function() {
  1472. var query = this._parseHashQuery(),
  1473. params;
  1474. // try and parse from URL hash first
  1475. if (query) {
  1476. params = OC.parseQueryString(this._decodeQuery(query));
  1477. }
  1478. // else read from query attributes
  1479. if (!params) {
  1480. params = OC.parseQueryString(this._decodeQuery(location.search));
  1481. }
  1482. return params || {};
  1483. },
  1484. _onPopState: function(e) {
  1485. if (this._cancelPop) {
  1486. this._cancelPop = false;
  1487. return;
  1488. }
  1489. var params;
  1490. if (!this._handlers.length) {
  1491. return;
  1492. }
  1493. params = (e && e.state) || this.parseUrlQuery() || {};
  1494. for (var i = 0; i < this._handlers.length; i++) {
  1495. this._handlers[i](params);
  1496. }
  1497. }
  1498. };
  1499. // fallback to hashchange when no history support
  1500. if (window.history.pushState) {
  1501. window.onpopstate = _.bind(OC.Util.History._onPopState, OC.Util.History);
  1502. }
  1503. else {
  1504. $(window).on('hashchange', _.bind(OC.Util.History._onPopState, OC.Util.History));
  1505. }
  1506. /**
  1507. * Get a variable by name
  1508. * @param {string} name
  1509. * @return {*}
  1510. */
  1511. OC.get=function(name) {
  1512. var namespaces = name.split(".");
  1513. var tail = namespaces.pop();
  1514. var context=window;
  1515. for(var i = 0; i < namespaces.length; i++) {
  1516. context = context[namespaces[i]];
  1517. if(!context){
  1518. return false;
  1519. }
  1520. }
  1521. return context[tail];
  1522. };
  1523. /**
  1524. * Set a variable by name
  1525. * @param {string} name
  1526. * @param {*} value
  1527. */
  1528. OC.set=function(name, value) {
  1529. var namespaces = name.split(".");
  1530. var tail = namespaces.pop();
  1531. var context=window;
  1532. for(var i = 0; i < namespaces.length; i++) {
  1533. if(!context[namespaces[i]]){
  1534. context[namespaces[i]]={};
  1535. }
  1536. context = context[namespaces[i]];
  1537. }
  1538. context[tail]=value;
  1539. };
  1540. // fix device width on windows phone
  1541. (function() {
  1542. if ("-ms-user-select" in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/)) {
  1543. var msViewportStyle = document.createElement("style");
  1544. msViewportStyle.appendChild(
  1545. document.createTextNode("@-ms-viewport{width:auto!important}")
  1546. );
  1547. document.getElementsByTagName("head")[0].appendChild(msViewportStyle);
  1548. }
  1549. })();
  1550. /**
  1551. * Namespace for apps
  1552. * @namespace OCA
  1553. */
  1554. window.OCA = {};
  1555. /**
  1556. * select a range in an input field
  1557. * @link http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
  1558. * @param {type} start
  1559. * @param {type} end
  1560. */
  1561. jQuery.fn.selectRange = function(start, end) {
  1562. return this.each(function() {
  1563. if (this.setSelectionRange) {
  1564. this.focus();
  1565. this.setSelectionRange(start, end);
  1566. } else if (this.createTextRange) {
  1567. var range = this.createTextRange();
  1568. range.collapse(true);
  1569. range.moveEnd('character', end);
  1570. range.moveStart('character', start);
  1571. range.select();
  1572. }
  1573. });
  1574. };
  1575. /**
  1576. * check if an element exists.
  1577. * allows you to write if ($('#myid').exists()) to increase readability
  1578. * @link http://stackoverflow.com/questions/31044/is-there-an-exists-function-for-jquery
  1579. */
  1580. jQuery.fn.exists = function(){
  1581. return this.length > 0;
  1582. };
  1583. function getScrollBarWidth() {
  1584. var inner = document.createElement('p');
  1585. inner.style.width = "100%";
  1586. inner.style.height = "200px";
  1587. var outer = document.createElement('div');
  1588. outer.style.position = "absolute";
  1589. outer.style.top = "0px";
  1590. outer.style.left = "0px";
  1591. outer.style.visibility = "hidden";
  1592. outer.style.width = "200px";
  1593. outer.style.height = "150px";
  1594. outer.style.overflow = "hidden";
  1595. outer.appendChild (inner);
  1596. document.body.appendChild (outer);
  1597. var w1 = inner.offsetWidth;
  1598. outer.style.overflow = 'scroll';
  1599. var w2 = inner.offsetWidth;
  1600. if(w1 === w2) {
  1601. w2 = outer.clientWidth;
  1602. }
  1603. document.body.removeChild (outer);
  1604. return (w1 - w2);
  1605. }