2023-11-15 16:28:02 +08:00
|
|
|
import React, { Component, Fragment } from 'react';
|
|
|
|
import moment from 'moment';
|
2023-05-13 15:42:54 +08:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import classnames from 'classnames';
|
2023-10-26 17:15:42 +08:00
|
|
|
import Loading from '../../../components/loading';
|
|
|
|
import { gettext, historyRepoID, PER_PAGE } from '../../../utils/constants';
|
|
|
|
import { seafileAPI } from '../../../utils/seafile-api';
|
|
|
|
import { Utils } from '../../../utils/utils';
|
|
|
|
import editUtilities from '../../../utils/editor-utilities';
|
|
|
|
import toaster from '../../../components/toast';
|
2023-05-13 15:42:54 +08:00
|
|
|
import HistoryVersion from './history-version';
|
2023-10-26 17:15:42 +08:00
|
|
|
import Switch from '../../../components/common/switch';
|
2023-05-13 15:42:54 +08:00
|
|
|
|
2023-11-15 16:28:02 +08:00
|
|
|
moment.locale(window.app.config.lang);
|
|
|
|
|
2023-06-19 17:35:30 +08:00
|
|
|
const { docUuid } = window.fileHistory.pageOptions;
|
|
|
|
|
2023-05-13 15:42:54 +08:00
|
|
|
class SidePanel extends Component {
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
isLoading: true,
|
2023-11-15 16:28:02 +08:00
|
|
|
historyGroups: [],
|
2023-05-13 15:42:54 +08:00
|
|
|
errorMessage: '',
|
2023-06-19 17:35:30 +08:00
|
|
|
hasMore: false,
|
|
|
|
fileOwner: '',
|
|
|
|
isReloadingData: false,
|
2023-05-13 15:42:54 +08:00
|
|
|
};
|
2023-11-15 16:28:02 +08:00
|
|
|
this.currentPage = 1;
|
2023-05-13 15:42:54 +08:00
|
|
|
}
|
|
|
|
|
2023-11-15 16:28:02 +08:00
|
|
|
// listSdocDailyHistoryDetail
|
2023-05-13 15:42:54 +08:00
|
|
|
|
2023-11-15 16:28:02 +08:00
|
|
|
componentDidMount() {
|
|
|
|
this.firstLoadSdocHistory();
|
2023-05-13 15:42:54 +08:00
|
|
|
}
|
|
|
|
|
2023-11-15 16:28:02 +08:00
|
|
|
firstLoadSdocHistory() {
|
|
|
|
this.currentPage = 1;
|
|
|
|
seafileAPI.listSdocHistory(docUuid, this.currentPage, PER_PAGE).then(res => {
|
|
|
|
const result = res.data;
|
|
|
|
const resultCount = result.histories.length;
|
2023-06-19 17:35:30 +08:00
|
|
|
this.setState({
|
2023-11-15 16:28:02 +08:00
|
|
|
historyGroups: this.formatHistories(result.histories),
|
|
|
|
hasMore: resultCount >= PER_PAGE,
|
2023-06-19 17:35:30 +08:00
|
|
|
isLoading: false,
|
|
|
|
fileOwner: result.histories[0].creator_email,
|
|
|
|
});
|
2023-11-15 16:28:02 +08:00
|
|
|
if (result.histories[0]) {
|
|
|
|
this.props.onSelectHistoryVersion(result.histories[0], result.histories[1]);
|
|
|
|
}
|
|
|
|
}).catch((error) => {
|
|
|
|
this.setState({isLoading: false});
|
|
|
|
throw Error('there has an error in server');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
formatHistories(histories) {
|
|
|
|
const oldHistoryGroups = this.state.historyGroups;
|
|
|
|
if (!Array.isArray(histories) || histories.length === 0) return oldHistoryGroups;
|
|
|
|
const newHistoryGroups = oldHistoryGroups.slice(0);
|
|
|
|
histories.forEach(history => {
|
|
|
|
const { date } = history;
|
|
|
|
const month = moment(date).format('YYYY-MM');
|
|
|
|
const monthItem = newHistoryGroups.find(item => item.month === month);
|
|
|
|
if (monthItem) {
|
|
|
|
monthItem.children.push({ day: moment(date).format('YYYY-MM-DD'), showDaily: false, children: [ history ] });
|
|
|
|
} else {
|
|
|
|
newHistoryGroups.push({
|
|
|
|
month,
|
|
|
|
children: [
|
|
|
|
{ day: moment(date).format('YYYY-MM-DD'), showDaily: false, children: [ history ] }
|
|
|
|
]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return newHistoryGroups;
|
2023-05-13 15:42:54 +08:00
|
|
|
}
|
|
|
|
|
2023-06-19 17:35:30 +08:00
|
|
|
updateResultState(result) {
|
2023-11-15 16:28:02 +08:00
|
|
|
const resultCount = result.histories.length;
|
|
|
|
this.setState({
|
|
|
|
historyGroups: this.formatHistories(result.histories),
|
|
|
|
hasMore: resultCount >= PER_PAGE,
|
|
|
|
isLoading: false,
|
|
|
|
fileOwner: result.histories[0].creator_email,
|
|
|
|
});
|
2023-06-19 17:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
loadMore = () => {
|
|
|
|
if (!this.state.isReloadingData) {
|
2023-11-15 16:28:02 +08:00
|
|
|
this.currentPage = this.currentPage + 1;
|
|
|
|
this.setState({ isReloadingData: true }, () => {
|
|
|
|
seafileAPI.listSdocHistory(docUuid, this.currentPage, PER_PAGE).then(res => {
|
|
|
|
this.updateResultState(res.data);
|
|
|
|
this.setState({isReloadingData: false});
|
|
|
|
});
|
2023-06-19 17:35:30 +08:00
|
|
|
});
|
2023-05-13 15:42:54 +08:00
|
|
|
}
|
2023-09-13 08:40:50 +08:00
|
|
|
};
|
2023-06-19 17:35:30 +08:00
|
|
|
|
|
|
|
renameHistoryVersion = (objID, newName) => {
|
|
|
|
seafileAPI.renameSdocHistory(docUuid, objID, newName).then((res) => {
|
|
|
|
this.setState({
|
2023-11-15 16:28:02 +08:00
|
|
|
historyGroups: this.state.historyGroups.map(item => {
|
2023-06-19 17:35:30 +08:00
|
|
|
if (item.obj_id == objID) {
|
|
|
|
item.name = newName;
|
|
|
|
}
|
|
|
|
return item;
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}).catch(error => {
|
|
|
|
const errorMessage = Utils.getErrorMsg(error, true);
|
|
|
|
this.setState({ isLoading: false, errorMessage: errorMessage });
|
|
|
|
});
|
2023-09-13 08:40:50 +08:00
|
|
|
};
|
2023-05-13 15:42:54 +08:00
|
|
|
|
|
|
|
onScrollHandler = (event) => {
|
|
|
|
const clientHeight = event.target.clientHeight;
|
|
|
|
const scrollHeight = event.target.scrollHeight;
|
|
|
|
const scrollTop = event.target.scrollTop;
|
|
|
|
const isBottom = (clientHeight + scrollTop + 1 >= scrollHeight);
|
2023-06-19 17:35:30 +08:00
|
|
|
if (isBottom && this.state.hasMore) {
|
2023-05-13 15:42:54 +08:00
|
|
|
this.loadMore();
|
|
|
|
}
|
2023-09-13 08:40:50 +08:00
|
|
|
};
|
2023-05-13 15:42:54 +08:00
|
|
|
|
2023-06-19 17:35:30 +08:00
|
|
|
restoreVersion = (currentItem) => {
|
|
|
|
const { commit_id, path } = currentItem;
|
|
|
|
editUtilities.revertFile(path, commit_id).then(res => {
|
2023-05-13 15:42:54 +08:00
|
|
|
if (res.data.success) {
|
2023-11-15 16:28:02 +08:00
|
|
|
this.setState({isLoading: true}, () => {
|
|
|
|
this.firstLoadSdocHistory();
|
|
|
|
});
|
2023-05-13 15:42:54 +08:00
|
|
|
}
|
2023-06-19 17:35:30 +08:00
|
|
|
let message = gettext('Successfully restored.');
|
|
|
|
toaster.success(message);
|
2023-05-13 15:42:54 +08:00
|
|
|
}).catch(error => {
|
|
|
|
const errorMessage = Utils.getErrorMsg(error, true);
|
|
|
|
toaster.danger(gettext(errorMessage));
|
|
|
|
});
|
2023-09-13 08:40:50 +08:00
|
|
|
};
|
2023-05-13 15:42:54 +08:00
|
|
|
|
2023-11-15 16:28:02 +08:00
|
|
|
onSelectHistoryVersion = (path) => {
|
2023-05-13 15:42:54 +08:00
|
|
|
const { isShowChanges } = this.props;
|
2023-11-15 16:28:02 +08:00
|
|
|
const { historyGroups } = this.state;
|
|
|
|
const historyVersion = historyGroups[path[0]].children[path[1]].children[path[2]];
|
|
|
|
this.props.onSelectHistoryVersion(historyVersion, isShowChanges);
|
2023-09-13 08:40:50 +08:00
|
|
|
};
|
2023-05-13 15:42:54 +08:00
|
|
|
|
2023-06-15 16:27:24 +08:00
|
|
|
copyHistoryFile = (historyVersion) => {
|
2023-06-19 17:35:30 +08:00
|
|
|
const { path, obj_id, ctime_format } = historyVersion;
|
|
|
|
seafileAPI.sdocCopyHistoryFile(historyRepoID, path, obj_id, ctime_format).then(res => {
|
2023-06-15 16:27:24 +08:00
|
|
|
let message = gettext('Successfully copied %(name)s.');
|
|
|
|
let filename = res.data.file_name;
|
|
|
|
message = message.replace('%(name)s', filename);
|
|
|
|
toaster.success(message);
|
|
|
|
}).catch(error => {
|
|
|
|
const errorMessage = Utils.getErrorMsg(error, true);
|
|
|
|
toaster.danger(gettext(errorMessage));
|
|
|
|
});
|
2023-09-13 08:40:50 +08:00
|
|
|
};
|
2023-06-15 16:27:24 +08:00
|
|
|
|
2023-11-15 16:28:02 +08:00
|
|
|
showDailyHistory = (path, callback) => {
|
|
|
|
const { historyGroups } = this.state;
|
|
|
|
const newHistoryGroups = historyGroups.slice(0);
|
|
|
|
const dayHistoryGroup = newHistoryGroups[path[0]].children[path[1]];
|
|
|
|
if (dayHistoryGroup.showDaily) {
|
|
|
|
dayHistoryGroup.showDaily = false;
|
|
|
|
this.setState({ historyGroups: newHistoryGroups }, () => {
|
|
|
|
callback && callback();
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (dayHistoryGroup.children.length > 1) {
|
|
|
|
dayHistoryGroup.showDaily = true;
|
|
|
|
this.setState({ historyGroups: newHistoryGroups }, () => {
|
|
|
|
callback && callback();
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
seafileAPI.listSdocDailyHistoryDetail(docUuid, dayHistoryGroup.children[0].ctime).then(res => {
|
|
|
|
const histories = res.data.histories;
|
|
|
|
dayHistoryGroup.children.push(...histories);
|
|
|
|
dayHistoryGroup.showDaily = true;
|
|
|
|
this.setState({ historyGroups: newHistoryGroups }, () => {
|
|
|
|
callback && callback();
|
|
|
|
});
|
|
|
|
}).catch(error => {
|
|
|
|
const errorMessage = Utils.getErrorMsg(error, true);
|
|
|
|
toaster.danger(gettext(errorMessage));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2023-05-13 15:42:54 +08:00
|
|
|
renderHistoryVersions = () => {
|
2023-11-15 16:28:02 +08:00
|
|
|
const { isLoading, historyGroups, errorMessage } = this.state;
|
|
|
|
if (historyGroups.length === 0) {
|
2023-05-13 15:42:54 +08:00
|
|
|
if (isLoading) {
|
|
|
|
return (
|
|
|
|
<div className="h-100 w-100 d-flex align-items-center justify-content-center">
|
|
|
|
<Loading />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (errorMessage) {
|
|
|
|
return (
|
|
|
|
<div className="h-100 w-100 d-flex align-items-center justify-content-center error-message">
|
|
|
|
{gettext(errorMessage)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="h-100 w-100 d-flex align-items-center justify-content-center empty-tip-color">
|
2023-08-25 18:04:38 +08:00
|
|
|
{gettext('No version history')}
|
2023-05-13 15:42:54 +08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2023-11-15 16:28:02 +08:00
|
|
|
{historyGroups.map((monthHistoryGroup, historyGroupIndex) => {
|
2023-05-13 15:42:54 +08:00
|
|
|
return (
|
2023-11-15 16:28:02 +08:00
|
|
|
<Fragment key={monthHistoryGroup.month}>
|
|
|
|
<div className="history-list-item history-month-title" key={monthHistoryGroup.month}>{monthHistoryGroup.month}</div>
|
|
|
|
{monthHistoryGroup.children.map((dayHistoryGroup, dayHistoryGroupIndex) => {
|
|
|
|
const { children, showDaily } = dayHistoryGroup;
|
|
|
|
const displayHistories = showDaily ? children : children.slice(0, 1);
|
|
|
|
return displayHistories.map((history, index) => {
|
|
|
|
return (
|
|
|
|
<HistoryVersion
|
|
|
|
key={history.commit_id}
|
|
|
|
path={[historyGroupIndex, dayHistoryGroupIndex, index]}
|
|
|
|
showDaily={index === 0 && showDaily}
|
|
|
|
currentVersion={this.props.currentVersion}
|
|
|
|
historyVersion={history}
|
|
|
|
onSelectHistoryVersion={this.onSelectHistoryVersion}
|
|
|
|
onRestore={this.restoreVersion}
|
|
|
|
onCopy={this.copyHistoryFile}
|
|
|
|
renameHistoryVersion={this.renameHistoryVersion}
|
|
|
|
showDailyHistory={this.showDailyHistory}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}).flat()}
|
|
|
|
</Fragment>
|
2023-05-13 15:42:54 +08:00
|
|
|
);
|
|
|
|
})}
|
|
|
|
{isLoading && (
|
|
|
|
<div className="loading-more d-flex align-items-center justify-content-center w-100">
|
|
|
|
<Loading />
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
);
|
2023-09-13 08:40:50 +08:00
|
|
|
};
|
2023-05-13 15:42:54 +08:00
|
|
|
|
|
|
|
onShowChanges = () => {
|
2023-11-15 16:28:02 +08:00
|
|
|
const { isShowChanges } = this.props;
|
|
|
|
this.props.onShowChanges(!isShowChanges);
|
2023-09-13 08:40:50 +08:00
|
|
|
};
|
2023-05-13 15:42:54 +08:00
|
|
|
|
|
|
|
render() {
|
2023-11-15 16:28:02 +08:00
|
|
|
const { historyGroups } = this.state;
|
2023-05-13 15:42:54 +08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="sdoc-file-history-panel h-100 o-hidden d-flex flex-column">
|
|
|
|
<div className="sdoc-file-history-select-range">
|
|
|
|
<div className="sdoc-file-history-select-range-title">
|
|
|
|
{gettext('History Versions')}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div
|
2023-11-15 16:28:02 +08:00
|
|
|
className={classnames('sdoc-file-history-versions', { 'o-hidden': historyGroups.length === 0 } )}
|
2023-05-13 15:42:54 +08:00
|
|
|
onScroll={this.onScrollHandler}
|
|
|
|
>
|
|
|
|
{this.renderHistoryVersions()}
|
|
|
|
</div>
|
|
|
|
<div className="sdoc-file-history-diff-switch d-flex align-items-center">
|
|
|
|
<Switch
|
|
|
|
checked={this.props.isShowChanges}
|
|
|
|
placeholder={gettext('Show changes')}
|
|
|
|
className="sdoc-history-show-changes w-100"
|
|
|
|
size="small"
|
|
|
|
onChange={this.onShowChanges}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SidePanel.propTypes = {
|
|
|
|
isShowChanges: PropTypes.bool,
|
|
|
|
currentVersion: PropTypes.object,
|
|
|
|
onSelectHistoryVersion: PropTypes.func,
|
|
|
|
onShowChanges: PropTypes.func,
|
|
|
|
};
|
|
|
|
|
|
|
|
export default SidePanel;
|