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.

public.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /*
  2. * Copyright (c) 2014
  3. * @copyright Copyright (c) 2016, Björn Schießle <bjoern@schiessle.org>
  4. *
  5. * This file is licensed under the Affero General Public License version 3
  6. * or later.
  7. *
  8. * See the COPYING-README file.
  9. *
  10. */
  11. /* global FileActions, Files, FileList */
  12. /* global dragOptions, folderDropOptions */
  13. if (!OCA.Sharing) {
  14. OCA.Sharing = {};
  15. }
  16. if (!OCA.Files) {
  17. OCA.Files = {};
  18. }
  19. /**
  20. * @namespace
  21. */
  22. OCA.Sharing.PublicApp = {
  23. _initialized: false,
  24. /**
  25. * Initializes the public share app.
  26. *
  27. * @param $el container
  28. */
  29. initialize: function ($el) {
  30. var self = this;
  31. var fileActions;
  32. if (this._initialized) {
  33. return;
  34. }
  35. fileActions = new OCA.Files.FileActions();
  36. // default actions
  37. fileActions.registerDefaultActions();
  38. // legacy actions
  39. fileActions.merge(window.FileActions);
  40. // regular actions
  41. fileActions.merge(OCA.Files.fileActions);
  42. // in case apps would decide to register file actions later,
  43. // replace the global object with this one
  44. OCA.Files.fileActions = fileActions;
  45. this._initialized = true;
  46. this.initialDir = $('#dir').val();
  47. var token = $('#sharingToken').val();
  48. // file list mode ?
  49. if ($el.find('#filestable').length) {
  50. var filesClient = new OC.Files.Client({
  51. host: OC.getHost(),
  52. port: OC.getPort(),
  53. userName: token,
  54. // note: password not be required, the endpoint
  55. // will recognize previous validation from the session
  56. root: OC.getRootPath() + '/public.php/webdav',
  57. useHTTPS: OC.getProtocol() === 'https'
  58. });
  59. this.fileList = new OCA.Files.FileList(
  60. $el,
  61. {
  62. id: 'files.public',
  63. scrollContainer: $('#content-wrapper'),
  64. dragOptions: dragOptions,
  65. folderDropOptions: folderDropOptions,
  66. fileActions: fileActions,
  67. detailsViewEnabled: false,
  68. filesClient: filesClient,
  69. enableUpload: true
  70. }
  71. );
  72. this.files = OCA.Files.Files;
  73. this.files.initialize();
  74. // TODO: move to PublicFileList.initialize() once
  75. // the code was split into a separate class
  76. OC.Plugins.attach('OCA.Sharing.PublicFileList', this.fileList);
  77. }
  78. var mimetype = $('#mimetype').val();
  79. var mimetypeIcon = $('#mimetypeIcon').val();
  80. mimetypeIcon = mimetypeIcon.substring(0, mimetypeIcon.length - 3);
  81. mimetypeIcon = mimetypeIcon + 'svg';
  82. var previewSupported = $('#previewSupported').val();
  83. if (typeof FileActions !== 'undefined') {
  84. // Show file preview if previewer is available, images are already handled by the template
  85. if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) {
  86. // Trigger default action if not download TODO
  87. var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ);
  88. if (typeof action !== 'undefined') {
  89. action($('#filename').val());
  90. }
  91. }
  92. }
  93. // dynamically load image previews
  94. var bottomMargin = 350;
  95. var previewWidth = $(window).width();
  96. var previewHeight = $(window).height() - bottomMargin;
  97. previewHeight = Math.max(200, previewHeight);
  98. var params = {
  99. x: Math.ceil(previewWidth * window.devicePixelRatio),
  100. y: Math.ceil(previewHeight * window.devicePixelRatio),
  101. a: 'true',
  102. file: encodeURIComponent(this.initialDir + $('#filename').val()),
  103. t: token,
  104. scalingup: 0
  105. };
  106. var imgcontainer = $('<a href="' + $('#previewURL').val()
  107. + '" target="_blank"><img class="publicpreview" alt=""></a>');
  108. var img = imgcontainer.find('.publicpreview');
  109. img.css({
  110. 'max-width': previewWidth,
  111. 'max-height': previewHeight
  112. });
  113. var fileSize = parseInt($('#filesize').val(), 10);
  114. var maxGifSize = parseInt($('#maxSizeAnimateGif').val(), 10);
  115. if (mimetype === 'image/gif' &&
  116. (maxGifSize === -1 || fileSize <= (maxGifSize * 1024 * 1024))) {
  117. img.attr('src', $('#downloadURL').val());
  118. imgcontainer.appendTo('#imgframe');
  119. } else if (mimetype.substr(0, mimetype.indexOf('/')) === 'text' && window.btoa) {
  120. // Undocumented Url to public WebDAV endpoint
  121. var url = parent.location.protocol + '//' + location.host + OC.linkTo('', 'public.php/webdav');
  122. $.ajax({
  123. url: url,
  124. headers: {
  125. Authorization: 'Basic ' + btoa(token + ':'),
  126. Range: 'bytes=0-1000'
  127. }
  128. }).then(function (data) {
  129. self._showTextPreview(data, previewHeight);
  130. });
  131. } else if ((previewSupported === 'true' && mimetype.substr(0, mimetype.indexOf('/')) !== 'video') ||
  132. mimetype.substr(0, mimetype.indexOf('/')) === 'image' &&
  133. mimetype !== 'image/svg+xml') {
  134. img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params));
  135. imgcontainer.appendTo('#imgframe');
  136. } else if (mimetype.substr(0, mimetype.indexOf('/')) !== 'video') {
  137. img.attr('src', OC.Util.replaceSVGIcon(mimetypeIcon));
  138. img.attr('width', 128);
  139. imgcontainer.appendTo('#imgframe');
  140. }
  141. else if (previewSupported === 'true') {
  142. $('#imgframe > video').attr('poster', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params));
  143. }
  144. if (this.fileList) {
  145. // TODO: move this to a separate PublicFileList class that extends OCA.Files.FileList (+ unit tests)
  146. this.fileList.getDownloadUrl = function (filename, dir, isDir) {
  147. var path = dir || this.getCurrentDirectory();
  148. if (_.isArray(filename)) {
  149. filename = JSON.stringify(filename);
  150. }
  151. var params = {
  152. path: path
  153. };
  154. if (filename) {
  155. params.files = filename;
  156. }
  157. return OC.generateUrl('/s/' + token + '/download') + '?' + OC.buildQueryString(params);
  158. };
  159. this.fileList.getUploadUrl = function(fileName, dir) {
  160. if (_.isUndefined(dir)) {
  161. dir = this.getCurrentDirectory();
  162. }
  163. var pathSections = dir.split('/');
  164. if (!_.isUndefined(fileName)) {
  165. pathSections.push(fileName);
  166. }
  167. var encodedPath = '';
  168. _.each(pathSections, function(section) {
  169. if (section !== '') {
  170. encodedPath += '/' + encodeURIComponent(section);
  171. }
  172. });
  173. var base = '';
  174. if (!this._uploader.isXHRUpload()) {
  175. // also add auth in URL due to POST workaround
  176. base = OC.getProtocol() + '://' + token + '@' + OC.getHost() + (OC.getPort() ? ':' + OC.getPort() : '');
  177. }
  178. return base + OC.getRootPath() + '/public.php/webdav' + encodedPath;
  179. };
  180. this.fileList.getAjaxUrl = function (action, params) {
  181. params = params || {};
  182. params.t = token;
  183. return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params);
  184. };
  185. this.fileList.linkTo = function (dir) {
  186. return OC.generateUrl('/s/' + token + '', {dir: dir});
  187. };
  188. this.fileList.generatePreviewUrl = function (urlSpec) {
  189. urlSpec = urlSpec || {};
  190. if (!urlSpec.x) {
  191. urlSpec.x = 32;
  192. }
  193. if (!urlSpec.y) {
  194. urlSpec.y = 32;
  195. }
  196. urlSpec.x *= window.devicePixelRatio;
  197. urlSpec.y *= window.devicePixelRatio;
  198. urlSpec.x = Math.ceil(urlSpec.x);
  199. urlSpec.y = Math.ceil(urlSpec.y);
  200. urlSpec.t = $('#dirToken').val();
  201. return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec);
  202. };
  203. this.fileList.updateEmptyContent = function() {
  204. this.$el.find('#emptycontent .uploadmessage').text(
  205. t('files_sharing', 'You can upload into this folder')
  206. );
  207. OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments);
  208. };
  209. this.fileList._uploader.on('fileuploadadd', function(e, data) {
  210. if (!data.headers) {
  211. data.headers = {};
  212. }
  213. data.headers.Authorization = 'Basic ' + btoa(token + ':');
  214. });
  215. // do not allow sharing from the public page
  216. delete this.fileList.fileActions.actions.all.Share;
  217. this.fileList.changeDirectory(this.initialDir || '/', false, true);
  218. // URL history handling
  219. this.fileList.$el.on('changeDirectory', _.bind(this._onDirectoryChanged, this));
  220. OC.Util.History.addOnPopStateHandler(_.bind(this._onUrlChanged, this));
  221. $('#download').click(function (e) {
  222. e.preventDefault();
  223. OC.redirect(FileList.getDownloadUrl());
  224. });
  225. }
  226. $(document).on('click', '#directLink', function () {
  227. $(this).focus();
  228. $(this).select();
  229. });
  230. $('.save-form').submit(function (event) {
  231. event.preventDefault();
  232. var remote = $(this).find('#remote_address').val();
  233. var token = $('#sharingToken').val();
  234. var owner = $('#save').data('owner');
  235. var ownerDisplayName = $('#save').data('owner-display-name');
  236. var name = $('#save').data('name');
  237. var isProtected = $('#save').data('protected') ? 1 : 0;
  238. OCA.Sharing.PublicApp._createFederatedShare(remote, token, owner, ownerDisplayName, name, isProtected);
  239. });
  240. $('#remote_address').on("keyup paste", function() {
  241. if ($(this).val() === '') {
  242. $('#save-button-confirm').prop('disabled', true);
  243. } else {
  244. $('#save-button-confirm').prop('disabled', false);
  245. }
  246. });
  247. $('#save #save-button').click(function () {
  248. $(this).hide();
  249. $('.save-form').css('display', 'inline');
  250. $('#remote_address').focus();
  251. });
  252. // legacy
  253. window.FileList = this.fileList;
  254. },
  255. _showTextPreview: function (data, previewHeight) {
  256. var textDiv = $('<div/>').addClass('text-preview');
  257. textDiv.text(data);
  258. textDiv.appendTo('#imgframe');
  259. var divHeight = textDiv.height();
  260. if (data.length > 999) {
  261. var ellipsis = $('<div/>').addClass('ellipsis');
  262. ellipsis.html('(&#133;)');
  263. ellipsis.appendTo('#imgframe');
  264. }
  265. if (divHeight > previewHeight) {
  266. textDiv.height(previewHeight);
  267. }
  268. },
  269. _onDirectoryChanged: function (e) {
  270. OC.Util.History.pushState({
  271. // arghhhh, why is this not called "dir" !?
  272. path: e.dir
  273. });
  274. },
  275. _onUrlChanged: function (params) {
  276. this.fileList.changeDirectory(params.path || params.dir, false, true);
  277. },
  278. /**
  279. * fall back to old behaviour where we redirect the user to his server to mount
  280. * the public link instead of creating a dedicated federated share
  281. *
  282. * @param remote
  283. * @param token
  284. * @param owner
  285. * @param ownerDisplayName
  286. * @param name
  287. * @param isProtected
  288. * @private
  289. */
  290. _legacyCreateFederatedShare: function (remote, token, owner, ownerDisplayName, name, isProtected) {
  291. var location = window.location.protocol + '//' + window.location.host + OC.webroot;
  292. if(remote.substr(-1) !== '/') {
  293. remote += '/'
  294. }
  295. var url = remote + 'index.php/apps/files#' + 'remote=' + encodeURIComponent(location) // our location is the remote for the other server
  296. + "&token=" + encodeURIComponent(token) + "&owner=" + encodeURIComponent(owner) +"&ownerDisplayName=" + encodeURIComponent(ownerDisplayName) + "&name=" + encodeURIComponent(name) + "&protected=" + isProtected;
  297. if (remote.indexOf('://') > 0) {
  298. OC.redirect(url);
  299. } else {
  300. // if no protocol is specified, we automatically detect it by testing https and http
  301. // this check needs to happen on the server due to the Content Security Policy directive
  302. $.get(OC.generateUrl('apps/files_sharing/testremote'), {remote: remote}).then(function (protocol) {
  303. if (protocol !== 'http' && protocol !== 'https') {
  304. OC.dialogs.alert(t('files_sharing', 'No compatible server found at {remote}', {remote: remote}),
  305. t('files_sharing', 'Invalid server URL'));
  306. } else {
  307. OC.redirect(protocol + '://' + url);
  308. }
  309. });
  310. }
  311. },
  312. _createFederatedShare: function (remote, token, owner, ownerDisplayName, name, isProtected) {
  313. var toggleLoading = function() {
  314. var iconClass = $('#save-button-confirm').attr('class');
  315. var loading = iconClass.indexOf('icon-loading-small') !== -1;
  316. if(loading) {
  317. $('#save-button-confirm')
  318. .removeClass("icon-loading-small")
  319. .addClass("icon-confirm");
  320. }
  321. else {
  322. $('#save-button-confirm')
  323. .removeClass("icon-confirm")
  324. .addClass("icon-loading-small");
  325. }
  326. };
  327. toggleLoading();
  328. if (remote.indexOf('@') === -1) {
  329. this._legacyCreateFederatedShare(remote, token, owner, ownerDisplayName, name, isProtected);
  330. toggleLoading();
  331. return;
  332. }
  333. $.post(
  334. OC.generateUrl('/apps/federatedfilesharing/createFederatedShare'),
  335. {
  336. 'shareWith': remote,
  337. 'token': token
  338. }
  339. ).done(
  340. function (data) {
  341. var url = data.remoteUrl;
  342. if (url.indexOf('://') > 0) {
  343. OC.redirect(url);
  344. } else {
  345. OC.redirect('http://' + url);
  346. }
  347. }
  348. ).fail(
  349. function (jqXHR) {
  350. OC.dialogs.alert(JSON.parse(jqXHR.responseText).message,
  351. t('files_sharing', 'Failed to add the public link to your Nextcloud'));
  352. toggleLoading();
  353. }
  354. );
  355. }
  356. };
  357. $(document).ready(function () {
  358. // FIXME: replace with OC.Plugins.register()
  359. if (window.TESTING) {
  360. return;
  361. }
  362. var App = OCA.Sharing.PublicApp;
  363. // defer app init, to give a chance to plugins to register file actions
  364. _.defer(function () {
  365. App.initialize($('#preview'));
  366. });
  367. if (window.Files) {
  368. // HACK: for oc-dialogs previews that depends on Files:
  369. Files.generatePreviewUrl = function (urlSpec) {
  370. return App.fileList.generatePreviewUrl(urlSpec);
  371. };
  372. }
  373. $('#share-menutoggle').click(function() {
  374. $('#share-menu').toggleClass('open');
  375. });
  376. });
  377. $(document).mouseup(function(e) {
  378. var container = $('#share-menu');
  379. // if the target of the click isn't the container nor a descendant of the container
  380. if (!container.is(e.target) && container.has(e.target).length === 0) {
  381. container.removeClass('open');
  382. }
  383. });