mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 15:38:15 +00:00
update draft (#2491)
This commit is contained in:
@@ -9,6 +9,7 @@ import DraftContent from './pages/drafts/draft-content';
|
|||||||
import ReviewContent from './pages/drafts/review-content';
|
import ReviewContent from './pages/drafts/review-content';
|
||||||
import FilesActivities from './pages/dashboard/files-activities';
|
import FilesActivities from './pages/dashboard/files-activities';
|
||||||
import Starred from './pages/starred/starred';
|
import Starred from './pages/starred/starred';
|
||||||
|
import editUtilties from './utils/editor-utilties';
|
||||||
|
|
||||||
import 'seafile-ui';
|
import 'seafile-ui';
|
||||||
import './assets/css/fa-solid.css';
|
import './assets/css/fa-solid.css';
|
||||||
@@ -25,9 +26,24 @@ class App extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
isSidePanelClosed: false,
|
isSidePanelClosed: false,
|
||||||
|
draftCounts: 0,
|
||||||
|
draftList:[]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getDrafts()
|
||||||
|
}
|
||||||
|
|
||||||
|
getDrafts = () => {
|
||||||
|
editUtilties.listDrafts().then(res => {
|
||||||
|
this.setState({
|
||||||
|
draftCounts: res.data.draft_counts,
|
||||||
|
draftList: res.data.data
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onCloseSidePanel = () => {
|
onCloseSidePanel = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isSidePanelClosed: !this.state.isSidePanelClosed
|
isSidePanelClosed: !this.state.isSidePanelClosed
|
||||||
@@ -41,18 +57,19 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
let href = window.location.href.split('/');
|
let href = window.location.href.split('/');
|
||||||
let currentTab = href[href.length - 2];
|
let currentTab = href[href.length - 2];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<SidePanel isSidePanelClosed={this.state.isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab} />
|
<SidePanel isSidePanelClosed={this.state.isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab} draftCounts={this.state.draftCounts} />
|
||||||
|
|
||||||
<MainPanel onShowSidePanel={this.onShowSidePanel}>
|
<MainPanel onShowSidePanel={this.onShowSidePanel}>
|
||||||
<Router>
|
<Router>
|
||||||
<FilesActivities path={siteRoot + 'dashboard'} />
|
<FilesActivities path={siteRoot + 'dashboard'} />
|
||||||
<DraftsView path={siteRoot + 'drafts'} currentTab={currentTab}>
|
<DraftsView path={siteRoot + 'drafts'} currentTab={currentTab}>
|
||||||
<DraftContent path='/' />
|
<DraftContent path='/' getDrafts={this.getDrafts} draftList={this.state.draftList}/>
|
||||||
<ReviewContent path='reviews' />
|
<ReviewContent path='reviews' />
|
||||||
</DraftsView>
|
</DraftsView>
|
||||||
<Starred path={siteRoot + 'starred'} />
|
<Starred path={siteRoot + 'starred'} />
|
||||||
|
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { Link } from '@reach/router';
|
import { Link } from '@reach/router';
|
||||||
import { gettext, siteRoot } from '../utils/constants';
|
import { gettext, siteRoot } from '../utils/constants';
|
||||||
import { seafileAPI } from '../utils/seafile-api';
|
import { seafileAPI } from '../utils/seafile-api';
|
||||||
|
import { Badge } from 'reactstrap';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
currentTab: PropTypes.string.isRequired,
|
currentTab: PropTypes.string.isRequired,
|
||||||
@@ -172,7 +173,7 @@ class MainSideNav extends React.Component {
|
|||||||
<li className={`tab ${this.state.currentTab === 'drafts' ? 'tab-cur' : ''}`} onClick={() => this.tabItemClick('drafts')}>
|
<li className={`tab ${this.state.currentTab === 'drafts' ? 'tab-cur' : ''}`} onClick={() => this.tabItemClick('drafts')}>
|
||||||
<Link to={siteRoot + 'drafts/'} title={gettext('Drafts')}>
|
<Link to={siteRoot + 'drafts/'} title={gettext('Drafts')}>
|
||||||
<span className="sf2-icon-edit" aria-hidden="true"></span>
|
<span className="sf2-icon-edit" aria-hidden="true"></span>
|
||||||
{gettext('Drafts')}
|
{gettext('Drafts')} {this.props.draftCounts === 0 ? '' : <Badge color="info" pill>{this.props.draftCounts}</Badge>}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="tab" id="share-admin-nav">
|
<li className="tab" id="share-admin-nav">
|
||||||
|
@@ -19,7 +19,7 @@ class SidePanel extends React.Component {
|
|||||||
<Logo onCloseSidePanel={this.props.onCloseSidePanel}/>
|
<Logo onCloseSidePanel={this.props.onCloseSidePanel}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="side-panel-center">
|
<div className="side-panel-center">
|
||||||
<MainSideNav currentTab={this.props.currentTab}/>
|
<MainSideNav currentTab={this.props.currentTab} draftCounts={this.props.draftCounts}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="side-panel-footer">
|
<div className="side-panel-footer">
|
||||||
<SideNavFooter />
|
<SideNavFooter />
|
||||||
|
@@ -13,6 +13,8 @@ let mode = window.app.pageOptions.mode;
|
|||||||
let draftID = window.app.pageOptions.draftID;
|
let draftID = window.app.pageOptions.draftID;
|
||||||
let reviewID = window.app.pageOptions.reviewID;
|
let reviewID = window.app.pageOptions.reviewID;
|
||||||
let isDraft = window.app.pageOptions.isDraft;
|
let isDraft = window.app.pageOptions.isDraft;
|
||||||
|
let hasDraft = window.app.pageOptions.hasDraft;
|
||||||
|
let draftFilePath = window.app.pageOptions.draftFilePath;
|
||||||
let userName = window.app.userInfo.username;
|
let userName = window.app.userInfo.username;
|
||||||
let dirPath = '/';
|
let dirPath = '/';
|
||||||
|
|
||||||
@@ -210,6 +212,10 @@ class EditorUtilities {
|
|||||||
getUserAvatar(size) {
|
getUserAvatar(size) {
|
||||||
return seafileAPI.getUserAvatar(userName, size);
|
return seafileAPI.getUserAvatar(userName, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
goDraftPage() {
|
||||||
|
window.location.href = serviceUrl + '/lib/' + repoID + '/file' + draftFilePath + '?mode=edit'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -286,6 +292,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
draftID={draftID}
|
draftID={draftID}
|
||||||
reviewID={reviewID}
|
reviewID={reviewID}
|
||||||
isDraft={isDraft}
|
isDraft={isDraft}
|
||||||
|
hasDraft={hasDraft}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,6 @@ class DraftContent extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
draftList: [],
|
|
||||||
isLoadingDraft: true,
|
isLoadingDraft: true,
|
||||||
isMenuShow: false,
|
isMenuShow: false,
|
||||||
menuPosition: {top:'', left: ''},
|
menuPosition: {top:'', left: ''},
|
||||||
@@ -32,11 +31,9 @@ class DraftContent extends React.Component {
|
|||||||
|
|
||||||
initDraftList() {
|
initDraftList() {
|
||||||
this.setState({isLoadingDraft: true});
|
this.setState({isLoadingDraft: true});
|
||||||
editUtilties.listDrafts().then(res => {
|
this.props.getDrafts();
|
||||||
this.setState({
|
this.setState({
|
||||||
draftList: res.data.data,
|
isLoadingDraft: false,
|
||||||
isLoadingDraft: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +110,14 @@ class DraftContent extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="cur-view-content">
|
<div className="cur-view-content">
|
||||||
{this.state.isLoadingDraft && <Loading /> }
|
{this.state.isLoadingDraft && <Loading /> }
|
||||||
{(!this.state.isLoadingDraft && this.state.draftList.length !==0) &&
|
{(!this.state.isLoadingDraft && this.props.draftList.length !==0) &&
|
||||||
<DraftListView
|
<DraftListView
|
||||||
draftList={this.state.draftList}
|
draftList={this.props.draftList}
|
||||||
isItemFreezed={this.state.isItemFreezed}
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
onMenuToggleClick={this.onMenuToggleClick}
|
onMenuToggleClick={this.onMenuToggleClick}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{(!this.state.isLoadingDraft && this.state.draftList.length === 0) &&
|
{(!this.state.isLoadingDraft && this.props.draftList.length === 0) &&
|
||||||
<div className="message empty-tip">
|
<div className="message empty-tip">
|
||||||
<h2>{gettext('No draft yet')}</h2>
|
<h2>{gettext('No draft yet')}</h2>
|
||||||
<p>{gettext('Draft is a way to let you collaborate with others on files. You can create a draft from a file, edit the draft and then ask for a review. The original file will be updated only after the draft be reviewed.')}</p>
|
<p>{gettext('Draft is a way to let you collaborate with others on files. You can create a draft from a file, edit the draft and then ask for a review. The original file will be updated only after the draft be reviewed.')}</p>
|
||||||
|
@@ -42,7 +42,12 @@ class DraftsView(APIView):
|
|||||||
username = request.user.username
|
username = request.user.username
|
||||||
data = [x.to_dict() for x in Draft.objects.filter(username=username)]
|
data = [x.to_dict() for x in Draft.objects.filter(username=username)]
|
||||||
|
|
||||||
return Response({'data': data})
|
draft_counts = len(data)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
result['data'] = data
|
||||||
|
result['draft_counts'] = draft_counts
|
||||||
|
return Response(result)
|
||||||
|
|
||||||
@add_org_context
|
@add_org_context
|
||||||
def post(self, request, org_id, format=None):
|
def post(self, request, org_id, format=None):
|
||||||
|
@@ -18,17 +18,21 @@ from seahub.api2.utils import api_error
|
|||||||
|
|
||||||
from seahub.utils import check_filename_with_rename, is_pro_version, \
|
from seahub.utils import check_filename_with_rename, is_pro_version, \
|
||||||
gen_inner_file_upload_url, is_valid_dirent_name, normalize_file_path, \
|
gen_inner_file_upload_url, is_valid_dirent_name, normalize_file_path, \
|
||||||
normalize_dir_path
|
normalize_dir_path, get_file_type_and_ext
|
||||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
||||||
from seahub.views import check_folder_permission
|
from seahub.views import check_folder_permission
|
||||||
from seahub.utils.file_op import check_file_lock, if_locked_by_online_office
|
from seahub.utils.file_op import check_file_lock, if_locked_by_online_office
|
||||||
from seahub.views.file import can_preview_file, can_edit_file
|
from seahub.views.file import can_preview_file, can_edit_file
|
||||||
from seahub.constants import PERMISSION_READ_WRITE
|
from seahub.constants import PERMISSION_READ_WRITE
|
||||||
from seahub.utils.repo import parse_repo_perm
|
from seahub.utils.repo import parse_repo_perm
|
||||||
|
from seahub.utils.file_types import MARKDOWN, TEXT
|
||||||
|
|
||||||
from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, \
|
from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, \
|
||||||
FILE_LOCK_EXPIRATION_DAYS, OFFICE_TEMPLATE_ROOT
|
FILE_LOCK_EXPIRATION_DAYS, OFFICE_TEMPLATE_ROOT
|
||||||
|
|
||||||
|
from seahub.drafts.models import Draft, DraftReview
|
||||||
|
from seahub.drafts.utils import is_draft_file
|
||||||
|
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
from pysearpc import SearpcError
|
from pysearpc import SearpcError
|
||||||
|
|
||||||
@@ -283,6 +287,27 @@ class FileView(APIView):
|
|||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
new_file_path = posixpath.join(parent_dir, new_file_name)
|
new_file_path = posixpath.join(parent_dir, new_file_name)
|
||||||
|
|
||||||
|
# rename draft file
|
||||||
|
filetype, fileext = get_file_type_and_ext(new_file_name)
|
||||||
|
if filetype == MARKDOWN or filetype == TEXT:
|
||||||
|
is_draft, review_id, draft_id = is_draft_file(repo.id, path)
|
||||||
|
if is_draft:
|
||||||
|
try:
|
||||||
|
draft = Draft.objects.get(pk=draft_id)
|
||||||
|
draft.draft_file_path = new_file_path
|
||||||
|
draft.save()
|
||||||
|
except Draft.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if review_id is not None:
|
||||||
|
try:
|
||||||
|
review = DraftReview.objects.get(pk=review_id, draft_id=draft)
|
||||||
|
review.draft_file_path = new_file_path
|
||||||
|
review.save()
|
||||||
|
except DraftReview.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
file_info = self.get_file_info(username, repo_id, new_file_path)
|
file_info = self.get_file_info(username, repo_id, new_file_path)
|
||||||
return Response(file_info)
|
return Response(file_info)
|
||||||
|
|
||||||
|
@@ -35,6 +35,9 @@ class DraftManager(models.Manager):
|
|||||||
draft_file_name = get_draft_file_name(repo.id, file_path)
|
draft_file_name = get_draft_file_name(repo.id, file_path)
|
||||||
draft_file_path = '/Drafts/' + draft_file_name
|
draft_file_path = '/Drafts/' + draft_file_name
|
||||||
|
|
||||||
|
if seafile_api.get_file_id_by_path(repo.id, draft_file_path):
|
||||||
|
raise DraftFileExist
|
||||||
|
|
||||||
# copy file to draft dir
|
# copy file to draft dir
|
||||||
seafile_api.copy_file(repo.id, file_uuid.parent_path, file_uuid.filename,
|
seafile_api.copy_file(repo.id, file_uuid.parent_path, file_uuid.filename,
|
||||||
repo.id, '/Drafts', draft_file_name,
|
repo.id, '/Drafts', draft_file_name,
|
||||||
|
@@ -4,6 +4,7 @@ import os
|
|||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
|
|
||||||
from seahub.utils import normalize_file_path, check_filename_with_rename
|
from seahub.utils import normalize_file_path, check_filename_with_rename
|
||||||
|
from seahub.tags.models import FileUUIDMap
|
||||||
|
|
||||||
|
|
||||||
def create_user_draft_repo(username, org_id=-1):
|
def create_user_draft_repo(username, org_id=-1):
|
||||||
@@ -20,12 +21,10 @@ def create_user_draft_repo(username, org_id=-1):
|
|||||||
def get_draft_file_name(repo_id, file_path):
|
def get_draft_file_name(repo_id, file_path):
|
||||||
file_path = normalize_file_path(file_path)
|
file_path = normalize_file_path(file_path)
|
||||||
file_name, file_ext = os.path.splitext(os.path.basename(file_path))
|
file_name, file_ext = os.path.splitext(os.path.basename(file_path))
|
||||||
# md5 = hashlib.md5((repo_id + file_path).encode('utf-8')).hexdigest()[:10]
|
|
||||||
|
|
||||||
draft_file_name = "%s%s%s" % (file_name, '(draft)', file_ext)
|
draft_file_name = "%s%s%s" % (file_name, '(draft)', file_ext)
|
||||||
new_file_name = check_filename_with_rename(repo_id, '/Drafts', draft_file_name)
|
|
||||||
|
|
||||||
return new_file_name
|
return draft_file_name
|
||||||
|
|
||||||
|
|
||||||
def is_draft_file(repo_id, file_path):
|
def is_draft_file(repo_id, file_path):
|
||||||
@@ -47,3 +46,25 @@ def is_draft_file(repo_id, file_path):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return is_draft, review_id, draft_id
|
return is_draft, review_id, draft_id
|
||||||
|
|
||||||
|
def has_draft_file(repo_id, file_path):
|
||||||
|
has_draft = False
|
||||||
|
draft_file_path = None
|
||||||
|
|
||||||
|
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_fileuuidmap_by_path(
|
||||||
|
repo_id, parent_path, filename, is_dir=False)
|
||||||
|
|
||||||
|
from .models import Draft
|
||||||
|
if file_uuid:
|
||||||
|
try:
|
||||||
|
draft = Draft.objects.get(origin_file_uuid=file_uuid)
|
||||||
|
has_draft = True
|
||||||
|
draft_file_path = draft.draft_file_path
|
||||||
|
except Draft.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return has_draft, draft_file_path
|
||||||
|
@@ -29,6 +29,8 @@
|
|||||||
draftID: '{{ draft_id }}',
|
draftID: '{{ draft_id }}',
|
||||||
reviewID: '{{ review_id }}',
|
reviewID: '{{ review_id }}',
|
||||||
isDraft: '{{ is_draft }}',
|
isDraft: '{{ is_draft }}',
|
||||||
|
hasDraft: '{{ has_draft }}',
|
||||||
|
draftFilePath: '{{ draft_file_path }}',
|
||||||
},
|
},
|
||||||
userInfo: {
|
userInfo: {
|
||||||
username: '{{ user.username }}',
|
username: '{{ user.username }}',
|
||||||
|
@@ -71,7 +71,7 @@ from seahub.utils.repo import is_repo_owner, parse_repo_perm
|
|||||||
from seahub.group.utils import is_group_member
|
from seahub.group.utils import is_group_member
|
||||||
from seahub.thumbnail.utils import extract_xmind_image, get_thumbnail_src, \
|
from seahub.thumbnail.utils import extract_xmind_image, get_thumbnail_src, \
|
||||||
XMIND_IMAGE_SIZE, THUMBNAIL_ROOT
|
XMIND_IMAGE_SIZE, THUMBNAIL_ROOT
|
||||||
from seahub.drafts.utils import is_draft_file
|
from seahub.drafts.utils import is_draft_file, has_draft_file
|
||||||
|
|
||||||
from seahub.constants import HASH_URLS
|
from seahub.constants import HASH_URLS
|
||||||
|
|
||||||
@@ -625,6 +625,11 @@ def view_lib_file(request, repo_id, path):
|
|||||||
|
|
||||||
is_draft, review_id, draft_id = is_draft_file(repo.id, path)
|
is_draft, review_id, draft_id = is_draft_file(repo.id, path)
|
||||||
|
|
||||||
|
has_draft = False
|
||||||
|
draft_file_path = ''
|
||||||
|
if not is_draft:
|
||||||
|
has_draft, draft_file_path = has_draft_file(repo.id, path)
|
||||||
|
|
||||||
if filetype == MARKDOWN:
|
if filetype == MARKDOWN:
|
||||||
return_dict['protocol'] = request.is_secure() and 'https' or 'http'
|
return_dict['protocol'] = request.is_secure() and 'https' or 'http'
|
||||||
return_dict['domain'] = get_current_site(request).domain
|
return_dict['domain'] = get_current_site(request).domain
|
||||||
@@ -636,6 +641,8 @@ def view_lib_file(request, repo_id, path):
|
|||||||
return_dict['draft_id'] = draft_id
|
return_dict['draft_id'] = draft_id
|
||||||
return_dict['review_id'] = review_id
|
return_dict['review_id'] = review_id
|
||||||
return_dict['is_draft'] = is_draft
|
return_dict['is_draft'] = is_draft
|
||||||
|
return_dict['has_draft'] = has_draft
|
||||||
|
return_dict['draft_file_path'] = draft_file_path
|
||||||
else:
|
else:
|
||||||
return_dict['file_content'] = file_content
|
return_dict['file_content'] = file_content
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user