diff --git a/frontend/src/pages/wiki/main-panel.js b/frontend/src/pages/wiki/main-panel.js
index 48d4ad1125..c49b226e03 100644
--- a/frontend/src/pages/wiki/main-panel.js
+++ b/frontend/src/pages/wiki/main-panel.js
@@ -105,7 +105,7 @@ class MainPanel extends Component {
{this.props.permission == 'rw' && (
Utils.isDesktop() ?
:
-
+
)}
diff --git a/frontend/src/wiki.js b/frontend/src/wiki.js
index 205450a24b..f842253cab 100644
--- a/frontend/src/wiki.js
+++ b/frontend/src/wiki.js
@@ -27,7 +27,7 @@ class Wiki extends Component {
pathExist: true,
closeSideBar: false,
isViewFile: true,
- isDataLoading: true,
+ isDataLoading: false,
direntList: [],
content: '',
permission: '',
@@ -43,6 +43,7 @@ class Wiki extends Component {
window.onpopstate = this.onpopstate;
this.indexPath = '/index.md';
this.homePath = '/home.md';
+ this.pythonWrapper = null;
}
componentWillMount() {
@@ -52,39 +53,54 @@ class Wiki extends Component {
}
componentDidMount() {
+ this.loadSidePanel(initialPath);
this.loadWikiData(initialPath);
+
+ this.links = document.querySelectorAll(`#wiki-file-content a`);
+ this.links.forEach(link => link.addEventListener('click', this.onConentLinkClick));
}
- loadWikiData = (initialPath) => {
- this.loadSidePanel(initialPath);
-
- if (isDir === 'None') {
- if (initialPath === '/home.md') {
- this.showDir('/');
- } else {
- this.setState({pathExist: false});
- let fileUrl = siteRoot + 'published/' + slug + Utils.encodePath(initialPath);
- window.history.pushState({url: fileUrl, path: initialPath}, initialPath, fileUrl);
- }
- } else if (isDir === 'True') {
- this.showDir(initialPath);
- } else if (isDir === 'False') {
- this.showFile(initialPath);
- }
+ componentWillUnmount() {
+ this.links.forEach(link => link.removeEventListener('click', this.onConentLinkClick));
}
loadSidePanel = (initialPath) => {
if (hasIndex) {
this.loadIndexNode();
- } else {
- if (isDir === 'None') {
- initialPath = '/';
- this.loadNodeAndParentsByPath('/');
- } else {
- this.loadNodeAndParentsByPath(initialPath);
- }
+ return;
}
+ // load dir list
+ initialPath = isDir === 'None' ? '/' : initialPath;
+ this.loadNodeAndParentsByPath(initialPath);
+ }
+
+ loadWikiData = (initialPath) => {
+ this.pythonWrapper = document.getElementById('wiki-file-content');
+ if (isDir === 'False') {
+ // this.showFile(initialPath);
+ this.setState({path: initialPath});
+ return;
+ }
+
+ // if it is a file list, remove the template content provided by python
+ this.removePythonWrapper();
+
+ if (isDir === 'True') {
+ this.showDir(initialPath);
+ return;
+ }
+
+ if (isDir === 'None' && initialPath === '/home.md') {
+ this.showDir('/');
+ return;
+ }
+
+ if (isDir === 'None') {
+ this.setState({pathExist: false});
+ let fileUrl = siteRoot + 'published/' + slug + Utils.encodePath(initialPath);
+ window.history.pushState({url: fileUrl, path: initialPath}, initialPath, fileUrl);
+ }
}
loadIndexNode = () => {
@@ -119,7 +135,8 @@ class Wiki extends Component {
isViewFile: true,
path: filePath,
});
-
+
+ this.removePythonWrapper();
seafileAPI.getWikiFileContent(slug, filePath).then(res => {
let data = res.data;
this.setState({
@@ -221,6 +238,29 @@ class Wiki extends Component {
});
}
+ removePythonWrapper = () => {
+ if (this.pythonWrapper) {
+ document.body.removeChild(this.pythonWrapper);
+ this.pythonWrapper = null;
+ }
+ }
+
+ onConentLinkClick = (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ let link = '';
+ if (event.target.tagName !== 'A') {
+ let target = event.target.parentNode;
+ while (target.tagName !== 'A') {
+ target = target.parentNode;
+ }
+ link = target.href;
+ } else {
+ link = event.target.href;
+ }
+ this.onLinkClick(link);
+ }
+
onLinkClick = (link) => {
const url = link;
if (Utils.isWikiInternalMarkdownLink(url, slug)) {
diff --git a/media/css/seahub_react.css b/media/css/seahub_react.css
index 4414ab2952..6d4a63e852 100644
--- a/media/css/seahub_react.css
+++ b/media/css/seahub_react.css
@@ -1220,3 +1220,124 @@ a.table-sort-op:hover {
box-shadow: 0 0 6px #ccc;
text-align: center;
}
+
+#wiki-file-content {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 20%;
+ top: 90px;
+ z-index: 2;
+ background: #fff;
+ overflow: auto;
+ display: flex;
+}
+
+#wiki-file-content .article {
+ margin-right: 200px;
+ padding: 10px 30px 20px;
+}
+
+#wiki-file-content .seafile-markdown-outline {
+ position: fixed;
+ top: 97px;
+ right: 0;
+ width: 200px;
+ overflow: auto;
+ height: 80%;
+}
+
+@media (max-width: 767px) {
+ #wiki-file-content {
+ left: 0;
+ }
+
+ #wiki-file-content .article {
+ margin-right: 0;
+ width: 100%;
+ }
+
+ #wiki-file-content .seafile-markdown-outline {
+ display: none;
+ }
+}
+
+.seafile-md-viewer-content .article {
+ padding: 0;
+}
+.seafile-md-viewer-content {
+ background: #fff;
+ padding: 70px 75px;
+ border:1px solid #e6e6dd;
+ min-height: calc(100% - 60px);
+}
+.seafile-md-viewer-outline-heading2,
+.seafile-md-viewer-outline-heading3 {
+ margin-left: .75rem;
+ line-height: 2.5;
+ color:#666;
+ white-space: nowrap;
+ overflow:hidden;
+ text-overflow:ellipsis;
+ cursor:pointer;
+}
+.seafile-md-viewer-outline-heading3 {
+ margin-left: 2rem;
+}
+.seafile-md-viewer-outline-heading2:hover,
+.seafile-md-viewer-outline-heading3:hover {
+ color: #eb8205;
+}
+.seafile-markdown-outline {
+ position: fixed;
+ padding-right: 1rem;
+ top: 97px;
+ right: 0;
+ width: 200px;
+ overflow: auto;
+ height: 80%;
+}
+.seafile-editor-outline {
+ border-left: 1px solid #ddd;
+}
+.seafile-markdown-outline .active {
+ color: #eb8205;
+ border-left: 1px solid #eb8205;
+}
+.seafile-markdown-outline .outline-h2,
+.seafile-markdown-outline .outline-h3 {
+ height: 30px;
+ margin-left: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 14px;
+}
+.seafile-markdown-outline .outline-h2 {
+ padding-left: 20px;
+}
+.seafile-markdown-outline .outline-h3 {
+ padding-left: 40px;
+}
+
+#wiki-file-content .seafile-markdown-outline .outline-h2,
+#wiki-file-content .seafile-markdown-outline .outline-h3 {
+ height: 24px;
+ font-size: 12px;
+ color: #4d5156;
+}
+
+#wiki-file-content .seafile-markdown-outline .outline-h2.active,
+#wiki-file-content .seafile-markdown-outline .outline-h3.active {
+ color: #eb8205;
+}
+
+#wiki-file-content .seafile-markdown-outline .seafile-markdown-outline {
+ overflow-y: hidden;
+ margin-right: 10px;
+}
+
+#wiki-file-content .seafile-markdown-outline .seafile-markdown-outline:hover {
+ overflow-y: auto;
+}
+
diff --git a/requirements.txt b/requirements.txt
index a40a2e9228..11e19d9365 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,3 +22,4 @@ python-cas
djangosaml2==0.20.0
pysaml2==6.5.1
cffi==1.14.0
+Markdown
diff --git a/seahub/base/templatetags/seahub_tags.py b/seahub/base/templatetags/seahub_tags.py
index 79c0d7803f..a9de49bac9 100644
--- a/seahub/base/templatetags/seahub_tags.py
+++ b/seahub/base/templatetags/seahub_tags.py
@@ -304,6 +304,8 @@ def translate_seahub_time(value, autoescape=None):
return mark_safe(time_with_tag)
+
+@register.filter(name='translate_seahub_time_str')
def translate_seahub_time_str(val):
"""Convert python datetime to human friendly format."""
diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html
index ed63a5d7ea..ef8a8452ca 100644
--- a/seahub/templates/base_for_react.html
+++ b/seahub/templates/base_for_react.html
@@ -28,6 +28,7 @@
+ {%block extra_content %}{% endblock %}
+
{% render_bundle 'wiki' 'js' %}
{% endblock %}
diff --git a/seahub/wiki/views.py b/seahub/wiki/views.py
index 2deb7e58c1..025ed4e3bb 100644
--- a/seahub/wiki/views.py
+++ b/seahub/wiki/views.py
@@ -1,22 +1,28 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import os
+import re
import logging
-import urllib.request, urllib.error, urllib.parse
+import urllib.request
import posixpath
+from datetime import datetime
-import seaserv
+import markdown
+import lxml.html
from seaserv import seafile_api
from django.urls import reverse
-from django.http import Http404, HttpResponseRedirect
+from django.http import HttpResponseRedirect
+from django.utils.safestring import mark_safe
from django.shortcuts import render, get_object_or_404, redirect
from django.utils.translation import ugettext as _
-from seahub.auth.decorators import login_required
from seahub.share.models import FileShare
from seahub.wiki.models import Wiki
from seahub.views import check_folder_permission
-from seahub.utils import get_service_url, get_file_type_and_ext, render_permission_error
+from seahub.utils import get_file_type_and_ext, render_permission_error, \
+ gen_inner_file_get_url, render_error
+from seahub.views.file import send_file_access_msg
from seahub.utils.file_types import *
+from seahub.settings import SERVICE_URL
# Get an instance of a logger
logger = logging.getLogger(__name__)
@@ -88,6 +94,98 @@ def slug(request, slug, file_path="home.md"):
repo = seafile_api.get_repo(wiki.repo_id)
+ file_content, outlines, latest_contributor, last_modified = '', [], '', 0
+ if is_dir is False:
+ send_file_access_msg(request, repo, file_path, 'web')
+
+ file_name = os.path.basename(file_path)
+ token = seafile_api.get_fileserver_access_token(
+ repo.repo_id, file_id, 'download', request.user.username, 'False')
+ if not token:
+ return render_error(request, _('Internal Server Error'))
+
+ url = gen_inner_file_get_url(token, file_name)
+ try:
+ file_response = urllib.request.urlopen(url).read().decode()
+ except Exception as e:
+ logger.error(e)
+ return render_error(request, _('Internal Server Error'))
+
+ if file_type == MARKDOWN:
+ # Convert a markdown string to HTML
+ try:
+ html_content = markdown.markdown(file_response)
+ except Exception as e:
+ logger.error(e)
+ return render_error(request, _('Internal Server Error'))
+
+ # Parse the html and replace image url to wiki mode
+ html_doc = lxml.html.fromstring(html_content)
+ img_elements = html_doc.xpath('//img') # Get the
![]()
elements
+ img_url_re = re.compile(r'^%s/lib/%s/file/.*raw=1$' % (SERVICE_URL.strip('/'), repo.id))
+ for img in img_elements:
+ img_url = img.attrib.get('src', '')
+ if img_url_re.match(img_url) is not None:
+ img_path = img_url[img_url.find('/file/')+5:img_url.find('?')]
+ new_img_url = '%s/view-image-via-public-wiki/?slug=%s&path=%s' \
+ % (SERVICE_URL.strip('/'), slug, img_path)
+ html_content = html_content.replace(img_url, new_img_url)
+ elif re.compile(r'^\.\./*|^\./').match(img_url):
+ if img_url.startswith('../'):
+ img_path = os.path.join(os.path.dirname(os.path.dirname(file_path)), img_url[3:])
+ else:
+ img_path = os.path.join(os.path.dirname(file_path), img_url[2:])
+ new_img_url = '%s/view-image-via-public-wiki/?slug=%s&path=%s' \
+ % (SERVICE_URL.strip('/'), slug, img_path)
+ html_content = html_content.replace(img_url, new_img_url)
+
+ # Replace link url to wiki mode
+ link_elements = html_doc.xpath('//a') # Get the
elements
+ file_link_re = re.compile(r'^%s/lib/%s/file/.*' % (SERVICE_URL.strip('/'), repo.id))
+ md_link_re = re.compile(r'^%s/lib/%s/file/.*\.md$' % (SERVICE_URL.strip('/'), repo.id))
+ dir_link_re = re.compile(r'^%s/library/%s/(.*)' % (SERVICE_URL.strip('/'), repo.id))
+ for link in link_elements:
+ link_url = link.attrib.get('href', '')
+ if file_link_re.match(link_url) is not None:
+ link_path = link_url[link_url.find('/file/') + 5:].strip('/')
+ if md_link_re.match(link_url) is not None:
+ new_md_url = '%s/published/%s/%s' % (SERVICE_URL.strip('/'), slug, link_path)
+ html_content = html_content.replace(link_url, new_md_url)
+ else:
+ new_file_url = '%s/d/%s/files/?p=%s&dl=1' % (SERVICE_URL.strip('/'), fs.token, link_path)
+ html_content = html_content.replace(link_url, new_file_url)
+ elif dir_link_re.match(link_url) is not None:
+ link_path = dir_link_re.match(link_url).groups()[0].strip('/')
+ dir_path = link_path[link_path.find('/'):].strip('/')
+ new_dir_url = '%s/published/%s/%s' % (SERVICE_URL.strip('/'), slug, dir_path)
+ html_content = html_content.replace(link_url, new_dir_url)
+
+ # Get markdown outlines and format label
+ for p in html_content.split('\n'):
+ if p.startswith('') and p.endswith('
'):
+ head = p.replace('', '' % p.strip(''), 1)
+ html_content = html_content.replace(p, head)
+ elif p.startswith('') and p.endswith('
'):
+ head = p.replace('', '' % p.strip(''), 1)
+ html_content = html_content.replace(p, head)
+ outline = '
' + p.strip('
') + ''
+ outlines.append(mark_safe(outline))
+ elif p.startswith('') and p.endswith('
'):
+ head = p.replace('', '' % p.strip(''), 1)
+ html_content = html_content.replace(p, head)
+ outline = '
' + p.strip('
') + ''
+ outlines.append(mark_safe(outline))
+
+ file_content = mark_safe(html_content)
+
+ try:
+ dirent = seafile_api.get_dirent_by_path(wiki.repo_id, file_path)
+ if dirent:
+ latest_contributor, last_modified = dirent.modifier, dirent.mtime
+ except Exception as e:
+ logger.warning(e)
+ last_modified = datetime.fromtimestamp(last_modified)
+
return render(request, "wiki/wiki.html", {
"wiki": wiki,
"repo_name": repo.name if repo else '',
@@ -97,6 +195,10 @@ def slug(request, slug, file_path="home.md"):
"user_can_write": user_can_write,
"file_path": file_path,
"filename": os.path.splitext(os.path.basename(file_path))[0],
+ "file_content": file_content,
+ "outlines": outlines,
+ "modifier": latest_contributor,
+ "modify_time": last_modified,
"repo_id": wiki.repo_id,
"search_repo_id": wiki.repo_id,
"search_wiki": True,