fileactions.js 26 KB

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