mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-05 17:02:47 +00:00
feat: set icon
This commit is contained in:
39
frontend/package-lock.json
generated
39
frontend/package-lock.json
generated
@@ -11,6 +11,8 @@
|
|||||||
"@codemirror/lang-markdown": "6.2.3",
|
"@codemirror/lang-markdown": "6.2.3",
|
||||||
"@codemirror/language-data": "6.3.1",
|
"@codemirror/language-data": "6.3.1",
|
||||||
"@codemirror/view": "6.22.1",
|
"@codemirror/view": "6.22.1",
|
||||||
|
"@emoji-mart/data": "^1.2.1",
|
||||||
|
"@emoji-mart/react": "^1.1.1",
|
||||||
"@gatsbyjs/reach-router": "1.3.9",
|
"@gatsbyjs/reach-router": "1.3.9",
|
||||||
"@seafile/react-image-lightbox": "2.0.2",
|
"@seafile/react-image-lightbox": "2.0.2",
|
||||||
"@seafile/resumablejs": "1.1.16",
|
"@seafile/resumablejs": "1.1.16",
|
||||||
@@ -26,6 +28,8 @@
|
|||||||
"copy-to-clipboard": "^3.0.8",
|
"copy-to-clipboard": "^3.0.8",
|
||||||
"crypto-js": "4.2.0",
|
"crypto-js": "4.2.0",
|
||||||
"deep-copy": "1.4.2",
|
"deep-copy": "1.4.2",
|
||||||
|
"emoji-mart": "^5.6.0",
|
||||||
|
"glamor": "^2.20.40",
|
||||||
"i18next": "^17.0.13",
|
"i18next": "^17.0.13",
|
||||||
"i18next-browser-languagedetector": "^3.0.3",
|
"i18next-browser-languagedetector": "^3.0.3",
|
||||||
"i18next-xhr-backend": "^3.1.2",
|
"i18next-xhr-backend": "^3.1.2",
|
||||||
@@ -2820,6 +2824,20 @@
|
|||||||
"postcss-selector-parser": "^6.0.10"
|
"postcss-selector-parser": "^6.0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emoji-mart/data": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw=="
|
||||||
|
},
|
||||||
|
"node_modules/@emoji-mart/react": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"emoji-mart": "^5.2",
|
||||||
|
"react": "^16.8 || ^17 || ^18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emotion/babel-plugin": {
|
"node_modules/@emotion/babel-plugin": {
|
||||||
"version": "11.11.0",
|
"version": "11.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
||||||
@@ -10374,6 +10392,11 @@
|
|||||||
"url": "https://github.com/sindresorhus/emittery?sponsor=1"
|
"url": "https://github.com/sindresorhus/emittery?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/emoji-mart": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow=="
|
||||||
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
@@ -30527,6 +30550,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"@emoji-mart/data": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw=="
|
||||||
|
},
|
||||||
|
"@emoji-mart/react": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@emotion/babel-plugin": {
|
"@emotion/babel-plugin": {
|
||||||
"version": "11.11.0",
|
"version": "11.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
||||||
@@ -36409,6 +36443,11 @@
|
|||||||
"integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==",
|
"integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"emoji-mart": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow=="
|
||||||
|
},
|
||||||
"emoji-regex": {
|
"emoji-regex": {
|
||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
"@codemirror/lang-markdown": "6.2.3",
|
"@codemirror/lang-markdown": "6.2.3",
|
||||||
"@codemirror/language-data": "6.3.1",
|
"@codemirror/language-data": "6.3.1",
|
||||||
"@codemirror/view": "6.22.1",
|
"@codemirror/view": "6.22.1",
|
||||||
|
"@emoji-mart/data": "^1.2.1",
|
||||||
|
"@emoji-mart/react": "^1.1.1",
|
||||||
"@gatsbyjs/reach-router": "1.3.9",
|
"@gatsbyjs/reach-router": "1.3.9",
|
||||||
"@seafile/react-image-lightbox": "2.0.2",
|
"@seafile/react-image-lightbox": "2.0.2",
|
||||||
"@seafile/resumablejs": "1.1.16",
|
"@seafile/resumablejs": "1.1.16",
|
||||||
@@ -21,6 +23,8 @@
|
|||||||
"copy-to-clipboard": "^3.0.8",
|
"copy-to-clipboard": "^3.0.8",
|
||||||
"crypto-js": "4.2.0",
|
"crypto-js": "4.2.0",
|
||||||
"deep-copy": "1.4.2",
|
"deep-copy": "1.4.2",
|
||||||
|
"emoji-mart": "^5.6.0",
|
||||||
|
"glamor": "^2.20.40",
|
||||||
"i18next": "^17.0.13",
|
"i18next": "^17.0.13",
|
||||||
"i18next-browser-languagedetector": "^3.0.3",
|
"i18next-browser-languagedetector": "^3.0.3",
|
||||||
"i18next-xhr-backend": "^3.1.2",
|
"i18next-xhr-backend": "^3.1.2",
|
||||||
|
165
frontend/src/pages/wiki2/editor-component/page-header.jsx
Normal file
165
frontend/src/pages/wiki2/editor-component/page-header.jsx
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Input, Popover, PopoverBody } from 'reactstrap';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import data from '@emoji-mart/data';
|
||||||
|
import Picker from '@emoji-mart/react';
|
||||||
|
import { init } from 'emoji-mart';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
|
||||||
|
init({ data }); // init data for emoji-mart, used in Picker and `getRandomEmoji` method
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
currentPageConfig: PropTypes.object.isRequired,
|
||||||
|
onUpdatePage: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PageHeader = ({ currentPageConfig, onUpdatePage }) => {
|
||||||
|
const [isShowController, setIsShowController] = useState(false);
|
||||||
|
const [isShowIconPanel, setIsShowIconPanel] = useState(false);
|
||||||
|
const iconPanelPopoverRef = useRef(null);
|
||||||
|
|
||||||
|
console.log('currentPageConfig', currentPageConfig);
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
setIsShowController(!isShowController);
|
||||||
|
}, [isShowController]);
|
||||||
|
|
||||||
|
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 handleIconPanelDisplayedChange = useCallback(() => {
|
||||||
|
setIsShowIconPanel(!isShowIconPanel);
|
||||||
|
}, [isShowIconPanel]);
|
||||||
|
|
||||||
|
const handleClickAddIcon = useCallback(() => {
|
||||||
|
setRandomEmoji();
|
||||||
|
handleIconPanelDisplayedChange();
|
||||||
|
}, [handleIconPanelDisplayedChange, setRandomEmoji]);
|
||||||
|
|
||||||
|
const handleIconPanelListener = useCallback((e) => {
|
||||||
|
const isClickInPopover = iconPanelPopoverRef.current.contains(e.target);
|
||||||
|
if (!isClickInPopover) handleIconPanelDisplayedChange();
|
||||||
|
}, [handleIconPanelDisplayedChange]);
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isShowIconPanel) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Avoid open behavior closing popover
|
||||||
|
addEventListener('click', handleIconPanelListener);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
if (!isShowIconPanel) removeEventListener('click', handleIconPanelListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeEventListener('click', handleIconPanelListener);
|
||||||
|
};
|
||||||
|
}, [handleIconPanelListener, isShowIconPanel]);
|
||||||
|
|
||||||
|
const handleIconRemove = () => {
|
||||||
|
handleSetIcon('');
|
||||||
|
handleIconPanelDisplayedChange();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddCover = useCallback(() => { }, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='wiki-page-header-wrapper'
|
||||||
|
>
|
||||||
|
<div className='wiki-cover'></div>
|
||||||
|
<div className='wiki-page-gap-container'>
|
||||||
|
<div
|
||||||
|
className='wiki-editor-header'
|
||||||
|
onMouseEnter={changeControllerDisplayed}
|
||||||
|
onMouseLeave={changeControllerDisplayed}
|
||||||
|
>
|
||||||
|
|
||||||
|
<div className='wiki-icon-container'>
|
||||||
|
<div className={classnames('wiki-icon-wrapper', { show: currentPageConfig.icon })} id='wiki-icon-wrapper' onClick={handleIconPanelDisplayedChange}>
|
||||||
|
<span>{currentPageConfig.icon}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Popover
|
||||||
|
flip
|
||||||
|
target="wiki-icon-wrapper"
|
||||||
|
toggle={() => void 0}
|
||||||
|
placement="bottom"
|
||||||
|
isOpen={currentPageConfig.icon && isShowIconPanel}
|
||||||
|
innerClassName='wiki-icon-panel'
|
||||||
|
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)}
|
||||||
|
previewPosition="none"
|
||||||
|
skinTonePosition="none"
|
||||||
|
locale={window.seafile.lang.slice(0, 2)}
|
||||||
|
maxFrequentRows={2}
|
||||||
|
/>
|
||||||
|
</PopoverBody>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
<div className={classnames('wiki-page-controller', { show: isShowController })}>
|
||||||
|
{!currentPageConfig.icon && (
|
||||||
|
<div className='wiki-page-controller-item' onClick={handleClickAddIcon}>
|
||||||
|
<i className='fa fa-save'></i>
|
||||||
|
<span className='text'>{gettext('Add icon')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className='wiki-page-controller-item'>
|
||||||
|
<i className='fa fa-save'></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;
|
@@ -1,13 +1,13 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { SdocWikiViewer } from '@seafile/sdoc-editor';
|
import { SdocWikiViewer } from '@seafile/sdoc-editor';
|
||||||
import { Input } from 'reactstrap';
|
|
||||||
import { gettext, username } from '../../utils/constants';
|
import { gettext, username } 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';
|
||||||
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';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
@@ -32,6 +32,7 @@ class MainPanel extends Component {
|
|||||||
docUuid: '',
|
docUuid: '',
|
||||||
currentPageConfig: {},
|
currentPageConfig: {},
|
||||||
};
|
};
|
||||||
|
this.scrollRef = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
@@ -53,20 +54,10 @@ class MainPanel extends Component {
|
|||||||
return { ...props, docUuid: window.seafile.docUuid, currentPageConfig };
|
return { ...props, docUuid: window.seafile.docUuid, currentPageConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRenameDocument = (e) => {
|
|
||||||
const { nativeEvent: { isComposing } } = e;
|
|
||||||
if (isComposing) return;
|
|
||||||
|
|
||||||
const newName = e.target.value.trim();
|
|
||||||
const { currentPageConfig } = this.state;
|
|
||||||
const { id, name, icon } = currentPageConfig;
|
|
||||||
if (newName === name) return;
|
|
||||||
const pageConfig = { name: newName, icon };
|
|
||||||
this.props.onUpdatePage(id, pageConfig);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { permission, pathExist, isDataLoading, isViewFile, config } = this.props;
|
const { permission, pathExist, isDataLoading, isViewFile, config, onUpdatePage } = this.props;
|
||||||
const { currentPageConfig = {}, } = this.state;
|
const { currentPageConfig = {}, } = this.state;
|
||||||
const isViewingFile = pathExist && !isDataLoading && isViewFile;
|
const isViewingFile = pathExist && !isDataLoading && isViewFile;
|
||||||
const isReadOnly = !(permission === 'rw');
|
const isReadOnly = !(permission === 'rw');
|
||||||
@@ -77,6 +68,7 @@ class MainPanel extends Component {
|
|||||||
<WikiTopNav
|
<WikiTopNav
|
||||||
config={config}
|
config={config}
|
||||||
currentPageId={this.props.currentPageId}
|
currentPageId={this.props.currentPageId}
|
||||||
|
currentPageConfig={currentPageConfig}
|
||||||
/>
|
/>
|
||||||
{username &&
|
{username &&
|
||||||
<Account />
|
<Account />
|
||||||
@@ -89,14 +81,17 @@ class MainPanel extends Component {
|
|||||||
}
|
}
|
||||||
{this.props.pathExist && this.props.isDataLoading && <Loading />}
|
{this.props.pathExist && this.props.isDataLoading && <Loading />}
|
||||||
{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='wiki-editor-container'>
|
||||||
|
<PageHeader onUpdatePage={onUpdatePage} currentPageConfig={currentPageConfig} />
|
||||||
<SdocWikiViewer
|
<SdocWikiViewer
|
||||||
document={this.props.editorContent}
|
document={this.props.editorContent}
|
||||||
docUuid={this.state.docUuid}
|
docUuid={this.state.docUuid}
|
||||||
isWikiReadOnly={isReadOnly}
|
isWikiReadOnly={isReadOnly}
|
||||||
topSlot={<Input className='sf-wiki-title' onCompositionEnd={this.handleRenameDocument} bsSize="lg" onChange={this.handleRenameDocument} defaultValue={currentPageConfig.name} />}
|
scrollRef={this.scrollRef}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -37,16 +37,26 @@ function getPaths(navigation, currentPageId, pages) {
|
|||||||
return pathStr.split('-').map(id => idPageMap[id]);
|
return pathStr.split('-').map(id => idPageMap[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WikiTopNav({ config, currentPageId }) {
|
function WikiTopNav({ config, currentPageId, currentPageConfig }) {
|
||||||
const { navigation, pages } = config;
|
const { navigation, pages } = config;
|
||||||
const paths = getPaths(navigation, currentPageId, pages);
|
const paths = getPaths(navigation, currentPageId, pages);
|
||||||
|
const customIcon = currentPageConfig.icon;
|
||||||
return (
|
return (
|
||||||
<div className="wiki2-top-nav d-flex">
|
<div className="wiki2-top-nav d-flex">
|
||||||
{paths.map((item, index) => {
|
{paths.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
<div className='wiki2-top-nav-item d-flex'>
|
<div className='wiki2-top-nav-item d-flex'>
|
||||||
<NavItemIcon symbol={'file'} disable={true} />
|
{
|
||||||
|
item.type === 'folder' && (<NavItemIcon symbol={'wiki-folder'} disable={true} />)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
item.type !== 'folder' && (
|
||||||
|
customIcon
|
||||||
|
? <span className='nav-item-icon nav-item-icon-disable'>{customIcon}</span>
|
||||||
|
: <NavItemIcon symbol={'file'} disable={true} />
|
||||||
|
)
|
||||||
|
}
|
||||||
{item.name}
|
{item.name}
|
||||||
</div>
|
</div>
|
||||||
{index !== paths.length - 1 && <div>/</div>}
|
{index !== paths.length - 1 && <div>/</div>}
|
||||||
@@ -60,6 +70,7 @@ function WikiTopNav({ config, currentPageId }) {
|
|||||||
WikiTopNav.propTypes = {
|
WikiTopNav.propTypes = {
|
||||||
config: PropTypes.object,
|
config: PropTypes.object,
|
||||||
currentPageId: PropTypes.string,
|
currentPageId: PropTypes.string,
|
||||||
|
currentPageConfig: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WikiTopNav;
|
export default WikiTopNav;
|
||||||
|
352
frontend/src/pages/wiki2/view-structure/views/view-item.js
Normal file
352
frontend/src/pages/wiki2/view-structure/views/view-item.js
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import ViewEditPopover from './view-edit-popover';
|
||||||
|
import PageDropdownMenu from './page-dropdownmenu';
|
||||||
|
import DeleteDialog from './delete-dialog';
|
||||||
|
import { gettext } from '../../../../utils/constants';
|
||||||
|
import AddNewPageDialog from '../add-new-page-dialog';
|
||||||
|
import Icon from '../../../../components/icon';
|
||||||
|
import NavItemIcon from '../nav-item-icon';
|
||||||
|
import DraggedViewItem from '../views/dragged-view-item';
|
||||||
|
|
||||||
|
class ViewItem extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isShowViewEditor: false,
|
||||||
|
isShowViewOperationDropdown: false,
|
||||||
|
isShowDeleteDialog: false,
|
||||||
|
isShowInsertPage: false,
|
||||||
|
viewName: props.view.name || '',
|
||||||
|
viewIcon: props.view.icon,
|
||||||
|
isSelected: props.currentPageId === props.view.id,
|
||||||
|
isMouseEnter: false,
|
||||||
|
};
|
||||||
|
this.viewItemRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
this.setState({ isMouseEnter: true });
|
||||||
|
if (this.state.isSelected) return;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseMove = () => {
|
||||||
|
if (!this.state.isMouseEnter) this.setState({ isMouseEnter: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
this.setState({ isMouseEnter: false });
|
||||||
|
if (this.state.isSelected) return;
|
||||||
|
};
|
||||||
|
|
||||||
|
onCurrentPageChanged = (currentPageId) => {
|
||||||
|
const { isSelected } = this.state;
|
||||||
|
if (currentPageId === this.props.view.id && isSelected === false) {
|
||||||
|
this.setState({ isSelected: true });
|
||||||
|
} else if (currentPageId !== this.props.view.id && isSelected === true) {
|
||||||
|
this.setState({ isSelected: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleViewEditor = (e) => {
|
||||||
|
if (e) e.stopPropagation();
|
||||||
|
this.setState({ isShowViewEditor: !this.state.isShowViewEditor }, () => {
|
||||||
|
if (!this.state.isShowViewEditor) {
|
||||||
|
this.saveViewProperties();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleInsertPage = () => {
|
||||||
|
this.setState({ isShowInsertPage: !this.state.isShowInsertPage });
|
||||||
|
};
|
||||||
|
|
||||||
|
saveViewProperties = () => {
|
||||||
|
const { name, icon, id } = this.props.view;
|
||||||
|
const { viewIcon } = this.state;
|
||||||
|
let viewName = this.state.viewName.trim();
|
||||||
|
if (viewIcon !== icon || viewName !== name) {
|
||||||
|
let newView = {};
|
||||||
|
if (viewName !== name) {
|
||||||
|
newView.name = viewName;
|
||||||
|
}
|
||||||
|
if (viewIcon !== icon) {
|
||||||
|
newView.icon = viewIcon;
|
||||||
|
}
|
||||||
|
this.props.onUpdatePage(id, newView);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeName = (newViewName) => {
|
||||||
|
this.setState({ viewName: newViewName });
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeIcon = (newViewIcon) => {
|
||||||
|
this.setState({ viewIcon: newViewIcon });
|
||||||
|
};
|
||||||
|
|
||||||
|
openDeleteDialog = () => {
|
||||||
|
this.setState({ isShowDeleteDialog: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
closeDeleteDialog = () => {
|
||||||
|
this.setState({ isShowDeleteDialog: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
onViewOperationDropdownToggle = () => {
|
||||||
|
const isShowViewOperationDropdown = !this.state.isShowViewOperationDropdown;
|
||||||
|
this.setState({ isShowViewOperationDropdown });
|
||||||
|
this.changeItemFreeze(isShowViewOperationDropdown);
|
||||||
|
};
|
||||||
|
|
||||||
|
changeItemFreeze = (isFreeze) => {
|
||||||
|
if (isFreeze) {
|
||||||
|
this.viewItemRef.classList.add('view-freezed');
|
||||||
|
} else {
|
||||||
|
this.viewItemRef.classList.remove('view-freezed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderIcon = (icon) => {
|
||||||
|
if (!icon) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (icon.includes('dtable-icon')) {
|
||||||
|
return <span className={`mr-2 dtable-font ${icon}`}></span>;
|
||||||
|
} else {
|
||||||
|
return <Icon className="mr-2" symbol={icon} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setDocUuid = (docUuid) => {
|
||||||
|
window.seafile['docUuid'] = docUuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
getFolderChildrenHeight = () => {
|
||||||
|
const folded = this.props.getFoldState(this.props.view.id);
|
||||||
|
if (folded) return 0;
|
||||||
|
return 'auto';
|
||||||
|
};
|
||||||
|
|
||||||
|
onClickFolderChildren = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
renderView = (view, index, pagesLength, isOnlyOneView) => {
|
||||||
|
const { isEditMode, views, folderId, pathStr } = this.props;
|
||||||
|
const id = view.id;
|
||||||
|
if (!views.find(item => item.id === id)) return;
|
||||||
|
return (
|
||||||
|
<DraggedViewItem
|
||||||
|
key={id}
|
||||||
|
pagesLength={pagesLength}
|
||||||
|
isOnlyOneView={isOnlyOneView}
|
||||||
|
infolder={false}
|
||||||
|
view={Object.assign({}, views.find(item => item.id === id), view)}
|
||||||
|
viewIndex={index}
|
||||||
|
folderId={folderId}
|
||||||
|
isEditMode={isEditMode}
|
||||||
|
renderFolderMenuItems={this.props.renderFolderMenuItems}
|
||||||
|
duplicatePage={this.props.duplicatePage}
|
||||||
|
onSetFolderId={this.props.onSetFolderId}
|
||||||
|
onSelectView={this.props.onSelectView}
|
||||||
|
onUpdatePage={this.props.onUpdatePage}
|
||||||
|
onDeleteView={this.props.onDeleteView}
|
||||||
|
onMoveViewToFolder={(targetFolderId) => {
|
||||||
|
this.props.onMoveViewToFolder(folderId, view.id, targetFolderId);
|
||||||
|
}}
|
||||||
|
onMoveView={this.props.onMoveView}
|
||||||
|
onMoveFolder={this.props.onMoveFolder}
|
||||||
|
views={views}
|
||||||
|
pathStr={pathStr + '-' + view.id}
|
||||||
|
currentPageId={this.props.currentPageId}
|
||||||
|
addPageInside={this.props.addPageInside}
|
||||||
|
getFoldState={this.props.getFoldState}
|
||||||
|
toggleExpand={this.props.toggleExpand}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleExpand = (e) => {
|
||||||
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
|
this.props.toggleExpand(this.props.view.id);
|
||||||
|
this.forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
onAddNewPage = (newPage) => {
|
||||||
|
const { view } = this.props;
|
||||||
|
this.props.addPageInside(Object.assign({ parentPageId: view.id }, newPage));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
connectDragSource, connectDragPreview, connectDropTarget, isOver, canDrop, isDragging,
|
||||||
|
infolder, view, pagesLength, isEditMode, folderId, isOnlyOneView, pathStr,
|
||||||
|
} = this.props;
|
||||||
|
const { isShowViewEditor, viewName, viewIcon, isSelected } = this.state;
|
||||||
|
const isOverView = isOver && canDrop;
|
||||||
|
if (isSelected) this.setDocUuid(view.docUuid);
|
||||||
|
|
||||||
|
let viewCanDropTop;
|
||||||
|
let viewCanDrop;
|
||||||
|
if (infolder) {
|
||||||
|
viewCanDropTop = false;
|
||||||
|
viewCanDrop = isOverView;
|
||||||
|
} else {
|
||||||
|
viewCanDropTop = isOverView && isDragging;
|
||||||
|
viewCanDrop = isOverView && !isDragging;
|
||||||
|
}
|
||||||
|
let viewEditorId = `view-editor-${view.id}`;
|
||||||
|
let fn = isEditMode ? connectDragSource : (argu) => { argu; };
|
||||||
|
let childNumber = Array.isArray(view.children) ? view.children.length : 0;
|
||||||
|
|
||||||
|
const folded = this.props.getFoldState(view.id);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
fn(connectDropTarget(
|
||||||
|
connectDragPreview(
|
||||||
|
<div
|
||||||
|
className={classnames('view-item', 'view',
|
||||||
|
{ 'selected-view': isSelected },
|
||||||
|
{ 'view-can-drop-top': viewCanDropTop },
|
||||||
|
{ 'view-can-drop': viewCanDrop },
|
||||||
|
{ 'readonly': !isEditMode },
|
||||||
|
)}
|
||||||
|
ref={ref => this.viewItemRef = ref}
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseMove={this.onMouseMove}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
id={viewEditorId}
|
||||||
|
>
|
||||||
|
<div className="view-item-main" onClick={isShowViewEditor ? () => { } : (e) => this.props.onSelectView(view.id)}>
|
||||||
|
<div className='view-content' style={pathStr ? { marginLeft: `${(pathStr.split('-').length - 1) * 24}px` } : {}}>
|
||||||
|
{childNumber === 0 && (
|
||||||
|
view.icon
|
||||||
|
? <span className='nav-item-icon nav-item-icon-disable'>{view.icon}</span>
|
||||||
|
: <NavItemIcon symbol={'file'} disable={true} />)
|
||||||
|
}
|
||||||
|
{(!this.state.isMouseEnter && childNumber > 0) &&
|
||||||
|
<NavItemIcon symbol={'files'} disable={true} />
|
||||||
|
}
|
||||||
|
{(this.state.isMouseEnter && childNumber > 0) &&
|
||||||
|
<NavItemIcon
|
||||||
|
className="icon-expand-folder"
|
||||||
|
symbol={folded ? 'right-slide' : 'drop-down'}
|
||||||
|
onClick={this.toggleExpand}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{/* {this.renderIcon(view.icon)} */}
|
||||||
|
<span className="view-title text-truncate" title={view.name}>{view.name}</span>
|
||||||
|
{isShowViewEditor && (
|
||||||
|
<ViewEditPopover
|
||||||
|
viewName={viewName}
|
||||||
|
viewIcon={viewIcon}
|
||||||
|
viewEditorId={viewEditorId}
|
||||||
|
onChangeName={this.onChangeName}
|
||||||
|
onChangeIcon={this.onChangeIcon}
|
||||||
|
toggleViewEditor={this.toggleViewEditor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex">
|
||||||
|
{isEditMode &&
|
||||||
|
<>
|
||||||
|
<div className="more-view-operation" onClick={this.onViewOperationDropdownToggle}>
|
||||||
|
<Icon symbol={'more-level'} />
|
||||||
|
{this.state.isShowViewOperationDropdown &&
|
||||||
|
<PageDropdownMenu
|
||||||
|
view={view}
|
||||||
|
views={this.props.views}
|
||||||
|
pagesLength={pagesLength}
|
||||||
|
isOnlyOneView={isOnlyOneView}
|
||||||
|
folderId={folderId}
|
||||||
|
canDelete={true}
|
||||||
|
canDuplicate={true}
|
||||||
|
toggle={this.onViewOperationDropdownToggle}
|
||||||
|
renderFolderMenuItems={this.props.renderFolderMenuItems}
|
||||||
|
toggleViewEditor={this.toggleViewEditor}
|
||||||
|
duplicatePage={this.props.duplicatePage}
|
||||||
|
onSetFolderId={this.props.onSetFolderId}
|
||||||
|
onDeleteView={this.openDeleteDialog}
|
||||||
|
onMoveViewToFolder={this.props.onMoveViewToFolder}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="ml-2" onClick={this.toggleInsertPage}>
|
||||||
|
<span className='fas fa-plus'></span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{this.state.isShowDeleteDialog &&
|
||||||
|
<DeleteDialog
|
||||||
|
closeDeleteDialog={this.closeDeleteDialog}
|
||||||
|
handleSubmit={this.props.onDeleteView.bind(this, view.id)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{this.state.isShowInsertPage &&
|
||||||
|
<AddNewPageDialog
|
||||||
|
toggle={this.toggleInsertPage}
|
||||||
|
onAddNewPage={this.onAddNewPage}
|
||||||
|
title={gettext('Add page inside')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
className="view-folder-children"
|
||||||
|
style={{ height: this.getFolderChildrenHeight() }}
|
||||||
|
onClick={this.onClickFolderChildren}
|
||||||
|
>
|
||||||
|
{view.children &&
|
||||||
|
view.children.map((item, index) => {
|
||||||
|
return this.renderView(item, index, pagesLength, isOnlyOneView);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewItem.propTypes = {
|
||||||
|
isOver: PropTypes.bool,
|
||||||
|
canDrop: PropTypes.bool,
|
||||||
|
isDragging: PropTypes.bool,
|
||||||
|
draggedPage: PropTypes.object,
|
||||||
|
isEditMode: PropTypes.bool,
|
||||||
|
infolder: PropTypes.bool,
|
||||||
|
view: PropTypes.object,
|
||||||
|
folder: PropTypes.object,
|
||||||
|
views: PropTypes.array,
|
||||||
|
viewIndex: PropTypes.number,
|
||||||
|
folderId: PropTypes.string,
|
||||||
|
pagesLength: PropTypes.number,
|
||||||
|
connectDragSource: PropTypes.func,
|
||||||
|
connectDragPreview: PropTypes.func,
|
||||||
|
connectDropTarget: PropTypes.func,
|
||||||
|
renderFolderMenuItems: PropTypes.func,
|
||||||
|
duplicatePage: PropTypes.func,
|
||||||
|
onSetFolderId: PropTypes.func,
|
||||||
|
onSelectView: PropTypes.func,
|
||||||
|
onUpdatePage: PropTypes.func,
|
||||||
|
onDeleteView: PropTypes.func,
|
||||||
|
onMoveViewToFolder: PropTypes.func,
|
||||||
|
onMoveView: PropTypes.func,
|
||||||
|
isOnlyOneView: PropTypes.bool,
|
||||||
|
onMoveFolder: PropTypes.func,
|
||||||
|
pathStr: PropTypes.string,
|
||||||
|
currentPageId: PropTypes.string,
|
||||||
|
addPageInside: PropTypes.func,
|
||||||
|
getFoldState: PropTypes.func,
|
||||||
|
toggleExpand: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewItem;
|
@@ -70,6 +70,10 @@ img[src=""] {
|
|||||||
color: #212529;
|
color: #212529;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-panel-center .cur-view-content .sf-wiki-title:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* reset article h1 */
|
/* reset article h1 */
|
||||||
.wiki2-main-panel .article h1 {
|
.wiki2-main-panel .article h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
@@ -141,21 +145,6 @@ img[src=""] {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sdoc-editor-container .wiki-editor-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px 20px;
|
|
||||||
height: 56px;
|
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
background-color: #fff;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sdoc-editor-container .wiki-editor-header .doc-ops {
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sdoc-editor-container .wiki-viewer-container {
|
.sdoc-editor-container .wiki-viewer-container {
|
||||||
@@ -243,3 +232,117 @@ img[src=""] {
|
|||||||
.sdoc-editor-container .sdoc-editor-content .article #sdoc-editor {
|
.sdoc-editor-container .sdoc-editor-content .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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-panel-center .cur-view-content .sf-wiki-title {
|
||||||
|
padding-left: 0;
|
||||||
|
border: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 26pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-view-content .wiki-cover {
|
||||||
|
height: 30vh;
|
||||||
|
width: 100%;
|
||||||
|
background-color: aqua;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-view-content .wiki-editor-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-view-content .sdoc-scroll-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-view-content .sdoc-editor-container .sdoc-editor-content {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller {
|
||||||
|
display: flex;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller.show {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-icon-container {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-icon-wrapper {
|
||||||
|
display: none;
|
||||||
|
margin-top: -48px;
|
||||||
|
width: 78px;
|
||||||
|
height: 78px;
|
||||||
|
font-size: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-icon-wrapper:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #d3c5c370;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-icon-wrapper.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller-item .text {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-page-controller-item:hover {
|
||||||
|
background-color: #efefef;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-icon-panel {
|
||||||
|
width: 352px;
|
||||||
|
height: 435px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
width: 352px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-icon-panel-body div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
Reference in New Issue
Block a user