mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-01 15:09:14 +00:00
feat: list revisions
This commit is contained in:
@@ -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",
|
||||
|
@@ -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');
|
||||
}
|
||||
|
@@ -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);
|
||||
|
3
frontend/src/css/sdoc-revisions.css
Normal file
3
frontend/src/css/sdoc-revisions.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.sdoc-revisions .sdoc-revision:hover {
|
||||
background-color: #f8f8f8;
|
||||
}
|
122
frontend/src/pages/sdoc-revisions/index.js
Normal file
122
frontend/src/pages/sdoc-revisions/index.js
Normal file
@@ -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 (<span className={classnames('', className)}>{perPageCount}</span>);
|
||||
}
|
||||
return (
|
||||
<a href={`?per_page=${perPageCount}${extraHref}`} className={classnames('per-page', className)}>{perPageCount}</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderRevisions = () => {
|
||||
if (!Array.isArray(validRevisions) || validRevisions.length === 0) {
|
||||
return (
|
||||
<div className="empty-tips">
|
||||
<h2 className="alc">{gettext('This file has not revisions yet')}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<table className="file-audit-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="25%" className="user">{gettext('User')}</th>
|
||||
<th width="50%">{gettext('File_name')}</th>
|
||||
<th width="25%">{gettext('Ctime')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{validRevisions.map(revision => {
|
||||
return (
|
||||
<tr key={revision.doc_uuid} className="sdoc-revision">
|
||||
<td width="25%" className="user">{revision.nickname}</td>
|
||||
<td width="50%">
|
||||
<a href={`${siteRoot}lib/${repo['id']}/file${revision.file_path}`}>
|
||||
{revision.filename}
|
||||
</a>
|
||||
</td>
|
||||
<td width="25%">{moment(revision.created_at).format('YYYY-MM-DD HH:MM')}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
renderFooter = () => {
|
||||
return (
|
||||
<div id="paginator">
|
||||
{currentPage !== 1 && (
|
||||
<a href={`?page=${prevPage}&per_page=${perPage}${extraHref}`} className="mr-1">{gettext('Previous')}</a>
|
||||
)}
|
||||
{pageNext && (
|
||||
<a href={`?page=${nextPage}&per_page=${perPage}${extraHref}`} className="mr-1">{gettext('Next')}</a>
|
||||
)}
|
||||
{(currentPage !== 1 || pageNext) && (<span className="mr-1">{'|'}</span>)}
|
||||
<span className="mr-1">{gettext('Per page: ')}</span>
|
||||
{this.renderPerPage(25, 'mr-1')}
|
||||
{this.renderPerPage(50, 'mr-1')}
|
||||
{this.renderPerPage(100)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<div id="header" className="d-flex">
|
||||
<a href={siteRoot} id="logo">
|
||||
<img src={`${mediaUrl}${logoPath}`} title={siteTitle} alt="logo" width={logoWidth} height={logoHeight} />
|
||||
</a>
|
||||
</div>
|
||||
<div id="main" className="container-fluid w100 flex-auto ov-auto sdoc-revisions">
|
||||
<div id="wide-panel-noframe" className="row">
|
||||
<div className="col-md-10 col-md-offset-1">
|
||||
<h2 className="file-access-hd">
|
||||
<span className="op-target mr-1">{filename}</span>
|
||||
{gettext('Revisions')}
|
||||
</h2>
|
||||
<div className="file-audit-list-topbar">
|
||||
<p className="path">
|
||||
<span className="mr-1">{gettext('Current Path:')}</span>
|
||||
{validZipped.map((item, index) => {
|
||||
if (forloopLast) {
|
||||
return (<a key={index} href={`${viewLibFile.slice(0, -1)}${item[1]}`} target="_blank" rel="noreferrer">{item[0]}</a>);
|
||||
}
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<a href={`${viewLibFile.slice(0, -1)}${item[1]}`} target="_blank" rel="noreferrer">{item[0]}</a>
|
||||
{index !== validZipped.length - 1 && (
|
||||
<span className="mr-1 ml-1">{'/'}</span>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
{this.renderRevisions()}
|
||||
{this.renderFooter()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDom.render(<SdocRevisions />, document.getElementById('wrapper'));
|
@@ -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')},
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
@@ -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(
|
||||
|
@@ -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<repo_id>[-0-9a-f]{36})/$', SeadocAccessToken.as_view(), name='seadoc_access_token'),
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
})
|
||||
|
43
seahub/templates/sdoc_revisions.html
Normal file
43
seahub/templates/sdoc_revisions.html
Normal file
@@ -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 %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "css/bootstrap.min.css" %}"/>
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/seahub.css?t=1398068110" />
|
||||
{% render_bundle 'sdocRevisions' 'css'%}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
window.app.config.lang = '{{LANGUAGE_CODE}}';
|
||||
console.log('{{page_next}}')
|
||||
window.sdocRevisions = {
|
||||
repo: { "id": "{{ repo.id }}", "name": "{{ repo.name }}" },
|
||||
revisions: '{{ revisions|escapejs }}',
|
||||
count: '{{ count }}',
|
||||
filePath: '{{ path|escapejs }}',
|
||||
docUuid: '{{ docUuid }}',
|
||||
perPage: '{{ per_page }}',
|
||||
filename: '{{ filename }}',
|
||||
zipped: '{{ zipped|escapejs }}',
|
||||
currentPage: Number('{{ current_page }}'),
|
||||
prevPage: Number('{{ prev_page }}'),
|
||||
nextPage: Number('{{ next_page }}'),
|
||||
perPage: Number('{{ per_page }}'),
|
||||
pageNext: '{{ page_next }}' === 'False' ? false : true,
|
||||
extraHref: '{{ extra_href|escapejs }}',
|
||||
|
||||
}
|
||||
{% if not forloop.last %}
|
||||
window.sdocRevisions['forloopLast'] = false;
|
||||
window.sdocRevisions['viewLibFile'] = '{% url 'lib_view' repo.id repo.name '' %}';
|
||||
{% else %}
|
||||
window.sdocRevisions['forloopLast'] = true;
|
||||
window.sdocRevisions['viewLibFile'] = '{% url 'view_lib_file' repo.id '' %}';
|
||||
{% endif %}
|
||||
|
||||
|
||||
</script>
|
||||
{% render_bundle 'sdocRevisions' 'js'%}
|
||||
{% endblock %}
|
@@ -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<repo_id>[-0-9a-f]{36})/$', repo_download_dir, name='repo_download_dir'),
|
||||
re_path(r'^repo/file_revisions/(?P<repo_id>[-0-9a-f]{36})/$', file_revisions, name='file_revisions'),
|
||||
re_path(r'^repo/sdoc_revision/(?P<repo_id>[-0-9a-f]{36})/$', sdoc_revision, name='sdoc_revision'),
|
||||
re_path(r'^repo/sdoc_revisions/(?P<repo_id>[-0-9a-f]{36})/$', sdoc_revisions, name='sdoc_revisions'),
|
||||
re_path(r'^repo/file-access/(?P<repo_id>[-0-9a-f]{36})/$', file_access, name='file_access'),
|
||||
re_path(r'^repo/text_diff/(?P<repo_id>[-0-9a-f]{36})/$', text_diff, name='text_diff'),
|
||||
re_path(r'^repo/history/(?P<repo_id>[-0-9a-f]{36})/$', repo_history, name='repo_history'),
|
||||
|
Reference in New Issue
Block a user