mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-18 16:36:15 +00:00
New file history (#2359)
This commit is contained in:
committed by
Daniel Pan
parent
5924c65d08
commit
ca0110e996
@@ -1,4 +1,3 @@
|
||||
|
||||
export const dirPath = '/';
|
||||
export const gettext = window.gettext;
|
||||
|
||||
@@ -11,8 +10,15 @@ export const logoWidth = window.app.config.logoWidth;
|
||||
export const logoHeight = window.app.config.logoHeight;
|
||||
export const isPro = window.app.config.isPro === "True";
|
||||
|
||||
// wiki
|
||||
export const slug = window.wiki ? window.wiki.config.slug : '';
|
||||
export const repoID = window.wiki ? window.wiki.config.repoId : '';
|
||||
export const serviceUrl = window.wiki ? window.wiki.config.serviceUrl : '';
|
||||
export const initialFilePath = window.wiki ? window.wiki.config.initial_file_path : '';
|
||||
export const permission = window.wiki ? window.wiki.config.permission : '';
|
||||
|
||||
// file history
|
||||
export const PER_PAGE = 25;
|
||||
export const historyRepoID = window.fileHistory ? window.fileHistory.pageOptions.repoID : '';
|
||||
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 : '';
|
||||
|
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import NodeMenuControl from '../menu-component/node-menu-control';
|
||||
|
||||
moment.locale(window.app.config.lang);
|
||||
const propTypes = {
|
||||
isItemFrezeed: PropTypes.bool.isRequired,
|
||||
isFirstItem: PropTypes.bool.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
currentItem: PropTypes.object.isRequired,
|
||||
onMenuControlClick: PropTypes.func.isRequired,
|
||||
onHistoryItemClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class HistoryListItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isShowOperationIcon: false
|
||||
};
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
if (!this.props.isItemFrezeed) {
|
||||
this.setState({isShowOperationIcon: true});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseLeave = () => {
|
||||
if (!this.props.isItemFrezeed) {
|
||||
this.setState({isShowOperationIcon: false});
|
||||
}
|
||||
}
|
||||
|
||||
onItemClick = () => {
|
||||
if (this.props.item.commit_id === this.props.currentItem.commit_id) {
|
||||
return;
|
||||
}
|
||||
this.setState({isShowOperationIcon: false}); //restore to default state
|
||||
this.props.onHistoryItemClick(this.props.item);
|
||||
}
|
||||
|
||||
onMenuControlClick = (e) => {
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
this.props.onMenuControlClick(e, this.props.item , this.props.isFirstItem);
|
||||
}
|
||||
|
||||
render() {
|
||||
let item = this.props.item;
|
||||
let time = moment(item.ctime).format('MMMDo Ah:mm');
|
||||
let isHigtlightItem = false;
|
||||
if (this.props.item && this.props.currentItem) {
|
||||
isHigtlightItem = this.props.item.commit_id === this.props.currentItem.commit_id;
|
||||
}
|
||||
return (
|
||||
<li
|
||||
className={`history-list-item ${isHigtlightItem ? 'item-active' : ''}`}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onClick={this.onItemClick}
|
||||
>
|
||||
<div className="history-info">
|
||||
<div className="time">{time}</div>
|
||||
<div className="owner">
|
||||
<span className="squire-icon"></span>
|
||||
<span>{item.creator_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="history-operation">
|
||||
<NodeMenuControl
|
||||
isShow={this.state.isShowOperationIcon || isHigtlightItem}
|
||||
onClick={this.onMenuControlClick}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryListItem.propTypes = propTypes;
|
||||
|
||||
export default HistoryListItem;
|
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, filePath } from '../constance';
|
||||
import URLDecorator from '../../utils/url-decorator';
|
||||
|
||||
const propTypes = {
|
||||
isFirstItem: PropTypes.bool.isRequired,
|
||||
isListMenuShow: PropTypes.bool.isRequired,
|
||||
menuPosition: PropTypes.object.isRequired,
|
||||
currentItem: PropTypes.object,
|
||||
onDownloadFile: PropTypes.func.isRequired,
|
||||
onRestoreFile: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class HistoryListMenu extends React.Component {
|
||||
|
||||
onDownloadFile = () => {
|
||||
this.props.onDownloadFile();
|
||||
}
|
||||
|
||||
onRestoreFile = () => {
|
||||
this.props.onRestoreFile();
|
||||
}
|
||||
|
||||
render() {
|
||||
let style = {};
|
||||
let position = this.props.menuPosition;
|
||||
if (this.props.isListMenuShow) {
|
||||
style = {position: 'fixed',left: position.left + 'px',top: position.top + 'px',display: 'block'};
|
||||
}
|
||||
|
||||
if (!this.props.currentItem) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let objID = this.props.currentItem.rev_file_id;
|
||||
let url = URLDecorator.getUrl({type: 'download_historic_file', filePath: filePath, objID: objID});
|
||||
|
||||
if (this.props.isFirstItem) {
|
||||
return (
|
||||
<ul className="dropdown-menu" style={style}>
|
||||
<li className="dropdown-item" onClick={this.onDownloadFile}>
|
||||
<a href={url}>{gettext('Download')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="dropdown-menu" style={style}>
|
||||
<li className="dropdown-item" onClick={this.onRestoreFile}>{gettext('Restore')}</li>
|
||||
<li className="dropdown-item" onClick={this.onDownloadFile}>
|
||||
<a href={url}>{gettext('Download')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HistoryListMenu.propTypes = propTypes;
|
||||
|
||||
export default HistoryListMenu;
|
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import HisotyListItem from './history-list-item';
|
||||
import Loading from '../loading';
|
||||
|
||||
const propTypes = {
|
||||
hasMore: PropTypes.bool.isRequired,
|
||||
isReloadingData: PropTypes.bool.isRequired,
|
||||
isItemFrezeed: PropTypes.bool.isRequired,
|
||||
historyList: PropTypes.array.isRequired,
|
||||
currentItem: PropTypes.object,
|
||||
reloadMore: PropTypes.func.isRequired,
|
||||
onMenuControlClick: PropTypes.func.isRequired,
|
||||
onHistoryItemClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class HistoryListView extends React.Component {
|
||||
|
||||
onScrollHandler = (event) => {
|
||||
const clientHeight = event.target.clientHeight;
|
||||
const scrollHeight = event.target.scrollHeight;
|
||||
const scrollTop = event.target.scrollTop;
|
||||
const isBottom = (clientHeight + scrollTop + 1 >= scrollHeight);
|
||||
let hasMore = this.props.hasMore;
|
||||
if (isBottom && hasMore) {
|
||||
this.props.reloadMore();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ul className="history-list-container" onScroll={this.onScrollHandler}>
|
||||
{this.props.historyList.map((item, index) => {
|
||||
return (
|
||||
<HisotyListItem
|
||||
key={index}
|
||||
item={item}
|
||||
isFirstItem={index === 0}
|
||||
currentItem={this.props.currentItem}
|
||||
isItemFrezeed={this.props.isItemFrezeed}
|
||||
onMenuControlClick={this.props.onMenuControlClick}
|
||||
onHistoryItemClick={this.props.onHistoryItemClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{this.props.isReloadingData && <li><Loading /></li>}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryListView.propTypes = propTypes;
|
||||
|
||||
export default HistoryListView;
|
9
frontend/src/components/loading.js
Normal file
9
frontend/src/components/loading.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
function Loading() {
|
||||
return (
|
||||
<span className="loading-icon loading-tip"></span>
|
||||
);
|
||||
}
|
||||
|
||||
export default Loading;
|
47
frontend/src/components/logo.js
Normal file
47
frontend/src/components/logo.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import PropsType from 'prop-types';
|
||||
|
||||
const siteRoot = window.app.config.siteRoot;
|
||||
const mediaUrl = window.app.config.mediaUrl;
|
||||
const logoPath = window.app.config.logoPath;
|
||||
const logoWidth = window.app.config.logoWidth;
|
||||
const logoHeight = window.app.config.logoHeight;
|
||||
const siteTitle = window.app.config.siteTitle;
|
||||
|
||||
const propsType = {
|
||||
onCloseSidePanel: PropsType.func.isRequired,
|
||||
};
|
||||
|
||||
class Logo extends React.Component {
|
||||
|
||||
closeSide = () => {
|
||||
this.props.onCloseSidePanel();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="logo">
|
||||
<a href={siteRoot} id="logo">
|
||||
<img
|
||||
src={mediaUrl + logoPath}
|
||||
height={logoHeight}
|
||||
width={logoWidth}
|
||||
title={siteTitle}
|
||||
alt="logo"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
className="sf2-icon-x1 sf-popover-close side-panel-close op-icon d-md-none"
|
||||
onClick={this.closeSide}
|
||||
title="Close"
|
||||
aria-label="Close"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Logo.propsType = propsType;
|
||||
|
||||
export default Logo;
|
@@ -1,4 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const propTypes = {
|
||||
isShow: PropTypes.bool.isRequired,
|
||||
currentNode: PropTypes.object,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class NodeMenuControl extends React.Component {
|
||||
|
||||
@@ -10,12 +17,14 @@ class NodeMenuControl extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<i
|
||||
className={`fas fa-ellipsis-v ${this.props.isShow ? "" : "hide"}`}
|
||||
className={`fas fa-ellipsis-v ${this.props.isShow ? '' : 'hide'}`}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
</i>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NodeMenuControl.propTypes = propTypes;
|
||||
|
||||
export default NodeMenuControl;
|
108
frontend/src/css/file-history.css
Normal file
108
frontend/src/css/file-history.css
Normal file
@@ -0,0 +1,108 @@
|
||||
.history-main {
|
||||
background-color: #fafaf9;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.history, .markdown-viewer-render-content {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.history-heading {
|
||||
height: 3.1875rem;
|
||||
line-height: 3.1875rem;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
position: relative;
|
||||
padding: .5rem 0rem;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
height: 36px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.history .history-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.history-list-container {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.history-list-container .history-list-item {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0 0.8rem;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.history-list-container .history-list-item:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.item-active {
|
||||
color: #fff;
|
||||
background-color: #feac74 !important;
|
||||
}
|
||||
|
||||
.item-active i {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.history-list-item .history-info {
|
||||
flex: 1;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.history-list-item .history-operation {
|
||||
width: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.history-info .time {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.history-info .owner {
|
||||
margin-top: 0.25rem;
|
||||
color: #888;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.owner .squire-icon {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
background-color: #549b5a;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.history-body .dropdown-menu {
|
||||
min-width: 8rem;
|
||||
}
|
||||
|
||||
.history-body .dropdown-menu a {
|
||||
text-decoration: none;
|
||||
color: #6e7687;
|
||||
}
|
||||
|
||||
.history-viewer-contanier {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.markdown-viewer-render-content {
|
||||
margin: 20px 40px;
|
||||
border: 1px solid #e6e6dd;
|
||||
}
|
||||
|
27
frontend/src/css/layout.css
Normal file
27
frontend/src/css/layout.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.side-panel {
|
||||
flex: 1 1 20%;
|
||||
}
|
||||
.main-panel {
|
||||
flex: 1 1 80%;
|
||||
}
|
||||
|
||||
.side-panel-north,
|
||||
.main-panel-north {
|
||||
padding: .5rem 1rem;
|
||||
background: #f4f4f7;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.side-panel-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid #eee;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.main-panel-center {
|
||||
flex: 1;
|
||||
}
|
78
frontend/src/file-history.js
Normal file
78
frontend/src/file-history.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import editUtilties from './utils/editor-utilties';
|
||||
import { filePath } from './components/constance';
|
||||
import URLDecorator from './utils/url-decorator';
|
||||
import { processor } from '@seafile/seafile-editor/src/lib/seafile-markdown2html';
|
||||
import SidePanel from './pages/file-history/side-panel';
|
||||
import MainPanel from './pages/file-history/main-panel';
|
||||
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/file-history.css';
|
||||
|
||||
class FileHistory extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
content: '',
|
||||
renderingContent: true,
|
||||
fileOwner: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let _this = this;
|
||||
editUtilties.getFileDownloadLink(filePath).then(res => {
|
||||
const downLoadUrl = res.data;
|
||||
editUtilties.getFileContent(downLoadUrl).then((res) => {
|
||||
let content = res.data;
|
||||
processor.process(content, function(err, file) {
|
||||
_this.setState({
|
||||
renderingContent: false,
|
||||
content: String(file)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onHistoryItemClick = (item) => {
|
||||
let _this = this;
|
||||
let objID = item.rev_file_id;
|
||||
let downLoadURL = URLDecorator.getUrl({type: 'download_historic_file', filePath: filePath, objID: objID});
|
||||
this.setState({renderingContent: true});
|
||||
editUtilties.getFileContent(downLoadURL).then((res) => {
|
||||
let content = res.data;
|
||||
processor.process(content, function(err, file) {
|
||||
_this.setState({
|
||||
renderingContent: false,
|
||||
content: String(file)
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div id="main" className="history-main">
|
||||
<SidePanel
|
||||
fileOwner={this.state.fileOwner}
|
||||
onHistoryItemClick={this.onHistoryItemClick}
|
||||
/>
|
||||
<MainPanel
|
||||
content={this.state.content}
|
||||
renderingContent={this.state.renderingContent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render (
|
||||
<FileHistory />,
|
||||
document.getElementById('wrapper')
|
||||
);
|
15
frontend/src/globals.js
Normal file
15
frontend/src/globals.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export const gettext = window.gettext;
|
||||
export const siteRoot = window.app.config.siteRoot;
|
||||
export const lang = window.app.config.lang;
|
||||
|
||||
export const getUrl = (options) => {
|
||||
switch (options.name) {
|
||||
case 'user_profile': return siteRoot + 'profile/' + options.username + '/';
|
||||
case 'common_lib': return siteRoot + '#common/lib/' + options.repoID + options.path;
|
||||
case 'view_lib_file': return `${siteRoot}lib/${options.repoID}/file${options.filePath}`;
|
||||
case 'download_historic_file': return `${siteRoot}repo/${options.repoID}/${options.objID}/download/?p=${options.filePath}`;
|
||||
case 'view_historic_file': return `${siteRoot}repo/${options.repoID}/history/files/?obj_id=${options.objID}&commit_id=${options.commitID}&p=${options.filePath}`;
|
||||
case 'diff_historic_file': return `${siteRoot}repo/text_diff/${options.repoID}/?commit=${options.commitID}&p=${options.filePath}`;
|
||||
|
||||
}
|
||||
}
|
45
frontend/src/pages/file-history/main-panel.js
Normal file
45
frontend/src/pages/file-history/main-panel.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Prism from 'prismjs';
|
||||
import Loading from '../../components/loading';
|
||||
import '../../css/initial-style.css';
|
||||
require('@seafile/seafile-editor/src/lib/code-hight-package');
|
||||
|
||||
const contentClass = 'markdown-viewer-render-content';
|
||||
const propTypes = {
|
||||
renderingContent: PropTypes.bool.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
class MainPanel extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
Prism.highlightAll();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="main-panel viewer">
|
||||
<div className="main-panel-north">
|
||||
<div className="history-heading"></div>
|
||||
</div>
|
||||
<div className="main-panel-center history-viewer-contanier">
|
||||
<div className="content-viewer">
|
||||
{
|
||||
this.props.renderingContent ?
|
||||
(<div className={contentClass + ' article'}><Loading /></div>) :
|
||||
(<div
|
||||
className={contentClass + ' article'}
|
||||
dangerouslySetInnerHTML={{ __html: this.props.content }}
|
||||
/>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MainPanel.propTypes = propTypes;
|
||||
|
||||
export default MainPanel;
|
176
frontend/src/pages/file-history/side-panel.js
Normal file
176
frontend/src/pages/file-history/side-panel.js
Normal file
@@ -0,0 +1,176 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, PER_PAGE, filePath, fileName } from '../../components/constance';
|
||||
import editUtilties from '../../utils/editor-utilties';
|
||||
import Loading from '../../components/loading';
|
||||
import HistoryListView from '../../components/history-list-view/history-list-view';
|
||||
import HistoryListMenu from '../../components/history-list-view/history-list-menu';
|
||||
|
||||
const propTypes = {
|
||||
onHistoryItemClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SidePanel extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
historyInfo: '',
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
fileOwner: '',
|
||||
isListMenuShow: false,
|
||||
isFirstItem: false,
|
||||
currentItem: null,
|
||||
menuPosition: {top: '', left: ''},
|
||||
isItemFrezeed: false,
|
||||
isReloadingData: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
editUtilties.getFileHistoryRecord(filePath, 1, PER_PAGE).then(res => {
|
||||
this.initResultState(res.data);
|
||||
document.addEventListener('click', this.onHideContextMenu);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.onHideContextMenu);
|
||||
}
|
||||
|
||||
refershFileList() {
|
||||
editUtilties.getFileHistoryRecord(filePath, 1, PER_PAGE).then(res => {
|
||||
this.initResultState(res.data);
|
||||
});
|
||||
}
|
||||
|
||||
initResultState(result) {
|
||||
if (result.data.length) {
|
||||
this.setState({
|
||||
historyInfo: result.data,
|
||||
currentPage: result.page,
|
||||
hasMore: result.total_count > (PER_PAGE * this.state.currentPage),
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
fileOwner: result.data[0].creator_email,
|
||||
currentItem: result.data[0],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateResultState(result) {
|
||||
if (result.data.length) {
|
||||
this.setState({
|
||||
historyInfo: [...this.state.historyInfo, ...result.data],
|
||||
currentPage: result.page,
|
||||
hasMore: result.total_count > (PER_PAGE * this.state.currentPage),
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
fileOwner: result.data[0].creator_email
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onShowContenxtMenu = (e, item, isFirstItem) => {
|
||||
let left = e.clientX - 8*16;
|
||||
let top = e.clientY + 10;
|
||||
this.setState({
|
||||
currentItem: item,
|
||||
isFirstItem: isFirstItem,
|
||||
isListMenuShow: !this.state.isListMenuShow,
|
||||
menuPosition: {top: top, left: left},
|
||||
isItemFrezeed: !this.state.isItemFrezeed,
|
||||
});
|
||||
}
|
||||
|
||||
onHideContextMenu = (e) => {
|
||||
this.setState({
|
||||
isListMenuShow: false,
|
||||
isItemFrezeed: false
|
||||
});
|
||||
}
|
||||
|
||||
reloadMore = () => {
|
||||
if (!this.state.isReloadingData) {
|
||||
let currentPage = this.state.currentPage + 1;
|
||||
this.setState({
|
||||
currentPage: currentPage,
|
||||
isReloadingData: true,
|
||||
});
|
||||
editUtilties.getFileHistoryRecord(filePath, currentPage, PER_PAGE).then(res => {
|
||||
this.updateResultState(res.data);
|
||||
this.setState({
|
||||
isReloadingData: false
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onRestoreFile = () => {
|
||||
this.onHideContextMenu();
|
||||
let commitId = this.state.currentItem.commit_id;
|
||||
editUtilties.revertFile(filePath, commitId).then(res => {
|
||||
if (res.data.success) {
|
||||
this.setState({isLoading: true});
|
||||
this.refershFileList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDownloadFile = () => {
|
||||
this.onHideContextMenu();
|
||||
}
|
||||
|
||||
onHistoryItemClick =(item) => {
|
||||
this.setState({currentItem: item});
|
||||
this.props.onHistoryItemClick(item);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="side-panel">
|
||||
<div className="side-panel-north">
|
||||
<div className="history-heading">
|
||||
<a href="javascript:window.history.back()" className="go-back" title="Back">
|
||||
<span className="icon-chevron-left"></span>
|
||||
</a>
|
||||
<span className="history-doc-name">{fileName}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="side-panel-center history">
|
||||
<div className="panel-heading history-heading">{gettext('History Versions')}</div>
|
||||
<div className="history-body">
|
||||
{this.state.isLoading && <Loading />}
|
||||
{this.state.historyInfo &&
|
||||
<HistoryListView
|
||||
hasMore={this.state.hasMore}
|
||||
isReloadingData={this.state.isReloadingData}
|
||||
historyList={this.state.historyInfo}
|
||||
onMenuControlClick={this.onShowContenxtMenu}
|
||||
isItemFrezeed={this.state.isItemFrezeed}
|
||||
reloadMore={this.reloadMore}
|
||||
currentItem={this.state.currentItem}
|
||||
onHistoryItemClick={this.onHistoryItemClick}
|
||||
/>
|
||||
}
|
||||
<HistoryListMenu
|
||||
isListMenuShow={this.state.isListMenuShow}
|
||||
menuPosition={this.state.menuPosition}
|
||||
isFirstItem={this.state.isFirstItem}
|
||||
currentItem={this.state.currentItem}
|
||||
onRestoreFile={this.onRestoreFile}
|
||||
onDownloadFile={this.onDownloadFile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SidePanel.propTypes = propTypes;
|
||||
|
||||
export default SidePanel;
|
@@ -1,4 +1,4 @@
|
||||
import { slug, repoID, siteRoot } from '../components/constance';
|
||||
import { slug, repoID, siteRoot, historyRepoID } from '../components/constance';
|
||||
import { SeafileAPI } from 'seafile-js';
|
||||
import cookie from 'react-cookies';
|
||||
|
||||
@@ -9,61 +9,60 @@ seafileAPI.initForSeahubUsage({ siteRoot, xcsrfHeaders });
|
||||
class EditorUtilities {
|
||||
|
||||
getFiles() {
|
||||
return seafileAPI.listWikiDir(slug, "/").then(items => {
|
||||
const files = items.data.dir_file_list.map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
type: item.type === 'dir' ? 'dir' : 'file',
|
||||
isExpanded: item.type === 'dir' ? true : false,
|
||||
parent_path: item.parent_dir,
|
||||
last_update_time: item.last_update_time,
|
||||
size: item.size
|
||||
}
|
||||
})
|
||||
return files;
|
||||
})
|
||||
return seafileAPI.listWikiDir(slug, '/').then(items => {
|
||||
const files = items.data.dir_file_list.map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
type: item.type === 'dir' ? 'dir' : 'file',
|
||||
isExpanded: item.type === 'dir' ? true : false,
|
||||
parent_path: item.parent_dir,
|
||||
last_update_time: item.last_update_time,
|
||||
size: item.size
|
||||
};
|
||||
});
|
||||
return files;
|
||||
});
|
||||
}
|
||||
|
||||
listRepoDir() {
|
||||
return seafileAPI.listDir(repoID, "/",{recursive: true}).then(items => {
|
||||
const files = items.data.map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
type: item.type === 'dir' ? 'dir' : 'file',
|
||||
isExpanded: item.type === 'dir' ? true : false,
|
||||
parent_path: item.parent_dir,
|
||||
last_update_time: item.mtime,
|
||||
size: item.size
|
||||
}
|
||||
})
|
||||
return seafileAPI.listDir(repoID, '/',{recursive: true}).then(items => {
|
||||
const files = items.data.map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
type: item.type === 'dir' ? 'dir' : 'file',
|
||||
isExpanded: item.type === 'dir' ? true : false,
|
||||
parent_path: item.parent_dir,
|
||||
last_update_time: item.mtime,
|
||||
size: item.size
|
||||
};
|
||||
});
|
||||
|
||||
return files;
|
||||
})
|
||||
return files;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
createFile(filePath) {
|
||||
return seafileAPI.createFile(repoID, filePath)
|
||||
return seafileAPI.createFile(repoID, filePath);
|
||||
}
|
||||
|
||||
deleteFile(filePath) {
|
||||
return seafileAPI.deleteFile(repoID, filePath)
|
||||
return seafileAPI.deleteFile(repoID, filePath);
|
||||
}
|
||||
|
||||
renameFile(filePath, newFileName) {
|
||||
return seafileAPI.renameFile(repoID, filePath, newFileName)
|
||||
return seafileAPI.renameFile(repoID, filePath, newFileName);
|
||||
}
|
||||
|
||||
createDir(dirPath) {
|
||||
return seafileAPI.createDir(repoID, dirPath)
|
||||
return seafileAPI.createDir(repoID, dirPath);
|
||||
}
|
||||
|
||||
deleteDir(dirPath) {
|
||||
return seafileAPI.deleteDir(repoID, dirPath)
|
||||
return seafileAPI.deleteDir(repoID, dirPath);
|
||||
}
|
||||
|
||||
renameDir(dirPath, newDirName) {
|
||||
return seafileAPI.renameDir(repoID, dirPath, newDirName)
|
||||
return seafileAPI.renameDir(repoID, dirPath, newDirName);
|
||||
}
|
||||
|
||||
getWikiFileContent(slug, filePath) {
|
||||
@@ -82,6 +81,22 @@ class EditorUtilities {
|
||||
return seafileAPI.getAccountInfo();
|
||||
}
|
||||
|
||||
// file history
|
||||
getFileDownloadLink(filePath) {
|
||||
return seafileAPI.getFileDownloadLink(historyRepoID, filePath);
|
||||
}
|
||||
|
||||
getFileContent(filePath) {
|
||||
return seafileAPI.getFileContent(filePath);
|
||||
}
|
||||
|
||||
getFileHistoryRecord(filePath, page, per_page) {
|
||||
return seafileAPI.getFileHistoryRecord(historyRepoID, filePath, page, per_page);
|
||||
}
|
||||
|
||||
revertFile(filePath, commitID) {
|
||||
return seafileAPI.revertFile(historyRepoID, filePath, commitID);
|
||||
}
|
||||
}
|
||||
|
||||
const editorUtilities = new EditorUtilities();
|
||||
|
40
frontend/src/utils/url-decorator.js
Normal file
40
frontend/src/utils/url-decorator.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const siteRoot = window.app.config.siteRoot;
|
||||
const repoID = window.fileHistory.pageOptions.repoID;
|
||||
|
||||
class URLDecorator {
|
||||
|
||||
static getUrl(options) {
|
||||
let url = '';
|
||||
let params = '';
|
||||
switch (options.type) {
|
||||
case 'user_profile':
|
||||
url = siteRoot + 'profile/' + options.username + '/';
|
||||
break;
|
||||
case 'common_lib':
|
||||
url = siteRoot + '#common/lib/' + repoID + options.path;
|
||||
break;
|
||||
case 'view_lib_file':
|
||||
url = siteRoot + 'lib/' + repoID + '/file' + options.filePath;
|
||||
break;
|
||||
case 'download_historic_file':
|
||||
params = 'p=' + options.filePath;
|
||||
url = siteRoot + 'repo/' + repoID + '/' + options.objID + '/download?' + params;
|
||||
break;
|
||||
case 'view_historic_file':
|
||||
params = 'obj_id=' + options.objID + '&commit_id=' + options.commitID + '&p=' + options.filePath;
|
||||
url = siteRoot + 'repo/' + options.repoID + 'history/files/?' + params;
|
||||
break;
|
||||
case 'diff_historic_file':
|
||||
params = 'commit_id=' + options.commitID + '&p=' + options.filePath;
|
||||
url = siteRoot + 'repo/text_diff/' + repoID + '/?' + params;
|
||||
break;
|
||||
default:
|
||||
url = '';
|
||||
break;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default URLDecorator;
|
Reference in New Issue
Block a user