From db64e5a18efaad9b4303b817a79deb5d25e0b51c Mon Sep 17 00:00:00 2001 From: Michael An <2331806369@qq.com> Date: Fri, 15 Mar 2019 12:11:32 +0800 Subject: [PATCH] change viewer (#3115) --- .../components/markdown-view/comments-list.js | 305 ------------------ .../components/markdown-view/history-list.js | 7 + .../markdown-viewer-side-panel.js | 164 ---------- .../src/components/markdown-view/outline.js | 4 +- .../toolbar/markdown-viewer-toolbar.js | 17 + frontend/src/css/comments-list.css | 12 +- .../src/css/markdown-viewer/comments-list.css | 138 -------- .../css/markdown-viewer/markdown-editor.css | 125 +++---- frontend/src/markdown-editor.js | 156 +++++++-- 9 files changed, 208 insertions(+), 720 deletions(-) delete mode 100644 frontend/src/components/markdown-view/comments-list.js delete mode 100644 frontend/src/components/markdown-view/markdown-viewer-side-panel.js delete mode 100644 frontend/src/css/markdown-viewer/comments-list.css diff --git a/frontend/src/components/markdown-view/comments-list.js b/frontend/src/components/markdown-view/comments-list.js deleted file mode 100644 index 0531ea6d7e..0000000000 --- a/frontend/src/components/markdown-view/comments-list.js +++ /dev/null @@ -1,305 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { processor } from '@seafile/seafile-editor/dist/utils/seafile-markdown2html'; -import { Button, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; -import Loading from '../loading'; -import { gettext } from '../../utils/constants'; -import moment from 'moment'; -import '../../css/markdown-viewer/comments-list.css'; - -const propTypes = { - editorUtilities: PropTypes.object.isRequired, - scrollToQuote: PropTypes.func.isRequired, - getCommentsNumber: PropTypes.func.isRequired, - commentsNumber: PropTypes.number.isRequired, -}; - -class CommentsList extends React.Component { - - constructor(props) { - super(props); - this.state = { - commentsList: [], - showResolvedComment: true, - }; - } - - listComments = () => { - this.props.editorUtilities.listComments().then((response) => { - this.setState({ - commentsList: response.data.comments - }); - }); - } - - handleCommentChange = (event) => { - this.setState({ - comment: event.target.value, - }); - } - - submitComment = () => { - let comment = this.refs.commentTextarea.value; - if (comment.trim().length > 0) { - this.props.editorUtilities.postComment(comment.trim()).then((response) => { - this.listComments(); - this.props.getCommentsNumber(); - }); - } - this.refs.commentTextarea.value = ''; - } - - resolveComment = (event) => { - this.props.editorUtilities.updateComment(event.target.id, 'true').then((response) => { - this.listComments(); - }); - } - - deleteComment = (event) => { - this.props.editorUtilities.deleteComment(event.target.id).then((response) => { - this.props.getCommentsNumber(); - this.listComments(); - }); - } - - editComment = (commentID, newComment) => { - this.props.editorUtilities.updateComment(commentID, null, null, newComment).then((res) => { - this.props.getCommentsNumber(); - this.listComments(); - }); - } - - setQuoteText = (text) => { - if (text.length > 0) { - this.refs.commentTextarea.value = '> ' + text; - } - } - - scrollToQuote = (detail) => { - this.props.scrollToQuote(detail); - this.refs.commentTextarea.value = ''; - } - - toggleResolvedComment = () => { - this.setState({ - showResolvedComment: !this.state.showResolvedComment - }); - } - - componentWillMount() { - this.listComments(); - } - - componentWillReceiveProps(nextProps) { - if (this.props.commentsNumber !== nextProps.commentsNumber) { - this.listComments(); - } - } - - render() { - return ( -
-
-
{gettext('Show resolved comments')}
-
- -
-
- -
-
- - -
-
-
- ); - } -} - -CommentsList.propTypes = propTypes; - -const CommentItempropTypes = { - editorUtilities: PropTypes.object.isRequired, - item: PropTypes.object, - time: PropTypes.string, - editComment: PropTypes.func, - showResolvedComment: PropTypes.bool, - deleteComment: PropTypes.func, - resolveComment: PropTypes.func, - commentsList: PropTypes.array, - scrollToQuote: PropTypes.func.isRequired, -}; - -class CommentItem extends React.Component { - - constructor(props) { - super(props); - this.state = { - dropdownOpen: false, - html: '', - quote: '', - newComment: this.props.item.comment, - editable: false, - }; - } - - toggleDropDownMenu = () => { - this.setState({ - dropdownOpen: !this.state.dropdownOpen, - }); - } - - convertComment = (item) => { - processor.process(item.comment).then( - (result) => { - let comment = String(result); - this.setState({ - comment: comment - }); - } - ); - if (item.detail) { - const quote = JSON.parse(item.detail).quote; - processor.process(quote).then( - (result) => { - let quote = String(result); - this.setState({ - quote: quote - }); - } - ); - } - } - - toggleEditComment = () => { - this.setState({ - editable: !this.state.editable - }); - } - - updateComment = (event) => { - const newComment = this.state.newComment; - if (this.props.item.comment !== newComment) { - this.props.editComment(event.target.id, newComment); - } - this.toggleEditComment(); - } - - handleCommentChange = (event) => { - this.setState({ - newComment: event.target.value, - }); - } - - scrollToQuote = () => { - const position = JSON.parse(this.props.item.detail).position; - this.props.scrollToQuote(position); - } - - componentWillMount() { - this.convertComment(this.props.item); - } - - componentWillReceiveProps(nextProps) { - this.convertComment(nextProps.item); - } - - render() { - const item = this.props.item; - const { id, user_email, avatar_url, user_name, resolved } = item; - if (item.resolved && !this.props.showResolvedComment) { - return null; - } - if (this.state.editable) { - return( -
  • -
    - -
    -
    {user_name}
    -
    {this.props.time}
    -
    -
    -
    - - {' '} - -
    -
  • - ); - } - return ( -
  • -
    - -
    -
    {user_name}
    -
    {this.props.time}
    -
    - - - - - - {(user_email === this.props.editorUtilities.userName) && - {gettext('Delete')} - } - {(user_email === this.props.editorUtilities.userName) && - {gettext('Edit')} - } - {!resolved && - {gettext('Mark as resolved')} - } - - -
    - {item.detail && -
    -
    -
    - } -
    -
  • - ); - } -} - -CommentItem.propTypes = CommentItempropTypes; - -export default CommentsList; diff --git a/frontend/src/components/markdown-view/history-list.js b/frontend/src/components/markdown-view/history-list.js index a38a7ffbf4..36066cfb46 100644 --- a/frontend/src/components/markdown-view/history-list.js +++ b/frontend/src/components/markdown-view/history-list.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import axios from 'axios'; import Loading from '../loading'; import moment from 'moment'; +import { gettext } from '../../utils/constants'; import '../../css/markdown-viewer/history-viewer.css'; const propTypes = { @@ -97,6 +98,12 @@ class HistoryList extends React.Component { render() { return (
    +
    +
    + +
    +
    {gettext('History Versions')}
    +
    diff --git a/frontend/src/css/comments-list.css b/frontend/src/css/comments-list.css index b39d026f76..b5e90761c2 100644 --- a/frontend/src/css/comments-list.css +++ b/frontend/src/css/comments-list.css @@ -4,7 +4,8 @@ flex-direction: column; width: 29%; } -.seafile-comment-title { +.seafile-comment-title, +.seafile-history-title { border-bottom: 1px solid #e5e5e5; min-height: 3em; line-height: 3em; @@ -12,15 +13,18 @@ display: flex; background-color: #fafaf9; } -.seafile-comment-title .seafile-comment-title-text { +.seafile-comment-title .seafile-comment-title-text, +.seafile-history-title .seafile-history-title-text { width: 100%; text-align: center; font-weight: 700; } -.seafile-comment-title .seafile-comment-title-close { +.seafile-comment-title .seafile-comment-title-close, +.seafile-history-title .seafile-history-title-close { color: #b9b9b9; } -.seafile-comment-title .seafile-comment-title-close:hover { +.seafile-comment-title .seafile-comment-title-close:hover, +.seafile-history-title .seafile-history-title-close:hover { color: #888; } .seafile-comment-toggle-resolved { diff --git a/frontend/src/css/markdown-viewer/comments-list.css b/frontend/src/css/markdown-viewer/comments-list.css deleted file mode 100644 index ef4843a3d3..0000000000 --- a/frontend/src/css/markdown-viewer/comments-list.css +++ /dev/null @@ -1,138 +0,0 @@ -.seafile-comment { - background-color: #fff; - display: flex; - flex-direction: column; - flex: 0 0 auto; - min-height: 18.5em; - z-index: 3; - width: 380px; -} -.seafile-comment-title { - border-bottom: 1px solid #e5e5e5; - min-height: 2em; - line-height: 2em; - padding: 2px 1em; - display: flex; - flex-direction: row; - justify-content: space-between; - background-color: #fafaf9; - position: absolute; - z-index: 1; - width: 30%; -} -.seafile-comment-title .seafile-comment-title-close { - color: #b9b9b9; -} -.seafile-comment-title .seafile-comment-title-close:hover { - color: #888; -} -.seafile-comment-list { - height: calc(100% - 40px); - overflow-y: auto; - margin: 30px 0 120px; -} -.seafile-comment::-webkit-scrollbar { - display: none; -} -.seafile-comment-list .comment-vacant { - padding: 1em; - text-align: center; -} -.seafile-comment-item { - padding: 15px 10px; - margin-bottom: 0; -} -.seafile-comment-item .seafile-comment-info { - padding-bottom: 0.5em; - height: 3em; - display: flex; - justify-content: flex-start; -} -.seafile-comment-item .seafile-comment-info .reviewer-info { - padding-left: 10px; -} -.seafile-comment-item .seafile-comment-info .review-time { - font-size: 10px; - color: #777; -} -.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown { - margin-left: auto; -} -.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown button { - border: none; - box-shadow: none; - background-color: #fff; -} -.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown .seafile-comment-dropdown-btn { - color: #999; - background-color: transparent; -} -.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown:hover .seafile-comment-dropdown-btn { - color: #555; - background-color: transparent; -} -.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown button:hover, -.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown button:focus { - border: none; - box-shadow: none; - background-color: #eee; -} -.seafile-comment-item .seafile-comment-content { - margin-left: 42px; -} -.seafile-comment-item .seafile-comment-content ol, -.seafile-comment-item .seafile-comment-content ul, -.seafile-comment-item .seafile-comment-content li { - margin-left: 10px; -} -.seafile-comment-item .seafile-comment-content table, -.seafile-comment-item .seafile-comment-content th, -.seafile-comment-item .seafile-comment-content td { - border: 1px solid #333; -} -.seafile-comment-item blockquote { - cursor: pointer; -} -.seafile-comment-footer { - background-color: #fafaf9; - border-top: 1px solid #e5e5e5; - min-height: 120px; - position: absolute; - bottom: 0; - padding: 0; - width: 30%; -} -.seafile-comment-footer .seafile-add-comment { - margin: 10px 20px 5px 15px; - height: 100%; - width: 100%; -} -.seafile-add-comment .add-comment-input, -.seafile-edit-comment .edit-comment-input { - background-color: #fff; - border: 1px solid #e6e6dd; - padding: 5px; - min-height: 70px; - border-radius: 5px; - width: 100%; -} -.seafile-add-comment .add-comment-input { - height: calc(100% - 50px); - width: 92%; - max-height: 300px; -} -.seafile-comment-footer .seafile-add-comment .submit-comment { - margin-top: 5px; - width: 60px; - height: 28px; -} -.seafile-comment-item-resolved { - background-color: #e6ffed; -} -.seafile-comment-footer .seafile-add-comment .comment-btn, -.seafile-edit-comment .comment-btn { - height: 28px; -} -.seafile-edit-comment { - margin-top: 10px; -} diff --git a/frontend/src/css/markdown-viewer/markdown-editor.css b/frontend/src/css/markdown-viewer/markdown-editor.css index 1774e8beeb..788c235907 100644 --- a/frontend/src/css/markdown-viewer/markdown-editor.css +++ b/frontend/src/css/markdown-viewer/markdown-editor.css @@ -13,29 +13,52 @@ align-items: center; } .seafile-md-viewer-container { - width: 70%; + width: 100%; background-color: #fafaf9; - overflow: hidden; height: 100%; -} -.seafile-md-viewer-container:hover { + position: relative; overflow: auto; } +.seafile-md-viewer-container.side-panel-on { + width: 70%; +} .seafile-md-viewer-slate { flex: auto; position: relative; margin: 20px 40px; + margin-right: 30%; } .seafile-md-viewer-main { - flex:auto; - overflow:auto; + flex: auto; + overflow: auto; background:#fafaf9; width: 70%; } -.seafile-editor-outline .active { +.seafile-md-viewer-slate.side-panel-on { + margin-right: 40px; +} +/* outline */ +.seafile-md-viewer .seafile-editor-outline { + background-color: #fafaf9; + margin: 40px; + border-left: 0; + width: 20%; + position: fixed; + top: 68px; + overflow-y: auto; + right: 5%; + z-index: 1; + height: 80%; +} +.seafile-md-viewer .seafile-editor-outline .active { color: #eb8205; border-left: 1px solid #eb8205; } +.seafile-md-viewer .seafile-editor-outline-heading { + padding: 7px 0; + border-bottom: 1px solid #eee; + color: #a0a0a0; +} .seafile-editor-outline .outline-h2, .seafile-editor-outline .outline-h3 { height: 30px; margin-left: 0; @@ -52,71 +75,26 @@ } /* side-panel */ .seafile-md-viewer-side-panel { - height: 100%; - overflow:hidden; - user-select: none; - width: 30%; -} -.seafile-md-viewer-side-panel .seafile-editor-outline { - border-left: 0; -} -.seafile-md-viewer-side-panel:hover { - overflow: auto; -} -.seafile-md-viewer-side-panel-heading { - padding:7px 0; - border-bottom: 1px solid #eee; - color: #a0a0a0; -} -.seafile-md-viewer-side-panel-content { - padding:8px 0; - font-size: 0.875rem; -} - -.seafile-md-viewer-side-panel { - border-left: 1px solid #e6e6dd; - background-color: #fff; height: 100%; overflow: hidden; + width: 30%; + position: fixed; + right: 0; + top: 87px; } -.seafile-md-viewer-side-panel .tab-content { - height: calc(100% - 39px); - overflow-y: auto; - overflow-x: hidden; +.seafile-md-viewer-side-panel .seafile-comment, +.seafile-md-viewer-side-panel .seafile-history-side-panel { + width: 100%; + height: 100%; } -.seafile-md-viewer-side-panel .tab-content .outline { - padding: 20px; +.seafile-md-viewer-side-panel .seafile-comment .add-comment-input, +.seafile-md-viewer-side-panel .seafile-comment .edit-comment-input { + background-color: #fff; + width: 100%; } -.seafile-md-viewer-side-panel .md-side-panel-nav { - margin: 0; +.seafile-md-viewer-side-panel .seafile-history-side-panel { + border-left: 1px solid #e6e6dd; } -.md-side-panel-nav .nav-item { - width: 33.3%; - padding-top: 4px; -} -.md-side-panel-nav .nav-item .nav-link { - margin: 0 auto; -} -.md-side-panel-nav .nav-item i { - padding: 0 8px; - font-size: 1rem; - width: 1rem; -} -.comments-number { - font-size: 12px; - width: 16px; - height: 16px; - border-radius: 8px; - text-align: center; - line-height: 16px; - font-weight: 600; - background-color: #fd9644; - position: absolute; - top: 10%; - right: 30%; - color: #fff; -} - .seafile-viewer-comment-btn { position: absolute; top: 0; @@ -131,11 +109,13 @@ cursor: pointer; background-color: #eee; } - +.seafile-md-viewer .seafile-comment .seafile-comment-footer { + min-height: 230px; +} +.seafile-md-viewer .seafile-comment-toggle-resolved { + width: 100%; +} @media (max-width:991.8px) { - .seafile-md-viewer-side-panel { - display:none; - } .seafile-editor-outline { display: none; } @@ -143,9 +123,4 @@ width: calc(100% - 80px); margin: 20px 40px; } -} -@media (min-width:992px) { - .seafile-md-viewer-side-panel { - width: 30%; - } } \ No newline at end of file diff --git a/frontend/src/markdown-editor.js b/frontend/src/markdown-editor.js index ec304bb465..d057de4150 100644 --- a/frontend/src/markdown-editor.js +++ b/frontend/src/markdown-editor.js @@ -18,13 +18,16 @@ import { serialize, deserialize } from '@seafile/seafile-editor/dist/utils/slate import LocalDraftDialog from '@seafile/seafile-editor/dist/components/local-draft-dialog'; import DiffViewer from '@seafile/seafile-editor/dist/viewer/diff-viewer'; import MarkdownViewerToolbar from './components/toolbar/markdown-viewer-toolbar'; -import MarkdownViewerSidePanel from './components/markdown-view/markdown-viewer-side-panel'; +import HistoryList from './components/markdown-view/history-list'; +import CommentPanel from './components/file-view/comment-panel'; +import OutlineView from './components/markdown-view/outline'; import Loading from './components/loading'; import { findRange } from '@seafile/slate-react'; import './css/markdown-viewer/markdown-editor.css'; const CryptoJS = require('crypto-js'); +const URL = require('url-parse'); const { repoID, repoName, filePath, fileName, mode, draftID, isDraft, hasDraft } = window.app.pageOptions; const { siteRoot, serviceUrl, seafileCollabServer } = window.app.config; const userInfo = window.app.userInfo; @@ -300,9 +303,11 @@ class MarkdownEditor extends React.Component { collabUsers: userInfo ? [{user: userInfo, is_editing: false}] : [], commentsNumber: null, - activeTab: 'outline', loadingDiff: false, value: null, + isShowComments: false, + isShowHistory: false, + isShowOutline: true, }; if (this.state.collabServer) { @@ -721,17 +726,8 @@ class MarkdownEditor extends React.Component { }); } - tabItemClick = (tab) => { - if (this.state.activeTab !== tab) { - this.setState({ - activeTab: tab - }); - } - } - setBtnPosition = (e) => { - let isShowComments = this.state.activeTab === 'comments' ? true : false; - if (!isShowComments) return; + if (!this.state.isShowComments) return; const nativeSelection = window.getSelection(); if (!nativeSelection.rangeCount) { this.range = null; @@ -855,9 +851,95 @@ class MarkdownEditor extends React.Component { return newNodes; } + scrollToNode = (node) => { + let url = new URL(window.location.href); + url.set('hash', 'user-content-' + node.text); + window.location.href = url.toString(); + } + + findScrollContainer = (el, window) => { + let parent = el.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 = (path) => { + if (!path) return; + const win = window; + if (path.length > 2) { + // deal with code block or chart + path[0] = path[0] > 1 ? path[0] - 1 : path[0] + 1; + path = path.slice(0, 1); + } + let node = this.state.value.document.getNode(path); + if (!node) { + path = path.slice(0, 1); + node = this.state.value.document.getNode(path); + } + if (node) { + let element = win.document.querySelector(`[data-key="${node.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; + } + } + } + + toggleHistory = () => { + if (this.state.isShowHistory) { + this.setState({ + isShowHistory: false, + isShowOutline: true, + isShowComments: false, + }); + } else { + this.setState({ + isShowHistory: true, + isShowOutline: false, + isShowComments: false, + }); + } + } + + toggleCommentList = () => { + if (this.state.isShowComments) { + this.setState({ + isShowHistory: false, + isShowOutline: true, + isShowComments: false, + }); + } else { + this.setState({ + isShowHistory: false, + isShowOutline: false, + isShowComments: true, + }); + } + } + render() { let component; - let isShowComments = this.state.activeTab === 'comments' ? true : false; + let sidePanel = (this.state.isShowHistory || this.state.isShowComments) ? true : false; if (this.state.loading) { return (
    @@ -882,11 +964,15 @@ class MarkdownEditor extends React.Component { toggleShareLinkDialog={this.toggleShareLinkDialog} onEdit={this.onEdit} toggleNewDraft={editorUtilities.createDraftFile} + commentsNumber={this.state.commentsNumber} + toggleCommentList={this.toggleCommentList} + showFileHistory={true} + toggleHistory={this.toggleHistory} />
    -
    +
    { - this.state.activeTab === 'history' ? + this.state.isShowHistory ?
    { this.state.loadingDiff ? @@ -899,30 +985,38 @@ class MarkdownEditor extends React.Component {
    : -
    +
    - {isShowComments && + {this.state.isShowComments && }
    } + { + this.state.isShowOutline && + + } +
    +
    + {this.state.isShowComments && } + { + this.state.isShowHistory && + + }
    -
    ); @@ -952,7 +1046,7 @@ class MarkdownEditor extends React.Component { fileTagList={this.state.fileTagList} deleteDraft={this.deleteDraft} showDraftSaved={this.state.showDraftSaved} - />; + /> } return (