diff --git a/frontend/src/components/dialog/create-file-dialog.js b/frontend/src/components/dialog/create-file-dialog.js index 7d83d467df..225539dc1b 100644 --- a/frontend/src/components/dialog/create-file-dialog.js +++ b/frontend/src/components/dialog/create-file-dialog.js @@ -16,6 +16,7 @@ class CreateFile extends React.Component { this.state = { parentPath: '', childName: props.fileType, + isDraft: false, }; this.newInput = React.createRef(); } @@ -28,7 +29,8 @@ class CreateFile extends React.Component { handleSubmit = () => { let path = this.state.parentPath + this.state.childName; - this.props.onAddFile(path); + let isDraft = this.state.isDraft; + this.props.onAddFile(path, isDraft); } handleKeyPress = (e) => { @@ -37,6 +39,55 @@ class CreateFile extends React.Component { } } + handleCheck = () => { + let pos = this.state.childName.lastIndexOf("."); + + if (this.state.isDraft) { + // from draft to not draft + // case 1, normally, the file name is ended with `(draft)`, like `test(draft).md` + // case 2, the file name is not ended with `(draft)`, the user has deleted some characters, like `test(dra.md` + let p = this.state.childName.substring(pos-7, pos); + let fileName = this.state.childName.substring(0, pos-7); + let fileType = this.state.childName.substring(pos); + if (p === '(draft)') { + // remove `(draft)` from file name + this.setState({ + childName: fileName + fileType, + isDraft: !this.state.isDraft + }) + } else { + // don't change file name + this.setState({ + isDraft: !this.state.isDraft + }) + } + } + + if (!this.state.isDraft) { + // from not draft to draft + // case 1, test.md ===> test(draft).md + // case 2, .md ===> (draft).md + // case 3, no '.' in the file name, don't change the file name + if (pos > 0) { + let fileName = this.state.childName.substring(0, pos); + let fileType = this.state.childName.substring(pos); + this.setState({ + childName: fileName + '(draft)' + fileType, + isDraft: !this.state.isDraft + }) + } else if (pos === 0 ) { + this.setState({ + childName: '(draft)' + this.state.childname, + isDraft: !this.state.isdraft + }) + } else { + this.setState({ + isDraft: !this.state.isdraft + }) + } + } + } + toggle = () => { this.props.addFileCancel(); } @@ -67,6 +118,13 @@ class CreateFile extends React.Component { {this.newInput = input;}} id="fileName" placeholder={gettext('newName')} value={this.state.childName} onChange={this.handleChange}/> + + + diff --git a/frontend/src/pages/drafts/draft-content.js b/frontend/src/pages/drafts/draft-content.js index 34c812d3b2..9771d4ce7b 100644 --- a/frontend/src/pages/drafts/draft-content.js +++ b/frontend/src/pages/drafts/draft-content.js @@ -64,7 +64,8 @@ class DraftContent extends React.Component { let draft = this.state.currentDraft; editUtilties.createDraftReview(draft.id).then(res => { - window.open(siteRoot + 'drafts/review/' + res.data.id); + const w = window.open() + w.location = siteRoot + 'drafts/review/' + res.data.id; }).catch((error) => { if (error.response.status == '409') { Toast.error('The draft review is existing.'); diff --git a/frontend/src/pages/repo-wiki-mode/main-panel.js b/frontend/src/pages/repo-wiki-mode/main-panel.js index 44212d3e6d..48b2dd7208 100644 --- a/frontend/src/pages/repo-wiki-mode/main-panel.js +++ b/frontend/src/pages/repo-wiki-mode/main-panel.js @@ -158,9 +158,9 @@ class MainPanel extends Component { this.setState({showFileDialog: !this.state.showFileDialog}); } - onMainAddFile = (filePath) => { + onMainAddFile = (filePath, isDraft) => { this.setState({showFileDialog: !this.state.showFileDialog}); - this.props.onMainAddFile(filePath); + this.props.onMainAddFile(filePath, isDraft); } onMainAddFolder = (dirPath) => { diff --git a/frontend/src/pages/repo-wiki-mode/side-panel.js b/frontend/src/pages/repo-wiki-mode/side-panel.js index 2846460967..b10824b888 100644 --- a/frontend/src/pages/repo-wiki-mode/side-panel.js +++ b/frontend/src/pages/repo-wiki-mode/side-panel.js @@ -127,9 +127,9 @@ class SidePanel extends Component { this.props.onAddFolderNode(dirPath); } - onAddFileNode = (filePath) => { + onAddFileNode = (filePath, isDraft) => { this.setState({showFile: !this.state.showFile}); - this.props.onAddFileNode(filePath); + this.props.onAddFileNode(filePath, isDraft); } onRenameNode = (newName) => { diff --git a/frontend/src/repo-wiki-mode.js b/frontend/src/repo-wiki-mode.js index 17ab222683..a6c7a20ec1 100644 --- a/frontend/src/repo-wiki-mode.js +++ b/frontend/src/repo-wiki-mode.js @@ -252,8 +252,8 @@ class Wiki extends Component { }); } - onAddFileNode = (filePath) => { - editorUtilities.createFile(filePath).then(res => { + onAddFileNode = (filePath, isDraft) => { + editorUtilities.createFile(filePath, isDraft).then(res => { let tree = this.state.tree_data.clone(); let name = this.getFileNameByPath(filePath); let index = filePath.lastIndexOf('/'); diff --git a/frontend/src/utils/editor-utilties.js b/frontend/src/utils/editor-utilties.js index ec86d17f3b..9ef815895d 100644 --- a/frontend/src/utils/editor-utilties.js +++ b/frontend/src/utils/editor-utilties.js @@ -38,8 +38,8 @@ class EditorUtilities { }); } - createFile(filePath) { - return seafileAPI.createFile(repoID, filePath); + createFile(filePath, isDraft) { + return seafileAPI.createFile(repoID, filePath, isDraft); } deleteFile(filePath) { diff --git a/seahub/api2/endpoints/draft_reviews.py b/seahub/api2/endpoints/draft_reviews.py index 9b1b35adbe..11c85376d9 100644 --- a/seahub/api2/endpoints/draft_reviews.py +++ b/seahub/api2/endpoints/draft_reviews.py @@ -1,4 +1,6 @@ # Copyright (c) 2012-2016 Seafile Ltd. +import os + from rest_framework import status from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAuthenticated @@ -89,7 +91,33 @@ class DraftReviewView(APIView): return api_error(status.HTTP_409_CONFLICT, 'There is a conflict between the draft and the original file') - file_id = seafile_api.get_file_id_by_path(r.origin_repo_id, r.origin_file_path) + origin_file_path = r.origin_file_path + + # if it is a new draft + # case1. '/path/test(draft).md' ---> '/path/test.md' + # case2. '/path/test(dra.md' ---> '/path/test(dra.md' + if d.draft_file_path == r.origin_file_path: + new_draft_dir = os.path.dirname(origin_file_path) + new_draft_name = os.path.basename(origin_file_path) + + draft_flag = os.path.splitext(new_draft_name)[0][-7:] + + # remove `(draft)` from file name + if draft_flag == '(draft)': + f = os.path.splitext(new_draft_name)[0][:-7] + file_type = os.path.splitext(new_draft_name)[-1] + new_draft_name = f + file_type + + if new_draft_dir == '/': + origin_file_path = new_draft_dir + new_draft_name + else: + origin_file_path = new_draft_dir + '/' + new_draft_name + + r.draft_file_path = origin_file_path + r.origin_file_path = origin_file_path + + # get draft published version + file_id = seafile_api.get_file_id_by_path(r.origin_repo_id, origin_file_path) r.publish_file_version = file_id r.save() d.delete() diff --git a/seahub/api2/endpoints/drafts.py b/seahub/api2/endpoints/drafts.py index 38139baa0a..8ffcaaae48 100644 --- a/seahub/api2/endpoints/drafts.py +++ b/seahub/api2/endpoints/drafts.py @@ -56,17 +56,17 @@ class DraftsView(APIView): error_msg = 'Library %s not found.' % repo_id return api_error(status.HTTP_404_NOT_FOUND, error_msg) - file_id = seafile_api.get_file_id_by_path(repo.id, file_path) - if not file_id: - return api_error(status.HTTP_404_NOT_FOUND, - "File %s not found" % file_path) - # perm check perm = check_folder_permission(request, repo.id, file_path) if perm != PERMISSION_READ_WRITE: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) + file_id = seafile_api.get_file_id_by_path(repo.id, file_path) + if not file_id: + return api_error(status.HTTP_404_NOT_FOUND, + "File %s not found" % file_path) + username = request.user.username try: diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 13fe0d8959..393360f8da 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -58,6 +58,7 @@ from seahub.thumbnail.utils import generate_thumbnail from seahub.notifications.models import UserNotification from seahub.options.models import UserOptions from seahub.profile.models import Profile, DetailedProfile +from seahub.drafts.models import Draft from seahub.signals import (repo_created, repo_deleted) from seahub.share.models import FileShare, OrgFileShare, UploadLinkShare from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \ @@ -2632,6 +2633,7 @@ class FileView(APIView): username = request.user.username parent_dir = os.path.dirname(path) operation = request.POST.get('operation', '') + is_draft = request.POST.get('is_draft', '') file_info = {} if operation.lower() == 'rename': @@ -2772,6 +2774,17 @@ class FileView(APIView): return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to create file.') + if is_draft.lower() == 'true': + file_name = os.path.basename(path) + file_dir = os.path.dirname(path) + + draft_type = os.path.splitext(file_name)[0][-7:] + file_type = os.path.splitext(file_name)[-1] + + if draft_type != '(draft)': + f = os.path.splitext(file_name)[0] + path = file_dir + '/' + f + '(draft)' + file_type + new_file_name = os.path.basename(path) if not seafile_api.is_valid_filename('fake_repo_id', new_file_name): @@ -2787,6 +2800,10 @@ class FileView(APIView): return api_error(HTTP_520_OPERATION_FAILED, 'Failed to create file.') + if is_draft.lower() == 'true': + repo = seafile_api.get_repo(repo_id) + Draft.objects.add(username, repo, path, file_exist=False) + if request.GET.get('reloaddir', '').lower() == 'true': return reloaddir(request, repo, parent_dir) else: diff --git a/seahub/drafts/models.py b/seahub/drafts/models.py index c3705efbfe..43e53c0c01 100644 --- a/seahub/drafts/models.py +++ b/seahub/drafts/models.py @@ -10,7 +10,7 @@ from seahub.base.fields import LowerCaseCharField from seahub.base.models import TimestampedModel from seahub.base.templatetags.seahub_tags import email2nickname from seahub.tags.models import FileUUIDMap -from seahub.utils import normalize_file_path +from seahub.utils import normalize_file_path, EMPTY_SHA1 from seahub.utils.timeutils import datetime_to_isoformat_timestr from .utils import create_user_draft_repo, get_draft_file_name @@ -24,24 +24,8 @@ class DraftFileConflict(Exception): class DraftManager(models.Manager): - def get_user_draft_repo_id(self, username): - r = self.filter(username=username).first() - if r is None: - return None - else: - return r.draft_repo_id - - def add(self, username, repo, file_path, file_id=None, org_id=-1): - file_path = normalize_file_path(file_path) - parent_path = os.path.dirname(file_path) - filename = os.path.basename(file_path) - file_uuid = FileUUIDMap.objects.get_or_create_fileuuidmap( - repo.id, parent_path, filename, is_dir=False) - - if file_id is None: - file_id = seafile_api.get_file_id_by_path(repo.id, file_path) - - # create drafts dir if any + def create_exist_file_draft(self, repo, username, file_uuid, file_path): + # create drafts dir if any draft_dir_id = seafile_api.get_dir_id_by_path(repo.id, '/Drafts') if draft_dir_id is None: seafile_api.post_dir(repo.id, '/', 'Drafts', username) @@ -56,10 +40,25 @@ class DraftManager(models.Manager): repo.id, '/Drafts', draft_file_name, username=username, need_progress=0, synchronous=1) + return draft_file_path + + def add(self, username, repo, file_path, file_exist=True, file_id=None, org_id=-1): + file_path = normalize_file_path(file_path) + parent_path = os.path.dirname(file_path) + filename = os.path.basename(file_path) + file_uuid = FileUUIDMap.objects.get_or_create_fileuuidmap( + repo.id, parent_path, filename, is_dir=False) + + if file_id is None: + file_id = seafile_api.get_file_id_by_path(repo.id, file_path) + + if file_exist: + file_path = self.create_exist_file_draft(repo, username, file_uuid, file_path) + draft = self.model(username=username, origin_repo_id=repo.id, origin_file_uuid=file_uuid, origin_file_version=file_id, - draft_file_path=draft_file_path) + draft_file_path=file_path) draft.save(using=self._db) return draft @@ -80,7 +79,8 @@ class Draft(TimestampedModel): def delete(self): draft_file_name = os.path.basename(self.draft_file_path) - seafile_api.del_file(self.origin_repo_id, '/Drafts/', + draft_file_path = os.path.dirname(self.draft_file_path) + seafile_api.del_file(self.origin_repo_id, draft_file_path, draft_file_name, self.username) super(Draft, self).delete() @@ -101,22 +101,30 @@ class Draft(TimestampedModel): if not file_id: raise DraftFileConflict - if file_id != self.origin_file_version: + draft_file_name = os.path.basename(self.draft_file_path) + draft_file_path = os.path.dirname(self.draft_file_path) + + file_name = self.origin_file_uuid.filename + + if file_id != self.origin_file_version and self.draft_file_path != origin_file_path: raise DraftFileConflict - draft_file_name = os.path.basename(self.draft_file_path) + if self.draft_file_path == origin_file_path: + f = os.path.splitext(draft_file_name)[0][:-7] + file_type = os.path.splitext(draft_file_name)[-1] + file_name = f + file_type + # move draft file to origin file seafile_api.move_file( - self.origin_repo_id, '/Drafts', draft_file_name, + self.origin_repo_id, draft_file_path, draft_file_name, self.origin_repo_id, self.origin_file_uuid.parent_path, - self.origin_file_uuid.filename, replace=1, + file_name, replace=1, username=self.username, need_progress=0, synchronous=1 ) - def to_dict(self): uuid = self.origin_file_uuid - file_path = posixpath.join(uuid.parent_path, uuid.filename) # TODO: refactor uuid + file_path = posixpath.join(uuid.parent_path, uuid.filename) repo = seafile_api.get_repo(self.origin_repo_id)