diff --git a/frontend/src/components/context-menu/actions.js b/frontend/src/components/context-menu/actions.js
index 4fbca00514..132f8d35a9 100644
--- a/frontend/src/components/context-menu/actions.js
+++ b/frontend/src/components/context-menu/actions.js
@@ -31,3 +31,27 @@ export function showMenu(opts = {}, target) {
export function hideMenu(opts = {}, target) {
dispatchGlobalEvent(MENU_HIDE, assign({}, opts, { type: MENU_HIDE }), target);
}
+
+export function handleContextClick(event, id, menuList, currentObject = null) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ let x = event.clientX || (event.touches && event.touches[0].pageX);
+ let y = event.clientY || (event.touches && event.touches[0].pageY);
+
+ hideMenu();
+
+ let showMenuConfig = {
+ id: id,
+ position: { x, y },
+ target: event.target,
+ currentObject: currentObject,
+ menuList: menuList,
+ };
+
+ if (menuList.length === 0) {
+ return;
+ }
+
+ showMenu(showMenuConfig);
+}
diff --git a/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js
index ada6dc00fc..2200d7941c 100644
--- a/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js
+++ b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js
@@ -32,7 +32,8 @@ const propTypes = {
onItemUnshare: PropTypes.func.isRequired,
onItemRename: PropTypes.func,
onItemDelete: PropTypes.func,
- onMonitorRepo: PropTypes.func
+ onMonitorRepo: PropTypes.func,
+ onContextMenu: PropTypes.func.isRequired,
};
class SharedRepoListItem extends React.Component {
@@ -157,7 +158,7 @@ class SharedRepoListItem extends React.Component {
};
onMenuItemClick = (e) => {
- let operation = e.target.dataset.toggle;
+ let operation = e.target.dataset.toggle || e.target.dataset.operation;
switch (operation) {
case 'Rename':
this.onItemRenameToggle();
@@ -616,6 +617,10 @@ class SharedRepoListItem extends React.Component {
}
};
+ handleContextMenu = (e) => {
+ this.props.onContextMenu(e, this.props.repo);
+ };
+
renderPCUI = () => {
const { isStarred } = this.state;
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
@@ -627,6 +632,7 @@ class SharedRepoListItem extends React.Component {
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
onFocus={this.onMouseEnter}
+ onContextMenu={this.handleContextMenu}
>

diff --git a/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js b/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js
index 566e05dc76..e25946bd30 100644
--- a/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js
+++ b/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js
@@ -7,6 +7,8 @@ import toaster from '../toast';
import LibsMobileThead from '../libs-mobile-thead';
import Loading from '../loading';
import { LIST_MODE } from '../dir-view-mode/constants';
+import ContextMenu from '../context-menu/context-menu';
+import { hideMenu, handleContextClick } from '../context-menu/actions';
const propTypes = {
currentViewMode: PropTypes.string,
@@ -33,6 +35,7 @@ class SharedRepoListView extends React.Component {
this.state = {
isItemFreezed: false,
};
+ this.repoItems = [];
}
sortByName = (e) => {
@@ -86,13 +89,41 @@ class SharedRepoListView extends React.Component {
this.props.onItemRename(repo, newName);
};
+ setRepoItemRef = (index) => item => {
+ this.repoItems[index] = item;
+ };
+
+ getRepoIndex = (repo) => {
+ return this.props.repoList.findIndex(item => {
+ return item.repo_id === repo.repo_id;
+ });
+ };
+
+ onMenuItemClick = (operation, currentObject, event) => {
+ const index = this.getRepoIndex(currentObject);
+ if (this.repoItems[index]) {
+ this.repoItems[index].onMenuItemClick(event);
+ }
+ hideMenu();
+ };
+
+ onContextMenu = (event, repo) => {
+ event.preventDefault();
+ const { libraryType, currentGroup } = this.props;
+ const isPublic = libraryType === 'public';
+ const id = isPublic ? 'shared-repo-item-menu' : `shared-repo-item-menu-${currentGroup.id}`;
+ const menuList = Utils.getSharedRepoOperationList(repo, currentGroup, isPublic);
+ handleContextClick(event, id, menuList, repo);
+ };
+
renderRepoListView = () => {
const { currentViewMode = LIST_MODE } = this.props;
return (
- {this.props.repoList.map(repo => {
+ {this.props.repoList.map((repo, index) => {
return (
);
})}
@@ -113,31 +145,43 @@ class SharedRepoListView extends React.Component {
};
renderPCUI = () => {
- const { theadHidden = false, currentViewMode = LIST_MODE } = this.props;
+ const { theadHidden = false, currentViewMode = LIST_MODE, currentGroup, libraryType } = this.props;
const { sortByName, sortByTime, sortBySize, sortIcon } = this.getSortMetaData();
- return currentViewMode == LIST_MODE ? (
-
+ const content = currentViewMode == LIST_MODE ? (
+ <>
+
+ >
) : (
{this.renderRepoListView()}
);
+
+ return (
+ <>
+ {content}
+ ;
+ >
+ );
};
renderMobileUI = () => {
diff --git a/frontend/src/pages/groups/group-item.js b/frontend/src/pages/groups/group-item.js
index cae1d3e600..2c8fa1bad2 100644
--- a/frontend/src/pages/groups/group-item.js
+++ b/frontend/src/pages/groups/group-item.js
@@ -129,6 +129,7 @@ class GroupItem extends React.Component {
{group.repos.length === 0 ?
emptyTip :
{
+ this.props.onContextMenu(event, this.props.repo);
+ };
+
renderPCUI = () => {
const { isStarred } = this.state;
const { repo, currentViewMode = LIST_MODE } = this.props;
@@ -311,7 +316,7 @@ class MylibRepoListItem extends React.Component {
let iconTitle = Utils.getLibIconTitle(repo);
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
return currentViewMode == LIST_MODE ? (
-
+

diff --git a/frontend/src/pages/my-libs/mylib-repo-list-view.js b/frontend/src/pages/my-libs/mylib-repo-list-view.js
index 2dde33cade..ec6e80d243 100644
--- a/frontend/src/pages/my-libs/mylib-repo-list-view.js
+++ b/frontend/src/pages/my-libs/mylib-repo-list-view.js
@@ -5,6 +5,9 @@ import { gettext, storages } from '../../utils/constants';
import MylibRepoListItem from './mylib-repo-list-item';
import LibsMobileThead from '../../components/libs-mobile-thead';
import { LIST_MODE } from '../../components/dir-view-mode/constants';
+import ContextMenu from '../../components/context-menu/context-menu';
+import { Utils } from '../../utils/utils';
+import { hideMenu, handleContextClick } from '../../components/context-menu/actions';
const propTypes = {
sortBy: PropTypes.string.isRequired,
@@ -26,6 +29,7 @@ class MylibRepoListView extends React.Component {
this.state = {
isItemFreezed: false,
};
+ this.repoItems = [];
}
onFreezedItem = () => {
@@ -57,12 +61,37 @@ class MylibRepoListView extends React.Component {
this.props.sortRepoList(sortBy, sortOrder);
};
+ onContextMenu = (event, repo) => {
+ event.preventDefault();
+ const id = 'mylib-repo-item-menu';
+ const menuList = Utils.getRepoOperationList(repo);
+ handleContextClick(event, id, menuList, repo);
+ };
+
+ setRepoItemRef = (index) => item => {
+ this.repoItems[index] = item;
+ };
+
+ getRepoIndex = (repo) => {
+ return this.props.repoList.findIndex(item => {
+ return item.repo_id === repo.repo_id;
+ });
+ };
+
+ onMenuItemClick = (operation, currentObject) => {
+ const index = this.getRepoIndex(currentObject);
+ this.repoItems[index].onMenuItemClick(operation);
+
+ hideMenu();
+ };
+
renderRepoListView = () => {
return (
- {this.props.repoList.map(item => {
+ {this.props.repoList.map((item, index) => {
return (
);
})}
@@ -130,6 +160,10 @@ class MylibRepoListView extends React.Component {
{this.renderMobileUI()}
+
);
}
diff --git a/frontend/src/pages/shared-libs/shared-libs.js b/frontend/src/pages/shared-libs/shared-libs.js
index e992dcd245..05be4d7fa3 100644
--- a/frontend/src/pages/shared-libs/shared-libs.js
+++ b/frontend/src/pages/shared-libs/shared-libs.js
@@ -17,6 +17,8 @@ import ShareDialog from '../../components/dialog/share-dialog';
import SortOptionsDialog from '../../components/dialog/sort-options';
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
import { GRID_MODE, LIST_MODE } from '../../components/dir-view-mode/constants';
+import ContextMenu from '../../components/context-menu/context-menu';
+import { hideMenu, handleContextClick } from '../../components/context-menu/actions';
class Content extends Component {
@@ -25,6 +27,7 @@ class Content extends Component {
this.state = {
isItemFreezed: false
};
+ this.libItems = [];
}
freezeItem = (freezed) => {
@@ -54,6 +57,30 @@ class Content extends Component {
this.props.sortItems(sortBy, sortOrder);
};
+ onContextMenu = (event, repo) => {
+ event.preventDefault();
+ const id = 'shared-libs-item-menu';
+ const menuList = Utils.getSharedLibsOperationList(repo);
+ handleContextClick(event, id, menuList, repo);
+ };
+
+ setLibItemRef = (index) => item => {
+ this.libItems[index] = item;
+ };
+
+ getLibIndex = (lib) => {
+ return this.props.items.findIndex(item => {
+ return item.repo_id === lib.repo_id;
+ });
+ };
+
+ onMenuItemClick = (operation, currentObject, event) => {
+ const index = this.getLibIndex(currentObject);
+ this.libItems[index].onMenuItemClick(operation, event);
+
+ hideMenu();
+ };
+
render() {
const { loading, errorMsg, items, sortBy, sortOrder, theadHidden, inAllLibs, currentViewMode } = this.props;
@@ -95,6 +122,7 @@ class Content extends Component {
<>
{items.map((item, index) => {
return ;
})}
>
);
const content = currentViewMode == LIST_MODE ? (
-
- {isDesktop ? desktopThead : }
-
- {itemsContent}
-
-
+ <>
+
+ {isDesktop ? desktopThead : }
+
+ {itemsContent}
+
+
+ >
) : (
{itemsContent}
);
- return items.length ? content : emptyTip;
+ return items.length ? (
+ <>
+ {content}
+
+ >
+ ) : emptyTip;
}
}
}
@@ -265,6 +304,29 @@ class Item extends Component {
});
};
+ handleContextMenu = (event) => {
+ this.props.onContextMenu(event, this.props.data);
+ };
+
+ onMenuItemClick = (operation, event) => {
+ switch (operation) {
+ case 'Share':
+ this.share(event);
+ break;
+ case 'Unshare':
+ this.leaveShare(event);
+ break;
+ case 'Watch File Changes':
+ this.watchFileChanges();
+ break;
+ case 'Unwatch File Changes':
+ this.unwatchFileChanges();
+ break;
+ default:
+ break;
+ }
+ };
+
render() {
if (this.state.unshared) {
return null;
@@ -288,7 +350,7 @@ class Item extends Component {
return (
{currentViewMode == LIST_MODE ? (
-
+

@@ -459,7 +522,8 @@ Item.propTypes = {
data: PropTypes.object.isRequired,
isItemFreezed: PropTypes.bool.isRequired,
freezeItem: PropTypes.func.isRequired,
- onMonitorRepo: PropTypes.func.isRequired
+ onMonitorRepo: PropTypes.func.isRequired,
+ onContextMenu: PropTypes.func.isRequired,
};
class SharedLibraries extends Component {
diff --git a/frontend/src/pages/shared-with-all/index.js b/frontend/src/pages/shared-with-all/index.js
index 57dc4589b2..f91c2f2799 100644
--- a/frontend/src/pages/shared-with-all/index.js
+++ b/frontend/src/pages/shared-with-all/index.js
@@ -138,6 +138,7 @@ class PublicSharedView extends React.Component {
{(!this.state.isLoading && this.state.repoList.length === 0) && emptyTip}
{(!this.state.isLoading && this.state.repoList.length > 0) &&
!(op === DIVIDER && arr[i + 1] === DIVIDER));
+ },
+
+ getAdvancedOperations: function () {
+ const operations = [];
+ const { API_TOKEN, LABEL_CURRENT_STATE, OLD_FILES_AUTO_DELETE } = TextTranslation;
+
+ operations.push(API_TOKEN);
+
+ if (enableRepoSnapshotLabel) {
+ operations.push(LABEL_CURRENT_STATE);
+ }
+
+ if (enableRepoAutoDel) {
+ operations.push(OLD_FILES_AUTO_DELETE);
+ }
+
+ return operations;
+ },
+
+ getSharedLibsOperationList: function (lib) {
+ const { SHARE, UNSHARE, WATCH_FILE_CHANGES, UNWATCH_FILE_CHANGES } = TextTranslation;
+ const operations = [];
+
+ if (isPro && lib.is_admin) {
+ operations.push(SHARE);
+ }
+ operations.push(UNSHARE);
+
+ const monitorOp = lib.monitored ? UNWATCH_FILE_CHANGES : WATCH_FILE_CHANGES;
+ operations.push(monitorOp);
+
+ return operations;
+ },
+
+ getPublicSharedRepoOperationList: function (repo) {
+ const { UNSHARE } = TextTranslation;
+ const operations = [];
+ const isRepoOwner = repo.owner_email === username;
+
+ if (isSystemStaff || isRepoOwner) {
+ operations.push(UNSHARE);
+ }
+
+ return operations;
+ },
+
+ getSharedRepoOperationList: function (repo, currentGroup, isPublic) {
+ const operations = [];
+ const { SHARE, UNSHARE, DELETE, RENAME, FOLDER_PERMISSION, SHARE_ADMIN, UNWATCH_FILE_CHANGES, WATCH_FILE_CHANGES, HISTORY_SETTING, ADVANCED, CHANGE_PASSWORD, RESET_PASSWORD } = TextTranslation;
+
+ const isStaff = currentGroup && currentGroup.admins && currentGroup.admins.indexOf(username) > -1;
+ const isRepoOwner = repo.owner_email === username;
+ const isAdmin = repo.is_admin;
+ const DIVIDER = 'Divider';
+
+ if (isPublic) {
+ if (isSystemStaff || isRepoOwner) {
+ operations.push(UNSHARE);
+ }
+ return operations;
+ }
+
+ if (isPro) {
+ if (repo.owner_email.indexOf('@seafile_group') !== -1) {
+ // is group admin
+ if (isStaff) {
+ if (repo.owner_email === `${currentGroup.id}@seafile_group`) {
+ operations.push(SHARE, DELETE, RENAME);
+ if (folderPermEnabled) {
+ operations.push(FOLDER_PERMISSION);
+ }
+ operations.push(SHARE_ADMIN, DIVIDER);
+ if (repo.encrypted) {
+ operations.push(CHANGE_PASSWORD);
+ }
+ if (repo.encrypted && enableResetEncryptedRepoPassword && isEmailConfigured) {
+ operations.push(RESET_PASSWORD);
+ }
+ if (repo.permission === 'r' || repo.permission === 'rw') {
+ const monitorOp = repo.monitored ? UNWATCH_FILE_CHANGES : WATCH_FILE_CHANGES;
+ operations.push(monitorOp);
+ }
+ operations.push(DIVIDER, HISTORY_SETTING);
+ if (Utils.isDesktop()) {
+ const subOpList = Utils.getAdvancedOperations();
+ operations.push({ ...ADVANCED, subOpList });
+ }
+ return operations;
+ } else {
+ operations.push(UNSHARE);
+ }
+ }
+ } else {
+ if (isRepoOwner || isAdmin) {
+ operations.push(SHARE);
+ }
+ if (isStaff || isRepoOwner || isAdmin) {
+ operations.push(UNSHARE);
+ }
+ }
+ if (repo.permission === 'r' || repo.permission === 'rw') {
+ const monitorOp = repo.monitored ? UNWATCH_FILE_CHANGES : WATCH_FILE_CHANGES;
+ operations.push(monitorOp);
+ }
+ } else {
+ if (isRepoOwner) {
+ operations.push(SHARE);
+ }
+ if (isStaff || isRepoOwner) {
+ operations.push(UNSHARE);
+ }
+ }
+
+ return operations;
+ },
+
sharePerms: function (permission) {
var title;
switch (permission) {
| | |