diff --git a/frontend/config/webpack.entry.js b/frontend/config/webpack.entry.js index 2b6617403d..575b9281da 100644 --- a/frontend/config/webpack.entry.js +++ b/frontend/config/webpack.entry.js @@ -8,6 +8,7 @@ const entryFiles = { wiki: "/wiki.js", fileHistory: "/file-history.js", fileHistoryOld: "/file-history-old.js", + sdocFileHistory: "/pages/sdoc-file-history/index.js", app: "/app.js", draft: "/draft.js", sharedDirView: "/shared-dir-view.js", diff --git a/frontend/src/pages/sdoc-file-history/index.js b/frontend/src/pages/sdoc-file-history/index.js new file mode 100644 index 0000000000..1454b25c22 --- /dev/null +++ b/frontend/src/pages/sdoc-file-history/index.js @@ -0,0 +1,261 @@ +import React, { Fragment } from 'react'; +import ReactDom from 'react-dom'; +import { Button } from 'reactstrap'; +import { Utils } from '../../utils/utils'; +import { seafileAPI } from '../../utils/seafile-api'; +import { gettext, PER_PAGE, filePath, fileName, historyRepoID, canDownload, canCompare } from '../../utils/constants'; +import editUtilities from '../../utils/editor-utilities'; +import Loading from '../../components/loading'; +import Logo from '../../components/logo'; +import CommonToolbar from '../../components/toolbar/common-toolbar'; +import HistoryItem from '../file-history-old/history-item'; + +import '../../css/layout.css'; +import '../../css/toolbar.css'; +import '../../css/search.css'; +import '../../css/file-history-old.css'; + +class SdocFileHistory extends React.Component { + + constructor(props) { + super(props); + this.state = { + historyList: [], + currentPage: 1, + hasMore: false, + nextCommit: undefined, + filePath: '', + oldFilePath: '', + isLoading: true, + isReloadingData: false, + }; + } + + componentDidMount() { + this.listOldHistoryRecords(historyRepoID, filePath); + } + + listNewHistoryRecords = (filePath, PER_PAGE) => { + editUtilities.listFileHistoryRecords(filePath, 1, PER_PAGE).then(res => { + let historyData = res.data; + if (!historyData) { + this.setState({isLoading: false}); + throw Error('There is an error in server.'); + } + this.initNewRecords(res.data); + }); + } + + listOldHistoryRecords = (repoID, filePath) => { + seafileAPI.listOldFileHistoryRecords(repoID, filePath).then((res) => { + let historyData = res.data; + if (!historyData) { + this.setState({isLoading: false}); + throw Error('There is an error in server.'); + } + this.initOldRecords(res.data); + }); + } + + initNewRecords(result) { + if (result.total_count < 5) { + if (result.data.length) { + let commitID = result.data[result.data.length-1].commit_id; + let path = result.data[result.data.length-1].path; + let oldPath = result.data[result.data.length-1].old_path; + path = oldPath ? oldPath : path; + seafileAPI.listOldFileHistoryRecords(historyRepoID, path, commitID).then((res) => { + if (!res.data) { + this.setState({isLoading: false}); + throw Error('There is an error in server.'); + } + this.setState({ + historyList: result.data.concat(res.data.data.slice(1, res.data.data.length)), + isLoading: false, + }); + }); + } else { + seafileAPI.listOldFileHistoryRecords(historyRepoID, filePath).then((res) => { + if (!res.data) { + this.setState({isLoading: false}); + throw Error('There is an error in server.'); + } + this.setState({ + historyList: res.data.data, + isLoading: false, + }); + }); + } + } else { + this.setState({ + historyList: result.data, + currentPage: result.page, + hasMore: result.total_count > (PER_PAGE * this.state.currentPage), + isLoading: false, + }); + } + } + + initOldRecords(result) { + if (result.data.length) { + this.setState({ + historyList: result.data, + nextCommit: result.next_start_commit, + filePath: result.data[result.data.length-1].path, + oldFilePath: result.data[result.data.length-1].rev_renamed_old_path, + isLoading: false, + }); + } else { + this.setState({nextCommit: result.next_start_commit,}); + if (this.state.nextCommit) { + seafileAPI.listOldFileHistoryRecords(historyRepoID, filePath, this.state.nextCommit).then((res) => { + this.initOldRecords(res.data); + }); + } else { + this.setState({isLoading: 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(); + } + } + + reloadMore = () => { + if (!this.state.isReloadingData) { + const commitID = this.state.nextCommit; + const filePath = this.state.filePath; + const oldFilePath = this.state.oldFilePath; + this.setState({ isReloadingData: true }); + if (oldFilePath) { + seafileAPI.listOldFileHistoryRecords(historyRepoID, oldFilePath, commitID).then((res) => { + this.updateOldRecords(res.data, oldFilePath); + }); + } else { + seafileAPI.listOldFileHistoryRecords(historyRepoID, filePath, commitID).then((res) => { + this.updateOldRecords(res.data, filePath); + }); + } + } + } + + updateNewRecords(result) { + this.setState({ + historyList: [...this.state.historyList, ...result.data], + currentPage: result.page, + hasMore: result.total_count > (PER_PAGE * this.state.currentPage), + isReloadingData: false, + }); + } + + updateOldRecords(result, filePath) { + if (result.data.length) { + this.setState({ + historyList: [...this.state.historyList, ...result.data], + nextCommit: result.next_start_commit, + filePath: result.data[result.data.length-1].path, + oldFilePath: result.data[result.data.length-1].rev_renamed_old_path, + isReloadingData: false, + }); + } else { + this.setState({nextCommit: result.next_start_commit,}); + if (this.state.nextCommit) { + seafileAPI.listOldFileHistoryRecords(historyRepoID, filePath, this.state.nextCommit).then((res) => { + this.updateOldRecords(res.data, filePath); + }); + } + } + } + + onItemRestore = (item) => { + let commitId = item.commit_id; + let filePath = item.path; + editUtilities.revertFile(filePath, commitId).then(res => { + if (res.data.success) { + this.setState({ isLoading: true }); + this.refreshFileList(); + } + }); + } + + refreshFileList() { + seafileAPI.listOldFileHistoryRecords(historyRepoID, filePath).then((res) => { + this.initOldRecords(res.data); + }); + } + + onSearchedClick = (searchedItem) => { + Utils.handleSearchedItemClick(searchedItem); + } + + onBackClick = (event) => { + event.preventDefault(); + window.history.back(); + } + + render() { + return ( + + +
+
+ + + + +

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

+
+ + + + + + + + + + + {!this.state.isLoading && + + {this.state.historyList.map((item, index) => { + return ( + + ); + })} + + } +
{gettext('Time')}{gettext('Modifier')}{gettext('Size')}
+ {(this.state.isReloadingData || this.state.isLoading) && } + {this.state.nextCommit && !this.state.isLoading && !this.state.isReloadingData && + + } +
+
+
+
+ ); + } +} + +ReactDom.render(, document.getElementById('wrapper')); diff --git a/frontend/src/shared-file-view-sdoc.js b/frontend/src/shared-file-view-sdoc.js index 5825ad927e..a342f43795 100644 --- a/frontend/src/shared-file-view-sdoc.js +++ b/frontend/src/shared-file-view-sdoc.js @@ -4,9 +4,10 @@ import { SimpleViewer } from '@seafile/sdoc-editor'; import { I18nextProvider } from 'react-i18next'; import i18n from './_i18n/i18n-sdoc-editor'; import Loading from './components/loading'; +import { Utils } from './utils/utils'; -const { serviceURL } = window.app.config; -const { username } = window.app.pageOptions; +const { serviceURL, siteRoot } = window.app.config; +const { username, filePerm } = window.app.pageOptions; const { repoID, filePath, fileName, rawPath } = window.shared.pageOptions; window.seafile = { @@ -16,6 +17,9 @@ window.seafile = { docPath: filePath, serviceUrl: serviceURL, username, + siteRoot, + docPerm: filePerm, + historyURL: Utils.generateHistoryURL(siteRoot, repoID, filePath), }; ReactDom.render( diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index 879d7fe100..e901e56f5e 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -1536,6 +1536,11 @@ export const Utils = { updateTabTitle: function(content) { const title = document.getElementsByTagName('title')[0]; title.innerText = content; + }, + + generateHistoryURL: function(siteRoot, repoID, path) { + if (!siteRoot || !repoID || !path) return ''; + return siteRoot + 'repo/file_revisions/' + repoID + '/?p=' + this.encodePath(path); } }; diff --git a/frontend/src/view-file-sdoc.js b/frontend/src/view-file-sdoc.js index 5e414170e0..07d9c9df33 100644 --- a/frontend/src/view-file-sdoc.js +++ b/frontend/src/view-file-sdoc.js @@ -4,10 +4,11 @@ import { SimpleEditor } from '@seafile/sdoc-editor'; import { I18nextProvider } from 'react-i18next'; import i18n from './_i18n/i18n-sdoc-editor'; import Loading from './components/loading'; +import { Utils } from './utils/utils'; -const { serviceURL, avatarURL } = window.app.config; +const { serviceURL, avatarURL, siteRoot } = window.app.config; const { username, name } = window.app.userInfo; -const { repoID, docPath, docName, docUuid, seadocAccessToken, seadocServerUrl } = window.app.pageOptions; +const { repoID, docPath, docName, docUuid, seadocAccessToken, seadocServerUrl, filePerm } = window.app.pageOptions; window.seafile = { repoID, @@ -21,6 +22,9 @@ window.seafile = { name, username, avatarURL, + siteRoot, + docPerm: filePerm, + historyURL: Utils.generateHistoryURL(siteRoot, repoID, docPath), }; ReactDom.render( diff --git a/seahub/templates/sdoc_file_revisions.html b/seahub/templates/sdoc_file_revisions.html new file mode 100644 index 0000000000..73ab7bfecf --- /dev/null +++ b/seahub/templates/sdoc_file_revisions.html @@ -0,0 +1,23 @@ +{% extends "base_for_react.html" %} +{% load render_bundle from webpack_loader %} + +{% block extra_style %} +{% render_bundle 'sdocFileHistory' 'css'%} +{% endblock %} + +{% block extra_script %} + + {% render_bundle 'sdocFileHistory' 'js'%} +{% endblock %} diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 822eb251ff..e7126c0061 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -751,8 +751,8 @@ def file_revisions(request, repo_id): u_filename = os.path.basename(path) - filetype, file_ext = [x.lower() for x in get_file_type_and_ext(u_filename)] - if filetype == 'text' or filetype == 'markdown': + file_type, file_ext = [x.lower() for x in get_file_type_and_ext(u_filename)] + if file_type == 'text' or file_type == 'markdown' or file_type == 'sdoc': can_compare = True else: can_compare = False @@ -778,6 +778,17 @@ def file_revisions(request, repo_id): if repo_perm != 'rw' or (is_locked and not locked_by_me): can_revert_file = False + if file_type == 'sdoc': + return render(request, 'sdoc_file_revisions.html', { + 'repo': repo, + 'path': path, + 'u_filename': u_filename, + 'zipped': zipped, + 'is_owner': is_owner, + 'can_compare': can_compare, + 'can_revert_file': can_revert_file, + }) + # Whether use new file history API which read file history from db. suffix_list = get_file_history_suffix() if suffix_list and isinstance(suffix_list, list): @@ -786,7 +797,7 @@ def file_revisions(request, repo_id): suffix_list = [] use_new_api = True if file_ext in suffix_list else False - use_new_style = True if use_new_api and filetype == 'markdown' else False + use_new_style = True if use_new_api and file_type == 'markdown' else False if use_new_style: return render(request, 'file_revisions_new.html', {