mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-05 08:53:14 +00:00
[view history/snapshot/trash file] rewrote them with react
This commit is contained in:
@@ -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'),
|
||||
|
@@ -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"],
|
||||
|
26
frontend/src/components/file-content-view/audio.js
Normal file
26
frontend/src/components/file-content-view/audio.js
Normal file
@@ -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 (
|
||||
<div className="file-view-content flex-1 audio-file-view">
|
||||
<AudioPlayer { ...videoJsOptions } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileContent;
|
48
frontend/src/components/file-content-view/image.js
Normal file
48
frontend/src/components/file-content-view/image.js
Normal file
@@ -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 (
|
||||
<div className="file-view-content flex-1 image-file-view">
|
||||
{previousImage && (
|
||||
<a href={previousImageUrl} id="img-prev" title={gettext('you can also press ← ')}><span className="fas fa-chevron-left"></span></a>
|
||||
)}
|
||||
{nextImage && (
|
||||
<a href={nextImageUrl} id="img-next" title={gettext('you can also press →')}><span className="fas fa-chevron-right"></span></a>
|
||||
)}
|
||||
<img src={rawPath} alt={fileName} id="image-view" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileContent;
|
23
frontend/src/components/file-content-view/markdown.js
Normal file
23
frontend/src/components/file-content-view/markdown.js
Normal file
@@ -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 (
|
||||
<div className="file-view-content flex-1 o-auto">
|
||||
<div className="md-content">
|
||||
<MarkdownViewer
|
||||
markdownContent={fileContent}
|
||||
showTOC={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileContent;
|
16
frontend/src/components/file-content-view/pdf.js
Normal file
16
frontend/src/components/file-content-view/pdf.js
Normal file
@@ -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 (
|
||||
<div className="file-view-content flex-1 pdf-file-view">
|
||||
<PDFViewer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileContent;
|
19
frontend/src/components/file-content-view/svg.js
Normal file
19
frontend/src/components/file-content-view/svg.js
Normal file
@@ -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 (
|
||||
<div className="file-view-content flex-1 svg-file-view">
|
||||
<img src={rawPath} alt={fileName} id="svg-view" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileContent;
|
48
frontend/src/components/file-content-view/text.js
Normal file
48
frontend/src/components/file-content-view/text.js
Normal file
@@ -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 (
|
||||
<div className="file-view-content flex-1 text-file-view">
|
||||
<CodeMirror
|
||||
ref="code-mirror-editor"
|
||||
value={fileContent}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileContent;
|
28
frontend/src/components/file-content-view/video.js
Normal file
28
frontend/src/components/file-content-view/video.js
Normal file
@@ -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 (
|
||||
<div className="file-view-content flex-1 video-file-view">
|
||||
<VideoPlayer { ...videoJsOptions } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileContent;
|
@@ -49,9 +49,9 @@ class FileInfo extends React.PureComponent {
|
||||
/>
|
||||
}
|
||||
</h2>
|
||||
<div className="last-modification">
|
||||
<div className="meta-info">
|
||||
<a href={`${siteRoot}profile/${encodeURIComponent(latestContributor)}/`}>{latestContributorName}</a>
|
||||
<span className="last-modification-time">{moment(lastModificationTime * 1000).format('YYYY-MM-DD HH:mm')}</span>
|
||||
<span className="ml-2">{moment(lastModificationTime * 1000).format('YYYY-MM-DD HH:mm')}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
14
frontend/src/components/history-trash-file-view/download.js
Normal file
14
frontend/src/components/history-trash-file-view/download.js
Normal file
@@ -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 (
|
||||
<a href={`${siteRoot}repo/${repoID}/${objID}/download/?file_name=${encodeURIComponent(fileName)}&p=${encodeURIComponent(path)}`} className="btn btn-secondary">{gettext('Download')}</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default Download;
|
@@ -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 = <p>{gettext('Online view is not applicable to this file format')}</p>;
|
||||
} else {
|
||||
errorMsg = <p className="error">{err}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="file-view-content flex-1 o-auto">
|
||||
<div className="file-view-tip">
|
||||
{errorMsg}
|
||||
{canDownloadFile && <Download />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
FileViewTip.propTypes = propTypes;
|
||||
|
||||
export default FileViewTip;
|
54
frontend/src/components/history-trash-file-view/file-view.js
Normal file
54
frontend/src/components/history-trash-file-view/file-view.js
Normal file
@@ -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 (
|
||||
<div className="h-100 d-flex flex-column">
|
||||
<div className="file-view-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h2 className="file-title">{fileName}</h2>
|
||||
<p className="meta-info m-0">{fromTrash ? `${gettext('Current Path: ')}${gettext('Trash')}`: commitTime}</p>
|
||||
</div>
|
||||
{canDownloadFile && <Download />}
|
||||
</div>
|
||||
<div className="file-view-body flex-auto d-flex o-hidden">
|
||||
{this.props.content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableWatermark) {
|
||||
watermark.init({
|
||||
watermark_txt: `${siteName} ${userNickName}`,
|
||||
watermark_alpha: 0.075
|
||||
});
|
||||
}
|
||||
|
||||
FileView.propTypes = propTypes;
|
||||
|
||||
export default FileView;
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
9
frontend/src/css/md-file-view.css
Normal file
9
frontend/src/css/md-file-view.css
Normal file
@@ -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;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
.video-js {
|
||||
.video-file-view .video-js {
|
||||
width: calc(100% - 40px);
|
||||
max-width: 800px;
|
||||
max-height: 100%;
|
||||
|
64
frontend/src/history-trash-file-view.js
Normal file
64
frontend/src/history-trash-file-view.js
Normal file
@@ -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 (
|
||||
<FileView content={<FileViewTip />} />
|
||||
);
|
||||
}
|
||||
|
||||
let content;
|
||||
switch (fileType) {
|
||||
case 'Image':
|
||||
content = <Image />;
|
||||
break;
|
||||
case 'SVG':
|
||||
content = <SVG />;
|
||||
break;
|
||||
case 'PDF':
|
||||
content = <PDF />;
|
||||
break;
|
||||
case 'Text':
|
||||
content = <Text />;
|
||||
break;
|
||||
case 'Markdown':
|
||||
content = <Markdown />;
|
||||
break;
|
||||
case 'Video':
|
||||
content = <Video />;
|
||||
break;
|
||||
case 'Audio':
|
||||
content = <Audio />;
|
||||
break;
|
||||
default:
|
||||
content = <FileViewTip err='File preview unsupported' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<FileView content={content} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render (
|
||||
<HistoryTrashFileView />,
|
||||
document.getElementById('wrapper')
|
||||
);
|
@@ -34,7 +34,7 @@ class FileContent extends React.Component {
|
||||
}]
|
||||
};
|
||||
return (
|
||||
<div className="file-view-content flex-1">
|
||||
<div className="file-view-content flex-1 audio-file-view">
|
||||
<AudioPlayer { ...videoJsOptions } />
|
||||
</div>
|
||||
);
|
||||
|
@@ -34,7 +34,7 @@ class FileContent extends React.Component {
|
||||
}]
|
||||
};
|
||||
return (
|
||||
<div className="file-view-content flex-1">
|
||||
<div className="file-view-content flex-1 video-file-view">
|
||||
<VideoPlayer { ...videoJsOptions } />
|
||||
</div>
|
||||
);
|
||||
|
47
seahub/templates/history_file_view_react.html
Normal file
47
seahub/templates/history_file_view_react.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{% extends "base_for_react.html" %}
|
||||
{% load seahub_tags i18n %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
|
||||
{% block sub_title %}{{file_name}} - {% endblock %}
|
||||
|
||||
{% block extra_style %}
|
||||
{% if filetype == 'PDF' %}
|
||||
<link rel="resource" type="application/l10n" href="{{ MEDIA_URL }}js/pdf/locale/locale.properties" />
|
||||
{% endif %}
|
||||
{% render_bundle 'historyTrashFileView' 'css' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
// overwrite the one in base_for_react.html
|
||||
window.app.pageOptions = {
|
||||
fromTrash: {% if from_trash %}true{% else %}false{% endif %},
|
||||
|
||||
fileName: '{{ file_name|escapejs }}',
|
||||
commitTime: '{{ current_commit.props.ctime|tsstr_sec }}',
|
||||
|
||||
canDownloadFile: {% if can_download_file %}true{% else %}false{% endif %},
|
||||
repoID: '{{ repo.id }}',
|
||||
objID: '{{ obj_id }}',
|
||||
path: '{{ path|escapejs }}',
|
||||
|
||||
fileType: '{{filetype}}',
|
||||
rawPath: '{{ raw_path|escapejs }}',
|
||||
err: {% if err %}'{{ err }}'{% else %}''{% endif %},
|
||||
|
||||
// for text file
|
||||
fileExt: '{{ fileext|escapejs }}',
|
||||
// for text/markdown file
|
||||
fileContent: '{{ file_content|escapejs }}'
|
||||
};
|
||||
</script>
|
||||
{% render_bundle 'historyTrashFileView' 'js' %}
|
||||
{% if filetype == 'PDF' %}
|
||||
<script type="text/javascript">
|
||||
var sf_file_url = '{{ raw_path|escapejs }}';
|
||||
var sf_pdfworkerjs_url = '{{MEDIA_URL}}js/pdf/pdf.worker.min.js';
|
||||
</script>
|
||||
<script type="text/javascript" src="{{MEDIA_URL}}js/pdf/pdf.min.js"></script>
|
||||
<script type="text/javascript" src="{{MEDIA_URL}}js/pdf/viewer.js"></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@@ -1007,7 +1007,8 @@ def view_history_file(request, repo_id):
|
||||
repo = ret_dict['repo']
|
||||
ret_dict['zipped'] = gen_path_link(path, repo.name)
|
||||
|
||||
return render(request, 'view_history_file.html', ret_dict)
|
||||
#return render(request, 'view_history_file.html', ret_dict)
|
||||
return render(request, 'history_file_view_react.html', ret_dict)
|
||||
|
||||
@repo_passwd_set_required
|
||||
def view_trash_file(request, repo_id):
|
||||
@@ -1032,7 +1033,9 @@ def view_trash_file(request, repo_id):
|
||||
tmp_path = posixpath.join(basedir.rstrip('/'), ret_dict['path'].lstrip('/'))
|
||||
ret_dict['path'] = tmp_path
|
||||
|
||||
return render(request, 'view_trash_file.html', ret_dict)
|
||||
#return render(request, 'view_trash_file.html', ret_dict)
|
||||
ret_dict['from_trash'] = True
|
||||
return render(request, 'history_file_view_react.html', ret_dict)
|
||||
|
||||
@repo_passwd_set_required
|
||||
def view_snapshot_file(request, repo_id):
|
||||
@@ -1054,7 +1057,8 @@ def view_snapshot_file(request, repo_id):
|
||||
repo = ret_dict['repo']
|
||||
ret_dict['zipped'] = gen_path_link(path, repo.name)
|
||||
|
||||
return render(request, 'view_snapshot_file.html', ret_dict)
|
||||
#return render(request, 'view_snapshot_file.html', ret_dict)
|
||||
return render(request, 'history_file_view_react.html', ret_dict)
|
||||
|
||||
def _download_file_from_share_link(request, fileshare):
|
||||
"""Download shared file.
|
||||
|
Reference in New Issue
Block a user