mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-31 14:42:10 +00:00
Add draft comment button (#2536)
This commit is contained in:
@@ -3,7 +3,9 @@ import ReactDOM from 'react-dom';
|
||||
/* eslint-disable */
|
||||
import Prism from 'prismjs';
|
||||
/* eslint-enable */
|
||||
import { siteRoot, gettext, draftID, reviewID, draftOriginFilePath, draftFilePath, draftOriginRepoID, draftFileName, opStatus, publishFileVersion, originFileVersion, author, authorAvatar } from './utils/constants';
|
||||
import { siteRoot, gettext, reviewID, draftOriginFilePath, draftFilePath, draftOriginRepoID,
|
||||
draftFileName, opStatus, publishFileVersion, originFileVersion, author, authorAvatar
|
||||
} from './utils/constants';
|
||||
import { seafileAPI } from './utils/seafile-api';
|
||||
import axios from 'axios';
|
||||
import DiffViewer from '@seafile/seafile-editor/dist/viewer/diff-viewer';
|
||||
@@ -12,6 +14,7 @@ import Toast from './components/toast';
|
||||
import ReviewComments from './components/review-list-view/review-comments';
|
||||
import { Tooltip } from 'reactstrap';
|
||||
import AddReviewerDialog from './components/dialog/add-reviewer-dialog.js';
|
||||
import { findRange } from '@seafile/slate-react';
|
||||
|
||||
import 'seafile-ui';
|
||||
import './assets/css/fa-solid.css';
|
||||
@@ -41,6 +44,9 @@ class DraftReview extends React.Component {
|
||||
showReviewerDialog: false,
|
||||
reviewers: [],
|
||||
};
|
||||
this.selectedText = '';
|
||||
this.newIndex = null;
|
||||
this.oldIndex = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -74,6 +80,11 @@ class DraftReview extends React.Component {
|
||||
});
|
||||
}));
|
||||
}
|
||||
document.addEventListener('selectionchange', this.setBtnPosition);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('selectionchange', this.setBtnPosition);
|
||||
}
|
||||
|
||||
onCloseReview = () => {
|
||||
@@ -171,6 +182,138 @@ class DraftReview extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
setBtnPosition = (e) => {
|
||||
const nativeSelection = window.getSelection();
|
||||
if (!nativeSelection.rangeCount) {
|
||||
return;
|
||||
}
|
||||
if (nativeSelection.isCollapsed === false) {
|
||||
const nativeRange = nativeSelection.getRangeAt(0);
|
||||
let range = findRange(nativeRange, this.refs.diffViewer.value);
|
||||
if (!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 - 113 + this.refs.viewContent.scrollTop}px`;
|
||||
style.right = `${this.refs.viewContent.clientWidth - rect.x - 70}px`;
|
||||
return range;
|
||||
}
|
||||
else {
|
||||
let style = this.refs.commentbtn.style;
|
||||
style.top = '0px';
|
||||
style.right = '5000px';
|
||||
}
|
||||
}
|
||||
|
||||
addComment = (e) => {
|
||||
e.stopPropagation();
|
||||
let range = this.setBtnPosition();
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
const focusBlock = document.getClosestBlock(focus.key);
|
||||
const anchorBlock = document.getClosestBlock(anchor.key);
|
||||
if (anchorBlock && anchor.offset == 0 && focusBlock && focus.offset != 0) {
|
||||
range = range.setFocus(focus.setOffset(0));
|
||||
}
|
||||
// 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 blockPath = document.createSelection(range).anchor.path.slice(0, 1);
|
||||
let node = document.getNode(blockPath);
|
||||
this.selectedText = window.getSelection().toString().trim();
|
||||
this.newIndex = node.data.get('new_index');
|
||||
this.oldIndex = node.data.get('old_index');
|
||||
this.setState({
|
||||
isShowComments: true
|
||||
});
|
||||
}
|
||||
|
||||
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, selectedText) => {
|
||||
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(selectedText) > 0) {
|
||||
key = node.key;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (typeof(key) === 'string') {
|
||||
const win = window;
|
||||
const element = win.document.querySelector(`[data-key="${key}"]`);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.getCommentsNumber();
|
||||
this.listReviewers();
|
||||
@@ -194,7 +337,7 @@ class DraftReview extends React.Component {
|
||||
{ opStatus == 'open' && <a href={draftLink} className="draft-link">{gettext('Edit draft')}</a>}
|
||||
{ opStatus !== 'open' && <a href={OriginFileLink} className="view-file-link">{gettext('View File')}</a>}
|
||||
</React.Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="button-group">
|
||||
<div className={'seafile-toggle-diff'}>
|
||||
@@ -239,18 +382,28 @@ class DraftReview extends React.Component {
|
||||
<div className="cur-view-container content-container"
|
||||
onMouseMove={onResizeMove} onMouseUp={this.onResizeMouseUp} ref="comment">
|
||||
<div style={{width:(100-this.state.commentWidth)+'%'}}
|
||||
className={!this.state.isShowComments ? 'cur-view-content' : 'cur-view-content cur-view-content-commenton'} >
|
||||
className={!this.state.isShowComments ? 'cur-view-content' : 'cur-view-content cur-view-content-commenton'} ref="viewContent">
|
||||
{this.state.isLoading ?
|
||||
<div className="markdown-viewer-render-content article">
|
||||
<Loading />
|
||||
</div>
|
||||
:
|
||||
<div className="markdown-viewer-render-content article">
|
||||
<div className="markdown-viewer-render-content article" ref="mainPanel">
|
||||
{this.state.isShowDiff ?
|
||||
<DiffViewer newMarkdownContent={this.state.draftContent} oldMarkdownContent={this.state.draftOriginContent} />
|
||||
<DiffViewer
|
||||
newMarkdownContent={this.state.draftContent}
|
||||
oldMarkdownContent={this.state.draftOriginContent}
|
||||
ref="diffViewer"
|
||||
/>
|
||||
:
|
||||
<DiffViewer newMarkdownContent={this.state.draftContent} oldMarkdownContent={this.state.draftContent} />
|
||||
<DiffViewer
|
||||
newMarkdownContent={this.state.draftContent}
|
||||
oldMarkdownContent={this.state.draftContent}
|
||||
ref="diffViewer"
|
||||
/>
|
||||
}
|
||||
<i className="fa fa-comments review-comment-btn"
|
||||
ref="commentbtn" onMouseDown={this.addComment}></i>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -259,9 +412,13 @@ class DraftReview extends React.Component {
|
||||
<div className="seafile-comment-resize" onMouseDown={this.onResizeMouseDown}></div>
|
||||
<ReviewComments
|
||||
toggleCommentList={this.toggleCommentList}
|
||||
commentsNumber={this.state.commentsNumber}
|
||||
scrollToQuote={this.scrollToQuote}
|
||||
getCommentsNumber={this.getCommentsNumber}
|
||||
commentsNumber={this.state.commentsNumber}
|
||||
inResizing={this.state.inResizing}
|
||||
selectedText={this.selectedText}
|
||||
newIndex={this.newIndex}
|
||||
oldIndex={this.oldIndex}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
Reference in New Issue
Block a user