Skip to content

Commit

Permalink
duplicate page (#6286)
Browse files Browse the repository at this point in the history
* duplicate page

* change duplicatePage frontend

* update

* update

---------

Co-authored-by: Michael An <[email protected]>
  • Loading branch information
JoinTyang and Michael18811380328 authored Jul 3, 2024
1 parent 69069b7 commit 76af3d2
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 56 deletions.
15 changes: 12 additions & 3 deletions frontend/src/pages/wiki2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,15 @@ class Wiki extends Component {
});
};

updateWikiConfig = (wikiConfig) => {
this.setState({
config: new WikiConfig(wikiConfig || {}),
});
};

saveWikiConfig = (wikiConfig, onSuccess, onError) => {
wikiAPI.updateWiki2Config(wikiId, JSON.stringify(wikiConfig)).then(res => {
this.setState({
config: new WikiConfig(wikiConfig || {}),
});
this.updateWikiConfig(wikiConfig);
onSuccess && onSuccess();
}).catch((error) => {
let errorMsg = Utils.getErrorMsg(error);
Expand Down Expand Up @@ -187,6 +191,10 @@ class Wiki extends Component {
}
const { pages } = config;
const currentPage = PageUtils.getPageById(pages, pageId);
if (!currentPage) {
callback && callback();
return;
}
const { path, id, name, docUuid } = currentPage;
if (path !== this.state.path) this.showPage(pageId, path);
this.onCloseSide();
Expand Down Expand Up @@ -225,6 +233,7 @@ class Wiki extends Component {
onCloseSide={this.onCloseSide}
config={this.state.config}
saveWikiConfig={this.saveWikiConfig}
updateWikiConfig={this.updateWikiConfig}
setCurrentPage={this.setCurrentPage}
currentPageId={this.state.currentPageId}
onUpdatePage={this.onUpdatePage}
Expand Down
23 changes: 11 additions & 12 deletions frontend/src/pages/wiki2/side-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const propTypes = {
isLoading: PropTypes.bool.isRequired,
config: PropTypes.object.isRequired,
saveWikiConfig: PropTypes.func.isRequired,
updateWikiConfig: PropTypes.func.isRequired,
setCurrentPage: PropTypes.func.isRequired,
currentPageId: PropTypes.string,
onUpdatePage: PropTypes.func.isRequired,
Expand Down Expand Up @@ -75,18 +76,16 @@ class SidePanel extends Component {
};

duplicatePage = async (fromPageConfig, successCallback, errorCallback) => {
const { config } = this.props;
const { name, from_page_id } = fromPageConfig;
const { navigation, pages } = config;
const fromPage = PageUtils.getPageById(pages, from_page_id);
const newPageId = generateUniqueId(navigation);
let newPageConfig = {
...fromPage,
id: newPageId,
name,
};
const newPage = new Page({ ...newPageConfig });
this.addPage(newPage, this.current_folder_id, successCallback, errorCallback);
const { from_page_id } = fromPageConfig;
wikiAPI.duplicateWiki2Page(wikiId, from_page_id).then(res => {
const newConfig = JSON.parse(res.data.wiki_config);
this.props.updateWikiConfig(newConfig);
successCallback && successCallback();
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
errorCallback && errorCallback();
});
};

addPage = (page, parentId, successCallback, errorCallback, jumpToNewPage = true) => {
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/pages/wiki2/top-nav/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ function getPaths(navigation, currentPageId, pages) {
}
if (node.children) {
node.children.forEach(child => {
child._path = newPath;
runNode(child);
if (child) {
child._path = newPath;
runNode(child);
}
});
}
}
Expand Down
1 change: 0 additions & 1 deletion frontend/src/pages/wiki2/wiki-nav/page-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ export default class PageUtils {
});
}

// move page into folder or page(已解决)
static movePage(navigation, moved_page_id, target_page_id, source_id, target_id, move_position) {
let movedPage = null;
function _cutPageRecursion(item, page_id) {
Expand Down
10 changes: 1 addition & 9 deletions frontend/src/pages/wiki2/wiki-nav/pages/page-dropdownmenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,8 @@ export default class PageDropdownMenu extends Component {

duplicatePage = () => {
const { page, folderId } = this.props;
const { id: from_page_id, name } = page;
let duplicateCount = 1;
let newName = name + '(copy)';
while (this.pageNameMap[newName]) {
newName = `${name}(copy${duplicateCount})`;
duplicateCount++;
}
const onsuccess = () => {};
this.props.onSetFolderId(folderId);
this.props.duplicatePage({ name: newName, from_page_id }, onsuccess, this.duplicatePageFailure);
this.props.duplicatePage({ from_page_id: page.id }, () => {}, this.duplicatePageFailure);
};

duplicatePageFailure = () => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/wiki2/wiki-nav/pages/page-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class PageItem extends Component {
};

renderPage = (page, index, pagesLength, isOnlyOnePage) => {
if (!page) return;
const { isEditMode, pages, folderId, pathStr } = this.props;
const id = page.id;
if (!pages.find(item => item.id === id)) return;
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/utils/wiki-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,13 @@ class WikiAPI {
return this.req.get(url);
}

createWiki2Page(wikiId, pageName) {
createWiki2Page(wikiId, pageName, currentId) {
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/pages/';
let form = new FormData();
form.append('page_name', pageName);
if (currentId) {
form.append('current_id', currentId);
}
return this._sendPostRequest(url, form);
}

Expand All @@ -206,6 +209,13 @@ class WikiAPI {
return this.req.put(url, params);
}

duplicateWiki2Page(wikiId, pageId) {
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/duplicate-page/';
let form = new FormData();
form.append('page_id', pageId);
return this._sendPostRequest(url, form);
}

}

let wikiAPI = new WikiAPI();
Expand Down
162 changes: 136 additions & 26 deletions seahub/api2/endpoints/wiki2.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import time
import uuid
import urllib.request, urllib.error, urllib.parse
from copy import deepcopy

from rest_framework import status
from rest_framework.authentication import SessionAuthentication
Expand All @@ -25,15 +26,16 @@
from seahub.wiki2.models import Wiki2 as Wiki
from seahub.wiki2.utils import is_valid_wiki_name, can_edit_wiki, get_wiki_dirs_by_path, \
get_wiki_config, WIKI_PAGES_DIR, WIKI_CONFIG_PATH, WIKI_CONFIG_FILE_NAME, is_group_wiki, \
check_wiki_admin_permission, check_wiki_permission, get_page_ids_in_folder
check_wiki_admin_permission, check_wiki_permission, get_page_ids_in_folder, get_all_wiki_ids, \
get_and_gen_page_nav_by_id, get_current_level_page_ids, save_wiki_config
from seahub.utils import is_org_context, get_user_repos, gen_inner_file_get_url, gen_file_upload_url, \
normalize_dir_path, is_pro_version, check_filename_with_rename, is_valid_dirent_name, get_no_duplicate_obj_name
from seahub.views import check_folder_permission
from seahub.views.file import send_file_access_msg
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.utils.file_op import check_file_lock, ONLINE_OFFICE_LOCK_OWNER, if_locked_by_online_office
from seahub.utils.repo import parse_repo_perm
from seahub.seadoc.utils import get_seadoc_file_uuid, gen_seadoc_access_token
from seahub.seadoc.utils import get_seadoc_file_uuid, gen_seadoc_access_token, copy_sdoc_images_with_sdoc_uuid
from seahub.settings import SEADOC_SERVER_URL, ENABLE_STORAGE_CLASSES, STORAGE_CLASS_MAPPING_POLICY, \
ENCRYPTED_LIBRARY_VERSION
from seahub.seadoc.sdoc_server_api import SdocServerAPI
Expand Down Expand Up @@ -324,27 +326,11 @@ def put(self, request, wiki_id):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)

repo_id = wiki.repo_id
obj_id = json.dumps({'parent_dir': WIKI_CONFIG_PATH})

dir_id = seafile_api.get_dir_id_by_path(repo_id, WIKI_CONFIG_PATH)
if not dir_id:
seafile_api.mkdir_with_parents(repo_id, '/', WIKI_CONFIG_PATH, username)

token = seafile_api.get_fileserver_access_token(
repo_id, obj_id, 'upload-link', username, use_onetime=False)
if not token:
return None
upload_link = gen_file_upload_url(token, 'upload-api')
upload_link = upload_link + '?replace=1'

files = {
'file': (WIKI_CONFIG_FILE_NAME, wiki_config)
}
data = {'parent_dir': WIKI_CONFIG_PATH, 'relative_path': '', 'replace': 1}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error(resp.text)
# save config
try:
save_wiki_config(wiki, username, wiki_config)
except Exception as e:
logger.exception(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

Expand Down Expand Up @@ -427,14 +413,15 @@ def post(self, request, wiki_id):
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)

folder_id = request.data.get('folder_id', None)
current_id = request.data.get('current_id', None)
wiki_config = get_wiki_config(repo_id, request.user.username)

navigation = wiki_config.get('navigation', [])
if not folder_id:
if not current_id:
page_ids = {element.get('id') for element in navigation if element.get('type') != 'folder'}
else:
page_ids = get_page_ids_in_folder(navigation, folder_id)
page_ids = []
get_current_level_page_ids(navigation, current_id, page_ids)

pages = wiki_config.get('pages', [])
exist_page_names = [page.get('name') for page in pages if page.get('id') in page_ids]
Expand Down Expand Up @@ -613,3 +600,126 @@ def delete(self, request, wiki_id, page_id):
logger.error(e)

return Response({'success': True})


class Wiki2DuplicatePageView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, )
throttle_classes = (UserRateThrottle, )

def post(self, request, wiki_id):
page_id = request.data.get('page_id', None)

if not page_id:
error_msg = 'page_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

try:
wiki = Wiki.objects.get(id=wiki_id)
except Wiki.DoesNotExist:
error_msg = "Wiki not found."
return api_error(status.HTTP_404_NOT_FOUND, error_msg)

username = request.user.username
if not check_wiki_permission(wiki, username):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)

repo_id = wiki.repo_id

# resource check
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)

wiki_config = get_wiki_config(repo_id, username)
navigation = wiki_config.get('navigation', [])
pages = wiki_config.get('pages', [])

page_ids = []
get_current_level_page_ids(navigation, page_id, page_ids)

current_exist_page_names = [page.get('name') for page in pages if page.get('id') in page_ids]
id_set = get_all_wiki_ids(navigation) # get all id for generate different page id

# old page to new page
old_to_new = {}
get_and_gen_page_nav_by_id(id_set, navigation, page_id, old_to_new)

# page_id not exist in wiki
if not old_to_new:
error_msg = 'page %s not found.' % page_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)

src_repo_id = repo_id
dst_repo_id = repo_id
new_pages = deepcopy(pages)
for page in pages:
src_dirents = []
dst_dirents = []
old_page_id = page.get('id')
new_page_id = old_to_new.get(old_page_id)
if not new_page_id:
continue

page_name = page.get('name')
dst_sdoc_uuid = uuid.uuid4()

src_path = page.get('path')
src_file_name = os.path.basename(src_path)

new_file_name = src_file_name
parent_dir = os.path.join(WIKI_PAGES_DIR, str(dst_sdoc_uuid))
path = os.path.join(parent_dir, new_file_name)
seafile_api.mkdir_with_parents(repo_id, '/', parent_dir.strip('/'), request.user.username)

new_page_name = page_name
if old_page_id == page_id:
# only need rename current level page name
new_page_name = get_no_duplicate_obj_name(page_name, current_exist_page_names)

new_page = {
'id': new_page_id,
'name': new_page_name,
'path': path,
'icon': '',
'docUuid': str(dst_sdoc_uuid)
}
new_pages.append(new_page)

src_dirent = src_file_name
dst_dirent = src_dirent
src_dirents.append(src_dirent)
dst_dirents.append(dst_dirent)

src_doc_uuid = page.get('docUuid')
src_parent_dir = os.path.join(WIKI_PAGES_DIR, str(src_doc_uuid))
dst_parent_dir = parent_dir

seafile_api.copy_file(src_repo_id,
src_parent_dir,
json.dumps(src_dirents),
dst_repo_id,
dst_parent_dir,
json.dumps(dst_dirents),
username=username,
need_progress=1,
synchronous=0
)

FileUUIDMap.objects.create_fileuuidmap_by_uuid(dst_sdoc_uuid, dst_repo_id, parent_dir, dst_dirent, is_dir=False)
copy_sdoc_images_with_sdoc_uuid(src_repo_id, src_doc_uuid, dst_repo_id, dst_sdoc_uuid, username, is_async=False)

wiki_config['pages'] = new_pages
wiki_config = json.dumps(wiki_config)

# save config
try:
save_wiki_config(wiki, username, wiki_config)
except Exception as e:
logger.exception(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

return Response({'wiki_config': wiki_config})
28 changes: 28 additions & 0 deletions seahub/seadoc/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,34 @@ def copy_sdoc_images(src_repo_id, src_path, dst_repo_id, dst_path, username, is_
return


def copy_sdoc_images_with_sdoc_uuid(src_repo_id, src_file_uuid, dst_repo_id, dst_file_uuid, username, is_async=True):
src_image_parent_path = SDOC_IMAGES_DIR + src_file_uuid + '/'
src_dir_id = seafile_api.get_dir_id_by_path(src_repo_id, src_image_parent_path)
if not src_dir_id:
return

dst_image_parent_path = gen_seadoc_image_parent_path(
dst_file_uuid, dst_repo_id, username=username)
image_dirents = seafile_api.list_dir_by_path(src_repo_id, src_image_parent_path)
image_names = [item.obj_name for item in image_dirents]

if is_async:
need_progress=1
synchronous=0
else:
need_progress=0
synchronous=1
seafile_api.copy_file(
src_repo_id, src_image_parent_path,
json.dumps(image_names),
dst_repo_id, dst_image_parent_path,
json.dumps(image_names),
username=username,
need_progress=need_progress, synchronous=synchronous,
)
return


def move_sdoc_images(src_repo_id, src_path, dst_repo_id, dst_path, username, is_async=True):
src_repo = seafile_api.get_repo(src_repo_id)
src_file_uuid = get_seadoc_file_uuid(src_repo, src_path)
Expand Down
Loading

0 comments on commit 76af3d2

Please sign in to comment.