diff --git a/frontend/config/webpack.entry.js b/frontend/config/webpack.entry.js index 7e46e2ce6a..ce7ab8628c 100644 --- a/frontend/config/webpack.entry.js +++ b/frontend/config/webpack.entry.js @@ -10,6 +10,7 @@ const entryFiles = { fileHistoryOld: "/file-history-old.js", sdocFileHistory: "/pages/sdoc-file-history/index.js", sdocRevision: "/pages/sdoc-revision/index.js", + sdocRevisions: "/pages/sdoc-revisions/index.js", app: "/app.js", draft: "/draft.js", sharedDirView: "/shared-dir-view.js", diff --git a/frontend/src/components/dirent-grid-view/dirent-grid-view.js b/frontend/src/components/dirent-grid-view/dirent-grid-view.js index e17319f950..38686c2ab8 100644 --- a/frontend/src/components/dirent-grid-view/dirent-grid-view.js +++ b/frontend/src/components/dirent-grid-view/dirent-grid-view.js @@ -159,6 +159,9 @@ class DirentGridView extends React.Component{ case 'Start revise': this.onStartRevise(currentObject); break; + case 'List revisions': + this.openRevisionsPage(currentObject); + break; case 'Comment': this.onCommentItem(); break; @@ -297,14 +300,22 @@ class DirentGridView extends React.Component{ let repoID = this.props.repoID; let filePath = this.getDirentPath(currentObject); seafileAPI.sdocStartRevise(repoID, filePath).then((res) => { - let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(res.data.file_path); + const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(res.data.file_path); window.open(url); }).catch(error => { - let errMessage = Utils.getErrorMsg(error); + const errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); } + openRevisionsPage = (currentObject) => { + const repoID = this.props.repoID; + const filePath = this.getDirentPath(currentObject); + const url = Utils.generateRevisionsURL(siteRoot, repoID, filePath); + if (!url) return; + window.open(url); + } + onCommentItem = () => { this.props.showDirentDetail('comments'); } diff --git a/frontend/src/components/dirent-list-view/dirent-list-item.js b/frontend/src/components/dirent-list-view/dirent-list-item.js index d9a2bbf7c8..87f72f41fe 100644 --- a/frontend/src/components/dirent-list-view/dirent-list-item.js +++ b/frontend/src/components/dirent-list-view/dirent-list-item.js @@ -273,6 +273,9 @@ class DirentListItem extends React.Component { case 'Start revise': this.onStartRevise(); break; + case 'List revisions': + this.openRevisionsPage(); + break; case 'Comment': this.props.onDirentClick(this.props.dirent); this.props.showDirentDetail('comments'); @@ -385,17 +388,25 @@ class DirentListItem extends React.Component { } onStartRevise = () => { - let repoID = this.props.repoID; - let filePath = this.getDirentPath(this.props.dirent); + const repoID = this.props.repoID; + const filePath = this.getDirentPath(this.props.dirent); seafileAPI.sdocStartRevise(repoID, filePath).then((res) => { - let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(res.data.file_path); + const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(res.data.file_path); window.open(url); }).catch(error => { - let errMessage = Utils.getErrorMsg(error); + const errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); } + openRevisionsPage = () => { + const repoID = this.props.repoID; + const filePath = this.getDirentPath(this.props.dirent); + const url = Utils.generateRevisionsURL(siteRoot, repoID, filePath); + if (!url) return; + window.open(url); + } + onHistory = () => { let repoID = this.props.repoID; let filePath = this.getDirentPath(this.props.dirent); diff --git a/frontend/src/css/sdoc-revisions.css b/frontend/src/css/sdoc-revisions.css new file mode 100644 index 0000000000..3a4813923c --- /dev/null +++ b/frontend/src/css/sdoc-revisions.css @@ -0,0 +1,3 @@ +.sdoc-revisions .sdoc-revision:hover { + background-color: #f8f8f8; +} diff --git a/frontend/src/pages/sdoc-revisions/index.js b/frontend/src/pages/sdoc-revisions/index.js new file mode 100644 index 0000000000..4f8caeade1 --- /dev/null +++ b/frontend/src/pages/sdoc-revisions/index.js @@ -0,0 +1,122 @@ +import React, { Component, Fragment } from 'react'; +import ReactDom from 'react-dom'; +import moment from 'moment'; +import classnames from 'classnames'; +import { siteRoot, mediaUrl, logoPath, siteTitle, logoHeight, logoWidth, gettext } from '../../utils/constants'; +import '../../css/sdoc-revisions.css'; + +moment.locale(window.app.config.lang); +const { filename, zipped, forloopLast, repo, viewLibFile, revisions, currentPage, prevPage, + nextPage, perPage, pageNext, extraHref } = window.sdocRevisions; +const validZipped = JSON.parse(zipped); +const validRevisions = JSON.parse(revisions); + +class SdocRevisions extends Component { + + renderPerPage = (perPageCount, className) => { + if (perPage === perPageCount) { + return ({perPageCount}); + } + return ( + {perPageCount} + ); + } + + renderRevisions = () => { + if (!Array.isArray(validRevisions) || validRevisions.length === 0) { + return ( +
+

{gettext('This file has not revisions yet')}

+
+ ); + } + return ( + + + + + + + + + + {validRevisions.map(revision => { + return ( + + + + + + ); + })} + +
{gettext('User')}{gettext('File_name')}{gettext('Ctime')}
{revision.nickname} + + {revision.filename} + + {moment(revision.created_at).format('YYYY-MM-DD HH:MM')}
+ ); + } + + renderFooter = () => { + return ( +
+ {currentPage !== 1 && ( + {gettext('Previous')} + )} + {pageNext && ( + {gettext('Next')} + )} + {(currentPage !== 1 || pageNext) && ({'|'})} + {gettext('Per page: ')} + {this.renderPerPage(25, 'mr-1')} + {this.renderPerPage(50, 'mr-1')} + {this.renderPerPage(100)} +
+ ); + } + + render() { + return ( + <> + +
+
+
+

+ {filename} + {gettext('Revisions')} +

+
+

+ {gettext('Current Path:')} + {validZipped.map((item, index) => { + if (forloopLast) { + return ({item[0]}); + } + return ( + + {item[0]} + {index !== validZipped.length - 1 && ( + {'/'} + )} + + ); + })} +

+
+ {this.renderRevisions()} + {this.renderFooter()} +
+
+
+ + ); + } +} + +ReactDom.render(, document.getElementById('wrapper')); diff --git a/frontend/src/utils/text-translation.js b/frontend/src/utils/text-translation.js index 1a02c976c8..2afa6fdfb1 100644 --- a/frontend/src/utils/text-translation.js +++ b/frontend/src/utils/text-translation.js @@ -19,6 +19,7 @@ const TextTranslation = { 'MASK_AS_DRAFT' : {key : 'Mask as draft', value : gettext('Mark as draft')}, 'UNMASK_AS_DRAFT' : {key : 'Unmask as draft', value : gettext('Unmark as draft')}, 'START_REVISE' : {key : 'Start revise', value : gettext('Start revise')}, + 'LIST_REVISIONS': { key: 'List revisions', value: gettext('List revisions') }, 'COMMENT' : {key : 'Comment', value : gettext('Comment')}, 'HISTORY' : {key : 'History', value : gettext('History')}, 'ACCESS_LOG' : {key : 'Access Log', value : gettext('Access Log')}, diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index d680eee91d..8dced1abe5 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -527,7 +527,7 @@ export const Utils = { getFileOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) { let list = []; const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK, MASK_AS_DRAFT, UNMASK_AS_DRAFT, - START_REVISE, COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT } = TextTranslation; + START_REVISE, LIST_REVISIONS, COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT } = TextTranslation; const permission = dirent.permission; const { isCustomPermission, customPermission } = Utils.getUserPermission(permission); @@ -601,6 +601,7 @@ export const Utils = { list.push(MASK_AS_DRAFT); } list.push(START_REVISE); + list.push(LIST_REVISIONS); } if (enableFileComment) { list.push(COMMENT); @@ -1548,6 +1549,12 @@ export const Utils = { generateRevisionURL: function(siteRoot, repoID, path) { if (!siteRoot || !repoID || !path) return ''; return siteRoot + 'repo/sdoc_revision/' + repoID + '/?p=' + this.encodePath(path); + }, + + generateRevisionsURL: function(siteRoot, repoID, path) { + if (!siteRoot || !repoID || !path) return ''; + console.log(siteRoot + 'repo/sdoc_revisions/' + repoID + '/?p=' + this.encodePath(path)) + return siteRoot + 'repo/sdoc_revisions/' + repoID + '/?p=' + this.encodePath(path); } }; diff --git a/frontend/src/view-file-sdoc.js b/frontend/src/view-file-sdoc.js index 5e238973c0..0735e04b13 100644 --- a/frontend/src/view-file-sdoc.js +++ b/frontend/src/view-file-sdoc.js @@ -11,7 +11,7 @@ const { username, name } = window.app.userInfo; const { repoID, repoName, parentDir, filePerm, docPath, docName, docUuid, seadocAccessToken, seadocServerUrl, assetsUrl, - isSdocRevision, isPublished, + isSdocRevision, isPublished, originFilename, revisionCreatedAt, } = window.app.pageOptions; window.seafile = { @@ -36,6 +36,8 @@ window.seafile = { isSdocRevision, isPublished, revisionURL: Utils.generateRevisionURL(siteRoot, repoID, docPath), + originFilename, + revisionCreatedAt, }; ReactDom.render( diff --git a/seahub/seadoc/urls.py b/seahub/seadoc/urls.py index e881488e60..ebabbdcf81 100644 --- a/seahub/seadoc/urls.py +++ b/seahub/seadoc/urls.py @@ -3,7 +3,6 @@ from .apis import SeadocAccessToken, SeadocUploadLink, SeadocDownloadLink, Seado SeadocUploadImage, SeadocDownloadImage, SeadocCopyHistoryFile, SeadocHistory, SeadocDrafts, SeadocMaskAsDraft, \ SeadocCommentsView, SeadocCommentView, SeadocRevisions, SeadocPublishRevision -from .views import sdoc_revision urlpatterns = [ re_path(r'^access-token/(?P[-0-9a-f]{36})/$', SeadocAccessToken.as_view(), name='seadoc_access_token'), diff --git a/seahub/seadoc/utils.py b/seahub/seadoc/utils.py index 5d9e7fd07c..1d067d8463 100644 --- a/seahub/seadoc/utils.py +++ b/seahub/seadoc/utils.py @@ -194,3 +194,29 @@ def is_seadoc_revision(doc_uuid): else: info = {'is_sdoc_revision': False} return info + +def gen_path_link(path, repo_name): + """ + Generate navigate paths and links in repo page. + + """ + if path and path[-1] != '/': + path += '/' + + paths = [] + links = [] + if path and path != '/': + paths = path[1:-1].split('/') + i = 1 + for name in paths: + link = '/' + '/'.join(paths[:i]) + i = i + 1 + links.append(link) + if repo_name: + paths.insert(0, repo_name) + links.insert(0, '/') + + zipped = list(zip(paths, links)) + + return zipped + diff --git a/seahub/seadoc/views.py b/seahub/seadoc/views.py index 8b4530dda2..4b949d76c2 100644 --- a/seahub/seadoc/views.py +++ b/seahub/seadoc/views.py @@ -2,12 +2,16 @@ import os from django.shortcuts import render from django.utils.translation import gettext as _ from seaserv import get_repo +from urllib.parse import quote +import json + from seahub.auth.decorators import login_required from seahub.utils import render_error from seahub.views import check_folder_permission, validate_owner, get_seadoc_file_uuid from seahub.tags.models import FileUUIDMap +from seahub.seadoc.models import SeadocRevision -from .utils import is_seadoc_revision, get_seadoc_download_link +from .utils import is_seadoc_revision, get_seadoc_download_link, gen_path_link @login_required @@ -63,3 +67,73 @@ def sdoc_revision(request, repo_id): return render(request, 'sdoc_revision.html', return_dict) + +@login_required +def sdoc_revisions(request, repo_id): + """List file revisions in file version history page. + """ + repo = get_repo(repo_id) + if not repo: + error_msg = _("Library does not exist") + return render_error(request, error_msg) + + # perm check + if not check_folder_permission(request, repo_id, '/'): + error_msg = _("Permission denied.") + return render_error(request, error_msg) + + path = request.GET.get('p', '/') + if not path: + return render_error(request) + + if path[-1] == '/': + path = path[:-1] + filename = os.path.basename(path) + + # Make sure page request is an int. If not, deliver first page. + try: + current_page = int(request.GET.get('page', '1')) + per_page = int(request.GET.get('per_page', '100')) + except ValueError: + current_page = 1 + per_page = 100 + + start = per_page * (current_page - 1) + limit = per_page + 1 + + file_uuid = get_seadoc_file_uuid(repo, path) + origin_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid) + + revision_queryset = SeadocRevision.objects.list_by_origin_doc_uuid(origin_uuid_map.uuid, start, limit) + count = SeadocRevision.objects.filter(origin_doc_uuid=origin_uuid_map.uuid).count() + zipped = gen_path_link(path, repo.name) + extra_href = "&p=%s" % quote(path) + + uuid_set = set() + for item in revision_queryset: + uuid_set.add(item.doc_uuid) + uuid_set.add(item.origin_doc_uuid) + + fileuuidmap_queryset = FileUUIDMap.objects.filter(uuid__in=list(uuid_set)) + revisions = [revision.to_dict(fileuuidmap_queryset) for revision in revision_queryset] + + if len(revisions) == per_page + 1: + page_next = True + else: + page_next = False + + return render(request, 'sdoc_revisions.html', { + 'repo': repo, + 'revisions': json.dumps(revisions), + 'count': count, + 'docUuid': file_uuid, + 'path': path, + 'filename': filename, + 'zipped': json.dumps(zipped), + 'extra_href': extra_href, + 'current_page': current_page, + 'prev_page': current_page - 1, + 'next_page': current_page + 1, + 'per_page': per_page, + 'page_next': page_next, + }) diff --git a/seahub/templates/sdoc_revisions.html b/seahub/templates/sdoc_revisions.html new file mode 100644 index 0000000000..9fd6211597 --- /dev/null +++ b/seahub/templates/sdoc_revisions.html @@ -0,0 +1,43 @@ +{% extends "base_for_react.html" %} +{% load render_bundle from webpack_loader %} +{% load seahub_tags avatar_tags i18n static %} + +{% block extra_style %} + + +{% render_bundle 'sdocRevisions' 'css'%} +{% endblock %} + +{% block extra_script %} + + {% render_bundle 'sdocRevisions' 'js'%} +{% endblock %} diff --git a/seahub/urls.py b/seahub/urls.py index e3b61d4412..f3b8310641 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -194,7 +194,7 @@ from seahub.api2.endpoints.admin.virus_scan_records import AdminVirusFilesView, from seahub.api2.endpoints.file_participants import FileParticipantsView, FileParticipantView from seahub.api2.endpoints.repo_related_users import RepoRelatedUsersView from seahub.api2.endpoints.repo_auto_delete import RepoAutoDeleteView -from seahub.seadoc.views import sdoc_revision +from seahub.seadoc.views import sdoc_revision, sdoc_revisions from seahub.ocm.settings import OCM_ENDPOINT @@ -222,6 +222,7 @@ urlpatterns = [ re_path(r'^repo/download_dir/(?P[-0-9a-f]{36})/$', repo_download_dir, name='repo_download_dir'), re_path(r'^repo/file_revisions/(?P[-0-9a-f]{36})/$', file_revisions, name='file_revisions'), re_path(r'^repo/sdoc_revision/(?P[-0-9a-f]{36})/$', sdoc_revision, name='sdoc_revision'), + re_path(r'^repo/sdoc_revisions/(?P[-0-9a-f]{36})/$', sdoc_revisions, name='sdoc_revisions'), re_path(r'^repo/file-access/(?P[-0-9a-f]{36})/$', file_access, name='file_access'), re_path(r'^repo/text_diff/(?P[-0-9a-f]{36})/$', text_diff, name='text_diff'), re_path(r'^repo/history/(?P[-0-9a-f]{36})/$', repo_history, name='repo_history'),