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 55KB

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