1
0
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:
er-pai-r
2023-07-07 16:07:14 +08:00
parent 74f18d8b79
commit cee0349f3c
13 changed files with 312 additions and 11 deletions

View File

@@ -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",

View File

@@ -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');
}

View File

@@ -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);

View File

@@ -0,0 +1,3 @@
.sdoc-revisions .sdoc-revision:hover {
background-color: #f8f8f8;
}

View 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'));

View File

@@ -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')},

View File

@@ -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);
}
};

View File

@@ -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(

View File

@@ -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'),

View File

@@ -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

View File

@@ -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,
})

View 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 %}

View File

@@ -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'),