diff --git a/frontend/config/webpack.config.dev.js b/frontend/config/webpack.config.dev.js index 0d47dcc1f3..a39e9edbc8 100644 --- a/frontend/config/webpack.config.dev.js +++ b/frontend/config/webpack.config.dev.js @@ -144,6 +144,11 @@ module.exports = { require.resolve('react-dev-utils/webpackHotDevClient'), paths.appSrc + "/shared-file-view-unknown.js", ], + historyTrashFileView: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/history-trash-file-view.js", + ], viewFileText: [ 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 c56d323b3e..3447d5ae83 100644 --- a/frontend/config/webpack.config.prod.js +++ b/frontend/config/webpack.config.prod.js @@ -77,6 +77,7 @@ module.exports = { sharedFileViewDocument: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-document.js"], sharedFileViewSpreadsheet: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-spreadsheet.js"], sharedFileViewUnknown: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-unknown.js"], + historyTrashFileViewImage: [require.resolve('./polyfills'), paths.appSrc + "/history-trash-file-view-image.js"], viewFileText: [require.resolve('./polyfills'), paths.appSrc + "/view-file-text.js"], viewFileImage: [require.resolve('./polyfills'), paths.appSrc + "/view-file-image.js"], viewFileXmind: [require.resolve('./polyfills'), paths.appSrc + "/view-file-xmind.js"], diff --git a/frontend/src/components/dialog/generate-share-link.js b/frontend/src/components/dialog/generate-share-link.js index 78a5bc5557..1541eeef19 100644 --- a/frontend/src/components/dialog/generate-share-link.js +++ b/frontend/src/components/dialog/generate-share-link.js @@ -36,6 +36,7 @@ class GenerateShareLink extends React.Component { sendLinkEmails: '', sendLinkMessage: '', sendLinkErrorMessage: '', + fileInfo: null, }; this.permissions = { 'can_edit': false, @@ -58,6 +59,12 @@ class GenerateShareLink extends React.Component { this.setState({isLoading: false}); } }); + + seafileAPI.getFileInfo(repoID, path).then((res) => { + if (res.data) { + this.setState({fileInfo: res.data}); + } + }); } onPasswordInputChecked = () => { @@ -99,11 +106,16 @@ class GenerateShareLink extends React.Component { 'can_edit': false, 'can_download': true }; - } else { + } else if (permission == 'preview') { this.permissions = { 'can_edit': false, 'can_download': false }; + } else if (permission == 'editOnCloudAndDownload'){ + this.permissions = { + 'can_edit': true, + 'can_download': true + }; } } @@ -371,6 +383,7 @@ class GenerateShareLink extends React.Component { ); } else { + let fileInfo = this.state.fileInfo; return (
@@ -446,6 +459,13 @@ class GenerateShareLink extends React.Component { this.setPermission('preview')} />{' '}{gettext('Preview only')} + {(Utils.isOfficeFile(this.props.itemPath) && fileInfo && fileInfo.can_edit) && + + + + } {this.state.errorInfo && {gettext(this.state.errorInfo)}}
diff --git a/frontend/src/components/file-content-view/audio.js b/frontend/src/components/file-content-view/audio.js new file mode 100644 index 0000000000..f3fbcbe190 --- /dev/null +++ b/frontend/src/components/file-content-view/audio.js @@ -0,0 +1,26 @@ +import React from 'react'; +import AudioPlayer from '../audio-player'; + +import '../../css/audio-file-view.css'; + +const { rawPath } = window.app.pageOptions; + +class FileContent extends React.Component { + render() { + const videoJsOptions = { + autoplay: false, + controls: true, + preload: 'auto', + sources: [{ + src: rawPath + }] + }; + return ( +
+ +
+ ); + } +} + +export default FileContent; diff --git a/frontend/src/components/file-content-view/image.js b/frontend/src/components/file-content-view/image.js new file mode 100644 index 0000000000..395ffa3cb3 --- /dev/null +++ b/frontend/src/components/file-content-view/image.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { Utils } from '../../utils/utils'; +import { gettext, siteRoot } from '../../utils/constants'; + +import '../../css/image-file-view.css'; + +const { + repoID, + fileName, previousImage, nextImage, rawPath +} = window.app.pageOptions; + +let previousImageUrl, nextImageUrl; +if (previousImage) { + previousImageUrl = `${siteRoot}lib/${repoID}/file${Utils.encodePath(previousImage)}`; +} +if (nextImage) { + nextImageUrl = `${siteRoot}lib/${repoID}/file${Utils.encodePath(nextImage)}`; +} + +class FileContent extends React.Component { + + componentDidMount() { + document.addEventListener('keydown', (e) => { + if (previousImage && e.keyCode == 37) { // press '<-' + location.href = previousImageUrl; + } + if (nextImage && e.keyCode == 39) { // press '->' + location.href = nextImageUrl; + } + }); + } + + render() { + return ( +
+ {previousImage && ( + + )} + {nextImage && ( + + )} + {fileName} +
+ ); + } +} + +export default FileContent; diff --git a/frontend/src/components/file-content-view/markdown.js b/frontend/src/components/file-content-view/markdown.js new file mode 100644 index 0000000000..4bcfa7fd3a --- /dev/null +++ b/frontend/src/components/file-content-view/markdown.js @@ -0,0 +1,23 @@ +import React from 'react'; +import MarkdownViewer from '@seafile/seafile-editor/dist/viewer/markdown-viewer'; + +import '../../css/md-file-view.css'; + +const { fileContent } = window.app.pageOptions; + +class FileContent extends React.Component { + render() { + return ( +
+
+ +
+
+ ); + } +} + +export default FileContent; diff --git a/frontend/src/components/file-content-view/pdf.js b/frontend/src/components/file-content-view/pdf.js new file mode 100644 index 0000000000..6f6e7027c9 --- /dev/null +++ b/frontend/src/components/file-content-view/pdf.js @@ -0,0 +1,16 @@ +import React from 'react'; +import PDFViewer from '../pdf-viewer'; + +import '../../css/pdf-file-view.css'; + +class FileContent extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +export default FileContent; diff --git a/frontend/src/components/file-content-view/svg.js b/frontend/src/components/file-content-view/svg.js new file mode 100644 index 0000000000..bb958170f8 --- /dev/null +++ b/frontend/src/components/file-content-view/svg.js @@ -0,0 +1,19 @@ +import React from 'react'; + +import '../../css/svg-file-view.css'; + +const { + fileName, rawPath +} = window.app.pageOptions; + +class FileContent extends React.Component { + render() { + return ( +
+ {fileName} +
+ ); + } +} + +export default FileContent; diff --git a/frontend/src/components/file-content-view/text.js b/frontend/src/components/file-content-view/text.js new file mode 100644 index 0000000000..c8bfd0cb2c --- /dev/null +++ b/frontend/src/components/file-content-view/text.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { Utils } from '../../utils/utils'; + +import CodeMirror from 'react-codemirror'; +import 'codemirror/mode/javascript/javascript'; +import 'codemirror/mode/css/css'; +import 'codemirror/mode/clike/clike'; +import 'codemirror/mode/php/php'; +import 'codemirror/mode/sql/sql'; +import 'codemirror/mode/vue/vue'; +import 'codemirror/mode/xml/xml'; +import 'codemirror/mode/go/go'; +import 'codemirror/mode/python/python'; +import 'codemirror/mode/htmlmixed/htmlmixed'; +import 'codemirror/lib/codemirror.css'; + +import '../../css/text-file-view.css'; + +const { + fileExt, fileContent +} = window.app.pageOptions; + +const options = { + lineNumbers: true, + mode: Utils.chooseLanguage(fileExt), + extraKeys: {'Ctrl': 'autocomplete'}, + theme: 'default', + textWrapping: true, + lineWrapping: true, + readOnly: true, + cursorBlinkRate: -1 // hide the cursor +}; + +class FileContent extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +export default FileContent; diff --git a/frontend/src/components/file-content-view/video.js b/frontend/src/components/file-content-view/video.js new file mode 100644 index 0000000000..7ee0238d24 --- /dev/null +++ b/frontend/src/components/file-content-view/video.js @@ -0,0 +1,28 @@ +import React from 'react'; +import VideoPlayer from '../video-player'; + +import '../../css/video-file-view.css'; + +const { + rawPath +} = window.app.pageOptions; + +class FileContent extends React.Component { + render() { + const videoJsOptions = { + autoplay: false, + controls: true, + preload: 'auto', + sources: [{ + src: rawPath + }] + }; + return ( +
+ +
+ ); + } +} + +export default FileContent; diff --git a/frontend/src/components/file-view/file-info.js b/frontend/src/components/file-view/file-info.js index fdc3a54178..2ece013a04 100644 --- a/frontend/src/components/file-view/file-info.js +++ b/frontend/src/components/file-view/file-info.js @@ -49,9 +49,9 @@ class FileInfo extends React.PureComponent { /> } -
+
{latestContributorName} - {moment(lastModificationTime * 1000).format('YYYY-MM-DD HH:mm')} + {moment(lastModificationTime * 1000).format('YYYY-MM-DD HH:mm')}
); diff --git a/frontend/src/components/history-trash-file-view/download.js b/frontend/src/components/history-trash-file-view/download.js new file mode 100644 index 0000000000..e3259be571 --- /dev/null +++ b/frontend/src/components/history-trash-file-view/download.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { gettext, siteRoot } from '../../utils/constants'; + +const { + fileName, repoID, objID, path +} = window.app.pageOptions; + +function Download() { + return ( + {gettext('Download')} + ); +} + +export default Download; diff --git a/frontend/src/components/history-trash-file-view/file-view-tip.js b/frontend/src/components/history-trash-file-view/file-view-tip.js new file mode 100644 index 0000000000..5a4a74c93d --- /dev/null +++ b/frontend/src/components/history-trash-file-view/file-view-tip.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { gettext } from '../../utils/constants'; +import Download from './download'; + +const propTypes = { + err: PropTypes.string +}; +const { canDownloadFile, err } = window.app.pageOptions; +const UNSUPPORTED = 'File preview unsupported'; + +class FileViewTip extends React.Component { + + render() { + let errorMsg; + if (err == UNSUPPORTED || this.props.err == UNSUPPORTED) { + errorMsg =

{gettext('Online view is not applicable to this file format')}

; + } else { + errorMsg =

{err}

; + } + + return ( +
+
+ {errorMsg} + {canDownloadFile && } +
+
+ ); + } +} +FileViewTip.propTypes = propTypes; + +export default FileViewTip; diff --git a/frontend/src/components/history-trash-file-view/file-view.js b/frontend/src/components/history-trash-file-view/file-view.js new file mode 100644 index 0000000000..ffa6ce3360 --- /dev/null +++ b/frontend/src/components/history-trash-file-view/file-view.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import watermark from 'watermark-dom'; +import { gettext, siteName } from '../../utils/constants'; +import Download from './download'; + +import '../../css/file-view.css'; + +const propTypes = { + content: PropTypes.object.isRequired +}; + +const { + fromTrash, + fileName, commitTime, + canDownloadFile, + enableWatermark, userNickName +} = window.app.pageOptions; + + +class FileView extends React.Component { + + constructor(props) { + super(props); + } + + render() { + return ( +
+
+
+

{fileName}

+

{fromTrash ? `${gettext('Current Path: ')}${gettext('Trash')}`: commitTime}

+
+ {canDownloadFile && } +
+
+ {this.props.content} +
+
+ ); + } +} + +if (enableWatermark) { + watermark.init({ + watermark_txt: `${siteName} ${userNickName}`, + watermark_alpha: 0.075 + }); +} + +FileView.propTypes = propTypes; + +export default FileView; diff --git a/frontend/src/css/audio-file-view.css b/frontend/src/css/audio-file-view.css index 514724099e..9239eaf39b 100644 --- a/frontend/src/css/audio-file-view.css +++ b/frontend/src/css/audio-file-view.css @@ -1,9 +1,9 @@ -.video-js { +.audio-file-view .video-js { width: calc(100% - 40px); max-width: 500px; height: 3em; margin: 0 auto; } -.video-js .vjs-fullscreen-control { +.audio-file-view .video-js .vjs-fullscreen-control { display: none; } diff --git a/frontend/src/css/file-view.css b/frontend/src/css/file-view.css index 32237f931e..79db754161 100644 --- a/frontend/src/css/file-view.css +++ b/frontend/src/css/file-view.css @@ -29,12 +29,9 @@ body { .file-locked-icon { margin-left: .5rem; } -.last-modification { +.meta-info { font-size: .8125rem; } -.last-modification-time { - margin-left: .5rem; -} .file-view-content { padding: 30px 0; background: #f4f4f4; diff --git a/frontend/src/css/md-file-view.css b/frontend/src/css/md-file-view.css new file mode 100644 index 0000000000..4db22b0e88 --- /dev/null +++ b/frontend/src/css/md-file-view.css @@ -0,0 +1,9 @@ +.md-content { + box-shadow: 0 0 6px #ccc; + border: 1px solid #ccc; + padding: 70px 75px; + width: calc(100% - 40px); + max-width: 950px; + background: #fff; + margin: 0 auto; +} diff --git a/frontend/src/css/video-file-view.css b/frontend/src/css/video-file-view.css index 3d7d4fd33f..16cdaa9fb3 100644 --- a/frontend/src/css/video-file-view.css +++ b/frontend/src/css/video-file-view.css @@ -1,4 +1,4 @@ -.video-js { +.video-file-view .video-js { width: calc(100% - 40px); max-width: 800px; max-height: 100%; diff --git a/frontend/src/history-trash-file-view.js b/frontend/src/history-trash-file-view.js new file mode 100644 index 0000000000..d95ae969fd --- /dev/null +++ b/frontend/src/history-trash-file-view.js @@ -0,0 +1,64 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Utils } from './utils/utils'; +import { gettext, siteRoot } from './utils/constants'; +import FileView from './components/history-trash-file-view/file-view'; +import FileViewTip from './components/history-trash-file-view/file-view-tip'; +import Image from './components/file-content-view/image'; +import SVG from './components/file-content-view/svg'; +import PDF from './components/file-content-view/pdf'; +import Text from './components/file-content-view/text'; +import Markdown from './components/file-content-view/markdown'; +import Video from './components/file-content-view/video'; +import Audio from './components/file-content-view/audio'; + +const { + fileType, err +} = window.app.pageOptions; + +class HistoryTrashFileView extends React.Component { + + render() { + if (err) { + return ( + } /> + ); + } + + let content; + switch (fileType) { + case 'Image': + content = ; + break; + case 'SVG': + content = ; + break; + case 'PDF': + content = ; + break; + case 'Text': + content = ; + break; + case 'Markdown': + content = ; + break; + case 'Video': + content =