mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 07:27:04 +00:00
Draft review (#2416)
This commit is contained in:
@@ -5,6 +5,8 @@ import { siteRoot } from './utils/constants';
|
||||
import SidePanel from './components/side-panel';
|
||||
import MainPanel from './components/main-panel';
|
||||
import DraftsView from './pages/drafts/drafts-view';
|
||||
import DraftContent from './pages/drafts/draft-content';
|
||||
import ReviewContent from './pages/drafts/review-content';
|
||||
import FilesActivities from './pages/dashboard/files-activities';
|
||||
import Starred from './pages/starred/starred';
|
||||
|
||||
@@ -49,7 +51,10 @@ class App extends Component {
|
||||
<MainPanel onShowSidePanel={this.onShowSidePanel}>
|
||||
<Router>
|
||||
<FilesActivities path={siteRoot + 'dashboard'} />
|
||||
<DraftsView path={siteRoot + 'drafts'} />
|
||||
<DraftsView path={siteRoot + 'drafts'} currentTab={currentTab}>
|
||||
<DraftContent path='/' />
|
||||
<ReviewContent path='reviews' />
|
||||
</DraftsView>
|
||||
<Starred path={siteRoot + 'starred'} />
|
||||
</Router>
|
||||
</MainPanel>
|
||||
|
@@ -47,8 +47,22 @@ class DraftListItem extends React.Component {
|
||||
onDraftEditClick = () => {
|
||||
let draft = this.props.draft;
|
||||
let filePath = draft.draft_file_path;
|
||||
let repoID = draft.draft_repo_id;
|
||||
window.location.href= siteRoot + 'lib/' + repoID + '/file' + filePath + '?mode=edit';
|
||||
let repoID = draft.origin_repo_id;
|
||||
let url = siteRoot + 'lib/' + repoID + '/file' + filePath + '?mode=edit&draft_id=' + draft.id;
|
||||
window.open(url)
|
||||
}
|
||||
|
||||
onLibraryClick = () => {
|
||||
let draft = this.props.draft;
|
||||
let repoID = draft.origin_repo_id;
|
||||
let url = siteRoot + '#common/lib/' + repoID;
|
||||
window.open(url)
|
||||
}
|
||||
|
||||
onReviewClick = () => {
|
||||
let draft = this.props.draft;
|
||||
let url = siteRoot + 'drafts/review/' + draft.review_id + '/';
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
getFileName(filePath) {
|
||||
@@ -65,13 +79,19 @@ class DraftListItem extends React.Component {
|
||||
<tr className={this.state.highlight} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td className="icon"><img src={siteRoot + 'media/img/file/192/txt.png'} /></td>
|
||||
<td className="name a-simulate" onClick={this.onDraftEditClick}>{fileName}</td>
|
||||
<td className="owner">{draft.owner}</td>
|
||||
<td className="library a-simulate" onClick={this.onLibraryClick}>{draft.repo_name}</td>
|
||||
<td className="review">
|
||||
{ draft.review_id && draft.review_status === 'open' ? <span className="a-simulate" onClick={this.onReviewClick}>#{draft.review_id}</span> : <span>--</span> }
|
||||
</td>
|
||||
<td className="update">{localTime}</td>
|
||||
<td className="menu-toggle">
|
||||
<NodeMenuControl
|
||||
isShow={this.state.isMenuControlShow}
|
||||
onClick={this.onMenuToggleClick}
|
||||
/>
|
||||
{
|
||||
this.props.draft.review_status !== 'open' &&
|
||||
<NodeMenuControl
|
||||
isShow={this.state.isMenuControlShow}
|
||||
onClick={this.onMenuToggleClick}
|
||||
/>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@@ -13,18 +13,28 @@ class DraftListMenu extends React.Component {
|
||||
|
||||
render() {
|
||||
let style = {};
|
||||
let {isMenuShow, menuPosition} = this.props;
|
||||
let {isMenuShow, menuPosition, currentDraft} = this.props;
|
||||
if (isMenuShow) {
|
||||
style = {position: 'fixed', top: menuPosition.top, left: menuPosition.left, display: 'block'};
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
if (currentDraft.review_status === null) {
|
||||
return (
|
||||
<ul className="dropdown-menu" style={style}>
|
||||
<li className="dropdown-item" onClick={this.props.onDeleteHandler}>{gettext('Delete')}</li>
|
||||
<li className="dropdown-item" onClick={this.props.onPublishHandler}>{gettext('Publish')}</li>
|
||||
<li className="dropdown-item" onClick={this.props.onReviewHandler}>{gettext('Ask for review')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if (currentDraft.review_status === 'closed' ) {
|
||||
return (
|
||||
<ul className="dropdown-menu" style={style}>
|
||||
<li className="dropdown-item" onClick={this.props.onDeleteHandler}>{gettext('Delete')}</li>
|
||||
<li className="dropdown-item" onClick={this.props.onReviewHandler}>{gettext('Ask for review')}</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,8 +20,9 @@ class DraftListView extends React.Component {
|
||||
<tr>
|
||||
<th style={{width: '4%'}}>{/*img*/}</th>
|
||||
<th style={{width: '46%'}}>{gettext('Name')}</th>
|
||||
<th style={{width: '20%'}}>{gettext('Owner')}</th>
|
||||
<th style={{width: '20%'}}>{gettext('Last Update')}</th>
|
||||
<th style={{width: '20%'}}>{gettext('Library')}</th>
|
||||
<th style={{width: '10%'}}>{gettext('Review')}</th>
|
||||
<th style={{width: '10%'}}>{gettext('Last Update')}</th>
|
||||
<th style={{width: '10%'}}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@@ -147,13 +147,13 @@ class MainSideNav extends React.Component {
|
||||
<h3 className="sf-heading">Tools</h3>
|
||||
<ul className="side-tabnav-tabs">
|
||||
<li className={`tab ${this.state.currentTab === 'starred' ? 'tab-cur' : ''}`}>
|
||||
<a href={siteRoot + 'starred/'} title={gettext('Favorites')} onClick={() => this.tabItemClick('favorites')}>
|
||||
<Link to={siteRoot + 'starred/'} title={gettext('Favorites')} onClick={() => this.tabItemClick('favorites')}>
|
||||
<span className="sf2-icon-star" aria-hidden="true"></span>
|
||||
{gettext('Favorites')}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li className={`tab ${this.state.currentTab === 'dashboard' ? 'tab-cur' : ''}`}>
|
||||
<Link to={siteRoot + 'dashboard'} title={gettext('Acitivities')} onClick={() => this.tabItemClick('dashboard')}>
|
||||
<Link to={siteRoot + 'dashboard/'} title={gettext('Acitivities')} onClick={() => this.tabItemClick('dashboard')}>
|
||||
<span className="sf2-icon-clock" aria-hidden="true"></span>
|
||||
{gettext('Acitivities')}
|
||||
</Link>
|
||||
@@ -165,7 +165,7 @@ class MainSideNav extends React.Component {
|
||||
</a>
|
||||
</li>
|
||||
<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>
|
||||
{gettext('Drafts')}
|
||||
</Link>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { processor, processorGetAST } from '@seafile/seafile-editor/src/lib/seafile-markdown2html';
|
||||
import { processor, processorGetAST } from '@seafile/seafile-editor/dist/utils/seafile-markdown2html';
|
||||
import Prism from 'prismjs';
|
||||
import WikiOutline from './wiki-outline';
|
||||
|
||||
@@ -7,7 +7,7 @@ var URL = require('url-parse');
|
||||
|
||||
const gettext = window.gettext;
|
||||
|
||||
require('@seafile/seafile-editor/src/lib/code-hight-package');
|
||||
require('@seafile/seafile-editor/dist/editor/code-hight-package');
|
||||
|
||||
const contentClass = "wiki-md-viewer-rendered-content";
|
||||
|
||||
|
68
frontend/src/components/review-list-view/review-list-item.js
Normal file
68
frontend/src/components/review-list-view/review-list-item.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { siteRoot, lang } from '../../utils/constants';
|
||||
import moment from 'moment';
|
||||
|
||||
moment.locale(lang);
|
||||
const propTypes = {
|
||||
isItemFreezed: PropTypes.bool.isRequired,
|
||||
}
|
||||
class ReviewListItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
highlight: '',
|
||||
};
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
highlight: 'tr-highlight'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseLeave = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
highlight: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onReviewsClick = () => {
|
||||
let item = this.props.item;
|
||||
let filePath = item.draft_file_path;
|
||||
let itemID = item.id;
|
||||
window.open(siteRoot + 'drafts/review/' + itemID);
|
||||
}
|
||||
|
||||
getFileName(filePath) {
|
||||
let lastIndex = filePath.lastIndexOf("/");
|
||||
return filePath.slice(lastIndex+1);
|
||||
}
|
||||
|
||||
render() {
|
||||
let item = this.props.item;
|
||||
let fileName = this.getFileName(item.draft_file_path);
|
||||
let localTime = moment.utc(item.updated_at).toDate();
|
||||
localTime = moment(localTime).fromNow();
|
||||
|
||||
return (
|
||||
<tr className={this.state.highlight} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td className="icon" style={{width: "4%"}}><img src={siteRoot + "media/img/file/192/txt.png"} /></td>
|
||||
<td className="name a-simulate" style={{width: "46%"}} onClick={this.onReviewsClick}>{fileName}</td>
|
||||
<td className='library'>{item.draft_origin_repo_name}</td>
|
||||
<td className="status" style={{width: "20%"}}>{item.status}</td>
|
||||
<td className="update" style={{width: "20%"}}>{localTime}</td>
|
||||
<td className="menu-toggle"></td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReviewListItem.propTypes = propTypes;
|
||||
|
||||
export default ReviewListItem;
|
47
frontend/src/components/review-list-view/review-list-view.js
Normal file
47
frontend/src/components/review-list-view/review-list-view.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import ReviewListItem from './review-list-item';
|
||||
|
||||
const propTypes = {
|
||||
isItemFreezed: PropTypes.bool.isRequired,
|
||||
itemsList: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
class ReviewListView extends React.Component {
|
||||
|
||||
render() {
|
||||
let items = this.props.itemsList;
|
||||
return (
|
||||
<div className="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{width: '4%'}}>{/*img*/}</th>
|
||||
<th style={{width: '26%'}}>{gettext('Name')}</th>
|
||||
<th style={{width: '20%'}}>{gettext('Library')}</th>
|
||||
<th style={{width: '20%'}}>{gettext('Status')}</th>
|
||||
<th style={{width: '20%'}}>{gettext('Last Update')}</th>
|
||||
<th style={{width: '10%'}}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ items && items.map((item) => {
|
||||
return (
|
||||
<ReviewListItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
isItemFreezed={this.props.isItemFreezed}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReviewListView.propTypes = propTypes;
|
||||
|
||||
export default ReviewListView;
|
@@ -142,3 +142,14 @@
|
||||
.article hr.active {
|
||||
border-top: 1px solid #eb8205;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
background-color: #fafaf9;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.markdown-viewer-render-content {
|
||||
margin: 20px 40px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e6e6dd;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#wrapper {
|
||||
#wrapper, .wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
/* for top bottom layout*/
|
||||
#header {
|
||||
height: 49px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -112,4 +111,82 @@
|
||||
[role=group] {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 0.625rem;
|
||||
display: flex;
|
||||
flex-shrink:0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
box-shadow: 0 3px 2px -2px rgba(200,200,200,.15);
|
||||
}
|
||||
|
||||
.header .cur-file-info {
|
||||
display: flex;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.header .info-item {
|
||||
display: flex;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin-right: 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header .file-copywriting {
|
||||
margin-left: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.header .file-feature {
|
||||
width: 2.9375rem;
|
||||
height: 2.9375rem;
|
||||
font-size: 1.8rem;
|
||||
background-color: #fbcb09;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.header .file-operation-btn {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.review {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.review .cur-file-info {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.review .file-feature {
|
||||
width: 4.1875rem;
|
||||
height: 4.1875rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.review-state {
|
||||
position: relative;
|
||||
padding: 0.75rem 1.25rem;
|
||||
margin: auto 0.5rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.review-state-finished {
|
||||
color: #316100;
|
||||
background-color: #dff1cc;
|
||||
border-color: #d2ecb8;
|
||||
}
|
||||
|
||||
.review-state-closed {
|
||||
color: #6b1110;
|
||||
background-color: #f5d2d2;
|
||||
border-color: #f1c1c0;
|
||||
}
|
||||
|
133
frontend/src/draft-review.js
Normal file
133
frontend/src/draft-review.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Prism from 'prismjs';
|
||||
import { siteRoot, gettext, draftID, reviewID, draftOriginFilePath, draftFilePath, draftOriginRepoID, draftFileName, opStatus, publishFileVersion, originFileVersion } from './utils/constants';
|
||||
import { seafileAPI } from './utils/seafile-api';
|
||||
import axios from 'axios';
|
||||
import DiffViewer from '@seafile/seafile-editor/dist/viewer/diff-viewer';
|
||||
import Loading from './components/loading';
|
||||
import Toast from './components/toast';
|
||||
|
||||
import 'seafile-ui';
|
||||
import './assets/css/fa-solid.css';
|
||||
import './assets/css/fa-regular.css';
|
||||
import './assets/css/fontawesome.css';
|
||||
import './css/layout.css';
|
||||
import './css/initial-style.css';
|
||||
import './css/toolbar.css';
|
||||
|
||||
require('@seafile/seafile-editor/dist/editor/code-hight-package');
|
||||
|
||||
class DraftReview extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
draftContent: '',
|
||||
draftOriginContent: '',
|
||||
reviewStatus: opStatus,
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (publishFileVersion == 'None') {
|
||||
axios.all([
|
||||
seafileAPI.getFileDownloadLink(draftOriginRepoID, draftFilePath),
|
||||
seafileAPI.getFileDownloadLink(draftOriginRepoID, draftOriginFilePath)
|
||||
]).then(axios.spread((res1, res2) => {
|
||||
axios.all([
|
||||
seafileAPI.getFileContent(res1.data),
|
||||
seafileAPI.getFileContent(res2.data)
|
||||
]).then(axios.spread((draftContent, draftOriginContent) => {
|
||||
this.setState({
|
||||
draftContent: draftContent.data,
|
||||
draftOriginContent: draftOriginContent.data,
|
||||
isLoading: false
|
||||
});
|
||||
}));
|
||||
}));
|
||||
} else {
|
||||
let dl0 = siteRoot + 'repo/' + draftOriginRepoID + '/' + publishFileVersion + '/download?' + 'p=' + draftOriginFilePath;
|
||||
let dl = siteRoot + 'repo/' + draftOriginRepoID + '/' + originFileVersion + '/download?' + 'p=' + draftOriginFilePath;
|
||||
axios.all([
|
||||
seafileAPI.getFileContent(dl0),
|
||||
seafileAPI.getFileContent(dl)
|
||||
]).then(axios.spread((draftContent, draftOriginContent) => {
|
||||
this.setState({
|
||||
draftContent: draftContent.data,
|
||||
draftOriginContent: draftOriginContent.data,
|
||||
isLoading: false,
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
onCloseReview = () => {
|
||||
seafileAPI.updateReviewStatus(reviewID, 'closed').then(res => {
|
||||
this.setState({reviewStatus: 'closed'});
|
||||
Toast.success('Review close succeeded.')
|
||||
}).catch(() => {
|
||||
Toast.error('Review close failed.')
|
||||
});
|
||||
}
|
||||
|
||||
onPublishReview = () => {
|
||||
seafileAPI.updateReviewStatus(reviewID, 'finished').then(res => {
|
||||
this.setState({reviewStatus: 'finished'});
|
||||
Toast.success('Review publish succeeded.')
|
||||
}).catch(() => {
|
||||
Toast.error('Review publish failed.')
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="wrapper">
|
||||
<div id="header" className="header review">
|
||||
<div className="cur-file-info">
|
||||
<div className="info-item file-feature">
|
||||
<span className="fas fa-code-merge"></span>
|
||||
</div>
|
||||
<div className="info-item file-info">
|
||||
<span className="file-name">{draftFileName}</span>
|
||||
<span className="file-copywriting">{gettext('review')}</span>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
this.state.reviewStatus === 'open' &&
|
||||
<div className="cur-file-operation">
|
||||
<button className="btn btn-secondary file-operation-btn" title={gettext('Close Review')} onClick={this.onCloseReview}>{gettext("Close")}</button>
|
||||
<button className="btn btn-success file-operation-btn" title={gettext('Publish Review')} onClick={this.onPublishReview}>{gettext("Publish")}</button>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
this.state.reviewStatus === 'finished' &&
|
||||
<div className="review-state review-state-finished">{gettext('Finished')}</div>
|
||||
}
|
||||
{
|
||||
this.state.reviewStatus === 'closed' &&
|
||||
<div className="review-state review-state-closed">{gettext('Closed')}</div>
|
||||
}
|
||||
</div>
|
||||
<div id="main" className="main">
|
||||
<div className="cur-view-container content-container">
|
||||
<div className="cur-view-content">
|
||||
<div className="markdown-viewer-render-content article">
|
||||
{
|
||||
this.state.isLoading ?
|
||||
<Loading /> :
|
||||
<DiffViewer markdownContent={this.state.draftContent} markdownContent1={this.state.draftOriginContent} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render (
|
||||
<DraftReview />,
|
||||
document.getElementById('wrapper')
|
||||
);
|
@@ -10,6 +10,7 @@ let siteRoot = window.app.config.siteRoot;
|
||||
let domain = window.app.pageOptions.domain;
|
||||
let protocol = window.app.pageOptions.protocol;
|
||||
let mode = window.app.pageOptions.mode;
|
||||
let draftID = window.app.pageOptions.draftID;
|
||||
let dirPath = '/';
|
||||
|
||||
const serviceUrl = window.app.config.serviceUrl;
|
||||
@@ -25,6 +26,7 @@ function getImageFileNameWithTimestamp() {
|
||||
return 'image-' + d.toString() + '.png';
|
||||
}
|
||||
|
||||
|
||||
class EditorUtilities {
|
||||
|
||||
constructor () {
|
||||
@@ -170,6 +172,14 @@ class EditorUtilities {
|
||||
getFileHistoryVersion(commitID) {
|
||||
return seafileAPI.getFileRevision(repoID, commitID, filePath);
|
||||
}
|
||||
|
||||
createDraftReview() {
|
||||
return seafileAPI.createDraftReview(draftID)
|
||||
.then(res => {
|
||||
let url = serviceUrl + '/drafts/review/' + res.data.id;
|
||||
return url;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +253,7 @@ class MarkdownEditor extends React.Component {
|
||||
collabServer={this.state.collabServer}
|
||||
showFileHistory={true}
|
||||
mode={mode}
|
||||
draftID={draftID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
136
frontend/src/pages/drafts/draft-content.js
Normal file
136
frontend/src/pages/drafts/draft-content.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import React from 'react';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import editUtilties from '../../utils/editor-utilties';
|
||||
import Toast from '../../components/toast';
|
||||
import Loading from '../../components/loading';
|
||||
import DraftListView from '../../components/draft-list-view/draft-list-view';
|
||||
import DraftListMenu from '../../components/draft-list-view/draft-list-menu';
|
||||
|
||||
|
||||
class DraftContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
draftList: [],
|
||||
isLoadingDraft: true,
|
||||
isMenuShow: false,
|
||||
menuPosition: {top:'', left: ''},
|
||||
currentDraft: null,
|
||||
isItemFreezed: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initDraftList();
|
||||
document.addEventListener('click', this.onHideContextMenu);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.onHideContextMenu);
|
||||
}
|
||||
|
||||
initDraftList() {
|
||||
this.setState({isLoadingDraft: true});
|
||||
editUtilties.listDrafts().then(res => {
|
||||
this.setState({
|
||||
draftList: res.data.data,
|
||||
isLoadingDraft: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteHandler = () => {
|
||||
let draft = this.state.currentDraft;
|
||||
editUtilties.deleteDraft(draft.id).then(res => {
|
||||
this.initDraftList();
|
||||
Toast.success('Delete draft succeeded.');
|
||||
}).catch(() => {
|
||||
Toast.error('Delete draft failed.');
|
||||
});
|
||||
}
|
||||
|
||||
onPublishHandler = () => {
|
||||
let draft = this.state.currentDraft;
|
||||
editUtilties.publishDraft(draft.id).then(res => {
|
||||
this.initDraftList();
|
||||
Toast.success('Publish draft succeeded.');
|
||||
}).catch(() => {
|
||||
Toast.error('Publish draft failed.');
|
||||
});
|
||||
}
|
||||
|
||||
onReviewHandler = () => {
|
||||
let draft = this.state.currentDraft;
|
||||
|
||||
editUtilties.createDraftReview(draft.id).then(res => {
|
||||
window.open(siteRoot + 'drafts/review/' + res.data.id);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status == '409') {
|
||||
Toast.error("The draft review is existing.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMenuToggleClick = (e, draft) => {
|
||||
if (this.state.isMenuShow) {
|
||||
this.onHideContextMenu();
|
||||
} else {
|
||||
this.onShowContextMenu(e, draft);
|
||||
}
|
||||
}
|
||||
|
||||
onShowContextMenu = (e, draft) => {
|
||||
let left = e.clientX - 8*16;
|
||||
let top = e.clientY + 10;
|
||||
let position = {top: top, left: left};
|
||||
this.setState({
|
||||
isMenuShow: true,
|
||||
menuPosition: position,
|
||||
currentDraft: draft,
|
||||
isItemFreezed: true
|
||||
});
|
||||
}
|
||||
|
||||
onHideContextMenu = () => {
|
||||
this.setState({
|
||||
isMenuShow: false,
|
||||
currentDraft: null,
|
||||
isItemFreezed: false
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-content" style={{padding: 0}}>
|
||||
{this.state.isLoadingDraft && <Loading /> }
|
||||
{(!this.state.isLoadingDraft && this.state.draftList.length !==0) &&
|
||||
<DraftListView
|
||||
draftList={this.state.draftList}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onMenuToggleClick={this.onMenuToggleClick}
|
||||
/>
|
||||
}
|
||||
{(!this.state.isLoadingDraft && this.state.draftList.length === 0) &&
|
||||
<div className="message empty-tip">
|
||||
<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>
|
||||
</div>
|
||||
}
|
||||
{this.state.isMenuShow &&
|
||||
<DraftListMenu
|
||||
isMenuShow={this.state.isMenuShow}
|
||||
currentDraft={this.state.currentDraft}
|
||||
menuPosition={this.state.menuPosition}
|
||||
onPublishHandler={this.onPublishHandler}
|
||||
onDeleteHandler={this.onDeleteHandler}
|
||||
onReviewHandler={this.onReviewHandler}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DraftContent;
|
@@ -1,124 +1,43 @@
|
||||
import React from 'react';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import editUtilties from '../../utils/editor-utilties';
|
||||
import Toast from '../../components/toast/';
|
||||
import Loading from '../../components/loading';
|
||||
import DraftListView from '../../components/draft-list-view/draft-list-view';
|
||||
import DraftListMenu from '../../components/draft-list-view/draft-list-menu';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { Link } from '@reach/router';
|
||||
|
||||
class DraftsView extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
draftList: [],
|
||||
isLoadingDraft: true,
|
||||
isMenuShow: false,
|
||||
menuPosition: {top:'', left: ''},
|
||||
currentDraft: null,
|
||||
isItemFreezed: false,
|
||||
currentTab: this.props.currentTab
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initDraftList();
|
||||
document.addEventListener('click', this.onHideContextMenu);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.onHideContextMenu);
|
||||
}
|
||||
|
||||
initDraftList() {
|
||||
this.setState({isLoadingDraft: true});
|
||||
editUtilties.listDrafts().then(res => {
|
||||
this.setState({
|
||||
draftList: res.data.data,
|
||||
isLoadingDraft: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteHandler = () => {
|
||||
let draft = this.state.currentDraft;
|
||||
editUtilties.deleteDraft(draft.id).then(res => {
|
||||
this.initDraftList();
|
||||
Toast.success(gettext('Delete draft succeeded.'));
|
||||
}).catch(() => {
|
||||
Toast.error(gettext('Delete draft failed.'));
|
||||
});
|
||||
}
|
||||
|
||||
onPublishHandler = () => {
|
||||
let draft = this.state.currentDraft;
|
||||
editUtilties.publishDraft(draft.id).then(res => {
|
||||
this.initDraftList();
|
||||
Toast.success(gettext('Publish draft succeeded.'));
|
||||
}).catch(() => {
|
||||
Toast.error(gettext('Publish draft failed.'));
|
||||
});
|
||||
}
|
||||
|
||||
onMenuToggleClick = (e, draft) => {
|
||||
if (this.state.isMenuShow) {
|
||||
this.onHideContextMenu();
|
||||
} else {
|
||||
this.onShowContextMenu(e, draft);
|
||||
}
|
||||
}
|
||||
|
||||
onShowContextMenu = (e, draft) => {
|
||||
let left = e.clientX - 8*16;
|
||||
let top = e.clientY + 10;
|
||||
let position = {top: top, left: left};
|
||||
tabItemClick = (param) => {
|
||||
this.setState({
|
||||
isMenuShow: true,
|
||||
menuPosition: position,
|
||||
currentDraft: draft,
|
||||
isItemFreezed: true
|
||||
});
|
||||
}
|
||||
|
||||
onHideContextMenu = () => {
|
||||
this.setState({
|
||||
isMenuShow: false,
|
||||
currentDraft: null,
|
||||
isItemFreezed: false
|
||||
});
|
||||
currentTab: param
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path panel-heading text-left">{gettext('Drafts')}</div>
|
||||
<div className="cur-view-content" style={{padding: 0}}>
|
||||
{this.state.isLoadingDraft && <Loading /> }
|
||||
{(!this.state.isLoadingDraft && this.state.draftList.length !==0) &&
|
||||
<DraftListView
|
||||
draftList={this.state.draftList}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onMenuToggleClick={this.onMenuToggleClick}
|
||||
/>
|
||||
}
|
||||
{(!this.state.isLoadingDraft && this.state.draftList.length === 0) &&
|
||||
<div className="message empty-tip">
|
||||
<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>
|
||||
</div>
|
||||
}
|
||||
{this.state.isMenuShow &&
|
||||
<DraftListMenu
|
||||
isMenuShow={this.state.isMenuShow}
|
||||
currentDraft={this.state.currentDraft}
|
||||
menuPosition={this.state.menuPosition}
|
||||
onPublishHandler={this.onPublishHandler}
|
||||
onDeleteHandler={this.onDeleteHandler}
|
||||
/>
|
||||
}
|
||||
<div className="cur-view-path">
|
||||
<ul className="tab-tabs-nav">
|
||||
<li className={`tab ${this.state.currentTab === 'drafts' ? 'ui-state-active': ''}`} onClick={() => this.tabItemClick('drafts')}>
|
||||
<Link className='a' to={siteRoot + 'drafts'} title={gettext('Drafts')}>
|
||||
{gettext('Drafts')}
|
||||
</Link>
|
||||
</li>
|
||||
<li className={`tab ${this.state.currentTab === 'reviews' ? 'ui-state-active': ''}`} onClick={() => this.tabItemClick('reviews')}>
|
||||
<Link className='a' to={siteRoot + 'drafts/reviews'} title={gettext('reviews')}>
|
||||
{gettext('Reviews')}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DraftsView;
|
||||
export default DraftsView;
|
||||
|
52
frontend/src/pages/drafts/review-content.js
Normal file
52
frontend/src/pages/drafts/review-content.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import editUtilties from '../../utils/editor-utilties';
|
||||
import Loading from '../../components/loading';
|
||||
import ReviewListView from '../../components/review-list-view/review-list-view';
|
||||
|
||||
class ReviewContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
reviewsList: [],
|
||||
isLoadingReviews: true,
|
||||
isItemFreezed: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initReviewList();
|
||||
}
|
||||
|
||||
initReviewList() {
|
||||
this.setState({isLoadingReviews: true});
|
||||
editUtilties.listReviews().then(res => {
|
||||
this.setState({
|
||||
reviewsList: res.data.data,
|
||||
isLoadingReviews: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-content" style={{padding: 0}}>
|
||||
{this.state.isLoadingReviews && <Loading /> }
|
||||
{(!this.state.isLoadingReviews && this.state.reviewsList.length !==0) &&
|
||||
<ReviewListView
|
||||
itemsList={this.state.reviewsList}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
/>
|
||||
}
|
||||
{(!this.state.isLoadingReviews && this.state.reviewsList.length === 0) &&
|
||||
<div className="message empty-tip">
|
||||
<h2>{gettext('There is no Review file existing')}</h2>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ReviewContent;
|
@@ -3,10 +3,10 @@ import PropTypes from 'prop-types';
|
||||
import Prism from 'prismjs';
|
||||
import Loading from '../../components/loading';
|
||||
import CommonToolbar from '../../components/toolbar/common-toolbar';
|
||||
import DiffViewer from '@seafile/seafile-editor/dist/diff-viewer/diff-viewer';
|
||||
import DiffViewer from '@seafile/seafile-editor/dist/viewer/diff-viewer';
|
||||
import '../../css/initial-style.css';
|
||||
|
||||
require('@seafile/seafile-editor/src/lib/code-hight-package');
|
||||
require('@seafile/seafile-editor/dist/editor/code-hight-package');
|
||||
|
||||
const contentClass = 'markdown-viewer-render-content';
|
||||
const propTypes = {
|
||||
|
@@ -115,7 +115,7 @@ class MainPanel extends Component {
|
||||
<div className="btn-group">
|
||||
<button className="btn btn-secondary btn-icon sf-view-mode-change-btn sf2-icon-list-view" id='list' title={gettext('List')} onClick={this.switchViewMode}></button>
|
||||
<button className="btn btn-secondary btn-icon sf-view-mode-change-btn sf2-icon-grid-view" id='grid' title={gettext('Grid')} onClick={this.switchViewMode}></button>
|
||||
<button className={`btn btn-secondary btn-icon sf-view-mode-change-btn sf2-icon-wiki-view ${this.state.isWikiMode ? 'current-mode' : ''}`} id='wiki' title={gettext('wiki')} onClick={this.switchViewMode}></button>
|
||||
<button className={`btn btn-secondary btn-icon sf-view-mode-change-btn sf2-icon-two-columns ${this.state.isWikiMode ? 'current-mode' : ''}`} id='wiki' title={gettext('wiki')} onClick={this.switchViewMode}></button>
|
||||
</div>
|
||||
</div>
|
||||
<CommonToolbar onSearchedClick={this.props.onSearchedClick} searchPlaceholder={'Search files in this library'}/>
|
||||
|
@@ -26,3 +26,13 @@ export const historyRepoID = window.fileHistory ? window.fileHistory.pageOptions
|
||||
export const repoName = window.fileHistory ? window.fileHistory.pageOptions.repoName : '';
|
||||
export const filePath = window.fileHistory ? window.fileHistory.pageOptions.filePath : '';
|
||||
export const fileName = window.fileHistory ? window.fileHistory.pageOptions.fileName : '';
|
||||
|
||||
// Draft review
|
||||
export const draftFilePath = window.draftReview ? window.draftReview.config.draftFilePath: '';
|
||||
export const draftOriginFilePath = window.draftReview ? window.draftReview.config.draftOriginFilePath: '';
|
||||
export const draftOriginRepoID = window.draftReview ? window.draftReview.config.draftOriginRepoID: '';
|
||||
export const draftFileName = window.draftReview ? window.draftReview.config.draftFileName: '';
|
||||
export const reviewID = window.draftReview ? window.draftReview.config.reviewID : '';
|
||||
export const opStatus = window.draftReview ? window.draftReview.config.opStatus : '';
|
||||
export const publishFileVersion = window.draftReview ? window.draftReview.config.publishFileVersion : '';
|
||||
export const originFileVersion = window.draftReview ? window.draftReview.config.originFileVersion : '';
|
||||
|
@@ -119,6 +119,14 @@ class EditorUtilities {
|
||||
return seafileAPI.cancelZipTask(zip_token);
|
||||
}
|
||||
|
||||
createDraftReview(id) {
|
||||
return seafileAPI.createDraftReview(id);
|
||||
}
|
||||
|
||||
listReviews() {
|
||||
return seafileAPI.listReviews();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const editorUtilities = new EditorUtilities();
|
||||
|
Reference in New Issue
Block a user