diff --git a/frontend/config/webpack.config.dev.js b/frontend/config/webpack.config.dev.js index 4f292b0fd2..f4b090b052 100644 --- a/frontend/config/webpack.config.dev.js +++ b/frontend/config/webpack.config.dev.js @@ -69,6 +69,11 @@ module.exports = { require.resolve('react-dev-utils/webpackHotDevClient'), paths.appSrc + "/file-history.js", ], + fileHistoryOld: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/file-history-old.js", + ], app: [ require.resolve('./polyfills'), require.resolve('react-dev-utils/webpackHotDevClient'), diff --git a/frontend/config/webpack.config.prod.js b/frontend/config/webpack.config.prod.js index ba1514849f..6fbe09b931 100644 --- a/frontend/config/webpack.config.prod.js +++ b/frontend/config/webpack.config.prod.js @@ -62,6 +62,7 @@ module.exports = { wiki: [require.resolve('./polyfills'), paths.appSrc + "/wiki.js"], repoview: [require.resolve('./polyfills'), paths.appSrc + "/repo-wiki-mode.js"], fileHistory: [require.resolve('./polyfills'), paths.appSrc + "/file-history.js"], + fileHistoryOld: [require.resolve('./polyfills'), paths.appSrc + "/file-history-old.js"], app: [require.resolve('./polyfills'), paths.appSrc + "/app.js"], draft: [require.resolve('./polyfills'), paths.appSrc + "/draft.js"], draw: [require.resolve('./polyfills'), paths.appSrc + "/draw/draw.js"], diff --git a/frontend/src/components/file-view/file-toolbar.js b/frontend/src/components/file-view/file-toolbar.js index b74cb146d4..d035b1e76d 100644 --- a/frontend/src/components/file-view/file-toolbar.js +++ b/frontend/src/components/file-view/file-toolbar.js @@ -100,7 +100,7 @@ class FileToolbar extends React.Component { icon="fa fa-history" text={gettext('History')} tag="a" - href={`${siteRoot}repo/file_revisions/${repoID}/?p=${encodeURIComponent(filePath)}&referer=${encodeURIComponent(location.href)}`} + href={`${siteRoot}repo/file_revisions/${repoID}/?p=${encodeURIComponent(filePath)}`} /> )} {(canEditFile && !err) && diff --git a/frontend/src/css/file-history-old.css b/frontend/src/css/file-history-old.css new file mode 100644 index 0000000000..a5e8c7024a --- /dev/null +++ b/frontend/src/css/file-history-old.css @@ -0,0 +1,70 @@ +@media (min-width: 768px){ + .old-history-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #e8e8e8; + background-color: #f4f4f7; + font-size: 1rem; + padding: 0.5rem 1rem; + } + + .old-history-main { + padding-top: 16px; + margin-left: 8.333333%; + display: inline !important; + overflow: auto; + position: relative; + min-height: 1px; + padding-right: .9375rem; + padding-left: .9375rem; + } + + .old-history-main .go-back { + font-size: 25px; + color: #ccc; + float: left; + } + + .old-history-main .go-back:hover { + color:#ff9933; + text-decoration: none; + } + + .old-history-main p { + color: #808080; + font-size: 12px; + margin-top: 0; + margin-bottom: 1rem; + } + + .old-history-main h2 { + font-size: 1.5em; + color: #222; + font-weight: bold; + line-height: 1.5; + } + + .old-history-main .file-name { + color: #ee8204; + word-wrap: break-word; + } + + .old-history-main .commit-list { + width: 100%; + margin: 8px 0 40px; + } + + .old-history-main .commit-list .avatar { + width: 16px; + height: 16px; + border-radius: 2px; + } + + .old-history-main .commit-list .username { + vertical-align: middle; + color: #eb8205; + text-decoration: none; + font-weight: bold; + } +} \ No newline at end of file diff --git a/frontend/src/file-history-old.js b/frontend/src/file-history-old.js new file mode 100644 index 0000000000..f606fa9ec3 --- /dev/null +++ b/frontend/src/file-history-old.js @@ -0,0 +1,209 @@ +import React, { Fragment } from 'react'; +import ReactDOM from 'react-dom'; +import { Utils } from './utils/utils'; +import { seafileAPI } from './utils/seafile-api'; +import { siteRoot, gettext, PER_PAGE, filePath, fileName, historyRepoID, useNewAPI } from './utils/constants'; +import editUtilties from './utils/editor-utilties'; +import Loading from './components/loading'; +import Logo from './components/logo'; +import CommonToolbar from './components/toolbar/common-toolbar'; +import HistoryItem from './pages/file-history-old/history-item'; + +import './assets/css/fa-solid.css'; +import './assets/css/fa-regular.css'; +import './assets/css/fontawesome.css'; +import './css/layout.css'; +import './css/toolbar.css'; +import './css/search.css'; +import './css/file-history-old.css'; + +class FileHistory extends React.Component { + + constructor(props) { + super(props); + this.state = { + historyList: [], + currentPage: 1, + hasMore: false, + isLoading: true, + isError: false, + fileOwner: '', + isReloadingData: false, + }; + } + + onSearchedClick = (selectedItem) => { + if (selectedItem.is_dir === true) { + let url = siteRoot + 'library/' + selectedItem.repo_id + '/' + selectedItem.repo_name + selectedItem.path; + let newWindow = window.open('about:blank'); + newWindow.location.href = url; + } else { + let url = siteRoot + 'lib/' + selectedItem.repo_id + '/file' + Utils.encodePath(selectedItem.path); + let newWindow = window.open('about:blank'); + newWindow.location.href = url; + } + } + + onCloseSidePanel = () => { + // do nothing + } + + componentDidMount() { + if (useNewAPI) { + editUtilties.listFileHistoryRecords(filePath, 1, PER_PAGE).then(res => { + let historyList = res.data; + if (historyList.length === 0) { + this.setState({isLoading: false}); + throw Error('there has an error in server'); + } + this.initResultState(res.data); + }); + } else { + seafileAPI.getFileHistory(historyRepoID, filePath).then((res) => { + let historyList = res.data; + if (historyList.length === 0) { + this.setState({isLoading: false}); + throw Error('there has an error in server'); + } + this.initResultState(res.data); + }) + } + + } + + refershFileList() { + editUtilties.listFileHistoryRecords(filePath, 1, PER_PAGE).then(res => { + this.initResultState(res.data); + }); + } + + initResultState(result) { + if (useNewAPI) { + if (result.data.length) { + this.setState({ + historyList: result.data, + currentPage: result.page, + hasMore: result.total_count > (PER_PAGE * this.state.currentPage), + isLoading: false, + isError: false, + fileOwner: result.data[0].creator_email, + }); + } + } else { + if (result.data.length) { + this.setState({ + historyList: result.data, + isLoading: false, + isError: false, + fileOwner: result.data[0].creator_email, + }); + } + } + } + + updateResultState(result) { + if (result.data.length) { + this.setState({ + historyList: [...this.state.historyList, ...result.data], + currentPage: result.page, + hasMore: result.total_count > (PER_PAGE * this.state.currentPage), + isLoading: false, + isError: false, + fileOwner: result.data[0].creator_email + }); + } + } + + reloadMore = () => { + if (!this.state.isReloadingData) { + let currentPage = this.state.currentPage + 1; + this.setState({ + currentPage: currentPage, + isReloadingData: true, + }); + editUtilties.listFileHistoryRecords(filePath, currentPage, PER_PAGE).then(res => { + this.updateResultState(res.data); + this.setState({isReloadingData: false}); + }); + } + } + + 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.state.hasMore; + if (isBottom && hasMore) { + this.reloadMore(); + } + } + + onItemRestore = (item) => { + let commitId = item.commit_id; + editUtilties.revertFile(filePath, commitId).then(res => { + if (res.data.success) { + this.setState({isLoading: true}); + this.refershFileList(); + } + }); + } + + render() { + return ( + + +
+
+
+ + + +

