mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-08 02:10:24 +00:00
Update draft page (#3036)
* draft-update * test * update draft reviewer api * update draft page * add file path
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import AsyncSelect from 'react-select/lib/Async';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
||||
import UserSelect from '../user-select.js';
|
||||
import '../../css/add-reviewer-dialog.css';
|
||||
|
||||
const propTypes = {
|
||||
showReviewerDialog: PropTypes.bool.isRequired,
|
||||
reviewID: PropTypes.string.isRequired,
|
||||
draftID: PropTypes.string.isRequired,
|
||||
toggleAddReviewerDialog: PropTypes.func.isRequired,
|
||||
reviewers: PropTypes.array.isRequired
|
||||
};
|
||||
@@ -27,7 +27,7 @@ class AddReviewerDialog extends React.Component {
|
||||
}
|
||||
|
||||
listReviewers = () => {
|
||||
seafileAPI.listReviewers(this.props.reviewID).then((res) => {
|
||||
seafileAPI.listDraftReviewers(this.props.draftID).then((res) => {
|
||||
this.setState({
|
||||
reviewers: res.data.reviewers
|
||||
});
|
||||
@@ -41,29 +41,9 @@ class AddReviewerDialog extends React.Component {
|
||||
this.Options = [];
|
||||
}
|
||||
|
||||
loadOptions = (value, callback) => {
|
||||
if (value.trim().length > 0) {
|
||||
seafileAPI.searchUsers(value.trim()).then((res) => {
|
||||
this.Options = [];
|
||||
for (let i = 0 ; i < res.data.users.length; i++) {
|
||||
let obj = {};
|
||||
obj.value = res.data.users[i].name;
|
||||
obj.email = res.data.users[i].email;
|
||||
obj.label =
|
||||
<React.Fragment>
|
||||
<img src={res.data.users[i].avatar_url} className="avatar reviewer-select-avatar" alt=""/>
|
||||
<span className='reviewer-select-name'>{res.data.users[i].name}</span>
|
||||
</React.Fragment>;
|
||||
this.Options.push(obj);
|
||||
}
|
||||
callback(this.Options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addReviewers = () => {
|
||||
if (this.state.selectedOption.length > 0 ) {
|
||||
this.refs.reviewSelect.select.onChange([], { action: 'clear' });
|
||||
this.refs.reviewSelect.clearSelect();
|
||||
let reviewers = [];
|
||||
for (let i = 0; i < this.state.selectedOption.length; i ++) {
|
||||
reviewers[i] = this.state.selectedOption[i].email;
|
||||
@@ -72,7 +52,7 @@ class AddReviewerDialog extends React.Component {
|
||||
loading: true,
|
||||
errorMsg: [],
|
||||
});
|
||||
seafileAPI.addReviewers(this.props.reviewID, reviewers).then((res) => {
|
||||
seafileAPI.addDraftReviewers(this.props.draftID, reviewers).then((res) => {
|
||||
if (res.data.failed.length > 0) {
|
||||
let errorMsg = [];
|
||||
for (let i = 0 ; i < res.data.failed.length ; i++) {
|
||||
@@ -95,7 +75,7 @@ class AddReviewerDialog extends React.Component {
|
||||
|
||||
deleteReviewer = (event) => {
|
||||
let reviewer = event.target.getAttribute('name');
|
||||
seafileAPI.deleteReviewer(this.props.reviewID, reviewer).then((res) => {
|
||||
seafileAPI.deleteDraftReviewer(this.props.draftID, reviewer).then((res) => {
|
||||
if (res.data === 200) {
|
||||
let newReviewers = [];
|
||||
for (let i = 0; i < this.state.reviewers.length; i ++) {
|
||||
@@ -116,12 +96,12 @@ class AddReviewerDialog extends React.Component {
|
||||
<ModalHeader>{gettext('Request a review')}</ModalHeader>
|
||||
<ModalBody >
|
||||
<p>{gettext('Add new reviewer')}</p>
|
||||
<AsyncSelect
|
||||
className='reviewer-select' isMulti isFocused
|
||||
loadOptions={this.loadOptions}
|
||||
<UserSelect
|
||||
placeholder={gettext('Please enter 1 or more character')}
|
||||
onChange={this.handleSelectChange}
|
||||
ref="reviewSelect" isClearable classNamePrefix
|
||||
onSelectChange={this.handleSelectChange}
|
||||
ref="reviewSelect"
|
||||
isMulti={true}
|
||||
className='reviewer-select'
|
||||
/>
|
||||
{this.state.errorMsg.length > 0 &&
|
||||
this.state.errorMsg.map((item, index = 0, arr) => {
|
||||
|
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { reviewID, gettext, name } from '../../utils/constants';
|
||||
import { gettext, name, draftRepoID, draftFilePath } from '../../utils/constants';
|
||||
import { processor } from '../../utils/seafile-markdown2html';
|
||||
|
||||
import '../../css/review-comment-dialog.css';
|
||||
@@ -13,6 +13,7 @@ const commentDialogPropTypes = {
|
||||
quote: PropTypes.string,
|
||||
newIndex: PropTypes.number,
|
||||
oldIndex: PropTypes.number,
|
||||
draftID: PropTypes.string,
|
||||
};
|
||||
|
||||
class ReviewCommentDialog extends React.Component {
|
||||
@@ -42,12 +43,12 @@ class ReviewCommentDialog extends React.Component {
|
||||
oldIndex: this.props.oldIndex
|
||||
};
|
||||
let detailJSON = JSON.stringify(detail);
|
||||
seafileAPI.addReviewComment(reviewID, comment, detailJSON).then((response) => {
|
||||
seafileAPI.postComment(draftRepoID, draftFilePath, comment, detailJSON).then((response) => {
|
||||
this.props.onCommentAdded();
|
||||
});
|
||||
}
|
||||
else {
|
||||
seafileAPI.addReviewComment(reviewID, comment).then((response) => {
|
||||
seafileAPI.postComment(draftRepoID, draftFilePath, comment).then((response) => {
|
||||
this.props.onCommentAdded();
|
||||
});
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { processor } from '../../utils/seafile-markdown2html';
|
||||
import { Button, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { reviewID, gettext } from '../../utils/constants';
|
||||
import { gettext, draftFilePath, draftRepoID } from '../../utils/constants';
|
||||
import Loading from '../../components/loading.js';
|
||||
import reviewComment from '../../models/review-comment.js';
|
||||
import { username } from '../../utils/constants.js';
|
||||
@@ -24,14 +24,14 @@ class ReviewComments extends React.Component {
|
||||
this.state = {
|
||||
commentsList: [],
|
||||
inResizing: false,
|
||||
commentFooterHeight: 30,
|
||||
commentFooterHeight: 25,
|
||||
showResolvedComment: true,
|
||||
comment: '',
|
||||
};
|
||||
}
|
||||
|
||||
listComments = (scroll) => {
|
||||
seafileAPI.listReviewComments(reviewID).then((response) => {
|
||||
seafileAPI.listComments(draftRepoID, draftFilePath).then((response) => {
|
||||
response.data.comments.reverse();
|
||||
let commentList = [];
|
||||
response.data.comments.forEach(item => {
|
||||
@@ -56,7 +56,7 @@ class ReviewComments extends React.Component {
|
||||
submitComment = () => {
|
||||
let comment = this.state.comment.trim();
|
||||
if (comment.length > 0) {
|
||||
seafileAPI.addReviewComment(reviewID, comment).then((response) => {
|
||||
seafileAPI.postComment(draftRepoID, draftFilePath, comment).then((response) => {
|
||||
this.listComments(true);
|
||||
this.props.getCommentsNumber();
|
||||
});
|
||||
@@ -67,7 +67,7 @@ class ReviewComments extends React.Component {
|
||||
}
|
||||
|
||||
resolveComment = (event) => {
|
||||
seafileAPI.updateReviewComment(reviewID, event.target.id, 'true').then((res) => {
|
||||
seafileAPI.updateComment(draftRepoID, event.target.id, 'true').then((res) => {
|
||||
this.props.getCommentsNumber();
|
||||
this.listComments();
|
||||
});
|
||||
@@ -80,7 +80,7 @@ class ReviewComments extends React.Component {
|
||||
}
|
||||
|
||||
deleteComment = (event) => {
|
||||
seafileAPI.deleteReviewComment(reviewID, event.target.id).then((res) => {
|
||||
seafileAPI.deleteComment(draftRepoID, event.target.id).then((res) => {
|
||||
this.props.getCommentsNumber();
|
||||
this.listComments();
|
||||
});
|
||||
|
@@ -190,7 +190,7 @@
|
||||
width: 1rem;
|
||||
}
|
||||
.review-side-panel .tab-content {
|
||||
height: 100%;
|
||||
height: calc(100% - 38px);
|
||||
}
|
||||
.review-side-panel .tab-content .comments,
|
||||
.review-side-panel .tab-content .tab-pane {
|
@@ -56,6 +56,7 @@
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.history-list-container {
|
||||
|
@@ -84,7 +84,7 @@
|
||||
color: #555;
|
||||
}
|
||||
.seafile-comment-item .seafile-comment-content {
|
||||
margin-top: 10px;
|
||||
margin: 10px 0 0 40px;
|
||||
}
|
||||
.seafile-comment-item .seafile-comment-content ol,
|
||||
.seafile-comment-item .seafile-comment-content ul,
|
||||
@@ -115,7 +115,7 @@
|
||||
.seafile-comment-footer {
|
||||
background-color: #fafaf9;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
min-height: 170px;
|
||||
min-height: 120px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding-top: 0;
|
||||
@@ -130,7 +130,7 @@
|
||||
background-color: #fff;
|
||||
border: 1px solid #e6e6dd;
|
||||
padding: 5px;
|
||||
height: calc(100% - 100px);
|
||||
height: calc(100% - 50px);
|
||||
min-height: 70px;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
|
@@ -5,13 +5,12 @@ import { Button } from 'reactstrap';
|
||||
/* eslint-disable */
|
||||
import Prism from 'prismjs';
|
||||
/* eslint-enable */
|
||||
import { siteRoot, gettext } from './utils/constants';
|
||||
import { siteRoot, gettext, draftOriginFilePath, draftFilePath, author, authorAvatar, originFileExists, draftID, draftFileName, draftRepoID } from './utils/constants';
|
||||
import { seafileAPI } from './utils/seafile-api';
|
||||
import axios from 'axios';
|
||||
import DiffViewer from '@seafile/seafile-editor/dist/viewer/diff-viewer';
|
||||
import { serialize } from '@seafile/seafile-editor/dist/utils/slate2markdown/serialize';
|
||||
import Loading from './components/loading';
|
||||
import toaster from './components/toast';
|
||||
import ReviewComments from './components/review-list-view/review-comments';
|
||||
import ReviewCommentDialog from './components/review-list-view/review-comment-dialog.js';
|
||||
import { Tooltip } from 'reactstrap';
|
||||
@@ -21,6 +20,7 @@ import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap';
|
||||
import classnames from 'classnames';
|
||||
import HistoryList from './pages/review/history-list';
|
||||
import { Value, Document, Block } from 'slate';
|
||||
import ModalPortal from './components/modal-portal';
|
||||
|
||||
import './assets/css/fa-solid.css';
|
||||
import './assets/css/fa-regular.css';
|
||||
@@ -28,10 +28,10 @@ import './assets/css/fontawesome.css';
|
||||
import './css/layout.css';
|
||||
import './css/toolbar.css';
|
||||
import './css/dirent-detail.css';
|
||||
import './css/draft-review.css';
|
||||
import './css/draft.css';
|
||||
|
||||
require('@seafile/seafile-editor/dist/editor/code-hight-package');
|
||||
const { draftID, draftFileName, draftRepoID, draftFilePath, draftOriginFilePath, originFileExists } = window.draft.config;
|
||||
const URL = require('url-parse');
|
||||
|
||||
class Draft extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -44,11 +44,22 @@ class Draft extends React.Component {
|
||||
showDiffTip: false,
|
||||
activeTab: 'reviewInfo',
|
||||
commentsNumber: null,
|
||||
changedNodes: [],
|
||||
originRepoName: '',
|
||||
isShowCommentDialog: false,
|
||||
unresolvedComments: 0,
|
||||
activeItem: null,
|
||||
historyList: [],
|
||||
showReviewerDialog: false,
|
||||
reviewers: [],
|
||||
inResizing: false,
|
||||
rightPartWidth: 30,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initialContent();
|
||||
this.quote = '';
|
||||
this.newIndex = null;
|
||||
this.oldIndex = null;
|
||||
this.changeIndex = -1;
|
||||
this.range = null;
|
||||
}
|
||||
|
||||
initialContent = () => {
|
||||
@@ -68,6 +79,44 @@ class Draft extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const hash = window.location.hash;
|
||||
if (hash.indexOf('#history-') === 0) {
|
||||
const currentCommitID = hash.slice(9, 49);
|
||||
const preCommitID = hash.slice(50, 90);
|
||||
let preItemFilePath, currentItemFilePath;
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
activeTab: 'history',
|
||||
});
|
||||
seafileAPI.listFileHistoryRecords(draftRepoID, draftFilePath, 1, 25).then((res) => {
|
||||
const historyList = res.data.data;
|
||||
this.setState({
|
||||
historyList: historyList,
|
||||
totalReversionCount: res.data.total_count
|
||||
});
|
||||
for (let i = 0, length = historyList.length; i < length; i++) {
|
||||
if (preCommitID === historyList[i].commit_id) {
|
||||
this.setState({
|
||||
activeItem: i
|
||||
});
|
||||
preItemFilePath = historyList[i].path;
|
||||
}
|
||||
if (currentCommitID === historyList[i].commit_id) {
|
||||
currentItemFilePath = historyList[i].path;
|
||||
}
|
||||
if (preItemFilePath && currentItemFilePath) break;
|
||||
}
|
||||
axios.all([
|
||||
seafileAPI.getFileRevision(draftRepoID, currentCommitID, currentItemFilePath),
|
||||
seafileAPI.getFileRevision(draftRepoID, preCommitID, preItemFilePath)
|
||||
]).then(axios.spread((res1, res2) => {
|
||||
axios.all([seafileAPI.getFileContent(res1.data), seafileAPI.getFileContent(res2.data)]).then(axios.spread((content1, content2) => {
|
||||
this.setDiffViewerContent(content2.data, content1.data);
|
||||
}));
|
||||
}));
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
axios.all([
|
||||
seafileAPI.getFileDownloadLink(draftRepoID, draftFilePath),
|
||||
seafileAPI.getFileDownloadLink(draftRepoID, draftOriginFilePath)
|
||||
@@ -81,9 +130,180 @@ class Draft extends React.Component {
|
||||
draftOriginContent: draftOriginContent.data,
|
||||
isLoading: false
|
||||
});
|
||||
let that = this;
|
||||
setTimeout(() => {
|
||||
that.getChangedNodes();
|
||||
}, 100);
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
onHistoryItemClick = (currentItem, preItem, activeItem) => {
|
||||
const preCommitID = preItem.commit_id;
|
||||
const currentCommitID = currentItem.commit_id;
|
||||
const url = 'history-' + preCommitID + '-' + currentCommitID;
|
||||
this.setURL(url);
|
||||
this.setState({
|
||||
activeItem: activeItem
|
||||
});
|
||||
axios.all([
|
||||
seafileAPI.getFileRevision(draftRepoID, currentCommitID, currentItem.path),
|
||||
seafileAPI.getFileRevision(draftRepoID, preCommitID, preItem.path)
|
||||
]).then(axios.spread((res1, res2) => {
|
||||
axios.all([seafileAPI.getFileContent(res1.data), seafileAPI.getFileContent(res2.data)]).then(axios.spread((content1,content2) => {
|
||||
this.setDiffViewerContent(content1.data, content2.data);
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
onHistoryListChange = (historyList) => {
|
||||
this.setState({
|
||||
historyList: historyList
|
||||
});
|
||||
}
|
||||
|
||||
getCommentsNumber = () => {
|
||||
seafileAPI.listComments(draftRepoID, draftFilePath).then((res) => {
|
||||
let number = res.data.total_count;
|
||||
let comments = res.data.comments;
|
||||
let unresolvedComments = 0;
|
||||
for (let i = 0; i < res.data.total_count; i++) {
|
||||
if (comments[i].resolved === false) {
|
||||
unresolvedComments++;
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
commentsNumber: number,
|
||||
unresolvedComments: unresolvedComments,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addComment = (e) => {
|
||||
e.stopPropagation();
|
||||
this.getQuote();
|
||||
if (!this.quote) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
isShowCommentDialog: true
|
||||
});
|
||||
}
|
||||
|
||||
onCommentAdded = () => {
|
||||
this.getCommentsNumber();
|
||||
this.toggleCommentDialog();
|
||||
}
|
||||
|
||||
toggleCommentDialog = () => {
|
||||
this.setState({
|
||||
isShowCommentDialog: !this.state.isShowCommentDialog
|
||||
});
|
||||
}
|
||||
|
||||
getOriginRepoInfo = () => {
|
||||
seafileAPI.getRepoInfo(draftRepoID).then((res) => {
|
||||
this.setState({
|
||||
originRepoName: res.data.repo_name
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getChangedNodes = () => {
|
||||
const nodes = this.refs.diffViewer.value.document.nodes;
|
||||
let keys = [];
|
||||
let lastDiffState = '';
|
||||
nodes.map((node) => {
|
||||
if (node.data.get('diff_state') === 'diff-added' && lastDiffState !== 'diff-added') {
|
||||
keys.push(node.key);
|
||||
} else if (node.data.get('diff_state') === 'diff-removed' && lastDiffState !== 'diff-removed') {
|
||||
keys.push(node.key);
|
||||
} else if (node.data.get('diff_state') === 'diff-replaced' && lastDiffState !== 'diff-replaced') {
|
||||
keys.push(node.key);
|
||||
}
|
||||
lastDiffState = node.data.get('diff_state');
|
||||
});
|
||||
this.setState({
|
||||
changedNodes: keys
|
||||
});
|
||||
}
|
||||
|
||||
scrollToChangedNode = (scroll) => {
|
||||
if (this.state.changedNodes.length == 0) return;
|
||||
if (scroll === 'up') { this.changeIndex++; } else { this.changeIndex--; }
|
||||
if (this.changeIndex > this.state.changedNodes.length - 1) {
|
||||
this.changeIndex = 0;
|
||||
}
|
||||
if (this.changeIndex < 0) {
|
||||
this.changeIndex = this.state.changedNodes.length - 1;
|
||||
}
|
||||
const win = window;
|
||||
let key = this.state.changedNodes[this.changeIndex];
|
||||
let element = win.document.querySelector(`[data-key="${key}"]`);
|
||||
// fix code-block or tables
|
||||
while (element.className.indexOf('diff-') === -1 && element.tagName !== 'BODY') {
|
||||
element = element.parentNode;
|
||||
}
|
||||
const scroller = this.findScrollContainer(element, win);
|
||||
const isWindow = scroller == win.document.body || scroller == win.document.documentElement;
|
||||
if (isWindow) {
|
||||
win.scrollTo(0, element.offsetTop);
|
||||
} else {
|
||||
scroller.scrollTop = element.offsetTop;
|
||||
}
|
||||
}
|
||||
|
||||
findScrollContainer = (element, window) => {
|
||||
let parent = element.parentNode;
|
||||
const OVERFLOWS = ['auto', 'overlay', 'scroll'];
|
||||
let scroller;
|
||||
while (!scroller) {
|
||||
if (!parent.parentNode) break;
|
||||
const style = window.getComputedStyle(parent);
|
||||
const { overflowY } = style;
|
||||
if (OVERFLOWS.includes(overflowY)) {
|
||||
scroller = parent;
|
||||
break;
|
||||
}
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
if (!scroller) {
|
||||
return window.document.body;
|
||||
}
|
||||
return scroller;
|
||||
}
|
||||
|
||||
scrollToQuote = (newIndex, oldIndex, quote) => {
|
||||
const nodes = this.refs.diffViewer.value.document.nodes;
|
||||
let key;
|
||||
nodes.map((node) => {
|
||||
if (node.data.get('old_index') == oldIndex && node.data.get('new_index') == newIndex) {
|
||||
key = node.key;
|
||||
}
|
||||
});
|
||||
if (typeof(key) !== 'string') {
|
||||
nodes.map((node) => {
|
||||
if (node.text.indexOf(quote) > 0) {
|
||||
key = node.key;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (typeof(key) === 'string') {
|
||||
const win = window;
|
||||
let element = win.document.querySelector(`[data-key="${key}"]`);
|
||||
while (element.tagName === 'CODE') {
|
||||
element = element.parentNode;
|
||||
}
|
||||
const scroller = this.findScrollContainer(element, win);
|
||||
const isWindow = scroller == win.document.body || scroller == win.document.documentElement;
|
||||
if (isWindow) {
|
||||
win.scrollTo(0, element.offsetTop);
|
||||
} else {
|
||||
scroller.scrollTop = element.offsetTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showDiffViewer = () => {
|
||||
return (
|
||||
@@ -100,11 +320,26 @@ class Draft extends React.Component {
|
||||
ref="diffViewer"
|
||||
/>
|
||||
}
|
||||
<i className="fa fa-plus-square review-comment-btn" ref="commentbtn" onMouseDown={this.addComment}></i>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
listReviewers = () => {
|
||||
seafileAPI.listDraftReviewers(draftID).then((res) => {
|
||||
this.setState({
|
||||
reviewers: res.data.reviewers
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onSwitchShowDiff = () => {
|
||||
if (!this.state.isShowDiff) {
|
||||
let that = this;
|
||||
setTimeout(() => {
|
||||
that.getChangedNodes();
|
||||
}, 100);
|
||||
}
|
||||
this.setState({
|
||||
isShowDiff: !this.state.isShowDiff,
|
||||
});
|
||||
@@ -116,6 +351,14 @@ class Draft extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
toggleAddReviewerDialog = () => {
|
||||
if (this.state.showReviewerDialog) {
|
||||
this.listReviewers();
|
||||
}
|
||||
this.setState({
|
||||
showReviewerDialog: !this.state.showReviewerDialog
|
||||
});
|
||||
}
|
||||
|
||||
showDiffButton = () => {
|
||||
return (
|
||||
@@ -137,18 +380,70 @@ class Draft extends React.Component {
|
||||
const OriginFileLink = siteRoot + 'lib/' + draftRepoID + '/file' + draftOriginFilePath + '/';
|
||||
seafileAPI.publishDraft(draftID).then(res => {
|
||||
window.location.href = OriginFileLink;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
initialDiffViewerContent = () => {
|
||||
seafileAPI.listFileHistoryRecords(draftRepoID, draftFilePath, 1, 25).then((res) => {
|
||||
this.setState({
|
||||
historyList: res.data.data,
|
||||
totalReversionCount: res.data.total_count
|
||||
});
|
||||
if (res.data.data.length > 1) {
|
||||
axios.all([
|
||||
seafileAPI.getFileRevision(draftRepoID, res.data.data[0].commit_id, draftFilePath),
|
||||
seafileAPI.getFileRevision(draftRepoID, res.data.data[1].commit_id, draftFilePath)
|
||||
]).then(axios.spread((res1, res2) => {
|
||||
axios.all([seafileAPI.getFileContent(res1.data), seafileAPI.getFileContent(res2.data)]).then(axios.spread((content1,content2) => {
|
||||
this.setState({
|
||||
draftContent: content1.data,
|
||||
draftOriginContent: content2.data
|
||||
});
|
||||
}));
|
||||
}));
|
||||
} else {
|
||||
seafileAPI.getFileRevision(draftRepoID, res.data.data[0].commit_id, draftFilePath).then((res) => {
|
||||
seafileAPI.getFileContent(res.data).then((content) => {
|
||||
this.setState({
|
||||
draftContent: content.data,
|
||||
draftOriginContent: ''
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDiffViewerContent = (newContent, prevContent) => {
|
||||
this.setState({
|
||||
draftContent: newContent,
|
||||
draftOriginContent: prevContent
|
||||
});
|
||||
}
|
||||
|
||||
setURL = (newurl) => {
|
||||
let url = new URL(window.location.href);
|
||||
url.set('hash', newurl);
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
|
||||
tabItemClick = (tab) => {
|
||||
if (this.state.activeTab !== tab) {
|
||||
if (tab !== 'history' && window.location.hash) {
|
||||
this.setURL('#');
|
||||
}
|
||||
if (tab == 'reviewInfo') {
|
||||
this.initialContent();
|
||||
}
|
||||
else if (tab == 'history') {
|
||||
this.initialDiffViewerContent();
|
||||
}
|
||||
this.setState({
|
||||
activeTab: tab
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
showNavItem = (showTab) => {
|
||||
switch(showTab) {
|
||||
case 'info':
|
||||
@@ -198,9 +493,174 @@ class Draft extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
setBtnPosition = (e) => {
|
||||
const nativeSelection = window.getSelection();
|
||||
if (!nativeSelection.rangeCount) {
|
||||
this.range = null;
|
||||
return;
|
||||
}
|
||||
if (nativeSelection.isCollapsed === false) {
|
||||
const nativeRange = nativeSelection.getRangeAt(0);
|
||||
const focusNode = nativeSelection.focusNode;
|
||||
if ((focusNode.tagName === 'I') ||
|
||||
(focusNode.nodeType !== 3 && focusNode.getAttribute('class') === 'language-type')) {
|
||||
// fix select last paragraph
|
||||
let fragment = nativeRange.cloneContents();
|
||||
let startNode = fragment.firstChild.firstChild;
|
||||
if (!startNode) {
|
||||
return;
|
||||
}
|
||||
let newNativeRange = document.createRange();
|
||||
newNativeRange.setStartBefore(startNode);
|
||||
newNativeRange.setEndAfter(startNode);
|
||||
this.range = findRange(newNativeRange, this.refs.diffViewer.value);
|
||||
}
|
||||
else {
|
||||
this.range = findRange(nativeRange, this.refs.diffViewer.value);
|
||||
}
|
||||
if (!this.range) {
|
||||
return;
|
||||
}
|
||||
let rect = nativeRange.getBoundingClientRect();
|
||||
// fix Safari bug
|
||||
if (navigator.userAgent.indexOf('Chrome') < 0 && navigator.userAgent.indexOf('Safari') > 0) {
|
||||
if (nativeRange.collapsed && rect.top == 0 && rect.height == 0) {
|
||||
if (nativeRange.startOffset == 0) {
|
||||
nativeRange.setEnd(nativeRange.endContainer, 1);
|
||||
} else {
|
||||
nativeRange.setStart(nativeRange.startContainer, nativeRange.startOffset - 1);
|
||||
}
|
||||
rect = nativeRange.getBoundingClientRect();
|
||||
if (rect.top == 0 && rect.height == 0) {
|
||||
if (nativeRange.getClientRects().length) {
|
||||
rect = nativeRange.getClientRects()[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let style = this.refs.commentbtn.style;
|
||||
style.top = `${rect.top - 100 + this.refs.viewContent.scrollTop}px`;
|
||||
}
|
||||
else {
|
||||
let style = this.refs.commentbtn.style;
|
||||
style.top = '-1000px';
|
||||
}
|
||||
}
|
||||
|
||||
getQuote = () => {
|
||||
let range = this.range;
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
this.quote = '';
|
||||
const { document } = this.refs.diffViewer.value;
|
||||
let { anchor, focus } = range;
|
||||
const anchorText = document.getNode(anchor.key);
|
||||
const focusText = document.getNode(focus.key);
|
||||
const anchorInline = document.getClosestInline(anchor.key);
|
||||
const focusInline = document.getClosestInline(focus.key);
|
||||
// COMPAT: If the selection is at the end of a non-void inline node, and
|
||||
// there is a node after it, put it in the node after instead. This
|
||||
// standardizes the behavior, since it's indistinguishable to the user.
|
||||
if (anchorInline && anchor.offset == anchorText.text.length) {
|
||||
const block = document.getClosestBlock(anchor.key);
|
||||
const nextText = block.getNextText(anchor.key);
|
||||
if (nextText) {
|
||||
range = range.moveAnchorTo(nextText.key, 0);
|
||||
}
|
||||
}
|
||||
if (focusInline && focus.offset == focusText.text.length) {
|
||||
const block = document.getClosestBlock(focus.key);
|
||||
const nextText = block.getNextText(focus.key);
|
||||
if (nextText) {
|
||||
range = range.moveFocusTo(nextText.key, 0);
|
||||
}
|
||||
}
|
||||
let fragment = document.getFragmentAtRange(range);
|
||||
let nodes = this.removeNullNode(fragment.nodes);
|
||||
let newFragment = Document.create({
|
||||
nodes: nodes
|
||||
});
|
||||
let newValue = Value.create({
|
||||
document: newFragment
|
||||
});
|
||||
this.quote = serialize(newValue.toJSON());
|
||||
let blockPath = document.createSelection(range).anchor.path.slice(0, 1);
|
||||
let node = document.getNode(blockPath);
|
||||
this.newIndex = node.data.get('new_index');
|
||||
this.oldIndex = node.data.get('old_index');
|
||||
}
|
||||
|
||||
removeNullNode = (oldNodes) => {
|
||||
let newNodes = [];
|
||||
oldNodes.map((node) => {
|
||||
const text = node.text.trim();
|
||||
const childNodes = node.nodes;
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
if ((childNodes && childNodes.size === 1) || (!childNodes)) {
|
||||
newNodes.push(node);
|
||||
}
|
||||
else if (childNodes.size > 1) {
|
||||
let nodes = this.removeNullNode(childNodes);
|
||||
let newNode = Block.create({
|
||||
nodes: nodes,
|
||||
data: node.data,
|
||||
key: node.key,
|
||||
type: node.type
|
||||
});
|
||||
newNodes.push(newNode);
|
||||
}
|
||||
});
|
||||
return newNodes;
|
||||
}
|
||||
|
||||
onResizeMouseUp = () => {
|
||||
if (this.state.inResizing) {
|
||||
this.setState({
|
||||
inResizing: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onResizeMouseDown = () => {
|
||||
this.setState({
|
||||
inResizing: true
|
||||
});
|
||||
};
|
||||
|
||||
onResizeMouseMove = (e) => {
|
||||
let rate = 100 - e.nativeEvent.clientX / this.refs.main.clientWidth * 100;
|
||||
if (rate < 20 || rate > 60) {
|
||||
this.setState({
|
||||
inResizing: false
|
||||
});
|
||||
return null;
|
||||
}
|
||||
this.setState({
|
||||
rightPartWidth: rate
|
||||
});
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.getCommentsNumber();
|
||||
this.listReviewers();
|
||||
this.getOriginRepoInfo();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initialContent();
|
||||
document.addEventListener('selectionchange', this.setBtnPosition);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('selectionchange', this.setBtnPosition);
|
||||
}
|
||||
|
||||
render() {
|
||||
const onResizeMove = this.state.inResizing ? this.onResizeMouseMove : null;
|
||||
const draftLink = siteRoot + 'lib/' + draftRepoID + '/file' + draftFilePath + '?mode=edit';
|
||||
const OriginFileLink = siteRoot + 'lib/' + draftRepoID + '/file' + draftOriginFilePath + '/';
|
||||
return(
|
||||
<div className="wrapper">
|
||||
<div id="header" className="header review">
|
||||
@@ -226,10 +686,9 @@ class Draft extends React.Component {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main" className="main">
|
||||
<div className="cur-view-container">
|
||||
<div className='cur-view-content'>
|
||||
<div id="main" className="main" ref="main">
|
||||
<div className="cur-view-container" onMouseMove={onResizeMove} onMouseUp={this.onResizeMouseUp} >
|
||||
<div className='cur-view-content' ref="viewContent" style={{width:(100 - this.state.rightPartWidth) + '%'}}>
|
||||
{this.state.isLoading ?
|
||||
<div className="markdown-viewer-render-content article">
|
||||
<Loading />
|
||||
@@ -240,31 +699,213 @@ class Draft extends React.Component {
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="cur-view-right-part">
|
||||
<div className="seafile-comment-resize"></div>
|
||||
<div className="cur-view-right-part" style={{width:(this.state.rightPartWidth) + '%'}}>
|
||||
<div className="seafile-comment-resize" onMouseDown={this.onResizeMouseDown}></div>
|
||||
<div className="review-side-panel">
|
||||
{this.renderNavItems()}
|
||||
<TabContent activeTab={this.state.activeTab}>
|
||||
<TabPane tabId="reviewInfo">
|
||||
review info
|
||||
<div className="review-side-panel-body">
|
||||
<SidePanelReviewers
|
||||
reviewers={this.state.reviewers}
|
||||
toggleAddReviewerDialog={this.toggleAddReviewerDialog}/>
|
||||
<SidePanelAuthor/>
|
||||
<UnresolvedComments number={this.state.unresolvedComments}/>
|
||||
{(this.state.isShowDiff === true && this.state.changedNodes.length > 0) &&
|
||||
<SidePanelChanges
|
||||
changedNumber={this.state.changedNodes.length}
|
||||
scrollToChangedNode={this.scrollToChangedNode}/>
|
||||
}
|
||||
<SidePanelOrigin originRepoName={this.state.originRepoName}/>
|
||||
<a href={draftLink}><Button color="secondary">{gettext('Edit Draft')}</Button></a>
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane tabId="comments" className="comments">
|
||||
comments
|
||||
<ReviewComments
|
||||
scrollToQuote={this.scrollToQuote}
|
||||
getCommentsNumber={this.getCommentsNumber}
|
||||
commentsNumber={this.state.commentsNumber}
|
||||
inResizing={false}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tabId="history" className="history">
|
||||
history list
|
||||
<HistoryList
|
||||
activeItem={this.state.activeItem}
|
||||
historyList={this.state.historyList}
|
||||
totalReversionCount={this.state.totalReversionCount}
|
||||
onHistoryItemClick={this.onHistoryItemClick}
|
||||
onHistoryListChange={this.onHistoryListChange}
|
||||
/>
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ this.state.showReviewerDialog &&
|
||||
<ModalPortal>
|
||||
<AddReviewerDialog
|
||||
showReviewerDialog={this.state.showReviewerDialog}
|
||||
toggleAddReviewerDialog={this.toggleAddReviewerDialog}
|
||||
draftID={draftID}
|
||||
reviewers={this.state.reviewers}
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
{this.state.isShowCommentDialog &&
|
||||
<ModalPortal>
|
||||
<ReviewCommentDialog
|
||||
toggleCommentDialog={this.toggleCommentDialog}
|
||||
onCommentAdded={this.onCommentAdded}
|
||||
quote={this.quote}
|
||||
draftID={draftID}
|
||||
newIndex={this.newIndex}
|
||||
oldIndex={this.oldIndex}
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SidePanelReviewers extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="review-side-panel-item">
|
||||
<div className="review-side-panel-header">{gettext('Reviewers')}
|
||||
<i className="fa fa-cog" onClick={this.props.toggleAddReviewerDialog}></i>
|
||||
</div>
|
||||
{ this.props.reviewers.length > 0 ?
|
||||
this.props.reviewers.map((item, index = 0, arr) => {
|
||||
return (
|
||||
<div className="reviewer-info" key={index}>
|
||||
<img className="avatar review-side-panel-avatar" src={item.avatar_url} alt=""/>
|
||||
<span className="reviewer-name">{item.user_name}</span>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
:
|
||||
<span>{gettext('No reviewer yet.')}</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sidePanelReviewersPropTypes = {
|
||||
reviewers: PropTypes.array.isRequired,
|
||||
toggleAddReviewerDialog: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
SidePanelReviewers.propTypes = sidePanelReviewersPropTypes;
|
||||
|
||||
class SidePanelAuthor extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="review-side-panel-item">
|
||||
<div className="review-side-panel-header">{gettext('Author')}</div>
|
||||
<div className="author-info">
|
||||
<img className="avatar review-side-panel-avatar" src={authorAvatar} alt=""/>
|
||||
<span className="author-name">{author}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SidePanelOrigin extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="dirent-table-container">
|
||||
<table className="table-thead-hidden">
|
||||
<thead>
|
||||
<tr><th width="25%"></th><th width="75%"></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>{gettext('Location')}</th><td>{this.props.originRepoName}{draftOriginFilePath}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SidePanelOriginPropTypes = {
|
||||
originRepoName: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
SidePanelOrigin.propTypes = SidePanelOriginPropTypes;
|
||||
|
||||
|
||||
class UnresolvedComments extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="review-side-panel-item">
|
||||
<div className="review-side-panel-header">{gettext('Comments')}</div>
|
||||
<div className="changes-info">
|
||||
<span>{gettext('Unresolved comments:')}{' '}{this.props.number}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const UnresolvedCommentsPropTypes = {
|
||||
number: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
UnresolvedComments.propTypes = UnresolvedCommentsPropTypes;
|
||||
|
||||
|
||||
class SidePanelChanges extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="review-side-panel-item">
|
||||
<div className="review-side-panel-header">{gettext('Changes')}</div>
|
||||
<div className="changes-info">
|
||||
<span>{gettext('Number of changes:')}{' '}{this.props.changedNumber}</span>
|
||||
{ this.props.changedNumber > 0 &&
|
||||
<div>
|
||||
<i className="fa fa-arrow-circle-up" onClick={() => { this.props.scrollToChangedNode('down');}}></i>
|
||||
<i className="fa fa-arrow-circle-down" onClick={() => { this.props.scrollToChangedNode('up');}}></i>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sidePanelChangesPropTypes = {
|
||||
changedNumber: PropTypes.number.isRequired,
|
||||
scrollToChangedNode: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
SidePanelChanges.propTypes = sidePanelChangesPropTypes;
|
||||
|
||||
|
||||
ReactDOM.render (
|
||||
<Draft />,
|
||||
document.getElementById('wrapper')
|
||||
|
@@ -21,9 +21,9 @@ class HistoryList extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
onClick = (event, key, preCommitID, currentCommitID)=> {
|
||||
onClick = (event, key, preItem, currentItem)=> {
|
||||
if (key === this.state.activeItem) return false;
|
||||
this.props.onHistoryItemClick(currentCommitID, preCommitID, key);
|
||||
this.props.onHistoryItemClick(currentItem, preItem, key);
|
||||
}
|
||||
|
||||
onScroll = (event) => {
|
||||
@@ -51,7 +51,7 @@ class HistoryList extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="history-body" style={{ 'height': '500px'}}>
|
||||
<div className="history-body">
|
||||
<ul onScroll={this.onScroll} className={'history-list-container'}>
|
||||
{
|
||||
this.props.historyList ?
|
||||
@@ -65,11 +65,11 @@ class HistoryList extends React.Component {
|
||||
onClick={this.onClick}
|
||||
ctime={item.ctime}
|
||||
className={this.props.activeItem === index ? 'item-active': ''}
|
||||
currentCommitId={item.commit_id}
|
||||
name={item.creator_name}
|
||||
index={index}
|
||||
key={index}
|
||||
preCommitId={arr[preItemIndex].commit_id}
|
||||
preItem={arr[preItemIndex]}
|
||||
currentItem={item}
|
||||
/>
|
||||
);
|
||||
}) : <Loading/>
|
||||
@@ -88,7 +88,7 @@ class HistoryItem extends React.Component {
|
||||
render() {
|
||||
let time = moment.parseZone(this.props.ctime).format('YYYY-MM-DD HH:mm');
|
||||
return (
|
||||
<li onClick={(event) => this.props.onClick(event, this.props.index, this.props.preCommitId, this.props.currentCommitId)} className={'history-list-item ' + this.props.className}>
|
||||
<li onClick={(event) => this.props.onClick(event, this.props.index, this.props.preItem, this.props.currentItem)} className={'history-list-item ' + this.props.className}>
|
||||
<div className="history-info">
|
||||
<div className="time">{time}</div>
|
||||
<div className="owner"><i className="squire-icon"/><span>{this.props.name}</span></div>
|
||||
|
@@ -61,20 +61,14 @@ export const filePath = window.fileHistory ? window.fileHistory.pageOptions.file
|
||||
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 draftID = window.draftReview ? window.draftReview.config.draftID : '';
|
||||
export const opStatus = window.draftReview ? window.draftReview.config.opStatus : '';
|
||||
export const reviewPerm = window.draftReview ? window.draftReview.config.perm : '';
|
||||
export const publishFileVersion = window.draftReview ? window.draftReview.config.publishFileVersion : '';
|
||||
export const originFileVersion = window.draftReview ? window.draftReview.config.originFileVersion : '';
|
||||
export const author = window.draftReview ? window.draftReview.config.author : '';
|
||||
export const authorAvatar = window.draftReview ? window.draftReview.config.authorAvatar : '';
|
||||
export const originFileExists = window.draftReview ? window.draftReview.config.originFileExists : '';
|
||||
export const draftFileExists = window.draftReview ? window.draftReview.config.draftFileExists : '';
|
||||
export const draftFilePath = window.draft ? window.draft.config.draftFilePath: '';
|
||||
export const draftOriginFilePath = window.draft ? window.draft.config.draftOriginFilePath: '';
|
||||
export const draftFileName = window.draft ? window.draft.config.draftFileName: '';
|
||||
export const draftID = window.draft ? window.draft.config.draftID : '';
|
||||
export const draftRepoID = window.draft ? window.draft.config.draftRepoID : '';
|
||||
export const author = window.draft ? window.draft.config.author : '';
|
||||
export const authorAvatar = window.draft ? window.draft.config.authorAvatar : '';
|
||||
export const originFileExists = window.draft ? window.draft.config.originFileExists : '';
|
||||
|
||||
// org admin
|
||||
export const orgID = window.org ? window.org.pageOptions.orgID : '';
|
||||
|
@@ -16,6 +16,7 @@ from seahub.api2.utils import api_error, user_to_dict
|
||||
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||
from seahub.base.accounts import User
|
||||
from seahub.tags.models import FileUUIDMap
|
||||
from seahub.views import check_folder_permission
|
||||
from seahub.utils import is_valid_username
|
||||
from seahub.drafts.models import Draft, DraftReviewer
|
||||
@@ -42,7 +43,7 @@ class DraftReviewerView(APIView):
|
||||
|
||||
# get reviewer list
|
||||
reviewers = []
|
||||
for x in d.reviewreviewer_set.all():
|
||||
for x in d.draftreviewer_set.all():
|
||||
reviewer = user_to_dict(x.reviewer, request=request, avatar_size=avatar_size)
|
||||
reviewers.append(reviewer)
|
||||
|
||||
@@ -88,7 +89,7 @@ class DraftReviewerView(APIView):
|
||||
})
|
||||
continue
|
||||
|
||||
uuid = d.origin_file_uuid
|
||||
uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid(d.origin_file_uuid)
|
||||
origin_file_path = posixpath.join(uuid.parent_path, uuid.filename)
|
||||
# check perm
|
||||
if seafile_api.check_permission_by_path(d.origin_repo_id, origin_file_path, reviewer) != 'rw':
|
||||
|
Reference in New Issue
Block a user