diff --git a/frontend/src/components/markdown-viewer.js b/frontend/src/components/markdown-viewer.js index a4aa67df5d..3345f60f02 100644 --- a/frontend/src/components/markdown-viewer.js +++ b/frontend/src/components/markdown-viewer.js @@ -13,7 +13,18 @@ const viewerPropTypes = { activeTitleIndex: PropTypes.number }; +const contentClass = 'markdown-content'; + class MarkdownContentViewer extends React.Component { + componentDidUpdate () { + var links = document.querySelectorAll(`.${contentClass} a`); + links.forEach((li) => {li.addEventListener('click', this.onLinkClick); }); + } + + onLinkClick = (event) => { + event.preventDefault(); + this.props.onLinkClick(event); + } render() { if (this.props.isFileLoading) { diff --git a/frontend/src/components/toolbar/dir-operation-toolbar.js b/frontend/src/components/toolbar/dir-operation-toolbar.js index a033ec6222..fc98944713 100644 --- a/frontend/src/components/toolbar/dir-operation-toolbar.js +++ b/frontend/src/components/toolbar/dir-operation-toolbar.js @@ -2,6 +2,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { Utils } from '../../utils/utils'; import { gettext, siteRoot } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; import ModalPortal from '../modal-portal'; import CreateFolder from '../../components/dialog/create-folder-dialog'; import CreateFile from '../../components/dialog/create-file-dialog'; @@ -16,6 +17,11 @@ const propTypes = { onAddFolder: PropTypes.func.isRequired, onUploadFile: PropTypes.func.isRequired, onUploadFolder: PropTypes.func.isRequired, + isDraft: PropTypes.bool, + hasDraft: PropTypes.bool, + reviewStatus: PropTypes.any, + goDraftPage: PropTypes.func, + goReviewPage: PropTypes.func, }; class DirOperationToolbar extends React.Component { @@ -63,6 +69,14 @@ class DirOperationToolbar extends React.Component { window.location.href= siteRoot + 'lib/' + repoID + '/file' + path + '?mode=edit'; } + onNewDraft = (e) => { + e.preventDefault(); + let { path, repoID } = this.props; + seafileAPI.createDraft(repoID, path).then(res => { + window.location.href = siteRoot + 'lib/' + res.data.origin_repo_id + '/file' + res.data.draft_file_path + '?mode=edit'; + }); + } + onUploadClick = (e) => { this.toggleOperationMenu(e); this.setState({ @@ -115,14 +129,43 @@ class DirOperationToolbar extends React.Component { this.props.onAddFolder(dirPath); } + onViewReview = () => { + this.props.goReviewPage(); + } + + onViewDraft = () => { + this.props.goDraftPage(); + } + render() { - let dirName = this.props.path.replace('\/',''); + let isFile = this.props.isViewFile; + let itemName; + if (this.props.isViewFile) { + itemName = Utils.getFileName(this.props.path) + } else { + itemName = this.props.path.replace('\/',''); + } + return (
{(this.props.isViewFile && this.props.permission === 'rw') && ( + + + )} + + {(this.props.isViewFile && this.props.permission !== 'None' && !this.props.isDraft && !this.props.hasDraft) && ( + + )} + {(this.props.reviewStatus === 'open') && + + } + {(!this.props.isDraft && this.props.hasDraft) && + + } + {!this.props.isViewFile && ( {Utils.isSupportUploadFolder() ? @@ -170,8 +213,8 @@ class DirOperationToolbar extends React.Component { {this.state.isShareDialogShow && : { - let { mtime, permission, last_modifier_name } = res.data; + let { mtime, permission, last_modifier_name, is_draft, has_draft, + review_status, review_id, draft_file_path } = res.data; seafileAPI.getFileDownloadLink(repoID, filePath).then((res) => { seafileAPI.getFileContent(res.data).then((res) => { this.setState({ @@ -235,6 +241,11 @@ class Wiki extends Component { latestContributor: last_modifier_name, lastModified: moment.unix(mtime).fromNow(), isFileLoading: false, + isDraft: is_draft, + hasDraft: has_draft, + reviewStatus: review_status, + reviewID: review_id, + draftFilePath: draft_file_path }); }); }); @@ -271,6 +282,18 @@ class Wiki extends Component { }); } + + onLinkClick = (event) => { + const url = event.path[2].href; + if (this.isInternalMarkdownLink(url)) { + let path = this.getPathFromInternalMarkdownLink(url); + this.showFile(path); + } else if (this.isInternalDirLink(url)) { + let path = this.getPathFromInternalDirLink(url); + this.showDir(path); + } + } + updateDirent = (dirent, paramKey, paramValue) => { let newDirentList = this.state.direntList.map(item => { if (item.name === dirent.name) { @@ -705,24 +728,24 @@ class Wiki extends Component { } isInternalMarkdownLink(url) { - var re = new RegExp(siteRoot + 'lib/' + repoID + '/file' + '.*\.md$'); + var re = new RegExp(serviceUrl + '/wiki/lib/' + repoID + '/file' + '.*\.md$'); return re.test(url); } isInternalDirLink(url) { - var re = new RegExp(siteRoot + '#[a-z\-]*?/lib/' + repoID + '/.*'); + var re = new RegExp(serviceUrl + '/wiki/lib/' + repoID + '/.*'); return re.test(url); } getPathFromInternalMarkdownLink(url) { - var re = new RegExp(siteRoot + 'lib/' + repoID + '/file' + '(.*\.md)'); + var re = new RegExp(serviceUrl + '/wiki/lib/' + repoID + '/file' + '(.*\.md)'); var array = re.exec(url); var path = decodeURIComponent(array[1]); return path; } getPathFromInternalDirLink(url) { - var re = new RegExp(siteRoot + '#[a-z\-]*?/lib/' + repoID + '(/.*)'); + var re = new RegExp(serviceUrl + '/wiki/lib/' + repoID + '(/.*)'); var array = re.exec(url); var path = decodeURIComponent(array[1]); @@ -776,6 +799,14 @@ class Wiki extends Component { this.loadSidePanel(initialPath); } + goReviewPage = () => { + window.location.href = siteRoot + 'drafts/review/' + this.state.reviewID; + } + + goDraftPage = () => { + window.location.href = siteRoot + 'lib/' + repoID + '/file' + this.state.draftFilePath + '?mode=edit'; + } + render() { let { libNeedDecrypt } = this.state; if (libNeedDecrypt) { @@ -823,6 +854,7 @@ class Wiki extends Component { onItemSelected={this.onDirentSelected} onItemDelete={this.onMainPanelItemDelete} onItemRename={this.onMainPanelItemRename} + onLinkClick={this.onLinkClick} onItemMove={this.onMoveItem} onItemCopy={this.onCopyItem} onAddFile={this.onAddFile} @@ -835,6 +867,11 @@ class Wiki extends Component { onItemsCopy={this.onCopyItems} onItemsDelete={this.onDeleteItems} hash={this.hash} + isDraft={this.state.isDraft} + hasDraft={this.state.hasDraft} + reviewStatus={this.state.reviewStatus} + goDraftPage={this.goDraftPage} + goReviewPage={this.goReviewPage} />
); diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 8a9d502fc5..1a89bb86c5 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -36,6 +36,7 @@ export const repoID = window.wiki ? window.wiki.config.repoId : ''; 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 : ''; // file history export const PER_PAGE = 25; diff --git a/seahub/api2/views.py b/seahub/api2/views.py index c851d1f2cf..5bc4c8d5c8 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -58,6 +58,7 @@ from seahub.notifications.models import UserNotification from seahub.options.models import UserOptions from seahub.profile.models import Profile, DetailedProfile from seahub.drafts.models import Draft +from seahub.drafts.utils import is_draft_file, has_draft_file from seahub.signals import (repo_created, repo_deleted) from seahub.share.models import FileShare, OrgFileShare, UploadLinkShare from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \ @@ -78,7 +79,7 @@ from seahub.utils.repo import get_repo_owner, get_library_storages, \ parse_repo_perm from seahub.utils.star import star_file, unstar_file, get_dir_starred_files from seahub.utils.file_tags import get_files_tags_in_dir -from seahub.utils.file_types import DOCUMENT +from seahub.utils.file_types import DOCUMENT, MARKDOWN from seahub.utils.file_size import get_file_size_unit from seahub.utils.file_op import check_file_lock from seahub.utils.timeutils import utc_to_local, \ @@ -3028,12 +3029,28 @@ class FileDetailView(APIView): real_path = path real_repo_id = repo_id + file_name = os.path.basename(path) entry = {} entry["type"] = "file" entry["id"] = obj_id - entry["name"] = os.path.basename(path) + entry["name"] = file_name entry["permission"] = permission + file_type, file_ext = get_file_type_and_ext(file_name) + if file_type == MARKDOWN: + is_draft, review_id, draft_id, review_status = is_draft_file(repo_id, path) + + has_draft = False + draft_file_path = '' + if not is_draft: + has_draft, draft_file_path, draft_id, review_id, review_status = has_draft_file(repo_id, path) + + entry['review_id'] = review_id + entry['review_status'] = review_status + entry['is_draft'] = is_draft + entry['has_draft'] = has_draft + entry['draft_file_path'] = draft_file_path + # fetch file contributors and latest contributor try: # get real path for sub repo