fileactions.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. /**
  2. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-FileCopyrightText: 2012-2016 ownCloud, Inc.
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. (function() {
  7. /**
  8. * Construct a new FileActions instance
  9. * @constructs FileActions
  10. * @memberof OCA.Files
  11. */
  12. var FileActions = function() {
  13. this.initialize();
  14. };
  15. FileActions.TYPE_DROPDOWN = 0;
  16. FileActions.TYPE_INLINE = 1;
  17. FileActions.prototype = {
  18. /** @lends FileActions.prototype */
  19. actions: {},
  20. defaults: {},
  21. icons: {},
  22. /**
  23. * @deprecated
  24. */
  25. currentFile: null,
  26. /**
  27. * Dummy jquery element, for events
  28. */
  29. $el: null,
  30. _fileActionTriggerTemplate: null,
  31. /**
  32. * @private
  33. */
  34. initialize: function() {
  35. this.clear();
  36. // abusing jquery for events until we get a real event lib
  37. this.$el = $('<div class="dummy-fileactions hidden"></div>');
  38. $('body').append(this.$el);
  39. this._showMenuClosure = _.bind(this._showMenu, this);
  40. },
  41. /**
  42. * Adds an event handler
  43. *
  44. * @param {String} eventName event name
  45. * @param {Function} callback
  46. */
  47. on: function(eventName, callback) {
  48. this.$el.on(eventName, callback);
  49. },
  50. /**
  51. * Removes an event handler
  52. *
  53. * @param {String} eventName event name
  54. * @param {Function} callback
  55. */
  56. off: function(eventName, callback) {
  57. this.$el.off(eventName, callback);
  58. },
  59. /**
  60. * Notifies the event handlers
  61. *
  62. * @param {String} eventName event name
  63. * @param {Object} data data
  64. */
  65. _notifyUpdateListeners: function(eventName, data) {
  66. this.$el.trigger(new $.Event(eventName, data));
  67. },
  68. /**
  69. * Merges the actions from the given fileActions into
  70. * this instance.
  71. *
  72. * @param {OCA.Files.FileActions} fileActions instance of OCA.Files.FileActions
  73. */
  74. merge: function(fileActions) {
  75. var self = this;
  76. // merge first level to avoid unintended overwriting
  77. _.each(fileActions.actions, function(sourceMimeData, mime) {
  78. var targetMimeData = self.actions[mime];
  79. if (!targetMimeData) {
  80. targetMimeData = {};
  81. }
  82. self.actions[mime] = _.extend(targetMimeData, sourceMimeData);
  83. });
  84. this.defaults = _.extend(this.defaults, fileActions.defaults);
  85. this.icons = _.extend(this.icons, fileActions.icons);
  86. },
  87. /**
  88. * @deprecated use #registerAction() instead
  89. */
  90. register: function(mime, name, permissions, icon, action, displayName) {
  91. return this.registerAction({
  92. name: name,
  93. mime: mime,
  94. permissions: permissions,
  95. icon: icon,
  96. actionHandler: action,
  97. displayName: displayName || name
  98. });
  99. },
  100. /**
  101. * Register action
  102. *
  103. * @param {OCA.Files.FileAction} action object
  104. */
  105. registerAction: function (action) {
  106. var mime = action.mime;
  107. var name = action.name;
  108. var actionSpec = {
  109. action: function(fileName, context) {
  110. // Actions registered in one FileAction may be executed on a
  111. // different one (for example, due to the "merge" function),
  112. // so the listeners have to be updated on the FileActions
  113. // from the context instead of on the one in which it was
  114. // originally registered.
  115. if (context && context.fileActions) {
  116. context.fileActions._notifyUpdateListeners('beforeTriggerAction', {action: actionSpec, fileName: fileName, context: context});
  117. }
  118. action.actionHandler(fileName, context);
  119. if (context && context.fileActions) {
  120. context.fileActions._notifyUpdateListeners('afterTriggerAction', {action: actionSpec, fileName: fileName, context: context});
  121. }
  122. },
  123. name: name,
  124. displayName: action.displayName,
  125. mime: mime,
  126. filename: action.filename,
  127. order: action.order || 0,
  128. icon: action.icon,
  129. iconClass: action.iconClass,
  130. permissions: action.permissions,
  131. type: action.type || FileActions.TYPE_DROPDOWN,
  132. altText: action.altText || ''
  133. };
  134. if (_.isUndefined(action.displayName)) {
  135. actionSpec.displayName = t('files', name);
  136. }
  137. if (_.isFunction(action.render)) {
  138. actionSpec.render = action.render;
  139. }
  140. if (_.isFunction(action.shouldRender)) {
  141. actionSpec.shouldRender = action.shouldRender;
  142. }
  143. if (!this.actions[mime]) {
  144. this.actions[mime] = {};
  145. }
  146. this.actions[mime][name] = actionSpec;
  147. this.icons[name] = action.icon;
  148. this._notifyUpdateListeners('registerAction', {action: action});
  149. },
  150. /**
  151. * Clears all registered file actions.
  152. */
  153. clear: function() {
  154. this.actions = {};
  155. this.defaults = {};
  156. this.icons = {};
  157. this.currentFile = null;
  158. },
  159. /**
  160. * Sets the default action for a given mime type.
  161. *
  162. * @param {String} mime mime type
  163. * @param {String} name action name
  164. */
  165. setDefault: function (mime, name) {
  166. this.defaults[mime] = name;
  167. this._notifyUpdateListeners('setDefault', {defaultAction: {mime: mime, name: name}});
  168. },
  169. /**
  170. * Returns a map of file actions handlers matching the given conditions
  171. *
  172. * @param {string} mime mime type
  173. * @param {string} type "dir" or "file"
  174. * @param {number} permissions permissions
  175. * @param {string} filename filename
  176. *
  177. * @return {Object.<string,OCA.Files.FileActions~actionHandler>} map of action name to action spec
  178. */
  179. get: function(mime, type, permissions, filename) {
  180. var actions = this.getActions(mime, type, permissions, filename);
  181. var filteredActions = {};
  182. $.each(actions, function (name, action) {
  183. filteredActions[name] = action.action;
  184. });
  185. return filteredActions;
  186. },
  187. /**
  188. * Returns an array of file actions matching the given conditions
  189. *
  190. * @param {string} mime mime type
  191. * @param {string} type "dir" or "file"
  192. * @param {number} permissions permissions
  193. * @param {string} filename filename
  194. *
  195. * @return {Array.<OCA.Files.FileAction>} array of action specs
  196. */
  197. getActions: function(mime, type, permissions, filename) {
  198. var actions = {};
  199. if (this.actions.all) {
  200. actions = $.extend(actions, this.actions.all);
  201. }
  202. if (type) {//type is 'dir' or 'file'
  203. if (this.actions[type]) {
  204. actions = $.extend(actions, this.actions[type]);
  205. }
  206. }
  207. if (mime) {
  208. var mimePart = mime.substr(0, mime.indexOf('/'));
  209. if (this.actions[mimePart]) {
  210. actions = $.extend(actions, this.actions[mimePart]);
  211. }
  212. if (this.actions[mime]) {
  213. actions = $.extend(actions, this.actions[mime]);
  214. }
  215. }
  216. var filteredActions = {};
  217. var self = this;
  218. $.each(actions, function(name, action) {
  219. if (self.allowedPermissions(action.permissions, permissions) &&
  220. self.allowedFilename(action.filename, filename)) {
  221. filteredActions[name] = action;
  222. }
  223. });
  224. return filteredActions;
  225. },
  226. allowedPermissions: function(actionPermissions, permissions) {
  227. return (actionPermissions === OC.PERMISSION_NONE || (actionPermissions & permissions));
  228. },
  229. allowedFilename: function(actionFilename, filename) {
  230. return (!filename || filename === '' || !actionFilename
  231. || actionFilename === '' || actionFilename === filename);
  232. },
  233. /**
  234. * Returns the default file action handler for the given conditions
  235. *
  236. * @param {string} mime mime type
  237. * @param {string} type "dir" or "file"
  238. * @param {number} permissions permissions
  239. *
  240. * @return {OCA.Files.FileActions~actionHandler} action handler
  241. *
  242. * @deprecated use getDefaultFileAction instead
  243. */
  244. getDefault: function (mime, type, permissions) {
  245. var defaultActionSpec = this.getDefaultFileAction(mime, type, permissions);
  246. if (defaultActionSpec) {
  247. return defaultActionSpec.action;
  248. }
  249. return undefined;
  250. },
  251. /**
  252. * Returns the default file action handler for the current file
  253. *
  254. * @return {OCA.Files.FileActions~actionSpec} action spec
  255. * @since 8.2
  256. */
  257. getCurrentDefaultFileAction: function() {
  258. var mime = this.getCurrentMimeType();
  259. var type = this.getCurrentType();
  260. var permissions = this.getCurrentPermissions();
  261. return this.getDefaultFileAction(mime, type, permissions);
  262. },
  263. /**
  264. * Returns the default file action handler for the given conditions
  265. *
  266. * @param {string} mime mime type
  267. * @param {string} type "dir" or "file"
  268. * @param {number} permissions permissions
  269. *
  270. * @return {OCA.Files.FileActions~actionSpec} action spec
  271. * @since 8.2
  272. */
  273. getDefaultFileAction: function(mime, type, permissions) {
  274. var mimePart;
  275. if (mime) {
  276. mimePart = mime.substr(0, mime.indexOf('/'));
  277. }
  278. var name = false;
  279. if (mime && this.defaults[mime]) {
  280. name = this.defaults[mime];
  281. } else if (mime && this.defaults[mimePart]) {
  282. name = this.defaults[mimePart];
  283. } else if (type && this.defaults[type]) {
  284. name = this.defaults[type];
  285. } else {
  286. name = this.defaults.all;
  287. }
  288. var actions = this.getActions(mime, type, permissions);
  289. return actions[name];
  290. },
  291. /**
  292. * Default function to render actions
  293. *
  294. * @param {OCA.Files.FileAction} actionSpec file action spec
  295. * @param {boolean} isDefault true if the action is a default one,
  296. * false otherwise
  297. * @param {OCA.Files.FileActionContext} context action context
  298. */
  299. _defaultRenderAction: function(actionSpec, isDefault, context) {
  300. if (!isDefault) {
  301. var params = {
  302. name: actionSpec.name,
  303. nameLowerCase: actionSpec.name.toLowerCase(),
  304. displayName: actionSpec.displayName,
  305. icon: actionSpec.icon,
  306. iconClass: actionSpec.iconClass,
  307. altText: actionSpec.altText,
  308. hasDisplayName: !!actionSpec.displayName
  309. };
  310. if (_.isFunction(actionSpec.icon)) {
  311. params.icon = actionSpec.icon(context.$file.attr('data-file'), context);
  312. }
  313. if (_.isFunction(actionSpec.iconClass)) {
  314. params.iconClass = actionSpec.iconClass(context.$file.attr('data-file'), context);
  315. }
  316. var $actionLink = this._makeActionLink(params, context);
  317. context.$file.find('a.name>span.fileactions').append($actionLink);
  318. $actionLink.addClass('permanent');
  319. return $actionLink;
  320. }
  321. },
  322. /**
  323. * Renders the action link element
  324. *
  325. * @param {Object} params action params
  326. */
  327. _makeActionLink: function(params) {
  328. return $(OCA.Files.Templates['file_action_trigger'](params));
  329. },
  330. /**
  331. * Displays the file actions dropdown menu
  332. *
  333. * @param {string} fileName file name
  334. * @param {OCA.Files.FileActionContext} context rendering context
  335. */
  336. _showMenu: function(fileName, context) {
  337. var menu;
  338. var $trigger = context.$file.closest('tr').find('.fileactions .action-menu');
  339. $trigger.addClass('open');
  340. $trigger.attr('aria-expanded', 'true');
  341. menu = new OCA.Files.FileActionsMenu();
  342. context.$file.find('td.filename').append(menu.$el);
  343. menu.$el.on('afterHide', function() {
  344. context.$file.removeClass('mouseOver');
  345. $trigger.removeClass('open');
  346. $trigger.attr('aria-expanded', 'false');
  347. menu.remove();
  348. });
  349. context.$file.addClass('mouseOver');
  350. menu.show(context);
  351. },
  352. /**
  353. * Renders the menu trigger on the given file list row
  354. *
  355. * @param {Object} $tr file list row element
  356. * @param {OCA.Files.FileActionContext} context rendering context
  357. */
  358. _renderMenuTrigger: function($tr, context) {
  359. // remove previous
  360. $tr.find('.action-menu').remove();
  361. var $el = this._renderInlineAction({
  362. name: 'menu',
  363. displayName: '',
  364. iconClass: 'icon-more',
  365. altText: t('files', 'Actions'),
  366. action: this._showMenuClosure
  367. }, false, context);
  368. $el.addClass('permanent');
  369. $el.attr('aria-expanded', 'false');
  370. },
  371. /**
  372. * Renders the action element by calling actionSpec.render() and
  373. * registers the click event to process the action.
  374. *
  375. * @param {OCA.Files.FileAction} actionSpec file action to render
  376. * @param {boolean} isDefault true if the action is a default action,
  377. * false otherwise
  378. * @param {OCA.Files.FileActionContext} context rendering context
  379. */
  380. _renderInlineAction: function(actionSpec, isDefault, context) {
  381. if (actionSpec.shouldRender) {
  382. if (!actionSpec.shouldRender(context)) {
  383. return;
  384. }
  385. }
  386. var renderFunc = actionSpec.render || _.bind(this._defaultRenderAction, this);
  387. var $actionEl = renderFunc(actionSpec, isDefault, context);
  388. if (!$actionEl || !$actionEl.length) {
  389. return;
  390. }
  391. $actionEl.on(
  392. 'click', {
  393. a: null
  394. },
  395. function(event) {
  396. event.stopPropagation();
  397. event.preventDefault();
  398. if ($actionEl.hasClass('open')) {
  399. return;
  400. }
  401. var $file = $(event.target).closest('tr');
  402. if ($file.hasClass('busy')) {
  403. return;
  404. }
  405. var currentFile = $file.find('td.filename');
  406. var fileName = $file.attr('data-file');
  407. context.fileActions.currentFile = currentFile;
  408. var callContext = _.extend({}, context);
  409. if (!context.dir && context.fileList) {
  410. callContext.dir = $file.attr('data-path') || context.fileList.getCurrentDirectory();
  411. }
  412. if (!context.fileInfoModel && context.fileList) {
  413. callContext.fileInfoModel = context.fileList.getModelForFile(fileName);
  414. if (!callContext.fileInfoModel) {
  415. console.warn('No file info model found for file "' + fileName + '"');
  416. }
  417. }
  418. actionSpec.action(
  419. fileName,
  420. callContext
  421. );
  422. }
  423. );
  424. return $actionEl;
  425. },
  426. /**
  427. * Trigger the given action on the given file.
  428. *
  429. * @param {string} actionName action name
  430. * @param {OCA.Files.FileInfoModel} fileInfoModel file info model
  431. * @param {OCA.Files.FileList} [fileList] file list, for compatibility with older action handlers [DEPRECATED]
  432. *
  433. * @return {boolean} true if the action handler was called, false otherwise
  434. *
  435. * @since 8.2
  436. */
  437. triggerAction: function(actionName, fileInfoModel, fileList) {
  438. var actionFunc;
  439. var actions = this.get(
  440. fileInfoModel.get('mimetype'),
  441. fileInfoModel.isDirectory() ? 'dir' : 'file',
  442. fileInfoModel.get('permissions'),
  443. fileInfoModel.get('name')
  444. );
  445. if (actionName) {
  446. actionFunc = actions[actionName];
  447. } else {
  448. actionFunc = this.getDefault(
  449. fileInfoModel.get('mimetype'),
  450. fileInfoModel.isDirectory() ? 'dir' : 'file',
  451. fileInfoModel.get('permissions')
  452. );
  453. }
  454. if (!actionFunc) {
  455. actionFunc = actions['Download'];
  456. }
  457. if (!actionFunc) {
  458. return false;
  459. }
  460. var context = {
  461. fileActions: this,
  462. fileInfoModel: fileInfoModel,
  463. dir: fileInfoModel.get('path')
  464. };
  465. var fileName = fileInfoModel.get('name');
  466. this.currentFile = fileName;
  467. if (fileList) {
  468. // compatibility with action handlers that expect these
  469. context.fileList = fileList;
  470. context.$file = fileList.findFileEl(fileName);
  471. }
  472. actionFunc(fileName, context);
  473. },
  474. /**
  475. * Display file actions for the given element
  476. * @param parent "td" element of the file for which to display actions
  477. * @param triggerEvent if true, triggers the fileActionsReady on the file
  478. * list afterwards (false by default)
  479. * @param fileList OCA.Files.FileList instance on which the action is
  480. * done, defaults to OCA.Files.App.fileList
  481. */
  482. display: function (parent, triggerEvent, fileList) {
  483. if (!fileList) {
  484. console.warn('FileActions.display() MUST be called with a OCA.Files.FileList instance');
  485. return;
  486. }
  487. this.currentFile = parent;
  488. var self = this;
  489. var $tr = parent.closest('tr');
  490. var actions = this.getActions(
  491. this.getCurrentMimeType(),
  492. this.getCurrentType(),
  493. this.getCurrentPermissions(),
  494. this.getCurrentFile()
  495. );
  496. var nameLinks;
  497. if ($tr.data('renaming')) {
  498. return;
  499. }
  500. // recreate fileactions container
  501. nameLinks = parent.children('a.name');
  502. nameLinks.find('.fileactions, .nametext .action').remove();
  503. nameLinks.append('<span class="fileactions"></span>');
  504. var defaultAction = this.getDefaultFileAction(
  505. this.getCurrentMimeType(),
  506. this.getCurrentType(),
  507. this.getCurrentPermissions()
  508. );
  509. var context = {
  510. $file: $tr,
  511. fileActions: this,
  512. fileList: fileList
  513. };
  514. $.each(actions, function (name, actionSpec) {
  515. if (actionSpec.type === FileActions.TYPE_INLINE) {
  516. self._renderInlineAction(
  517. actionSpec,
  518. defaultAction && actionSpec.name === defaultAction.name,
  519. context
  520. );
  521. }
  522. });
  523. function objectValues(obj) {
  524. var res = [];
  525. for (var i in obj) {
  526. if (obj.hasOwnProperty(i)) {
  527. res.push(obj[i]);
  528. }
  529. }
  530. return res;
  531. }
  532. // polyfill
  533. if (!Object.values) {
  534. Object.values = objectValues;
  535. }
  536. var menuActions = Object.values(actions).filter(function (action) {
  537. return action.type !== OCA.Files.FileActions.TYPE_INLINE && (!defaultAction || action.name !== defaultAction.name)
  538. });
  539. // do not render the menu if nothing is in it
  540. if (menuActions.length > 0) {
  541. this._renderMenuTrigger($tr, context);
  542. }
  543. if (triggerEvent){
  544. fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList, $files: $tr}));
  545. }
  546. },
  547. getCurrentFile: function () {
  548. return this.currentFile.parent().attr('data-file');
  549. },
  550. getCurrentMimeType: function () {
  551. return this.currentFile.parent().attr('data-mime');
  552. },
  553. getCurrentType: function () {
  554. return this.currentFile.parent().attr('data-type');
  555. },
  556. getCurrentPermissions: function () {
  557. return this.currentFile.parent().data('permissions');
  558. },
  559. /**
  560. * Register the actions that are used by default for the files app.
  561. */
  562. registerDefaultActions: function() {
  563. this.registerAction({
  564. name: 'Download',
  565. displayName: t('files', 'Download'),
  566. order: -20,
  567. mime: 'all',
  568. permissions: OC.PERMISSION_READ,
  569. iconClass: 'icon-download',
  570. actionHandler: function (filename, context) {
  571. var dir = context.dir || context.fileList.getCurrentDirectory();
  572. var isDir = context.$file.attr('data-type') === 'dir';
  573. var url = context.fileList.getDownloadUrl(filename, dir, isDir);
  574. var downloadFileaction = $(context.$file).find('.fileactions .action-download');
  575. // don't allow a second click on the download action
  576. if(downloadFileaction.hasClass('disabled')) {
  577. return;
  578. }
  579. if (url) {
  580. var disableLoadingState = function() {
  581. context.fileList.showFileBusyState(filename, false);
  582. };
  583. context.fileList.showFileBusyState(filename, true);
  584. OCA.Files.Files.handleDownload(url, disableLoadingState);
  585. }
  586. }
  587. });
  588. this.registerAction({
  589. name: 'Rename',
  590. displayName: t('files', 'Rename'),
  591. mime: 'all',
  592. order: -30,
  593. permissions: OC.PERMISSION_UPDATE,
  594. iconClass: 'icon-rename',
  595. actionHandler: function (filename, context) {
  596. context.fileList.rename(filename);
  597. }
  598. });
  599. this.registerAction({
  600. name: 'MoveCopy',
  601. displayName: function(context) {
  602. var permissions = context.fileInfoModel.attributes.permissions;
  603. if (permissions & OC.PERMISSION_UPDATE) {
  604. if (!context.fileInfoModel.canDownload()) {
  605. return t('files', 'Move');
  606. }
  607. return t('files', 'Move or copy');
  608. }
  609. return t('files', 'Copy');
  610. },
  611. mime: 'all',
  612. order: -25,
  613. permissions: $('#isPublic').val() ? OC.PERMISSION_UPDATE : OC.PERMISSION_READ,
  614. iconClass: 'icon-external',
  615. actionHandler: function (filename, context) {
  616. var permissions = context.fileInfoModel.attributes.permissions;
  617. var actions = OC.dialogs.FILEPICKER_TYPE_COPY;
  618. if (permissions & OC.PERMISSION_UPDATE) {
  619. if (!context.fileInfoModel.canDownload()) {
  620. actions = OC.dialogs.FILEPICKER_TYPE_MOVE;
  621. } else {
  622. actions = OC.dialogs.FILEPICKER_TYPE_COPY_MOVE;
  623. }
  624. }
  625. var dialogDir = context.dir;
  626. if (typeof context.fileList.dirInfo.dirLastCopiedTo !== 'undefined') {
  627. dialogDir = context.fileList.dirInfo.dirLastCopiedTo;
  628. }
  629. OC.dialogs.filepicker(t('files', 'Choose target folder'), function(targetPath, type) {
  630. if (type === OC.dialogs.FILEPICKER_TYPE_COPY) {
  631. context.fileList.copy(filename, targetPath, false, context.dir);
  632. }
  633. if (type === OC.dialogs.FILEPICKER_TYPE_MOVE) {
  634. context.fileList.move(filename, targetPath, false, context.dir);
  635. }
  636. context.fileList.dirInfo.dirLastCopiedTo = targetPath;
  637. }, false, "httpd/unix-directory", true, actions, dialogDir);
  638. }
  639. });
  640. if (Boolean(OC.appswebroots.files_reminders) && Boolean(OC.appswebroots.notifications)) {
  641. this.registerAction({
  642. name: 'SetReminder',
  643. displayName: function(_context) {
  644. return t('files', 'Set reminder');
  645. },
  646. mime: 'all',
  647. order: -24,
  648. icon: function(_filename, _context) {
  649. return OC.imagePath('files_reminders', 'alarm.svg')
  650. },
  651. permissions: $('#isPublic').val() ? null : OC.PERMISSION_READ,
  652. actionHandler: function(_filename, _context) {},
  653. });
  654. }
  655. if (!/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
  656. this.registerAction({
  657. name: 'EditLocally',
  658. displayName: function(context) {
  659. var locked = context.$file.data('locked');
  660. if (!locked) {
  661. return t('files', 'Edit locally');
  662. }
  663. },
  664. mime: 'all',
  665. order: -23,
  666. icon: function(filename, context) {
  667. var locked = context.$file.data('locked');
  668. if (!locked) {
  669. return OC.imagePath('files', 'computer.svg')
  670. }
  671. },
  672. permissions: OC.PERMISSION_UPDATE,
  673. actionHandler: function (filename, context) {
  674. var dir = context.dir || context.fileList.getCurrentDirectory();
  675. var path = dir === '/' ? dir + filename : dir + '/' + filename;
  676. context.fileList.openLocalClient(path);
  677. },
  678. });
  679. }
  680. this.registerAction({
  681. name: 'Open',
  682. mime: 'dir',
  683. permissions: OC.PERMISSION_READ,
  684. icon: '',
  685. actionHandler: function (filename, context) {
  686. let dir, id
  687. if (context.$file) {
  688. dir = context.$file.attr('data-path')
  689. id = context.$file.attr('data-id')
  690. } else {
  691. dir = context.fileList.getCurrentDirectory()
  692. id = context.fileId
  693. }
  694. if (OCA.Files.App && OCA.Files.App.getActiveView() !== 'files') {
  695. OCA.Files.App.setActiveView('files', {silent: true});
  696. OCA.Files.App.fileList.changeDirectory(OC.joinPaths(dir, filename), true, true);
  697. } else {
  698. context.fileList.changeDirectory(OC.joinPaths(dir, filename), true, false, parseInt(id, 10));
  699. }
  700. },
  701. displayName: t('files', 'Open')
  702. });
  703. this.registerAction({
  704. name: 'Delete',
  705. displayName: function(context) {
  706. var mountType = context.$file.attr('data-mounttype');
  707. var type = context.$file.attr('data-type');
  708. var deleteTitle = (type && type === 'file')
  709. ? t('files', 'Delete file')
  710. : t('files', 'Delete folder')
  711. if (mountType === 'external-root') {
  712. deleteTitle = t('files', 'Disconnect storage');
  713. } else if (mountType === 'shared-root') {
  714. deleteTitle = t('files', 'Leave this share');
  715. }
  716. return deleteTitle;
  717. },
  718. mime: 'all',
  719. order: 1000,
  720. // permission is READ because we show a hint instead if there is no permission
  721. permissions: OC.PERMISSION_DELETE,
  722. iconClass: 'icon-delete',
  723. actionHandler: function(fileName, context) {
  724. // if there is no permission to delete do nothing
  725. if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) {
  726. return;
  727. }
  728. context.fileList.do_delete(fileName, context.dir);
  729. $('.tipsy').remove();
  730. // close sidebar on delete
  731. const path = context.dir + '/' + fileName
  732. if (OCA.Files.Sidebar && OCA.Files.Sidebar.file === path) {
  733. OCA.Files.Sidebar.close()
  734. }
  735. }
  736. });
  737. this.setDefault('dir', 'Open');
  738. }
  739. };
  740. OCA.Files.FileActions = FileActions;
  741. /**
  742. * Replaces the button icon with a loading spinner and vice versa
  743. * - also adds the class disabled to the passed in element
  744. *
  745. * @param {jQuery} $buttonElement The button element
  746. * @param {boolean} showIt whether to show the spinner(true) or to hide it(false)
  747. */
  748. OCA.Files.FileActions.updateFileActionSpinner = function($buttonElement, showIt) {
  749. var $icon = $buttonElement.find('.icon');
  750. if (showIt) {
  751. var $loadingIcon = $('<span class="icon icon-loading-small"></span>');
  752. $icon.after($loadingIcon);
  753. $icon.addClass('hidden');
  754. } else {
  755. $buttonElement.find('.icon-loading-small').remove();
  756. $buttonElement.find('.icon').removeClass('hidden');
  757. }
  758. };
  759. /**
  760. * File action attributes.
  761. *
  762. * @todo make this a real class in the future
  763. * @typedef {Object} OCA.Files.FileAction
  764. *
  765. * @property {String} name identifier of the action
  766. * @property {(String|OCA.Files.FileActions~displayNameFunction)} displayName
  767. * display name string for the action, or function that returns the display name.
  768. * Defaults to the name given in name property
  769. * @property {String} mime mime type
  770. * @property {String} filename filename
  771. * @property {number} permissions permissions
  772. * @property {(Function|String)} icon icon path to the icon or function that returns it (deprecated, use iconClass instead)
  773. * @property {(String|OCA.Files.FileActions~iconClassFunction)} iconClass class name of the icon (recommended for theming)
  774. * @property {OCA.Files.FileActions~renderActionFunction} [render] optional rendering function
  775. * @property {OCA.Files.FileActions~actionHandler} actionHandler action handler function
  776. */
  777. /**
  778. * File action context attributes.
  779. *
  780. * @typedef {Object} OCA.Files.FileActionContext
  781. *
  782. * @property {Object} $file jQuery file row element
  783. * @property {OCA.Files.FileActions} fileActions file actions object
  784. * @property {OCA.Files.FileList} fileList file list object
  785. */
  786. /**
  787. * Render function for actions.
  788. * The function must render a link element somewhere in the DOM
  789. * and return it. The function should NOT register the event handler
  790. * as this will be done after the link was returned.
  791. *
  792. * @callback OCA.Files.FileActions~renderActionFunction
  793. * @param {OCA.Files.FileAction} actionSpec action definition
  794. * @param {Object} $row row container
  795. * @param {boolean} isDefault true if the action is the default one,
  796. * false otherwise
  797. * @return {Object} jQuery link object
  798. */
  799. /**
  800. * Display name function for actions.
  801. * The function returns the display name of the action using
  802. * the given context information..
  803. *
  804. * @callback OCA.Files.FileActions~displayNameFunction
  805. * @param {OCA.Files.FileActionContext} context action context
  806. * @return {String} display name
  807. */
  808. /**
  809. * Icon class function for actions.
  810. * The function returns the icon class of the action using
  811. * the given context information.
  812. *
  813. * @callback OCA.Files.FileActions~iconClassFunction
  814. * @param {String} fileName name of the file on which the action must be performed
  815. * @param {OCA.Files.FileActionContext} context action context
  816. * @return {String} icon class
  817. */
  818. /**
  819. * Action handler function for file actions
  820. *
  821. * @callback OCA.Files.FileActions~actionHandler
  822. * @param {String} fileName name of the file on which the action must be performed
  823. * @param context context
  824. * @param {String} context.dir directory of the file
  825. * @param {OCA.Files.FileInfoModel} fileInfoModel file info model
  826. * @param {Object} [context.$file] jQuery element of the file [DEPRECATED]
  827. * @param {OCA.Files.FileList} [context.fileList] the FileList instance on which the action occurred [DEPRECATED]
  828. * @param {OCA.Files.FileActions} context.fileActions the FileActions instance on which the action occurred
  829. */
  830. // global file actions to be used by all lists
  831. OCA.Files.fileActions = new OCA.Files.FileActions();
  832. })();