sharedialogview.js 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135
  1. /*
  2. * Copyright (c) 2015
  3. *
  4. * This file is licensed under the Affero General Public License version 3
  5. * or later.
  6. *
  7. * See the COPYING-README file.
  8. *
  9. */
  10. /* globals Handlebars */
  11. (function() {
  12. if(!OC.Share) {
  13. OC.Share = {};
  14. }
  15. /**
  16. * @class OCA.Share.ShareDialogView
  17. * @member {OC.Share.ShareItemModel} model
  18. * @member {jQuery} $el
  19. * @memberof OCA.Sharing
  20. * @classdesc
  21. *
  22. * Represents the GUI of the share dialogue
  23. *
  24. */
  25. var ShareDialogView = OC.Backbone.View.extend({
  26. /** @type {Object} **/
  27. _templates: {},
  28. /** @type {boolean} **/
  29. _showLink: true,
  30. _lookup: false,
  31. _lookupAllowed: false,
  32. /** @type {string} **/
  33. tagName: 'div',
  34. /** @type {OC.Share.ShareConfigModel} **/
  35. configModel: undefined,
  36. /** @type {object} **/
  37. resharerInfoView: undefined,
  38. /** @type {object} **/
  39. linkShareView: undefined,
  40. /** @type {object} **/
  41. shareeListView: undefined,
  42. /** @type {object} **/
  43. _lastSuggestions: undefined,
  44. /** @type {object} **/
  45. _lastRecommendations: undefined,
  46. /** @type {int} **/
  47. _pendingOperationsCount: 0,
  48. events: {
  49. 'focus .shareWithField': 'onShareWithFieldFocus',
  50. 'input .shareWithField': 'onShareWithFieldChanged',
  51. 'click .shareWithConfirm': '_confirmShare'
  52. },
  53. initialize: function(options) {
  54. var view = this;
  55. this.model.on('fetchError', function() {
  56. OC.Notification.showTemporary(t('core', 'Share details could not be loaded for this item.'));
  57. });
  58. if(!_.isUndefined(options.configModel)) {
  59. this.configModel = options.configModel;
  60. } else {
  61. throw 'missing OC.Share.ShareConfigModel';
  62. }
  63. this.configModel.on('change:isRemoteShareAllowed', function() {
  64. view.render();
  65. });
  66. this.configModel.on('change:isRemoteGroupShareAllowed', function() {
  67. view.render();
  68. });
  69. this.model.on('change:permissions', function() {
  70. view.render();
  71. });
  72. this.model.on('request', this._onRequest, this);
  73. this.model.on('sync', this._onEndRequest, this);
  74. var subViewOptions = {
  75. model: this.model,
  76. configModel: this.configModel
  77. };
  78. var subViews = {
  79. resharerInfoView: 'ShareDialogResharerInfoView',
  80. linkShareView: 'ShareDialogLinkShareView',
  81. shareeListView: 'ShareDialogShareeListView'
  82. };
  83. for(var name in subViews) {
  84. var className = subViews[name];
  85. this[name] = _.isUndefined(options[name])
  86. ? new OC.Share[className](subViewOptions)
  87. : options[name];
  88. }
  89. _.bindAll(this,
  90. 'autocompleteHandler',
  91. '_onSelectRecipient',
  92. 'onShareWithFieldChanged',
  93. 'onShareWithFieldFocus'
  94. );
  95. OC.Plugins.attach('OC.Share.ShareDialogView', this);
  96. },
  97. onShareWithFieldChanged: function() {
  98. var $el = this.$el.find('.shareWithField');
  99. if ($el.val().length < 2) {
  100. $el.removeClass('error').tooltip('hide');
  101. }
  102. },
  103. /* trigger search after the field was re-selected */
  104. onShareWithFieldFocus: function() {
  105. var $shareWithField = this.$el.find('.shareWithField');
  106. $shareWithField.autocomplete("search", $shareWithField.val());
  107. },
  108. _getSuggestions: function(searchTerm, perPage, model, lookup) {
  109. if (this._lastSuggestions &&
  110. this._lastSuggestions.searchTerm === searchTerm &&
  111. this._lastSuggestions.lookup === lookup &&
  112. this._lastSuggestions.perPage === perPage &&
  113. this._lastSuggestions.model === model) {
  114. return this._lastSuggestions.promise;
  115. }
  116. var deferred = $.Deferred();
  117. var view = this;
  118. $.get(
  119. OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
  120. {
  121. format: 'json',
  122. search: searchTerm,
  123. lookup: lookup,
  124. perPage: perPage,
  125. itemType: model.get('itemType')
  126. },
  127. function (result) {
  128. if (result.ocs.meta.statuscode === 100) {
  129. var filter = function(users, groups, remotes, remote_groups, emails, circles, rooms) {
  130. if (typeof(emails) === 'undefined') {
  131. emails = [];
  132. }
  133. if (typeof(circles) === 'undefined') {
  134. circles = [];
  135. }
  136. if (typeof(rooms) === 'undefined') {
  137. rooms = [];
  138. }
  139. var usersLength;
  140. var groupsLength;
  141. var remotesLength;
  142. var remoteGroupsLength;
  143. var emailsLength;
  144. var circlesLength;
  145. var roomsLength;
  146. var i, j;
  147. //Filter out the current user
  148. usersLength = users.length;
  149. for (i = 0; i < usersLength; i++) {
  150. if (users[i].value.shareWith === OC.currentUser) {
  151. users.splice(i, 1);
  152. break;
  153. }
  154. }
  155. // Filter out the owner of the share
  156. if (model.hasReshare()) {
  157. usersLength = users.length;
  158. for (i = 0 ; i < usersLength; i++) {
  159. if (users[i].value.shareWith === model.getReshareOwner()) {
  160. users.splice(i, 1);
  161. break;
  162. }
  163. }
  164. }
  165. var shares = model.get('shares');
  166. var sharesLength = shares.length;
  167. // Now filter out all sharees that are already shared with
  168. for (i = 0; i < sharesLength; i++) {
  169. var share = shares[i];
  170. if (share.share_type === OC.Share.SHARE_TYPE_USER) {
  171. usersLength = users.length;
  172. for (j = 0; j < usersLength; j++) {
  173. if (users[j].value.shareWith === share.share_with) {
  174. users.splice(j, 1);
  175. break;
  176. }
  177. }
  178. } else if (share.share_type === OC.Share.SHARE_TYPE_GROUP) {
  179. groupsLength = groups.length;
  180. for (j = 0; j < groupsLength; j++) {
  181. if (groups[j].value.shareWith === share.share_with) {
  182. groups.splice(j, 1);
  183. break;
  184. }
  185. }
  186. } else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE) {
  187. remotesLength = remotes.length;
  188. for (j = 0; j < remotesLength; j++) {
  189. if (remotes[j].value.shareWith === share.share_with) {
  190. remotes.splice(j, 1);
  191. break;
  192. }
  193. }
  194. } else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE_GROUP) {
  195. remoteGroupsLength = remote_groups.length;
  196. for (j = 0; j < remoteGroupsLength; j++) {
  197. if (remote_groups[j].value.shareWith === share.share_with) {
  198. remote_groups.splice(j, 1);
  199. break;
  200. }
  201. }
  202. } else if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
  203. emailsLength = emails.length;
  204. for (j = 0; j < emailsLength; j++) {
  205. if (emails[j].value.shareWith === share.share_with) {
  206. emails.splice(j, 1);
  207. break;
  208. }
  209. }
  210. } else if (share.share_type === OC.Share.SHARE_TYPE_CIRCLE) {
  211. circlesLength = circles.length;
  212. for (j = 0; j < circlesLength; j++) {
  213. if (circles[j].value.shareWith === share.share_with) {
  214. circles.splice(j, 1);
  215. break;
  216. }
  217. }
  218. } else if (share.share_type === OC.Share.SHARE_TYPE_ROOM) {
  219. roomsLength = rooms.length;
  220. for (j = 0; j < roomsLength; j++) {
  221. if (rooms[j].value.shareWith === share.share_with) {
  222. rooms.splice(j, 1);
  223. break;
  224. }
  225. }
  226. }
  227. }
  228. };
  229. filter(
  230. result.ocs.data.exact.users,
  231. result.ocs.data.exact.groups,
  232. result.ocs.data.exact.remotes,
  233. result.ocs.data.exact.remote_groups,
  234. result.ocs.data.exact.emails,
  235. result.ocs.data.exact.circles,
  236. result.ocs.data.exact.rooms
  237. );
  238. var exactUsers = result.ocs.data.exact.users;
  239. var exactGroups = result.ocs.data.exact.groups;
  240. var exactRemotes = result.ocs.data.exact.remotes;
  241. var exactRemoteGroups = result.ocs.data.exact.remote_groups;
  242. var exactEmails = [];
  243. if (typeof(result.ocs.data.emails) !== 'undefined') {
  244. exactEmails = result.ocs.data.exact.emails;
  245. }
  246. var exactCircles = [];
  247. if (typeof(result.ocs.data.circles) !== 'undefined') {
  248. exactCircles = result.ocs.data.exact.circles;
  249. }
  250. var exactRooms = [];
  251. if (typeof(result.ocs.data.rooms) !== 'undefined') {
  252. exactRooms = result.ocs.data.exact.rooms;
  253. }
  254. var exactMatches = exactUsers.concat(exactGroups).concat(exactRemotes).concat(exactRemoteGroups).concat(exactEmails).concat(exactCircles).concat(exactRooms);
  255. filter(
  256. result.ocs.data.users,
  257. result.ocs.data.groups,
  258. result.ocs.data.remotes,
  259. result.ocs.data.remote_groups,
  260. result.ocs.data.emails,
  261. result.ocs.data.circles,
  262. result.ocs.data.rooms
  263. );
  264. var users = result.ocs.data.users;
  265. var groups = result.ocs.data.groups;
  266. var remotes = result.ocs.data.remotes;
  267. var remoteGroups = result.ocs.data.remote_groups;
  268. var lookup = result.ocs.data.lookup;
  269. var lookupEnabled = result.ocs.data.lookupEnabled;
  270. var emails = [];
  271. if (typeof(result.ocs.data.emails) !== 'undefined') {
  272. emails = result.ocs.data.emails;
  273. }
  274. var circles = [];
  275. if (typeof(result.ocs.data.circles) !== 'undefined') {
  276. circles = result.ocs.data.circles;
  277. }
  278. var rooms = [];
  279. if (typeof(result.ocs.data.rooms) !== 'undefined') {
  280. rooms = result.ocs.data.rooms;
  281. }
  282. var suggestions = exactMatches.concat(users).concat(groups).concat(remotes).concat(remoteGroups).concat(emails).concat(circles).concat(rooms).concat(lookup);
  283. function dynamicSort(property) {
  284. return function (a,b) {
  285. var aProperty = '';
  286. var bProperty = '';
  287. if (typeof a[property] !== 'undefined') {
  288. aProperty = a[property];
  289. }
  290. if (typeof b[property] !== 'undefined') {
  291. bProperty = b[property];
  292. }
  293. return (aProperty < bProperty) ? -1 : (aProperty > bProperty) ? 1 : 0;
  294. }
  295. }
  296. /**
  297. * Sort share entries by uuid to properly group them
  298. */
  299. var grouped = suggestions.sort(dynamicSort('uuid'));
  300. var previousUuid = null;
  301. var groupedLength = grouped.length;
  302. var result = [];
  303. /**
  304. * build the result array that only contains all contact entries from
  305. * merged contacts, if the search term matches its contact name
  306. */
  307. for (var i = 0; i < groupedLength; i++) {
  308. if (typeof grouped[i].uuid !== 'undefined' && grouped[i].uuid === previousUuid) {
  309. grouped[i].merged = true;
  310. }
  311. if (searchTerm === grouped[i].name || typeof grouped[i].merged === 'undefined') {
  312. result.push(grouped[i]);
  313. }
  314. previousUuid = grouped[i].uuid;
  315. }
  316. var moreResultsAvailable =
  317. (
  318. OC.config['sharing.maxAutocompleteResults'] > 0
  319. && Math.min(perPage, OC.config['sharing.maxAutocompleteResults'])
  320. <= Math.max(
  321. users.length + exactUsers.length,
  322. groups.length + exactGroups.length,
  323. remoteGroups.length + exactRemoteGroups.length,
  324. remotes.length + exactRemotes.length,
  325. emails.length + exactEmails.length,
  326. circles.length + exactCircles.length,
  327. rooms.length + exactRooms.length,
  328. lookup.length
  329. )
  330. );
  331. if (!view._lookup && lookupEnabled) {
  332. result.push(
  333. {
  334. label: t('core', 'Search globally'),
  335. value: {},
  336. lookup: true
  337. }
  338. )
  339. }
  340. deferred.resolve(result, exactMatches, moreResultsAvailable, lookupEnabled);
  341. } else {
  342. deferred.reject(result.ocs.meta.message);
  343. }
  344. }
  345. ).fail(function() {
  346. deferred.reject();
  347. });
  348. this._lastSuggestions = {
  349. searchTerm: searchTerm,
  350. lookup: lookup,
  351. perPage: perPage,
  352. model: model,
  353. promise: deferred.promise()
  354. };
  355. return this._lastSuggestions.promise;
  356. },
  357. _getRecommendations: function(model) {
  358. if (this._lastRecommendations &&
  359. this._lastRecommendations.model === model) {
  360. return this._lastRecommendations.promise;
  361. }
  362. var deferred = $.Deferred();
  363. $.get(
  364. OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees_recommended',
  365. {
  366. format: 'json',
  367. itemType: model.get('itemType')
  368. },
  369. function (result) {
  370. if (result.ocs.meta.statuscode === 100) {
  371. var filter = function(users, groups, remotes, remote_groups, emails, circles, rooms) {
  372. if (typeof(emails) === 'undefined') {
  373. emails = [];
  374. }
  375. if (typeof(circles) === 'undefined') {
  376. circles = [];
  377. }
  378. if (typeof(rooms) === 'undefined') {
  379. rooms = [];
  380. }
  381. var usersLength;
  382. var groupsLength;
  383. var remotesLength;
  384. var remoteGroupsLength;
  385. var emailsLength;
  386. var circlesLength;
  387. var roomsLength;
  388. var i, j;
  389. //Filter out the current user
  390. usersLength = users.length;
  391. for (i = 0; i < usersLength; i++) {
  392. if (users[i].value.shareWith === OC.currentUser) {
  393. users.splice(i, 1);
  394. break;
  395. }
  396. }
  397. // Filter out the owner of the share
  398. if (model.hasReshare()) {
  399. usersLength = users.length;
  400. for (i = 0 ; i < usersLength; i++) {
  401. if (users[i].value.shareWith === model.getReshareOwner()) {
  402. users.splice(i, 1);
  403. break;
  404. }
  405. }
  406. }
  407. var shares = model.get('shares');
  408. var sharesLength = shares.length;
  409. // Now filter out all sharees that are already shared with
  410. for (i = 0; i < sharesLength; i++) {
  411. var share = shares[i];
  412. if (share.share_type === OC.Share.SHARE_TYPE_USER) {
  413. usersLength = users.length;
  414. for (j = 0; j < usersLength; j++) {
  415. if (users[j].value.shareWith === share.share_with) {
  416. users.splice(j, 1);
  417. break;
  418. }
  419. }
  420. } else if (share.share_type === OC.Share.SHARE_TYPE_GROUP) {
  421. groupsLength = groups.length;
  422. for (j = 0; j < groupsLength; j++) {
  423. if (groups[j].value.shareWith === share.share_with) {
  424. groups.splice(j, 1);
  425. break;
  426. }
  427. }
  428. } else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE) {
  429. remotesLength = remotes.length;
  430. for (j = 0; j < remotesLength; j++) {
  431. if (remotes[j].value.shareWith === share.share_with) {
  432. remotes.splice(j, 1);
  433. break;
  434. }
  435. }
  436. } else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE_GROUP) {
  437. remoteGroupsLength = remote_groups.length;
  438. for (j = 0; j < remoteGroupsLength; j++) {
  439. if (remote_groups[j].value.shareWith === share.share_with) {
  440. remote_groups.splice(j, 1);
  441. break;
  442. }
  443. }
  444. } else if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
  445. emailsLength = emails.length;
  446. for (j = 0; j < emailsLength; j++) {
  447. if (emails[j].value.shareWith === share.share_with) {
  448. emails.splice(j, 1);
  449. break;
  450. }
  451. }
  452. } else if (share.share_type === OC.Share.SHARE_TYPE_CIRCLE) {
  453. circlesLength = circles.length;
  454. for (j = 0; j < circlesLength; j++) {
  455. if (circles[j].value.shareWith === share.share_with) {
  456. circles.splice(j, 1);
  457. break;
  458. }
  459. }
  460. } else if (share.share_type === OC.Share.SHARE_TYPE_ROOM) {
  461. roomsLength = rooms.length;
  462. for (j = 0; j < roomsLength; j++) {
  463. if (rooms[j].value.shareWith === share.share_with) {
  464. rooms.splice(j, 1);
  465. break;
  466. }
  467. }
  468. }
  469. }
  470. };
  471. filter(
  472. result.ocs.data.exact.users,
  473. result.ocs.data.exact.groups,
  474. result.ocs.data.exact.remotes,
  475. result.ocs.data.exact.remote_groups,
  476. result.ocs.data.exact.emails,
  477. result.ocs.data.exact.circles,
  478. result.ocs.data.exact.rooms
  479. );
  480. var exactUsers = result.ocs.data.exact.users;
  481. var exactGroups = result.ocs.data.exact.groups;
  482. var exactRemotes = result.ocs.data.exact.remotes || [];
  483. var exactRemoteGroups = result.ocs.data.exact.remote_groups || [];
  484. var exactEmails = [];
  485. if (typeof(result.ocs.data.emails) !== 'undefined') {
  486. exactEmails = result.ocs.data.exact.emails;
  487. }
  488. var exactCircles = [];
  489. if (typeof(result.ocs.data.circles) !== 'undefined') {
  490. exactCircles = result.ocs.data.exact.circles;
  491. }
  492. var exactRooms = [];
  493. if (typeof(result.ocs.data.rooms) !== 'undefined') {
  494. exactRooms = result.ocs.data.exact.rooms;
  495. }
  496. var exactMatches = exactUsers.concat(exactGroups).concat(exactRemotes).concat(exactRemoteGroups).concat(exactEmails).concat(exactCircles).concat(exactRooms);
  497. filter(
  498. result.ocs.data.users,
  499. result.ocs.data.groups,
  500. result.ocs.data.remotes,
  501. result.ocs.data.remote_groups,
  502. result.ocs.data.emails,
  503. result.ocs.data.circles,
  504. result.ocs.data.rooms
  505. );
  506. var users = result.ocs.data.users;
  507. var groups = result.ocs.data.groups;
  508. var remotes = result.ocs.data.remotes || [];
  509. var remoteGroups = result.ocs.data.remote_groups || [];
  510. var lookup = result.ocs.data.lookup || [];
  511. var emails = [];
  512. if (typeof(result.ocs.data.emails) !== 'undefined') {
  513. emails = result.ocs.data.emails;
  514. }
  515. var circles = [];
  516. if (typeof(result.ocs.data.circles) !== 'undefined') {
  517. circles = result.ocs.data.circles;
  518. }
  519. var rooms = [];
  520. if (typeof(result.ocs.data.rooms) !== 'undefined') {
  521. rooms = result.ocs.data.rooms;
  522. }
  523. var suggestions = exactMatches.concat(users).concat(groups).concat(remotes).concat(remoteGroups).concat(emails).concat(circles).concat(rooms).concat(lookup);
  524. function dynamicSort(property) {
  525. return function (a,b) {
  526. var aProperty = '';
  527. var bProperty = '';
  528. if (typeof a[property] !== 'undefined') {
  529. aProperty = a[property];
  530. }
  531. if (typeof b[property] !== 'undefined') {
  532. bProperty = b[property];
  533. }
  534. return (aProperty < bProperty) ? -1 : (aProperty > bProperty) ? 1 : 0;
  535. }
  536. }
  537. /**
  538. * Sort share entries by uuid to properly group them
  539. */
  540. var grouped = suggestions.sort(dynamicSort('uuid'));
  541. var previousUuid = null;
  542. var groupedLength = grouped.length;
  543. var result = [];
  544. /**
  545. * build the result array that only contains all contact entries from
  546. * merged contacts, if the search term matches its contact name
  547. */
  548. for (var i = 0; i < groupedLength; i++) {
  549. if (typeof grouped[i].uuid !== 'undefined' && grouped[i].uuid === previousUuid) {
  550. grouped[i].merged = true;
  551. }
  552. if (typeof grouped[i].merged === 'undefined') {
  553. result.push(grouped[i]);
  554. }
  555. previousUuid = grouped[i].uuid;
  556. }
  557. deferred.resolve(result, exactMatches, false);
  558. } else {
  559. deferred.reject(result.ocs.meta.message);
  560. }
  561. }
  562. ).fail(function() {
  563. deferred.reject();
  564. });
  565. this._lastRecommendations = {
  566. model: model,
  567. promise: deferred.promise()
  568. };
  569. return this._lastRecommendations.promise;
  570. },
  571. recommendationHandler: function (response) {
  572. var view = this;
  573. var $shareWithField = $('.shareWithField');
  574. this._getRecommendations(
  575. view.model
  576. ).done(function(suggestions) {
  577. console.info('recommendations', suggestions);
  578. if (suggestions.length > 0) {
  579. $shareWithField
  580. .autocomplete("option", "autoFocus", true);
  581. response(suggestions);
  582. } else {
  583. console.info('no sharing recommendations found');
  584. response();
  585. }
  586. }).fail(function(message) {
  587. console.error('could not load recommendations', message)
  588. });
  589. },
  590. autocompleteHandler: function (search, response) {
  591. // If nothing is entered we show recommendations instead of search
  592. // results
  593. if (search.term.length === 0) {
  594. console.info(search.term, 'empty search term -> using recommendations');
  595. this.recommendationHandler(response);
  596. return;
  597. }
  598. var $shareWithField = $('.shareWithField'),
  599. view = this,
  600. $loading = this.$el.find('.shareWithLoading'),
  601. $confirm = this.$el.find('.shareWithConfirm');
  602. var count = OC.config['sharing.minSearchStringLength'];
  603. if (search.term.trim().length < count) {
  604. var title = n('core',
  605. 'At least {count} character is needed for autocompletion',
  606. 'At least {count} characters are needed for autocompletion',
  607. count,
  608. { count: count }
  609. );
  610. $shareWithField.addClass('error')
  611. .attr('data-original-title', title)
  612. .tooltip('hide')
  613. .tooltip({
  614. placement: 'bottom',
  615. trigger: 'manual'
  616. })
  617. .tooltip('fixTitle')
  618. .tooltip('show');
  619. response();
  620. return;
  621. }
  622. $loading.removeClass('hidden');
  623. $loading.addClass('inlineblock');
  624. $confirm.addClass('hidden');
  625. this._pendingOperationsCount++;
  626. $shareWithField.removeClass('error')
  627. .tooltip('hide');
  628. var perPage = parseInt(OC.config['sharing.maxAutocompleteResults'], 10) || 200;
  629. this._getSuggestions(
  630. search.term.trim(),
  631. perPage,
  632. view.model,
  633. view._lookup
  634. ).done(function(suggestions, exactMatches, moreResultsAvailable) {
  635. view._pendingOperationsCount--;
  636. if (view._pendingOperationsCount === 0) {
  637. $loading.addClass('hidden');
  638. $loading.removeClass('inlineblock');
  639. $confirm.removeClass('hidden');
  640. }
  641. if (suggestions.length > 0) {
  642. $shareWithField
  643. .autocomplete("option", "autoFocus", true);
  644. response(suggestions);
  645. // show a notice that the list is truncated
  646. // this is the case if one of the search results is at least as long as the max result config option
  647. if(moreResultsAvailable) {
  648. var message = t('core', 'This list is maybe truncated - please refine your search term to see more results.');
  649. $('.ui-autocomplete').append('<li class="autocomplete-note">' + message + '</li>');
  650. }
  651. } else {
  652. var title = t('core', 'No users or groups found for {search}', {search: $shareWithField.val()});
  653. if (!view.configModel.get('allowGroupSharing')) {
  654. title = t('core', 'No users found for {search}', {search: $('.shareWithField').val()});
  655. }
  656. $shareWithField.addClass('error')
  657. .attr('data-original-title', title)
  658. .tooltip('hide')
  659. .tooltip({
  660. placement: 'top',
  661. trigger: 'manual'
  662. })
  663. .tooltip('fixTitle')
  664. .tooltip('show');
  665. response();
  666. }
  667. }).fail(function(message) {
  668. view._pendingOperationsCount--;
  669. if (view._pendingOperationsCount === 0) {
  670. $loading.addClass('hidden');
  671. $loading.removeClass('inlineblock');
  672. $confirm.removeClass('hidden');
  673. }
  674. if (message) {
  675. OC.Notification.showTemporary(t('core', 'An error occurred ("{message}"). Please try again', { message: message }));
  676. } else {
  677. OC.Notification.showTemporary(t('core', 'An error occurred. Please try again'));
  678. }
  679. });
  680. },
  681. autocompleteRenderItem: function(ul, item) {
  682. var icon = 'icon-user';
  683. var text = escapeHTML(item.label);
  684. var description = '';
  685. var type = '';
  686. var getTranslatedType = function(type) {
  687. switch (type) {
  688. case 'HOME':
  689. return t('core', 'Home');
  690. case 'WORK':
  691. return t('core', 'Work');
  692. case 'OTHER':
  693. return t('core', 'Other');
  694. default:
  695. return '' + type;
  696. }
  697. };
  698. if (typeof item.type !== 'undefined' && item.type !== null) {
  699. type = getTranslatedType(item.type) + ' ';
  700. }
  701. if (typeof item.name !== 'undefined') {
  702. text = escapeHTML(item.name);
  703. }
  704. if (item.value.shareType === OC.Share.SHARE_TYPE_GROUP) {
  705. icon = 'icon-contacts-dark';
  706. } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE) {
  707. icon = 'icon-shared';
  708. description += item.value.shareWith;
  709. } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE_GROUP) {
  710. text = t('core', '{sharee} (remote group)', { sharee: text }, undefined, { escape: false });
  711. icon = 'icon-shared';
  712. description += item.value.shareWith;
  713. } else if (item.value.shareType === OC.Share.SHARE_TYPE_EMAIL) {
  714. icon = 'icon-mail';
  715. description += item.value.shareWith;
  716. } else if (item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) {
  717. text = t('core', '{sharee} ({type}, {owner})', {sharee: text, type: item.value.circleInfo, owner: item.value.circleOwner}, undefined, {escape: false});
  718. icon = 'icon-circle';
  719. } else if (item.value.shareType === OC.Share.SHARE_TYPE_ROOM) {
  720. icon = 'icon-talk';
  721. }
  722. var insert = $("<div class='share-autocomplete-item'/>");
  723. if (item.merged) {
  724. insert.addClass('merged');
  725. text = item.value.shareWith;
  726. description = type;
  727. } else if (item.lookup) {
  728. text = item.label;
  729. icon = false;
  730. insert.append('<span class="icon icon-search search-globally"></span>');
  731. } else {
  732. var avatar = $("<div class='avatardiv'></div>").appendTo(insert);
  733. if (item.value.shareType === OC.Share.SHARE_TYPE_USER || item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) {
  734. avatar.avatar(item.value.shareWith, 32, undefined, undefined, undefined, item.label);
  735. } else {
  736. if (typeof item.uuid === 'undefined') {
  737. item.uuid = text;
  738. }
  739. avatar.imageplaceholder(item.uuid, text, 32);
  740. }
  741. description = type + description;
  742. }
  743. if (description !== '') {
  744. insert.addClass('with-description');
  745. }
  746. $("<div class='autocomplete-item-text'></div>")
  747. .html(
  748. text.replace(
  749. new RegExp(this.term, "gi"),
  750. "<span class='ui-state-highlight'>$&</span>")
  751. + '<span class="autocomplete-item-details">' + description + '</span>'
  752. )
  753. .appendTo(insert);
  754. insert.attr('title', item.value.shareWith);
  755. if (icon) {
  756. insert.append('<span class="icon ' + icon + '" title="' + text + '"></span>');
  757. }
  758. insert = $("<a>")
  759. .append(insert);
  760. return $("<li>")
  761. .addClass((item.value.shareType === OC.Share.SHARE_TYPE_GROUP) ? 'group' : 'user')
  762. .append(insert)
  763. .appendTo(ul);
  764. },
  765. _onSelectRecipient: function(e, s) {
  766. var self = this;
  767. if (e.keyCode == 9) {
  768. e.preventDefault();
  769. if (typeof s.item.name !== 'undefined') {
  770. e.target.value = s.item.name;
  771. } else {
  772. e.target.value = s.item.label;
  773. }
  774. setTimeout(function() {
  775. $(e.target).attr('disabled', false)
  776. .autocomplete('search', $(e.target).val());
  777. }, 0);
  778. return false;
  779. }
  780. if (s.item.lookup) {
  781. // Retrigger search but with global lookup this time
  782. this._lookup = true;
  783. var $shareWithField = this.$el.find('.shareWithField');
  784. var val = $shareWithField.val();
  785. setTimeout(function() {
  786. console.debug('searching again, but globally. search term: ' + val);
  787. $shareWithField.autocomplete("search", val);
  788. }, 0);
  789. return false;
  790. }
  791. e.preventDefault();
  792. // Ensure that the keydown handler for the input field is not
  793. // called; otherwise it would try to add the recipient again, which
  794. // would fail.
  795. e.stopImmediatePropagation();
  796. $(e.target).attr('disabled', true)
  797. .val(s.item.label);
  798. var $loading = this.$el.find('.shareWithLoading');
  799. var $confirm = this.$el.find('.shareWithConfirm');
  800. $loading.removeClass('hidden');
  801. $loading.addClass('inlineblock');
  802. $confirm.addClass('hidden');
  803. this._pendingOperationsCount++;
  804. this.model.addShare(s.item.value, {success: function() {
  805. // Adding a share changes the suggestions.
  806. self._lastSuggestions = undefined;
  807. $(e.target).val('')
  808. .attr('disabled', false);
  809. self._pendingOperationsCount--;
  810. if (self._pendingOperationsCount === 0) {
  811. $loading.addClass('hidden');
  812. $loading.removeClass('inlineblock');
  813. $confirm.removeClass('hidden');
  814. }
  815. }, error: function(obj, msg) {
  816. OC.Notification.showTemporary(msg);
  817. $(e.target).attr('disabled', false)
  818. .autocomplete('search', $(e.target).val());
  819. self._pendingOperationsCount--;
  820. if (self._pendingOperationsCount === 0) {
  821. $loading.addClass('hidden');
  822. $loading.removeClass('inlineblock');
  823. $confirm.removeClass('hidden');
  824. }
  825. }});
  826. },
  827. _confirmShare: function() {
  828. var self = this;
  829. var $shareWithField = $('.shareWithField');
  830. var $loading = this.$el.find('.shareWithLoading');
  831. var $confirm = this.$el.find('.shareWithConfirm');
  832. $loading.removeClass('hidden');
  833. $loading.addClass('inlineblock');
  834. $confirm.addClass('hidden');
  835. this._pendingOperationsCount++;
  836. $shareWithField.prop('disabled', true);
  837. // Disabling the autocompletion does not clear its search timeout;
  838. // removing the focus from the input field does, but only if the
  839. // autocompletion is not disabled when the field loses the focus.
  840. // Thus, the field has to be disabled before disabling the
  841. // autocompletion to prevent an old pending search result from
  842. // appearing once the field is enabled again.
  843. $shareWithField.autocomplete('close');
  844. $shareWithField.autocomplete('disable');
  845. var restoreUI = function() {
  846. self._pendingOperationsCount--;
  847. if (self._pendingOperationsCount === 0) {
  848. $loading.addClass('hidden');
  849. $loading.removeClass('inlineblock');
  850. $confirm.removeClass('hidden');
  851. }
  852. $shareWithField.prop('disabled', false);
  853. $shareWithField.focus();
  854. };
  855. var perPage = parseInt(OC.config['sharing.maxAutocompleteResults'], 10) || 200;
  856. this._getSuggestions(
  857. $shareWithField.val(),
  858. perPage,
  859. this.model,
  860. this._lookup
  861. ).done(function(suggestions, exactMatches) {
  862. if (suggestions.length === 0) {
  863. restoreUI();
  864. $shareWithField.autocomplete('enable');
  865. // There is no need to show an error message here; it will
  866. // be automatically shown when the autocomplete is activated
  867. // again (due to the focus on the field) and it finds no
  868. // matches.
  869. return;
  870. }
  871. if (exactMatches.length !== 1) {
  872. restoreUI();
  873. $shareWithField.autocomplete('enable');
  874. return;
  875. }
  876. var actionSuccess = function() {
  877. // Adding a share changes the suggestions.
  878. self._lastSuggestions = undefined;
  879. $shareWithField.val('');
  880. restoreUI();
  881. $shareWithField.autocomplete('enable');
  882. };
  883. var actionError = function(obj, msg) {
  884. restoreUI();
  885. $shareWithField.autocomplete('enable');
  886. OC.Notification.showTemporary(msg);
  887. };
  888. self.model.addShare(exactMatches[0].value, {
  889. success: actionSuccess,
  890. error: actionError
  891. });
  892. }).fail(function(message) {
  893. restoreUI();
  894. $shareWithField.autocomplete('enable');
  895. // There is no need to show an error message here; it will be
  896. // automatically shown when the autocomplete is activated again
  897. // (due to the focus on the field) and getting the suggestions
  898. // fail.
  899. });
  900. },
  901. _toggleLoading: function(state) {
  902. this._loading = state;
  903. this.$el.find('.subView').toggleClass('hidden', state);
  904. this.$el.find('.loading').toggleClass('hidden', !state);
  905. },
  906. _onRequest: function() {
  907. // only show the loading spinner for the first request (for now)
  908. if (!this._loadingOnce) {
  909. this._toggleLoading(true);
  910. }
  911. },
  912. _onEndRequest: function() {
  913. var self = this;
  914. this._toggleLoading(false);
  915. if (!this._loadingOnce) {
  916. this._loadingOnce = true;
  917. }
  918. },
  919. render: function() {
  920. var self = this;
  921. var baseTemplate = OC.Share.Templates['sharedialogview'];
  922. this.$el.html(baseTemplate({
  923. cid: this.cid,
  924. shareLabel: t('core', 'Share'),
  925. sharePlaceholder: this._renderSharePlaceholderPart(),
  926. isSharingAllowed: this.model.sharePermissionPossible()
  927. }));
  928. var $shareField = this.$el.find('.shareWithField');
  929. if ($shareField.length) {
  930. var shareFieldKeydownHandler = function(event) {
  931. if (event.keyCode !== 13) {
  932. return true;
  933. }
  934. self._confirmShare();
  935. return false;
  936. };
  937. $shareField.autocomplete({
  938. minLength: 0,
  939. delay: 750,
  940. focus: function(event) {
  941. event.preventDefault();
  942. },
  943. source: this.autocompleteHandler,
  944. select: this._onSelectRecipient,
  945. open: function() {
  946. var autocomplete = $(this).autocomplete('widget');
  947. var numberOfItems = autocomplete.find('li').size();
  948. autocomplete.removeClass('item-count-1');
  949. autocomplete.removeClass('item-count-2');
  950. if (numberOfItems <= 2) {
  951. autocomplete.addClass('item-count-' + numberOfItems);
  952. }
  953. }
  954. }).data('ui-autocomplete')._renderItem = this.autocompleteRenderItem;
  955. $shareField.on('keydown', null, shareFieldKeydownHandler);
  956. }
  957. this.resharerInfoView.$el = this.$el.find('.resharerInfoView');
  958. this.resharerInfoView.render();
  959. this.linkShareView.$el = this.$el.find('.linkShareView');
  960. this.linkShareView.render();
  961. this.shareeListView.$el = this.$el.find('.shareeListView');
  962. this.shareeListView.render();
  963. this.$el.find('.hasTooltip').tooltip();
  964. return this;
  965. },
  966. /**
  967. * sets whether share by link should be displayed or not. Default is
  968. * true.
  969. *
  970. * @param {bool} showLink
  971. */
  972. setShowLink: function(showLink) {
  973. this._showLink = (typeof showLink === 'boolean') ? showLink : true;
  974. this.linkShareView.showLink = this._showLink;
  975. },
  976. _renderSharePlaceholderPart: function () {
  977. var allowRemoteSharing = this.configModel.get('isRemoteShareAllowed');
  978. var allowMailSharing = this.configModel.get('isMailShareAllowed');
  979. if (!allowRemoteSharing && allowMailSharing) {
  980. return t('core', 'Name or email address...');
  981. }
  982. if (allowRemoteSharing && !allowMailSharing) {
  983. return t('core', 'Name or federated cloud ID...');
  984. }
  985. if (allowRemoteSharing && allowMailSharing) {
  986. return t('core', 'Name, federated cloud ID or email address...');
  987. }
  988. return t('core', 'Name...');
  989. },
  990. });
  991. OC.Share.ShareDialogView = ShareDialogView;
  992. })();