1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-16 23:29:49 +00:00

feat: sdoc published revision support view (#5709)

* feat: sdoc published revision support view

* feat: restore code

* feat: update revision id
This commit is contained in:
杨国璇
2023-10-26 17:15:42 +08:00
committed by GitHub
parent ddf910cd8b
commit 517b558b55
12 changed files with 223 additions and 99 deletions

View File

@@ -7,8 +7,8 @@ const entryFiles = {
wiki: "/wiki.js", wiki: "/wiki.js",
fileHistory: "/file-history.js", fileHistory: "/file-history.js",
fileHistoryOld: "/file-history-old.js", fileHistoryOld: "/file-history-old.js",
sdocFileHistory: "/pages/sdoc-file-history/index.js", sdocFileHistory: "/pages/sdoc/sdoc-file-history/index.js",
sdocRevision: "/pages/sdoc-revision/index.js", sdocPublishedRevision: "/pages/sdoc/sdoc-published-revision/index.js",
app: "/app.js", app: "/app.js",
draft: "/draft.js", draft: "/draft.js",
sharedDirView: "/shared-dir-view.js", sharedDirView: "/shared-dir-view.js",

View File

@@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { EventBus, EXTERNAL_EVENT } from '@seafile/sdoc-editor'; import { EventBus, EXTERNAL_EVENT } from '@seafile/sdoc-editor';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../../utils/utils';
import toaster from '../../components/toast'; import toaster from '../../../components/toast';
import InternalLinkDialog from '../../components/dialog/internal-link-dialog'; import InternalLinkDialog from '../../../components/dialog/internal-link-dialog';
import ShareDialog from '../../components/dialog/share-dialog'; import ShareDialog from '../../../components/dialog/share-dialog';
const propTypes = { const propTypes = {
repoID: PropTypes.string.isRequired, repoID: PropTypes.string.isRequired,

View File

@@ -2,11 +2,11 @@ import moment from 'moment';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem} from 'reactstrap'; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem} from 'reactstrap';
import { gettext, filePath } from '../../utils/constants'; import { gettext, filePath } from '../../../utils/constants';
import URLDecorator from '../../utils/url-decorator'; import URLDecorator from '../../../utils/url-decorator';
import Rename from '../../components/rename'; import Rename from '../../../components/rename';
import '../../css/history-record-item.css'; import '../../../css/history-record-item.css';
moment.locale(window.app.config.lang); moment.locale(window.app.config.lang);

View File

@@ -3,16 +3,16 @@ import ReactDom from 'react-dom';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import classnames from 'classnames'; import classnames from 'classnames';
import { DiffViewer } from '@seafile/sdoc-editor'; import { DiffViewer } from '@seafile/sdoc-editor';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../../utils/seafile-api';
import { gettext, historyRepoID } from '../../utils/constants'; import { gettext, historyRepoID } from '../../../utils/constants';
import Loading from '../../components/loading'; import Loading from '../../../components/loading';
import GoBack from '../../components/common/go-back'; import GoBack from '../../../components/common/go-back';
import SidePanel from './side-panel'; import SidePanel from './side-panel';
import { Utils } from '../../utils/utils'; import { Utils } from '../../../utils/utils';
import toaster from '../../components/toast'; import toaster from '../../../components/toast';
import '../../css/layout.css'; import '../../../css/layout.css';
import '../../css/sdoc-file-history.css'; import '../../../css/sdoc-file-history.css';
const { serviceURL, avatarURL, siteRoot } = window.app.config; const { serviceURL, avatarURL, siteRoot } = window.app.config;
const { username, name } = window.app.pageOptions; const { username, name } = window.app.pageOptions;

View File

@@ -1,14 +1,14 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import Loading from '../../components/loading'; import Loading from '../../../components/loading';
import { gettext, historyRepoID, PER_PAGE } from '../../utils/constants'; import { gettext, historyRepoID, PER_PAGE } from '../../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../../utils/utils';
import editUtilities from '../../utils/editor-utilities'; import editUtilities from '../../../utils/editor-utilities';
import toaster from '../../components/toast'; import toaster from '../../../components/toast';
import HistoryVersion from './history-version'; import HistoryVersion from './history-version';
import Switch from '../../components/common/switch'; import Switch from '../../../components/common/switch';
const { docUuid } = window.fileHistory.pageOptions; const { docUuid } = window.fileHistory.pageOptions;

View File

@@ -0,0 +1,55 @@
import React, { Suspense } from 'react';
import ReactDom from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { PublishedRevisionViewer } from '@seafile/sdoc-editor';
import i18n from '../../../_i18n/i18n-sdoc-editor';
import { Utils } from '../../../utils/utils';
import Loading from '../../../components/loading';
const { serviceURL, avatarURL, siteRoot, lang } = window.app.config;
const { username, name } = window.app.userInfo || {};
const {
repoID, repoName, parentDir, filePerm,
docPath, docName, docUuid, seadocAccessToken, seadocServerUrl, assetsUrl,
isSdocRevision, isPublished, originFilename, revisionCreatedAt, originFileVersion,
originFilePath, originDocUuid, revisionId,
} = window.app.pageOptions;
window.seafile = {
repoID,
docPath,
docName,
docUuid,
isOpenSocket: true,
serviceUrl: serviceURL,
accessToken: seadocAccessToken,
sdocServer: seadocServerUrl,
name,
username,
avatarURL,
siteRoot,
docPerm: filePerm,
historyURL: Utils.generateHistoryURL(siteRoot, repoID, docPath),
parentFolderURL: `${siteRoot}library/${repoID}/${Utils.encodePath(repoName + parentDir)}`,
assetsUrl,
isShowInternalLink: true,
isStarIconShown: true, // for star/unstar
isSdocRevision,
isPublished,
originFilename,
originFileVersion,
originFilePath,
originDocUuid,
revisionCreatedAt,
lang,
revisionId,
};
ReactDom.render(
<I18nextProvider i18n={ i18n } >
<Suspense fallback={<Loading />}>
<PublishedRevisionViewer />
</Suspense>
</I18nextProvider>,
document.getElementById('wrapper')
);

View File

@@ -4,7 +4,7 @@ import { I18nextProvider } from 'react-i18next';
import i18n from './_i18n/i18n-sdoc-editor'; import i18n from './_i18n/i18n-sdoc-editor';
import { Utils } from './utils/utils'; import { Utils } from './utils/utils';
import Loading from './components/loading'; import Loading from './components/loading';
import SdocEditor from './pages/sdoc-editor'; import SdocEditor from './pages/sdoc/sdoc-editor';
const { serviceURL, avatarURL, siteRoot, lang } = window.app.config; const { serviceURL, avatarURL, siteRoot, lang } = window.app.config;
const { username, name } = window.app.userInfo; const { username, name } = window.app.userInfo;
@@ -12,7 +12,7 @@ const {
repoID, repoName, parentDir, filePerm, repoID, repoName, parentDir, filePerm,
docPath, docName, docUuid, seadocAccessToken, seadocServerUrl, assetsUrl, docPath, docName, docUuid, seadocAccessToken, seadocServerUrl, assetsUrl,
isSdocRevision, isPublished, originFilename, revisionCreatedAt, originFileVersion, isSdocRevision, isPublished, originFilename, revisionCreatedAt, originFileVersion,
originFilePath, originDocUuid, originFilePath, originDocUuid, revisionId,
} = window.app.pageOptions; } = window.app.pageOptions;
window.seafile = { window.seafile = {
@@ -36,13 +36,13 @@ window.seafile = {
isStarIconShown: true, // for star/unstar isStarIconShown: true, // for star/unstar
isSdocRevision, isSdocRevision,
isPublished, isPublished,
revisionURL: Utils.generateRevisionURL(siteRoot, repoID, docPath),
originFilename, originFilename,
originFileVersion, originFileVersion,
originFilePath, originFilePath,
originDocUuid, originDocUuid,
revisionCreatedAt, revisionCreatedAt,
lang, lang,
revisionId,
}; };
ReactDom.render( ReactDom.render(

View File

@@ -1933,10 +1933,6 @@ class SdocRevisionBaseVersionContent(APIView):
if not revision: if not revision:
error_msg = 'Revision %s not found.' % file_uuid error_msg = 'Revision %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg) return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if revision.is_published:
error_msg = 'Revision %s is already published.' % file_uuid
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
origin_doc_path = revision.origin_doc_path origin_doc_path = revision.origin_doc_path
if not origin_doc_path: if not origin_doc_path:
@@ -1968,3 +1964,51 @@ class SdocRevisionBaseVersionContent(APIView):
'content': resp.content 'content': resp.content
}) })
class SeadocPublishedRevisionContent(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
revision = SeadocRevision.objects.get_by_doc_uuid(file_uuid)
if not revision:
error_msg = 'Revision %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if not revision.is_published:
error_msg = 'Revision %s is not published.' % file_uuid
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
origin_doc_path = revision.origin_doc_path
if not origin_doc_path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Origin file path is invalid.')
publish_file_version = revision.publish_file_version
if not publish_file_version:
return api_error(status.HTTP_400_BAD_REQUEST, 'Origin file version is missing.')
origin_doc_uuid = revision.origin_doc_uuid
origin_doc_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(origin_doc_uuid)
if not origin_doc_uuid_map:
error_msg = 'Origin file uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
username = request.user.username
token = seafile_api.get_fileserver_access_token(origin_doc_uuid_map.repo_id,
publish_file_version, 'download', username)
if not token:
error_msg = 'Origin file %s not found.' % origin_doc_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
origin_file_name = os.path.basename(origin_doc_path)
download_url = gen_inner_file_get_url(token, origin_file_name)
resp = requests.get(download_url)
return Response({
'content': resp.content
})

View File

@@ -3,7 +3,7 @@ from .apis import SeadocAccessToken, SeadocUploadLink, SeadocDownloadLink, Seado
SeadocUploadImage, SeadocDownloadImage, SeadocAsyncCopyImages, SeadocQueryCopyMoveProgressView, SeadocCopyHistoryFile, SeadocHistory, SeadocDrafts, SeadocMaskAsDraft, \ SeadocUploadImage, SeadocDownloadImage, SeadocAsyncCopyImages, SeadocQueryCopyMoveProgressView, SeadocCopyHistoryFile, SeadocHistory, SeadocDrafts, SeadocMaskAsDraft, \
SeadocCommentsView, SeadocCommentView, SeadocStartRevise, SeadocPublishRevision, SeadocRevisionsCount, SeadocRevisions, \ SeadocCommentsView, SeadocCommentView, SeadocStartRevise, SeadocPublishRevision, SeadocRevisionsCount, SeadocRevisions, \
SeadocCommentRepliesView, SeadocCommentReplyView, SeadocFileView, SeadocFileUUIDView, SeadocDirView, SdocRevisionBaseVersionContent, SeadocRevisionView, \ SeadocCommentRepliesView, SeadocCommentReplyView, SeadocFileView, SeadocFileUUIDView, SeadocDirView, SdocRevisionBaseVersionContent, SeadocRevisionView, \
SeadocFilesInfoView, DeleteSeadocOtherRevision SeadocFilesInfoView, DeleteSeadocOtherRevision, SeadocPublishedRevisionContent
# api/v2.1/seadoc/ # api/v2.1/seadoc/
@@ -31,6 +31,7 @@ urlpatterns = [
re_path(r'^revision/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocRevisionView.as_view(), name='seadoc_revision'), re_path(r'^revision/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocRevisionView.as_view(), name='seadoc_revision'),
re_path(r'^revision/base-version-content/(?P<file_uuid>[-0-9a-f]{36})/$', SdocRevisionBaseVersionContent.as_view(), name='sdoc_revision_base_version_content'), re_path(r'^revision/base-version-content/(?P<file_uuid>[-0-9a-f]{36})/$', SdocRevisionBaseVersionContent.as_view(), name='sdoc_revision_base_version_content'),
re_path(r'^revision/origin-file-content/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocOriginFileContent.as_view(), name='sdoc_revision_origin_file_content'), re_path(r'^revision/origin-file-content/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocOriginFileContent.as_view(), name='sdoc_revision_origin_file_content'),
re_path(r'^revision/published-content/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocPublishedRevisionContent.as_view(), name='sdoc_published_revision_content'),
re_path(r'^delete-revision/(?P<file_uuid>[-0-9a-f]{36})/(?P<revision_id>\d+)/$', DeleteSeadocOtherRevision.as_view(), name='sdoc_delete_other_revision'), re_path(r'^delete-revision/(?P<file_uuid>[-0-9a-f]{36})/(?P<revision_id>\d+)/$', DeleteSeadocOtherRevision.as_view(), name='sdoc_delete_other_revision'),
re_path(r'^file/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocFileView.as_view(), name='seadoc_file_view'), re_path(r'^file/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocFileView.as_view(), name='seadoc_file_view'),
re_path(r'^file-uuid/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocFileUUIDView.as_view(), name='seadoc_file_uuid_view'), re_path(r'^file-uuid/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocFileUUIDView.as_view(), name='seadoc_file_uuid_view'),

View File

@@ -0,0 +1,36 @@
{% extends 'file_view_react.html' %}
{% load render_bundle from webpack_loader %}
{% load seahub_tags %}
{% block extra_style %}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}sdoc-editor/sdoc-editor-font.css" />
{% render_bundle 'sdocPublishedRevision' 'css' %}
{% endblock %}
{% block extra_data %}
docPath: '{{ path|escapejs }}',
docName: '{{ filename|escapejs }}',
docUuid: '{{ file_uuid }}',
assetsUrl: '{{ assets_url }}',
seadocAccessToken: '{{ seadoc_access_token }}',
seadocServerUrl: '{{ seadoc_server_url }}',
isSdocRevision: {% if is_sdoc_revision %}true{% else %}false{% endif %},
revisionId: '{{ revision_id }}',
originDocUuid: '{{ origin_doc_uuid }}',
originParentPath: '{{ origin_parent_path }}',
originFilename: '{{ origin_filename }}',
originFilePath: '{{ origin_file_path }}',
originFileVersion: '{{ origin_file_version }}',
publishFileVersion: '{{ publish_file_version }}',
publisher: '{{ publisher }}',
publisherNickname: '{{ publisher_nickname }}',
isPublished: {% if is_published %}true{% else %}false{% endif %},
revisionCreatedAt: '{{ revision_created_at }}',
revisionUpdatedAt: '{{ revision_updated_at }}',
isSdocDraft: {% if is_sdoc_draft %}true{% else %}false{% endif %}
{% endblock %}
{% block render_bundle %}
<script src="{{ MEDIA_URL }}js/init-scroll-bar.js" ></script>
{% render_bundle 'sdocPublishedRevision' 'js' %}
{% endblock %}

View File

@@ -2121,6 +2121,7 @@ def get_file_content_from_cache(file_id, repo_id, file_name):
@login_required @login_required
@repo_passwd_set_required @repo_passwd_set_required
def view_sdoc_revision(request, repo_id, revision_id): def view_sdoc_revision(request, repo_id, revision_id):
username = request.user.username
# resource check # resource check
repo = seafile_api.get_repo(repo_id) repo = seafile_api.get_repo(repo_id)
@@ -2130,21 +2131,34 @@ def view_sdoc_revision(request, repo_id, revision_id):
revision = SeadocRevision.objects.get_by_revision_id(repo_id, revision_id) revision = SeadocRevision.objects.get_by_revision_id(repo_id, revision_id)
if not revision: if not revision:
return render_error(request, 'revision not found') return render_error(request, 'revision not found')
uuid_map = FileUUIDMap.objects.filter(
uuid=revision.doc_uuid).first() is_published = revision.is_published
if not uuid_map: if is_published:
return render_error(request, _('File does not exist')) origin_file_uuid = revision.origin_doc_uuid
origin_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(origin_file_uuid)
if not origin_uuid_map:
return render_error(request, _('Origin file does not exist'))
parent_dir = origin_uuid_map.parent_path
filename = origin_uuid_map.filename
path = posixpath.join(parent_dir, filename)
file_id = seafile_api.get_file_id_by_path(repo_id, path)
if not file_id:
return render_error(request, _('File does not exist'))
path = posixpath.join(uuid_map.parent_path, uuid_map.filename) else:
file_id = seafile_api.get_file_id_by_path(repo_id, path) uuid_map = FileUUIDMap.objects.filter(uuid=revision.doc_uuid).first()
if not file_id: if not uuid_map:
return render_error(request, _('File does not exist')) return render_error(request, _('File does not exist'))
parent_dir = uuid_map.parent_path
filename = uuid_map.filename
path = posixpath.join(parent_dir, filename)
file_id = seafile_api.get_file_id_by_path(repo_id, path)
if not file_id:
return render_error(request, _('File does not exist'))
# permission check # permission check
username = request.user.username
parent_dir = uuid_map.parent_path
filename = uuid_map.filename
permission = check_folder_permission(request, repo_id, parent_dir) permission = check_folder_permission(request, repo_id, parent_dir)
if not permission: if not permission:
return convert_repo_path_when_can_not_view_file(request, repo_id, path) return convert_repo_path_when_can_not_view_file(request, repo_id, path)
@@ -2174,69 +2188,39 @@ def view_sdoc_revision(request, repo_id, revision_id):
'seafile_collab_server': SEAFILE_COLLAB_SERVER, 'seafile_collab_server': SEAFILE_COLLAB_SERVER,
} }
is_locked = False
locked_by_me = False
# check whether file is starred # check whether file is starred
is_starred = is_file_starred(username, repo_id, path, org_id) if not is_published:
return_dict['is_starred'] = is_starred is_starred = is_file_starred(username, repo_id, path, org_id)
return_dict['is_starred'] = is_starred
# check file lock info # check file lock info
try: try:
is_locked, locked_by_me = check_file_lock(repo_id, path, username) is_locked, locked_by_me = check_file_lock(repo_id, path, username)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
is_locked = False is_locked = False
locked_by_me = False locked_by_me = False
if is_pro_version() and permission == 'rw': if is_pro_version() and permission == 'rw':
can_lock_unlock_file = True can_lock_unlock_file = True
else:
can_lock_unlock_file = False
return_dict['file_locked'] = is_locked
return_dict['locked_by_me'] = locked_by_me
return_dict['can_lock_unlock_file'] = can_lock_unlock_file
# file shared link
l = FileShare.objects.filter(repo_id=repo_id).filter(
username=username).filter(path=path)
fileshare = l[0] if len(l) > 0 else None
file_shared_link = gen_file_share_link(fileshare.token) if fileshare else ''
return_dict['fileshare'] = fileshare,
return_dict['file_shared_link'] = file_shared_link
if parse_repo_perm(permission).can_download and \
request.user.permissions.can_generate_share_link():
return_dict['can_share_file'] = True
else:
return_dict['can_share_file'] = False
# fetch file contributors and latest contributor
try:
# get real path for sub repo
real_path = repo.origin_path + path if repo.origin_path else path
dirent = seafile_api.get_dirent_by_path(repo.store_id, real_path)
if dirent:
latest_contributor, last_modified = dirent.modifier, dirent.mtime
else: else:
latest_contributor, last_modified = None, 0 can_lock_unlock_file = False
except Exception as e:
logger.error(e)
latest_contributor, last_modified = None, 0
return_dict['latest_contributor'] = latest_contributor return_dict['file_locked'] = is_locked
return_dict['last_modified'] = last_modified return_dict['locked_by_me'] = locked_by_me
return_dict['can_lock_unlock_file'] = can_lock_unlock_file
# get file type and extention return_dict['can_share_file'] = False
filetype, fileext = get_file_type_and_ext(filename) return_dict['filetype'] = 'SDoc'
return_dict['fileext'] = fileext
return_dict['filetype'] = filetype
file_uuid = str(uuid_map.uuid) file_uuid = revision.doc_uuid
return_dict['file_uuid'] = file_uuid return_dict['file_uuid'] = file_uuid
return_dict['assets_url'] = '/api/v2.1/seadoc/download-image/' + file_uuid
return_dict['seadoc_server_url'] = SEADOC_SERVER_URL return_dict['seadoc_server_url'] = SEADOC_SERVER_URL
return_dict['assets_url'] = '/api/v2.1/seadoc/download-image/' + origin_file_uuid if is_published else file_uuid
can_edit_file = True can_edit_file = not is_published
if parse_repo_perm(permission).can_edit_on_web is False: if parse_repo_perm(permission).can_edit_on_web is False:
can_edit_file = False can_edit_file = False
elif is_locked and not locked_by_me: elif is_locked and not locked_by_me:
@@ -2251,4 +2235,8 @@ def view_sdoc_revision(request, repo_id, revision_id):
return_dict.update(revision_info) return_dict.update(revision_info)
send_file_access_msg(request, repo, path, 'web') send_file_access_msg(request, repo, path, 'web')
if is_published:
return render(request, 'sdoc_published_revision_file_view.html', return_dict)
return render(request, 'sdoc_file_view_react.html', return_dict) return render(request, 'sdoc_file_view_react.html', return_dict)