mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-30 21:50:59 +00:00
Wiki publish (#6512)
* add wiki publish publish wiki permission optimize code * optimize code * optimize code * update * Update wiki2.py * Update wiki2.py * Update urls.py * Update wiki_publish.html --------- Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com> Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
This commit is contained in:
parent
de576a7237
commit
ac37e9dc31
126
frontend/src/components/dialog/publish-wiki-dialog.js
Normal file
126
frontend/src/components/dialog/publish-wiki-dialog.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { gettext, serviceURL } from '../../utils/constants';
|
||||||
|
import { Button, Modal, ModalHeader, Input, ModalBody, ModalFooter, Alert, InputGroup, InputGroupAddon } from 'reactstrap';
|
||||||
|
import toaster from '../toast';
|
||||||
|
import wikiAPI from '../../utils/wiki-api';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
wiki: PropTypes.object,
|
||||||
|
onPublish: PropTypes.func.isRequired,
|
||||||
|
toggleCancel: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class PublishWikiDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
url: serviceURL + '/wiki/publish/' + this.props.customUrl,
|
||||||
|
errMessage: '',
|
||||||
|
isSubmitBtnActive: false,
|
||||||
|
};
|
||||||
|
this.newInput = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
this.setState({
|
||||||
|
isSubmitBtnActive: !!e.target.value.trim(),
|
||||||
|
url: e.target.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
let { isValid, errMessage } = this.validateInput();
|
||||||
|
if (!isValid) {
|
||||||
|
this.setState({
|
||||||
|
errMessage: errMessage,
|
||||||
|
url: serviceURL + '/wiki/publish/',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.props.onPublish(this.state.url.trim());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteCustomUrl = () => {
|
||||||
|
let wiki_id = this.props.wiki.id;
|
||||||
|
wikiAPI.deletePublishWikiLink(wiki_id).then((res) => {
|
||||||
|
this.setState({ url: serviceURL + '/wiki/publish/' });
|
||||||
|
toaster.success(gettext('Successfully.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
let errorMsg = error.response.data.error_msg;
|
||||||
|
toaster.danger(errorMsg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.handleSubmit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
validateInput = () => {
|
||||||
|
let url = this.state.url.trim();
|
||||||
|
let isValid = true;
|
||||||
|
let errMessage = '';
|
||||||
|
if (!url) {
|
||||||
|
isValid = false;
|
||||||
|
errMessage = gettext('url is required.');
|
||||||
|
return { isValid, errMessage };
|
||||||
|
}
|
||||||
|
if (!(url.includes(serviceURL + '/wiki/publish/'))) {
|
||||||
|
isValid = false;
|
||||||
|
errMessage = gettext('url need include specific prefix.');
|
||||||
|
return { isValid, errMessage };
|
||||||
|
}
|
||||||
|
return { isValid, errMessage };
|
||||||
|
};
|
||||||
|
|
||||||
|
copyLink = () => {
|
||||||
|
copy(this.state.url);
|
||||||
|
toaster.success(gettext('URL is copied to the clipboard'));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Publish Wiki')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<p>{gettext('Customize URL')}</p>
|
||||||
|
<InputGroup>
|
||||||
|
<Input
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
innerRef={this.newInput}
|
||||||
|
placeholder="customize url"
|
||||||
|
value={this.state.url}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<InputGroupAddon addonType="append">
|
||||||
|
<Button color="primary" onClick={this.copyLink} className="border-0">{gettext('Copy')}</Button>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
<span className='tip mb-1' style={{ fontSize: '14px' }}>
|
||||||
|
{gettext('The custom part of the URL must be between 5 and 30 characters long and may only contain letters (a-z), numbers, and hyphens.')}
|
||||||
|
</span>
|
||||||
|
{this.state.errMessage && <Alert color="danger" className="mt-2">{this.state.errMessage}</Alert>}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.deleteCustomUrl} disabled={this.props.customUrl === ''}>{gettext('Delete')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PublishWikiDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default PublishWikiDialog;
|
@ -7,6 +7,9 @@ import ModalPortal from '../modal-portal';
|
|||||||
import DeleteWikiDialog from '../dialog/delete-wiki-dialog';
|
import DeleteWikiDialog from '../dialog/delete-wiki-dialog';
|
||||||
import RenameWikiDialog from '../dialog/rename-wiki-dialog';
|
import RenameWikiDialog from '../dialog/rename-wiki-dialog';
|
||||||
import ShareWikiDialog from '../dialog/share-wiki-dialog';
|
import ShareWikiDialog from '../dialog/share-wiki-dialog';
|
||||||
|
import PublishWikiDialog from '../dialog/publish-wiki-dialog';
|
||||||
|
import wikiAPI from '../../utils/wiki-api';
|
||||||
|
import toaster from '../toast';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
wiki: PropTypes.object.isRequired,
|
wiki: PropTypes.object.isRequired,
|
||||||
@ -26,6 +29,8 @@ class WikiCardItem extends Component {
|
|||||||
isShowRenameDialog: false,
|
isShowRenameDialog: false,
|
||||||
isItemMenuShow: false,
|
isItemMenuShow: false,
|
||||||
isShowShareDialog: false,
|
isShowShareDialog: false,
|
||||||
|
isShowPublishDialog: false,
|
||||||
|
customUrl: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +53,10 @@ class WikiCardItem extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onPublishToggle = (e) => {
|
||||||
|
this.getPublishWikiLink();
|
||||||
|
};
|
||||||
|
|
||||||
onDeleteCancel = () => {
|
onDeleteCancel = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isShowDeleteDialog: !this.state.isShowDeleteDialog,
|
isShowDeleteDialog: !this.state.isShowDeleteDialog,
|
||||||
@ -77,6 +86,39 @@ class WikiCardItem extends Component {
|
|||||||
this.setState({ isShowRenameDialog: false });
|
this.setState({ isShowRenameDialog: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
publishWiki = (url) => {
|
||||||
|
const urlIndex = url.indexOf('/publish/');
|
||||||
|
const publish_url = url.substring(urlIndex + '/publish/'.length);
|
||||||
|
wikiAPI.publishWiki(this.props.wiki.id, publish_url).then((res) => {
|
||||||
|
const { publish_url } = res.data;
|
||||||
|
this.setState({ customUrl: publish_url });
|
||||||
|
toaster.success(gettext('Successfully.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
let errorMsg = error.response.data.error_msg;
|
||||||
|
toaster.danger(errorMsg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getPublishWikiLink = () => {
|
||||||
|
wikiAPI.getPublishWikiLink(this.props.wiki.id).then((res) => {
|
||||||
|
const { publish_url } = res.data;
|
||||||
|
this.setState({
|
||||||
|
customUrl: publish_url,
|
||||||
|
isShowPublishDialog: !this.state.isShowPublishDialog,
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
this.setState({
|
||||||
|
isShowPublishDialog: !this.state.isShowPublishDialog,
|
||||||
|
});
|
||||||
|
if (error.response) {
|
||||||
|
let errorMsg = error.response.data.error_msg;
|
||||||
|
toaster.danger(errorMsg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
clickWikiCard = (link) => {
|
clickWikiCard = (link) => {
|
||||||
window.open(link);
|
window.open(link);
|
||||||
};
|
};
|
||||||
@ -130,6 +172,7 @@ class WikiCardItem extends Component {
|
|||||||
let showDelete = false;
|
let showDelete = false;
|
||||||
let showLeaveShare = false;
|
let showLeaveShare = false;
|
||||||
let showDropdownMenu = false;
|
let showDropdownMenu = false;
|
||||||
|
let showPublish = false;
|
||||||
|
|
||||||
if (isDepartment) {
|
if (isDepartment) {
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
@ -137,6 +180,7 @@ class WikiCardItem extends Component {
|
|||||||
showDelete = true;
|
showDelete = true;
|
||||||
showShare = true;
|
showShare = true;
|
||||||
showRename = true;
|
showRename = true;
|
||||||
|
showPublish = true;
|
||||||
} else {
|
} else {
|
||||||
showLeaveShare = true;
|
showLeaveShare = true;
|
||||||
}
|
}
|
||||||
@ -146,6 +190,7 @@ class WikiCardItem extends Component {
|
|||||||
showShare = true;
|
showShare = true;
|
||||||
showDelete = true;
|
showDelete = true;
|
||||||
showRename = true;
|
showRename = true;
|
||||||
|
showPublish = true;
|
||||||
} else {
|
} else {
|
||||||
showLeaveShare = true;
|
showLeaveShare = true;
|
||||||
}
|
}
|
||||||
@ -180,6 +225,8 @@ class WikiCardItem extends Component {
|
|||||||
<DropdownMenu right={true} className="dtable-dropdown-menu">
|
<DropdownMenu right={true} className="dtable-dropdown-menu">
|
||||||
{showRename &&
|
{showRename &&
|
||||||
<DropdownItem onClick={this.onRenameToggle}>{gettext('Rename')}</DropdownItem>}
|
<DropdownItem onClick={this.onRenameToggle}>{gettext('Rename')}</DropdownItem>}
|
||||||
|
{showPublish &&
|
||||||
|
<DropdownItem onClick={this.onPublishToggle}>{gettext('Publish')}</DropdownItem>}
|
||||||
{showShare &&
|
{showShare &&
|
||||||
<DropdownItem onClick={this.onShareToggle}>{gettext('Share')}</DropdownItem>
|
<DropdownItem onClick={this.onShareToggle}>{gettext('Share')}</DropdownItem>
|
||||||
}
|
}
|
||||||
@ -200,7 +247,12 @@ class WikiCardItem extends Component {
|
|||||||
<div className="wiki-item-owner">
|
<div className="wiki-item-owner">
|
||||||
{isShowAvatar && (isDepartment ? this.renderDept() : this.renderAvatar())}
|
{isShowAvatar && (isDepartment ? this.renderDept() : this.renderAvatar())}
|
||||||
</div>
|
</div>
|
||||||
<div className="wiki-item-updated-time">{moment(wiki.updated_at).fromNow()}</div>
|
<div className="wiki-item-updated-time">
|
||||||
|
{moment(wiki.updated_at).fromNow()}
|
||||||
|
{wiki.is_published &&
|
||||||
|
<span style={{ marginLeft: '25%' }}>published</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.state.isShowDeleteDialog &&
|
{this.state.isShowDeleteDialog &&
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
@ -264,6 +316,16 @@ class WikiCardItem extends Component {
|
|||||||
/>
|
/>
|
||||||
</ModalPortal>
|
</ModalPortal>
|
||||||
}
|
}
|
||||||
|
{this.state.isShowPublishDialog &&
|
||||||
|
<ModalPortal>
|
||||||
|
<PublishWikiDialog
|
||||||
|
toggleCancel={this.onPublishToggle}
|
||||||
|
onPublish={this.publishWiki}
|
||||||
|
wiki={wiki}
|
||||||
|
customUrl={this.state.customUrl}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { Modal } from 'reactstrap';
|
|||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import wikiAPI from '../../utils/wiki-api';
|
import wikiAPI from '../../utils/wiki-api';
|
||||||
import SDocServerApi from '../../utils/sdoc-server-api';
|
import SDocServerApi from '../../utils/sdoc-server-api';
|
||||||
import { wikiId, siteRoot, lang, isWiki2, seadocServerUrl, gettext } from '../../utils/constants';
|
import { wikiId, siteRoot, lang, isWiki2, seadocServerUrl, gettext, wikiPermission } from '../../utils/constants';
|
||||||
import WikiConfig from './models/wiki-config';
|
import WikiConfig from './models/wiki-config';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import SidePanel from './side-panel';
|
import SidePanel from './side-panel';
|
||||||
@ -53,11 +53,21 @@ class Wiki extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePath = () => {
|
handlePath = () => {
|
||||||
|
const custom_url = window.location.pathname.substring(1);
|
||||||
|
if (custom_url.includes('wiki/publish')) {
|
||||||
|
return custom_url;
|
||||||
|
}
|
||||||
|
|
||||||
return isWiki2 ? 'wikis/' : 'published/';
|
return isWiki2 ? 'wikis/' : 'published/';
|
||||||
};
|
};
|
||||||
|
|
||||||
getWikiConfig = () => {
|
getWikiConfig = () => {
|
||||||
wikiAPI.getWiki2Config(wikiId).then(res => {
|
let wikiAPIConfig;
|
||||||
|
if (wikiPermission === 'public') {
|
||||||
|
wikiAPIConfig = wikiAPI.getWiki2PublishConfig(wikiId);
|
||||||
|
} else {
|
||||||
|
wikiAPIConfig = wikiAPI.getWiki2Config(wikiId);
|
||||||
|
}
|
||||||
|
wikiAPIConfig.then(res => {
|
||||||
const { wiki_config, repo_id, id: wikiRepoId } = res.data.wiki;
|
const { wiki_config, repo_id, id: wikiRepoId } = res.data.wiki;
|
||||||
const config = new WikiConfig(wiki_config || {});
|
const config = new WikiConfig(wiki_config || {});
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -137,7 +147,13 @@ class Wiki extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
isDataLoading: true,
|
isDataLoading: true,
|
||||||
});
|
});
|
||||||
wikiAPI.getWiki2Page(wikiId, pageId).then(res => {
|
let getWikiPage;
|
||||||
|
if (wikiPermission === 'public') {
|
||||||
|
getWikiPage = wikiAPI.getWiki2PublishPage(wikiId, pageId);
|
||||||
|
} else {
|
||||||
|
getWikiPage = wikiAPI.getWiki2Page(wikiId, pageId);
|
||||||
|
}
|
||||||
|
getWikiPage.then(res => {
|
||||||
const { permission, seadoc_access_token, assets_url } = res.data;
|
const { permission, seadoc_access_token, assets_url } = res.data;
|
||||||
this.setState({
|
this.setState({
|
||||||
permission,
|
permission,
|
||||||
@ -154,7 +170,10 @@ class Wiki extends Component {
|
|||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
params.set('page_id', pageId);
|
params.set('page_id', pageId);
|
||||||
const fileUrl = `${siteRoot}${this.handlePath()}${wikiId}/?${params.toString()}`;
|
let fileUrl = `${siteRoot}${this.handlePath()}${wikiId}/?${params.toString()}`;
|
||||||
|
if (this.handlePath().includes('wiki/publish')) {
|
||||||
|
fileUrl = `${siteRoot}${this.handlePath()}?${params.toString()}`;
|
||||||
|
}
|
||||||
window.history.pushState({ url: fileUrl, path: filePath }, filePath, fileUrl);
|
window.history.pushState({ url: fileUrl, path: filePath }, filePath, fileUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { SdocWikiEditor } from '@seafile/sdoc-editor';
|
import { SdocWikiEditor } from '@seafile/sdoc-editor';
|
||||||
import { gettext, username } from '../../utils/constants';
|
import { gettext, username, wikiPermission } from '../../utils/constants';
|
||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import Account from '../../components/common/account';
|
import Account from '../../components/common/account';
|
||||||
@ -69,7 +69,7 @@ class MainPanel extends Component {
|
|||||||
currentPageId={this.props.currentPageId}
|
currentPageId={this.props.currentPageId}
|
||||||
currentPageConfig={currentPageConfig}
|
currentPageConfig={currentPageConfig}
|
||||||
/>
|
/>
|
||||||
{username && <Account />}
|
{username && wikiPermission !== 'public' && <Account />}
|
||||||
</div>
|
</div>
|
||||||
<div className="main-panel-center">
|
<div className="main-panel-center">
|
||||||
<div className={`cur-view-content ${isViewingFile ? 'o-hidden' : ''}`}>
|
<div className={`cur-view-content ${isViewingFile ? 'o-hidden' : ''}`}>
|
||||||
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import deepCopy from 'deep-copy';
|
import deepCopy from 'deep-copy';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import { gettext, isWiki2, wikiId } from '../../utils/constants';
|
import { gettext, isWiki2, wikiId, wikiPermission } from '../../utils/constants';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
import WikiNav from './wiki-nav/index';
|
import WikiNav from './wiki-nav/index';
|
||||||
@ -160,12 +160,16 @@ class SidePanel extends Component {
|
|||||||
<div className={`wiki2-side-panel${this.props.closeSideBar ? '' : ' left-zero'}`}>
|
<div className={`wiki2-side-panel${this.props.closeSideBar ? '' : ' left-zero'}`}>
|
||||||
<div className="wiki2-side-panel-top">
|
<div className="wiki2-side-panel-top">
|
||||||
<h4 className="text-truncate ml-0 mb-0" title={repoName}>{repoName}</h4>
|
<h4 className="text-truncate ml-0 mb-0" title={repoName}>{repoName}</h4>
|
||||||
<div id='wiki-add-new-page' className='add-new-page' onClick={this.handleAddNewPage.bind(true)}>
|
{wikiPermission !== 'public' &&
|
||||||
<i className='sf3-font sf3-font-new-page'></i>
|
<div>
|
||||||
</div>
|
<div id='wiki-add-new-page' className='add-new-page' onClick={this.handleAddNewPage.bind(true)}>
|
||||||
<UncontrolledTooltip className='wiki-new-page-tooltip' target="wiki-add-new-page">
|
<i className='sf3-font sf3-font-new-page'></i>
|
||||||
{gettext('New page')}
|
</div>
|
||||||
</UncontrolledTooltip>
|
<UncontrolledTooltip className='wiki-new-page-tooltip' target="wiki-add-new-page">
|
||||||
|
{gettext('New page')}
|
||||||
|
</UncontrolledTooltip>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="wiki2-side-nav">
|
<div className="wiki2-side-nav">
|
||||||
{isLoading ? <Loading/> : this.renderWikiNav()}
|
{isLoading ? <Loading/> : this.renderWikiNav()}
|
||||||
@ -178,6 +182,9 @@ class SidePanel extends Component {
|
|||||||
getWikiConfig={this.props.getWikiConfig}
|
getWikiConfig={this.props.getWikiConfig}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{wikiPermission !== 'public' &&
|
||||||
|
<WikiExternalOperations onAddWikiPage={this.handleAddNewPage.bind(false)} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import NameEditPopover from '../../common/name-edit-popover';
|
|||||||
import NavItemIcon from '../../common/nav-item-icon';
|
import NavItemIcon from '../../common/nav-item-icon';
|
||||||
import PageDropdownMenu from './page-dropdownmenu';
|
import PageDropdownMenu from './page-dropdownmenu';
|
||||||
import DeleteDialog from '../../common/delete-dialog';
|
import DeleteDialog from '../../common/delete-dialog';
|
||||||
import { gettext } from '../../../../utils/constants';
|
import { gettext, wikiPermission } from '../../../../utils/constants';
|
||||||
import AddNewPageDialog from '../add-new-page-dialog';
|
import AddNewPageDialog from '../add-new-page-dialog';
|
||||||
import Icon from '../../../../components/icon';
|
import Icon from '../../../../components/icon';
|
||||||
import DraggedPageItem from './dragged-page-item';
|
import DraggedPageItem from './dragged-page-item';
|
||||||
@ -225,7 +225,7 @@ class PageItem extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
{isEditMode &&
|
{isEditMode && wikiPermission !== 'public' &&
|
||||||
<>
|
<>
|
||||||
<div className="more-wiki-page-operation" onClick={this.toggleDropdown}>
|
<div className="more-wiki-page-operation" onClick={this.toggleDropdown}>
|
||||||
<Icon symbol={'more-level'} />
|
<Icon symbol={'more-level'} />
|
||||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { DropTarget, DragLayer } from 'react-dnd';
|
import { DropTarget, DragLayer } from 'react-dnd';
|
||||||
import html5DragDropContext from './html5DragDropContext';
|
import html5DragDropContext from './html5DragDropContext';
|
||||||
import DraggedPageItem from './pages/dragged-page-item';
|
import DraggedPageItem from './pages/dragged-page-item';
|
||||||
import { repoID, gettext } from '../../../utils/constants';
|
import { repoID, gettext, wikiPermission } from '../../../utils/constants';
|
||||||
|
|
||||||
import '../css/wiki-nav.css';
|
import '../css/wiki-nav.css';
|
||||||
|
|
||||||
@ -108,10 +108,12 @@ class WikiNav extends Component {
|
|||||||
{navigation.map((item, index) => {
|
{navigation.map((item, index) => {
|
||||||
return this.renderPage(item, index, pages.length, isOnlyOnePage, id_page_map, layerDragProps);
|
return this.renderPage(item, index, pages.length, isOnlyOnePage, id_page_map, layerDragProps);
|
||||||
})}
|
})}
|
||||||
<div className='wiki2-trash' onClick={this.props.toggelTrashDialog}>
|
{wikiPermission !== 'public' &&
|
||||||
<span className="sf3-font-trash sf3-font mr-2"></span>
|
<div className='wiki2-trash' onClick={this.props.toggelTrashDialog}>
|
||||||
<span>{gettext('Trash')}</span>
|
<span className="sf3-font-recycle1 sf3-font mr-2"></span>
|
||||||
</div>
|
<span>{gettext('Trash')}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import React, { useCallback, useRef, useState } from 'react';
|
|||||||
import { UncontrolledPopover } from 'reactstrap';
|
import { UncontrolledPopover } from 'reactstrap';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { gettext } from '../../../utils/constants';
|
import { gettext, wikiPermission } from '../../../utils/constants';
|
||||||
import { WIKI_COVER_LIST } from '../constant';
|
import { WIKI_COVER_LIST } from '../constant';
|
||||||
|
|
||||||
import './page-cover.css';
|
import './page-cover.css';
|
||||||
@ -52,29 +52,33 @@ function PageCover({ currentPageConfig, onUpdatePage }) {
|
|||||||
<div id="wiki-page-cover" className='wiki-page-cover' onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
<div id="wiki-page-cover" className='wiki-page-cover' onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
<img className='wiki-page-cover__img' alt={gettext('Cover')} src={getCoverImgUrl(currentPageConfig.cover_img_url)} />
|
<img className='wiki-page-cover__img' alt={gettext('Cover')} src={getCoverImgUrl(currentPageConfig.cover_img_url)} />
|
||||||
<div className={classNames('wiki-page-cover__controller', { show: isShowCoverController })}>
|
<div className={classNames('wiki-page-cover__controller', { show: isShowCoverController })}>
|
||||||
<div className='wiki-cover-controller-btn' id='wiki-change-cover-btn'>{gettext('Change cover')}</div>
|
{wikiPermission !== 'public' &&
|
||||||
|
<div className='wiki-cover-controller-btn' id='wiki-change-cover-btn'>{gettext('Change cover')}</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UncontrolledPopover
|
{wikiPermission !== 'public' &&
|
||||||
ref={popoverRef}
|
<UncontrolledPopover
|
||||||
flip
|
ref={popoverRef}
|
||||||
target="wiki-change-cover-btn"
|
flip
|
||||||
placement="bottom"
|
target="wiki-change-cover-btn"
|
||||||
hideArrow={true}
|
placement="bottom"
|
||||||
popperClassName='wiki-page-cover-popover'
|
hideArrow={true}
|
||||||
innerClassName='wiki-page-cover-panel wiki-page-panel'
|
popperClassName='wiki-page-cover-popover'
|
||||||
trigger="legacy"
|
innerClassName='wiki-page-cover-panel wiki-page-panel'
|
||||||
>
|
trigger="legacy"
|
||||||
<div className='wiki-page-cover-panel__header popover-header'>
|
>
|
||||||
<span>{gettext('Gallery')}</span>
|
<div className='wiki-page-cover-panel__header popover-header'>
|
||||||
<span onClick={removeCoverImage} className='wiki-remove-icon-btn'>{gettext('Remove')}</span>
|
<span>{gettext('Gallery')}</span>
|
||||||
</div>
|
<span onClick={removeCoverImage} className='wiki-remove-icon-btn'>{gettext('Remove')}</span>
|
||||||
<div className='wiki-page-cover-panel__body popover-body'>
|
</div>
|
||||||
{WIKI_COVER_LIST.map(imgName => (
|
<div className='wiki-page-cover-panel__body popover-body'>
|
||||||
<img key={imgName} onClick={updateCoverImage.bind(null, imgName)} className='wiki-cover-gallery-img' alt={gettext('Cover')} src={getCoverImgUrl(`${imgName}`)} />
|
{WIKI_COVER_LIST.map(imgName => (
|
||||||
))}
|
<img key={imgName} onClick={updateCoverImage.bind(null, imgName)} className='wiki-cover-gallery-img' alt={gettext('Cover')} src={getCoverImgUrl(`${imgName}`)} />
|
||||||
</div>
|
))}
|
||||||
</UncontrolledPopover>
|
</div>
|
||||||
|
</UncontrolledPopover>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { UncontrolledPopover } from 'reactstrap';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Picker from '@emoji-mart/react';
|
import Picker from '@emoji-mart/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { gettext } from '../../../utils/constants';
|
import { gettext, wikiPermission } from '../../../utils/constants';
|
||||||
import { data } from './../utils/emoji-utils';
|
import { data } from './../utils/emoji-utils';
|
||||||
|
|
||||||
const PageIcon = ({ currentPageConfig, onUpdatePage }) => {
|
const PageIcon = ({ currentPageConfig, onUpdatePage }) => {
|
||||||
@ -27,32 +27,34 @@ const PageIcon = ({ currentPageConfig, onUpdatePage }) => {
|
|||||||
<span>{currentPageConfig.icon}</span>
|
<span>{currentPageConfig.icon}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UncontrolledPopover
|
{wikiPermission !== 'public' &&
|
||||||
ref={popoverRef}
|
<UncontrolledPopover
|
||||||
flip
|
ref={popoverRef}
|
||||||
target="wiki-page-icon-container"
|
flip
|
||||||
placement="bottom"
|
target="wiki-page-icon-container"
|
||||||
hideArrow={true}
|
placement="bottom"
|
||||||
popperClassName='wiki-page-icon-popover'
|
hideArrow={true}
|
||||||
innerClassName='wiki-page-icon-panel wiki-page-panel'
|
popperClassName='wiki-page-icon-popover'
|
||||||
trigger="legacy"
|
innerClassName='wiki-page-icon-panel wiki-page-panel'
|
||||||
>
|
trigger="legacy"
|
||||||
<div className='wiki-page-icon-panel__header popover-header'>
|
>
|
||||||
<span>{gettext('Emojis')}</span>
|
<div className='wiki-page-icon-panel__header popover-header'>
|
||||||
<span onClick={handleIconRemove} className='wiki-remove-icon-btn'>{gettext('Remove')}</span>
|
<span>{gettext('Emojis')}</span>
|
||||||
</div>
|
<span onClick={handleIconRemove} className='wiki-remove-icon-btn'>{gettext('Remove')}</span>
|
||||||
<div className='wiki-page-icon-panel__body popover-body'>
|
</div>
|
||||||
<Picker
|
<div className='wiki-page-icon-panel__body popover-body'>
|
||||||
data={data}
|
<Picker
|
||||||
onEmojiSelect={(emoji) => handleSetIcon(emoji.native)}
|
data={data}
|
||||||
previewPosition="none"
|
onEmojiSelect={(emoji) => handleSetIcon(emoji.native)}
|
||||||
skinTonePosition="none"
|
previewPosition="none"
|
||||||
locale={window.seafile.lang.slice(0, 2)}
|
skinTonePosition="none"
|
||||||
maxFrequentRows={2}
|
locale={window.seafile.lang.slice(0, 2)}
|
||||||
theme="light"
|
maxFrequentRows={2}
|
||||||
/>
|
theme="light"
|
||||||
</div>
|
/>
|
||||||
</UncontrolledPopover>
|
</div>
|
||||||
|
</UncontrolledPopover>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { throttle } from '../utils';
|
import { throttle } from '../utils';
|
||||||
|
import { wikiPermission } from '../../../utils/constants';
|
||||||
|
|
||||||
function PageTitleEditor({ isUpdateBySide, currentPageConfig, onUpdatePage }) {
|
function PageTitleEditor({ isUpdateBySide, currentPageConfig, onUpdatePage }) {
|
||||||
|
|
||||||
@ -87,18 +88,28 @@ function PageTitleEditor({ isUpdateBySide, currentPageConfig, onUpdatePage }) {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div>
|
||||||
className='wiki-sdoc-title'
|
{wikiPermission === 'public' ?
|
||||||
contentEditable
|
<div
|
||||||
suppressContentEditableWarning
|
className='wiki-sdoc-title'
|
||||||
ref={contentEditableRef}
|
>
|
||||||
onInput={handleInput}
|
{pageName}
|
||||||
onKeyDown={onKeyDown}
|
</div> :
|
||||||
onCompositionStart={onCompositionStart}
|
<div
|
||||||
onCompositionEnd={onCompositionEnd}
|
className='wiki-sdoc-title'
|
||||||
>
|
contentEditable
|
||||||
{pageName}
|
suppressContentEditableWarning
|
||||||
|
ref={contentEditableRef}
|
||||||
|
onInput={handleInput}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onCompositionStart={onCompositionStart}
|
||||||
|
onCompositionEnd={onCompositionEnd}
|
||||||
|
>
|
||||||
|
{pageName}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { gettext } from '../../../utils/constants';
|
import { gettext, wikiPermission } from '../../../utils/constants';
|
||||||
import { WIKI_COVER_LIST } from '../constant';
|
import { WIKI_COVER_LIST } from '../constant';
|
||||||
import PageIcon from './page-icon';
|
import PageIcon from './page-icon';
|
||||||
import { generateARandomEmoji, generateEmojiIcon } from '../utils/emoji-utils';
|
import { generateARandomEmoji, generateEmojiIcon } from '../utils/emoji-utils';
|
||||||
@ -56,13 +56,13 @@ const PageTitle = ({ isUpdateBySide, currentPageConfig, onUpdatePage }) => {
|
|||||||
<PageIcon currentPageConfig={currentPageConfig} onUpdatePage={onUpdatePage} />
|
<PageIcon currentPageConfig={currentPageConfig} onUpdatePage={onUpdatePage} />
|
||||||
)}
|
)}
|
||||||
<div className={classnames('wiki-page-controller', { 'show': isShowController })}>
|
<div className={classnames('wiki-page-controller', { 'show': isShowController })}>
|
||||||
{!currentPageConfig.icon && (
|
{!currentPageConfig.icon && wikiPermission !== 'public' && (
|
||||||
<div className='wiki-page-controller-item' onClick={handleAddIcon}>
|
<div className='wiki-page-controller-item' onClick={handleAddIcon}>
|
||||||
<i className='sf3-font sf3-font-icon'></i>
|
<i className='sf3-font sf3-font-icon'></i>
|
||||||
<span className='text'>{gettext('Add icon')}</span>
|
<span className='text'>{gettext('Add icon')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!currentPageConfig.cover_img_url && (
|
{!currentPageConfig.cover_img_url && wikiPermission !== 'public' && (
|
||||||
<div className='wiki-page-controller-item' onClick={handleAddCover}>
|
<div className='wiki-page-controller-item' onClick={handleAddCover}>
|
||||||
<i className='sf3-font sf3-font-image'></i>
|
<i className='sf3-font sf3-font-image'></i>
|
||||||
<span className='text'>{gettext('Add cover')}</span>
|
<span className='text'>{gettext('Add cover')}</span>
|
||||||
|
@ -115,6 +115,7 @@ export const wikiId = window.wiki ? window.wiki.config.wikiId : '';
|
|||||||
export const repoID = window.wiki ? window.wiki.config.repoId : '';
|
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 wikiPermission = window.wiki ? window.wiki.config.permission : '';
|
||||||
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 : '';
|
export const serviceUrl = window.wiki ? window.wiki.config.serviceUrl : '';
|
||||||
export const isPublicWiki = window.wiki ? window.wiki.config.isPublicWiki === 'True' : '';
|
export const isPublicWiki = window.wiki ? window.wiki.config.isPublicWiki === 'True' : '';
|
||||||
|
@ -176,6 +176,11 @@ class WikiAPI {
|
|||||||
return this.req.get(url);
|
return this.req.get(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWiki2PublishConfig(wikiId) {
|
||||||
|
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/publish/config/';
|
||||||
|
return this.req.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
createWiki2Page(wikiId, pageName, currentId) {
|
createWiki2Page(wikiId, pageName, currentId) {
|
||||||
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/pages/';
|
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/pages/';
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
@ -211,6 +216,11 @@ class WikiAPI {
|
|||||||
return this.req.get(url);
|
return this.req.get(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWiki2PublishPage(wikiId, pageId) {
|
||||||
|
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/publish/page/' + pageId + '/';
|
||||||
|
return this.req.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
renameWiki2(wikiId, wikiName) {
|
renameWiki2(wikiId, wikiName) {
|
||||||
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/';
|
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/';
|
||||||
let params = {
|
let params = {
|
||||||
@ -253,6 +263,23 @@ class WikiAPI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishWiki(wikiId, publish_url) {
|
||||||
|
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/publish/';
|
||||||
|
let form = new FormData();
|
||||||
|
form.append('publish_url', publish_url);
|
||||||
|
return this._sendPostRequest(url, form);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPublishWikiLink(wikiId) {
|
||||||
|
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/publish/';
|
||||||
|
return this.req.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePublishWikiLink(wikiId, customUrl) {
|
||||||
|
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/publish/';
|
||||||
|
return this.req.delete(url);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let wikiAPI = new WikiAPI();
|
let wikiAPI = new WikiAPI();
|
||||||
|
@ -8,6 +8,7 @@ import posixpath
|
|||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
import re
|
||||||
import urllib.request, urllib.error, urllib.parse
|
import urllib.request, urllib.error, urllib.parse
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from constance import config
|
from constance import config
|
||||||
@ -26,7 +27,7 @@ from seahub.api2.throttling import UserRateThrottle
|
|||||||
from seahub.api2.utils import api_error, to_python_boolean, is_wiki_repo
|
from seahub.api2.utils import api_error, to_python_boolean, is_wiki_repo
|
||||||
from seahub.utils.db_api import SeafileDB
|
from seahub.utils.db_api import SeafileDB
|
||||||
from seahub.wiki2.models import Wiki2 as Wiki
|
from seahub.wiki2.models import Wiki2 as Wiki
|
||||||
from seahub.wiki2.models import WikiPageTrash
|
from seahub.wiki2.models import WikiPageTrash, Wiki2Publish
|
||||||
from seahub.wiki2.utils import is_valid_wiki_name, can_edit_wiki, get_wiki_dirs_by_path, \
|
from seahub.wiki2.utils import is_valid_wiki_name, can_edit_wiki, get_wiki_dirs_by_path, \
|
||||||
get_wiki_config, WIKI_PAGES_DIR, WIKI_CONFIG_PATH, WIKI_CONFIG_FILE_NAME, is_group_wiki, \
|
get_wiki_config, WIKI_PAGES_DIR, WIKI_CONFIG_PATH, WIKI_CONFIG_FILE_NAME, is_group_wiki, \
|
||||||
check_wiki_admin_permission, check_wiki_permission, get_all_wiki_ids, get_and_gen_page_nav_by_id, \
|
check_wiki_admin_permission, check_wiki_permission, get_all_wiki_ids, get_and_gen_page_nav_by_id, \
|
||||||
@ -36,7 +37,6 @@ from seahub.wiki2.utils import is_valid_wiki_name, can_edit_wiki, get_wiki_dirs_
|
|||||||
from seahub.utils import is_org_context, get_user_repos, gen_inner_file_get_url, gen_file_upload_url, \
|
from seahub.utils import is_org_context, get_user_repos, gen_inner_file_get_url, gen_file_upload_url, \
|
||||||
normalize_dir_path, is_pro_version, check_filename_with_rename, is_valid_dirent_name, get_no_duplicate_obj_name
|
normalize_dir_path, is_pro_version, check_filename_with_rename, is_valid_dirent_name, get_no_duplicate_obj_name
|
||||||
from seahub.views import check_folder_permission
|
from seahub.views import check_folder_permission
|
||||||
from seahub.views.file import send_file_access_msg
|
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||||
from seahub.utils.file_op import check_file_lock, ONLINE_OFFICE_LOCK_OWNER, if_locked_by_online_office
|
from seahub.utils.file_op import check_file_lock, ONLINE_OFFICE_LOCK_OWNER, if_locked_by_online_office
|
||||||
from seahub.utils.repo import parse_repo_perm, get_repo_owner
|
from seahub.utils.repo import parse_repo_perm, get_repo_owner
|
||||||
@ -61,7 +61,8 @@ HTTP_520_OPERATION_FAILED = 520
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def _merge_wiki_in_groups(group_wikis):
|
|
||||||
|
def _merge_wiki_in_groups(group_wikis, publish_wiki_ids):
|
||||||
|
|
||||||
group_ids = [gw.group_id for gw in group_wikis]
|
group_ids = [gw.group_id for gw in group_wikis]
|
||||||
group_id_wikis_map = {key: [] for key in group_ids}
|
group_id_wikis_map = {key: [] for key in group_ids}
|
||||||
@ -77,7 +78,8 @@ def _merge_wiki_in_groups(group_wikis):
|
|||||||
repo_info = {
|
repo_info = {
|
||||||
"type": "group",
|
"type": "group",
|
||||||
"permission": gw.permission,
|
"permission": gw.permission,
|
||||||
"owner_nickname": owner_nickname
|
"owner_nickname": owner_nickname,
|
||||||
|
"is_published": True if wiki.repo_id in publish_wiki_ids else False
|
||||||
}
|
}
|
||||||
wiki_info.update(repo_info)
|
wiki_info.update(repo_info)
|
||||||
group_id = gw.group_id
|
group_id = gw.group_id
|
||||||
@ -105,8 +107,14 @@ class Wikis2View(APIView):
|
|||||||
user_groups = ccnet_api.get_org_groups_by_user(org_id, username, return_ancestors=True)
|
user_groups = ccnet_api.get_org_groups_by_user(org_id, username, return_ancestors=True)
|
||||||
else:
|
else:
|
||||||
user_groups = ccnet_api.get_groups(username, return_ancestors=True)
|
user_groups = ccnet_api.get_groups(username, return_ancestors=True)
|
||||||
|
|
||||||
owned_wikis = [r for r in owned if is_wiki_repo(r)]
|
owned_wikis = [r for r in owned if is_wiki_repo(r)]
|
||||||
|
shared_wikis = [r for r in shared if is_wiki_repo(r)]
|
||||||
|
group_wikis = [r for r in groups if is_wiki_repo(r)]
|
||||||
|
wiki_ids = [w.repo_id for w in owned_wikis + shared_wikis + group_wikis]
|
||||||
|
publish_wiki_ids = []
|
||||||
|
published_wikis = Wiki2Publish.objects.filter(repo_id__in=wiki_ids)
|
||||||
|
for w in published_wikis:
|
||||||
|
publish_wiki_ids.append(w.repo_id)
|
||||||
wiki_list = []
|
wiki_list = []
|
||||||
for r in owned_wikis:
|
for r in owned_wikis:
|
||||||
r.owner = username
|
r.owner = username
|
||||||
@ -116,12 +124,12 @@ class Wikis2View(APIView):
|
|||||||
repo_info = {
|
repo_info = {
|
||||||
"type": "mine",
|
"type": "mine",
|
||||||
"permission": 'rw',
|
"permission": 'rw',
|
||||||
"owner_nickname": email2nickname(username)
|
"owner_nickname": email2nickname(username),
|
||||||
|
"is_published": True if wiki_info['repo_id'] in publish_wiki_ids else False
|
||||||
}
|
}
|
||||||
wiki_info.update(repo_info)
|
wiki_info.update(repo_info)
|
||||||
wiki_list.append(wiki_info)
|
wiki_list.append(wiki_info)
|
||||||
|
|
||||||
shared_wikis = [r for r in shared if is_wiki_repo(r)]
|
|
||||||
for r in shared_wikis:
|
for r in shared_wikis:
|
||||||
owner = r.user
|
owner = r.user
|
||||||
r.owner = owner
|
r.owner = owner
|
||||||
@ -135,12 +143,12 @@ class Wikis2View(APIView):
|
|||||||
repo_info = {
|
repo_info = {
|
||||||
"type": "shared",
|
"type": "shared",
|
||||||
"permission": r.permission,
|
"permission": r.permission,
|
||||||
"owner_nickname": owner_nickname
|
"owner_nickname": owner_nickname,
|
||||||
|
"is_published": True if wiki_info['repo_id'] in publish_wiki_ids else False
|
||||||
}
|
}
|
||||||
wiki_info.update(repo_info)
|
wiki_info.update(repo_info)
|
||||||
wiki_list.append(wiki_info)
|
wiki_list.append(wiki_info)
|
||||||
|
|
||||||
group_wikis = [r for r in groups if is_wiki_repo(r)]
|
|
||||||
group_id_in_wikis = list(set([r.group_id for r in group_wikis]))
|
group_id_in_wikis = list(set([r.group_id for r in group_wikis]))
|
||||||
try:
|
try:
|
||||||
group_ids_admins_map = {}
|
group_ids_admins_map = {}
|
||||||
@ -157,7 +165,7 @@ class Wikis2View(APIView):
|
|||||||
r.owner = r.user
|
r.owner = r.user
|
||||||
|
|
||||||
group_wiki_list = []
|
group_wiki_list = []
|
||||||
group_id_wikis_map = _merge_wiki_in_groups(group_wikis)
|
group_id_wikis_map = _merge_wiki_in_groups(group_wikis, publish_wiki_ids)
|
||||||
for group_obj in user_wiki_groups:
|
for group_obj in user_wiki_groups:
|
||||||
group_wiki = {
|
group_wiki = {
|
||||||
'group_name': group_obj.group_name,
|
'group_name': group_obj.group_name,
|
||||||
@ -354,6 +362,8 @@ class Wiki2View(APIView):
|
|||||||
else:
|
else:
|
||||||
seafile_api.remove_repo(wiki.repo_id)
|
seafile_api.remove_repo(wiki.repo_id)
|
||||||
|
|
||||||
|
Wiki2Publish.objects.filter(repo_id=wiki.repo_id).delete()
|
||||||
|
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
@ -426,6 +436,35 @@ class Wiki2ConfigView(APIView):
|
|||||||
|
|
||||||
return Response({'wiki': wiki})
|
return Response({'wiki': wiki})
|
||||||
|
|
||||||
|
|
||||||
|
class Wiki2PublishConfigView(APIView):
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def get(self, request, wiki_id):
|
||||||
|
wiki = Wiki.objects.get(wiki_id=wiki_id)
|
||||||
|
if not wiki:
|
||||||
|
error_msg = "Wiki not found."
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
if not Wiki2Publish.objects.filter(repo_id=wiki.repo_id).exists():
|
||||||
|
error_msg = "Wiki not found."
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, 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)
|
||||||
|
|
||||||
|
wiki = wiki.to_dict()
|
||||||
|
wiki_config = get_wiki_config(repo.repo_id, '')
|
||||||
|
|
||||||
|
wiki['wiki_config'] = wiki_config
|
||||||
|
|
||||||
|
return Response({'wiki': wiki})
|
||||||
|
|
||||||
|
|
||||||
class Wiki2PagesView(APIView):
|
class Wiki2PagesView(APIView):
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated, )
|
permission_classes = (IsAuthenticated, )
|
||||||
@ -613,8 +652,6 @@ class Wiki2PageView(APIView):
|
|||||||
throttle_classes = (UserRateThrottle, )
|
throttle_classes = (UserRateThrottle, )
|
||||||
|
|
||||||
def get(self, request, wiki_id, page_id):
|
def get(self, request, wiki_id, page_id):
|
||||||
|
|
||||||
|
|
||||||
wiki = Wiki.objects.get(wiki_id=wiki_id)
|
wiki = Wiki.objects.get(wiki_id=wiki_id)
|
||||||
if not wiki:
|
if not wiki:
|
||||||
error_msg = "Wiki not found."
|
error_msg = "Wiki not found."
|
||||||
@ -658,9 +695,6 @@ class Wiki2PageView(APIView):
|
|||||||
if not file_id:
|
if not file_id:
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, "File not found")
|
return api_error(status.HTTP_404_NOT_FOUND, "File not found")
|
||||||
|
|
||||||
# send stats message
|
|
||||||
send_file_access_msg(request, repo, path, 'api')
|
|
||||||
|
|
||||||
filename = os.path.basename(path)
|
filename = os.path.basename(path)
|
||||||
try:
|
try:
|
||||||
dirent = seafile_api.get_dirent_by_path(repo.repo_id, path)
|
dirent = seafile_api.get_dirent_by_path(repo.repo_id, path)
|
||||||
@ -767,6 +801,68 @@ class Wiki2PageView(APIView):
|
|||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
|
|
||||||
|
|
||||||
|
class Wiki2PublishPageView(APIView):
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def get(self, request, wiki_id, page_id):
|
||||||
|
|
||||||
|
wiki = Wiki.objects.get(wiki_id=wiki_id)
|
||||||
|
if not wiki:
|
||||||
|
error_msg = "Wiki not found."
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
repo_id = wiki.repo_id
|
||||||
|
if not Wiki2Publish.objects.filter(repo_id=repo_id).exists():
|
||||||
|
error_msg = "Wiki not found."
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
repo = seafile_api.get_repo(repo_id)
|
||||||
|
if not repo:
|
||||||
|
error_msg = 'Library %s not found.' % repo_id
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
wiki_config = get_wiki_config(repo_id, "")
|
||||||
|
pages = wiki_config.get('pages', [])
|
||||||
|
page_info = next(filter(lambda t: t['id'] == page_id, pages), {})
|
||||||
|
path = page_info.get('path')
|
||||||
|
doc_uuid = page_info.get('docUuid')
|
||||||
|
|
||||||
|
if not page_info:
|
||||||
|
error_msg = 'page %s not found.' % page_id
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
filename = os.path.basename(path)
|
||||||
|
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
|
||||||
|
|
||||||
|
assets_url = '/api/v2.1/seadoc/download-image/' + doc_uuid
|
||||||
|
seadoc_access_token = gen_seadoc_access_token(doc_uuid, filename, request.user.username, permission='r',
|
||||||
|
default_title='')
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
"latest_contributor": email2nickname(latest_contributor),
|
||||||
|
"last_modified": last_modified,
|
||||||
|
"permission": 'r',
|
||||||
|
"seadoc_access_token": seadoc_access_token,
|
||||||
|
"assets_url": assets_url,
|
||||||
|
})
|
||||||
|
|
||||||
class Wiki2DuplicatePageView(APIView):
|
class Wiki2DuplicatePageView(APIView):
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated, )
|
permission_classes = (IsAuthenticated, )
|
||||||
@ -1073,4 +1169,102 @@ class WikiPageTrashView(APIView):
|
|||||||
|
|
||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
|
|
||||||
|
class Wiki2PublishView(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
def _check_custom_url(self, publish_url):
|
||||||
|
|
||||||
|
return True if re.search(r'^[-0-9a-zA-Z]+$', publish_url) else False
|
||||||
|
|
||||||
|
def get(self, request, wiki_id):
|
||||||
|
wiki = Wiki.objects.get(wiki_id=wiki_id)
|
||||||
|
if not wiki:
|
||||||
|
error_msg = "Wiki not found."
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
username = request.user.username
|
||||||
|
repo_owner = get_repo_owner(request, wiki_id)
|
||||||
|
wiki.owner = repo_owner
|
||||||
|
if not check_wiki_admin_permission(wiki, username):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
publish_config = Wiki2Publish.objects.get(repo_id=wiki.repo_id)
|
||||||
|
publish_url = publish_config.publish_url
|
||||||
|
creator = publish_config.username
|
||||||
|
created_at = publish_config.created_at
|
||||||
|
visit_count = publish_config.visit_count
|
||||||
|
except Wiki2Publish.DoesNotExist:
|
||||||
|
publish_url = ''
|
||||||
|
creator = ''
|
||||||
|
created_at = ''
|
||||||
|
visit_count = 0
|
||||||
|
publish_info = {
|
||||||
|
'publish_url': publish_url,
|
||||||
|
'creator': creator,
|
||||||
|
'created_at': created_at,
|
||||||
|
'visit_count': visit_count
|
||||||
|
}
|
||||||
|
return Response(publish_info)
|
||||||
|
|
||||||
|
def post(self, request, wiki_id):
|
||||||
|
publish_url = request.data.get('publish_url', None)
|
||||||
|
if not publish_url:
|
||||||
|
error_msg = 'wiki custom url invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
publish_url = publish_url.strip()
|
||||||
|
if not self._check_custom_url(publish_url):
|
||||||
|
error_msg = _('URL is invalid')
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
if len(publish_url) < 5 or len(publish_url) > 30:
|
||||||
|
error_msg = _('The custom part of URL should have 5-30 characters.')
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
wiki = Wiki.objects.get(wiki_id=wiki_id)
|
||||||
|
if not wiki:
|
||||||
|
error_msg = "Wiki not found."
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
# check permission
|
||||||
|
repo_owner = get_repo_owner(request, wiki_id)
|
||||||
|
wiki.owner = repo_owner
|
||||||
|
username = request.user.username
|
||||||
|
if not check_wiki_admin_permission(wiki, username):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
wiki_pub = Wiki2Publish.objects.filter(publish_url=publish_url).first()
|
||||||
|
if wiki_pub:
|
||||||
|
if wiki_pub.repo_id != wiki_id:
|
||||||
|
error_msg = _('This custom domain is already in use and cannot be used for your wiki')
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
return Response({"publish_url": publish_url})
|
||||||
|
|
||||||
|
wiki_publish = Wiki2Publish.objects.filter(repo_id=wiki.repo_id).first()
|
||||||
|
if not wiki_publish:
|
||||||
|
wiki_publish = Wiki2Publish(repo_id=wiki.repo_id, username=username, publish_url=publish_url)
|
||||||
|
else:
|
||||||
|
wiki_publish.publish_url = publish_url
|
||||||
|
wiki_publish.save()
|
||||||
|
|
||||||
|
return Response({"publish_url": publish_url})
|
||||||
|
|
||||||
|
def delete(self, request, wiki_id):
|
||||||
|
wiki = Wiki.objects.get(wiki_id=wiki_id)
|
||||||
|
if not wiki:
|
||||||
|
error_msg = "Wiki not found."
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
repo_owner = get_repo_owner(request, wiki_id)
|
||||||
|
wiki.owner = repo_owner
|
||||||
|
username = request.user.username
|
||||||
|
if not check_wiki_admin_permission(wiki, username):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
publish_config = Wiki2Publish.objects.filter(repo_id=wiki.repo_id).first()
|
||||||
|
if publish_config:
|
||||||
|
publish_config.delete()
|
||||||
|
return Response({'success': True})
|
||||||
|
@ -61,6 +61,7 @@ from seahub.base.accounts import User
|
|||||||
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
||||||
from seahub.repo_tags.models import RepoTags
|
from seahub.repo_tags.models import RepoTags
|
||||||
from seahub.file_tags.models import FileTags
|
from seahub.file_tags.models import FileTags
|
||||||
|
from seahub.wiki2.models import Wiki2Publish
|
||||||
if HAS_FILE_SEARCH or HAS_FILE_SEASEARCH:
|
if HAS_FILE_SEARCH or HAS_FILE_SEASEARCH:
|
||||||
from seahub.search.utils import search_files, ai_search_files, format_repos
|
from seahub.search.utils import search_files, ai_search_files, format_repos
|
||||||
|
|
||||||
@ -410,11 +411,13 @@ class SeadocDownloadImage(APIView):
|
|||||||
|
|
||||||
repo_id = uuid_map.repo_id
|
repo_id = uuid_map.repo_id
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
|
wiki_publish = Wiki2Publish.objects.filter(repo_id=repo_id).first()
|
||||||
# permission check
|
# permission check
|
||||||
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
|
if not wiki_publish:
|
||||||
if not can_access_seadoc_asset(request, repo_id, file_path, file_uuid):
|
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
|
||||||
error_msg = 'Permission denied.'
|
if not can_access_seadoc_asset(request, repo_id, file_path, file_uuid):
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
# main
|
# main
|
||||||
parent_path = gen_seadoc_image_parent_path(file_uuid, repo_id, username)
|
parent_path = gen_seadoc_image_parent_path(file_uuid, repo_id, username)
|
||||||
|
35
seahub/templates/wiki/wiki_publish.html
Normal file
35
seahub/templates/wiki/wiki_publish.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{% extends "base_for_react.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load render_bundle from webpack_loader %}
|
||||||
|
{% load seahub_tags %}
|
||||||
|
{% block extra_ogp_tags %}
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:site_name" content="{{ site_name }}">
|
||||||
|
<meta property="og:url" content="{{ service_url }}{{ request.path }}" />
|
||||||
|
<meta property="og:title" content="{{ repo_name }}" />
|
||||||
|
<meta property="og:image" content="{{ service_url }}{{ MEDIA_URL }}img/file/{{ filename|file_icon_filter }}" />
|
||||||
|
<meta property="og:description" content="{{ filename }}" />
|
||||||
|
{% endblock %}
|
||||||
|
{% block extra_style %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/seafile-editor-font/seafile-editor-font.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}sdoc-editor/sdoc-editor-font.css" />
|
||||||
|
{% render_bundle 'wiki2' 'css' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block wiki_title %}{{repo_name}}{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_script %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.wiki = {
|
||||||
|
config: {
|
||||||
|
wikiId: "{{ wiki.id }}",
|
||||||
|
repoName: "{{ wiki.name }}",
|
||||||
|
initial_path: "{{ file_path|escapejs }}",
|
||||||
|
isWiki2: true,
|
||||||
|
seadocServerUrl: "{{ seadoc_server_url }}",
|
||||||
|
permission: "{{ permission }}",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{% render_bundle 'wiki2' 'js' %}
|
||||||
|
{% endblock %}
|
@ -206,10 +206,9 @@ from seahub.api2.endpoints.repo_related_users import RepoRelatedUsersView
|
|||||||
from seahub.api2.endpoints.repo_auto_delete import RepoAutoDeleteView
|
from seahub.api2.endpoints.repo_auto_delete import RepoAutoDeleteView
|
||||||
from seahub.seadoc.views import sdoc_revision, sdoc_revisions, sdoc_to_docx
|
from seahub.seadoc.views import sdoc_revision, sdoc_revisions, sdoc_to_docx
|
||||||
from seahub.ocm.settings import OCM_ENDPOINT
|
from seahub.ocm.settings import OCM_ENDPOINT
|
||||||
|
from seahub.wiki2.views import wiki_view, wiki_publish_view
|
||||||
from seahub.wiki2.views import wiki_view
|
|
||||||
from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView, Wiki2PagesView, Wiki2PageView, \
|
from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView, Wiki2PagesView, Wiki2PageView, \
|
||||||
Wiki2DuplicatePageView, WikiPageTrashView
|
Wiki2DuplicatePageView, WikiPageTrashView, Wiki2PublishView, Wiki2PublishConfigView, Wiki2PublishPageView
|
||||||
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
|
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
|
||||||
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
|
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
|
||||||
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView
|
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView
|
||||||
@ -546,11 +545,13 @@ urlpatterns = [
|
|||||||
re_path(r'^api/v2.1/wikis2/$', Wikis2View.as_view(), name='api-v2.1-wikis2'),
|
re_path(r'^api/v2.1/wikis2/$', Wikis2View.as_view(), name='api-v2.1-wikis2'),
|
||||||
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/$', Wiki2View.as_view(), name='api-v2.1-wiki2'),
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/$', Wiki2View.as_view(), name='api-v2.1-wiki2'),
|
||||||
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/config/$', Wiki2ConfigView.as_view(), name='api-v2.1-wiki2-config'),
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/config/$', Wiki2ConfigView.as_view(), name='api-v2.1-wiki2-config'),
|
||||||
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/publish/config/$', Wiki2PublishConfigView.as_view(), name='api-v2.1-wiki2-publish-config'),
|
||||||
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/pages/$', Wiki2PagesView.as_view(), name='api-v2.1-wiki2-pages'),
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/pages/$', Wiki2PagesView.as_view(), name='api-v2.1-wiki2-pages'),
|
||||||
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/page/(?P<page_id>[-0-9a-zA-Z]{4})/$', Wiki2PageView.as_view(), name='api-v2.1-wiki2-page'),
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/page/(?P<page_id>[-0-9a-zA-Z]{4})/$', Wiki2PageView.as_view(), name='api-v2.1-wiki2-page'),
|
||||||
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/publish/page/(?P<page_id>[-0-9a-zA-Z]{4})/$', Wiki2PublishPageView.as_view(), name='api-v2.1-wiki2-page'),
|
||||||
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/duplicate-page/$', Wiki2DuplicatePageView.as_view(), name='api-v2.1-wiki2-duplicate-page'),
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/duplicate-page/$', Wiki2DuplicatePageView.as_view(), name='api-v2.1-wiki2-duplicate-page'),
|
||||||
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/trash/', WikiPageTrashView.as_view(), name='api-v2.1-wiki2-trash'),
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/trash/', WikiPageTrashView.as_view(), name='api-v2.1-wiki2-trash'),
|
||||||
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/publish/$', Wiki2PublishView.as_view(), name='api-v2.1-wiki2-publish'),
|
||||||
## user::drafts
|
## user::drafts
|
||||||
re_path(r'^api/v2.1/drafts/$', DraftsView.as_view(), name='api-v2.1-drafts'),
|
re_path(r'^api/v2.1/drafts/$', DraftsView.as_view(), name='api-v2.1-drafts'),
|
||||||
re_path(r'^api/v2.1/drafts/(?P<pk>\d+)/$', DraftView.as_view(), name='api-v2.1-draft'),
|
re_path(r'^api/v2.1/drafts/(?P<pk>\d+)/$', DraftView.as_view(), name='api-v2.1-draft'),
|
||||||
@ -737,6 +738,7 @@ urlpatterns = [
|
|||||||
re_path(r'^api/v2.1/admin/invitations/(?P<token>[a-f0-9]{32})/$', AdminInvitation.as_view(), name='api-v2.1-admin-invitation'),
|
re_path(r'^api/v2.1/admin/invitations/(?P<token>[a-f0-9]{32})/$', AdminInvitation.as_view(), name='api-v2.1-admin-invitation'),
|
||||||
|
|
||||||
re_path(r'^wikis/(?P<wiki_id>[^/]+)/$', wiki_view, name='wiki'),
|
re_path(r'^wikis/(?P<wiki_id>[^/]+)/$', wiki_view, name='wiki'),
|
||||||
|
re_path(r'^wiki/publish/(?P<publish_url>[-0-9a-zA-Z]+)/$', wiki_publish_view, name='wiki-publish'),
|
||||||
|
|
||||||
path('avatar/', include('seahub.avatar.urls')),
|
path('avatar/', include('seahub.avatar.urls')),
|
||||||
path('group/', include('seahub.group.urls')),
|
path('group/', include('seahub.group.urls')),
|
||||||
|
@ -33,7 +33,6 @@ class Wiki2(object):
|
|||||||
self.name = wiki.repo_name
|
self.name = wiki.repo_name
|
||||||
self.updated_at = timestamp_to_isoformat_timestr(wiki.last_modify)
|
self.updated_at = timestamp_to_isoformat_timestr(wiki.last_modify)
|
||||||
self.repo_id = wiki.repo_id
|
self.repo_id = wiki.repo_id
|
||||||
|
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
@ -71,6 +70,25 @@ class WikiPageTrash(models.Model):
|
|||||||
'size': self.size
|
'size': self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Wiki2Publish(models.Model):
|
||||||
|
repo_id = models.CharField(max_length=36, unique=True, db_index=True)
|
||||||
|
publish_url = models.CharField(max_length=40, null=True, unique=True)
|
||||||
|
username = models.CharField(max_length=255)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
visit_count = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'wiki_wiki2_publish'
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'repo_id': self.repo_id,
|
||||||
|
'publish_url': self.publish_url,
|
||||||
|
'username': self.username,
|
||||||
|
'created_at': self.created_at,
|
||||||
|
'visit_count': self.visit_count
|
||||||
|
}
|
||||||
|
|
||||||
###### signal handlers
|
###### signal handlers
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from seahub.signals import repo_deleted
|
from seahub.signals import repo_deleted
|
||||||
|
@ -11,6 +11,7 @@ from django.http import Http404
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from seahub.wiki2.models import Wiki2 as Wiki
|
from seahub.wiki2.models import Wiki2 as Wiki
|
||||||
|
from seahub.wiki2.models import Wiki2Publish
|
||||||
from seahub.utils import get_file_type_and_ext, render_permission_error
|
from seahub.utils import get_file_type_and_ext, render_permission_error
|
||||||
from seahub.utils.file_types import SEADOC
|
from seahub.utils.file_types import SEADOC
|
||||||
from seahub.auth.decorators import login_required
|
from seahub.auth.decorators import login_required
|
||||||
@ -81,3 +82,66 @@ def wiki_view(request, wiki_id):
|
|||||||
"permission": permission,
|
"permission": permission,
|
||||||
"enable_user_clean_trash": config.ENABLE_USER_CLEAN_TRASH
|
"enable_user_clean_trash": config.ENABLE_USER_CLEAN_TRASH
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def wiki_publish_view(request, publish_url):
|
||||||
|
""" view wiki page. for wiki2
|
||||||
|
1 permission
|
||||||
|
All user
|
||||||
|
"""
|
||||||
|
# get wiki_publish object or 404
|
||||||
|
wiki_publish = Wiki2Publish.objects.filter(publish_url=publish_url).first()
|
||||||
|
if not wiki_publish:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
wiki_id = wiki_publish.repo_id
|
||||||
|
wiki = Wiki.objects.get(wiki_id=wiki_id)
|
||||||
|
if not wiki:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
repo_owner = get_repo_owner(request, wiki_id)
|
||||||
|
wiki.owner = repo_owner
|
||||||
|
|
||||||
|
page_id = request.GET.get('page_id')
|
||||||
|
file_path = ''
|
||||||
|
|
||||||
|
if page_id:
|
||||||
|
wiki_config = get_wiki_config(wiki.repo_id, request.user.username)
|
||||||
|
pages = wiki_config.get('pages', [])
|
||||||
|
page_info = next(filter(lambda t: t['id'] == page_id, pages), {})
|
||||||
|
file_path = page_info.get('path', '')
|
||||||
|
|
||||||
|
is_page = False
|
||||||
|
if file_path:
|
||||||
|
is_page = True
|
||||||
|
|
||||||
|
latest_contributor = ''
|
||||||
|
last_modified = 0
|
||||||
|
file_type, ext = get_file_type_and_ext(posixpath.basename(file_path))
|
||||||
|
repo = seafile_api.get_repo(wiki.repo_id)
|
||||||
|
if is_page and file_type == SEADOC:
|
||||||
|
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)
|
||||||
|
# update visit_count
|
||||||
|
try:
|
||||||
|
current_count = wiki_publish.visit_count
|
||||||
|
wiki_publish.visit_count = current_count + 1
|
||||||
|
wiki_publish.save()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
|
||||||
|
return render(request, "wiki/wiki_publish.html", {
|
||||||
|
"wiki": wiki,
|
||||||
|
"file_path": file_path,
|
||||||
|
"repo_name": repo.name if repo else '',
|
||||||
|
"modifier": latest_contributor,
|
||||||
|
"modify_time": last_modified,
|
||||||
|
"seadoc_server_url": SEADOC_SERVER_URL,
|
||||||
|
"permission": 'public'
|
||||||
|
})
|
||||||
|
@ -1557,3 +1557,16 @@ CREATE TABLE `sdoc_operation_log` (
|
|||||||
KEY `sdoc_operation_log_doc_uuid` (`doc_uuid`),
|
KEY `sdoc_operation_log_doc_uuid` (`doc_uuid`),
|
||||||
KEY `sdoc_idx_operation_log_doc_uuid_op_id` (`doc_uuid`,`op_id`)
|
KEY `sdoc_idx_operation_log_doc_uuid_op_id` (`doc_uuid`,`op_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `wiki_wiki2_publish` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`repo_id` varchar(36) NOT NULL,
|
||||||
|
`publish_url` varchar(40) DEFAULT NULL,
|
||||||
|
`username` varchar(255) NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`visit_count` int(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `repo_id` (`repo_id`),
|
||||||
|
UNIQUE KEY `publish_url` (`publish_url`),
|
||||||
|
KEY `ix_wiki2_publish_repo_id` (`repo_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
Loading…
Reference in New Issue
Block a user