@@ -191,12 +195,14 @@ class DirPanel extends React.Component {
{!this.props.pathExist ?
errMessage :
- {this.props.path === '/' && !(this.props.usedRepoTags.length === 0 && this.props.readmeMarkdown === null) && (
+ {showRepoInfoBar && (
)}
{
+ this.setState({
+ draftCounts: res.data.draft_counts,
+ reviewCounts: res.data.review_counts,
+ })
+ })
seafileAPI.listRepoTags(repoID).then(res => {
let usedRepoTags = [];
res.data.repo_tags.forEach(item => {
@@ -696,6 +704,8 @@ class DirView extends React.Component {
onLibDecryptDialog={this.onLibDecryptDialog}
usedRepoTags={this.state.usedRepoTags}
readmeMarkdown={this.state.readmeMarkdown}
+ draftCounts={this.state.draftCounts}
+ reviewCounts={this.state.reviewCounts}
/>
);
}
diff --git a/frontend/src/components/repo-info-bar.js b/frontend/src/components/repo-info-bar.js
index 9838a5ab3e..0d37771945 100644
--- a/frontend/src/components/repo-info-bar.js
+++ b/frontend/src/components/repo-info-bar.js
@@ -3,7 +3,9 @@ import PropTypes from 'prop-types';
import ModalPortal from './modal-portal';
import { Modal } from 'reactstrap';
import ListTaggedFilesDialog from './dialog/list-taggedfiles-dialog';
-import { siteRoot } from '../utils/constants';
+import ListRepoDraftsDialog from './dialog/list-repo-drafts-dialog';
+import ListRepoReviewsDialog from './dialog/list-repo-reviews-dialog';
+import { siteRoot, gettext } from '../utils/constants';
import { Utils } from '../utils/utils';
import '../css/repo-info-bar.css';
@@ -13,6 +15,8 @@ const propTypes = {
currentPath: PropTypes.string.isRequired,
usedRepoTags: PropTypes.array.isRequired,
readmeMarkdown: PropTypes.object,
+ draftCounts: PropTypes.number,
+ reviewCounts: PropTypes.number,
};
class RepoInfoBar extends React.Component {
@@ -22,6 +26,8 @@ class RepoInfoBar extends React.Component {
this.state = {
currentTag: null,
isListTaggedFileShow: false,
+ showRepoDrafts: false,
+ showRepoReviews: false,
};
}
@@ -38,6 +44,18 @@ class RepoInfoBar extends React.Component {
});
}
+ toggleDrafts = () => {
+ this.setState({
+ showRepoDrafts: !this.state.showRepoDrafts
+ });
+ }
+
+ toggleReviews = () => {
+ this.setState({
+ showRepoReviews: !this.state.showRepoReviews
+ });
+ }
+
render() {
let {repoID, currentPath, usedRepoTags, readmeMarkdown} = this.props;
let href = readmeMarkdown !== null ? siteRoot + 'lib/' + repoID + '/file' + Utils.joinPath(currentPath, readmeMarkdown.name) : '';
@@ -59,24 +77,61 @@ class RepoInfoBar extends React.Component {
})}
)}
- {readmeMarkdown !== null && (
-
- )}
+
+ {readmeMarkdown !== null && (
+
+
+ {readmeMarkdown.name}
+
+ )}
+ {this.props.draftCounts > 0 &&
+
+
+ {gettext('draft')}
+
+ {this.props.draftCounts > 1 ? this.props.draftCounts + ' files' : this.props.draftCounts + ' file'}
+
+
+ }
+ {this.props.reviewCounts > 0 &&
+
+
+ {gettext('review')}
+
+ {this.props.reviewCounts > 1 ? this.props.reviewCounts + ' files' : this.props.reviewCounts + ' file'}
+
+
+ }
+
{this.state.isListTaggedFileShow && (
-
-
-
+
)}
+
+ {this.state.showRepoDrafts && (
+
+
+
+ )}
+
+ {this.state.showRepoReviews && (
+
+
+
+ )}
+
);
}
diff --git a/frontend/src/css/repo-info-bar.css b/frontend/src/css/repo-info-bar.css
index 1d29acb9c8..fa87f8eb3b 100644
--- a/frontend/src/css/repo-info-bar.css
+++ b/frontend/src/css/repo-info-bar.css
@@ -1,6 +1,5 @@
.repo-info-bar {
- margin-bottom: 5px;
- padding: 0 10px;
+ padding: 10px;
border: 1px solid #e6e6dd;
border-radius: 5px;
background: #f8f8f8;
@@ -8,7 +7,6 @@
.used-tag-list {
list-style: none;
- margin: 8px 0;
}
.used-tag-item {
@@ -38,7 +36,8 @@
}
.readme-file {
- margin: 8px 15px;
+ margin: 0 15px;
+ display: inline-block;
}
.readme-file a {
diff --git a/frontend/src/models/draft.js b/frontend/src/models/draft.js
new file mode 100644
index 0000000000..106967d288
--- /dev/null
+++ b/frontend/src/models/draft.js
@@ -0,0 +1,15 @@
+import moment from 'moment';
+
+class Draft {
+
+ constructor(item) {
+ this.created = item.created_at;
+ this.createdStr = moment((new Date(item.created_at)).getTime()).format('YYYY-MM-DD HH:mm');
+ this.id = item.id;
+ this.ownerNickname = item.owner_nickname;
+ this.originRepoID = item.origin_repo_id;
+ this.draftFilePath = item.draft_file_path;
+ }
+}
+
+export default Draft;
diff --git a/frontend/src/models/review.js b/frontend/src/models/review.js
new file mode 100644
index 0000000000..a508966f6d
--- /dev/null
+++ b/frontend/src/models/review.js
@@ -0,0 +1,14 @@
+import moment from 'moment';
+
+class Review {
+
+ constructor(item) {
+ this.created = item.created_at;
+ this.createdStr = moment((new Date(item.created_at)).getTime()).format('YYYY-MM-DD HH:mm');
+ this.id = item.id;
+ this.creatorName = item.creator_name;
+ this.draftFilePath = item.draft_file_path;
+ }
+}
+
+export default Review;
diff --git a/frontend/src/pages/repo-wiki-mode/main-panel.js b/frontend/src/pages/repo-wiki-mode/main-panel.js
index 30f4ccb4b8..b5521941a4 100644
--- a/frontend/src/pages/repo-wiki-mode/main-panel.js
+++ b/frontend/src/pages/repo-wiki-mode/main-panel.js
@@ -63,6 +63,8 @@ const propTypes = {
reviewID: PropTypes.any,
usedRepoTags: PropTypes.array.isRequired,
readmeMarkdown: PropTypes.object,
+ draftCounts: PropTypes.number,
+ reviewCounts: PropTypes.number,
};
class MainPanel extends Component {
@@ -160,6 +162,9 @@ class MainPanel extends Component {
render() {
const ErrMessage = (
{gettext('Folder does not exist.')}
);
+ const showRepoInfoBar = this.props.path === '/' && (
+ this.props.usedRepoTags.length != 0 || this.props.readmeMarkdown != null ||
+ this.props.draftCounts != 0 || this.props.reviewCounts != 0);
return (
@@ -240,12 +245,14 @@ class MainPanel extends Component {
:
- {this.props.path === '/' && !(this.props.usedRepoTags.length === 0 && this.props.readmeMarkdown === null) && (
+ {showRepoInfoBar && (
)}
{
+ this.setState({
+ draftCounts: res.data.draft_counts,
+ reviewCounts: res.data.review_counts
+ });
+ });
seafileAPI.listRepoTags(repoID).then(res => {
let usedRepoTags = [];
res.data.repo_tags.forEach(item => {
@@ -1007,6 +1015,8 @@ class Wiki extends Component {
goReviewPage={this.goReviewPage}
usedRepoTags={this.state.usedRepoTags}
readmeMarkdown={this.state.readmeMarkdown}
+ draftCounts={this.state.draftCounts}
+ reviewCounts={this.state.reviewCounts}
/>
);
diff --git a/seahub/api2/endpoints/repo_draft_review_info.py b/seahub/api2/endpoints/repo_draft_review_info.py
new file mode 100644
index 0000000000..b3fcc6f1f3
--- /dev/null
+++ b/seahub/api2/endpoints/repo_draft_review_info.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2012-2016 Seafile Ltd.
+import logging
+
+from rest_framework import status
+from rest_framework.authentication import SessionAuthentication
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+from seaserv import seafile_api
+from django.utils.translation import ugettext as _
+
+from seahub.api2.authentication import TokenAuthentication
+from seahub.api2.throttling import UserRateThrottle
+from seahub.api2.utils import api_error
+from seahub.views import check_folder_permission
+
+from seahub.drafts.models import Draft, DraftReview
+
+logger = logging.getLogger(__name__)
+
+
+class RepoDraftReviewCounts(APIView):
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated, )
+ throttle_classes = (UserRateThrottle, )
+
+ def get(self, request, repo_id, format=None):
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # check perm
+ perm = check_folder_permission(request, repo_id, '/')
+ if not perm:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ result = {}
+
+ # get draft counts
+ result['draft_counts'] = Draft.objects.get_draft_counts_by_repo_id(repo_id)
+ result['review_counts'] = DraftReview.objects.get_review_counts_by_repo_id(repo_id)
+
+ return Response(result)
+
+
+class RepoDraftInfo(APIView):
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated, )
+ throttle_classes = (UserRateThrottle, )
+
+ def get(self, request, repo_id, format=None):
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # check perm
+ perm = check_folder_permission(request, repo_id, '/')
+ if not perm:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ result = {}
+
+ # list draft
+ drafts = Draft.objects.list_draft_by_repo_id(repo_id)
+ result['drafts'] = drafts
+
+ return Response(result)
+
+
+class RepoReviewInfo(APIView):
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated, )
+ throttle_classes = (UserRateThrottle, )
+
+ def get(self, request, repo_id, format=None):
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # check perm
+ perm = check_folder_permission(request, repo_id, '/')
+ if not perm:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ result = {}
+
+ # list review
+ reviews = DraftReview.objects.list_review_by_repo_id(repo_id)
+ result['reviews'] = reviews
+
+ return Response(result)
diff --git a/seahub/drafts/models.py b/seahub/drafts/models.py
index a94745acf1..af0c5750f8 100644
--- a/seahub/drafts/models.py
+++ b/seahub/drafts/models.py
@@ -28,6 +28,29 @@ class OriginalFileConflict(Exception):
class DraftManager(models.Manager):
+ def get_draft_counts_by_repo_id(self, repo_id):
+ num = self.filter(origin_repo_id=repo_id).count()
+
+ return num
+
+ def list_draft_by_repo_id(self, repo_id):
+ """list draft by repo id
+ """
+ drafts = []
+ qs = self.filter(origin_repo_id=repo_id)
+
+ for d in qs:
+ draft = {}
+ draft['id'] = d.id
+ draft['owner_nickname'] = email2nickname(d.username)
+ draft['origin_repo_id'] = d.origin_repo_id
+ draft['draft_file_path'] = d.draft_file_path
+ draft['created_at'] = datetime_to_isoformat_timestr(d.created_at)
+
+ drafts.append(draft)
+
+ return drafts
+
def list_draft_by_username(self, username, with_reviews=True):
"""list all user drafts
If with_reviews is true, return the draft associated review
@@ -246,6 +269,25 @@ class DraftReviewExist(Exception):
class DraftReviewManager(models.Manager):
+ def get_review_counts_by_repo_id(self, repo_id, status='open'):
+ num = self.filter(origin_repo_id=repo_id, status=status).count()
+
+ return num
+
+ def list_review_by_repo_id(self, repo_id, status='open'):
+ reviews = []
+ qs = self.filter(origin_repo_id=repo_id, status=status)
+
+ for review in qs:
+ review_obj = {}
+ review_obj['id'] = review.id
+ review_obj['creator_name'] = email2nickname(review.creator)
+ review_obj['created_at'] = datetime_to_isoformat_timestr(review.created_at)
+ review_obj['draft_file_path'] = review.draft_file_path
+ reviews.append(review_obj)
+
+ return reviews
+
def add(self, creator, draft):
try:
d_r = self.get(creator=creator, draft_id=draft)
diff --git a/seahub/urls.py b/seahub/urls.py
index 8f3a09ac39..b5967effd9 100644
--- a/seahub/urls.py
+++ b/seahub/urls.py
@@ -70,6 +70,8 @@ from seahub.api2.endpoints.wikis import WikisView, WikiView
from seahub.api2.endpoints.drafts import DraftsView, DraftView
from seahub.api2.endpoints.draft_reviews import DraftReviewsView, DraftReviewView
from seahub.api2.endpoints.draft_review_reviewer import DraftReviewReviewerView
+from seahub.api2.endpoints.repo_draft_review_info import RepoDraftInfo, \
+ RepoReviewInfo, RepoDraftReviewCounts
from seahub.api2.endpoints.file_review import FileReviewView
from seahub.api2.endpoints.activities import ActivitiesView
from seahub.api2.endpoints.wiki_pages import WikiPageView, WikiPagesView, WikiPagesDirView, WikiPageContentView
@@ -373,6 +375,10 @@ urlpatterns = [
url(r'^api/v2.1/file-review/$', FileReviewView.as_view(), name='api-v2.1-file-review'),
+ url(r'^api/v2.1/repo/(?P
[-0-9a-f]{36})/drafts/$', RepoDraftInfo.as_view(), name='api-v2.1-repo-drafts' ),
+ url(r'^api/v2.1/repo/(?P[-0-9a-f]{36})/reviews/$', RepoReviewInfo.as_view(), name='api-v2.1-repo-reviews' ),
+ url(r'^api/v2.1/repo/(?P[-0-9a-f]{36})/draft-review-counts/$', RepoDraftReviewCounts.as_view(), name='api-v2.1-repo-draft-review-counts' ),
+
## user::activities
url(r'^api/v2.1/activities/$', ActivitiesView.as_view(), name='api-v2.1-acitvity'),