diff --git a/frontend/src/components/wiki-markdown-viewer.js b/frontend/src/components/wiki-markdown-viewer.js index 602b1a49cd..9c3da3d8b3 100644 --- a/frontend/src/components/wiki-markdown-viewer.js +++ b/frontend/src/components/wiki-markdown-viewer.js @@ -1,8 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import MarkdownViewer from '@seafile/seafile-editor/dist/viewer/markdown-viewer'; -import { gettext } from '../utils/constants'; +import { gettext, repoID, slug, serviceURL, isPublicWiki } from '../utils/constants'; import Loading from './loading'; +import { Utils } from '../utils/utils'; const propTypes = { children: PropTypes.object, @@ -10,7 +11,8 @@ const propTypes = { markdownContent: PropTypes.string.isRequired, latestContributor: PropTypes.string.isRequired, lastModified: PropTypes.string.isRequired, - onLinkClick: PropTypes.func.isRequired + onLinkClick: PropTypes.func.isRequired, + isWiki: PropTypes.bool }; const contentClass = 'wiki-page-content'; @@ -102,6 +104,76 @@ class WikiMarkdownViewer extends React.Component { this.setState({activeTitleIndex: activeTitleIndex}); } + changeInlineNode = (item) => { + if (item.object == 'inline') { + let url; + + // change image url + if (item.type == 'image' && isPublicWiki) { + url = item.data.src; + const re = new RegExp(serviceURL + '/lib/' + repoID +'/file.*raw=1'); + // different repo + if (!re.test(url)) { + return; + } + // get image path + let index = url.indexOf('/file'); + let index2 = url.indexOf('?'); + const imagePath = url.substring(index + 5, index2); + // replace url + item.data.src = serviceURL + '/view-image-via-public-wiki/?slug=' + slug + '&path=' + imagePath; + } + + else if (item.type == 'link') { + url = item.data.href; + // change file url + if (Utils.isInternalMarkdownLink(url, repoID)) { + let path = Utils.getPathFromInternalMarkdownLink(url, repoID); + // replace url + item.data.href = serviceURL + '/wikis/' + slug + path; + } + // change dir url + else if (Utils.isInternalDirLink(url, repoID)) { + let path = Utils.getPathFromInternalDirLink(url, repoID, slug); + // replace url + item.data.href = serviceURL + '/wikis/' + slug + path; + } + } + } + + return item; + } + + modifyValueBeforeRender = (value) => { + let nodes = value.document.nodes; + let newNodes = Utils.changeMarkdownNodes(nodes, this.changeInlineNode); + value.document.nodes = newNodes; + return value; + } + + renderMarkdown = () => { + if (this.props.isWiki) { + return ( + + ) + } + + return ( + + ) + } + render() { if (this.props.isFileLoading) { return @@ -110,12 +182,7 @@ class WikiMarkdownViewer extends React.Component {
{this.props.children} - + {this.renderMarkdown()}

{gettext('Last modified by')} {this.props.latestContributor}, {this.props.lastModified}

@@ -123,6 +190,11 @@ class WikiMarkdownViewer extends React.Component { } } +const defaultProps = { + isWiki: false, +} + WikiMarkdownViewer.propTypes = propTypes; +MarkdownViewer.defaultProps = defaultProps; export default WikiMarkdownViewer; diff --git a/frontend/src/pages/wiki/main-panel.js b/frontend/src/pages/wiki/main-panel.js index 3408d721fd..fc9808b60d 100644 --- a/frontend/src/pages/wiki/main-panel.js +++ b/frontend/src/pages/wiki/main-panel.js @@ -117,6 +117,7 @@ class MainPanel extends Component { lastModified = {this.props.lastModified} latestContributor={this.props.latestContributor} onLinkClick={this.props.onLinkClick} + isWiki={true} /> } {!this.props.isViewFileState && diff --git a/frontend/src/shared-file-view-markdown.js b/frontend/src/shared-file-view-markdown.js index cb718f8e15..23c94ef995 100644 --- a/frontend/src/shared-file-view-markdown.js +++ b/frontend/src/shared-file-view-markdown.js @@ -1,7 +1,7 @@ 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 { serviceURL, 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'; @@ -17,7 +17,7 @@ import './assets/css/fa-regular.css'; import './assets/css/fontawesome.css'; let loginUser = window.app.pageOptions.name; -const { serviceURL, repoID, sharedToken, trafficOverLimit, fileName, fileSize, rawPath, sharedBy, siteName, enableWatermark, download } = window.shared.pageOptions; +const { repoID, sharedToken, trafficOverLimit, fileName, fileSize, rawPath, sharedBy, siteName, enableWatermark, download } = window.shared.pageOptions; class SharedFileViewMarkdown extends React.Component { @@ -62,6 +62,33 @@ class SharedFileViewMarkdown extends React.Component { } } + changeImageURL = (innerNode) => { + if (innerNode.type == 'image') { + let imageUrl = innerNode.data.src; + + const re = new RegExp(serviceURL + '/lib/' + repoID +'/file.*raw=1'); + + // different repo + if (!re.test(imageUrl)) { + return; + } + // get image path + let index = imageUrl.indexOf('/file'); + let index2 = imageUrl.indexOf('?'); + const imagePath = imageUrl.substring(index + 5, index2); + // change image url + innerNode.data.src = serviceURL + '/view-image-via-share-link/?token=' + sharedToken + '&path=' + imagePath; + } + return innerNode; + } + + modifyValueBeforeRender = (value) => { + let nodes = value.document.nodes; + let newNodes = Utils.changeMarkdownNodes(nodes, this.changeImageURL); + value.document.nodes = newNodes; + return value; + } + render() { if (this.state.loading) { return @@ -102,9 +129,9 @@ class SharedFileViewMarkdown extends React.Component { diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index f735ef0c2d..f9744d1a04 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -13,6 +13,7 @@ export const isPro = window.app.config.isPro === 'True'; export const lang = window.app.config.lang; export const fileServerRoot = window.app.config.fileServerRoot; export const seafileVersion = window.app.config.seafileVersion; +export const serviceURL = window.app.config.serviceURL; //pageOptions export const seafileCollabServer = window.app.pageOptions.seafileCollabServer; @@ -46,6 +47,7 @@ export const initialPath = window.wiki ? window.wiki.config.initial_path : ''; export const permission = window.wiki ? window.wiki.config.permission === 'True' : ''; export const isDir = window.wiki ? window.wiki.config.isDir : ''; export const serviceUrl = window.wiki ? window.wiki.config.serviceUrl : ''; +export const isPublicWiki = window.wiki ? window.wiki.config.isPublicWiki === 'True': ''; // file history export const PER_PAGE = 25; diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index 53a4282e66..42a0ae7e9e 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -1,4 +1,4 @@ -import { mediaUrl, gettext, siteRoot } from './constants'; +import { mediaUrl, gettext, serviceURL } from './constants'; import { strChineseFirstPY } from './pinyin-by-unicode'; export const Utils = { @@ -404,30 +404,59 @@ export const Utils = { }, isInternalMarkdownLink: function(url, repoID) { - var re = new RegExp(siteRoot + 'lib/' + repoID + '.*\.md$'); + var re = new RegExp(serviceURL + '/lib/' + repoID + '.*\.md$'); return re.test(url); }, isInternalDirLink: function(url, repoID) { - var re = new RegExp(siteRoot + 'library/' + repoID + '.*'); + var re = new RegExp(serviceURL + '/library/' + repoID + '.*'); return re.test(url); }, getPathFromInternalMarkdownLink: function(url, repoID) { - var re = new RegExp(siteRoot + 'lib/' + repoID + '/file' + '(.*\.md)'); + var re = new RegExp(serviceURL + '/lib/' + repoID + '/file' + '(.*\.md)'); var array = re.exec(url); var path = decodeURIComponent(array[1]); return path; }, getPathFromInternalDirLink: function(url, repoID, repoName) { - var re = new RegExp(siteRoot + 'library/' + repoID + '/' + repoName + '(/.*)'); + var repoName = encodeURIComponent(repoName); + var re = new RegExp(serviceURL + '/library/' + repoID + '/' + repoName + '(/.*)'); var array = re.exec(url); var path = decodeURIComponent(array[1]); return path; }, + isWikiInternalMarkdownLink: function(url, slug) { + var slug = encodeURIComponent(slug); + var re = new RegExp(serviceURL + '/wikis/' + slug + '.*\.md$'); + return re.test(url); + }, + + isWikiInternalDirLink: function(url, slug) { + var slug = encodeURIComponent(slug); + var re = new RegExp(serviceURL + '/wikis/' + slug + '.*'); + return re.test(url); + }, + + getPathFromWikiInternalMarkdownLink: function(url, slug) { + var slug = encodeURIComponent(slug); + var re = new RegExp(serviceURL + '/wikis/' + slug + '(.*\.md)'); + var array = re.exec(url); + var path = decodeURIComponent(array[1]); + return path; + }, + + getPathFromWikiInternalDirLink: function(url, slug) { + var slug = encodeURIComponent(slug); + var re = new RegExp(serviceURL + '/wikis/' + slug+ '(/.*)'); + var array = re.exec(url); + var path = decodeURIComponent(array[1]); + return path; + }, + compareTwoWord: function(wordA, wordB) { // compare wordA and wordB at lower case // if wordA >= wordB, return 1 @@ -577,6 +606,17 @@ export const Utils = { } }); return items; - } + }, + + changeMarkdownNodes: function(nodes, fn) { + nodes.map((item) => { + fn(item); + if (item.nodes && item.nodes.length > 0){ + Utils.changeMarkdownNodes(item.nodes, fn); + } + }); + + return nodes; + }, }; diff --git a/frontend/src/wiki.js b/frontend/src/wiki.js index 34010363e3..4413fbaf17 100644 --- a/frontend/src/wiki.js +++ b/frontend/src/wiki.js @@ -119,11 +119,11 @@ class Wiki extends Component { onLinkClick = (link) => { const url = link; - if (Utils.isInternalMarkdownLink(url, repoID)) { - let path = Utils.getPathFromInternalMarkdownLink(url, repoID); + if (Utils.isWikiInternalMarkdownLink(url, slug)) { + let path = Utils.getPathFromWikiInternalMarkdownLink(url, slug); this.initMainPanelData(path); - } else if (Utils.isInternalDirLink(url, repoID)) { - let path = Utils.getPathFromInternalDirLink(url, repoID, slug); + } else if (Utils.isWikiInternalDirLink(url, slug)) { + let path = Utils.getPathFromWikiInternalDirLink(url, slug); this.initWikiData(path); } else { window.location.href = url; diff --git a/seahub/base/context_processors.py b/seahub/base/context_processors.py index e6767d58e8..afc8fa88dd 100644 --- a/seahub/base/context_processors.py +++ b/seahub/base/context_processors.py @@ -22,7 +22,7 @@ from seahub.settings import SEAFILE_VERSION, SITE_TITLE, SITE_NAME, \ MEDIA_ROOT, SHOW_LOGOUT_ICON, CUSTOM_LOGO_PATH, CUSTOM_FAVICON_PATH from seahub.constants import DEFAULT_ADMIN -from seahub.utils import get_site_name +from seahub.utils import get_site_name, get_service_url try: from seahub.settings import SEACLOUD_MODE @@ -118,7 +118,8 @@ def base(request): 'is_pro': True if is_pro_version() else False, 'enable_repo_wiki_mode': dj_settings.ENABLE_REPO_WIKI_MODE, 'enable_upload_folder': dj_settings.ENABLE_UPLOAD_FOLDER, - 'enable_resumable_fileupload': dj_settings.ENABLE_RESUMABLE_FILEUPLOAD + 'enable_resumable_fileupload': dj_settings.ENABLE_RESUMABLE_FILEUPLOAD, + 'service_url': get_service_url().rstrip('/'), } if request.user.is_staff: diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html index 9f1abf6fc2..94348046fd 100644 --- a/seahub/templates/base_for_react.html +++ b/seahub/templates/base_for_react.html @@ -35,6 +35,7 @@ isPro: '{{ is_pro }}', lang: '{{ LANGUAGE_CODE }}', fileServerRoot: '{{ FILE_SERVER_ROOT }}', + serviceURL: '{{ service_url }}', seafileVersion: '{{ seafile_version }}', }, pageOptions: { diff --git a/seahub/templates/shared_file_view_react_markdown.html b/seahub/templates/shared_file_view_react_markdown.html index 569afe843d..7cda36f20c 100644 --- a/seahub/templates/shared_file_view_react_markdown.html +++ b/seahub/templates/shared_file_view_react_markdown.html @@ -17,7 +17,6 @@ siteName: '{{ site_name }}', enableWatermark: '{{ enable_watermark }}' == 'True', download: '{{ permissions.can_download}}' == 'True', - serviceURL: '{{ serviceUrl }}', } }; diff --git a/seahub/templates/wiki/wiki.html b/seahub/templates/wiki/wiki.html index e4f824cd42..43fb099ba4 100644 --- a/seahub/templates/wiki/wiki.html +++ b/seahub/templates/wiki/wiki.html @@ -11,7 +11,7 @@ slug: "{{ wiki.slug }}", repoId: "{{ wiki.repo_id }}", initial_path: "{{ file_path }}", - permission: "{{ user_can_write }}", + isPublicWiki: "{{ is_public_wiki }}", } }; diff --git a/seahub/urls.py b/seahub/urls.py index b5967effd9..80fbd4c875 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -12,7 +12,8 @@ from seahub.views.sso import * from seahub.views.file import view_history_file, view_trash_file,\ view_snapshot_file, file_edit, view_shared_file, view_file_via_shared_dir,\ text_diff, view_raw_file, download_file, view_lib_file, \ - file_access, view_lib_file_via_smart_link, view_media_file_via_share_link + file_access, view_lib_file_via_smart_link, view_media_file_via_share_link, \ + view_media_file_via_public_wiki from seahub.views.repo import repo_history_view, view_shared_dir, \ view_shared_upload_link, view_lib_as_wiki from notifications.views import notification_list @@ -362,6 +363,7 @@ urlpatterns = [ url(r'^api/v2.1/wikis/(?P[^/]+)/dir/$', WikiPagesDirView.as_view(), name='api-v2.1-wiki-pages-dir'), url(r'^api/v2.1/wikis/(?P[^/]+)/content/$', WikiPageContentView.as_view(), name='api-v2.1-wiki-pages-content'), url(r'^api/v2.1/wikis/(?P[^/]+)/pages/(?P[^/]+)/$', WikiPageView.as_view(), name='api-v2.1-wiki-page'), + url(r'^view-image-via-public-wiki/$', view_media_file_via_public_wiki, name='view_media_file_via_public_wiki'), ## user::drafts url(r'^api/v2.1/drafts/$', DraftsView.as_view(), name='api-v2.1-drafts'), diff --git a/seahub/views/file.py b/seahub/views/file.py index 828a6927a2..61b3f74804 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -48,7 +48,7 @@ from seahub.base.accounts import ANONYMOUS_EMAIL from seahub.share.models import FileShare, check_share_link_common from seahub.share.decorators import share_link_audit, share_link_login_required from seahub.wiki.utils import get_wiki_dirent -from seahub.wiki.models import WikiDoesNotExist, WikiPageMissing +from seahub.wiki.models import Wiki, WikiDoesNotExist, WikiPageMissing from seahub.utils import render_error, is_org_context, \ get_file_type_and_ext, gen_file_get_url, gen_file_share_link, \ render_permission_error, is_pro_version, is_textual_file, \ @@ -1213,7 +1213,6 @@ def view_shared_file(request, fileshare): 'traffic_over_limit': traffic_over_limit, 'permissions': permissions, 'enable_watermark': ENABLE_WATERMARK, - 'serviceUrl': get_service_url().rstrip('/'), }) @share_link_audit @@ -2041,21 +2040,10 @@ def view_media_file_via_share_link(request): return render_error(request, 'File does not exist') # read file from cache, if hit - cache_key = normalize_cache_key(file_id, token=token) - file_content = cache.get(cache_key) - if not file_content: - # otherwise, read file from database and update cache - access_token = seafile_api.get_fileserver_access_token(repo_id, - file_id, 'view', '', use_onetime=False) + err_msg, file_content = get_file_content_from_cache(file_id, repo_id, shared_file_name) - if not access_token: - err_msg = 'Unable to view file' - return render_error(request, err_msg) - - shared_file_raw_path = gen_inner_file_get_url(access_token, shared_file_name) - - err, file_content, encode = repo_file_get(shared_file_raw_path, 'auto') - cache.set(cache_key, file_content, 24 * 60 * 60) + if err_msg: + return render_error(request, err_msg) # If the image does not exist in markdown serviceURL = get_service_url().rstrip('/') @@ -2077,3 +2065,69 @@ def view_media_file_via_share_link(request): dl_or_raw_url = gen_file_get_url(access_token, image_file_name) return HttpResponseRedirect(dl_or_raw_url) + + +def view_media_file_via_public_wiki(request): + image_path = request.GET.get('path', '') + slug = request.GET.get('slug', '') + if not image_path or not slug: + return HttpResponseBadRequest('invalid params') + + # check file type + image_file_name = os.path.basename(image_path) + file_type, file_ext = get_file_type_and_ext(image_file_name) + if file_type != IMAGE: + err_msg = 'Invalid file type' + return render_error(request, err_msg) + + # get wiki object or 404 + try: + wiki = Wiki.objects.get(slug=slug) + except Wiki.DoesNotExist: + err_msg = "Wiki not found." + return render_error(request, err_msg) + + if wiki.permission != 'public': + return render_permission_error(request, 'Permission denied') + + # recourse check + repo_id = wiki.repo_id + repo = seafile_api.get_repo(repo_id) + if not repo: + return render_error(request, 'Repo does not exist') + + # get image + obj_id = seafile_api.get_file_id_by_path(repo_id, image_path) + if not obj_id: + return render_error(request, 'Image does not exist') + + access_token = seafile_api.get_fileserver_access_token(repo_id, + obj_id, 'view', '', use_onetime=False) + + dl_or_raw_url = gen_file_get_url(access_token, image_file_name) + + return HttpResponseRedirect(dl_or_raw_url) + + +def get_file_content_from_cache(file_id, repo_id, file_name): + err_msg = '' + file_content = '' + + cache_key = normalize_cache_key(file_id) + # read file from cache, if hit + file_content = cache.get(cache_key) + if not file_content: + # otherwise, read file from database and update cache + access_token = seafile_api.get_fileserver_access_token(repo_id, + file_id, 'view', '', use_onetime=False) + + if not access_token: + err_msg = 'Unable to view file' + return err_msg, file_content + + file_raw_path = gen_inner_file_get_url(access_token, file_name) + + err_msg, file_content, encode = repo_file_get(file_raw_path, 'auto') + cache.set(cache_key, file_content, 24 * 60 * 60) + + return err_msg, file_content diff --git a/seahub/wiki/views.py b/seahub/wiki/views.py index 6fe9b976a8..1031087d8e 100644 --- a/seahub/wiki/views.py +++ b/seahub/wiki/views.py @@ -65,22 +65,18 @@ def slug(request, slug, file_path="home.md"): file_url = reverse('view_lib_file', args=[wiki.repo_id, file_path]) return HttpResponseRedirect(file_url + "?raw=1") - if not req_user: - user_can_write = False - elif req_user == wiki.username or check_folder_permission( - request, wiki.repo_id, '/') == 'rw': - user_can_write = True - else: - user_can_write = False + is_public_wiki = False + if wiki.permission == 'public': + is_public_wiki = True return render(request, "wiki/wiki.html", { "wiki": wiki, "page_name": file_path, - "user_can_write": user_can_write, "file_path": file_path, "repo_id": wiki.repo_id, "search_repo_id": wiki.repo_id, "search_wiki": True, + "is_public_wiki": is_public_wiki, })