1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-02 15:38:15 +00:00

export/convert sdoc to docx (#5837)

* sdoc to docx

* update
This commit is contained in:
lian
2023-12-26 17:50:06 +08:00
committed by GitHub
parent b943b071ee
commit ab72e093d5
11 changed files with 225 additions and 35 deletions

View File

@@ -123,6 +123,14 @@ class DirentGridView extends React.Component {
this.props.onItemConvert(currentObject, dstType);
};
exportDocx = () => {
const serviceUrl = window.app.config.serviceURL;
let repoID = this.props.repoID;
let filePath = this.getDirentPath(this.props.dirent);
let exportToDocxUrl = serviceUrl + '/repo/sdoc_export_to_docx/' + repoID + '/?file_path=' + filePath;
window.location.href = exportToDocxUrl;
};
onMenuItemClick = (operation, currentObject, event) => {
hideMenu();
switch(operation) {
@@ -150,6 +158,12 @@ class DirentGridView extends React.Component {
case 'Convert to Markdown':
this.onItemConvert(currentObject, event, 'markdown');
break;
case 'Convert to docx':
this.onItemConvert(currentObject, event, 'docx');
break;
case 'Export docx':
this.exportDocx();
break;
case 'Convert to sdoc':
this.onItemConvert(currentObject, event, 'sdoc');
break;

View File

@@ -230,6 +230,14 @@ class DirentListItem extends React.Component {
this.setState({isShareDialogShow: !this.state.isShareDialogShow});
};
exportDocx = () => {
const serviceUrl = window.app.config.serviceURL;
let repoID = this.props.repoID;
let filePath = this.getDirentPath(this.props.dirent);
let exportToDocxUrl = serviceUrl + '/repo/sdoc_export_to_docx/' + repoID + '/?file_path=' + filePath;
window.location.href = exportToDocxUrl;
};
closeSharedDialog = () => {
this.setState({isShareDialogShow: !this.state.isShareDialogShow});
};
@@ -277,6 +285,12 @@ class DirentListItem extends React.Component {
case 'Convert to Markdown':
this.onItemConvert(event, 'markdown');
break;
case 'Convert to docx':
this.onItemConvert(event, 'docx');
break;
case 'Export docx':
this.exportDocx();
break;
case 'Convert to sdoc':
this.onItemConvert(event, 'sdoc');
break;

View File

@@ -25,6 +25,8 @@ const TextTranslation = {
'UNLOCK' : {key : 'Unlock', value : gettext('Unlock')},
'CONVERT_TO_MARKDOWN' : {key : 'Convert to Markdown', value : gettext('Convert to Markdown')},
'CONVERT_TO_SDOC' : {key : 'Convert to sdoc', value : gettext('Convert to sdoc')},
'CONVERT_TO_DOCX' : {key : 'Convert to docx', value : gettext('Convert to docx')},
'EXPORT_DOCX' : {key : 'Export docx', value : gettext('Export docx')},
'MARK_AS_DRAFT' : {key : 'Mark as draft', value : gettext('Mark as draft')},
'UNMARK_AS_DRAFT' : {key : 'Unmark as draft', value : gettext('Unmark as draft')},
'HISTORY' : {key : 'History', value : gettext('History')},

View File

@@ -530,7 +530,7 @@ export const Utils = {
getFileOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
let list = [];
const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK, FREEZE_DOCUMENT,
HISTORY, ACCESS_LOG, PROPERTIES, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT, CONVERT_TO_MARKDOWN, CONVERT_TO_SDOC } = TextTranslation;
HISTORY, ACCESS_LOG, PROPERTIES, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT, CONVERT_TO_MARKDOWN, CONVERT_TO_DOCX, EXPORT_DOCX, CONVERT_TO_SDOC } = TextTranslation;
const permission = dirent.permission;
const { isCustomPermission, customPermission } = Utils.getUserPermission(permission);
@@ -613,6 +613,8 @@ export const Utils = {
if (dirent.name.endsWith('.sdoc')) {
list.push(CONVERT_TO_MARKDOWN);
list.push(CONVERT_TO_DOCX);
list.push(EXPORT_DOCX);
}
}

View File

@@ -33,7 +33,7 @@ from seahub.tags.models import FileUUIDMap
from seahub.seadoc.models import SeadocHistoryName, SeadocDraft, SeadocCommentReply
from seahub.base.models import FileComment
from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, OFFICE_TEMPLATE_ROOT
from seahub.api2.endpoints.utils import convert_file
from seahub.api2.endpoints.utils import convert_file, sdoc_convert_to_docx
from seahub.seadoc.utils import get_seadoc_file_uuid
from seahub.drafts.models import Draft
@@ -572,13 +572,16 @@ class FileView(APIView):
if extension == '.md':
src_type = 'markdown'
filename = filename[:-2] + 'sdoc'
new_filename = filename[:-2] + 'sdoc'
elif extension == '.sdoc':
src_type = 'sdoc'
filename = filename[:-4] + 'md'
if dst_type == 'markdown':
new_filename = filename[:-4] + 'md'
if dst_type == 'docx':
new_filename = filename[:-4] + 'docx'
new_file_name = check_filename_or_rename(repo_id, parent_dir, filename)
new_file_path = posixpath.join(parent_dir, new_file_name)
new_filename = check_filename_or_rename(repo_id, parent_dir, new_filename)
new_file_path = posixpath.join(parent_dir, new_filename)
download_token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', username)
@@ -587,16 +590,38 @@ class FileView(APIView):
use_onetime=True)
doc_uuid = get_seadoc_file_uuid(repo, path)
if dst_type != 'docx':
try:
resp = convert_file(path, username, doc_uuid, download_token, upload_token, src_type, dst_type)
if resp.status_code == 500:
logger.error('convert file error status: %s body: %s', resp.status_code, resp.text)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
resp = convert_file(path, username, doc_uuid,
download_token, upload_token,
src_type, dst_type)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
if resp.status_code == 500:
logger.error('convert file error status: %s body: %s',
resp.status_code, resp.text)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
'Internal Server Error')
else:
try:
resp = sdoc_convert_to_docx(path, username, doc_uuid,
download_token, upload_token,
src_type, dst_type)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
if resp.status_code != 200:
logger.error('convert file error status: %s body: %s',
resp.status_code, resp.text)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
'Internal Server Error')
file_info = self.get_file_info(username, repo_id, new_file_path)
return Response(file_info)

View File

@@ -1,21 +1,23 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import re
import datetime
import jwt
import time
import urllib.request, urllib.parse, urllib.error
import logging
import requests
import jwt
from urllib.parse import urljoin
import datetime
import urllib.request
import urllib.parse
import urllib.error
from urllib.parse import urljoin
from rest_framework import status
from seaserv import ccnet_api, seafile_api
from pysearpc import SearpcError
from seahub.api2.utils import api_error
from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email
from seahub.utils import get_log_events_by_time, is_pro_version, is_org_context
from seahub.settings import SEADOC_PRIVATE_KEY, FILE_CONVERTER_SERVER_URL
try:
@@ -25,6 +27,7 @@ except ImportError:
logger = logging.getLogger(__name__)
def api_check_group(func):
"""
Decorator for check if group valid
@@ -33,7 +36,7 @@ def api_check_group(func):
group_id = int(group_id) # Checked by URL Conf
try:
group = ccnet_api.get_group(int(group_id))
except SearpcError as e:
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
@@ -46,6 +49,7 @@ def api_check_group(func):
return _decorated
def add_org_context(func):
def _decorated(view, request, *args, **kwargs):
if is_org_context(request):
@@ -56,6 +60,7 @@ def add_org_context(func):
return _decorated
def is_org_user(username, org_id=None):
""" Check if an user is an org user.
@@ -78,6 +83,7 @@ def is_org_user(username, org_id=None):
logger.error(e)
return False
def get_user_contact_email_dict(email_list):
email_list = set(email_list)
user_contact_email_dict = {}
@@ -87,6 +93,7 @@ def get_user_contact_email_dict(email_list):
return user_contact_email_dict
def get_user_name_dict(email_list):
email_list = set(email_list)
user_name_dict = {}
@@ -96,6 +103,7 @@ def get_user_name_dict(email_list):
return user_name_dict
def get_repo_dict(repo_id_list):
repo_id_list = set(repo_id_list)
repo_dict = {}
@@ -116,7 +124,6 @@ def get_group_dict(group_id_list):
if group_id not in group_dict:
group_dict[group_id] = ''
group = ccnet_api.get_group(int(group_id))
print(group)
if group:
group_dict[group_id] = group
@@ -134,6 +141,7 @@ def check_time_period_valid(start, end):
return True
def get_log_events_by_type_and_time(log_type, start, end):
start_struct_time = datetime.datetime.strptime(start, "%Y-%m-%d")
start_timestamp = time.mktime(start_struct_time.timetuple())
@@ -146,6 +154,7 @@ def get_log_events_by_type_and_time(log_type, start, end):
events = events if events else []
return events
def generate_links_header_for_paginator(base_url, page, per_page, total_count, option_dict={}):
def is_first_page(page):
@@ -236,3 +245,40 @@ def convert_file(path, username, doc_uuid, download_token, upload_token, src_typ
resp = requests.post(url, json=params, headers=headers, timeout=30)
return resp
def sdoc_convert_to_docx(path, username, doc_uuid, download_token,
upload_token, src_type, dst_type):
headers = convert_file_gen_headers()
params = {
'path': path,
'username': username,
'doc_uuid': doc_uuid,
'download_token': download_token,
'upload_token': upload_token,
'src_type': src_type,
'dst_type': dst_type,
}
url = urljoin(FILE_CONVERTER_SERVER_URL, '/api/v1/sdoc-convert-to-docx/')
resp = requests.post(url, json=params, headers=headers, timeout=30)
return resp
def sdoc_export_to_docx(path, username, doc_uuid, download_token,
src_type, dst_type):
headers = convert_file_gen_headers()
params = {
'path': path,
'username': username,
'doc_uuid': doc_uuid,
'download_token': download_token,
'src_type': src_type,
'dst_type': dst_type,
}
url = urljoin(FILE_CONVERTER_SERVER_URL, '/api/v1/sdoc-export-to-docx/')
resp = requests.post(url, json=params, headers=headers, timeout=30)
return resp

View File

@@ -36,14 +36,14 @@ from seahub.seadoc.settings import SDOC_REVISIONS_DIR, SDOC_IMAGES_DIR
from seahub.utils.file_types import SEADOC, IMAGE
from seahub.utils.file_op import if_locked_by_online_office
from seahub.utils import get_file_type_and_ext, normalize_file_path, \
normalize_dir_path, PREVIEW_FILEEXT, get_file_history, \
gen_inner_file_get_url, gen_inner_file_upload_url, \
get_service_url, is_valid_username, is_pro_version, get_file_history_by_day, get_file_daily_history_detail
normalize_dir_path, PREVIEW_FILEEXT, \
gen_inner_file_get_url, gen_inner_file_upload_url, gen_file_get_url, \
get_service_url, is_valid_username, is_pro_version, \
get_file_history_by_day, get_file_daily_history_detail
from seahub.tags.models import FileUUIDMap
from seahub.utils.error_msg import file_type_error_msg
from seahub.utils.repo import parse_repo_perm
from seahub.seadoc.models import SeadocHistoryName, SeadocDraft, SeadocRevision, SeadocCommentReply, SeadocNotification
from seahub.utils.file_revisions import get_file_revisions_within_limit
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
from seahub.base.templatetags.seahub_tags import email2nickname, \
email2contact_email
@@ -243,6 +243,44 @@ class SeadocDownloadLink(APIView):
return Response({'download_link': download_link})
class SeadocImageDownloadLink(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
# jwt permission check
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# argument check
image_name = request.GET.get('image_name')
if not image_name:
error_msg = 'image_name invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# recource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
image_path = f'{SDOC_IMAGES_DIR}{file_uuid}/{image_name}'
file_id = seafile_api.get_file_id_by_path(repo_id, image_path)
if not file_id:
error_msg = f'image {image_path} not found.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', "")
download_link = gen_file_get_url(token, image_name)
return Response({'download_link': download_link})
class SeadocOriginFileContent(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)

View File

@@ -1,5 +1,5 @@
from django.urls import re_path
from .apis import SeadocAccessToken, SeadocUploadLink, SeadocDownloadLink, SeadocOriginFileContent, SeadocUploadFile, \
from .apis import SeadocAccessToken, SeadocUploadLink, SeadocDownloadLink, SeadocImageDownloadLink, SeadocOriginFileContent, SeadocUploadFile, \
SeadocUploadImage, SeadocDownloadImage, SeadocAsyncCopyImages, SeadocQueryCopyMoveProgressView, SeadocCopyHistoryFile, SeadocHistory, SeadocDrafts, SeadocMaskAsDraft, \
SeadocCommentsView, SeadocCommentView, SeadocStartRevise, SeadocPublishRevision, SeadocRevisionsCount, SeadocRevisions, \
SeadocCommentRepliesView, SeadocCommentReplyView, SeadocFileView, SeadocFileUUIDView, SeadocDirView, SdocRevisionBaseVersionContent, SeadocRevisionView, \
@@ -13,6 +13,7 @@ urlpatterns = [
re_path(r'^upload-file/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocUploadFile.as_view(), name='seadoc_upload_file'),
re_path(r'^upload-link/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocUploadLink.as_view(), name='seadoc_upload_link'),
re_path(r'^download-link/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocDownloadLink.as_view(), name='seadoc_download_link'),
re_path(r'^image-download-link/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocImageDownloadLink.as_view(), name='seadoc_image_download_link'),
re_path(r'^upload-image/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocUploadImage.as_view(), name='seadoc_upload_image'),
re_path(r'^download-image/(?P<file_uuid>[-0-9a-f]{36})/(?P<filename>.*)$', SeadocDownloadImage.as_view(), name='seadoc_download_image'),
re_path(r'^async-copy-images/(?P<file_uuid>[-0-9a-f]{36})/$', SeadocAsyncCopyImages.as_view(), name='seadoc_async_copy_images'),

View File

@@ -1,16 +1,19 @@
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 urllib.parse import quote
from django.shortcuts import render
from django.http import HttpResponse
from django.utils.translation import gettext as _
from seaserv import get_repo, seafile_api
from seahub.auth.decorators import login_required
from seahub.utils import render_error
from seahub.utils import render_error, normalize_file_path
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 seahub.api2.endpoints.utils import sdoc_export_to_docx
from .utils import is_seadoc_revision, get_seadoc_download_link, gen_path_link
@@ -137,3 +140,48 @@ def sdoc_revisions(request, repo_id):
'per_page': per_page,
'page_next': page_next,
})
@login_required
def sdoc_to_docx(request, repo_id):
# argument check
file_path = request.GET.get('file_path')
file_path = normalize_file_path(file_path)
if not file_path:
error_msg = _("File path invalid.")
return render_error(request, error_msg)
# resource check
repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = _("Library does not exist")
return render_error(request, error_msg)
file_id = seafile_api.get_file_id_by_path(repo_id, file_path)
if not file_id:
error_msg = 'File %s not found.' % file_path
return render_error(request, error_msg)
# permission check
if not check_folder_permission(request, repo_id, '/'):
error_msg = _("Permission denied.")
return render_error(request, error_msg)
username = request.user.username
filename = os.path.basename(file_path)
doc_uuid = get_seadoc_file_uuid(repo, file_path)
download_token = seafile_api.get_fileserver_access_token(repo_id, file_id,
'download', username)
src_type = 'sdoc'
dst_type = 'docx'
resp_with_docx_file = sdoc_export_to_docx(file_path, username, doc_uuid,
download_token, src_type, dst_type)
docx_mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
response = HttpResponse(content_type=docx_mime_type)
new_file_name = quote(f'{filename[:-5]}.docx')
response['Content-Disposition'] = f'attachment; filename={new_file_name}'
response.write(resp_with_docx_file.content)
return response

View File

@@ -198,8 +198,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, sdoc_revisions
from seahub.seadoc.views import sdoc_revision, sdoc_revisions, sdoc_to_docx
from seahub.ocm.settings import OCM_ENDPOINT
from seahub.ai.apis import LibrarySdocIndexes, Search, LibrarySdocIndex, TaskStatus, \
@@ -231,6 +230,7 @@ urlpatterns = [
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/sdoc_export_to_docx/(?P<repo_id>[-0-9a-f]{36})/$', sdoc_to_docx, name='sdoc_export_to_docx'),
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'),

View File

@@ -13,4 +13,4 @@ SEADOC = 'SDoc'
MARKDOWN_SUPPORT_CONVERT_TYPES = ['sdoc']
SDOC_SUPPORT_CONVERT_TYPES = ['markdown']
SDOC_SUPPORT_CONVERT_TYPES = ['markdown', 'docx']