1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-28 03:10:45 +00:00
This commit is contained in:
awu0403 2025-04-27 10:15:00 +08:00 committed by GitHub
commit 5e932206c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 127 additions and 3 deletions

View File

@ -106,6 +106,13 @@ class SidePanel extends PureComponent {
});
};
exportPage = async (fromPageConfig) => {
const { pageId, exportType } = fromPageConfig;
const serviceUrl = window.app.config.serviceURL;
let exportPageUrl = serviceUrl + '/api/v2.1/wiki2/' + wikiId + '/page/' + pageId + '/export/?exportType=' + exportType;
window.location.href = exportPageUrl;
};
addPage = (page, parent_id, successCallback, errorCallback, jumpToNewPage = true) => {
const { config } = this.props;
const navigation = config.navigation;
@ -160,6 +167,7 @@ class SidePanel extends PureComponent {
updateWikiConfig={this.props.updateWikiConfig}
onAddNewPage={this.onAddNewPage}
duplicatePage={this.duplicatePage}
exportPage={this.exportPage}
getCurrentPageId={this.props.getCurrentPageId}
addPageInside={this.addPageInside}
toggleTrashDialog={this.toggleTrashDialog}

View File

@ -17,6 +17,7 @@ export default class PageDropdownMenu extends Component {
toggleInsertSiblingPage: PropTypes.func,
duplicatePage: PropTypes.func,
onDeletePage: PropTypes.func,
exportPage: PropTypes.func,
isOnlyOnePage: PropTypes.bool,
};
@ -61,10 +62,24 @@ export default class PageDropdownMenu extends Component {
this.props.duplicatePage({ from_page_id: page.id }, () => {}, this.duplicatePageFailure);
};
exportPageToSdoc = () => {
const { page } = this.props;
this.props.exportPage({ pageId: page.id, exportType: 'sdoc' }, () => {}, this.exportPageFailure);
};
exportPageToMarkdown = () => {
const { page } = this.props;
this.props.exportPage({ pageId: page.id, exportType: 'markdown' }, () => {}, this.exportPageFailure);
};
duplicatePageFailure = () => {
toaster.danger(gettext('Failed to duplicate page'));
};
exportPageFailure = () => {
toaster.danger(gettext('Failed to export page'));
};
handleCopyLink = () => {
const { page } = this.props;
const wikiLink = getWikPageLink(page.id);
@ -119,6 +134,14 @@ export default class PageDropdownMenu extends Component {
<i className="sf3-font sf3-font-copy1" aria-hidden="true" />
<span className="item-text">{gettext('Duplicate page')}</span>
</DropdownItem>
<DropdownItem onClick={this.exportPageToSdoc}>
<i className="sf3-font sf3-font-copy1" aria-hidden="true" />
<span className="item-text">{gettext('Export as sdoc')}</span>
</DropdownItem>
<DropdownItem onClick={this.exportPageToMarkdown}>
<i className="sf3-font sf3-font-copy1" aria-hidden="true" />
<span className="item-text">{gettext('Export as Markdown')}</span>
</DropdownItem>
{(isOnlyOnePage || pagesLength === 1) ? '' : (
<DropdownItem onClick={this.onDeletePage}>
<i className="sf3-font sf3-font-delete1" aria-hidden="true" />

View File

@ -261,6 +261,7 @@ class PageItem extends Component {
toggleNameEditor={this.toggleNameEditor}
duplicatePage={this.props.duplicatePage}
onDeletePage={this.props.onDeletePage.bind(this, page.id)}
exportPage={this.props.exportPage}
toggleInsertSiblingPage={this.toggleInsertSiblingPage}
/>
}
@ -350,6 +351,7 @@ PageItem.propTypes = {
connectDragPreview: PropTypes.func,
connectDropTarget: PropTypes.func,
duplicatePage: PropTypes.func,
exportPage: PropTypes.func,
setCurrentPage: PropTypes.func,
onUpdatePage: PropTypes.func,
onDeletePage: PropTypes.func,

View File

@ -85,6 +85,7 @@ class WikiNav extends Component {
pages={pages}
pageIndex={index}
duplicatePage={this.props.duplicatePage}
exportPage={this.props.exportPage}
setCurrentPage={this.props.setCurrentPage}
onUpdatePage={this.props.onUpdatePage}
onDeletePage={this.props.onDeletePage}

View File

@ -288,6 +288,22 @@ def sdoc_export_to_docx(path, username, doc_uuid, download_url,
return resp
def sdoc_export_to_md(path, doc_uuid, download_url,
src_type, dst_type):
headers = convert_file_gen_headers()
params = {
'path': path,
'doc_uuid': doc_uuid,
'download_url': download_url,
'src_type': src_type,
'dst_type': dst_type,
}
url = FILE_CONVERTER_SERVER_URL.rstrip('/') + '/api/v1/sdoc-export-to-md/'
resp = requests.post(url, json=params, headers=headers, timeout=30)
return resp
def format_date(start, end):
start_struct_time = datetime.datetime.strptime(start, "%Y-%m-%d")
start_timestamp = time.mktime(start_struct_time.timetuple())

View File

@ -7,8 +7,10 @@ import posixpath
import datetime
import uuid
import re
import requests
from copy import deepcopy
from constance import config
from urllib.parse import quote
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
@ -18,11 +20,13 @@ from rest_framework.views import APIView
from seaserv import seafile_api
from pysearpc import SearpcError
from django.utils.translation import gettext as _
from django.http import HttpResponse
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.endpoints.utils import sdoc_export_to_md
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error, is_wiki_repo
from seahub.utils import HAS_FILE_SEARCH, HAS_FILE_SEASEARCH, get_service_url
from seahub.utils import HAS_FILE_SEARCH, HAS_FILE_SEASEARCH
if HAS_FILE_SEARCH or HAS_FILE_SEASEARCH:
from seahub.search.utils import search_wikis, ai_search_wikis
from seahub.utils.db_api import SeafileDB
@ -35,7 +39,7 @@ from seahub.wiki2.utils import is_valid_wiki_name, get_wiki_config, WIKI_PAGES_D
delete_page, move_nav, revert_nav, get_sub_ids_by_page_id, get_parent_id_stack, add_convert_wiki_task
from seahub.utils import is_org_context, get_user_repos, is_pro_version, is_valid_dirent_name, \
get_no_duplicate_obj_name, HAS_FILE_SEARCH, HAS_FILE_SEASEARCH
get_no_duplicate_obj_name, HAS_FILE_SEARCH, HAS_FILE_SEASEARCH, gen_file_get_url, get_service_url
from seahub.views import check_folder_permission
from seahub.base.templatetags.seahub_tags import email2nickname
@ -56,6 +60,7 @@ from seahub.constants import PERMISSION_READ_WRITE
from seaserv import ccnet_api
from seahub.share.utils import is_repo_admin
HTTP_520_OPERATION_FAILED = 520
@ -1564,3 +1569,71 @@ class WikiConvertView(APIView):
return Response({"task_id": task_id})
class WikiPageExport(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def get(self, request, wiki_id, page_id):
types = ['sdoc', 'markdown']
export_type = request.GET.get('exportType')
if export_type not in types:
return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid export type')
# resource check
wiki = Wiki.objects.get(wiki_id=wiki_id)
if not wiki:
error_msg = "Wiki not found."
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = wiki.repo_id
repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
username = request.user.username
wiki_config = get_wiki_config(repo_id, username)
navigation = wiki_config.get('navigation', [])
pages = wiki_config.get('pages', [])
id_set = get_all_wiki_ids(navigation)
if page_id not in id_set:
error_msg = "Page not found."
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
permission = check_wiki_permission(wiki, username)
if permission != 'rw':
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
page_path = ''
page_name = ''
doc_uuid = ''
for page in pages:
if page_id == page.get('id'):
page_path = page.get('path')
page_name = page.get('name')
doc_uuid = page.get('docUuid')
break
try:
file_id = seafile_api.get_file_id_by_path(repo_id, page_path)
filename = os.path.basename(page_path)
download_token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', username)
download_url = gen_file_get_url(download_token, filename)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
response = HttpResponse(content_type='application/octet-stream')
if export_type == 'markdown':
resp_with_md_file = sdoc_export_to_md(page_path, doc_uuid, download_url, 'sdoc', 'md')
new_filename = f'{page_name}.md'
encoded_filename = quote(new_filename.encode('utf-8'))
response.write(resp_with_md_file.content)
elif export_type == 'sdoc':
sdoc_content = requests.get(download_url).content
new_filename = f'{page_name}.sdoc'
encoded_filename = quote(new_filename)
response.write(sdoc_content)
response['Content-Disposition'] = 'attachment;filename*=utf-8''%s;filename="%s"' % (encoded_filename, encoded_filename)
return response

View File

@ -215,7 +215,7 @@ from seahub.ocm.settings import OCM_ENDPOINT
from seahub.wiki2.views import wiki_view, wiki_publish_view, wiki_history_view
from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView, Wiki2PagesView, Wiki2PageView, \
Wiki2DuplicatePageView, WikiPageTrashView, Wiki2PublishView, Wiki2PublishConfigView, Wiki2PublishPageView, \
WikiSearch, WikiConvertView
WikiSearch, WikiConvertView, WikiPageExport
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
from seahub.api2.endpoints.user_list import UserListView
from seahub.api2.endpoints.seahub_io import SeahubIOStatus
@ -579,6 +579,7 @@ urlpatterns = [
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/publish/config/$', Wiki2PublishConfigView.as_view(), name='api-v2.1-wiki2-publish-config'),
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/pages/$', Wiki2PagesView.as_view(), name='api-v2.1-wiki2-pages'),
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/page/(?P<page_id>[-0-9a-zA-Z]{4})/$', Wiki2PageView.as_view(), name='api-v2.1-wiki2-page'),
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/page/(?P<page_id>[-0-9a-zA-Z]{4})/export/$', WikiPageExport.as_view(), name='api-v2.1-wiki2'),
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/publish/page/(?P<page_id>[-0-9a-zA-Z]{4})/$', Wiki2PublishPageView.as_view(), name='api-v2.1-wiki2-page'),
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/duplicate-page/$', Wiki2DuplicatePageView.as_view(), name='api-v2.1-wiki2-duplicate-page'),
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/trash/', WikiPageTrashView.as_view(), name='api-v2.1-wiki2-trash'),