feat: improve ux

This commit is contained in:
fantasticit 2022-05-05 10:32:47 +08:00
parent 59b5fc3539
commit 549aff2cd6
9 changed files with 121 additions and 47 deletions

View File

@ -5,7 +5,6 @@ import { useCreateDocument } from 'data/document';
import { usePublicTemplates, useOwnTemplates } from 'data/template'; import { usePublicTemplates, useOwnTemplates } from 'data/template';
import { TemplateList } from 'components/template/list'; import { TemplateList } from 'components/template/list';
import { TemplateCardEmpty } from 'components/template/card'; import { TemplateCardEmpty } from 'components/template/card';
import styles from './index.module.scss'; import styles from './index.module.scss';
interface IProps { interface IProps {
@ -56,7 +55,7 @@ export const DocumentCreator: React.FC<IProps> = ({ wikiId, parentDocumentId, vi
width: '96vh', width: '96vh',
}} }}
bodyStyle={{ bodyStyle={{
maxHeight: 'calc(90vh - 120px)', maxHeight: 'calc(80vh - 150px)',
overflow: 'auto', overflow: 'auto',
}} }}
key={wikiId} key={wikiId}

View File

@ -32,7 +32,7 @@ const MessagesRender = ({ messageData, loading, error, onClick = null, page = 1,
return ( return (
<div <div
className={styles.itemsWrap} className={styles.itemsWrap}
style={{ margin: '8px -16px' }} style={{ margin: '8px -16px', minHeight: 224 }}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();

View File

@ -5,7 +5,7 @@ export const Placeholder = () => {
<Skeleton <Skeleton
placeholder={ placeholder={
<> <>
{Array.from({ length: 6 }).fill( {Array.from({ length: 7 }).fill(
<Skeleton.Title style={{ width: '100%', marginBottom: 12, marginTop: 12 }} /> <Skeleton.Title style={{ width: '100%', marginBottom: 12, marginTop: 12 }} />
)} )}
</> </>

View File

@ -1,19 +1,53 @@
import React from 'react'; import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { Spin } from '@douyinfe/semi-ui'; import Router from 'next/router';
import cls from 'classnames';
import { Spin, Button, Nav, Space, Typography, Tooltip, Switch, Popover, Popconfirm } from '@douyinfe/semi-ui';
import { IconChevronLeft, IconArticle } from '@douyinfe/semi-icons';
import { useUser } from 'data/user'; import { useUser } from 'data/user';
import { useTemplate } from 'data/template';
import { Seo } from 'components/seo'; import { Seo } from 'components/seo';
import { DataRender } from 'components/data-render'; import { DataRender } from 'components/data-render';
import { useTemplate } from 'data/template'; import { Theme } from 'components/theme';
import { Editor } from './editor'; import { User } from 'components/user';
import { DocumentStyle } from 'components/document/style';
import { useDocumentStyle } from 'hooks/use-document-style';
import { useWindowSize } from 'hooks/use-window-size';
import { CollaborationEditor } from 'tiptap/editor';
import styles from './index.module.scss';
interface IProps { interface IProps {
templateId: string; templateId: string;
} }
const { Text } = Typography;
export const TemplateEditor: React.FC<IProps> = ({ templateId }) => { export const TemplateEditor: React.FC<IProps> = ({ templateId }) => {
const { user } = useUser(); const { user } = useUser();
const { data, loading, error, updateTemplate, deleteTemplate } = useTemplate(templateId); const { data, loading, error, updateTemplate, deleteTemplate } = useTemplate(templateId);
const { width: windowWidth } = useWindowSize();
const [title, setTitle] = useState(data.title);
const [isPublic, setPublic] = useState(false);
const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => {
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
}, [width]);
const goback = useCallback(() => {
Router.back();
}, []);
const handleDelte = useCallback(() => {
deleteTemplate().then(() => {
goback();
});
}, [deleteTemplate, goback]);
useEffect(() => {
if (!data) return;
setPublic(data.isPublic);
}, [data]);
return ( return (
<DataRender <DataRender
loading={loading} loading={loading}
@ -27,9 +61,53 @@ export const TemplateEditor: React.FC<IProps> = ({ templateId }) => {
return ( return (
<> <>
<Seo title={data.title} /> <Seo title={data.title} />
{user && data && ( <div className={styles.wrap}>
<Editor user={user} data={data} updateTemplate={updateTemplate} deleteTemplate={deleteTemplate} /> <header>
)} <Nav
style={{ overflow: 'auto' }}
mode="horizontal"
header={
<>
<Tooltip content="返回" position="bottom">
<Button onClick={goback} icon={<IconChevronLeft />} style={{ marginRight: 16 }} />
</Tooltip>
<Text strong ellipsis={{ showTooltip: true }} style={{ width: ~~(windowWidth / 4) }}>
{title}
</Text>
</>
}
footer={
<Space>
<Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
<Button icon={<IconArticle />} theme="borderless" type="tertiary" />
</Popover>
<Tooltip position="bottom" content={isPublic ? '公开模板' : '个人模板'}>
<Switch onChange={(v) => updateTemplate({ isPublic: v })}></Switch>
</Tooltip>
<Popconfirm title="删除模板" content="模板删除后不可恢复,谨慎操作!" onConfirm={handleDelte}>
<Button type="danger"></Button>
</Popconfirm>
<Theme />
<User />
</Space>
}
></Nav>
</header>
<main className={styles.contentWrap}>
<div className={styles.editorWrap}>
<div className={cls(styles.contentWrap, editorWrapClassNames)} style={{ fontSize }}>
<CollaborationEditor
menubar
editable
user={user}
id={data.id}
type="template"
onTitleUpdate={setTitle}
/>
</div>
</div>
</main>
</div>
</> </>
); );
}} }}

View File

@ -14,26 +14,27 @@ interface IProps {
export const WikiOrDocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => { export const WikiOrDocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => {
const { isMobile } = useWindowSize(); const { isMobile } = useWindowSize();
const { wikiId, docId } = useQuery<{ wikiId?: string; docId?: string }>(); const { wikiId, docId } = useQuery<{ wikiId?: string; docId?: string }>();
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const [createDocumentModalVisible, toggleCreateDocumentModalVisible] = useToggle(false); const [createDocumentModalVisible, toggleCreateDocumentModalVisible] = useToggle(false);
return ( return (
<> <>
<Dropdown <Dropdown
trigger="click"
visible={dropdownVisible}
onVisibleChange={toggleDropdownVisible}
render={ render={
<Dropdown.Menu> // @ts-ignore
<Dropdown.Menu onClick={toggleDropdownVisible}>
<Dropdown.Item onClick={toggleVisible}></Dropdown.Item> <Dropdown.Item onClick={toggleVisible}></Dropdown.Item>
{wikiId && <Dropdown.Item onClick={toggleCreateDocumentModalVisible}></Dropdown.Item>} {wikiId && <Dropdown.Item onClick={toggleCreateDocumentModalVisible}></Dropdown.Item>}
</Dropdown.Menu> </Dropdown.Menu>
} }
> >
{children || isMobile ? ( <span onClick={toggleDropdownVisible}>
<Button type="primary" theme="solid" icon={<IconPlus />} size="small" /> {children || <Button type="primary" theme="solid" icon={<IconPlus />} size="small" />}
) : ( </span>
<Button type="primary" theme="solid" icon={<IconChevronDown />} iconPosition="right">
</Button>
)}
</Dropdown> </Dropdown>
<WikiCreator visible={visible} toggleVisible={toggleVisible} /> <WikiCreator visible={visible} toggleVisible={toggleVisible} />
{wikiId && ( {wikiId && (

View File

@ -67,6 +67,7 @@ const menus = [
export const RouterHeader: React.FC = () => { export const RouterHeader: React.FC = () => {
const { pathname } = useRouter(); const { pathname } = useRouter();
const { width, isMobile } = useWindowSize(); const { width, isMobile } = useWindowSize();
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
const [recentModalVisible, toggleRecentModalVisible] = useToggle(false); const [recentModalVisible, toggleRecentModalVisible] = useToggle(false);
const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false); const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false);
@ -82,8 +83,11 @@ export const RouterHeader: React.FC = () => {
<Dropdown <Dropdown
trigger="click" trigger="click"
position="bottomRight" position="bottomRight"
visible={dropdownVisible}
onVisibleChange={toggleDropdownVisible}
render={ render={
<Dropdown.Menu> // @ts-ignore
<Dropdown.Menu onClick={toggleDropdownVisible}>
{menus.slice(0, 1).map((menu) => { {menus.slice(0, 1).map((menu) => {
return ( return (
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}> <Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
@ -103,7 +107,7 @@ export const RouterHeader: React.FC = () => {
</Dropdown.Menu> </Dropdown.Menu>
} }
> >
<Button icon={<IconMenu />} type="tertiary" theme="borderless" /> <Button icon={<IconMenu />} type="tertiary" theme="borderless" onMouseDown={toggleDropdownVisible} />
</Dropdown> </Dropdown>
</Space> </Space>

View File

@ -84,7 +84,9 @@ export const RecentModal = ({ visible, toggleVisible }) => {
onCancel={toggleVisible} onCancel={toggleVisible}
style={{ maxWidth: '96vw' }} style={{ maxWidth: '96vw' }}
> >
<div style={{ paddingBottom: 24 }}>
<RecentDocs /> <RecentDocs />
</div>
</Modal> </Modal>
); );
}; };

View File

@ -150,7 +150,9 @@ export const WikiModal = ({ visible, toggleVisible }) => {
onCancel={toggleVisible} onCancel={toggleVisible}
style={{ maxWidth: '96vw' }} style={{ maxWidth: '96vw' }}
> >
<div style={{ paddingBottom: 24 }}>
<WikiContent /> <WikiContent />
</div>
</Modal> </Modal>
); );
}; };

View File

@ -25,27 +25,6 @@ type IProps = Pick<
status: ProviderStatus; status: ProviderStatus;
}; };
function scrollEditor(editor) {
try {
/**
*
*/
setTimeout(() => {
try {
const element = editor.options.element;
// 脏代码:这里使用 parentElement 是和布局有关的,需要根据实际情况修改
const parentElement = element.parentNode as HTMLElement;
const nextScrollTop = element.scrollHeight;
parentElement.scrollTop = nextScrollTop;
} catch (e) {
//
}
}, 0);
} catch (e) {
//
}
}
export const EditorInstance = forwardRef((props: IProps, ref) => { export const EditorInstance = forwardRef((props: IProps, ref) => {
const { hocuspocusProvider, editable, user, onTitleUpdate, status, menubar, renderInEditorPortal } = props; const { hocuspocusProvider, editable, user, onTitleUpdate, status, menubar, renderInEditorPortal } = props;
const $headerContainer = useRef<HTMLDivElement>(); const $headerContainer = useRef<HTMLDivElement>();
@ -113,6 +92,8 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
// 监听键盘收起、打开 // 监听键盘收起、打开
useEffect(() => { useEffect(() => {
if (!isMobile) return;
let cleanUp = () => {}; let cleanUp = () => {};
const focusIn = () => { const focusIn = () => {
setTimeout(() => { setTimeout(() => {
@ -149,7 +130,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
return () => { return () => {
cleanUp(); cleanUp();
}; };
}, []); }, [isMobile]);
return ( return (
<> <>
@ -159,22 +140,29 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
description="我们已与您断开连接,您可以继续编辑文档。一旦重新连接,我们会自动重新提交数据。" description="我们已与您断开连接,您可以继续编辑文档。一旦重新连接,我们会自动重新提交数据。"
/> />
)} )}
{/* FIXME需要菜单栏但是无法编辑则认为进入了编辑模式但是没有编辑权限 */}
{/* FIXME需要菜单栏但是无法编辑则认为进入了编辑模式但是没有编辑权限也许有更好的判断 */}
{!editable && menubar && ( {!editable && menubar && (
<Banner type="warning" description="您没有编辑权限,暂不能编辑该文档。" closeable={false} /> <Banner type="warning" description="您没有编辑权限,暂不能编辑该文档。" closeable={false} />
)} )}
{menubar && ( {menubar && (
<header className={cls(isMobile && styles.mobileToolbar)} ref={$headerContainer}> <header className={cls(isMobile && styles.mobileToolbar)} ref={$headerContainer}>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
</header> </header>
)} )}
<main ref={$mainContainer}> <main ref={$mainContainer}>
<EditorContent editor={editor} /> <EditorContent editor={editor} />
{protals} {protals}
</main> </main>
{editable && menubar && ( {editable && menubar && (
<BackTop target={() => $mainContainer.current} style={{ right: 16, bottom: 65 }} visibilityHeight={200} /> <BackTop
target={() => $mainContainer.current}
style={{ right: isMobile ? 16 : 100, bottom: 65 }}
visibilityHeight={200}
/>
)} )}
</> </>
); );