chore: format code

This commit is contained in:
fantasticit 2022-03-12 11:27:56 +08:00
parent f629eedbb0
commit 6c716b0dc2
225 changed files with 2624 additions and 3979 deletions

View File

@ -20,11 +20,7 @@
"pm2": "concurrently \"pnpm:pm2:*\"", "pm2": "concurrently \"pnpm:pm2:*\"",
"pm2:server": "pnpm run --dir packages/server pm2", "pm2:server": "pnpm run --dir packages/server pm2",
"pm2:client": "pnpm run --dir packages/client pm2", "pm2:client": "pnpm run --dir packages/client pm2",
"lint": "eslint . -c ./.eslintrc.js --fix --quiet 'packages/**/*.{ts,tsx,js,jsx}'", "format": "prettier --write --parser typescript \"packages/**/*.ts?(x)\""
"format": "npm run format:md && npm run format:json && npm run format:source",
"format:md": "prettier --parser markdown --write './**/*.md'",
"format:json": "prettier --parser json --write './**/*.json'",
"format:source": "prettier --write './**/*.{js,ts}'"
}, },
"dependencies": { "dependencies": {
"concurrently": "^7.0.0", "concurrently": "^7.0.0",
@ -36,7 +32,18 @@
"node": ">=16.5.0" "node": ">=16.5.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.11.0",
"eslint-config-next": "12.0.10",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.3",
"eslint-plugin-react-hooks": "^4.3.0",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"typescript": "^4.5.5" "typescript": "^4.5.5"
} }
} }

View File

@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}

View File

@ -6,7 +6,6 @@
"prebuild": "rimraf .next", "prebuild": "rimraf .next",
"build": "next build", "build": "next build",
"start": "cross-env NODE_ENV=production next start -p 5002", "start": "cross-env NODE_ENV=production next start -p 5002",
"lint": "next lint",
"pm2": "pm2 start npm --name @think/client -- start" "pm2": "pm2 start npm --name @think/client -- start"
}, },
"dependencies": { "dependencies": {
@ -74,9 +73,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "17.0.13", "@types/node": "17.0.13",
"@types/react": "17.0.38", "@types/react": "17.0.38",
"eslint": "8.8.0",
"eslint-config-next": "12.0.10",
"tsconfig-paths-webpack-plugin": "^3.5.2", "tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "4.5.5" "typescript": "4.5.5"
} }
} }

View File

