mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-05 00:43:53 +00:00
optimize code
This commit is contained in:
@@ -1,123 +0,0 @@
|
|||||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
||||||
import { Popover, PopoverBody } from 'reactstrap';
|
|
||||||
import { init } from 'emoji-mart';
|
|
||||||
import Picker from '@emoji-mart/react';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import data from '@emoji-mart/data';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { gettext } from '../../../utils/constants';
|
|
||||||
|
|
||||||
// init data for emoji-mart, used in Picker and `getRandomEmoji` method
|
|
||||||
init({ data });
|
|
||||||
|
|
||||||
const HeaderIcon = ({ currentPageConfig, onUpdatePage }, ref) => {
|
|
||||||
const [isShowIconPanel, setIsShowIconPanel] = useState(false);
|
|
||||||
const iconPanelPopoverRef = useRef(null);
|
|
||||||
|
|
||||||
|
|
||||||
const handleSetIcon = useCallback((emoji, cb) => {
|
|
||||||
onUpdatePage(currentPageConfig.id, { name: currentPageConfig.name, icon: emoji });
|
|
||||||
cb && cb();
|
|
||||||
}, [currentPageConfig.id, currentPageConfig.name, onUpdatePage]);
|
|
||||||
|
|
||||||
const setRandomEmoji = useCallback(() => {
|
|
||||||
const nativeEmojis = Reflect.ownKeys(data.natives);
|
|
||||||
const emojiCount = nativeEmojis.length;
|
|
||||||
const emoji = nativeEmojis[Math.floor(Math.random() * emojiCount)];
|
|
||||||
handleSetIcon(emoji);
|
|
||||||
}, [handleSetIcon]);
|
|
||||||
|
|
||||||
const handleIconRemove = () => {
|
|
||||||
handleSetIcon('');
|
|
||||||
handleIconPanelDisplayedChange(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIconPanelDisplayedChange = useCallback((isShowIconPanel) => {
|
|
||||||
setIsShowIconPanel(isShowIconPanel);
|
|
||||||
// setIsShowCoverPanel(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleClickAddIcon = useCallback(() => {
|
|
||||||
setRandomEmoji();
|
|
||||||
handleIconPanelDisplayedChange(false);
|
|
||||||
}, [handleIconPanelDisplayedChange, setRandomEmoji]);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
|
||||||
return { handleClickAddIcon };
|
|
||||||
}, [handleClickAddIcon]);
|
|
||||||
|
|
||||||
const handleIconPanelListener = useCallback((e) => {
|
|
||||||
const isClickInPopover = iconPanelPopoverRef.current.contains(e.target);
|
|
||||||
if (!isClickInPopover) handleIconPanelDisplayedChange(false);
|
|
||||||
}, [handleIconPanelDisplayedChange]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isShowIconPanel) {
|
|
||||||
setTimeout(() => {
|
|
||||||
// Avoid open behavior closing popover
|
|
||||||
addEventListener('click', handleIconPanelListener);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
if (!isShowIconPanel) removeEventListener('click', handleIconPanelListener);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
removeEventListener('click', handleIconPanelListener);
|
|
||||||
};
|
|
||||||
}, [handleIconPanelListener, isShowIconPanel]);
|
|
||||||
|
|
||||||
// Update current page favicon
|
|
||||||
useEffect(() => {
|
|
||||||
let faviconUrl = '';
|
|
||||||
if (currentPageConfig.icon) {
|
|
||||||
faviconUrl = `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${currentPageConfig.icon}</text></svg>`;
|
|
||||||
} else {
|
|
||||||
const { serviceUrl, mediaUrl, faviconPath } = window.seafile;
|
|
||||||
faviconUrl = `${serviceUrl}${mediaUrl}${faviconPath}`;
|
|
||||||
}
|
|
||||||
document.getElementById('favicon').href = faviconUrl;
|
|
||||||
}, [currentPageConfig.icon]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={classnames('wiki-icon-container', { gap: currentPageConfig.icon && !currentPageConfig.cover_img_url })}>
|
|
||||||
<div className={classnames('wiki-icon-wrapper', { show: currentPageConfig.icon })} id='wiki-icon-wrapper' onClick={handleIconPanelDisplayedChange.bind(null, !isShowIconPanel)}>
|
|
||||||
<span>{currentPageConfig.icon}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Popover
|
|
||||||
flip
|
|
||||||
target="wiki-icon-wrapper"
|
|
||||||
toggle={() => void 0}
|
|
||||||
placement="bottom"
|
|
||||||
isOpen={!!(isShowIconPanel && !!currentPageConfig.icon)}
|
|
||||||
popperClassName='wiki-icon-popover'
|
|
||||||
hideArrow={true}
|
|
||||||
>
|
|
||||||
<div ref={iconPanelPopoverRef}>
|
|
||||||
<div className='wiki-icon-panel-header popover-header'>
|
|
||||||
<span>{gettext('Emojis')}</span>
|
|
||||||
<span onClick={handleIconRemove} className='wiki-remove-icon-btn'>{gettext('Remove')}</span>
|
|
||||||
</div>
|
|
||||||
<PopoverBody className='wiki-icon-panel-body'>
|
|
||||||
<Picker
|
|
||||||
data={data}
|
|
||||||
onEmojiSelect={(emoji) => handleSetIcon(emoji.native, handleIconPanelDisplayedChange.bind(null, false))}
|
|
||||||
previewPosition="none"
|
|
||||||
skinTonePosition="none"
|
|
||||||
locale={window.seafile.lang.slice(0, 2)}
|
|
||||||
maxFrequentRows={2}
|
|
||||||
theme="light"
|
|
||||||
/>
|
|
||||||
</PopoverBody>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
HeaderIcon.propTypes = {
|
|
||||||
currentPageConfig: PropTypes.object.isRequired,
|
|
||||||
onUpdatePage: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default forwardRef(HeaderIcon);
|
|
@@ -1,166 +0,0 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import { Input, Popover, PopoverBody } from 'reactstrap';
|
|
||||||
import { gettext } from '../../../utils/constants';
|
|
||||||
import { WIKI_COVER_LIST } from '../constant';
|
|
||||||
import HeaderIcon from './header-icon';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
currentPageConfig: PropTypes.object.isRequired,
|
|
||||||
onUpdatePage: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PageHeader = ({ currentPageConfig, onUpdatePage }) => {
|
|
||||||
const [isShowController, setIsShowController] = useState(false);
|
|
||||||
const [isShowCoverController, setIsShowCoverController] = useState(false);
|
|
||||||
const [isShowCoverPanel, setIsShowCoverPanel] = useState(false);
|
|
||||||
const coverPanelPopoverRef = useRef(null);
|
|
||||||
const headerWrapperRef = useRef(null);
|
|
||||||
const headerIconRef = useRef({ handleClickAddIcon: () => void 0 });
|
|
||||||
|
|
||||||
const getCoverImgUrl = useCallback((imageName) => {
|
|
||||||
const { serviceUrl, mediaUrl } = window.seafile;
|
|
||||||
return `${serviceUrl}${mediaUrl}img/wiki/cover/${imageName}`;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleRenameDocument = useCallback((e) => {
|
|
||||||
const { nativeEvent: { isComposing } } = e;
|
|
||||||
if (isComposing) return;
|
|
||||||
|
|
||||||
const newName = e.target.value.trim();
|
|
||||||
const { id, name, icon } = currentPageConfig;
|
|
||||||
if (newName === name) return;
|
|
||||||
const pageConfig = { name: newName, icon };
|
|
||||||
onUpdatePage && onUpdatePage(id, pageConfig);
|
|
||||||
}, [currentPageConfig, onUpdatePage]);
|
|
||||||
|
|
||||||
const changeControllerDisplayed = useCallback((isShowController) => {
|
|
||||||
if (isShowCoverPanel) return;
|
|
||||||
setIsShowController(isShowController);
|
|
||||||
}, [isShowCoverPanel]);
|
|
||||||
|
|
||||||
const handleCoverPanelDisplayedChange = useCallback((isShowCoverPanel) => {
|
|
||||||
setIsShowCoverPanel(isShowCoverPanel);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleCoverPanelListener = useCallback((e) => {
|
|
||||||
const isClickInPopover = coverPanelPopoverRef.current.contains(e.target);
|
|
||||||
if (!isClickInPopover) handleCoverPanelDisplayedChange(false);
|
|
||||||
}, [handleCoverPanelDisplayedChange]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isShowCoverPanel) {
|
|
||||||
setTimeout(() => {
|
|
||||||
// Avoid open behavior closing popover
|
|
||||||
addEventListener('click', handleCoverPanelListener);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
if (!isShowCoverPanel) removeEventListener('click', handleCoverPanelListener);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
removeEventListener('click', handleCoverPanelListener);
|
|
||||||
};
|
|
||||||
}, [handleCoverPanelListener, isShowCoverPanel]);
|
|
||||||
|
|
||||||
const handleAddCover = useCallback(() => {
|
|
||||||
const coverName = WIKI_COVER_LIST[Math.floor(Math.random() * WIKI_COVER_LIST.length)];
|
|
||||||
const coverImgUrl = `${coverName}`;
|
|
||||||
onUpdatePage(currentPageConfig.id, { name: currentPageConfig.name, cover_img_url: coverImgUrl });
|
|
||||||
}, [currentPageConfig.id, currentPageConfig.name, onUpdatePage]);
|
|
||||||
|
|
||||||
const showCoverController = useCallback(() => {
|
|
||||||
setIsShowCoverController(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const hideCoverController = useCallback((e) => {
|
|
||||||
if (isShowCoverPanel) return;
|
|
||||||
setIsShowCoverController(false);
|
|
||||||
}, [isShowCoverPanel]);
|
|
||||||
|
|
||||||
const handleSetCoverImage = (coverName) => {
|
|
||||||
const coverImgUrl = `${coverName}`;
|
|
||||||
onUpdatePage(currentPageConfig.id, { name: currentPageConfig.name, cover_img_url: coverImgUrl });
|
|
||||||
handleCoverPanelDisplayedChange(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCoverRemove = () => {
|
|
||||||
onUpdatePage(currentPageConfig.id, { name: currentPageConfig.name, cover_img_url: '' });
|
|
||||||
handleCoverPanelDisplayedChange(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className='wiki-page-header-wrapper'
|
|
||||||
ref={headerWrapperRef}
|
|
||||||
onMouseEnter={changeControllerDisplayed.bind(null, true)}
|
|
||||||
onMouseLeave={changeControllerDisplayed.bind(null, false)}
|
|
||||||
|
|
||||||
>
|
|
||||||
{currentPageConfig.cover_img_url && (
|
|
||||||
<div
|
|
||||||
className='wiki-cover'
|
|
||||||
onMouseMove={showCoverController}
|
|
||||||
onMouseLeave={hideCoverController}
|
|
||||||
>
|
|
||||||
<img className='wiki-cover-img' alt={gettext('Cover')} src={getCoverImgUrl(currentPageConfig.cover_img_url)} />
|
|
||||||
{isShowCoverController && (
|
|
||||||
<div className='wiki-cover-controller'>
|
|
||||||
<div onClick={handleCoverPanelDisplayedChange.bind(true)} className='wiki-cover-controller-btn' id='wiki-change-cover-btn'>{gettext('Change cover')}</div>
|
|
||||||
<Popover
|
|
||||||
flip
|
|
||||||
target="wiki-change-cover-btn"
|
|
||||||
toggle={() => void 0}
|
|
||||||
placement="bottom"
|
|
||||||
isOpen={!!(isShowCoverPanel && currentPageConfig.cover_img_url)}
|
|
||||||
innerClassName='wiki-cover-panel'
|
|
||||||
hideArrow={true}
|
|
||||||
popperClassName='wiki-cover-popover'
|
|
||||||
>
|
|
||||||
<div ref={coverPanelPopoverRef}>
|
|
||||||
<div className='wiki-icon-panel-header popover-header'>
|
|
||||||
<span>{gettext('Gallery')}</span>
|
|
||||||
<span onClick={handleCoverRemove} className='wiki-remove-icon-btn'>{gettext('Remove')}</span>
|
|
||||||
</div>
|
|
||||||
<PopoverBody className='wiki-cover-panel-body'>
|
|
||||||
{
|
|
||||||
WIKI_COVER_LIST.map(imgName => (
|
|
||||||
<img key={imgName} onClick={handleSetCoverImage.bind(null, imgName)} className='wiki-cover-gallery-img' alt={gettext('Cover')} src={getCoverImgUrl(`${imgName}`)} />
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</PopoverBody>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className='wiki-page-gap-container'>
|
|
||||||
<div className='wiki-editor-header'>
|
|
||||||
<HeaderIcon currentPageConfig={currentPageConfig} onUpdatePage={onUpdatePage} ref={headerIconRef} />
|
|
||||||
<div className={classnames('wiki-page-controller', { show: isShowController })}>
|
|
||||||
{!currentPageConfig.icon && (
|
|
||||||
<div className='wiki-page-controller-item' onClick={headerIconRef.current.handleClickAddIcon}>
|
|
||||||
<i className='sf3-font sf3-font-icon'></i>
|
|
||||||
<span className='text'>{gettext('Add icon')}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!currentPageConfig.cover_img_url && (
|
|
||||||
<div onClick={handleAddCover} className='wiki-page-controller-item'>
|
|
||||||
<i className='sf3-font sf3-font-image'></i>
|
|
||||||
<span className='text'>{gettext('Add cover')}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Input className='sf-wiki-title' onCompositionEnd={handleRenameDocument} bsSize="lg" onChange={handleRenameDocument} defaultValue={currentPageConfig.name} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PageHeader.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default PageHeader;
|
|
@@ -7,7 +7,7 @@ import { Utils } from '../../utils/utils';
|
|||||||
import Account from '../../components/common/account';
|
import Account from '../../components/common/account';
|
||||||
import WikiTopNav from './top-nav';
|
import WikiTopNav from './top-nav';
|
||||||
import { getCurrentPageConfig } from './utils';
|
import { getCurrentPageConfig } from './utils';
|
||||||
import PageHeader from './editor-component/page-header';
|
import RightHeader from './wiki-right-header';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
@@ -82,7 +82,7 @@ class MainPanel extends Component {
|
|||||||
{isViewingFile && Utils.isSdocFile(this.props.path) && (
|
{isViewingFile && Utils.isSdocFile(this.props.path) && (
|
||||||
<div className='sdoc-scroll-container' id='sdoc-scroll-container' ref={this.scrollRef}>
|
<div className='sdoc-scroll-container' id='sdoc-scroll-container' ref={this.scrollRef}>
|
||||||
<div className='wiki-editor-container'>
|
<div className='wiki-editor-container'>
|
||||||
<PageHeader onUpdatePage={onUpdatePage} currentPageConfig={currentPageConfig} />
|
<RightHeader onUpdatePage={onUpdatePage} currentPageConfig={currentPageConfig} />
|
||||||
<SdocWikiEditor
|
<SdocWikiEditor
|
||||||
document={this.props.editorContent}
|
document={this.props.editorContent}
|
||||||
docUuid={this.state.docUuid}
|
docUuid={this.state.docUuid}
|
||||||
|
22
frontend/src/pages/wiki2/utils/emoji-utils.js
Normal file
22
frontend/src/pages/wiki2/utils/emoji-utils.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { init } from 'emoji-mart';
|
||||||
|
import data from '@emoji-mart/data';
|
||||||
|
|
||||||
|
// init data for emoji-mart, used in Picker and `getRandomEmoji` method
|
||||||
|
init({ data });
|
||||||
|
|
||||||
|
const generateARandomEmoji = () => {
|
||||||
|
const nativeEmojis = Reflect.ownKeys(data.natives);
|
||||||
|
const emojiCount = nativeEmojis.length;
|
||||||
|
const emoji = nativeEmojis[Math.floor(Math.random() * emojiCount)];
|
||||||
|
return emoji;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateEmojiIcon = (icon) => {
|
||||||
|
return `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${icon}</text></svg>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
data,
|
||||||
|
generateEmojiIcon,
|
||||||
|
generateARandomEmoji,
|
||||||
|
};
|
14
frontend/src/pages/wiki2/wiki-right-header/index.js
Normal file
14
frontend/src/pages/wiki2/wiki-right-header/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PageCover from './page-cover';
|
||||||
|
import PageTitle from './page-title';
|
||||||
|
|
||||||
|
export default function RightHeader({ currentPageConfig, onUpdatePage }) {
|
||||||
|
|
||||||
|
const props = { currentPageConfig, onUpdatePage };
|
||||||
|
return (
|
||||||
|
<div className='wiki-page-header-wrapper'>
|
||||||
|
<PageCover {...props} />
|
||||||
|
<PageTitle {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
86
frontend/src/pages/wiki2/wiki-right-header/page-cover.css
Normal file
86
frontend/src/pages/wiki2/wiki-right-header/page-cover.css
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/* Cover */
|
||||||
|
.cur-view-content .wiki-page-cover {
|
||||||
|
position: relative;
|
||||||
|
height: 30vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover__img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover__controller {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover__controller.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover__controller .wiki-cover-controller-btn {
|
||||||
|
padding: 4px 6px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6a6767b3;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover__controller .wiki-cover-controller-btn:hover {
|
||||||
|
color: #4d5156;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover-popover {
|
||||||
|
max-width: max-content !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover-panel__header {
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover-panel__body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover-panel .wiki-cover-gallery-img {
|
||||||
|
width: 120px;
|
||||||
|
height: 64px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-cover-panel .wiki-cover-gallery-img:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* icon */
|
||||||
|
|
||||||
|
/* common */
|
||||||
|
.wiki-page-panel .popover-header {
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-panel .popover-header>span {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-panel .popover-header>span:last-child:hover {
|
||||||
|
background-color: #efefef;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
87
frontend/src/pages/wiki2/wiki-right-header/page-cover.js
Normal file
87
frontend/src/pages/wiki2/wiki-right-header/page-cover.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
|
import { UncontrolledPopover } from 'reactstrap';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { WIKI_COVER_LIST } from '../constant';
|
||||||
|
|
||||||
|
import './page-cover.css';
|
||||||
|
|
||||||
|
function PageCover({ currentPageConfig, onUpdatePage }) {
|
||||||
|
|
||||||
|
const [isShowCoverController, setIsShowCoverController] = useState(false);
|
||||||
|
const popoverRef = useRef(null);
|
||||||
|
|
||||||
|
const onMouseEnter = useCallback(() => {
|
||||||
|
setIsShowCoverController(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMouseLeave = useCallback(() => {
|
||||||
|
if (popoverRef.current?.state.isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsShowCoverController(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getCoverImgUrl = useCallback((imageName) => {
|
||||||
|
const { serviceUrl, mediaUrl } = window.seafile;
|
||||||
|
return `${serviceUrl}${mediaUrl}img/wiki/cover/${imageName}`;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updatePageCover = useCallback((imageName) => {
|
||||||
|
onUpdatePage(currentPageConfig.id, { name: currentPageConfig.name, cover_img_url: imageName });
|
||||||
|
setTimeout(() => {
|
||||||
|
popoverRef.current?.toggle();
|
||||||
|
}, 300);
|
||||||
|
}, [currentPageConfig.id, currentPageConfig.name, onUpdatePage]);
|
||||||
|
|
||||||
|
const removeCoverImage = useCallback(() => {
|
||||||
|
updatePageCover('');
|
||||||
|
}, [updatePageCover]);
|
||||||
|
|
||||||
|
const updateCoverImage = useCallback((imageName) => {
|
||||||
|
updatePageCover(imageName);
|
||||||
|
}, [updatePageCover]);
|
||||||
|
|
||||||
|
if (!currentPageConfig.cover_img_url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='wiki-page-cover' onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
|
<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='wiki-cover-controller-btn' id='wiki-change-cover-btn'>{gettext('Change cover')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UncontrolledPopover
|
||||||
|
ref={popoverRef}
|
||||||
|
flip
|
||||||
|
target="wiki-change-cover-btn"
|
||||||
|
placement="bottom"
|
||||||
|
hideArrow={true}
|
||||||
|
popperClassName='wiki-page-cover-popover'
|
||||||
|
innerClassName='wiki-page-cover-panel wiki-page-panel'
|
||||||
|
trigger="legacy"
|
||||||
|
>
|
||||||
|
<div className='wiki-page-cover-panel__header popover-header'>
|
||||||
|
<span>{gettext('Gallery')}</span>
|
||||||
|
<span onClick={removeCoverImage} className='wiki-remove-icon-btn'>{gettext('Remove')}</span>
|
||||||
|
</div>
|
||||||
|
<div className='wiki-page-cover-panel__body popover-body'>
|
||||||
|
{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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PageCover.propTypes = {
|
||||||
|
currentPageConfig: PropTypes.object,
|
||||||
|
onUpdatePage: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageCover;
|
65
frontend/src/pages/wiki2/wiki-right-header/page-icon.js
Normal file
65
frontend/src/pages/wiki2/wiki-right-header/page-icon.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import React, { useCallback, useRef } from 'react';
|
||||||
|
import { UncontrolledPopover } from 'reactstrap';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Picker from '@emoji-mart/react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { data } from './../utils/emoji-utils';
|
||||||
|
|
||||||
|
const PageIcon = ({ currentPageConfig, onUpdatePage }) => {
|
||||||
|
const popoverRef = useRef(null);
|
||||||
|
|
||||||
|
const handleSetIcon = useCallback((emoji) => {
|
||||||
|
onUpdatePage(currentPageConfig.id, { name: currentPageConfig.name, icon: emoji });
|
||||||
|
setTimeout(() => {
|
||||||
|
popoverRef.current?.toggle();
|
||||||
|
}, 300);
|
||||||
|
}, [currentPageConfig.id, currentPageConfig.name, onUpdatePage]);
|
||||||
|
|
||||||
|
const handleIconRemove = useCallback(() => {
|
||||||
|
handleSetIcon('');
|
||||||
|
}, [handleSetIcon]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classNames('wiki-page-icon-wrapper', {'no-page-cover': currentPageConfig.cover_img_url})}>
|
||||||
|
<div className='wiki-page-icon-container' id='wiki-page-icon-container'>
|
||||||
|
<span>{currentPageConfig.icon}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UncontrolledPopover
|
||||||
|
ref={popoverRef}
|
||||||
|
flip
|
||||||
|
target="wiki-page-icon-container"
|
||||||
|
placement="bottom"
|
||||||
|
hideArrow={true}
|
||||||
|
popperClassName='wiki-page-icon-popover'
|
||||||
|
innerClassName='wiki-page-icon-panel wiki-page-panel'
|
||||||
|
trigger="legacy"
|
||||||
|
>
|
||||||
|
<div className='wiki-page-icon-panel__header popover-header'>
|
||||||
|
<span>{gettext('Emojis')}</span>
|
||||||
|
<span onClick={handleIconRemove} className='wiki-remove-icon-btn'>{gettext('Remove')}</span>
|
||||||
|
</div>
|
||||||
|
<div className='wiki-page-icon-panel__body popover-body'>
|
||||||
|
<Picker
|
||||||
|
data={data}
|
||||||
|
onEmojiSelect={(emoji) => handleSetIcon(emoji.native)}
|
||||||
|
previewPosition="none"
|
||||||
|
skinTonePosition="none"
|
||||||
|
locale={window.seafile.lang.slice(0, 2)}
|
||||||
|
maxFrequentRows={2}
|
||||||
|
theme="light"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</UncontrolledPopover>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PageIcon.propTypes = {
|
||||||
|
currentPageConfig: PropTypes.object.isRequired,
|
||||||
|
onUpdatePage: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageIcon;
|
95
frontend/src/pages/wiki2/wiki-right-header/page-title.css
Normal file
95
frontend/src/pages/wiki2/wiki-right-header/page-title.css
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
.wiki-page-title-wrapper {
|
||||||
|
position: relative;
|
||||||
|
padding: 0 50px 0 142px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-wrapper {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-wrapper.no-page-cover {
|
||||||
|
margin-top: -48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-container {
|
||||||
|
position: relative;
|
||||||
|
width: 78px;
|
||||||
|
height: 78px;
|
||||||
|
font-size: 78px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-container:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #d3c5c370;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-popover {
|
||||||
|
max-width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-panel__header {
|
||||||
|
/* height: 337px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-panel__body {
|
||||||
|
padding: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-panel__body em-emoji-picker {
|
||||||
|
height: 337px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-icon-panel__body em-emoji-picker section {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 10px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller.show {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller-item {
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #949491;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
margin-left: 6px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller-item:hover {
|
||||||
|
background-color: #efefef;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller-item:first-of-type {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller-item .text {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sdoc title */
|
||||||
|
.main-panel-center .cur-view-content .wiki-sdoc-title {
|
||||||
|
border: none;
|
||||||
|
font-size: 26pt;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0 50px 0 0;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-panel-center .cur-view-content .wiki-sdoc-title:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
89
frontend/src/pages/wiki2/wiki-right-header/page-title.js
Normal file
89
frontend/src/pages/wiki2/wiki-right-header/page-title.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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 HeaderIcon from './page-icon';
|
||||||
|
import { generateARandomEmoji, generateEmojiIcon } from '../utils/emoji-utils';
|
||||||
|
|
||||||
|
import './page-title.css';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
currentPageConfig: PropTypes.object.isRequired,
|
||||||
|
onUpdatePage: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PageTitle = ({ currentPageConfig, onUpdatePage }) => {
|
||||||
|
const [isShowController, setIsShowController] = useState(false);
|
||||||
|
|
||||||
|
const handleRenameDocument = useCallback((e) => {
|
||||||
|
const { nativeEvent: { isComposing } } = e;
|
||||||
|
if (isComposing) return;
|
||||||
|
|
||||||
|
const newName = e.target.value.trim();
|
||||||
|
const { id, name, icon } = currentPageConfig;
|
||||||
|
if (newName === name) return;
|
||||||
|
const pageConfig = { name: newName, icon };
|
||||||
|
onUpdatePage && onUpdatePage(id, pageConfig);
|
||||||
|
}, [currentPageConfig, onUpdatePage]);
|
||||||
|
|
||||||
|
const onMouseEnter = useCallback(() => {
|
||||||
|
setIsShowController(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMouseLeave = useCallback(() => {
|
||||||
|
setIsShowController(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAddIcon = useCallback(() => {
|
||||||
|
const icon = generateARandomEmoji();
|
||||||
|
onUpdatePage(currentPageConfig.id, { name: currentPageConfig.name, icon: icon });
|
||||||
|
}, [currentPageConfig.id, currentPageConfig.name, onUpdatePage]);
|
||||||
|
|
||||||
|
const handleAddCover = useCallback(() => {
|
||||||
|
const coverName = WIKI_COVER_LIST[Math.floor(Math.random() * WIKI_COVER_LIST.length)];
|
||||||
|
const coverImgUrl = `${coverName}`;
|
||||||
|
onUpdatePage(currentPageConfig.id, { name: currentPageConfig.name, cover_img_url: coverImgUrl });
|
||||||
|
}, [currentPageConfig.id, currentPageConfig.name, onUpdatePage]);
|
||||||
|
|
||||||
|
// Update current page favicon
|
||||||
|
useEffect(() => {
|
||||||
|
let faviconUrl = '';
|
||||||
|
if (currentPageConfig.icon) {
|
||||||
|
faviconUrl = generateEmojiIcon(currentPageConfig.icon);
|
||||||
|
} else {
|
||||||
|
const { serviceUrl, mediaUrl, faviconPath } = window.seafile;
|
||||||
|
faviconUrl = `${serviceUrl}${mediaUrl}${faviconPath}`;
|
||||||
|
}
|
||||||
|
document.getElementById('favicon').href = faviconUrl;
|
||||||
|
}, [currentPageConfig.icon]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='wiki-page-title-wrapper' onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
|
{currentPageConfig.icon && (
|
||||||
|
<HeaderIcon currentPageConfig={currentPageConfig} onUpdatePage={onUpdatePage} />
|
||||||
|
)}
|
||||||
|
<div className={classnames('wiki-page-controller', {'show': isShowController})}>
|
||||||
|
{!currentPageConfig.icon && (
|
||||||
|
<div className='wiki-page-controller-item' onClick={handleAddIcon}>
|
||||||
|
<i className='sf3-font sf3-font-icon'></i>
|
||||||
|
<span className='text'>{gettext('Add icon')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!currentPageConfig.cover_img_url && (
|
||||||
|
<div className='wiki-page-controller-item' onClick={handleAddCover}>
|
||||||
|
<i className='sf3-font sf3-font-image'></i>
|
||||||
|
<span className='text'>{gettext('Add cover')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Input className='wiki-sdoc-title' onCompositionEnd={handleRenameDocument} bsSize="lg" onChange={handleRenameDocument} defaultValue={currentPageConfig.name} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PageTitle.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default PageTitle;
|
@@ -23,18 +23,6 @@ body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-panel-center .cur-view-content .sf-wiki-title {
|
|
||||||
border: none;
|
|
||||||
font-size: 26pt;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0 50px 0 0;
|
|
||||||
color: #212529;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-panel-center .cur-view-content .sf-wiki-title:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* scroll container */
|
/* scroll container */
|
||||||
/* wiki editor */
|
/* wiki editor */
|
||||||
.cur-view-content .sdoc-scroll-container {
|
.cur-view-content .sdoc-scroll-container {
|
||||||
@@ -109,159 +97,3 @@ body {
|
|||||||
.sdoc-editor-container .sdoc-article-container .article #sdoc-editor {
|
.sdoc-editor-container .sdoc-article-container .article #sdoc-editor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* editor layout */
|
|
||||||
.main-panel-center .cur-view-content .wiki-page-gap-container {
|
|
||||||
padding: 0 50px;
|
|
||||||
padding-left: 142px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* wiki page header */
|
|
||||||
.wiki-page-controller {
|
|
||||||
display: flex;
|
|
||||||
visibility: hidden;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-page-controller.show {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-page-controller-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-left: 6px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #949491;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-page-controller-item:first-of-type {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-page-controller-item:hover {
|
|
||||||
background-color: #efefef;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-page-controller-item .text {
|
|
||||||
margin-left: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-container {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
line-height: 78px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-container.gap {
|
|
||||||
margin-top: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-wrapper {
|
|
||||||
display: none;
|
|
||||||
margin-top: -48px;
|
|
||||||
position: relative;
|
|
||||||
width: 78px;
|
|
||||||
height: 78px;
|
|
||||||
font-size: 78px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-wrapper:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #d3c5c370;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-wrapper.show {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Icon */
|
|
||||||
.wiki-icon-popover {
|
|
||||||
max-width: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-panel-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 6px 14px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-panel-header>span {
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-panel-header>span:hover {
|
|
||||||
background-color: #efefef;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-panel-body {
|
|
||||||
padding: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-icon-panel-body em-emoji-picker {
|
|
||||||
height: 337px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cover */
|
|
||||||
.cur-view-content .wiki-cover {
|
|
||||||
position: relative;
|
|
||||||
height: 30vh;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cur-view-content .wiki-cover .wiki-cover-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-cover .wiki-cover-controller {
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
right: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-cover .wiki-cover-controller .wiki-cover-controller-btn {
|
|
||||||
padding: 4px 6px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6a6767b3;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-cover .wiki-cover-controller .wiki-cover-controller-btn:hover {
|
|
||||||
color: #4d5156;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-cover-popover {
|
|
||||||
max-width: max-content !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-cover-panel-body {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
background-color: #fff;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-cover-panel .wiki-cover-gallery-img {
|
|
||||||
width: 120px;
|
|
||||||
height: 64px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.wiki-cover-panel .wiki-cover-gallery-img:hover {
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user