diff --git a/frontend/src/components/dialog/share-to-group.js b/frontend/src/components/dialog/share-to-group.js
index 2775ac25b8d..0478280f829 100644
--- a/frontend/src/components/dialog/share-to-group.js
+++ b/frontend/src/components/dialog/share-to-group.js
@@ -113,6 +113,7 @@ const propTypes = {
itemPath: PropTypes.string.isRequired,
itemType: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired,
+ repoType: PropTypes.string.isRequired,
isRepoOwner: PropTypes.bool.isRequired,
onAddCustomPermissionToggle: PropTypes.func,
};
@@ -126,7 +127,8 @@ class ShareToGroup extends React.Component {
selectedOption: null,
errorMsg: [],
permission: 'rw',
- sharedItems: []
+ sharedItems: [],
+ isWiki: this.props.repoType === 'wiki'
};
this.permissions = [];
let { itemType, isRepoOwner } = props;
@@ -141,6 +143,9 @@ class ShareToGroup extends React.Component {
if (this.props.isGroupOwnedRepo) {
this.permissions = ['rw', 'r', 'cloud-edit', 'preview'];
}
+ if (this.state.isWiki) {
+ this.permissions = ['rw', 'r'];
+ }
}
handleSelectChange = (option) => {
@@ -344,6 +349,7 @@ class ShareToGroup extends React.Component {
permissions={this.permissions}
onPermissionChanged={this.setPermission}
enableAddCustomPermission={isPro}
+ isWiki={this.state.isWiki}
onAddCustomPermissionToggle={this.props.onAddCustomPermissionToggle}
/>
diff --git a/frontend/src/components/dialog/share-to-user.js b/frontend/src/components/dialog/share-to-user.js
index 6983b83013f..9d75c9ea7f3 100644
--- a/frontend/src/components/dialog/share-to-user.js
+++ b/frontend/src/components/dialog/share-to-user.js
@@ -153,6 +153,7 @@ const propTypes = {
itemType: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired,
isRepoOwner: PropTypes.bool.isRequired,
+ repoType: PropTypes.string.isRequired,
onAddCustomPermissionToggle: PropTypes.func,
};
@@ -164,7 +165,8 @@ class ShareToUser extends React.Component {
selectedOption: null,
errorMsg: [],
permission: 'rw',
- sharedItems: []
+ sharedItems: [],
+ isWiki: this.props.repoType === 'wiki'
};
this.options = [];
this.permissions = [];
@@ -180,6 +182,9 @@ class ShareToUser extends React.Component {
if (this.props.isGroupOwnedRepo) {
this.permissions = ['rw', 'r', 'cloud-edit', 'preview'];
}
+ if (this.state.isWiki) {
+ this.permissions = ['rw', 'r'];
+ }
}
handleSelectChange = (option) => {
@@ -369,6 +374,7 @@ class ShareToUser extends React.Component {
permissions={this.permissions}
onPermissionChanged={this.setPermission}
enableAddCustomPermission={isPro}
+ isWiki={this.state.isWiki}
onAddCustomPermissionToggle={this.props.onAddCustomPermissionToggle}
/>
diff --git a/frontend/src/components/dialog/share-wiki-dialog.js b/frontend/src/components/dialog/share-wiki-dialog.js
new file mode 100644
index 00000000000..c459457cb9e
--- /dev/null
+++ b/frontend/src/components/dialog/share-wiki-dialog.js
@@ -0,0 +1,176 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
+import { gettext, username, additionalShareDialogNote, canShareRepo } from '../../utils/constants';
+import ShareToUser from './share-to-user';
+import ShareToGroup from './share-to-group';
+import { seafileAPI } from '../../utils/seafile-api';
+import { Utils } from '../../utils/utils';
+import Loading from '../loading';
+import toaster from '../toast';
+
+import '../../css/share-link-dialog.css';
+
+const propTypes = {
+ isGroupOwnedRepo: PropTypes.bool,
+ itemType: PropTypes.string.isRequired, // there will be three choose: ['library', 'dir', 'file']
+ itemName: PropTypes.string.isRequired,
+ itemPath: PropTypes.string.isRequired,
+ toggleDialog: PropTypes.func.isRequired,
+ repoID: PropTypes.string.isRequired,
+ repoEncrypted: PropTypes.bool,
+ enableDirPrivateShare: PropTypes.bool,
+};
+
+class ShareWikiDialog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ activeTab: 'shareToUser',
+ isRepoJudgemented: false,
+ isRepoOwner: false,
+ isGroupOwnedRepo: false,
+ };
+ }
+
+ componentDidMount() {
+ let repoID = this.props.repoID;
+ seafileAPI.getRepoInfo(repoID).then(res => {
+ const isGroupOwnedRepo = res.data.owner_email.indexOf('@seafile_group') > -1;
+ let isRepoOwner = res.data.owner_email === username;
+ this.setState({
+ isRepoJudgemented: true,
+ isRepoOwner: isRepoOwner,
+ repoType: res.data.repo_type,
+ isGroupOwnedRepo: isGroupOwnedRepo,
+ });
+ }).catch(error => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+
+ toggle = (tab) => {
+ if (this.state.activeTab !== tab) {
+ this.setState({ activeTab: tab });
+ }
+ };
+
+ onAddCustomPermissionToggle = () => {
+ this.toggle('customSharePermission');
+ };
+
+ renderDirContent = () => {
+
+ if (!this.state.isRepoJudgemented) {
+ return ;
+ }
+
+ let activeTab = this.state.activeTab;
+ let { repoEncrypted, enableDirPrivateShare, itemType } = this.props;
+ // for encrypted repo, 'dir private share' is only enabled for the repo itself,
+ // not for the folders in it.
+ if (repoEncrypted) {
+ enableDirPrivateShare = itemType == 'library';
+ }
+ return (
+
+
+
+
+
+
+ {enableDirPrivateShare &&
+
+ {(activeTab === 'shareToUser' && canShareRepo) &&
+
+
+
+ }
+ {(activeTab === 'shareToGroup' && canShareRepo) &&
+
+
+
+ }
+
+ }
+
+
+
+ );
+ };
+
+ onTabKeyDown = (e) => {
+ if (e.key == 'Enter' || e.key == 'Space') {
+ e.target.click();
+ }
+ };
+
+ renderExternalShareMessage = () => {
+ if (additionalShareDialogNote && (typeof additionalShareDialogNote) === 'object') {
+ return (
+
+
{additionalShareDialogNote.title}
+
{additionalShareDialogNote.content}
+
+ );
+ }
+ return null;
+ };
+
+ render() {
+ const { itemType, itemName } = this.props;
+ return (
+
+
+
+ {gettext('Share')} {itemName}
+ {this.renderExternalShareMessage()}
+
+
+ {(itemType === 'library') && this.renderDirContent()}
+
+
+
+ );
+ }
+}
+
+ShareWikiDialog.propTypes = propTypes;
+
+export default ShareWikiDialog;
diff --git a/frontend/src/components/select-editor/select-editor.js b/frontend/src/components/select-editor/select-editor.js
index abcae3eff02..70c99e149f2 100644
--- a/frontend/src/components/select-editor/select-editor.js
+++ b/frontend/src/components/select-editor/select-editor.js
@@ -9,6 +9,7 @@ const propTypes = {
isTextMode: PropTypes.bool.isRequired, // there will be two mode. first: text and select. second: just select
isEditing: PropTypes.bool,
isEditIconShow: PropTypes.bool.isRequired,
+ isWiki: PropTypes.bool,
autoFocus: PropTypes.bool,
options: PropTypes.array.isRequired,
currentOption: PropTypes.string.isRequired,
@@ -56,7 +57,7 @@ class SelectEditor extends React.Component {
}
const { enableAddCustomPermission } = this.props;
- if (enableAddCustomPermission) {
+ if (enableAddCustomPermission && !this.props.isWiki) {
const option = {
value: gettext('Add custom permission'),
isDisabled: true,
diff --git a/frontend/src/components/select-editor/share-permission-editor.js b/frontend/src/components/select-editor/share-permission-editor.js
index 1da3eb4a60a..324d938f02b 100644
--- a/frontend/src/components/select-editor/share-permission-editor.js
+++ b/frontend/src/components/select-editor/share-permission-editor.js
@@ -11,6 +11,7 @@ const propTypes = {
repoID: PropTypes.string,
isTextMode: PropTypes.bool.isRequired,
isEditing: PropTypes.bool,
+ isWiki: PropTypes.bool,
autoFocus: PropTypes.bool,
isEditIconShow: PropTypes.bool.isRequired,
permissions: PropTypes.array.isRequired,
@@ -136,6 +137,7 @@ class SharePermissionEditor extends React.Component {
translateOption={this.translatePermission}
translateExplanation={this.translateExplanation}
enableAddCustomPermission={this.props.enableAddCustomPermission}
+ isWiki={this.props.isWiki}
onAddCustomPermissionToggle={this.props.onAddCustomPermissionToggle}
/>
);
diff --git a/frontend/src/components/wiki-card-view/wiki-card-group.js b/frontend/src/components/wiki-card-view/wiki-card-group.js
index ec414d2ea14..df60a8146f8 100644
--- a/frontend/src/components/wiki-card-view/wiki-card-group.js
+++ b/frontend/src/components/wiki-card-view/wiki-card-group.js
@@ -7,7 +7,9 @@ import { SIDE_PANEL_FOLDED_WIDTH } from '../../constants';
const propTypes = {
wikis: PropTypes.array.isRequired,
+ group: PropTypes.object,
deleteWiki: PropTypes.func.isRequired,
+ unshareGroupWiki: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
isDepartment: PropTypes.bool.isRequired,
isShowAvatar: PropTypes.bool.isRequired,
@@ -48,10 +50,14 @@ class WikiCardGroup extends Component {
};
render() {
- const { wikis, title, isDepartment, toggelAddWikiDialog } = this.props;
+ const { wikis, title, isDepartment, toggelAddWikiDialog, group } = this.props;
const containerWidth = this.getContainerWidth();
const numberOfWiki = Math.floor(containerWidth / 180);
const grids = (Math.floor((containerWidth - (numberOfWiki + 1) * 16) / numberOfWiki) + 'px ').repeat(numberOfWiki);
+ let isGroup = false;
+ if (group){
+ isGroup = true;
+ }
return (
@@ -60,11 +66,21 @@ class WikiCardGroup extends Component {
{wikis.map((wiki, index) => {
- return (
+ return (isGroup ?
:
{
+ this.setState({
+ isShowShareDialog: !this.state.isShowShareDialog,
+ });
+ };
+
onDeleteToggle = (e) => {
e.preventDefault();
this.setState({
@@ -52,6 +62,14 @@ class WikiCardItem extends Component {
});
};
+ onItemUnshare = () => {
+ let wiki = this.props.wiki;
+ this.props.unshareGroupWiki(wiki, this.props.group.group_id);
+ this.setState({
+ isShowDeleteDialog: !this.state.isShowDeleteDialog,
+ });
+ };
+
renameWiki = (newName) => {
if (this.props.wiki.name !== newName) {
this.props.renameWiki(this.props.wiki, newName);
@@ -94,7 +112,17 @@ class WikiCardItem extends Component {
render() {
const { wiki, isDepartment, isShowAvatar } = this.props;
+ let isAdmin = false;
+ if (wiki.admins){
+ isAdmin = wiki.admins.includes(username);
+ }
+ let isGroupOwner = false;
+ if (this.props.group){
+ isGroupOwner = wiki.owner.split('@')[0] === this.props.group.group_id.toString();
+ }
+ let isWikiOwner = username === wiki.owner;
let isOldVersion = wiki.version !== 'v2';
+ let enableShare = username === wiki.owner || isAdmin;
let publishedUrl = `${siteRoot}published/${encodeURIComponent(wiki.slug)}/`;
let editUrl = `${siteRoot}wikis/${wiki.id}/`;
let wikiName = isOldVersion ? `${wiki.name} (old version)` : wiki.name;
@@ -120,11 +148,23 @@ class WikiCardItem extends Component {
style={{ 'minWidth': '0' }}
/>
- {gettext('Rename')}
+ {(isWikiOwner || isAdmin) &&
+ {gettext('Rename')}
+ }
+ {enableShare &&
+ {gettext('Share')}
+ }
{isOldVersion ?
{gettext('Unpublish')}
- :
- {gettext('Delete')}
+ : ((isDepartment && isGroupOwner) ?
+ {gettext('Delete')} :
+ (isDepartment ?
+ {gettext('Leave')} :
+ (isWikiOwner ?
+ {gettext('Delete')} :
+ {gettext('Leave')}
+ ))
+ )
}
@@ -145,14 +185,34 @@ class WikiCardItem extends Component {
content={{gettext('Are you sure you want to unpublish Wiki')}{' '}{wiki.name} ?
}
footer={gettext('Unpublish')}
/>
- :
- {gettext('Are you sure you want to delete Wiki')}{' '}{wiki.name} ?}
- footer={gettext('Delete')}
- />
+ : ((isDepartment && isGroupOwner) ?
+ {gettext('Are you sure you want to delete Wiki')}{' '}{wiki.name} ?}
+ footer={gettext('Delete')}
+ /> : isDepartment ? {gettext('Are you sure you want to leave share Wiki')}{' '}{wiki.name} ?}
+ footer={gettext('Leave')}
+ /> : (isWikiOwner ? {gettext('Are you sure you want to delete Wiki')}{' '}{wiki.name} ?}
+ footer={gettext('Delete')}
+ /> : {gettext('Are you sure you want to leave share Wiki')}{' '}{wiki.name} ?}
+ footer={gettext('Leave')}
+ />
+ )
+ )
}
}
@@ -165,6 +225,19 @@ class WikiCardItem extends Component {
/>
}
+ {this.state.isShowShareDialog &&
+
+
+
+ }
>
);
}
diff --git a/frontend/src/components/wiki-card-view/wiki-card-view.js b/frontend/src/components/wiki-card-view/wiki-card-view.js
index e0a86523916..44622b545ed 100644
--- a/frontend/src/components/wiki-card-view/wiki-card-view.js
+++ b/frontend/src/components/wiki-card-view/wiki-card-view.js
@@ -12,6 +12,8 @@ const propTypes = {
data: PropTypes.object.isRequired,
deleteWiki: PropTypes.func.isRequired,
renameWiki: PropTypes.func.isRequired,
+ leaveSharedWiki: PropTypes.func.isRequired,
+ unshareGroupWiki: PropTypes.func.isRequired,
toggelAddWikiDialog: PropTypes.func,
sidePanelRate: PropTypes.number,
isSidePanelFolded: PropTypes.bool,
@@ -30,7 +32,7 @@ class WikiCardView extends Component {
if (!canPublishRepo || !isPro) return;
let departmentMap = {};
wikiAPI.listWikiDepartments().then(res => {
- res.data.forEach(item => departmentMap[item.email] = true);
+ res.data.forEach(item => departmentMap[item.id] = true);
this.setState({ departmentMap });
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
@@ -41,12 +43,15 @@ class WikiCardView extends Component {
classifyWikis = (wikis) => {
let v1Wikis = [];
let myWikis = [];
+ let sharedWikis = [];
let department2WikisMap = {};
for (let i = 0; i < wikis.length; i++) {
if (wikis[i].version === 'v1') {
v1Wikis.push(wikis[i]);
} else if (wikis[i].owner === username) {
myWikis.push(wikis[i]);
+ } else if (wikis[i].type === 'shared') {
+ sharedWikis.push(wikis[i]);
} else {
if (!department2WikisMap[wikis[i].owner]) {
department2WikisMap[wikis[i].owner] = [];
@@ -54,11 +59,12 @@ class WikiCardView extends Component {
department2WikisMap[wikis[i].owner].push(wikis[i]);
}
}
- return { department2WikisMap, myWikis, v1Wikis };
+ return { department2WikisMap, myWikis, v1Wikis, sharedWikis };
};
+
render() {
- let { loading, errorMsg, wikis } = this.props.data;
+ let { loading, errorMsg, wikis, groupWikis } = this.props.data;
const { toggelAddWikiDialog, sidePanelRate, isSidePanelFolded } = this.props;
if (loading) {
@@ -67,13 +73,14 @@ class WikiCardView extends Component {
if (errorMsg) {
return {errorMsg}
;
}
- const { v1Wikis, myWikis, department2WikisMap } = this.classifyWikis(wikis);
+ const { v1Wikis, myWikis, sharedWikis } = this.classifyWikis(wikis);
let wikiCardGroups = [];
wikiCardGroups.push(
);
- for (let deptID in department2WikisMap) {
+ wikiCardGroups.push(
+
+ );
+ for (let deptID in groupWikis) {
+ groupWikis[deptID].wiki_info.length !== 0 &&
wikiCardGroups.push(
);
}
@@ -104,6 +127,7 @@ class WikiCardView extends Component {
key='old-Wikis'
deleteWiki={this.props.deleteWiki}
renameWiki={this.props.renameWiki}
+ unshareGroupWiki={this.props.unshareGroupWiki}
isSidePanelFolded={isSidePanelFolded}
sidePanelRate={sidePanelRate}
wikis={v1Wikis}
diff --git a/frontend/src/pages/wikis/wikis.js b/frontend/src/pages/wikis/wikis.js
index 3617f1a8822..5913695ddb8 100644
--- a/frontend/src/pages/wikis/wikis.js
+++ b/frontend/src/pages/wikis/wikis.js
@@ -9,6 +9,8 @@ import EmptyTip from '../../components/empty-tip';
import AddWikiDialog from '../../components/dialog/add-wiki-dialog';
import wikiAPI from '../../utils/wiki-api';
import WikiCardView from '../../components/wiki-card-view/wiki-card-view';
+import { seafileAPI } from '../../utils/seafile-api';
+
const propTypes = {
sidePanelRate: PropTypes.number,
@@ -23,6 +25,7 @@ class Wikis extends Component {
errorMsg: '',
currentDeptID: '',
wikis: [],
+ groupWikis: [],
isShowAddWikiMenu: false,
isShowAddDialog: false,
isDropdownMenuShown: false,
@@ -35,6 +38,7 @@ class Wikis extends Component {
getWikis = () => {
let wikis = [];
+ let groupWikis = [];
wikiAPI.listWikis().then(res => {
wikis = wikis.concat(res.data.data);
wikis.map(wiki => {
@@ -42,12 +46,20 @@ class Wikis extends Component {
});
wikiAPI.listWikis2().then(res => {
let wikis2 = res.data.wikis;
+ groupWikis = res.data.group_wikis;
+ groupWikis.forEach(group => {
+ group.wiki_info.forEach(wiki => {
+ wiki.version = 'v2';
+ wiki.admins = group.group_admins;
+ });
+ });
wikis2.map(wiki => {
return wiki['version'] = 'v2';
});
this.setState({
loading: false,
- wikis: wikis.concat(wikis2)
+ wikis: wikis.concat(wikis2),
+ groupWikis: groupWikis
});
}).catch((error) => {
this.setState({
@@ -89,11 +101,22 @@ class Wikis extends Component {
addWiki = (wikiName, currentDeptID) => {
wikiAPI.addWiki2(wikiName, currentDeptID).then((res) => {
let wikis = this.state.wikis.slice(0);
+ let groupWikis = this.state.groupWikis;
let new_wiki = res.data;
new_wiki['version'] = 'v2';
- wikis.push(new_wiki);
+ if (currentDeptID){
+ groupWikis.filter(group => {
+ if (group.group_id === currentDeptID){
+ group.wiki_info.push(new_wiki);
+ }
+ return group;
+ });
+ } else {
+ wikis.push(new_wiki);
+ }
this.setState({
wikis,
+ groupWikis,
currentDeptID: '',
});
}).catch((error) => {
@@ -110,7 +133,14 @@ class Wikis extends Component {
let wikis = this.state.wikis.filter(item => {
return item.name !== wiki.name;
});
- this.setState({ wikis: wikis });
+ let groupWikis = this.state.groupWikis.filter(group => {
+ group.wiki_info = group.wiki_info.filter(item => item.name !== wiki.name);
+ return group;
+ });
+ this.setState({
+ wikis: wikis,
+ groupWikis: groupWikis,
+ });
}).catch((error) => {
if (error.response) {
let errorMsg = error.response.data.error_msg;
@@ -122,7 +152,14 @@ class Wikis extends Component {
let wikis = this.state.wikis.filter(item => {
return item.name !== wiki.name;
});
- this.setState({ wikis: wikis });
+ let groupWikis = this.state.groupWikis.filter(group => {
+ group.wiki_info = group.wiki_info.filter(item => item.name !== wiki.name);
+ return group;
+ });
+ this.setState({
+ wikis: wikis,
+ groupWikis: groupWikis,
+ });
}).catch((error) => {
if (error.response) {
let errorMsg = error.response.data.error_msg;
@@ -132,6 +169,57 @@ class Wikis extends Component {
}
};
+ leaveSharedWiki = (wiki) => {
+ if (!wiki.owner.includes('@seafile_group')) {
+ let options = {
+ 'share_type': 'personal',
+ 'from': wiki.owner
+ };
+ seafileAPI.leaveShareRepo(wiki.repo_id, options).then(res => {
+ let wikis = this.state.wikis.filter(item => {
+ return item.name !== wiki.name;
+ });
+ this.setState({
+ wikis: wikis,
+ });
+ }).catch((error) => {
+ let errorMsg = Utils.getErrorMsg(error, true);
+ toaster.danger(errorMsg);
+ });
+ } else {
+ seafileAPI.leaveShareGroupOwnedRepo(wiki.repo_id).then(res => {
+ let wikis = this.state.wikis.filter(item => {
+ return item.name !== wiki.name;
+ });
+ this.setState({
+ wikis: wikis,
+ });
+ }).catch((error) => {
+ let errorMsg = Utils.getErrorMsg(error, true);
+ toaster.danger(errorMsg);
+ });
+ }
+
+ };
+
+ unshareGroupWiki = (wiki, groupId) => {
+ seafileAPI.unshareRepoToGroup(wiki.repo_id, groupId).then(() => {
+ let groupWikis = this.state.groupWikis.map(group => {
+ if (group.group_id === groupId) {
+ return {
+ ...group,
+ wiki_info: group.wiki_info.filter(item => item.name !== wiki.name)
+ };
+ }
+ return group;
+ });
+ this.setState({ groupWikis: groupWikis });
+ }).catch(error => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ };
+
renameWiki = (wiki, newName) => {
if (wiki.version === 'v1') {
wikiAPI.renameWiki(wiki.id, newName).then(() => {
@@ -213,6 +301,8 @@ class Wikis extends Component {
= 3 else '',
+ }
+ wiki_info.update(repo_info)
+ if w.id in filter_repo_type_ids_map['shared']:
+ repo_info = {
+ 'type': 'shared',
+ "last_modified": timestamp_to_isoformat_timestr(w.last_modify),
+ "size": w.size,
+ "encrypted": w.encrypted,
+ "permission": w.permission,
+ "status": normalize_repo_status_code(w.status),
+ "salt": w.salt if w.enc_version >= 3 else '',
+ }
+ wiki_info.update(repo_info)
+
if is_group_wiki(wiki):
group_id = int(wiki.owner.split('@')[0])
wiki_info['owner_nickname'] = group_id_to_name(group_id)
else:
wiki_info['owner_nickname'] = email2nickname(wiki.owner)
wiki_list.append(wiki_info)
-
+
wiki_list = sorted(wiki_list, key=lambda x: x.get('updated_at'), reverse=True)
- return Response({'wikis': wiki_list})
+ return Response({'wikis': wiki_list, 'group_wikis': group_wiki_list})
def post(self, request, format=None):
"""Add a new wiki.