From 90324d3d87e395f83136c613c6380df1d1cc1f50 Mon Sep 17 00:00:00 2001 From: C_Q Date: Fri, 1 Feb 2019 18:44:10 +0800 Subject: [PATCH] Init text shared (#2908) --- frontend/config/webpack.config.dev.js | 7 +- frontend/config/webpack.config.prod.js | 1 + frontend/package-lock.json | 33 +++++ frontend/package.json | 1 + frontend/src/css/shared-file-view.css | 29 ++++ frontend/src/shared-file-view-text.js | 133 ++++++++++++++++++ frontend/src/utils/utils.js | 33 +++++ ...kdown.html => shared_file_view_react.html} | 13 +- seahub/views/file.py | 5 +- .../views/file/test_view_shared_file.py | 11 +- 10 files changed, 253 insertions(+), 13 deletions(-) create mode 100644 frontend/src/shared-file-view-text.js rename seahub/templates/{shared_file_view_react_markdown.html => shared_file_view_react.html} (61%) diff --git a/frontend/config/webpack.config.dev.js b/frontend/config/webpack.config.dev.js index 6ca430d0e3..2ead6c245a 100644 --- a/frontend/config/webpack.config.dev.js +++ b/frontend/config/webpack.config.dev.js @@ -88,7 +88,12 @@ module.exports = { require.resolve('./polyfills'), require.resolve('react-dev-utils/webpackHotDevClient'), paths.appSrc + "/shared-file-view-markdown.js", - ] + ], + sharedFileViewText: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/shared-file-view-text.js", + ], }, output: { diff --git a/frontend/config/webpack.config.prod.js b/frontend/config/webpack.config.prod.js index 3b9aaced76..2da0f28a4f 100644 --- a/frontend/config/webpack.config.prod.js +++ b/frontend/config/webpack.config.prod.js @@ -66,6 +66,7 @@ module.exports = { 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"], + sharedFileViewText: [require.resolve('./polyfills'), paths.appSrc + "/shared-file-view-text.js"], }, output: { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0cb692fa0d..a226e60f62 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2809,6 +2809,16 @@ "sha.js": "^2.4.8" } }, + "create-react-class": { + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "create-react-context": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.3.tgz", @@ -7317,11 +7327,21 @@ "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", @@ -9843,6 +9863,19 @@ "prop-types": "^15.6.0" } }, + "react-codemirror": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-codemirror/-/react-codemirror-1.0.0.tgz", + "integrity": "sha1-kUZ7U7H12A2Rai/QtMetuFqQAbo=", + "requires": { + "classnames": "^2.2.5", + "codemirror": "^5.18.2", + "create-react-class": "^15.5.1", + "lodash.debounce": "^4.0.8", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.5.4" + } + }, "react-cookies": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/react-cookies/-/react-cookies-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5d60a4fe78..b5f8465a7e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "prop-types": "^15.6.2", "raf": "3.4.0", "react": "^16.4.2", + "react-codemirror": "^1.0.0", "react-cookies": "^0.1.0", "react-dom": "^16.5.2", "react-image-lightbox": "^5.1.0", diff --git a/frontend/src/css/shared-file-view.css b/frontend/src/css/shared-file-view.css index 6a4e671007..d64a392d89 100644 --- a/frontend/src/css/shared-file-view.css +++ b/frontend/src/css/shared-file-view.css @@ -30,6 +30,7 @@ .shared-file-view-head a { color: #fff; + text-decoration: none; } .shared-file-view-head h2 { @@ -66,6 +67,34 @@ overflow: auto; } +.shared-file-view-body .txt-view .CodeMirror, +.shared-file-view-body .txt-view .file-view-tip { + height: auto; + min-height: 400px; + border: 1px solid #ccc; + margin: 0 auto; + box-shadow: 0 0 6px #ccc; + width: 816px; + padding: 40px 96px; + line-height: 1.5em; + background-color: #fff; +} + +.shared-file-view-body .txt-view .file-view-tip { + min-height: 100px; + text-align: center; +} + +.shared-file-view-body .txt-view .CodeMirror-scroll { + min-height: 400px; +} + +.file-enc-cont { + width: 950px; + margin: -20px auto 6px; + text-align: right; +} + @media (max-width: 991.98px) { .shared-file-view-head { width: 100%; diff --git a/frontend/src/shared-file-view-text.js b/frontend/src/shared-file-view-text.js new file mode 100644 index 0000000000..7234b19288 --- /dev/null +++ b/frontend/src/shared-file-view-text.js @@ -0,0 +1,133 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Account from './components/common/account'; +import CodeMirror from 'react-codemirror'; +import { Button } from 'reactstrap'; +import { seafileAPI } from './utils/seafile-api'; +import { Utils } from './utils/utils'; +import watermark from 'watermark-dom'; +import { serviceURL, gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle } from './utils/constants'; + +import 'codemirror/lib/codemirror.css'; +import './assets/css/fa-solid.css'; +import './assets/css/fa-regular.css'; +import './assets/css/fontawesome.css'; +import './css/shared-file-view.css'; +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'; + +const loginUser = window.app.pageOptions.name; +const { trafficOverLimit, fileName, fileSize, sharedBy, siteName, enableWatermark, download, encoding, fileContent, sharedToken, fileEncodingList, err, fileext } = window.shared.pageOptions; +const URL = require('url-parse'); + +const options={ + lineNumbers: false, + mode: Utils.chooseLanguage(fileext), + extraKeys: {'Ctrl': 'autocomplete'}, + theme: 'default', + autoMatchParens: true, + textWrapping: true, + lineWrapping: true, + readOnly: 'nocursor', +}; + +class SharedFileViewText extends React.Component { + + constructor(props) { + super(props); + } + + changeEncode = (e) => { + let url = new URL(serviceURL) + '/f/' + sharedToken + '/?file_enc=' + e.target.value; + window.location.href = url.toString(); + } + + fileEncode = () => { + const list = fileEncodingList.substring(1, fileEncodingList.length - 1).replace(/\'*/g,"").replace(/\s*/g,"").split(','); + return ( +
+ + +
+ ); + } + + render() { + return ( +
+
+ + + logo + + + { loginUser && } +
+
+
+
+

{fileName}

+

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

+
+ {download && +
+ {(loginUser && loginUser !== sharedBy) && + + }{' '} + {(trafficOverLimit === 'False') && + + } +
+ } +
+
+ {this.fileEncode()} +
+ { err ?
{err}
: + } +
+
+
+
+ ); + } +} + +if (enableWatermark) { + let watermark_txt; + if (loginUser) { + watermark_txt = siteName + " " + loginUser; + } else { + watermark_txt = gettext("Anonymous User"); + } + watermark.init({ + watermark_txt: watermark_txt, + watermark_alpha: 0.075 + }); +} + +ReactDOM.render ( + , + document.getElementById('wrapper') +); diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index 43d2b6ee90..e5ad680fcb 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -666,4 +666,37 @@ export const Utils = { return nodes; }, + chooseLanguage: function(suffix) { + let mode; + switch(suffix) { + case 'py': + mode = 'python'; + break; + case 'js': + mode = 'javascript'; + break; + case 'c': + mode = 'text/x-csrc'; + break; + case 'cpp': + mode = 'text/x-c++src'; + break; + case 'java': + mode = 'text/x-java'; + break; + case 'cs': + mode = 'text/x-csharp'; + break; + case 'mdf': + mode = 'text/x-sql'; + break; + case 'html': + mode = 'htmlmixed'; + break; + default: + mode = suffix; + } + return mode; + }, + }; diff --git a/seahub/templates/shared_file_view_react_markdown.html b/seahub/templates/shared_file_view_react.html similarity index 61% rename from seahub/templates/shared_file_view_react_markdown.html rename to seahub/templates/shared_file_view_react.html index 7cda36f20c..4cd2b747ff 100644 --- a/seahub/templates/shared_file_view_react_markdown.html +++ b/seahub/templates/shared_file_view_react.html @@ -16,9 +16,18 @@ sharedBy: '{{ shared_by|email2nickname }}', siteName: '{{ site_name }}', enableWatermark: '{{ enable_watermark }}' == 'True', - download: '{{ permissions.can_download}}' == 'True', + download: '{{ permissions.can_download }}' == 'True', + fileEncodingList: '{{ file_encoding_list|escapejs }}', + encoding: '{{ encoding }}', + fileContent: '{{ file_content|escapejs }}', + err: '{{ err }}', + fileext: '{{ fileext }}', } }; -{% render_bundle 'sharedFileViewMarkdown' %} +{% if filetype == 'Markdown' %} + {% render_bundle 'sharedFileViewMarkdown' %} +{% elif filetype == 'Text' %} + {% render_bundle 'sharedFileViewText' %} +{% endif %} {% endblock %} diff --git a/seahub/views/file.py b/seahub/views/file.py index 61b3f74804..0f5835caa3 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -1189,8 +1189,9 @@ def view_shared_file(request, fileshare): permissions = fileshare.get_permissions() template = 'shared_file_view.html' - if filetype == MARKDOWN: - template = 'shared_file_view_react_markdown.html' + + if is_textual_file(file_type=filetype): + template = 'shared_file_view_react.html' return render(request, template, { 'repo': repo, diff --git a/tests/seahub/views/file/test_view_shared_file.py b/tests/seahub/views/file/test_view_shared_file.py index 02db2038cc..5c9746417b 100644 --- a/tests/seahub/views/file/test_view_shared_file.py +++ b/tests/seahub/views/file/test_view_shared_file.py @@ -34,11 +34,8 @@ class ViewSharedFileTest(TestCase, Fixtures): def test_can_render(self): resp = self.client.get(reverse('view_shared_file', args=[self.fs.token])) self.assertEqual(200, resp.status_code) - self.assertTemplateUsed(resp, 'shared_file_view.html') - + self.assertTemplateUsed(resp, 'shared_file_view_react.html') self.assertContains(resp, os.path.basename(self.file)) - dl_url_tag = 'href="?dl=1"' - self.assertContains(resp, dl_url_tag) def test_can_download(self): dl_url = reverse('view_shared_file', args=[self.fs.token]) + '?dl=1' @@ -134,15 +131,13 @@ class ViewSharedFileTest(TestCase, Fixtures): 'password': '12345678', }) self.assertEqual(200, resp.status_code) - self.assertTemplateUsed(resp, 'shared_file_view.html') + self.assertTemplateUsed(resp, 'shared_file_view_react.html') self.assertContains(resp, os.path.basename(self.file)) - dl_url_tag = 'href="?dl=1"' - self.assertContains(resp, dl_url_tag) def _assert_render_file_page_without_passwd(self, fs): resp = self.client.get(reverse('view_shared_file', args=[fs.token])) self.assertEqual(200, resp.status_code) - self.assertTemplateUsed(resp, 'shared_file_view.html') + self.assertTemplateUsed(resp, 'shared_file_view_react.html') def test_can_view_enc(self): self._assert_redirect_to_password_page(self.enc_fs)