@ -1,18 +1,16 @@
import { Space, Typography } from "@douyinfe/semi-ui"; import { Space, Typography } from '@douyinfe/semi-ui';
import { IconLikeHeart } from "@douyinfe/semi-icons"; import { IconLikeHeart } from '@douyinfe/semi-icons';
const { Text } = Typography; const { Text } = Typography;
export const Author = () => { export const Author = () => {
return ( return (
<div style={{ padding: "16px 0", textAlign: "center" }}> <div style={{ padding: '16px 0', textAlign: 'center' }}>
<Text> <Text>
<Space> <Space>
Develop by Develop by
<Text link={{ href: "https://github.com/fantasticit/think" }}> <Text link={{ href: 'https://github.com/fantasticit/think' }}>fantasticit</Text>
fantasticit with <IconLikeHeart style={{ color: 'red' }} />
</Text>
with <IconLikeHeart style={{ color: "red" }} />
</Space> </Space>
</Text> </Text>
</div> </div>

View File

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { Empty, Spin, Typography } from "@douyinfe/semi-ui"; import { Empty, Spin, Typography } from '@douyinfe/semi-ui';
type RenderProps = React.ReactNode | (() => React.ReactNode); type RenderProps = React.ReactNode | (() => React.ReactNode);
@ -18,11 +18,10 @@ const defaultLoading = () => {
}; };
const defaultRenderError = (error) => { const defaultRenderError = (error) => {
return <Text>{(error && error.message) || "未知错误"}</Text>; return <Text>{(error && error.message) || '未知错误'}</Text>;
}; };
const runRender = (fn, ...args) => const runRender = (fn, ...args) => (typeof fn === 'function' ? fn.apply(null, args) : fn);
typeof fn === "function" ? fn.apply(null, args) : fn;
export const DataRender: React.FC<IProps> = ({ export const DataRender: React.FC<IProps> = ({
loading, loading,

View File

@ -1,17 +1,14 @@
import React from "react"; import React from 'react';
import { Button } from "@douyinfe/semi-ui"; import { Button } from '@douyinfe/semi-ui';
import { useToggle } from "hooks/useToggle"; import { useToggle } from 'hooks/useToggle';
import { useQuery } from "hooks/useQuery"; import { useQuery } from 'hooks/useQuery';
import { DocumentCreator as DocumenCreatorForm } from "components/document/create"; import { DocumentCreator as DocumenCreatorForm } from 'components/document/create';
interface IProps { interface IProps {
onCreateDocument?: () => void; onCreateDocument?: () => void;
} }
export const DocumentCreator: React.FC<IProps> = ({ export const DocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => {
onCreateDocument,
children,
}) => {
const { wikiId, docId } = useQuery<{ wikiId?: string; docId?: string }>(); const { wikiId, docId } = useQuery<{ wikiId?: string; docId?: string }>();
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);

View File

@ -1,11 +1,11 @@
import React, { useCallback } from "react"; import React, { useCallback } from 'react';
import { Dropdown, Button, Typography, Space } from "@douyinfe/semi-ui"; import { Dropdown, Button, Typography, Space } from '@douyinfe/semi-ui';
import { IconMore, IconStar, IconPlus } from "@douyinfe/semi-icons"; import { IconMore, IconStar, IconPlus } from '@douyinfe/semi-icons';
import { DocumentLinkCopyer } from "components/document/link"; import { DocumentLinkCopyer } from 'components/document/link';
import { DocumentDeletor } from "components/document/delete"; import { DocumentDeletor } from 'components/document/delete';
import { DocumentCreator } from "components/document/create"; import { DocumentCreator } from 'components/document/create';
import { DocumentStar } from "components/document/star"; import { DocumentStar } from 'components/document/star';
import { useToggle } from "hooks/useToggle"; import { useToggle } from 'hooks/useToggle';
interface IProps { interface IProps {
wikiId: string; wikiId: string;
@ -65,8 +65,8 @@ export const DocumentActions: React.FC<IProps> = ({
<IconStar <IconStar
style={{ style={{
color: star color: star
? "rgba(var(--semi-amber-4), 1)" ? 'rgba(var(--semi-amber-4), 1)'
: "rgba(var(--semi-grey-3), 1)", : 'rgba(var(--semi-grey-3), 1)',
}} }}
/> />
{text} {text}
@ -80,22 +80,13 @@ export const DocumentActions: React.FC<IProps> = ({
</Dropdown.Item> </Dropdown.Item>
<Dropdown.Divider /> <Dropdown.Divider />
<Dropdown.Item onClick={prevent}> <Dropdown.Item onClick={prevent}>
<DocumentDeletor <DocumentDeletor wikiId={wikiId} documentId={documentId} onDelete={onDelete} />
wikiId={wikiId}
documentId={documentId}
onDelete={onDelete}
/>
</Dropdown.Item> </Dropdown.Item>
</Dropdown.Menu> </Dropdown.Menu>
} }
> >
{children || ( {children || (
<Button <Button onClick={prevent} icon={<IconMore />} theme="borderless" type="tertiary" />
onClick={prevent}
icon={<IconMore />}
theme="borderless"
type="tertiary"
/>
)} )}
</Dropdown> </Dropdown>
{showCreateDocument && ( {showCreateDocument && (

View File

@ -1,27 +1,18 @@
import type { IDocument } from "@think/domains"; import type { IDocument } from '@think/domains';
import { useCallback } from "react"; import { useCallback } from 'react';
import Router from "next/router"; import Router from 'next/router';
import Link from "next/link"; import Link from 'next/link';
import { import { Button, Space, Typography, Tooltip, Avatar, Skeleton } from '@douyinfe/semi-ui';
Button, import { IconEdit, IconUser } from '@douyinfe/semi-icons';
Space, import { LocaleTime } from 'components/locale-time';
Typography, import { IconDocument } from 'components/icons/IconDocument';
Tooltip, import { DocumentShare } from 'components/document/share';
Avatar, import { DocumentStar } from 'components/document/star';
Skeleton, import styles from './index.module.scss';
} from "@douyinfe/semi-ui";
import { IconEdit, IconUser } from "@douyinfe/semi-icons";
import { LocaleTime } from "components/locale-time";
import { IconDocument } from "components/icons/IconDocument";
import { DocumentShare } from "components/document/share";
import { DocumentStar } from "components/document/star";
import styles from "./index.module.scss";
const { Text } = Typography; const { Text } = Typography;
export const DocumentCard: React.FC<{ document: IDocument }> = ({ export const DocumentCard: React.FC<{ document: IDocument }> = ({ document }) => {
document,
}) => {
const gotoEdit = useCallback(() => { const gotoEdit = useCallback(() => {
Router.push(`/wiki/${document.wikiId}/document/${document.id}/edit`); Router.push(`/wiki/${document.wikiId}/document/${document.id}/edit`);
}, [document]); }, [document]);
@ -112,7 +103,7 @@ export const DocumentCardPlaceholder = () => {
</main> </main>
<footer> <footer>
<Text type="tertiary" size="small"> <Text type="tertiary" size="small">
<div style={{ display: "flex" }}> <div style={{ display: 'flex' }}>
<Skeleton.Paragraph rows={1} style={{ width: 100 }} /> <Skeleton.Paragraph rows={1} style={{ width: 100 }} />
</div> </div>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import { import {
Button, Button,
Modal, Modal,
@ -14,14 +14,14 @@ import {
Popconfirm, Popconfirm,
AvatarGroup, AvatarGroup,
Avatar, Avatar,
} from "@douyinfe/semi-ui"; } from '@douyinfe/semi-ui';
import { IconUserAdd, IconDelete } from "@douyinfe/semi-icons"; import { IconUserAdd, IconDelete } from '@douyinfe/semi-icons';
import { useUser } from "data/user"; import { useUser } from 'data/user';
import { EventEmitter } from "helpers/event-emitter"; import { EventEmitter } from 'helpers/event-emitter';
import { useToggle } from "hooks/useToggle"; import { useToggle } from 'hooks/useToggle';
import { useCollaborationDocument } from "data/document"; import { useCollaborationDocument } from 'data/document';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { DocumentLinkCopyer } from "components/document/link"; import { DocumentLinkCopyer } from 'components/document/link';
interface IProps { interface IProps {
wikiId: string; wikiId: string;
@ -32,48 +32,38 @@ const { Paragraph } = Typography;
const { Column } = Table; const { Column } = Table;
const CollaborationEventEmitter = new EventEmitter(); const CollaborationEventEmitter = new EventEmitter();
const KEY = "JOIN_USER"; const KEY = 'JOIN_USER';
export const joinUser = (users) => { export const joinUser = (users) => {
CollaborationEventEmitter.emit(KEY, users); CollaborationEventEmitter.emit(KEY, users);
}; };
const renderChecked = const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked, docAuth) => {
(onChange, authKey: "readable" | "editable") => (checked, docAuth) => { const handle = (evt) => {
const handle = (evt) => { const data = {
const data = { ...docAuth.auth,
...docAuth.auth, userName: docAuth.user.name,
userName: docAuth.user.name,
};
data[authKey] = evt.target.checked;
onChange(data);
}; };
data[authKey] = evt.target.checked;
return ( onChange(data);
<Checkbox
style={{ display: "inline-block" }}
checked={checked}
onChange={handle}
/>
);
}; };
export const DocumentCollaboration: React.FC<IProps> = ({ return <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
wikiId, };
documentId,
}) => { export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId }) => {
const { user: currentUser } = useUser(); const { user: currentUser } = useUser();
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const { users, loading, error, addUser, updateUser, deleteUser } = const { users, loading, error, addUser, updateUser, deleteUser } =
useCollaborationDocument(documentId); useCollaborationDocument(documentId);
const [inviteUser, setInviteUser] = useState(""); const [inviteUser, setInviteUser] = useState('');
const [collaborationUsers, setCollaborationUsers] = useState([]); const [collaborationUsers, setCollaborationUsers] = useState([]);
const handleOk = () => { const handleOk = () => {
addUser(inviteUser).then(() => { addUser(inviteUser).then(() => {
Toast.success("添加成功"); Toast.success('添加成功');
setInviteUser(""); setInviteUser('');
}); });
}; };
@ -95,9 +85,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({
if ( if (
collaborationUsers.length === newCollaborationUsers.length && collaborationUsers.length === newCollaborationUsers.length &&
newCollaborationUsers.every((newUser) => { newCollaborationUsers.every((newUser) => {
return collaborationUsers.find( return collaborationUsers.find((existUser) => existUser.id === newUser.id);
(existUser) => existUser.id === newUser.id
);
}) })
) { ) {
return; return;
@ -120,11 +108,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({
if (error) if (error)
return ( return (
<Tooltip content="邀请他人协作" position="bottom"> <Tooltip content="邀请他人协作" position="bottom">
<Button <Button theme="borderless" type="tertiary" icon={<IconUserAdd />}></Button>
theme="borderless"
type="tertiary"
icon={<IconUserAdd />}
></Button>
</Tooltip> </Tooltip>
); );
@ -150,13 +134,13 @@ export const DocumentCollaboration: React.FC<IProps> = ({
></Button> ></Button>
</Tooltip> </Tooltip>
<Modal <Modal
title={"文档协作"} title={'文档协作'}
okText={"邀请对方"} okText={'邀请对方'}
visible={visible} visible={visible}
onOk={handleOk} onOk={handleOk}
onCancel={() => toggleVisible(false)} onCancel={() => toggleVisible(false)}
maskClosable={false} maskClosable={false}
style={{ maxWidth: "96vw" }} style={{ maxWidth: '96vw' }}
footer={null} footer={null}
> >
<Tabs type="line"> <Tabs type="line">
@ -169,14 +153,14 @@ export const DocumentCollaboration: React.FC<IProps> = ({
></Input> ></Input>
<Paragraph style={{ marginTop: 16 }}> <Paragraph style={{ marginTop: 16 }}>
<span style={{ verticalAlign: "middle" }}> <span style={{ verticalAlign: 'middle' }}>
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} /> <DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
</span> </span>
</Paragraph> </Paragraph>
<Button <Button
theme="solid" theme="solid"
block block
style={{ margin: "24px 0" }} style={{ margin: '24px 0' }}
disabled={!inviteUser} disabled={!inviteUser}
onClick={handleOk} onClick={handleOk}
> >
@ -190,25 +174,20 @@ export const DocumentCollaboration: React.FC<IProps> = ({
error={error} error={error}
loadingContent={<Spin />} loadingContent={<Spin />}
normalContent={() => ( normalContent={() => (
<Table <Table style={{ margin: '24px 0' }} dataSource={users} size="small" pagination>
style={{ margin: "24px 0" }}
dataSource={users}
size="small"
pagination
>
<Column title="用户名" dataIndex="user.name" key="name" /> <Column title="用户名" dataIndex="user.name" key="name" />
<Column <Column
title="是否可读" title="是否可读"
dataIndex="auth.readable" dataIndex="auth.readable"
key="readable" key="readable"
render={renderChecked(updateUser, "readable")} render={renderChecked(updateUser, 'readable')}
align="center" align="center"
/> />
<Column <Column
title="是否可编辑" title="是否可编辑"
dataIndex="auth.editable" dataIndex="auth.editable"
key="editable" key="editable"
render={renderChecked(updateUser, "editable")} render={renderChecked(updateUser, 'editable')}
align="center" align="center"
/> />
<Column <Column
@ -221,11 +200,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({
title="确认删除该成员?" title="确认删除该成员?"
onConfirm={() => handleDelete(document)} onConfirm={() => handleDelete(document)}
> >
<Button <Button type="tertiary" theme="borderless" icon={<IconDelete />} />
type="tertiary"
theme="borderless"
icon={<IconDelete />}
/>
</Popconfirm> </Popconfirm>
)} )}
/> />

View File

@ -1,16 +1,10 @@
import React from "react"; import React from 'react';
import type { IComment, IUser } from "@think/domains"; import type { IComment, IUser } from '@think/domains';
import { import { Avatar, Typography, Space, Popconfirm, Skeleton } from '@douyinfe/semi-ui';
Avatar, import { IconUser } from '@douyinfe/semi-icons';
Typography, import { LocaleTime } from 'components/locale-time';
Space, import { useUser } from 'data/user';
Popconfirm, import styles from './index.module.scss';
Skeleton,
} from "@douyinfe/semi-ui";
import { IconUser } from "@douyinfe/semi-icons";
import { LocaleTime } from "components/locale-time";
import { useUser } from "data/user";
import styles from "./index.module.scss";
interface IProps { interface IProps {
comment: IComment; comment: IComment;
@ -52,27 +46,15 @@ export const CommentItem: React.FC<IProps> = ({
</main> </main>
<footer> <footer>
<Space> <Space>
<Text <Text type="secondary" size="small" onClick={() => replyComment(comment)}>
type="secondary"
size="small"
onClick={() => replyComment(comment)}
>
</Text> </Text>
{user && user.id === comment.createUserId && ( {user && user.id === comment.createUserId && (
<Text <Text type="secondary" size="small" onClick={() => editComment(comment)}>
type="secondary"
size="small"
onClick={() => editComment(comment)}
>
</Text> </Text>
)} )}
<Popconfirm <Popconfirm showArrow title="确认删除该评论?" onConfirm={() => deleteComment(comment)}>
showArrow
title="确认删除该评论?"
onConfirm={() => deleteComment(comment)}
>
<Text type="secondary" size="small"> <Text type="secondary" size="small">
</Text> </Text>
@ -96,7 +78,7 @@ export const CommentItemPlaceholder = () => {
</header> </header>
<main> <main>
<div> <div>
<Skeleton.Paragraph style={{ width: "100%" }} rows={3} /> <Skeleton.Paragraph style={{ width: '100%' }} rows={3} />
</div> </div>
</main> </main>
</div> </div>

View File

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import type { IComment } from "@think/domains"; import type { IComment } from '@think/domains';
import { CommentItem } from "./Item"; import { CommentItem } from './Item';
interface IProps { interface IProps {
comments: Array<IComment>; comments: Array<IComment>;
@ -11,18 +11,9 @@ interface IProps {
const PADDING_LEFT = 32; const PADDING_LEFT = 32;
const CommentInner = ({ const CommentInner = ({ data, depth, replyComment, editComment, deleteComment }) => {
data,
depth,
replyComment,
editComment,
deleteComment,
}) => {
return ( return (
<div <div key={'comment' + depth} style={{ paddingLeft: depth > 0 ? PADDING_LEFT : 0 }}>
key={"comment" + depth}
style={{ paddingLeft: depth > 0 ? PADDING_LEFT : 0 }}
>
{(data || []).map((item) => { {(data || []).map((item) => {
const hasChildren = item.children && item.children.length; const hasChildren = item.children && item.children.length;
return ( return (
@ -36,7 +27,7 @@ const CommentInner = ({
></CommentItem> ></CommentItem>
{hasChildren ? ( {hasChildren ? (
<CommentInner <CommentInner
key={"comment-inner" + depth} key={'comment-inner' + depth}
data={item.children} data={item.children}
depth={depth + 1} depth={depth + 1}
replyComment={replyComment} replyComment={replyComment}
@ -59,7 +50,7 @@ export const Comments: React.FC<IProps> = ({
}) => { }) => {
return ( return (
<CommentInner <CommentInner
key={"root-menu"} key={'root-menu'}
data={comments} data={comments}
depth={0} depth={0}
replyComment={replyComment} replyComment={replyComment}

View File

@ -1,21 +1,14 @@
import React, { useRef, useState } from "react"; import React, { useRef, useState } from 'react';
import { useEditor, EditorContent } from "@tiptap/react"; import { useEditor, EditorContent } from '@tiptap/react';
import { import { Avatar, Button, Space, Typography, Banner, Pagination } from '@douyinfe/semi-ui';
Avatar, import { useToggle } from 'hooks/useToggle';
Button, import { DEFAULT_EXTENSION, Document, CommentMenuBar } from 'components/tiptap';
Space, import { DataRender } from 'components/data-render';
Typography, import { useUser } from 'data/user';
Banner, import { useComments } from 'data/comment';
Pagination, import { Comments } from './comments';
} from "@douyinfe/semi-ui"; import { CommentItemPlaceholder } from './comments/Item';
import { useToggle } from "hooks/useToggle"; import styles from './index.module.scss';
import { DEFAULT_EXTENSION, Document, CommentMenuBar } from "components/tiptap";
import { DataRender } from "components/data-render";
import { useUser } from "data/user";
import { useComments } from "data/comment";
import { Comments } from "./comments";
import { CommentItemPlaceholder } from "./comments/Item";
import styles from "./index.module.scss";
interface IProps { interface IProps {
documentId: string; documentId: string;
@ -140,9 +133,7 @@ export const CommentEditor: React.FC<IProps> = ({ documentId }) => {
title={<Text> {replyComment.createUser.name}</Text>} title={<Text> {replyComment.createUser.name}</Text>}
description={ description={
<Paragraph ellipsis={{ rows: 2 }}> <Paragraph ellipsis={{ rows: 2 }}>
<div <div dangerouslySetInnerHTML={{ __html: replyComment.html }}></div>
dangerouslySetInnerHTML={{ __html: replyComment.html }}
></div>
</Paragraph> </Paragraph>
} }
onClose={handleClose} onClose={handleClose}
@ -176,7 +167,7 @@ export const CommentEditor: React.FC<IProps> = ({ documentId }) => {
{isEdit ? ( {isEdit ? (
<> <>
<div className={styles.editorWrap}> <div className={styles.editorWrap}>
<div style={{ width: "100%", overflow: "auto" }}> <div style={{ width: '100%', overflow: 'auto' }}>
<CommentMenuBar editor={editor} /> <CommentMenuBar editor={editor} />
</div> </div>
<div className={styles.innerWrap}> <div className={styles.innerWrap}>
@ -188,11 +179,7 @@ export const CommentEditor: React.FC<IProps> = ({ documentId }) => {
<Button theme="solid" type="primary" onClick={save}> <Button theme="solid" type="primary" onClick={save}>
</Button> </Button>
<Button <Button theme="borderless" type="tertiary" onClick={handleClose}>
theme="borderless"
type="tertiary"
onClick={handleClose}
>
</Button> </Button>
</Space> </Space>

View File

@ -1,18 +1,12 @@
import { import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
Dispatch, import Router from 'next/router';
SetStateAction, import { Modal, Tabs, TabPane, Checkbox } from '@douyinfe/semi-ui';
useCallback, import { useCreateDocument } from 'data/document';
useEffect, import { usePublicTemplates, useOwnTemplates } from 'data/template';
useState, import { TemplateList } from 'components/template/list';
} from "react"; import { TemplateCardEmpty } from 'components/template/card';
import Router from "next/router";
import { Modal, Tabs, TabPane, Checkbox } from "@douyinfe/semi-ui";
import { useCreateDocument } from "data/document";
import { usePublicTemplates, useOwnTemplates } from "data/template";
import { TemplateList } from "components/template/list";
import { TemplateCardEmpty } from "components/template/card";
import styles from "./index.module.scss"; import styles from './index.module.scss';
interface IProps { interface IProps {
wikiId: string; wikiId: string;
@ -31,7 +25,7 @@ export const DocumentCreator: React.FC<IProps> = ({
}) => { }) => {
const { loading, create } = useCreateDocument(); const { loading, create } = useCreateDocument();
const [createChildDoc, setCreateChildDoc] = useState(false); const [createChildDoc, setCreateChildDoc] = useState(false);
const [templateId, setTemplateId] = useState(""); const [templateId, setTemplateId] = useState('');
const handleOk = () => { const handleOk = () => {
const data = { const data = {
@ -42,7 +36,7 @@ export const DocumentCreator: React.FC<IProps> = ({
create(data).then((res) => { create(data).then((res) => {
toggleVisible(false); toggleVisible(false);
onCreate && onCreate(); onCreate && onCreate();
setTemplateId(""); setTemplateId('');
Router.push({ Router.push({
pathname: `/wiki/${wikiId}/document/${res.id}/edit`, pathname: `/wiki/${wikiId}/document/${res.id}/edit`,
}); });
@ -64,12 +58,12 @@ export const DocumentCreator: React.FC<IProps> = ({
onCancel={handleCancel} onCancel={handleCancel}
okButtonProps={{ loading }} okButtonProps={{ loading }}
style={{ style={{
maxWidth: "96vw", maxWidth: '96vw',
width: "calc(80vw - 120px)", width: 'calc(80vw - 120px)',
}} }}
bodyStyle={{ bodyStyle={{
maxHeight: "calc(90vh - 120px)", maxHeight: 'calc(90vh - 120px)',
overflow: "auto", overflow: 'auto',
}} }}
key={wikiId} key={wikiId}
> >
@ -94,7 +88,7 @@ export const DocumentCreator: React.FC<IProps> = ({
firstListItem={ firstListItem={
<TemplateCardEmpty <TemplateCardEmpty
getClassNames={() => !templateId && styles.isActive} getClassNames={() => !templateId && styles.isActive}
onClick={() => setTemplateId("")} onClick={() => setTemplateId('')}
/> />
} }
/> />
@ -107,7 +101,7 @@ export const DocumentCreator: React.FC<IProps> = ({
firstListItem={ firstListItem={
<TemplateCardEmpty <TemplateCardEmpty
getClassNames={() => !templateId && styles.isActive} getClassNames={() => !templateId && styles.isActive}
onClick={() => setTemplateId("")} onClick={() => setTemplateId('')}
/> />
} }
/> />

View File

@ -1,8 +1,8 @@
import React, { useCallback } from "react"; import React, { useCallback } from 'react';
import Router from "next/router"; import Router from 'next/router';
import { Typography, Space, Modal } from "@douyinfe/semi-ui"; import { Typography, Space, Modal } from '@douyinfe/semi-ui';
import { IconDelete } from "@douyinfe/semi-icons"; import { IconDelete } from '@douyinfe/semi-icons';
import { useDeleteDocument } from "data/document"; import { useDeleteDocument } from 'data/document';
interface IProps { interface IProps {
wikiId: string; wikiId: string;
@ -12,16 +12,12 @@ interface IProps {
const { Text } = Typography; const { Text } = Typography;
export const DocumentDeletor: React.FC<IProps> = ({ export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, onDelete }) => {
wikiId,
documentId,
onDelete,
}) => {
const { deleteDocument: api, loading } = useDeleteDocument(documentId); const { deleteDocument: api, loading } = useDeleteDocument(documentId);
const deleteAction = useCallback(() => { const deleteAction = useCallback(() => {
Modal.error({ Modal.error({
title: "确定删除吗?", title: '确定删除吗?',
content: <Text></Text>, content: <Text></Text>,
onOk: () => { onOk: () => {
api().then(() => { api().then(() => {
@ -32,8 +28,8 @@ export const DocumentDeletor: React.FC<IProps> = ({
}); });
}); });
}, },
okButtonProps: { loading, type: "danger" }, okButtonProps: { loading, type: 'danger' },
style: { maxWidth: "96vw" }, style: { maxWidth: '96vw' },
}); });
}, [wikiId, documentId, api, loading, onDelete]); }, [wikiId, documentId, api, loading, onDelete]);

View File

@ -1,9 +1,9 @@
import React, { useMemo, useEffect } from "react"; import React, { useMemo, useEffect } from 'react';
import cls from "classnames"; import cls from 'classnames';
import { useEditor, EditorContent } from "@tiptap/react"; import { useEditor, EditorContent } from '@tiptap/react';
import { Layout, Nav, BackTop, Toast } from "@douyinfe/semi-ui"; import { Layout, Nav, BackTop, Toast } from '@douyinfe/semi-ui';
import { ILoginUser, IAuthority } from "@think/domains"; import { ILoginUser, IAuthority } from '@think/domains';
import { useToggle } from "hooks/useToggle"; import { useToggle } from 'hooks/useToggle';
import { import {
DEFAULT_EXTENSION, DEFAULT_EXTENSION,
DocumentWithTitle, DocumentWithTitle,
@ -13,10 +13,10 @@ import {
destoryProvider, destoryProvider,
MenuBar, MenuBar,
Toc, Toc,
} from "components/tiptap"; } from 'components/tiptap';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { joinUser } from "components/document/collaboration"; import { joinUser } from 'components/document/collaboration';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Header, Content } = Layout; const { Header, Content } = Layout;
@ -28,22 +28,16 @@ interface IProps {
style: React.CSSProperties; style: React.CSSProperties;
} }
export const Editor: React.FC<IProps> = ({ export const Editor: React.FC<IProps> = ({ user, documentId, authority, className, style }) => {
user,
documentId,
authority,
className,
style,
}) => {
if (!user) return null; if (!user) return null;
const provider = useMemo(() => { const provider = useMemo(() => {
return getProvider({ return getProvider({
targetId: documentId, targetId: documentId,
token: user.token, token: user.token,
cacheType: "EDITOR", cacheType: 'EDITOR',
user, user,
docType: "document", docType: 'document',
events: { events: {
onAwarenessUpdate({ states }) { onAwarenessUpdate({ states }) {
joinUser({ states }); joinUser({ states });
@ -63,16 +57,16 @@ export const Editor: React.FC<IProps> = ({
const [loading, toggleLoading] = useToggle(true); const [loading, toggleLoading] = useToggle(true);
useEffect(() => { useEffect(() => {
provider.on("synced", () => { provider.on('synced', () => {
toggleLoading(false); toggleLoading(false);
}); });
provider.on("status", async ({ status }) => { provider.on('status', async ({ status }) => {
console.log("status", status); console.log('status', status);
}); });
return () => { return () => {
destoryProvider(provider, "EDITOR"); destoryProvider(provider, 'EDITOR');
}; };
}, []); }, []);
@ -92,11 +86,7 @@ export const Editor: React.FC<IProps> = ({
<div className={cls(styles.contentWrap, className)}> <div className={cls(styles.contentWrap, className)}>
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </div>
<BackTop <BackTop target={() => document.querySelector('#js-template-editor-container')} />
target={() =>
document.querySelector("#js-template-editor-container")
}
/>
</main> </main>
</div> </div>
); );

View File

@ -1,5 +1,5 @@
import Router from "next/router"; import Router from 'next/router';
import React, { useCallback, useMemo } from "react"; import React, { useCallback, useMemo } from 'react';
import { import {
Layout, Layout,
Nav, Nav,
@ -10,20 +10,20 @@ import {
Tooltip, Tooltip,
Spin, Spin,
Popover, Popover,
} from "@douyinfe/semi-ui"; } from '@douyinfe/semi-ui';
import { IconChevronLeft, IconArticle } from "@douyinfe/semi-icons"; import { IconChevronLeft, IconArticle } from '@douyinfe/semi-icons';
import { useUser } from "data/user"; import { useUser } from 'data/user';
import { useDocumentDetail } from "data/document"; import { useDocumentDetail } from 'data/document';
import { Seo } from "components/seo"; import { Seo } from 'components/seo';
import { Theme } from "components/theme"; import { Theme } from 'components/theme';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { DocumentShare } from "components/document/share"; import { DocumentShare } from 'components/document/share';
import { DocumentStar } from "components/document/star"; import { DocumentStar } from 'components/document/star';
import { DocumentCollaboration } from "components/document/collaboration"; import { DocumentCollaboration } from 'components/document/collaboration';
import { DocumentStyle } from "components/document/style"; import { DocumentStyle } from 'components/document/style';
import { useDocumentStyle } from "hooks/useDocumentStyle"; import { useDocumentStyle } from 'hooks/useDocumentStyle';
import { Editor } from "./editor"; import { Editor } from './editor';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Header, Content } = Layout; const { Header, Content } = Layout;
const { Text } = Typography; const { Text } = Typography;
@ -37,9 +37,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
const { width, fontSize } = useDocumentStyle(); const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => { const editorWrapClassNames = useMemo(() => {
return width === "standardWidth" return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
? styles.isStandardWidth
: styles.isFullWidth;
}, [width]); }, [width]);
const { user } = useUser(); const { user } = useUser();
@ -59,11 +57,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
const DocumentTitle = ( const DocumentTitle = (
<> <>
<Tooltip content="返回" position="bottom"> <Tooltip content="返回" position="bottom">
<Button <Button onClick={goback} icon={<IconChevronLeft />} style={{ marginRight: 16 }} />
onClick={goback}
icon={<IconChevronLeft />}
style={{ marginRight: 16 }}
/>
</Tooltip> </Tooltip>
<DataRender <DataRender
loading={docAuthLoading} loading={docAuthLoading}
@ -71,9 +65,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
loadingContent={ loadingContent={
<Skeleton <Skeleton
active active
placeholder={ placeholder={<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />}
<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />
}
loading={true} loading={true}
/> />
} }
@ -104,17 +96,8 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
)} )}
<DocumentShare key="share" documentId={documentId} /> <DocumentShare key="share" documentId={documentId} />
<DocumentStar key="star" documentId={documentId} /> <DocumentStar key="star" documentId={documentId} />
<Popover <Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
key="style" <Button icon={<IconArticle />} theme="borderless" type="tertiary" />
zIndex={1061}
position="bottomLeft"
content={<DocumentStyle />}
>
<Button
icon={<IconArticle />}
theme="borderless"
type="tertiary"
/>
</Popover> </Popover>
<Theme /> <Theme />
</Space> </Space>

View File

@ -1,8 +1,8 @@
import React, { useCallback } from "react"; import React, { useCallback } from 'react';
import { Typography, Space } from "@douyinfe/semi-ui"; import { Typography, Space } from '@douyinfe/semi-ui';
import { IconLink } from "@douyinfe/semi-icons"; import { IconLink } from '@douyinfe/semi-icons';
import { copy } from "helpers/copy"; import { copy } from 'helpers/copy';
import { buildUrl } from "helpers/url"; import { buildUrl } from 'helpers/url';
interface IProps { interface IProps {
wikiId: string; wikiId: string;
@ -11,16 +11,13 @@ interface IProps {
const { Text } = Typography; const { Text } = Typography;
export const DocumentLinkCopyer: React.FC<IProps> = ({ export const DocumentLinkCopyer: React.FC<IProps> = ({ wikiId, documentId }) => {
wikiId,
documentId,
}) => {
const handle = useCallback(() => { const handle = useCallback(() => {
copy(buildUrl(`/wiki/${wikiId}/document/${documentId}`)); copy(buildUrl(`/wiki/${wikiId}/document/${documentId}`));
}, [wikiId, documentId]); }, [wikiId, documentId]);
return ( return (
<Text onClick={handle} style={{ cursor: "pointer" }}> <Text onClick={handle} style={{ cursor: 'pointer' }}>
<Space> <Space>
<IconLink /> <IconLink />

View File

@ -1,9 +1,9 @@
import React from "react"; import React from 'react';
import { useEditor, EditorContent } from "@tiptap/react"; import { useEditor, EditorContent } from '@tiptap/react';
import { Layout } from "@douyinfe/semi-ui"; import { Layout } from '@douyinfe/semi-ui';
import { IDocument } from "@think/domains"; import { IDocument } from '@think/domains';
import { DEFAULT_EXTENSION, DocumentWithTitle } from "components/tiptap"; import { DEFAULT_EXTENSION, DocumentWithTitle } from 'components/tiptap';
import { safeJSONParse } from "helpers/json"; import { safeJSONParse } from 'helpers/json';
interface IProps { interface IProps {
document: IDocument; document: IDocument;
@ -15,7 +15,7 @@ export const DocumentContent: React.FC<IProps> = ({ document }) => {
if (json && json.content) { if (json && json.content) {
json = { json = {
type: "doc", type: 'doc',
content: json.content.slice(1), content: json.content.slice(1),
}; };
} }

View File

@ -1,8 +1,8 @@
import React, { useMemo, useEffect } from "react"; import React, { useMemo, useEffect } from 'react';
import { useEditor, EditorContent } from "@tiptap/react"; import { useEditor, EditorContent } from '@tiptap/react';
import { Layout } from "@douyinfe/semi-ui"; import { Layout } from '@douyinfe/semi-ui';
import { ILoginUser } from "@think/domains"; import { ILoginUser } from '@think/domains';
import { useToggle } from "hooks/useToggle"; import { useToggle } from 'hooks/useToggle';
import { import {
DEFAULT_EXTENSION, DEFAULT_EXTENSION,
DocumentWithTitle, DocumentWithTitle,
@ -10,10 +10,10 @@ import {
getCollaborationCursorExtension, getCollaborationCursorExtension,
getProvider, getProvider,
destoryProvider, destoryProvider,
} from "components/tiptap"; } from 'components/tiptap';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { joinUser } from "components/document/collaboration"; import { joinUser } from 'components/document/collaboration';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Content } = Layout; const { Content } = Layout;
@ -29,9 +29,9 @@ export const Editor: React.FC<IProps> = ({ user, documentId }) => {
return getProvider({ return getProvider({
targetId: documentId, targetId: documentId,
token: user.token, token: user.token,
cacheType: "READER", cacheType: 'READER',
user, user,
docType: "document", docType: 'document',
events: { events: {
onAwarenessUpdate({ states }) { onAwarenessUpdate({ states }) {
joinUser({ states }); joinUser({ states });
@ -51,12 +51,12 @@ export const Editor: React.FC<IProps> = ({ user, documentId }) => {
const [loading, toggleLoading] = useToggle(true); const [loading, toggleLoading] = useToggle(true);
useEffect(() => { useEffect(() => {
provider.on("synced", () => { provider.on('synced', () => {
toggleLoading(false); toggleLoading(false);
}); });
return () => { return () => {
destoryProvider(provider, "READER"); destoryProvider(provider, 'READER');
}; };
}, []); }, []);

View File

@ -1,6 +1,6 @@
import Router from "next/router"; import Router from 'next/router';
import React, { useCallback, useMemo } from "react"; import React, { useCallback, useMemo } from 'react';
import cls from "classnames"; import cls from 'classnames';
import { import {
Layout, Layout,
Nav, Nav,
@ -11,22 +11,22 @@ import {
Tooltip, Tooltip,
Popover, Popover,
BackTop, BackTop,
} from "@douyinfe/semi-ui"; } from '@douyinfe/semi-ui';
import { IconEdit, IconArticle } from "@douyinfe/semi-icons"; import { IconEdit, IconArticle } from '@douyinfe/semi-icons';
import { Seo } from "components/seo"; import { Seo } from 'components/seo';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { DocumentShare } from "components/document/share"; import { DocumentShare } from 'components/document/share';
import { DocumentStar } from "components/document/star"; import { DocumentStar } from 'components/document/star';
import { DocumentCollaboration } from "components/document/collaboration"; import { DocumentCollaboration } from 'components/document/collaboration';
import { DocumentStyle } from "components/document/style"; import { DocumentStyle } from 'components/document/style';
import { CommentEditor } from "components/document/comments"; import { CommentEditor } from 'components/document/comments';
import { useDocumentStyle } from "hooks/useDocumentStyle"; import { useDocumentStyle } from 'hooks/useDocumentStyle';
import { useUser } from "data/user"; import { useUser } from 'data/user';
import { useDocumentDetail } from "data/document"; import { useDocumentDetail } from 'data/document';
import { DocumentSkeleton } from "components/tiptap"; import { DocumentSkeleton } from 'components/tiptap';
import { Editor } from "./editor"; import { Editor } from './editor';
import { CreateUser } from "./user"; import { CreateUser } from './user';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Header } = Layout; const { Header } = Layout;
const { Text } = Typography; const { Text } = Typography;
@ -39,9 +39,7 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
if (!documentId) return null; if (!documentId) return null;
const { width, fontSize } = useDocumentStyle(); const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => { const editorWrapClassNames = useMemo(() => {
return width === "standardWidth" return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
? styles.isStandardWidth
: styles.isFullWidth;
}, [width]); }, [width]);
const { user } = useUser(); const { user } = useUser();
@ -61,7 +59,7 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
<div className={styles.wrap}> <div className={styles.wrap}>
<Header className={styles.headerWrap}> <Header className={styles.headerWrap}>
<Nav <Nav
style={{ overflow: "auto", paddingLeft: 0, paddingRight: 0 }} style={{ overflow: 'auto', paddingLeft: 0, paddingRight: 0 }}
mode="horizontal" mode="horizontal"
header={ header={
<DataRender <DataRender
@ -70,18 +68,12 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
loadingContent={ loadingContent={
<Skeleton <Skeleton
active active
placeholder={ placeholder={<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />}
<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />
}
loading={true} loading={true}
/> />
} }
normalContent={() => ( normalContent={() => (
<Text <Text strong ellipsis={{ showTooltip: true }} style={{ width: 120 }}>
strong
ellipsis={{ showTooltip: true }}
style={{ width: 120 }}
>
{document.title} {document.title}
</Text> </Text>
)} )}
@ -107,27 +99,15 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
<DocumentStar key="star" documentId={documentId} /> <DocumentStar key="star" documentId={documentId} />
</> </>
)} )}
<Popover <Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
key="style" <Button icon={<IconArticle />} theme="borderless" type="tertiary" />
zIndex={1061}
position="bottomLeft"
content={<DocumentStyle />}
>
<Button
icon={<IconArticle />}
theme="borderless"
type="tertiary"
/>
</Popover> </Popover>
</Space> </Space>
} }
></Nav> ></Nav>
</Header> </Header>
<Layout className={styles.contentWrap}> <Layout className={styles.contentWrap}>
<div <div className={cls(styles.editorWrap, editorWrapClassNames)} style={{ fontSize }}>
className={cls(styles.editorWrap, editorWrapClassNames)}
style={{ fontSize }}
>
<DataRender <DataRender
loading={docAuthLoading} loading={docAuthLoading}
error={docAuthError} error={docAuthError}
@ -136,20 +116,14 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
return ( return (
<> <>
<Seo title={document.title} /> <Seo title={document.title} />
<Editor <Editor key={document.id} user={user} documentId={document.id} />
key={document.id}
user={user}
documentId={document.id}
/>
<div style={{ marginBottom: 24 }}> <div style={{ marginBottom: 24 }}>
<CreateUser document={document} /> <CreateUser document={document} />
</div> </div>
<div className={styles.commentWrap}> <div className={styles.commentWrap}>
<CommentEditor documentId={document.id} /> <CommentEditor documentId={document.id} />
</div> </div>
<BackTop <BackTop target={() => window.document.querySelector('.Pane2')} />
target={() => window.document.querySelector(".Pane2")}
/>
</> </>
); );
}} }}

View File

@ -1,5 +1,5 @@
import React, { useMemo, useEffect } from "react"; import React, { useMemo, useEffect } from 'react';
import cls from "classnames"; import cls from 'classnames';
import { import {
Layout, Layout,
Nav, Nav,
@ -11,20 +11,20 @@ import {
Popover, Popover,
Modal, Modal,
BackTop, BackTop,
} from "@douyinfe/semi-ui"; } from '@douyinfe/semi-ui';
import { IconArticle } from "@douyinfe/semi-icons"; import { IconArticle } from '@douyinfe/semi-icons';
import { Seo } from "components/seo"; import { Seo } from 'components/seo';
import { LogoImage, LogoText } from "components/logo"; import { LogoImage, LogoText } from 'components/logo';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { DocumentStyle } from "components/document/style"; import { DocumentStyle } from 'components/document/style';
import { User } from "components/user"; import { User } from 'components/user';
import { Theme } from "components/theme"; import { Theme } from 'components/theme';
import { useDocumentStyle } from "hooks/useDocumentStyle"; import { useDocumentStyle } from 'hooks/useDocumentStyle';
import { usePublicDocument } from "data/document"; import { usePublicDocument } from 'data/document';
import { DocumentSkeleton } from "components/tiptap"; import { DocumentSkeleton } from 'components/tiptap';
import { DocumentContent } from "../content"; import { DocumentContent } from '../content';
import { CreateUser } from "../user"; import { CreateUser } from '../user';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Header, Content } = Layout; const { Header, Content } = Layout;
const { Text, Title } = Typography; const { Text, Title } = Typography;
@ -34,29 +34,24 @@ interface IProps {
hideLogo?: boolean; hideLogo?: boolean;
} }
export const DocumentPublicReader: React.FC<IProps> = ({ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo = true }) => {
documentId,
hideLogo = true,
}) => {
if (!documentId) return null; if (!documentId) return null;
const { data, loading, error, query } = usePublicDocument(documentId); const { data, loading, error, query } = usePublicDocument(documentId);
const { width, fontSize } = useDocumentStyle(); const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => { const editorWrapClassNames = useMemo(() => {
return width === "standardWidth" return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
? styles.isStandardWidth
: styles.isFullWidth;
}, [width]); }, [width]);
useEffect(() => { useEffect(() => {
if (!error) return; if (!error) return;
if (error.statusCode !== 400) return; if (error.statusCode !== 400) return;
Modal.confirm({ Modal.confirm({
title: "请输入密码", title: '请输入密码',
content: ( content: (
<> <>
<Seo title={"输入密码后查看"} /> <Seo title={'输入密码后查看'} />
<Input <Input
id="js-share-document-password" id="js-share-document-password"
style={{ marginTop: 24 }} style={{ marginTop: 24 }}
@ -70,9 +65,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({
hasCancel: false, hasCancel: false,
maskClosable: false, maskClosable: false,
onOk() { onOk() {
const $input = document.querySelector( const $input = document.querySelector('#js-share-document-password') as HTMLInputElement;
"#js-share-document-password"
) as HTMLInputElement;
query($input.value); query($input.value);
}, },
}); });
@ -93,17 +86,8 @@ export const DocumentPublicReader: React.FC<IProps> = ({
} }
footer={ footer={
<Space> <Space>
<Popover <Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
key="style" <Button icon={<IconArticle />} theme="borderless" type="tertiary" />
zIndex={1061}
position="bottomLeft"
content={<DocumentStyle />}
>
<Button
icon={<IconArticle />}
theme="borderless"
type="tertiary"
/>
</Popover> </Popover>
<Theme /> <Theme />
<User /> <User />
@ -116,18 +100,12 @@ export const DocumentPublicReader: React.FC<IProps> = ({
loadingContent={ loadingContent={
<Skeleton <Skeleton
active active
placeholder={ placeholder={<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />}
<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />
}
loading={true} loading={true}
/> />
} }
normalContent={() => ( normalContent={() => (
<Text <Text strong ellipsis={{ showTooltip: true }} style={{ width: 120 }}>
strong
ellipsis={{ showTooltip: true }}
style={{ width: 120 }}
>
{data.title} {data.title}
</Text> </Text>
)} )}
@ -139,10 +117,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({
loading={loading} loading={loading}
error={error} error={error}
loadingContent={ loadingContent={
<div <div className={cls(styles.editorWrap, editorWrapClassNames)} style={{ fontSize }}>
className={cls(styles.editorWrap, editorWrapClassNames)}
style={{ fontSize }}
>
<DocumentSkeleton /> <DocumentSkeleton />
</div> </div>
} }
@ -156,16 +131,14 @@ export const DocumentPublicReader: React.FC<IProps> = ({
id="js-share-document-editor-container" id="js-share-document-editor-container"
> >
<Title>{data.title}</Title> <Title>{data.title}</Title>
<div style={{ margin: "24px 0" }}> <div style={{ margin: '24px 0' }}>
<CreateUser document={data} /> <CreateUser document={data} />
</div> </div>
<DocumentContent document={data} /> <DocumentContent document={data} />
</div> </div>
<BackTop <BackTop
target={() => target={() =>
document.querySelector( document.querySelector('#js-share-document-editor-container').parentNode
"#js-share-document-editor-container"
).parentNode
} }
/> />
</> </>

View File

@ -1,7 +1,7 @@
import { Space, Typography, Avatar } from "@douyinfe/semi-ui"; import { Space, Typography, Avatar } from '@douyinfe/semi-ui';
import { IconUser } from "@douyinfe/semi-icons"; import { IconUser } from '@douyinfe/semi-icons';
import { IDocument } from "@think/domains"; import { IDocument } from '@think/domains';
import { LocaleTime } from "components/locale-time"; import { LocaleTime } from 'components/locale-time';
const { Text } = Typography; const { Text } = Typography;
@ -11,10 +11,7 @@ export const CreateUser: React.FC<{ document: IDocument }> = ({ document }) => {
return ( return (
<Text type="tertiary" size="small"> <Text type="tertiary" size="small">
<Space> <Space>
<Avatar <Avatar size="extra-extra-small" src={document.createUser && document.createUser.avatar}>
size="extra-extra-small"
src={document.createUser && document.createUser.avatar}
>
<IconUser /> <IconUser />
</Avatar> </Avatar>
<div> <div>
@ -25,7 +22,7 @@ export const CreateUser: React.FC<{ document: IDocument }> = ({ document }) => {
<p> <p>
<LocaleTime date={document.updatedAt} timeago /> <LocaleTime date={document.updatedAt} timeago />
{" ⦁ "} {' ⦁ '}
{document.views} {document.views}
</p> </p>
</div> </div>

View File

@ -1,19 +1,16 @@
import React, { useMemo, useState, useEffect } from "react"; import React, { useMemo, useState, useEffect } from 'react';
import { Button, Modal, Input, Typography, Toast } from "@douyinfe/semi-ui"; import { Button, Modal, Input, Typography, Toast } from '@douyinfe/semi-ui';
import { IconLink } from "@douyinfe/semi-icons"; import { IconLink } from '@douyinfe/semi-icons';
import { isPublicDocument } from "@think/domains"; import { isPublicDocument } from '@think/domains';
import { getDocumentShareURL } from "helpers/url"; import { getDocumentShareURL } from 'helpers/url';
import { ShareIllustration } from "illustrations/share"; import { ShareIllustration } from 'illustrations/share';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { useToggle } from "hooks/useToggle"; import { useToggle } from 'hooks/useToggle';
import { useDocumentDetail } from "data/document"; import { useDocumentDetail } from 'data/document';
interface IProps { interface IProps {
documentId: string; documentId: string;
render?: (arg: { render?: (arg: { isPublic: boolean; toggleVisible: (arg: boolean) => void }) => React.ReactNode;
isPublic: boolean;
toggleVisible: (arg: boolean) => void;
}) => React.ReactNode;
} }
const { Text } = Typography; const { Text } = Typography;
@ -21,18 +18,12 @@ const { Text } = Typography;
export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => { export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const { data, loading, error, toggleStatus } = useDocumentDetail(documentId); const { data, loading, error, toggleStatus } = useDocumentDetail(documentId);
const [sharePassword, setSharePassword] = useState(""); const [sharePassword, setSharePassword] = useState('');
const isPublic = useMemo( const isPublic = useMemo(() => data && isPublicDocument(data.document.status), [data]);
() => data && isPublicDocument(data.document.status), const shareUrl = useMemo(() => data && getDocumentShareURL(data.document.id), [data]);
[data]
);
const shareUrl = useMemo(
() => data && getDocumentShareURL(data.document.id),
[data]
);
const handleOk = () => { const handleOk = () => {
toggleStatus({ sharePassword: isPublic ? "" : sharePassword }); toggleStatus({ sharePassword: isPublic ? '' : sharePassword });
}; };
useEffect(() => { useEffect(() => {
@ -46,18 +37,18 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
render({ isPublic, toggleVisible }) render({ isPublic, toggleVisible })
) : ( ) : (
<Button type="primary" theme="light" onClick={toggleVisible}> <Button type="primary" theme="light" onClick={toggleVisible}>
{isPublic ? "分享中" : "分享"} {isPublic ? '分享中' : '分享'}
</Button> </Button>
)} )}
<Modal <Modal
title={isPublic ? "关闭分享" : "开启分享"} title={isPublic ? '关闭分享' : '开启分享'}
okText={isPublic ? "关闭分享" : "开启分享"} okText={isPublic ? '关闭分享' : '开启分享'}
visible={visible} visible={visible}
onOk={handleOk} onOk={handleOk}
onCancel={() => toggleVisible(false)} onCancel={() => toggleVisible(false)}
maskClosable={false} maskClosable={false}
style={{ maxWidth: "96vw" }} style={{ maxWidth: '96vw' }}
> >
<DataRender <DataRender
loading={loading} loading={loading}
@ -65,7 +56,7 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
normalContent={() => { normalContent={() => {
return ( return (
<div> <div>
<div style={{ textAlign: "center" }}> <div style={{ textAlign: 'center' }}>
<ShareIllustration /> <ShareIllustration />
</div> </div>
{isPublic ? ( {isPublic ? (
@ -73,7 +64,7 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
ellipsis ellipsis
icon={<IconLink />} icon={<IconLink />}
copyable={{ copyable={{
onCopy: () => Toast.success({ content: "复制文本成功" }), onCopy: () => Toast.success({ content: '复制文本成功' }),
}} }}
style={{ style={{
width: 320, width: 320,
@ -93,8 +84,8 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<Text type="tertiary"> <Text type="tertiary">
{isPublic {isPublic
? "分享开启后,该页面包含的所有内容均可访问,请谨慎开启" ? '分享开启后,该页面包含的所有内容均可访问,请谨慎开启'
: " 分享关闭后,其他人将不能继续访问该页面"} : ' 分享关闭后,其他人将不能继续访问该页面'}
</Text> </Text>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { Typography, Tooltip, Button } from "@douyinfe/semi-ui"; import { Typography, Tooltip, Button } from '@douyinfe/semi-ui';
import { IconStar } from "@douyinfe/semi-icons"; import { IconStar } from '@douyinfe/semi-icons';
import { useDocumentStar } from "data/document"; import { useDocumentStar } from 'data/document';
interface IProps { interface IProps {
documentId: string; documentId: string;
@ -16,7 +16,7 @@ const { Text } = Typography;
export const DocumentStar: React.FC<IProps> = ({ documentId, render }) => { export const DocumentStar: React.FC<IProps> = ({ documentId, render }) => {
const { data, toggleStar } = useDocumentStar(documentId); const { data, toggleStar } = useDocumentStar(documentId);
const text = data ? "取消收藏" : "收藏文档"; const text = data ? '取消收藏' : '收藏文档';
return ( return (
<> <>
@ -28,9 +28,7 @@ export const DocumentStar: React.FC<IProps> = ({ documentId, render }) => {
icon={<IconStar />} icon={<IconStar />}
theme="borderless" theme="borderless"
style={{ style={{
color: data color: data ? 'rgba(var(--semi-amber-4), 1)' : 'rgba(var(--semi-grey-3), 1)',
? "rgba(var(--semi-amber-4), 1)"
: "rgba(var(--semi-grey-3), 1)",
}} }}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();

View File

@ -1,7 +1,7 @@
import React from "react"; import React from 'react';
import { RadioGroup, Radio, Typography, Slider } from "@douyinfe/semi-ui"; import { RadioGroup, Radio, Typography, Slider } from '@douyinfe/semi-ui';
import { useDocumentStyle } from "hooks/useDocumentStyle"; import { useDocumentStyle } from 'hooks/useDocumentStyle';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Text } = Typography; const { Text } = Typography;
@ -12,7 +12,7 @@ export const DocumentStyle = () => {
<div className={styles.wrap}> <div className={styles.wrap}>
<div className={styles.item}> <div className={styles.item}>
<Text></Text> <Text></Text>
<Text style={{ fontSize: "0.8em" }}> {fontSize}px</Text> <Text style={{ fontSize: '0.8em' }}> {fontSize}px</Text>
<Slider <Slider
min={12} min={12}
max={24} max={24}
@ -29,10 +29,10 @@ export const DocumentStyle = () => {
type="button" type="button"
value={width} value={width}
onChange={(e) => setWidth(e.target.value)} onChange={(e) => setWidth(e.target.value)}
style={{ marginTop: "0.5em" }} style={{ marginTop: '0.5em' }}
> >
<Radio value={"standardWidth"}></Radio> <Radio value={'standardWidth'}></Radio>
<Radio value={"fullWidth"}></Radio> <Radio value={'fullWidth'}></Radio>
</RadioGroup> </RadioGroup>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { Typography } from "@douyinfe/semi-ui"; import { Typography } from '@douyinfe/semi-ui';
interface IProps { interface IProps {
illustration?: React.ReactNode; illustration?: React.ReactNode;
@ -12,16 +12,14 @@ export const Empty: React.FC<IProps> = ({ illustration = null, message }) => {
return ( return (
<div <div
style={{ style={{
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
flexDirection: "column", flexDirection: 'column',
margin: "16px 0", margin: '16px 0',
}} }}
> >
{illustration && ( {illustration && <main style={{ textAlign: 'center' }}>{illustration}</main>}
<main style={{ textAlign: "center" }}>{illustration}</main> <footer style={{ textAlign: 'center' }}>
)}
<footer style={{ textAlign: "center" }}>
<Text type="tertiary">{message}</Text> <Text type="tertiary">{message}</Text>
</footer> </footer>
</div> </div>

View File

@ -1,5 +1,4 @@
import React from "react"; import React, { MouseEventHandler } from 'react';
import { MouseEventHandler } from "react";
type CellProperties = { type CellProperties = {
active: boolean; active: boolean;
@ -16,21 +15,21 @@ const getBaseStyles = (cellSize) => ({
cell: { cell: {
width: cellSize, width: cellSize,
height: cellSize, height: cellSize,
background: "#fff", background: '#fff',
cursor: "pointer", cursor: 'pointer',
borderRadius: 3, borderRadius: 3,
border: "1px solid #bababa", border: '1px solid #bababa',
}, },
active: { active: {
border: "1px solid #4d6cdd", border: '1px solid #4d6cdd',
background: "#4d6cdd", background: '#4d6cdd',
}, },
hover: { hover: {
border: "1px solid #fff", border: '1px solid #fff',
background: "#4d6cdd", background: '#4d6cdd',
}, },
disabled: { disabled: {
filter: "brightness(0.7)", filter: 'brightness(0.7)',
}, },
}); });
@ -51,10 +50,10 @@ export const GridCell = ({
}: CellProperties) => { }: CellProperties) => {
const baseStyles = getBaseStyles(cellSize); const baseStyles = getBaseStyles(cellSize);
const cellStyles = { const cellStyles = {
cell: getMergedStyle(baseStyles, styles, "cell"), cell: getMergedStyle(baseStyles, styles, 'cell'),
active: getMergedStyle(baseStyles, styles, "active"), active: getMergedStyle(baseStyles, styles, 'active'),
hover: getMergedStyle(baseStyles, styles, "hover"), hover: getMergedStyle(baseStyles, styles, 'hover'),
disabled: getMergedStyle(baseStyles, styles, "disabled"), disabled: getMergedStyle(baseStyles, styles, 'disabled'),
}; };
return ( return (

View File

@ -1,8 +1,7 @@
import React, { useMemo } from "react"; import React, { useMemo, useState, useCallback } from 'react';
import { useState, useCallback } from "react"; import { Typography } from '@douyinfe/semi-ui';
import { Typography } from "@douyinfe/semi-ui"; import { debounce } from 'helpers/debounce';
import { debounce } from "helpers/debounce"; import { GridCell } from './grid-cell';
import { GridCell } from "./grid-cell";
const { Text } = Typography; const { Text } = Typography;
@ -28,12 +27,12 @@ type CoordsType = {
const getBaseStyles = (cols, cellSize) => ({ const getBaseStyles = (cols, cellSize) => ({
grid: { grid: {
position: "relative", position: 'relative',
display: "grid", display: 'grid',
color: "#444", color: '#444',
margin: "8px 0", margin: '8px 0',
gridGap: "4px 6px", gridGap: '4px 6px',
gridTemplateColumns: Array(cols).fill(`${cellSize}px`).join(" "), gridTemplateColumns: Array(cols).fill(`${cellSize}px`).join(' '),
}, },
}); });
@ -88,8 +87,8 @@ export const GridSelect = ({
const isCellDisabled = disabled; const isCellDisabled = disabled;
cells.push( cells.push(
<GridCell <GridCell
id={x + "-" + y} id={x + '-' + y}
key={x + "-" + y} key={x + '-' + y}
onClick={() => onClick({ x, y, isCellDisabled })} onClick={() => onClick({ x, y, isCellDisabled })}
onMouseEnter={onHover.bind(null, { x, y, isCellDisabled })} onMouseEnter={onHover.bind(null, { x, y, isCellDisabled })}
active={isActive} active={isActive}
@ -115,10 +114,7 @@ export const GridSelect = ({
onHover, onHover,
]); ]);
const baseStyles = useMemo( const baseStyles = useMemo(() => getBaseStyles(cols, cellSize), [cols, cellSize]);
() => getBaseStyles(cols, cellSize),
[cols, cellSize]
);
return ( return (
<div> <div>
@ -133,10 +129,8 @@ export const GridSelect = ({
> >
{cells} {cells}
</div> </div>
<footer style={{ textAlign: "center" }}> <footer style={{ textAlign: 'center' }}>
<Text> <Text>{hoverCell ? `${hoverCell.y + 1} x ${hoverCell.x + 1}` : null}</Text>
{hoverCell ? `${hoverCell.y + 1} x ${hoverCell.x + 1}` : null}
</Text>
</footer> </footer>
</div> </div>
); );

View File

@ -1 +1 @@
export * from "./grid-select"; export * from './grid-select';

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconAddColumnAfter: React.FC<{ style?: React.CSSProperties }> = ({ export const IconAddColumnAfter: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconAddColumnBefore: React.FC<{ style?: React.CSSProperties }> = ({ export const IconAddColumnBefore: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconAddRowAfter: React.FC<{ style?: React.CSSProperties }> = ({ export const IconAddRowAfter: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconAddRowBefore: React.FC<{ style?: React.CSSProperties }> = ({ export const IconAddRowBefore: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconAttachment: React.FC<{ style?: React.CSSProperties }> = ({ export const IconAttachment: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconCenter: React.FC<{ style?: React.CSSProperties }> = ({ export const IconCenter: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconClear: React.FC<{ style?: React.CSSProperties }> = ({ export const IconClear: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconCodeBlock: React.FC<{ style?: React.CSSProperties }> = ({ export const IconCodeBlock: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconDeleteColumn: React.FC<{ style?: React.CSSProperties }> = ({ export const IconDeleteColumn: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconDeleteRow: React.FC<{ style?: React.CSSProperties }> = ({ export const IconDeleteRow: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconDeleteTable: React.FC<{ style?: React.CSSProperties }> = ({ export const IconDeleteTable: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,9 +1,7 @@
import React from "react"; import React from 'react';
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconDocument: React.FC<{ style?: React.CSSProperties }> = ({ export const IconDocument: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,9 +1,7 @@
import React from "react"; import React from 'react';
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconDocumentFill: React.FC<{ style?: React.CSSProperties }> = ({ export const IconDocumentFill: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconEmoji: React.FC<{ style?: React.CSSProperties }> = ({ export const IconEmoji: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,18 +1,11 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconFont: React.FC<{ style?: React.CSSProperties }> = ({ export const IconFont: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}
svg={ svg={
<svg <svg role="presentation" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
role="presentation"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
>
<path <path
d="M14 12.5h-4l-.874 2.186A.5.5 0 0 1 8.66 15H7.273a.5.5 0 0 1-.456-.705l4.05-9A.5.5 0 0 1 11.323 5h1.354a.5.5 0 0 1 .456.295l4.05 9a.5.5 0 0 1-.456.705h-1.388a.5.5 0 0 1-.465-.314L14 12.5zm-.6-1.5L12 7.5 10.6 11h2.8z" d="M14 12.5h-4l-.874 2.186A.5.5 0 0 1 8.66 15H7.273a.5.5 0 0 1-.456-.705l4.05-9A.5.5 0 0 1 11.323 5h1.354a.5.5 0 0 1 .456.295l4.05 9a.5.5 0 0 1-.456.705h-1.388a.5.5 0 0 1-.465-.314L14 12.5zm-.6-1.5L12 7.5 10.6 11h2.8z"
fill="currentColor" fill="currentColor"

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconFull: React.FC<{ style?: React.CSSProperties }> = ({ export const IconFull: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconHalf: React.FC<{ style?: React.CSSProperties }> = ({ export const IconHalf: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconImage: React.FC<{ style?: React.CSSProperties }> = ({ export const IconImage: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconInfo: React.FC<{ style?: React.CSSProperties }> = ({ export const IconInfo: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconLeft: React.FC<{ style?: React.CSSProperties }> = ({ export const IconLeft: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconLink: React.FC<{ style?: React.CSSProperties }> = ({ export const IconLink: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconMath: React.FC<{ style?: React.CSSProperties }> = ({ export const IconMath: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconMergeCell: React.FC<{ style?: React.CSSProperties }> = ({ export const IconMergeCell: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,9 +1,7 @@
import React from "react"; import React from 'react';
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconMessage: React.FC<{ style?: React.CSSProperties }> = ({ export const IconMessage: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconMind: React.FC<{ style?: React.CSSProperties }> = ({ export const IconMind: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,9 +1,7 @@
import React from "react"; import React from 'react';
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconOverview: React.FC<{ style?: React.CSSProperties }> = ({ export const IconOverview: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconRight: React.FC<{ style?: React.CSSProperties }> = ({ export const IconRight: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,9 +1,7 @@
import React from "react"; import React from 'react';
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconSearch: React.FC<{ style?: React.CSSProperties }> = ({ export const IconSearch: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,9 +1,7 @@
import React from "react"; import React from 'react';
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconSetting: React.FC<{ style?: React.CSSProperties }> = ({ export const IconSetting: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,9 +1,7 @@
import React from "react"; import React from 'react';
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconShare: React.FC<{ style?: React.CSSProperties }> = ({ export const IconShare: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconSplitCell: React.FC<{ style?: React.CSSProperties }> = ({ export const IconSplitCell: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,18 +1,12 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconStatus: React.FC<{ style?: React.CSSProperties }> = ({ export const IconStatus: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}
svg={ svg={
<svg width="16" height="16" viewBox="0 0 256 256" role="presentation"> <svg width="16" height="16" viewBox="0 0 256 256" role="presentation">
<g <g transform="rotate(-45 100.071 47.645)" fill="currentColor" fill-rule="evenodd">
transform="rotate(-45 100.071 47.645)"
fill="currentColor"
fill-rule="evenodd"
>
<path <path
d="m44.625 4.22-47 46.951A26 26 0 0 0-10 69.566V190c0 14.36 11.64 26 26 26h94c14.36 0 26-11.64 26-26V69.566a26 26 0 0 0-7.625-18.395l-47-46.95c-10.151-10.14-26.599-10.14-36.75 0ZM67.24 18.37l47 46.95a6 6 0 0 1 1.76 4.246V190a6 6 0 0 1-6 6H16a6 6 0 0 1-6-6V69.566a6 6 0 0 1 1.76-4.245l47-46.95a6 6 0 0 1 8.48 0Z" d="m44.625 4.22-47 46.951A26 26 0 0 0-10 69.566V190c0 14.36 11.64 26 26 26h94c14.36 0 26-11.64 26-26V69.566a26 26 0 0 0-7.625-18.395l-47-46.95c-10.151-10.14-26.599-10.14-36.75 0ZM67.24 18.37l47 46.95a6 6 0 0 1 1.76 4.246V190a6 6 0 0 1-6 6H16a6 6 0 0 1-6-6V69.566a6 6 0 0 1 1.76-4.245l47-46.95a6 6 0 0 1 8.48 0Z"
fill-rule="nonzero" fill-rule="nonzero"

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconTable: React.FC<{ style?: React.CSSProperties }> = ({ export const IconTable: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconTask: React.FC<{ style?: React.CSSProperties }> = ({ export const IconTask: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconZoomIn: React.FC<{ style?: React.CSSProperties }> = ({ export const IconZoomIn: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,8 +1,6 @@
import { Icon } from "@douyinfe/semi-ui"; import { Icon } from '@douyinfe/semi-ui';
export const IconZoomOut: React.FC<{ style?: React.CSSProperties }> = ({ export const IconZoomOut: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
style = {},
}) => {
return ( return (
<Icon <Icon
style={style} style={style}

View File

@ -1,36 +1,36 @@
export * from "./IconDocument"; export * from './IconDocument';
export * from "./IconDocumentFill"; export * from './IconDocumentFill';
export * from "./IconMessage"; export * from './IconMessage';
export * from "./IconOverview"; export * from './IconOverview';
export * from "./IconSetting"; export * from './IconSetting';
export * from "./IconShare"; export * from './IconShare';
export * from "./IconLeft"; export * from './IconLeft';
export * from "./IconRight"; export * from './IconRight';
export * from "./IconFull"; export * from './IconFull';
export * from "./IconHalf"; export * from './IconHalf';
export * from "./IconCenter"; export * from './IconCenter';
export * from "./IconFont"; export * from './IconFont';
export * from "./IconTask"; export * from './IconTask';
export * from "./IconLink"; export * from './IconLink';
export * from "./IconClear"; export * from './IconClear';
export * from "./IconImage"; export * from './IconImage';
export * from "./IconMind"; export * from './IconMind';
export * from "./IconZoomIn"; export * from './IconZoomIn';
export * from "./IconZoomOut"; export * from './IconZoomOut';
export * from "./IconTable"; export * from './IconTable';
export * from "./IconCodeBlock"; export * from './IconCodeBlock';
export * from "./IconStatus"; export * from './IconStatus';
export * from "./IconInfo"; export * from './IconInfo';
export * from "./IconEmoji"; export * from './IconEmoji';
export * from "./IconAddColumnBefore"; export * from './IconAddColumnBefore';
export * from "./IconAddColumnAfter"; export * from './IconAddColumnAfter';
export * from "./IconDeleteColumn"; export * from './IconDeleteColumn';
export * from "./IconAddRowBefore"; export * from './IconAddRowBefore';
export * from "./IconAddRowAfter"; export * from './IconAddRowAfter';
export * from "./IconDeleteRow"; export * from './IconDeleteRow';
export * from "./IconDeleteTable"; export * from './IconDeleteTable';
export * from "./IconMergeCell"; export * from './IconMergeCell';
export * from "./IconSplitCell"; export * from './IconSplitCell';
export * from "./IconAttachment"; export * from './IconAttachment';
export * from "./IconMath"; export * from './IconMath';
export * from "./IconSearch"; export * from './IconSearch';

View File

@ -1,7 +1,7 @@
import React, { useRef, useState, useEffect } from "react"; import React, { useRef, useState, useEffect } from 'react';
import distanceInWords from "date-fns/formatDistance"; import distanceInWords from 'date-fns/formatDistance';
import dateFormat from "date-fns/format"; import dateFormat from 'date-fns/format';
import zh from "date-fns/locale/zh-CN"; import zh from 'date-fns/locale/zh-CN';
let callbacks: Array<() => void> = []; let callbacks: Array<() => void> = [];
@ -30,18 +30,14 @@ const getTimeago = (date: number | string | Date) => {
}); });
content = content content = content
.replace("about", "") .replace('about', '')
.replace("less than a minute ago", "just now") .replace('less than a minute ago', 'just now')
.replace("minute", "min"); .replace('minute', 'min');
return content; return content;
}; };
export const LocaleTime: React.FC<Props> = ({ export const LocaleTime: React.FC<Props> = ({ date, timeago, format = 'yyyy-MM-dd HH:mm:ss' }) => {
date,
timeago,
format = "yyyy-MM-dd HH:mm:ss",
}) => {
const [_, setMinutesMounted] = useState(0); // eslint-disable-line no-unused-vars const [_, setMinutesMounted] = useState(0); // eslint-disable-line no-unused-vars
const callback = useRef<() => void>(); const callback = useRef<() => void>();
@ -59,7 +55,5 @@ export const LocaleTime: React.FC<Props> = ({
const formated = dateFormat(new Date(date), format); const formated = dateFormat(new Date(date), format);
return ( return <time dateTime={formated}>{timeago ? getTimeago(date) : formated}</time>;
<time dateTime={formated}>{timeago ? getTimeago(date) : formated}</time>
);
}; };

View File

@ -1,12 +1,12 @@
import Link from "next/link"; import Link from 'next/link';
import { Typography } from "@douyinfe/semi-ui"; import { Typography } from '@douyinfe/semi-ui';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Text } = Typography; const { Text } = Typography;
export const LogoImage = () => { export const LogoImage = () => {
return ( return (
<Link href={"/"} as={"/"}> <Link href={'/'} as={'/'}>
<a style={{ width: 36, height: 36 }}> <a style={{ width: 36, height: 36 }}>
<svg <svg
viewBox="0 0 1024 1024" viewBox="0 0 1024 1024"
@ -39,7 +39,7 @@ export const LogoImage = () => {
export const LogoText = () => { export const LogoText = () => {
return ( return (
<Link href={"/"} as={"/"}> <Link href={'/'} as={'/'}>
<a className={styles.wrap}> <a className={styles.wrap}>
<Text></Text> <Text></Text>
</a> </a>

View File

@ -1,4 +1,4 @@
import { Skeleton } from "@douyinfe/semi-ui"; import { Skeleton } from '@douyinfe/semi-ui';
export const Placeholder = () => { export const Placeholder = () => {
return ( return (
@ -6,9 +6,7 @@ export const Placeholder = () => {
placeholder={ placeholder={
<> <>
{Array.from({ length: 6 }).fill( {Array.from({ length: 6 }).fill(
<Skeleton.Title <Skeleton.Title style={{ width: '100%', marginBottom: 12, marginTop: 12 }} />
style={{ width: "100%", marginBottom: 12, marginTop: 12 }}
/>
)} )}
</> </>
} }

View File

@ -1,5 +1,5 @@
import React, { useEffect } from "react"; import React, { useEffect } from 'react';
import Link from "next/link"; import Link from 'next/link';
import { import {
Typography, Typography,
Dropdown, Dropdown,
@ -9,18 +9,14 @@ import {
TabPane, TabPane,
Pagination, Pagination,
Notification, Notification,
} from "@douyinfe/semi-ui"; } from '@douyinfe/semi-ui';
import { IconMessage } from "components/icons/IconMessage"; import { IconMessage } from 'components/icons/IconMessage';
import { import { useAllMessages, useReadMessages, useUnreadMessages } from 'data/message';
useAllMessages, import { EmptyBoxIllustration } from 'illustrations/empty-box';
useReadMessages, import { DataRender } from 'components/data-render';
useUnreadMessages, import { Empty } from 'components/empty';
} from "data/message"; import { Placeholder } from './Placeholder';
import { EmptyBoxIllustration } from "illustrations/empty-box"; import styles from './index.module.scss';
import { DataRender } from "components/data-render";
import { Empty } from "components/empty";
import { Placeholder } from "./Placeholder";
import styles from "./index.module.scss";
const { Text } = Typography; const { Text } = Typography;
const PAGE_SIZE = 6; const PAGE_SIZE = 6;
@ -49,7 +45,7 @@ const MessagesRender = ({
return ( return (
<div <div
className={styles.itemsWrap} className={styles.itemsWrap}
style={{ margin: "8px -16px" }} style={{ margin: '8px -16px' }}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -59,10 +55,7 @@ const MessagesRender = ({
<> <>
{messages.map((msg) => { {messages.map((msg) => {
return ( return (
<div <div className={styles.itemWrap} onClick={() => handleRead(msg.id)}>
className={styles.itemWrap}
onClick={() => handleRead(msg.id)}
>
<Link href={msg.url}> <Link href={msg.url}>
<a className={styles.item}> <a className={styles.item}>
<div className={styles.leftWrap}> <div className={styles.leftWrap}>
@ -89,17 +82,14 @@ const MessagesRender = ({
total={total} total={total}
currentPage={page} currentPage={page}
pageSize={PAGE_SIZE} pageSize={PAGE_SIZE}
style={{ textAlign: "center" }} style={{ textAlign: 'center' }}
onPageChange={onPageChange} onPageChange={onPageChange}
/> />
</div> </div>
)} )}
</> </>
) : ( ) : (
<Empty <Empty illustration={<EmptyBoxIllustration />} message="暂无消息" />
illustration={<EmptyBoxIllustration />}
message="暂无消息"
/>
)} )}
</div> </div>
); );
@ -146,7 +136,7 @@ export const Message = () => {
const msg = unreadMsgs.data[0]; const msg = unreadMsgs.data[0];
Notification.info({ Notification.info({
title: "消息通知", title: '消息通知',
content: ( content: (
<Link href={msg.url}> <Link href={msg.url}>
<a className={styles.item}> <a className={styles.item}>
@ -178,17 +168,13 @@ export const Message = () => {
position="bottomRight" position="bottomRight"
trigger="click" trigger="click"
content={ content={
<div style={{ width: 300, padding: "16px 16px 0" }}> <div style={{ width: 300, padding: '16px 16px 0' }}>
<Tabs <Tabs
type="line" type="line"
size="small" size="small"
tabBarExtraContent={ tabBarExtraContent={
unreadMsgs && unreadMsgs.total > 0 ? ( unreadMsgs && unreadMsgs.total > 0 ? (
<Text <Text type="quaternary" onClick={clearAll} style={{ cursor: 'pointer' }}>
type="quaternary"
onClick={clearAll}
style={{ cursor: "pointer" }}
>
</Text> </Text>
) : null ) : null

View File

@ -1 +1 @@
export * from "./resizeable"; export * from './resizeable';

View File

@ -1,7 +1,7 @@
import React, { useRef, useEffect } from "react"; import React, { useRef, useEffect } from 'react';
import { useClickOutside } from "hooks/use-click-outside"; import { useClickOutside } from 'hooks/use-click-outside';
import interact from "interactjs"; import interact from 'interactjs';
import styles from "./style.module.scss"; import styles from './style.module.scss';
interface IProps { interface IProps {
width: number; width: number;
@ -12,12 +12,7 @@ interface IProps {
const MIN_WIDTH = 50; const MIN_WIDTH = 50;
const MIN_HEIGHT = 50; const MIN_HEIGHT = 50;
export const Resizeable: React.FC<IProps> = ({ export const Resizeable: React.FC<IProps> = ({ width, height, onChange, children }) => {
width,
height,
onChange,
children,
}) => {
const $container = useRef<HTMLDivElement>(null); const $container = useRef<HTMLDivElement>(null);
const $topLeft = useRef<HTMLDivElement>(null); const $topLeft = useRef<HTMLDivElement>(null);
const $topRight = useRef<HTMLDivElement>(null); const $topRight = useRef<HTMLDivElement>(null);
@ -67,24 +62,24 @@ export const Resizeable: React.FC<IProps> = ({
style={{ width, height }} style={{ width, height }}
> >
<span <span
className={styles.resizer + " " + styles.topLeft} className={styles.resizer + ' ' + styles.topLeft}
ref={$topLeft} ref={$topLeft}
data-type={"topLeft"} data-type={'topLeft'}
></span> ></span>
<span <span
className={styles.resizer + " " + styles.topRight} className={styles.resizer + ' ' + styles.topRight}
ref={$topRight} ref={$topRight}
data-type={"topRight"} data-type={'topRight'}
></span> ></span>
<span <span
className={styles.resizer + " " + styles.bottomLeft} className={styles.resizer + ' ' + styles.bottomLeft}
ref={$bottomLeft} ref={$bottomLeft}
data-type={"bottomLeft"} data-type={'bottomLeft'}
></span> ></span>
<span <span
className={styles.resizer + " " + styles.bottomRight} className={styles.resizer + ' ' + styles.bottomRight}
ref={$bottomRight} ref={$bottomRight}
data-type={"bottomRight"} data-type={'bottomRight'}
></span> ></span>
{children} {children}
</div> </div>

View File

@ -38,10 +38,7 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
<div className={styles.leftWrap}> <div className={styles.leftWrap}>
<IconDocumentFill style={{ marginRight: 12 }} /> <IconDocumentFill style={{ marginRight: 12 }} />
<div> <div>
<Text <Text ellipsis={{ showTooltip: true }} style={{ width: 180 }}>
ellipsis={{ showTooltip: true }}
style={{ width: 180 }}
>
{doc.title} {doc.title}
</Text> </Text>
@ -100,12 +97,7 @@ export const Search = () => {
return ( return (
<> <>
<Button <Button type="tertiary" theme="borderless" icon={<IconSearch />} onClick={toggleVisible} />
type="tertiary"
theme="borderless"
icon={<IconSearch />}
onClick={toggleVisible}
/>
<Modal <Modal
visible={visible} visible={visible}
title="文档搜索" title="文档搜索"
@ -126,12 +118,7 @@ export const Search = () => {
setKeyword(val); setKeyword(val);
}} }}
onEnterPress={search} onEnterPress={search}
suffix={ suffix={<SemiIconSearch onClick={search} style={{ cursor: 'pointer' }} />}
<SemiIconSearch
onClick={search}
style={{ cursor: 'pointer' }}
/>
}
showClear showClear
/> />
</div> </div>

View File

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import { Helmet } from "react-helmet"; import { Helmet } from 'react-helmet';
interface IProps { interface IProps {
title: string; title: string;

View File

@ -1,22 +1,14 @@
import type { ITemplate } from "@think/domains"; import type { ITemplate } from '@think/domains';
import { useCallback } from "react"; import { useCallback } from 'react';
import cls from "classnames"; import cls from 'classnames';
import Router from "next/router"; import Router from 'next/router';
import { import { Button, Space, Typography, Tooltip, Avatar, Skeleton, Modal } from '@douyinfe/semi-ui';
Button, import { IconEdit, IconUser, IconPlus } from '@douyinfe/semi-icons';
Space, import { IconDocument } from 'components/icons/IconDocument';
Typography, import { TemplateReader } from 'components/template/reader';
Tooltip, import { useUser } from 'data/user';
Avatar, import styles from './index.module.scss';
Skeleton, import { useToggle } from 'hooks/useToggle';
Modal,
} from "@douyinfe/semi-ui";
import { IconEdit, IconUser, IconPlus } from "@douyinfe/semi-icons";
import { IconDocument } from "components/icons/IconDocument";
import { TemplateReader } from "components/template/reader";
import { useUser } from "data/user";
import styles from "./index.module.scss";
import { useToggle } from "hooks/useToggle";
const { Text } = Typography; const { Text } = Typography;
@ -31,7 +23,7 @@ export interface IProps {
export const TemplateCard: React.FC<IProps> = ({ export const TemplateCard: React.FC<IProps> = ({
template, template,
onClick, onClick,
getClassNames = (id) => "", getClassNames = (id) => '',
onOpenPreview, onOpenPreview,
onClosePreview, onClosePreview,
}) => { }) => {
@ -46,10 +38,10 @@ export const TemplateCard: React.FC<IProps> = ({
<> <>
<Modal <Modal
title="模板预览" title="模板预览"
width={"calc(100vh - 120px)"} width={'calc(100vh - 120px)'}
height={"calc(100vh - 120px)"} height={'calc(100vh - 120px)'}
bodyStyle={{ bodyStyle={{
overflow: "auto", overflow: 'auto',
}} }}
visible={visible} visible={visible}
onCancel={() => { onCancel={() => {
@ -100,7 +92,7 @@ export const TemplateCard: React.FC<IProps> = ({
</main> </main>
<footer> <footer>
<Text type="tertiary" size="small"> <Text type="tertiary" size="small">
<div style={{ display: "flex" }}> <div style={{ display: 'flex' }}>
使 使
{template.usageAmount} {template.usageAmount}
</div> </div>
@ -118,11 +110,7 @@ export const TemplateCard: React.FC<IProps> = ({
</Button> </Button>
{onClick && ( {onClick && (
<Button <Button type="primary" theme="solid" onClick={() => onClick && onClick(template.id)}>
type="primary"
theme="solid"
onClick={() => onClick && onClick(template.id)}
>
使 使
</Button> </Button>
)} )}
@ -156,7 +144,7 @@ export const TemplateCardPlaceholder = () => {
</main> </main>
<footer> <footer>
<Text type="tertiary" size="small"> <Text type="tertiary" size="small">
<div style={{ display: "flex" }}> <div style={{ display: 'flex' }}>
<Skeleton.Paragraph rows={1} style={{ width: 100 }} /> <Skeleton.Paragraph rows={1} style={{ width: 100 }} />
</div> </div>
@ -166,36 +154,33 @@ export const TemplateCardPlaceholder = () => {
); );
}; };
export const TemplateCardEmpty = ({ export const TemplateCardEmpty = ({ getClassNames = () => '', onClick = () => {} }) => {
getClassNames = () => "",
onClick = () => {},
}) => {
return ( return (
<div className={cls(styles.cardWrap, getClassNames())} onClick={onClick}> <div className={cls(styles.cardWrap, getClassNames())} onClick={onClick}>
<div <div
style={{ style={{
height: 131, height: 131,
position: "relative", position: 'relative',
}} }}
> >
<div <div
style={{ style={{
position: "absolute", position: 'absolute',
left: "50%", left: '50%',
top: "50%", top: '50%',
transform: `translate(-50%, -50%)`, transform: `translate(-50%, -50%)`,
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<Text link style={{ textAlign: "center" }}> <Text link style={{ textAlign: 'center' }}>
<IconPlus <IconPlus
style={{ style={{
width: 36, width: 36,
height: 36, height: 36,
fontSize: 36, fontSize: 36,
margin: "0 auto 12px", margin: '0 auto 12px',
}} }}
/> />
</Text> </Text>

View File

@ -1,7 +1,7 @@
import React, { useMemo, useCallback, useState, useEffect } from "react"; import React, { useMemo, useCallback, useState, useEffect } from 'react';
import Router from "next/router"; import Router from 'next/router';
import cls from "classnames"; import cls from 'classnames';
import { useEditor, EditorContent } from "@tiptap/react"; import { useEditor, EditorContent } from '@tiptap/react';
import { import {
Button, Button,
Nav, Nav,
@ -14,10 +14,10 @@ import {
Popover, Popover,
Popconfirm, Popconfirm,
BackTop, BackTop,
} from "@douyinfe/semi-ui"; } from '@douyinfe/semi-ui';
import { IconChevronLeft, IconArticle } from "@douyinfe/semi-icons"; import { IconChevronLeft, IconArticle } from '@douyinfe/semi-icons';
import { ILoginUser, ITemplate } from "@think/domains"; import { ILoginUser, ITemplate } from '@think/domains';
import { Theme } from "components/theme"; import { Theme } from 'components/theme';
import { import {
DEFAULT_EXTENSION, DEFAULT_EXTENSION,
DocumentWithTitle, DocumentWithTitle,
@ -25,13 +25,13 @@ import {
getProvider, getProvider,
MenuBar, MenuBar,
Toc, Toc,
} from "components/tiptap"; } from 'components/tiptap';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { User } from "components/user"; import { User } from 'components/user';
import { DocumentStyle } from "components/document/style"; import { DocumentStyle } from 'components/document/style';
import { useDocumentStyle } from "hooks/useDocumentStyle"; import { useDocumentStyle } from 'hooks/useDocumentStyle';
import { safeJSONParse } from "helpers/json"; import { safeJSONParse } from 'helpers/json';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Text } = Typography; const { Text } = Typography;
@ -58,27 +58,21 @@ export const Editor: React.FC<IProps> = ({
return getProvider({ return getProvider({
targetId: data.id, targetId: data.id,
token: user.token, token: user.token,
cacheType: "READER", cacheType: 'READER',
user, user,
docType: "template", docType: 'template',
}); });
}, [data, user.token]); }, [data, user.token]);
const editor = useEditor({ const editor = useEditor({
editable: true, editable: true,
extensions: [ extensions: [...DEFAULT_EXTENSION, DocumentWithTitle, getCollaborationExtension(provider)],
...DEFAULT_EXTENSION,
DocumentWithTitle,
getCollaborationExtension(provider),
],
content: safeJSONParse(data && data.content), content: safeJSONParse(data && data.content),
}); });
const [isPublic, setPublic] = useState(false); const [isPublic, setPublic] = useState(false);
const { width, fontSize } = useDocumentStyle(); const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => { const editorWrapClassNames = useMemo(() => {
return width === "standardWidth" return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
? styles.isStandardWidth
: styles.isFullWidth;
}, [width]); }, [width]);
const goback = useCallback(() => { const goback = useCallback(() => {
@ -100,7 +94,7 @@ export const Editor: React.FC<IProps> = ({
<div className={styles.wrap}> <div className={styles.wrap}>
<header> <header>
<Nav <Nav
style={{ overflow: "auto" }} style={{ overflow: 'auto' }}
mode="horizontal" mode="horizontal"
header={ header={
<DataRender <DataRender
@ -109,9 +103,7 @@ export const Editor: React.FC<IProps> = ({
loadingContent={ loadingContent={
<Skeleton <Skeleton
active active
placeholder={ placeholder={<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />}
<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />
}
loading={true} loading={true}
/> />
} }
@ -124,11 +116,7 @@ export const Editor: React.FC<IProps> = ({
style={{ marginRight: 16 }} style={{ marginRight: 16 }}
/> />
</Tooltip> </Tooltip>
<Text <Text strong ellipsis={{ showTooltip: true }} style={{ width: 120 }}>
strong
ellipsis={{ showTooltip: true }}
style={{ width: 120 }}
>
{data.title} {data.title}
</Text> </Text>
</> </>
@ -137,25 +125,11 @@ export const Editor: React.FC<IProps> = ({
} }
footer={ footer={
<Space> <Space>
<Popover <Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
key="style" <Button icon={<IconArticle />} theme="borderless" type="tertiary" />
zIndex={1061}
position="bottomLeft"
content={<DocumentStyle />}
>
<Button
icon={<IconArticle />}
theme="borderless"
type="tertiary"
/>
</Popover> </Popover>
<Tooltip <Tooltip position="bottom" content={isPublic ? '公开模板' : '个人模板'}>
position="bottom" <Switch onChange={(v) => updateTemplate({ isPublic: v })}></Switch>
content={isPublic ? "公开模板" : "个人模板"}
>
<Switch
onChange={(v) => updateTemplate({ isPublic: v })}
></Switch>
</Tooltip> </Tooltip>
<Popconfirm <Popconfirm
title="删除模板" title="删除模板"
@ -194,11 +168,7 @@ export const Editor: React.FC<IProps> = ({
> >
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </div>
<BackTop <BackTop target={() => document.querySelector('#js-template-editor-container')} />
target={() =>
document.querySelector("#js-template-editor-container")
}
/>
</main> </main>
</div> </div>
); );

View File

@ -1,10 +1,10 @@
import React from "react"; import React from 'react';
import { Spin } from "@douyinfe/semi-ui"; import { Spin } from '@douyinfe/semi-ui';
import { useUser } from "data/user"; import { useUser } from 'data/user';
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 { useTemplate } from 'data/template';
import { Editor } from "./editor"; import { Editor } from './editor';
interface IProps { interface IProps {
templateId: string; templateId: string;
@ -12,8 +12,7 @@ interface IProps {
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 } = const { data, loading, error, updateTemplate, deleteTemplate } = useTemplate(templateId);
useTemplate(templateId);
return ( return (
<DataRender <DataRender

View File

@ -1,12 +1,12 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from 'react';
import { List, Pagination } from "@douyinfe/semi-ui"; import { List, Pagination } from '@douyinfe/semi-ui';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { import {
IProps as ITemplateCardProps, IProps as ITemplateCardProps,
TemplateCardPlaceholder, TemplateCardPlaceholder,
TemplateCard, TemplateCard,
} from "components/template/card"; } from 'components/template/card';
import { Empty } from "components/empty"; import { Empty } from 'components/empty';
const grid = { const grid = {
gutter: 16, gutter: 16,
@ -17,7 +17,7 @@ const grid = {
xl: 8, xl: 8,
}; };
interface IProps extends Omit<ITemplateCardProps, "template"> { interface IProps extends Omit<ITemplateCardProps, 'template'> {
// TODO: 修复类型 // TODO: 修复类型
hook: any; hook: any;
firstListItem?: React.ReactNode; firstListItem?: React.ReactNode;
@ -81,15 +81,15 @@ export const TemplateList: React.FC<IProps> = ({
</List.Item> </List.Item>
); );
}} }}
emptyContent={<Empty message={"暂无模板"} />} emptyContent={<Empty message={'暂无模板'} />}
></List> ></List>
{data.data.length > pageSize ? ( {data.data.length > pageSize ? (
<Pagination <Pagination
size="small" size="small"
style={{ style={{
width: "100%", width: '100%',
flexBasis: "100%", flexBasis: '100%',
justifyContent: "center", justifyContent: 'center',
}} }}
pageSize={pageSize} pageSize={pageSize}
total={data.data.length} total={data.data.length}

View File

@ -1,13 +1,13 @@
import React, { useMemo } from "react"; import React, { useMemo } from 'react';
import cls from "classnames"; import cls from 'classnames';
import { useEditor, EditorContent } from "@tiptap/react"; import { useEditor, EditorContent } from '@tiptap/react';
import { Layout, Spin, Typography } from "@douyinfe/semi-ui"; import { Layout, Spin, Typography } from '@douyinfe/semi-ui';
import { IUser, ITemplate } from "@think/domains"; import { IUser, ITemplate } from '@think/domains';
import { DEFAULT_EXTENSION, DocumentWithTitle } from "components/tiptap"; import { DEFAULT_EXTENSION, DocumentWithTitle } from 'components/tiptap';
import { DataRender } from "components/data-render"; import { DataRender } from 'components/data-render';
import { useDocumentStyle } from "hooks/useDocumentStyle"; import { useDocumentStyle } from 'hooks/useDocumentStyle';
import { safeJSONParse } from "helpers/json"; import { safeJSONParse } from 'helpers/json';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const { Content } = Layout; const { Content } = Layout;
const { Title } = Typography; const { Title } = Typography;
@ -27,7 +27,7 @@ export const Editor: React.FC<IProps> = ({ user, data, loading, error }) => {
if (json && json.content) { if (json && json.content) {
json = { json = {
type: "doc", type: 'doc',
content: json.content.slice(1), content: json.content.slice(1),
}; };
} }
@ -40,9 +40,7 @@ export const Editor: React.FC<IProps> = ({ user, data, loading, error }) => {
const { width, fontSize } = useDocumentStyle(); const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => { const editorWrapClassNames = useMemo(() => {
return width === "standardWidth" return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
? styles.isStandardWidth
: styles.isFullWidth;
}, [width]); }, [width]);
return ( return (

View File

@ -1,10 +1,10 @@
import React from "react"; import React from 'react';
import { Spin } from "@douyinfe/semi-ui"; import { Spin } from '@douyinfe/semi-ui';
import { useUser } from "data/user"; import { useUser } from 'data/user';
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 { useTemplate } from 'data/template';
import { Editor } from "./editor"; import { Editor } from './editor';
interface IProps { interface IProps {
templateId: string; templateId: string;

View File

@ -1,20 +1,16 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import { Button, Tooltip } from "@douyinfe/semi-ui"; import { Button, Tooltip } from '@douyinfe/semi-ui';
import { IconSun, IconMoon } from "@douyinfe/semi-icons"; import { IconSun, IconMoon } from '@douyinfe/semi-icons';
import { useTheme } from "hooks/useTheme"; import { useTheme } from 'hooks/useTheme';
export const Theme = () => { export const Theme = () => {
const { theme, toggle } = useTheme(); const { theme, toggle } = useTheme();
const Icon = theme === "dark" ? IconSun : IconMoon; const Icon = theme === 'dark' ? IconSun : IconMoon;
const text = theme === "dark" ? "切换到亮色模式" : "切换到深色模式"; const text = theme === 'dark' ? '切换到亮色模式' : '切换到深色模式';
return ( return (
<Tooltip content={text} position="bottom"> <Tooltip content={text} position="bottom">
<Button <Button onClick={toggle} icon={<Icon style={{ fontSize: 20 }} />} theme="borderless"></Button>
onClick={toggle}
icon={<Icon style={{ fontSize: 20 }} />}
theme="borderless"
></Button>
</Tooltip> </Tooltip>
); );
}; };

View File

@ -1,57 +1,57 @@
import { Document, TitledDocument, Title } from "./extensions/title"; import { Document, TitledDocument, Title } from './extensions/title';
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from '@tiptap/extension-placeholder';
import Paragraph from "@tiptap/extension-paragraph"; import Paragraph from '@tiptap/extension-paragraph';
import Text from "@tiptap/extension-text"; import Text from '@tiptap/extension-text';
import Strike from "@tiptap/extension-strike"; import Strike from '@tiptap/extension-strike';
import Underline from "@tiptap/extension-underline"; import Underline from '@tiptap/extension-underline';
import TextStyle from "@tiptap/extension-text-style"; import TextStyle from '@tiptap/extension-text-style';
import { Color } from "@tiptap/extension-color"; import { Color } from '@tiptap/extension-color';
import Blockquote from "@tiptap/extension-blockquote"; import Blockquote from '@tiptap/extension-blockquote';
import Bold from "@tiptap/extension-bold"; import Bold from '@tiptap/extension-bold';
import Code from "@tiptap/extension-code"; import Code from '@tiptap/extension-code';
import Highlight from "@tiptap/extension-highlight"; import Highlight from '@tiptap/extension-highlight';
import TextAlign from "@tiptap/extension-text-align"; import TextAlign from '@tiptap/extension-text-align';
import Dropcursor from "@tiptap/extension-dropcursor"; import Dropcursor from '@tiptap/extension-dropcursor';
import Gapcursor from "@tiptap/extension-gapcursor"; import Gapcursor from '@tiptap/extension-gapcursor';
import HardBreak from "@tiptap/extension-hard-break"; import HardBreak from '@tiptap/extension-hard-break';
import Heading from "@tiptap/extension-heading"; import Heading from '@tiptap/extension-heading';
import Italic from "@tiptap/extension-italic"; import Italic from '@tiptap/extension-italic';
import OrderedList from "@tiptap/extension-ordered-list"; import OrderedList from '@tiptap/extension-ordered-list';
import BulletList from "@tiptap/extension-bullet-list"; import BulletList from '@tiptap/extension-bullet-list';
import ListItem from "@tiptap/extension-list-item"; import ListItem from '@tiptap/extension-list-item';
import TaskList from "@tiptap/extension-task-list"; import TaskList from '@tiptap/extension-task-list';
import TaskItem from "@tiptap/extension-task-item"; import TaskItem from '@tiptap/extension-task-item';
import { HorizontalRule } from "./extensions/horizontal-rule"; import { HorizontalRule } from './extensions/horizontal-rule';
import { BackgroundColor } from "./extensions/background-color"; import { BackgroundColor } from './extensions/background-color';
import { Link } from "./extensions/link"; import { Link } from './extensions/link';
import { FontSize } from "./extensions/font-size"; import { FontSize } from './extensions/font-size';
import { ColorHighlighter } from "./extensions/color-highlight"; import { ColorHighlighter } from './extensions/color-highlight';
import { Indent } from "./extensions/indent"; import { Indent } from './extensions/indent';
import { Div } from "./extensions/div"; import { Div } from './extensions/div';
import { Banner } from "./extensions/banner"; import { Banner } from './extensions/banner';
import { CodeBlock } from "./extensions/code-block"; import { CodeBlock } from './extensions/code-block';
import { Iframe } from "./extensions/iframe"; import { Iframe } from './extensions/iframe';
import { Mind } from "./extensions/mind"; import { Mind } from './extensions/mind';
import { Image } from "./extensions/image"; import { Image } from './extensions/image';
import { Status } from "./extensions/status"; import { Status } from './extensions/status';
import { Paste } from "./extensions/paste"; import { Paste } from './extensions/paste';
import { Table, TableRow, TableCell, TableHeader } from "./extensions/table"; import { Table, TableRow, TableCell, TableHeader } from './extensions/table';
import { Toc } from "./extensions/toc"; import { Toc } from './extensions/toc';
import { TrailingNode } from "./extensions/trailing-node"; import { TrailingNode } from './extensions/trailing-node';
import { Attachment } from "./extensions/attachment"; import { Attachment } from './extensions/attachment';
import { Katex } from "./extensions/katex"; import { Katex } from './extensions/katex';
import { DocumentReference } from "./extensions/documents/reference"; import { DocumentReference } from './extensions/documents/reference';
import { DocumentChildren } from "./extensions/documents/children"; import { DocumentChildren } from './extensions/documents/children';
export { Document, TitledDocument }; export { Document, TitledDocument };
export const BaseExtension = [ export const BaseExtension = [
Placeholder.configure({ Placeholder.configure({
placeholder: ({ node }) => { placeholder: ({ node }) => {
if (node.type.name === "title") { if (node.type.name === 'title') {
return "请输入标题"; return '请输入标题';
} }
return "请输入内容"; return '请输入内容';
}, },
showOnlyWhenEditable: true, showOnlyWhenEditable: true,
}), }),
@ -80,7 +80,7 @@ export const BaseExtension = [
}), }),
Highlight.configure({ multicolor: true }), Highlight.configure({ multicolor: true }),
TextAlign.configure({ TextAlign.configure({
types: ["heading", "paragraph", "image"], types: ['heading', 'paragraph', 'image'],
}), }),
Link.configure({ openOnClick: false }), Link.configure({ openOnClick: false }),
Blockquote, Blockquote,

View File

@ -1,12 +1,7 @@
import { import { Editor, posToDOMRect, isTextSelection, isNodeSelection } from '@tiptap/core';
Editor, import { EditorState, Plugin, PluginKey } from 'prosemirror-state';
posToDOMRect, import { EditorView } from 'prosemirror-view';
isTextSelection, import tippy, { Instance, Props } from 'tippy.js';
isNodeSelection,
} from "@tiptap/core";
import { EditorState, Plugin, PluginKey } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import tippy, { Instance, Props } from "tippy.js";
export interface BubbleMenuPluginProps { export interface BubbleMenuPluginProps {
pluginKey: PluginKey | string; pluginKey: PluginKey | string;
@ -46,9 +41,9 @@ export class BubbleMenuView {
public renderContainerSelector?: string; public renderContainerSelector?: string;
public matchRenderContainer?: BubbleMenuPluginProps["matchRenderContainer"]; public matchRenderContainer?: BubbleMenuPluginProps['matchRenderContainer'];
public shouldShow: Exclude<BubbleMenuPluginProps["shouldShow"], null> = ({ public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({
view, view,
state, state,
from, from,
@ -60,8 +55,7 @@ export class BubbleMenuView {
// Sometime check for `empty` is not enough. // Sometime check for `empty` is not enough.
// Doubleclick an empty paragraph returns a node size of 2. // Doubleclick an empty paragraph returns a node size of 2.
// So we check also for an empty text size. // So we check also for an empty text size.
const isEmptyTextBlock = const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection);
!doc.textBetween(from, to).length && isTextSelection(state.selection);
if (!view.hasFocus() || empty || isEmptyTextBlock) { if (!view.hasFocus() || empty || isEmptyTextBlock) {
return false; return false;
@ -89,16 +83,16 @@ export class BubbleMenuView {
this.shouldShow = shouldShow; this.shouldShow = shouldShow;
} }
this.element.addEventListener("mousedown", this.mousedownHandler, { this.element.addEventListener('mousedown', this.mousedownHandler, {
capture: true, capture: true,
}); });
this.view.dom.addEventListener("dragstart", this.dragstartHandler); this.view.dom.addEventListener('dragstart', this.dragstartHandler);
this.editor.on("focus", this.focusHandler); this.editor.on('focus', this.focusHandler);
this.editor.on("blur", this.blurHandler); this.editor.on('blur', this.blurHandler);
this.tippyOptions = tippyOptions; this.tippyOptions = tippyOptions;
// Detaches menu content from its current parent // Detaches menu content from its current parent
this.element.remove(); this.element.remove();
this.element.style.visibility = "visible"; this.element.style.visibility = 'visible';
} }
mousedownHandler = () => { mousedownHandler = () => {
@ -121,10 +115,7 @@ export class BubbleMenuView {
return; return;
} }
if ( if (event?.relatedTarget && this.element.parentNode?.contains(event.relatedTarget as Node)) {
event?.relatedTarget &&
this.element.parentNode?.contains(event.relatedTarget as Node)
) {
return; return;
} }
@ -144,28 +135,24 @@ export class BubbleMenuView {
getReferenceClientRect: null, getReferenceClientRect: null,
content: this.element, content: this.element,
interactive: true, interactive: true,
trigger: "manual", trigger: 'manual',
placement: "top", placement: 'top',
hideOnClick: "toggle", hideOnClick: 'toggle',
...this.tippyOptions, ...this.tippyOptions,
}); });
// maybe we have to hide tippy on its own blur event as well // maybe we have to hide tippy on its own blur event as well
if (this.tippy.popper.firstChild) { if (this.tippy.popper.firstChild) {
(this.tippy.popper.firstChild as HTMLElement).addEventListener( (this.tippy.popper.firstChild as HTMLElement).addEventListener('blur', (event) => {
"blur", this.blurHandler({ event });
(event) => { });
this.blurHandler({ event });
}
);
} }
} }
update(view: EditorView, oldState?: EditorState) { update(view: EditorView, oldState?: EditorState) {
const { state, composing } = view; const { state, composing } = view;
const { doc, selection } = state; const { doc, selection } = state;
const isSame = const isSame = oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
if (composing || isSame) { if (composing || isSame) {
return; return;
@ -242,21 +229,19 @@ export class BubbleMenuView {
destroy() { destroy() {
this.tippy?.destroy(); this.tippy?.destroy();
this.element.removeEventListener("mousedown", this.mousedownHandler, { this.element.removeEventListener('mousedown', this.mousedownHandler, {
capture: true, capture: true,
}); });
this.view.dom.removeEventListener("dragstart", this.dragstartHandler); this.view.dom.removeEventListener('dragstart', this.dragstartHandler);
this.editor.off("focus", this.focusHandler); this.editor.off('focus', this.focusHandler);
this.editor.off("blur", this.blurHandler); this.editor.off('blur', this.blurHandler);
} }
} }
export const BubbleMenuPlugin = (options: BubbleMenuPluginProps) => { export const BubbleMenuPlugin = (options: BubbleMenuPluginProps) => {
return new Plugin({ return new Plugin({
key: key:
typeof options.pluginKey === "string" typeof options.pluginKey === 'string' ? new PluginKey(options.pluginKey) : options.pluginKey,
? new PluginKey(options.pluginKey)
: options.pluginKey,
view: (view) => new BubbleMenuView({ view, ...options }), view: (view) => new BubbleMenuView({ view, ...options }),
}); });
}; };

View File

@ -1,12 +1,9 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import { BubbleMenuPlugin, BubbleMenuPluginProps } from "./bubble-menu-plugin"; import { BubbleMenuPlugin, BubbleMenuPluginProps } from './bubble-menu-plugin';
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
export type BubbleMenuProps = Omit< export type BubbleMenuProps = Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'> & {
Optional<BubbleMenuPluginProps, "pluginKey">,
"element"
> & {
className?: string; className?: string;
}; };
@ -23,7 +20,7 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = (props) => {
} }
const { const {
pluginKey = "bubbleMenu", pluginKey = 'bubbleMenu',
editor, editor,
tippyOptions = {}, tippyOptions = {},
shouldShow = null, shouldShow = null,
@ -46,11 +43,7 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = (props) => {
}, [props.editor, element]); }, [props.editor, element]);
return ( return (
<div <div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
ref={setElement}
className={props.className}
style={{ visibility: "hidden" }}
>
{props.children} {props.children}
</div> </div>
); );

View File

@ -1,52 +1,47 @@
import React from "react"; import React from 'react';
import { Dropdown } from "@douyinfe/semi-ui"; import { Dropdown } from '@douyinfe/semi-ui';
import styles from "./style.module.scss"; import styles from './style.module.scss';
const colors = [ const colors = [
"rgb(23, 43, 77)", 'rgb(23, 43, 77)',
"rgb(7, 71, 166)", 'rgb(7, 71, 166)',
"rgb(0, 141, 166)", 'rgb(0, 141, 166)',
"rgb(0, 102, 68)", 'rgb(0, 102, 68)',
"rgb(255, 153, 31)", 'rgb(255, 153, 31)',
"rgb(191, 38, 0)", 'rgb(191, 38, 0)',
"rgb(64, 50, 148)", 'rgb(64, 50, 148)',
"rgb(151, 160, 175)", 'rgb(151, 160, 175)',
"rgb(76, 154, 255)", 'rgb(76, 154, 255)',
"rgb(0, 184, 217)", 'rgb(0, 184, 217)',
"rgb(54, 179, 126)", 'rgb(54, 179, 126)',
"rgb(255, 196, 0)", 'rgb(255, 196, 0)',
"rgb(255, 86, 48)", 'rgb(255, 86, 48)',
"rgb(101, 84, 192)", 'rgb(101, 84, 192)',
"rgb(255, 255, 255)", 'rgb(255, 255, 255)',
"rgb(179, 212, 255)", 'rgb(179, 212, 255)',
"rgb(179, 245, 255)", 'rgb(179, 245, 255)',
"rgb(171, 245, 209)", 'rgb(171, 245, 209)',
"rgb(255, 240, 179)", 'rgb(255, 240, 179)',
"rgb(255, 189, 173)", 'rgb(255, 189, 173)',
"rgb(234, 230, 255)", 'rgb(234, 230, 255)',
]; ];
export const Color: React.FC<{ export const Color: React.FC<{
onSetColor; onSetColor;
disabled?: boolean; disabled?: boolean;
}> = ({ children, onSetColor, disabled = false }) => { }> = ({ children, onSetColor, disabled = false }) => {
if (disabled) if (disabled) return <span style={{ display: 'inline-block' }}>{children}</span>;
return <span style={{ display: "inline-block" }}>{children}</span>;
return ( return (
<Dropdown <Dropdown
zIndex={10000} zIndex={10000}
trigger="click" trigger="click"
position={"bottom"} position={'bottom'}
render={ render={
<div className={styles.colorWrap}> <div className={styles.colorWrap}>
{colors.map((color) => { {colors.map((color) => {
return ( return (
<div <div key={color} className={styles.colorItem} onClick={() => onSetColor(color)}>
key={color}
className={styles.colorItem}
onClick={() => onSetColor(color)}
>
<span style={{ backgroundColor: color }}></span> <span style={{ backgroundColor: color }}></span>
</div> </div>
); );
@ -54,7 +49,7 @@ export const Color: React.FC<{
</div> </div>
} }
> >
<span style={{ display: "inline-block" }}>{children}</span> <span style={{ display: 'inline-block' }}>{children}</span>
</Dropdown> </Dropdown>
); );
}; };

View File

@ -2,11 +2,11 @@ export const Divider = () => {
return ( return (
<div <div
style={{ style={{
display: "inline-block", display: 'inline-block',
width: 1, width: 1,
height: 24, height: 24,
margin: "0 6px", margin: '0 6px',
backgroundColor: "var(--semi-color-border)", backgroundColor: 'var(--semi-color-border)',
}} }}
></div> ></div>
); );

View File

@ -1,13 +1,9 @@
import { Node, mergeAttributes } from "@tiptap/core"; import { Node, mergeAttributes } from '@tiptap/core';
import { import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
NodeViewWrapper, import { Button, Tooltip } from '@douyinfe/semi-ui';
NodeViewContent, import { IconDownload } from '@douyinfe/semi-icons';
ReactNodeViewRenderer, import { download } from '../../utils/download';
} from "@tiptap/react"; import styles from './index.module.scss';
import { Button, Tooltip } from "@douyinfe/semi-ui";
import { IconDownload } from "@douyinfe/semi-icons";
import { download } from "../../utils/download";
import styles from "./index.module.scss";
const Render = ({ node }) => { const Render = ({ node }) => {
const { name, url } = node.attrs; const { name, url } = node.attrs;
@ -19,7 +15,7 @@ const Render = ({ node }) => {
<span> <span>
<Tooltip zIndex={10000} content="下载"> <Tooltip zIndex={10000} content="下载">
<Button <Button
theme={"borderless"} theme={'borderless'}
type="tertiary" type="tertiary"
icon={<IconDownload />} icon={<IconDownload />}
onClick={() => download(url, name)} onClick={() => download(url, name)}
@ -33,27 +29,24 @@ const Render = ({ node }) => {
}; };
export const Attachment = Node.create({ export const Attachment = Node.create({
name: "attachment", name: 'attachment',
group: "block", group: 'block',
draggable: true, draggable: true,
addOptions() { addOptions() {
return { return {
HTMLAttributes: { HTMLAttributes: {
class: "attachment", class: 'attachment',
}, },
}; };
}, },
parseHTML() { parseHTML() {
return [{ tag: "div[class=attachment]" }]; return [{ tag: 'div[class=attachment]' }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
"div",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
];
}, },
addAttributes() { addAttributes() {

View File

@ -1,11 +1,11 @@
import { Extension } from "@tiptap/core"; import { Extension } from '@tiptap/core';
import "@tiptap/extension-text-style"; import '@tiptap/extension-text-style';
export type ColorOptions = { export type ColorOptions = {
types: string[]; types: string[];
}; };
declare module "@tiptap/core" { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {
backgroundColor: { backgroundColor: {
/** /**
@ -21,11 +21,11 @@ declare module "@tiptap/core" {
} }
export const BackgroundColor = Extension.create<ColorOptions>({ export const BackgroundColor = Extension.create<ColorOptions>({
name: "backgroundColor", name: 'backgroundColor',
addOptions() { addOptions() {
return { return {
types: ["textStyle"], types: ['textStyle'],
}; };
}, },
@ -36,8 +36,7 @@ export const BackgroundColor = Extension.create<ColorOptions>({
attributes: { attributes: {
backgroundColor: { backgroundColor: {
default: null, default: null,
parseHTML: (element) => parseHTML: (element) => element.style.backgroundColor.replace(/['"]+/g, ''),
element.style.backgroundColor.replace(/['"]+/g, ""),
renderHTML: (attributes) => { renderHTML: (attributes) => {
if (!attributes.backgroundColor) { if (!attributes.backgroundColor) {
return {}; return {};
@ -58,13 +57,13 @@ export const BackgroundColor = Extension.create<ColorOptions>({
setBackgroundColor: setBackgroundColor:
(color) => (color) =>
({ chain }) => { ({ chain }) => {
return chain().setMark("textStyle", { backgroundColor: color }).run(); return chain().setMark('textStyle', { backgroundColor: color }).run();
}, },
unsetBackgroundColor: unsetBackgroundColor:
() => () =>
({ chain }) => { ({ chain }) => {
return chain() return chain()
.setMark("textStyle", { backgroundColor: null }) .setMark('textStyle', { backgroundColor: null })
.removeEmptyTextStyle() .removeEmptyTextStyle()
.run(); .run();
}, },

View File

@ -1,13 +1,9 @@
import { Node, Command, mergeAttributes } from "@tiptap/core"; import { Node, Command, mergeAttributes } from '@tiptap/core';
import { import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
NodeViewWrapper, import { Banner as SemiBanner } from '@douyinfe/semi-ui';
NodeViewContent, import styles from './index.module.scss';
ReactNodeViewRenderer,
} from "@tiptap/react";
import { Banner as SemiBanner } from "@douyinfe/semi-ui";
import styles from "./index.module.scss";
declare module "@tiptap/core" { declare module '@tiptap/core' {
interface Commands { interface Commands {
banner: { banner: {
setBanner: () => Command; setBanner: () => Command;
@ -16,34 +12,31 @@ declare module "@tiptap/core" {
} }
const BannerExtension = Node.create({ const BannerExtension = Node.create({
name: "banner", name: 'banner',
content: "block*", content: 'block*',
group: "block", group: 'block',
defining: true, defining: true,
draggable: true, draggable: true,
addAttributes() { addAttributes() {
return { return {
type: { type: {
default: "info", default: 'info',
}, },
}; };
}, },
parseHTML() { parseHTML() {
return [{ tag: "div" }]; return [{ tag: 'div' }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return [
"div", 'div',
{ class: "banner" }, { class: 'banner' },
[ [
"div", 'div',
mergeAttributes( mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes),
(this.options && this.options.HTMLAttributes) || {},
HTMLAttributes
),
0, 0,
], ],
]; ];

View File

@ -1,16 +1,12 @@
import React, { useRef } from "react"; import React, { useRef } from 'react';
import { import { ReactNodeViewRenderer, NodeViewWrapper, NodeViewContent } from '@tiptap/react';
ReactNodeViewRenderer, import { Button, Select, Tooltip } from '@douyinfe/semi-ui';
NodeViewWrapper, import { IconCopy } from '@douyinfe/semi-icons';
NodeViewContent, import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
} from "@tiptap/react";
import { Button, Select, Tooltip } from "@douyinfe/semi-ui";
import { IconCopy } from "@douyinfe/semi-icons";
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
// @ts-ignore // @ts-ignore
import { lowlight } from "lowlight"; import { lowlight } from 'lowlight';
import { copy } from "helpers/copy"; import { copy } from 'helpers/copy';
import styles from "./index.module.scss"; import styles from './index.module.scss';
const Render = ({ const Render = ({
editor, editor,
@ -29,7 +25,7 @@ const Render = ({
{isEditable && ( {isEditable && (
<Select <Select
size="small" size="small"
defaultValue={defaultLanguage || "null"} defaultValue={defaultLanguage || 'null'}
onChange={(value) => updateAttributes({ language: value })} onChange={(value) => updateAttributes({ language: value })}
className={styles.selectorWrap} className={styles.selectorWrap}
> >

View File

@ -1,9 +1,9 @@
import { Extension } from "@tiptap/core"; import { Extension } from '@tiptap/core';
import { Plugin } from "prosemirror-state"; import { Plugin } from 'prosemirror-state';
import findColors from "../utils/find-colors"; import findColors from '../utils/find-colors';
export const ColorHighlighter = Extension.create({ export const ColorHighlighter = Extension.create({
name: "colorHighlighter", name: 'colorHighlighter',
addProseMirrorPlugins() { addProseMirrorPlugins() {
return [ return [
@ -13,9 +13,7 @@ export const ColorHighlighter = Extension.create({
return findColors(doc); return findColors(doc);
}, },
apply(transaction, oldState) { apply(transaction, oldState) {
return transaction.docChanged return transaction.docChanged ? findColors(transaction.doc) : oldState;
? findColors(transaction.doc)
: oldState;
}, },
}, },
props: { props: {

View File

@ -1,24 +1,20 @@
import { Node, mergeAttributes } from "@tiptap/core"; import { Node, mergeAttributes } from '@tiptap/core';
export const Div = Node.create({ export const Div = Node.create({
name: "div", name: 'div',
addOptions() { addOptions() {
return { return {
HTMLAttributes: {}, HTMLAttributes: {},
}; };
}, },
content: "block*", content: 'block*',
group: "block", group: 'block',
defining: true, defining: true,
parseHTML() { parseHTML() {
return [{ tag: "div" }]; return [{ tag: 'div' }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
"div",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
];
}, },
// @ts-ignore // @ts-ignore
addCommands() { addCommands() {
@ -26,17 +22,17 @@ export const Div = Node.create({
setDiv: setDiv:
(attributes) => (attributes) =>
({ commands }) => { ({ commands }) => {
return commands.wrapIn("div", attributes); return commands.wrapIn('div', attributes);
}, },
toggleDiv: toggleDiv:
(attributes) => (attributes) =>
({ commands }) => { ({ commands }) => {
return commands.toggleWrap("div", attributes); return commands.toggleWrap('div', attributes);
}, },
unsetDiv: unsetDiv:
(attributes) => (attributes) =>
({ commands }) => { ({ commands }) => {
return commands.lift("div"); return commands.lift('div');
}, },
}; };
}, },

View File

@ -5,24 +5,20 @@ import {
textInputRule, textInputRule,
textblockTypeInputRule, textblockTypeInputRule,
wrappingInputRule, wrappingInputRule,
} from "@tiptap/core"; } from '@tiptap/core';
import { import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
NodeViewWrapper, import { useRouter } from 'next/router';
NodeViewContent, import Link from 'next/link';
ReactNodeViewRenderer, import { Space, Popover, Tag, Input, Typography } from '@douyinfe/semi-ui';
} from "@tiptap/react"; import { useChildrenDocument } from 'data/document';
import { useRouter } from "next/router"; import { DataRender } from 'components/data-render';
import Link from "next/link"; import { Empty } from 'components/empty';
import { Space, Popover, Tag, Input, Typography } from "@douyinfe/semi-ui"; import { IconDocument } from 'components/icons';
import { useChildrenDocument } from "data/document"; import styles from './index.module.scss';
import { DataRender } from "components/data-render";
import { Empty } from "components/empty";
import { IconDocument } from "components/icons";
import styles from "./index.module.scss";
const { Text } = Typography; const { Text } = Typography;
declare module "@tiptap/core" { declare module '@tiptap/core' {
interface Commands { interface Commands {
documentChildren: { documentChildren: {
setDocumentChildren: () => Command; setDocumentChildren: () => Command;
@ -33,33 +29,30 @@ declare module "@tiptap/core" {
export const DocumentChildrenInputRegex = /^documentChildren\$$/; export const DocumentChildrenInputRegex = /^documentChildren\$$/;
const DocumentChildrenExtension = Node.create({ const DocumentChildrenExtension = Node.create({
name: "documentChildren", name: 'documentChildren',
group: "block", group: 'block',
defining: true, defining: true,
draggable: true, draggable: true,
addAttributes() { addAttributes() {
return { return {
color: { color: {
default: "grey", default: 'grey',
}, },
text: { text: {
default: "", default: '',
}, },
}; };
}, },
parseHTML() { parseHTML() {
return [{ tag: "div[data-type=documentChildren]" }]; return [{ tag: 'div[data-type=documentChildren]' }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return [
"div", 'div',
mergeAttributes( mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes),
(this.options && this.options.HTMLAttributes) || {},
HTMLAttributes
),
]; ];
}, },
@ -91,12 +84,8 @@ const Render = () => {
const { pathname, query } = useRouter(); const { pathname, query } = useRouter();
const wikiId = query?.wikiId; const wikiId = query?.wikiId;
const documentId = query?.documentId; const documentId = query?.documentId;
const isShare = pathname.includes("share"); const isShare = pathname.includes('share');
const { const { data: documents, loading, error } = useChildrenDocument({ wikiId, documentId, isShare });
data: documents,
loading,
error,
} = useChildrenDocument({ wikiId, documentId, isShare });
return ( return (
<NodeViewWrapper as="div" className={styles.wrap}> <NodeViewWrapper as="div" className={styles.wrap}>
@ -120,7 +109,7 @@ const Render = () => {
key={doc.id} key={doc.id}
href={{ href={{
pathname: `${ pathname: `${
!isShare ? "" : "/share" !isShare ? '' : '/share'
}/wiki/[wikiId]/document/[documentId]`, }/wiki/[wikiId]/document/[documentId]`,
query: { wikiId: doc.wikiId, documentId: doc.id }, query: { wikiId: doc.wikiId, documentId: doc.id },
}} }}

View File

@ -5,32 +5,21 @@ import {
textInputRule, textInputRule,
textblockTypeInputRule, textblockTypeInputRule,
wrappingInputRule, wrappingInputRule,
} from "@tiptap/core"; } from '@tiptap/core';
import { import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
NodeViewWrapper, import { useRouter } from 'next/router';
NodeViewContent, import Link from 'next/link';
ReactNodeViewRenderer, import { Space, Select, Popover, Tag, Input, Typography } from '@douyinfe/semi-ui';
} from "@tiptap/react"; import { useWikiTocs } from 'data/wiki';
import { useRouter } from "next/router"; import { useDocumentDetail } from 'data/document';
import Link from "next/link"; import { DataRender } from 'components/data-render';
import { import { Empty } from 'components/empty';
Space, import { IconDocument } from 'components/icons';
Select, import styles from './index.module.scss';
Popover,
Tag,
Input,
Typography,
} from "@douyinfe/semi-ui";
import { useWikiTocs } from "data/wiki";
import { useDocumentDetail } from "data/document";
import { DataRender } from "components/data-render";
import { Empty } from "components/empty";
import { IconDocument } from "components/icons";
import styles from "./index.module.scss";
const { Text } = Typography; const { Text } = Typography;
declare module "@tiptap/core" { declare module '@tiptap/core' {
interface Commands { interface Commands {
documentReference: { documentReference: {
setDocumentReference: () => Command; setDocumentReference: () => Command;
@ -41,36 +30,33 @@ declare module "@tiptap/core" {
export const DocumentReferenceInputRegex = /^documentReference\$$/; export const DocumentReferenceInputRegex = /^documentReference\$$/;
const DocumentReferenceExtension = Node.create({ const DocumentReferenceExtension = Node.create({
name: "documentReference", name: 'documentReference',
group: "block", group: 'block',
defining: true, defining: true,
draggable: true, draggable: true,
addAttributes() { addAttributes() {
return { return {
wikiId: { wikiId: {
default: "", default: '',
}, },
documentId: { documentId: {
default: "", default: '',
}, },
title: { title: {
default: "", default: '',
}, },
}; };
}, },
parseHTML() { parseHTML() {
return [{ tag: "div[data-type=documentReference]" }]; return [{ tag: 'div[data-type=documentReference]' }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return [
"div", 'div',
mergeAttributes( mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes),
(this.options && this.options.HTMLAttributes) || {},
HTMLAttributes
),
]; ];
}, },
@ -101,14 +87,10 @@ const DocumentReferenceExtension = Node.create({
const Render = ({ editor, node, updateAttributes }) => { const Render = ({ editor, node, updateAttributes }) => {
const { pathname, query } = useRouter(); const { pathname, query } = useRouter();
const wikiIdFromUrl = query?.wikiId; const wikiIdFromUrl = query?.wikiId;
const isShare = pathname.includes("share"); const isShare = pathname.includes('share');
const isEditable = editor.isEditable; const isEditable = editor.isEditable;
const { wikiId, documentId, title } = node.attrs; const { wikiId, documentId, title } = node.attrs;
const { const { data: tocs, loading, error } = useWikiTocs(isShare ? null : wikiIdFromUrl);
data: tocs,
loading,
error,
} = useWikiTocs(isShare ? null : wikiIdFromUrl);
return ( return (
<NodeViewWrapper as="div" className={styles.wrap}> <NodeViewWrapper as="div" className={styles.wrap}>
@ -141,9 +123,7 @@ const Render = ({ editor, node, updateAttributes }) => {
<Link <Link
key={documentId} key={documentId}
href={{ href={{
pathname: `${ pathname: `${!isShare ? '' : '/share'}/wiki/[wikiId]/document/[documentId]`,
!isShare ? "" : "/share"
}/wiki/[wikiId]/document/[documentId]`,
query: { wikiId, documentId }, query: { wikiId, documentId },
}} }}
> >

View File

@ -1,11 +1,11 @@
import { Extension } from "@tiptap/core"; import { Extension } from '@tiptap/core';
import "@tiptap/extension-text-style"; import '@tiptap/extension-text-style';
type FontSizeOptions = { type FontSizeOptions = {
types: string[]; types: string[];
}; };
declare module "@tiptap/core" { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {
fontSize: { fontSize: {
setFontSize: (size: string) => ReturnType; setFontSize: (size: string) => ReturnType;
@ -15,11 +15,11 @@ declare module "@tiptap/core" {
} }
export const FontSize = Extension.create<FontSizeOptions>({ export const FontSize = Extension.create<FontSizeOptions>({
name: "fontSize", name: 'fontSize',
addOptions() { addOptions() {
return { return {
types: ["textStyle"], types: ['textStyle'],
}; };
}, },
@ -30,8 +30,7 @@ export const FontSize = Extension.create<FontSizeOptions>({
attributes: { attributes: {
fontSize: { fontSize: {
default: null, default: null,
parseHTML: (element) => parseHTML: (element) => element.style.fontSize.replace(/['"]+/g, ''),
element.style.fontSize.replace(/['"]+/g, ""),
renderHTML: (attributes) => { renderHTML: (attributes) => {
if (!attributes.fontSize) { if (!attributes.fontSize) {
return {}; return {};
@ -52,15 +51,12 @@ export const FontSize = Extension.create<FontSizeOptions>({
setFontSize: setFontSize:
(fontSize) => (fontSize) =>
({ chain }) => { ({ chain }) => {
return chain().setMark("textStyle", { fontSize }).run(); return chain().setMark('textStyle', { fontSize }).run();
}, },
unsetFontSize: unsetFontSize:
() => () =>
({ chain }) => { ({ chain }) => {
return chain() return chain().setMark('textStyle', { fontSize: null }).removeEmptyTextStyle().run();
.setMark("textStyle", { fontSize: null })
.removeEmptyTextStyle()
.run();
}, },
}; };
}, },

View File

@ -1,11 +1,11 @@
import { Node, nodeInputRule, mergeAttributes } from "@tiptap/core"; import { Node, nodeInputRule, mergeAttributes } from '@tiptap/core';
import { TextSelection } from "prosemirror-state"; import { TextSelection } from 'prosemirror-state';
export interface HorizontalRuleOptions { export interface HorizontalRuleOptions {
HTMLAttributes: Record<string, any>; HTMLAttributes: Record<string, any>;
} }
declare module "@tiptap/core" { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {
horizontalRule: { horizontalRule: {
/** /**
@ -17,26 +17,23 @@ declare module "@tiptap/core" {
} }
export const HorizontalRule = Node.create<HorizontalRuleOptions>({ export const HorizontalRule = Node.create<HorizontalRuleOptions>({
name: "horizontalRule", name: 'horizontalRule',
group: "block", group: 'block',
addOptions() { addOptions() {
return { return {
HTMLAttributes: { HTMLAttributes: {
class: "hr-line", class: 'hr-line',
}, },
}; };
}, },
parseHTML() { parseHTML() {
return [{ tag: "div[class=hr-line]" }]; return [{ tag: 'div[class=hr-line]' }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
"div",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
];
}, },
addCommands() { addCommands() {
@ -57,8 +54,7 @@ export const HorizontalRule = Node.create<HorizontalRuleOptions>({
tr.setSelection(TextSelection.create(tr.doc, $to.pos)); tr.setSelection(TextSelection.create(tr.doc, $to.pos));
} else { } else {
// add node after horizontal rule if its the end of the document // add node after horizontal rule if its the end of the document
const node = const node = $to.parent.type.contentMatch.defaultType?.create();
$to.parent.type.contentMatch.defaultType?.create();
if (node) { if (node) {
tr.insert(posAfter, node); tr.insert(posAfter, node);

View File

@ -1,24 +1,20 @@
import { Node, mergeAttributes } from "@tiptap/core"; import { Node, mergeAttributes } from '@tiptap/core';
import { import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
NodeViewWrapper, import { Input } from '@douyinfe/semi-ui';
NodeViewContent, import { Resizeable } from 'components/resizeable';
ReactNodeViewRenderer, import styles from './index.module.scss';
} from "@tiptap/react";
import { Input } from "@douyinfe/semi-ui";
import { Resizeable } from "components/resizeable";
import styles from "./index.module.scss";
const IframeNode = Node.create({ const IframeNode = Node.create({
name: "external-iframe", name: 'external-iframe',
content: "", content: '',
marks: "", marks: '',
group: "block", group: 'block',
draggable: true, draggable: true,
addOptions() { addOptions() {
return { return {
HTMLAttributes: { HTMLAttributes: {
"data-type": "external-iframe", 'data-type': 'external-iframe',
}, },
}; };
}, },
@ -26,7 +22,7 @@ const IframeNode = Node.create({
addAttributes() { addAttributes() {
return { return {
width: { width: {
default: "100%", default: '100%',
}, },
height: { height: {
default: 54, default: 54,
@ -46,10 +42,7 @@ const IframeNode = Node.create({
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return ['iframe', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
"iframe",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
];
}, },
// @ts-ignore // @ts-ignore
@ -91,17 +84,14 @@ const Render = ({ editor, node, updateAttributes }) => {
{isEditable && ( {isEditable && (
<div className={styles.handlerWrap}> <div className={styles.handlerWrap}>
<Input <Input
placeholder={"输入外链地址"} placeholder={'输入外链地址'}
value={url} value={url}
onChange={(url) => updateAttributes({ url })} onChange={(url) => updateAttributes({ url })}
></Input> ></Input>
</div> </div>
)} )}
{url && ( {url && (
<div <div className={styles.innerWrap} style={{ pointerEvents: !isEditable ? 'auto' : 'none' }}>
className={styles.innerWrap}
style={{ pointerEvents: !isEditable ? "auto" : "none" }}
>
<iframe src={url}></iframe> <iframe src={url}></iframe>
</div> </div>
)} )}
@ -119,7 +109,7 @@ const Render = ({ editor, node, updateAttributes }) => {
{content} {content}
</Resizeable> </Resizeable>
) : ( ) : (
<div style={{ width, height, maxWidth: "100%" }}>{content}</div> <div style={{ width, height, maxWidth: '100%' }}>{content}</div>
)} )}
</NodeViewWrapper> </NodeViewWrapper>
); );

View File

@ -1,12 +1,8 @@
import { Plugin } from "prosemirror-state"; import { Plugin } from 'prosemirror-state';
import { Image as TImage } from "@tiptap/extension-image"; import { Image as TImage } from '@tiptap/extension-image';
import { import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
NodeViewWrapper, import { Resizeable } from 'components/resizeable';
NodeViewContent, import { uploadFile } from 'services/file';
ReactNodeViewRenderer,
} from "@tiptap/react";
import { Resizeable } from "components/resizeable";
import { uploadFile } from "services/file";
const Render = ({ editor, node, updateAttributes }) => { const Render = ({ editor, node, updateAttributes }) => {
const isEditable = editor.isEditable; const isEditable = editor.isEditable;
@ -16,25 +12,16 @@ const Render = ({ editor, node, updateAttributes }) => {
updateAttributes({ height: size.height, width: size.width }); updateAttributes({ height: size.height, width: size.width });
}; };
const content = src && ( const content = src && <img src={src} alt={title} style={{ width: '100%', height: '100%' }} />;
<img src={src} alt={title} style={{ width: "100%", height: "100%" }} />
);
return ( return (
<NodeViewWrapper <NodeViewWrapper as="div" style={{ textAlign, fontSize: 0, maxWidth: '100%' }}>
as="div"
style={{ textAlign, fontSize: 0, maxWidth: "100%" }}
>
{isEditable ? ( {isEditable ? (
<Resizeable width={width} height={height} onChange={onResize}> <Resizeable width={width} height={height} onChange={onResize}>
{content} {content}
</Resizeable> </Resizeable>
) : ( ) : (
<div <div style={{ display: 'inline-block', width, height, maxWidth: '100%' }}>{content}</div>
style={{ display: "inline-block", width, height, maxWidth: "100%" }}
>
{content}
</div>
)} )}
</NodeViewWrapper> </NodeViewWrapper>
); );
@ -54,10 +41,10 @@ export const Image = TImage.extend({
default: null, default: null,
}, },
width: { width: {
default: "auto", default: 'auto',
}, },
height: { height: {
default: "auto", default: 'auto',
}, },
}; };
}, },

View File

@ -1,10 +1,10 @@
import { Command, Extension } from "@tiptap/core"; import { Command, Extension } from '@tiptap/core';
import { sinkListItem, liftListItem } from "prosemirror-schema-list"; import { sinkListItem, liftListItem } from 'prosemirror-schema-list';
import { TextSelection, AllSelection, Transaction } from "prosemirror-state"; import { TextSelection, AllSelection, Transaction } from 'prosemirror-state';
import { clamp } from "../utils/shared"; import { clamp } from '../utils/shared';
import { isListActive } from "../utils/active"; import { isListActive } from '../utils/active';
import { getNodeType } from "../utils/type"; import { getNodeType } from '../utils/type';
import { isListNode } from "../utils/node"; import { isListNode } from '../utils/node';
type IndentOptions = { type IndentOptions = {
types: string[]; types: string[];
@ -12,7 +12,7 @@ type IndentOptions = {
defaultIndentLevel: number; defaultIndentLevel: number;
}; };
declare module "@tiptap/core" { declare module '@tiptap/core' {
interface Commands { interface Commands {
indent: { indent: {
indent: () => Command; indent: () => Command;
@ -28,11 +28,7 @@ export enum IndentProps {
less = -30, less = -30,
} }
function setNodeIndentMarkup( function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number): Transaction {
tr: Transaction,
pos: number,
delta: number
): Transaction {
if (!tr.doc) return tr; if (!tr.doc) return tr;
const node = tr.doc.nodeAt(pos); const node = tr.doc.nodeAt(pos);
@ -58,9 +54,7 @@ function updateIndentLevel(tr: Transaction, delta: number): Transaction {
if (!doc || !selection) return tr; if (!doc || !selection) return tr;
if ( if (!(selection instanceof TextSelection || selection instanceof AllSelection)) {
!(selection instanceof TextSelection || selection instanceof AllSelection)
) {
return tr; return tr;
} }
@ -69,7 +63,7 @@ function updateIndentLevel(tr: Transaction, delta: number): Transaction {
doc.nodesBetween(from, to, (node, pos) => { doc.nodesBetween(from, to, (node, pos) => {
const nodeType = node.type; const nodeType = node.type;
if (nodeType.name === "paragraph" || nodeType.name === "heading") { if (nodeType.name === 'paragraph' || nodeType.name === 'heading') {
tr = setNodeIndentMarkup(tr, pos, delta); tr = setNodeIndentMarkup(tr, pos, delta);
return false; return false;
} }
@ -83,11 +77,11 @@ function updateIndentLevel(tr: Transaction, delta: number): Transaction {
} }
export const Indent = Extension.create<IndentOptions>({ export const Indent = Extension.create<IndentOptions>({
name: "indent", name: 'indent',
addOptions() { addOptions() {
return { return {
types: ["heading", "paragraph"], types: ['heading', 'paragraph'],
indentLevels: [0, 30, 60, 90, 120, 150, 180, 210], indentLevels: [0, 30, 60, 90, 120, 150, 180, 210],
defaultIndentLevel: 0, defaultIndentLevel: 0,
}; };
@ -104,8 +98,7 @@ export const Indent = Extension.create<IndentOptions>({
style: `margin-left: ${attributes.indent}px!important;`, style: `margin-left: ${attributes.indent}px!important;`,
}), }),
parseHTML: (element) => parseHTML: (element) =>
parseInt(element.style.marginLeft) || parseInt(element.style.marginLeft) || this.options.defaultIndentLevel,
this.options.defaultIndentLevel,
}, },
}, },
}, },
@ -118,9 +111,7 @@ export const Indent = Extension.create<IndentOptions>({
() => () =>
({ tr, state, dispatch }) => { ({ tr, state, dispatch }) => {
if (isListActive(this.editor)) { if (isListActive(this.editor)) {
const name = this.editor.can().liftListItem("taskItem") const name = this.editor.can().liftListItem('taskItem') ? 'taskItem' : 'listItem';
? "taskItem"
: "listItem";
const type = getNodeType(name, state.schema); const type = getNodeType(name, state.schema);
return sinkListItem(type)(state, dispatch); return sinkListItem(type)(state, dispatch);
} }
@ -140,9 +131,7 @@ export const Indent = Extension.create<IndentOptions>({
() => () =>
({ tr, state, dispatch }) => { ({ tr, state, dispatch }) => {
if (isListActive(this.editor)) { if (isListActive(this.editor)) {
const name = this.editor.can().liftListItem("taskItem") const name = this.editor.can().liftListItem('taskItem') ? 'taskItem' : 'listItem';
? "taskItem"
: "listItem";
const type = getNodeType(name, state.schema); const type = getNodeType(name, state.schema);
return liftListItem(type)(state, dispatch); return liftListItem(type)(state, dispatch);
} }
@ -164,10 +153,10 @@ export const Indent = Extension.create<IndentOptions>({
// @ts-ignore // @ts-ignore
addKeyboardShortcuts() { addKeyboardShortcuts() {
return { return {
Tab: () => { 'Tab': () => {
return this.editor.commands.indent(); return this.editor.commands.indent();
}, },
"Shift-Tab": () => { 'Shift-Tab': () => {
return this.editor.commands.outdent(); return this.editor.commands.outdent();
}, },
}; };

View File

@ -1,21 +1,12 @@
import { import { Node, Command, mergeAttributes, wrappingInputRule } from '@tiptap/core';
Node, import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
Command, import { Popover, TextArea, Typography, Space } from '@douyinfe/semi-ui';
mergeAttributes, import { IconHelpCircle } from '@douyinfe/semi-icons';
wrappingInputRule, import katex from 'katex';
} from "@tiptap/core"; import styles from './index.module.scss';
import { import { useMemo } from 'react';
NodeViewWrapper,
NodeViewContent,
ReactNodeViewRenderer,
} from "@tiptap/react";
import { Popover, TextArea, Typography, Space } from "@douyinfe/semi-ui";
import { IconHelpCircle } from "@douyinfe/semi-icons";
import katex from "katex";
import styles from "./index.module.scss";
import { useMemo } from "react";
declare module "@tiptap/core" { declare module '@tiptap/core' {
interface Commands { interface Commands {
katex: { katex: {
setKatex: () => Command; setKatex: () => Command;
@ -28,30 +19,27 @@ const { Text } = Typography;
export const KatexInputRegex = /^\$\$(.+)?\$$/; export const KatexInputRegex = /^\$\$(.+)?\$$/;
const KatexExtension = Node.create({ const KatexExtension = Node.create({
name: "katex", name: 'katex',
group: "block", group: 'block',
defining: true, defining: true,
draggable: true, draggable: true,
addAttributes() { addAttributes() {
return { return {
text: { text: {
default: "", default: '',
}, },
}; };
}, },
parseHTML() { parseHTML() {
return [{ tag: "div[data-type=katex]" }]; return [{ tag: 'div[data-type=katex]' }];
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return [ return [
"div", 'div',
mergeAttributes( mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes),
(this.options && this.options.HTMLAttributes) || {},
HTMLAttributes
),
]; ];
}, },
@ -94,10 +82,7 @@ const Render = ({ editor, node, updateAttributes }) => {
}, [text]); }, [text]);
const content = text ? ( const content = text ? (
<span <span contentEditable={false} dangerouslySetInnerHTML={{ __html: formatText }}></span>
contentEditable={false}
dangerouslySetInnerHTML={{ __html: formatText }}
></span>
) : ( ) : (
<span contentEditable={false}></span> <span contentEditable={false}></span>
); );
@ -118,10 +103,7 @@ const Render = ({ editor, node, updateAttributes }) => {
onChange={(v) => updateAttributes({ text: v })} onChange={(v) => updateAttributes({ text: v })}
style={{ marginBottom: 8 }} style={{ marginBottom: 8 }}
/> />
<Text <Text type="tertiary" link={{ href: 'https://katex.org/', target: '_blank' }}>
type="tertiary"
link={{ href: "https://katex.org/", target: "_blank" }}
>
<Space> <Space>
<IconHelpCircle /> <IconHelpCircle />

Some files were not shown because too many files have changed in this diff Show More