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.

sharedialoglinkshareview.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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 Clipboard, Handlebars */
  11. (function() {
  12. if (!OC.Share) {
  13. OC.Share = {};
  14. }
  15. var PASSWORD_PLACEHOLDER = '**********';
  16. var PASSWORD_PLACEHOLDER_MESSAGE = t('core', 'Choose a password for the public link');
  17. var PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL = t('core', 'Choose a password for the public link or press the "Enter" key');
  18. var TEMPLATE =
  19. '{{#if shareAllowed}}' +
  20. '<span class="icon-loading-small hidden"></span>' +
  21. '<input type="checkbox" name="linkCheckbox" id="linkCheckbox-{{cid}}" class="checkbox linkCheckbox" value="1" {{#if isLinkShare}}checked="checked"{{/if}} />' +
  22. '<label for="linkCheckbox-{{cid}}">{{linkShareLabel}}</label>' +
  23. '<br />' +
  24. '<div class="oneline">' +
  25. '<label for="linkText-{{cid}}" class="hidden-visually">{{urlLabel}}</label>' +
  26. '<input id="linkText-{{cid}}" class="linkText {{#unless isLinkShare}}hidden{{/unless}}" type="text" readonly="readonly" value="{{shareLinkURL}}" />' +
  27. '{{#if singleAction}}' +
  28. '<a class="{{#unless isLinkShare}}hidden-visually{{/unless}} clipboardButton icon icon-clippy" data-clipboard-target="#linkText-{{cid}}"></a>' +
  29. '{{else}}' +
  30. '<a class="{{#unless isLinkShare}}hidden-visually{{/unless}}" href="#"><span class="linkMore icon icon-more"></span></a>' +
  31. '{{{popoverMenu}}}' +
  32. '{{/if}}' +
  33. '</div>' +
  34. '{{#if publicUpload}}' +
  35. '<div>' +
  36. '<span class="icon-loading-small hidden"></span>' +
  37. '<input type="radio" name="publicUpload" value="{{publicUploadRValue}}" id="sharingDialogAllowPublicUpload-r-{{cid}}" class="radio publicUploadRadio" {{{publicUploadRChecked}}} />' +
  38. '<label for="sharingDialogAllowPublicUpload-r-{{cid}}">{{publicUploadRLabel}}</label>' +
  39. '</div>' +
  40. '<div>' +
  41. '<span class="icon-loading-small hidden"></span>' +
  42. '<input type="radio" name="publicUpload" value="{{publicUploadRWValue}}" id="sharingDialogAllowPublicUpload-rw-{{cid}}" class="radio publicUploadRadio" {{{publicUploadRWChecked}}} />' +
  43. '<label for="sharingDialogAllowPublicUpload-rw-{{cid}}">{{publicUploadRWLabel}}</label>' +
  44. '</div>' +
  45. '<div>' +
  46. '<span class="icon-loading-small hidden"></span>' +
  47. '<input type="radio" name="publicUpload" value="{{publicUploadWValue}}" id="sharingDialogAllowPublicUpload-w-{{cid}}" class="radio publicUploadRadio" {{{publicUploadWChecked}}} />' +
  48. '<label for="sharingDialogAllowPublicUpload-w-{{cid}}">{{publicUploadWLabel}}</label>' +
  49. '</div>' +
  50. '{{/if}}' +
  51. ' {{#if publicEditing}}' +
  52. '<div id="allowPublicEditingWrapper">' +
  53. ' <span class="icon-loading-small hidden"></span>' +
  54. ' <input type="checkbox" value="1" name="allowPublicEditing" id="sharingDialogAllowPublicEditing-{{cid}}" class="checkbox publicEditingCheckbox" {{{publicEditingChecked}}} />' +
  55. '<label for="sharingDialogAllowPublicEditing-{{cid}}">{{publicEditingLabel}}</label>' +
  56. '</div>' +
  57. ' {{/if}}' +
  58. ' {{#if showPasswordCheckBox}}' +
  59. '<input type="checkbox" name="showPassword" id="showPassword-{{cid}}" class="checkbox showPasswordCheckbox" {{#if isPasswordSet}}checked="checked"{{/if}} value="1" />' +
  60. '<label for="showPassword-{{cid}}">{{enablePasswordLabel}}</label>' +
  61. ' {{/if}}' +
  62. '<div id="linkPass" class="oneline linkPass {{#unless isPasswordSet}}hidden{{/unless}}">' +
  63. ' <label for="linkPassText-{{cid}}" class="hidden-visually">{{passwordLabel}}</label>' +
  64. ' {{#if showPasswordCheckBox}}' +
  65. ' <input id="linkPassText-{{cid}}" class="linkPassText" type="password" placeholder="{{passwordPlaceholder}}" autocomplete="new-password" />' +
  66. ' {{else}}' +
  67. ' <input id="linkPassText-{{cid}}" class="linkPassText" type="password" placeholder="{{passwordPlaceholderInitial}}" autocomplete="new-password" />' +
  68. ' {{/if}}' +
  69. ' <span class="icon icon-loading-small hidden"></span>' +
  70. '</div>' +
  71. '{{else}}' +
  72. // FIXME: this doesn't belong in this view
  73. '{{#if noSharingPlaceholder}}<input id="shareWith-{{cid}}" class="shareWithField" type="text" placeholder="{{noSharingPlaceholder}}" disabled="disabled"/>{{/if}}' +
  74. '{{/if}}'
  75. ;
  76. var TEMPLATE_POPOVER_MENU =
  77. '<div class="popovermenu bubble hidden menu socialSharingMenu">' +
  78. '<ul>' +
  79. '<li>' +
  80. '<a href="#" class="shareOption menuitem clipboardButton" data-clipboard-target="#linkText-{{cid}}">' +
  81. '<span class="icon icon-clippy" ></span>' +
  82. '<span>{{copyLabel}}</span>' +
  83. '</a>' +
  84. '</li>' +
  85. '{{#each social}}' +
  86. '<li>' +
  87. '<a href="#" class="shareOption menuitem pop-up" data-url="{{url}}" data-window="{{newWindow}}">' +
  88. '<span class="icon {{iconClass}}"' +
  89. '></span><span>{{label}}' +
  90. '</span>' +
  91. '</a>' +
  92. '</li>' +
  93. '{{/each}}' +
  94. '</ul>' +
  95. '</div>';
  96. /**
  97. * @class OCA.Share.ShareDialogLinkShareView
  98. * @member {OC.Share.ShareItemModel} model
  99. * @member {jQuery} $el
  100. * @memberof OCA.Sharing
  101. * @classdesc
  102. *
  103. * Represents the GUI of the share dialogue
  104. *
  105. */
  106. var ShareDialogLinkShareView = OC.Backbone.View.extend({
  107. /** @type {string} **/
  108. id: 'shareDialogLinkShare',
  109. /** @type {OC.Share.ShareConfigModel} **/
  110. configModel: undefined,
  111. /** @type {Function} **/
  112. _template: undefined,
  113. /** @type {Function} **/
  114. _popoverMenuTemplate: undefined,
  115. /** @type {boolean} **/
  116. showLink: true,
  117. events: {
  118. 'focusout input.linkPassText': 'onPasswordEntered',
  119. 'keyup input.linkPassText': 'onPasswordKeyUp',
  120. 'click .linkCheckbox': 'onLinkCheckBoxChange',
  121. 'click .linkText': 'onLinkTextClick',
  122. 'change .publicEditingCheckbox': 'onAllowPublicEditingChange',
  123. 'click .showPasswordCheckbox': 'onShowPasswordClick',
  124. 'click .icon-more': 'onToggleMenu',
  125. 'click .pop-up': 'onPopUpClick',
  126. 'change .publicUploadRadio': 'onPublicUploadChange'
  127. },
  128. initialize: function(options) {
  129. var view = this;
  130. this.model.on('change:permissions', function() {
  131. view.render();
  132. });
  133. this.model.on('change:itemType', function() {
  134. view.render();
  135. });
  136. this.model.on('change:allowPublicUploadStatus', function() {
  137. view.render();
  138. });
  139. this.model.on('change:hideFileListStatus', function() {
  140. view.render();
  141. });
  142. this.model.on('change:linkShare', function() {
  143. view.render();
  144. });
  145. if(!_.isUndefined(options.configModel)) {
  146. this.configModel = options.configModel;
  147. } else {
  148. throw 'missing OC.Share.ShareConfigModel';
  149. }
  150. _.bindAll(
  151. this,
  152. 'onLinkCheckBoxChange',
  153. 'onPasswordEntered',
  154. 'onPasswordKeyUp',
  155. 'onLinkTextClick',
  156. 'onShowPasswordClick',
  157. 'onAllowPublicEditingChange',
  158. 'onPublicUploadChange'
  159. );
  160. var clipboard = new Clipboard('.clipboardButton');
  161. clipboard.on('success', function(e) {
  162. var $input = $(e.trigger);
  163. $input.tooltip('hide')
  164. .attr('data-original-title', t('core', 'Copied!'))
  165. .tooltip('fixTitle')
  166. .tooltip({placement: 'bottom', trigger: 'manual'})
  167. .tooltip('show');
  168. _.delay(function() {
  169. $input.tooltip('hide');
  170. if (OC.Share.Social.Collection.size() == 0) {
  171. $input.attr('data-original-title', t('core', 'Copy'))
  172. .tooltip('fixTitle');
  173. } else {
  174. $input.tooltip("destroy");
  175. }
  176. }, 3000);
  177. });
  178. clipboard.on('error', function (e) {
  179. var $input = $(e.trigger);
  180. var actionMsg = '';
  181. if (/iPhone|iPad/i.test(navigator.userAgent)) {
  182. actionMsg = t('core', 'Not supported!');
  183. } else if (/Mac/i.test(navigator.userAgent)) {
  184. actionMsg = t('core', 'Press ⌘-C to copy.');
  185. } else {
  186. actionMsg = t('core', 'Press Ctrl-C to copy.');
  187. }
  188. $input.tooltip('hide')
  189. .attr('data-original-title', actionMsg)
  190. .tooltip('fixTitle')
  191. .tooltip({placement: 'bottom', trigger: 'manual'})
  192. .tooltip('show');
  193. _.delay(function () {
  194. $input.tooltip('hide');
  195. if (OC.Share.Social.Collection.size() == 0) {
  196. $input.attr('data-original-title', t('core', 'Copy'))
  197. .tooltip('fixTitle');
  198. } else {
  199. $input.tooltip("destroy");
  200. }
  201. }, 3000);
  202. });
  203. },
  204. onLinkCheckBoxChange: function() {
  205. var $checkBox = this.$el.find('.linkCheckbox');
  206. var $loading = $checkBox.siblings('.icon-loading-small');
  207. if(!$loading.hasClass('hidden')) {
  208. return false;
  209. }
  210. if($checkBox.is(':checked')) {
  211. if(this.configModel.get('enforcePasswordForPublicLink') === false && this.configModel.get('enableLinkPasswordByDefault') === false) {
  212. $loading.removeClass('hidden');
  213. // this will create it
  214. this.model.saveLinkShare();
  215. } else {
  216. this.$el.find('.linkPass').slideToggle(OC.menuSpeed);
  217. this.$el.find('.linkPassText').focus();
  218. }
  219. } else {
  220. if (this.model.get('linkShare').isLinkShare) {
  221. $loading.removeClass('hidden');
  222. this.model.removeLinkShare();
  223. } else {
  224. this.$el.find('.linkPass').slideToggle(OC.menuSpeed);
  225. }
  226. }
  227. },
  228. onLinkTextClick: function() {
  229. var $el = this.$el.find('.linkText');
  230. $el.focus();
  231. $el.select();
  232. },
  233. onShowPasswordClick: function() {
  234. this.$el.find('.linkPass').slideToggle(OC.menuSpeed);
  235. if(!this.$el.find('.showPasswordCheckbox').is(':checked')) {
  236. this.model.saveLinkShare({
  237. password: ''
  238. });
  239. } else {
  240. if (!OC.Util.isIE()) {
  241. this.$el.find('.linkPassText').focus();
  242. }
  243. }
  244. },
  245. onPasswordKeyUp: function(event) {
  246. if(event.keyCode === 13) {
  247. this.onPasswordEntered();
  248. }
  249. },
  250. onPasswordEntered: function() {
  251. var $loading = this.$el.find('.linkPass .icon-loading-small');
  252. if (!$loading.hasClass('hidden')) {
  253. // still in process
  254. return;
  255. }
  256. var $input = this.$el.find('.linkPassText');
  257. $input.removeClass('error');
  258. var password = $input.val();
  259. if (this.$el.find('.linkPassText').attr('placeholder') === PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL) {
  260. // in IE9 the password might be the placeholder due to bugs in the placeholders polyfill
  261. if(password === PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL) {
  262. password = '';
  263. }
  264. } else {
  265. // in IE9 the password might be the placeholder due to bugs in the placeholders polyfill
  266. if(password === '' || password === PASSWORD_PLACEHOLDER || password === PASSWORD_PLACEHOLDER_MESSAGE) {
  267. return;
  268. }
  269. }
  270. $loading
  271. .removeClass('hidden')
  272. .addClass('inlineblock');
  273. this.model.saveLinkShare({
  274. password: password
  275. }, {
  276. complete: function(model) {
  277. $loading.removeClass('inlineblock').addClass('hidden');
  278. },
  279. error: function(model, msg) {
  280. // destroy old tooltips
  281. $input.tooltip('destroy');
  282. $input.addClass('error');
  283. $input.attr('title', msg);
  284. $input.tooltip({placement: 'bottom', trigger: 'manual'});
  285. $input.tooltip('show');
  286. }
  287. });
  288. },
  289. onAllowPublicEditingChange: function() {
  290. var $checkbox = this.$('.publicEditingCheckbox');
  291. $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock');
  292. var permissions = OC.PERMISSION_READ;
  293. if($checkbox.is(':checked')) {
  294. permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_READ;
  295. }
  296. this.model.saveLinkShare({
  297. permissions: permissions
  298. });
  299. },
  300. onPublicUploadChange: function(e) {
  301. var permissions = e.currentTarget.value;
  302. this.model.saveLinkShare({
  303. permissions: permissions
  304. });
  305. },
  306. render: function() {
  307. var linkShareTemplate = this.template();
  308. var resharingAllowed = this.model.sharePermissionPossible();
  309. if(!resharingAllowed
  310. || !this.showLink
  311. || !this.configModel.isShareWithLinkAllowed())
  312. {
  313. var templateData = {shareAllowed: false};
  314. if (!resharingAllowed) {
  315. // add message
  316. templateData.noSharingPlaceholder = t('core', 'Resharing is not allowed');
  317. }
  318. this.$el.html(linkShareTemplate(templateData));
  319. return this;
  320. }
  321. var publicUpload =
  322. this.model.isFolder()
  323. && this.model.createPermissionPossible()
  324. && this.configModel.isPublicUploadEnabled();
  325. var publicUploadRWChecked = '';
  326. var publicUploadRChecked = '';
  327. var publicUploadWChecked = '';
  328. switch (this.model.linkSharePermissions()) {
  329. case OC.PERMISSION_READ:
  330. publicUploadRChecked = 'checked';
  331. break;
  332. case OC.PERMISSION_CREATE:
  333. publicUploadWChecked = 'checked';
  334. break;
  335. case OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE:
  336. publicUploadRWChecked = 'checked';
  337. break;
  338. }
  339. var publicEditingChecked = '';
  340. if(this.model.isPublicEditingAllowed()) {
  341. publicEditingChecked = 'checked="checked"';
  342. }
  343. var isLinkShare = this.model.get('linkShare').isLinkShare;
  344. var isPasswordSet = !!this.model.get('linkShare').password;
  345. var showPasswordCheckBox = isLinkShare
  346. && ( !this.configModel.get('enforcePasswordForPublicLink')
  347. || !this.model.get('linkShare').password);
  348. var passwordPlaceholderInitial = this.configModel.get('enforcePasswordForPublicLink')
  349. ? PASSWORD_PLACEHOLDER_MESSAGE : PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL;
  350. var publicEditable =
  351. !this.model.isFolder()
  352. && isLinkShare
  353. && this.model.updatePermissionPossible();
  354. var link = this.model.get('linkShare').link;
  355. var social = [];
  356. OC.Share.Social.Collection.each(function(model) {
  357. var url = model.get('url');
  358. url = url.replace('{{reference}}', link);
  359. social.push({
  360. url: url,
  361. label: t('core', 'Share to {name}', {name: model.get('name')}),
  362. name: model.get('name'),
  363. iconClass: model.get('iconClass'),
  364. newWindow: model.get('newWindow')
  365. });
  366. });
  367. var popover = this.popoverMenuTemplate({
  368. cid: this.cid,
  369. copyLabel: t('core', 'Copy'),
  370. social: social
  371. });
  372. this.$el.html(linkShareTemplate({
  373. cid: this.cid,
  374. shareAllowed: true,
  375. isLinkShare: isLinkShare,
  376. shareLinkURL: this.model.get('linkShare').link,
  377. linkShareLabel: t('core', 'Share link'),
  378. urlLabel: t('core', 'Link'),
  379. enablePasswordLabel: t('core', 'Password protect'),
  380. passwordLabel: t('core', 'Password'),
  381. passwordPlaceholder: isPasswordSet ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE,
  382. passwordPlaceholderInitial: passwordPlaceholderInitial,
  383. isPasswordSet: isPasswordSet,
  384. showPasswordCheckBox: showPasswordCheckBox,
  385. publicUpload: publicUpload && isLinkShare,
  386. publicEditing: publicEditable,
  387. publicEditingChecked: publicEditingChecked,
  388. publicEditingLabel: t('core', 'Allow editing'),
  389. mailPrivatePlaceholder: t('core', 'Email link to person'),
  390. mailButtonText: t('core', 'Send'),
  391. singleAction: OC.Share.Social.Collection.size() == 0,
  392. popoverMenu: popover,
  393. publicUploadRWLabel: t('core', 'Allow upload and editing'),
  394. publicUploadRLabel: t('core', 'Read only'),
  395. publicUploadWLabel: t('core', 'File drop (upload only)'),
  396. publicUploadRWValue: OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE,
  397. publicUploadRValue: OC.PERMISSION_READ,
  398. publicUploadWValue: OC.PERMISSION_CREATE,
  399. publicUploadRWChecked: publicUploadRWChecked,
  400. publicUploadRChecked: publicUploadRChecked,
  401. publicUploadWChecked: publicUploadWChecked
  402. }));
  403. if (OC.Share.Social.Collection.size() == 0) {
  404. this.$el.find('.clipboardButton').tooltip({
  405. placement: 'bottom',
  406. title: t('core', 'Copy'),
  407. trigger: 'hover'
  408. });
  409. }
  410. this.delegateEvents();
  411. return this;
  412. },
  413. onToggleMenu: function(event) {
  414. event.preventDefault();
  415. event.stopPropagation();
  416. var $element = $(event.target);
  417. var $li = $element.closest('.oneline');
  418. var $menu = $li.find('.popovermenu');
  419. OC.showMenu(null, $menu);
  420. this._menuOpen = $li.data('share-id');
  421. },
  422. /**
  423. * @returns {Function} from Handlebars
  424. * @private
  425. */
  426. template: function () {
  427. if (!this._template) {
  428. this._template = Handlebars.compile(TEMPLATE);
  429. }
  430. return this._template;
  431. },
  432. /**
  433. * renders the popover template and returns the resulting HTML
  434. *
  435. * @param {Object} data
  436. * @returns {string}
  437. */
  438. popoverMenuTemplate: function(data) {
  439. if(!this._popoverMenuTemplate) {
  440. this._popoverMenuTemplate = Handlebars.compile(TEMPLATE_POPOVER_MENU);
  441. }
  442. return this._popoverMenuTemplate(data);
  443. },
  444. onPopUpClick: function(event) {
  445. event.preventDefault();
  446. event.stopPropagation();
  447. var url = $(event.currentTarget).data('url');
  448. var newWindow = $(event.currentTarget).data('window');
  449. $(event.currentTarget).tooltip('hide');
  450. if (url) {
  451. if (newWindow === true) {
  452. var width = 600;
  453. var height = 400;
  454. var left = (screen.width / 2) - (width / 2);
  455. var top = (screen.height / 2) - (height / 2);
  456. window.open(url, 'name', 'width=' + width + ', height=' + height + ', top=' + top + ', left=' + left);
  457. } else {
  458. window.location.href = url;
  459. }
  460. }
  461. }
  462. });
  463. OC.Share.ShareDialogLinkShareView = ShareDialogLinkShareView;
  464. })();