From d3c72eeedc9ca7c63357152cee7fc37a78a9d312 Mon Sep 17 00:00:00 2001 From: C_Q Date: Fri, 4 Jan 2019 18:30:03 +0800 Subject: [PATCH] init shared file view (#2748) --- frontend/config/webpack.config.dev.js | 5 + frontend/config/webpack.config.prod.js | 1 + frontend/package-lock.json | 5 + frontend/package.json | 1 + .../dialog/save-shared-file-dialog.js | 80 +++++++++++ .../components/file-chooser/file-chooser.js | 65 +++++---- frontend/src/css/shared-file-view.css | 77 ++++++++++ frontend/src/shared-file-view-markdown.js | 132 ++++++++++++++++++ .../shared_file_view_react_markdown.html | 24 ++++ seahub/views/file.py | 6 +- 10 files changed, 367 insertions(+), 29 deletions(-) create mode 100644 frontend/src/components/dialog/save-shared-file-dialog.js create mode 100644 frontend/src/css/shared-file-view.css create mode 100644 frontend/src/shared-file-view-markdown.js create mode 100644 seahub/templates/shared_file_view_react_markdown.html diff --git a/frontend/config/webpack.config.dev.js b/frontend/config/webpack.config.dev.js index c9d33652c4..6ca430d0e3 100644 --- a/frontend/config/webpack.config.dev.js +++ b/frontend/config/webpack.config.dev.js @@ -83,6 +83,11 @@ module.exports = { require.resolve('./polyfills'), require.resolve('react-dev-utils/webpackHotDevClient'), paths.appSrc + "/draw/draw.js", + ], + sharedFileViewMarkdown: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/shared-file-view-markdown.js", ] }, diff --git a/frontend/config/webpack.config.prod.js b/frontend/config/webpack.config.prod.js index 192f7e3aa5..3b9aaced76 100644 --- a/frontend/config/webpack.config.prod.js +++ b/frontend/config/webpack.config.prod.js @@ -65,6 +65,7 @@ module.exports = { app: [require.resolve('./polyfills'), paths.appSrc + "/app.js"], draftReview: [require.resolve('./polyfills'), paths.appSrc + "/draft-review.js"], draw: [require.resolve('./polyfills'), paths.appSrc + "/draw/draw.js"], + sharedFileViewMarkdown: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-markdown.js"], }, output: { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 34607b0730..0c1463c901 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12236,6 +12236,11 @@ "graceful-fs": "^4.1.2" } }, + "watermark-dom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/watermark-dom/-/watermark-dom-1.0.0.tgz", + "integrity": "sha1-q3lx6nCz5m8BxN/FiU6SFKvu3KA=" + }, "wbuf": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 02871190a6..170aa94d74 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,6 +39,7 @@ "url-loader": "0.6.2", "url-parse": "^1.4.3", "vfile": "^3.0.0", + "watermark-dom": "^1.0.0", "whatwg-fetch": "2.0.3" }, "scripts": { diff --git a/frontend/src/components/dialog/save-shared-file-dialog.js b/frontend/src/components/dialog/save-shared-file-dialog.js new file mode 100644 index 0000000000..b9b3f78c29 --- /dev/null +++ b/frontend/src/components/dialog/save-shared-file-dialog.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Alert } from 'reactstrap'; +import { gettext } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import FileChooser from '../file-chooser/file-chooser'; + +const propTypes = { + repoID: PropTypes.string.isRequired, + sharedToken: PropTypes.string.isRequired, + toggleCancel: PropTypes.func.isRequired, + handleSaveSharedFile: PropTypes.func.isRequired, +}; + +class SaveSharedFileDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + repo: null, + selectedPath: '', + errMessage: '', + }; + } + + onSaveSharedFile = () => { + seafileAPI.saveSharedFile(this.state.repo.repo_id, this.state.selectedPath, this.props.sharedToken).then((res) => { + this.props.toggleCancel(); + this.props.handleSaveSharedFile(); + }).catch((error) => { + if (error.response) { + this.setState({ + errMessage: error.response.data.error_msg + }); + } + }); + } + + onDirentItemClick = (repo, selectedPath, dirent) => { + if (dirent.type === 'dir') { + this.setState({ + repo: repo, + selectedPath: selectedPath, + }); + } + else { + this.setState({ + repo: null, + selectedPath: '', + }); + } + } + + render() { + return ( + + {gettext('Select Folder')} + + + {this.state.errMessage && {this.state.errMessage}} + + + + { this.state.selectedPath ? + + : + + } + + + ); + } +} + +SaveSharedFileDialog.propTypes = propTypes; + +export default SaveSharedFileDialog; diff --git a/frontend/src/components/file-chooser/file-chooser.js b/frontend/src/components/file-chooser/file-chooser.js index babeecf8de..2217885dc8 100644 --- a/frontend/src/components/file-chooser/file-chooser.js +++ b/frontend/src/components/file-chooser/file-chooser.js @@ -9,7 +9,7 @@ import '../../css/file-chooser.css'; const propTypes = { isShowFile: PropTypes.bool, - repoID: PropTypes.string.isRequired, + repoID: PropTypes.string, onDirentItemClick: PropTypes.func, onRepoItemClick: PropTypes.func, }; @@ -30,26 +30,31 @@ class FileChooser extends React.Component { } componentDidMount() { - let repoID = this.props.repoID; - seafileAPI.getRepoInfo(repoID).then(res => { - // need to optimized - let repoInfo = new RepoInfo(res.data); - this.setState({ - currentRepoInfo: repoInfo, + if (this.props.repoID) { + let repoID = this.props.repoID; + seafileAPI.getRepoInfo(repoID).then(res => { + // need to optimized + let repoInfo = new RepoInfo(res.data); + this.setState({ + currentRepoInfo: repoInfo, + }); }); - }); + } } onOtherRepoToggle = () => { if (!this.state.hasRequest) { - let { currentRepoInfo } = this.state; + let that = this; seafileAPI.listRepos().then(res => { // todo optimized code let repos = res.data.repos; let repoList = []; let repoIdList = []; for(let i = 0; i < repos.length; i++) { - if (repos[i].repo_name === currentRepoInfo.repo_name || repos[i].permission !== 'rw') { + if (repos[i].permission !== 'rw') { + continue; + } + if (that.props.repoID && (repos[i].repo_name === that.state.currentRepoInfo.repo_name)) { continue; } if (repoIdList.indexOf(repos[i].repo_id) > -1) { @@ -63,7 +68,8 @@ class FileChooser extends React.Component { isOtherRepoShow: !this.state.isOtherRepoShow, }); }); - } else { + } + else { this.setState({isOtherRepoShow: !this.state.isOtherRepoShow}); } } @@ -93,24 +99,27 @@ class FileChooser extends React.Component { render() { return (
-
-
- - {gettext('Current Library')} + { + this.props.repoID && +
+
+ + {gettext('Current Library')} +
+ { + this.state.isCurrentRepoShow && this.state.currentRepoInfo && + + }
- { - this.state.isCurrentRepoShow && this.state.currentRepoInfo && - - } -
+ }
diff --git a/frontend/src/css/shared-file-view.css b/frontend/src/css/shared-file-view.css new file mode 100644 index 0000000000..b67a6c43cd --- /dev/null +++ b/frontend/src/css/shared-file-view.css @@ -0,0 +1,77 @@ +#wrapper { + height: 100%; +} + +.shared-file-view-md { + height: 100%; + overflow-y: hidden; +} + +.shared-file-view-md-header { + background: #f4f4f7; + height: 53px; + border-bottom: 1px solid #e8e8e8; + padding: 8px 16px 4px; + justify-content: space-between; +} + +.shared-file-view-md-main { + height: calc(100% - 53px); +} + +.shared-file-view-head { + width: 950px; + margin: 0 auto; + height: 50px; +} + +.shared-file-view-head a { + color: #fff; +} + +.shared-file-view-head h2 { + margin-top: 9px; + color: #222; + font-size: 1.4em; + margin-bottom: 0px; + font-weight: 400; +} + +.shared-file-view-head .share-by { + margin: 0; +} + +.shared-file-view-head .shared-file-op-btn { + padding: 7px; + margin-top: 9px; +} + +.shared-file-view-body { + height: calc(100% - 50px); + padding: 30px 0 15px; + background: #f4f4f4; + border: 1px solid #ededed; + margin-top: 12px; + overflow: auto; +} + +.shared-file-view-body .md-view { + min-height: 400px; + border: 1px solid #ccc; + margin: 0 auto; + box-shadow: 0 0 6px #ccc; + width: 756px; + background: #fff; + overflow: auto; +} + +@media (max-width: 991.98px) { + .shared-file-view-head { + width: 100%; + padding: 10px 20px; + height: 60px; + } + .shared-file-view-body { + height: calc(100% - 60px); + } +} diff --git a/frontend/src/shared-file-view-markdown.js b/frontend/src/shared-file-view-markdown.js new file mode 100644 index 0000000000..71acd36e49 --- /dev/null +++ b/frontend/src/shared-file-view-markdown.js @@ -0,0 +1,132 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Account from './components/common/account'; +import { gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle } from './utils/constants'; +import { Button } from 'reactstrap'; +import { seafileAPI } from './utils/seafile-api'; +import { Utils } from './utils/utils'; +import Loading from './components/loading'; +import SaveSharedFileDialog from './components/dialog/save-shared-file-dialog'; +import toaster from './components/toast'; +import watermark from 'watermark-dom'; +import MarkdownViewer from '@seafile/seafile-editor/dist/viewer/markdown-viewer'; + +import './css/shared-file-view.css'; +import './assets/css/fa-solid.css'; +import './assets/css/fa-regular.css'; +import './assets/css/fontawesome.css'; + +let loginUser = window.app.pageOptions.name; +const { repoID, sharedToken, trafficOverLimit, fileName, fileSize, rawPath, sharedBy, siteName, enableWatermark, download } = window.shared.pageOptions; + +class SharedFileViewMarkdown extends React.Component { + + constructor(props) { + super(props); + this.state = { + markdownContent: '', + loading: true, + showSaveSharedFileDialog: false, + }; + } + + handleSaveSharedFileDialog = () => { + this.setState({ + showSaveSharedFileDialog: true + }); + } + + toggleCancel = () => { + this.setState({ + showSaveSharedFileDialog: false + }); + } + + handleSaveSharedFile = () => { + toaster.success(gettext('Successfully saved'), { + duration: 3 + }); + } + + componentDidMount() { + seafileAPI.getFileContent(rawPath).then((res) => { + this.setState({ + markdownContent: res.data, + loading: false + }); + }); + if (trafficOverLimit == "True") { + toaster.danger(gettext('File download is disabled: the share link traffic of owner is used up.'), { + duration: 3 + }); + } + } + + render() { + if (this.state.loading) { + return + } + return ( +
+
+ + + logo + + + { loginUser && } +
+
+
+
+

{fileName}

+

{gettext('Shared by:')}{' '}{sharedBy}

+
+ {download && +
+ {(loginUser && loginUser !== sharedBy) && + + }{' '} + {(trafficOverLimit === "False") && + + } +
+ } +
+
+
+ +
+
+
+ {this.state.showSaveSharedFileDialog && + + } +
+ ); + } +} + +if (enableWatermark) { + let watermark_txt; + if (loginUser) { + watermark_txt = siteName + " " + loginUser; + } else { + watermark_txt = gettext("Anonymous User"); + } + watermark.init({ watermark_txt: watermark_txt }); +} + +ReactDOM.render ( + , + document.getElementById('wrapper') +); diff --git a/seahub/templates/shared_file_view_react_markdown.html b/seahub/templates/shared_file_view_react_markdown.html new file mode 100644 index 0000000000..7cda36f20c --- /dev/null +++ b/seahub/templates/shared_file_view_react_markdown.html @@ -0,0 +1,24 @@ +{% extends "base_for_react.html" %} +{% load seahub_tags i18n %} +{% load render_bundle from webpack_loader %} + +{% block extra_script %} + +{% render_bundle 'sharedFileViewMarkdown' %} +{% endblock %} diff --git a/seahub/views/file.py b/seahub/views/file.py index cf62011a47..4b99fcfda2 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -1176,7 +1176,11 @@ def view_shared_file(request, fileshare): permissions = fileshare.get_permissions() - return render(request, 'shared_file_view.html', { + template = 'shared_file_view.html' + if filetype == MARKDOWN: + template = 'shared_file_view_react_markdown.html' + + return render(request, template, { 'repo': repo, 'obj_id': obj_id, 'path': path,