1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-17 06:27:28 +00:00

update wiki mode

This commit is contained in:
ilearnit 2018-12-12 02:34:58 +00:00
parent ce5e3a0cc7
commit 1fc0882b10
8 changed files with 141 additions and 16 deletions

View File

@ -13,7 +13,18 @@ const viewerPropTypes = {
activeTitleIndex: PropTypes.number activeTitleIndex: PropTypes.number
}; };
const contentClass = 'markdown-content';
class MarkdownContentViewer extends React.Component { 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() { render() {
if (this.props.isFileLoading) { if (this.props.isFileLoading) {

View File

@ -2,6 +2,7 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import { gettext, siteRoot } from '../../utils/constants'; import { gettext, siteRoot } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import ModalPortal from '../modal-portal'; import ModalPortal from '../modal-portal';
import CreateFolder from '../../components/dialog/create-folder-dialog'; import CreateFolder from '../../components/dialog/create-folder-dialog';
import CreateFile from '../../components/dialog/create-file-dialog'; import CreateFile from '../../components/dialog/create-file-dialog';
@ -16,6 +17,11 @@ const propTypes = {
onAddFolder: PropTypes.func.isRequired, onAddFolder: PropTypes.func.isRequired,
onUploadFile: PropTypes.func.isRequired, onUploadFile: PropTypes.func.isRequired,
onUploadFolder: 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 { class DirOperationToolbar extends React.Component {
@ -63,6 +69,14 @@ class DirOperationToolbar extends React.Component {
window.location.href= siteRoot + 'lib/' + repoID + '/file' + path + '?mode=edit'; 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) => { onUploadClick = (e) => {
this.toggleOperationMenu(e); this.toggleOperationMenu(e);
this.setState({ this.setState({
@ -115,14 +129,43 @@ class DirOperationToolbar extends React.Component {
this.props.onAddFolder(dirPath); this.props.onAddFolder(dirPath);
} }
onViewReview = () => {
this.props.goReviewPage();
}
onViewDraft = () => {
this.props.goDraftPage();
}
render() { 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 ( return (
<Fragment> <Fragment>
<div className="operation"> <div className="operation">
{(this.props.isViewFile && this.props.permission === 'rw') && ( {(this.props.isViewFile && this.props.permission === 'rw') && (
<Fragment>
<button className="btn btn-secondary operation-item" title={gettext('Edit File')} onClick={this.onEditClick}>{gettext('Edit')}</button> <button className="btn btn-secondary operation-item" title={gettext('Edit File')} onClick={this.onEditClick}>{gettext('Edit')}</button>
<button className="btn btn-secondary operation-item" title={gettext('Share')} onClick={this.onShareClick}>{gettext('Share')}</button>
</Fragment>
)} )}
{(this.props.isViewFile && this.props.permission !== 'None' && !this.props.isDraft && !this.props.hasDraft) && (
<button className="btn btn-secondary operation-item" title={gettext('New Draft')} onClick={this.onNewDraft}>{gettext('New Draft')}</button>
)}
{(this.props.reviewStatus === 'open') &&
<button className="btn btn-secondary operation-item" title={gettext('View Review')} onClick={this.onViewReview}>{gettext('View Review')}</button>
}
{(!this.props.isDraft && this.props.hasDraft) &&
<button className="btn btn-secondary operation-item" title={gettext('View Draft')} onClick={this.onViewDraft}>{gettext('View Draft')}</button>
}
{!this.props.isViewFile && ( {!this.props.isViewFile && (
<Fragment> <Fragment>
{Utils.isSupportUploadFolder() ? {Utils.isSupportUploadFolder() ?
@ -170,8 +213,8 @@ class DirOperationToolbar extends React.Component {
{this.state.isShareDialogShow && {this.state.isShareDialogShow &&
<ModalPortal> <ModalPortal>
<ShareDialog <ShareDialog
isDir={true} isDir={!isFile}
itemName={dirName} itemName={itemName}
itemPath={this.props.path} itemPath={this.props.path}
repoID={this.props.repoID} repoID={this.props.repoID}
toggleDialog={this.onShareClick} toggleDialog={this.onShareClick}

View File

@ -177,3 +177,7 @@ img[src=""] {
.wiki-md-viewer-rendered-content.article h1 { .wiki-md-viewer-rendered-content.article h1 {
margin-top: 0; margin-top: 0;
} }
.markdown-content a {
cursor: pointer;
}

View File

@ -117,22 +117,22 @@ class EditorUtilities {
if (fileNode.isImage()) { if (fileNode.isImage()) {
url = serviceUrl + '/lib/' + repoID + '/file' + encodeURIComponent(fileNode.path()) + '?raw=1'; url = serviceUrl + '/lib/' + repoID + '/file' + encodeURIComponent(fileNode.path()) + '?raw=1';
} else { } else {
url = serviceUrl + '/lib/' + repoID + '/file' + encodeURIComponent(fileNode.path()); url = serviceUrl + '/wiki/lib/' + repoID + '/file' + encodeURIComponent(fileNode.path());
} }
} else { } else {
url = serviceUrl + '/#common/lib/' + repoID + encodeURIComponent(fileNode.path()); url = serviceUrl + '/wiki/lib/' + repoID + encodeURIComponent(fileNode.path());
} }
return url; return url;
} }
isInternalFileLink(url) { isInternalFileLink(url) {
var re = new RegExp(this.serviceUrl + '/lib/[0-9a-f-]{36}/file.*'); var re = new RegExp(this.serviceUrl + '/wiki/lib/[0-9a-f-]{36}/file.*');
return re.test(url); return re.test(url);
} }
isInternalDirLink(url) { isInternalDirLink(url) {
var re = new RegExp(serviceUrl + '/#[a-z\-]*?/lib/' + '[0-9a-f\-]{36}.*'); var re = new RegExp(serviceUrl + '/wiki/lib/' + '[0-9a-f\-]{36}.*');
return re.test(url); return re.test(url);
} }

View File

@ -48,6 +48,12 @@ const propTypes = {
onItemsMove: PropTypes.func.isRequired, onItemsMove: PropTypes.func.isRequired,
onItemsCopy: PropTypes.func.isRequired, onItemsCopy: PropTypes.func.isRequired,
onItemsDelete: PropTypes.func.isRequired, onItemsDelete: PropTypes.func.isRequired,
onLinkClick: PropTypes.func.isRequired,
isDraft: PropTypes.bool,
hasDraft: PropTypes.bool,
reviewStatus: PropTypes.any,
goReviewPage: PropTypes.func,
goDraftPage: PropTypes.func,
}; };
class MainPanel extends Component { class MainPanel extends Component {
@ -185,6 +191,11 @@ class MainPanel extends Component {
<DirOperationToolBar <DirOperationToolBar
path={this.props.path} path={this.props.path}
repoID={repoID} repoID={repoID}
isDraft={this.props.isDraft}
hasDraft={this.props.hasDraft}
reviewStatus={this.props.reviewStatus}
goDraftPage={this.props.goDraftPage}
goReviewPage={this.props.goReviewPage}
permission={this.props.permission} permission={this.props.permission}
isViewFile={this.props.isViewFile} isViewFile={this.props.isViewFile}
onAddFile={this.props.onAddFile} onAddFile={this.props.onAddFile}
@ -224,6 +235,7 @@ class MainPanel extends Component {
isFileLoading={this.props.isFileLoading} isFileLoading={this.props.isFileLoading}
activeTitleIndex={this.state.activeTitleIndex} activeTitleIndex={this.state.activeTitleIndex}
onContentRendered={this.onContentRendered} onContentRendered={this.onContentRendered}
onLinkClick={this.props.onLinkClick}
/> : /> :
<Fragment> <Fragment>
<DirentListView <DirentListView

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import moment from 'moment'; import moment from 'moment';
import { gettext, repoID, siteRoot, initialPath, isDir } from './utils/constants'; import { gettext, repoID, siteRoot, initialPath, isDir, serviceUrl } from './utils/constants';
import { seafileAPI } from './utils/seafile-api'; import { seafileAPI } from './utils/seafile-api';
import { Utils } from './utils/utils'; import { Utils } from './utils/utils';
import SidePanel from './pages/repo-wiki-mode/side-panel'; import SidePanel from './pages/repo-wiki-mode/side-panel';
@ -42,7 +42,12 @@ class Wiki extends Component {
isDirentSelected: false, isDirentSelected: false,
isAllDirentSelected: false, isAllDirentSelected: false,
selectedDirentList: [], selectedDirentList: [],
libNeedDecrypt: false libNeedDecrypt: false,
isDraft: false,
hasDraft: false,
reviewStatus: '',
reviewID: '',
draftFilePath: '',
}; };
window.onpopstate = this.onpopstate; window.onpopstate = this.onpopstate;
this.hash = ''; this.hash = '';
@ -226,7 +231,8 @@ class Wiki extends Component {
this.setState({isFileLoading: true}); this.setState({isFileLoading: true});
seafileAPI.getFileInfo(repoID, filePath).then((res) => { seafileAPI.getFileInfo(repoID, filePath).then((res) => {
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.getFileDownloadLink(repoID, filePath).then((res) => {
seafileAPI.getFileContent(res.data).then((res) => { seafileAPI.getFileContent(res.data).then((res) => {
this.setState({ this.setState({
@ -235,6 +241,11 @@ class Wiki extends Component {
latestContributor: last_modifier_name, latestContributor: last_modifier_name,
lastModified: moment.unix(mtime).fromNow(), lastModified: moment.unix(mtime).fromNow(),
isFileLoading: false, 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) => { updateDirent = (dirent, paramKey, paramValue) => {
let newDirentList = this.state.direntList.map(item => { let newDirentList = this.state.direntList.map(item => {
if (item.name === dirent.name) { if (item.name === dirent.name) {
@ -705,24 +728,24 @@ class Wiki extends Component {
} }
isInternalMarkdownLink(url) { 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); return re.test(url);
} }
isInternalDirLink(url) { isInternalDirLink(url) {
var re = new RegExp(siteRoot + '#[a-z\-]*?/lib/' + repoID + '/.*'); var re = new RegExp(serviceUrl + '/wiki/lib/' + repoID + '/.*');
return re.test(url); return re.test(url);
} }
getPathFromInternalMarkdownLink(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 array = re.exec(url);
var path = decodeURIComponent(array[1]); var path = decodeURIComponent(array[1]);
return path; return path;
} }
getPathFromInternalDirLink(url) { 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 array = re.exec(url);
var path = decodeURIComponent(array[1]); var path = decodeURIComponent(array[1]);
@ -776,6 +799,14 @@ class Wiki extends Component {
this.loadSidePanel(initialPath); 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() { render() {
let { libNeedDecrypt } = this.state; let { libNeedDecrypt } = this.state;
if (libNeedDecrypt) { if (libNeedDecrypt) {
@ -823,6 +854,7 @@ class Wiki extends Component {
onItemSelected={this.onDirentSelected} onItemSelected={this.onDirentSelected}
onItemDelete={this.onMainPanelItemDelete} onItemDelete={this.onMainPanelItemDelete}
onItemRename={this.onMainPanelItemRename} onItemRename={this.onMainPanelItemRename}
onLinkClick={this.onLinkClick}
onItemMove={this.onMoveItem} onItemMove={this.onMoveItem}
onItemCopy={this.onCopyItem} onItemCopy={this.onCopyItem}
onAddFile={this.onAddFile} onAddFile={this.onAddFile}
@ -835,6 +867,11 @@ class Wiki extends Component {
onItemsCopy={this.onCopyItems} onItemsCopy={this.onCopyItems}
onItemsDelete={this.onDeleteItems} onItemsDelete={this.onDeleteItems}
hash={this.hash} hash={this.hash}
isDraft={this.state.isDraft}
hasDraft={this.state.hasDraft}
reviewStatus={this.state.reviewStatus}
goDraftPage={this.goDraftPage}
goReviewPage={this.goReviewPage}
/> />
</div> </div>
); );

View File

@ -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 initialPath = window.wiki ? window.wiki.config.initial_path : '';
export const permission = window.wiki ? window.wiki.config.permission === 'True' : ''; export const permission = window.wiki ? window.wiki.config.permission === 'True' : '';
export const isDir = window.wiki ? window.wiki.config.isDir : ''; export const isDir = window.wiki ? window.wiki.config.isDir : '';
export const serviceUrl = window.wiki ? window.wiki.config.serviceUrl : '';
// file history // file history
export const PER_PAGE = 25; export const PER_PAGE = 25;

View File

@ -58,6 +58,7 @@ from seahub.notifications.models import UserNotification
from seahub.options.models import UserOptions from seahub.options.models import UserOptions
from seahub.profile.models import Profile, DetailedProfile from seahub.profile.models import Profile, DetailedProfile
from seahub.drafts.models import Draft 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.signals import (repo_created, repo_deleted)
from seahub.share.models import FileShare, OrgFileShare, UploadLinkShare from seahub.share.models import FileShare, OrgFileShare, UploadLinkShare
from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \ 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 parse_repo_perm
from seahub.utils.star import star_file, unstar_file, get_dir_starred_files 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_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_size import get_file_size_unit
from seahub.utils.file_op import check_file_lock from seahub.utils.file_op import check_file_lock
from seahub.utils.timeutils import utc_to_local, \ from seahub.utils.timeutils import utc_to_local, \
@ -3028,12 +3029,28 @@ class FileDetailView(APIView):
real_path = path real_path = path
real_repo_id = repo_id real_repo_id = repo_id
file_name = os.path.basename(path)
entry = {} entry = {}
entry["type"] = "file" entry["type"] = "file"
entry["id"] = obj_id entry["id"] = obj_id
entry["name"] = os.path.basename(path) entry["name"] = file_name
entry["permission"] = permission 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 # fetch file contributors and latest contributor
try: try:
# get real path for sub repo # get real path for sub repo