{fileName}{gettext(' History Versions')}

+

{gettext('Tip: a new version will be generated after each modification, and you can restore the file to a previous version.')}

+
+
+ {this.state.isLoading && } + {!this.state.isLoading && + + + + + + + + + + + {this.state.historyList.map((item, index) => { + return ( + + ); + })} + +
{gettext('Time')}{gettext('Modifier')}{gettext('Size')}{gettext('Operation')}
+ } +
+
+
+
+ ); + } +} + +ReactDOM.render ( + , + document.getElementById('wrapper') +); diff --git a/frontend/src/pages/file-history-old/history-item.js b/frontend/src/pages/file-history-old/history-item.js new file mode 100644 index 0000000000..417413542f --- /dev/null +++ b/frontend/src/pages/file-history-old/history-item.js @@ -0,0 +1,76 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import moment from 'moment'; +import { gettext, siteRoot, filePath, historyRepoID } from '../../utils/constants'; +import URLDecorator from '../../utils/url-decorator'; + +moment.locale(window.app.config.lang); + +const propTypes = { + item: PropTypes.object.isRequired, + index: PropTypes.number.isRequired, + onItemRestore: PropTypes.func.isRequired, +}; + +class HistoryItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + active: false, + }; + } + + onMouseEnter = () => { + this.setState({ + active: true + }); + } + + onMouseLeave = () => { + this.setState({ + active: false + }); + } + + onItemRestore = () => { + this.props.onItemRestore(this.props.item); + } + + render() { + let item = this.props.item; + let downloadUrl = URLDecorator.getUrl({type: 'download_historic_file', filePath: filePath, objID: item.rev_file_id}); + let userProfileURL = `${siteRoot}profile/${encodeURIComponent(item.creator_email)}/`; + let viewUrl = `${siteRoot}repo/${historyRepoID}/history/files/?obj_id=${item.rev_file_id}&commit_id=${item.commit_id}&p=${filePath}`; + let diffUrl = `${siteRoot}repo/text_diff/${historyRepoID}/?commit=${item.commit_id}&p=${filePath}`; + return ( + + + + + {this.props.index === 0 ? gettext('(current version)') : ''} + + + {' '} + {item.creator_name} + + {item.size} + + {this.state.active && + + {this.props.index === 0 ? '' : {gettext('Restore')}} + {gettext('Download')} + {gettext('View')} + {gettext('Diff')} + + } + + + + ); + } +} + +HistoryItem.propTypes = propTypes; + +export default HistoryItem; diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 400b5e1cba..cf2ade354e 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -66,6 +66,7 @@ export const historyRepoID = window.fileHistory ? window.fileHistory.pageOptions 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 : ''; +export const useNewAPI = window.fileHistory ? window.fileHistory.pageOptions.use_new_api : ''; // Draft review export const draftFilePath = window.draft ? window.draft.config.draftFilePath: ''; diff --git a/seahub/templates/file_revisions_old.html b/seahub/templates/file_revisions_old.html new file mode 100644 index 0000000000..6bbcc1ac87 --- /dev/null +++ b/seahub/templates/file_revisions_old.html @@ -0,0 +1,25 @@ +{% extends "base_for_react.html" %} +{% load render_bundle from webpack_loader %} + +{% block extra_style %} +{% render_bundle 'fileHistoryOld' 'css'%} +{% endblock %} + +{% block extra_script %} + + {% render_bundle 'fileHistoryOld' 'js'%} +{% endblock %} diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index ee87e2b993..24aa14f461 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -830,25 +830,18 @@ def file_revisions(request, repo_id): if repo_perm != 'rw' or (is_locked and not locked_by_me): can_revert_file = False - # for 'go back' - referer = request.GET.get('referer', '') - - # Whether use new file revisions page which read file history from db. - if request.GET.get('_new', None) is not None: - if request.GET.get('_new') == '0': - use_new_page = False - else: - use_new_page = True + # Whether use new file history API which read file history from db. + suffix_list = seafevents_api.get_file_history_suffix() + if suffix_list and isinstance(suffix_list, list): + suffix_list = [x.lower() for x in suffix_list] else: - suffix_list = seafevents_api.get_file_history_suffix() - if suffix_list and isinstance(suffix_list, list): - suffix_list = [x.lower() for x in suffix_list] - else: - logger.error('Wrong type of suffix_list: %s' % repr(suffix_list)) - suffix_list = [] - use_new_page = True if file_ext in suffix_list else False + logger.error('Wrong type of suffix_list: %s' % repr(suffix_list)) + suffix_list = [] + use_new_api = True if file_ext in suffix_list else False - if use_new_page: + use_new_style = True if filetype == 'markdown' else False + + if use_new_style: return render(request, 'file_revisions_new.html', { 'repo': repo, 'path': path, @@ -857,10 +850,9 @@ def file_revisions(request, repo_id): 'is_owner': is_owner, 'can_compare': can_compare, 'can_revert_file': can_revert_file, - 'referer': referer, }) - return render(request, 'file_revisions.html', { + return render(request, 'file_revisions_old.html', { 'repo': repo, 'path': path, 'u_filename': u_filename, @@ -869,7 +861,7 @@ def file_revisions(request, repo_id): 'can_compare': can_compare, 'can_revert_file': can_revert_file, 'can_download_file': parse_repo_perm(repo_perm).can_download, - 'referer': referer, + 'use_new_api': use_new_api })