You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

js.js 51KB

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