Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export wiki page to sdoc or markdown #7419

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions frontend/src/pages/wiki2/side-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,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;
Expand Down Expand Up @@ -159,6 +166,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}
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/pages/wiki2/wiki-nav/pages/page-dropdownmenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -120,6 +135,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" />
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/wiki2/wiki-nav/pages/page-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,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}
/>
}
Expand Down Expand Up @@ -352,6 +353,7 @@ PageItem.propTypes = {
connectDragPreview: PropTypes.func,
connectDropTarget: PropTypes.func,
duplicatePage: PropTypes.func,
exportPage: PropTypes.func,
setCurrentPage: PropTypes.func,
onUpdatePage: PropTypes.func,
onDeletePage: PropTypes.func,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/wiki2/wiki-nav/wiki-nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
16 changes: 16 additions & 0 deletions seahub/api2/endpoints/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
77 changes: 75 additions & 2 deletions seahub/api2/endpoints/wiki2.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
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
Expand All @@ -18,11 +20,13 @@
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
Expand All @@ -35,7 +39,7 @@
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
Expand All @@ -56,6 +60,7 @@
from seaserv import ccnet_api
from seahub.share.utils import is_repo_admin


HTTP_520_OPERATION_FAILED = 520


Expand Down Expand Up @@ -1493,3 +1498,71 @@ def post(self, request):

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 = ''
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
if export_type == 'markdown':
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)
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 = HttpResponse(content_type='application/octet-stream')
response['Content-Disposition'] = 'attachment;filename*=utf-8''%s;filename="%s"' % (encoded_filename, encoded_filename)
response.write(resp_with_md_file.content)
elif export_type == 'sdoc':
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)
sdoc_content = requests.get(download_url).content
new_filename = f'{page_name}.sdoc'
encoded_filename = quote(new_filename)

response = HttpResponse(content_type='application/octet-stream')
response['Content-Disposition'] = f'attachment;filename*=utf-8'f'{encoded_filename};filename="{encoded_filename}"'
response.write(sdoc_content)

return response
3 changes: 2 additions & 1 deletion seahub/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@
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
Expand Down Expand Up @@ -550,6 +550,7 @@
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'),
Expand Down
Loading