mirror of https://github.com/fantasticit/think.git
feat: improve ux
This commit is contained in:
parent
59b5fc3539
commit
549aff2cd6
|
@ -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}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 }} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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 && (
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,9 @@ export const RecentModal = ({ visible, toggleVisible }) => {
|
||||||
onCancel={toggleVisible}
|
onCancel={toggleVisible}
|
||||||
style={{ maxWidth: '96vw' }}
|
style={{ maxWidth: '96vw' }}
|
||||||
>
|
>
|
||||||
<RecentDocs />
|
<div style={{ paddingBottom: 24 }}>
|
||||||
|
<RecentDocs />
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -150,7 +150,9 @@ export const WikiModal = ({ visible, toggleVisible }) => {
|
||||||
onCancel={toggleVisible}
|
onCancel={toggleVisible}
|
||||||
style={{ maxWidth: '96vw' }}
|
style={{ maxWidth: '96vw' }}
|
||||||
>
|
>
|
||||||
<WikiContent />
|
<div style={{ paddingBottom: 24 }}>
|
||||||
|
<WikiContent />
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue