From d562ee176537a4b4195b44da9d1096554d1048bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98JoinTyang=E2=80=99?= Date: Mon, 13 May 2024 16:29:45 +0800 Subject: [PATCH] wiki add wiki2 --- .../wiki-list-view/wiki-list-item.js | 2 - frontend/src/pages/wiki2/index.js | 20 +- frontend/src/pages/wikis/wikis.js | 62 ++- frontend/src/utils/wiki-api.js | 73 +++- seahub/api2/endpoints/wiki2.py | 410 ++++++++++++++++++ seahub/api2/endpoints/wikis.py | 120 +---- seahub/settings.py | 1 + seahub/urls.py | 19 +- seahub/wiki/models.py | 6 +- seahub/wiki/utils.py | 5 - seahub/wiki/views.py | 96 ---- seahub/wiki2/__init__.py | 0 seahub/wiki2/models.py | 102 +++++ seahub/wiki2/utils.py | 43 ++ seahub/wiki2/views.py | 122 ++++++ 15 files changed, 823 insertions(+), 258 deletions(-) create mode 100644 seahub/api2/endpoints/wiki2.py create mode 100644 seahub/wiki2/__init__.py create mode 100644 seahub/wiki2/models.py create mode 100644 seahub/wiki2/utils.py create mode 100644 seahub/wiki2/views.py diff --git a/frontend/src/components/wiki-list-view/wiki-list-item.js b/frontend/src/components/wiki-list-view/wiki-list-item.js index a3622282a75..86cbd466aba 100644 --- a/frontend/src/components/wiki-list-view/wiki-list-item.js +++ b/frontend/src/components/wiki-list-view/wiki-list-item.js @@ -71,9 +71,7 @@ class WikiListItem extends Component { let userProfileURL = `${siteRoot}profile/${encodeURIComponent(wiki.owner)}/`; let fileIconUrl = Utils.getDefaultLibIconUrl(false); let isOldVersion = wiki.version !== 'v2'; - // let publishedUrl = `${siteRoot}published/${encodeURIComponent(wiki.slug)}/`; let publishedUrl = `${siteRoot}published/${encodeURIComponent(wiki.slug)}/`; - // let editUrl = `${siteRoot}edit-wiki/${encodeURIComponent(wiki.slug)}/`; let editUrl = `${siteRoot}edit-wiki/${wiki.id}/`; const desktopItem = ( diff --git a/frontend/src/pages/wiki2/index.js b/frontend/src/pages/wiki2/index.js index c0d773dbd6c..ac730ee8f67 100644 --- a/frontend/src/pages/wiki2/index.js +++ b/frontend/src/pages/wiki2/index.js @@ -78,7 +78,7 @@ class Wiki extends Component { }; getWikiConfig = () => { - wikiAPI.getWikiConfig(wikiId).then(res => { + wikiAPI.getWiki2Config(wikiId).then(res => { const { wiki_config, repo_id } = res.data.wiki; this.setState({ config: new WikiConfig(JSON.parse(wiki_config) || {}), @@ -95,7 +95,7 @@ class Wiki extends Component { }; saveWikiConfig = (wikiConfig, onSuccess, onError) => { - wikiAPI.updateWikiConfig(wikiId, JSON.stringify(wikiConfig)).then(res => { + wikiAPI.updateWiki2Config(wikiId, JSON.stringify(wikiConfig)).then(res => { this.setState({ config: new WikiConfig(wikiConfig || {}), }); @@ -152,11 +152,11 @@ class Wiki extends Component { }; loadIndexNode = () => { - wikiAPI.listWikiDir(wikiId, '/').then(res => { + wikiAPI.listWiki2Dir(wikiId, '/').then(res => { let tree = this.state.treeData; this.addFirstResponseListToNode(res.data.dirent_list, tree.root); let indexNode = tree.getNodeByPath(this.indexPath); - wikiAPI.getWikiFileContent(wikiId, indexNode.path).then(res => { + wikiAPI.getWiki2FileContent(wikiId, indexNode.path).then(res => { this.setState({ treeData: tree, indexNode: indexNode, @@ -186,7 +186,7 @@ class Wiki extends Component { }); this.removePythonWrapper(); - wikiAPI.getWikiFileContent(wikiId, filePath).then(res => { + wikiAPI.getWiki2FileContent(wikiId, filePath).then(res => { let data = res.data; this.setState({ isDataLoading: false, @@ -211,7 +211,7 @@ class Wiki extends Component { loadDirentList = (dirPath) => { this.setState({isDataLoading: true}); - wikiAPI.listWikiDir(wikiId, dirPath).then(res => { + wikiAPI.listWiki2Dir(wikiId, dirPath).then(res => { let direntList = res.data.dirent_list.map(item => { let dirent = new Dirent(item); return dirent; @@ -241,7 +241,7 @@ class Wiki extends Component { let tree = this.state.treeData.clone(); let node = tree.getNodeByPath(path); if (!node.isLoaded) { - wikiAPI.listWikiDir(wikiId, node.path).then(res => { + wikiAPI.listWiki2Dir(wikiId, node.path).then(res => { this.addResponseListToNode(res.data.dirent_list, node); let parentNode = tree.getNodeByPath(node.parentNode.path); parentNode.isExpanded = true; @@ -262,7 +262,7 @@ class Wiki extends Component { if (Utils.isMarkdownFile(path)) { path = Utils.getDirName(path); } - wikiAPI.listWikiDir(wikiId, path, true).then(res => { + wikiAPI.listWiki2Dir(wikiId, path, true).then(res => { let direntList = res.data.dirent_list; let results = {}; for (let i = 0; i < direntList.length; i++) { @@ -425,7 +425,7 @@ class Wiki extends Component { if (!node.isLoaded) { let tree = this.state.treeData.clone(); node = tree.getNodeByPath(node.path); - wikiAPI.listWikiDir(wikiId, node.path).then(res => { + wikiAPI.listWiki2Dir(wikiId, node.path).then(res => { this.addResponseListToNode(res.data.dirent_list, node); tree.collapseNode(node); this.setState({treeData: tree}); @@ -473,7 +473,7 @@ class Wiki extends Component { let tree = this.state.treeData.clone(); node = tree.getNodeByPath(node.path); if (!node.isLoaded) { - wikiAPI.listWikiDir(wikiId, node.path).then(res => { + wikiAPI.listWiki2Dir(wikiId, node.path).then(res => { this.addResponseListToNode(res.data.dirent_list, node); this.setState({treeData: tree}); }); diff --git a/frontend/src/pages/wikis/wikis.js b/frontend/src/pages/wikis/wikis.js index 5d490795bab..17f76514008 100644 --- a/frontend/src/pages/wikis/wikis.js +++ b/frontend/src/pages/wikis/wikis.js @@ -34,10 +34,30 @@ class Wikis extends Component { } getWikis = () => { + let wikis = [] wikiAPI.listWikis().then(res => { + wikis = wikis.concat(res.data.data) + wikis.map(wiki => { + return wiki['version'] = 'v1' + }) + wikiAPI.listWikis2().then(res => { + let wikis2 = res.data.data; + wikis2.map(wiki => { + return wiki['version'] = 'v2' + }) + this.setState({ + loading: false, + wikis: wikis.concat(wikis2) + }); + }).catch((error) => { + this.setState({ + loading: false, + errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403 + }); + }); this.setState({ loading: false, - wikis: res.data.data + wikis: wikis }); }).catch((error) => { this.setState({ @@ -61,9 +81,11 @@ class Wikis extends Component { }; addWiki = (wikiName) => { - wikiAPI.addWiki(wikiName).then((res) => { + wikiAPI.addWiki2(wikiName).then((res) => { let wikis = this.state.wikis.slice(0); - wikis.push(res.data); + let new_wiki = res.data; + new_wiki['version'] = 'v2' + wikis.push(new_wiki); this.setState({ wikis }); }).catch((error) => { if (error.response) { @@ -74,17 +96,31 @@ class Wikis extends Component { }; deleteWiki = (wiki) => { - wikiAPI.deleteWiki(wiki.id).then(() => { - let wikis = this.state.wikis.filter(item => { - return item.name !== wiki.name; + if (wiki.version === 'v1') { + wikiAPI.deleteWiki(wiki.id).then(() => { + let wikis = this.state.wikis.filter(item => { + return item.name !== wiki.name; + }); + this.setState({wikis: wikis}); + }).catch((error) => { + if(error.response) { + let errorMsg = error.response.data.error_msg; + toaster.danger(errorMsg); + } }); - this.setState({wikis: wikis}); - }).catch((error) => { - if(error.response) { - let errorMsg = error.response.data.error_msg; - toaster.danger(errorMsg); - } - }); + } else { + wikiAPI.deleteWiki2(wiki.id).then(() => { + let wikis = this.state.wikis.filter(item => { + return item.name !== wiki.name; + }); + this.setState({wikis: wikis}); + }).catch((error) => { + if(error.response) { + let errorMsg = error.response.data.error_msg; + toaster.danger(errorMsg); + } + }); + } }; render() { diff --git a/frontend/src/utils/wiki-api.js b/frontend/src/utils/wiki-api.js index dbe0067d8ba..97d21052bc9 100644 --- a/frontend/src/utils/wiki-api.js +++ b/frontend/src/utils/wiki-api.js @@ -55,10 +55,10 @@ class WikiAPI { } - getWikiFileContent(slug, filePath) { + getWikiFileContent(wikiId, filePath) { const path = encodeURIComponent(filePath); const time = new Date().getTime(); - const url = this.server + '/api/v2.1/wikis/' + encodeURIComponent(slug) + '/content/' + '?p=' + path + '&_=' + time; + const url = this.server + '/api/v2.1/wikis/' + wikiId + '/content/' + '?p=' + path + '&_=' + time; return this.req.get(url); } @@ -104,16 +104,77 @@ class WikiAPI { return this.req.delete(url); } - updateWikiConfig(wikiId, wikiConfig) { - const url = this.server + '/api/v2.1/wiki-config/' + wikiId + '/'; + + // for wiki2 + listWiki2Dir(wikiId, dirPath, withParents) { + const path = encodeURIComponent(dirPath); + let url = this.server + '/api/v2.1/wikis2/' + wikiId + '/dir/?p=' + path; + if (withParents) { + url = this.server + '/api/v2.1/wikis2/' + wikiId + '/dir/?p=' + path + '&with_parents=' + withParents; + } + return this.req.get(url); + } + + + getWiki2FileContent(wikiId, filePath) { + const path = encodeURIComponent(filePath); + const time = new Date().getTime(); + const url = this.server + '/api/v2.1/wikis2/' + wikiId + '/content/' + '?p=' + path + '&_=' + time; + return this.req.get(url); + } + + + listWikis2(options) { + /* + * options: `{type: 'shared'}`, `{type: ['mine', 'shared', ...]}` + */ + let url = this.server + '/api/v2.1/wikis2/'; + if (!options) { + // fetch all types of wikis + return this.req.get(url); + } + return this.req.get(url, { + params: options, + paramsSerializer: { + serialize: function(params) { + let list = []; + for (let key in params) { + if (Array.isArray(params[key])) { + for (let i = 0, len = params[key].length; i < len; i++) { + list.push(key + '=' + encodeURIComponent(params[key][i])); + } + } else { + list.push(key + '=' + encodeURIComponent(params[key])); + } + } + return list.join('&'); + } + } + }); + } + + addWiki2(wikiName) { + const url = this.server + '/api/v2.1/wikis2/'; + let form = new FormData(); + form.append('name', wikiName); + return this._sendPostRequest(url, form); + } + + deleteWiki2(wikiId) { + const url = this.server + '/api/v2.1/wikis2/' + wikiId + '/'; + return this.req.delete(url); + } + + updateWiki2Config(wikiId, wikiConfig) { + const url = this.server + '/api/v2.1/wiki2-config/' + wikiId + '/'; let params = { wiki_config: wikiConfig }; return this.req.put(url, params); } - getWikiConfig(wikiId) { - const url = this.server + '/api/v2.1/wiki-config/' + wikiId + '/'; + getWiki2Config(wikiId) { + const url = this.server + '/api/v2.1/wiki2-config/' + wikiId + '/'; return this.req.get(url); } diff --git a/seahub/api2/endpoints/wiki2.py b/seahub/api2/endpoints/wiki2.py new file mode 100644 index 00000000000..c49bd145b23 --- /dev/null +++ b/seahub/api2/endpoints/wiki2.py @@ -0,0 +1,410 @@ +# Copyright (c) 2012-2016 Seafile Ltd. + +import os +import json +import logging +import requests +import posixpath +import urllib.request, urllib.error, urllib.parse + +from rest_framework import status +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated, IsAuthenticated, IsAuthenticatedOrReadOnly +from rest_framework.response import Response +from rest_framework.views import APIView +from seaserv import seafile_api, edit_repo +from pysearpc import SearpcError +from django.utils.translation import gettext as _ + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error, to_python_boolean +from seahub.wiki2.models import Wiki2 as Wiki +from seahub.wiki2.utils import is_valid_wiki_name, can_edit_wiki, get_wiki_dirs_by_path +from seahub.utils import is_org_context, get_user_repos, gen_inner_file_get_url, gen_file_upload_url, normalize_dir_path +from seahub.share.models import FileShare +from seahub.views import check_folder_permission +from seahub.views.file import send_file_access_msg +from seahub.base.templatetags.seahub_tags import email2nickname + + +WIKI_CONFIG_PATH = '_Internal/Wiki' +WIKI_CONFIG_FILE_NAME = 'index.json' +HTTP_520_OPERATION_FAILED = 520 + + +logger = logging.getLogger(__name__) + + +class Wiki2sView(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated, ) + throttle_classes = (UserRateThrottle, ) + + def get(self, request, format=None): + """List all wikis. + """ + # parse request params + filter_by = { + 'mine': False, + 'shared': False, + 'group': False, + 'org': False, + } + + rtype = request.GET.get('type', "") + if not rtype: + # set all to True, no filter applied + filter_by = filter_by.fromkeys(iter(filter_by.keys()), True) + + for f in rtype.split(','): + f = f.strip() + filter_by[f] = True + + username = request.user.username + org_id = request.user.org.org_id if is_org_context(request) else None + (owned, shared, groups, public) = get_user_repos(username, org_id) + + filter_repo_ids = [] + if filter_by['mine']: + filter_repo_ids += ([r.id for r in owned]) + + if filter_by['shared']: + filter_repo_ids += ([r.id for r in shared]) + import urllib.request, urllib.error, urllib.parse + + if filter_by['group']: + filter_repo_ids += ([r.id for r in groups]) + + if filter_by['org']: + filter_repo_ids += ([r.id for r in public]) + + filter_repo_ids = list(set(filter_repo_ids)) + + wikis = Wiki.objects.filter(repo_id__in=filter_repo_ids) + + wiki_list = [] + for wiki in wikis: + wiki_info = wiki.to_dict() + wiki_info['can_edit'] = (username == wiki.username) + wiki_list.append(wiki_info) + + return Response({'data': wiki_list}) + + def post(self, request, format=None): + """Add a new wiki. + """ + username = request.user.username + + if not request.user.permissions.can_add_repo(): + return api_error(status.HTTP_403_FORBIDDEN, + 'You do not have permission to create library.') + + wiki_name = request.data.get("name", None) + if not wiki_name: + return api_error(status.HTTP_400_BAD_REQUEST, 'wiki name is required.') + + if not is_valid_wiki_name(wiki_name): + msg = _('Name can only contain letters, numbers, blank, hyphen or underscore.') + return api_error(status.HTTP_400_BAD_REQUEST, msg) + + org_id = -1 + if is_org_context(request): + org_id = request.user.org.org_id + + try: + wiki = Wiki.objects.add(wiki_name=wiki_name, username=username, org_id=org_id, permission='public') + except Exception as e: + logger.error(e) + msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, msg) + + repo_id = wiki.repo_id + fs = FileShare.objects.get_dir_link_by_path(username, repo_id, '/') + if not fs: + fs = FileShare.objects.create_dir_link(username, repo_id, '/', + permission='view_download', org_id=org_id) + + return Response(wiki.to_dict()) + + +class Wiki2View(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated, ) + throttle_classes = (UserRateThrottle, ) + + def delete(self, request, wiki_id): + """Delete a wiki. + """ + username = request.user.username + try: + wiki = Wiki.objects.get(id=wiki_id) + except Wiki.DoesNotExist: + error_msg = 'Wiki not found.' + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + owner = wiki.username + if owner != username: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + wiki.delete() + + # file_name = os.path.basename(path) + repo_id = wiki.repo_id + file_name = WIKI_CONFIG_FILE_NAME + try: + seafile_api.del_file(repo_id, WIKI_CONFIG_PATH, + json.dumps([file_name]), + request.user.username) + except SearpcError as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response() + + +class Wiki2ConfigView(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated, ) + throttle_classes = (UserRateThrottle, ) + + def put(self, request, wiki_id): + """Edit a wiki config + """ + username = request.user.username + + try: + wiki = Wiki.objects.get(id=wiki_id) + except Wiki.DoesNotExist: + error_msg = "Wiki not found." + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + if not can_edit_wiki(wiki, request.user.username): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + repo_id = wiki.repo_id + obj_id = json.dumps({'parent_dir': WIKI_CONFIG_PATH}) + + dir_id = seafile_api.get_dir_id_by_path(repo_id, WIKI_CONFIG_PATH) + if not dir_id: + seafile_api.mkdir_with_parents(repo_id, '/', WIKI_CONFIG_PATH, username) + + token = seafile_api.get_fileserver_access_token( + repo_id, obj_id, 'upload-link', username, use_onetime=False) + if not token: + return None + upload_link = gen_file_upload_url(token, 'upload-api') + upload_link = upload_link + '?replace=1' + + wiki_config = request.data.get('wiki_config', '{}') + + files = { + 'file': (WIKI_CONFIG_FILE_NAME, wiki_config) + } + data = {'parent_dir': WIKI_CONFIG_PATH, 'relative_path': '', 'replace': 1} + resp = requests.post(upload_link, files=files, data=data) + if not resp.ok: + logger.error(resp.text) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + wiki = wiki.to_dict() + wiki['wiki_config'] = wiki_config + return Response({'wiki': wiki}) + + def get(self, request, wiki_id): + + try: + wiki = Wiki.objects.get(id=wiki_id) + except Wiki.DoesNotExist: + error_msg = "Wiki not found." + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + if not can_edit_wiki(wiki, request.user.username): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + path = posixpath.join(WIKI_CONFIG_PATH, WIKI_CONFIG_FILE_NAME) + try: + repo = seafile_api.get_repo(wiki.repo_id) + if not repo: + error_msg = "Wiki library not found." + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + except SearpcError: + error_msg = _("Internal Server Error") + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + try: + file_id = seafile_api.get_file_id_by_path(repo.repo_id, path) + except SearpcError as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + wiki = wiki.to_dict() + if not file_id: + wiki['wiki_config'] = '{}' + return Response({'wiki': wiki}) + + token = seafile_api.get_fileserver_access_token(repo.repo_id, file_id, 'download', request.user.username, use_onetime=True) + + if not token: + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + url = gen_inner_file_get_url(token, WIKI_CONFIG_FILE_NAME) + resp = requests.get(url) + content = resp.content + + wiki['wiki_config'] = content + + return Response({'wiki': wiki}) + + +class Wiki2PagesDirView(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticatedOrReadOnly,) + throttle_classes = (UserRateThrottle,) + + def get(self, request, wiki_id): + """List all dir files in a wiki. + """ + try: + wiki = Wiki.objects.get(id=wiki_id) + except Wiki.DoesNotExist: + error_msg = "Wiki not found." + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # perm check + if not wiki.has_read_perm(request): + error_msg = "Permission denied" + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + try: + repo = seafile_api.get_repo(wiki.repo_id) + if not repo: + error_msg = "Wiki library not found." + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + except SearpcError: + error_msg = "Internal Server Error" + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + with_parents = request.GET.get('with_parents', 'false') + if with_parents not in ('true', 'false'): + error_msg = 'with_parents invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + with_parents = to_python_boolean(with_parents) + + parent_dir = request.GET.get("p", '/') + parent_dir = normalize_dir_path(parent_dir) + + dir_id = seafile_api.get_dir_id_by_path(repo.repo_id, parent_dir) + if not dir_id: + error_msg = 'Folder %s not found.' % parent_dir + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + parent_dir_list = [] + if not with_parents: + # only return dirent list in current parent folder + parent_dir_list.append(parent_dir) + else: + # if value of 'p' parameter is '/a/b/c' add with_parents's is 'true' + # then return dirent list in '/', '/a', '/a/b' and '/a/b/c'. + if parent_dir == '/': + parent_dir_list.append(parent_dir) + else: + tmp_parent_dir = '/' + parent_dir_list.append(tmp_parent_dir) + for folder_name in parent_dir.strip('/').split('/'): + tmp_parent_dir = posixpath.join(tmp_parent_dir, folder_name) + parent_dir_list.append(tmp_parent_dir) + + all_dirs_info = [] + for parent_dir in parent_dir_list: + all_dirs = get_wiki_dirs_by_path(repo.repo_id, parent_dir, []) + all_dirs_info += all_dirs + + return Response({ + "dirent_list": all_dirs_info + }) + + +class Wiki2PageContentView(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticatedOrReadOnly,) + throttle_classes = (UserRateThrottle,) + + def get(self, request, wiki_id): + """Get content of a wiki + """ + path = request.GET.get('p', '/') + try: + wiki = Wiki.objects.get(id=wiki_id) + except Wiki.DoesNotExist: + error_msg = "Wiki not found." + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # perm check + if not wiki.has_read_perm(request): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + if request.user.username: + parent_dir = os.path.dirname(path) + permission = check_folder_permission(request, wiki.repo_id, parent_dir) + else: + permission = 'r' + + try: + repo = seafile_api.get_repo(wiki.repo_id) + if not repo: + error_msg = "Wiki library not found." + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + except SearpcError: + error_msg = _("Internal Server Error") + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + file_id = None + try: + file_id = seafile_api.get_file_id_by_path(repo.repo_id, path) + except SearpcError as e: + logger.error(e) + return api_error(HTTP_520_OPERATION_FAILED, + "Failed to get file id by path.") + if not file_id: + return api_error(status.HTTP_404_NOT_FOUND, "File not found") + + # send stats message + send_file_access_msg(request, repo, path, 'api') + + file_name = os.path.basename(path) + token = seafile_api.get_fileserver_access_token(repo.repo_id, + file_id, 'download', request.user.username, 'False') + + if not token: + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + url = gen_inner_file_get_url(token, file_name) + file_response = urllib.request.urlopen(url) + content = file_response.read() + + try: + dirent = seafile_api.get_dirent_by_path(repo.repo_id, path) + if dirent: + latest_contributor, last_modified = dirent.modifier, dirent.mtime + else: + latest_contributor, last_modified = None, 0 + except SearpcError as e: + logger.error(e) + latest_contributor, last_modified = None, 0 + + return Response({ + "content": content, + "latest_contributor": email2nickname(latest_contributor), + "last_modified": last_modified, + "permission": permission, + }) diff --git a/seahub/api2/endpoints/wikis.py b/seahub/api2/endpoints/wikis.py index 0019ba66ca3..af9baff067a 100644 --- a/seahub/api2/endpoints/wikis.py +++ b/seahub/api2/endpoints/wikis.py @@ -20,7 +20,7 @@ from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error from seahub.wiki.models import Wiki, DuplicateWikiNameError -from seahub.wiki.utils import is_valid_wiki_name, slugfy_wiki_name, can_edit_wiki +from seahub.wiki.utils import is_valid_wiki_name, slugfy_wiki_name from seahub.utils import is_org_context, get_user_repos, gen_inner_file_get_url, gen_file_upload_url from seahub.utils.repo import is_group_repo_staff, is_repo_owner from seahub.views import check_folder_permission @@ -28,10 +28,6 @@ from seahub.share.models import FileShare -WIKI_CONFIG_PATH = '_Internal/Wiki' -WIKI_CONFIG_FILE_NAME = 'index.json' - - logger = logging.getLogger(__name__) @@ -107,7 +103,7 @@ def post(self, request, format=None): org_id = request.user.org.org_id try: - wiki = Wiki.objects.add(wiki_name=wiki_name, username=username, org_id=org_id, permission='public', version='v2') + wiki = Wiki.objects.add(wiki_name=wiki_name, username=username, org_id=org_id, permission='public') except DuplicateWikiNameError: msg = _('%s is taken by others, please try another name.') % wiki_name return api_error(status.HTTP_400_BAD_REQUEST, msg) @@ -143,21 +139,8 @@ def delete(self, request, wiki_id): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - # Wiki.objects.filter(slug=slug).delete() wiki.delete() - # file_name = os.path.basename(path) - repo_id = wiki.repo_id - file_name = WIKI_CONFIG_FILE_NAME - try: - seafile_api.del_file(repo_id, WIKI_CONFIG_PATH, - json.dumps([file_name]), - request.user.username) - except SearpcError as e: - logger.error(e) - error_msg = 'Internal Server Error' - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - return Response() def put(self, request, wiki_id): @@ -224,102 +207,3 @@ def post(self, request, wiki_id): "Unable to rename wiki") return Response(wiki.to_dict()) - - -class WikiConfigView(APIView): - authentication_classes = (TokenAuthentication, SessionAuthentication) - permission_classes = (IsAuthenticated, ) - throttle_classes = (UserRateThrottle, ) - - def put(self, request, wiki_id): - """Edit a wiki config - """ - username = request.user.username - - try: - wiki = Wiki.objects.get(id=wiki_id) - except Wiki.DoesNotExist: - error_msg = "Wiki not found." - return api_error(status.HTTP_404_NOT_FOUND, error_msg) - - if not can_edit_wiki(wiki, request.user.username): - error_msg = 'Permission denied.' - return api_error(status.HTTP_403_FORBIDDEN, error_msg) - - repo_id = wiki.repo_id - obj_id = json.dumps({'parent_dir': WIKI_CONFIG_PATH}) - - dir_id = seafile_api.get_dir_id_by_path(repo_id, WIKI_CONFIG_PATH) - if not dir_id: - seafile_api.mkdir_with_parents(repo_id, '/', WIKI_CONFIG_PATH, username) - - token = seafile_api.get_fileserver_access_token( - repo_id, obj_id, 'upload-link', username, use_onetime=False) - if not token: - return None - upload_link = gen_file_upload_url(token, 'upload-api') - upload_link = upload_link + '?replace=1' - - wiki_config = request.data.get('wiki_config', '{}') - - files = { - 'file': (WIKI_CONFIG_FILE_NAME, wiki_config) - } - data = {'parent_dir': WIKI_CONFIG_PATH, 'relative_path': '', 'replace': 1} - resp = requests.post(upload_link, files=files, data=data) - if not resp.ok: - logger.error(resp.text) - error_msg = 'Internal Server Error' - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - - wiki = wiki.to_dict() - wiki['wiki_config'] = wiki_config - return Response({'wiki': wiki}) - - def get(self, request, wiki_id): - - try: - wiki = Wiki.objects.get(id=wiki_id) - except Wiki.DoesNotExist: - error_msg = "Wiki not found." - return api_error(status.HTTP_404_NOT_FOUND, error_msg) - - if not can_edit_wiki(wiki, request.user.username): - error_msg = 'Permission denied.' - return api_error(status.HTTP_403_FORBIDDEN, error_msg) - - path = posixpath.join(WIKI_CONFIG_PATH, WIKI_CONFIG_FILE_NAME) - try: - repo = seafile_api.get_repo(wiki.repo_id) - if not repo: - error_msg = "Wiki library not found." - return api_error(status.HTTP_404_NOT_FOUND, error_msg) - except SearpcError: - error_msg = _("Internal Server Error") - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - - try: - file_id = seafile_api.get_file_id_by_path(repo.repo_id, path) - except SearpcError as e: - logger.error(e) - error_msg = 'Internal Server Error' - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - - wiki = wiki.to_dict() - if not file_id: - wiki['wiki_config'] = '{}' - return Response({'wiki': wiki}) - - token = seafile_api.get_fileserver_access_token(repo.repo_id, file_id, 'download', request.user.username, use_onetime=True) - - if not token: - error_msg = 'Internal Server Error' - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - - url = gen_inner_file_get_url(token, WIKI_CONFIG_FILE_NAME) - resp = requests.get(url) - content = resp.content - - wiki['wiki_config'] = content - - return Response({'wiki': wiki}) diff --git a/seahub/settings.py b/seahub/settings.py index 7d8f05535df..0732519da56 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -245,6 +245,7 @@ 'seahub.institutions', 'seahub.invitations', 'seahub.wiki', + 'seahub.wiki2', 'seahub.group', 'seahub.notifications', 'seahub.options', diff --git a/seahub/urls.py b/seahub/urls.py index 78233aedc22..ee89b0b3e1b 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -86,7 +86,7 @@ from seahub.api2.endpoints.notifications import NotificationsView, NotificationView from seahub.api2.endpoints.repo_file_uploaded_bytes import RepoFileUploadedBytesView from seahub.api2.endpoints.user_avatar import UserAvatarView -from seahub.api2.endpoints.wikis import WikisView, WikiView, WikiConfigView +from seahub.api2.endpoints.wikis import WikisView, WikiView from seahub.api2.endpoints.drafts import DraftsView, DraftView from seahub.api2.endpoints.draft_reviewer import DraftReviewerView from seahub.api2.endpoints.repo_draft_info import RepoDraftInfo, RepoDraftCounts @@ -203,7 +203,9 @@ from seahub.ai.apis import LibrarySdocIndexes, Search, LibrarySdocIndex, TaskStatus, \ LibraryIndexState, QuestionAnsweringSearchInLibrary, FileDownloadToken -from seahub.wiki.views import edit_slug +from seahub.wiki2.views import edit_wiki + +from seahub.api2.endpoints.wiki2 import Wiki2sView, Wiki2View, Wiki2ConfigView, Wiki2PagesDirView, Wiki2PageContentView urlpatterns = [ path('accounts/', include('seahub.base.registration_urls')), @@ -516,10 +518,19 @@ re_path(r'^api/v2.1/wikis/$', WikisView.as_view(), name='api-v2.1-wikis'), re_path(r'^api/v2.1/wikis/(?P\d+)/$', WikiView.as_view(), name='api-v2.1-wiki'), re_path(r'^api/v2.1/wikis/(?P\d+)/dir/$', WikiPagesDirView.as_view(), name='api-v2.1-wiki-pages-dir'), - re_path(r'^api/v2.1/wiki-config/(?P\d+)/$', WikiConfigView.as_view(), name='api-v2.1-wiki-config'), + # re_path(r'^api/v2.1/wiki-config/(?P\d+)/$', WikiConfigView.as_view(), name='api-v2.1-wiki-config'), re_path(r'^api/v2.1/wikis/(?P\d+)/content/$', WikiPageContentView.as_view(), name='api-v2.1-wiki-pages-content'), path('view-image-via-public-wiki/', view_media_file_via_public_wiki, name='view_media_file_via_public_wiki'), + ## user::wiki + re_path(r'^api/v2.1/wikis2/$', Wiki2sView.as_view(), name='api-v2.1-wikis2'), + re_path(r'^api/v2.1/wikis2/(?P\d+)/$', Wiki2View.as_view(), name='api-v2.1-wiki2'), + re_path(r'^api/v2.1/wikis2/(?P\d+)/dir/$', Wiki2PagesDirView.as_view(), name='api-v2.1-wiki2-pages-dir'), + re_path(r'^api/v2.1/wiki2-config/(?P\d+)/$', Wiki2ConfigView.as_view(), name='api-v2.1-wiki2-config'), + re_path(r'^api/v2.1/wikis2/(?P\d+)/content/$', Wiki2PageContentView.as_view(), name='api-v2.1-wiki2-pages-content'), + # # 下面这个是不是在新的wiki 中用不到了? + # path('view-image-via-public-wiki/', view_media_file_via_public_wiki, name='view_media_file_via_public_wiki'), + ## user::drafts re_path(r'^api/v2.1/drafts/$', DraftsView.as_view(), name='api-v2.1-drafts'), re_path(r'^api/v2.1/drafts/(?P\d+)/$', DraftView.as_view(), name='api-v2.1-draft'), @@ -701,7 +712,7 @@ re_path(r'^api/v2.1/admin/invitations/$', AdminInvitations.as_view(), name='api-v2.1-admin-invitations'), re_path(r'^api/v2.1/admin/invitations/(?P[a-f0-9]{32})/$', AdminInvitation.as_view(), name='api-v2.1-admin-invitation'), - re_path(r'^edit-wiki/(?P[^/]+)/(?P.*)$', edit_slug, name='edit_slug'), + re_path(r'^edit-wiki/(?P[^/]+)/(?P.*)$', edit_wiki, name='edit_wiki'), path('avatar/', include('seahub.avatar.urls')), path('notice/', include('seahub.notifications.urls')), diff --git a/seahub/wiki/models.py b/seahub/wiki/models.py index b446ab3cc3d..2029a5183b0 100644 --- a/seahub/wiki/models.py +++ b/seahub/wiki/models.py @@ -23,7 +23,7 @@ class DuplicateWikiNameError(Exception): class WikiManager(models.Manager): def add(self, wiki_name, username, permission='private', repo_id=None, - org_id=-1, version='v1'): + org_id=-1): if not permission: permission = 'private' @@ -44,7 +44,7 @@ def add(self, wiki_name, username, permission='private', repo_id=None, wiki = self.model(username=username, name=wiki_name, slug=slug, repo_id=repo.id, permission=permission, - created_at=now, version=version) + created_at=now) wiki.save(using=self._db) return wiki @@ -64,7 +64,6 @@ class Wiki(models.Model): repo_id = models.CharField(max_length=36, db_index=True) permission = models.CharField(max_length=50) # private, public created_at = models.DateTimeField(default=timezone.now, db_index=True) - version = models.CharField(max_length=10, default='v1') objects = WikiManager() class Meta: @@ -110,7 +109,6 @@ def to_dict(self): 'created_at': datetime_to_isoformat_timestr(self.created_at), 'updated_at': timestamp_to_isoformat_timestr(self.updated_at), 'repo_id': self.repo_id, - 'version': self.version, } diff --git a/seahub/wiki/utils.py b/seahub/wiki/utils.py index f3ca42788c0..250256e18df 100644 --- a/seahub/wiki/utils.py +++ b/seahub/wiki/utils.py @@ -84,8 +84,3 @@ def get_wiki_dirs_by_path(repo_id, path, all_dirs): all_dirs.append(entry) return all_dirs - - -def can_edit_wiki(wiki, username): - permission = seafile_api.check_permission_by_path(wiki.repo_id, '/', username) - return permission == PERMISSION_READ_WRITE diff --git a/seahub/wiki/views.py b/seahub/wiki/views.py index c849382279d..1480f603715 100644 --- a/seahub/wiki/views.py +++ b/seahub/wiki/views.py @@ -27,8 +27,6 @@ from seahub.views.file import send_file_access_msg from seahub.utils.file_types import IMAGE, MARKDOWN, SEADOC from seahub.seadoc.utils import get_seadoc_file_uuid -from seahub.auth.decorators import login_required -from seahub.wiki.utils import can_edit_wiki # Get an instance of a logger logger = logging.getLogger(__name__) @@ -264,100 +262,6 @@ def slug(request, slug, file_path="home.md"): }) -@login_required -def edit_slug(request, wiki_id, file_path): - """ edit wiki page. for wiki2 - """ - # get wiki object or 404 - wiki = get_object_or_404(Wiki, id=wiki_id) - file_path = "/" + file_path - - # # perm check - req_user = request.user.username - if not can_edit_wiki(wiki, req_user): - return render_permission_error(request, 'Permission denied.') - - is_dir = None - file_id = seafile_api.get_file_id_by_path(wiki.repo_id, file_path) - if file_id: - is_dir = False - - dir_id = seafile_api.get_dir_id_by_path(wiki.repo_id, file_path) - if dir_id: - is_dir = True - - file_type, ext = get_file_type_and_ext(posixpath.basename(file_path)) - if file_type == IMAGE: - 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 - - has_index = False - dirs = seafile_api.list_dir_by_path(wiki.repo_id, '/') - for dir_obj in dirs: - if dir_obj.obj_name == 'index.md': - has_index = True - break - - try: - fs = FileShare.objects.filter(repo_id=wiki.repo_id, path='/').first() - except FileShare.DoesNotExist: - fs = FileShare.objects.create_dir_link(wiki.username, wiki.repo_id, '/', - permission='view_download') - wiki.permission = 'public' - wiki.save() - is_public_wiki = True - - repo = seafile_api.get_repo(wiki.repo_id) - - file_content = '' - h1_head_content = '' - outlines = [] - latest_contributor = '' - last_modified = 0 - assets_url = '' - - if is_dir is False and file_type == SEADOC: - file_uuid = get_seadoc_file_uuid(repo, file_path) - assets_url = '/api/v2.1/seadoc/download-image/' + file_uuid - - last_modified = datetime.fromtimestamp(last_modified) - - return render(request, "wiki/wiki_edit.html", { - "wiki": wiki, - "repo_name": repo.name if repo else '', - "page_name": file_path, - "shared_token": fs.token, - "shared_type": fs.s_type, - "user_can_write": user_can_write, - "file_path": file_path, - "filename": os.path.splitext(os.path.basename(file_path))[0], - "h1_head_content": h1_head_content, - "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, - "is_public_wiki": is_public_wiki, - "is_dir": is_dir, - "has_index": has_index, - "assets_url": assets_url, - }) - - ''' @login_required def edit_page(request, slug, page_name="home"): diff --git a/seahub/wiki2/__init__.py b/seahub/wiki2/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seahub/wiki2/models.py b/seahub/wiki2/models.py new file mode 100644 index 00000000000..7e8282dea01 --- /dev/null +++ b/seahub/wiki2/models.py @@ -0,0 +1,102 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +from django.db import models +from django.utils import timezone +from seaserv import seafile_api + +from seahub.base.fields import LowerCaseCharField +from seahub.base.templatetags.seahub_tags import email2nickname +from seahub.utils.timeutils import (timestamp_to_isoformat_timestr, + datetime_to_isoformat_timestr) + +class WikiDoesNotExist(Exception): + pass + + +class WikiManager(models.Manager): + def add(self, wiki_name, username, permission='private', repo_id=None, + org_id=-1): + if not permission: + permission = 'private' + + now = timezone.now() + if repo_id is None: # create new repo to store the wiki pages + if org_id and org_id > 0: + repo_id = seafile_api.create_org_repo(wiki_name, '', username, org_id) + else: + repo_id = seafile_api.create_repo(wiki_name, '', username) + + repo = seafile_api.get_repo(repo_id) + assert repo is not None + + wiki = self.model(username=username, name=wiki_name, + repo_id=repo.id, permission=permission, + created_at=now) + wiki.save(using=self._db) + return wiki + + +class Wiki2(models.Model): + """New wiki model to enable a user has multiple wikis and replace + personal wiki. + """ + PERM_CHOICES = ( + ('private', 'private'), + ('public', 'public'), + ) + + username = LowerCaseCharField(max_length=255) + name = models.CharField(max_length=255) + repo_id = models.CharField(max_length=36, db_index=True) + permission = models.CharField(max_length=50) # private, public + created_at = models.DateTimeField(default=timezone.now, db_index=True) + objects = WikiManager() + + class Meta: + db_table = 'wiki_wiki2' + unique_together = (('username', 'repo_id'),) + ordering = ["name"] + + @property + def updated_at(self): + assert len(self.repo_id) == 36 + + repo = seafile_api.get_repo(self.repo_id) + if not repo: + return '' + + return repo.last_modify + + def has_read_perm(self, request): + from seahub.views import check_folder_permission + if self.permission == 'public': + return True + else: # private + if not request.user.is_authenticated: + return False + repo_perm = check_folder_permission(request, self.repo_id, '/') + if not repo_perm: + return False + return True + + def to_dict(self): + return { + 'id': self.pk, + 'owner': self.username, + 'owner_nickname': email2nickname(self.username), + 'name': self.name, + 'permission': self.permission, + 'created_at': datetime_to_isoformat_timestr(self.created_at), + 'updated_at': timestamp_to_isoformat_timestr(self.updated_at), + 'repo_id': self.repo_id, + } + + +###### signal handlers +from django.dispatch import receiver +from seahub.signals import repo_deleted + +@receiver(repo_deleted) +def remove_wiki(sender, **kwargs): + repo_id = kwargs['repo_id'] + + Wiki2.objects.filter(repo_id=repo_id).delete() diff --git a/seahub/wiki2/utils.py b/seahub/wiki2/utils.py new file mode 100644 index 00000000000..e607c41da4a --- /dev/null +++ b/seahub/wiki2/utils.py @@ -0,0 +1,43 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +# -*- coding: utf-8 -*- +import re +import stat +import logging + +from seaserv import seafile_api +from seahub.constants import PERMISSION_READ_WRITE + +logger = logging.getLogger(__name__) + + +def is_valid_wiki_name(name): + name = name.strip() + if len(name) > 255 or len(name) < 1: + return False + return True if re.match('^[\w\s-]+$', name, re.U) else False + + +def get_wiki_dirs_by_path(repo_id, path, all_dirs): + dirs = seafile_api.list_dir_by_path(repo_id, path) + + for dirent in dirs: + entry = {} + if stat.S_ISDIR(dirent.mode): + entry["type"] = 'dir' + else: + entry["type"] = 'file' + + entry["parent_dir"] = path + entry["id"] = dirent.obj_id + entry["name"] = dirent.obj_name + entry["size"] = dirent.size + entry["mtime"] = dirent.mtime + + all_dirs.append(entry) + + return all_dirs + + +def can_edit_wiki(wiki, username): + permission = seafile_api.check_permission_by_path(wiki.repo_id, '/', username) + return permission == PERMISSION_READ_WRITE diff --git a/seahub/wiki2/views.py b/seahub/wiki2/views.py new file mode 100644 index 00000000000..92419ebc7da --- /dev/null +++ b/seahub/wiki2/views.py @@ -0,0 +1,122 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +import os +import re +import logging +import urllib.request +import posixpath +from datetime import datetime + +import markdown +import bleach +try: + from lxml import html +except ImportError: + html = None +from seaserv import seafile_api +from django.urls import reverse +from django.http import HttpResponseRedirect +from django.shortcuts import render, get_object_or_404, redirect + +from seahub.share.models import FileShare +from seahub.wiki2.models import Wiki2 as Wiki +from seahub.views import check_folder_permission +from seahub.utils import get_file_type_and_ext, render_permission_error +from seahub.utils.file_types import IMAGE, SEADOC +from seahub.seadoc.utils import get_seadoc_file_uuid +from seahub.auth.decorators import login_required +from seahub.wiki2.utils import can_edit_wiki + +# Get an instance of a logger +logger = logging.getLogger(__name__) + + +@login_required +def edit_wiki(request, wiki_id, file_path): + """ edit wiki page. for wiki2 + """ + # get wiki object or 404 + wiki = get_object_or_404(Wiki, id=wiki_id) + file_path = "/" + file_path + + # # perm check + req_user = request.user.username + if not can_edit_wiki(wiki, req_user): + return render_permission_error(request, 'Permission denied.') + + is_dir = None + file_id = seafile_api.get_file_id_by_path(wiki.repo_id, file_path) + if file_id: + is_dir = False + + dir_id = seafile_api.get_dir_id_by_path(wiki.repo_id, file_path) + if dir_id: + is_dir = True + + file_type, ext = get_file_type_and_ext(posixpath.basename(file_path)) + if file_type == IMAGE: + 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 + + has_index = False + dirs = seafile_api.list_dir_by_path(wiki.repo_id, '/') + for dir_obj in dirs: + if dir_obj.obj_name == 'index.md': + has_index = True + break + + try: + fs = FileShare.objects.filter(repo_id=wiki.repo_id, path='/').first() + except FileShare.DoesNotExist: + fs = FileShare.objects.create_dir_link(wiki.username, wiki.repo_id, '/', + permission='view_download') + wiki.permission = 'public' + wiki.save() + is_public_wiki = True + + repo = seafile_api.get_repo(wiki.repo_id) + + file_content = '' + outlines = [] + latest_contributor = '' + last_modified = 0 + assets_url = '' + + if is_dir is False and file_type == SEADOC: + file_uuid = get_seadoc_file_uuid(repo, file_path) + assets_url = '/api/v2.1/seadoc/download-image/' + file_uuid + + last_modified = datetime.fromtimestamp(last_modified) + + return render(request, "wiki/wiki_edit.html", { + "wiki": wiki, + "repo_name": repo.name if repo else '', + "page_name": file_path, + "shared_token": fs.token, + "shared_type": fs.s_type, + "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, + "is_public_wiki": is_public_wiki, + "is_dir": is_dir, + "has_index": has_index, + "assets_url": assets_url, + })