mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-25 06:29:16 +00:00
optimize code (#6517)
This commit is contained in:
@@ -39,6 +39,7 @@ class Wiki extends Component {
|
||||
seadoc_access_token: '',
|
||||
assets_url: '',
|
||||
wikiRepoId: null,
|
||||
isUpdateBySide: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,19 +86,23 @@ class Wiki extends Component {
|
||||
};
|
||||
|
||||
updateWikiConfig = (wikiConfig) => {
|
||||
this.setState({
|
||||
config: new WikiConfig(wikiConfig || {}),
|
||||
});
|
||||
this.setState({ config: new WikiConfig(wikiConfig || {}) });
|
||||
};
|
||||
|
||||
saveWikiConfig = (wikiConfig, onSuccess, onError) => {
|
||||
saveWikiConfig = (wikiConfig, isUpdateBySide = false) => {
|
||||
wikiAPI.updateWiki2Config(wikiId, JSON.stringify(wikiConfig)).then(res => {
|
||||
this.updateWikiConfig(wikiConfig);
|
||||
onSuccess && onSuccess();
|
||||
this.setState({
|
||||
config: new WikiConfig(wikiConfig),
|
||||
isUpdateBySide,
|
||||
});
|
||||
if (isUpdateBySide) {
|
||||
setTimeout(() => {
|
||||
this.setState({ isUpdateBySide: false });
|
||||
}, 300);
|
||||
}
|
||||
}).catch((error) => {
|
||||
let errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
onError && onError();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -201,7 +206,7 @@ class Wiki extends Component {
|
||||
this.updateDocumentTitle(name);
|
||||
};
|
||||
|
||||
onUpdatePage = (pageId, newPage) => {
|
||||
onUpdatePage = (pageId, newPage, isUpdateBySide) => {
|
||||
if (newPage.name === '') {
|
||||
toaster.danger(gettext('Page name cannot be empty'));
|
||||
return;
|
||||
@@ -218,7 +223,7 @@ class Wiki extends Component {
|
||||
return page;
|
||||
});
|
||||
const newConfig = { ...config, pages: newPages };
|
||||
this.saveWikiConfig(newConfig);
|
||||
this.saveWikiConfig(newConfig, isUpdateBySide);
|
||||
};
|
||||
|
||||
updateDocumentTitle = (newTitle) => {
|
||||
@@ -233,7 +238,6 @@ class Wiki extends Component {
|
||||
closeSideBar={this.state.closeSideBar}
|
||||
onCloseSide={this.onCloseSide}
|
||||
config={this.state.config}
|
||||
saveWikiConfig={this.saveWikiConfig}
|
||||
updateWikiConfig={this.updateWikiConfig}
|
||||
setCurrentPage={this.setCurrentPage}
|
||||
currentPageId={this.state.currentPageId}
|
||||
@@ -251,6 +255,7 @@ class Wiki extends Component {
|
||||
seadoc_access_token={this.state.seadoc_access_token}
|
||||
assets_url={this.state.assets_url}
|
||||
onUpdatePage={this.onUpdatePage}
|
||||
isUpdateBySide={this.state.isUpdateBySide}
|
||||
/>
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
<Modal isOpen={!this.state.closeSideBar} toggle={this.onCloseSide} contentClassName="d-none"></Modal>
|
||||
|
@@ -20,6 +20,7 @@ const propTypes = {
|
||||
assets_url: PropTypes.string,
|
||||
config: PropTypes.object,
|
||||
currentPageId: PropTypes.string,
|
||||
isUpdateBySide: PropTypes.bool,
|
||||
onUpdatePage: PropTypes.func,
|
||||
onAddWikiPage: PropTypes.func,
|
||||
};
|
||||
@@ -35,16 +36,6 @@ class MainPanel extends Component {
|
||||
this.scrollRef = React.createRef();
|
||||
}
|
||||
|
||||
getHeaderHeight = () => {
|
||||
const pageCover = document.getElementById('wiki-page-cover');
|
||||
const pageCoverHeight = pageCover?.offsetHeight || 0;
|
||||
const pageTitle = document.getElementById('wiki-page-title');
|
||||
const pageTitleHeight = pageTitle?.offsetHeight || 0;
|
||||
|
||||
const topNavHeight = 44;
|
||||
return pageCoverHeight + pageTitleHeight + topNavHeight;
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
const { seadoc_access_token, currentPageId, config } = props;
|
||||
const appConfig = window.app.config;
|
||||
@@ -66,7 +57,7 @@ class MainPanel extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { permission, pathExist, isDataLoading, isViewFile, config, onUpdatePage } = this.props;
|
||||
const { permission, pathExist, isDataLoading, isViewFile, config, onUpdatePage, isUpdateBySide } = this.props;
|
||||
const { currentPageConfig = {} } = this.state;
|
||||
const isViewingFile = pathExist && !isDataLoading && isViewFile;
|
||||
const isReadOnly = !(permission === 'rw');
|
||||
@@ -90,13 +81,12 @@ class MainPanel extends Component {
|
||||
{isViewingFile && Utils.isSdocFile(this.props.path) && (
|
||||
<div className='sdoc-scroll-container' id='sdoc-scroll-container' ref={this.scrollRef}>
|
||||
<div className='wiki-editor-container'>
|
||||
<RightHeader onUpdatePage={onUpdatePage} currentPageConfig={currentPageConfig} />
|
||||
<RightHeader isUpdateBySide={isUpdateBySide} currentPageConfig={currentPageConfig} onUpdatePage={onUpdatePage} />
|
||||
<SdocWikiEditor
|
||||
document={this.props.editorContent}
|
||||
docUuid={this.state.docUuid}
|
||||
isWikiReadOnly={isReadOnly}
|
||||
scrollRef={this.scrollRef}
|
||||
getHeaderHeight={this.getHeaderHeight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -21,7 +21,6 @@ const propTypes = {
|
||||
closeSideBar: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
config: PropTypes.object.isRequired,
|
||||
saveWikiConfig: PropTypes.func.isRequired,
|
||||
updateWikiConfig: PropTypes.func.isRequired,
|
||||
setCurrentPage: PropTypes.func.isRequired,
|
||||
currentPageId: PropTypes.string,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
.wiki2-top-nav .wiki2-top-nav-item {
|
||||
align-items: center;
|
||||
padding: 2px 6px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.wiki2-top-nav>div:not(.wiki2-top-nav-item) {
|
||||
|
@@ -50,7 +50,7 @@ function WikiTopNav({ config, currentPageId }) {
|
||||
<Fragment key={item.id}>
|
||||
<div className='wiki2-top-nav-item d-flex'>
|
||||
{item.icon ? <CustomIcon icon={item.icon} /> : <NavItemIcon symbol={'file'} disable={true} />}
|
||||
{item.name}
|
||||
<span className='text-truncate'>{item.name}</span>
|
||||
</div>
|
||||
{index !== paths.length - 1 && <div>/</div>}
|
||||
</Fragment>
|
||||
|
@@ -53,6 +53,22 @@ const getWikPageLink = (pageId) => {
|
||||
return `${origin}${pathname}?page_id=${pageId}`;
|
||||
};
|
||||
|
||||
const throttle = (fn, delay) => {
|
||||
let timer;
|
||||
return function () {
|
||||
let _this = this;
|
||||
let args = arguments;
|
||||
if (timer) {
|
||||
return;
|
||||
}
|
||||
timer = setTimeout(function () {
|
||||
fn.apply(_this, args);
|
||||
timer = null;
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
generatorBase64Code,
|
||||
generateUniqueId,
|
||||
@@ -60,4 +76,5 @@ export {
|
||||
getIconURL,
|
||||
getCurrentPageConfig,
|
||||
getWikPageLink,
|
||||
throttle,
|
||||
};
|
||||
|
@@ -73,7 +73,8 @@ class PageItem extends Component {
|
||||
const { name, id } = this.props.page;
|
||||
const pageName = this.state.pageName.trim();
|
||||
if (pageName !== name) {
|
||||
this.props.onUpdatePage(id, { name: pageName });
|
||||
const isUpdateBySide = true;
|
||||
this.props.onUpdatePage(id, { name: pageName }, isUpdateBySide);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import PageCover from './page-cover';
|
||||
import PageTitle from './page-title';
|
||||
|
||||
function RightHeader({ currentPageConfig, onUpdatePage }) {
|
||||
function RightHeader({ isUpdateBySide, currentPageConfig, onUpdatePage }) {
|
||||
|
||||
const props = { currentPageConfig, onUpdatePage };
|
||||
const props = { isUpdateBySide, currentPageConfig, onUpdatePage };
|
||||
return (
|
||||
<>
|
||||
<PageCover {...props} />
|
||||
@@ -15,6 +15,7 @@ function RightHeader({ currentPageConfig, onUpdatePage }) {
|
||||
}
|
||||
|
||||
RightHeader.propTypes = {
|
||||
isUpdateBySide: PropTypes.bool,
|
||||
currentPageConfig: PropTypes.object,
|
||||
onUpdatePage: PropTypes.func,
|
||||
};
|
||||
|
111
frontend/src/pages/wiki2/wiki-right-header/page-title-editor.js
Normal file
111
frontend/src/pages/wiki2/wiki-right-header/page-title-editor.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { throttle } from '../utils';
|
||||
|
||||
function PageTitleEditor({ isUpdateBySide, currentPageConfig, onUpdatePage }) {
|
||||
|
||||
const [pageName, setPageName] = useState(currentPageConfig.name);
|
||||
const isChineseInput = useRef(false);
|
||||
const contentEditableRef = useRef(null);
|
||||
const selectionRef = useRef(null);
|
||||
|
||||
const saveSelection = () => {
|
||||
const sel = window.getSelection();
|
||||
if (sel.rangeCount > 0) {
|
||||
const range = sel.getRangeAt(0);
|
||||
selectionRef.current = {
|
||||
startContainer: range.startContainer,
|
||||
startOffset: range.startOffset,
|
||||
endContainer: range.endContainer,
|
||||
endOffset: range.endOffset
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const restoreSelection = () => {
|
||||
if (selectionRef.current) {
|
||||
const { startContainer, startOffset, endContainer, endOffset } = selectionRef.current;
|
||||
const range = window.document.createRange();
|
||||
range.setStart(startContainer, startOffset);
|
||||
range.setEnd(endContainer, endOffset);
|
||||
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event) => {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
const onCompositionStart = useCallback(() => {
|
||||
isChineseInput.current = true;
|
||||
}, []);
|
||||
|
||||
const onCompositionEnd = useCallback((e) => {
|
||||
isChineseInput.current = false;
|
||||
setPageName(e.target.innerText);
|
||||
saveSelection();
|
||||
|
||||
const newName = e.target.innerText.trim();
|
||||
const { id, icon } = currentPageConfig;
|
||||
const pageConfig = { name: newName, icon };
|
||||
const delayUpdate = throttle(onUpdatePage, 500);
|
||||
delayUpdate(id, pageConfig);
|
||||
}, [currentPageConfig, onUpdatePage]);
|
||||
|
||||
const handleInput = useCallback((e) => {
|
||||
saveSelection();
|
||||
if (isChineseInput.current === false) {
|
||||
setPageName(e.target.innerText);
|
||||
|
||||
const newName = e.target.innerText.trim();
|
||||
if (newName === pageName) return;
|
||||
const { id, icon } = currentPageConfig;
|
||||
const pageConfig = { name: newName, icon };
|
||||
|
||||
const delayUpdate = throttle(onUpdatePage, 500);
|
||||
delayUpdate(id, pageConfig);
|
||||
}
|
||||
}, [currentPageConfig, onUpdatePage, pageName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pageName !== currentPageConfig.name && isUpdateBySide) {
|
||||
setPageName(currentPageConfig.name);
|
||||
selectionRef.current = null;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPageConfig.name, isUpdateBySide]);
|
||||
|
||||
useEffect(() => {
|
||||
restoreSelection();
|
||||
}, [pageName]);
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className='wiki-sdoc-title'
|
||||
contentEditable
|
||||
suppressContentEditableWarning
|
||||
ref={contentEditableRef}
|
||||
onInput={handleInput}
|
||||
onKeyDown={onKeyDown}
|
||||
onCompositionStart={onCompositionStart}
|
||||
onCompositionEnd={onCompositionEnd}
|
||||
>
|
||||
{pageName}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PageTitleEditor.propTypes = {
|
||||
isUpdateBySide: PropTypes.bool,
|
||||
currentPageConfig: PropTypes.object,
|
||||
onUpdatePage: PropTypes.func,
|
||||
};
|
||||
|
||||
export default PageTitleEditor;
|
@@ -97,3 +97,7 @@
|
||||
.main-panel-center .cur-view-content .wiki-sdoc-title:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.main-panel-center .cur-view-content .wiki-sdoc-title:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
@@ -1,69 +1,22 @@
|
||||
import React, { useCallback, useEffect, useState, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Input } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { WIKI_COVER_LIST } from '../constant';
|
||||
import PageIcon from './page-icon';
|
||||
import { generateARandomEmoji, generateEmojiIcon } from '../utils/emoji-utils';
|
||||
import PageTitleEditor from './page-title-editor';
|
||||
|
||||
import './page-title.css';
|
||||
|
||||
const propTypes = {
|
||||
isUpdateBySide: PropTypes.bool,
|
||||
currentPageConfig: PropTypes.object.isRequired,
|
||||
onUpdatePage: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const PageTitle = ({ currentPageConfig, onUpdatePage }) => {
|
||||
const PageTitle = ({ isUpdateBySide, currentPageConfig, onUpdatePage }) => {
|
||||
const [isShowController, setIsShowController] = useState(false);
|
||||
const [pageName, setPageName] = useState(currentPageConfig.name);
|
||||
const isChineseInput = useRef(false);
|
||||
const isTyping = useRef(false);
|
||||
const timer = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (pageName !== currentPageConfig.name && isTyping.current === false) {
|
||||
setPageName(currentPageConfig.name);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPageConfig.name]);
|
||||
|
||||
const onKeyDown = useCallback(() => {
|
||||
isTyping.current = true;
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
timer.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onKeyUp = useCallback(() => {
|
||||
timer.current = setTimeout(() => {
|
||||
isTyping.current = false;
|
||||
}, 2000);
|
||||
}, []);
|
||||
|
||||
const onCompositionStart = useCallback(() => {
|
||||
isChineseInput.current = true;
|
||||
}, []);
|
||||
|
||||
const onCompositionEnd = useCallback((e) => {
|
||||
isChineseInput.current = false;
|
||||
const newName = e.target.value.trim();
|
||||
const { id, icon } = currentPageConfig;
|
||||
const pageConfig = { name: newName, icon };
|
||||
onUpdatePage && onUpdatePage(id, pageConfig);
|
||||
}, [currentPageConfig, onUpdatePage]);
|
||||
|
||||
const onChange = useCallback((e) => {
|
||||
setPageName(e.target.value);
|
||||
if (isChineseInput.current === false) {
|
||||
const newName = e.target.value.trim();
|
||||
if (newName === pageName) return;
|
||||
const { id, icon } = currentPageConfig;
|
||||
const pageConfig = { name: newName, icon };
|
||||
onUpdatePage && onUpdatePage(id, pageConfig);
|
||||
}
|
||||
}, [currentPageConfig, onUpdatePage, pageName]);
|
||||
|
||||
const onMouseEnter = useCallback(() => {
|
||||
setIsShowController(true);
|
||||
@@ -116,16 +69,7 @@ const PageTitle = ({ currentPageConfig, onUpdatePage }) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
className='wiki-sdoc-title'
|
||||
bsSize="lg"
|
||||
onCompositionStart={onCompositionStart}
|
||||
onCompositionEnd={onCompositionEnd}
|
||||
onKeyDown={onKeyDown}
|
||||
onKeyUp={onKeyUp}
|
||||
onChange={onChange}
|
||||
value={pageName}
|
||||
/>
|
||||
<PageTitleEditor isUpdateBySide={isUpdateBySide} currentPageConfig={currentPageConfig} onUpdatePage={onUpdatePage} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -74,7 +74,7 @@ body {
|
||||
justify-content: center;
|
||||
min-height: 800px;
|
||||
width: 100%;
|
||||
padding: 0 142px;
|
||||
padding: 0 142px 60px;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
Reference in New Issue
Block a user